Compare commits

...

18 Commits

Author SHA1 Message Date
2382717600 timezone: Fix regression caused by #3361 (#3418)
Because of that PR, TimeZoneRule was bigger than 0x4000 thanks to a
misuse of a constant.

This commit address this issue and add a new unit test to ensure the size of
TimeZoneRule is 0x4000 bytes.

Also address suggestions that were lost on the original PR.
2022-06-24 21:11:56 +02:00
30ee70a9bc time: Make TimeZoneRule blittable and avoid copies (#3361)
* time: Make TimeZoneRule blittable and avoid copies

This drastically reduce overhead of using TimeZoneRule around the
codebase.

Effect on games is unknown

* Add missing Box type

* Ensure we clean the structure still

This doesn't perform any copies

* Address gdkchan's comments

* Simplify Box
2022-06-24 19:04:57 +02:00
232b1012b0 Fix ThreadingLock deadlock on invalid access and TerminateProcess (#3407) 2022-06-24 02:53:16 +02:00
e747f5cd83 Ensure texture ID is valid before getting texture descriptor (#3406) 2022-06-24 02:41:57 +02:00
8aff17a93c UI: Some Avalonia cleanup (#3358) 2022-06-23 15:59:02 -03:00
f2a41b7a1c Rewrite kernel memory allocator (#3316)
* Rewrite kernel memory allocator

* Remove unused using

* Adjust private static field naming

* Change UlongBitSize to UInt64BitSize

* Fix unused argument, change argument order to be inline with official code and disable random allocation
2022-06-22 12:28:14 -03:00
c881cd2d14 Fix doubling of detected gamepads on program start (#3398)
* Fix doubling of detected gamepads (sometimes the connected event is fired when the app starts even though the pad was connected for some time now).

The fix rejects the gamepad if one with the same ID is already present.

* Fixed review findings
2022-06-20 19:01:55 +02:00
68f9091870 Account for res scale changes when updating bindings (#3403)
Fixes a regression introduced by the texture bindings PR.

Also renames TextureStatePerStage, as it's no longer per stage.
2022-06-17 17:41:38 -03:00
99ffc061d3 Optimize Texture Binding and Shader Specialization Checks (#3399)
* Changes 1

* Changes 2

* Better ModifiedSequence handling

This should handle PreciseEvents properly, and simplifies a few things.

* Minor changes, remove debug log

* Handle stage.Info being null

Hopefully fixes Catherine crash

* Fix shader specialization fast texture lookup

* Fix some things.

* Address Feedback Part 1

* Make method static.
2022-06-17 13:09:14 -03:00
d987cacfb7 Fix VIC out of bounds copy (#3386)
* Fix VIC out of bounds copy

* Update the assert
2022-06-17 12:01:52 -03:00
851f56b08a Support Array/3D depth-stencil render target, and single layer clears (#3400)
* Support Array/3D depth-stencil render target, and single layer clears

* Alignment
2022-06-14 13:30:39 -03:00
b1bd6a50b5 Less invasive fix for EventFd blocking operations (#3394) 2022-06-12 09:29:12 +02:00
70895bdb04 Allow concurrent BSD EventFd read/write (#3385) 2022-06-11 14:58:30 -03:00
830cbf91bb Ignore ClipControl on draw texture fallback (#3388) 2022-06-11 14:31:17 -03:00
9a9349f0f4 Fix instanced indexed inline draw index count (#3389) 2022-06-10 23:44:49 -03:00
46cc7b55f0 Fix instanced indexed inline draws (#3383) 2022-06-05 21:24:28 -03:00
dd8f97ab9e Remove freed memory range from tree on memory block disposal (#3347)
* Remove freed memory range from tree on memory block disposal

* PR feedback
2022-06-05 15:12:42 -03:00
633c5ec330 Extend uses count from ushort to uint on Operand Data structure (#3374) 2022-06-05 14:15:27 -03:00
74 changed files with 3257 additions and 1657 deletions

View File

@ -14,10 +14,11 @@ namespace ARMeilleure.IntermediateRepresentation
public byte Kind; public byte Kind;
public byte Type; public byte Type;
public byte SymbolType; public byte SymbolType;
public byte Padding; // Unused space.
public ushort AssignmentsCount; public ushort AssignmentsCount;
public ushort AssignmentsCapacity; public ushort AssignmentsCapacity;
public ushort UsesCount; public uint UsesCount;
public ushort UsesCapacity; public uint UsesCapacity;
public Operation* Assignments; public Operation* Assignments;
public Operation* Uses; public Operation* Uses;
public ulong Value; public ulong Value;
@ -84,11 +85,11 @@ namespace ARMeilleure.IntermediateRepresentation
{ {
Debug.Assert(Kind != OperandKind.Memory); Debug.Assert(Kind != OperandKind.Memory);
return new ReadOnlySpan<Operation>(_data->Uses, _data->UsesCount); return new ReadOnlySpan<Operation>(_data->Uses, (int)_data->UsesCount);
} }
} }
public int UsesCount => _data->UsesCount; public int UsesCount => (int)_data->UsesCount;
public int AssignmentsCount => _data->AssignmentsCount; public int AssignmentsCount => _data->AssignmentsCount;
public bool Relocatable => Symbol.Type != SymbolType.None; public bool Relocatable => Symbol.Type != SymbolType.None;
@ -265,6 +266,13 @@ namespace ARMeilleure.IntermediateRepresentation
data = Allocators.References.Allocate<T>(initialCapacity); data = Allocators.References.Allocate<T>(initialCapacity);
} }
private static void New<T>(ref T* data, ref uint count, ref uint capacity, uint initialCapacity) where T : unmanaged
{
count = 0;
capacity = initialCapacity;
data = Allocators.References.Allocate<T>(initialCapacity);
}
private static void Add<T>(T item, ref T* data, ref ushort count, ref ushort capacity) where T : unmanaged private static void Add<T>(T item, ref T* data, ref ushort count, ref ushort capacity) where T : unmanaged
{ {
if (count < capacity) if (count < capacity)
@ -294,6 +302,40 @@ namespace ARMeilleure.IntermediateRepresentation
} }
} }
private static void Add<T>(T item, ref T* data, ref uint count, ref uint capacity) where T : unmanaged
{
if (count < capacity)
{
data[count++] = item;
return;
}
// Could not add item in the fast path, fallback onto the slow path.
ExpandAdd(item, ref data, ref count, ref capacity);
static void ExpandAdd(T item, ref T* data, ref uint count, ref uint capacity)
{
uint newCount = checked(count + 1);
uint newCapacity = (uint)Math.Min(capacity * 2, int.MaxValue);
if (newCapacity <= capacity)
{
throw new OverflowException();
}
var oldSpan = new Span<T>(data, (int)count);
capacity = newCapacity;
data = Allocators.References.Allocate<T>(capacity);
oldSpan.CopyTo(new Span<T>(data, (int)count));
data[count] = item;
count = newCount;
}
}
private static void Remove<T>(in T item, ref T* data, ref ushort count) where T : unmanaged private static void Remove<T>(in T item, ref T* data, ref ushort count) where T : unmanaged
{ {
var span = new Span<T>(data, count); var span = new Span<T>(data, count);
@ -314,6 +356,26 @@ namespace ARMeilleure.IntermediateRepresentation
} }
} }
private static void Remove<T>(in T item, ref T* data, ref uint count) where T : unmanaged
{
var span = new Span<T>(data, (int)count);
for (int i = 0; i < span.Length; i++)
{
if (EqualityComparer<T>.Default.Equals(span[i], item))
{
if (i + 1 < count)
{
span.Slice(i + 1).CopyTo(span.Slice(i));
}
count--;
return;
}
}
}
public override int GetHashCode() public override int GetHashCode()
{ {
if (Kind == OperandKind.LocalVariable) if (Kind == OperandKind.LocalVariable)

View File

@ -1,9 +1,9 @@
<Application <Application
x:Class="Ryujinx.Ava.App" x:Class="Ryujinx.Ava.App"
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:sty="using:FluentAvalonia.Styling" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> xmlns:sty="using:FluentAvalonia.Styling">
<Application.Styles> <Application.Styles>
<sty:FluentAvaloniaTheme UseSystemThemeOnWindows="False"/> <sty:FluentAvaloniaTheme UseSystemThemeOnWindows="False" />
</Application.Styles> </Application.Styles>
</Application> </Application>

View File

@ -2,7 +2,6 @@ using Avalonia;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.Threading;
using FluentAvalonia.Styling; using FluentAvalonia.Styling;
using Ryujinx.Ava.Ui.Windows; using Ryujinx.Ava.Ui.Windows;
using Ryujinx.Common; using Ryujinx.Common;
@ -13,7 +12,7 @@ using System.IO;
namespace Ryujinx.Ava namespace Ryujinx.Ava
{ {
public class App : Avalonia.Application public class App : Application
{ {
public override void Initialize() public override void Initialize()
{ {
@ -46,7 +45,7 @@ namespace Ryujinx.Ava
private void ShowRestartDialog() private void ShowRestartDialog()
{ {
// TODO. Implement Restart Dialog when SettingsWindow is implemented. // TODO: Implement Restart Dialog when SettingsWindow is implemented.
} }
private void ThemeChanged_Event(object sender, ReactiveEventArgs<string> e) private void ThemeChanged_Event(object sender, ReactiveEventArgs<string> e)

View File

@ -57,7 +57,7 @@ namespace Ryujinx.Ava
private static readonly Cursor InvisibleCursor = new Cursor(StandardCursorType.None); private static readonly Cursor InvisibleCursor = new Cursor(StandardCursorType.None);
private readonly AccountManager _accountManager; private readonly AccountManager _accountManager;
private UserChannelPersistence _userChannelPersistence; private readonly UserChannelPersistence _userChannelPersistence;
private readonly InputManager _inputManager; private readonly InputManager _inputManager;
@ -82,7 +82,6 @@ namespace Ryujinx.Ava
private bool _dialogShown; private bool _dialogShown;
private WindowsMultimediaTimerResolution _windowsMultimediaTimerResolution; private WindowsMultimediaTimerResolution _windowsMultimediaTimerResolution;
private KeyboardStateSnapshot _lastKeyboardSnapshot;
private readonly CancellationTokenSource _gpuCancellationTokenSource; private readonly CancellationTokenSource _gpuCancellationTokenSource;
@ -126,7 +125,6 @@ namespace Ryujinx.Ava
_glLogLevel = ConfigurationState.Instance.Logger.GraphicsDebugLevel; _glLogLevel = ConfigurationState.Instance.Logger.GraphicsDebugLevel;
_inputManager.SetMouseDriver(new AvaloniaMouseDriver(renderer)); _inputManager.SetMouseDriver(new AvaloniaMouseDriver(renderer));
_keyboardInterface = (IKeyboard)_inputManager.KeyboardDriver.GetGamepad("0"); _keyboardInterface = (IKeyboard)_inputManager.KeyboardDriver.GetGamepad("0");
_lastKeyboardSnapshot = _keyboardInterface.GetKeyboardStateSnapshot();
NpadManager = _inputManager.CreateNpadManager(); NpadManager = _inputManager.CreateNpadManager();
TouchScreenManager = _inputManager.CreateTouchScreenManager(); TouchScreenManager = _inputManager.CreateTouchScreenManager();
@ -722,9 +720,7 @@ namespace Ryujinx.Ava
} }
} }
var memoryConfiguration = ConfigurationState.Instance.System.ExpandRam.Value var memoryConfiguration = ConfigurationState.Instance.System.ExpandRam.Value ? HLE.MemoryConfiguration.MemoryConfiguration6GB : HLE.MemoryConfiguration.MemoryConfiguration4GB;
? HLE.MemoryConfiguration.MemoryConfiguration6GB
: HLE.MemoryConfiguration.MemoryConfiguration4GB;
IntegrityCheckLevel fsIntegrityCheckLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None; IntegrityCheckLevel fsIntegrityCheckLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None;
@ -898,7 +894,7 @@ namespace Ryujinx.Ava
} }
} }
private void HandleScreenState(KeyboardStateSnapshot keyboard, KeyboardStateSnapshot lastKeyboard) private void HandleScreenState()
{ {
if (ConfigurationState.Instance.Hid.EnableMouse) if (ConfigurationState.Instance.Hid.EnableMouse)
{ {
@ -935,19 +931,12 @@ namespace Ryujinx.Ava
{ {
Dispatcher.UIThread.Post(() => Dispatcher.UIThread.Post(() =>
{ {
KeyboardStateSnapshot keyboard = _keyboardInterface.GetKeyboardStateSnapshot(); HandleScreenState();
HandleScreenState(keyboard, _lastKeyboardSnapshot); if (_keyboardInterface.GetKeyboardStateSnapshot().IsPressed(Key.Delete) && _parent.WindowState != WindowState.FullScreen)
if (keyboard.IsPressed(Key.Delete))
{ {
if (_parent.WindowState != WindowState.FullScreen) Ptc.Continue();
{
Ptc.Continue();
}
} }
_lastKeyboardSnapshot = keyboard;
}); });
} }

View File

@ -1,9 +1,7 @@
<Styles <Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StyleInclude Source="avares://Ryujinx.Ava/Assets/Styles/Styles.xaml" /> <StyleInclude Source="avares://Ryujinx.Ava/Assets/Styles/Styles.xaml" />
<Design.PreviewWith> <Design.PreviewWith>
<Border Padding="20" Height="2000"> <Border Height="2000" Padding="20">
<StackPanel Spacing="5"> <StackPanel Spacing="5">
<TextBlock Text="Code Font Family" /> <TextBlock Text="Code Font Family" />
<Grid RowDefinitions="*,Auto"> <Grid RowDefinitions="*,Auto">
@ -27,8 +25,12 @@
Name="btnRem" Name="btnRem"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Content="Add" /> Content="Add" />
<TextBox Width="100" VerticalAlignment="Center" Text="Rrrrr" Watermark="Hello" <TextBox
UseFloatingWatermark="True" /> Width="100"
VerticalAlignment="Center"
Text="Rrrrr"
UseFloatingWatermark="True"
Watermark="Hello" />
<CheckBox>Test Check</CheckBox> <CheckBox>Test Check</CheckBox>
</StackPanel> </StackPanel>
</Grid> </Grid>

View File

@ -1,9 +1,7 @@
<Styles <Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StyleInclude Source="avares://Ryujinx.Ava/Assets/Styles/Styles.xaml" /> <StyleInclude Source="avares://Ryujinx.Ava/Assets/Styles/Styles.xaml" />
<Design.PreviewWith> <Design.PreviewWith>
<Border Padding="20" Height="2000"> <Border Height="2000" Padding="20">
<StackPanel Spacing="5"> <StackPanel Spacing="5">
<TextBlock Text="Code Font Family" /> <TextBlock Text="Code Font Family" />
<Grid RowDefinitions="*,Auto"> <Grid RowDefinitions="*,Auto">
@ -27,8 +25,12 @@
Name="btnRem" Name="btnRem"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Content="Add" /> Content="Add" />
<TextBox Width="100" VerticalAlignment="Center" Text="Rrrrr" Watermark="Hello" <TextBox
UseFloatingWatermark="True" /> Width="100"
VerticalAlignment="Center"
Text="Rrrrr"
UseFloatingWatermark="True"
Watermark="Hello" />
<CheckBox>Test Check</CheckBox> <CheckBox>Test Check</CheckBox>
</StackPanel> </StackPanel>
</Grid> </Grid>

View File

@ -1,10 +1,10 @@
<Styles <Styles
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:sys="clr-namespace:System;assembly=netstandard"
xmlns:sys="clr-namespace:System;assembly=netstandard"> xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia">
<Design.PreviewWith> <Design.PreviewWith>
<Border Padding="20" Height="2000"> <Border Height="2000" Padding="20">
<StackPanel Spacing="5"> <StackPanel Spacing="5">
<TextBlock Text="Code Font Family" /> <TextBlock Text="Code Font Family" />
<Grid RowDefinitions="*,Auto"> <Grid RowDefinitions="*,Auto">
@ -22,15 +22,19 @@
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<ToggleButton <ToggleButton
Name="btnAdd" Name="btnAdd"
HorizontalAlignment="Right"
Height="28" Height="28"
HorizontalAlignment="Right"
Content="Addy" /> Content="Addy" />
<Button <Button
Name="btnRem" Name="btnRem"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Content="Add" /> Content="Add" />
<TextBox Width="100" VerticalAlignment="Center" Text="Rrrrr" Watermark="Hello" <TextBox
UseFloatingWatermark="True" /> Width="100"
VerticalAlignment="Center"
Text="Rrrrr"
UseFloatingWatermark="True"
Watermark="Hello" />
<CheckBox>Test Check</CheckBox> <CheckBox>Test Check</CheckBox>
</StackPanel> </StackPanel>
</Grid> </Grid>
@ -62,13 +66,10 @@
<Style Selector="Image.huge"> <Style Selector="Image.huge">
<Setter Property="Width" Value="120" /> <Setter Property="Width" Value="120" />
</Style> </Style>
<Style Selector="RadioButton"> <Style Selector="#TitleBarHost &gt; Image">
<Setter Property="VerticalContentAlignment" Value="Center" />
</Style>
<Style Selector="#TitleBarHost > Image">
<Setter Property="Margin" Value="10" /> <Setter Property="Margin" Value="10" />
</Style> </Style>
<Style Selector="#TitleBarHost > Label"> <Style Selector="#TitleBarHost &gt; Label">
<Setter Property="Margin" Value="5" /> <Setter Property="Margin" Value="5" />
<Setter Property="FontSize" Value="14" /> <Setter Property="FontSize" Value="14" />
</Style> </Style>
@ -225,12 +226,12 @@
<StaticResource x:Key="ListViewItemBackgroundPointerOver" ResourceKey="SystemAccentColorDark2" /> <StaticResource x:Key="ListViewItemBackgroundPointerOver" ResourceKey="SystemAccentColorDark2" />
<StaticResource x:Key="ListViewItemBackgroundSelectedPressed" ResourceKey="ThemeAccentColorBrush" /> <StaticResource x:Key="ListViewItemBackgroundSelectedPressed" ResourceKey="ThemeAccentColorBrush" />
<StaticResource x:Key="ListViewItemBackgroundSelectedPointerOver" ResourceKey="SystemAccentColorDark2" /> <StaticResource x:Key="ListViewItemBackgroundSelectedPointerOver" ResourceKey="SystemAccentColorDark2" />
<SolidColorBrush x:Key="DataGridGridLinesBrush" <SolidColorBrush
Color="{DynamicResource SystemBaseMediumLowColor}" x:Key="DataGridGridLinesBrush"
Opacity="0.4" /> Opacity="0.4"
Color="{DynamicResource SystemBaseMediumLowColor}" />
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush" Color="{DynamicResource DataGridSelectionColor}" /> <SolidColorBrush x:Key="DataGridSelectionBackgroundBrush" Color="{DynamicResource DataGridSelectionColor}" />
<SolidColorBrush x:Key="MenuFlyoutPresenterBorderBrush" <SolidColorBrush x:Key="MenuFlyoutPresenterBorderBrush" Color="{DynamicResource MenuFlyoutPresenterBorderColor}" />
Color="{DynamicResource MenuFlyoutPresenterBorderColor}" />
<SolidColorBrush x:Key="ThemeAccentColorBrush" Color="{DynamicResource SystemAccentColor}" /> <SolidColorBrush x:Key="ThemeAccentColorBrush" Color="{DynamicResource SystemAccentColor}" />
<SolidColorBrush x:Key="ListBoxBackground" Color="{DynamicResource ThemeContentBackgroundColor}" /> <SolidColorBrush x:Key="ListBoxBackground" Color="{DynamicResource ThemeContentBackgroundColor}" />
<SolidColorBrush x:Key="ThemeForegroundBrush" Color="{DynamicResource ThemeForegroundColor}" /> <SolidColorBrush x:Key="ThemeForegroundBrush" Color="{DynamicResource ThemeForegroundColor}" />
@ -241,7 +242,6 @@
<SolidColorBrush x:Key="SplitButtonBackgroundCheckedDisabled" Color="#00E81123" /> <SolidColorBrush x:Key="SplitButtonBackgroundCheckedDisabled" Color="#00E81123" />
<Thickness x:Key="PageMargin">40 0 40 0</Thickness> <Thickness x:Key="PageMargin">40 0 40 0</Thickness>
<Thickness x:Key="Margin">0 5 0 5</Thickness> <Thickness x:Key="Margin">0 5 0 5</Thickness>
<Thickness x:Key="TextMargin">0 4 0 0</Thickness>
<Thickness x:Key="MenuItemPadding">5 0 5 0</Thickness> <Thickness x:Key="MenuItemPadding">5 0 5 0</Thickness>
<Color x:Key="MenuFlyoutPresenterBorderColor">#00000000</Color> <Color x:Key="MenuFlyoutPresenterBorderColor">#00000000</Color>
<Color x:Key="SystemAccentColor">#FF00C3E3</Color> <Color x:Key="SystemAccentColor">#FF00C3E3</Color>

View File

@ -1,17 +1,21 @@
<Window xmlns="https://github.com/avaloniaui" <Window
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="Ryujinx.Ava.Ui.Applet.ErrorAppletWindow"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns="https://github.com/avaloniaui"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
x:Class="Ryujinx.Ava.Ui.Applet.ErrorAppletWindow" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
CanResize="False" Title="{locale:Locale ErrorWindowTitle}"
SizeToContent="Height" Width="450"
Width="450" Height="340"
Height="340" CanResize="False"
Title="{locale:Locale ErrorWindowTitle}"> SizeToContent="Height"
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="20"> mc:Ignorable="d">
<Grid
Margin="20"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
@ -21,11 +25,28 @@
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition /> <ColumnDefinition />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Image Grid.Row="1" Grid.RowSpan="2" Margin="5, 10, 20 , 10" Grid.Column="0" <Image
Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" Height="80" MinWidth="50" /> Grid.Row="1"
<TextBlock Grid.Row="1" Margin="10" Grid.Column="1" VerticalAlignment="Stretch" TextWrapping="Wrap" Grid.RowSpan="2"
Text="{Binding Message}" /> Grid.Column="0"
<StackPanel Name="ButtonStack" Margin="10" Spacing="10" Grid.Row="2" Grid.Column="1" Height="80"
HorizontalAlignment="Right" Orientation="Horizontal" /> MinWidth="50"
Margin="5,10,20,10"
Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" />
<TextBlock
Grid.Row="1"
Grid.Column="1"
Margin="10"
VerticalAlignment="Stretch"
Text="{Binding Message}"
TextWrapping="Wrap" />
<StackPanel
Name="ButtonStack"
Grid.Row="2"
Grid.Column="1"
Margin="10"
HorizontalAlignment="Right"
Orientation="Horizontal"
Spacing="10" />
</Grid> </Grid>
</Window> </Window>

View File

@ -1,12 +1,16 @@
<UserControl xmlns="https://github.com/avaloniaui" <UserControl
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="Ryujinx.Ava.Ui.Controls.SwkbdAppletDialog"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns="https://github.com/avaloniaui"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Class="Ryujinx.Ava.Ui.Controls.SwkbdAppletDialog" xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
Width="400"> Width="400"
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="20"> mc:Ignorable="d">
<Grid
Margin="20"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
@ -18,15 +22,43 @@
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition /> <ColumnDefinition />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Image Grid.Row="1" VerticalAlignment="Center" Grid.RowSpan="5" Margin="5, 10, 20 , 10" <Image
Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" Height="80" Grid.Row="1"
MinWidth="50" /> Grid.RowSpan="5"
<TextBlock Grid.Row="1" Margin="5" Grid.Column="1" Text="{Binding MainText}" TextWrapping="Wrap" /> Height="80"
<TextBlock Grid.Row="2" Margin="5" Grid.Column="1" Text="{Binding SecondaryText}" TextWrapping="Wrap" /> MinWidth="50"
<TextBox Name="Input" KeyUp="Message_KeyUp" UseFloatingWatermark="True" TextInput="Message_TextInput" Margin="5,10,20,10"
Text="{Binding Message}" Grid.Row="2" VerticalAlignment="Center"
Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Stretch" TextWrapping="Wrap" /> Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" />
<TextBlock Name="Error" Margin="5" Grid.Row="4" Grid.Column="1" HorizontalAlignment="Stretch" <TextBlock
TextWrapping="Wrap" /> Grid.Row="1"
Grid.Column="1"
Margin="5"
Text="{Binding MainText}"
TextWrapping="Wrap" />
<TextBlock
Grid.Row="2"
Grid.Column="1"
Margin="5"
Text="{Binding SecondaryText}"
TextWrapping="Wrap" />
<TextBox
Name="Input"
Grid.Row="2"
Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
KeyUp="Message_KeyUp"
Text="{Binding Message}"
TextInput="Message_TextInput"
TextWrapping="Wrap"
UseFloatingWatermark="True" />
<TextBlock
Name="Error"
Grid.Row="4"
Grid.Column="1"
Margin="5"
HorizontalAlignment="Stretch"
TextWrapping="Wrap" />
</Grid> </Grid>
</UserControl> </UserControl>

View File

@ -1,188 +1,219 @@
<UserControl xmlns="https://github.com/avaloniaui" <UserControl
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="Ryujinx.Ava.Ui.Controls.GameGridView"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns="https://github.com/avaloniaui"
xmlns:flex="clr-namespace:Avalonia.Flexbox;assembly=Avalonia.Flexbox" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls" xmlns:flex="clr-namespace:Avalonia.Flexbox;assembly=Avalonia.Flexbox"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Class="Ryujinx.Ava.Ui.Controls.GameGridView"> xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
<UserControl.Resources> d:DesignHeight="450"
<controls:BitmapArrayValueConverter x:Key="ByteImage" /> d:DesignWidth="800"
<MenuFlyout x:Key="GameContextMenu" Opened="MenuBase_OnMenuOpened"> mc:Ignorable="d">
<MenuItem <UserControl.Resources>
<controls:BitmapArrayValueConverter x:Key="ByteImage" />
<MenuFlyout x:Key="GameContextMenu" Opened="MenuBase_OnMenuOpened">
<MenuItem
Command="{Binding ToggleFavorite}" Command="{Binding ToggleFavorite}"
Header="{locale:Locale GameListContextMenuToggleFavorite}" Header="{locale:Locale GameListContextMenuToggleFavorite}"
ToolTip.Tip="{locale:Locale GameListContextMenuToggleFavoriteToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuToggleFavoriteToolTip}" />
<Separator /> <Separator />
<MenuItem <MenuItem
Command="{Binding OpenUserSaveDirectory}" Command="{Binding OpenUserSaveDirectory}"
Header="{locale:Locale GameListContextMenuOpenUserSaveDirectory}" Header="{locale:Locale GameListContextMenuOpenUserSaveDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserSaveDirectoryToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserSaveDirectoryToolTip}" />
<MenuItem <MenuItem
Command="{Binding OpenDeviceSaveDirectory}" Command="{Binding OpenDeviceSaveDirectory}"
Header="{locale:Locale GameListContextMenuOpenUserDeviceDirectory}" Header="{locale:Locale GameListContextMenuOpenUserDeviceDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserDeviceDirectoryToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserDeviceDirectoryToolTip}" />
<MenuItem <MenuItem
Command="{Binding OpenBcatSaveDirectory}" Command="{Binding OpenBcatSaveDirectory}"
Header="{locale:Locale GameListContextMenuOpenUserBcatDirectory}" Header="{locale:Locale GameListContextMenuOpenUserBcatDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserBcatDirectoryToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserBcatDirectoryToolTip}" />
<Separator /> <Separator />
<MenuItem <MenuItem
Command="{Binding OpenTitleUpdateManager}" Command="{Binding OpenTitleUpdateManager}"
Header="{locale:Locale GameListContextMenuManageTitleUpdates}" Header="{locale:Locale GameListContextMenuManageTitleUpdates}"
ToolTip.Tip="{locale:Locale GameListContextMenuManageTitleUpdatesToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuManageTitleUpdatesToolTip}" />
<MenuItem <MenuItem
Command="{Binding OpenDlcManager}" Command="{Binding OpenDlcManager}"
Header="{locale:Locale GameListContextMenuManageDlc}" Header="{locale:Locale GameListContextMenuManageDlc}"
ToolTip.Tip="{locale:Locale GameListContextMenuManageDlcToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuManageDlcToolTip}" />
<MenuItem <MenuItem
Command="{Binding OpenCheatManager}" Command="{Binding OpenCheatManager}"
Header="{locale:Locale GameListContextMenuManageCheat}" Header="{locale:Locale GameListContextMenuManageCheat}"
ToolTip.Tip="{locale:Locale GameListContextMenuManageCheatToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuManageCheatToolTip}" />
<MenuItem <MenuItem
Command="{Binding OpenModsDirectory}" Command="{Binding OpenModsDirectory}"
Header="{locale:Locale GameListContextMenuOpenModsDirectory}" Header="{locale:Locale GameListContextMenuOpenModsDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenModsDirectoryToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuOpenModsDirectoryToolTip}" />
<MenuItem <MenuItem
Command="{Binding OpenSdModsDirectory}" Command="{Binding OpenSdModsDirectory}"
Header="{locale:Locale GameListContextMenuOpenSdModsDirectory}" Header="{locale:Locale GameListContextMenuOpenSdModsDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenSdModsDirectoryToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuOpenSdModsDirectoryToolTip}" />
<Separator /> <Separator />
<MenuItem Header="{locale:Locale GameListContextMenuCacheManagement}"> <MenuItem Header="{locale:Locale GameListContextMenuCacheManagement}">
<MenuItem <MenuItem
Command="{Binding PurgePtcCache}" Command="{Binding PurgePtcCache}"
Header="{locale:Locale GameListContextMenuCacheManagementPurgePptc}" Header="{locale:Locale GameListContextMenuCacheManagementPurgePptc}"
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementPurgePptcToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementPurgePptcToolTip}" />
<MenuItem <MenuItem
Command="{Binding PurgeShaderCache}" Command="{Binding PurgeShaderCache}"
Header="{locale:Locale GameListContextMenuCacheManagementPurgeShaderCache}" Header="{locale:Locale GameListContextMenuCacheManagementPurgeShaderCache}"
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementPurgeShaderCacheToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementPurgeShaderCacheToolTip}" />
<MenuItem <MenuItem
Command="{Binding OpenPtcDirectory}" Command="{Binding OpenPtcDirectory}"
Header="{locale:Locale GameListContextMenuCacheManagementOpenPptcDirectory}" Header="{locale:Locale GameListContextMenuCacheManagementOpenPptcDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementOpenPptcDirectoryToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementOpenPptcDirectoryToolTip}" />
<MenuItem <MenuItem
Command="{Binding OpenShaderCacheDirectory}" Command="{Binding OpenShaderCacheDirectory}"
Header="{locale:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectory}" Header="{locale:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip}" />
</MenuItem> </MenuItem>
<MenuItem Header="{locale:Locale GameListContextMenuExtractData}"> <MenuItem Header="{locale:Locale GameListContextMenuExtractData}">
<MenuItem <MenuItem
Command="{Binding ExtractExeFs}" Command="{Binding ExtractExeFs}"
Header="{locale:Locale GameListContextMenuExtractDataExeFS}" Header="{locale:Locale GameListContextMenuExtractDataExeFS}"
ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataExeFSToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataExeFSToolTip}" />
<MenuItem <MenuItem
Command="{Binding ExtractRomFs}" Command="{Binding ExtractRomFs}"
Header="{locale:Locale GameListContextMenuExtractDataRomFS}" Header="{locale:Locale GameListContextMenuExtractDataRomFS}"
ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataRomFSToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataRomFSToolTip}" />
<MenuItem <MenuItem
Command="{Binding ExtractLogo}" Command="{Binding ExtractLogo}"
Header="{locale:Locale GameListContextMenuExtractDataLogo}" Header="{locale:Locale GameListContextMenuExtractDataLogo}"
ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataLogoToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataLogoToolTip}" />
</MenuItem> </MenuItem>
</MenuFlyout> </MenuFlyout>
</UserControl.Resources> </UserControl.Resources>
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="*" /> <RowDefinition Height="*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<ListBox Grid.Row="0" <ListBox
Padding="8" Grid.Row="0"
HorizontalAlignment="Stretch" Padding="8"
DoubleTapped="GameList_DoubleTapped" HorizontalAlignment="Stretch"
SelectionChanged="GameList_SelectionChanged" VerticalAlignment="Stretch"
ContextFlyout="{StaticResource GameContextMenu}" ContextFlyout="{StaticResource GameContextMenu}"
VerticalAlignment="Stretch" DoubleTapped="GameList_DoubleTapped"
Items="{Binding AppsObservableList}"> Items="{Binding AppsObservableList}"
<ListBox.ItemsPanel> SelectionChanged="GameList_SelectionChanged">
<ItemsPanelTemplate> <ListBox.ItemsPanel>
<flex:FlexPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch" JustifyContent="Center" <ItemsPanelTemplate>
AlignContent="FlexStart" /> <flex:FlexPanel
</ItemsPanelTemplate> HorizontalAlignment="Stretch"
</ListBox.ItemsPanel> VerticalAlignment="Stretch"
<ListBox.Styles> AlignContent="FlexStart"
<Style Selector="ListBoxItem"> JustifyContent="Center" />
<Setter Property="Padding" Value="0" /> </ItemsPanelTemplate>
<Setter Property="Margin" Value="5" /> </ListBox.ItemsPanel>
<Setter Property="CornerRadius" Value="5" /> <ListBox.Styles>
<Setter Property="Background" Value="{DynamicResource SystemAccentColorDark3}" /> <Style Selector="ListBoxItem">
<Style.Animations> <Setter Property="Padding" Value="0" />
<Animation Duration="0:0:0.7"> <Setter Property="Margin" Value="5" />
<KeyFrame Cue="0%"> <Setter Property="CornerRadius" Value="5" />
<Setter Property="MaxWidth" Value="0"/> <Setter Property="Background" Value="{DynamicResource SystemAccentColorDark3}" />
<Setter Property="Opacity" Value="0.0"/> <Style.Animations>
</KeyFrame> <Animation Duration="0:0:0.7">
<KeyFrame Cue="50%"> <KeyFrame Cue="0%">
<Setter Property="MaxWidth" Value="1000"/> <Setter Property="MaxWidth" Value="0" />
<Setter Property="Opacity" Value="0.3"/> <Setter Property="Opacity" Value="0.0" />
</KeyFrame> </KeyFrame>
<KeyFrame Cue="100%"> <KeyFrame Cue="50%">
<Setter Property="MaxWidth" Value="1000"/> <Setter Property="MaxWidth" Value="1000" />
<Setter Property="Opacity" Value="1.0"/> <Setter Property="Opacity" Value="0.3" />
</KeyFrame> </KeyFrame>
</Animation> <KeyFrame Cue="100%">
</Style.Animations> <Setter Property="MaxWidth" Value="1000" />
</Style> <Setter Property="Opacity" Value="1.0" />
</ListBox.Styles> </KeyFrame>
<ListBox.ItemTemplate> </Animation>
<DataTemplate> </Style.Animations>
<Grid> </Style>
<Grid.Styles> </ListBox.Styles>
<Style Selector="ui|SymbolIcon.small.icon"> <ListBox.ItemTemplate>
<Setter Property="FontSize" Value="15" /> <DataTemplate>
</Style> <Grid>
<Style Selector="ui|SymbolIcon.normal.icon"> <Grid.Styles>
<Setter Property="FontSize" Value="19" /> <Style Selector="ui|SymbolIcon.small.icon">
</Style> <Setter Property="FontSize" Value="15" />
<Style Selector="ui|SymbolIcon.large.icon"> </Style>
<Setter Property="FontSize" Value="23" /> <Style Selector="ui|SymbolIcon.normal.icon">
</Style> <Setter Property="FontSize" Value="19" />
<Style Selector="ui|SymbolIcon.huge.icon"> </Style>
<Setter Property="FontSize" Value="26" /> <Style Selector="ui|SymbolIcon.large.icon">
</Style> <Setter Property="FontSize" Value="23" />
</Grid.Styles> </Style>
<Border <Style Selector="ui|SymbolIcon.huge.icon">
Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}" <Setter Property="FontSize" Value="26" />
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}" </Style>
Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}" </Grid.Styles>
Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}" <Border
Margin="0"
Padding="{Binding $parent[UserControl].DataContext.GridItemPadding}"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Padding="{Binding $parent[UserControl].DataContext.GridItemPadding}" CornerRadius="5" VerticalAlignment="Stretch"
VerticalAlignment="Stretch" Margin="0" ClipToBounds="True"> Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}"
<Grid Margin="0"> Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}"
<Grid.RowDefinitions> Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
<RowDefinition Height="Auto" /> Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
<RowDefinition Height="Auto" /> ClipToBounds="True"
</Grid.RowDefinitions> CornerRadius="5">
<Image HorizontalAlignment="Stretch" VerticalAlignment="Top" Margin="0" Grid.Row="0" <Grid Margin="0">
Source="{Binding Icon, Converter={StaticResource ByteImage}}" /> <Grid.RowDefinitions>
<StackPanel IsVisible="{Binding $parent[UserControl].DataContext.ShowNames}" <RowDefinition Height="Auto" />
Height="50" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" <RowDefinition Height="Auto" />
Margin="5" Grid.Row="1"> </Grid.RowDefinitions>
<TextBlock Text="{Binding TitleName}" TextAlignment="Center" TextWrapping="Wrap" <Image
HorizontalAlignment="Stretch" /> Grid.Row="0"
</StackPanel> Margin="0"
</Grid> HorizontalAlignment="Stretch"
</Border> VerticalAlignment="Top"
<ui:SymbolIcon Classes.icon="true" Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}" Source="{Binding Icon, Converter={StaticResource ByteImage}}" />
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}" <StackPanel
Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}" Grid.Row="1"
Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}" Height="50"
Foreground="Yellow" Symbol="StarFilled" Margin="5"
IsVisible="{Binding Favorite}" Margin="5" VerticalAlignment="Top" HorizontalAlignment="Stretch"
HorizontalAlignment="Left" /> VerticalAlignment="Stretch"
<ui:SymbolIcon Classes.icon="true" Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}" IsVisible="{Binding $parent[UserControl].DataContext.ShowNames}">
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}" <TextBlock
Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}" HorizontalAlignment="Stretch"
Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}" Text="{Binding TitleName}"
Foreground="Black" Symbol="Star" TextAlignment="Center"
IsVisible="{Binding Favorite}" Margin="5" VerticalAlignment="Top" TextWrapping="Wrap" />
HorizontalAlignment="Left" /> </StackPanel>
</Grid> </Grid>
</DataTemplate> </Border>
</ListBox.ItemTemplate> <ui:SymbolIcon
</ListBox> Margin="5"
</Grid> HorizontalAlignment="Left"
VerticalAlignment="Top"
Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}"
Classes.icon="true"
Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}"
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
Foreground="Yellow"
IsVisible="{Binding Favorite}"
Symbol="StarFilled" />
<ui:SymbolIcon
Margin="5"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}"
Classes.icon="true"
Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}"
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
Foreground="Black"
IsVisible="{Binding Favorite}"
Symbol="Star" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</UserControl> </UserControl>

View File

@ -1,13 +1,16 @@
<UserControl xmlns="https://github.com/avaloniaui" <UserControl
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="Ryujinx.Ava.Ui.Controls.GameListView"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns="https://github.com/avaloniaui"
xmlns:flex="clr-namespace:Avalonia.Flexbox;assembly=Avalonia.Flexbox" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls" xmlns:flex="clr-namespace:Avalonia.Flexbox;assembly=Avalonia.Flexbox"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Class="Ryujinx.Ava.Ui.Controls.GameListView"> xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<UserControl.Resources> <UserControl.Resources>
<controls:BitmapArrayValueConverter x:Key="ByteImage" /> <controls:BitmapArrayValueConverter x:Key="ByteImage" />
<MenuFlyout x:Key="GameContextMenu" Opened="MenuBase_OnMenuOpened"> <MenuFlyout x:Key="GameContextMenu" Opened="MenuBase_OnMenuOpened">
@ -88,18 +91,23 @@
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="*" /> <RowDefinition Height="*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<ListBox Grid.Row="0" <ListBox
Padding="8" Name="GameListBox"
HorizontalAlignment="Stretch" Grid.Row="0"
DoubleTapped="GameList_DoubleTapped" Padding="8"
SelectionChanged="GameList_SelectionChanged" HorizontalAlignment="Stretch"
ContextFlyout="{StaticResource GameContextMenu}" VerticalAlignment="Stretch"
VerticalAlignment="Stretch" ContextFlyout="{StaticResource GameContextMenu}"
Name="GameListBox" DoubleTapped="GameList_DoubleTapped"
Items="{Binding AppsObservableList}"> Items="{Binding AppsObservableList}"
SelectionChanged="GameList_SelectionChanged">
<ListBox.ItemsPanel> <ListBox.ItemsPanel>
<ItemsPanelTemplate> <ItemsPanelTemplate>
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Orientation="Vertical" Spacing="2" /> <StackPanel
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Orientation="Vertical"
Spacing="2" />
</ItemsPanelTemplate> </ItemsPanelTemplate>
</ListBox.ItemsPanel> </ListBox.ItemsPanel>
<ListBox.Styles> <ListBox.Styles>
@ -112,16 +120,16 @@
<Style.Animations> <Style.Animations>
<Animation Duration="0:0:0.7"> <Animation Duration="0:0:0.7">
<KeyFrame Cue="0%"> <KeyFrame Cue="0%">
<Setter Property="MaxHeight" Value="0"/> <Setter Property="MaxHeight" Value="0" />
<Setter Property="Opacity" Value="0.0"/> <Setter Property="Opacity" Value="0.0" />
</KeyFrame> </KeyFrame>
<KeyFrame Cue="50%"> <KeyFrame Cue="50%">
<Setter Property="MaxHeight" Value="1000"/> <Setter Property="MaxHeight" Value="1000" />
<Setter Property="Opacity" Value="0.3"/> <Setter Property="Opacity" Value="0.3" />
</KeyFrame> </KeyFrame>
<KeyFrame Cue="100%"> <KeyFrame Cue="100%">
<Setter Property="MaxHeight" Value="1000"/> <Setter Property="MaxHeight" Value="1000" />
<Setter Property="Opacity" Value="1.0"/> <Setter Property="Opacity" Value="1.0" />
</KeyFrame> </KeyFrame>
</Animation> </Animation>
</Style.Animations> </Style.Animations>
@ -130,54 +138,96 @@
<ListBox.ItemTemplate> <ListBox.ItemTemplate>
<DataTemplate> <DataTemplate>
<Grid> <Grid>
<Border HorizontalAlignment="Stretch" <Border
Padding="10" CornerRadius="5" Margin="0"
VerticalAlignment="Stretch" Margin="0" ClipToBounds="True"> Padding="10"
<Grid > HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ClipToBounds="True"
CornerRadius="5">
<Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="10"/> <ColumnDefinition Width="10" />
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition/> <RowDefinition />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Image <Image
Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}" Grid.RowSpan="3"
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}" Grid.Column="0"
Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}" Margin="0"
Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}" Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}"
Grid.RowSpan="3" Grid.Column="0" Margin="0" Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}"
Source="{Binding Icon, Converter={StaticResource ByteImage}}" /> Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
<StackPanel Orientation="Vertical" Spacing="5" VerticalAlignment="Top" HorizontalAlignment="Left" Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
Grid.Column="2"> Source="{Binding Icon, Converter={StaticResource ByteImage}}" />
<TextBlock Text="{Binding TitleName}" TextAlignment="Left" TextWrapping="Wrap" <StackPanel
HorizontalAlignment="Stretch" /> Grid.Column="2"
<TextBlock Text="{Binding Developer}" TextAlignment="Left" TextWrapping="Wrap" HorizontalAlignment="Left"
HorizontalAlignment="Stretch" /> VerticalAlignment="Top"
<TextBlock Text="{Binding Version}" TextAlignment="Left" TextWrapping="Wrap" Orientation="Vertical"
HorizontalAlignment="Stretch" /> Spacing="5">
</StackPanel> <TextBlock
<StackPanel Orientation="Vertical" Spacing="5" VerticalAlignment="Top" HorizontalAlignment="Right" HorizontalAlignment="Stretch"
Grid.Column="3"> Text="{Binding TitleName}"
<TextBlock Text="{Binding TimePlayed}" TextAlignment="Right" TextWrapping="Wrap" TextAlignment="Left"
HorizontalAlignment="Stretch" /> TextWrapping="Wrap" />
<TextBlock Text="{Binding LastPlayed}" TextAlignment="Right" TextWrapping="Wrap" <TextBlock
HorizontalAlignment="Stretch" /> HorizontalAlignment="Stretch"
<TextBlock Text="{Binding FileSize}" TextAlignment="Right" TextWrapping="Wrap" Text="{Binding Developer}"
HorizontalAlignment="Stretch" /> TextAlignment="Left"
</StackPanel> TextWrapping="Wrap" />
<ui:SymbolIcon Grid.Row="0" Grid.Column="0" FontSize="20" <TextBlock
Foreground="Yellow" HorizontalAlignment="Stretch"
Symbol="StarFilled" Text="{Binding Version}"
IsVisible="{Binding Favorite}" Margin="-5, -5, 0, 0" VerticalAlignment="Top" TextAlignment="Left"
HorizontalAlignment="Left" /> TextWrapping="Wrap" />
<ui:SymbolIcon Grid.Row="0" Grid.Column="0" FontSize="20" </StackPanel>
Foreground="Black" <StackPanel
Symbol="Star" Grid.Column="3"
IsVisible="{Binding Favorite}" Margin="-5, -5, 0, 0" VerticalAlignment="Top" HorizontalAlignment="Right"
HorizontalAlignment="Left" /> VerticalAlignment="Top"
Orientation="Vertical"
Spacing="5">
<TextBlock
HorizontalAlignment="Stretch"
Text="{Binding TimePlayed}"
TextAlignment="Right"
TextWrapping="Wrap" />
<TextBlock
HorizontalAlignment="Stretch"
Text="{Binding LastPlayed}"
TextAlignment="Right"
TextWrapping="Wrap" />
<TextBlock
HorizontalAlignment="Stretch"
Text="{Binding FileSize}"
TextAlignment="Right"
TextWrapping="Wrap" />
</StackPanel>
<ui:SymbolIcon
Grid.Row="0"
Grid.Column="0"
Margin="-5,-5,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
FontSize="20"
Foreground="Yellow"
IsVisible="{Binding Favorite}"
Symbol="StarFilled" />
<ui:SymbolIcon
Grid.Row="0"
Grid.Column="0"
Margin="-5,-5,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
FontSize="20"
Foreground="Black"
IsVisible="{Binding Favorite}"
Symbol="Star" />
</Grid> </Grid>
</Border> </Border>
</Grid> </Grid>

View File

@ -1,18 +1,31 @@
<UserControl xmlns="https://github.com/avaloniaui" <UserControl
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="Ryujinx.Ava.Ui.Controls.InputDialog"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns="https://github.com/avaloniaui"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
x:Class="Ryujinx.Ava.Ui.Controls.InputDialog"> xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Center" Margin="5,10,5, 5"> mc:Ignorable="d">
<Grid
Margin="5,10,5,5"
HorizontalAlignment="Stretch"
VerticalAlignment="Center">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock HorizontalAlignment="Center" Text="{Binding Message}" /> <TextBlock HorizontalAlignment="Center" Text="{Binding Message}" />
<TextBox MaxLength="{Binding MaxLength}" Grid.Row="1" Margin="10" Width="300" HorizontalAlignment="Center" <TextBox
Text="{Binding Input, Mode=TwoWay}" /> Grid.Row="1"
<TextBlock Grid.Row="2" Margin="5, 5, 5, 10" HorizontalAlignment="Center" Text="{Binding SubMessage}" /> Width="300"
Margin="10"
HorizontalAlignment="Center"
MaxLength="{Binding MaxLength}"
Text="{Binding Input, Mode=TwoWay}" />
<TextBlock
Grid.Row="2"
Margin="5,5,5,10"
HorizontalAlignment="Center"
Text="{Binding SubMessage}" />
</Grid> </Grid>
</UserControl> </UserControl>

View File

@ -1,14 +1,18 @@
<Window xmlns="https://github.com/avaloniaui" <Window
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="Ryujinx.Ava.Ui.Controls.UpdateWaitWindow"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns="https://github.com/avaloniaui"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Class="Ryujinx.Ava.Ui.Controls.UpdateWaitWindow" xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
WindowStartupLocation="CenterOwner" Title="Ryujinx - Waiting"
SizeToContent="WidthAndHeight" SizeToContent="WidthAndHeight"
Title="Ryujinx - Waiting"> WindowStartupLocation="CenterOwner"
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="20"> mc:Ignorable="d">
<Grid
Margin="20"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
@ -17,12 +21,22 @@
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition /> <ColumnDefinition />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Image Grid.Row="1" Margin="5, 10, 20 , 10" Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" <Image
Height="70" Grid.Row="1"
MinWidth="50" /> Height="70"
<StackPanel Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" Orientation="Vertical"> MinWidth="50"
<TextBlock Margin="5" Name="PrimaryText" /> Margin="5,10,20,10"
<TextBlock VerticalAlignment="Center" Name="SecondaryText" Margin="5" /> Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" />
<StackPanel
Grid.Row="1"
Grid.Column="1"
VerticalAlignment="Center"
Orientation="Vertical">
<TextBlock Name="PrimaryText" Margin="5" />
<TextBlock
Name="SecondaryText"
Margin="5"
VerticalAlignment="Center" />
</StackPanel> </StackPanel>
</Grid> </Grid>
</Window> </Window>

View File

@ -1,28 +1,41 @@
<window:StyleableWindow xmlns="https://github.com/avaloniaui" <window:StyleableWindow
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="Ryujinx.Ava.Ui.Windows.AboutWindow"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns="https://github.com/avaloniaui"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="350" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
x:Class="Ryujinx.Ava.Ui.Windows.AboutWindow" xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
CanResize="False" Title="Ryujinx - About"
WindowStartupLocation="CenterOwner" Width="850"
Width="850" MinHeight="550" Height="550" Height="550"
SizeToContent="Width" MinWidth="500"
MinWidth="500" MinHeight="550"
Title="Ryujinx - About"> d:DesignHeight="350"
<Grid Margin="15" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> d:DesignWidth="400"
CanResize="False"
SizeToContent="Width"
WindowStartupLocation="CenterOwner"
mc:Ignorable="d">
<Grid
Margin="15"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto" />
<RowDefinition Height="*"/> <RowDefinition Height="*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Grid Grid.Row="1" Margin="20" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Grid.Column="0"> <Grid
Grid.Row="1"
Grid.Column="0"
Margin="20"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="*" /> <RowDefinition Height="*" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
@ -40,93 +53,168 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Image Grid.Row="0" Grid.Column="0" Grid.RowSpan="3" Margin="5, 10, 20 , 10" <Image
Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" Height="110" MinWidth="50" /> Grid.Row="0"
<TextBlock FontSize="35" TextAlignment="Center" Grid.Row="0" Grid.Column="1" Text="Ryujinx" Grid.RowSpan="3"
Margin="0,20,0,0" /> Grid.Column="0"
<TextBlock FontSize="16" TextAlignment="Center" Grid.Row="1" Grid.Column="1" Text="(REE-YOU-JINX)" Height="110"
Margin="0,0,0,0" /> MinWidth="50"
<Button Grid.Column="1" Background="Transparent" HorizontalAlignment="Center" Margin="0" Grid.Row="2" Margin="5,10,20,10"
Tag="https://www.ryujinx.org/" Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" />
Click="Button_OnClick"> <TextBlock
<TextBlock ToolTip.Tip="{locale:Locale AboutUrlTooltipMessage}" Grid.Row="0"
TextAlignment="Center" TextDecorations="Underline" Text="www.ryujinx.org" /> Grid.Column="1"
Margin="0,20,0,0"
FontSize="35"
Text="Ryujinx"
TextAlignment="Center" />
<TextBlock
Grid.Row="1"
Grid.Column="1"
Margin="0,0,0,0"
FontSize="16"
Text="(REE-YOU-JINX)"
TextAlignment="Center" />
<Button
Grid.Row="2"
Grid.Column="1"
Margin="0"
HorizontalAlignment="Center"
Background="Transparent"
Click="Button_OnClick"
Tag="https://www.ryujinx.org/">
<TextBlock
Text="www.ryujinx.org"
TextAlignment="Center"
TextDecorations="Underline"
ToolTip.Tip="{locale:Locale AboutUrlTooltipMessage}" />
</Button> </Button>
</Grid> </Grid>
<TextBlock TextAlignment="Center" VerticalAlignment="Center" HorizontalAlignment="Center" <TextBlock
Text="{Binding Version}" Grid.Row="1" /> Grid.Row="1"
<TextBlock Grid.Row="2" TextAlignment="Center" HorizontalAlignment="Center" Margin="20" HorizontalAlignment="Center"
Text="{locale:Locale AboutDisclaimerMessage}" VerticalAlignment="Center"
MaxLines="2" /> Text="{Binding Version}"
<TextBlock Grid.Row="3" TextAlignment="Center" HorizontalAlignment="Center" Margin="20" TextAlignment="Center" />
Text="{locale:Locale AboutAmiiboDisclaimerMessage}" <TextBlock
Name="AmiiboLabel" Grid.Row="2"
PointerPressed="AmiiboLabel_OnPointerPressed" Margin="20"
MaxLines="2" /> HorizontalAlignment="Center"
<StackPanel Spacing="10" Orientation="Horizontal" Grid.Row="4" HorizontalAlignment="Center"> MaxLines="2"
<StackPanel Orientation="Vertical" Text="{locale:Locale AboutDisclaimerMessage}"
ToolTip.Tip="{locale:Locale AboutPatreonUrlTooltipMessage}"> TextAlignment="Center" />
<Button Height="65" Background="Transparent" Tag="https://www.patreon.com/ryujinx" <TextBlock
Click="Button_OnClick"> Name="AmiiboLabel"
Grid.Row="3"
Margin="20"
HorizontalAlignment="Center"
MaxLines="2"
PointerPressed="AmiiboLabel_OnPointerPressed"
Text="{locale:Locale AboutAmiiboDisclaimerMessage}"
TextAlignment="Center" />
<StackPanel
Grid.Row="4"
HorizontalAlignment="Center"
Orientation="Horizontal"
Spacing="10">
<StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutPatreonUrlTooltipMessage}">
<Button
Height="65"
Background="Transparent"
Click="Button_OnClick"
Tag="https://www.patreon.com/ryujinx">
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition /> <RowDefinition />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Patreon.png?assembly=Ryujinx.Ui.Common" /> <Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Patreon.png?assembly=Ryujinx.Ui.Common" />
<TextBlock Grid.Row="1" Margin="0,5,0,0" Text="Patreon" HorizontalAlignment="Center" /> <TextBlock
Grid.Row="1"
Margin="0,5,0,0"
HorizontalAlignment="Center"
Text="Patreon" />
</Grid> </Grid>
</Button> </Button>
</StackPanel> </StackPanel>
<StackPanel Orientation="Vertical" <StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutGithubUrlTooltipMessage}">
ToolTip.Tip="{locale:Locale AboutGithubUrlTooltipMessage}"> <Button
<Button Height="65" Background="Transparent" Tag="https://github.com/Ryujinx/Ryujinx" Height="65"
Click="Button_OnClick"> Background="Transparent"
Click="Button_OnClick"
Tag="https://github.com/Ryujinx/Ryujinx">
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition /> <RowDefinition />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_GitHub.png?assembly=Ryujinx.Ui.Common" /> <Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_GitHub.png?assembly=Ryujinx.Ui.Common" />
<TextBlock Grid.Row="1" Margin="0,5,0,0" Text="GitHub" HorizontalAlignment="Center" /> <TextBlock
Grid.Row="1"
Margin="0,5,0,0"
HorizontalAlignment="Center"
Text="GitHub" />
</Grid> </Grid>
</Button> </Button>
</StackPanel> </StackPanel>
<StackPanel Orientation="Vertical" <StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutDiscordUrlTooltipMessage}">
ToolTip.Tip="{locale:Locale AboutDiscordUrlTooltipMessage}"> <Button
<Button Height="65" Background="Transparent" Tag="https://discordapp.com/invite/N2FmfVc" Height="65"
Click="Button_OnClick"> Background="Transparent"
Click="Button_OnClick"
Tag="https://discordapp.com/invite/N2FmfVc">
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition /> <RowDefinition />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Discord.png?assembly=Ryujinx.Ui.Common" /> <Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Discord.png?assembly=Ryujinx.Ui.Common" />
<TextBlock Grid.Row="1" Margin="0,5,0,0" Text="Discord" HorizontalAlignment="Center" /> <TextBlock
Grid.Row="1"
Margin="0,5,0,0"
HorizontalAlignment="Center"
Text="Discord" />
</Grid> </Grid>
</Button> </Button>
</StackPanel> </StackPanel>
<StackPanel Orientation="Vertical" <StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutTwitterUrlTooltipMessage}">
ToolTip.Tip="{locale:Locale AboutTwitterUrlTooltipMessage}"> <Button
<Button Height="65" Background="Transparent" Tag="https://twitter.com/RyujinxEmu" Height="65"
Click="Button_OnClick"> Background="Transparent"
Click="Button_OnClick"
Tag="https://twitter.com/RyujinxEmu">
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition /> <RowDefinition />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Twitter.png?assembly=Ryujinx.Ui.Common" /> <Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Twitter.png?assembly=Ryujinx.Ui.Common" />
<TextBlock Grid.Row="1" Margin="0,5,0,0" Text="Twitter" HorizontalAlignment="Center" /> <TextBlock
Grid.Row="1"
Margin="0,5,0,0"
HorizontalAlignment="Center"
Text="Twitter" />
</Grid> </Grid>
</Button> </Button>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
</Grid> </Grid>
<Border Grid.Row="1" Grid.Column="1" VerticalAlignment="Stretch" Margin="5" Width="2" BorderBrush="White" <Border
BorderThickness="1,0,0,0"> Grid.Row="1"
Grid.Column="1"
Width="2"
Margin="5"
VerticalAlignment="Stretch"
BorderBrush="White"
BorderThickness="1,0,0,0">
<Separator Width="0" /> <Separator Width="0" />
</Border> </Border>
<Grid Grid.Row="1" Margin="20" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Grid.Column="2"> <Grid
Grid.Row="1"
Grid.Column="2"
Margin="20"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
@ -136,27 +224,58 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock Text="{locale:Locale AboutRyujinxAboutTitle}" FontWeight="Bold" TextDecorations="Underline" /> <TextBlock
<TextBlock LineHeight="20" Grid.Row="1" Margin="20,5,5,5" FontWeight="Bold"
Text="{locale:Locale AboutRyujinxAboutContent}" /> Text="{locale:Locale AboutRyujinxAboutTitle}"
<TextBlock Grid.Row="2" Margin="0,10,0,0" Text="{locale:Locale AboutRyujinxMaintainersTitle}" TextDecorations="Underline" />
FontWeight="Bold" <TextBlock
TextDecorations="Underline" /> Grid.Row="1"
<TextBlock LineHeight="20" Grid.Row="3" Margin="20,5,5,5" Margin="20,5,5,5"
Text="{Binding Developers}" /> LineHeight="20"
<Button Background="Transparent" HorizontalAlignment="Right" Grid.Row="4" Text="{locale:Locale AboutRyujinxAboutContent}" />
Tag="https://github.com/Ryujinx/Ryujinx/graphs/contributors?type=a" Click="Button_OnClick"> <TextBlock
<TextBlock ToolTip.Tip="{locale:Locale AboutRyujinxMaintainersContentTooltipMessage}" Grid.Row="2"
TextAlignment="Right" TextDecorations="Underline" Margin="0,10,0,0"
Text="{locale:Locale AboutRyujinxContributorsButtonHeader}" /> FontWeight="Bold"
Text="{locale:Locale AboutRyujinxMaintainersTitle}"
TextDecorations="Underline" />
<TextBlock
Grid.Row="3"
Margin="20,5,5,5"
LineHeight="20"
Text="{Binding Developers}" />
<Button
Grid.Row="4"
HorizontalAlignment="Right"
Background="Transparent"
Click="Button_OnClick"
Tag="https://github.com/Ryujinx/Ryujinx/graphs/contributors?type=a">
<TextBlock
Text="{locale:Locale AboutRyujinxContributorsButtonHeader}"
TextAlignment="Right"
TextDecorations="Underline"
ToolTip.Tip="{locale:Locale AboutRyujinxMaintainersContentTooltipMessage}" />
</Button> </Button>
<TextBlock Grid.Row="5" Margin="0,0,0,0" Text="{locale:Locale AboutRyujinxSupprtersTitle}" <TextBlock
FontWeight="Bold" Grid.Row="5"
TextDecorations="Underline" /> Margin="0,0,0,0"
<Border Width="460" Grid.Row="6" VerticalAlignment="Stretch" Height="200" BorderThickness="1" Margin="20,5" FontWeight="Bold"
BorderBrush="White" Padding="5"> Text="{locale:Locale AboutRyujinxSupprtersTitle}"
<TextBlock TextWrapping="Wrap" VerticalAlignment="Top" Name="SupportersTextBlock" TextDecorations="Underline" />
Text="{Binding Supporters}" /> <Border
Grid.Row="6"
Width="460"
Height="200"
Margin="20,5"
Padding="5"
VerticalAlignment="Stretch"
BorderBrush="White"
BorderThickness="1">
<TextBlock
Name="SupportersTextBlock"
VerticalAlignment="Top"
Text="{Binding Supporters}"
TextWrapping="Wrap" />
</Border> </Border>
</Grid> </Grid>
</Grid> </Grid>

View File

@ -1,25 +1,25 @@
<window:StyleableWindow <window:StyleableWindow
x:Class="Ryujinx.Ava.Ui.Windows.MainWindow" x:Class="Ryujinx.Ava.Ui.Windows.MainWindow"
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls" xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls"
xmlns:models="clr-namespace:Ryujinx.Ava.Ui.Models"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="clr-namespace:Ryujinx.Ava.Ui.Models"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels" xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows" xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
Title="Ryujinx" Title="Ryujinx"
Height="785"
Width="1280" Width="1280"
d:DesignHeight="720" Height="785"
d:DesignWidth="1280"
MinWidth="1024" MinWidth="1024"
MinHeight="680" MinHeight="680"
WindowStartupLocation="CenterScreen" d:DesignHeight="720"
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">
<Window.Styles> <Window.Styles>
<Style Selector="TitleBar:fullscreen"> <Style Selector="TitleBar:fullscreen">
@ -38,23 +38,28 @@
<RowDefinition Height="*" /> <RowDefinition Height="*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<controls:OffscreenTextBox Name="HiddenTextBox" Grid.Row="0" /> <controls:OffscreenTextBox Name="HiddenTextBox" Grid.Row="0" />
<ContentControl Grid.Row="1" <ContentControl
Focusable="False" Grid.Row="1"
IsVisible="False" Focusable="False"
KeyboardNavigation.IsTabStop="False"> IsVisible="False"
<ui:ContentDialog Name="ContentDialog" KeyboardNavigation.IsTabStop="False">
KeyboardNavigation.IsTabStop="False" <ui:ContentDialog
IsPrimaryButtonEnabled="True" Name="ContentDialog"
IsSecondaryButtonEnabled="True" IsPrimaryButtonEnabled="True"
IsVisible="True" /> IsSecondaryButtonEnabled="True"
IsVisible="True"
KeyboardNavigation.IsTabStop="False" />
</ContentControl> </ContentControl>
<StackPanel IsVisible="False" Grid.Row="0"> <StackPanel Grid.Row="0" IsVisible="False">
<controls:HotKeyControl Name="FullscreenHotKey" Command="{ReflectionBinding ToggleFullscreen}" /> <controls:HotKeyControl Name="FullscreenHotKey" Command="{ReflectionBinding ToggleFullscreen}" />
<controls:HotKeyControl Name="FullscreenHotKey2" Command="{ReflectionBinding ToggleFullscreen}" /> <controls:HotKeyControl Name="FullscreenHotKey2" Command="{ReflectionBinding ToggleFullscreen}" />
<controls:HotKeyControl Name="DockToggleHotKey" Command="{ReflectionBinding ToggleDockMode}" /> <controls:HotKeyControl Name="DockToggleHotKey" Command="{ReflectionBinding ToggleDockMode}" />
<controls:HotKeyControl Name="ExitHotKey" Command="{ReflectionBinding ExitCurrentState}" /> <controls:HotKeyControl Name="ExitHotKey" Command="{ReflectionBinding ExitCurrentState}" />
</StackPanel> </StackPanel>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Row="1"> <Grid
Grid.Row="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
@ -73,47 +78,51 @@
<DockPanel HorizontalAlignment="Stretch"> <DockPanel HorizontalAlignment="Stretch">
<Menu <Menu
Name="Menu" Name="Menu"
Margin="0"
Height="35" Height="35"
Margin="0"
HorizontalAlignment="Left"> HorizontalAlignment="Left">
<Menu.ItemsPanel> <Menu.ItemsPanel>
<ItemsPanelTemplate> <ItemsPanelTemplate>
<DockPanel HorizontalAlignment="Stretch" Margin="0" /> <DockPanel Margin="0" HorizontalAlignment="Stretch" />
</ItemsPanelTemplate> </ItemsPanelTemplate>
</Menu.ItemsPanel> </Menu.ItemsPanel>
<MenuItem <MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarFile}">
VerticalAlignment="Center" <MenuItem
Header="{locale:Locale MenuBarFile}"> Command="{ReflectionBinding OpenFile}"
<MenuItem IsEnabled="{Binding EnableNonGameRunningControls}" Header="{locale:Locale MenuBarFileOpenFromFile}"
Command="{ReflectionBinding OpenFile}" IsEnabled="{Binding EnableNonGameRunningControls}"
Header="{locale:Locale MenuBarFileOpenFromFile}" ToolTip.Tip="{locale:Locale LoadApplicationFileTooltip}" />
ToolTip.Tip="{locale:Locale LoadApplicationFileTooltip}" /> <MenuItem
<MenuItem IsEnabled="{Binding EnableNonGameRunningControls}" Command="{ReflectionBinding OpenFolder}"
Command="{ReflectionBinding OpenFolder}" Header="{locale:Locale MenuBarFileOpenUnpacked}"
Header="{locale:Locale MenuBarFileOpenUnpacked}" IsEnabled="{Binding EnableNonGameRunningControls}"
ToolTip.Tip="{locale:Locale LoadApplicationFolderTooltip}" /> ToolTip.Tip="{locale:Locale LoadApplicationFolderTooltip}" />
<MenuItem Header="{locale:Locale MenuBarFileOpenApplet}" <MenuItem Header="{locale:Locale MenuBarFileOpenApplet}" IsEnabled="{Binding IsAppletMenuActive}">
IsEnabled="{Binding IsAppletMenuActive}"> <MenuItem
<MenuItem Command="{ReflectionBinding OpenMiiApplet}" Header="Mii Edit Applet" Command="{ReflectionBinding OpenMiiApplet}"
ToolTip.Tip="{locale:Locale MenuBarFileOpenAppletOpenMiiAppletToolTip}" /> Header="Mii Edit Applet"
ToolTip.Tip="{locale:Locale MenuBarFileOpenAppletOpenMiiAppletToolTip}" />
</MenuItem> </MenuItem>
<Separator /> <Separator />
<MenuItem Command="{ReflectionBinding OpenRyujinxFolder}" <MenuItem
Header="{locale:Locale MenuBarFileOpenEmuFolder}" Command="{ReflectionBinding OpenRyujinxFolder}"
ToolTip.Tip="{locale:Locale OpenRyujinxFolderTooltip}" /> Header="{locale:Locale MenuBarFileOpenEmuFolder}"
<MenuItem Command="{ReflectionBinding OpenLogsFolder}" ToolTip.Tip="{locale:Locale OpenRyujinxFolderTooltip}" />
Header="{locale:Locale MenuBarFileOpenLogsFolder}" <MenuItem
ToolTip.Tip="{locale:Locale OpenRyujinxLogsTooltip}" /> Command="{ReflectionBinding OpenLogsFolder}"
Header="{locale:Locale MenuBarFileOpenLogsFolder}"
ToolTip.Tip="{locale:Locale OpenRyujinxLogsTooltip}" />
<Separator /> <Separator />
<MenuItem Command="{ReflectionBinding CloseWindow}" <MenuItem
Header="{locale:Locale MenuBarFileExit}" Command="{ReflectionBinding CloseWindow}"
ToolTip.Tip="{locale:Locale ExitTooltip}" /> Header="{locale:Locale MenuBarFileExit}"
ToolTip.Tip="{locale:Locale ExitTooltip}" />
</MenuItem> </MenuItem>
<MenuItem <MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarOptions}">
VerticalAlignment="Center" <MenuItem
Header="{locale:Locale MenuBarOptions}"> Command="{ReflectionBinding ToggleFullscreen}"
<MenuItem Command="{ReflectionBinding ToggleFullscreen}" Header="{locale:Locale MenuBarOptionsToggleFullscreen}"
Header="{locale:Locale MenuBarOptionsToggleFullscreen}" InputGesture="F11" /> InputGesture="F11" />
<MenuItem Header="{locale:Locale MenuBarOptionsStartGamesInFullscreen}"> <MenuItem Header="{locale:Locale MenuBarOptionsStartGamesInFullscreen}">
<MenuItem.Icon> <MenuItem.Icon>
<CheckBox IsChecked="{Binding StartGamesInFullscreen, Mode=TwoWay}" /> <CheckBox IsChecked="{Binding StartGamesInFullscreen, Mode=TwoWay}" />
@ -126,60 +135,82 @@
</MenuItem> </MenuItem>
<Separator /> <Separator />
<MenuItem Header="{locale:Locale MenuBarOptionsChangeLanguage}"> <MenuItem Header="{locale:Locale MenuBarOptionsChangeLanguage}">
<MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="en_US" <MenuItem
Header="American English" /> Command="{ReflectionBinding ChangeLanguage}"
<MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="pt_BR" CommandParameter="en_US"
Header="Brazilian Portuguese" /> Header="American English" />
<MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="es_ES" <MenuItem
Header="Castilian Spanish" /> Command="{ReflectionBinding ChangeLanguage}"
<MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="fr_FR" CommandParameter="pt_BR"
Header="French" /> Header="Brazilian Portuguese" />
<MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="de_DE" <MenuItem
Header="German" /> Command="{ReflectionBinding ChangeLanguage}"
<MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="el_GR" CommandParameter="es_ES"
Header="Greek" /> Header="Castilian Spanish" />
<MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="it_IT" <MenuItem
Header="Italian" /> Command="{ReflectionBinding ChangeLanguage}"
<MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="ko_KR" CommandParameter="fr_FR"
Header="Korean" /> Header="French" />
<MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="ru_RU" <MenuItem
Header="Russian" /> Command="{ReflectionBinding ChangeLanguage}"
<MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="tr_TR" CommandParameter="de_DE"
Header="Turkish" /> Header="German" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="el_GR"
Header="Greek" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="it_IT"
Header="Italian" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="ko_KR"
Header="Korean" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="ru_RU"
Header="Russian" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="tr_TR"
Header="Turkish" />
</MenuItem> </MenuItem>
<Separator /> <Separator />
<MenuItem Command="{ReflectionBinding OpenSettings}" <MenuItem
Header="{locale:Locale MenuBarOptionsSettings}" Command="{ReflectionBinding OpenSettings}"
ToolTip.Tip="{locale:Locale OpenSettingsTooltip}" /> Header="{locale:Locale MenuBarOptionsSettings}"
<MenuItem Command="{ReflectionBinding ManageProfiles}" ToolTip.Tip="{locale:Locale OpenSettingsTooltip}" />
IsEnabled="{Binding EnableNonGameRunningControls}" <MenuItem
Header="{locale:Locale MenuBarOptionsManageUserProfiles}" Command="{ReflectionBinding ManageProfiles}"
ToolTip.Tip="{locale:Locale OpenProfileManagerTooltip}" /> Header="{locale:Locale MenuBarOptionsManageUserProfiles}"
IsEnabled="{Binding EnableNonGameRunningControls}"
ToolTip.Tip="{locale:Locale OpenProfileManagerTooltip}" />
</MenuItem> </MenuItem>
<MenuItem <MenuItem
Name="ActionsMenuItem"
VerticalAlignment="Center" VerticalAlignment="Center"
Header="{locale:Locale MenuBarActions}" Header="{locale:Locale MenuBarActions}"
Name="ActionsMenuItem"
IsEnabled="{Binding IsGameRunning}"> IsEnabled="{Binding IsGameRunning}">
<MenuItem <MenuItem
Click="PauseEmulation_Click" Click="PauseEmulation_Click"
Header="{locale:Locale MenuBarOptionsPauseEmulation}" Header="{locale:Locale MenuBarOptionsPauseEmulation}"
InputGesture="{Binding PauseKey}"
IsEnabled="{Binding !IsPaused}" IsEnabled="{Binding !IsPaused}"
IsVisible="{Binding !IsPaused}" IsVisible="{Binding !IsPaused}" />
InputGesture="{Binding PauseKey}" />
<MenuItem <MenuItem
Click="ResumeEmulation_Click" Click="ResumeEmulation_Click"
Header="{locale:Locale MenuBarOptionsResumeEmulation}" Header="{locale:Locale MenuBarOptionsResumeEmulation}"
InputGesture="{Binding PauseKey}"
IsEnabled="{Binding IsPaused}" IsEnabled="{Binding IsPaused}"
IsVisible="{Binding IsPaused}" IsVisible="{Binding IsPaused}" />
InputGesture="{Binding PauseKey}" />
<MenuItem <MenuItem
Click="StopEmulation_Click" Click="StopEmulation_Click"
Header="{locale:Locale MenuBarOptionsStopEmulation}" Header="{locale:Locale MenuBarOptionsStopEmulation}"
ToolTip.Tip="{locale:Locale StopEmulationTooltip}" InputGesture="Escape"
IsEnabled="{Binding IsGameRunning}" InputGesture="Escape" /> IsEnabled="{Binding IsGameRunning}"
<MenuItem Command="{ReflectionBinding SimulateWakeUpMessage}" ToolTip.Tip="{locale:Locale StopEmulationTooltip}" />
Header="{locale:Locale MenuBarOptionsSimulateWakeUpMessage}" /> <MenuItem Command="{ReflectionBinding SimulateWakeUpMessage}" Header="{locale:Locale MenuBarOptionsSimulateWakeUpMessage}" />
<Separator /> <Separator />
<MenuItem <MenuItem
Name="ScanAmiiboMenuItem" Name="ScanAmiiboMenuItem"
@ -187,41 +218,38 @@
Command="{ReflectionBinding OpenAmiiboWindow}" Command="{ReflectionBinding OpenAmiiboWindow}"
Header="{locale:Locale MenuBarActionsScanAmiibo}" Header="{locale:Locale MenuBarActionsScanAmiibo}"
IsEnabled="{Binding IsAmiiboRequested}" /> IsEnabled="{Binding IsAmiiboRequested}" />
<MenuItem Command="{ReflectionBinding TakeScreenshot}" <MenuItem
IsEnabled="{Binding IsGameRunning}" Command="{ReflectionBinding TakeScreenshot}"
Header="{locale:Locale MenuBarFileToolsTakeScreenshot}" Header="{locale:Locale MenuBarFileToolsTakeScreenshot}"
InputGesture="{Binding ScreenshotKey}" /> InputGesture="{Binding ScreenshotKey}"
<MenuItem Command="{ReflectionBinding HideUi}" IsEnabled="{Binding IsGameRunning}" />
IsEnabled="{Binding IsGameRunning}" <MenuItem
Header="{locale:Locale MenuBarFileToolsHideUi}" Command="{ReflectionBinding HideUi}"
InputGesture="{Binding ShowUiKey}" /> Header="{locale:Locale MenuBarFileToolsHideUi}"
<MenuItem Command="{ReflectionBinding OpenCheatManagerForCurrentApp}" InputGesture="{Binding ShowUiKey}"
IsEnabled="{Binding IsGameRunning}" IsEnabled="{Binding IsGameRunning}" />
Header="{locale:Locale GameListContextMenuManageCheat}" /> <MenuItem
Command="{ReflectionBinding OpenCheatManagerForCurrentApp}"
Header="{locale:Locale GameListContextMenuManageCheat}"
IsEnabled="{Binding IsGameRunning}" />
</MenuItem> </MenuItem>
<MenuItem <MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarTools}">
VerticalAlignment="Center" <MenuItem Header="{locale:Locale MenuBarToolsInstallFirmware}" IsEnabled="{Binding EnableNonGameRunningControls}">
Header="{locale:Locale MenuBarTools}"> <MenuItem Command="{ReflectionBinding InstallFirmwareFromFile}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromFile}" />
<MenuItem Header="{locale:Locale MenuBarToolsInstallFirmware}" <MenuItem Command="{ReflectionBinding InstallFirmwareFromFolder}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromDirectory}" />
IsEnabled="{Binding EnableNonGameRunningControls}">
<MenuItem Command="{ReflectionBinding InstallFirmwareFromFile}"
Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromFile}" />
<MenuItem Command="{ReflectionBinding InstallFirmwareFromFolder}"
Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromDirectory}" />
</MenuItem> </MenuItem>
</MenuItem> </MenuItem>
<MenuItem <MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarHelp}">
VerticalAlignment="Center"
Header="{locale:Locale MenuBarHelp}">
<MenuItem <MenuItem
Name="UpdateMenuItem" Name="UpdateMenuItem"
Command="{ReflectionBinding CheckForUpdates}" Command="{ReflectionBinding CheckForUpdates}"
Header="{locale:Locale MenuBarHelpCheckForUpdates}" Header="{locale:Locale MenuBarHelpCheckForUpdates}"
ToolTip.Tip="{locale:Locale CheckUpdatesTooltip}" /> ToolTip.Tip="{locale:Locale CheckUpdatesTooltip}" />
<Separator /> <Separator />
<MenuItem Command="{ReflectionBinding OpenAboutWindow}" <MenuItem
Header="{locale:Locale MenuBarHelpAbout}" Command="{ReflectionBinding OpenAboutWindow}"
ToolTip.Tip="{locale:Locale OpenAboutTooltip}" /> Header="{locale:Locale MenuBarHelpAbout}"
ToolTip.Tip="{locale:Locale OpenAboutTooltip}" />
</MenuItem> </MenuItem>
</Menu> </Menu>
</DockPanel> </DockPanel>
@ -230,152 +258,213 @@
Name="Content" Name="Content"
Grid.Row="1" Grid.Row="1"
Padding="0" Padding="0"
IsVisible="{Binding ShowContent}"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
BorderBrush="{DynamicResource ThemeControlBorderColor}" BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="0,0,0,0" BorderThickness="0,0,0,0"
DockPanel.Dock="Top"> DockPanel.Dock="Top"
IsVisible="{Binding ShowContent}">
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<DockPanel Grid.Row="0" HorizontalAlignment="Stretch" Margin="0,0,0,5"> <DockPanel
Grid.Row="0"
Margin="0,0,0,5"
HorizontalAlignment="Stretch">
<Button <Button
IsEnabled="{Binding IsGrid}" VerticalAlignment="Stretch" MinWidth="40" Width="40" Width="40"
Margin="5,2,0,2" Command="{ReflectionBinding SetListMode}"> MinWidth="40"
<ui:FontIcon FontFamily="avares://FluentAvalonia/Fonts#Symbols" Margin="5,2,0,2"
VerticalAlignment="Center" VerticalAlignment="Stretch"
Margin="0" Command="{ReflectionBinding SetListMode}"
Glyph="{controls:GlyphValueConverter List}" IsEnabled="{Binding IsGrid}">
HorizontalAlignment="Stretch" /> <ui:FontIcon
Margin="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
FontFamily="avares://FluentAvalonia/Fonts#Symbols"
Glyph="{controls:GlyphValueConverter List}" />
</Button> </Button>
<Button <Button
IsEnabled="{Binding IsList}" VerticalAlignment="Stretch" MinWidth="40" Width="40" Width="40"
Margin="5,2,5,2" Command="{ReflectionBinding SetGridMode}"> MinWidth="40"
<ui:FontIcon FontFamily="avares://FluentAvalonia/Fonts#Symbols" Margin="5,2,5,2"
VerticalAlignment="Center" VerticalAlignment="Stretch"
Margin="0" Command="{ReflectionBinding SetGridMode}"
Glyph="{controls:GlyphValueConverter Grid}" IsEnabled="{Binding IsList}">
HorizontalAlignment="Stretch" /> <ui:FontIcon
Margin="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
FontFamily="avares://FluentAvalonia/Fonts#Symbols"
Glyph="{controls:GlyphValueConverter Grid}" />
</Button> </Button>
<TextBlock Text="{locale:Locale IconSize}" <TextBlock
VerticalAlignment="Center" Margin="10,0" Margin="10,0"
ToolTip.Tip="{locale:Locale IconSizeTooltip}" /> VerticalAlignment="Center"
<Slider Width="150" Margin="5,-10,5 ,0" Height="35" Text="{locale:Locale IconSize}"
ToolTip.Tip="{locale:Locale IconSizeTooltip}" ToolTip.Tip="{locale:Locale IconSizeTooltip}" />
VerticalAlignment="Center" Minimum="1" Maximum="4" IsSnapToTickEnabled="True" <Slider
TickFrequency="1" Value="{Binding GridSizeScale}" /> Width="150"
<CheckBox Margin="0" IsChecked="{Binding ShowNames, Mode=TwoWay}" VerticalAlignment="Center" Height="35"
IsVisible="{Binding IsGrid}"> Margin="5,-10,5,0"
<TextBlock Text="{locale:Locale CommonShowNames}" Margin="5,3,0,0" /> VerticalAlignment="Center"
IsSnapToTickEnabled="True"
Maximum="4"
Minimum="1"
TickFrequency="1"
ToolTip.Tip="{locale:Locale IconSizeTooltip}"
Value="{Binding GridSizeScale}" />
<CheckBox
Margin="0"
VerticalAlignment="Center"
IsChecked="{Binding ShowNames, Mode=TwoWay}"
IsVisible="{Binding IsGrid}">
<TextBlock Margin="5,3,0,0" Text="{locale:Locale CommonShowNames}" />
</CheckBox> </CheckBox>
<TextBox <TextBox
Name="SearchBox" Name="SearchBox"
DockPanel.Dock="Right"
VerticalAlignment="Center"
MinWidth="200" MinWidth="200"
Margin="5,0,5,0" Margin="5,0,5,0"
HorizontalAlignment="Right" HorizontalAlignment="Right"
VerticalAlignment="Center"
DockPanel.Dock="Right"
KeyUp="SearchBox_OnKeyUp" KeyUp="SearchBox_OnKeyUp"
Text="{Binding SearchText}" Text="{Binding SearchText}"
Watermark="{locale:Locale MenuSearch}" /> Watermark="{locale:Locale MenuSearch}" />
<ui:DropDownButton DockPanel.Dock="Right" <ui:DropDownButton
HorizontalAlignment="Right" Width="150" VerticalAlignment="Center" Width="150"
Content="{Binding SortName}"> HorizontalAlignment="Right"
VerticalAlignment="Center"
Content="{Binding SortName}"
DockPanel.Dock="Right">
<ui:DropDownButton.Flyout> <ui:DropDownButton.Flyout>
<Flyout Placement="Bottom"> <Flyout Placement="Bottom">
<StackPanel Orientation="Vertical" HorizontalAlignment="Stretch" Margin="0"> <StackPanel
Margin="0"
HorizontalAlignment="Stretch"
Orientation="Vertical">
<StackPanel> <StackPanel>
<RadioButton Tag="Favorite" <RadioButton
IsChecked="{Binding IsSortedByFavorite, Mode=OneTime}" Checked="Sort_Checked"
GroupName="Sort" Content="{locale:Locale CommonFavorite}"
Content="{locale:Locale CommonFavorite}" GroupName="Sort"
Checked="Sort_Checked" /> IsChecked="{Binding IsSortedByFavorite, Mode=OneTime}"
<RadioButton Tag="Title" GroupName="Sort" Tag="Favorite" />
IsChecked="{Binding IsSortedByTitle, Mode=OneTime}" <RadioButton
Content="{locale:Locale GameListHeaderApplication}" Checked="Sort_Checked"
Checked="Sort_Checked" /> Content="{locale:Locale GameListHeaderApplication}"
<RadioButton Tag="Developer" GroupName="Sort" GroupName="Sort"
IsChecked="{Binding IsSortedByDeveloper, Mode=OneTime}" IsChecked="{Binding IsSortedByTitle, Mode=OneTime}"
Content="{locale:Locale GameListHeaderDeveloper}" Tag="Title" />
Checked="Sort_Checked" /> <RadioButton
<RadioButton Tag="TotalTimePlayed" GroupName="Sort" Checked="Sort_Checked"
IsChecked="{Binding IsSortedByTimePlayed, Mode=OneTime}" Content="{locale:Locale GameListHeaderDeveloper}"
Content="{locale:Locale GameListHeaderTimePlayed}" GroupName="Sort"
Checked="Sort_Checked" /> IsChecked="{Binding IsSortedByDeveloper, Mode=OneTime}"
<RadioButton Tag="LastPlayed" GroupName="Sort" Tag="Developer" />
IsChecked="{Binding IsSortedByLastPlayed, Mode=OneTime}" <RadioButton
Content="{locale:Locale GameListHeaderLastPlayed}" Checked="Sort_Checked"
Checked="Sort_Checked" /> Content="{locale:Locale GameListHeaderTimePlayed}"
<RadioButton Tag="FileType" GroupName="Sort" GroupName="Sort"
IsChecked="{Binding IsSortedByType, Mode=OneTime}" IsChecked="{Binding IsSortedByTimePlayed, Mode=OneTime}"
Content="{locale:Locale GameListHeaderFileExtension}" Tag="TotalTimePlayed" />
Checked="Sort_Checked" /> <RadioButton
<RadioButton Tag="FileSize" GroupName="Sort" Checked="Sort_Checked"
IsChecked="{Binding IsSortedBySize, Mode=OneTime}" Content="{locale:Locale GameListHeaderLastPlayed}"
Content="{locale:Locale GameListHeaderFileSize}" GroupName="Sort"
Checked="Sort_Checked" /> IsChecked="{Binding IsSortedByLastPlayed, Mode=OneTime}"
<RadioButton Tag="Path" GroupName="Sort" Tag="LastPlayed" />
IsChecked="{Binding IsSortedByPath, Mode=OneTime}" <RadioButton
Content="{locale:Locale GameListHeaderPath}" Checked="Sort_Checked"
Checked="Sort_Checked" /> Content="{locale:Locale GameListHeaderFileExtension}"
GroupName="Sort"
IsChecked="{Binding IsSortedByType, Mode=OneTime}"
Tag="FileType" />
<RadioButton
Checked="Sort_Checked"
Content="{locale:Locale GameListHeaderFileSize}"
GroupName="Sort"
IsChecked="{Binding IsSortedBySize, Mode=OneTime}"
Tag="FileSize" />
<RadioButton
Checked="Sort_Checked"
Content="{locale:Locale GameListHeaderPath}"
GroupName="Sort"
IsChecked="{Binding IsSortedByPath, Mode=OneTime}"
Tag="Path" />
</StackPanel> </StackPanel>
<Border HorizontalAlignment="Stretch" Margin="5" Height="2" BorderBrush="White" <Border
Width="60" BorderThickness="0,1,0,0"> Width="60"
<Separator HorizontalAlignment="Stretch" Height="0" /> Height="2"
Margin="5"
HorizontalAlignment="Stretch"
BorderBrush="White"
BorderThickness="0,1,0,0">
<Separator Height="0" HorizontalAlignment="Stretch" />
</Border> </Border>
<RadioButton Tag="Ascending" IsChecked="{Binding IsAscending, Mode=OneTime}" <RadioButton
GroupName="Order" Checked="Order_Checked"
Content="{locale:Locale OrderAscending}" Checked="Order_Checked" /> Content="{locale:Locale OrderAscending}"
<RadioButton Tag="Descending" GroupName="Order" GroupName="Order"
IsChecked="{Binding !IsAscending, Mode=OneTime}" IsChecked="{Binding IsAscending, Mode=OneTime}"
Content="{locale:Locale OrderDescending}" Checked="Order_Checked" /> Tag="Ascending" />
<RadioButton
Checked="Order_Checked"
Content="{locale:Locale OrderDescending}"
GroupName="Order"
IsChecked="{Binding !IsAscending, Mode=OneTime}"
Tag="Descending" />
</StackPanel> </StackPanel>
</Flyout> </Flyout>
</ui:DropDownButton.Flyout> </ui:DropDownButton.Flyout>
</ui:DropDownButton> </ui:DropDownButton>
<TextBlock DockPanel.Dock="Right" HorizontalAlignment="Right" <TextBlock
Text="{locale:Locale CommonSort}" VerticalAlignment="Center" Margin="10,0" /> Margin="10,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
DockPanel.Dock="Right"
Text="{locale:Locale CommonSort}" />
</DockPanel> </DockPanel>
<controls:GameListView <controls:GameListView
x:Name="GameList" x:Name="GameList"
Grid.Row="1" Grid.Row="1"
HorizontalContentAlignment="Stretch"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalContentAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
IsVisible="{Binding IsList}" /> IsVisible="{Binding IsList}" />
<controls:GameGridView <controls:GameGridView
x:Name="GameGrid" x:Name="GameGrid"
Grid.Row="1" Grid.Row="1"
HorizontalContentAlignment="Stretch"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalContentAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
IsVisible="{Binding IsGrid}" /> IsVisible="{Binding IsGrid}" />
</Grid> </Grid>
</ContentControl> </ContentControl>
<Grid Grid.Row="1" <Grid
VerticalAlignment="Stretch" Grid.Row="1"
Background="{DynamicResource ThemeContentBackgroundColor}" HorizontalAlignment="Stretch"
IsVisible="{Binding ShowLoadProgress}" VerticalAlignment="Stretch"
ZIndex="1000" Background="{DynamicResource ThemeContentBackgroundColor}"
HorizontalAlignment="Stretch"> IsVisible="{Binding ShowLoadProgress}"
ZIndex="1000">
<Grid <Grid
HorizontalAlignment="Center"
IsVisible="{Binding ShowLoadProgress}"
Margin="40" Margin="40"
VerticalAlignment="Center"> HorizontalAlignment="Center"
VerticalAlignment="Center"
IsVisible="{Binding ShowLoadProgress}">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Border <Border
Grid.Column="0"
Grid.RowSpan="2" Grid.RowSpan="2"
IsVisible="{Binding ShowLoadProgress}" Grid.Column="0"
Width="256" Width="256"
Height="256" Height="256"
Margin="10" Margin="10"
@ -383,62 +472,64 @@
BorderBrush="Black" BorderBrush="Black"
BorderThickness="2" BorderThickness="2"
BoxShadow="4 4 32 8 #40000000" BoxShadow="4 4 32 8 #40000000"
CornerRadius="3"> CornerRadius="3"
IsVisible="{Binding ShowLoadProgress}">
<Image <Image
IsVisible="{Binding ShowLoadProgress}"
Width="256" Width="256"
Height="256" Height="256"
IsVisible="{Binding ShowLoadProgress}"
Source="{Binding SelectedIcon, Converter={StaticResource ByteImage}}" /> Source="{Binding SelectedIcon, Converter={StaticResource ByteImage}}" />
</Border> </Border>
<Grid Grid.Column="1" <Grid
IsVisible="{Binding ShowLoadProgress}" Grid.Column="1"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Center"> VerticalAlignment="Center"
IsVisible="{Binding ShowLoadProgress}">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock <TextBlock
IsVisible="{Binding ShowLoadProgress}"
Grid.Row="0" Grid.Row="0"
Margin="10" Margin="10"
FontSize="30" FontSize="30"
FontWeight="Bold" FontWeight="Bold"
TextWrapping="Wrap"
Text="{Binding LoadHeading}"
TextAlignment="Left" />
<Border
IsVisible="{Binding ShowLoadProgress}" IsVisible="{Binding ShowLoadProgress}"
Text="{Binding LoadHeading}"
TextAlignment="Left"
TextWrapping="Wrap" />
<Border
Grid.Row="1" Grid.Row="1"
CornerRadius="5" Margin="10"
ClipToBounds="True"
BorderBrush="{Binding ProgressBarBackgroundColor}"
Padding="0" Padding="0"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Margin="10" BorderBrush="{Binding ProgressBarBackgroundColor}"
BorderThickness="1"> BorderThickness="1"
ClipToBounds="True"
CornerRadius="5"
IsVisible="{Binding ShowLoadProgress}">
<ProgressBar <ProgressBar
IsVisible="{Binding ShowLoadProgress}"
Height="10" Height="10"
MinWidth="500"
Margin="0" Margin="0"
Padding="0" Padding="0"
CornerRadius="5"
ClipToBounds="True"
MinWidth="500"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Background="{Binding ProgressBarBackgroundColor}" Background="{Binding ProgressBarBackgroundColor}"
ClipToBounds="True"
CornerRadius="5"
Foreground="{Binding ProgressBarForegroundColor}" Foreground="{Binding ProgressBarForegroundColor}"
IsIndeterminate="{Binding IsLoadingIndeterminate}"
IsVisible="{Binding ShowLoadProgress}"
Maximum="{Binding ProgressMaximum}" Maximum="{Binding ProgressMaximum}"
Minimum="0" Minimum="0"
IsIndeterminate="{Binding IsLoadingIndeterminate}"
Value="{Binding ProgressValue}" /> Value="{Binding ProgressValue}" />
</Border> </Border>
<TextBlock <TextBlock
IsVisible="{Binding ShowLoadProgress}"
Grid.Row="2" Grid.Row="2"
Margin="10" Margin="10"
FontSize="18" FontSize="18"
IsVisible="{Binding ShowLoadProgress}"
Text="{Binding CacheLoadStatus}" Text="{Binding CacheLoadStatus}"
TextAlignment="Left" /> TextAlignment="Left" />
</Grid> </Grid>
@ -450,8 +541,8 @@
Height="30" Height="30"
Margin="0,0" Margin="0,0"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Background="{DynamicResource ThemeContentBackgroundColor}"
VerticalAlignment="Bottom" VerticalAlignment="Bottom"
Background="{DynamicResource ThemeContentBackgroundColor}"
DockPanel.Dock="Bottom" DockPanel.Dock="Bottom"
IsVisible="{Binding ShowMenuAndStatusBar}"> IsVisible="{Binding ShowMenuAndStatusBar}">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
@ -460,8 +551,11 @@
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" IsVisible="{Binding EnableNonGameRunningControls}" <StackPanel
VerticalAlignment="Center" Margin="10,0"> Grid.Column="0"
Margin="10,0"
VerticalAlignment="Center"
IsVisible="{Binding EnableNonGameRunningControls}">
<Grid Margin="0"> <Grid Margin="0">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
@ -476,7 +570,10 @@
VerticalAlignment="Center" VerticalAlignment="Center"
Background="Transparent" Background="Transparent"
Command="{ReflectionBinding LoadApplications}"> Command="{ReflectionBinding LoadApplications}">
<ui:SymbolIcon Symbol="Refresh" Height="100" Width="50" /> <ui:SymbolIcon
Width="50"
Height="100"
Symbol="Refresh" />
</Button> </Button>
<TextBlock <TextBlock
Name="LoadStatus" Name="LoadStatus"
@ -489,11 +586,11 @@
Name="LoadProgressBar" Name="LoadProgressBar"
Grid.Column="2" Grid.Column="2"
Height="6" Height="6"
Maximum="{Binding StatusBarProgressMaximum}"
Value="{Binding StatusBarProgressValue}"
VerticalAlignment="Center" VerticalAlignment="Center"
Foreground="{DynamicResource HighlightColor}" Foreground="{DynamicResource HighlightColor}"
IsVisible="{Binding EnableNonGameRunningControls}" /> IsVisible="{Binding EnableNonGameRunningControls}"
Maximum="{Binding StatusBarProgressMaximum}"
Value="{Binding StatusBarProgressValue}" />
</Grid> </Grid>
</StackPanel> </StackPanel>
<StackPanel <StackPanel
@ -505,132 +602,137 @@
Orientation="Horizontal"> Orientation="Horizontal">
<TextBlock <TextBlock
Name="VsyncStatus" Name="VsyncStatus"
Margin="0,0,5,0"
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Center" VerticalAlignment="Center"
Foreground="{Binding VsyncColor}" Foreground="{Binding VsyncColor}"
PointerReleased="VsyncStatus_PointerReleased"
IsVisible="{Binding !ShowLoadProgress}" IsVisible="{Binding !ShowLoadProgress}"
Margin="0,0,5,0" PointerReleased="VsyncStatus_PointerReleased"
Text="VSync" Text="VSync"
TextAlignment="Left" /> TextAlignment="Left" />
<Border <Border
Width="2" Width="2"
Margin="2,0"
IsVisible="{Binding !ShowLoadProgress}"
BorderThickness="1"
Height="12" Height="12"
BorderBrush="Gray" /> Margin="2,0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock <TextBlock
Margin="5,0,5,0"
Name="DockedStatus" Name="DockedStatus"
IsVisible="{Binding !ShowLoadProgress}" Margin="5,0,5,0"
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Center" VerticalAlignment="Center"
IsVisible="{Binding !ShowLoadProgress}"
PointerReleased="DockedStatus_PointerReleased" PointerReleased="DockedStatus_PointerReleased"
Text="{Binding DockedStatusText}" Text="{Binding DockedStatusText}"
TextAlignment="Left" /> TextAlignment="Left" />
<Border <Border
Width="2" Width="2"
Margin="2,0"
IsVisible="{Binding !ShowLoadProgress}"
BorderThickness="1"
Height="12" Height="12"
BorderBrush="Gray" /> Margin="2,0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock <TextBlock
Margin="5,0,5,0"
Name="AspectRatioStatus" Name="AspectRatioStatus"
IsVisible="{Binding !ShowLoadProgress}" Margin="5,0,5,0"
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Center" VerticalAlignment="Center"
IsVisible="{Binding !ShowLoadProgress}"
PointerReleased="AspectRatioStatus_PointerReleased" PointerReleased="AspectRatioStatus_PointerReleased"
Text="{Binding AspectRatioStatusText}" Text="{Binding AspectRatioStatusText}"
TextAlignment="Left" /> TextAlignment="Left" />
<Border <Border
Width="2" Width="2"
Margin="2,0"
IsVisible="{Binding !ShowLoadProgress}"
BorderThickness="1"
Height="12" Height="12"
BorderBrush="Gray" /> Margin="2,0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<ui:ToggleSplitButton <ui:ToggleSplitButton
Name="VolumeStatus"
Margin="-2,0,-3,0" Margin="-2,0,-3,0"
Padding="5,0,0,5" Padding="5,0,0,5"
Name="VolumeStatus"
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Center" VerticalAlignment="Center"
IsVisible="{Binding !ShowLoadProgress}"
BorderBrush="{DynamicResource ThemeContentBackgroundColor}"
Background="{DynamicResource ThemeContentBackgroundColor}" Background="{DynamicResource ThemeContentBackgroundColor}"
BorderBrush="{DynamicResource ThemeContentBackgroundColor}"
Content="{Binding VolumeStatusText}"
IsChecked="{Binding VolumeMuted}" IsChecked="{Binding VolumeMuted}"
Content="{Binding VolumeStatusText}"> IsVisible="{Binding !ShowLoadProgress}">
<ui:ToggleSplitButton.Flyout> <ui:ToggleSplitButton.Flyout>
<Flyout Placement="Bottom" ShowMode="TransientWithDismissOnPointerMoveAway"> <Flyout Placement="Bottom" ShowMode="TransientWithDismissOnPointerMoveAway">
<Grid Margin="0"> <Grid Margin="0">
<Slider Value="{Binding Volume}" <Slider
ToolTip.Tip="{locale:Locale AudioVolumeTooltip}" Width="150"
Minimum="0" Margin="0"
Maximum="1" Padding="0"
TickFrequency="0.05" IsSnapToTickEnabled="True"
IsSnapToTickEnabled="True" LargeChange="0.05"
Padding="0" Maximum="1"
Margin="0" Minimum="0"
SmallChange="0.01" SmallChange="0.01"
LargeChange="0.05" TickFrequency="0.05"
Width="150" /> ToolTip.Tip="{locale:Locale AudioVolumeTooltip}"
Value="{Binding Volume}" />
</Grid> </Grid>
</Flyout> </Flyout>
</ui:ToggleSplitButton.Flyout> </ui:ToggleSplitButton.Flyout>
</ui:ToggleSplitButton> </ui:ToggleSplitButton>
<Border <Border
Width="2" Width="2"
Margin="2,0"
IsVisible="{Binding !ShowLoadProgress}"
BorderThickness="1"
Height="12" Height="12"
BorderBrush="Gray" /> Margin="2,0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock <TextBlock
Margin="5,0,5,0" Margin="5,0,5,0"
IsVisible="{Binding !ShowLoadProgress}"
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Center" VerticalAlignment="Center"
IsVisible="{Binding !ShowLoadProgress}"
Text="{Binding GameStatusText}" Text="{Binding GameStatusText}"
TextAlignment="Left" /> TextAlignment="Left" />
<Border <Border
Width="2" Width="2"
IsVisible="{Binding !ShowLoadProgress}"
Margin="2,0"
BorderThickness="1"
Height="12" Height="12"
BorderBrush="Gray" /> Margin="2,0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock <TextBlock
Margin="5,0,5,0" Margin="5,0,5,0"
IsVisible="{Binding !ShowLoadProgress}"
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Center" VerticalAlignment="Center"
IsVisible="{Binding !ShowLoadProgress}"
Text="{Binding FifoStatusText}" Text="{Binding FifoStatusText}"
TextAlignment="Left" /> TextAlignment="Left" />
<Border <Border
Width="2" Width="2"
Margin="2,0"
IsVisible="{Binding !ShowLoadProgress}"
BorderThickness="1"
Height="12" Height="12"
BorderBrush="Gray" /> Margin="2,0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock <TextBlock
Margin="5,0,5,0" Margin="5,0,5,0"
IsVisible="{Binding !ShowLoadProgress}"
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Center" VerticalAlignment="Center"
IsVisible="{Binding !ShowLoadProgress}"
Text="{Binding GpuStatusText}" Text="{Binding GpuStatusText}"
TextAlignment="Left" /> TextAlignment="Left" />
</StackPanel> </StackPanel>
<StackPanel VerticalAlignment="Center" IsVisible="{Binding ShowFirmwareStatus}" Grid.Column="3" <StackPanel
Orientation="Horizontal" Margin="10, 0"> Grid.Column="3"
Margin="10,0"
VerticalAlignment="Center"
IsVisible="{Binding ShowFirmwareStatus}"
Orientation="Horizontal">
<TextBlock <TextBlock
Name="FirmwareStatus" Name="FirmwareStatus"
Margin="0"
HorizontalAlignment="Right" HorizontalAlignment="Right"
VerticalAlignment="Center" VerticalAlignment="Center"
Margin="0"
Text="{locale:Locale StatusBarSystemVersion}" /> Text="{locale:Locale StatusBarSystemVersion}" />
</StackPanel> </StackPanel>
</Grid> </Grid>

View File

@ -1,18 +1,26 @@
<window:StyleableWindow xmlns="https://github.com/avaloniaui" <window:StyleableWindow
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="Ryujinx.Ava.Ui.Windows.UpdaterWindow"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns="https://github.com/avaloniaui"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="350" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
x:Class="Ryujinx.Ava.Ui.Windows.UpdaterWindow" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows" xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
CanResize="False" Title="Ryujinx Updater"
SizeToContent="Height" Width="500"
Width="500" MinHeight="500" Height="500" Height="500"
WindowStartupLocation="CenterOwner" MinWidth="500"
MinWidth="500" MinHeight="500"
Title="Ryujinx Updater"> d:DesignHeight="350"
<Grid Margin="20" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> d:DesignWidth="400"
CanResize="False"
SizeToContent="Height"
WindowStartupLocation="CenterOwner"
mc:Ignorable="d">
<Grid
Margin="20"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition /> <RowDefinition />
<RowDefinition /> <RowDefinition />
@ -20,17 +28,38 @@
<RowDefinition /> <RowDefinition />
<RowDefinition /> <RowDefinition />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock Grid.Row="1" HorizontalAlignment="Stretch" TextAlignment="Center" Height="20" Name="MainText" /> <TextBlock
<TextBlock Height="20" HorizontalAlignment="Stretch" TextAlignment="Center" Name="SecondaryText" Grid.Row="2" /> Name="MainText"
<ProgressBar IsVisible="False" HorizontalAlignment="Stretch" Name="ProgressBar" Maximum="100" Minimum="0" Grid.Row="1"
Margin="20" Grid.Row="3" /> Height="20"
<StackPanel IsVisible="False" Name="ButtonBox" Orientation="Horizontal" Spacing="20" Grid.Row="4" HorizontalAlignment="Stretch"
HorizontalAlignment="Right"> TextAlignment="Center" />
<Button Command="{Binding YesPressed}" MinWidth="50"> <TextBlock
<TextBlock TextAlignment="Center" Text="{locale:Locale InputDialogYes}" /> Name="SecondaryText"
Grid.Row="2"
Height="20"
HorizontalAlignment="Stretch"
TextAlignment="Center" />
<ProgressBar
Name="ProgressBar"
Grid.Row="3"
Margin="20"
HorizontalAlignment="Stretch"
IsVisible="False"
Maximum="100"
Minimum="0" />
<StackPanel
Name="ButtonBox"
Grid.Row="4"
HorizontalAlignment="Right"
IsVisible="False"
Orientation="Horizontal"
Spacing="20">
<Button MinWidth="50" Command="{Binding YesPressed}">
<TextBlock Text="{locale:Locale InputDialogYes}" TextAlignment="Center" />
</Button> </Button>
<Button Command="{Binding NoPressed}" MinWidth="50"> <Button MinWidth="50" Command="{Binding NoPressed}">
<TextBlock TextAlignment="Center" Text="{locale:Locale InputDialogNo}" /> <TextBlock Text="{locale:Locale InputDialogNo}" TextAlignment="Center" />
</Button> </Button>
</StackPanel> </StackPanel>
</Grid> </Grid>

View File

@ -0,0 +1,12 @@
namespace Ryujinx.Common.Memory
{
public class Box<T> where T : unmanaged
{
public T Data;
public Box()
{
Data = new T();
}
}
}

View File

@ -22,7 +22,17 @@ namespace Ryujinx.Common
public static long AlignUp(long value, int size) public static long AlignUp(long value, int size)
{ {
return (value + (size - 1)) & -(long)size; return AlignUp(value, (long)size);
}
public static ulong AlignUp(ulong value, ulong size)
{
return (ulong)AlignUp((long)value, (long)size);
}
public static long AlignUp(long value, long size)
{
return (value + (size - 1)) & -size;
} }
public static uint AlignDown(uint value, int size) public static uint AlignDown(uint value, int size)
@ -42,7 +52,17 @@ namespace Ryujinx.Common
public static long AlignDown(long value, int size) public static long AlignDown(long value, int size)
{ {
return value & -(long)size; return AlignDown(value, (long)size);
}
public static ulong AlignDown(ulong value, ulong size)
{
return (ulong)AlignDown((long)value, (long)size);
}
public static long AlignDown(long value, long size)
{
return value & -size;
} }
public static int DivRoundUp(int value, int dividend) public static int DivRoundUp(int value, int dividend)

View File

@ -10,9 +10,10 @@ namespace Ryujinx.Graphics.GAL
void ClearBuffer(BufferHandle destination, int offset, int size, uint value); void ClearBuffer(BufferHandle destination, int offset, int size, uint value);
void ClearRenderTargetColor(int index, uint componentMask, ColorF color); void ClearRenderTargetColor(int index, int layer, uint componentMask, ColorF color);
void ClearRenderTargetDepthStencil( void ClearRenderTargetDepthStencil(
int layer,
float depthValue, float depthValue,
bool depthMask, bool depthMask,
int stencilValue, int stencilValue,

View File

@ -4,19 +4,21 @@
{ {
public CommandType CommandType => CommandType.ClearRenderTargetColor; public CommandType CommandType => CommandType.ClearRenderTargetColor;
private int _index; private int _index;
private int _layer;
private uint _componentMask; private uint _componentMask;
private ColorF _color; private ColorF _color;
public void Set(int index, uint componentMask, ColorF color) public void Set(int index, int layer, uint componentMask, ColorF color)
{ {
_index = index; _index = index;
_layer = layer;
_componentMask = componentMask; _componentMask = componentMask;
_color = color; _color = color;
} }
public static void Run(ref ClearRenderTargetColorCommand command, ThreadedRenderer threaded, IRenderer renderer) public static void Run(ref ClearRenderTargetColorCommand command, ThreadedRenderer threaded, IRenderer renderer)
{ {
renderer.Pipeline.ClearRenderTargetColor(command._index, command._componentMask, command._color); renderer.Pipeline.ClearRenderTargetColor(command._index, command._layer, command._componentMask, command._color);
} }
} }
} }

View File

@ -3,13 +3,15 @@
struct ClearRenderTargetDepthStencilCommand : IGALCommand struct ClearRenderTargetDepthStencilCommand : IGALCommand
{ {
public CommandType CommandType => CommandType.ClearRenderTargetDepthStencil; public CommandType CommandType => CommandType.ClearRenderTargetDepthStencil;
private int _layer;
private float _depthValue; private float _depthValue;
private bool _depthMask; private bool _depthMask;
private int _stencilValue; private int _stencilValue;
private int _stencilMask; private int _stencilMask;
public void Set(float depthValue, bool depthMask, int stencilValue, int stencilMask) public void Set(int layer, float depthValue, bool depthMask, int stencilValue, int stencilMask)
{ {
_layer = layer;
_depthValue = depthValue; _depthValue = depthValue;
_depthMask = depthMask; _depthMask = depthMask;
_stencilValue = stencilValue; _stencilValue = stencilValue;
@ -18,7 +20,7 @@
public static void Run(ref ClearRenderTargetDepthStencilCommand command, ThreadedRenderer threaded, IRenderer renderer) public static void Run(ref ClearRenderTargetDepthStencilCommand command, ThreadedRenderer threaded, IRenderer renderer)
{ {
renderer.Pipeline.ClearRenderTargetDepthStencil(command._depthValue, command._depthMask, command._stencilValue, command._stencilMask); renderer.Pipeline.ClearRenderTargetDepthStencil(command._layer, command._depthValue, command._depthMask, command._stencilValue, command._stencilMask);
} }
} }
} }

View File

@ -40,15 +40,15 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand(); _renderer.QueueCommand();
} }
public void ClearRenderTargetColor(int index, uint componentMask, ColorF color) public void ClearRenderTargetColor(int index, int layer, uint componentMask, ColorF color)
{ {
_renderer.New<ClearRenderTargetColorCommand>().Set(index, componentMask, color); _renderer.New<ClearRenderTargetColorCommand>().Set(index, layer, componentMask, color);
_renderer.QueueCommand(); _renderer.QueueCommand();
} }
public void ClearRenderTargetDepthStencil(float depthValue, bool depthMask, int stencilValue, int stencilMask) public void ClearRenderTargetDepthStencil(int layer, float depthValue, bool depthMask, int stencilValue, int stencilMask)
{ {
_renderer.New<ClearRenderTargetDepthStencilCommand>().Set(depthValue, depthMask, stencilValue, stencilMask); _renderer.New<ClearRenderTargetDepthStencilCommand>().Set(layer, depthValue, depthMask, stencilValue, stencilMask);
_renderer.QueueCommand(); _renderer.QueueCommand();
} }

View File

@ -188,6 +188,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
_channel.BufferManager.SetComputeStorageBufferBindings(info.SBuffers); _channel.BufferManager.SetComputeStorageBufferBindings(info.SBuffers);
_channel.BufferManager.SetComputeUniformBufferBindings(info.CBuffers); _channel.BufferManager.SetComputeUniformBufferBindings(info.CBuffers);
int maxTextureBinding = -1;
int maxImageBinding = -1;
TextureBindingInfo[] textureBindings = _channel.TextureManager.RentComputeTextureBindings(info.Textures.Count); TextureBindingInfo[] textureBindings = _channel.TextureManager.RentComputeTextureBindings(info.Textures.Count);
for (int index = 0; index < info.Textures.Count; index++) for (int index = 0; index < info.Textures.Count; index++)
@ -202,6 +205,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
descriptor.CbufSlot, descriptor.CbufSlot,
descriptor.HandleIndex, descriptor.HandleIndex,
descriptor.Flags); descriptor.Flags);
if (descriptor.Binding > maxTextureBinding)
{
maxTextureBinding = descriptor.Binding;
}
} }
TextureBindingInfo[] imageBindings = _channel.TextureManager.RentComputeImageBindings(info.Images.Count); TextureBindingInfo[] imageBindings = _channel.TextureManager.RentComputeImageBindings(info.Images.Count);
@ -220,9 +228,18 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
descriptor.CbufSlot, descriptor.CbufSlot,
descriptor.HandleIndex, descriptor.HandleIndex,
descriptor.Flags); descriptor.Flags);
if (descriptor.Binding > maxImageBinding)
{
maxImageBinding = descriptor.Binding;
}
} }
_channel.TextureManager.CommitComputeBindings(); _channel.TextureManager.SetComputeMaxBindings(maxTextureBinding, maxImageBinding);
// Should never return false for mismatching spec state, since the shader was fetched above.
_channel.TextureManager.CommitComputeBindings(cs.SpecializationState);
_channel.BufferManager.CommitComputeBindings(); _channel.BufferManager.CommitComputeBindings();
_context.Renderer.Pipeline.DispatchCompute(qmd.CtaRasterWidth, qmd.CtaRasterHeight, qmd.CtaRasterDepth); _context.Renderer.Pipeline.DispatchCompute(qmd.CtaRasterWidth, qmd.CtaRasterHeight, qmd.CtaRasterDepth);

View File

@ -18,6 +18,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
private bool _instancedDrawPending; private bool _instancedDrawPending;
private bool _instancedIndexed; private bool _instancedIndexed;
private bool _instancedIndexedInline;
private int _instancedFirstIndex; private int _instancedFirstIndex;
private int _instancedFirstVertex; private int _instancedFirstVertex;
@ -134,13 +135,16 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
{ {
_instancedDrawPending = true; _instancedDrawPending = true;
int ibCount = _drawState.IbStreamer.InlineIndexCount;
_instancedIndexed = _drawState.DrawIndexed; _instancedIndexed = _drawState.DrawIndexed;
_instancedIndexedInline = ibCount != 0;
_instancedFirstIndex = firstIndex; _instancedFirstIndex = firstIndex;
_instancedFirstVertex = (int)_state.State.FirstVertex; _instancedFirstVertex = (int)_state.State.FirstVertex;
_instancedFirstInstance = (int)_state.State.FirstInstance; _instancedFirstInstance = (int)_state.State.FirstInstance;
_instancedIndexCount = indexCount; _instancedIndexCount = ibCount != 0 ? ibCount : indexCount;
var drawState = _state.State.VertexBufferDrawState; var drawState = _state.State.VertexBufferDrawState;
@ -451,8 +455,18 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
{ {
_instancedDrawPending = false; _instancedDrawPending = false;
if (_instancedIndexed) bool indexedInline = _instancedIndexedInline;
if (_instancedIndexed || indexedInline)
{ {
if (indexedInline)
{
int inlineIndexCount = _drawState.IbStreamer.GetAndResetInlineIndexCount();
BufferRange br = new BufferRange(_drawState.IbStreamer.GetInlineIndexBuffer(), 0, inlineIndexCount * 4);
_channel.BufferManager.SetIndexBuffer(br, IndexType.UInt);
}
_context.Renderer.Pipeline.DrawIndexed( _context.Renderer.Pipeline.DrawIndexed(
_instancedIndexCount, _instancedIndexCount,
_instanceIndex + 1, _instanceIndex + 1,
@ -491,8 +505,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
} }
int index = (argument >> 6) & 0xf; int index = (argument >> 6) & 0xf;
int layer = (argument >> 10) & 0x3ff;
engine.UpdateRenderTargetState(useControl: false, singleUse: index); engine.UpdateRenderTargetState(useControl: false, layered: layer != 0, singleUse: index);
// If there is a mismatch on the host clip region and the one explicitly defined by the guest // If there is a mismatch on the host clip region and the one explicitly defined by the guest
// on the screen scissor state, then we need to force only one texture to be bound to avoid // on the screen scissor state, then we need to force only one texture to be bound to avoid
@ -567,7 +582,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
ColorF color = new ColorF(clearColor.Red, clearColor.Green, clearColor.Blue, clearColor.Alpha); ColorF color = new ColorF(clearColor.Red, clearColor.Green, clearColor.Blue, clearColor.Alpha);
_context.Renderer.Pipeline.ClearRenderTargetColor(index, componentMask, color); _context.Renderer.Pipeline.ClearRenderTargetColor(index, layer, componentMask, color);
} }
if (clearDepth || clearStencil) if (clearDepth || clearStencil)
@ -588,6 +603,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
} }
_context.Renderer.Pipeline.ClearRenderTargetDepthStencil( _context.Renderer.Pipeline.ClearRenderTargetDepthStencil(
layer,
depthValue, depthValue,
clearDepth, clearDepth,
stencilValue, stencilValue,

View File

@ -20,6 +20,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// </summary> /// </summary>
public bool HasInlineIndexData => _inlineIndexCount != 0; public bool HasInlineIndexData => _inlineIndexCount != 0;
/// <summary>
/// Total numbers of indices that have been pushed.
/// </summary>
public int InlineIndexCount => _inlineIndexCount;
/// <summary> /// <summary>
/// Gets the handle for the host buffer currently holding the inline index buffer data. /// Gets the handle for the host buffer currently holding the inline index buffer data.
/// </summary> /// </summary>

View File

@ -201,7 +201,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
// of the shader for the new state. // of the shader for the new state.
if (_shaderSpecState != null) if (_shaderSpecState != null)
{ {
if (!_shaderSpecState.MatchesGraphics(_channel, GetPoolState(), GetGraphicsState())) if (!_shaderSpecState.MatchesGraphics(_channel, GetPoolState(), GetGraphicsState(), false))
{ {
ForceShaderUpdate(); ForceShaderUpdate();
} }
@ -275,7 +275,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
{ {
UpdateStorageBuffers(); UpdateStorageBuffers();
_channel.TextureManager.CommitGraphicsBindings(); if (!_channel.TextureManager.CommitGraphicsBindings(_shaderSpecState))
{
// Shader must be reloaded.
UpdateShaderState();
}
_channel.BufferManager.CommitGraphicsBindings(); _channel.BufferManager.CommitGraphicsBindings();
} }
@ -362,8 +367,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// Updates render targets (color and depth-stencil buffers) based on current render target state. /// Updates render targets (color and depth-stencil buffers) based on current render target state.
/// </summary> /// </summary>
/// <param name="useControl">Use draw buffers information from render target control register</param> /// <param name="useControl">Use draw buffers information from render target control register</param>
/// <param name="layered">Indicates if the texture is layered</param>
/// <param name="singleUse">If this is not -1, it indicates that only the given indexed target will be used.</param> /// <param name="singleUse">If this is not -1, it indicates that only the given indexed target will be used.</param>
public void UpdateRenderTargetState(bool useControl, int singleUse = -1) public void UpdateRenderTargetState(bool useControl, bool layered = false, int singleUse = -1)
{ {
var memoryManager = _channel.MemoryManager; var memoryManager = _channel.MemoryManager;
var rtControl = _state.State.RtControl; var rtControl = _state.State.RtControl;
@ -399,7 +405,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
Image.Texture color = memoryManager.Physical.TextureCache.FindOrCreateTexture( Image.Texture color = memoryManager.Physical.TextureCache.FindOrCreateTexture(
memoryManager, memoryManager,
colorState, colorState,
_vtgWritesRtLayer, _vtgWritesRtLayer || layered,
samplesInX, samplesInX,
samplesInY, samplesInY,
sizeHint); sizeHint);
@ -433,6 +439,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
memoryManager, memoryManager,
dsState, dsState,
dsSize, dsSize,
_vtgWritesRtLayer || layered,
samplesInX, samplesInX,
samplesInY, samplesInY,
sizeHint); sizeHint);
@ -1148,6 +1155,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
return; return;
} }
int maxTextureBinding = -1;
int maxImageBinding = -1;
Span<TextureBindingInfo> textureBindings = _channel.TextureManager.RentGraphicsTextureBindings(stage, info.Textures.Count); Span<TextureBindingInfo> textureBindings = _channel.TextureManager.RentGraphicsTextureBindings(stage, info.Textures.Count);
if (info.UsesRtLayer) if (info.UsesRtLayer)
@ -1167,6 +1177,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
descriptor.CbufSlot, descriptor.CbufSlot,
descriptor.HandleIndex, descriptor.HandleIndex,
descriptor.Flags); descriptor.Flags);
if (descriptor.Binding > maxTextureBinding)
{
maxTextureBinding = descriptor.Binding;
}
} }
TextureBindingInfo[] imageBindings = _channel.TextureManager.RentGraphicsImageBindings(stage, info.Images.Count); TextureBindingInfo[] imageBindings = _channel.TextureManager.RentGraphicsImageBindings(stage, info.Images.Count);
@ -1185,8 +1200,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
descriptor.CbufSlot, descriptor.CbufSlot,
descriptor.HandleIndex, descriptor.HandleIndex,
descriptor.Flags); descriptor.Flags);
if (descriptor.Binding > maxImageBinding)
{
maxImageBinding = descriptor.Binding;
}
} }
_channel.TextureManager.SetGraphicsMaxBindings(maxTextureBinding, maxImageBinding);
_channel.BufferManager.SetGraphicsStorageBufferBindings(stage, info.SBuffers); _channel.BufferManager.SetGraphicsStorageBufferBindings(stage, info.SBuffers);
_channel.BufferManager.SetGraphicsUniformBufferBindings(stage, info.CBuffers); _channel.BufferManager.SetGraphicsUniformBufferBindings(stage, info.CBuffers);
} }

View File

@ -131,10 +131,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// Updates render targets (color and depth-stencil buffers) based on current render target state. /// Updates render targets (color and depth-stencil buffers) based on current render target state.
/// </summary> /// </summary>
/// <param name="useControl">Use draw buffers information from render target control register</param> /// <param name="useControl">Use draw buffers information from render target control register</param>
/// <param name="layered">Indicates if the texture is layered</param>
/// <param name="singleUse">If this is not -1, it indicates that only the given indexed target will be used.</param> /// <param name="singleUse">If this is not -1, it indicates that only the given indexed target will be used.</param>
public void UpdateRenderTargetState(bool useControl, int singleUse = -1) public void UpdateRenderTargetState(bool useControl, bool layered = false, int singleUse = -1)
{ {
_stateUpdater.UpdateRenderTargetState(useControl, singleUse); _stateUpdater.UpdateRenderTargetState(useControl, layered, singleUse);
} }
/// <summary> /// <summary>

View File

@ -1,6 +1,7 @@
using Ryujinx.Cpu.Tracking; using Ryujinx.Cpu.Tracking;
using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.Graphics.Gpu.Memory;
using System; using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Gpu.Image namespace Ryujinx.Graphics.Gpu.Image
{ {
@ -16,6 +17,7 @@ namespace Ryujinx.Graphics.Gpu.Image
protected GpuContext Context; protected GpuContext Context;
protected PhysicalMemory PhysicalMemory; protected PhysicalMemory PhysicalMemory;
protected int SequenceNumber; protected int SequenceNumber;
protected int ModifiedSequenceNumber;
protected T1[] Items; protected T1[] Items;
protected T2[] DescriptorCache; protected T2[] DescriptorCache;
@ -41,6 +43,9 @@ namespace Ryujinx.Graphics.Gpu.Image
private readonly CpuMultiRegionHandle _memoryTracking; private readonly CpuMultiRegionHandle _memoryTracking;
private readonly Action<ulong, ulong> _modifiedDelegate; private readonly Action<ulong, ulong> _modifiedDelegate;
private int _modifiedSequenceOffset;
private bool _modified;
/// <summary> /// <summary>
/// Creates a new instance of the GPU resource pool. /// Creates a new instance of the GPU resource pool.
/// </summary> /// </summary>
@ -79,6 +84,16 @@ namespace Ryujinx.Graphics.Gpu.Image
return PhysicalMemory.Read<T2>(Address + (ulong)id * DescriptorSize); return PhysicalMemory.Read<T2>(Address + (ulong)id * DescriptorSize);
} }
/// <summary>
/// Gets a reference to the descriptor for a given ID.
/// </summary>
/// <param name="id">ID of the descriptor. This is effectively a zero-based index</param>
/// <returns>A reference to the descriptor</returns>
public ref readonly T2 GetDescriptorRef(int id)
{
return ref MemoryMarshal.Cast<byte, T2>(PhysicalMemory.GetSpan(Address + (ulong)id * DescriptorSize, DescriptorSize))[0];
}
/// <summary> /// <summary>
/// Gets the GPU resource with the given ID. /// Gets the GPU resource with the given ID.
/// </summary> /// </summary>
@ -86,6 +101,16 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <returns>The GPU resource with the given ID</returns> /// <returns>The GPU resource with the given ID</returns>
public abstract T1 Get(int id); public abstract T1 Get(int id);
/// <summary>
/// Checks if a given ID is valid and inside the range of the pool.
/// </summary>
/// <param name="id">ID of the descriptor. This is effectively a zero-based index</param>
/// <returns>True if the specified ID is valid, false otherwise</returns>
public bool IsValidId(int id)
{
return (uint)id <= MaximumId;
}
/// <summary> /// <summary>
/// Synchronizes host memory with guest memory. /// Synchronizes host memory with guest memory.
/// This causes invalidation of pool entries, /// This causes invalidation of pool entries,
@ -93,7 +118,13 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
public void SynchronizeMemory() public void SynchronizeMemory()
{ {
_modified = false;
_memoryTracking.QueryModified(_modifiedDelegate); _memoryTracking.QueryModified(_modifiedDelegate);
if (_modified)
{
UpdateModifiedSequence();
}
} }
/// <summary> /// <summary>
@ -103,6 +134,8 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="mSize">Size of the modified region</param> /// <param name="mSize">Size of the modified region</param>
private void RegionModified(ulong mAddress, ulong mSize) private void RegionModified(ulong mAddress, ulong mSize)
{ {
_modified = true;
if (mAddress < Address) if (mAddress < Address)
{ {
mAddress = Address; mAddress = Address;
@ -118,6 +151,15 @@ namespace Ryujinx.Graphics.Gpu.Image
InvalidateRangeImpl(mAddress, mSize); InvalidateRangeImpl(mAddress, mSize);
} }
/// <summary>
/// Updates the modified sequence number using the current sequence number and offset,
/// indicating that it has been modified.
/// </summary>
protected void UpdateModifiedSequence()
{
ModifiedSequenceNumber = SequenceNumber + _modifiedSequenceOffset;
}
/// <summary> /// <summary>
/// An action to be performed when a precise memory access occurs to this resource. /// An action to be performed when a precise memory access occurs to this resource.
/// Makes sure that the dirty flags are checked. /// Makes sure that the dirty flags are checked.
@ -129,6 +171,16 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
if (write && Context.SequenceNumber == SequenceNumber) if (write && Context.SequenceNumber == SequenceNumber)
{ {
if (ModifiedSequenceNumber == SequenceNumber + _modifiedSequenceOffset)
{
// The modified sequence number is offset when PreciseActions occur so that
// users checking it will see an increment and know the pool has changed since
// their last look, even though the main SequenceNumber has not been changed.
_modifiedSequenceOffset++;
}
// Force the pool to be checked again the next time it is used.
SequenceNumber--; SequenceNumber--;
} }

View File

@ -8,6 +8,11 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
class Sampler : IDisposable class Sampler : IDisposable
{ {
/// <summary>
/// True if the sampler is disposed, false otherwise.
/// </summary>
public bool IsDisposed { get; private set; }
/// <summary> /// <summary>
/// Host sampler object. /// Host sampler object.
/// </summary> /// </summary>
@ -101,6 +106,8 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
public void Dispose() public void Dispose()
{ {
IsDisposed = true;
_hostSampler.Dispose(); _hostSampler.Dispose();
_anisoSampler?.Dispose(); _anisoSampler?.Dispose();
} }

View File

@ -48,6 +48,8 @@ namespace Ryujinx.Graphics.Gpu.Image
Items[i] = null; Items[i] = null;
} }
} }
UpdateModifiedSequence();
} }
SequenceNumber = Context.SequenceNumber; SequenceNumber = Context.SequenceNumber;
@ -71,6 +73,39 @@ namespace Ryujinx.Graphics.Gpu.Image
return sampler; return sampler;
} }
/// <summary>
/// Checks if the pool was modified, and returns the last sequence number where a modification was detected.
/// </summary>
/// <returns>A number that increments each time a modification is detected</returns>
public int CheckModified()
{
if (SequenceNumber != Context.SequenceNumber)
{
SequenceNumber = Context.SequenceNumber;
if (_forcedAnisotropy != GraphicsConfig.MaxAnisotropy)
{
_forcedAnisotropy = GraphicsConfig.MaxAnisotropy;
for (int i = 0; i < Items.Length; i++)
{
if (Items[i] != null)
{
Items[i].Dispose();
Items[i] = null;
}
}
UpdateModifiedSequence();
}
SynchronizeMemory();
}
return ModifiedSequenceNumber;
}
/// <summary> /// <summary>
/// Implementation of the sampler pool range invalidation. /// Implementation of the sampler pool range invalidation.
/// </summary> /// </summary>

View File

@ -100,6 +100,11 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
public bool AlwaysFlushOnOverlap { get; private set; } public bool AlwaysFlushOnOverlap { get; private set; }
/// <summary>
/// Increments when the host texture is swapped, or when the texture is removed from all pools.
/// </summary>
public int InvalidatedSequence { get; private set; }
private int _depth; private int _depth;
private int _layers; private int _layers;
public int FirstLayer { get; private set; } public int FirstLayer { get; private set; }
@ -1407,6 +1412,7 @@ namespace Ryujinx.Graphics.Gpu.Image
DisposeTextures(); DisposeTextures();
HostTexture = hostTexture; HostTexture = hostTexture;
InvalidatedSequence++;
} }
/// <summary> /// <summary>
@ -1535,6 +1541,8 @@ namespace Ryujinx.Graphics.Gpu.Image
_poolOwners.Clear(); _poolOwners.Clear();
} }
InvalidatedSequence++;
} }
/// <summary> /// <summary>

View File

@ -1,8 +1,12 @@
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.Types; using Ryujinx.Graphics.Gpu.Engine.Types;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Gpu.Shader;
using Ryujinx.Graphics.Shader; using Ryujinx.Graphics.Shader;
using System; using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Gpu.Image namespace Ryujinx.Graphics.Gpu.Image
{ {
@ -31,21 +35,30 @@ namespace Ryujinx.Graphics.Gpu.Image
private readonly TextureBindingInfo[][] _textureBindings; private readonly TextureBindingInfo[][] _textureBindings;
private readonly TextureBindingInfo[][] _imageBindings; private readonly TextureBindingInfo[][] _imageBindings;
private struct TextureStatePerStage private struct TextureState
{ {
public ITexture Texture; public ITexture Texture;
public ISampler Sampler; public ISampler Sampler;
public int TextureHandle;
public int SamplerHandle;
public int InvalidatedSequence;
public Texture CachedTexture;
public Sampler CachedSampler;
public int ScaleIndex;
public TextureUsageFlags UsageFlags;
} }
private readonly TextureStatePerStage[][] _textureState; private TextureState[] _textureState;
private readonly TextureStatePerStage[][] _imageState; private TextureState[] _imageState;
private int[] _textureBindingsCount; private int[] _textureBindingsCount;
private int[] _imageBindingsCount; private int[] _imageBindingsCount;
private int _textureBufferIndex; private int _texturePoolSequence;
private int _samplerPoolSequence;
private bool _rebind; private int _textureBufferIndex;
private readonly float[] _scales; private readonly float[] _scales;
private bool _scaleChanged; private bool _scaleChanged;
@ -72,8 +85,8 @@ namespace Ryujinx.Graphics.Gpu.Image
_textureBindings = new TextureBindingInfo[stages][]; _textureBindings = new TextureBindingInfo[stages][];
_imageBindings = new TextureBindingInfo[stages][]; _imageBindings = new TextureBindingInfo[stages][];
_textureState = new TextureStatePerStage[stages][]; _textureState = new TextureState[InitialTextureStateSize];
_imageState = new TextureStatePerStage[stages][]; _imageState = new TextureState[InitialImageStateSize];
_textureBindingsCount = new int[stages]; _textureBindingsCount = new int[stages];
_imageBindingsCount = new int[stages]; _imageBindingsCount = new int[stages];
@ -82,9 +95,6 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
_textureBindings[stage] = new TextureBindingInfo[InitialTextureStateSize]; _textureBindings[stage] = new TextureBindingInfo[InitialTextureStateSize];
_imageBindings[stage] = new TextureBindingInfo[InitialImageStateSize]; _imageBindings[stage] = new TextureBindingInfo[InitialImageStateSize];
_textureState[stage] = new TextureStatePerStage[InitialTextureStateSize];
_imageState[stage] = new TextureStatePerStage[InitialImageStateSize];
} }
} }
@ -99,15 +109,6 @@ namespace Ryujinx.Graphics.Gpu.Image
if (count > _textureBindings[stage].Length) if (count > _textureBindings[stage].Length)
{ {
Array.Resize(ref _textureBindings[stage], count); Array.Resize(ref _textureBindings[stage], count);
Array.Resize(ref _textureState[stage], count);
}
int toClear = Math.Max(_textureBindingsCount[stage], count);
TextureStatePerStage[] state = _textureState[stage];
for (int i = 0; i < toClear; i++)
{
state[i] = new TextureStatePerStage();
} }
_textureBindingsCount[stage] = count; _textureBindingsCount[stage] = count;
@ -126,15 +127,6 @@ namespace Ryujinx.Graphics.Gpu.Image
if (count > _imageBindings[stage].Length) if (count > _imageBindings[stage].Length)
{ {
Array.Resize(ref _imageBindings[stage], count); Array.Resize(ref _imageBindings[stage], count);
Array.Resize(ref _imageState[stage], count);
}
int toClear = Math.Max(_imageBindingsCount[stage], count);
TextureStatePerStage[] state = _imageState[stage];
for (int i = 0; i < toClear; i++)
{
state[i] = new TextureStatePerStage();
} }
_imageBindingsCount[stage] = count; _imageBindingsCount[stage] = count;
@ -142,6 +134,24 @@ namespace Ryujinx.Graphics.Gpu.Image
return _imageBindings[stage]; return _imageBindings[stage];
} }
/// <summary>
/// Sets the max binding indexes for textures and images.
/// </summary>
/// <param name="maxTextureBinding">The maximum texture binding</param>
/// <param name="maxImageBinding">The maximum image binding</param>
public void SetMaxBindings(int maxTextureBinding, int maxImageBinding)
{
if (maxTextureBinding >= _textureState.Length)
{
Array.Resize(ref _textureState, maxTextureBinding + 1);
}
if (maxImageBinding >= _imageState.Length)
{
Array.Resize(ref _imageState, maxImageBinding + 1);
}
}
/// <summary> /// <summary>
/// Sets the textures constant buffer index. /// Sets the textures constant buffer index.
/// The constant buffer specified holds the texture handles. /// The constant buffer specified holds the texture handles.
@ -222,18 +232,18 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Updates the texture scale for a given texture or image. /// Updates the texture scale for a given texture or image.
/// </summary> /// </summary>
/// <param name="texture">Start GPU virtual address of the pool</param> /// <param name="texture">Start GPU virtual address of the pool</param>
/// <param name="binding">The related texture binding</param> /// <param name="usageFlags">The related texture usage flags</param>
/// <param name="index">The texture/image binding index</param> /// <param name="index">The texture/image binding index</param>
/// <param name="stage">The active shader stage</param> /// <param name="stage">The active shader stage</param>
/// <returns>True if the given texture has become blacklisted, indicating that its host texture may have changed.</returns> /// <returns>True if the given texture has become blacklisted, indicating that its host texture may have changed.</returns>
private bool UpdateScale(Texture texture, TextureBindingInfo binding, int index, ShaderStage stage) private bool UpdateScale(Texture texture, TextureUsageFlags usageFlags, int index, ShaderStage stage)
{ {
float result = 1f; float result = 1f;
bool changed = false; bool changed = false;
if ((binding.Flags & TextureUsageFlags.NeedsScaleValue) != 0 && texture != null) if ((usageFlags & TextureUsageFlags.NeedsScaleValue) != 0 && texture != null)
{ {
if ((binding.Flags & TextureUsageFlags.ResScaleUnsupported) != 0) if ((usageFlags & TextureUsageFlags.ResScaleUnsupported) != 0)
{ {
changed = texture.ScaleMode != TextureScaleMode.Blacklisted; changed = texture.ScaleMode != TextureScaleMode.Blacklisted;
texture.BlacklistScale(); texture.BlacklistScale();
@ -323,7 +333,9 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Ensures that the bindings are visible to the host GPU. /// Ensures that the bindings are visible to the host GPU.
/// Note: this actually performs the binding using the host graphics API. /// Note: this actually performs the binding using the host graphics API.
/// </summary> /// </summary>
public void CommitBindings() /// <param name="specState">Specialization state for the bound shader</param>
/// <returns>True if all bound textures match the current shader specialiation state, false otherwise</returns>
public bool CommitBindings(ShaderSpecializationState specState)
{ {
ulong texturePoolAddress = _texturePoolAddress; ulong texturePoolAddress = _texturePoolAddress;
@ -331,10 +343,38 @@ namespace Ryujinx.Graphics.Gpu.Image
? _texturePoolCache.FindOrCreate(_channel, texturePoolAddress, _texturePoolMaximumId) ? _texturePoolCache.FindOrCreate(_channel, texturePoolAddress, _texturePoolMaximumId)
: null; : null;
// Check if the texture pool has been modified since bindings were last committed.
// If it wasn't, then it's possible to avoid looking up textures again when the handle remains the same.
bool poolModified = false;
if (texturePool != null)
{
int texturePoolSequence = texturePool.CheckModified();
if (_texturePoolSequence != texturePoolSequence)
{
poolModified = true;
_texturePoolSequence = texturePoolSequence;
}
}
if (_samplerPool != null)
{
int samplerPoolSequence = _samplerPool.CheckModified();
if (_samplerPoolSequence != samplerPoolSequence)
{
poolModified = true;
_samplerPoolSequence = samplerPoolSequence;
}
}
bool specStateMatches = true;
if (_isCompute) if (_isCompute)
{ {
CommitTextureBindings(texturePool, ShaderStage.Compute, 0); specStateMatches &= CommitTextureBindings(texturePool, ShaderStage.Compute, 0, poolModified, specState);
CommitImageBindings (texturePool, ShaderStage.Compute, 0); specStateMatches &= CommitImageBindings(texturePool, ShaderStage.Compute, 0, poolModified, specState);
} }
else else
{ {
@ -342,14 +382,57 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
int stageIndex = (int)stage - 1; int stageIndex = (int)stage - 1;
CommitTextureBindings(texturePool, stage, stageIndex); specStateMatches &= CommitTextureBindings(texturePool, stage, stageIndex, poolModified, specState);
CommitImageBindings (texturePool, stage, stageIndex); specStateMatches &= CommitImageBindings(texturePool, stage, stageIndex, poolModified, specState);
} }
} }
CommitRenderScale(); CommitRenderScale();
_rebind = false; return specStateMatches;
}
/// <summary>
/// Fetch the constant buffers used for a texture to cache.
/// </summary>
/// <param name="stageIndex">Stage index of the constant buffer</param>
/// <param name="cachedTextureBufferIndex">The currently cached texture buffer index</param>
/// <param name="cachedSamplerBufferIndex">The currently cached sampler buffer index</param>
/// <param name="cachedTextureBuffer">The currently cached texture buffer data</param>
/// <param name="cachedSamplerBuffer">The currently cached sampler buffer data</param>
/// <param name="textureBufferIndex">The new texture buffer index</param>
/// <param name="samplerBufferIndex">The new sampler buffer index</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void UpdateCachedBuffer(
int stageIndex,
ref int cachedTextureBufferIndex,
ref int cachedSamplerBufferIndex,
ref ReadOnlySpan<int> cachedTextureBuffer,
ref ReadOnlySpan<int> cachedSamplerBuffer,
int textureBufferIndex,
int samplerBufferIndex)
{
if (textureBufferIndex != cachedTextureBufferIndex)
{
ref BufferBounds bounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, textureBufferIndex);
cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(bounds.Address, (int)bounds.Size));
cachedTextureBufferIndex = textureBufferIndex;
if (samplerBufferIndex == textureBufferIndex)
{
cachedSamplerBuffer = cachedTextureBuffer;
cachedSamplerBufferIndex = samplerBufferIndex;
}
}
if (samplerBufferIndex != cachedSamplerBufferIndex)
{
ref BufferBounds bounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, samplerBufferIndex);
cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(bounds.Address, (int)bounds.Size));
cachedSamplerBufferIndex = samplerBufferIndex;
}
} }
/// <summary> /// <summary>
@ -358,13 +441,16 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
/// <param name="pool">The current texture pool</param> /// <param name="pool">The current texture pool</param>
/// <param name="stage">The shader stage using the textures to be bound</param> /// <param name="stage">The shader stage using the textures to be bound</param>
/// <param name="stageIndex">The stage number of the specified shader stage</param> /// <param name="stageIndex">The stage number of the specified shader stage</param
private void CommitTextureBindings(TexturePool pool, ShaderStage stage, int stageIndex) /// <param name="poolModified">True if either the texture or sampler pool was modified, false otherwise</param>
/// <param name="specState">Specialization state for the bound shader</param>
/// <returns>True if all bound textures match the current shader specialiation state, false otherwise</returns>
private bool CommitTextureBindings(TexturePool pool, ShaderStage stage, int stageIndex, bool poolModified, ShaderSpecializationState specState)
{ {
int textureCount = _textureBindingsCount[stageIndex]; int textureCount = _textureBindingsCount[stageIndex];
if (textureCount == 0) if (textureCount == 0)
{ {
return; return true;
} }
var samplerPool = _samplerPool; var samplerPool = _samplerPool;
@ -372,17 +458,27 @@ namespace Ryujinx.Graphics.Gpu.Image
if (pool == null) if (pool == null)
{ {
Logger.Error?.Print(LogClass.Gpu, $"Shader stage \"{stage}\" uses textures, but texture pool was not set."); Logger.Error?.Print(LogClass.Gpu, $"Shader stage \"{stage}\" uses textures, but texture pool was not set.");
return; return true;
} }
bool specStateMatches = true;
int cachedTextureBufferIndex = -1;
int cachedSamplerBufferIndex = -1;
ReadOnlySpan<int> cachedTextureBuffer = Span<int>.Empty;
ReadOnlySpan<int> cachedSamplerBuffer = Span<int>.Empty;
for (int index = 0; index < textureCount; index++) for (int index = 0; index < textureCount; index++)
{ {
TextureBindingInfo bindingInfo = _textureBindings[stageIndex][index]; TextureBindingInfo bindingInfo = _textureBindings[stageIndex][index];
TextureUsageFlags usageFlags = bindingInfo.Flags;
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex); (int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex);
int packedId = ReadPackedId(stageIndex, bindingInfo.Handle, textureBufferIndex, samplerBufferIndex); UpdateCachedBuffer(stageIndex, ref cachedTextureBufferIndex, ref cachedSamplerBufferIndex, ref cachedTextureBuffer, ref cachedSamplerBuffer, textureBufferIndex, samplerBufferIndex);
int textureId = UnpackTextureId(packedId);
int packedId = TextureHandle.ReadPackedId(bindingInfo.Handle, cachedTextureBuffer, cachedSamplerBuffer);
int textureId = TextureHandle.UnpackTextureId(packedId);
int samplerId; int samplerId;
if (_samplerIndex == SamplerIndex.ViaHeaderIndex) if (_samplerIndex == SamplerIndex.ViaHeaderIndex)
@ -391,10 +487,42 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
else else
{ {
samplerId = UnpackSamplerId(packedId); samplerId = TextureHandle.UnpackSamplerId(packedId);
} }
Texture texture = pool.Get(textureId); ref TextureState state = ref _textureState[bindingInfo.Binding];
if (!poolModified &&
state.TextureHandle == textureId &&
state.SamplerHandle == samplerId &&
state.CachedTexture != null &&
state.CachedTexture.InvalidatedSequence == state.InvalidatedSequence &&
state.CachedSampler?.IsDisposed != true)
{
// The texture is already bound.
state.CachedTexture.SynchronizeMemory();
if ((state.ScaleIndex != index || state.UsageFlags != usageFlags) &&
UpdateScale(state.CachedTexture, usageFlags, index, stage))
{
ITexture hostTextureRebind = state.CachedTexture.GetTargetTexture(bindingInfo.Target);
state.Texture = hostTextureRebind;
state.ScaleIndex = index;
state.UsageFlags = usageFlags;
_context.Renderer.Pipeline.SetTexture(bindingInfo.Binding, hostTextureRebind);
}
continue;
}
state.TextureHandle = textureId;
state.SamplerHandle = samplerId;
ref readonly TextureDescriptor descriptor = ref pool.GetForBinding(textureId, out Texture texture);
specStateMatches &= specState.MatchesTexture(stage, index, descriptor);
ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target); ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
@ -407,30 +535,38 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
else else
{ {
if (_textureState[stageIndex][index].Texture != hostTexture || _rebind) if (state.Texture != hostTexture)
{ {
if (UpdateScale(texture, bindingInfo, index, stage)) if (UpdateScale(texture, usageFlags, index, stage))
{ {
hostTexture = texture?.GetTargetTexture(bindingInfo.Target); hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
} }
_textureState[stageIndex][index].Texture = hostTexture; state.Texture = hostTexture;
state.ScaleIndex = index;
state.UsageFlags = usageFlags;
_context.Renderer.Pipeline.SetTexture(bindingInfo.Binding, hostTexture); _context.Renderer.Pipeline.SetTexture(bindingInfo.Binding, hostTexture);
} }
Sampler sampler = samplerPool?.Get(samplerId); Sampler sampler = samplerPool?.Get(samplerId);
state.CachedSampler = sampler;
ISampler hostSampler = sampler?.GetHostSampler(texture); ISampler hostSampler = sampler?.GetHostSampler(texture);
if (_textureState[stageIndex][index].Sampler != hostSampler || _rebind) if (state.Sampler != hostSampler)
{ {
_textureState[stageIndex][index].Sampler = hostSampler; state.Sampler = hostSampler;
_context.Renderer.Pipeline.SetSampler(bindingInfo.Binding, hostSampler); _context.Renderer.Pipeline.SetSampler(bindingInfo.Binding, hostSampler);
} }
state.CachedTexture = texture;
state.InvalidatedSequence = texture?.InvalidatedSequence ?? 0;
} }
} }
return specStateMatches;
} }
/// <summary> /// <summary>
@ -440,38 +576,90 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="pool">The current texture pool</param> /// <param name="pool">The current texture pool</param>
/// <param name="stage">The shader stage using the textures to be bound</param> /// <param name="stage">The shader stage using the textures to be bound</param>
/// <param name="stageIndex">The stage number of the specified shader stage</param> /// <param name="stageIndex">The stage number of the specified shader stage</param>
private void CommitImageBindings(TexturePool pool, ShaderStage stage, int stageIndex) /// <param name="poolModified">True if either the texture or sampler pool was modified, false otherwise</param>
/// <param name="specState">Specialization state for the bound shader</param>
/// <returns>True if all bound images match the current shader specialiation state, false otherwise</returns>
private bool CommitImageBindings(TexturePool pool, ShaderStage stage, int stageIndex, bool poolModified, ShaderSpecializationState specState)
{ {
int imageCount = _imageBindingsCount[stageIndex]; int imageCount = _imageBindingsCount[stageIndex];
if (imageCount == 0) if (imageCount == 0)
{ {
return; return true;
} }
if (pool == null) if (pool == null)
{ {
Logger.Error?.Print(LogClass.Gpu, $"Shader stage \"{stage}\" uses images, but texture pool was not set."); Logger.Error?.Print(LogClass.Gpu, $"Shader stage \"{stage}\" uses images, but texture pool was not set.");
return; return true;
} }
// Scales for images appear after the texture ones. // Scales for images appear after the texture ones.
int baseScaleIndex = _textureBindingsCount[stageIndex]; int baseScaleIndex = _textureBindingsCount[stageIndex];
int cachedTextureBufferIndex = -1;
int cachedSamplerBufferIndex = -1;
ReadOnlySpan<int> cachedTextureBuffer = Span<int>.Empty;
ReadOnlySpan<int> cachedSamplerBuffer = Span<int>.Empty;
bool specStateMatches = true;
for (int index = 0; index < imageCount; index++) for (int index = 0; index < imageCount; index++)
{ {
TextureBindingInfo bindingInfo = _imageBindings[stageIndex][index]; TextureBindingInfo bindingInfo = _imageBindings[stageIndex][index];
TextureUsageFlags usageFlags = bindingInfo.Flags;
int scaleIndex = baseScaleIndex + index;
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex); (int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex);
int packedId = ReadPackedId(stageIndex, bindingInfo.Handle, textureBufferIndex, samplerBufferIndex); UpdateCachedBuffer(stageIndex, ref cachedTextureBufferIndex, ref cachedSamplerBufferIndex, ref cachedTextureBuffer, ref cachedSamplerBuffer, textureBufferIndex, samplerBufferIndex);
int textureId = UnpackTextureId(packedId);
Texture texture = pool.Get(textureId); int packedId = TextureHandle.ReadPackedId(bindingInfo.Handle, cachedTextureBuffer, cachedSamplerBuffer);
int textureId = TextureHandle.UnpackTextureId(packedId);
ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target); ref TextureState state = ref _imageState[bindingInfo.Binding];
bool isStore = bindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore); bool isStore = bindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore);
if (!poolModified &&
state.TextureHandle == textureId &&
state.CachedTexture != null &&
state.CachedTexture.InvalidatedSequence == state.InvalidatedSequence)
{
Texture cachedTexture = state.CachedTexture;
// The texture is already bound.
cachedTexture.SynchronizeMemory();
if (isStore)
{
cachedTexture?.SignalModified();
}
if ((state.ScaleIndex != index || state.UsageFlags != usageFlags) &&
UpdateScale(state.CachedTexture, usageFlags, scaleIndex, stage))
{
ITexture hostTextureRebind = state.CachedTexture.GetTargetTexture(bindingInfo.Target);
Format format = bindingInfo.Format == 0 ? cachedTexture.Format : bindingInfo.Format;
state.Texture = hostTextureRebind;
state.ScaleIndex = scaleIndex;
state.UsageFlags = usageFlags;
_context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTextureRebind, format);
}
continue;
}
state.TextureHandle = textureId;
ref readonly TextureDescriptor descriptor = ref pool.GetForBinding(textureId, out Texture texture);
specStateMatches &= specState.MatchesImage(stage, index, descriptor);
ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
if (hostTexture != null && texture.Target == Target.TextureBuffer) if (hostTexture != null && texture.Target == Target.TextureBuffer)
{ {
// Ensure that the buffer texture is using the correct buffer as storage. // Ensure that the buffer texture is using the correct buffer as storage.
@ -494,14 +682,16 @@ namespace Ryujinx.Graphics.Gpu.Image
texture?.SignalModified(); texture?.SignalModified();
} }
if (_imageState[stageIndex][index].Texture != hostTexture || _rebind) if (state.Texture != hostTexture)
{ {
if (UpdateScale(texture, bindingInfo, baseScaleIndex + index, stage)) if (UpdateScale(texture, usageFlags, scaleIndex, stage))
{ {
hostTexture = texture?.GetTargetTexture(bindingInfo.Target); hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
} }
_imageState[stageIndex][index].Texture = hostTexture; state.Texture = hostTexture;
state.ScaleIndex = scaleIndex;
state.UsageFlags = usageFlags;
Format format = bindingInfo.Format; Format format = bindingInfo.Format;
@ -512,8 +702,13 @@ namespace Ryujinx.Graphics.Gpu.Image
_context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTexture, format); _context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTexture, format);
} }
state.CachedTexture = texture;
state.InvalidatedSequence = texture?.InvalidatedSequence ?? 0;
} }
} }
return specStateMatches;
} }
/// <summary> /// <summary>
@ -537,13 +732,28 @@ namespace Ryujinx.Graphics.Gpu.Image
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(cbufSlot, bufferIndex); (int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(cbufSlot, bufferIndex);
int packedId = ReadPackedId(stageIndex, handle, textureBufferIndex, samplerBufferIndex); int packedId = ReadPackedId(stageIndex, handle, textureBufferIndex, samplerBufferIndex);
int textureId = UnpackTextureId(packedId); int textureId = TextureHandle.UnpackTextureId(packedId);
ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa); ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa);
TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId); TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId);
return texturePool.GetDescriptor(textureId); TextureDescriptor descriptor;
if (texturePool.IsValidId(textureId))
{
descriptor = texturePool.GetDescriptor(textureId);
}
else
{
// If the ID is not valid, we just return a default descriptor with the most common state.
// Since this is used for shader specialization, doing so might avoid the need for recompilations.
descriptor = new TextureDescriptor();
descriptor.Word4 |= (uint)TextureTarget.Texture2D << 23;
descriptor.Word5 |= 1u << 31; // Coords normalized.
}
return descriptor;
} }
/// <summary> /// <summary>
@ -555,6 +765,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="textureBufferIndex">Index of the constant buffer holding the texture handles</param> /// <param name="textureBufferIndex">Index of the constant buffer holding the texture handles</param>
/// <param name="samplerBufferIndex">Index of the constant buffer holding the sampler handles</param> /// <param name="samplerBufferIndex">Index of the constant buffer holding the sampler handles</param>
/// <returns>The packed texture and sampler ID (the real texture handle)</returns> /// <returns>The packed texture and sampler ID (the real texture handle)</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int ReadPackedId(int stageIndex, int wordOffset, int textureBufferIndex, int samplerBufferIndex) private int ReadPackedId(int stageIndex, int wordOffset, int textureBufferIndex, int samplerBufferIndex)
{ {
(int textureWordOffset, int samplerWordOffset, TextureHandleType handleType) = TextureHandle.UnpackOffsets(wordOffset); (int textureWordOffset, int samplerWordOffset, TextureHandleType handleType) = TextureHandle.UnpackOffsets(wordOffset);
@ -590,32 +801,13 @@ namespace Ryujinx.Graphics.Gpu.Image
return handle; return handle;
} }
/// <summary>
/// Unpacks the texture ID from the real texture handle.
/// </summary>
/// <param name="packedId">The real texture handle</param>
/// <returns>The texture ID</returns>
private static int UnpackTextureId(int packedId)
{
return (packedId >> 0) & 0xfffff;
}
/// <summary>
/// Unpacks the sampler ID from the real texture handle.
/// </summary>
/// <param name="packedId">The real texture handle</param>
/// <returns>The sampler ID</returns>
private static int UnpackSamplerId(int packedId)
{
return (packedId >> 20) & 0xfff;
}
/// <summary> /// <summary>
/// Force all bound textures and images to be rebound the next time CommitBindings is called. /// Force all bound textures and images to be rebound the next time CommitBindings is called.
/// </summary> /// </summary>
public void Rebind() public void Rebind()
{ {
_rebind = true; Array.Clear(_textureState);
Array.Clear(_imageState);
} }
/// <summary> /// <summary>

View File

@ -349,6 +349,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="memoryManager">GPU memory manager where the texture is mapped</param> /// <param name="memoryManager">GPU memory manager where the texture is mapped</param>
/// <param name="dsState">Depth-stencil buffer texture to find or create</param> /// <param name="dsState">Depth-stencil buffer texture to find or create</param>
/// <param name="size">Size of the depth-stencil texture</param> /// <param name="size">Size of the depth-stencil texture</param>
/// <param name="layered">Indicates if the texture might be accessed with a non-zero layer index</param>
/// <param name="samplesInX">Number of samples in the X direction, for MSAA</param> /// <param name="samplesInX">Number of samples in the X direction, for MSAA</param>
/// <param name="samplesInY">Number of samples in the Y direction, for MSAA</param> /// <param name="samplesInY">Number of samples in the Y direction, for MSAA</param>
/// <param name="sizeHint">A hint indicating the minimum used size for the texture</param> /// <param name="sizeHint">A hint indicating the minimum used size for the texture</param>
@ -357,6 +358,7 @@ namespace Ryujinx.Graphics.Gpu.Image
MemoryManager memoryManager, MemoryManager memoryManager,
RtDepthStencilState dsState, RtDepthStencilState dsState,
Size3D size, Size3D size,
bool layered,
int samplesInX, int samplesInX,
int samplesInY, int samplesInY,
Size sizeHint) Size sizeHint)
@ -364,9 +366,24 @@ namespace Ryujinx.Graphics.Gpu.Image
int gobBlocksInY = dsState.MemoryLayout.UnpackGobBlocksInY(); int gobBlocksInY = dsState.MemoryLayout.UnpackGobBlocksInY();
int gobBlocksInZ = dsState.MemoryLayout.UnpackGobBlocksInZ(); int gobBlocksInZ = dsState.MemoryLayout.UnpackGobBlocksInZ();
Target target = (samplesInX | samplesInY) != 1 Target target;
? Target.Texture2DMultisample
: Target.Texture2D; if (dsState.MemoryLayout.UnpackIsTarget3D())
{
target = Target.Texture3D;
}
else if ((samplesInX | samplesInY) != 1)
{
target = size.Depth > 1 && layered
? Target.Texture2DMultisampleArray
: Target.Texture2DMultisample;
}
else
{
target = size.Depth > 1 && layered
? Target.Texture2DArray
: Target.Texture2D;
}
FormatInfo formatInfo = dsState.Format.Convert(); FormatInfo formatInfo = dsState.Format.Convert();

View File

@ -241,25 +241,6 @@ namespace Ryujinx.Graphics.Gpu.Image
return (TextureMsaaMode)((Word7 >> 8) & 0xf); return (TextureMsaaMode)((Word7 >> 8) & 0xf);
} }
/// <summary>
/// Create the equivalent of this TextureDescriptor for the shader cache.
/// </summary>
/// <returns>The equivalent of this TextureDescriptor for the shader cache.</returns>
public GuestTextureDescriptor ToCache()
{
GuestTextureDescriptor result = new GuestTextureDescriptor
{
Handle = uint.MaxValue,
Format = UnpackFormat(),
Target = UnpackTextureTarget(),
IsSrgb = UnpackSrgb(),
IsTextureCoordNormalized = UnpackTextureCoordNormalized(),
};
return result;
}
/// <summary> /// <summary>
/// Check if two descriptors are equal. /// Check if two descriptors are equal.
/// </summary> /// </summary>

View File

@ -1,5 +1,6 @@
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.Types; using Ryujinx.Graphics.Gpu.Engine.Types;
using Ryujinx.Graphics.Gpu.Shader;
using System; using System;
namespace Ryujinx.Graphics.Gpu.Image namespace Ryujinx.Graphics.Gpu.Image
@ -10,9 +11,11 @@ namespace Ryujinx.Graphics.Gpu.Image
class TextureManager : IDisposable class TextureManager : IDisposable
{ {
private readonly GpuContext _context; private readonly GpuContext _context;
private readonly GpuChannel _channel;
private readonly TextureBindingsManager _cpBindingsManager; private readonly TextureBindingsManager _cpBindingsManager;
private readonly TextureBindingsManager _gpBindingsManager; private readonly TextureBindingsManager _gpBindingsManager;
private readonly TexturePoolCache _texturePoolCache;
private readonly Texture[] _rtColors; private readonly Texture[] _rtColors;
private readonly ITexture[] _rtHostColors; private readonly ITexture[] _rtHostColors;
@ -35,6 +38,7 @@ namespace Ryujinx.Graphics.Gpu.Image
public TextureManager(GpuContext context, GpuChannel channel) public TextureManager(GpuContext context, GpuChannel channel)
{ {
_context = context; _context = context;
_channel = channel;
TexturePoolCache texturePoolCache = new TexturePoolCache(context); TexturePoolCache texturePoolCache = new TexturePoolCache(context);
@ -43,6 +47,7 @@ namespace Ryujinx.Graphics.Gpu.Image
_cpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, scales, isCompute: true); _cpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, scales, isCompute: true);
_gpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, scales, isCompute: false); _gpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, scales, isCompute: false);
_texturePoolCache = texturePoolCache;
_rtColors = new Texture[Constants.TotalRenderTargets]; _rtColors = new Texture[Constants.TotalRenderTargets];
_rtHostColors = new ITexture[Constants.TotalRenderTargets]; _rtHostColors = new ITexture[Constants.TotalRenderTargets];
@ -99,6 +104,16 @@ namespace Ryujinx.Graphics.Gpu.Image
_cpBindingsManager.SetTextureBufferIndex(index); _cpBindingsManager.SetTextureBufferIndex(index);
} }
/// <summary>
/// Sets the max binding indexes on the compute pipeline.
/// </summary>
/// <param name="maxTextureBinding">The maximum texture binding</param>
/// <param name="maxImageBinding">The maximum image binding</param>
public void SetComputeMaxBindings(int maxTextureBinding, int maxImageBinding)
{
_cpBindingsManager.SetMaxBindings(maxTextureBinding, maxImageBinding);
}
/// <summary> /// <summary>
/// Sets the texture constant buffer index on the graphics pipeline. /// Sets the texture constant buffer index on the graphics pipeline.
/// </summary> /// </summary>
@ -108,6 +123,16 @@ namespace Ryujinx.Graphics.Gpu.Image
_gpBindingsManager.SetTextureBufferIndex(index); _gpBindingsManager.SetTextureBufferIndex(index);
} }
/// <summary>
/// Sets the max binding indexes on the graphics pipeline.
/// </summary>
/// <param name="maxTextureBinding">The maximum texture binding</param>
/// <param name="maxImageBinding">The maximum image binding</param>
public void SetGraphicsMaxBindings(int maxTextureBinding, int maxImageBinding)
{
_gpBindingsManager.SetMaxBindings(maxTextureBinding, maxImageBinding);
}
/// <summary> /// <summary>
/// Sets the current sampler pool on the compute pipeline. /// Sets the current sampler pool on the compute pipeline.
/// </summary> /// </summary>
@ -335,25 +360,48 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <summary> /// <summary>
/// Commits bindings on the compute pipeline. /// Commits bindings on the compute pipeline.
/// </summary> /// </summary>
public void CommitComputeBindings() /// <param name="specState">Specialization state for the bound shader</param>
/// <returns>True if all bound textures match the current shader specialization state, false otherwise</returns>
public bool CommitComputeBindings(ShaderSpecializationState specState)
{ {
// Every time we switch between graphics and compute work, // Every time we switch between graphics and compute work,
// we must rebind everything. // we must rebind everything.
// Since compute work happens less often, we always do that // Since compute work happens less often, we always do that
// before and after the compute dispatch. // before and after the compute dispatch.
_cpBindingsManager.Rebind(); _cpBindingsManager.Rebind();
_cpBindingsManager.CommitBindings(); bool result = _cpBindingsManager.CommitBindings(specState);
_gpBindingsManager.Rebind(); _gpBindingsManager.Rebind();
return result;
} }
/// <summary> /// <summary>
/// Commits bindings on the graphics pipeline. /// Commits bindings on the graphics pipeline.
/// </summary> /// </summary>
public void CommitGraphicsBindings() /// <param name="specState">Specialization state for the bound shader</param>
/// <returns>True if all bound textures match the current shader specialization state, false otherwise</returns>
public bool CommitGraphicsBindings(ShaderSpecializationState specState)
{ {
_gpBindingsManager.CommitBindings(); bool result = _gpBindingsManager.CommitBindings(specState);
UpdateRenderTargets(); UpdateRenderTargets();
return result;
}
/// <summary>
/// Returns a texture pool from the cache, with the given address and maximum id.
/// </summary>
/// <param name="poolGpuVa">GPU virtual address of the texture pool</param>
/// <param name="maximumId">Maximum ID of the texture pool</param>
/// <returns>The texture pool</returns>
public TexturePool GetTexturePool(ulong poolGpuVa, int maximumId)
{
ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa);
TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId);
return texturePool;
} }
/// <summary> /// <summary>

View File

@ -14,6 +14,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
private readonly GpuChannel _channel; private readonly GpuChannel _channel;
private readonly ConcurrentQueue<Texture> _dereferenceQueue = new ConcurrentQueue<Texture>(); private readonly ConcurrentQueue<Texture> _dereferenceQueue = new ConcurrentQueue<Texture>();
private TextureDescriptor _defaultDescriptor;
/// <summary> /// <summary>
/// Intrusive linked list node used on the texture pool cache. /// Intrusive linked list node used on the texture pool cache.
@ -32,6 +33,62 @@ namespace Ryujinx.Graphics.Gpu.Image
_channel = channel; _channel = channel;
} }
/// <summary>
/// Gets the texture descripor and texture with the given ID with no bounds check or synchronization.
/// </summary>
/// <param name="id">ID of the texture. This is effectively a zero-based index</param>
/// <param name="texture">The texture with the given ID</param>
/// <returns>The texture descriptor with the given ID</returns>
private ref readonly TextureDescriptor GetInternal(int id, out Texture texture)
{
texture = Items[id];
ref readonly TextureDescriptor descriptor = ref GetDescriptorRef(id);
if (texture == null)
{
TextureInfo info = GetInfo(descriptor, out int layerSize);
ProcessDereferenceQueue();
texture = PhysicalMemory.TextureCache.FindOrCreateTexture(_channel.MemoryManager, TextureSearchFlags.ForSampler, info, layerSize);
// If this happens, then the texture address is invalid, we can't add it to the cache.
if (texture == null)
{
return ref descriptor;
}
texture.IncrementReferenceCount(this, id);
Items[id] = texture;
DescriptorCache[id] = descriptor;
}
else
{
if (texture.ChangedSize)
{
// Texture changed size at one point - it may be a different size than the sampler expects.
// This can be triggered when the size is changed by a size hint on copy or draw, but the texture has been sampled before.
int baseLevel = descriptor.UnpackBaseLevel();
int width = Math.Max(1, descriptor.UnpackWidth() >> baseLevel);
int height = Math.Max(1, descriptor.UnpackHeight() >> baseLevel);
if (texture.Info.Width != width || texture.Info.Height != height)
{
texture.ChangeSize(width, height, texture.Info.DepthOrLayers);
}
}
// Memory is automatically synchronized on texture creation.
texture.SynchronizeMemory();
}
return ref descriptor;
}
/// <summary> /// <summary>
/// Gets the texture with the given ID. /// Gets the texture with the given ID.
/// </summary> /// </summary>
@ -51,56 +108,49 @@ namespace Ryujinx.Graphics.Gpu.Image
SynchronizeMemory(); SynchronizeMemory();
} }
Texture texture = Items[id]; GetInternal(id, out Texture texture);
if (texture == null)
{
TextureDescriptor descriptor = GetDescriptor(id);
TextureInfo info = GetInfo(descriptor, out int layerSize);
ProcessDereferenceQueue();
texture = PhysicalMemory.TextureCache.FindOrCreateTexture(_channel.MemoryManager, TextureSearchFlags.ForSampler, info, layerSize);
// If this happens, then the texture address is invalid, we can't add it to the cache.
if (texture == null)
{
return null;
}
texture.IncrementReferenceCount(this, id);
Items[id] = texture;
DescriptorCache[id] = descriptor;
}
else
{
if (texture.ChangedSize)
{
// Texture changed size at one point - it may be a different size than the sampler expects.
// This can be triggered when the size is changed by a size hint on copy or draw, but the texture has been sampled before.
TextureDescriptor descriptor = GetDescriptor(id);
int baseLevel = descriptor.UnpackBaseLevel();
int width = Math.Max(1, descriptor.UnpackWidth() >> baseLevel);
int height = Math.Max(1, descriptor.UnpackHeight() >> baseLevel);
if (texture.Info.Width != width || texture.Info.Height != height)
{
texture.ChangeSize(width, height, texture.Info.DepthOrLayers);
}
}
// Memory is automatically synchronized on texture creation.
texture.SynchronizeMemory();
}
return texture; return texture;
} }
/// <summary>
/// Gets the texture descriptor and texture with the given ID.
/// </summary>
/// <remarks>
/// This method assumes that the pool has been manually synchronized before doing binding.
/// </remarks>
/// <param name="id">ID of the texture. This is effectively a zero-based index</param>
/// <param name="texture">The texture with the given ID</param>
/// <returns>The texture descriptor with the given ID</returns>
public ref readonly TextureDescriptor GetForBinding(int id, out Texture texture)
{
if ((uint)id >= Items.Length)
{
texture = null;
return ref _defaultDescriptor;
}
// When getting for binding, assume the pool has already been synchronized.
return ref GetInternal(id, out texture);
}
/// <summary>
/// Checks if the pool was modified, and returns the last sequence number where a modification was detected.
/// </summary>
/// <returns>A number that increments each time a modification is detected</returns>
public int CheckModified()
{
if (SequenceNumber != Context.SequenceNumber)
{
SequenceNumber = Context.SequenceNumber;
SynchronizeMemory();
}
return ModifiedSequenceNumber;
}
/// <summary> /// <summary>
/// Forcibly remove a texture from this pool's items. /// Forcibly remove a texture from this pool's items.
/// If deferred, the dereference will be queued to occur on the render thread. /// If deferred, the dereference will be queued to occur on the render thread.
@ -175,7 +225,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="descriptor">The texture descriptor</param> /// <param name="descriptor">The texture descriptor</param>
/// <param name="layerSize">Layer size for textures using a sub-range of mipmap levels, otherwise 0</param> /// <param name="layerSize">Layer size for textures using a sub-range of mipmap levels, otherwise 0</param>
/// <returns>The texture information</returns> /// <returns>The texture information</returns>
private TextureInfo GetInfo(TextureDescriptor descriptor, out int layerSize) private TextureInfo GetInfo(in TextureDescriptor descriptor, out int layerSize)
{ {
int depthOrLayers = descriptor.UnpackDepth(); int depthOrLayers = descriptor.UnpackDepth();
int levels = descriptor.UnpackLevels(); int levels = descriptor.UnpackLevels();

View File

@ -378,6 +378,25 @@ namespace Ryujinx.Graphics.Gpu.Memory
return _gpUniformBuffers[stage].Buffers[index].Address; return _gpUniformBuffers[stage].Buffers[index].Address;
} }
/// <summary>
/// Gets the bounds of the uniform buffer currently bound at the given index.
/// </summary>
/// <param name="isCompute">Indicates whenever the uniform is requested by the 3D or compute engine</param>
/// <param name="stage">Index of the shader stage, if the uniform is for the 3D engine</param>
/// <param name="index">Index of the uniform buffer binding</param>
/// <returns>The uniform buffer bounds, or an undefined value if the buffer is not currently bound</returns>
public ref BufferBounds GetUniformBufferBounds(bool isCompute, int stage, int index)
{
if (isCompute)
{
return ref _cpUniformBuffers.Buffers[index];
}
else
{
return ref _gpUniformBuffers[stage].Buffers[index];
}
}
/// <summary> /// <summary>
/// Ensures that the compute engine bindings are visible to the host GPU. /// Ensures that the compute engine bindings are visible to the host GPU.
/// Note: this actually performs the binding using the host graphics API. /// Note: this actually performs the binding using the host graphics API.

View File

@ -35,6 +35,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
HostProgram = hostProgram; HostProgram = hostProgram;
SpecializationState = specializationState; SpecializationState = specializationState;
Shaders = shaders; Shaders = shaders;
SpecializationState.Prepare(shaders);
} }
/// <summary> /// <summary>

View File

@ -418,7 +418,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{ {
if (IsShaderEqual(channel.MemoryManager, cpShader.Shaders[0], gpuVa)) if (IsShaderEqual(channel.MemoryManager, cpShader.Shaders[0], gpuVa))
{ {
return cpShader.SpecializationState.MatchesCompute(channel, poolState); return cpShader.SpecializationState.MatchesCompute(channel, poolState, true);
} }
return false; return false;
@ -454,7 +454,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
} }
} }
return gpShaders.SpecializationState.MatchesGraphics(channel, poolState, graphicsState); return gpShaders.SpecializationState.MatchesGraphics(channel, poolState, graphicsState, true);
} }
/// <summary> /// <summary>

View File

@ -35,7 +35,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{ {
foreach (var entry in _entries) foreach (var entry in _entries)
{ {
if (entry.SpecializationState.MatchesGraphics(channel, poolState, graphicsState)) if (entry.SpecializationState.MatchesGraphics(channel, poolState, graphicsState, true))
{ {
program = entry; program = entry;
return true; return true;
@ -57,7 +57,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{ {
foreach (var entry in _entries) foreach (var entry in _entries)
{ {
if (entry.SpecializationState.MatchesCompute(channel, poolState)) if (entry.SpecializationState.MatchesCompute(channel, poolState, true))
{ {
program = entry; program = entry;
return true; return true;

View File

@ -1,9 +1,14 @@
using Ryujinx.Common.Memory; using Ryujinx.Common.Memory;
using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Gpu.Shader.DiskCache; using Ryujinx.Graphics.Gpu.Shader.DiskCache;
using Ryujinx.Graphics.Shader; using Ryujinx.Graphics.Shader;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Gpu.Shader namespace Ryujinx.Graphics.Gpu.Shader
{ {
@ -158,6 +163,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
} }
private readonly Dictionary<TextureKey, Box<TextureSpecializationState>> _textureSpecialization; private readonly Dictionary<TextureKey, Box<TextureSpecializationState>> _textureSpecialization;
private KeyValuePair<TextureKey, Box<TextureSpecializationState>>[] _allTextures;
private Box<TextureSpecializationState>[][] _textureByBinding;
private Box<TextureSpecializationState>[][] _imageByBinding;
/// <summary> /// <summary>
/// Creates a new instance of the shader specialization state. /// Creates a new instance of the shader specialization state.
@ -194,6 +202,48 @@ namespace Ryujinx.Graphics.Gpu.Shader
} }
} }
/// <summary>
/// Prepare the shader specialization state for quick binding lookups.
/// </summary>
/// <param name="stages">The shader stages</param>
public void Prepare(CachedShaderStage[] stages)
{
_allTextures = _textureSpecialization.ToArray();
_textureByBinding = new Box<TextureSpecializationState>[stages.Length][];
_imageByBinding = new Box<TextureSpecializationState>[stages.Length][];
for (int i = 0; i < stages.Length; i++)
{
CachedShaderStage stage = stages[i];
if (stage?.Info != null)
{
var textures = stage.Info.Textures;
var images = stage.Info.Images;
var texBindings = new Box<TextureSpecializationState>[textures.Count];
var imageBindings = new Box<TextureSpecializationState>[images.Count];
int stageIndex = Math.Max(i - 1, 0); // Don't count VertexA for looking up spec state. No-Op for compute.
for (int j = 0; j < textures.Count; j++)
{
var texture = textures[j];
texBindings[j] = GetTextureSpecState(stageIndex, texture.HandleIndex, texture.CbufSlot);
}
for (int j = 0; j < images.Count; j++)
{
var image = images[j];
imageBindings[j] = GetTextureSpecState(stageIndex, image.HandleIndex, image.CbufSlot);
}
_textureByBinding[i] = texBindings;
_imageByBinding[i] = imageBindings;
}
}
}
/// <summary> /// <summary>
/// Indicates that the shader accesses the early Z force state. /// Indicates that the shader accesses the early Z force state.
/// </summary> /// </summary>
@ -396,15 +446,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="channel">GPU channel</param> /// <param name="channel">GPU channel</param>
/// <param name="poolState">Texture pool state</param> /// <param name="poolState">Texture pool state</param>
/// <param name="graphicsState">Graphics state</param> /// <param name="graphicsState">Graphics state</param>
/// <param name="checkTextures">Indicates whether texture descriptors should be checked</param>
/// <returns>True if the state matches, false otherwise</returns> /// <returns>True if the state matches, false otherwise</returns>
public bool MatchesGraphics(GpuChannel channel, GpuChannelPoolState poolState, GpuChannelGraphicsState graphicsState) public bool MatchesGraphics(GpuChannel channel, GpuChannelPoolState poolState, GpuChannelGraphicsState graphicsState, bool checkTextures)
{ {
if (graphicsState.ViewportTransformDisable != GraphicsState.ViewportTransformDisable) if (graphicsState.ViewportTransformDisable != GraphicsState.ViewportTransformDisable)
{ {
return false; return false;
} }
return Matches(channel, poolState, isCompute: false); return Matches(channel, poolState, checkTextures, isCompute: false);
} }
/// <summary> /// <summary>
@ -412,10 +463,64 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary> /// </summary>
/// <param name="channel">GPU channel</param> /// <param name="channel">GPU channel</param>
/// <param name="poolState">Texture pool state</param> /// <param name="poolState">Texture pool state</param>
/// <param name="checkTextures">Indicates whether texture descriptors should be checked</param>
/// <returns>True if the state matches, false otherwise</returns> /// <returns>True if the state matches, false otherwise</returns>
public bool MatchesCompute(GpuChannel channel, GpuChannelPoolState poolState) public bool MatchesCompute(GpuChannel channel, GpuChannelPoolState poolState, bool checkTextures)
{ {
return Matches(channel, poolState, isCompute: true); return Matches(channel, poolState, checkTextures, isCompute: true);
}
/// <summary>
/// Fetch the constant buffers used for a texture to cache.
/// </summary>
/// <param name="channel">GPU channel</param>
/// <param name="isCompute">Indicates whenever the check is requested by the 3D or compute engine</param>
/// <param name="cachedTextureBufferIndex">The currently cached texture buffer index</param>
/// <param name="cachedSamplerBufferIndex">The currently cached sampler buffer index</param>
/// <param name="cachedTextureBuffer">The currently cached texture buffer data</param>
/// <param name="cachedSamplerBuffer">The currently cached sampler buffer data</param>
/// <param name="cachedStageIndex">The currently cached stage</param>
/// <param name="textureBufferIndex">The new texture buffer index</param>
/// <param name="samplerBufferIndex">The new sampler buffer index</param>
/// <param name="stageIndex">Stage index of the constant buffer</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void UpdateCachedBuffer(
GpuChannel channel,
bool isCompute,
ref int cachedTextureBufferIndex,
ref int cachedSamplerBufferIndex,
ref ReadOnlySpan<int> cachedTextureBuffer,
ref ReadOnlySpan<int> cachedSamplerBuffer,
ref int cachedStageIndex,
int textureBufferIndex,
int samplerBufferIndex,
int stageIndex)
{
bool stageChange = stageIndex != cachedStageIndex;
if (stageChange || textureBufferIndex != cachedTextureBufferIndex)
{
ref BufferBounds bounds = ref channel.BufferManager.GetUniformBufferBounds(isCompute, stageIndex, textureBufferIndex);
cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(channel.MemoryManager.Physical.GetSpan(bounds.Address, (int)bounds.Size));
cachedTextureBufferIndex = textureBufferIndex;
if (samplerBufferIndex == textureBufferIndex)
{
cachedSamplerBuffer = cachedTextureBuffer;
cachedSamplerBufferIndex = samplerBufferIndex;
}
}
if (stageChange || samplerBufferIndex != cachedSamplerBufferIndex)
{
ref BufferBounds bounds = ref channel.BufferManager.GetUniformBufferBounds(isCompute, stageIndex, samplerBufferIndex);
cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(channel.MemoryManager.Physical.GetSpan(bounds.Address, (int)bounds.Size));
cachedSamplerBufferIndex = samplerBufferIndex;
}
cachedStageIndex = stageIndex;
} }
/// <summary> /// <summary>
@ -423,9 +528,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary> /// </summary>
/// <param name="channel">GPU channel</param> /// <param name="channel">GPU channel</param>
/// <param name="poolState">Texture pool state</param> /// <param name="poolState">Texture pool state</param>
/// <param name="checkTextures">Indicates whether texture descriptors should be checked</param>
/// <param name="isCompute">Indicates whenever the check is requested by the 3D or compute engine</param> /// <param name="isCompute">Indicates whenever the check is requested by the 3D or compute engine</param>
/// <returns>True if the state matches, false otherwise</returns> /// <returns>True if the state matches, false otherwise</returns>
private bool Matches(GpuChannel channel, GpuChannelPoolState poolState, bool isCompute) private bool Matches(GpuChannel channel, GpuChannelPoolState poolState, bool checkTextures, bool isCompute)
{ {
int constantBufferUsePerStageMask = _constantBufferUsePerStage; int constantBufferUsePerStageMask = _constantBufferUsePerStage;
@ -445,55 +551,62 @@ namespace Ryujinx.Graphics.Gpu.Shader
constantBufferUsePerStageMask &= ~(1 << index); constantBufferUsePerStageMask &= ~(1 << index);
} }
foreach (var kv in _textureSpecialization) if (checkTextures)
{ {
TextureKey textureKey = kv.Key; TexturePool pool = channel.TextureManager.GetTexturePool(poolState.TexturePoolGpuVa, poolState.TexturePoolMaximumId);
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(textureKey.CbufSlot, poolState.TextureBufferIndex); int cachedTextureBufferIndex = -1;
int cachedSamplerBufferIndex = -1;
int cachedStageIndex = -1;
ReadOnlySpan<int> cachedTextureBuffer = Span<int>.Empty;
ReadOnlySpan<int> cachedSamplerBuffer = Span<int>.Empty;
ulong textureCbAddress; foreach (var kv in _allTextures)
ulong samplerCbAddress;
if (isCompute)
{ {
textureCbAddress = channel.BufferManager.GetComputeUniformBufferAddress(textureBufferIndex); TextureKey textureKey = kv.Key;
samplerCbAddress = channel.BufferManager.GetComputeUniformBufferAddress(samplerBufferIndex);
}
else
{
textureCbAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(textureKey.StageIndex, textureBufferIndex);
samplerCbAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(textureKey.StageIndex, samplerBufferIndex);
}
if (!channel.MemoryManager.Physical.IsMapped(textureCbAddress) || !channel.MemoryManager.Physical.IsMapped(samplerCbAddress)) (int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(textureKey.CbufSlot, poolState.TextureBufferIndex);
{
continue; UpdateCachedBuffer(channel,
isCompute,
ref cachedTextureBufferIndex,
ref cachedSamplerBufferIndex,
ref cachedTextureBuffer,
ref cachedSamplerBuffer,
ref cachedStageIndex,
textureBufferIndex,
samplerBufferIndex,
textureKey.StageIndex);
int packedId = TextureHandle.ReadPackedId(textureKey.Handle, cachedTextureBuffer, cachedSamplerBuffer);
int textureId = TextureHandle.UnpackTextureId(packedId);
if (pool.IsValidId(textureId))
{
ref readonly Image.TextureDescriptor descriptor = ref pool.GetDescriptorRef(textureId);
if (!MatchesTexture(kv.Value, descriptor))
{
return false;
}
}
} }
}
Image.TextureDescriptor descriptor; return true;
}
if (isCompute)
{
descriptor = channel.TextureManager.GetComputeTextureDescriptor(
poolState.TexturePoolGpuVa,
poolState.TextureBufferIndex,
poolState.TexturePoolMaximumId,
textureKey.Handle,
textureKey.CbufSlot);
}
else
{
descriptor = channel.TextureManager.GetGraphicsTextureDescriptor(
poolState.TexturePoolGpuVa,
poolState.TextureBufferIndex,
poolState.TexturePoolMaximumId,
textureKey.StageIndex,
textureKey.Handle,
textureKey.CbufSlot);
}
Box<TextureSpecializationState> specializationState = kv.Value;
/// <summary>
/// Checks if the recorded texture state matches the given texture descriptor.
/// </summary>
/// <param name="specializationState">Texture specialization state</param>
/// <param name="descriptor">Texture descriptor</param>
/// <returns>True if the state matches, false otherwise</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool MatchesTexture(Box<TextureSpecializationState> specializationState, in Image.TextureDescriptor descriptor)
{
if (specializationState != null)
{
if (specializationState.Value.QueriedFlags.HasFlag(QueriedTextureStateFlags.CoordNormalized) && if (specializationState.Value.QueriedFlags.HasFlag(QueriedTextureStateFlags.CoordNormalized) &&
specializationState.Value.CoordNormalized != descriptor.UnpackTextureCoordNormalized()) specializationState.Value.CoordNormalized != descriptor.UnpackTextureCoordNormalized())
{ {
@ -504,6 +617,34 @@ namespace Ryujinx.Graphics.Gpu.Shader
return true; return true;
} }
/// <summary>
/// Checks if the recorded texture state for a given texture binding matches a texture descriptor.
/// </summary>
/// <param name="stage">The shader stage</param>
/// <param name="index">The texture index</param>
/// <param name="descriptor">Texture descriptor</param>
/// <returns>True if the state matches, false otherwise</returns>
public bool MatchesTexture(ShaderStage stage, int index, in Image.TextureDescriptor descriptor)
{
Box<TextureSpecializationState> specializationState = _textureByBinding[(int)stage][index];
return MatchesTexture(specializationState, descriptor);
}
/// <summary>
/// Checks if the recorded texture state for a given image binding matches a texture descriptor.
/// </summary>
/// <param name="stage">The shader stage</param>
/// <param name="index">The texture index</param>
/// <param name="descriptor">Texture descriptor</param>
/// <returns>True if the state matches, false otherwise</returns>
public bool MatchesImage(ShaderStage stage, int index, in Image.TextureDescriptor descriptor)
{
Box<TextureSpecializationState> specializationState = _imageByBinding[(int)stage][index];
return MatchesTexture(specializationState, descriptor);
}
/// <summary> /// <summary>
/// Reads shader specialization state that has been serialized. /// Reads shader specialization state that has been serialized.
/// </summary> /// </summary>

View File

@ -9,10 +9,13 @@ namespace Ryujinx.Graphics.OpenGL
class Framebuffer : IDisposable class Framebuffer : IDisposable
{ {
public int Handle { get; private set; } public int Handle { get; private set; }
private int _clearFbHandle;
private bool _clearFbInitialized;
private FramebufferAttachment _lastDsAttachment; private FramebufferAttachment _lastDsAttachment;
private readonly TextureView[] _colors; private readonly TextureView[] _colors;
private TextureView _depthStencil;
private int _colorsCount; private int _colorsCount;
private bool _dualSourceBlend; private bool _dualSourceBlend;
@ -20,6 +23,7 @@ namespace Ryujinx.Graphics.OpenGL
public Framebuffer() public Framebuffer()
{ {
Handle = GL.GenFramebuffer(); Handle = GL.GenFramebuffer();
_clearFbHandle = GL.GenFramebuffer();
_colors = new TextureView[8]; _colors = new TextureView[8];
} }
@ -55,20 +59,7 @@ namespace Ryujinx.Graphics.OpenGL
if (depthStencil != null) if (depthStencil != null)
{ {
FramebufferAttachment attachment; FramebufferAttachment attachment = GetAttachment(depthStencil.Format);
if (IsPackedDepthStencilFormat(depthStencil.Format))
{
attachment = FramebufferAttachment.DepthStencilAttachment;
}
else if (IsDepthOnlyFormat(depthStencil.Format))
{
attachment = FramebufferAttachment.DepthAttachment;
}
else
{
attachment = FramebufferAttachment.StencilAttachment;
}
GL.FramebufferTexture( GL.FramebufferTexture(
FramebufferTarget.Framebuffer, FramebufferTarget.Framebuffer,
@ -82,6 +73,8 @@ namespace Ryujinx.Graphics.OpenGL
{ {
_lastDsAttachment = 0; _lastDsAttachment = 0;
} }
_depthStencil = depthStencil;
} }
public void SetDualSourceBlend(bool enable) public void SetDualSourceBlend(bool enable)
@ -124,6 +117,22 @@ namespace Ryujinx.Graphics.OpenGL
GL.DrawBuffers(colorsCount, drawBuffers); GL.DrawBuffers(colorsCount, drawBuffers);
} }
private static FramebufferAttachment GetAttachment(Format format)
{
if (IsPackedDepthStencilFormat(format))
{
return FramebufferAttachment.DepthStencilAttachment;
}
else if (IsDepthOnlyFormat(format))
{
return FramebufferAttachment.DepthAttachment;
}
else
{
return FramebufferAttachment.StencilAttachment;
}
}
private static bool IsPackedDepthStencilFormat(Format format) private static bool IsPackedDepthStencilFormat(Format format)
{ {
return format == Format.D24UnormS8Uint || return format == Format.D24UnormS8Uint ||
@ -136,6 +145,78 @@ namespace Ryujinx.Graphics.OpenGL
return format == Format.D16Unorm || format == Format.D32Float; return format == Format.D16Unorm || format == Format.D32Float;
} }
public void AttachColorLayerForClear(int index, int layer)
{
TextureView color = _colors[index];
if (!IsLayered(color))
{
return;
}
BindClearFb();
GL.FramebufferTextureLayer(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0 + index, color.Handle, 0, layer);
}
public void DetachColorLayerForClear(int index)
{
TextureView color = _colors[index];
if (!IsLayered(color))
{
return;
}
GL.FramebufferTexture(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0 + index, 0, 0);
Bind();
}
public void AttachDepthStencilLayerForClear(int layer)
{
TextureView depthStencil = _depthStencil;
if (!IsLayered(depthStencil))
{
return;
}
BindClearFb();
GL.FramebufferTextureLayer(FramebufferTarget.Framebuffer, GetAttachment(depthStencil.Format), depthStencil.Handle, 0, layer);
}
public void DetachDepthStencilLayerForClear()
{
TextureView depthStencil = _depthStencil;
if (!IsLayered(depthStencil))
{
return;
}
GL.FramebufferTexture(FramebufferTarget.Framebuffer, GetAttachment(depthStencil.Format), 0, 0);
Bind();
}
private void BindClearFb()
{
GL.BindFramebuffer(FramebufferTarget.Framebuffer, _clearFbHandle);
if (!_clearFbInitialized)
{
SetDrawBuffersImpl(Constants.MaxRenderTargets);
_clearFbInitialized = true;
}
}
private static bool IsLayered(TextureView view)
{
return view != null &&
view.Target != Target.Texture1D &&
view.Target != Target.Texture2D &&
view.Target != Target.Texture2DMultisample &&
view.Target != Target.TextureBuffer;
}
public void Dispose() public void Dispose()
{ {
if (Handle != 0) if (Handle != 0)
@ -144,6 +225,13 @@ namespace Ryujinx.Graphics.OpenGL
Handle = 0; Handle = 0;
} }
if (_clearFbHandle != 0)
{
GL.DeleteFramebuffer(_clearFbHandle);
_clearFbHandle = 0;
}
} }
} }
} }

View File

@ -110,7 +110,7 @@ namespace Ryujinx.Graphics.OpenGL
Buffer.Clear(destination, offset, size, value); Buffer.Clear(destination, offset, size, value);
} }
public void ClearRenderTargetColor(int index, uint componentMask, ColorF color) public void ClearRenderTargetColor(int index, int layer, uint componentMask, ColorF color)
{ {
GL.ColorMask( GL.ColorMask(
index, index,
@ -119,14 +119,18 @@ namespace Ryujinx.Graphics.OpenGL
(componentMask & 4) != 0, (componentMask & 4) != 0,
(componentMask & 8) != 0); (componentMask & 8) != 0);
_framebuffer.AttachColorLayerForClear(index, layer);
float[] colors = new float[] { color.Red, color.Green, color.Blue, color.Alpha }; float[] colors = new float[] { color.Red, color.Green, color.Blue, color.Alpha };
GL.ClearBuffer(OpenTK.Graphics.OpenGL.ClearBuffer.Color, index, colors); GL.ClearBuffer(OpenTK.Graphics.OpenGL.ClearBuffer.Color, index, colors);
_framebuffer.DetachColorLayerForClear(index);
RestoreComponentMask(index); RestoreComponentMask(index);
} }
public void ClearRenderTargetDepthStencil(float depthValue, bool depthMask, int stencilValue, int stencilMask) public void ClearRenderTargetDepthStencil(int layer, float depthValue, bool depthMask, int stencilValue, int stencilMask)
{ {
bool stencilMaskChanged = bool stencilMaskChanged =
stencilMask != 0 && stencilMask != 0 &&
@ -144,6 +148,8 @@ namespace Ryujinx.Graphics.OpenGL
GL.DepthMask(depthMask); GL.DepthMask(depthMask);
} }
_framebuffer.AttachDepthStencilLayerForClear(layer);
if (depthMask && stencilMask != 0) if (depthMask && stencilMask != 0)
{ {
GL.ClearBuffer(ClearBufferCombined.DepthStencil, 0, depthValue, stencilValue); GL.ClearBuffer(ClearBufferCombined.DepthStencil, 0, depthValue, stencilValue);
@ -157,6 +163,8 @@ namespace Ryujinx.Graphics.OpenGL
GL.ClearBuffer(OpenTK.Graphics.OpenGL.ClearBuffer.Stencil, 0, ref stencilValue); GL.ClearBuffer(OpenTK.Graphics.OpenGL.ClearBuffer.Stencil, 0, ref stencilValue);
} }
_framebuffer.DetachDepthStencilLayerForClear();
if (stencilMaskChanged) if (stencilMaskChanged)
{ {
GL.StencilMaskSeparate(StencilFace.Front, _stencilFrontMask); GL.StencilMaskSeparate(StencilFace.Front, _stencilFrontMask);
@ -597,6 +605,8 @@ namespace Ryujinx.Graphics.OpenGL
GL.EndTransformFeedback(); GL.EndTransformFeedback();
} }
GL.ClipControl(ClipOrigin.UpperLeft, ClipDepthMode.NegativeOneToOne);
_drawTexture.Draw( _drawTexture.Draw(
view, view,
samp, samp,
@ -627,6 +637,8 @@ namespace Ryujinx.Graphics.OpenGL
{ {
GL.BeginTransformFeedback(_tfTopology); GL.BeginTransformFeedback(_tfTopology);
} }
RestoreClipControl();
} }
} }
} }

View File

@ -1,3 +1,4 @@
using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace Ryujinx.Graphics.Shader namespace Ryujinx.Graphics.Shader
@ -50,5 +51,63 @@ namespace Ryujinx.Graphics.Shader
{ {
return (handle & 0x3fff, (handle >> 14) & 0x3fff, (TextureHandleType)((uint)handle >> 28)); return (handle & 0x3fff, (handle >> 14) & 0x3fff, (TextureHandleType)((uint)handle >> 28));
} }
/// <summary>
/// Unpacks the texture ID from the real texture handle.
/// </summary>
/// <param name="packedId">The real texture handle</param>
/// <returns>The texture ID</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int UnpackTextureId(int packedId)
{
return (packedId >> 0) & 0xfffff;
}
/// <summary>
/// Unpacks the sampler ID from the real texture handle.
/// </summary>
/// <param name="packedId">The real texture handle</param>
/// <returns>The sampler ID</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int UnpackSamplerId(int packedId)
{
return (packedId >> 20) & 0xfff;
}
/// <summary>
/// Reads a packed texture and sampler ID (basically, the real texture handle)
/// from a given texture/sampler constant buffer.
/// </summary>
/// <param name="wordOffset">A word offset of the handle on the buffer (the "fake" shader handle)</param>
/// <param name="cachedTextureBuffer">The constant buffer to fetch texture IDs from</param>
/// <param name="cachedSamplerBuffer">The constant buffer to fetch sampler IDs from</param>
/// <returns>The packed texture and sampler ID (the real texture handle)</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ReadPackedId(int wordOffset, ReadOnlySpan<int> cachedTextureBuffer, ReadOnlySpan<int> cachedSamplerBuffer)
{
(int textureWordOffset, int samplerWordOffset, TextureHandleType handleType) = UnpackOffsets(wordOffset);
int handle = cachedTextureBuffer[textureWordOffset];
// The "wordOffset" (which is really the immediate value used on texture instructions on the shader)
// is a 13-bit value. However, in order to also support separate samplers and textures (which uses
// bindless textures on the shader), we extend it with another value on the higher 16 bits with
// another offset for the sampler.
// The shader translator has code to detect separate texture and sampler uses with a bindless texture,
// turn that into a regular texture access and produce those special handles with values on the higher 16 bits.
if (handleType != TextureHandleType.CombinedSampler)
{
int samplerHandle = cachedSamplerBuffer[samplerWordOffset];
if (handleType == TextureHandleType.SeparateSamplerId)
{
samplerHandle <<= 20;
}
handle |= samplerHandle;
}
return handle;
}
} }
} }

View File

@ -10,17 +10,22 @@ namespace Ryujinx.Graphics.Vic
{ {
static class Blender static class Blender
{ {
public static void BlendOne(Surface dst, Surface src, ref SlotStruct slot) public static void BlendOne(Surface dst, Surface src, ref SlotStruct slot, Rectangle targetRect)
{ {
if (Sse41.IsSupported && (dst.Width & 3) == 0) int x1 = targetRect.X;
int y1 = targetRect.Y;
int x2 = Math.Min(src.Width, x1 + targetRect.Width);
int y2 = Math.Min(src.Height, y1 + targetRect.Height);
if (Sse41.IsSupported && ((x1 | x2) & 3) == 0)
{ {
BlendOneSse41(dst, src, ref slot); BlendOneSse41(dst, src, ref slot, x1, y1, x2, y2);
return; return;
} }
for (int y = 0; y < dst.Height; y++) for (int y = y1; y < y2; y++)
{ {
for (int x = 0; x < dst.Width; x++) for (int x = x1; x < x2; x++)
{ {
int inR = src.GetR(x, y); int inR = src.GetR(x, y);
int inG = src.GetG(x, y); int inG = src.GetG(x, y);
@ -40,9 +45,9 @@ namespace Ryujinx.Graphics.Vic
} }
} }
private unsafe static void BlendOneSse41(Surface dst, Surface src, ref SlotStruct slot) private unsafe static void BlendOneSse41(Surface dst, Surface src, ref SlotStruct slot, int x1, int y1, int x2, int y2)
{ {
Debug.Assert((dst.Width & 3) == 0); Debug.Assert(((x1 | x2) & 3) == 0);
ref MatrixStruct mtx = ref slot.ColorMatrixStruct; ref MatrixStruct mtx = ref slot.ColorMatrixStruct;
@ -62,9 +67,9 @@ namespace Ryujinx.Graphics.Vic
Pixel* ip = srcPtr; Pixel* ip = srcPtr;
Pixel* op = dstPtr; Pixel* op = dstPtr;
for (int y = 0; y < dst.Height; y++, ip += src.Width, op += dst.Width) for (int y = y1; y < y2; y++, ip += src.Width, op += dst.Width)
{ {
for (int x = 0; x < dst.Width; x += 4) for (int x = x1; x < x2; x += 4)
{ {
Vector128<int> pixel1 = Sse41.ConvertToVector128Int32((ushort*)(ip + (uint)x)); Vector128<int> pixel1 = Sse41.ConvertToVector128Int32((ushort*)(ip + (uint)x));
Vector128<int> pixel2 = Sse41.ConvertToVector128Int32((ushort*)(ip + (uint)x + 1)); Vector128<int> pixel2 = Sse41.ConvertToVector128Int32((ushort*)(ip + (uint)x + 1));

View File

@ -0,0 +1,18 @@
namespace Ryujinx.Graphics.Vic
{
struct Rectangle
{
public readonly int X;
public readonly int Y;
public readonly int Width;
public readonly int Height;
public Rectangle(int x, int y, int width, int height)
{
X = x;
Y = y;
Width = width;
Height = height;
}
}
}

View File

@ -2,6 +2,7 @@
using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Vic.Image; using Ryujinx.Graphics.Vic.Image;
using Ryujinx.Graphics.Vic.Types; using Ryujinx.Graphics.Vic.Types;
using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Ryujinx.Graphics.Vic namespace Ryujinx.Graphics.Vic
@ -47,7 +48,19 @@ namespace Ryujinx.Graphics.Vic
using Surface src = SurfaceReader.Read(_rm, ref slot.SlotConfig, ref slot.SlotSurfaceConfig, ref offsets); using Surface src = SurfaceReader.Read(_rm, ref slot.SlotConfig, ref slot.SlotSurfaceConfig, ref offsets);
Blender.BlendOne(output, src, ref slot); int x1 = config.OutputConfig.TargetRectLeft;
int y1 = config.OutputConfig.TargetRectTop;
int x2 = config.OutputConfig.TargetRectRight + 1;
int y2 = config.OutputConfig.TargetRectBottom + 1;
int targetX = Math.Min(x1, x2);
int targetY = Math.Min(y1, y2);
int targetW = Math.Min(output.Width - targetX, Math.Abs(x2 - x1));
int targetH = Math.Min(output.Height - targetY, Math.Abs(y2 - y1));
Rectangle targetRect = new Rectangle(targetX, targetY, targetW, targetH);
Blender.BlendOne(output, src, ref slot, targetRect);
} }
SurfaceWriter.Write(_rm, output, ref config.OutputSurfaceConfig, ref _state.State.SetOutputSurface); SurfaceWriter.Write(_rm, output, ref config.OutputSurfaceConfig, ref _state.State.SetOutputSurface);

View File

@ -1,87 +0,0 @@
namespace Ryujinx.HLE.HOS.Kernel.Memory
{
class KMemoryRegionBlock
{
public long[][] Masks;
public ulong FreeCount;
public int MaxLevel;
public ulong StartAligned;
public ulong SizeInBlocksTruncated;
public ulong SizeInBlocksRounded;
public int Order;
public int NextOrder;
public bool TryCoalesce(int index, int count)
{
long mask = ((1L << count) - 1) << (index & 63);
index /= 64;
if (count >= 64)
{
int remaining = count;
int tempIdx = index;
do
{
if (Masks[MaxLevel - 1][tempIdx++] != -1L)
{
return false;
}
remaining -= 64;
}
while (remaining != 0);
remaining = count;
tempIdx = index;
do
{
Masks[MaxLevel - 1][tempIdx] = 0;
ClearMaskBit(MaxLevel - 2, tempIdx++);
remaining -= 64;
}
while (remaining != 0);
}
else
{
long value = Masks[MaxLevel - 1][index];
if ((mask & ~value) != 0)
{
return false;
}
value &= ~mask;
Masks[MaxLevel - 1][index] = value;
if (value == 0)
{
ClearMaskBit(MaxLevel - 2, index);
}
}
FreeCount -= (ulong)count;
return true;
}
public void ClearMaskBit(int startLevel, int index)
{
for (int level = startLevel; level >= 0; level--, index /= 64)
{
Masks[level][index / 64] &= ~(1L << (index & 63));
if (Masks[level][index / 64] != 0)
{
break;
}
}
}
}
}

View File

@ -1,102 +1,42 @@
using Ryujinx.Common;
using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Common;
using System.Diagnostics; using System.Diagnostics;
using System.Numerics;
namespace Ryujinx.HLE.HOS.Kernel.Memory namespace Ryujinx.HLE.HOS.Kernel.Memory
{ {
class KMemoryRegionManager class KMemoryRegionManager
{ {
private static readonly int[] BlockOrders = new int[] { 12, 16, 21, 22, 25, 29, 30 }; private readonly KPageHeap _pageHeap;
public ulong Address { get; private set; } public ulong Address { get; }
public ulong EndAddr { get; private set; } public ulong Size { get; }
public ulong Size { get; private set; } public ulong EndAddr => Address + Size;
private int _blockOrdersCount;
private readonly KMemoryRegionBlock[] _blocks;
private readonly ushort[] _pageReferenceCounts; private readonly ushort[] _pageReferenceCounts;
public KMemoryRegionManager(ulong address, ulong size, ulong endAddr) public KMemoryRegionManager(ulong address, ulong size, ulong endAddr)
{ {
_blocks = new KMemoryRegionBlock[BlockOrders.Length];
Address = address; Address = address;
Size = size; Size = size;
EndAddr = endAddr;
_blockOrdersCount = BlockOrders.Length;
for (int blockIndex = 0; blockIndex < _blockOrdersCount; blockIndex++)
{
_blocks[blockIndex] = new KMemoryRegionBlock();
_blocks[blockIndex].Order = BlockOrders[blockIndex];
int nextOrder = blockIndex == _blockOrdersCount - 1 ? 0 : BlockOrders[blockIndex + 1];
_blocks[blockIndex].NextOrder = nextOrder;
int currBlockSize = 1 << BlockOrders[blockIndex];
int nextBlockSize = currBlockSize;
if (nextOrder != 0)
{
nextBlockSize = 1 << nextOrder;
}
ulong startAligned = BitUtils.AlignDown(address, nextBlockSize);
ulong endAddrAligned = BitUtils.AlignDown(endAddr, currBlockSize);
ulong sizeInBlocksTruncated = (endAddrAligned - startAligned) >> BlockOrders[blockIndex];
ulong endAddrRounded = BitUtils.AlignUp(address + size, nextBlockSize);
ulong sizeInBlocksRounded = (endAddrRounded - startAligned) >> BlockOrders[blockIndex];
_blocks[blockIndex].StartAligned = startAligned;
_blocks[blockIndex].SizeInBlocksTruncated = sizeInBlocksTruncated;
_blocks[blockIndex].SizeInBlocksRounded = sizeInBlocksRounded;
ulong currSizeInBlocks = sizeInBlocksRounded;
int maxLevel = 0;
do
{
maxLevel++;
}
while ((currSizeInBlocks /= 64) != 0);
_blocks[blockIndex].MaxLevel = maxLevel;
_blocks[blockIndex].Masks = new long[maxLevel][];
currSizeInBlocks = sizeInBlocksRounded;
for (int level = maxLevel - 1; level >= 0; level--)
{
currSizeInBlocks = (currSizeInBlocks + 63) / 64;
_blocks[blockIndex].Masks[level] = new long[currSizeInBlocks];
}
}
_pageReferenceCounts = new ushort[size / KPageTableBase.PageSize]; _pageReferenceCounts = new ushort[size / KPageTableBase.PageSize];
if (size != 0) _pageHeap = new KPageHeap(address, size);
{ _pageHeap.Free(address, size / KPageTableBase.PageSize);
FreePages(address, size / KPageTableBase.PageSize); _pageHeap.UpdateUsedSize();
}
} }
public KernelResult AllocatePages(ulong pagesCount, bool backwards, out KPageList pageList) public KernelResult AllocatePages(out KPageList pageList, ulong pagesCount)
{ {
lock (_blocks) if (pagesCount == 0)
{ {
KernelResult result = AllocatePagesImpl(pagesCount, backwards, out pageList); pageList = new KPageList();
return KernelResult.Success;
}
lock (_pageHeap)
{
KernelResult result = AllocatePagesImpl(out pageList, pagesCount, false);
if (result == KernelResult.Success) if (result == KernelResult.Success)
{ {
@ -112,9 +52,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
public ulong AllocatePagesContiguous(KernelContext context, ulong pagesCount, bool backwards) public ulong AllocatePagesContiguous(KernelContext context, ulong pagesCount, bool backwards)
{ {
lock (_blocks) if (pagesCount == 0)
{ {
ulong address = AllocatePagesContiguousImpl(pagesCount, backwards); return 0;
}
lock (_pageHeap)
{
ulong address = AllocatePagesContiguousImpl(pagesCount, 1, backwards);
if (address != 0) if (address != 0)
{ {
@ -126,373 +71,110 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
} }
} }
private KernelResult AllocatePagesImpl(ulong pagesCount, bool backwards, out KPageList pageList) private KernelResult AllocatePagesImpl(out KPageList pageList, ulong pagesCount, bool random)
{ {
pageList = new KPageList(); pageList = new KPageList();
if (_blockOrdersCount > 0) int heapIndex = KPageHeap.GetBlockIndex(pagesCount);
{
if (GetFreePagesImpl() < pagesCount) if (heapIndex < 0)
{
return KernelResult.OutOfMemory;
}
}
else if (pagesCount != 0)
{ {
return KernelResult.OutOfMemory; return KernelResult.OutOfMemory;
} }
for (int blockIndex = _blockOrdersCount - 1; blockIndex >= 0; blockIndex--) for (int index = heapIndex; index >= 0; index--)
{ {
KMemoryRegionBlock block = _blocks[blockIndex]; ulong pagesPerAlloc = KPageHeap.GetBlockPagesCount(index);
ulong bestFitBlockSize = 1UL << block.Order; while (pagesCount >= pagesPerAlloc)
ulong blockPagesCount = bestFitBlockSize / KPageTableBase.PageSize;
// Check if this is the best fit for this page size.
// If so, try allocating as much requested pages as possible.
while (blockPagesCount <= pagesCount)
{ {
ulong address = AllocatePagesForOrder(blockIndex, backwards, bestFitBlockSize); ulong allocatedBlock = _pageHeap.AllocateBlock(index, random);
// The address being zero means that no free space was found on that order, if (allocatedBlock == 0)
// just give up and try with the next one.
if (address == 0)
{ {
break; break;
} }
// Add new allocated page(s) to the pages list. KernelResult result = pageList.AddRange(allocatedBlock, pagesPerAlloc);
// If an error occurs, then free all allocated pages and fail.
KernelResult result = pageList.AddRange(address, blockPagesCount);
if (result != KernelResult.Success) if (result != KernelResult.Success)
{ {
FreePages(address, blockPagesCount); FreePages(pageList);
_pageHeap.Free(allocatedBlock, pagesPerAlloc);
foreach (KPageNode pageNode in pageList)
{
FreePages(pageNode.Address, pageNode.PagesCount);
}
return result; return result;
} }
pagesCount -= blockPagesCount; pagesCount -= pagesPerAlloc;
} }
} }
// Success case, all requested pages were allocated successfully. if (pagesCount != 0)
if (pagesCount == 0)
{ {
return KernelResult.Success; FreePages(pageList);
return KernelResult.OutOfMemory;
} }
// Error case, free allocated pages and return out of memory. return KernelResult.Success;
foreach (KPageNode pageNode in pageList)
{
FreePages(pageNode.Address, pageNode.PagesCount);
}
pageList = null;
return KernelResult.OutOfMemory;
} }
private ulong AllocatePagesContiguousImpl(ulong pagesCount, bool backwards) private ulong AllocatePagesContiguousImpl(ulong pagesCount, ulong alignPages, bool random)
{ {
if (pagesCount == 0 || _blocks.Length < 1) int heapIndex = KPageHeap.GetAlignedBlockIndex(pagesCount, alignPages);
ulong allocatedBlock = _pageHeap.AllocateBlock(heapIndex, random);
if (allocatedBlock == 0)
{ {
return 0; return 0;
} }
int blockIndex = 0; ulong allocatedPages = KPageHeap.GetBlockPagesCount(heapIndex);
while ((1UL << _blocks[blockIndex].Order) / KPageTableBase.PageSize < pagesCount) if (allocatedPages > pagesCount)
{ {
if (++blockIndex >= _blocks.Length) _pageHeap.Free(allocatedBlock + pagesCount * KPageTableBase.PageSize, allocatedPages - pagesCount);
{
return 0;
}
} }
ulong tightestFitBlockSize = 1UL << _blocks[blockIndex].Order; return allocatedBlock;
ulong address = AllocatePagesForOrder(blockIndex, backwards, tightestFitBlockSize);
ulong requiredSize = pagesCount * KPageTableBase.PageSize;
if (address != 0 && tightestFitBlockSize > requiredSize)
{
FreePages(address + requiredSize, (tightestFitBlockSize - requiredSize) / KPageTableBase.PageSize);
}
return address;
} }
private ulong AllocatePagesForOrder(int blockIndex, bool backwards, ulong bestFitBlockSize) public void FreePage(ulong address)
{ {
ulong address = 0; lock (_pageHeap)
KMemoryRegionBlock block = null;
for (int currBlockIndex = blockIndex;
currBlockIndex < _blockOrdersCount && address == 0;
currBlockIndex++)
{ {
block = _blocks[currBlockIndex]; _pageHeap.Free(address, 1);
int index = 0;
bool zeroMask = false;
for (int level = 0; level < block.MaxLevel; level++)
{
long mask = block.Masks[level][index];
if (mask == 0)
{
zeroMask = true;
break;
}
if (backwards)
{
index = (index * 64 + 63) - BitOperations.LeadingZeroCount((ulong)mask);
}
else
{
index = index * 64 + BitOperations.LeadingZeroCount((ulong)BitUtils.ReverseBits64(mask));
}
}
if (block.SizeInBlocksTruncated <= (ulong)index || zeroMask)
{
continue;
}
block.FreeCount--;
int tempIdx = index;
for (int level = block.MaxLevel - 1; level >= 0; level--, tempIdx /= 64)
{
block.Masks[level][tempIdx / 64] &= ~(1L << (tempIdx & 63));
if (block.Masks[level][tempIdx / 64] != 0)
{
break;
}
}
address = block.StartAligned + ((ulong)index << block.Order);
} }
for (int currBlockIndex = blockIndex;
currBlockIndex < _blockOrdersCount && address == 0;
currBlockIndex++)
{
block = _blocks[currBlockIndex];
int index = 0;
bool zeroMask = false;
for (int level = 0; level < block.MaxLevel; level++)
{
long mask = block.Masks[level][index];
if (mask == 0)
{
zeroMask = true;
break;
}
if (backwards)
{
index = index * 64 + BitOperations.LeadingZeroCount((ulong)BitUtils.ReverseBits64(mask));
}
else
{
index = (index * 64 + 63) - BitOperations.LeadingZeroCount((ulong)mask);
}
}
if (block.SizeInBlocksTruncated <= (ulong)index || zeroMask)
{
continue;
}
block.FreeCount--;
int tempIdx = index;
for (int level = block.MaxLevel - 1; level >= 0; level--, tempIdx /= 64)
{
block.Masks[level][tempIdx / 64] &= ~(1L << (tempIdx & 63));
if (block.Masks[level][tempIdx / 64] != 0)
{
break;
}
}
address = block.StartAligned + ((ulong)index << block.Order);
}
if (address != 0)
{
// If we are using a larger order than best fit, then we should
// split it into smaller blocks.
ulong firstFreeBlockSize = 1UL << block.Order;
if (firstFreeBlockSize > bestFitBlockSize)
{
FreePages(address + bestFitBlockSize, (firstFreeBlockSize - bestFitBlockSize) / KPageTableBase.PageSize);
}
}
return address;
} }
private void FreePages(ulong address, ulong pagesCount) public void FreePages(KPageList pageList)
{ {
lock (_blocks) lock (_pageHeap)
{ {
ulong endAddr = address + pagesCount * KPageTableBase.PageSize; foreach (KPageNode pageNode in pageList)
int blockIndex = _blockOrdersCount - 1;
ulong addressRounded = 0;
ulong endAddrTruncated = 0;
for (; blockIndex >= 0; blockIndex--)
{ {
KMemoryRegionBlock allocInfo = _blocks[blockIndex]; _pageHeap.Free(pageNode.Address, pageNode.PagesCount);
int blockSize = 1 << allocInfo.Order;
addressRounded = BitUtils.AlignUp (address, blockSize);
endAddrTruncated = BitUtils.AlignDown(endAddr, blockSize);
if (addressRounded < endAddrTruncated)
{
break;
}
} }
}
}
void FreeRegion(ulong currAddress) public void FreePages(ulong address, ulong pagesCount)
{ {
for (int currBlockIndex = blockIndex; lock (_pageHeap)
currBlockIndex < _blockOrdersCount && currAddress != 0; {
currBlockIndex++) _pageHeap.Free(address, pagesCount);
{
KMemoryRegionBlock block = _blocks[currBlockIndex];
block.FreeCount++;
ulong freedBlocks = (currAddress - block.StartAligned) >> block.Order;
int index = (int)freedBlocks;
for (int level = block.MaxLevel - 1; level >= 0; level--, index /= 64)
{
long mask = block.Masks[level][index / 64];
block.Masks[level][index / 64] = mask | (1L << (index & 63));
if (mask != 0)
{
break;
}
}
int blockSizeDelta = 1 << (block.NextOrder - block.Order);
int freedBlocksTruncated = BitUtils.AlignDown((int)freedBlocks, blockSizeDelta);
if (!block.TryCoalesce(freedBlocksTruncated, blockSizeDelta))
{
break;
}
currAddress = block.StartAligned + ((ulong)freedBlocksTruncated << block.Order);
}
}
// Free inside aligned region.
ulong baseAddress = addressRounded;
while (baseAddress < endAddrTruncated)
{
ulong blockSize = 1UL << _blocks[blockIndex].Order;
FreeRegion(baseAddress);
baseAddress += blockSize;
}
int nextBlockIndex = blockIndex - 1;
// Free region between Address and aligned region start.
baseAddress = addressRounded;
for (blockIndex = nextBlockIndex; blockIndex >= 0; blockIndex--)
{
ulong blockSize = 1UL << _blocks[blockIndex].Order;
while (baseAddress - blockSize >= address)
{
baseAddress -= blockSize;
FreeRegion(baseAddress);
}
}
// Free region between aligned region end and End Address.
baseAddress = endAddrTruncated;
for (blockIndex = nextBlockIndex; blockIndex >= 0; blockIndex--)
{
ulong blockSize = 1UL << _blocks[blockIndex].Order;
while (baseAddress + blockSize <= endAddr)
{
FreeRegion(baseAddress);
baseAddress += blockSize;
}
}
} }
} }
public ulong GetFreePages() public ulong GetFreePages()
{ {
lock (_blocks) lock (_pageHeap)
{ {
return GetFreePagesImpl(); return _pageHeap.GetFreePagesCount();
} }
} }
private ulong GetFreePagesImpl()
{
ulong availablePages = 0;
for (int blockIndex = 0; blockIndex < _blockOrdersCount; blockIndex++)
{
KMemoryRegionBlock block = _blocks[blockIndex];
ulong blockPagesCount = (1UL << block.Order) / KPageTableBase.PageSize;
availablePages += blockPagesCount * block.FreeCount;
}
return availablePages;
}
public void IncrementPagesReferenceCount(ulong address, ulong pagesCount) public void IncrementPagesReferenceCount(ulong address, ulong pagesCount)
{ {
ulong index = GetPageOffset(address); ulong index = GetPageOffset(address);

View File

@ -0,0 +1,298 @@
using Ryujinx.Common;
using System;
using System.Numerics;
namespace Ryujinx.HLE.HOS.Kernel.Memory
{
class KPageBitmap
{
private struct RandomNumberGenerator
{
private uint _entropy;
private uint _bitsAvailable;
private void RefreshEntropy()
{
_entropy = 0;
_bitsAvailable = sizeof(uint) * 8;
}
private bool GenerateRandomBit()
{
if (_bitsAvailable == 0)
{
RefreshEntropy();
}
bool bit = (_entropy & 1) != 0;
_entropy >>= 1;
_bitsAvailable--;
return bit;
}
public int SelectRandomBit(ulong bitmap)
{
int selected = 0;
int bitsCount = UInt64BitSize / 2;
ulong mask = (1UL << bitsCount) - 1;
while (bitsCount != 0)
{
ulong low = bitmap & mask;
ulong high = (bitmap >> bitsCount) & mask;
bool chooseLow;
if (high == 0)
{
chooseLow = true;
}
else if (low == 0)
{
chooseLow = false;
}
else
{
chooseLow = GenerateRandomBit();
}
if (chooseLow)
{
bitmap = low;
}
else
{
bitmap = high;
selected += bitsCount;
}
bitsCount /= 2;
mask >>= bitsCount;
}
return selected;
}
}
private const int UInt64BitSize = sizeof(ulong) * 8;
private const int MaxDepth = 4;
private readonly RandomNumberGenerator _rng;
private readonly ArraySegment<ulong>[] _bitStorages;
private int _usedDepths;
public int BitsCount { get; private set; }
public int HighestDepthIndex => _usedDepths - 1;
public KPageBitmap()
{
_rng = new RandomNumberGenerator();
_bitStorages = new ArraySegment<ulong>[MaxDepth];
}
public ArraySegment<ulong> Initialize(ArraySegment<ulong> storage, ulong size)
{
_usedDepths = GetRequiredDepth(size);
for (int depth = HighestDepthIndex; depth >= 0; depth--)
{
_bitStorages[depth] = storage;
size = BitUtils.DivRoundUp(size, UInt64BitSize);
storage = storage.Slice((int)size);
}
return storage;
}
public ulong FindFreeBlock(bool random)
{
ulong offset = 0;
int depth = 0;
if (random)
{
do
{
ulong v = _bitStorages[depth][(int)offset];
if (v == 0)
{
return ulong.MaxValue;
}
offset = offset * UInt64BitSize + (ulong)_rng.SelectRandomBit(v);
}
while (++depth < _usedDepths);
}
else
{
do
{
ulong v = _bitStorages[depth][(int)offset];
if (v == 0)
{
return ulong.MaxValue;
}
offset = offset * UInt64BitSize + (ulong)BitOperations.TrailingZeroCount(v);
}
while (++depth < _usedDepths);
}
return offset;
}
public void SetBit(ulong offset)
{
SetBit(HighestDepthIndex, offset);
BitsCount++;
}
public void ClearBit(ulong offset)
{
ClearBit(HighestDepthIndex, offset);
BitsCount--;
}
public bool ClearRange(ulong offset, int count)
{
int depth = HighestDepthIndex;
var bits = _bitStorages[depth];
int bitInd = (int)(offset / UInt64BitSize);
if (count < UInt64BitSize)
{
int shift = (int)(offset % UInt64BitSize);
ulong mask = ((1UL << count) - 1) << shift;
ulong v = bits[bitInd];
if ((v & mask) != mask)
{
return false;
}
v &= ~mask;
bits[bitInd] = v;
if (v == 0)
{
ClearBit(depth - 1, (ulong)bitInd);
}
}
else
{
int remaining = count;
int i = 0;
do
{
if (bits[bitInd + i++] != ulong.MaxValue)
{
return false;
}
remaining -= UInt64BitSize;
}
while (remaining > 0);
remaining = count;
i = 0;
do
{
bits[bitInd + i] = 0;
ClearBit(depth - 1, (ulong)(bitInd + i));
i++;
remaining -= UInt64BitSize;
}
while (remaining > 0);
}
BitsCount -= count;
return true;
}
private void SetBit(int depth, ulong offset)
{
while (depth >= 0)
{
int ind = (int)(offset / UInt64BitSize);
int which = (int)(offset % UInt64BitSize);
ulong mask = 1UL << which;
ulong v = _bitStorages[depth][ind];
_bitStorages[depth][ind] = v | mask;
if (v != 0)
{
break;
}
offset = (ulong)ind;
depth--;
}
}
private void ClearBit(int depth, ulong offset)
{
while (depth >= 0)
{
int ind = (int)(offset / UInt64BitSize);
int which = (int)(offset % UInt64BitSize);
ulong mask = 1UL << which;
ulong v = _bitStorages[depth][ind];
v &= ~mask;
_bitStorages[depth][ind] = v;
if (v != 0)
{
break;
}
offset = (ulong)ind;
depth--;
}
}
private static int GetRequiredDepth(ulong regionSize)
{
int depth = 0;
do
{
regionSize /= UInt64BitSize;
depth++;
}
while (regionSize != 0);
return depth;
}
public static int CalculateManagementOverheadSize(ulong regionSize)
{
int overheadBits = 0;
for (int depth = GetRequiredDepth(regionSize) - 1; depth >= 0; depth--)
{
regionSize = BitUtils.DivRoundUp(regionSize, UInt64BitSize);
overheadBits += (int)regionSize;
}
return overheadBits * sizeof(ulong);
}
}
}

View File

@ -0,0 +1,283 @@
using Ryujinx.Common;
using System;
namespace Ryujinx.HLE.HOS.Kernel.Memory
{
class KPageHeap
{
private class Block
{
private KPageBitmap _bitmap = new KPageBitmap();
private ulong _heapAddress;
private ulong _endOffset;
public int Shift { get; private set; }
public int NextShift { get; private set; }
public ulong Size => 1UL << Shift;
public int PagesCount => (int)(Size / KPageTableBase.PageSize);
public int FreeBlocksCount => _bitmap.BitsCount;
public int FreePagesCount => FreeBlocksCount * PagesCount;
public ArraySegment<ulong> Initialize(ulong address, ulong size, int blockShift, int nextBlockShift, ArraySegment<ulong> bitStorage)
{
Shift = blockShift;
NextShift = nextBlockShift;
ulong endAddress = address + size;
ulong align = nextBlockShift != 0
? 1UL << nextBlockShift
: 1UL << blockShift;
address = BitUtils.AlignDown(address, align);
endAddress = BitUtils.AlignUp (endAddress, align);
_heapAddress = address;
_endOffset = (endAddress - address) / (1UL << blockShift);
return _bitmap.Initialize(bitStorage, _endOffset);
}
public ulong PushBlock(ulong address)
{
ulong offset = (address - _heapAddress) >> Shift;
_bitmap.SetBit(offset);
if (NextShift != 0)
{
int diff = 1 << (NextShift - Shift);
offset = BitUtils.AlignDown(offset, diff);
if (_bitmap.ClearRange(offset, diff))
{
return _heapAddress + (offset << Shift);
}
}
return 0;
}
public ulong PopBlock(bool random)
{
long sOffset = (long)_bitmap.FindFreeBlock(random);
if (sOffset < 0L)
{
return 0;
}
ulong offset = (ulong)sOffset;
_bitmap.ClearBit(offset);
return _heapAddress + (offset << Shift);
}
public static int CalculateManagementOverheadSize(ulong regionSize, int currBlockShift, int nextBlockShift)
{
ulong currBlockSize = 1UL << currBlockShift;
ulong nextBlockSize = 1UL << nextBlockShift;
ulong align = nextBlockShift != 0 ? nextBlockSize : currBlockSize;
return KPageBitmap.CalculateManagementOverheadSize((align * 2 + BitUtils.AlignUp(regionSize, align)) / currBlockSize);
}
}
private static readonly int[] _memoryBlockPageShifts = new int[] { 12, 16, 21, 22, 25, 29, 30 };
private readonly ulong _heapAddress;
private readonly ulong _heapSize;
private ulong _usedSize;
private readonly int _blocksCount;
private readonly Block[] _blocks;
public KPageHeap(ulong address, ulong size) : this(address, size, _memoryBlockPageShifts)
{
}
public KPageHeap(ulong address, ulong size, int[] blockShifts)
{
_heapAddress = address;
_heapSize = size;
_blocksCount = blockShifts.Length;
_blocks = new Block[_memoryBlockPageShifts.Length];
var currBitmapStorage = new ArraySegment<ulong>(new ulong[CalculateManagementOverheadSize(size, blockShifts)]);
for (int i = 0; i < blockShifts.Length; i++)
{
int currBlockShift = blockShifts[i];
int nextBlockShift = i != blockShifts.Length - 1 ? blockShifts[i + 1] : 0;
_blocks[i] = new Block();
currBitmapStorage = _blocks[i].Initialize(address, size, currBlockShift, nextBlockShift, currBitmapStorage);
}
}
public void UpdateUsedSize()
{
_usedSize = _heapSize - (GetFreePagesCount() * KPageTableBase.PageSize);
}
public ulong GetFreePagesCount()
{
ulong freeCount = 0;
for (int i = 0; i < _blocksCount; i++)
{
freeCount += (ulong)_blocks[i].FreePagesCount;
}
return freeCount;
}
public ulong AllocateBlock(int index, bool random)
{
ulong neededSize = _blocks[index].Size;
for (int i = index; i < _blocksCount; i++)
{
ulong address = _blocks[i].PopBlock(random);
if (address != 0)
{
ulong allocatedSize = _blocks[i].Size;
if (allocatedSize > neededSize)
{
Free(address + neededSize, (allocatedSize - neededSize) / KPageTableBase.PageSize);
}
return address;
}
}
return 0;
}
private void FreeBlock(ulong block, int index)
{
do
{
block = _blocks[index++].PushBlock(block);
}
while (block != 0);
}
public void Free(ulong address, ulong pagesCount)
{
if (pagesCount == 0)
{
return;
}
int bigIndex = _blocksCount - 1;
ulong start = address;
ulong end = address + pagesCount * KPageTableBase.PageSize;
ulong beforeStart = start;
ulong beforeEnd = start;
ulong afterStart = end;
ulong afterEnd = end;
while (bigIndex >= 0)
{
ulong blockSize = _blocks[bigIndex].Size;
ulong bigStart = BitUtils.AlignUp (start, blockSize);
ulong bigEnd = BitUtils.AlignDown(end, blockSize);
if (bigStart < bigEnd)
{
for (ulong block = bigStart; block < bigEnd; block += blockSize)
{
FreeBlock(block, bigIndex);
}
beforeEnd = bigStart;
afterStart = bigEnd;
break;
}
bigIndex--;
}
for (int i = bigIndex - 1; i >= 0; i--)
{
ulong blockSize = _blocks[i].Size;
while (beforeStart + blockSize <= beforeEnd)
{
beforeEnd -= blockSize;
FreeBlock(beforeEnd, i);
}
}
for (int i = bigIndex - 1; i >= 0; i--)
{
ulong blockSize = _blocks[i].Size;
while (afterStart + blockSize <= afterEnd)
{
FreeBlock(afterStart, i);
afterStart += blockSize;
}
}
}
public static int GetAlignedBlockIndex(ulong pagesCount, ulong alignPages)
{
ulong targetPages = Math.Max(pagesCount, alignPages);
for (int i = 0; i < _memoryBlockPageShifts.Length; i++)
{
if (targetPages <= GetBlockPagesCount(i))
{
return i;
}
}
return -1;
}
public static int GetBlockIndex(ulong pagesCount)
{
for (int i = _memoryBlockPageShifts.Length - 1; i >= 0; i--)
{
if (pagesCount >= GetBlockPagesCount(i))
{
return i;
}
}
return -1;
}
public static ulong GetBlockSize(int index)
{
return 1UL << _memoryBlockPageShifts[index];
}
public static ulong GetBlockPagesCount(int index)
{
return GetBlockSize(index) / KPageTableBase.PageSize;
}
private static int CalculateManagementOverheadSize(ulong regionSize, int[] blockShifts)
{
int overheadSize = 0;
for (int i = 0; i < blockShifts.Length; i++)
{
int currBlockShift = blockShifts[i];
int nextBlockShift = i != blockShifts.Length - 1 ? blockShifts[i + 1] : 0;
overheadSize += Block.CalculateManagementOverheadSize(regionSize, currBlockShift, nextBlockShift);
}
return BitUtils.AlignUp(overheadSize, KPageTableBase.PageSize);
}
}
}

View File

@ -555,7 +555,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
{ {
KMemoryRegionManager region = GetMemoryRegionManager(); KMemoryRegionManager region = GetMemoryRegionManager();
KernelResult result = region.AllocatePages(pagesCount, _aslrDisabled, out KPageList pageList); KernelResult result = region.AllocatePages(out KPageList pageList, pagesCount);
if (result != KernelResult.Success) if (result != KernelResult.Success)
{ {
@ -712,7 +712,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
KMemoryRegionManager region = GetMemoryRegionManager(); KMemoryRegionManager region = GetMemoryRegionManager();
KernelResult result = region.AllocatePages(pagesCount, _aslrDisabled, out KPageList pageList); KernelResult result = region.AllocatePages(out KPageList pageList, pagesCount);
using var _ = new OnScopeExit(() => pageList.DecrementPagesReferenceCount(Context.MemoryManager)); using var _ = new OnScopeExit(() => pageList.DecrementPagesReferenceCount(Context.MemoryManager));
@ -1276,7 +1276,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
KMemoryRegionManager region = GetMemoryRegionManager(); KMemoryRegionManager region = GetMemoryRegionManager();
KernelResult result = region.AllocatePages(remainingPages, _aslrDisabled, out KPageList pageList); KernelResult result = region.AllocatePages(out KPageList pageList, remainingPages);
using var _ = new OnScopeExit(() => pageList.DecrementPagesReferenceCount(Context.MemoryManager)); using var _ = new OnScopeExit(() => pageList.DecrementPagesReferenceCount(Context.MemoryManager));

View File

@ -735,11 +735,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
ulong argsPtr, ulong argsPtr,
ulong stackTop, ulong stackTop,
int priority, int priority,
int cpuCore) int cpuCore,
ThreadStart customThreadStart = null)
{ {
lock (_processLock) lock (_processLock)
{ {
return thread.Initialize(entrypoint, argsPtr, stackTop, priority, cpuCore, this, ThreadType.User, null); return thread.Initialize(entrypoint, argsPtr, stackTop, priority, cpuCore, this, ThreadType.User, customThreadStart);
} }
} }
@ -965,6 +966,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
SignalExitToDebugExited(); SignalExitToDebugExited();
SignalExit(); SignalExit();
} }
KernelStatic.GetCurrentThread().Exit();
} }
private void UnpauseAndTerminateAllThreadsExcept(KThread currentThread) private void UnpauseAndTerminateAllThreadsExcept(KThread currentThread)
@ -980,7 +983,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
foreach (KThread thread in _threads) foreach (KThread thread in _threads)
{ {
if ((thread.SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.TerminationPending) if (thread != currentThread && (thread.SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.TerminationPending)
{ {
thread.PrepareForTermination(); thread.PrepareForTermination();
} }

View File

@ -2350,6 +2350,18 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
[PointerSized] ulong stackTop, [PointerSized] ulong stackTop,
int priority, int priority,
int cpuCore) int cpuCore)
{
return CreateThread(out handle, entrypoint, argsPtr, stackTop, priority, cpuCore, null);
}
public KernelResult CreateThread(
out int handle,
ulong entrypoint,
ulong argsPtr,
ulong stackTop,
int priority,
int cpuCore,
ThreadStart customThreadStart)
{ {
handle = 0; handle = 0;
@ -2386,7 +2398,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
argsPtr, argsPtr,
stackTop, stackTop,
priority, priority,
cpuCore); cpuCore,
customThreadStart);
if (result == KernelResult.Success) if (result == KernelResult.Success)
{ {

View File

@ -90,7 +90,7 @@ namespace Ryujinx.HLE.HOS
KMemoryRegionManager region = context.MemoryManager.MemoryRegions[(int)memoryRegion]; KMemoryRegionManager region = context.MemoryManager.MemoryRegions[(int)memoryRegion];
KernelResult result = region.AllocatePages((ulong)codePagesCount, false, out KPageList pageList); KernelResult result = region.AllocatePages(out KPageList pageList, (ulong)codePagesCount);
if (result != KernelResult.Success) if (result != KernelResult.Success)
{ {

View File

@ -1,3 +1,4 @@
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel; using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Common;
@ -38,15 +39,15 @@ namespace Ryujinx.HLE.HOS.Services
private readonly Dictionary<int, Func<IpcService>> _ports = new Dictionary<int, Func<IpcService>>(); private readonly Dictionary<int, Func<IpcService>> _ports = new Dictionary<int, Func<IpcService>>();
public ManualResetEvent InitDone { get; } public ManualResetEvent InitDone { get; }
public Func<IpcService> SmObjectFactory { get; }
public string Name { get; } public string Name { get; }
public Func<IpcService> SmObjectFactory { get; }
public ServerBase(KernelContext context, string name, Func<IpcService> smObjectFactory = null) public ServerBase(KernelContext context, string name, Func<IpcService> smObjectFactory = null)
{ {
InitDone = new ManualResetEvent(false); InitDone = new ManualResetEvent(false);
_context = context;
Name = name; Name = name;
SmObjectFactory = smObjectFactory; SmObjectFactory = smObjectFactory;
_context = context;
const ProcessCreationFlags flags = const ProcessCreationFlags flags =
ProcessCreationFlags.EnableAslr | ProcessCreationFlags.EnableAslr |
@ -56,7 +57,7 @@ namespace Ryujinx.HLE.HOS.Services
ProcessCreationInfo creationInfo = new ProcessCreationInfo("Service", 1, 0, 0x8000000, 1, flags, 0, 0); ProcessCreationInfo creationInfo = new ProcessCreationInfo("Service", 1, 0, 0x8000000, 1, flags, 0, 0);
KernelStatic.StartInitialProcess(context, creationInfo, DefaultCapabilities, 44, ServerLoop); KernelStatic.StartInitialProcess(context, creationInfo, DefaultCapabilities, 44, Main);
} }
private void AddPort(int serverPortHandle, Func<IpcService> objectFactory) private void AddPort(int serverPortHandle, Func<IpcService> objectFactory)
@ -80,6 +81,11 @@ namespace Ryujinx.HLE.HOS.Services
_sessions.Add(serverSessionHandle, obj); _sessions.Add(serverSessionHandle, obj);
} }
private void Main()
{
ServerLoop();
}
private void ServerLoop() private void ServerLoop()
{ {
_selfProcess = KernelStatic.GetCurrentProcess(); _selfProcess = KernelStatic.GetCurrentProcess();

View File

@ -8,7 +8,6 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
{ {
private ulong _value; private ulong _value;
private readonly EventFdFlags _flags; private readonly EventFdFlags _flags;
private AutoResetEvent _event;
private object _lock = new object(); private object _lock = new object();
@ -19,9 +18,13 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
public EventFileDescriptor(ulong value, EventFdFlags flags) public EventFileDescriptor(ulong value, EventFdFlags flags)
{ {
// FIXME: We should support blocking operations.
// Right now they can't be supported because it would cause the
// service to lock up as we only have one thread processing requests.
flags |= EventFdFlags.NonBlocking;
_value = value; _value = value;
_flags = flags; _flags = flags;
_event = new AutoResetEvent(false);
WriteEvent = new ManualResetEvent(true); WriteEvent = new ManualResetEvent(true);
ReadEvent = new ManualResetEvent(true); ReadEvent = new ManualResetEvent(true);
@ -31,7 +34,6 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
public void Dispose() public void Dispose()
{ {
_event.Dispose();
WriteEvent.Dispose(); WriteEvent.Dispose();
ReadEvent.Dispose(); ReadEvent.Dispose();
} }
@ -57,7 +59,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
{ {
while (_value == 0) while (_value == 0)
{ {
_event.WaitOne(); Monitor.Wait(_lock);
} }
} }
else else
@ -106,7 +108,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
{ {
if (Blocking) if (Blocking)
{ {
_event.WaitOne(); Monitor.Wait(_lock);
} }
else else
{ {
@ -119,7 +121,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
writeSize = sizeof(ulong); writeSize = sizeof(ulong);
_value += count; _value += count;
_event.Set(); Monitor.Pulse(_lock);
WriteEvent.Set(); WriteEvent.Set();

View File

@ -2,7 +2,10 @@ using Ryujinx.Common.Logging;
using Ryujinx.Cpu; using Ryujinx.Cpu;
using Ryujinx.HLE.HOS.Services.Time.TimeZone; using Ryujinx.HLE.HOS.Services.Time.TimeZone;
using Ryujinx.HLE.Utilities; using Ryujinx.HLE.Utilities;
using Ryujinx.Memory;
using System; using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text; using System.Text;
namespace Ryujinx.HLE.HOS.Services.Time.StaticService namespace Ryujinx.HLE.HOS.Services.Time.StaticService
@ -100,15 +103,12 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
string locationName = StringUtils.ReadInlinedAsciiString(context.RequestData, 0x24); string locationName = StringUtils.ReadInlinedAsciiString(context.RequestData, 0x24);
ResultCode resultCode = _timeZoneContentManager.LoadTimeZoneRule(out TimeZoneRule rules, locationName); using (WritableRegion region = context.Memory.GetWritableRegion(bufferPosition, Unsafe.SizeOf<TimeZoneRule>()))
// Write TimeZoneRule if success
if (resultCode == ResultCode.Success)
{ {
MemoryHelper.Write(context.Memory, bufferPosition, rules); ref TimeZoneRule rules = ref MemoryMarshal.Cast<byte, TimeZoneRule>(region.Memory.Span)[0];
}
return resultCode; return _timeZoneContentManager.LoadTimeZoneRule(ref rules, locationName);
}
} }
[CommandHipc(100)] [CommandHipc(100)]

View File

@ -4,9 +4,12 @@ using Ryujinx.Cpu;
using Ryujinx.HLE.HOS.Services.Time.Clock; using Ryujinx.HLE.HOS.Services.Time.Clock;
using Ryujinx.HLE.HOS.Services.Time.TimeZone; using Ryujinx.HLE.HOS.Services.Time.TimeZone;
using Ryujinx.HLE.Utilities; using Ryujinx.HLE.Utilities;
using Ryujinx.Memory;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Time.StaticService namespace Ryujinx.HLE.HOS.Services.Time.StaticService
{ {
@ -165,11 +168,11 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
using (MemoryStream timeZoneBinaryStream = new MemoryStream(temp)) using (MemoryStream timeZoneBinaryStream = new MemoryStream(temp))
{ {
result = _timeZoneManager.ParseTimeZoneRuleBinary(out TimeZoneRule timeZoneRule, timeZoneBinaryStream); using (WritableRegion region = context.Memory.GetWritableRegion(timeZoneRuleBufferPosition, Unsafe.SizeOf<TimeZoneRule>()))
if (result == ResultCode.Success)
{ {
MemoryHelper.Write(context.Memory, timeZoneRuleBufferPosition, timeZoneRule); ref TimeZoneRule rule = ref MemoryMarshal.Cast<byte, TimeZoneRule>(region.Memory.Span)[0];
result = _timeZoneManager.ParseTimeZoneRuleBinary(ref rule, timeZoneBinaryStream);
} }
} }
@ -199,9 +202,9 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
throw new InvalidOperationException(); throw new InvalidOperationException();
} }
TimeZoneRule rules = MemoryHelper.Read<TimeZoneRule>(context.Memory, bufferPosition); ReadOnlySpan<TimeZoneRule> rules = MemoryMarshal.Cast<byte, TimeZoneRule>(context.Memory.GetSpan(bufferPosition, (int)bufferSize));
ResultCode resultCode = _timeZoneManager.ToCalendarTime(rules, posixTime, out CalendarInfo calendar); ResultCode resultCode = _timeZoneManager.ToCalendarTime(in rules[0], posixTime, out CalendarInfo calendar);
if (resultCode == 0) if (resultCode == 0)
{ {
@ -244,9 +247,9 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
throw new InvalidOperationException(); throw new InvalidOperationException();
} }
TimeZoneRule rules = MemoryHelper.Read<TimeZoneRule>(context.Memory, inBufferPosition); ReadOnlySpan<TimeZoneRule> rules = MemoryMarshal.Cast<byte, TimeZoneRule>(context.Memory.GetSpan(inBufferPosition, (int)inBufferSize));
ResultCode resultCode = _timeZoneManager.ToPosixTime(rules, calendarTime, out long posixTime); ResultCode resultCode = _timeZoneManager.ToPosixTime(in rules[0], calendarTime, out long posixTime);
if (resultCode == ResultCode.Success) if (resultCode == ResultCode.Success)
{ {

View File

@ -1,4 +1,5 @@
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Memory;
using Ryujinx.Common.Utilities; using Ryujinx.Common.Utilities;
using Ryujinx.HLE.Utilities; using Ryujinx.HLE.Utilities;
using System; using System;
@ -38,7 +39,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
new int[] { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } new int[] { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
}; };
private const string TimeZoneDefaultRule = ",M4.1.0,M10.5.0"; private static readonly byte[] TimeZoneDefaultRule = Encoding.ASCII.GetBytes(",M4.1.0,M10.5.0");
[StructLayout(LayoutKind.Sequential, Pack = 0x4, Size = 0x10)] [StructLayout(LayoutKind.Sequential, Pack = 0x4, Size = 0x10)]
private struct CalendarTimeInternal private struct CalendarTimeInternal
@ -133,7 +134,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
return (t1 - t0) == SecondsPerRepeat; return (t1 - t0) == SecondsPerRepeat;
} }
private static bool TimeTypeEquals(TimeZoneRule outRules, byte aIndex, byte bIndex) private static bool TimeTypeEquals(in TimeZoneRule outRules, byte aIndex, byte bIndex)
{ {
if (aIndex < 0 || aIndex >= outRules.TypeCount || bIndex < 0 || bIndex >= outRules.TypeCount) if (aIndex < 0 || aIndex >= outRules.TypeCount || bIndex < 0 || bIndex >= outRules.TypeCount)
{ {
@ -150,7 +151,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
StringUtils.CompareCStr(outRules.Chars[a.AbbreviationListIndex..], outRules.Chars[b.AbbreviationListIndex..]) == 0; StringUtils.CompareCStr(outRules.Chars[a.AbbreviationListIndex..], outRules.Chars[b.AbbreviationListIndex..]) == 0;
} }
private static int GetQZName(ReadOnlySpan<char> name, int namePosition, char delimiter) private static int GetQZName(ReadOnlySpan<byte> name, int namePosition, char delimiter)
{ {
int i = namePosition; int i = namePosition;
@ -162,13 +163,13 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
return i; return i;
} }
private static int GetTZName(char[] name, int namePosition) private static int GetTZName(ReadOnlySpan<byte> name, int namePosition)
{ {
int i = namePosition; int i = namePosition;
char c; char c;
while ((c = name[i]) != '\0' && !char.IsDigit(c) && c != ',' && c != '-' && c != '+') while ((c = (char)name[i]) != '\0' && !char.IsDigit(c) && c != ',' && c != '-' && c != '+')
{ {
i++; i++;
} }
@ -176,7 +177,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
return i; return i;
} }
private static bool GetNum(char[] name, ref int namePosition, out int num, int min, int max) private static bool GetNum(ReadOnlySpan<byte> name, ref int namePosition, out int num, int min, int max)
{ {
num = 0; num = 0;
@ -185,7 +186,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
return false; return false;
} }
char c = name[namePosition]; char c = (char)name[namePosition];
if (!char.IsDigit(c)) if (!char.IsDigit(c))
{ {
@ -205,7 +206,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
return false; return false;
} }
c = name[namePosition]; c = (char)name[namePosition];
} }
while (char.IsDigit(c)); while (char.IsDigit(c));
@ -217,7 +218,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
return true; return true;
} }
private static bool GetSeconds(char[] name, ref int namePosition, out int seconds) private static bool GetSeconds(ReadOnlySpan<byte> name, ref int namePosition, out int seconds)
{ {
seconds = 0; seconds = 0;
@ -266,7 +267,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
return true; return true;
} }
private static bool GetOffset(char[] name, ref int namePosition, ref int offset) private static bool GetOffset(ReadOnlySpan<byte> name, ref int namePosition, ref int offset)
{ {
bool isNegative = false; bool isNegative = false;
@ -304,7 +305,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
return true; return true;
} }
private static bool GetRule(char[] name, ref int namePosition, out Rule rule) private static bool GetRule(ReadOnlySpan<byte> name, ref int namePosition, out Rule rule)
{ {
rule = new Rule(); rule = new Rule();
@ -347,7 +348,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
isValid = GetNum(name, ref namePosition, out rule.Day, 0, DaysPerWekk - 1); isValid = GetNum(name, ref namePosition, out rule.Day, 0, DaysPerWekk - 1);
} }
else if (char.IsDigit(name[namePosition])) else if (char.IsDigit((char)name[namePosition]))
{ {
rule.Type = RuleType.DayOfYear; rule.Type = RuleType.DayOfYear;
isValid = GetNum(name, ref namePosition, out rule.Day, 0, DaysPerLYear - 1); isValid = GetNum(name, ref namePosition, out rule.Day, 0, DaysPerLYear - 1);
@ -385,19 +386,13 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
return 0; return 0;
} }
private static bool ParsePosixName(ReadOnlySpan<char> name, out TimeZoneRule outRules, bool lastDitch) private static bool ParsePosixName(ReadOnlySpan<byte> name, ref TimeZoneRule outRules, bool lastDitch)
{ {
outRules = new TimeZoneRule outRules = new TimeZoneRule();
{
Ats = new long[TzMaxTimes],
Types = new byte[TzMaxTimes],
Ttis = new TimeTypeInfo[TzMaxTypes],
Chars = new char[TzCharsArraySize]
};
int stdLen; int stdLen;
ReadOnlySpan<char> stdName = name; ReadOnlySpan<byte> stdName = name;
int namePosition = 0; int namePosition = 0;
int stdOffset = 0; int stdOffset = 0;
@ -428,7 +423,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
} }
else else
{ {
namePosition = GetTZName(name.ToArray(), namePosition); namePosition = GetTZName(name, namePosition);
stdLen = namePosition; stdLen = namePosition;
} }
@ -449,7 +444,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
int destLen = 0; int destLen = 0;
int dstOffset = 0; int dstOffset = 0;
ReadOnlySpan<char> destName = name.Slice(namePosition); ReadOnlySpan<byte> destName = name.Slice(namePosition);
if (TzCharsArraySize < charCount) if (TzCharsArraySize < charCount)
{ {
@ -476,7 +471,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
else else
{ {
destName = name.Slice(namePosition); destName = name.Slice(namePosition);
namePosition = GetTZName(name.ToArray(), namePosition); namePosition = GetTZName(name, namePosition);
destLen = namePosition; destLen = namePosition;
} }
@ -507,7 +502,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
if (name[namePosition] == '\0') if (name[namePosition] == '\0')
{ {
name = TimeZoneDefaultRule.ToCharArray(); name = TimeZoneDefaultRule;
namePosition = 0; namePosition = 0;
} }
@ -515,7 +510,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
{ {
namePosition++; namePosition++;
bool IsRuleValid = GetRule(name.ToArray(), ref namePosition, out Rule start); bool IsRuleValid = GetRule(name, ref namePosition, out Rule start);
if (!IsRuleValid) if (!IsRuleValid)
{ {
return false; return false;
@ -526,7 +521,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
return false; return false;
} }
IsRuleValid = GetRule(name.ToArray(), ref namePosition, out Rule end); IsRuleValid = GetRule(name, ref namePosition, out Rule end);
if (!IsRuleValid) if (!IsRuleValid)
{ {
return false; return false;
@ -738,7 +733,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
} }
charsPosition += stdLen; charsPosition += stdLen;
outRules.Chars[charsPosition++] = '\0'; outRules.Chars[charsPosition++] = 0;
if (destLen != 0) if (destLen != 0)
{ {
@ -746,7 +741,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
{ {
outRules.Chars[charsPosition + i] = destName[i]; outRules.Chars[charsPosition + i] = destName[i];
} }
outRules.Chars[charsPosition + destLen] = '\0'; outRules.Chars[charsPosition + destLen] = 0;
} }
return true; return true;
@ -882,20 +877,14 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
} }
} }
internal static bool ParsePosixName(string name, out TimeZoneRule outRules) internal static bool ParsePosixName(string name, ref TimeZoneRule outRules)
{ {
return ParsePosixName(name.ToCharArray(), out outRules, false); return ParsePosixName(Encoding.ASCII.GetBytes(name), ref outRules, false);
} }
internal static bool ParseTimeZoneBinary(out TimeZoneRule outRules, Stream inputData) internal static bool ParseTimeZoneBinary(ref TimeZoneRule outRules, Stream inputData)
{ {
outRules = new TimeZoneRule outRules = new TimeZoneRule();
{
Ats = new long[TzMaxTimes],
Types = new byte[TzMaxTimes],
Ttis = new TimeTypeInfo[TzMaxTypes],
Chars = new char[TzCharsArraySize]
};
BinaryReader reader = new BinaryReader(inputData); BinaryReader reader = new BinaryReader(inputData);
@ -1020,10 +1009,10 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
outRules.Ttis[i] = ttis; outRules.Ttis[i] = ttis;
} }
Encoding.ASCII.GetChars(p[..outRules.CharCount].ToArray()).CopyTo(outRules.Chars.AsSpan()); p[..outRules.CharCount].CopyTo(outRules.Chars);
p = p[outRules.CharCount..]; p = p[outRules.CharCount..];
outRules.Chars[outRules.CharCount] = '\0'; outRules.Chars[outRules.CharCount] = 0;
for (int i = 0; i < outRules.TypeCount; i++) for (int i = 0; i < outRules.TypeCount; i++)
{ {
@ -1077,27 +1066,30 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
throw new InvalidOperationException(); throw new InvalidOperationException();
} }
char[] tempName = new char[TzNameMax + 1]; byte[] tempName = new byte[TzNameMax + 1];
Array.Copy(workBuffer, position, tempName, 0, nRead); Array.Copy(workBuffer, position, tempName, 0, nRead);
if (nRead > 2 && tempName[0] == '\n' && tempName[nRead - 1] == '\n' && outRules.TypeCount + 2 <= TzMaxTypes) if (nRead > 2 && tempName[0] == '\n' && tempName[nRead - 1] == '\n' && outRules.TypeCount + 2 <= TzMaxTypes)
{ {
tempName[nRead - 1] = '\0'; tempName[nRead - 1] = 0;
char[] name = new char[TzNameMax]; byte[] name = new byte[TzNameMax];
Array.Copy(tempName, 1, name, 0, nRead - 1); Array.Copy(tempName, 1, name, 0, nRead - 1);
if (ParsePosixName(name, out TimeZoneRule tempRules, false)) Box<TimeZoneRule> tempRulesBox = new Box<TimeZoneRule>();
ref TimeZoneRule tempRules = ref tempRulesBox.Data;
if (ParsePosixName(name, ref tempRulesBox.Data, false))
{ {
int abbreviationCount = 0; int abbreviationCount = 0;
charCount = outRules.CharCount; charCount = outRules.CharCount;
Span<char> chars = outRules.Chars; Span<byte> chars = outRules.Chars;
for (int i = 0; i < tempRules.TypeCount; i++) for (int i = 0; i < tempRules.TypeCount; i++)
{ {
ReadOnlySpan<char> tempChars = tempRules.Chars; ReadOnlySpan<byte> tempChars = tempRules.Chars;
ReadOnlySpan<char> tempAbbreviation = tempChars[tempRules.Ttis[i].AbbreviationListIndex..]; ReadOnlySpan<byte> tempAbbreviation = tempChars[tempRules.Ttis[i].AbbreviationListIndex..];
int j; int j;
@ -1175,7 +1167,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
{ {
for (int i = 1; i < outRules.TimeCount; i++) for (int i = 1; i < outRules.TimeCount; i++)
{ {
if (TimeTypeEquals(outRules, outRules.Types[i], outRules.Types[0]) && DifferByRepeat(outRules.Ats[i], outRules.Ats[0])) if (TimeTypeEquals(in outRules, outRules.Types[i], outRules.Types[0]) && DifferByRepeat(outRules.Ats[i], outRules.Ats[0]))
{ {
outRules.GoBack = true; outRules.GoBack = true;
break; break;
@ -1184,7 +1176,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
for (int i = outRules.TimeCount - 2; i >= 0; i--) for (int i = outRules.TimeCount - 2; i >= 0; i--)
{ {
if (TimeTypeEquals(outRules, outRules.Types[outRules.TimeCount - 1], outRules.Types[i]) && DifferByRepeat(outRules.Ats[outRules.TimeCount - 1], outRules.Ats[i])) if (TimeTypeEquals(in outRules, outRules.Types[outRules.TimeCount - 1], outRules.Types[i]) && DifferByRepeat(outRules.Ats[outRules.TimeCount - 1], outRules.Ats[i]))
{ {
outRules.GoAhead = true; outRules.GoAhead = true;
break; break;
@ -1259,10 +1251,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
long remainingSeconds = time % SecondsPerDay; long remainingSeconds = time % SecondsPerDay;
calendarTime = new CalendarTimeInternal(); calendarTime = new CalendarTimeInternal();
calendarAdditionalInfo = new CalendarAdditionalInfo() calendarAdditionalInfo = new CalendarAdditionalInfo();
{
TimezoneName = new char[8]
};
while (timeDays < 0 || timeDays >= YearLengths[IsLeap((int)year)]) while (timeDays < 0 || timeDays >= YearLengths[IsLeap((int)year)])
{ {
@ -1353,13 +1342,10 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
return 0; return 0;
} }
private static ResultCode ToCalendarTimeInternal(TimeZoneRule rules, long time, out CalendarTimeInternal calendarTime, out CalendarAdditionalInfo calendarAdditionalInfo) private static ResultCode ToCalendarTimeInternal(in TimeZoneRule rules, long time, out CalendarTimeInternal calendarTime, out CalendarAdditionalInfo calendarAdditionalInfo)
{ {
calendarTime = new CalendarTimeInternal(); calendarTime = new CalendarTimeInternal();
calendarAdditionalInfo = new CalendarAdditionalInfo() calendarAdditionalInfo = new CalendarAdditionalInfo();
{
TimezoneName = new char[8]
};
ResultCode result; ResultCode result;
@ -1398,7 +1384,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
return ResultCode.TimeNotFound; return ResultCode.TimeNotFound;
} }
result = ToCalendarTimeInternal(rules, newTime, out calendarTime, out calendarAdditionalInfo); result = ToCalendarTimeInternal(in rules, newTime, out calendarTime, out calendarAdditionalInfo);
if (result != 0) if (result != 0)
{ {
return result; return result;
@ -1450,17 +1436,17 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
{ {
calendarAdditionalInfo.IsDaySavingTime = rules.Ttis[ttiIndex].IsDaySavingTime; calendarAdditionalInfo.IsDaySavingTime = rules.Ttis[ttiIndex].IsDaySavingTime;
ReadOnlySpan<char> timeZoneAbbreviation = rules.Chars.AsSpan()[rules.Ttis[ttiIndex].AbbreviationListIndex..]; ReadOnlySpan<byte> timeZoneAbbreviation = rules.Chars[rules.Ttis[ttiIndex].AbbreviationListIndex..];
int timeZoneSize = Math.Min(StringUtils.LengthCstr(timeZoneAbbreviation), 8); int timeZoneSize = Math.Min(StringUtils.LengthCstr(timeZoneAbbreviation), 8);
timeZoneAbbreviation[..timeZoneSize].CopyTo(calendarAdditionalInfo.TimezoneName.AsSpan()); timeZoneAbbreviation[..timeZoneSize].CopyTo(calendarAdditionalInfo.TimezoneName.ToSpan());
} }
return result; return result;
} }
private static ResultCode ToPosixTimeInternal(TimeZoneRule rules, CalendarTimeInternal calendarTime, out long posixTime) private static ResultCode ToPosixTimeInternal(in TimeZoneRule rules, CalendarTimeInternal calendarTime, out long posixTime)
{ {
posixTime = 0; posixTime = 0;
@ -1604,7 +1590,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
int direction; int direction;
ResultCode result = ToCalendarTimeInternal(rules, pivot, out CalendarTimeInternal candidateCalendarTime, out _); ResultCode result = ToCalendarTimeInternal(in rules, pivot, out CalendarTimeInternal candidateCalendarTime, out _);
if (result != 0) if (result != 0)
{ {
if (pivot > 0) if (pivot > 0)
@ -1675,9 +1661,9 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
return ResultCode.Success; return ResultCode.Success;
} }
internal static ResultCode ToCalendarTime(TimeZoneRule rules, long time, out CalendarInfo calendar) internal static ResultCode ToCalendarTime(in TimeZoneRule rules, long time, out CalendarInfo calendar)
{ {
ResultCode result = ToCalendarTimeInternal(rules, time, out CalendarTimeInternal calendarTime, out CalendarAdditionalInfo calendarAdditionalInfo); ResultCode result = ToCalendarTimeInternal(in rules, time, out CalendarTimeInternal calendarTime, out CalendarAdditionalInfo calendarAdditionalInfo);
calendar = new CalendarInfo() calendar = new CalendarInfo()
{ {
@ -1697,7 +1683,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
return result; return result;
} }
internal static ResultCode ToPosixTime(TimeZoneRule rules, CalendarTime calendarTime, out long posixTime) internal static ResultCode ToPosixTime(in TimeZoneRule rules, CalendarTime calendarTime, out long posixTime)
{ {
CalendarTimeInternal calendarTimeInternal = new CalendarTimeInternal() CalendarTimeInternal calendarTimeInternal = new CalendarTimeInternal()
{ {
@ -1710,7 +1696,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
Second = calendarTime.Second Second = calendarTime.Second
}; };
return ToPosixTimeInternal(rules, calendarTimeInternal, out posixTime); return ToPosixTimeInternal(in rules, calendarTimeInternal, out posixTime);
} }
} }
} }

View File

@ -16,7 +16,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using static Ryujinx.HLE.HOS.Services.Time.TimeZone.TimeZoneRule; using TimeZoneRuleBox = Ryujinx.Common.Memory.Box<Ryujinx.HLE.HOS.Services.Time.TimeZone.TimeZoneRule>;
namespace Ryujinx.HLE.HOS.Services.Time.TimeZone namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
{ {
@ -149,7 +149,11 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
continue; continue;
} }
TimeZone.ParseTimeZoneBinary(out TimeZoneRule tzRule, tzif.Get.AsStream()); TimeZoneRuleBox tzRuleBox = new TimeZoneRuleBox();
ref TimeZoneRule tzRule = ref tzRuleBox.Data;
TimeZone.ParseTimeZoneBinary(ref tzRule, tzif.Get.AsStream());
TimeTypeInfo ttInfo; TimeTypeInfo ttInfo;
if (tzRule.TimeCount > 0) // Find the current transition period if (tzRule.TimeCount > 0) // Find the current transition period
@ -174,10 +178,10 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
continue; continue;
} }
var abbrStart = tzRule.Chars.AsSpan(ttInfo.AbbreviationListIndex); var abbrStart = tzRule.Chars[ttInfo.AbbreviationListIndex..];
int abbrEnd = abbrStart.IndexOf('\0'); int abbrEnd = abbrStart.IndexOf((byte)0);
outList.Add((ttInfo.GmtOffset, locName, abbrStart.Slice(0, abbrEnd).ToString())); outList.Add((ttInfo.GmtOffset, locName, abbrStart[..abbrEnd].ToString()));
} }
} }
@ -276,15 +280,9 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
return (ResultCode)result.Value; return (ResultCode)result.Value;
} }
internal ResultCode LoadTimeZoneRule(out TimeZoneRule outRules, string locationName) internal ResultCode LoadTimeZoneRule(ref TimeZoneRule rules, string locationName)
{ {
outRules = new TimeZoneRule rules = default;
{
Ats = new long[TzMaxTimes],
Types = new byte[TzMaxTimes],
Ttis = new TimeTypeInfo[TzMaxTypes],
Chars = new char[TzCharsArraySize]
};
if (!HasTimeZoneBinaryTitle()) if (!HasTimeZoneBinaryTitle())
{ {
@ -295,7 +293,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
if (result == ResultCode.Success) if (result == ResultCode.Success)
{ {
result = Manager.ParseTimeZoneRuleBinary(out outRules, timeZoneBinaryStream); result = Manager.ParseTimeZoneRuleBinary(ref rules, timeZoneBinaryStream);
ncaFile.Dispose(); ncaFile.Dispose();
} }

View File

@ -1,14 +1,14 @@
using Ryujinx.HLE.HOS.Services.Time.Clock; using Ryujinx.Common.Memory;
using Ryujinx.HLE.HOS.Services.Time.Clock;
using Ryujinx.HLE.Utilities; using Ryujinx.HLE.Utilities;
using System.IO; using System.IO;
using static Ryujinx.HLE.HOS.Services.Time.TimeZone.TimeZoneRule;
namespace Ryujinx.HLE.HOS.Services.Time.TimeZone namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
{ {
class TimeZoneManager class TimeZoneManager
{ {
private bool _isInitialized; private bool _isInitialized;
private TimeZoneRule _myRules; private Box<TimeZoneRule> _myRules;
private string _deviceLocationName; private string _deviceLocationName;
private UInt128 _timeZoneRuleVersion; private UInt128 _timeZoneRuleVersion;
private uint _totalLocationNameCount; private uint _totalLocationNameCount;
@ -21,15 +21,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
_deviceLocationName = "UTC"; _deviceLocationName = "UTC";
_timeZoneRuleVersion = new UInt128(); _timeZoneRuleVersion = new UInt128();
_lock = new object(); _lock = new object();
_myRules = new Box<TimeZoneRule>();
// Empty rules
_myRules = new TimeZoneRule
{
Ats = new long[TzMaxTimes],
Types = new byte[TzMaxTimes],
Ttis = new TimeTypeInfo[TzMaxTypes],
Chars = new char[TzCharsArraySize]
};
_timeZoneUpdateTimePoint = SteadyClockTimePoint.GetRandom(); _timeZoneUpdateTimePoint = SteadyClockTimePoint.GetRandom();
} }
@ -78,7 +70,9 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
lock (_lock) lock (_lock)
{ {
bool timeZoneConversionSuccess = TimeZone.ParseTimeZoneBinary(out TimeZoneRule rules, timeZoneBinaryStream); Box<TimeZoneRule> rules = new Box<TimeZoneRule>();
bool timeZoneConversionSuccess = TimeZone.ParseTimeZoneBinary(ref rules.Data, timeZoneBinaryStream);
if (timeZoneConversionSuccess) if (timeZoneConversionSuccess)
{ {
@ -154,13 +148,13 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
return result; return result;
} }
public ResultCode ParseTimeZoneRuleBinary(out TimeZoneRule outRules, Stream timeZoneBinaryStream) public ResultCode ParseTimeZoneRuleBinary(ref TimeZoneRule outRules, Stream timeZoneBinaryStream)
{ {
ResultCode result = ResultCode.Success; ResultCode result = ResultCode.Success;
lock (_lock) lock (_lock)
{ {
bool timeZoneConversionSuccess = TimeZone.ParseTimeZoneBinary(out outRules, timeZoneBinaryStream); bool timeZoneConversionSuccess = TimeZone.ParseTimeZoneBinary(ref outRules, timeZoneBinaryStream);
if (!timeZoneConversionSuccess) if (!timeZoneConversionSuccess)
{ {
@ -208,7 +202,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
{ {
if (_isInitialized) if (_isInitialized)
{ {
result = ToCalendarTime(_myRules, time, out calendar); result = ToCalendarTime(in _myRules.Data, time, out calendar);
} }
else else
{ {
@ -220,13 +214,13 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
return result; return result;
} }
public ResultCode ToCalendarTime(TimeZoneRule rules, long time, out CalendarInfo calendar) public ResultCode ToCalendarTime(in TimeZoneRule rules, long time, out CalendarInfo calendar)
{ {
ResultCode result; ResultCode result;
lock (_lock) lock (_lock)
{ {
result = TimeZone.ToCalendarTime(rules, time, out calendar); result = TimeZone.ToCalendarTime(in rules, time, out calendar);
} }
return result; return result;
@ -240,7 +234,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
{ {
if (_isInitialized) if (_isInitialized)
{ {
result = ToPosixTime(_myRules, calendarTime, out posixTime); result = ToPosixTime(in _myRules.Data, calendarTime, out posixTime);
} }
else else
{ {
@ -252,13 +246,13 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
return result; return result;
} }
public ResultCode ToPosixTime(TimeZoneRule rules, CalendarTime calendarTime, out long posixTime) public ResultCode ToPosixTime(in TimeZoneRule rules, CalendarTime calendarTime, out long posixTime)
{ {
ResultCode result; ResultCode result;
lock (_lock) lock (_lock)
{ {
result = TimeZone.ToPosixTime(rules, calendarTime, out posixTime); result = TimeZone.ToPosixTime(in rules, calendarTime, out posixTime);
} }
return result; return result;

View File

@ -1,4 +1,5 @@
using System.Runtime.InteropServices; using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Time.TimeZone namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
{ {
@ -8,14 +9,12 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
public uint DayOfWeek; public uint DayOfWeek;
public uint DayOfYear; public uint DayOfYear;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public Array8<byte> TimezoneName;
public char[] TimezoneName;
[MarshalAs(UnmanagedType.I1)] [MarshalAs(UnmanagedType.I1)]
public bool IsDaySavingTime; public bool IsDaySavingTime;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] public Array3<byte> Padding;
public char[] Padding;
public int GmtOffset; public int GmtOffset;
} }

View File

@ -1,17 +1,19 @@
using System.Runtime.InteropServices; using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Time.TimeZone namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
{ {
[StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 4)] [StructLayout(LayoutKind.Sequential, Size = Size, Pack = 4)]
struct TimeTypeInfo public struct TimeTypeInfo
{ {
public const int Size = 0x10;
public int GmtOffset; public int GmtOffset;
[MarshalAs(UnmanagedType.I1)] [MarshalAs(UnmanagedType.I1)]
public bool IsDaySavingTime; public bool IsDaySavingTime;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] public Array3<byte> Padding1;
public char[] Padding1;
public int AbbreviationListIndex; public int AbbreviationListIndex;
@ -21,7 +23,6 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
[MarshalAs(UnmanagedType.I1)] [MarshalAs(UnmanagedType.I1)]
public bool IsGMT; public bool IsGMT;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] public ushort Padding2;
public char[] Padding2;
} }
} }

View File

@ -1,16 +1,18 @@
using System.Runtime.InteropServices; using Ryujinx.Common.Utilities;
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Time.TimeZone namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
{ {
[StructLayout(LayoutKind.Sequential, Pack = 4, Size = 0x4000, CharSet = CharSet.Ansi)] [StructLayout(LayoutKind.Sequential, Pack = 4, Size = 0x4000, CharSet = CharSet.Ansi)]
struct TimeZoneRule public struct TimeZoneRule
{ {
public const int TzMaxTypes = 128; public const int TzMaxTypes = 128;
public const int TzMaxChars = 50; public const int TzMaxChars = 50;
public const int TzMaxLeaps = 50; public const int TzMaxLeaps = 50;
public const int TzMaxTimes = 1000; public const int TzMaxTimes = 1000;
public const int TzNameMax = 255; public const int TzNameMax = 255;
public const int TzCharsArraySize = 2 * (TzNameMax + 1); public const int TzCharsArraySize = 2 * (TzNameMax + 1);
public int TimeCount; public int TimeCount;
public int TypeCount; public int TypeCount;
@ -22,17 +24,32 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
[MarshalAs(UnmanagedType.I1)] [MarshalAs(UnmanagedType.I1)]
public bool GoAhead; public bool GoAhead;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = TzMaxTimes)] [StructLayout(LayoutKind.Sequential, Size = sizeof(long) * TzMaxTimes)]
public long[] Ats; private struct AtsStorageStruct { }
[MarshalAs(UnmanagedType.ByValArray, SizeConst = TzMaxTimes)] private AtsStorageStruct _ats;
public byte[] Types;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = TzMaxTypes)] public Span<long> Ats => SpanHelpers.AsSpan<AtsStorageStruct, long>(ref _ats);
public TimeTypeInfo[] Ttis;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = TzCharsArraySize)] [StructLayout(LayoutKind.Sequential, Size = sizeof(byte) * TzMaxTimes)]
public char[] Chars; private struct TypesStorageStruct { }
private TypesStorageStruct _types;
public Span<byte> Types => SpanHelpers.AsByteSpan(ref _types);
[StructLayout(LayoutKind.Sequential, Size = TimeTypeInfo.Size * TzMaxTypes)]
private struct TimeTypeInfoStorageStruct { }
private TimeTypeInfoStorageStruct _ttis;
public Span<TimeTypeInfo> Ttis => SpanHelpers.AsSpan<TimeTypeInfoStorageStruct, TimeTypeInfo>(ref _ttis);
[StructLayout(LayoutKind.Sequential, Size = sizeof(byte) * TzCharsArraySize)]
private struct CharsStorageStruct { }
private CharsStorageStruct _chars;
public Span<byte> Chars => SpanHelpers.AsByteSpan(ref _chars);
public int DefaultType; public int DefaultType;
} }

View File

@ -128,7 +128,7 @@ namespace Ryujinx.HLE.Utilities
} }
} }
public static int CompareCStr(ReadOnlySpan<char> s1, ReadOnlySpan<char> s2) public static int CompareCStr(ReadOnlySpan<byte> s1, ReadOnlySpan<byte> s2)
{ {
int s1Index = 0; int s1Index = 0;
int s2Index = 0; int s2Index = 0;
@ -142,11 +142,11 @@ namespace Ryujinx.HLE.Utilities
return s2[s2Index] - s1[s1Index]; return s2[s2Index] - s1[s1Index];
} }
public static int LengthCstr(ReadOnlySpan<char> s) public static int LengthCstr(ReadOnlySpan<byte> s)
{ {
int i = 0; int i = 0;
while (s[i] != '\0') while (s[i] != 0)
{ {
i++; i++;
} }

View File

@ -79,6 +79,13 @@ namespace Ryujinx.Input.SDL2
return; return;
} }
// Sometimes a JoyStick connected event fires after the app starts even though it was connected before
// so it is rejected to avoid doubling the entries.
if (_gamepadsIds.Contains(id))
{
return;
}
if (_gamepadsInstanceIdsMapping.TryAdd(joystickInstanceId, id)) if (_gamepadsInstanceIdsMapping.TryAdd(joystickInstanceId, id))
{ {
_gamepadsIds.Add(id); _gamepadsIds.Add(id);

View File

@ -19,6 +19,8 @@ namespace Ryujinx.Memory
private ConcurrentDictionary<MemoryBlock, byte> _viewStorages; private ConcurrentDictionary<MemoryBlock, byte> _viewStorages;
private int _viewCount; private int _viewCount;
internal bool ForceWindows4KBView => _forceWindows4KBView;
/// <summary> /// <summary>
/// Pointer to the memory block data. /// Pointer to the memory block data.
/// </summary> /// </summary>
@ -145,7 +147,7 @@ namespace Ryujinx.Memory
srcBlock.IncrementViewCount(); srcBlock.IncrementViewCount();
} }
MemoryManagement.MapView(srcBlock._sharedMemory, srcOffset, GetPointerInternal(dstOffset, size), size, _forceWindows4KBView); MemoryManagement.MapView(srcBlock._sharedMemory, srcOffset, GetPointerInternal(dstOffset, size), size, this);
} }
/// <summary> /// <summary>
@ -156,7 +158,7 @@ namespace Ryujinx.Memory
/// <param name="size">Size of the range to be unmapped</param> /// <param name="size">Size of the range to be unmapped</param>
public void UnmapView(MemoryBlock srcBlock, ulong offset, ulong size) public void UnmapView(MemoryBlock srcBlock, ulong offset, ulong size)
{ {
MemoryManagement.UnmapView(srcBlock._sharedMemory, GetPointerInternal(offset, size), size, _forceWindows4KBView); MemoryManagement.UnmapView(srcBlock._sharedMemory, GetPointerInternal(offset, size), size, this);
} }
/// <summary> /// <summary>

View File

@ -68,17 +68,17 @@ namespace Ryujinx.Memory
} }
} }
public static void MapView(IntPtr sharedMemory, ulong srcOffset, IntPtr address, ulong size, bool force4KBMap) public static void MapView(IntPtr sharedMemory, ulong srcOffset, IntPtr address, ulong size, MemoryBlock owner)
{ {
if (OperatingSystem.IsWindows()) if (OperatingSystem.IsWindows())
{ {
if (force4KBMap) if (owner.ForceWindows4KBView)
{ {
MemoryManagementWindows.MapView4KB(sharedMemory, srcOffset, address, (IntPtr)size); MemoryManagementWindows.MapView4KB(sharedMemory, srcOffset, address, (IntPtr)size);
} }
else else
{ {
MemoryManagementWindows.MapView(sharedMemory, srcOffset, address, (IntPtr)size); MemoryManagementWindows.MapView(sharedMemory, srcOffset, address, (IntPtr)size, owner);
} }
} }
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
@ -91,17 +91,17 @@ namespace Ryujinx.Memory
} }
} }
public static void UnmapView(IntPtr sharedMemory, IntPtr address, ulong size, bool force4KBMap) public static void UnmapView(IntPtr sharedMemory, IntPtr address, ulong size, MemoryBlock owner)
{ {
if (OperatingSystem.IsWindows()) if (OperatingSystem.IsWindows())
{ {
if (force4KBMap) if (owner.ForceWindows4KBView)
{ {
MemoryManagementWindows.UnmapView4KB(address, (IntPtr)size); MemoryManagementWindows.UnmapView4KB(address, (IntPtr)size);
} }
else else
{ {
MemoryManagementWindows.UnmapView(sharedMemory, address, (IntPtr)size); MemoryManagementWindows.UnmapView(sharedMemory, address, (IntPtr)size, owner);
} }
} }
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())

View File

@ -68,9 +68,9 @@ namespace Ryujinx.Memory
return WindowsApi.VirtualFree(location, size, AllocationType.Decommit); return WindowsApi.VirtualFree(location, size, AllocationType.Decommit);
} }
public static void MapView(IntPtr sharedMemory, ulong srcOffset, IntPtr location, IntPtr size) public static void MapView(IntPtr sharedMemory, ulong srcOffset, IntPtr location, IntPtr size, MemoryBlock owner)
{ {
_placeholders.MapView(sharedMemory, srcOffset, location, size); _placeholders.MapView(sharedMemory, srcOffset, location, size, owner);
} }
public static void MapView4KB(IntPtr sharedMemory, ulong srcOffset, IntPtr location, IntPtr size) public static void MapView4KB(IntPtr sharedMemory, ulong srcOffset, IntPtr location, IntPtr size)
@ -106,9 +106,9 @@ namespace Ryujinx.Memory
} }
} }
public static void UnmapView(IntPtr sharedMemory, IntPtr location, IntPtr size) public static void UnmapView(IntPtr sharedMemory, IntPtr location, IntPtr size, MemoryBlock owner)
{ {
_placeholders.UnmapView(sharedMemory, location, size); _placeholders.UnmapView(sharedMemory, location, size, owner);
} }
public static void UnmapView4KB(IntPtr location, IntPtr size) public static void UnmapView4KB(IntPtr location, IntPtr size)
@ -154,7 +154,7 @@ namespace Ryujinx.Memory
} }
else else
{ {
_placeholders.UnmapView(IntPtr.Zero, address, size); _placeholders.UnreserveRange((ulong)address, (ulong)size);
} }
return WindowsApi.VirtualFree(address, IntPtr.Zero, AllocationType.Release); return WindowsApi.VirtualFree(address, IntPtr.Zero, AllocationType.Release);

View File

@ -227,6 +227,8 @@ namespace Ryujinx.Memory.Tracking
// Look up the virtual region using the region list. // Look up the virtual region using the region list.
// Signal up the chain to relevant handles. // Signal up the chain to relevant handles.
bool shouldThrow = false;
lock (TrackingLock) lock (TrackingLock)
{ {
ref var overlaps = ref ThreadStaticArray<VirtualRegion>.Get(); ref var overlaps = ref ThreadStaticArray<VirtualRegion>.Get();
@ -235,32 +237,41 @@ namespace Ryujinx.Memory.Tracking
if (count == 0 && !precise) if (count == 0 && !precise)
{ {
if (!_memoryManager.IsMapped(address)) if (_memoryManager.IsMapped(address))
{ {
_invalidAccessHandler?.Invoke(address); _memoryManager.TrackingReprotect(address & ~(ulong)(_pageSize - 1), (ulong)_pageSize, MemoryPermission.ReadAndWrite);
return false; // We can't handle this - it's probably a real invalid access.
// We can't continue - it's impossible to remove protection from the page.
// Even if the access handler wants us to continue, we wouldn't be able to.
throw new InvalidMemoryRegionException();
}
_memoryManager.TrackingReprotect(address & ~(ulong)(_pageSize - 1), (ulong)_pageSize, MemoryPermission.ReadAndWrite);
return false; // We can't handle this - it's probably a real invalid access.
}
for (int i = 0; i < count; i++)
{
VirtualRegion region = overlaps[i];
if (precise)
{
region.SignalPrecise(address, size, write);
} }
else else
{ {
region.Signal(address, size, write); shouldThrow = true;
} }
} }
else
{
for (int i = 0; i < count; i++)
{
VirtualRegion region = overlaps[i];
if (precise)
{
region.SignalPrecise(address, size, write);
}
else
{
region.Signal(address, size, write);
}
}
}
}
if (shouldThrow)
{
_invalidAccessHandler?.Invoke(address);
// We can't continue - it's impossible to remove protection from the page.
// Even if the access handler wants us to continue, we wouldn't be able to.
throw new InvalidMemoryRegionException();
} }
return true; return true;

View File

@ -44,6 +44,50 @@ namespace Ryujinx.Memory.WindowsShared
} }
} }
/// <summary>
/// Unreserves a range of memory that has been previously reserved with <see cref="ReserveRange"/>.
/// </summary>
/// <param name="address">Start address of the region to unreserve</param>
/// <param name="size">Size in bytes of the region to unreserve</param>
/// <exception cref="WindowsApiException">Thrown when the Windows API returns an error unreserving the memory</exception>
public void UnreserveRange(ulong address, ulong size)
{
ulong endAddress = address + size;
var overlaps = Array.Empty<IntervalTreeNode<ulong, ulong>>();
int count;
lock (_mappings)
{
count = _mappings.Get(address, endAddress, ref overlaps);
for (int index = 0; index < count; index++)
{
var overlap = overlaps[index];
if (IsMapped(overlap.Value))
{
if (!WindowsApi.UnmapViewOfFile2(WindowsApi.CurrentProcessHandle, (IntPtr)overlap.Start, 2))
{
throw new WindowsApiException("UnmapViewOfFile2");
}
}
_mappings.Remove(overlap);
}
}
if (count > 1)
{
CheckFreeResult(WindowsApi.VirtualFree(
(IntPtr)address,
(IntPtr)size,
AllocationType.Release | AllocationType.CoalescePlaceholders));
}
RemoveProtection(address, size);
}
/// <summary> /// <summary>
/// Maps a shared memory view on a previously reserved memory region. /// Maps a shared memory view on a previously reserved memory region.
/// </summary> /// </summary>
@ -51,13 +95,14 @@ namespace Ryujinx.Memory.WindowsShared
/// <param name="srcOffset">Offset in the shared memory to map</param> /// <param name="srcOffset">Offset in the shared memory to map</param>
/// <param name="location">Address to map the view into</param> /// <param name="location">Address to map the view into</param>
/// <param name="size">Size of the view in bytes</param> /// <param name="size">Size of the view in bytes</param>
public void MapView(IntPtr sharedMemory, ulong srcOffset, IntPtr location, IntPtr size) /// <param name="owner">Memory block that owns the mapping</param>
public void MapView(IntPtr sharedMemory, ulong srcOffset, IntPtr location, IntPtr size, MemoryBlock owner)
{ {
_partialUnmapLock.AcquireReaderLock(Timeout.Infinite); _partialUnmapLock.AcquireReaderLock(Timeout.Infinite);
try try
{ {
UnmapViewInternal(sharedMemory, location, size); UnmapViewInternal(sharedMemory, location, size, owner);
MapViewInternal(sharedMemory, srcOffset, location, size); MapViewInternal(sharedMemory, srcOffset, location, size);
} }
finally finally
@ -173,13 +218,14 @@ namespace Ryujinx.Memory.WindowsShared
/// <param name="sharedMemory">Shared memory that the view being unmapped belongs to</param> /// <param name="sharedMemory">Shared memory that the view being unmapped belongs to</param>
/// <param name="location">Address to unmap</param> /// <param name="location">Address to unmap</param>
/// <param name="size">Size of the region to unmap in bytes</param> /// <param name="size">Size of the region to unmap in bytes</param>
public void UnmapView(IntPtr sharedMemory, IntPtr location, IntPtr size) /// <param name="owner">Memory block that owns the mapping</param>
public void UnmapView(IntPtr sharedMemory, IntPtr location, IntPtr size, MemoryBlock owner)
{ {
_partialUnmapLock.AcquireReaderLock(Timeout.Infinite); _partialUnmapLock.AcquireReaderLock(Timeout.Infinite);
try try
{ {
UnmapViewInternal(sharedMemory, location, size); UnmapViewInternal(sharedMemory, location, size, owner);
} }
finally finally
{ {
@ -197,8 +243,9 @@ namespace Ryujinx.Memory.WindowsShared
/// <param name="sharedMemory">Shared memory that the view being unmapped belongs to</param> /// <param name="sharedMemory">Shared memory that the view being unmapped belongs to</param>
/// <param name="location">Address to unmap</param> /// <param name="location">Address to unmap</param>
/// <param name="size">Size of the region to unmap in bytes</param> /// <param name="size">Size of the region to unmap in bytes</param>
/// <param name="owner">Memory block that owns the mapping</param>
/// <exception cref="WindowsApiException">Thrown when the Windows API returns an error unmapping or remapping the memory</exception> /// <exception cref="WindowsApiException">Thrown when the Windows API returns an error unmapping or remapping the memory</exception>
private void UnmapViewInternal(IntPtr sharedMemory, IntPtr location, IntPtr size) private void UnmapViewInternal(IntPtr sharedMemory, IntPtr location, IntPtr size, MemoryBlock owner)
{ {
ulong startAddress = (ulong)location; ulong startAddress = (ulong)location;
ulong unmapSize = (ulong)size; ulong unmapSize = (ulong)size;
@ -272,7 +319,7 @@ namespace Ryujinx.Memory.WindowsShared
} }
} }
CoalesceForUnmap(startAddress, unmapSize); CoalesceForUnmap(startAddress, unmapSize, owner);
RemoveProtection(startAddress, unmapSize); RemoveProtection(startAddress, unmapSize);
} }
@ -281,15 +328,21 @@ namespace Ryujinx.Memory.WindowsShared
/// </summary> /// </summary>
/// <param name="address">Address of the region that was unmapped</param> /// <param name="address">Address of the region that was unmapped</param>
/// <param name="size">Size of the region that was unmapped in bytes</param> /// <param name="size">Size of the region that was unmapped in bytes</param>
private void CoalesceForUnmap(ulong address, ulong size) /// <param name="owner">Memory block that owns the mapping</param>
private void CoalesceForUnmap(ulong address, ulong size, MemoryBlock owner)
{ {
ulong endAddress = address + size; ulong endAddress = address + size;
ulong blockAddress = (ulong)owner.Pointer;
ulong blockEnd = blockAddress + owner.Size;
var overlaps = Array.Empty<IntervalTreeNode<ulong, ulong>>(); var overlaps = Array.Empty<IntervalTreeNode<ulong, ulong>>();
int unmappedCount = 0; int unmappedCount = 0;
lock (_mappings) lock (_mappings)
{ {
int count = _mappings.Get(address - MinimumPageSize, endAddress + MinimumPageSize, ref overlaps); int count = _mappings.Get(
Math.Max(address - MinimumPageSize, blockAddress),
Math.Min(endAddress + MinimumPageSize, blockEnd), ref overlaps);
if (count < 2) if (count < 2)
{ {
// Nothing to coalesce if we only have 1 or no overlaps. // Nothing to coalesce if we only have 1 or no overlaps.

View File

@ -24,6 +24,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Ryujinx.Audio\Ryujinx.Audio.csproj" /> <ProjectReference Include="..\Ryujinx.Audio\Ryujinx.Audio.csproj" />
<ProjectReference Include="..\Ryujinx.Cpu\Ryujinx.Cpu.csproj" /> <ProjectReference Include="..\Ryujinx.Cpu\Ryujinx.Cpu.csproj" />
<ProjectReference Include="..\Ryujinx.HLE\Ryujinx.HLE.csproj" />
<ProjectReference Include="..\Ryujinx.Memory\Ryujinx.Memory.csproj" /> <ProjectReference Include="..\Ryujinx.Memory\Ryujinx.Memory.csproj" />
<ProjectReference Include="..\Ryujinx.Tests.Unicorn\Ryujinx.Tests.Unicorn.csproj" /> <ProjectReference Include="..\Ryujinx.Tests.Unicorn\Ryujinx.Tests.Unicorn.csproj" />
<ProjectReference Include="..\ARMeilleure\ARMeilleure.csproj" /> <ProjectReference Include="..\ARMeilleure\ARMeilleure.csproj" />

View File

@ -0,0 +1,18 @@
using NUnit.Framework;
using Ryujinx.HLE.HOS.Services.Time.TimeZone;
using System.Runtime.CompilerServices;
namespace Ryujinx.Tests.Time
{
internal class TimeZoneRuleTests
{
class EffectInfoParameterTests
{
[Test]
public void EnsureTypeSize()
{
Assert.AreEqual(0x4000, Unsafe.SizeOf<TimeZoneRule>());
}
}
}
}