Compare commits

...

22 Commits

Author SHA1 Message Date
625f5fb88a Account for pool change on texture bindings cache (#3420)
* Account for pool change on texture bindings cache

* Reduce the number of checks needed
2022-06-25 16:52:38 +02:00
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
a3e7bb8eb4 Copy dependency for multisample and non-multisample textures (#3382)
* Use copy dependency for textures that differs in multisample but are otherwise compatible

* Remove allowMs flag as it's no longer required for correctness, it's just an optimization now

* Dispose intermmediate pool
2022-06-05 14:06:47 -03:00
2073ba2919 Fix a potential GPFIFO submission race (#3378)
The syncpoint maximum value represents the maximum possible syncpt value at a given time, however due to PBs being submitted before max was incremented, for a brief moment of time this is not the case which could lead to invalid behaviour if a game waits on the fence at that specific time.
2022-06-04 21:36:36 +02:00
d03124a992 Fix 3D semaphore counter type 0 handling (#3380)
Counter type 0 actually releases the semaphore payload rather than a constant zero as was previously thought. This is required by Skyrim.
2022-06-02 19:51:36 -03:00
81 changed files with 3495 additions and 1691 deletions

View File

@ -14,10 +14,11 @@ namespace ARMeilleure.IntermediateRepresentation
public byte Kind;
public byte Type;
public byte SymbolType;
public byte Padding; // Unused space.
public ushort AssignmentsCount;
public ushort AssignmentsCapacity;
public ushort UsesCount;
public ushort UsesCapacity;
public uint UsesCount;
public uint UsesCapacity;
public Operation* Assignments;
public Operation* Uses;
public ulong Value;
@ -84,11 +85,11 @@ namespace ARMeilleure.IntermediateRepresentation
{
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 bool Relocatable => Symbol.Type != SymbolType.None;
@ -178,7 +179,7 @@ namespace ARMeilleure.IntermediateRepresentation
{
Add(operation, ref addr._data->Assignments, ref addr._data->AssignmentsCount, ref addr._data->AssignmentsCapacity);
}
if (index != default)
{
Add(operation, ref index._data->Assignments, ref index._data->AssignmentsCount, ref index._data->AssignmentsCapacity);
@ -265,6 +266,13 @@ namespace ARMeilleure.IntermediateRepresentation
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
{
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
{
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()
{
if (Kind == OperandKind.LocalVariable)

View File

@ -1,9 +1,9 @@
<Application
x:Class="Ryujinx.Ava.App"
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>
<sty:FluentAvaloniaTheme UseSystemThemeOnWindows="False"/>
<sty:FluentAvaloniaTheme UseSystemThemeOnWindows="False" />
</Application.Styles>
</Application>

View File

@ -2,7 +2,6 @@ using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.Styling;
using Avalonia.Threading;
using FluentAvalonia.Styling;
using Ryujinx.Ava.Ui.Windows;
using Ryujinx.Common;
@ -13,7 +12,7 @@ using System.IO;
namespace Ryujinx.Ava
{
public class App : Avalonia.Application
public class App : Application
{
public override void Initialize()
{
@ -46,7 +45,7 @@ namespace Ryujinx.Ava
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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -10,9 +10,10 @@ namespace Ryujinx.Graphics.GAL
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(
int layer,
float depthValue,
bool depthMask,
int stencilValue,

View File

@ -4,19 +4,21 @@
{
public CommandType CommandType => CommandType.ClearRenderTargetColor;
private int _index;
private int _layer;
private uint _componentMask;
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;
_layer = layer;
_componentMask = componentMask;
_color = color;
}
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
{
public CommandType CommandType => CommandType.ClearRenderTargetDepthStencil;
private int _layer;
private float _depthValue;
private bool _depthMask;
private int _stencilValue;
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;
_depthMask = depthMask;
_stencilValue = stencilValue;
@ -18,7 +20,7 @@
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();
}
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();
}
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();
}

View File

@ -13,4 +13,12 @@ namespace Ryujinx.Graphics.GAL
CubemapArray,
TextureBuffer
}
public static class TargetExtensions
{
public static bool IsMultisample(this Target target)
{
return target == Target.Texture2DMultisample || target == Target.Texture2DMultisampleArray;
}
}
}

View File

@ -188,6 +188,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
_channel.BufferManager.SetComputeStorageBufferBindings(info.SBuffers);
_channel.BufferManager.SetComputeUniformBufferBindings(info.CBuffers);
int maxTextureBinding = -1;
int maxImageBinding = -1;
TextureBindingInfo[] textureBindings = _channel.TextureManager.RentComputeTextureBindings(info.Textures.Count);
for (int index = 0; index < info.Textures.Count; index++)
@ -202,6 +205,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
descriptor.CbufSlot,
descriptor.HandleIndex,
descriptor.Flags);
if (descriptor.Binding > maxTextureBinding)
{
maxTextureBinding = descriptor.Binding;
}
}
TextureBindingInfo[] imageBindings = _channel.TextureManager.RentComputeImageBindings(info.Images.Count);
@ -220,9 +228,18 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
descriptor.CbufSlot,
descriptor.HandleIndex,
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();
_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 _instancedIndexed;
private bool _instancedIndexedInline;
private int _instancedFirstIndex;
private int _instancedFirstVertex;
@ -134,13 +135,16 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
{
_instancedDrawPending = true;
int ibCount = _drawState.IbStreamer.InlineIndexCount;
_instancedIndexed = _drawState.DrawIndexed;
_instancedIndexedInline = ibCount != 0;
_instancedFirstIndex = firstIndex;
_instancedFirstVertex = (int)_state.State.FirstVertex;
_instancedFirstInstance = (int)_state.State.FirstInstance;
_instancedIndexCount = indexCount;
_instancedIndexCount = ibCount != 0 ? ibCount : indexCount;
var drawState = _state.State.VertexBufferDrawState;
@ -451,8 +455,18 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
{
_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(
_instancedIndexCount,
_instanceIndex + 1,
@ -491,8 +505,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
}
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
// 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);
_context.Renderer.Pipeline.ClearRenderTargetColor(index, componentMask, color);
_context.Renderer.Pipeline.ClearRenderTargetColor(index, layer, componentMask, color);
}
if (clearDepth || clearStencil)
@ -588,6 +603,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
}
_context.Renderer.Pipeline.ClearRenderTargetDepthStencil(
layer,
depthValue,
clearDepth,
stencilValue,

View File

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

View File

@ -44,7 +44,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// </summary>
private enum ReportCounterType
{
Zero = 0,
Payload = 0,
InputVertices = 1,
InputPrimitives = 3,
VertexShaderInvocations = 5,
@ -169,8 +169,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
switch (type)
{
case ReportCounterType.Zero:
resultHandler(null, 0);
case ReportCounterType.Payload:
resultHandler(null, (ulong)_state.State.SemaphorePayload);
break;
case ReportCounterType.SamplesPassed:
counter = _context.Renderer.ReportCounter(CounterType.SamplesPassed, resultHandler, false);

View File

@ -201,7 +201,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
// of the shader for the new state.
if (_shaderSpecState != null)
{
if (!_shaderSpecState.MatchesGraphics(_channel, GetPoolState(), GetGraphicsState()))
if (!_shaderSpecState.MatchesGraphics(_channel, GetPoolState(), GetGraphicsState(), false))
{
ForceShaderUpdate();
}
@ -275,7 +275,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
{
UpdateStorageBuffers();
_channel.TextureManager.CommitGraphicsBindings();
if (!_channel.TextureManager.CommitGraphicsBindings(_shaderSpecState))
{
// Shader must be reloaded.
UpdateShaderState();
}
_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.
/// </summary>
/// <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>
public void UpdateRenderTargetState(bool useControl, int singleUse = -1)
public void UpdateRenderTargetState(bool useControl, bool layered = false, int singleUse = -1)
{
var memoryManager = _channel.MemoryManager;
var rtControl = _state.State.RtControl;
@ -399,7 +405,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
Image.Texture color = memoryManager.Physical.TextureCache.FindOrCreateTexture(
memoryManager,
colorState,
_vtgWritesRtLayer,
_vtgWritesRtLayer || layered,
samplesInX,
samplesInY,
sizeHint);
@ -433,6 +439,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
memoryManager,
dsState,
dsSize,
_vtgWritesRtLayer || layered,
samplesInX,
samplesInY,
sizeHint);
@ -1148,6 +1155,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
return;
}
int maxTextureBinding = -1;
int maxImageBinding = -1;
Span<TextureBindingInfo> textureBindings = _channel.TextureManager.RentGraphicsTextureBindings(stage, info.Textures.Count);
if (info.UsesRtLayer)
@ -1167,6 +1177,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
descriptor.CbufSlot,
descriptor.HandleIndex,
descriptor.Flags);
if (descriptor.Binding > maxTextureBinding)
{
maxTextureBinding = descriptor.Binding;
}
}
TextureBindingInfo[] imageBindings = _channel.TextureManager.RentGraphicsImageBindings(stage, info.Images.Count);
@ -1185,8 +1200,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
descriptor.CbufSlot,
descriptor.HandleIndex,
descriptor.Flags);
if (descriptor.Binding > maxImageBinding)
{
maxImageBinding = descriptor.Binding;
}
}
_channel.TextureManager.SetGraphicsMaxBindings(maxTextureBinding, maxImageBinding);
_channel.BufferManager.SetGraphicsStorageBufferBindings(stage, info.SBuffers);
_channel.BufferManager.SetGraphicsUniformBufferBindings(stage, info.CBuffers);
}

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.
/// </summary>
/// <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>
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>

View File

@ -1,6 +1,7 @@
using Ryujinx.Cpu.Tracking;
using Ryujinx.Graphics.Gpu.Memory;
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Gpu.Image
{
@ -16,6 +17,7 @@ namespace Ryujinx.Graphics.Gpu.Image
protected GpuContext Context;
protected PhysicalMemory PhysicalMemory;
protected int SequenceNumber;
protected int ModifiedSequenceNumber;
protected T1[] Items;
protected T2[] DescriptorCache;
@ -41,6 +43,9 @@ namespace Ryujinx.Graphics.Gpu.Image
private readonly CpuMultiRegionHandle _memoryTracking;
private readonly Action<ulong, ulong> _modifiedDelegate;
private int _modifiedSequenceOffset;
private bool _modified;
/// <summary>
/// Creates a new instance of the GPU resource pool.
/// </summary>
@ -79,6 +84,16 @@ namespace Ryujinx.Graphics.Gpu.Image
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>
/// Gets the GPU resource with the given ID.
/// </summary>
@ -86,6 +101,16 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <returns>The GPU resource with the given ID</returns>
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>
/// Synchronizes host memory with guest memory.
/// This causes invalidation of pool entries,
@ -93,7 +118,13 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
public void SynchronizeMemory()
{
_modified = false;
_memoryTracking.QueryModified(_modifiedDelegate);
if (_modified)
{
UpdateModifiedSequence();
}
}
/// <summary>
@ -103,6 +134,8 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="mSize">Size of the modified region</param>
private void RegionModified(ulong mAddress, ulong mSize)
{
_modified = true;
if (mAddress < Address)
{
mAddress = Address;
@ -118,6 +151,15 @@ namespace Ryujinx.Graphics.Gpu.Image
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>
/// An action to be performed when a precise memory access occurs to this resource.
/// Makes sure that the dirty flags are checked.
@ -129,6 +171,16 @@ namespace Ryujinx.Graphics.Gpu.Image
{
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--;
}

View File

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

View File

@ -48,6 +48,8 @@ namespace Ryujinx.Graphics.Gpu.Image
Items[i] = null;
}
}
UpdateModifiedSequence();
}
SequenceNumber = Context.SequenceNumber;
@ -71,6 +73,39 @@ namespace Ryujinx.Graphics.Gpu.Image
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>
/// Implementation of the sampler pool range invalidation.
/// </summary>

View File

@ -100,6 +100,11 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
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 _layers;
public int FirstLayer { get; private set; }
@ -1136,32 +1141,22 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="range">Texture view physical memory ranges</param>
/// <param name="layerSize">Layer size on the given texture</param>
/// <param name="caps">Host GPU capabilities</param>
/// <param name="allowMs">Indicates that multisample textures are allowed to match non-multisample requested textures</param>
/// <param name="firstLayer">Texture view initial layer on this texture</param>
/// <param name="firstLevel">Texture view first mipmap level on this texture</param>
/// <returns>The level of compatiblilty a view with the given parameters created from this texture has</returns>
public TextureViewCompatibility IsViewCompatible(TextureInfo info, MultiRange range, int layerSize, Capabilities caps, bool allowMs, out int firstLayer, out int firstLevel)
public TextureViewCompatibility IsViewCompatible(TextureInfo info, MultiRange range, int layerSize, Capabilities caps, out int firstLayer, out int firstLevel)
{
TextureViewCompatibility result = TextureViewCompatibility.Full;
result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewFormatCompatible(Info, info, caps));
if (result != TextureViewCompatibility.Incompatible)
{
bool msTargetCompatible = false;
result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewTargetCompatible(Info, info));
if (allowMs)
bool bothMs = Info.Target.IsMultisample() && info.Target.IsMultisample();
if (bothMs && (Info.SamplesInX != info.SamplesInX || Info.SamplesInY != info.SamplesInY))
{
msTargetCompatible = Info.Target == Target.Texture2DMultisample && info.Target == Target.Texture2D;
}
if (!msTargetCompatible)
{
result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewTargetCompatible(Info, info));
if (Info.SamplesInX != info.SamplesInX || Info.SamplesInY != info.SamplesInY)
{
result = TextureViewCompatibility.Incompatible;
}
result = TextureViewCompatibility.Incompatible;
}
if (result == TextureViewCompatibility.Full && Info.FormatInfo.Format != info.FormatInfo.Format && !_context.Capabilities.SupportsMismatchingViewFormat)
@ -1417,6 +1412,7 @@ namespace Ryujinx.Graphics.Gpu.Image
DisposeTextures();
HostTexture = hostTexture;
InvalidatedSequence++;
}
/// <summary>
@ -1545,6 +1541,8 @@ namespace Ryujinx.Graphics.Gpu.Image
_poolOwners.Clear();
}
InvalidatedSequence++;
}
/// <summary>

View File

@ -1,8 +1,12 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.Types;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Gpu.Shader;
using Ryujinx.Graphics.Shader;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Gpu.Image
{
@ -28,24 +32,36 @@ namespace Ryujinx.Graphics.Gpu.Image
private readonly GpuChannel _channel;
private readonly TexturePoolCache _texturePoolCache;
private TexturePool _cachedTexturePool;
private SamplerPool _cachedSamplerPool;
private readonly TextureBindingInfo[][] _textureBindings;
private readonly TextureBindingInfo[][] _imageBindings;
private struct TextureStatePerStage
private struct TextureState
{
public ITexture Texture;
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 readonly TextureStatePerStage[][] _imageState;
private TextureState[] _textureState;
private TextureState[] _imageState;
private int[] _textureBindingsCount;
private int[] _imageBindingsCount;
private int _textureBufferIndex;
private int _texturePoolSequence;
private int _samplerPoolSequence;
private bool _rebind;
private int _textureBufferIndex;
private readonly float[] _scales;
private bool _scaleChanged;
@ -72,8 +88,8 @@ namespace Ryujinx.Graphics.Gpu.Image
_textureBindings = new TextureBindingInfo[stages][];
_imageBindings = new TextureBindingInfo[stages][];
_textureState = new TextureStatePerStage[stages][];
_imageState = new TextureStatePerStage[stages][];
_textureState = new TextureState[InitialTextureStateSize];
_imageState = new TextureState[InitialImageStateSize];
_textureBindingsCount = new int[stages];
_imageBindingsCount = new int[stages];
@ -82,9 +98,6 @@ namespace Ryujinx.Graphics.Gpu.Image
{
_textureBindings[stage] = new TextureBindingInfo[InitialTextureStateSize];
_imageBindings[stage] = new TextureBindingInfo[InitialImageStateSize];
_textureState[stage] = new TextureStatePerStage[InitialTextureStateSize];
_imageState[stage] = new TextureStatePerStage[InitialImageStateSize];
}
}
@ -99,15 +112,6 @@ namespace Ryujinx.Graphics.Gpu.Image
if (count > _textureBindings[stage].Length)
{
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;
@ -126,15 +130,6 @@ namespace Ryujinx.Graphics.Gpu.Image
if (count > _imageBindings[stage].Length)
{
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;
@ -142,6 +137,24 @@ namespace Ryujinx.Graphics.Gpu.Image
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>
/// Sets the textures constant buffer index.
/// The constant buffer specified holds the texture handles.
@ -222,18 +235,18 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Updates the texture scale for a given texture or image.
/// </summary>
/// <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="stage">The active shader stage</param>
/// <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;
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;
texture.BlacklistScale();
@ -323,7 +336,9 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Ensures that the bindings are visible to the host GPU.
/// Note: this actually performs the binding using the host graphics API.
/// </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;
@ -331,10 +346,43 @@ namespace Ryujinx.Graphics.Gpu.Image
? _texturePoolCache.FindOrCreate(_channel, texturePoolAddress, _texturePoolMaximumId)
: null;
SamplerPool samplerPool = _samplerPool;
// 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 = _cachedTexturePool != texturePool || _cachedSamplerPool != samplerPool;
_cachedTexturePool = texturePool;
_cachedSamplerPool = samplerPool;
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)
{
CommitTextureBindings(texturePool, ShaderStage.Compute, 0);
CommitImageBindings (texturePool, ShaderStage.Compute, 0);
specStateMatches &= CommitTextureBindings(texturePool, ShaderStage.Compute, 0, poolModified, specState);
specStateMatches &= CommitImageBindings(texturePool, ShaderStage.Compute, 0, poolModified, specState);
}
else
{
@ -342,14 +390,57 @@ namespace Ryujinx.Graphics.Gpu.Image
{
int stageIndex = (int)stage - 1;
CommitTextureBindings(texturePool, stage, stageIndex);
CommitImageBindings (texturePool, stage, stageIndex);
specStateMatches &= CommitTextureBindings(texturePool, stage, stageIndex, poolModified, specState);
specStateMatches &= CommitImageBindings(texturePool, stage, stageIndex, poolModified, specState);
}
}
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>
@ -358,13 +449,16 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
/// <param name="pool">The current texture pool</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>
private void CommitTextureBindings(TexturePool pool, ShaderStage stage, int stageIndex)
/// <param name="stageIndex">The stage number of the specified shader stage</param
/// <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];
if (textureCount == 0)
{
return;
return true;
}
var samplerPool = _samplerPool;
@ -372,17 +466,27 @@ namespace Ryujinx.Graphics.Gpu.Image
if (pool == null)
{
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++)
{
TextureBindingInfo bindingInfo = _textureBindings[stageIndex][index];
TextureUsageFlags usageFlags = bindingInfo.Flags;
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex);
int packedId = ReadPackedId(stageIndex, bindingInfo.Handle, textureBufferIndex, samplerBufferIndex);
int textureId = UnpackTextureId(packedId);
UpdateCachedBuffer(stageIndex, ref cachedTextureBufferIndex, ref cachedSamplerBufferIndex, ref cachedTextureBuffer, ref cachedSamplerBuffer, textureBufferIndex, samplerBufferIndex);
int packedId = TextureHandle.ReadPackedId(bindingInfo.Handle, cachedTextureBuffer, cachedSamplerBuffer);
int textureId = TextureHandle.UnpackTextureId(packedId);
int samplerId;
if (_samplerIndex == SamplerIndex.ViaHeaderIndex)
@ -391,10 +495,42 @@ namespace Ryujinx.Graphics.Gpu.Image
}
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);
@ -407,30 +543,38 @@ namespace Ryujinx.Graphics.Gpu.Image
}
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);
}
_textureState[stageIndex][index].Texture = hostTexture;
state.Texture = hostTexture;
state.ScaleIndex = index;
state.UsageFlags = usageFlags;
_context.Renderer.Pipeline.SetTexture(bindingInfo.Binding, hostTexture);
}
Sampler sampler = samplerPool?.Get(samplerId);
state.CachedSampler = sampler;
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);
}
state.CachedTexture = texture;
state.InvalidatedSequence = texture?.InvalidatedSequence ?? 0;
}
}
return specStateMatches;
}
/// <summary>
@ -440,38 +584,90 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="pool">The current texture pool</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>
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];
if (imageCount == 0)
{
return;
return true;
}
if (pool == null)
{
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.
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++)
{
TextureBindingInfo bindingInfo = _imageBindings[stageIndex][index];
TextureUsageFlags usageFlags = bindingInfo.Flags;
int scaleIndex = baseScaleIndex + index;
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex);
int packedId = ReadPackedId(stageIndex, bindingInfo.Handle, textureBufferIndex, samplerBufferIndex);
int textureId = UnpackTextureId(packedId);
UpdateCachedBuffer(stageIndex, ref cachedTextureBufferIndex, ref cachedSamplerBufferIndex, ref cachedTextureBuffer, ref cachedSamplerBuffer, textureBufferIndex, samplerBufferIndex);
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);
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)
{
// Ensure that the buffer texture is using the correct buffer as storage.
@ -494,14 +690,16 @@ namespace Ryujinx.Graphics.Gpu.Image
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);
}
_imageState[stageIndex][index].Texture = hostTexture;
state.Texture = hostTexture;
state.ScaleIndex = scaleIndex;
state.UsageFlags = usageFlags;
Format format = bindingInfo.Format;
@ -512,8 +710,13 @@ namespace Ryujinx.Graphics.Gpu.Image
_context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTexture, format);
}
state.CachedTexture = texture;
state.InvalidatedSequence = texture?.InvalidatedSequence ?? 0;
}
}
return specStateMatches;
}
/// <summary>
@ -537,13 +740,28 @@ namespace Ryujinx.Graphics.Gpu.Image
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(cbufSlot, bufferIndex);
int packedId = ReadPackedId(stageIndex, handle, textureBufferIndex, samplerBufferIndex);
int textureId = UnpackTextureId(packedId);
int textureId = TextureHandle.UnpackTextureId(packedId);
ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa);
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>
@ -555,6 +773,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <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>
/// <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)
{
(int textureWordOffset, int samplerWordOffset, TextureHandleType handleType) = TextureHandle.UnpackOffsets(wordOffset);
@ -590,32 +809,13 @@ namespace Ryujinx.Graphics.Gpu.Image
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>
/// Force all bound textures and images to be rebound the next time CommitBindings is called.
/// </summary>
public void Rebind()
{
_rebind = true;
Array.Clear(_textureState);
Array.Clear(_imageState);
}
/// <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="dsState">Depth-stencil buffer texture to find or create</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="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>
@ -357,6 +358,7 @@ namespace Ryujinx.Graphics.Gpu.Image
MemoryManager memoryManager,
RtDepthStencilState dsState,
Size3D size,
bool layered,
int samplesInX,
int samplesInY,
Size sizeHint)
@ -364,9 +366,24 @@ namespace Ryujinx.Graphics.Gpu.Image
int gobBlocksInY = dsState.MemoryLayout.UnpackGobBlocksInY();
int gobBlocksInZ = dsState.MemoryLayout.UnpackGobBlocksInZ();
Target target = (samplesInX | samplesInY) != 1
? Target.Texture2DMultisample
: Target.Texture2D;
Target target;
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();
@ -547,7 +564,6 @@ namespace Ryujinx.Graphics.Gpu.Image
range.Value,
sizeInfo.LayerSize,
_context.Capabilities,
flags.HasFlag(TextureSearchFlags.ForCopy),
out int firstLayer,
out int firstLevel);
@ -662,7 +678,6 @@ namespace Ryujinx.Graphics.Gpu.Image
overlap.Range,
overlap.LayerSize,
_context.Capabilities,
false,
out int firstLayer,
out int firstLevel);

View File

@ -2,7 +2,6 @@ using Ryujinx.Common;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Texture;
using System;
using System.Numerics;
namespace Ryujinx.Graphics.Gpu.Image
{
@ -657,6 +656,11 @@ namespace Ryujinx.Graphics.Gpu.Image
case Target.Texture2DMultisample:
case Target.Texture2DMultisampleArray:
if (rhs.Target == Target.Texture2D || rhs.Target == Target.Texture2DArray)
{
return TextureViewCompatibility.CopyOnly;
}
result = rhs.Target == Target.Texture2DMultisample ||
rhs.Target == Target.Texture2DMultisampleArray;
break;

View File

@ -241,25 +241,6 @@ namespace Ryujinx.Graphics.Gpu.Image
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>
/// Check if two descriptors are equal.
/// </summary>

View File

@ -1,5 +1,6 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.Types;
using Ryujinx.Graphics.Gpu.Shader;
using System;
namespace Ryujinx.Graphics.Gpu.Image
@ -10,9 +11,11 @@ namespace Ryujinx.Graphics.Gpu.Image
class TextureManager : IDisposable
{
private readonly GpuContext _context;
private readonly GpuChannel _channel;
private readonly TextureBindingsManager _cpBindingsManager;
private readonly TextureBindingsManager _gpBindingsManager;
private readonly TexturePoolCache _texturePoolCache;
private readonly Texture[] _rtColors;
private readonly ITexture[] _rtHostColors;
@ -35,6 +38,7 @@ namespace Ryujinx.Graphics.Gpu.Image
public TextureManager(GpuContext context, GpuChannel channel)
{
_context = context;
_channel = channel;
TexturePoolCache texturePoolCache = new TexturePoolCache(context);
@ -43,6 +47,7 @@ namespace Ryujinx.Graphics.Gpu.Image
_cpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, scales, isCompute: true);
_gpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, scales, isCompute: false);
_texturePoolCache = texturePoolCache;
_rtColors = new Texture[Constants.TotalRenderTargets];
_rtHostColors = new ITexture[Constants.TotalRenderTargets];
@ -99,6 +104,16 @@ namespace Ryujinx.Graphics.Gpu.Image
_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>
/// Sets the texture constant buffer index on the graphics pipeline.
/// </summary>
@ -108,6 +123,16 @@ namespace Ryujinx.Graphics.Gpu.Image
_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>
/// Sets the current sampler pool on the compute pipeline.
/// </summary>
@ -335,25 +360,48 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <summary>
/// Commits bindings on the compute pipeline.
/// </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,
// we must rebind everything.
// Since compute work happens less often, we always do that
// before and after the compute dispatch.
_cpBindingsManager.Rebind();
_cpBindingsManager.CommitBindings();
bool result = _cpBindingsManager.CommitBindings(specState);
_gpBindingsManager.Rebind();
return result;
}
/// <summary>
/// Commits bindings on the graphics pipeline.
/// </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();
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>

View File

@ -14,6 +14,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
private readonly GpuChannel _channel;
private readonly ConcurrentQueue<Texture> _dereferenceQueue = new ConcurrentQueue<Texture>();
private TextureDescriptor _defaultDescriptor;
/// <summary>
/// Intrusive linked list node used on the texture pool cache.
@ -32,6 +33,62 @@ namespace Ryujinx.Graphics.Gpu.Image
_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>
/// Gets the texture with the given ID.
/// </summary>
@ -51,56 +108,49 @@ namespace Ryujinx.Graphics.Gpu.Image
SynchronizeMemory();
}
Texture texture = Items[id];
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();
}
GetInternal(id, out Texture 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>
/// Forcibly remove a texture from this pool's items.
/// 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="layerSize">Layer size for textures using a sub-range of mipmap levels, otherwise 0</param>
/// <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 levels = descriptor.UnpackLevels();

View File

@ -378,6 +378,25 @@ namespace Ryujinx.Graphics.Gpu.Memory
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>
/// Ensures that the compute engine bindings are visible to the host GPU.
/// Note: this actually performs the binding using the host graphics API.

View File

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

View File

@ -418,7 +418,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
if (IsShaderEqual(channel.MemoryManager, cpShader.Shaders[0], gpuVa))
{
return cpShader.SpecializationState.MatchesCompute(channel, poolState);
return cpShader.SpecializationState.MatchesCompute(channel, poolState, true);
}
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>

View File

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

View File

@ -1,9 +1,14 @@
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Gpu.Shader.DiskCache;
using Ryujinx.Graphics.Shader;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Gpu.Shader
{
@ -158,6 +163,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
private readonly Dictionary<TextureKey, Box<TextureSpecializationState>> _textureSpecialization;
private KeyValuePair<TextureKey, Box<TextureSpecializationState>>[] _allTextures;
private Box<TextureSpecializationState>[][] _textureByBinding;
private Box<TextureSpecializationState>[][] _imageByBinding;
/// <summary>
/// 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>
/// Indicates that the shader accesses the early Z force state.
/// </summary>
@ -396,15 +446,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="channel">GPU channel</param>
/// <param name="poolState">Texture pool 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>
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)
{
return false;
}
return Matches(channel, poolState, isCompute: false);
return Matches(channel, poolState, checkTextures, isCompute: false);
}
/// <summary>
@ -412,10 +463,64 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary>
/// <param name="channel">GPU channel</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>
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>
@ -423,9 +528,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary>
/// <param name="channel">GPU channel</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>
/// <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;
@ -445,55 +551,62 @@ namespace Ryujinx.Graphics.Gpu.Shader
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;
ulong samplerCbAddress;
if (isCompute)
foreach (var kv in _allTextures)
{
textureCbAddress = channel.BufferManager.GetComputeUniformBufferAddress(textureBufferIndex);
samplerCbAddress = channel.BufferManager.GetComputeUniformBufferAddress(samplerBufferIndex);
}
else
{
textureCbAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(textureKey.StageIndex, textureBufferIndex);
samplerCbAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(textureKey.StageIndex, samplerBufferIndex);
}
TextureKey textureKey = kv.Key;
if (!channel.MemoryManager.Physical.IsMapped(textureCbAddress) || !channel.MemoryManager.Physical.IsMapped(samplerCbAddress))
{
continue;
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(textureKey.CbufSlot, poolState.TextureBufferIndex);
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;
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;
return true;
}
/// <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) &&
specializationState.Value.CoordNormalized != descriptor.UnpackTextureCoordNormalized())
{
@ -504,6 +617,34 @@ namespace Ryujinx.Graphics.Gpu.Shader
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>
/// Reads shader specialization state that has been serialized.
/// </summary>

View File

@ -9,10 +9,13 @@ namespace Ryujinx.Graphics.OpenGL
class Framebuffer : IDisposable
{
public int Handle { get; private set; }
private int _clearFbHandle;
private bool _clearFbInitialized;
private FramebufferAttachment _lastDsAttachment;
private readonly TextureView[] _colors;
private TextureView _depthStencil;
private int _colorsCount;
private bool _dualSourceBlend;
@ -20,6 +23,7 @@ namespace Ryujinx.Graphics.OpenGL
public Framebuffer()
{
Handle = GL.GenFramebuffer();
_clearFbHandle = GL.GenFramebuffer();
_colors = new TextureView[8];
}
@ -55,20 +59,7 @@ namespace Ryujinx.Graphics.OpenGL
if (depthStencil != null)
{
FramebufferAttachment attachment;
if (IsPackedDepthStencilFormat(depthStencil.Format))
{
attachment = FramebufferAttachment.DepthStencilAttachment;
}
else if (IsDepthOnlyFormat(depthStencil.Format))
{
attachment = FramebufferAttachment.DepthAttachment;
}
else
{
attachment = FramebufferAttachment.StencilAttachment;
}
FramebufferAttachment attachment = GetAttachment(depthStencil.Format);
GL.FramebufferTexture(
FramebufferTarget.Framebuffer,
@ -82,6 +73,8 @@ namespace Ryujinx.Graphics.OpenGL
{
_lastDsAttachment = 0;
}
_depthStencil = depthStencil;
}
public void SetDualSourceBlend(bool enable)
@ -124,6 +117,22 @@ namespace Ryujinx.Graphics.OpenGL
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)
{
return format == Format.D24UnormS8Uint ||
@ -136,6 +145,78 @@ namespace Ryujinx.Graphics.OpenGL
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()
{
if (Handle != 0)
@ -144,6 +225,13 @@ namespace Ryujinx.Graphics.OpenGL
Handle = 0;
}
if (_clearFbHandle != 0)
{
GL.DeleteFramebuffer(_clearFbHandle);
_clearFbHandle = 0;
}
}
}
}

View File

@ -0,0 +1,98 @@
using Ryujinx.Graphics.GAL;
using System;
using System.Collections.Generic;
namespace Ryujinx.Graphics.OpenGL.Image
{
class IntermmediatePool : IDisposable
{
private readonly Renderer _renderer;
private readonly List<TextureView> _entries;
public IntermmediatePool(Renderer renderer)
{
_renderer = renderer;
_entries = new List<TextureView>();
}
public TextureView GetOrCreateWithAtLeast(
Target target,
int blockWidth,
int blockHeight,
int bytesPerPixel,
Format format,
int width,
int height,
int depth,
int levels)
{
TextureView entry;
for (int i = 0; i < _entries.Count; i++)
{
entry = _entries[i];
if (entry.Target == target && entry.Format == format)
{
if (entry.Width < width || entry.Height < height || entry.Info.Depth < depth || entry.Info.Levels < levels)
{
width = Math.Max(width, entry.Width);
height = Math.Max(height, entry.Height);
depth = Math.Max(depth, entry.Info.Depth);
levels = Math.Max(levels, entry.Info.Levels);
entry.Dispose();
entry = CreateNew(target, blockWidth, blockHeight, bytesPerPixel, format, width, height, depth, levels);
_entries[i] = entry;
}
return entry;
}
}
entry = CreateNew(target, blockWidth, blockHeight, bytesPerPixel, format, width, height, depth, levels);
_entries.Add(entry);
return entry;
}
private TextureView CreateNew(
Target target,
int blockWidth,
int blockHeight,
int bytesPerPixel,
Format format,
int width,
int height,
int depth,
int levels)
{
return (TextureView)_renderer.CreateTexture(new TextureCreateInfo(
width,
height,
depth,
levels,
1,
blockWidth,
blockHeight,
bytesPerPixel,
format,
DepthStencilMode.Depth,
target,
SwizzleComponent.Red,
SwizzleComponent.Green,
SwizzleComponent.Blue,
SwizzleComponent.Alpha), 1f);
}
public void Dispose()
{
foreach (TextureView entry in _entries)
{
entry.Dispose();
}
_entries.Clear();
}
}
}

View File

@ -9,6 +9,8 @@ namespace Ryujinx.Graphics.OpenGL.Image
{
private readonly Renderer _renderer;
public IntermmediatePool IntermmediatePool { get; }
private int _srcFramebuffer;
private int _dstFramebuffer;
@ -18,6 +20,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
public TextureCopy(Renderer renderer)
{
_renderer = renderer;
IntermmediatePool = new IntermmediatePool(renderer);
}
public void Copy(
@ -25,7 +28,30 @@ namespace Ryujinx.Graphics.OpenGL.Image
TextureView dst,
Extents2D srcRegion,
Extents2D dstRegion,
bool linearFilter)
bool linearFilter,
int srcLayer = 0,
int dstLayer = 0,
int srcLevel = 0,
int dstLevel = 0)
{
int levels = Math.Min(src.Info.Levels - srcLevel, dst.Info.Levels - dstLevel);
int layers = Math.Min(src.Info.GetLayers() - srcLayer, dst.Info.GetLayers() - dstLayer);
Copy(src, dst, srcRegion, dstRegion, linearFilter, srcLayer, dstLayer, srcLevel, dstLevel, layers, levels);
}
public void Copy(
TextureView src,
TextureView dst,
Extents2D srcRegion,
Extents2D dstRegion,
bool linearFilter,
int srcLayer,
int dstLayer,
int srcLevel,
int dstLevel,
int layers,
int levels)
{
TextureView srcConverted = src.Format.IsBgr() != dst.Format.IsBgr() ? BgraSwap(src) : src;
@ -34,22 +60,29 @@ namespace Ryujinx.Graphics.OpenGL.Image
GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, GetSrcFramebufferLazy());
GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, GetDstFramebufferLazy());
int levels = Math.Min(src.Info.Levels, dst.Info.Levels);
int layers = Math.Min(src.Info.GetLayers(), dst.Info.GetLayers());
if (srcLevel != 0)
{
srcRegion = srcRegion.Reduce(srcLevel);
}
if (dstLevel != 0)
{
dstRegion = dstRegion.Reduce(dstLevel);
}
for (int level = 0; level < levels; level++)
{
for (int layer = 0; layer < layers; layer++)
{
if (layers > 1)
if ((srcLayer | dstLayer) != 0 || layers > 1)
{
Attach(FramebufferTarget.ReadFramebuffer, src.Format, srcConverted.Handle, level, layer);
Attach(FramebufferTarget.DrawFramebuffer, dst.Format, dst.Handle, level, layer);
Attach(FramebufferTarget.ReadFramebuffer, src.Format, srcConverted.Handle, srcLevel + level, srcLayer + layer);
Attach(FramebufferTarget.DrawFramebuffer, dst.Format, dst.Handle, dstLevel + level, dstLayer + layer);
}
else
{
Attach(FramebufferTarget.ReadFramebuffer, src.Format, srcConverted.Handle, level);
Attach(FramebufferTarget.DrawFramebuffer, dst.Format, dst.Handle, level);
Attach(FramebufferTarget.ReadFramebuffer, src.Format, srcConverted.Handle, srcLevel + level);
Attach(FramebufferTarget.DrawFramebuffer, dst.Format, dst.Handle, dstLevel + level);
}
ClearBufferMask mask = GetMask(src.Format);
@ -484,6 +517,8 @@ namespace Ryujinx.Graphics.OpenGL.Image
_copyPboHandle = 0;
}
IntermmediatePool.Dispose();
}
}
}

View File

@ -115,14 +115,77 @@ namespace Ryujinx.Graphics.OpenGL.Image
{
TextureView destinationView = (TextureView)destination;
_renderer.TextureCopy.CopyUnscaled(this, destinationView, 0, firstLayer, 0, firstLevel);
if (destinationView.Target.IsMultisample() || Target.IsMultisample())
{
Extents2D srcRegion = new Extents2D(0, 0, Width, Height);
Extents2D dstRegion = new Extents2D(0, 0, destinationView.Width, destinationView.Height);
TextureView intermmediate = _renderer.TextureCopy.IntermmediatePool.GetOrCreateWithAtLeast(
GetIntermmediateTarget(Target),
Info.BlockWidth,
Info.BlockHeight,
Info.BytesPerPixel,
Format,
Width,
Height,
Info.Depth,
Info.Levels);
GL.Disable(EnableCap.FramebufferSrgb);
_renderer.TextureCopy.Copy(this, intermmediate, srcRegion, srcRegion, true);
_renderer.TextureCopy.Copy(intermmediate, destinationView, srcRegion, dstRegion, true, 0, firstLayer, 0, firstLevel);
GL.Enable(EnableCap.FramebufferSrgb);
}
else
{
_renderer.TextureCopy.CopyUnscaled(this, destinationView, 0, firstLayer, 0, firstLevel);
}
}
public void CopyTo(ITexture destination, int srcLayer, int dstLayer, int srcLevel, int dstLevel)
{
TextureView destinationView = (TextureView)destination;
TextureView destinationView = (TextureView)destination;
_renderer.TextureCopy.CopyUnscaled(this, destinationView, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);
if (destinationView.Target.IsMultisample() || Target.IsMultisample())
{
Extents2D srcRegion = new Extents2D(0, 0, Width, Height);
Extents2D dstRegion = new Extents2D(0, 0, destinationView.Width, destinationView.Height);
TextureView intermmediate = _renderer.TextureCopy.IntermmediatePool.GetOrCreateWithAtLeast(
GetIntermmediateTarget(Target),
Info.BlockWidth,
Info.BlockHeight,
Info.BytesPerPixel,
Format,
Math.Max(1, Width >> srcLevel),
Math.Max(1, Height >> srcLevel),
1,
1);
GL.Disable(EnableCap.FramebufferSrgb);
_renderer.TextureCopy.Copy(this, intermmediate, srcRegion, srcRegion, true, srcLayer, 0, srcLevel, 0, 1, 1);
_renderer.TextureCopy.Copy(intermmediate, destinationView, srcRegion, dstRegion, true, 0, dstLayer, 0, dstLevel, 1, 1);
GL.Enable(EnableCap.FramebufferSrgb);
}
else
{
_renderer.TextureCopy.CopyUnscaled(this, destinationView, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);
}
}
private static Target GetIntermmediateTarget(Target srcTarget)
{
return srcTarget switch
{
Target.Texture2D => Target.Texture2DMultisample,
Target.Texture2DArray => Target.Texture2DMultisampleArray,
Target.Texture2DMultisampleArray => Target.Texture2DArray,
_ => Target.Texture2D
};
}
public void CopyTo(ITexture destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter)

View File

@ -110,7 +110,7 @@ namespace Ryujinx.Graphics.OpenGL
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(
index,
@ -119,14 +119,18 @@ namespace Ryujinx.Graphics.OpenGL
(componentMask & 4) != 0,
(componentMask & 8) != 0);
_framebuffer.AttachColorLayerForClear(index, layer);
float[] colors = new float[] { color.Red, color.Green, color.Blue, color.Alpha };
GL.ClearBuffer(OpenTK.Graphics.OpenGL.ClearBuffer.Color, index, colors);
_framebuffer.DetachColorLayerForClear(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 =
stencilMask != 0 &&
@ -144,6 +148,8 @@ namespace Ryujinx.Graphics.OpenGL
GL.DepthMask(depthMask);
}
_framebuffer.AttachDepthStencilLayerForClear(layer);
if (depthMask && stencilMask != 0)
{
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);
}
_framebuffer.DetachDepthStencilLayerForClear();
if (stencilMaskChanged)
{
GL.StencilMaskSeparate(StencilFace.Front, _stencilFrontMask);
@ -597,6 +605,8 @@ namespace Ryujinx.Graphics.OpenGL
GL.EndTransformFeedback();
}
GL.ClipControl(ClipOrigin.UpperLeft, ClipDepthMode.NegativeOneToOne);
_drawTexture.Draw(
view,
samp,
@ -627,6 +637,8 @@ namespace Ryujinx.Graphics.OpenGL
{
GL.BeginTransformFeedback(_tfTopology);
}
RestoreClipControl();
}
}
}

View File

@ -1,3 +1,4 @@
using System;
using System.Runtime.CompilerServices;
namespace Ryujinx.Graphics.Shader
@ -50,5 +51,63 @@ namespace Ryujinx.Graphics.Shader
{
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
{
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;
}
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 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;
@ -62,9 +67,9 @@ namespace Ryujinx.Graphics.Vic
Pixel* ip = srcPtr;
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> 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.Vic.Image;
using Ryujinx.Graphics.Vic.Types;
using System;
using System.Collections.Generic;
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);
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);

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 System.Diagnostics;
using System.Numerics;
namespace Ryujinx.HLE.HOS.Kernel.Memory
{
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 EndAddr { get; private set; }
public ulong Size { get; private set; }
private int _blockOrdersCount;
private readonly KMemoryRegionBlock[] _blocks;
public ulong Address { get; }
public ulong Size { get; }
public ulong EndAddr => Address + Size;
private readonly ushort[] _pageReferenceCounts;
public KMemoryRegionManager(ulong address, ulong size, ulong endAddr)
{
_blocks = new KMemoryRegionBlock[BlockOrders.Length];
Address = address;
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];
}
}
Size = size;
_pageReferenceCounts = new ushort[size / KPageTableBase.PageSize];
if (size != 0)
{
FreePages(address, size / KPageTableBase.PageSize);
}
_pageHeap = new KPageHeap(address, size);
_pageHeap.Free(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)
{
@ -112,9 +52,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
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)
{
@ -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();
if (_blockOrdersCount > 0)
{
if (GetFreePagesImpl() < pagesCount)
{
return KernelResult.OutOfMemory;
}
}
else if (pagesCount != 0)
int heapIndex = KPageHeap.GetBlockIndex(pagesCount);
if (heapIndex < 0)
{
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;
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)
while (pagesCount >= pagesPerAlloc)
{
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,
// just give up and try with the next one.
if (address == 0)
if (allocatedBlock == 0)
{
break;
}
// Add new allocated page(s) to the pages list.
// If an error occurs, then free all allocated pages and fail.
KernelResult result = pageList.AddRange(address, blockPagesCount);
KernelResult result = pageList.AddRange(allocatedBlock, pagesPerAlloc);
if (result != KernelResult.Success)
{
FreePages(address, blockPagesCount);
foreach (KPageNode pageNode in pageList)
{
FreePages(pageNode.Address, pageNode.PagesCount);
}
FreePages(pageList);
_pageHeap.Free(allocatedBlock, pagesPerAlloc);
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.
foreach (KPageNode pageNode in pageList)
{
FreePages(pageNode.Address, pageNode.PagesCount);
}
pageList = null;
return KernelResult.OutOfMemory;
return KernelResult.Success;
}
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;
}
int blockIndex = 0;
ulong allocatedPages = KPageHeap.GetBlockPagesCount(heapIndex);
while ((1UL << _blocks[blockIndex].Order) / KPageTableBase.PageSize < pagesCount)
if (allocatedPages > pagesCount)
{
if (++blockIndex >= _blocks.Length)
{
return 0;
}
_pageHeap.Free(allocatedBlock + pagesCount * KPageTableBase.PageSize, allocatedPages - pagesCount);
}
ulong tightestFitBlockSize = 1UL << _blocks[blockIndex].Order;
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;
return allocatedBlock;
}
private ulong AllocatePagesForOrder(int blockIndex, bool backwards, ulong bestFitBlockSize)
public void FreePage(ulong address)
{
ulong address = 0;
KMemoryRegionBlock block = null;
for (int currBlockIndex = blockIndex;
currBlockIndex < _blockOrdersCount && address == 0;
currBlockIndex++)
lock (_pageHeap)
{
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 + 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);
_pageHeap.Free(address, 1);
}
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;
int blockIndex = _blockOrdersCount - 1;
ulong addressRounded = 0;
ulong endAddrTruncated = 0;
for (; blockIndex >= 0; blockIndex--)
foreach (KPageNode pageNode in pageList)
{
KMemoryRegionBlock allocInfo = _blocks[blockIndex];
int blockSize = 1 << allocInfo.Order;
addressRounded = BitUtils.AlignUp (address, blockSize);
endAddrTruncated = BitUtils.AlignDown(endAddr, blockSize);
if (addressRounded < endAddrTruncated)
{
break;
}
_pageHeap.Free(pageNode.Address, pageNode.PagesCount);
}
}
}
void FreeRegion(ulong currAddress)
{
for (int currBlockIndex = blockIndex;
currBlockIndex < _blockOrdersCount && currAddress != 0;
currBlockIndex++)
{
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 void FreePages(ulong address, ulong pagesCount)
{
lock (_pageHeap)
{
_pageHeap.Free(address, pagesCount);
}
}
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)
{
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();
KernelResult result = region.AllocatePages(pagesCount, _aslrDisabled, out KPageList pageList);
KernelResult result = region.AllocatePages(out KPageList pageList, pagesCount);
if (result != KernelResult.Success)
{
@ -712,7 +712,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
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));
@ -1276,7 +1276,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
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));

View File

@ -735,11 +735,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
ulong argsPtr,
ulong stackTop,
int priority,
int cpuCore)
int cpuCore,
ThreadStart customThreadStart = null)
{
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();
SignalExit();
}
KernelStatic.GetCurrentThread().Exit();
}
private void UnpauseAndTerminateAllThreadsExcept(KThread currentThread)
@ -980,7 +983,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
foreach (KThread thread in _threads)
{
if ((thread.SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.TerminationPending)
if (thread != currentThread && (thread.SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.TerminationPending)
{
thread.PrepareForTermination();
}

View File

@ -2350,6 +2350,18 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
[PointerSized] ulong stackTop,
int priority,
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;
@ -2386,7 +2398,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
argsPtr,
stackTop,
priority,
cpuCore);
cpuCore,
customThreadStart);
if (result == KernelResult.Success)
{

View File

@ -90,7 +90,7 @@ namespace Ryujinx.HLE.HOS
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)
{

View File

@ -429,8 +429,6 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
Channel.PushHostCommandBuffer(CreateWaitCommandBuffer(header.Fence));
}
Channel.PushEntries(entries);
header.Fence.Id = _channelSyncpoint.Id;
if (header.Flags.HasFlag(SubmitGpfifoFlags.FenceIncrement) || header.Flags.HasFlag(SubmitGpfifoFlags.IncrementWithValue))
@ -449,6 +447,8 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
header.Fence.Value = _device.System.HostSyncpoint.ReadSyncpointMaxValue(header.Fence.Id);
}
Channel.PushEntries(entries);
if (header.Flags.HasFlag(SubmitGpfifoFlags.FenceIncrement))
{
Channel.PushHostCommandBuffer(CreateIncrementCommandBuffer(ref header.Fence, header.Flags));

View File

@ -1,3 +1,4 @@
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel;
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>>();
public ManualResetEvent InitDone { get; }
public Func<IpcService> SmObjectFactory { get; }
public string Name { get; }
public Func<IpcService> SmObjectFactory { get; }
public ServerBase(KernelContext context, string name, Func<IpcService> smObjectFactory = null)
{
InitDone = new ManualResetEvent(false);
_context = context;
Name = name;
SmObjectFactory = smObjectFactory;
_context = context;
const ProcessCreationFlags flags =
ProcessCreationFlags.EnableAslr |
@ -56,7 +57,7 @@ namespace Ryujinx.HLE.HOS.Services
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)
@ -80,6 +81,11 @@ namespace Ryujinx.HLE.HOS.Services
_sessions.Add(serverSessionHandle, obj);
}
private void Main()
{
ServerLoop();
}
private void ServerLoop()
{
_selfProcess = KernelStatic.GetCurrentProcess();

View File

@ -8,7 +8,6 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
{
private ulong _value;
private readonly EventFdFlags _flags;
private AutoResetEvent _event;
private object _lock = new object();
@ -19,9 +18,13 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
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;
_flags = flags;
_event = new AutoResetEvent(false);
WriteEvent = new ManualResetEvent(true);
ReadEvent = new ManualResetEvent(true);
@ -31,7 +34,6 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
public void Dispose()
{
_event.Dispose();
WriteEvent.Dispose();
ReadEvent.Dispose();
}
@ -57,7 +59,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
{
while (_value == 0)
{
_event.WaitOne();
Monitor.Wait(_lock);
}
}
else
@ -106,7 +108,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
{
if (Blocking)
{
_event.WaitOne();
Monitor.Wait(_lock);
}
else
{
@ -119,7 +121,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
writeSize = sizeof(ulong);
_value += count;
_event.Set();
Monitor.Pulse(_lock);
WriteEvent.Set();

View File

@ -2,7 +2,10 @@ using Ryujinx.Common.Logging;
using Ryujinx.Cpu;
using Ryujinx.HLE.HOS.Services.Time.TimeZone;
using Ryujinx.HLE.Utilities;
using Ryujinx.Memory;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
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);
ResultCode resultCode = _timeZoneContentManager.LoadTimeZoneRule(out TimeZoneRule rules, locationName);
// Write TimeZoneRule if success
if (resultCode == ResultCode.Success)
using (WritableRegion region = context.Memory.GetWritableRegion(bufferPosition, Unsafe.SizeOf<TimeZoneRule>()))
{
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)]

View File

@ -4,9 +4,12 @@ using Ryujinx.Cpu;
using Ryujinx.HLE.HOS.Services.Time.Clock;
using Ryujinx.HLE.HOS.Services.Time.TimeZone;
using Ryujinx.HLE.Utilities;
using Ryujinx.Memory;
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Time.StaticService
{
@ -165,11 +168,11 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
using (MemoryStream timeZoneBinaryStream = new MemoryStream(temp))
{
result = _timeZoneManager.ParseTimeZoneRuleBinary(out TimeZoneRule timeZoneRule, timeZoneBinaryStream);
if (result == ResultCode.Success)
using (WritableRegion region = context.Memory.GetWritableRegion(timeZoneRuleBufferPosition, Unsafe.SizeOf<TimeZoneRule>()))
{
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();
}
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)
{
@ -244,9 +247,9 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
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)
{

View File

@ -1,4 +1,5 @@
using Ryujinx.Common;
using Ryujinx.Common.Memory;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.Utilities;
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 }
};
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)]
private struct CalendarTimeInternal
@ -133,7 +134,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
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)
{
@ -150,7 +151,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
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;
@ -162,13 +163,13 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
return i;
}
private static int GetTZName(char[] name, int namePosition)
private static int GetTZName(ReadOnlySpan<byte> name, int namePosition)
{
int i = namePosition;
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++;
}
@ -176,7 +177,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
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;
@ -185,7 +186,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
return false;
}
char c = name[namePosition];
char c = (char)name[namePosition];
if (!char.IsDigit(c))
{
@ -205,7 +206,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
return false;
}
c = name[namePosition];
c = (char)name[namePosition];
}
while (char.IsDigit(c));
@ -217,7 +218,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
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;
@ -266,7 +267,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
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;
@ -304,7 +305,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
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();
@ -347,7 +348,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
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;
isValid = GetNum(name, ref namePosition, out rule.Day, 0, DaysPerLYear - 1);
@ -385,19 +386,13 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
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
{
Ats = new long[TzMaxTimes],
Types = new byte[TzMaxTimes],
Ttis = new TimeTypeInfo[TzMaxTypes],
Chars = new char[TzCharsArraySize]
};
outRules = new TimeZoneRule();
int stdLen;
ReadOnlySpan<char> stdName = name;
ReadOnlySpan<byte> stdName = name;
int namePosition = 0;
int stdOffset = 0;
@ -428,7 +423,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
}
else
{
namePosition = GetTZName(name.ToArray(), namePosition);
namePosition = GetTZName(name, namePosition);
stdLen = namePosition;
}
@ -449,7 +444,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
int destLen = 0;
int dstOffset = 0;
ReadOnlySpan<char> destName = name.Slice(namePosition);
ReadOnlySpan<byte> destName = name.Slice(namePosition);
if (TzCharsArraySize < charCount)
{
@ -476,7 +471,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
else
{
destName = name.Slice(namePosition);
namePosition = GetTZName(name.ToArray(), namePosition);
namePosition = GetTZName(name, namePosition);
destLen = namePosition;
}
@ -507,7 +502,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
if (name[namePosition] == '\0')
{
name = TimeZoneDefaultRule.ToCharArray();
name = TimeZoneDefaultRule;
namePosition = 0;
}
@ -515,7 +510,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
{
namePosition++;
bool IsRuleValid = GetRule(name.ToArray(), ref namePosition, out Rule start);
bool IsRuleValid = GetRule(name, ref namePosition, out Rule start);
if (!IsRuleValid)
{
return false;
@ -526,7 +521,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
return false;
}
IsRuleValid = GetRule(name.ToArray(), ref namePosition, out Rule end);
IsRuleValid = GetRule(name, ref namePosition, out Rule end);
if (!IsRuleValid)
{
return false;
@ -738,7 +733,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
}
charsPosition += stdLen;
outRules.Chars[charsPosition++] = '\0';
outRules.Chars[charsPosition++] = 0;
if (destLen != 0)
{
@ -746,7 +741,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
{
outRules.Chars[charsPosition + i] = destName[i];
}
outRules.Chars[charsPosition + destLen] = '\0';
outRules.Chars[charsPosition + destLen] = 0;
}
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
{
Ats = new long[TzMaxTimes],
Types = new byte[TzMaxTimes],
Ttis = new TimeTypeInfo[TzMaxTypes],
Chars = new char[TzCharsArraySize]
};
outRules = new TimeZoneRule();
BinaryReader reader = new BinaryReader(inputData);
@ -1020,10 +1009,10 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
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..];
outRules.Chars[outRules.CharCount] = '\0';
outRules.Chars[outRules.CharCount] = 0;
for (int i = 0; i < outRules.TypeCount; i++)
{
@ -1077,27 +1066,30 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
throw new InvalidOperationException();
}
char[] tempName = new char[TzNameMax + 1];
byte[] tempName = new byte[TzNameMax + 1];
Array.Copy(workBuffer, position, tempName, 0, nRead);
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);
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;
charCount = outRules.CharCount;
Span<char> chars = outRules.Chars;
Span<byte> chars = outRules.Chars;
for (int i = 0; i < tempRules.TypeCount; i++)
{
ReadOnlySpan<char> tempChars = tempRules.Chars;
ReadOnlySpan<char> tempAbbreviation = tempChars[tempRules.Ttis[i].AbbreviationListIndex..];
ReadOnlySpan<byte> tempChars = tempRules.Chars;
ReadOnlySpan<byte> tempAbbreviation = tempChars[tempRules.Ttis[i].AbbreviationListIndex..];
int j;
@ -1175,7 +1167,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
{
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;
break;
@ -1184,7 +1176,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
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;
break;
@ -1259,10 +1251,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
long remainingSeconds = time % SecondsPerDay;
calendarTime = new CalendarTimeInternal();
calendarAdditionalInfo = new CalendarAdditionalInfo()
{
TimezoneName = new char[8]
};
calendarAdditionalInfo = new CalendarAdditionalInfo();
while (timeDays < 0 || timeDays >= YearLengths[IsLeap((int)year)])
{
@ -1353,13 +1342,10 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
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();
calendarAdditionalInfo = new CalendarAdditionalInfo()
{
TimezoneName = new char[8]
};
calendarAdditionalInfo = new CalendarAdditionalInfo();
ResultCode result;
@ -1398,7 +1384,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
return ResultCode.TimeNotFound;
}
result = ToCalendarTimeInternal(rules, newTime, out calendarTime, out calendarAdditionalInfo);
result = ToCalendarTimeInternal(in rules, newTime, out calendarTime, out calendarAdditionalInfo);
if (result != 0)
{
return result;
@ -1450,17 +1436,17 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
{
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);
timeZoneAbbreviation[..timeZoneSize].CopyTo(calendarAdditionalInfo.TimezoneName.AsSpan());
timeZoneAbbreviation[..timeZoneSize].CopyTo(calendarAdditionalInfo.TimezoneName.ToSpan());
}
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;
@ -1604,7 +1590,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
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 (pivot > 0)
@ -1675,9 +1661,9 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
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()
{
@ -1697,7 +1683,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
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()
{
@ -1710,7 +1696,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
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.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
{
@ -149,7 +149,11 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
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;
if (tzRule.TimeCount > 0) // Find the current transition period
@ -174,10 +178,10 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
continue;
}
var abbrStart = tzRule.Chars.AsSpan(ttInfo.AbbreviationListIndex);
int abbrEnd = abbrStart.IndexOf('\0');
var abbrStart = tzRule.Chars[ttInfo.AbbreviationListIndex..];
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;
}
internal ResultCode LoadTimeZoneRule(out TimeZoneRule outRules, string locationName)
internal ResultCode LoadTimeZoneRule(ref TimeZoneRule rules, string locationName)
{
outRules = new TimeZoneRule
{
Ats = new long[TzMaxTimes],
Types = new byte[TzMaxTimes],
Ttis = new TimeTypeInfo[TzMaxTypes],
Chars = new char[TzCharsArraySize]
};
rules = default;
if (!HasTimeZoneBinaryTitle())
{
@ -295,7 +293,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
if (result == ResultCode.Success)
{
result = Manager.ParseTimeZoneRuleBinary(out outRules, timeZoneBinaryStream);
result = Manager.ParseTimeZoneRuleBinary(ref rules, timeZoneBinaryStream);
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 System.IO;
using static Ryujinx.HLE.HOS.Services.Time.TimeZone.TimeZoneRule;
namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
{
class TimeZoneManager
{
private bool _isInitialized;
private TimeZoneRule _myRules;
private Box<TimeZoneRule> _myRules;
private string _deviceLocationName;
private UInt128 _timeZoneRuleVersion;
private uint _totalLocationNameCount;
@ -21,15 +21,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
_deviceLocationName = "UTC";
_timeZoneRuleVersion = new UInt128();
_lock = new object();
// Empty rules
_myRules = new TimeZoneRule
{
Ats = new long[TzMaxTimes],
Types = new byte[TzMaxTimes],
Ttis = new TimeTypeInfo[TzMaxTypes],
Chars = new char[TzCharsArraySize]
};
_myRules = new Box<TimeZoneRule>();
_timeZoneUpdateTimePoint = SteadyClockTimePoint.GetRandom();
}
@ -78,7 +70,9 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
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)
{
@ -154,13 +148,13 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
return result;
}
public ResultCode ParseTimeZoneRuleBinary(out TimeZoneRule outRules, Stream timeZoneBinaryStream)
public ResultCode ParseTimeZoneRuleBinary(ref TimeZoneRule outRules, Stream timeZoneBinaryStream)
{
ResultCode result = ResultCode.Success;
lock (_lock)
{
bool timeZoneConversionSuccess = TimeZone.ParseTimeZoneBinary(out outRules, timeZoneBinaryStream);
bool timeZoneConversionSuccess = TimeZone.ParseTimeZoneBinary(ref outRules, timeZoneBinaryStream);
if (!timeZoneConversionSuccess)
{
@ -208,7 +202,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
{
if (_isInitialized)
{
result = ToCalendarTime(_myRules, time, out calendar);
result = ToCalendarTime(in _myRules.Data, time, out calendar);
}
else
{
@ -220,13 +214,13 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
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;
lock (_lock)
{
result = TimeZone.ToCalendarTime(rules, time, out calendar);
result = TimeZone.ToCalendarTime(in rules, time, out calendar);
}
return result;
@ -240,7 +234,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
{
if (_isInitialized)
{
result = ToPosixTime(_myRules, calendarTime, out posixTime);
result = ToPosixTime(in _myRules.Data, calendarTime, out posixTime);
}
else
{
@ -252,13 +246,13 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
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;
lock (_lock)
{
result = TimeZone.ToPosixTime(rules, calendarTime, out posixTime);
result = TimeZone.ToPosixTime(in rules, calendarTime, out posixTime);
}
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
{
@ -8,14 +9,12 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
public uint DayOfWeek;
public uint DayOfYear;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public char[] TimezoneName;
public Array8<byte> TimezoneName;
[MarshalAs(UnmanagedType.I1)]
public bool IsDaySavingTime;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public char[] Padding;
public Array3<byte> Padding;
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
{
[StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 4)]
struct TimeTypeInfo
[StructLayout(LayoutKind.Sequential, Size = Size, Pack = 4)]
public struct TimeTypeInfo
{
public const int Size = 0x10;
public int GmtOffset;
[MarshalAs(UnmanagedType.I1)]
public bool IsDaySavingTime;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public char[] Padding1;
public Array3<byte> Padding1;
public int AbbreviationListIndex;
@ -21,7 +23,6 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
[MarshalAs(UnmanagedType.I1)]
public bool IsGMT;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
public char[] Padding2;
public ushort 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
{
[StructLayout(LayoutKind.Sequential, Pack = 4, Size = 0x4000, CharSet = CharSet.Ansi)]
struct TimeZoneRule
public struct TimeZoneRule
{
public const int TzMaxTypes = 128;
public const int TzMaxChars = 50;
public const int TzMaxLeaps = 50;
public const int TzMaxTimes = 1000;
public const int TzNameMax = 255;
public const int TzCharsArraySize = 2 * (TzNameMax + 1);
public const int TzMaxTypes = 128;
public const int TzMaxChars = 50;
public const int TzMaxLeaps = 50;
public const int TzMaxTimes = 1000;
public const int TzNameMax = 255;
public const int TzCharsArraySize = 2 * (TzNameMax + 1);
public int TimeCount;
public int TypeCount;
@ -22,17 +24,32 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
[MarshalAs(UnmanagedType.I1)]
public bool GoAhead;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = TzMaxTimes)]
public long[] Ats;
[StructLayout(LayoutKind.Sequential, Size = sizeof(long) * TzMaxTimes)]
private struct AtsStorageStruct { }
[MarshalAs(UnmanagedType.ByValArray, SizeConst = TzMaxTimes)]
public byte[] Types;
private AtsStorageStruct _ats;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = TzMaxTypes)]
public TimeTypeInfo[] Ttis;
public Span<long> Ats => SpanHelpers.AsSpan<AtsStorageStruct, long>(ref _ats);
[MarshalAs(UnmanagedType.ByValArray, SizeConst = TzCharsArraySize)]
public char[] Chars;
[StructLayout(LayoutKind.Sequential, Size = sizeof(byte) * TzMaxTimes)]
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;
}

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

View File

@ -79,6 +79,13 @@ namespace Ryujinx.Input.SDL2
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))
{
_gamepadsIds.Add(id);

View File

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

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 (force4KBMap)
if (owner.ForceWindows4KBView)
{
MemoryManagementWindows.MapView4KB(sharedMemory, srcOffset, address, (IntPtr)size);
}
else
{
MemoryManagementWindows.MapView(sharedMemory, srcOffset, address, (IntPtr)size);
MemoryManagementWindows.MapView(sharedMemory, srcOffset, address, (IntPtr)size, owner);
}
}
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 (force4KBMap)
if (owner.ForceWindows4KBView)
{
MemoryManagementWindows.UnmapView4KB(address, (IntPtr)size);
}
else
{
MemoryManagementWindows.UnmapView(sharedMemory, address, (IntPtr)size);
MemoryManagementWindows.UnmapView(sharedMemory, address, (IntPtr)size, owner);
}
}
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())

View File

@ -68,9 +68,9 @@ namespace Ryujinx.Memory
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)
@ -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)
@ -154,7 +154,7 @@ namespace Ryujinx.Memory
}
else
{
_placeholders.UnmapView(IntPtr.Zero, address, size);
_placeholders.UnreserveRange((ulong)address, (ulong)size);
}
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.
// Signal up the chain to relevant handles.
bool shouldThrow = false;
lock (TrackingLock)
{
ref var overlaps = ref ThreadStaticArray<VirtualRegion>.Get();
@ -235,32 +237,41 @@ namespace Ryujinx.Memory.Tracking
if (count == 0 && !precise)
{
if (!_memoryManager.IsMapped(address))
if (_memoryManager.IsMapped(address))
{
_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();
}
_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);
_memoryManager.TrackingReprotect(address & ~(ulong)(_pageSize - 1), (ulong)_pageSize, MemoryPermission.ReadAndWrite);
return false; // We can't handle this - it's probably a real invalid access.
}
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;

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>
/// Maps a shared memory view on a previously reserved memory region.
/// </summary>
@ -51,13 +95,14 @@ namespace Ryujinx.Memory.WindowsShared
/// <param name="srcOffset">Offset in the shared memory to map</param>
/// <param name="location">Address to map the view into</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);
try
{
UnmapViewInternal(sharedMemory, location, size);
UnmapViewInternal(sharedMemory, location, size, owner);
MapViewInternal(sharedMemory, srcOffset, location, size);
}
finally
@ -173,13 +218,14 @@ namespace Ryujinx.Memory.WindowsShared
/// <param name="sharedMemory">Shared memory that the view being unmapped belongs to</param>
/// <param name="location">Address to unmap</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);
try
{
UnmapViewInternal(sharedMemory, location, size);
UnmapViewInternal(sharedMemory, location, size, owner);
}
finally
{
@ -197,8 +243,9 @@ namespace Ryujinx.Memory.WindowsShared
/// <param name="sharedMemory">Shared memory that the view being unmapped belongs to</param>
/// <param name="location">Address to unmap</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>
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 unmapSize = (ulong)size;
@ -272,7 +319,7 @@ namespace Ryujinx.Memory.WindowsShared
}
}
CoalesceForUnmap(startAddress, unmapSize);
CoalesceForUnmap(startAddress, unmapSize, owner);
RemoveProtection(startAddress, unmapSize);
}
@ -281,15 +328,21 @@ namespace Ryujinx.Memory.WindowsShared
/// </summary>
/// <param name="address">Address of the region that was unmapped</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 blockAddress = (ulong)owner.Pointer;
ulong blockEnd = blockAddress + owner.Size;
var overlaps = Array.Empty<IntervalTreeNode<ulong, ulong>>();
int unmappedCount = 0;
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)
{
// Nothing to coalesce if we only have 1 or no overlaps.

View File

@ -24,6 +24,7 @@
<ItemGroup>
<ProjectReference Include="..\Ryujinx.Audio\Ryujinx.Audio.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.Tests.Unicorn\Ryujinx.Tests.Unicorn.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>());
}
}
}
}