Compare commits

..

21 Commits

Author SHA1 Message Date
gdkchan
232b1012b0 Fix ThreadingLock deadlock on invalid access and TerminateProcess (#3407) 2022-06-24 02:53:16 +02:00
gdkchan
e747f5cd83 Ensure texture ID is valid before getting texture descriptor (#3406) 2022-06-24 02:41:57 +02:00
Ac_K
8aff17a93c UI: Some Avalonia cleanup (#3358) 2022-06-23 15:59:02 -03:00
gdkchan
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
RhavoX
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
riperiperi
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
riperiperi
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
gdkchan
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
gdkchan
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
gdkchan
b1bd6a50b5 Less invasive fix for EventFd blocking operations (#3394) 2022-06-12 09:29:12 +02:00
gdkchan
70895bdb04 Allow concurrent BSD EventFd read/write (#3385) 2022-06-11 14:58:30 -03:00
gdkchan
830cbf91bb Ignore ClipControl on draw texture fallback (#3388) 2022-06-11 14:31:17 -03:00
gdkchan
9a9349f0f4 Fix instanced indexed inline draw index count (#3389) 2022-06-10 23:44:49 -03:00
gdkchan
46cc7b55f0 Fix instanced indexed inline draws (#3383) 2022-06-05 21:24:28 -03:00
gdkchan
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
gdkchan
633c5ec330 Extend uses count from ushort to uint on Operand Data structure (#3374) 2022-06-05 14:15:27 -03:00
gdkchan
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
Billy Laws
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
Billy Laws
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
Mary
59490d54b5 infra: Switch to win10-x64 RID and fix PR comment for Avalonia and SDL2 artifact rename (#3375)
* infra: Switch to win10-x64 RID and fix PR comment for Avalonia and SDL2 artifact rename

* Address gdkchan's comments
2022-06-01 02:01:16 +02:00
gdkchan
e546e5933f Rewrite SVC handler using source generators rather than IL emit (#3371)
* Implement syscall handlers using a source generator

* Copy FlushProcessDataCache implementation to Syscall since it was only implemented on Syscall32

* Fix wrong argument order in some syscalls

* Delete old Reflection.Emit based syscall handling code

* Improvements to the code generation

* ControlCodeMemory address and size is always 64-bit
2022-05-31 17:12:46 -03:00
93 changed files with 4168 additions and 3106 deletions

View File

@@ -39,7 +39,7 @@ jobs:
- os: windows-latest - os: windows-latest
OS_NAME: Windows x64 OS_NAME: Windows x64
DOTNET_RUNTIME_IDENTIFIER: win-x64 DOTNET_RUNTIME_IDENTIFIER: win10-x64
RELEASE_ZIP_OS_NAME: win_x64 RELEASE_ZIP_OS_NAME: win_x64
fail-fast: false fail-fast: false

View File

@@ -36,19 +36,24 @@ jobs:
return core.error(`No artifacts found`); return core.error(`No artifacts found`);
} }
let body = `Download the artifacts for this pull request:\n`; let body = `Download the artifacts for this pull request:\n`;
let hidden_avalonia_artifacts = `\n\n <details><summary>Experimental GUI (Avalonia)</summary>\n`;
let hidden_headless_artifacts = `\n\n <details><summary>GUI-less (SDL2)</summary>\n`; let hidden_headless_artifacts = `\n\n <details><summary>GUI-less (SDL2)</summary>\n`;
let hidden_debug_artifacts = `\n\n <details><summary>Only for Developers</summary>\n`; let hidden_debug_artifacts = `\n\n <details><summary>Only for Developers</summary>\n`;
for (const art of artifacts) { for (const art of artifacts) {
if(art.name.includes('Debug')) { if(art.name.includes('Debug')) {
hidden_debug_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`; hidden_debug_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
} else if(art.name.includes('headless-sdl2')) { } else if(art.name.includes('ava-ryujinx')) {
hidden_avalonia_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
} else if(art.name.includes('sdl2-ryujinx-headless')) {
hidden_headless_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`; hidden_headless_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
} else { } else {
body += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`; body += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
} }
} }
hidden_avalonia_artifacts += `\n</details>`;
hidden_headless_artifacts += `\n</details>`; hidden_headless_artifacts += `\n</details>`;
hidden_debug_artifacts += `\n</details>`; hidden_debug_artifacts += `\n</details>`;
body += hidden_avalonia_artifacts;
body += hidden_headless_artifacts; body += hidden_headless_artifacts;
body += hidden_debug_artifacts; body += hidden_debug_artifacts;

View File

@@ -51,9 +51,9 @@ jobs:
run: "mkdir release_output" run: "mkdir release_output"
- name: Publish Windows - name: Publish Windows
run: | run: |
dotnet publish -c Release -r win-x64 -o ./publish_windows/publish /p:Version="${{ steps.version_info.outputs.build_version }}" /p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" /p:DebugType=embedded Ryujinx --self-contained dotnet publish -c Release -r win10-x64 -o ./publish_windows/publish /p:Version="${{ steps.version_info.outputs.build_version }}" /p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" /p:DebugType=embedded Ryujinx --self-contained
dotnet publish -c Release -r win-x64 -o ./publish_windows_sdl2_headless/publish /p:Version="${{ steps.version_info.outputs.build_version }}" /p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" /p:DebugType=embedded Ryujinx.Headless.SDL2 --self-contained dotnet publish -c Release -r win10-x64 -o ./publish_windows_sdl2_headless/publish /p:Version="${{ steps.version_info.outputs.build_version }}" /p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" /p:DebugType=embedded Ryujinx.Headless.SDL2 --self-contained
dotnet publish -c Release -r win-x64 -o ./publish_windows_ava/publish /p:Version="${{ steps.version_info.outputs.build_version }}" /p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" /p:DebugType=embedded Ryujinx.Ava --self-contained dotnet publish -c Release -r win10-x64 -o ./publish_windows_ava/publish /p:Version="${{ steps.version_info.outputs.build_version }}" /p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" /p:DebugType=embedded Ryujinx.Ava --self-contained
- name: Packing Windows builds - name: Packing Windows builds
run: | run: |
pushd publish_windows pushd publish_windows

View File

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

View File

@@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<RuntimeIdentifiers>win-x64;linux-x64;osx-x64</RuntimeIdentifiers> <RuntimeIdentifiers>win10-x64;linux-x64;osx-x64</RuntimeIdentifiers>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@@ -15,11 +15,11 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>libsoundio.dll</TargetPath> <TargetPath>libsoundio.dll</TargetPath>
</ContentWithTargetPath> </ContentWithTargetPath>
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.dylib" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win-x64'"> <ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.dylib" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win10-x64'">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>libsoundio.dylib</TargetPath> <TargetPath>libsoundio.dylib</TargetPath>
</ContentWithTargetPath> </ContentWithTargetPath>
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.so" Condition="'$(RuntimeIdentifier)' != 'win-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'"> <ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.so" Condition="'$(RuntimeIdentifier)' != 'win10-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>libsoundio.so</TargetPath> <TargetPath>libsoundio.so</TargetPath>
</ContentWithTargetPath> </ContentWithTargetPath>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers> <RuntimeIdentifiers>win10-x64;osx-x64;linux-x64</RuntimeIdentifiers>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Version>1.0.0-dirty</Version> <Version>1.0.0-dirty</Version>

View File

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

View File

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

View File

@@ -1,13 +1,16 @@
<UserControl xmlns="https://github.com/avaloniaui" <UserControl
x:Class="Ryujinx.Ava.Ui.Controls.GameGridView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 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:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:flex="clr-namespace:Avalonia.Flexbox;assembly=Avalonia.Flexbox" 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:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" d:DesignHeight="450"
x:Class="Ryujinx.Ava.Ui.Controls.GameGridView"> d:DesignWidth="800"
mc:Ignorable="d">
<UserControl.Resources> <UserControl.Resources>
<controls:BitmapArrayValueConverter x:Key="ByteImage" /> <controls:BitmapArrayValueConverter x:Key="ByteImage" />
<MenuFlyout x:Key="GameContextMenu" Opened="MenuBase_OnMenuOpened"> <MenuFlyout x:Key="GameContextMenu" Opened="MenuBase_OnMenuOpened">
@@ -88,18 +91,22 @@
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="*" /> <RowDefinition Height="*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<ListBox Grid.Row="0" <ListBox
Grid.Row="0"
Padding="8" Padding="8"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
DoubleTapped="GameList_DoubleTapped"
SelectionChanged="GameList_SelectionChanged"
ContextFlyout="{StaticResource GameContextMenu}"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
Items="{Binding AppsObservableList}"> ContextFlyout="{StaticResource GameContextMenu}"
DoubleTapped="GameList_DoubleTapped"
Items="{Binding AppsObservableList}"
SelectionChanged="GameList_SelectionChanged">
<ListBox.ItemsPanel> <ListBox.ItemsPanel>
<ItemsPanelTemplate> <ItemsPanelTemplate>
<flex:FlexPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch" JustifyContent="Center" <flex:FlexPanel
AlignContent="FlexStart" /> HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
AlignContent="FlexStart"
JustifyContent="Center" />
</ItemsPanelTemplate> </ItemsPanelTemplate>
</ListBox.ItemsPanel> </ListBox.ItemsPanel>
<ListBox.Styles> <ListBox.Styles>
@@ -111,16 +118,16 @@
<Style.Animations> <Style.Animations>
<Animation Duration="0:0:0.7"> <Animation Duration="0:0:0.7">
<KeyFrame Cue="0%"> <KeyFrame Cue="0%">
<Setter Property="MaxWidth" Value="0"/> <Setter Property="MaxWidth" Value="0" />
<Setter Property="Opacity" Value="0.0"/> <Setter Property="Opacity" Value="0.0" />
</KeyFrame> </KeyFrame>
<KeyFrame Cue="50%"> <KeyFrame Cue="50%">
<Setter Property="MaxWidth" Value="1000"/> <Setter Property="MaxWidth" Value="1000" />
<Setter Property="Opacity" Value="0.3"/> <Setter Property="Opacity" Value="0.3" />
</KeyFrame> </KeyFrame>
<KeyFrame Cue="100%"> <KeyFrame Cue="100%">
<Setter Property="MaxWidth" Value="1000"/> <Setter Property="MaxWidth" Value="1000" />
<Setter Property="Opacity" Value="1.0"/> <Setter Property="Opacity" Value="1.0" />
</KeyFrame> </KeyFrame>
</Animation> </Animation>
</Style.Animations> </Style.Animations>
@@ -144,42 +151,66 @@
</Style> </Style>
</Grid.Styles> </Grid.Styles>
<Border <Border
Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}" Margin="0"
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}" Padding="{Binding $parent[UserControl].DataContext.GridItemPadding}"
Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}"
Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Padding="{Binding $parent[UserControl].DataContext.GridItemPadding}" CornerRadius="5" VerticalAlignment="Stretch"
VerticalAlignment="Stretch" Margin="0" ClipToBounds="True"> Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}"
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 Margin="0">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Image HorizontalAlignment="Stretch" VerticalAlignment="Top" Margin="0" Grid.Row="0" <Image
Grid.Row="0"
Margin="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Source="{Binding Icon, Converter={StaticResource ByteImage}}" /> Source="{Binding Icon, Converter={StaticResource ByteImage}}" />
<StackPanel IsVisible="{Binding $parent[UserControl].DataContext.ShowNames}" <StackPanel
Height="50" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Row="1"
Margin="5" Grid.Row="1"> Height="50"
<TextBlock Text="{Binding TitleName}" TextAlignment="Center" TextWrapping="Wrap" Margin="5"
HorizontalAlignment="Stretch" /> HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
IsVisible="{Binding $parent[UserControl].DataContext.ShowNames}">
<TextBlock
HorizontalAlignment="Stretch"
Text="{Binding TitleName}"
TextAlignment="Center"
TextWrapping="Wrap" />
</StackPanel> </StackPanel>
</Grid> </Grid>
</Border> </Border>
<ui:SymbolIcon Classes.icon="true" Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}" <ui:SymbolIcon
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}" Margin="5"
Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}" HorizontalAlignment="Left"
VerticalAlignment="Top"
Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}" Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}"
Foreground="Yellow" Symbol="StarFilled" Classes.icon="true"
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.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.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}"
Foreground="Black" Symbol="Star" Classes.icon="true"
IsVisible="{Binding Favorite}" Margin="5" VerticalAlignment="Top" Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}"
HorizontalAlignment="Left" /> Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
Foreground="Black"
IsVisible="{Binding Favorite}"
Symbol="Star" />
</Grid> </Grid>
</DataTemplate> </DataTemplate>
</ListBox.ItemTemplate> </ListBox.ItemTemplate>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -100,6 +100,11 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
public bool AlwaysFlushOnOverlap { get; private set; } public bool AlwaysFlushOnOverlap { get; private set; }
/// <summary>
/// Increments when the host texture is swapped, or when the texture is removed from all pools.
/// </summary>
public int InvalidatedSequence { get; private set; }
private int _depth; private int _depth;
private int _layers; private int _layers;
public int FirstLayer { get; private set; } public int FirstLayer { get; private set; }
@@ -1136,33 +1141,23 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="range">Texture view physical memory ranges</param> /// <param name="range">Texture view physical memory ranges</param>
/// <param name="layerSize">Layer size on the given texture</param> /// <param name="layerSize">Layer size on the given texture</param>
/// <param name="caps">Host GPU capabilities</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="firstLayer">Texture view initial layer on this texture</param>
/// <param name="firstLevel">Texture view first mipmap level 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> /// <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; TextureViewCompatibility result = TextureViewCompatibility.Full;
result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewFormatCompatible(Info, info, caps)); result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewFormatCompatible(Info, info, caps));
if (result != TextureViewCompatibility.Incompatible) if (result != TextureViewCompatibility.Incompatible)
{
bool msTargetCompatible = false;
if (allowMs)
{
msTargetCompatible = Info.Target == Target.Texture2DMultisample && info.Target == Target.Texture2D;
}
if (!msTargetCompatible)
{ {
result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewTargetCompatible(Info, info)); result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewTargetCompatible(Info, info));
if (Info.SamplesInX != info.SamplesInX || Info.SamplesInY != info.SamplesInY) bool bothMs = Info.Target.IsMultisample() && info.Target.IsMultisample();
if (bothMs && (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) if (result == TextureViewCompatibility.Full && Info.FormatInfo.Format != info.FormatInfo.Format && !_context.Capabilities.SupportsMismatchingViewFormat)
{ {
@@ -1417,6 +1412,7 @@ namespace Ryujinx.Graphics.Gpu.Image
DisposeTextures(); DisposeTextures();
HostTexture = hostTexture; HostTexture = hostTexture;
InvalidatedSequence++;
} }
/// <summary> /// <summary>
@@ -1545,6 +1541,8 @@ namespace Ryujinx.Graphics.Gpu.Image
_poolOwners.Clear(); _poolOwners.Clear();
} }
InvalidatedSequence++;
} }
/// <summary> /// <summary>

View File

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

View File

@@ -349,6 +349,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="memoryManager">GPU memory manager where the texture is mapped</param> /// <param name="memoryManager">GPU memory manager where the texture is mapped</param>
/// <param name="dsState">Depth-stencil buffer texture to find or create</param> /// <param name="dsState">Depth-stencil buffer texture to find or create</param>
/// <param name="size">Size of the depth-stencil texture</param> /// <param name="size">Size of the depth-stencil texture</param>
/// <param name="layered">Indicates if the texture might be accessed with a non-zero layer index</param>
/// <param name="samplesInX">Number of samples in the X direction, for MSAA</param> /// <param name="samplesInX">Number of samples in the X direction, for MSAA</param>
/// <param name="samplesInY">Number of samples in the Y direction, for MSAA</param> /// <param name="samplesInY">Number of samples in the Y direction, for MSAA</param>
/// <param name="sizeHint">A hint indicating the minimum used size for the texture</param> /// <param name="sizeHint">A hint indicating the minimum used size for the texture</param>
@@ -357,6 +358,7 @@ namespace Ryujinx.Graphics.Gpu.Image
MemoryManager memoryManager, MemoryManager memoryManager,
RtDepthStencilState dsState, RtDepthStencilState dsState,
Size3D size, Size3D size,
bool layered,
int samplesInX, int samplesInX,
int samplesInY, int samplesInY,
Size sizeHint) Size sizeHint)
@@ -364,9 +366,24 @@ namespace Ryujinx.Graphics.Gpu.Image
int gobBlocksInY = dsState.MemoryLayout.UnpackGobBlocksInY(); int gobBlocksInY = dsState.MemoryLayout.UnpackGobBlocksInY();
int gobBlocksInZ = dsState.MemoryLayout.UnpackGobBlocksInZ(); int gobBlocksInZ = dsState.MemoryLayout.UnpackGobBlocksInZ();
Target target = (samplesInX | samplesInY) != 1 Target target;
? Target.Texture2DMultisample
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; : Target.Texture2D;
}
FormatInfo formatInfo = dsState.Format.Convert(); FormatInfo formatInfo = dsState.Format.Convert();
@@ -547,7 +564,6 @@ namespace Ryujinx.Graphics.Gpu.Image
range.Value, range.Value,
sizeInfo.LayerSize, sizeInfo.LayerSize,
_context.Capabilities, _context.Capabilities,
flags.HasFlag(TextureSearchFlags.ForCopy),
out int firstLayer, out int firstLayer,
out int firstLevel); out int firstLevel);
@@ -662,7 +678,6 @@ namespace Ryujinx.Graphics.Gpu.Image
overlap.Range, overlap.Range,
overlap.LayerSize, overlap.LayerSize,
_context.Capabilities, _context.Capabilities,
false,
out int firstLayer, out int firstLayer,
out int firstLevel); out int firstLevel);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -115,15 +115,78 @@ namespace Ryujinx.Graphics.OpenGL.Image
{ {
TextureView destinationView = (TextureView)destination; TextureView destinationView = (TextureView)destination;
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); _renderer.TextureCopy.CopyUnscaled(this, destinationView, 0, firstLayer, 0, firstLevel);
} }
}
public void CopyTo(ITexture destination, int srcLayer, int dstLayer, int srcLevel, int dstLevel) public void CopyTo(ITexture destination, int srcLayer, int dstLayer, int srcLevel, int dstLevel)
{ {
TextureView destinationView = (TextureView)destination; TextureView destinationView = (TextureView)destination;
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); _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) 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); Buffer.Clear(destination, offset, size, value);
} }
public void ClearRenderTargetColor(int index, uint componentMask, ColorF color) public void ClearRenderTargetColor(int index, int layer, uint componentMask, ColorF color)
{ {
GL.ColorMask( GL.ColorMask(
index, index,
@@ -119,14 +119,18 @@ namespace Ryujinx.Graphics.OpenGL
(componentMask & 4) != 0, (componentMask & 4) != 0,
(componentMask & 8) != 0); (componentMask & 8) != 0);
_framebuffer.AttachColorLayerForClear(index, layer);
float[] colors = new float[] { color.Red, color.Green, color.Blue, color.Alpha }; float[] colors = new float[] { color.Red, color.Green, color.Blue, color.Alpha };
GL.ClearBuffer(OpenTK.Graphics.OpenGL.ClearBuffer.Color, index, colors); GL.ClearBuffer(OpenTK.Graphics.OpenGL.ClearBuffer.Color, index, colors);
_framebuffer.DetachColorLayerForClear(index);
RestoreComponentMask(index); RestoreComponentMask(index);
} }
public void ClearRenderTargetDepthStencil(float depthValue, bool depthMask, int stencilValue, int stencilMask) public void ClearRenderTargetDepthStencil(int layer, float depthValue, bool depthMask, int stencilValue, int stencilMask)
{ {
bool stencilMaskChanged = bool stencilMaskChanged =
stencilMask != 0 && stencilMask != 0 &&
@@ -144,6 +148,8 @@ namespace Ryujinx.Graphics.OpenGL
GL.DepthMask(depthMask); GL.DepthMask(depthMask);
} }
_framebuffer.AttachDepthStencilLayerForClear(layer);
if (depthMask && stencilMask != 0) if (depthMask && stencilMask != 0)
{ {
GL.ClearBuffer(ClearBufferCombined.DepthStencil, 0, depthValue, stencilValue); GL.ClearBuffer(ClearBufferCombined.DepthStencil, 0, depthValue, stencilValue);
@@ -157,6 +163,8 @@ namespace Ryujinx.Graphics.OpenGL
GL.ClearBuffer(OpenTK.Graphics.OpenGL.ClearBuffer.Stencil, 0, ref stencilValue); GL.ClearBuffer(OpenTK.Graphics.OpenGL.ClearBuffer.Stencil, 0, ref stencilValue);
} }
_framebuffer.DetachDepthStencilLayerForClear();
if (stencilMaskChanged) if (stencilMaskChanged)
{ {
GL.StencilMaskSeparate(StencilFace.Front, _stencilFrontMask); GL.StencilMaskSeparate(StencilFace.Front, _stencilFrontMask);
@@ -597,6 +605,8 @@ namespace Ryujinx.Graphics.OpenGL
GL.EndTransformFeedback(); GL.EndTransformFeedback();
} }
GL.ClipControl(ClipOrigin.UpperLeft, ClipDepthMode.NegativeOneToOne);
_drawTexture.Draw( _drawTexture.Draw(
view, view,
samp, samp,
@@ -627,6 +637,8 @@ namespace Ryujinx.Graphics.OpenGL
{ {
GL.BeginTransformFeedback(_tfTopology); GL.BeginTransformFeedback(_tfTopology);
} }
RestoreClipControl();
} }
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,9 +0,0 @@
using System;
namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
{
class InvalidSvcException : Exception
{
public InvalidSvcException(string message) : base(message) { }
}
}

View File

@@ -0,0 +1,9 @@
using System;
namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
{
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
class PointerSizedAttribute : Attribute
{
}
}

View File

@@ -1,15 +0,0 @@
using System;
namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
{
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
public class RAttribute : Attribute
{
public readonly int Index;
public RAttribute(int index)
{
Index = index;
}
}
}

View File

@@ -0,0 +1,15 @@
using System;
namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
class SvcAttribute : Attribute
{
public int Id { get; }
public SvcAttribute(int id)
{
Id = id;
}
}
}

View File

@@ -0,0 +1,9 @@
using System;
namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
class SvcImplAttribute : Attribute
{
}
}

View File

@@ -12,6 +12,7 @@ using System.Threading;
namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
{ {
[SvcImpl]
class Syscall class Syscall
{ {
private readonly KernelContext _context; private readonly KernelContext _context;
@@ -23,6 +24,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
// Process // Process
[Svc(0x24)]
public KernelResult GetProcessId(out ulong pid, int handle) public KernelResult GetProcessId(out ulong pid, int handle)
{ {
KProcess currentProcess = KernelStatic.GetCurrentProcess(); KProcess currentProcess = KernelStatic.GetCurrentProcess();
@@ -167,9 +169,18 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return KernelResult.Success; return KernelResult.Success;
} }
[Svc(0x5f)]
public KernelResult FlushProcessDataCache(int processHandle, ulong address, ulong size)
{
// FIXME: This needs to be implemented as ARMv7 doesn't have any way to do cache maintenance operations on EL0.
// As we don't support (and don't actually need) to flush the cache, this is stubbed.
return KernelResult.Success;
}
// IPC // IPC
public KernelResult ConnectToNamedPort(out int handle, ulong namePtr) [Svc(0x1f)]
public KernelResult ConnectToNamedPort(out int handle, [PointerSized] ulong namePtr)
{ {
handle = 0; handle = 0;
@@ -222,6 +233,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return result; return result;
} }
[Svc(0x21)]
public KernelResult SendSyncRequest(int handle) public KernelResult SendSyncRequest(int handle)
{ {
KProcess currentProcess = KernelStatic.GetCurrentProcess(); KProcess currentProcess = KernelStatic.GetCurrentProcess();
@@ -236,7 +248,11 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return session.SendSyncRequest(); return session.SendSyncRequest();
} }
public KernelResult SendSyncRequestWithUserBuffer(ulong messagePtr, ulong messageSize, int handle) [Svc(0x22)]
public KernelResult SendSyncRequestWithUserBuffer(
[PointerSized] ulong messagePtr,
[PointerSized] ulong messageSize,
int handle)
{ {
if (!PageAligned(messagePtr)) if (!PageAligned(messagePtr))
{ {
@@ -283,7 +299,12 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return result; return result;
} }
public KernelResult SendAsyncRequestWithUserBuffer(out int doneEventHandle, ulong messagePtr, ulong messageSize, int handle) [Svc(0x23)]
public KernelResult SendAsyncRequestWithUserBuffer(
out int doneEventHandle,
[PointerSized] ulong messagePtr,
[PointerSized] ulong messageSize,
int handle)
{ {
doneEventHandle = 0; doneEventHandle = 0;
@@ -353,11 +374,12 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return result; return result;
} }
[Svc(0x40)]
public KernelResult CreateSession( public KernelResult CreateSession(
out int serverSessionHandle, out int serverSessionHandle,
out int clientSessionHandle, out int clientSessionHandle,
bool isLight, bool isLight,
ulong namePtr) [PointerSized] ulong namePtr)
{ {
serverSessionHandle = 0; serverSessionHandle = 0;
clientSessionHandle = 0; clientSessionHandle = 0;
@@ -419,6 +441,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return result; return result;
} }
[Svc(0x41)]
public KernelResult AcceptSession(out int sessionHandle, int portHandle) public KernelResult AcceptSession(out int sessionHandle, int portHandle)
{ {
sessionHandle = 0; sessionHandle = 0;
@@ -470,9 +493,10 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return result; return result;
} }
[Svc(0x43)]
public KernelResult ReplyAndReceive( public KernelResult ReplyAndReceive(
out int handleIndex, out int handleIndex,
ulong handlesPtr, [PointerSized] ulong handlesPtr,
int handlesCount, int handlesCount,
int replyTargetHandle, int replyTargetHandle,
long timeout) long timeout)
@@ -575,11 +599,12 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return result; return result;
} }
[Svc(0x44)]
public KernelResult ReplyAndReceiveWithUserBuffer( public KernelResult ReplyAndReceiveWithUserBuffer(
out int handleIndex, out int handleIndex,
ulong handlesPtr, [PointerSized] ulong messagePtr,
ulong messagePtr, [PointerSized] ulong messageSize,
ulong messageSize, [PointerSized] ulong handlesPtr,
int handlesCount, int handlesCount,
int replyTargetHandle, int replyTargetHandle,
long timeout) long timeout)
@@ -679,12 +704,13 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return result; return result;
} }
[Svc(0x70)]
public KernelResult CreatePort( public KernelResult CreatePort(
out int serverPortHandle, out int serverPortHandle,
out int clientPortHandle, out int clientPortHandle,
int maxSessions, int maxSessions,
bool isLight, bool isLight,
ulong namePtr) [PointerSized] ulong namePtr)
{ {
serverPortHandle = clientPortHandle = 0; serverPortHandle = clientPortHandle = 0;
@@ -714,7 +740,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return result; return result;
} }
public KernelResult ManageNamedPort(out int handle, ulong namePtr, int maxSessions) [Svc(0x71)]
public KernelResult ManageNamedPort(out int handle, [PointerSized] ulong namePtr, int maxSessions)
{ {
handle = 0; handle = 0;
@@ -766,6 +793,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return result; return result;
} }
[Svc(0x72)]
public KernelResult ConnectToPort(out int clientSessionHandle, int clientPortHandle) public KernelResult ConnectToPort(out int clientSessionHandle, int clientPortHandle)
{ {
clientSessionHandle = 0; clientSessionHandle = 0;
@@ -819,7 +847,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
// Memory // Memory
public KernelResult SetHeapSize(out ulong address, ulong size) [Svc(1)]
public KernelResult SetHeapSize([PointerSized] out ulong address, [PointerSized] ulong size)
{ {
if ((size & 0xfffffffe001fffff) != 0) if ((size & 0xfffffffe001fffff) != 0)
{ {
@@ -833,7 +862,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return process.MemoryManager.SetHeapSize(size, out address); return process.MemoryManager.SetHeapSize(size, out address);
} }
public KernelResult SetMemoryPermission(ulong address, ulong size, KMemoryPermission permission) [Svc(2)]
public KernelResult SetMemoryPermission([PointerSized] ulong address, [PointerSized] ulong size, KMemoryPermission permission)
{ {
if (!PageAligned(address)) if (!PageAligned(address))
{ {
@@ -865,9 +895,10 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return currentProcess.MemoryManager.SetMemoryPermission(address, size, permission); return currentProcess.MemoryManager.SetMemoryPermission(address, size, permission);
} }
[Svc(3)]
public KernelResult SetMemoryAttribute( public KernelResult SetMemoryAttribute(
ulong address, [PointerSized] ulong address,
ulong size, [PointerSized] ulong size,
MemoryAttribute attributeMask, MemoryAttribute attributeMask,
MemoryAttribute attributeValue) MemoryAttribute attributeValue)
{ {
@@ -905,7 +936,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return result; return result;
} }
public KernelResult MapMemory(ulong dst, ulong src, ulong size) [Svc(4)]
public KernelResult MapMemory([PointerSized] ulong dst, [PointerSized] ulong src, [PointerSized] ulong size)
{ {
if (!PageAligned(src | dst)) if (!PageAligned(src | dst))
{ {
@@ -941,7 +973,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return process.MemoryManager.Map(dst, src, size); return process.MemoryManager.Map(dst, src, size);
} }
public KernelResult UnmapMemory(ulong dst, ulong src, ulong size) [Svc(5)]
public KernelResult UnmapMemory([PointerSized] ulong dst, [PointerSized] ulong src, [PointerSized] ulong size)
{ {
if (!PageAligned(src | dst)) if (!PageAligned(src | dst))
{ {
@@ -977,7 +1010,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return process.MemoryManager.Unmap(dst, src, size); return process.MemoryManager.Unmap(dst, src, size);
} }
public KernelResult QueryMemory(ulong infoPtr, out ulong pageInfo, ulong address) [Svc(6)]
public KernelResult QueryMemory([PointerSized] ulong infoPtr, [PointerSized] out ulong pageInfo, [PointerSized] ulong address)
{ {
KernelResult result = QueryMemory(out MemoryInfo info, out pageInfo, address); KernelResult result = QueryMemory(out MemoryInfo info, out pageInfo, address);
@@ -1011,7 +1045,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return KernelResult.Success; return KernelResult.Success;
} }
public KernelResult MapSharedMemory(int handle, ulong address, ulong size, KMemoryPermission permission) [Svc(0x13)]
public KernelResult MapSharedMemory(int handle, [PointerSized] ulong address, [PointerSized] ulong size, KMemoryPermission permission)
{ {
if (!PageAligned(address)) if (!PageAligned(address))
{ {
@@ -1057,7 +1092,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
permission); permission);
} }
public KernelResult UnmapSharedMemory(int handle, ulong address, ulong size) [Svc(0x14)]
public KernelResult UnmapSharedMemory(int handle, [PointerSized] ulong address, [PointerSized] ulong size)
{ {
if (!PageAligned(address)) if (!PageAligned(address))
{ {
@@ -1097,7 +1133,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
currentProcess); currentProcess);
} }
public KernelResult CreateTransferMemory(out int handle, ulong address, ulong size, KMemoryPermission permission) [Svc(0x15)]
public KernelResult CreateTransferMemory(out int handle, [PointerSized] ulong address, [PointerSized] ulong size, KMemoryPermission permission)
{ {
handle = 0; handle = 0;
@@ -1160,7 +1197,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return result; return result;
} }
public KernelResult MapTransferMemory(int handle, ulong address, ulong size, KMemoryPermission permission) [Svc(0x51)]
public KernelResult MapTransferMemory(int handle, [PointerSized] ulong address, [PointerSized] ulong size, KMemoryPermission permission)
{ {
if (!PageAligned(address)) if (!PageAligned(address))
{ {
@@ -1206,7 +1244,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
permission); permission);
} }
public KernelResult UnmapTransferMemory(int handle, ulong address, ulong size) [Svc(0x52)]
public KernelResult UnmapTransferMemory(int handle, [PointerSized] ulong address, [PointerSized] ulong size)
{ {
if (!PageAligned(address)) if (!PageAligned(address))
{ {
@@ -1246,7 +1285,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
currentProcess); currentProcess);
} }
public KernelResult MapPhysicalMemory(ulong address, ulong size) [Svc(0x2c)]
public KernelResult MapPhysicalMemory([PointerSized] ulong address, [PointerSized] ulong size)
{ {
if (!PageAligned(address)) if (!PageAligned(address))
{ {
@@ -1281,7 +1321,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return process.MemoryManager.MapPhysicalMemory(address, size); return process.MemoryManager.MapPhysicalMemory(address, size);
} }
public KernelResult UnmapPhysicalMemory(ulong address, ulong size) [Svc(0x2d)]
public KernelResult UnmapPhysicalMemory([PointerSized] ulong address, [PointerSized] ulong size)
{ {
if (!PageAligned(address)) if (!PageAligned(address))
{ {
@@ -1316,7 +1357,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return process.MemoryManager.UnmapPhysicalMemory(address, size); return process.MemoryManager.UnmapPhysicalMemory(address, size);
} }
public KernelResult CreateCodeMemory(ulong address, ulong size, out int handle) [Svc(0x4b)]
public KernelResult CreateCodeMemory(out int handle, [PointerSized] ulong address, [PointerSized] ulong size)
{ {
handle = 0; handle = 0;
@@ -1356,7 +1398,13 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return currentProcess.HandleTable.GenerateHandle(codeMemory, out handle); return currentProcess.HandleTable.GenerateHandle(codeMemory, out handle);
} }
public KernelResult ControlCodeMemory(int handle, CodeMemoryOperation op, ulong address, ulong size, KMemoryPermission permission) [Svc(0x4c)]
public KernelResult ControlCodeMemory(
int handle,
CodeMemoryOperation op,
ulong address,
ulong size,
KMemoryPermission permission)
{ {
KProcess currentProcess = KernelStatic.GetCurrentProcess(); KProcess currentProcess = KernelStatic.GetCurrentProcess();
@@ -1428,7 +1476,12 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
} }
} }
public KernelResult SetProcessMemoryPermission(int handle, ulong src, ulong size, KMemoryPermission permission) [Svc(0x73)]
public KernelResult SetProcessMemoryPermission(
int handle,
[PointerSized] ulong src,
[PointerSized] ulong size,
KMemoryPermission permission)
{ {
if (!PageAligned(src)) if (!PageAligned(src))
{ {
@@ -1465,7 +1518,12 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return targetProcess.MemoryManager.SetProcessMemoryPermission(src, size, permission); return targetProcess.MemoryManager.SetProcessMemoryPermission(src, size, permission);
} }
public KernelResult MapProcessMemory(ulong dst, int handle, ulong src, ulong size) [Svc(0x74)]
public KernelResult MapProcessMemory(
[PointerSized] ulong dst,
int handle,
ulong src,
[PointerSized] ulong size)
{ {
if (!PageAligned(src) || !PageAligned(dst)) if (!PageAligned(src) || !PageAligned(dst))
{ {
@@ -1517,7 +1575,12 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return dstProcess.MemoryManager.MapPages(dst, pageList, MemoryState.ProcessMemory, KMemoryPermission.ReadAndWrite); return dstProcess.MemoryManager.MapPages(dst, pageList, MemoryState.ProcessMemory, KMemoryPermission.ReadAndWrite);
} }
public KernelResult UnmapProcessMemory(ulong dst, int handle, ulong src, ulong size) [Svc(0x75)]
public KernelResult UnmapProcessMemory(
[PointerSized] ulong dst,
int handle,
ulong src,
[PointerSized] ulong size)
{ {
if (!PageAligned(src) || !PageAligned(dst)) if (!PageAligned(src) || !PageAligned(dst))
{ {
@@ -1558,6 +1621,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return KernelResult.Success; return KernelResult.Success;
} }
[Svc(0x77)]
public KernelResult MapProcessCodeMemory(int handle, ulong dst, ulong src, ulong size) public KernelResult MapProcessCodeMemory(int handle, ulong dst, ulong src, ulong size)
{ {
if (!PageAligned(dst) || !PageAligned(src)) if (!PageAligned(dst) || !PageAligned(src))
@@ -1595,6 +1659,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return targetProcess.MemoryManager.MapProcessCodeMemory(dst, src, size); return targetProcess.MemoryManager.MapProcessCodeMemory(dst, src, size);
} }
[Svc(0x78)]
public KernelResult UnmapProcessCodeMemory(int handle, ulong dst, ulong src, ulong size) public KernelResult UnmapProcessCodeMemory(int handle, ulong dst, ulong src, ulong size)
{ {
if (!PageAligned(dst) || !PageAligned(src)) if (!PageAligned(dst) || !PageAligned(src))
@@ -1639,6 +1704,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
// System // System
[Svc(0x7b)]
public KernelResult TerminateProcess(int handle) public KernelResult TerminateProcess(int handle)
{ {
KProcess process = KernelStatic.GetCurrentProcess(); KProcess process = KernelStatic.GetCurrentProcess();
@@ -1668,11 +1734,13 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return result; return result;
} }
[Svc(7)]
public void ExitProcess() public void ExitProcess()
{ {
KernelStatic.GetCurrentProcess().TerminateCurrentProcess(); KernelStatic.GetCurrentProcess().TerminateCurrentProcess();
} }
[Svc(0x11)]
public KernelResult SignalEvent(int handle) public KernelResult SignalEvent(int handle)
{ {
KProcess process = KernelStatic.GetCurrentProcess(); KProcess process = KernelStatic.GetCurrentProcess();
@@ -1695,6 +1763,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return result; return result;
} }
[Svc(0x12)]
public KernelResult ClearEvent(int handle) public KernelResult ClearEvent(int handle)
{ {
KernelResult result; KernelResult result;
@@ -1717,6 +1786,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return result; return result;
} }
[Svc(0x16)]
public KernelResult CloseHandle(int handle) public KernelResult CloseHandle(int handle)
{ {
KProcess currentProcess = KernelStatic.GetCurrentProcess(); KProcess currentProcess = KernelStatic.GetCurrentProcess();
@@ -1724,6 +1794,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return currentProcess.HandleTable.CloseHandle(handle) ? KernelResult.Success : KernelResult.InvalidHandle; return currentProcess.HandleTable.CloseHandle(handle) ? KernelResult.Success : KernelResult.InvalidHandle;
} }
[Svc(0x17)]
public KernelResult ResetSignal(int handle) public KernelResult ResetSignal(int handle)
{ {
KProcess currentProcess = KernelStatic.GetCurrentProcess(); KProcess currentProcess = KernelStatic.GetCurrentProcess();
@@ -1753,11 +1824,13 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return result; return result;
} }
[Svc(0x1e)]
public ulong GetSystemTick() public ulong GetSystemTick()
{ {
return _context.TickSource.Counter; return _context.TickSource.Counter;
} }
[Svc(0x26)]
public void Break(ulong reason) public void Break(ulong reason)
{ {
KThread currentThread = KernelStatic.GetCurrentThread(); KThread currentThread = KernelStatic.GetCurrentThread();
@@ -1784,7 +1857,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
} }
} }
public void OutputDebugString(ulong strPtr, ulong size) [Svc(0x27)]
public void OutputDebugString([PointerSized] ulong strPtr, [PointerSized] ulong size)
{ {
KProcess process = KernelStatic.GetCurrentProcess(); KProcess process = KernelStatic.GetCurrentProcess();
@@ -1793,6 +1867,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
Logger.Warning?.Print(LogClass.KernelSvc, str); Logger.Warning?.Print(LogClass.KernelSvc, str);
} }
[Svc(0x29)]
public KernelResult GetInfo(out ulong value, InfoType id, int handle, long subId) public KernelResult GetInfo(out ulong value, InfoType id, int handle, long subId)
{ {
value = 0; value = 0;
@@ -2038,6 +2113,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return KernelResult.Success; return KernelResult.Success;
} }
[Svc(0x45)]
public KernelResult CreateEvent(out int wEventHandle, out int rEventHandle) public KernelResult CreateEvent(out int wEventHandle, out int rEventHandle)
{ {
KEvent Event = new KEvent(_context); KEvent Event = new KEvent(_context);
@@ -2063,7 +2139,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return result; return result;
} }
public KernelResult GetProcessList(out int count, ulong address, int maxCount) [Svc(0x65)]
public KernelResult GetProcessList(out int count, [PointerSized] ulong address, int maxCount)
{ {
count = 0; count = 0;
@@ -2112,6 +2189,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return KernelResult.Success; return KernelResult.Success;
} }
[Svc(0x6f)]
public KernelResult GetSystemInfo(out long value, uint id, int handle, long subId) public KernelResult GetSystemInfo(out long value, uint id, int handle, long subId)
{ {
value = 0; value = 0;
@@ -2168,6 +2246,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return KernelResult.Success; return KernelResult.Success;
} }
[Svc(0x30)]
public KernelResult GetResourceLimitLimitValue(out long limitValue, int handle, LimitableResource resource) public KernelResult GetResourceLimitLimitValue(out long limitValue, int handle, LimitableResource resource)
{ {
limitValue = 0; limitValue = 0;
@@ -2189,6 +2268,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return KernelResult.Success; return KernelResult.Success;
} }
[Svc(0x31)]
public KernelResult GetResourceLimitCurrentValue(out long limitValue, int handle, LimitableResource resource) public KernelResult GetResourceLimitCurrentValue(out long limitValue, int handle, LimitableResource resource)
{ {
limitValue = 0; limitValue = 0;
@@ -2210,6 +2290,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return KernelResult.Success; return KernelResult.Success;
} }
[Svc(0x37)]
public KernelResult GetResourceLimitPeakValue(out long peak, int handle, LimitableResource resource) public KernelResult GetResourceLimitPeakValue(out long peak, int handle, LimitableResource resource)
{ {
peak = 0; peak = 0;
@@ -2231,6 +2312,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return KernelResult.Success; return KernelResult.Success;
} }
[Svc(0x7d)]
public KernelResult CreateResourceLimit(out int handle) public KernelResult CreateResourceLimit(out int handle)
{ {
KResourceLimit limit = new KResourceLimit(_context); KResourceLimit limit = new KResourceLimit(_context);
@@ -2240,6 +2322,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return process.HandleTable.GenerateHandle(limit, out handle); return process.HandleTable.GenerateHandle(limit, out handle);
} }
[Svc(0x7e)]
public KernelResult SetResourceLimitLimitValue(int handle, LimitableResource resource, long limitValue) public KernelResult SetResourceLimitLimitValue(int handle, LimitableResource resource, long limitValue)
{ {
if (resource >= LimitableResource.Count) if (resource >= LimitableResource.Count)
@@ -2259,13 +2342,26 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
// Thread // Thread
[Svc(8)]
public KernelResult CreateThread(
out int handle,
[PointerSized] ulong entrypoint,
[PointerSized] ulong argsPtr,
[PointerSized] ulong stackTop,
int priority,
int cpuCore)
{
return CreateThread(out handle, entrypoint, argsPtr, stackTop, priority, cpuCore, null);
}
public KernelResult CreateThread( public KernelResult CreateThread(
out int handle, out int handle,
ulong entrypoint, ulong entrypoint,
ulong argsPtr, ulong argsPtr,
ulong stackTop, ulong stackTop,
int priority, int priority,
int cpuCore) int cpuCore,
ThreadStart customThreadStart)
{ {
handle = 0; handle = 0;
@@ -2302,7 +2398,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
argsPtr, argsPtr,
stackTop, stackTop,
priority, priority,
cpuCore); cpuCore,
customThreadStart);
if (result == KernelResult.Success) if (result == KernelResult.Success)
{ {
@@ -2320,6 +2417,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return result; return result;
} }
[Svc(9)]
public KernelResult StartThread(int handle) public KernelResult StartThread(int handle)
{ {
KProcess process = KernelStatic.GetCurrentProcess(); KProcess process = KernelStatic.GetCurrentProcess();
@@ -2347,6 +2445,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
} }
} }
[Svc(0xa)]
public void ExitThread() public void ExitThread()
{ {
KThread currentThread = KernelStatic.GetCurrentThread(); KThread currentThread = KernelStatic.GetCurrentThread();
@@ -2354,6 +2453,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
currentThread.Exit(); currentThread.Exit();
} }
[Svc(0xb)]
public void SleepThread(long timeout) public void SleepThread(long timeout)
{ {
if (timeout < 1) if (timeout < 1)
@@ -2371,6 +2471,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
} }
} }
[Svc(0xc)]
public KernelResult GetThreadPriority(out int priority, int handle) public KernelResult GetThreadPriority(out int priority, int handle)
{ {
KProcess process = KernelStatic.GetCurrentProcess(); KProcess process = KernelStatic.GetCurrentProcess();
@@ -2391,6 +2492,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
} }
} }
[Svc(0xd)]
public KernelResult SetThreadPriority(int handle, int priority) public KernelResult SetThreadPriority(int handle, int priority)
{ {
// TODO: NPDM check. // TODO: NPDM check.
@@ -2409,6 +2511,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return KernelResult.Success; return KernelResult.Success;
} }
[Svc(0xe)]
public KernelResult GetThreadCoreMask(out int preferredCore, out ulong affinityMask, int handle) public KernelResult GetThreadCoreMask(out int preferredCore, out ulong affinityMask, int handle)
{ {
KProcess process = KernelStatic.GetCurrentProcess(); KProcess process = KernelStatic.GetCurrentProcess();
@@ -2431,6 +2534,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
} }
} }
[Svc(0xf)]
public KernelResult SetThreadCoreMask(int handle, int preferredCore, ulong affinityMask) public KernelResult SetThreadCoreMask(int handle, int preferredCore, ulong affinityMask)
{ {
KProcess currentProcess = KernelStatic.GetCurrentProcess(); KProcess currentProcess = KernelStatic.GetCurrentProcess();
@@ -2479,11 +2583,13 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return thread.SetCoreAndAffinityMask(preferredCore, affinityMask); return thread.SetCoreAndAffinityMask(preferredCore, affinityMask);
} }
[Svc(0x10)]
public int GetCurrentProcessorNumber() public int GetCurrentProcessorNumber()
{ {
return KernelStatic.GetCurrentThread().CurrentCore; return KernelStatic.GetCurrentThread().CurrentCore;
} }
[Svc(0x25)]
public KernelResult GetThreadId(out ulong threadUid, int handle) public KernelResult GetThreadId(out ulong threadUid, int handle)
{ {
KProcess process = KernelStatic.GetCurrentProcess(); KProcess process = KernelStatic.GetCurrentProcess();
@@ -2504,6 +2610,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
} }
} }
[Svc(0x32)]
public KernelResult SetThreadActivity(int handle, bool pause) public KernelResult SetThreadActivity(int handle, bool pause)
{ {
KProcess process = KernelStatic.GetCurrentProcess(); KProcess process = KernelStatic.GetCurrentProcess();
@@ -2528,7 +2635,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return thread.SetActivity(pause); return thread.SetActivity(pause);
} }
public KernelResult GetThreadContext3(ulong address, int handle) [Svc(0x33)]
public KernelResult GetThreadContext3([PointerSized] ulong address, int handle)
{ {
KProcess currentProcess = KernelStatic.GetCurrentProcess(); KProcess currentProcess = KernelStatic.GetCurrentProcess();
KThread currentThread = KernelStatic.GetCurrentThread(); KThread currentThread = KernelStatic.GetCurrentThread();
@@ -2564,7 +2672,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
// Thread synchronization // Thread synchronization
public KernelResult WaitSynchronization(out int handleIndex, ulong handlesPtr, int handlesCount, long timeout) [Svc(0x18)]
public KernelResult WaitSynchronization(out int handleIndex, [PointerSized] ulong handlesPtr, int handlesCount, long timeout)
{ {
handleIndex = 0; handleIndex = 0;
@@ -2653,6 +2762,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return result; return result;
} }
[Svc(0x19)]
public KernelResult CancelSynchronization(int handle) public KernelResult CancelSynchronization(int handle)
{ {
KProcess process = KernelStatic.GetCurrentProcess(); KProcess process = KernelStatic.GetCurrentProcess();
@@ -2669,7 +2779,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return KernelResult.Success; return KernelResult.Success;
} }
public KernelResult ArbitrateLock(int ownerHandle, ulong mutexAddress, int requesterHandle) [Svc(0x1a)]
public KernelResult ArbitrateLock(int ownerHandle, [PointerSized] ulong mutexAddress, int requesterHandle)
{ {
if (IsPointingInsideKernel(mutexAddress)) if (IsPointingInsideKernel(mutexAddress))
{ {
@@ -2686,7 +2797,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return currentProcess.AddressArbiter.ArbitrateLock(ownerHandle, mutexAddress, requesterHandle); return currentProcess.AddressArbiter.ArbitrateLock(ownerHandle, mutexAddress, requesterHandle);
} }
public KernelResult ArbitrateUnlock(ulong mutexAddress) [Svc(0x1b)]
public KernelResult ArbitrateUnlock([PointerSized] ulong mutexAddress)
{ {
if (IsPointingInsideKernel(mutexAddress)) if (IsPointingInsideKernel(mutexAddress))
{ {
@@ -2703,9 +2815,10 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return currentProcess.AddressArbiter.ArbitrateUnlock(mutexAddress); return currentProcess.AddressArbiter.ArbitrateUnlock(mutexAddress);
} }
[Svc(0x1c)]
public KernelResult WaitProcessWideKeyAtomic( public KernelResult WaitProcessWideKeyAtomic(
ulong mutexAddress, [PointerSized] ulong mutexAddress,
ulong condVarAddress, [PointerSized] ulong condVarAddress,
int handle, int handle,
long timeout) long timeout)
{ {
@@ -2733,7 +2846,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
timeout); timeout);
} }
public KernelResult SignalProcessWideKey(ulong address, int count) [Svc(0x1d)]
public KernelResult SignalProcessWideKey([PointerSized] ulong address, int count)
{ {
KProcess currentProcess = KernelStatic.GetCurrentProcess(); KProcess currentProcess = KernelStatic.GetCurrentProcess();
@@ -2742,7 +2856,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return KernelResult.Success; return KernelResult.Success;
} }
public KernelResult WaitForAddress(ulong address, ArbitrationType type, int value, long timeout) [Svc(0x34)]
public KernelResult WaitForAddress([PointerSized] ulong address, ArbitrationType type, int value, long timeout)
{ {
if (IsPointingInsideKernel(address)) if (IsPointingInsideKernel(address))
{ {
@@ -2773,7 +2888,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
}; };
} }
public KernelResult SignalToAddress(ulong address, SignalType type, int value, int count) [Svc(0x35)]
public KernelResult SignalToAddress([PointerSized] ulong address, SignalType type, int value, int count)
{ {
if (IsPointingInsideKernel(address)) if (IsPointingInsideKernel(address))
{ {
@@ -2799,6 +2915,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
}; };
} }
[Svc(0x36)]
public KernelResult SynchronizePreemptionState() public KernelResult SynchronizePreemptionState()
{ {
KernelStatic.GetCurrentThread().SynchronizePreemptionState(); KernelStatic.GetCurrentThread().SynchronizePreemptionState();
@@ -2806,12 +2923,12 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return KernelResult.Success; return KernelResult.Success;
} }
private bool IsPointingInsideKernel(ulong address) private static bool IsPointingInsideKernel(ulong address)
{ {
return (address + 0x1000000000) < 0xffffff000; return (address + 0x1000000000) < 0xffffff000;
} }
private bool IsAddressNotWordAligned(ulong address) private static bool IsAddressNotWordAligned(ulong address)
{ {
return (address & 3) != 0; return (address & 3) != 0;
} }

View File

@@ -1,534 +0,0 @@
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Memory;
using Ryujinx.HLE.HOS.Kernel.Threading;
namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
{
class Syscall32
{
private readonly Syscall _syscall;
public Syscall32(Syscall syscall)
{
_syscall = syscall;
}
// IPC
public KernelResult ConnectToNamedPort32([R(1)] uint namePtr, [R(1)] out int handle)
{
return _syscall.ConnectToNamedPort(out handle, namePtr);
}
public KernelResult SendSyncRequest32([R(0)] int handle)
{
return _syscall.SendSyncRequest(handle);
}
public KernelResult SendSyncRequestWithUserBuffer32([R(0)] uint messagePtr, [R(1)] uint messageSize, [R(2)] int handle)
{
return _syscall.SendSyncRequestWithUserBuffer(messagePtr, messageSize, handle);
}
public KernelResult CreateSession32(
[R(2)] bool isLight,
[R(3)] uint namePtr,
[R(1)] out int serverSessionHandle,
[R(2)] out int clientSessionHandle)
{
return _syscall.CreateSession(out serverSessionHandle, out clientSessionHandle, isLight, namePtr);
}
public KernelResult AcceptSession32([R(1)] int portHandle, [R(1)] out int sessionHandle)
{
return _syscall.AcceptSession(out sessionHandle, portHandle);
}
public KernelResult ReplyAndReceive32(
[R(0)] uint timeoutLow,
[R(1)] uint handlesPtr,
[R(2)] int handlesCount,
[R(3)] int replyTargetHandle,
[R(4)] uint timeoutHigh,
[R(1)] out int handleIndex)
{
long timeout = (long)(timeoutLow | ((ulong)timeoutHigh << 32));
return _syscall.ReplyAndReceive(out handleIndex, handlesPtr, handlesCount, replyTargetHandle, timeout);
}
public KernelResult CreatePort32(
[R(0)] uint namePtr,
[R(2)] int maxSessions,
[R(3)] bool isLight,
[R(1)] out int serverPortHandle,
[R(2)] out int clientPortHandle)
{
return _syscall.CreatePort(out serverPortHandle, out clientPortHandle, maxSessions, isLight, namePtr);
}
public KernelResult ManageNamedPort32([R(1)] uint namePtr, [R(2)] int maxSessions, [R(1)] out int handle)
{
return _syscall.ManageNamedPort(out handle, namePtr, maxSessions);
}
public KernelResult ConnectToPort32([R(1)] int clientPortHandle, [R(1)] out int clientSessionHandle)
{
return _syscall.ConnectToPort(out clientSessionHandle, clientPortHandle);
}
// Memory
public KernelResult SetHeapSize32([R(1)] uint size, [R(1)] out uint address)
{
KernelResult result = _syscall.SetHeapSize(out ulong address64, size);
address = (uint)address64;
return result;
}
public KernelResult SetMemoryPermission32(
[R(0)] uint address,
[R(1)] uint size,
[R(2)] KMemoryPermission permission)
{
return _syscall.SetMemoryPermission(address, size, permission);
}
public KernelResult SetMemoryAttribute32(
[R(0)] uint address,
[R(1)] uint size,
[R(2)] MemoryAttribute attributeMask,
[R(3)] MemoryAttribute attributeValue)
{
return _syscall.SetMemoryAttribute(address, size, attributeMask, attributeValue);
}
public KernelResult MapMemory32([R(0)] uint dst, [R(1)] uint src, [R(2)] uint size)
{
return _syscall.MapMemory(dst, src, size);
}
public KernelResult UnmapMemory32([R(0)] uint dst, [R(1)] uint src, [R(2)] uint size)
{
return _syscall.UnmapMemory(dst, src, size);
}
public KernelResult QueryMemory32([R(0)] uint infoPtr, [R(1)] uint r1, [R(2)] uint address, [R(1)] out uint pageInfo)
{
KernelResult result = _syscall.QueryMemory(infoPtr, out ulong pageInfo64, address);
pageInfo = (uint)pageInfo64;
return result;
}
public KernelResult MapSharedMemory32([R(0)] int handle, [R(1)] uint address, [R(2)] uint size, [R(3)] KMemoryPermission permission)
{
return _syscall.MapSharedMemory(handle, address, size, permission);
}
public KernelResult UnmapSharedMemory32([R(0)] int handle, [R(1)] uint address, [R(2)] uint size)
{
return _syscall.UnmapSharedMemory(handle, address, size);
}
public KernelResult CreateTransferMemory32(
[R(1)] uint address,
[R(2)] uint size,
[R(3)] KMemoryPermission permission,
[R(1)] out int handle)
{
return _syscall.CreateTransferMemory(out handle, address, size, permission);
}
public KernelResult CreateCodeMemory32([R(1)] uint address, [R(2)] uint size, [R(1)] out int handle)
{
return _syscall.CreateCodeMemory(address, size, out handle);
}
public KernelResult ControlCodeMemory32(
[R(0)] int handle,
[R(1)] CodeMemoryOperation op,
[R(2)] uint addressLow,
[R(3)] uint addressHigh,
[R(4)] uint sizeLow,
[R(5)] uint sizeHigh,
[R(6)] KMemoryPermission permission)
{
ulong address = addressLow | ((ulong)addressHigh << 32);
ulong size = sizeLow | ((ulong)sizeHigh << 32);
return _syscall.ControlCodeMemory(handle, op, address, size, permission);
}
public KernelResult MapTransferMemory32([R(0)] int handle, [R(1)] uint address, [R(2)] uint size, [R(3)] KMemoryPermission permission)
{
return _syscall.MapTransferMemory(handle, address, size, permission);
}
public KernelResult UnmapTransferMemory32([R(0)] int handle, [R(1)] uint address, [R(2)] uint size)
{
return _syscall.UnmapTransferMemory(handle, address, size);
}
public KernelResult MapPhysicalMemory32([R(0)] uint address, [R(1)] uint size)
{
return _syscall.MapPhysicalMemory(address, size);
}
public KernelResult UnmapPhysicalMemory32([R(0)] uint address, [R(1)] uint size)
{
return _syscall.UnmapPhysicalMemory(address, size);
}
public KernelResult SetProcessMemoryPermission32(
[R(0)] int handle,
[R(1)] uint sizeLow,
[R(2)] uint srcLow,
[R(3)] uint srcHigh,
[R(4)] uint sizeHigh,
[R(5)] KMemoryPermission permission)
{
ulong src = srcLow | ((ulong)srcHigh << 32);
ulong size = sizeLow | ((ulong)sizeHigh << 32);
return _syscall.SetProcessMemoryPermission(handle, src, size, permission);
}
public KernelResult MapProcessMemory32([R(0)] uint dst, [R(1)] int handle, [R(2)] uint srcLow, [R(3)] uint srcHigh, [R(4)] uint size)
{
ulong src = srcLow | ((ulong)srcHigh << 32);
return _syscall.MapProcessMemory(dst, handle, src, size);
}
public KernelResult UnmapProcessMemory32([R(0)] uint dst, [R(1)] int handle, [R(2)] uint srcLow, [R(3)] uint srcHigh, [R(4)] uint size)
{
ulong src = srcLow | ((ulong)srcHigh << 32);
return _syscall.UnmapProcessMemory(dst, handle, src, size);
}
public KernelResult MapProcessCodeMemory32([R(0)] int handle, [R(1)] uint srcLow, [R(2)] uint dstLow, [R(3)] uint dstHigh, [R(4)] uint srcHigh, [R(5)] uint sizeLow, [R(6)] uint sizeHigh)
{
ulong src = srcLow | ((ulong)srcHigh << 32);
ulong dst = dstLow | ((ulong)dstHigh << 32);
ulong size = sizeLow | ((ulong)sizeHigh << 32);
return _syscall.MapProcessCodeMemory(handle, dst, src, size);
}
public KernelResult UnmapProcessCodeMemory32([R(0)] int handle, [R(1)] uint srcLow, [R(2)] uint dstLow, [R(3)] uint dstHigh, [R(4)] uint srcHigh, [R(5)] uint sizeLow, [R(6)] uint sizeHigh)
{
ulong src = srcLow | ((ulong)srcHigh << 32);
ulong dst = dstLow | ((ulong)dstHigh << 32);
ulong size = sizeLow | ((ulong)sizeHigh << 32);
return _syscall.UnmapProcessCodeMemory(handle, dst, src, size);
}
// System
public void ExitProcess32()
{
_syscall.ExitProcess();
}
public KernelResult TerminateProcess32([R(0)] int handle)
{
return _syscall.TerminateProcess(handle);
}
public KernelResult SignalEvent32([R(0)] int handle)
{
return _syscall.SignalEvent(handle);
}
public KernelResult ClearEvent32([R(0)] int handle)
{
return _syscall.ClearEvent(handle);
}
public KernelResult CloseHandle32([R(0)] int handle)
{
return _syscall.CloseHandle(handle);
}
public KernelResult ResetSignal32([R(0)] int handle)
{
return _syscall.ResetSignal(handle);
}
public void GetSystemTick32([R(0)] out uint resultLow, [R(1)] out uint resultHigh)
{
ulong result = _syscall.GetSystemTick();
resultLow = (uint)(result & uint.MaxValue);
resultHigh = (uint)(result >> 32);
}
public KernelResult GetProcessId32([R(1)] int handle, [R(1)] out uint pidLow, [R(2)] out uint pidHigh)
{
KernelResult result = _syscall.GetProcessId(out ulong pid, handle);
pidLow = (uint)(pid & uint.MaxValue);
pidHigh = (uint)(pid >> 32);
return result;
}
public void Break32([R(0)] uint reason, [R(1)] uint r1, [R(2)] uint info)
{
_syscall.Break(reason);
}
public void OutputDebugString32([R(0)] uint strPtr, [R(1)] uint size)
{
_syscall.OutputDebugString(strPtr, size);
}
public KernelResult GetInfo32(
[R(0)] uint subIdLow,
[R(1)] InfoType id,
[R(2)] int handle,
[R(3)] uint subIdHigh,
[R(1)] out uint valueLow,
[R(2)] out uint valueHigh)
{
long subId = (long)(subIdLow | ((ulong)subIdHigh << 32));
KernelResult result = _syscall.GetInfo(out ulong value, id, handle, subId);
valueHigh = (uint)(value >> 32);
valueLow = (uint)(value & uint.MaxValue);
return result;
}
public KernelResult CreateEvent32([R(1)] out int wEventHandle, [R(2)] out int rEventHandle)
{
return _syscall.CreateEvent(out wEventHandle, out rEventHandle);
}
public KernelResult GetProcessList32([R(1)] ulong address, [R(2)] int maxCount, [R(1)] out int count)
{
return _syscall.GetProcessList(out count, address, maxCount);
}
public KernelResult GetSystemInfo32([R(1)] uint subIdLow, [R(2)] uint id, [R(3)] int handle, [R(3)] uint subIdHigh, [R(1)] out int valueLow, [R(2)] out int valueHigh)
{
long subId = (long)(subIdLow | ((ulong)subIdHigh << 32));
KernelResult result = _syscall.GetSystemInfo(out long value, id, handle, subId);
valueHigh = (int)(value >> 32);
valueLow = (int)(value & uint.MaxValue);
return result;
}
public KernelResult GetResourceLimitLimitValue32([R(1)] int handle, [R(2)] LimitableResource resource, [R(1)] out int limitValueLow, [R(2)] out int limitValueHigh)
{
KernelResult result = _syscall.GetResourceLimitLimitValue(out long limitValue, handle, resource);
limitValueHigh = (int)(limitValue >> 32);
limitValueLow = (int)(limitValue & uint.MaxValue);
return result;
}
public KernelResult GetResourceLimitCurrentValue32([R(1)] int handle, [R(2)] LimitableResource resource, [R(1)] out int limitValueLow, [R(2)] out int limitValueHigh)
{
KernelResult result = _syscall.GetResourceLimitCurrentValue(out long limitValue, handle, resource);
limitValueHigh = (int)(limitValue >> 32);
limitValueLow = (int)(limitValue & uint.MaxValue);
return result;
}
public KernelResult GetResourceLimitPeakValue32([R(1)] int handle, [R(2)] LimitableResource resource, [R(1)] out int peakLow, [R(2)] out int peakHigh)
{
KernelResult result = _syscall.GetResourceLimitPeakValue(out long peak, handle, resource);
peakHigh = (int)(peak >> 32);
peakLow = (int)(peak & uint.MaxValue);
return result;
}
public KernelResult CreateResourceLimit32([R(1)] out int handle)
{
return _syscall.CreateResourceLimit(out handle);
}
public KernelResult SetResourceLimitLimitValue32([R(0)] int handle, [R(1)] LimitableResource resource, [R(2)] uint limitValueLow, [R(3)] uint limitValueHigh)
{
long limitValue = (long)(limitValueLow | ((ulong)limitValueHigh << 32));
return _syscall.SetResourceLimitLimitValue(handle, resource, limitValue);
}
public KernelResult FlushProcessDataCache32(
[R(0)] uint processHandle,
[R(2)] uint addressLow,
[R(3)] uint addressHigh,
[R(1)] uint sizeLow,
[R(4)] uint sizeHigh)
{
// FIXME: This needs to be implemented as ARMv7 doesn't have any way to do cache maintenance operations on EL0.
// As we don't support (and don't actually need) to flush the cache, this is stubbed.
return KernelResult.Success;
}
// Thread
public KernelResult CreateThread32(
[R(1)] uint entrypoint,
[R(2)] uint argsPtr,
[R(3)] uint stackTop,
[R(0)] int priority,
[R(4)] int cpuCore,
[R(1)] out int handle)
{
return _syscall.CreateThread(out handle, entrypoint, argsPtr, stackTop, priority, cpuCore);
}
public KernelResult StartThread32([R(0)] int handle)
{
return _syscall.StartThread(handle);
}
public void ExitThread32()
{
_syscall.ExitThread();
}
public void SleepThread32([R(0)] uint timeoutLow, [R(1)] uint timeoutHigh)
{
long timeout = (long)(timeoutLow | ((ulong)timeoutHigh << 32));
_syscall.SleepThread(timeout);
}
public KernelResult GetThreadPriority32([R(1)] int handle, [R(1)] out int priority)
{
return _syscall.GetThreadPriority(out priority, handle);
}
public KernelResult SetThreadPriority32([R(0)] int handle, [R(1)] int priority)
{
return _syscall.SetThreadPriority(handle, priority);
}
public KernelResult GetThreadCoreMask32([R(2)] int handle, [R(1)] out int preferredCore, [R(2)] out uint affinityMaskLow, [R(3)] out uint affinityMaskHigh)
{
KernelResult result = _syscall.GetThreadCoreMask(out preferredCore, out ulong affinityMask, handle);
affinityMaskLow = (uint)(affinityMask & uint.MaxValue);
affinityMaskHigh = (uint)(affinityMask >> 32);
return result;
}
public KernelResult SetThreadCoreMask32([R(0)] int handle, [R(1)] int preferredCore, [R(2)] uint affinityMaskLow, [R(3)] uint affinityMaskHigh)
{
ulong affinityMask = affinityMaskLow | ((ulong)affinityMaskHigh << 32);
return _syscall.SetThreadCoreMask(handle, preferredCore, affinityMask);
}
public int GetCurrentProcessorNumber32()
{
return _syscall.GetCurrentProcessorNumber();
}
public KernelResult GetThreadId32([R(1)] int handle, [R(1)] out uint threadUidLow, [R(2)] out uint threadUidHigh)
{
ulong threadUid;
KernelResult result = _syscall.GetThreadId(out threadUid, handle);
threadUidLow = (uint)(threadUid >> 32);
threadUidHigh = (uint)(threadUid & uint.MaxValue);
return result;
}
public KernelResult SetThreadActivity32([R(0)] int handle, [R(1)] bool pause)
{
return _syscall.SetThreadActivity(handle, pause);
}
public KernelResult GetThreadContext332([R(0)] uint address, [R(1)] int handle)
{
return _syscall.GetThreadContext3(address, handle);
}
// Thread synchronization
public KernelResult WaitSynchronization32(
[R(0)] uint timeoutLow,
[R(1)] uint handlesPtr,
[R(2)] int handlesCount,
[R(3)] uint timeoutHigh,
[R(1)] out int handleIndex)
{
long timeout = (long)(timeoutLow | ((ulong)timeoutHigh << 32));
return _syscall.WaitSynchronization(out handleIndex, handlesPtr, handlesCount, timeout);
}
public KernelResult CancelSynchronization32([R(0)] int handle)
{
return _syscall.CancelSynchronization(handle);
}
public KernelResult ArbitrateLock32([R(0)] int ownerHandle, [R(1)] uint mutexAddress, [R(2)] int requesterHandle)
{
return _syscall.ArbitrateLock(ownerHandle, mutexAddress, requesterHandle);
}
public KernelResult ArbitrateUnlock32([R(0)] uint mutexAddress)
{
return _syscall.ArbitrateUnlock(mutexAddress);
}
public KernelResult WaitProcessWideKeyAtomic32(
[R(0)] uint mutexAddress,
[R(1)] uint condVarAddress,
[R(2)] int handle,
[R(3)] uint timeoutLow,
[R(4)] uint timeoutHigh)
{
long timeout = (long)(timeoutLow | ((ulong)timeoutHigh << 32));
return _syscall.WaitProcessWideKeyAtomic(mutexAddress, condVarAddress, handle, timeout);
}
public KernelResult SignalProcessWideKey32([R(0)] uint address, [R(1)] int count)
{
return _syscall.SignalProcessWideKey(address, count);
}
public KernelResult WaitForAddress32([R(0)] uint address, [R(1)] ArbitrationType type, [R(2)] int value, [R(3)] uint timeoutLow, [R(4)] uint timeoutHigh)
{
long timeout = (long)(timeoutLow | ((ulong)timeoutHigh << 32));
return _syscall.WaitForAddress(address, type, value, timeout);
}
public KernelResult SignalToAddress32([R(0)] uint address, [R(1)] SignalType type, [R(2)] int value, [R(3)] int count)
{
return _syscall.SignalToAddress(address, type, value, count);
}
public KernelResult SynchronizePreemptionState32()
{
return _syscall.SynchronizePreemptionState();
}
}
}

View File

@@ -1,434 +0,0 @@
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Memory;
using Ryujinx.HLE.HOS.Kernel.Threading;
namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
{
class Syscall64
{
private readonly Syscall _syscall;
public Syscall64(Syscall syscall)
{
_syscall = syscall;
}
// IPC
public KernelResult ConnectToNamedPort64([R(1)] ulong namePtr, [R(1)] out int handle)
{
return _syscall.ConnectToNamedPort(out handle, namePtr);
}
public KernelResult SendSyncRequest64([R(0)] int handle)
{
return _syscall.SendSyncRequest(handle);
}
public KernelResult SendSyncRequestWithUserBuffer64([R(0)] ulong messagePtr, [R(1)] ulong messageSize, [R(2)] int handle)
{
return _syscall.SendSyncRequestWithUserBuffer(messagePtr, messageSize, handle);
}
public KernelResult SendAsyncRequestWithUserBuffer64(
[R(1)] ulong messagePtr,
[R(2)] ulong messageSize,
[R(3)] int handle,
[R(1)] out int doneEventHandle)
{
return _syscall.SendAsyncRequestWithUserBuffer(out doneEventHandle, messagePtr, messageSize, handle);
}
public KernelResult CreateSession64(
[R(2)] bool isLight,
[R(3)] ulong namePtr,
[R(1)] out int serverSessionHandle,
[R(2)] out int clientSessionHandle)
{
return _syscall.CreateSession(out serverSessionHandle, out clientSessionHandle, isLight, namePtr);
}
public KernelResult AcceptSession64([R(1)] int portHandle, [R(1)] out int sessionHandle)
{
return _syscall.AcceptSession(out sessionHandle, portHandle);
}
public KernelResult ReplyAndReceive64(
[R(1)] ulong handlesPtr,
[R(2)] int handlesCount,
[R(3)] int replyTargetHandle,
[R(4)] long timeout,
[R(1)] out int handleIndex)
{
return _syscall.ReplyAndReceive(out handleIndex, handlesPtr, handlesCount, replyTargetHandle, timeout);
}
public KernelResult ReplyAndReceiveWithUserBuffer64(
[R(1)] ulong messagePtr,
[R(2)] ulong messageSize,
[R(3)] ulong handlesPtr,
[R(4)] int handlesCount,
[R(5)] int replyTargetHandle,
[R(6)] long timeout,
[R(1)] out int handleIndex)
{
return _syscall.ReplyAndReceiveWithUserBuffer(
out handleIndex,
handlesPtr,
messagePtr,
messageSize,
handlesCount,
replyTargetHandle,
timeout);
}
public KernelResult CreatePort64(
[R(2)] int maxSessions,
[R(3)] bool isLight,
[R(4)] ulong namePtr,
[R(1)] out int serverPortHandle,
[R(2)] out int clientPortHandle)
{
return _syscall.CreatePort(out serverPortHandle, out clientPortHandle, maxSessions, isLight, namePtr);
}
public KernelResult ManageNamedPort64([R(1)] ulong namePtr, [R(2)] int maxSessions, [R(1)] out int handle)
{
return _syscall.ManageNamedPort(out handle, namePtr, maxSessions);
}
public KernelResult ConnectToPort64([R(1)] int clientPortHandle, [R(1)] out int clientSessionHandle)
{
return _syscall.ConnectToPort(out clientSessionHandle, clientPortHandle);
}
// Memory
public KernelResult SetHeapSize64([R(1)] ulong size, [R(1)] out ulong address)
{
return _syscall.SetHeapSize(out address, size);
}
public KernelResult SetMemoryPermission64(
[R(0)] ulong address,
[R(1)] ulong size,
[R(2)] KMemoryPermission permission)
{
return _syscall.SetMemoryPermission(address, size, permission);
}
public KernelResult SetMemoryAttribute64(
[R(0)] ulong address,
[R(1)] ulong size,
[R(2)] MemoryAttribute attributeMask,
[R(3)] MemoryAttribute attributeValue)
{
return _syscall.SetMemoryAttribute(address, size, attributeMask, attributeValue);
}
public KernelResult MapMemory64([R(0)] ulong dst, [R(1)] ulong src, [R(2)] ulong size)
{
return _syscall.MapMemory(dst, src, size);
}
public KernelResult UnmapMemory64([R(0)] ulong dst, [R(1)] ulong src, [R(2)] ulong size)
{
return _syscall.UnmapMemory(dst, src, size);
}
public KernelResult QueryMemory64([R(0)] ulong infoPtr, [R(2)] ulong address, [R(1)] out ulong pageInfo)
{
return _syscall.QueryMemory(infoPtr, out pageInfo, address);
}
public KernelResult MapSharedMemory64([R(0)] int handle, [R(1)] ulong address, [R(2)] ulong size, [R(3)] KMemoryPermission permission)
{
return _syscall.MapSharedMemory(handle, address, size, permission);
}
public KernelResult UnmapSharedMemory64([R(0)] int handle, [R(1)] ulong address, [R(2)] ulong size)
{
return _syscall.UnmapSharedMemory(handle, address, size);
}
public KernelResult CreateTransferMemory64(
[R(1)] ulong address,
[R(2)] ulong size,
[R(3)] KMemoryPermission permission,
[R(1)] out int handle)
{
return _syscall.CreateTransferMemory(out handle, address, size, permission);
}
public KernelResult CreateCodeMemory64([R(1)] ulong address, [R(2)] ulong size, [R(1)] out int handle)
{
return _syscall.CreateCodeMemory(address, size, out handle);
}
public KernelResult ControlCodeMemory64([R(0)] int handle, [R(1)] CodeMemoryOperation op, [R(2)] ulong address, [R(3)] ulong size, [R(4)] KMemoryPermission permission)
{
return _syscall.ControlCodeMemory(handle, op, address, size, permission);
}
public KernelResult MapTransferMemory64([R(0)] int handle, [R(1)] ulong address, [R(2)] ulong size, [R(3)] KMemoryPermission permission)
{
return _syscall.MapTransferMemory(handle, address, size, permission);
}
public KernelResult UnmapTransferMemory64([R(0)] int handle, [R(1)] ulong address, [R(2)] ulong size)
{
return _syscall.UnmapTransferMemory(handle, address, size);
}
public KernelResult MapPhysicalMemory64([R(0)] ulong address, [R(1)] ulong size)
{
return _syscall.MapPhysicalMemory(address, size);
}
public KernelResult UnmapPhysicalMemory64([R(0)] ulong address, [R(1)] ulong size)
{
return _syscall.UnmapPhysicalMemory(address, size);
}
public KernelResult SetProcessMemoryPermission64([R(0)] int handle, [R(1)] ulong src, [R(2)] ulong size, [R(3)] KMemoryPermission permission)
{
return _syscall.SetProcessMemoryPermission(handle, src, size, permission);
}
public KernelResult MapProcessMemory64([R(0)] ulong dst, [R(1)] int handle, [R(2)] ulong src, [R(3)] ulong size)
{
return _syscall.MapProcessMemory(dst, handle, src, size);
}
public KernelResult UnmapProcessMemory64([R(0)] ulong dst, [R(1)] int handle, [R(2)] ulong src, [R(3)] ulong size)
{
return _syscall.UnmapProcessMemory(dst, handle, src, size);
}
public KernelResult MapProcessCodeMemory64([R(0)] int handle, [R(1)] ulong dst, [R(2)] ulong src, [R(3)] ulong size)
{
return _syscall.MapProcessCodeMemory(handle, dst, src, size);
}
public KernelResult UnmapProcessCodeMemory64([R(0)] int handle, [R(1)] ulong dst, [R(2)] ulong src, [R(3)] ulong size)
{
return _syscall.UnmapProcessCodeMemory(handle, dst, src, size);
}
// System
public void ExitProcess64()
{
_syscall.ExitProcess();
}
public KernelResult TerminateProcess64([R(0)] int handle)
{
return _syscall.TerminateProcess(handle);
}
public KernelResult SignalEvent64([R(0)] int handle)
{
return _syscall.SignalEvent(handle);
}
public KernelResult ClearEvent64([R(0)] int handle)
{
return _syscall.ClearEvent(handle);
}
public KernelResult CloseHandle64([R(0)] int handle)
{
return _syscall.CloseHandle(handle);
}
public KernelResult ResetSignal64([R(0)] int handle)
{
return _syscall.ResetSignal(handle);
}
public ulong GetSystemTick64()
{
return _syscall.GetSystemTick();
}
public KernelResult GetProcessId64([R(1)] int handle, [R(1)] out ulong pid)
{
return _syscall.GetProcessId(out pid, handle);
}
public void Break64([R(0)] ulong reason, [R(1)] ulong x1, [R(2)] ulong info)
{
_syscall.Break(reason);
}
public void OutputDebugString64([R(0)] ulong strPtr, [R(1)] ulong size)
{
_syscall.OutputDebugString(strPtr, size);
}
public KernelResult GetInfo64([R(1)] InfoType id, [R(2)] int handle, [R(3)] long subId, [R(1)] out ulong value)
{
return _syscall.GetInfo(out value, id, handle, subId);
}
public KernelResult CreateEvent64([R(1)] out int wEventHandle, [R(2)] out int rEventHandle)
{
return _syscall.CreateEvent(out wEventHandle, out rEventHandle);
}
public KernelResult GetProcessList64([R(1)] ulong address, [R(2)] int maxCount, [R(1)] out int count)
{
return _syscall.GetProcessList(out count, address, maxCount);
}
public KernelResult GetSystemInfo64([R(1)] uint id, [R(2)] int handle, [R(3)] long subId, [R(1)] out long value)
{
return _syscall.GetSystemInfo(out value, id, handle, subId);
}
public KernelResult GetResourceLimitLimitValue64([R(1)] int handle, [R(2)] LimitableResource resource, [R(1)] out long limitValue)
{
return _syscall.GetResourceLimitLimitValue(out limitValue, handle, resource);
}
public KernelResult GetResourceLimitCurrentValue64([R(1)] int handle, [R(2)] LimitableResource resource, [R(1)] out long limitValue)
{
return _syscall.GetResourceLimitCurrentValue(out limitValue, handle, resource);
}
public KernelResult GetResourceLimitPeakValue64([R(1)] int handle, [R(2)] LimitableResource resource, [R(1)] out long peak)
{
return _syscall.GetResourceLimitPeakValue(out peak, handle, resource);
}
public KernelResult CreateResourceLimit64([R(1)] out int handle)
{
return _syscall.CreateResourceLimit(out handle);
}
public KernelResult SetResourceLimitLimitValue64([R(0)] int handle, [R(1)] LimitableResource resource, [R(2)] long limitValue)
{
return _syscall.SetResourceLimitLimitValue(handle, resource, limitValue);
}
// Thread
public KernelResult CreateThread64(
[R(1)] ulong entrypoint,
[R(2)] ulong argsPtr,
[R(3)] ulong stackTop,
[R(4)] int priority,
[R(5)] int cpuCore,
[R(1)] out int handle)
{
return _syscall.CreateThread(out handle, entrypoint, argsPtr, stackTop, priority, cpuCore);
}
public KernelResult StartThread64([R(0)] int handle)
{
return _syscall.StartThread(handle);
}
public void ExitThread64()
{
_syscall.ExitThread();
}
public void SleepThread64([R(0)] long timeout)
{
_syscall.SleepThread(timeout);
}
public KernelResult GetThreadPriority64([R(1)] int handle, [R(1)] out int priority)
{
return _syscall.GetThreadPriority(out priority, handle);
}
public KernelResult SetThreadPriority64([R(0)] int handle, [R(1)] int priority)
{
return _syscall.SetThreadPriority(handle, priority);
}
public KernelResult GetThreadCoreMask64([R(2)] int handle, [R(1)] out int preferredCore, [R(2)] out ulong affinityMask)
{
return _syscall.GetThreadCoreMask(out preferredCore, out affinityMask, handle);
}
public KernelResult SetThreadCoreMask64([R(0)] int handle, [R(1)] int preferredCore, [R(2)] ulong affinityMask)
{
return _syscall.SetThreadCoreMask(handle, preferredCore, affinityMask);
}
public int GetCurrentProcessorNumber64()
{
return _syscall.GetCurrentProcessorNumber();
}
public KernelResult GetThreadId64([R(1)] int handle, [R(1)] out ulong threadUid)
{
return _syscall.GetThreadId(out threadUid, handle);
}
public KernelResult SetThreadActivity64([R(0)] int handle, [R(1)] bool pause)
{
return _syscall.SetThreadActivity(handle, pause);
}
public KernelResult GetThreadContext364([R(0)] ulong address, [R(1)] int handle)
{
return _syscall.GetThreadContext3(address, handle);
}
// Thread synchronization
public KernelResult WaitSynchronization64([R(1)] ulong handlesPtr, [R(2)] int handlesCount, [R(3)] long timeout, [R(1)] out int handleIndex)
{
return _syscall.WaitSynchronization(out handleIndex, handlesPtr, handlesCount, timeout);
}
public KernelResult CancelSynchronization64([R(0)] int handle)
{
return _syscall.CancelSynchronization(handle);
}
public KernelResult ArbitrateLock64([R(0)] int ownerHandle, [R(1)] ulong mutexAddress, [R(2)] int requesterHandle)
{
return _syscall.ArbitrateLock(ownerHandle, mutexAddress, requesterHandle);
}
public KernelResult ArbitrateUnlock64([R(0)] ulong mutexAddress)
{
return _syscall.ArbitrateUnlock(mutexAddress);
}
public KernelResult WaitProcessWideKeyAtomic64(
[R(0)] ulong mutexAddress,
[R(1)] ulong condVarAddress,
[R(2)] int handle,
[R(3)] long timeout)
{
return _syscall.WaitProcessWideKeyAtomic(mutexAddress, condVarAddress, handle, timeout);
}
public KernelResult SignalProcessWideKey64([R(0)] ulong address, [R(1)] int count)
{
return _syscall.SignalProcessWideKey(address, count);
}
public KernelResult WaitForAddress64([R(0)] ulong address, [R(1)] ArbitrationType type, [R(2)] int value, [R(3)] long timeout)
{
return _syscall.WaitForAddress(address, type, value, timeout);
}
public KernelResult SignalToAddress64([R(0)] ulong address, [R(1)] SignalType type, [R(2)] int value, [R(3)] int count)
{
return _syscall.SignalToAddress(address, type, value, count);
}
public KernelResult SynchronizePreemptionState64()
{
return _syscall.SynchronizePreemptionState();
}
}
}

View File

@@ -1,20 +1,15 @@
using Ryujinx.Cpu; using Ryujinx.Cpu;
using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.HOS.Kernel.Threading;
using System;
namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
{ {
partial class SyscallHandler partial class SyscallHandler
{ {
private readonly KernelContext _context; private readonly KernelContext _context;
private readonly Syscall32 _syscall32;
private readonly Syscall64 _syscall64;
public SyscallHandler(KernelContext context) public SyscallHandler(KernelContext context)
{ {
_context = context; _context = context;
_syscall32 = new Syscall32(context.Syscall);
_syscall64 = new Syscall64(context.Syscall);
} }
public void SvcCall(IExecutionContext context, ulong address, int id) public void SvcCall(IExecutionContext context, ulong address, int id)
@@ -36,25 +31,11 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
if (context.IsAarch32) if (context.IsAarch32)
{ {
var svcFunc = SyscallTable.SvcTable32[id]; SyscallDispatch.Dispatch32(_context.Syscall, context, id);
if (svcFunc == null)
{
throw new NotImplementedException($"SVC 0x{id:X4} is not implemented.");
}
svcFunc(_syscall32, context);
} }
else else
{ {
var svcFunc = SyscallTable.SvcTable64[id]; SyscallDispatch.Dispatch64(_context.Syscall, context, id);
if (svcFunc == null)
{
throw new NotImplementedException($"SVC 0x{id:X4} is not implemented.");
}
svcFunc(_syscall64, context);
} }
currentThread.HandlePostSyscall(); currentThread.HandlePostSyscall();

View File

@@ -1,494 +0,0 @@
using Ryujinx.Common.Logging;
using Ryujinx.Cpu;
using Ryujinx.HLE.HOS.Kernel.Common;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
{
static class SyscallTable
{
private const int SvcFuncMaxArguments64 = 8;
private const int SvcFuncMaxArguments32 = 4;
private const int SvcMax = 0x80;
public static Action<Syscall32, IExecutionContext>[] SvcTable32 { get; }
public static Action<Syscall64, IExecutionContext>[] SvcTable64 { get; }
static SyscallTable()
{
SvcTable32 = new Action<Syscall32, IExecutionContext>[SvcMax];
SvcTable64 = new Action<Syscall64, IExecutionContext>[SvcMax];
Dictionary<int, string> svcFuncs64 = new Dictionary<int, string>
{
{ 0x01, nameof(Syscall64.SetHeapSize64) },
{ 0x02, nameof(Syscall64.SetMemoryPermission64) },
{ 0x03, nameof(Syscall64.SetMemoryAttribute64) },
{ 0x04, nameof(Syscall64.MapMemory64) },
{ 0x05, nameof(Syscall64.UnmapMemory64) },
{ 0x06, nameof(Syscall64.QueryMemory64) },
{ 0x07, nameof(Syscall64.ExitProcess64) },
{ 0x08, nameof(Syscall64.CreateThread64) },
{ 0x09, nameof(Syscall64.StartThread64) },
{ 0x0a, nameof(Syscall64.ExitThread64) },
{ 0x0b, nameof(Syscall64.SleepThread64) },
{ 0x0c, nameof(Syscall64.GetThreadPriority64) },
{ 0x0d, nameof(Syscall64.SetThreadPriority64) },
{ 0x0e, nameof(Syscall64.GetThreadCoreMask64) },
{ 0x0f, nameof(Syscall64.SetThreadCoreMask64) },
{ 0x10, nameof(Syscall64.GetCurrentProcessorNumber64) },
{ 0x11, nameof(Syscall64.SignalEvent64) },
{ 0x12, nameof(Syscall64.ClearEvent64) },
{ 0x13, nameof(Syscall64.MapSharedMemory64) },
{ 0x14, nameof(Syscall64.UnmapSharedMemory64) },
{ 0x15, nameof(Syscall64.CreateTransferMemory64) },
{ 0x16, nameof(Syscall64.CloseHandle64) },
{ 0x17, nameof(Syscall64.ResetSignal64) },
{ 0x18, nameof(Syscall64.WaitSynchronization64) },
{ 0x19, nameof(Syscall64.CancelSynchronization64) },
{ 0x1a, nameof(Syscall64.ArbitrateLock64) },
{ 0x1b, nameof(Syscall64.ArbitrateUnlock64) },
{ 0x1c, nameof(Syscall64.WaitProcessWideKeyAtomic64) },
{ 0x1d, nameof(Syscall64.SignalProcessWideKey64) },
{ 0x1e, nameof(Syscall64.GetSystemTick64) },
{ 0x1f, nameof(Syscall64.ConnectToNamedPort64) },
{ 0x21, nameof(Syscall64.SendSyncRequest64) },
{ 0x22, nameof(Syscall64.SendSyncRequestWithUserBuffer64) },
{ 0x23, nameof(Syscall64.SendAsyncRequestWithUserBuffer64) },
{ 0x24, nameof(Syscall64.GetProcessId64) },
{ 0x25, nameof(Syscall64.GetThreadId64) },
{ 0x26, nameof(Syscall64.Break64) },
{ 0x27, nameof(Syscall64.OutputDebugString64) },
{ 0x29, nameof(Syscall64.GetInfo64) },
{ 0x2c, nameof(Syscall64.MapPhysicalMemory64) },
{ 0x2d, nameof(Syscall64.UnmapPhysicalMemory64) },
{ 0x30, nameof(Syscall64.GetResourceLimitLimitValue64) },
{ 0x31, nameof(Syscall64.GetResourceLimitCurrentValue64) },
{ 0x32, nameof(Syscall64.SetThreadActivity64) },
{ 0x33, nameof(Syscall64.GetThreadContext364) },
{ 0x34, nameof(Syscall64.WaitForAddress64) },
{ 0x35, nameof(Syscall64.SignalToAddress64) },
{ 0x36, nameof(Syscall64.SynchronizePreemptionState64) },
{ 0x37, nameof(Syscall64.GetResourceLimitPeakValue64) },
{ 0x40, nameof(Syscall64.CreateSession64) },
{ 0x41, nameof(Syscall64.AcceptSession64) },
{ 0x43, nameof(Syscall64.ReplyAndReceive64) },
{ 0x44, nameof(Syscall64.ReplyAndReceiveWithUserBuffer64) },
{ 0x45, nameof(Syscall64.CreateEvent64) },
{ 0x4b, nameof(Syscall64.CreateCodeMemory64) },
{ 0x4c, nameof(Syscall64.ControlCodeMemory64) },
{ 0x51, nameof(Syscall64.MapTransferMemory64) },
{ 0x52, nameof(Syscall64.UnmapTransferMemory64) },
{ 0x65, nameof(Syscall64.GetProcessList64) },
{ 0x6f, nameof(Syscall64.GetSystemInfo64) },
{ 0x70, nameof(Syscall64.CreatePort64) },
{ 0x71, nameof(Syscall64.ManageNamedPort64) },
{ 0x72, nameof(Syscall64.ConnectToPort64) },
{ 0x73, nameof(Syscall64.SetProcessMemoryPermission64) },
{ 0x74, nameof(Syscall64.MapProcessMemory64) },
{ 0x75, nameof(Syscall64.UnmapProcessMemory64) },
{ 0x77, nameof(Syscall64.MapProcessCodeMemory64) },
{ 0x78, nameof(Syscall64.UnmapProcessCodeMemory64) },
{ 0x7B, nameof(Syscall64.TerminateProcess64) },
{ 0x7D, nameof(Syscall64.CreateResourceLimit64) },
{ 0x7E, nameof(Syscall64.SetResourceLimitLimitValue64) }
};
foreach (KeyValuePair<int, string> value in svcFuncs64)
{
SvcTable64[value.Key] = GenerateMethod<Syscall64>(value.Value, SvcFuncMaxArguments64);
}
Dictionary<int, string> svcFuncs32 = new Dictionary<int, string>
{
{ 0x01, nameof(Syscall32.SetHeapSize32) },
{ 0x02, nameof(Syscall32.SetMemoryPermission32) },
{ 0x03, nameof(Syscall32.SetMemoryAttribute32) },
{ 0x04, nameof(Syscall32.MapMemory32) },
{ 0x05, nameof(Syscall32.UnmapMemory32) },
{ 0x06, nameof(Syscall32.QueryMemory32) },
{ 0x07, nameof(Syscall32.ExitProcess32) },
{ 0x08, nameof(Syscall32.CreateThread32) },
{ 0x09, nameof(Syscall32.StartThread32) },
{ 0x0a, nameof(Syscall32.ExitThread32) },
{ 0x0b, nameof(Syscall32.SleepThread32) },
{ 0x0c, nameof(Syscall32.GetThreadPriority32) },
{ 0x0d, nameof(Syscall32.SetThreadPriority32) },
{ 0x0e, nameof(Syscall32.GetThreadCoreMask32) },
{ 0x0f, nameof(Syscall32.SetThreadCoreMask32) },
{ 0x10, nameof(Syscall32.GetCurrentProcessorNumber32) },
{ 0x11, nameof(Syscall32.SignalEvent32) },
{ 0x12, nameof(Syscall32.ClearEvent32) },
{ 0x13, nameof(Syscall32.MapSharedMemory32) },
{ 0x14, nameof(Syscall32.UnmapSharedMemory32) },
{ 0x15, nameof(Syscall32.CreateTransferMemory32) },
{ 0x16, nameof(Syscall32.CloseHandle32) },
{ 0x17, nameof(Syscall32.ResetSignal32) },
{ 0x18, nameof(Syscall32.WaitSynchronization32) },
{ 0x19, nameof(Syscall32.CancelSynchronization32) },
{ 0x1a, nameof(Syscall32.ArbitrateLock32) },
{ 0x1b, nameof(Syscall32.ArbitrateUnlock32) },
{ 0x1c, nameof(Syscall32.WaitProcessWideKeyAtomic32) },
{ 0x1d, nameof(Syscall32.SignalProcessWideKey32) },
{ 0x1e, nameof(Syscall32.GetSystemTick32) },
{ 0x1f, nameof(Syscall32.ConnectToNamedPort32) },
{ 0x21, nameof(Syscall32.SendSyncRequest32) },
{ 0x22, nameof(Syscall32.SendSyncRequestWithUserBuffer32) },
{ 0x24, nameof(Syscall32.GetProcessId32) },
{ 0x25, nameof(Syscall32.GetThreadId32) },
{ 0x26, nameof(Syscall32.Break32) },
{ 0x27, nameof(Syscall32.OutputDebugString32) },
{ 0x29, nameof(Syscall32.GetInfo32) },
{ 0x2c, nameof(Syscall32.MapPhysicalMemory32) },
{ 0x2d, nameof(Syscall32.UnmapPhysicalMemory32) },
{ 0x30, nameof(Syscall32.GetResourceLimitLimitValue32) },
{ 0x31, nameof(Syscall32.GetResourceLimitCurrentValue32) },
{ 0x32, nameof(Syscall32.SetThreadActivity32) },
{ 0x33, nameof(Syscall32.GetThreadContext332) },
{ 0x34, nameof(Syscall32.WaitForAddress32) },
{ 0x35, nameof(Syscall32.SignalToAddress32) },
{ 0x36, nameof(Syscall32.SynchronizePreemptionState32) },
{ 0x37, nameof(Syscall32.GetResourceLimitPeakValue32) },
{ 0x40, nameof(Syscall32.CreateSession32) },
{ 0x41, nameof(Syscall32.AcceptSession32) },
{ 0x43, nameof(Syscall32.ReplyAndReceive32) },
{ 0x45, nameof(Syscall32.CreateEvent32) },
{ 0x4b, nameof(Syscall32.CreateCodeMemory32) },
{ 0x4c, nameof(Syscall32.ControlCodeMemory32) },
{ 0x51, nameof(Syscall32.MapTransferMemory32) },
{ 0x52, nameof(Syscall32.UnmapTransferMemory32) },
{ 0x5F, nameof(Syscall32.FlushProcessDataCache32) },
{ 0x65, nameof(Syscall32.GetProcessList32) },
{ 0x6f, nameof(Syscall32.GetSystemInfo32) },
{ 0x70, nameof(Syscall32.CreatePort32) },
{ 0x71, nameof(Syscall32.ManageNamedPort32) },
{ 0x72, nameof(Syscall32.ConnectToPort32) },
{ 0x73, nameof(Syscall32.SetProcessMemoryPermission32) },
{ 0x74, nameof(Syscall32.MapProcessMemory32) },
{ 0x75, nameof(Syscall32.UnmapProcessMemory32) },
{ 0x77, nameof(Syscall32.MapProcessCodeMemory32) },
{ 0x78, nameof(Syscall32.UnmapProcessCodeMemory32) },
{ 0x7B, nameof(Syscall32.TerminateProcess32) },
{ 0x7D, nameof(Syscall32.CreateResourceLimit32) },
{ 0x7E, nameof(Syscall32.SetResourceLimitLimitValue32) }
};
foreach (KeyValuePair<int, string> value in svcFuncs32)
{
SvcTable32[value.Key] = GenerateMethod<Syscall32>(value.Value, SvcFuncMaxArguments32);
}
}
private static Action<T, IExecutionContext> GenerateMethod<T>(string svcName, int registerCleanCount)
{
Type[] argTypes = new Type[] { typeof(T), typeof(IExecutionContext) };
DynamicMethod method = new DynamicMethod(svcName, null, argTypes);
MethodInfo methodInfo = typeof(T).GetMethod(svcName);
ParameterInfo[] methodArgs = methodInfo.GetParameters();
ILGenerator generator = method.GetILGenerator();
void ConvertToArgType(Type sourceType)
{
CheckIfTypeIsSupported(sourceType, svcName);
switch (Type.GetTypeCode(sourceType))
{
case TypeCode.UInt32: generator.Emit(OpCodes.Conv_U4); break;
case TypeCode.Int32: generator.Emit(OpCodes.Conv_I4); break;
case TypeCode.UInt16: generator.Emit(OpCodes.Conv_U2); break;
case TypeCode.Int16: generator.Emit(OpCodes.Conv_I2); break;
case TypeCode.Byte: generator.Emit(OpCodes.Conv_U1); break;
case TypeCode.SByte: generator.Emit(OpCodes.Conv_I1); break;
case TypeCode.Boolean:
generator.Emit(OpCodes.Conv_I4);
generator.Emit(OpCodes.Ldc_I4_1);
generator.Emit(OpCodes.And);
break;
}
}
void ConvertToFieldType(Type sourceType)
{
CheckIfTypeIsSupported(sourceType, svcName);
switch (Type.GetTypeCode(sourceType))
{
case TypeCode.UInt32:
case TypeCode.Int32:
case TypeCode.UInt16:
case TypeCode.Int16:
case TypeCode.Byte:
case TypeCode.SByte:
case TypeCode.Boolean:
generator.Emit(OpCodes.Conv_U8);
break;
}
}
RAttribute GetRegisterAttribute(ParameterInfo parameterInfo)
{
RAttribute argumentAttribute = (RAttribute)parameterInfo.GetCustomAttribute(typeof(RAttribute));
if (argumentAttribute == null)
{
throw new InvalidOperationException($"Method \"{svcName}\" is missing a {typeof(RAttribute).Name} attribute on parameter \"{parameterInfo.Name}\"");
}
return argumentAttribute;
}
// For functions returning output values, the first registers
// are used to hold pointers where the value will be stored,
// so they can't be used to pass argument and we must
// skip them.
int byRefArgsCount = 0;
for (int index = 0; index < methodArgs.Length; index++)
{
if (methodArgs[index].ParameterType.IsByRef)
{
byRefArgsCount++;
}
}
BindingFlags staticNonPublic = BindingFlags.NonPublic | BindingFlags.Static;
// Print all the arguments for debugging purposes.
int inputArgsCount = methodArgs.Length - byRefArgsCount;
if (inputArgsCount != 0)
{
generator.Emit(OpCodes.Ldc_I4, inputArgsCount);
generator.Emit(OpCodes.Newarr, typeof(object));
string argsFormat = svcName;
for (int index = 0; index < methodArgs.Length; index++)
{
Type argType = methodArgs[index].ParameterType;
// Ignore out argument for printing
if (argType.IsByRef)
{
continue;
}
RAttribute registerAttribute = GetRegisterAttribute(methodArgs[index]);
argsFormat += $" {methodArgs[index].Name}: 0x{{{index}:X8}},";
generator.Emit(OpCodes.Dup);
generator.Emit(OpCodes.Ldc_I4, index);
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Ldc_I4, registerAttribute.Index);
MethodInfo info = typeof(IExecutionContext).GetMethod(nameof(IExecutionContext.GetX));
generator.Emit(OpCodes.Callvirt, info);
generator.Emit(OpCodes.Box, typeof(ulong));
generator.Emit(OpCodes.Stelem_Ref);
}
argsFormat = argsFormat.Substring(0, argsFormat.Length - 1);
generator.Emit(OpCodes.Ldstr, argsFormat);
}
else
{
generator.Emit(OpCodes.Ldnull);
generator.Emit(OpCodes.Ldstr, svcName);
}
MethodInfo printArgsMethod = typeof(SyscallTable).GetMethod(nameof(PrintArguments), staticNonPublic);
generator.Emit(OpCodes.Call, printArgsMethod);
// Call the SVC function handler.
generator.Emit(OpCodes.Ldarg_0);
List<(LocalBuilder, RAttribute)> locals = new List<(LocalBuilder, RAttribute)>();
for (int index = 0; index < methodArgs.Length; index++)
{
Type argType = methodArgs[index].ParameterType;
RAttribute registerAttribute = GetRegisterAttribute(methodArgs[index]);
if (argType.IsByRef)
{
argType = argType.GetElementType();
LocalBuilder local = generator.DeclareLocal(argType);
locals.Add((local, registerAttribute));
if (!methodArgs[index].IsOut)
{
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Ldc_I4, registerAttribute.Index);
MethodInfo info = typeof(IExecutionContext).GetMethod(nameof(IExecutionContext.GetX));
generator.Emit(OpCodes.Callvirt, info);
ConvertToArgType(argType);
generator.Emit(OpCodes.Stloc, local);
}
generator.Emit(OpCodes.Ldloca, local);
}
else
{
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Ldc_I4, registerAttribute.Index);
MethodInfo info = typeof(IExecutionContext).GetMethod(nameof(IExecutionContext.GetX));
generator.Emit(OpCodes.Callvirt, info);
ConvertToArgType(argType);
}
}
generator.Emit(OpCodes.Call, methodInfo);
Type retType = methodInfo.ReturnType;
// Print result code.
if (retType == typeof(KernelResult))
{
MethodInfo printResultMethod = typeof(SyscallTable).GetMethod(nameof(PrintResult), staticNonPublic);
generator.Emit(OpCodes.Dup);
generator.Emit(OpCodes.Ldstr, svcName);
generator.Emit(OpCodes.Call, printResultMethod);
}
uint registerInUse = 0;
// Save return value into register X0 (when the method has a return value).
if (retType != typeof(void))
{
CheckIfTypeIsSupported(retType, svcName);
LocalBuilder tempLocal = generator.DeclareLocal(retType);
generator.Emit(OpCodes.Stloc, tempLocal);
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Ldc_I4, 0);
generator.Emit(OpCodes.Ldloc, tempLocal);
ConvertToFieldType(retType);
MethodInfo info = typeof(IExecutionContext).GetMethod(nameof(IExecutionContext.SetX));
generator.Emit(OpCodes.Callvirt, info);
registerInUse |= 1u << 0;
}
for (int index = 0; index < locals.Count; index++)
{
(LocalBuilder local, RAttribute attribute) = locals[index];
if ((registerInUse & (1u << attribute.Index)) != 0)
{
throw new InvalidSvcException($"Method \"{svcName}\" has conflicting output values at register index \"{attribute.Index}\".");
}
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Ldc_I4, attribute.Index);
generator.Emit(OpCodes.Ldloc, local);
ConvertToFieldType(local.LocalType);
MethodInfo info = typeof(IExecutionContext).GetMethod(nameof(IExecutionContext.SetX));
generator.Emit(OpCodes.Callvirt, info);
registerInUse |= 1u << attribute.Index;
}
// Zero out the remaining unused registers.
for (int i = 0; i < registerCleanCount; i++)
{
if ((registerInUse & (1u << i)) != 0)
{
continue;
}
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Ldc_I4, i);
generator.Emit(OpCodes.Ldc_I8, 0L);
MethodInfo info = typeof(IExecutionContext).GetMethod(nameof(IExecutionContext.SetX));
generator.Emit(OpCodes.Callvirt, info);
}
generator.Emit(OpCodes.Ret);
return method.CreateDelegate<Action<T, IExecutionContext>>();
}
private static void CheckIfTypeIsSupported(Type type, string svcName)
{
switch (Type.GetTypeCode(type))
{
case TypeCode.UInt64:
case TypeCode.Int64:
case TypeCode.UInt32:
case TypeCode.Int32:
case TypeCode.UInt16:
case TypeCode.Int16:
case TypeCode.Byte:
case TypeCode.SByte:
case TypeCode.Boolean:
return;
}
throw new InvalidSvcException($"Method \"{svcName}\" has a invalid ref type \"{type.Name}\".");
}
private static void PrintArguments(object[] argValues, string formatOrSvcName)
{
if (argValues != null)
{
Logger.Trace?.Print(LogClass.KernelSvc, string.Format(formatOrSvcName, argValues));
}
else
{
Logger.Trace?.Print(LogClass.KernelSvc, formatOrSvcName);
}
}
private static void PrintResult(KernelResult result, string svcName)
{
if (result != KernelResult.Success &&
result != KernelResult.TimedOut &&
result != KernelResult.Cancelled &&
result != KernelResult.PortRemoteClosed &&
result != KernelResult.InvalidState)
{
Logger.Warning?.Print(LogClass.KernelSvc, $"{svcName} returned error {result}.");
}
else
{
Logger.Trace?.Print(LogClass.KernelSvc, $"{svcName} returned result {result}.");
}
}
}
}

View File

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

View File

@@ -429,8 +429,6 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
Channel.PushHostCommandBuffer(CreateWaitCommandBuffer(header.Fence)); Channel.PushHostCommandBuffer(CreateWaitCommandBuffer(header.Fence));
} }
Channel.PushEntries(entries);
header.Fence.Id = _channelSyncpoint.Id; header.Fence.Id = _channelSyncpoint.Id;
if (header.Flags.HasFlag(SubmitGpfifoFlags.FenceIncrement) || header.Flags.HasFlag(SubmitGpfifoFlags.IncrementWithValue)) 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); header.Fence.Value = _device.System.HostSyncpoint.ReadSyncpointMaxValue(header.Fence.Id);
} }
Channel.PushEntries(entries);
if (header.Flags.HasFlag(SubmitGpfifoFlags.FenceIncrement)) if (header.Flags.HasFlag(SubmitGpfifoFlags.FenceIncrement))
{ {
Channel.PushHostCommandBuffer(CreateIncrementCommandBuffer(ref header.Fence, header.Flags)); 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.Ipc;
using Ryujinx.HLE.HOS.Kernel; using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Common;
@@ -38,15 +39,15 @@ namespace Ryujinx.HLE.HOS.Services
private readonly Dictionary<int, Func<IpcService>> _ports = new Dictionary<int, Func<IpcService>>(); private readonly Dictionary<int, Func<IpcService>> _ports = new Dictionary<int, Func<IpcService>>();
public ManualResetEvent InitDone { get; } public ManualResetEvent InitDone { get; }
public Func<IpcService> SmObjectFactory { get; }
public string Name { get; } public string Name { get; }
public Func<IpcService> SmObjectFactory { get; }
public ServerBase(KernelContext context, string name, Func<IpcService> smObjectFactory = null) public ServerBase(KernelContext context, string name, Func<IpcService> smObjectFactory = null)
{ {
InitDone = new ManualResetEvent(false); InitDone = new ManualResetEvent(false);
_context = context;
Name = name; Name = name;
SmObjectFactory = smObjectFactory; SmObjectFactory = smObjectFactory;
_context = context;
const ProcessCreationFlags flags = const ProcessCreationFlags flags =
ProcessCreationFlags.EnableAslr | ProcessCreationFlags.EnableAslr |
@@ -56,7 +57,7 @@ namespace Ryujinx.HLE.HOS.Services
ProcessCreationInfo creationInfo = new ProcessCreationInfo("Service", 1, 0, 0x8000000, 1, flags, 0, 0); ProcessCreationInfo creationInfo = new ProcessCreationInfo("Service", 1, 0, 0x8000000, 1, flags, 0, 0);
KernelStatic.StartInitialProcess(context, creationInfo, DefaultCapabilities, 44, ServerLoop); KernelStatic.StartInitialProcess(context, creationInfo, DefaultCapabilities, 44, Main);
} }
private void AddPort(int serverPortHandle, Func<IpcService> objectFactory) private void AddPort(int serverPortHandle, Func<IpcService> objectFactory)
@@ -80,6 +81,11 @@ namespace Ryujinx.HLE.HOS.Services
_sessions.Add(serverSessionHandle, obj); _sessions.Add(serverSessionHandle, obj);
} }
private void Main()
{
ServerLoop();
}
private void ServerLoop() private void ServerLoop()
{ {
_selfProcess = KernelStatic.GetCurrentProcess(); _selfProcess = KernelStatic.GetCurrentProcess();

View File

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

View File

@@ -11,6 +11,9 @@
<ProjectReference Include="..\Ryujinx.Graphics.Host1x\Ryujinx.Graphics.Host1x.csproj" /> <ProjectReference Include="..\Ryujinx.Graphics.Host1x\Ryujinx.Graphics.Host1x.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Nvdec\Ryujinx.Graphics.Nvdec.csproj" /> <ProjectReference Include="..\Ryujinx.Graphics.Nvdec\Ryujinx.Graphics.Nvdec.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Vic\Ryujinx.Graphics.Vic.csproj" /> <ProjectReference Include="..\Ryujinx.Graphics.Vic\Ryujinx.Graphics.Vic.csproj" />
<ProjectReference Include="..\Ryujinx.Horizon.Generators\Ryujinx.Horizon.Generators.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
<ProjectReference Include="..\Ryujinx.Memory\Ryujinx.Memory.csproj" /> <ProjectReference Include="..\Ryujinx.Memory\Ryujinx.Memory.csproj" />
<ProjectReference Include="..\ARMeilleure\ARMeilleure.csproj" /> <ProjectReference Include="..\ARMeilleure\ARMeilleure.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Gpu\Ryujinx.Graphics.Gpu.csproj" /> <ProjectReference Include="..\Ryujinx.Graphics.Gpu\Ryujinx.Graphics.Gpu.csproj" />

View File

@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers> <RuntimeIdentifiers>win10-x64;osx-x64;linux-x64</RuntimeIdentifiers>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Version>1.0.0-dirty</Version> <Version>1.0.0-dirty</Version>

View File

@@ -0,0 +1,58 @@
using System.Text;
namespace Ryujinx.Horizon.Generators
{
class CodeGenerator
{
private const string Indent = " ";
private readonly StringBuilder _sb;
private string _currentIndent;
public CodeGenerator()
{
_sb = new StringBuilder();
}
public void EnterScope(string header = null)
{
if (header != null)
{
AppendLine(header);
}
AppendLine("{");
IncreaseIndentation();
}
public void LeaveScope()
{
DecreaseIndentation();
AppendLine("}");
}
public void IncreaseIndentation()
{
_currentIndent += Indent;
}
public void DecreaseIndentation()
{
_currentIndent = _currentIndent.Substring(0, _currentIndent.Length - Indent.Length);
}
public void AppendLine()
{
_sb.AppendLine();
}
public void AppendLine(string text)
{
_sb.AppendLine(_currentIndent + text);
}
public override string ToString()
{
return _sb.ToString();
}
}
}

View File

@@ -0,0 +1,524 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace Ryujinx.Horizon.Generators.Kernel
{
[Generator]
class SyscallGenerator : ISourceGenerator
{
private const string ClassNamespace = "Ryujinx.HLE.HOS.Kernel.SupervisorCall";
private const string ClassName = "SyscallDispatch";
private const string A32Suffix = "32";
private const string A64Suffix = "64";
private const string ResultVariableName = "result";
private const string ArgVariablePrefix = "arg";
private const string ResultCheckHelperName = "LogResultAsTrace";
private const string TypeSystemBoolean = "System.Boolean";
private const string TypeSystemInt32 = "System.Int32";
private const string TypeSystemInt64 = "System.Int64";
private const string TypeSystemUInt32 = "System.UInt32";
private const string TypeSystemUInt64 = "System.UInt64";
private const string NamespaceKernel = "Ryujinx.HLE.HOS.Kernel";
private const string TypeSvcAttribute = NamespaceKernel + ".SupervisorCall.SvcAttribute";
private const string TypePointerSizedAttribute = NamespaceKernel + ".SupervisorCall.PointerSizedAttribute";
private const string TypeKernelResultName = "KernelResult";
private const string TypeKernelResult = NamespaceKernel + ".Common." + TypeKernelResultName;
private const string TypeExecutionContext = "IExecutionContext";
private static readonly string[] _expectedResults = new string[]
{
$"{TypeKernelResultName}.Success",
$"{TypeKernelResultName}.TimedOut",
$"{TypeKernelResultName}.Cancelled",
$"{TypeKernelResultName}.PortRemoteClosed",
$"{TypeKernelResultName}.InvalidState"
};
private struct OutParameter
{
public readonly string Identifier;
public readonly bool NeedsSplit;
public OutParameter(string identifier, bool needsSplit = false)
{
Identifier = identifier;
NeedsSplit = needsSplit;
}
}
private struct RegisterAllocatorA32
{
private uint _useSet;
private int _linearIndex;
public int AllocateSingle()
{
return Allocate();
}
public (int, int) AllocatePair()
{
_linearIndex += _linearIndex & 1;
return (Allocate(), Allocate());
}
private int Allocate()
{
int regIndex;
if (_linearIndex < 4)
{
regIndex = _linearIndex++;
}
else
{
regIndex = -1;
for (int i = 0; i < 32; i++)
{
if ((_useSet & (1 << i)) == 0)
{
regIndex = i;
break;
}
}
Debug.Assert(regIndex != -1);
}
_useSet |= 1u << regIndex;
return regIndex;
}
public void AdvanceLinearIndex()
{
_linearIndex++;
}
}
private struct SyscallIdAndName : IComparable<SyscallIdAndName>
{
public readonly int Id;
public readonly string Name;
public SyscallIdAndName(int id, string name)
{
Id = id;
Name = name;
}
public int CompareTo(SyscallIdAndName other)
{
return Id.CompareTo(other.Id);
}
}
public void Execute(GeneratorExecutionContext context)
{
SyscallSyntaxReceiver syntaxReceiver = (SyscallSyntaxReceiver)context.SyntaxReceiver;
CodeGenerator generator = new CodeGenerator();
generator.AppendLine("using Ryujinx.Common.Logging;");
generator.AppendLine("using Ryujinx.Cpu;");
generator.AppendLine($"using {NamespaceKernel}.Common;");
generator.AppendLine($"using {NamespaceKernel}.Memory;");
generator.AppendLine($"using {NamespaceKernel}.Process;");
generator.AppendLine($"using {NamespaceKernel}.Threading;");
generator.AppendLine("using System;");
generator.AppendLine();
generator.EnterScope($"namespace {ClassNamespace}");
generator.EnterScope($"static class {ClassName}");
GenerateResultCheckHelper(generator);
generator.AppendLine();
List<SyscallIdAndName> syscalls = new List<SyscallIdAndName>();
foreach (var method in syntaxReceiver.SvcImplementations)
{
GenerateMethod32(generator, context.Compilation, method);
GenerateMethod64(generator, context.Compilation, method);
foreach (var attributeList in method.AttributeLists)
{
foreach (var attribute in attributeList.Attributes)
{
if (GetCanonicalTypeName(context.Compilation, attribute) != TypeSvcAttribute)
{
continue;
}
foreach (var attributeArg in attribute.ArgumentList.Arguments)
{
if (attributeArg.Expression.Kind() == SyntaxKind.NumericLiteralExpression)
{
LiteralExpressionSyntax numericLiteral = (LiteralExpressionSyntax)attributeArg.Expression;
syscalls.Add(new SyscallIdAndName((int)numericLiteral.Token.Value, method.Identifier.Text));
}
}
}
}
}
syscalls.Sort();
GenerateDispatch(generator, syscalls, A32Suffix);
generator.AppendLine();
GenerateDispatch(generator, syscalls, A64Suffix);
generator.LeaveScope();
generator.LeaveScope();
context.AddSource($"{ClassName}.g.cs", generator.ToString());
}
private static void GenerateResultCheckHelper(CodeGenerator generator)
{
generator.EnterScope($"private static bool {ResultCheckHelperName}({TypeKernelResultName} {ResultVariableName})");
string[] expectedChecks = new string[_expectedResults.Length];
for (int i = 0; i < expectedChecks.Length; i++)
{
expectedChecks[i] = $"{ResultVariableName} == {_expectedResults[i]}";
}
string checks = string.Join(" || ", expectedChecks);
generator.AppendLine($"return {checks};");
generator.LeaveScope();
}
private static void GenerateMethod32(CodeGenerator generator, Compilation compilation, MethodDeclarationSyntax method)
{
generator.EnterScope($"private static void {method.Identifier.Text}{A32Suffix}(Syscall syscall, {TypeExecutionContext} context)");
string[] args = new string[method.ParameterList.Parameters.Count];
int index = 0;
RegisterAllocatorA32 regAlloc = new RegisterAllocatorA32();
List<OutParameter> outParameters = new List<OutParameter>();
List<string> logInArgs = new List<string>();
List<string> logOutArgs = new List<string>();
foreach (var methodParameter in method.ParameterList.Parameters)
{
string name = methodParameter.Identifier.Text;
string argName = GetPrefixedArgName(name);
string typeName = methodParameter.Type.ToString();
string canonicalTypeName = GetCanonicalTypeName(compilation, methodParameter.Type);
if (methodParameter.Modifiers.Any(SyntaxKind.OutKeyword))
{
bool needsSplit = Is64BitInteger(canonicalTypeName) && !IsPointerSized(compilation, methodParameter);
outParameters.Add(new OutParameter(argName, needsSplit));
logOutArgs.Add($"{name}: {GetFormattedLogValue(argName, canonicalTypeName)}");
argName = $"out {typeName} {argName}";
regAlloc.AdvanceLinearIndex();
}
else
{
if (Is64BitInteger(canonicalTypeName))
{
if (IsPointerSized(compilation, methodParameter))
{
int registerIndex = regAlloc.AllocateSingle();
generator.AppendLine($"var {argName} = (uint)context.GetX({registerIndex});");
}
else
{
(int registerIndex, int registerIndex2) = regAlloc.AllocatePair();
string valueLow = $"(ulong)(uint)context.GetX({registerIndex})";
string valueHigh = $"(ulong)(uint)context.GetX({registerIndex2})";
string value = $"{valueLow} | ({valueHigh} << 32)";
generator.AppendLine($"var {argName} = ({typeName})({value});");
}
}
else
{
int registerIndex = regAlloc.AllocateSingle();
string value = GenerateCastFromUInt64($"context.GetX({registerIndex})", canonicalTypeName, typeName);
generator.AppendLine($"var {argName} = {value};");
}
logInArgs.Add($"{name}: {GetFormattedLogValue(argName, canonicalTypeName)}");
}
args[index++] = argName;
}
GenerateLogPrintBeforeCall(generator, method.Identifier.Text, logInArgs);
string returnTypeName = method.ReturnType.ToString();
string argsList = string.Join(", ", args);
int returnRegisterIndex = 0;
string result = null;
string canonicalReturnTypeName = null;
if (returnTypeName != "void")
{
generator.AppendLine($"var {ResultVariableName} = syscall.{method.Identifier.Text}({argsList});");
generator.AppendLine($"context.SetX({returnRegisterIndex++}, (uint){ResultVariableName});");
canonicalReturnTypeName = GetCanonicalTypeName(compilation, method.ReturnType);
if (Is64BitInteger(canonicalReturnTypeName))
{
generator.AppendLine($"context.SetX({returnRegisterIndex++}, (uint)({ResultVariableName} >> 32));");
}
result = GetFormattedLogValue(ResultVariableName, canonicalReturnTypeName);
}
else
{
generator.AppendLine($"syscall.{method.Identifier.Text}({argsList});");
}
foreach (OutParameter outParameter in outParameters)
{
generator.AppendLine($"context.SetX({returnRegisterIndex++}, (uint){outParameter.Identifier});");
if (outParameter.NeedsSplit)
{
generator.AppendLine($"context.SetX({returnRegisterIndex++}, (uint)({outParameter.Identifier} >> 32));");
}
}
while (returnRegisterIndex < 4)
{
generator.AppendLine($"context.SetX({returnRegisterIndex++}, 0);");
}
GenerateLogPrintAfterCall(generator, method.Identifier.Text, logOutArgs, result, canonicalReturnTypeName);
generator.LeaveScope();
generator.AppendLine();
}
private static void GenerateMethod64(CodeGenerator generator, Compilation compilation, MethodDeclarationSyntax method)
{
generator.EnterScope($"private static void {method.Identifier.Text}{A64Suffix}(Syscall syscall, {TypeExecutionContext} context)");
string[] args = new string[method.ParameterList.Parameters.Count];
int registerIndex = 0;
int index = 0;
List<OutParameter> outParameters = new List<OutParameter>();
List<string> logInArgs = new List<string>();
List<string> logOutArgs = new List<string>();
foreach (var methodParameter in method.ParameterList.Parameters)
{
string name = methodParameter.Identifier.Text;
string argName = GetPrefixedArgName(name);
string typeName = methodParameter.Type.ToString();
string canonicalTypeName = GetCanonicalTypeName(compilation, methodParameter.Type);
if (methodParameter.Modifiers.Any(SyntaxKind.OutKeyword))
{
outParameters.Add(new OutParameter(argName));
logOutArgs.Add($"{name}: {GetFormattedLogValue(argName, canonicalTypeName)}");
argName = $"out {typeName} {argName}";
registerIndex++;
}
else
{
string value = GenerateCastFromUInt64($"context.GetX({registerIndex++})", canonicalTypeName, typeName);
generator.AppendLine($"var {argName} = {value};");
logInArgs.Add($"{name}: {GetFormattedLogValue(argName, canonicalTypeName)}");
}
args[index++] = argName;
}
GenerateLogPrintBeforeCall(generator, method.Identifier.Text, logInArgs);
string argsList = string.Join(", ", args);
int returnRegisterIndex = 0;
string result = null;
string canonicalReturnTypeName = null;
if (method.ReturnType.ToString() != "void")
{
generator.AppendLine($"var {ResultVariableName} = syscall.{method.Identifier.Text}({argsList});");
generator.AppendLine($"context.SetX({returnRegisterIndex++}, (ulong){ResultVariableName});");
canonicalReturnTypeName = GetCanonicalTypeName(compilation, method.ReturnType);
result = GetFormattedLogValue(ResultVariableName, canonicalReturnTypeName);
}
else
{
generator.AppendLine($"syscall.{method.Identifier.Text}({argsList});");
}
foreach (OutParameter outParameter in outParameters)
{
generator.AppendLine($"context.SetX({returnRegisterIndex++}, (ulong){outParameter.Identifier});");
}
while (returnRegisterIndex < 8)
{
generator.AppendLine($"context.SetX({returnRegisterIndex++}, 0);");
}
GenerateLogPrintAfterCall(generator, method.Identifier.Text, logOutArgs, result, canonicalReturnTypeName);
generator.LeaveScope();
generator.AppendLine();
}
private static string GetFormattedLogValue(string value, string canonicalTypeName)
{
if (Is32BitInteger(canonicalTypeName))
{
return $"0x{{{value}:X8}}";
}
else if (Is64BitInteger(canonicalTypeName))
{
return $"0x{{{value}:X16}}";
}
return $"{{{value}}}";
}
private static string GetPrefixedArgName(string name)
{
return ArgVariablePrefix + name[0].ToString().ToUpperInvariant() + name.Substring(1);
}
private static string GetCanonicalTypeName(Compilation compilation, SyntaxNode syntaxNode)
{
TypeInfo typeInfo = compilation.GetSemanticModel(syntaxNode.SyntaxTree).GetTypeInfo(syntaxNode);
if (typeInfo.Type.ContainingNamespace == null)
{
return typeInfo.Type.Name;
}
return $"{typeInfo.Type.ContainingNamespace.ToDisplayString()}.{typeInfo.Type.Name}";
}
private static void GenerateLogPrintBeforeCall(CodeGenerator generator, string methodName, List<string> argList)
{
string log = $"{methodName}({string.Join(", ", argList)})";
GenerateLogPrint(generator, "Trace", "KernelSvc", log);
}
private static void GenerateLogPrintAfterCall(
CodeGenerator generator,
string methodName,
List<string> argList,
string result,
string canonicalResultTypeName)
{
string log = $"{methodName}({string.Join(", ", argList)})";
if (result != null)
{
log += $" = {result}";
}
if (canonicalResultTypeName == TypeKernelResult)
{
generator.EnterScope($"if ({ResultCheckHelperName}({ResultVariableName}))");
GenerateLogPrint(generator, "Trace", "KernelSvc", log);
generator.LeaveScope();
generator.EnterScope("else");
GenerateLogPrint(generator, "Warning", "KernelSvc", log);
generator.LeaveScope();
}
else
{
GenerateLogPrint(generator, "Trace", "KernelSvc", log);
}
}
private static void GenerateLogPrint(CodeGenerator generator, string logLevel, string logClass, string log)
{
generator.AppendLine($"Logger.{logLevel}?.PrintMsg(LogClass.{logClass}, $\"{log}\");");
}
private static void GenerateDispatch(CodeGenerator generator, List<SyscallIdAndName> syscalls, string suffix)
{
generator.EnterScope($"public static void Dispatch{suffix}(Syscall syscall, {TypeExecutionContext} context, int id)");
generator.EnterScope("switch (id)");
foreach (var syscall in syscalls)
{
generator.AppendLine($"case {syscall.Id}:");
generator.IncreaseIndentation();
generator.AppendLine($"{syscall.Name}{suffix}(syscall, context);");
generator.AppendLine("break;");
generator.DecreaseIndentation();
}
generator.AppendLine($"default:");
generator.IncreaseIndentation();
generator.AppendLine("throw new NotImplementedException($\"SVC 0x{id:X4} is not implemented.\");");
generator.DecreaseIndentation();
generator.LeaveScope();
generator.LeaveScope();
}
private static bool Is32BitInteger(string canonicalTypeName)
{
return canonicalTypeName == TypeSystemInt32 || canonicalTypeName == TypeSystemUInt32;
}
private static bool Is64BitInteger(string canonicalTypeName)
{
return canonicalTypeName == TypeSystemInt64 || canonicalTypeName == TypeSystemUInt64;
}
private static string GenerateCastFromUInt64(string value, string canonicalTargetTypeName, string targetTypeName)
{
if (canonicalTargetTypeName == TypeSystemBoolean)
{
return $"({value} & 1) != 0";
}
return $"({targetTypeName}){value}";
}
private static bool IsPointerSized(Compilation compilation, ParameterSyntax parameterSyntax)
{
foreach (var attributeList in parameterSyntax.AttributeLists)
{
foreach (var attribute in attributeList.Attributes)
{
if (GetCanonicalTypeName(compilation, attribute) == TypePointerSizedAttribute)
{
return true;
}
}
}
return false;
}
public void Initialize(GeneratorInitializationContext context)
{
context.RegisterForSyntaxNotifications(() => new SyscallSyntaxReceiver());
}
}
}

View File

@@ -0,0 +1,54 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Generic;
using System.Linq;
namespace Ryujinx.Horizon.Generators.Kernel
{
class SyscallSyntaxReceiver : ISyntaxReceiver
{
public List<MethodDeclarationSyntax> SvcImplementations { get; }
public SyscallSyntaxReceiver()
{
SvcImplementations = new List<MethodDeclarationSyntax>();
}
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
{
if (syntaxNode is ClassDeclarationSyntax classDeclaration && classDeclaration.AttributeLists.Count != 0)
{
foreach (var attributeList in classDeclaration.AttributeLists)
{
if (attributeList.Attributes.Any(x => x.Name.GetText().ToString() == "SvcImpl"))
{
foreach (var memberDeclaration in classDeclaration.Members)
{
if (memberDeclaration is MethodDeclarationSyntax methodDeclaration)
{
VisitMethod(methodDeclaration);
}
}
break;
}
}
}
}
private void VisitMethod(MethodDeclarationSyntax methodDeclaration)
{
if (methodDeclaration.AttributeLists.Count != 0)
{
foreach (var attributeList in methodDeclaration.AttributeLists)
{
if (attributeList.Attributes.Any(x => x.Name.GetText().ToString() == "Svc"))
{
SvcImplementations.Add(methodDeclaration);
break;
}
}
}
}
}
}

View File

@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.2.0" />
</ItemGroup>
</Project>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,7 +2,6 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<Configurations>Debug;Release</Configurations> <Configurations>Debug;Release</Configurations>
</PropertyGroup> </PropertyGroup>

View File

@@ -2,7 +2,6 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>

View File

@@ -70,7 +70,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Nvdec.FFmp
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Ava", "Ryujinx.Ava\Ryujinx.Ava.csproj", "{7C1B2721-13DA-4B62-B046-C626605ECCE6}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Ava", "Ryujinx.Ava\Ryujinx.Ava.csproj", "{7C1B2721-13DA-4B62-B046-C626605ECCE6}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Ui.Common", "Ryujinx.Ui.Common\Ryujinx.Ui.Common.csproj", "{BA161CA0-CD65-4E6E-B644-51C8D1E542DC}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Ui.Common", "Ryujinx.Ui.Common\Ryujinx.Ui.Common.csproj", "{BA161CA0-CD65-4E6E-B644-51C8D1E542DC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Horizon.Generators", "Ryujinx.Horizon.Generators\Ryujinx.Horizon.Generators.csproj", "{6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -206,6 +208,10 @@ Global
{BA161CA0-CD65-4E6E-B644-51C8D1E542DC}.Debug|Any CPU.Build.0 = Debug|Any CPU {BA161CA0-CD65-4E6E-B644-51C8D1E542DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BA161CA0-CD65-4E6E-B644-51C8D1E542DC}.Release|Any CPU.ActiveCfg = Release|Any CPU {BA161CA0-CD65-4E6E-B644-51C8D1E542DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BA161CA0-CD65-4E6E-B644-51C8D1E542DC}.Release|Any CPU.Build.0 = Release|Any CPU {BA161CA0-CD65-4E6E-B644-51C8D1E542DC}.Release|Any CPU.Build.0 = Release|Any CPU
{6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers> <RuntimeIdentifiers>win10-x64;osx-x64;linux-x64</RuntimeIdentifiers>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Version>1.0.0-dirty</Version> <Version>1.0.0-dirty</Version>