Compare commits

...

11 Commits

Author SHA1 Message Date
Emmanuel Hansen
d9053bbe37 Avalonia - Save Manager (#3476)
* Add save manager to account selector

* add fallback to app metadata for titlename if app is not in gamelist

* Allow recovering lost accounts
2022-12-02 13:16:43 +00:00
Mary-nyan
c25e8427aa amadeus: Fix wrong SendCommands logic (#3969)
* amadeus: Fix wrong SendCommands logic

Might help with audio desync, might cause audio stutters, will see!

* Address gdkchan's comment
2022-12-02 13:01:19 +00:00
Isaac Marovitz
21a081b185 Add back locales removed in #3955 (#3983)
Add back `SettingsButtonSave` & `SettingsButtonClose` removed in #3955

Fixes #3982
2022-12-02 12:46:18 +00:00
Isaac Marovitz
b540ea80d1 Ava GUI: Make Dialogue More Intuitive (#3955)
* Adjust button position and locales

* Shortcuts + Highlight default action

* Update Locales - Corrections Welcome

* Move `Apply` button back to right side

* OS Reactive Button layout

* Fix reversed boolean :)

* Fix accented button styling
2022-12-02 03:31:21 +01:00
Mary
d692a9b83e Revert "nuget: bump SixLabors.ImageSharp from 1.0.4 to 2.1.3 (#3976)"
This reverts commit 9677ddaa5d.

SixLabors.ImageShar switched to a shady and vague license starting with 2.x
without mentioning it on their changelog.

As a result we are staying on 1.x (licensed under Apache-2) and will
seak an alternative package.
2022-12-01 23:06:55 +01:00
dependabot[bot]
9677ddaa5d nuget: bump SixLabors.ImageSharp from 1.0.4 to 2.1.3 (#3976)
* nuget: bump SixLabors.ImageSharp from 1.0.4 to 2.1.3

Bumps [SixLabors.ImageSharp](https://github.com/SixLabors/ImageSharp) from 1.0.4 to 2.1.3.
- [Release notes](https://github.com/SixLabors/ImageSharp/releases)
- [Commits](https://github.com/SixLabors/ImageSharp/compare/v1.0.4...v2.1.3)

---
updated-dependencies:
- dependency-name: SixLabors.ImageSharp
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update for 2.x changes

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Mary <mary@mary.zone>
2022-12-01 21:54:41 +00:00
Mary-nyan
ce92e8cd04 chore: Update Silk.NET to 2.16.0 (#3953) 2022-12-01 19:11:56 +01:00
Isaac Marovitz
456fc04007 Better SDL2 Audio Init Error Logging (#3967)
* Better SDL2 Audio Init Error Logging

* Update Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs

Co-authored-by: Mary-nyan <thog@protonmail.com>

* Update Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs

Co-authored-by: Mary-nyan <thog@protonmail.com>

* Update Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs

Co-authored-by: Mary-nyan <thog@protonmail.com>

* Update Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update SDL2HardwareDeviceDriver.cs

* Update SDL2HardwareDeviceDriver.cs

Co-authored-by: Mary-nyan <thog@protonmail.com>
Co-authored-by: Ac_K <Acoustik666@gmail.com>
2022-12-01 17:53:13 +01:00
riperiperi
458452279c GPU: Track buffer migrations and flush source on incomplete copy (#3952)
* Track buffer migrations and flush source on incomplete copy

Makes sure that the modified range list is always from the latest iteration of the buffer, and flushes earlier iterations of a buffer if the data has not been migrated yet.

* Cleanup 1

* Reduce cost for redundant signal checks on Vulkan

* Only inherit the range list if there are pending ranges.

* Fix OpenGL

* Address Feedback

* Whoops
2022-12-01 16:30:13 +01:00
Mary-nyan
817b89767a infra: Add distribution files for macOS (#3934)
This upstream macOS packing and distribution files
2022-12-01 14:08:43 +01:00
TSRBerry
3fb583c98c Avalonia: Clean up leftover RenderTimer & Fix minimum and initial window size (#3935)
* ava: Cleanup RenderTimer

* ava: Remove ContentControl from RendererHost

* ava: Remove unused actual scale factor

* ava: Enable UseGpu for Linux

* ava: Set better initial size & Scale the window properly

* ava: Realign properties

* ava: Use explicit type & specify where the note applies
2022-11-30 23:34:25 +01:00
87 changed files with 2506 additions and 600 deletions

View File

@@ -1,5 +1,6 @@
using Ryujinx.Audio.Common; using Ryujinx.Audio.Common;
using Ryujinx.Audio.Integration; using Ryujinx.Audio.Integration;
using Ryujinx.Common.Logging;
using Ryujinx.Memory; using Ryujinx.Memory;
using Ryujinx.SDL2.Common; using Ryujinx.SDL2.Common;
using System; using System;
@@ -112,6 +113,9 @@ namespace Ryujinx.Audio.Backends.SDL2
if (device == 0) if (device == 0)
{ {
Logger.Error?.Print(LogClass.Application,
$"SDL2 open audio device initialization failed with error \"{SDL_GetError()}\"");
return 0; return 0;
} }
@@ -119,6 +123,7 @@ namespace Ryujinx.Audio.Backends.SDL2
if (!isValid) if (!isValid)
{ {
Logger.Error?.Print(LogClass.Application, "SDL2 open audio device is not valid");
SDL_CloseAudioDevice(device); SDL_CloseAudioDevice(device);
return 0; return 0;

View File

@@ -116,6 +116,11 @@ namespace Ryujinx.Audio.Renderer.Dsp
}; };
} }
public bool HasRemainingCommands(int sessionId)
{
return _sessionCommandList[sessionId] != null;
}
public void Signal() public void Signal()
{ {
_mailbox.SendMessage(MailboxMessage.RenderStart); _mailbox.SendMessage(MailboxMessage.RenderStart);

View File

@@ -670,6 +670,8 @@ namespace Ryujinx.Audio.Renderer.Server
{ {
_terminationEvent.Reset(); _terminationEvent.Reset();
if (!_manager.Processor.HasRemainingCommands(_sessionId))
{
GenerateCommandList(out CommandList commands); GenerateCommandList(out CommandList commands);
_manager.Processor.Send(_sessionId, _manager.Processor.Send(_sessionId,
@@ -680,6 +682,11 @@ namespace Ryujinx.Audio.Renderer.Server
_systemEvent.Signal(); _systemEvent.Signal();
} }
else else
{
_isDspRunningBehind = true;
}
}
else
{ {
_terminationEvent.Set(); _terminationEvent.Set();
} }

View File

@@ -168,6 +168,8 @@
"SettingsTabInputDirectKeyboardAccess": "Direkter Tastaturzugriff", "SettingsTabInputDirectKeyboardAccess": "Direkter Tastaturzugriff",
"SettingsButtonSave": "Speichern", "SettingsButtonSave": "Speichern",
"SettingsButtonClose": "Schließen", "SettingsButtonClose": "Schließen",
"SettingsButtonOk": "OK",
"SettingsButtonCancel": "Abbrechen",
"SettingsButtonApply": "Übernehmen", "SettingsButtonApply": "Übernehmen",
"ControllerSettingsPlayer": "Spieler", "ControllerSettingsPlayer": "Spieler",
"ControllerSettingsPlayer1": "Spieler 1", "ControllerSettingsPlayer1": "Spieler 1",

View File

@@ -168,6 +168,8 @@
"SettingsTabInputDirectKeyboardAccess": "Άμεση Πρόσβαση στο Πληκτρολόγιο", "SettingsTabInputDirectKeyboardAccess": "Άμεση Πρόσβαση στο Πληκτρολόγιο",
"SettingsButtonSave": "Αποθήκευση", "SettingsButtonSave": "Αποθήκευση",
"SettingsButtonClose": "Κλείσιμο", "SettingsButtonClose": "Κλείσιμο",
"SettingsButtonOk": "OK",
"SettingsButtonCancel": "Ακύρωση",
"SettingsButtonApply": "Εφαρμογή", "SettingsButtonApply": "Εφαρμογή",
"ControllerSettingsPlayer": "Παίχτης", "ControllerSettingsPlayer": "Παίχτης",
"ControllerSettingsPlayer1": "Παίχτης 1", "ControllerSettingsPlayer1": "Παίχτης 1",

View File

@@ -168,6 +168,8 @@
"SettingsTabInputDirectKeyboardAccess": "Direct Keyboard Access", "SettingsTabInputDirectKeyboardAccess": "Direct Keyboard Access",
"SettingsButtonSave": "Save", "SettingsButtonSave": "Save",
"SettingsButtonClose": "Close", "SettingsButtonClose": "Close",
"SettingsButtonOk": "OK",
"SettingsButtonCancel": "Cancel",
"SettingsButtonApply": "Apply", "SettingsButtonApply": "Apply",
"ControllerSettingsPlayer": "Player", "ControllerSettingsPlayer": "Player",
"ControllerSettingsPlayer1": "Player 1", "ControllerSettingsPlayer1": "Player 1",
@@ -594,7 +596,18 @@
"RyujinxUpdaterMessage": "Do you want to update Ryujinx to the latest version?", "RyujinxUpdaterMessage": "Do you want to update Ryujinx to the latest version?",
"SettingsTabHotkeysVolumeUpHotkey": "Increase Volume:", "SettingsTabHotkeysVolumeUpHotkey": "Increase Volume:",
"SettingsTabHotkeysVolumeDownHotkey": "Decrease Volume:", "SettingsTabHotkeysVolumeDownHotkey": "Decrease Volume:",
"VolumeShort": "Vol",
"SettingsEnableMacroHLE": "Enable Macro HLE", "SettingsEnableMacroHLE": "Enable Macro HLE",
"SettingsEnableMacroHLETooltip": "High-level emulation of GPU Macro code.\n\nImproves performance, but may cause graphical glitches in some games.\n\nLeave ON if unsure." "SettingsEnableMacroHLETooltip": "High-level emulation of GPU Macro code.\n\nImproves performance, but may cause graphical glitches in some games.\n\nLeave ON if unsure.",
"VolumeShort": "Vol",
"UserProfilesManageSaves": "Manage Saves",
"DeleteUserSave": "Do you want to delete user save for this game?",
"IrreversibleActionNote": "This action is not reversible.",
"SaveManagerHeading": "Manage Saves for {0}",
"SaveManagerTitle": "Save Manager",
"Name": "Name",
"Size": "Size",
"Search": "Search",
"UserProfilesRecoverLostAccounts": "Recover Lost Accounts",
"Recover": "Recover",
"UserProfilesRecoverHeading" : "Saves were found for the following accounts"
} }

View File

@@ -168,6 +168,8 @@
"SettingsTabInputDirectKeyboardAccess": "Acceso directo al teclado", "SettingsTabInputDirectKeyboardAccess": "Acceso directo al teclado",
"SettingsButtonSave": "Guardar", "SettingsButtonSave": "Guardar",
"SettingsButtonClose": "Cerrar", "SettingsButtonClose": "Cerrar",
"SettingsButtonOk": "OK",
"SettingsButtonCancel": "Cancelar",
"SettingsButtonApply": "Aplicar", "SettingsButtonApply": "Aplicar",
"ControllerSettingsPlayer": "Jugador", "ControllerSettingsPlayer": "Jugador",
"ControllerSettingsPlayer1": "Jugador 1", "ControllerSettingsPlayer1": "Jugador 1",

View File

@@ -161,6 +161,8 @@
"SettingsTabInputDirectKeyboardAccess": "Accès direct au clavier", "SettingsTabInputDirectKeyboardAccess": "Accès direct au clavier",
"SettingsButtonSave": "Enregistrer", "SettingsButtonSave": "Enregistrer",
"SettingsButtonClose": "Fermer", "SettingsButtonClose": "Fermer",
"SettingsButtonOk": "OK",
"SettingsButtonCancel": "Annuler",
"SettingsButtonApply": "Appliquer", "SettingsButtonApply": "Appliquer",
"ControllerSettingsPlayer": "Joueur", "ControllerSettingsPlayer": "Joueur",
"ControllerSettingsPlayer1": "Joueur 1", "ControllerSettingsPlayer1": "Joueur 1",

View File

@@ -168,6 +168,8 @@
"SettingsTabInputDirectKeyboardAccess": "Accesso diretto alla tastiera", "SettingsTabInputDirectKeyboardAccess": "Accesso diretto alla tastiera",
"SettingsButtonSave": "Salva", "SettingsButtonSave": "Salva",
"SettingsButtonClose": "Chiudi", "SettingsButtonClose": "Chiudi",
"SettingsButtonOk": "OK",
"SettingsButtonCancel": "Cancella",
"SettingsButtonApply": "Applica", "SettingsButtonApply": "Applica",
"ControllerSettingsPlayer": "Giocatore", "ControllerSettingsPlayer": "Giocatore",
"ControllerSettingsPlayer1": "Giocatore 1", "ControllerSettingsPlayer1": "Giocatore 1",

View File

@@ -168,6 +168,8 @@
"SettingsTabInputDirectKeyboardAccess": "キーボード直接アクセス", "SettingsTabInputDirectKeyboardAccess": "キーボード直接アクセス",
"SettingsButtonSave": "セーブ", "SettingsButtonSave": "セーブ",
"SettingsButtonClose": "閉じる", "SettingsButtonClose": "閉じる",
"SettingsButtonOk": "オーケー",
"SettingsButtonCancel": "キャンセル",
"SettingsButtonApply": "適用", "SettingsButtonApply": "適用",
"ControllerSettingsPlayer": "プレイヤー", "ControllerSettingsPlayer": "プレイヤー",
"ControllerSettingsPlayer1": "プレイヤー 1", "ControllerSettingsPlayer1": "プレイヤー 1",

View File

@@ -167,6 +167,8 @@
"SettingsTabInputDirectKeyboardAccess": "직접 키보드 액세스", "SettingsTabInputDirectKeyboardAccess": "직접 키보드 액세스",
"SettingsButtonSave": "구하다", "SettingsButtonSave": "구하다",
"SettingsButtonClose": "출구", "SettingsButtonClose": "출구",
"SettingsButtonOk": "좋아",
"SettingsButtonCancel": "취소",
"SettingsButtonApply": "적용하다", "SettingsButtonApply": "적용하다",
"ControllerSettingsPlayer": "플레이어", "ControllerSettingsPlayer": "플레이어",
"ControllerSettingsPlayer1": "플레이어 1", "ControllerSettingsPlayer1": "플레이어 1",

View File

@@ -168,6 +168,8 @@
"SettingsTabInputDirectKeyboardAccess": "Bezpośredni Dostęp do Klawiatury", "SettingsTabInputDirectKeyboardAccess": "Bezpośredni Dostęp do Klawiatury",
"SettingsButtonSave": "Zapisz", "SettingsButtonSave": "Zapisz",
"SettingsButtonClose": "Zamknij", "SettingsButtonClose": "Zamknij",
"SettingsButtonOk": "OK",
"SettingsButtonCancel": "Anuluj",
"SettingsButtonApply": "Zastosuj", "SettingsButtonApply": "Zastosuj",
"ControllerSettingsPlayer": "Gracz", "ControllerSettingsPlayer": "Gracz",
"ControllerSettingsPlayer1": "Gracz 1", "ControllerSettingsPlayer1": "Gracz 1",

View File

@@ -168,6 +168,8 @@
"SettingsTabInputDirectKeyboardAccess": "Acesso direto ao teclado", "SettingsTabInputDirectKeyboardAccess": "Acesso direto ao teclado",
"SettingsButtonSave": "Salvar", "SettingsButtonSave": "Salvar",
"SettingsButtonClose": "Fechar", "SettingsButtonClose": "Fechar",
"SettingsButtonOk": "OK",
"SettingsButtonCancel": "Cancelar",
"SettingsButtonApply": "Aplicar", "SettingsButtonApply": "Aplicar",
"ControllerSettingsPlayer": "Jogador", "ControllerSettingsPlayer": "Jogador",
"ControllerSettingsPlayer1": "Jogador 1", "ControllerSettingsPlayer1": "Jogador 1",

View File

@@ -167,6 +167,8 @@
"SettingsTabInputDirectKeyboardAccess": "Прямой доступ с клавиатуры", "SettingsTabInputDirectKeyboardAccess": "Прямой доступ с клавиатуры",
"SettingsButtonSave": "Сохранить", "SettingsButtonSave": "Сохранить",
"SettingsButtonClose": "Закрыть", "SettingsButtonClose": "Закрыть",
"SettingsButtonOk": "OK",
"SettingsButtonCancel": "Отмена",
"SettingsButtonApply": "Применить", "SettingsButtonApply": "Применить",
"ControllerSettingsPlayer": "Игрок", "ControllerSettingsPlayer": "Игрок",
"ControllerSettingsPlayer1": "Игрок 1", "ControllerSettingsPlayer1": "Игрок 1",

View File

@@ -168,6 +168,8 @@
"SettingsTabInputDirectKeyboardAccess": "Doğrudan Klavye Erişimi", "SettingsTabInputDirectKeyboardAccess": "Doğrudan Klavye Erişimi",
"SettingsButtonSave": "Kaydet", "SettingsButtonSave": "Kaydet",
"SettingsButtonClose": "Kapat", "SettingsButtonClose": "Kapat",
"SettingsButtonOk": "Tamam",
"SettingsButtonCancel": "İptal",
"SettingsButtonApply": "Uygula", "SettingsButtonApply": "Uygula",
"ControllerSettingsPlayer": "Oyuncu", "ControllerSettingsPlayer": "Oyuncu",
"ControllerSettingsPlayer1": "Oyuncu 1", "ControllerSettingsPlayer1": "Oyuncu 1",

View File

@@ -168,6 +168,8 @@
"SettingsTabInputDirectKeyboardAccess": "直通键盘控制", "SettingsTabInputDirectKeyboardAccess": "直通键盘控制",
"SettingsButtonSave": "保存", "SettingsButtonSave": "保存",
"SettingsButtonClose": "关闭", "SettingsButtonClose": "关闭",
"SettingsButtonOk": "批准",
"SettingsButtonCancel": "取消",
"SettingsButtonApply": "应用", "SettingsButtonApply": "应用",
"ControllerSettingsPlayer": "玩家", "ControllerSettingsPlayer": "玩家",
"ControllerSettingsPlayer1": "玩家 1", "ControllerSettingsPlayer1": "玩家 1",

View File

@@ -168,6 +168,8 @@
"SettingsTabInputDirectKeyboardAccess": "直通鍵盤控制", "SettingsTabInputDirectKeyboardAccess": "直通鍵盤控制",
"SettingsButtonSave": "儲存", "SettingsButtonSave": "儲存",
"SettingsButtonClose": "關閉", "SettingsButtonClose": "關閉",
"SettingsButtonOk": "嘛好",
"SettingsButtonCancel": "取消",
"SettingsButtonApply": "套用", "SettingsButtonApply": "套用",
"ControllerSettingsPlayer": "玩家", "ControllerSettingsPlayer": "玩家",
"ControllerSettingsPlayer1": "玩家 1", "ControllerSettingsPlayer1": "玩家 1",

View File

@@ -41,6 +41,9 @@
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush" Color="{DynamicResource DataGridSelectionColor}" /> <SolidColorBrush x:Key="DataGridSelectionBackgroundBrush" Color="{DynamicResource DataGridSelectionColor}" />
<SolidColorBrush x:Key="ThemeAccentColorBrush" Color="{DynamicResource SystemAccentColor}" /> <SolidColorBrush x:Key="ThemeAccentColorBrush" Color="{DynamicResource SystemAccentColor}" />
<SolidColorBrush x:Key="ThemeAccentBrush4" Color="{DynamicResource ThemeAccentColor4}" /> <SolidColorBrush x:Key="ThemeAccentBrush4" Color="{DynamicResource ThemeAccentColor4}" />
<Color x:Key="ControlFillColorSecondary">#008AA8</Color>
<SolidColorBrush x:Key="ControlFillColorSecondaryBrush" Color="{StaticResource ControlFillColorSecondary}" />
<StaticResource x:Key="ButtonBackgroundPointerOver" ResourceKey="ControlFillColorSecondaryBrush" />
<Color x:Key="SystemAccentColor">#FF00C3E3</Color> <Color x:Key="SystemAccentColor">#FF00C3E3</Color>
<Color x:Key="SystemAccentColorDark1">#FF99b000</Color> <Color x:Key="SystemAccentColorDark1">#FF99b000</Color>
<Color x:Key="SystemAccentColorDark2">#FF006d7d</Color> <Color x:Key="SystemAccentColorDark2">#FF006d7d</Color>

View File

@@ -1,7 +1,6 @@
<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:sys="clr-namespace:System;assembly=netstandard"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"> xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia">
<Design.PreviewWith> <Design.PreviewWith>
<Border Height="2000" Padding="20"> <Border Height="2000" Padding="20">
@@ -269,13 +268,15 @@
<Color x:Key="DataGridSelectionColor">#FF00FABB</Color> <Color x:Key="DataGridSelectionColor">#FF00FABB</Color>
<Color x:Key="ThemeContentBackgroundColor">#FF2D2D2D</Color> <Color x:Key="ThemeContentBackgroundColor">#FF2D2D2D</Color>
<Color x:Key="ThemeControlBorderColor">#FF505050</Color> <Color x:Key="ThemeControlBorderColor">#FF505050</Color>
<sys:Double x:Key="ScrollBarThickness">15</sys:Double> <x:Double x:Key="ScrollBarThickness">15</x:Double>
<sys:Double x:Key="FontSizeSmall">8</sys:Double> <x:Double x:Key="FontSizeSmall">8</x:Double>
<sys:Double x:Key="FontSizeNormal">10</sys:Double> <x:Double x:Key="FontSizeNormal">10</x:Double>
<sys:Double x:Key="FontSize">12</sys:Double> <x:Double x:Key="FontSize">12</x:Double>
<sys:Double x:Key="FontSizeLarge">15</sys:Double> <x:Double x:Key="FontSizeLarge">15</x:Double>
<sys:Double x:Key="ControlContentThemeFontSize">13</sys:Double> <x:Double x:Key="ControlContentThemeFontSize">13</x:Double>
<x:Double x:Key="MenuItemHeight">26</x:Double> <x:Double x:Key="MenuItemHeight">26</x:Double>
<x:Double x:Key="TabItemMinHeight">28</x:Double> <x:Double x:Key="TabItemMinHeight">28</x:Double>
<x:Double x:Key="ContentDialogMaxWidth">600</x:Double>
<x:Double x:Key="ContentDialogMaxHeight">756</x:Double>
</Styles.Resources> </Styles.Resources>
</Styles> </Styles>

View File

@@ -113,6 +113,11 @@ namespace Ryujinx.Ava.Common
return; return;
} }
OpenSaveDir(saveDataId);
}
public static void OpenSaveDir(ulong saveDataId)
{
string saveRootPath = Path.Combine(_virtualFileSystem.GetNandPath(), $"user/save/{saveDataId:x16}"); string saveRootPath = Path.Combine(_virtualFileSystem.GetNandPath(), $"user/save/{saveDataId:x16}");
if (!Directory.Exists(saveRootPath)) if (!Directory.Exists(saveRootPath))

View File

@@ -1,8 +1,5 @@
using ARMeilleure.Translation.PTC; using ARMeilleure.Translation.PTC;
using Avalonia; using Avalonia;
using Avalonia.Rendering;
using Avalonia.Threading;
using Ryujinx.Ava.Ui.Controls;
using Ryujinx.Ava.Ui.Windows; using Ryujinx.Ava.Ui.Windows;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
@@ -24,17 +21,14 @@ namespace Ryujinx.Ava
internal class Program internal class Program
{ {
public static double WindowScaleFactor { get; set; } public static double WindowScaleFactor { get; set; }
public static double ActualScaleFactor { get; set; }
public static string Version { get; private set; } public static string Version { get; private set; }
public static string ConfigurationPath { get; private set; } public static string ConfigurationPath { get; private set; }
public static bool PreviewerDetached { get; private set; } public static bool PreviewerDetached { get; private set; }
public static RenderTimer RenderTimer { get; private set; }
[DllImport("user32.dll", SetLastError = true)] [DllImport("user32.dll", SetLastError = true)]
public static extern int MessageBoxA(IntPtr hWnd, string text, string caption, uint type); public static extern int MessageBoxA(IntPtr hWnd, string text, string caption, uint type);
private const uint MB_ICONWARNING = 0x30; private const uint MB_ICONWARNING = 0x30;
private const int BaseDpi = 96;
public static void Main(string[] args) public static void Main(string[] args)
{ {
@@ -49,11 +43,7 @@ namespace Ryujinx.Ava
Initialize(args); Initialize(args);
RenderTimer = new RenderTimer();
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
RenderTimer.Dispose();
} }
public static AppBuilder BuildAvaloniaApp() public static AppBuilder BuildAvaloniaApp()
@@ -65,7 +55,7 @@ namespace Ryujinx.Ava
EnableMultiTouch = true, EnableMultiTouch = true,
EnableIme = true, EnableIme = true,
UseEGL = false, UseEGL = false,
UseGpu = false UseGpu = true
}) })
.With(new Win32PlatformOptions .With(new Win32PlatformOptions
{ {
@@ -75,12 +65,6 @@ namespace Ryujinx.Ava
CompositionBackdropCornerRadius = 8.0f, CompositionBackdropCornerRadius = 8.0f,
}) })
.UseSkia() .UseSkia()
.AfterSetup(_ =>
{
AvaloniaLocator.CurrentMutable
.Bind<IRenderTimer>().ToConstant(RenderTimer)
.Bind<IRenderLoop>().ToConstant(new RenderLoop(RenderTimer, Dispatcher.UIThread));
})
.LogToTrace(); .LogToTrace();
} }
@@ -115,7 +99,6 @@ namespace Ryujinx.Ava
ForceDpiAware.Windows(); ForceDpiAware.Windows();
WindowScaleFactor = ForceDpiAware.GetWindowScaleFactor(); WindowScaleFactor = ForceDpiAware.GetWindowScaleFactor();
ActualScaleFactor = ForceDpiAware.GetActualScaleFactor() / BaseDpi;
// Logging system information. // Logging system information.
PrintSystemInfo(); PrintSystemInfo();

View File

@@ -33,9 +33,9 @@
<PackageReference Include="OpenTK.Core" Version="4.7.5" /> <PackageReference Include="OpenTK.Core" Version="4.7.5" />
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" /> <PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" /> <PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
<PackageReference Include="Silk.NET.Vulkan" Version="2.10.1" /> <PackageReference Include="Silk.NET.Vulkan" Version="2.16.0" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.10.1" /> <PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.10.1" /> <PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />
<PackageReference Include="SPB" Version="0.0.4-build28" /> <PackageReference Include="SPB" Version="0.0.4-build28" />
<PackageReference Include="SharpZipLib" Version="1.4.1" /> <PackageReference Include="SharpZipLib" Version="1.4.1" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" /> <PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />

View File

@@ -1,6 +1,7 @@
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using LibHac;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.ViewModels; using Ryujinx.Ava.Ui.ViewModels;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
@@ -14,6 +15,8 @@ namespace Ryujinx.Ava.Ui.Controls
{ {
public AccountManager AccountManager { get; } public AccountManager AccountManager { get; }
public ContentManager ContentManager { get; } public ContentManager ContentManager { get; }
public VirtualFileSystem VirtualFileSystem { get; }
public HorizonClient HorizonClient { get; }
public UserProfileViewModel ViewModel { get; set; } public UserProfileViewModel ViewModel { get; set; }
public NavigationDialogHost() public NavigationDialogHost()
@@ -22,10 +25,12 @@ namespace Ryujinx.Ava.Ui.Controls
} }
public NavigationDialogHost(AccountManager accountManager, ContentManager contentManager, public NavigationDialogHost(AccountManager accountManager, ContentManager contentManager,
VirtualFileSystem virtualFileSystem) VirtualFileSystem virtualFileSystem, HorizonClient horizonClient)
{ {
AccountManager = accountManager; AccountManager = accountManager;
ContentManager = contentManager; ContentManager = contentManager;
VirtualFileSystem = virtualFileSystem;
HorizonClient = horizonClient;
ViewModel = new UserProfileViewModel(this); ViewModel = new UserProfileViewModel(this);
@@ -54,9 +59,10 @@ namespace Ryujinx.Ava.Ui.Controls
ContentFrame.Navigate(sourcePageType, parameter); ContentFrame.Navigate(sourcePageType, parameter);
} }
public static async Task Show(AccountManager ownerAccountManager, ContentManager ownerContentManager, VirtualFileSystem ownerVirtualFileSystem) public static async Task Show(AccountManager ownerAccountManager, ContentManager ownerContentManager,
VirtualFileSystem ownerVirtualFileSystem, HorizonClient ownerHorizonClient)
{ {
var content = new NavigationDialogHost(ownerAccountManager, ownerContentManager, ownerVirtualFileSystem); var content = new NavigationDialogHost(ownerAccountManager, ownerContentManager, ownerVirtualFileSystem, ownerHorizonClient);
ContentDialog contentDialog = new ContentDialog ContentDialog contentDialog = new ContentDialog
{ {
Title = LocaleManager.Instance["UserProfileWindowTitle"], Title = LocaleManager.Instance["UserProfileWindowTitle"],

View File

@@ -1,100 +0,0 @@
using Avalonia.Rendering;
using System;
using System.Threading;
using System.Timers;
namespace Ryujinx.Ava.Ui.Controls
{
internal class RenderTimer : IRenderTimer, IDisposable
{
public event Action<TimeSpan> Tick
{
add
{
_tick += value;
if (_subscriberCount++ == 0)
{
Start();
}
}
remove
{
if (--_subscriberCount == 0)
{
Stop();
}
_tick -= value;
}
}
private Thread _tickThread;
private readonly System.Timers.Timer _timer;
private Action<TimeSpan> _tick;
private int _subscriberCount;
private bool _isRunning;
private AutoResetEvent _resetEvent;
public RenderTimer()
{
_timer = new System.Timers.Timer(15);
_resetEvent = new AutoResetEvent(true);
_timer.Elapsed += Timer_Elapsed;
}
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
TickNow();
}
public void Start()
{
_timer.Start();
if (_tickThread == null)
{
_tickThread = new Thread(RunTick);
_tickThread.Name = "RenderTimerTickThread";
_tickThread.IsBackground = true;
_isRunning = true;
_tickThread.Start();
}
}
public void RunTick()
{
while (_isRunning)
{
_resetEvent.WaitOne();
_tick?.Invoke(TimeSpan.FromMilliseconds(Environment.TickCount));
}
}
public void TickNow()
{
lock (_timer)
{
_resetEvent.Set();
}
}
public void Stop()
{
_timer.Stop();
}
public void Dispose()
{
_timer.Elapsed -= Timer_Elapsed;
_timer.Stop();
_isRunning = false;
_resetEvent.Set();
_tickThread.Join();
_resetEvent.Dispose();
}
}
}

View File

@@ -4,11 +4,4 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Ryujinx.Ava.Ui.Controls.RendererHost"> x:Class="Ryujinx.Ava.Ui.Controls.RendererHost">
<ContentControl
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalAlignment="Stretch"
VerticalContentAlignment="Stretch"
Name="View"
/>
</UserControl> </UserControl>

View File

@@ -41,7 +41,7 @@ namespace Ryujinx.Ava.Ui.Controls
{ {
_currentWindow.WindowCreated += CurrentWindow_WindowCreated; _currentWindow.WindowCreated += CurrentWindow_WindowCreated;
_currentWindow.SizeChanged += CurrentWindow_SizeChanged; _currentWindow.SizeChanged += CurrentWindow_SizeChanged;
View.Content = _currentWindow; Content = _currentWindow;
} }
public void CreateVulkan() public void CreateVulkan()

View File

@@ -0,0 +1,102 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls"
xmlns:models="clr-namespace:Ryujinx.Ava.Ui.Models"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
Height="400"
Width="550"
x:Class="Ryujinx.Ava.Ui.Controls.SaveManager">
<UserControl.Resources>
<controls:BitmapArrayValueConverter x:Key="ByteImage" />
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid Grid.Row="0" HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel Spacing="10" Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Center">
<Label Content="{locale:Locale CommonSort}" VerticalAlignment="Center" />
<ComboBox SelectedIndex="{Binding SortIndex}" Width="100">
<ComboBoxItem>
<Label VerticalAlignment="Center" HorizontalContentAlignment="Left"
Content="{locale:Locale Name}" />
</ComboBoxItem>
<ComboBoxItem>
<Label VerticalAlignment="Center" HorizontalContentAlignment="Left"
Content="{locale:Locale Size}" />
</ComboBoxItem>
</ComboBox>
<ComboBox SelectedIndex="{Binding OrderIndex}" Width="150">
<ComboBoxItem>
<Label VerticalAlignment="Center" HorizontalContentAlignment="Left"
Content="{locale:Locale OrderAscending}" />
</ComboBoxItem>
<ComboBoxItem>
<Label VerticalAlignment="Center" HorizontalContentAlignment="Left"
Content="{locale:Locale Descending}" />
</ComboBoxItem>
</ComboBox>
</StackPanel>
<Grid Grid.Column="1" HorizontalAlignment="Stretch" Margin="10,0, 0, 0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Content="{locale:Locale Search}" VerticalAlignment="Center"/>
<TextBox Margin="5,0,0,0" Grid.Column="1" HorizontalAlignment="Stretch" Text="{Binding Search}"/>
</Grid>
</Grid>
<Border Grid.Row="1" Margin="0,5" BorderThickness="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<ListBox Name="SaveList" Items="{Binding View}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="models:SaveModel">
<Grid HorizontalAlignment="Stretch" Margin="0,5">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Orientation="Horizontal">
<Border Height="42" Margin="2" Width="42" Padding="10"
IsVisible="{Binding !InGameList}">
<ui:SymbolIcon Symbol="Help" FontSize="30" HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Border>
<Image IsVisible="{Binding InGameList}"
Margin="2"
Width="42"
Height="42"
Source="{Binding Icon, Converter={StaticResource ByteImage}}" />
<TextBlock MaxLines="3" Width="320" Margin="5" TextWrapping="Wrap"
Text="{Binding Title}" VerticalAlignment="Center" />
</StackPanel>
<StackPanel Grid.Column="1" Spacing="10" HorizontalAlignment="Right"
Orientation="Horizontal">
<Label Content="{Binding SizeString}" IsVisible="{Binding SizeAvailable}"
VerticalAlignment="Center" HorizontalAlignment="Right" />
<Button VerticalAlignment="Center" HorizontalAlignment="Right" Padding="10"
MinWidth="0" MinHeight="0" Name="OpenLocation" Command="{Binding OpenLocation}">
<ui:SymbolIcon Symbol="OpenFolder" HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Button>
<Button VerticalAlignment="Center" HorizontalAlignment="Right" Padding="10"
MinWidth="0" MinHeight="0" Name="Delete" Command="{Binding Delete}">
<ui:SymbolIcon Symbol="Delete" HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Button>
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Border>
</Grid>
</UserControl>

View File

@@ -0,0 +1,160 @@
using Avalonia.Controls;
using DynamicData;
using DynamicData.Binding;
using LibHac;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Shim;
using Ryujinx.Ava.Common;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.Models;
using Ryujinx.HLE.FileSystem;
using Ryujinx.Ui.App.Common;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using UserProfile = Ryujinx.Ava.Ui.Models.UserProfile;
namespace Ryujinx.Ava.Ui.Controls
{
public partial class SaveManager : UserControl
{
private readonly UserProfile _userProfile;
private readonly HorizonClient _horizonClient;
private readonly VirtualFileSystem _virtualFileSystem;
private int _sortIndex;
private int _orderIndex;
private ObservableCollection<SaveModel> _view = new ObservableCollection<SaveModel>();
private string _search;
public ObservableCollection<SaveModel> Saves { get; set; } = new ObservableCollection<SaveModel>();
public ObservableCollection<SaveModel> View
{
get => _view;
set => _view = value;
}
public int SortIndex
{
get => _sortIndex;
set
{
_sortIndex = value;
Sort();
}
}
public int OrderIndex
{
get => _orderIndex;
set
{
_orderIndex = value;
Sort();
}
}
public string Search
{
get => _search;
set
{
_search = value;
Sort();
}
}
public SaveManager()
{
InitializeComponent();
}
public SaveManager(UserProfile userProfile, HorizonClient horizonClient, VirtualFileSystem virtualFileSystem)
{
_userProfile = userProfile;
_horizonClient = horizonClient;
_virtualFileSystem = virtualFileSystem;
InitializeComponent();
DataContext = this;
Task.Run(LoadSaves);
}
public void LoadSaves()
{
Saves.Clear();
var saveDataFilter = SaveDataFilter.Make(programId: default, saveType: SaveDataType.Account,
new UserId((ulong)_userProfile.UserId.High, (ulong)_userProfile.UserId.Low), saveDataId: default, index: default);
using var saveDataIterator = new UniqueRef<SaveDataIterator>();
_horizonClient.Fs.OpenSaveDataIterator(ref saveDataIterator.Ref(), SaveDataSpaceId.User, in saveDataFilter).ThrowIfFailure();
Span<SaveDataInfo> saveDataInfo = stackalloc SaveDataInfo[10];
while (true)
{
saveDataIterator.Get.ReadSaveDataInfo(out long readCount, saveDataInfo).ThrowIfFailure();
if (readCount == 0)
{
break;
}
for (int i = 0; i < readCount; i++)
{
var save = saveDataInfo[i];
if (save.ProgramId.Value != 0)
{
var saveModel = new SaveModel(save, _horizonClient, _virtualFileSystem);
Saves.Add(saveModel);
saveModel.DeleteAction = () => { Saves.Remove(saveModel); };
}
Sort();
}
}
}
private void Sort()
{
Saves.AsObservableChangeSet()
.Filter(Filter)
.Sort(GetComparer())
.Bind(out var view).AsObservableList();
_view.Clear();
_view.AddRange(view);
}
private IComparer<SaveModel> GetComparer()
{
switch (SortIndex)
{
case 0:
return OrderIndex == 0
? SortExpressionComparer<SaveModel>.Ascending(save => save.Title)
: SortExpressionComparer<SaveModel>.Descending(save => save.Title);
case 1:
return OrderIndex == 0
? SortExpressionComparer<SaveModel>.Ascending(save => save.Size)
: SortExpressionComparer<SaveModel>.Descending(save => save.Size);
default:
return null;
}
}
private bool Filter(object arg)
{
if (arg is SaveModel save)
{
return string.IsNullOrWhiteSpace(_search) || save.Title.ToLower().Contains(_search.ToLower());
}
return false;
}
}
}

View File

@@ -10,6 +10,7 @@
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" 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"
Margin="0" Margin="0"
MinWidth="500"
Padding="0" Padding="0"
mc:Ignorable="d"> mc:Ignorable="d">
<UserControl.Resources> <UserControl.Resources>
@@ -63,7 +64,7 @@
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
MaxLength="{Binding MaxProfileNameLength}" MaxLength="{Binding MaxProfileNameLength}"
Text="{Binding Name}" /> Text="{Binding Name}" />
<TextBlock Text="{Locale:Locale UserProfilesUserId}" /> <TextBlock Name="IdText" Text="{Locale:Locale UserProfilesUserId}" />
<TextBlock Name="IdLabel" Text="{Binding UserId}" /> <TextBlock Name="IdLabel" Text="{Binding UserId}" />
</StackPanel> </StackPanel>
<StackPanel <StackPanel

View File

@@ -36,15 +36,8 @@ namespace Ryujinx.Ava.Ui.Controls
case NavigationMode.New: case NavigationMode.New:
var args = ((NavigationDialogHost parent, UserProfile profile, bool isNewUser))arg.Parameter; var args = ((NavigationDialogHost parent, UserProfile profile, bool isNewUser))arg.Parameter;
_isNewUser = args.isNewUser; _isNewUser = args.isNewUser;
if (!_isNewUser)
{
_profile = args.profile; _profile = args.profile;
TempProfile = new TempProfile(_profile); TempProfile = new TempProfile(_profile);
}
else
{
TempProfile = new TempProfile();
}
_parent = args.parent; _parent = args.parent;
break; break;
@@ -53,7 +46,8 @@ namespace Ryujinx.Ava.Ui.Controls
DataContext = TempProfile; DataContext = TempProfile;
AddPictureButton.IsVisible = _isNewUser; AddPictureButton.IsVisible = _isNewUser;
IdLabel.IsVisible = !_isNewUser; IdLabel.IsVisible = _profile != null;
IdText.IsVisible = _profile != null;
ChangePictureButton.IsVisible = !_isNewUser; ChangePictureButton.IsVisible = !_isNewUser;
} }
} }
@@ -87,7 +81,7 @@ namespace Ryujinx.Ava.Ui.Controls
return; return;
} }
if (_profile != null) if (_profile != null && !_isNewUser)
{ {
_profile.Name = TempProfile.Name; _profile.Name = TempProfile.Name;
_profile.Image = TempProfile.Image; _profile.Image = TempProfile.Image;
@@ -97,7 +91,7 @@ namespace Ryujinx.Ava.Ui.Controls
} }
else if (_isNewUser) else if (_isNewUser)
{ {
_parent.AccountManager.AddUser(TempProfile.Name, TempProfile.Image); _parent.AccountManager.AddUser(TempProfile.Name, TempProfile.Image, TempProfile.UserId);
} }
else else
{ {

View File

@@ -0,0 +1,70 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignWidth="800"
d:DesignHeight="450"
MinWidth="500"
MinHeight="400"
xmlns:Locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
x:Class="Ryujinx.Ava.Ui.Controls.UserRecoverer">
<Design.DataContext>
<viewModels:UserProfileViewModel />
</Design.DataContext>
<Grid HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Grid.Row="0"
Margin="5"
Height="30"
Width="50"
MinWidth="50"
HorizontalAlignment="Left"
Command="{Binding GoBack}">
<ui:SymbolIcon Symbol="Back"/>
</Button>
<TextBlock Grid.Row="1"
Text="{Locale:Locale UserProfilesRecoverHeading}"/>
<ListBox
Margin="5"
Grid.Row="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Items="{Binding LostProfiles}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border
Margin="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ClipToBounds="True"
CornerRadius="5">
<Grid Margin="0">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock
HorizontalAlignment="Stretch"
Text="{Binding UserId}"
TextAlignment="Left"
TextWrapping="Wrap" />
<Button Grid.Column="1"
HorizontalAlignment="Right"
Command="{Binding Recover}"
CommandParameter="{Binding}"
Content="{Locale:Locale Recover}"/>
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</UserControl>

View File

@@ -0,0 +1,44 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Navigation;
using Ryujinx.Ava.Ui.Models;
using Ryujinx.Ava.Ui.ViewModels;
namespace Ryujinx.Ava.Ui.Controls
{
public partial class UserRecoverer : UserControl
{
private UserProfileViewModel _viewModel;
private NavigationDialogHost _parent;
public UserRecoverer()
{
InitializeComponent();
AddHandler(Frame.NavigatedToEvent, (s, e) =>
{
NavigatedTo(e);
}, RoutingStrategies.Direct);
}
private void NavigatedTo(NavigationEventArgs arg)
{
if (Program.PreviewerDetached)
{
switch (arg.NavigationMode)
{
case NavigationMode.New:
var args = ((NavigationDialogHost parent, UserProfileViewModel viewModel))arg.Parameter;
_viewModel = args.viewModel;
_parent = args.parent;
break;
}
DataContext = _viewModel;
}
}
}
}

View File

@@ -10,6 +10,7 @@
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" 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"
d:DesignHeight="450" d:DesignHeight="450"
MinWidth="500"
d:DesignWidth="800" d:DesignWidth="800"
mc:Ignorable="d"> mc:Ignorable="d">
<UserControl.Resources> <UserControl.Resources>
@@ -25,6 +26,7 @@
</Grid.RowDefinitions> </Grid.RowDefinitions>
<ListBox <ListBox
Margin="5" Margin="5"
MaxHeight="300"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Center" VerticalAlignment="Center"
DoubleTapped="ProfilesList_DoubleTapped" DoubleTapped="ProfilesList_DoubleTapped"
@@ -88,21 +90,56 @@
</DataTemplate> </DataTemplate>
</ListBox.ItemTemplate> </ListBox.ItemTemplate>
</ListBox> </ListBox>
<StackPanel <Grid
Grid.Row="1" Grid.Row="1"
Margin="10,0" HorizontalAlignment="Center">
HorizontalAlignment="Center" <Grid.RowDefinitions>
Orientation="Horizontal" <RowDefinition Height="Auto"/>
Spacing="10"> <RowDefinition Height="Auto"/>
<Button Command="{Binding AddUser}" Content="{Locale:Locale UserProfilesAddNewProfile}" /> <RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Button <Button
HorizontalAlignment="Stretch"
Grid.Row="0"
Grid.Column="0"
Margin="2"
Command="{Binding AddUser}"
Content="{Locale:Locale UserProfilesAddNewProfile}" />
<Button
HorizontalAlignment="Stretch"
Grid.Row="0"
Margin="2"
Grid.Column="1"
Command="{Binding EditUser}" Command="{Binding EditUser}"
Content="{Locale:Locale UserProfilesEditProfile}" Content="{Locale:Locale UserProfilesEditProfile}"
IsEnabled="{Binding IsSelectedProfiledEditable}" /> IsEnabled="{Binding IsSelectedProfiledEditable}" />
<Button <Button
HorizontalAlignment="Stretch"
Grid.Row="1"
Grid.Column="0"
Margin="2"
Content="{Locale:Locale UserProfilesManageSaves}"
Command="{Binding ManageSaves}" />
<Button
HorizontalAlignment="Stretch"
Grid.Row="1"
Grid.Column="1"
Margin="2"
Command="{Binding DeleteUser}" Command="{Binding DeleteUser}"
Content="{Locale:Locale UserProfilesDeleteSelectedProfile}" Content="{Locale:Locale UserProfilesDeleteSelectedProfile}"
IsEnabled="{Binding IsSelectedProfileDeletable}" /> IsEnabled="{Binding IsSelectedProfileDeletable}" />
</StackPanel> <Button
HorizontalAlignment="Stretch"
Grid.Row="2"
Grid.ColumnSpan="2"
Grid.Column="0"
Margin="2"
Command="{Binding RecoverLostAccounts}"
Content="{Locale:Locale UserProfilesRecoverLostAccounts}" />
</Grid>
</Grid> </Grid>
</UserControl> </UserControl>

View File

@@ -0,0 +1,122 @@
using LibHac;
using LibHac.Fs;
using LibHac.Fs.Shim;
using LibHac.Ncm;
using Ryujinx.Ava.Common;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.Controls;
using Ryujinx.Ava.Ui.ViewModels;
using Ryujinx.Ava.Ui.Windows;
using Ryujinx.HLE.FileSystem;
using Ryujinx.Ui.App.Common;
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace Ryujinx.Ava.Ui.Models
{
public class SaveModel : BaseModel
{
private readonly HorizonClient _horizonClient;
private long _size;
public Action DeleteAction { get; set; }
public ulong SaveId { get; }
public ProgramId TitleId { get; }
public string TitleIdString => $"{TitleId.Value:X16}";
public UserId UserId { get; }
public bool InGameList { get; }
public string Title { get; }
public byte[] Icon { get; }
public long Size
{
get => _size; set
{
_size = value;
SizeAvailable = true;
OnPropertyChanged();
OnPropertyChanged(nameof(SizeString));
OnPropertyChanged(nameof(SizeAvailable));
}
}
public bool SizeAvailable { get; set; }
public string SizeString => $"{((float)_size * 0.000000954):0.###}MB";
public SaveModel(SaveDataInfo info, HorizonClient horizonClient, VirtualFileSystem virtualFileSystem)
{
_horizonClient = horizonClient;
SaveId = info.SaveDataId;
TitleId = info.ProgramId;
UserId = info.UserId;
var appData = MainWindow.MainWindowViewModel.Applications.FirstOrDefault(x => x.TitleId.ToUpper() == TitleIdString);
InGameList = appData != null;
if (InGameList)
{
Icon = appData.Icon;
Title = appData.TitleName;
}
else
{
var appMetadata = MainWindow.MainWindowViewModel.ApplicationLibrary.LoadAndSaveMetaData(TitleIdString);
Title = appMetadata.Title ?? TitleIdString;
}
Task.Run(() =>
{
var saveRoot = System.IO.Path.Combine(virtualFileSystem.GetNandPath(), $"user/save/{info.SaveDataId:x16}");
long total_size = GetDirectorySize(saveRoot);
long GetDirectorySize(string path)
{
long size = 0;
if (Directory.Exists(path))
{
var directories = Directory.GetDirectories(path);
foreach (var directory in directories)
{
size += GetDirectorySize(directory);
}
var files = Directory.GetFiles(path);
foreach (var file in files)
{
size += new FileInfo(file).Length;
}
}
return size;
}
Size = total_size;
});
}
public void OpenLocation()
{
ApplicationHelper.OpenSaveDir(SaveId);
}
public async void Delete()
{
var result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance["DeleteUserSave"],
LocaleManager.Instance["IrreversibleActionNote"],
LocaleManager.Instance["InputDialogYes"],
LocaleManager.Instance["InputDialogNo"], "");
if (result == UserResult.Yes)
{
_horizonClient.Fs.DeleteSaveData(SaveDataSpaceId.User, SaveId);
DeleteAction?.Invoke();
}
}
}
}

View File

@@ -45,10 +45,13 @@ namespace Ryujinx.Ava.Ui.Models
{ {
_profile = profile; _profile = profile;
if (_profile != null)
{
Image = profile.Image; Image = profile.Image;
Name = profile.Name; Name = profile.Name;
UserId = profile.UserId; UserId = profile.UserId;
} }
}
public TempProfile(){} public TempProfile(){}
} }

View File

@@ -1,3 +1,4 @@
using Ryujinx.Ava.Ui.Controls;
using Ryujinx.Ava.Ui.ViewModels; using Ryujinx.Ava.Ui.ViewModels;
using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.HLE.HOS.Services.Account.Acc;
using Profile = Ryujinx.HLE.HOS.Services.Account.Acc.UserProfile; using Profile = Ryujinx.HLE.HOS.Services.Account.Acc.UserProfile;
@@ -7,6 +8,7 @@ namespace Ryujinx.Ava.Ui.Models
public class UserProfile : BaseModel public class UserProfile : BaseModel
{ {
private readonly Profile _profile; private readonly Profile _profile;
private readonly NavigationDialogHost _owner;
private byte[] _image; private byte[] _image;
private string _name; private string _name;
private UserId _userId; private UserId _userId;
@@ -41,9 +43,10 @@ namespace Ryujinx.Ava.Ui.Models
} }
} }
public UserProfile(Profile profile) public UserProfile(Profile profile, NavigationDialogHost owner)
{ {
_profile = profile; _profile = profile;
_owner = owner;
Image = profile.Image; Image = profile.Image;
Name = profile.Name; Name = profile.Name;
@@ -57,5 +60,10 @@ namespace Ryujinx.Ava.Ui.Models
OnPropertyChanged(nameof(IsOpened)); OnPropertyChanged(nameof(IsOpened));
OnPropertyChanged(nameof(Name)); OnPropertyChanged(nameof(Name));
} }
public void Recover(UserProfile userProfile)
{
_owner.Navigate(typeof(UserEditor), (_owner, userProfile, true));
}
} }
} }

View File

@@ -76,6 +76,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
private bool _showAll; private bool _showAll;
private string _lastScannedAmiiboId; private string _lastScannedAmiiboId;
private ReadOnlyObservableCollection<ApplicationData> _appsObservableList; private ReadOnlyObservableCollection<ApplicationData> _appsObservableList;
public ApplicationLibrary ApplicationLibrary => _owner.ApplicationLibrary;
public string TitleName { get; internal set; } public string TitleName { get; internal set; }
@@ -103,8 +104,8 @@ namespace Ryujinx.Ava.Ui.ViewModels
public void Initialize() public void Initialize()
{ {
_owner.ApplicationLibrary.ApplicationCountUpdated += ApplicationLibrary_ApplicationCountUpdated; ApplicationLibrary.ApplicationCountUpdated += ApplicationLibrary_ApplicationCountUpdated;
_owner.ApplicationLibrary.ApplicationAdded += ApplicationLibrary_ApplicationAdded; ApplicationLibrary.ApplicationAdded += ApplicationLibrary_ApplicationAdded;
Ptc.PtcStateChanged -= ProgressHandler; Ptc.PtcStateChanged -= ProgressHandler;
Ptc.PtcStateChanged += ProgressHandler; Ptc.PtcStateChanged += ProgressHandler;
@@ -817,7 +818,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
Thread thread = new(() => Thread thread = new(() =>
{ {
_owner.ApplicationLibrary.LoadApplications(ConfigurationState.Instance.Ui.GameDirs.Value, ConfigurationState.Instance.System.Language); ApplicationLibrary.LoadApplications(ConfigurationState.Instance.Ui.GameDirs.Value, ConfigurationState.Instance.System.Language);
_isLoading = false; _isLoading = false;
}) })
@@ -1005,7 +1006,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
public async void ManageProfiles() public async void ManageProfiles()
{ {
await NavigationDialogHost.Show(_owner.AccountManager, _owner.ContentManager, _owner.VirtualFileSystem); await NavigationDialogHost.Show(_owner.AccountManager, _owner.ContentManager, _owner.VirtualFileSystem, _owner.LibHacHorizonManager.RyujinxClient);
} }
public async void OpenAboutWindow() public async void OpenAboutWindow()
@@ -1098,7 +1099,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
{ {
selection.Favorite = !selection.Favorite; selection.Favorite = !selection.Favorite;
_owner.ApplicationLibrary.LoadAndSaveMetaData(selection.TitleId, appMetadata => ApplicationLibrary.LoadAndSaveMetaData(selection.TitleId, appMetadata =>
{ {
appMetadata.Favorite = selection.Favorite; appMetadata.Favorite = selection.Favorite;
}); });

View File

@@ -119,6 +119,11 @@ namespace Ryujinx.Ava.Ui.ViewModels
} }
} }
public bool IsMacOS
{
get => OperatingSystem.IsMacOS();
}
public bool EnableDiscordIntegration { get; set; } public bool EnableDiscordIntegration { get; set; }
public bool CheckUpdatesOnStart { get; set; } public bool CheckUpdatesOnStart { get; set; }
public bool ShowConfirmExit { get; set; } public bool ShowConfirmExit { get; set; }
@@ -474,11 +479,40 @@ namespace Ryujinx.Ava.Ui.ViewModels
MainWindow.UpdateGraphicsConfig(); MainWindow.UpdateGraphicsConfig();
_previousVolumeLevel = Volume; _previousVolumeLevel = Volume;
if (_owner is SettingsWindow owner)
{
owner.ControllerSettings?.SaveCurrentProfile();
}
if (_owner.Owner is MainWindow window && _directoryChanged)
{
window.ViewModel.LoadApplications();
}
_directoryChanged = false;
} }
public void RevertIfNotSaved() public void RevertIfNotSaved()
{ {
Program.ReloadConfig(); Program.ReloadConfig();
} }
public void ApplyButton()
{
SaveSettings();
}
public void OkButton()
{
SaveSettings();
_owner.Close();
}
public void CancelButton()
{
RevertIfNotSaved();
_owner.Close();
}
} }
} }

View File

@@ -1,8 +1,14 @@
using Avalonia;
using Avalonia.Threading; using Avalonia.Threading;
using FluentAvalonia.UI.Controls;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Shim;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.Controls; using Ryujinx.Ava.Ui.Controls;
using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.HLE.HOS.Services.Account.Acc;
using System; using System;
using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using UserProfile = Ryujinx.Ava.Ui.Models.UserProfile; using UserProfile = Ryujinx.Ava.Ui.Models.UserProfile;
@@ -19,6 +25,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
public UserProfileViewModel() public UserProfileViewModel()
{ {
Profiles = new ObservableCollection<UserProfile>(); Profiles = new ObservableCollection<UserProfile>();
LostProfiles = new ObservableCollection<UserProfile>();
} }
public UserProfileViewModel(NavigationDialogHost owner) : this() public UserProfileViewModel(NavigationDialogHost owner) : this()
@@ -30,6 +37,8 @@ namespace Ryujinx.Ava.Ui.ViewModels
public ObservableCollection<UserProfile> Profiles { get; set; } public ObservableCollection<UserProfile> Profiles { get; set; }
public ObservableCollection<UserProfile> LostProfiles { get; set; }
public UserProfile SelectedProfile public UserProfile SelectedProfile
{ {
get => _selectedProfile; get => _selectedProfile;
@@ -65,12 +74,13 @@ namespace Ryujinx.Ava.Ui.ViewModels
public void LoadProfiles() public void LoadProfiles()
{ {
Profiles.Clear(); Profiles.Clear();
LostProfiles.Clear();
var profiles = _owner.AccountManager.GetAllUsers().OrderByDescending(x => x.AccountState == AccountState.Open); var profiles = _owner.AccountManager.GetAllUsers().OrderByDescending(x => x.AccountState == AccountState.Open);
foreach (var profile in profiles) foreach (var profile in profiles)
{ {
Profiles.Add(new UserProfile(profile)); Profiles.Add(new UserProfile(profile, _owner));
} }
SelectedProfile = Profiles.FirstOrDefault(x => x.UserId == _owner.AccountManager.LastOpenedUser.UserId); SelectedProfile = Profiles.FirstOrDefault(x => x.UserId == _owner.AccountManager.LastOpenedUser.UserId);
@@ -84,6 +94,42 @@ namespace Ryujinx.Ava.Ui.ViewModels
_owner.AccountManager.OpenUser(_selectedProfile.UserId); _owner.AccountManager.OpenUser(_selectedProfile.UserId);
} }
} }
var saveDataFilter = SaveDataFilter.Make(programId: default, saveType: SaveDataType.Account,
default, saveDataId: default, index: default);
using var saveDataIterator = new UniqueRef<SaveDataIterator>();
_owner.HorizonClient.Fs.OpenSaveDataIterator(ref saveDataIterator.Ref(), SaveDataSpaceId.User, in saveDataFilter).ThrowIfFailure();
Span<SaveDataInfo> saveDataInfo = stackalloc SaveDataInfo[10];
HashSet<HLE.HOS.Services.Account.Acc.UserId> lostAccounts = new HashSet<HLE.HOS.Services.Account.Acc.UserId>();
while (true)
{
saveDataIterator.Get.ReadSaveDataInfo(out long readCount, saveDataInfo).ThrowIfFailure();
if (readCount == 0)
{
break;
}
for (int i = 0; i < readCount; i++)
{
var save = saveDataInfo[i];
var id = new HLE.HOS.Services.Account.Acc.UserId((long)save.UserId.Id.Low, (long)save.UserId.Id.High);
if (Profiles.FirstOrDefault( x=> x.UserId == id) == null)
{
lostAccounts.Add(id);
}
}
}
foreach(var account in lostAccounts)
{
LostProfiles.Add(new UserProfile(new HLE.HOS.Services.Account.Acc.UserProfile(account, "", null), _owner));
}
} }
public void AddUser() public void AddUser()
@@ -93,6 +139,25 @@ namespace Ryujinx.Ava.Ui.ViewModels
_owner.Navigate(typeof(UserEditor), (this._owner, userProfile, true)); _owner.Navigate(typeof(UserEditor), (this._owner, userProfile, true));
} }
public async void ManageSaves()
{
UserProfile userProfile = _highlightedProfile ?? SelectedProfile;
SaveManager manager = new SaveManager(userProfile, _owner.HorizonClient, _owner.VirtualFileSystem);
ContentDialog contentDialog = new ContentDialog
{
Title = string.Format(LocaleManager.Instance["SaveManagerHeading"], userProfile.Name),
PrimaryButtonText = "",
SecondaryButtonText = "",
CloseButtonText = LocaleManager.Instance["UserProfilesClose"],
Content = manager,
Padding = new Thickness(0)
};
await contentDialog.ShowAsync();
}
public void EditUser() public void EditUser()
{ {
_owner.Navigate(typeof(UserEditor), (this._owner, _highlightedProfile ?? SelectedProfile, false)); _owner.Navigate(typeof(UserEditor), (this._owner, _highlightedProfile ?? SelectedProfile, false));
@@ -134,5 +199,15 @@ namespace Ryujinx.Ava.Ui.ViewModels
LoadProfiles(); LoadProfiles();
} }
public void GoBack()
{
_owner.GoBack();
}
public void RecoverLostAccounts()
{
_owner.Navigate(typeof(UserRecoverer), (this._owner, this));
}
} }
} }

View File

@@ -13,7 +13,7 @@
Title="Ryujinx" Title="Ryujinx"
Width="1280" Width="1280"
Height="785" Height="785"
MinWidth="1024" MinWidth="1092"
MinHeight="680" MinHeight="680"
d:DesignHeight="720" d:DesignHeight="720"
d:DesignWidth="1280" d:DesignWidth="1280"
@@ -57,6 +57,8 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<StackPanel <StackPanel
Name="MenuBar"
MinHeight="35"
Grid.Row="0" Grid.Row="0"
Margin="0" Margin="0"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
@@ -549,6 +551,7 @@
<Grid <Grid
Name="StatusBar" Name="StatusBar"
Grid.Row="2" Grid.Row="2"
MinHeight="30"
Height="30" Height="30"
Margin="0,0" Margin="0,0"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"

View File

@@ -36,6 +36,7 @@ namespace Ryujinx.Ava.Ui.Windows
{ {
public partial class MainWindow : StyleableWindow public partial class MainWindow : StyleableWindow
{ {
internal static MainWindowViewModel MainWindowViewModel { get; private set; }
private bool _canUpdate; private bool _canUpdate;
private bool _isClosing; private bool _isClosing;
private bool _isLoading; private bool _isLoading;
@@ -81,6 +82,8 @@ namespace Ryujinx.Ava.Ui.Windows
{ {
ViewModel = new MainWindowViewModel(this); ViewModel = new MainWindowViewModel(this);
MainWindowViewModel = ViewModel;
DataContext = ViewModel; DataContext = ViewModel;
InitializeComponent(); InitializeComponent();
@@ -90,7 +93,9 @@ namespace Ryujinx.Ava.Ui.Windows
Title = $"Ryujinx {Program.Version}"; Title = $"Ryujinx {Program.Version}";
Height /= Program.WindowScaleFactor; // NOTE: Height of MenuBar and StatusBar is not usable here, since it would still be 0 at this point.
double barHeight = MenuBar.MinHeight + StatusBar.MinHeight;
Height = ((Height - barHeight) / Program.WindowScaleFactor) + barHeight;
Width /= Program.WindowScaleFactor; Width /= Program.WindowScaleFactor;
if (Program.PreviewerDetached) if (Program.PreviewerDetached)

View File

@@ -955,16 +955,25 @@
Icon="Document" /> Icon="Document" />
</ui:NavigationView.MenuItems> </ui:NavigationView.MenuItems>
</ui:NavigationView> </ui:NavigationView>
<StackPanel <ReversibleStackPanel
Grid.Row="2" Grid.Row="2"
Margin="10" Margin="10"
Spacing="10" Spacing="10"
Orientation="Horizontal" Orientation="Horizontal"
HorizontalAlignment="Right"> HorizontalAlignment="Right"
<Button Content="{locale:Locale SettingsButtonSave}" Click="SaveButton_Clicked" /> ReverseOrder="{ReflectionBinding IsMacOS}">
<Button Content="{locale:Locale SettingsButtonClose}" Click="CloseButton_Clicked" /> <Button
<Button Content="{locale:Locale SettingsButtonApply}" HotKey="Enter"
Click="ApplyButton_Clicked" /> Classes="accent"
</StackPanel> Content="{locale:Locale SettingsButtonOk}"
Command="{ReflectionBinding OkButton}" />
<Button
HotKey="Escape"
Content="{locale:Locale SettingsButtonCancel}"
Command="{ReflectionBinding CancelButton}" />
<Button
Content="{locale:Locale SettingsButtonApply}"
Command="{ReflectionBinding ApplyButton}" />
</ReversibleStackPanel>
</Grid> </Grid>
</window:StyleableWindow> </window:StyleableWindow>

View File

@@ -199,36 +199,6 @@ namespace Ryujinx.Ava.Ui.Windows
} }
} }
private void SaveButton_Clicked(object sender, RoutedEventArgs e)
{
SaveSettings();
Close();
}
private void CloseButton_Clicked(object sender, RoutedEventArgs e)
{
ViewModel.RevertIfNotSaved();
Close();
}
private void ApplyButton_Clicked(object sender, RoutedEventArgs e)
{
SaveSettings();
}
private void SaveSettings()
{
ViewModel.SaveSettings();
ControllerSettings?.SaveCurrentProfile();
if (Owner is MainWindow window && ViewModel.DirectoryChanged)
{
window.ViewModel.LoadApplications();
}
ViewModel.DirectoryChanged = false;
}
protected override void OnClosed(EventArgs e) protected override void OnClosed(EventArgs e)
{ {
ControllerSettings.Dispose(); ControllerSettings.Dispose();

View File

@@ -29,6 +29,7 @@ namespace Ryujinx.Graphics.GAL
ReadOnlySpan<byte> GetBufferData(BufferHandle buffer, int offset, int size); ReadOnlySpan<byte> GetBufferData(BufferHandle buffer, int offset, int size);
Capabilities GetCapabilities(); Capabilities GetCapabilities();
ulong GetCurrentSync();
HardwareInfo GetHardwareInfo(); HardwareInfo GetHardwareInfo();
IProgram LoadProgramBinary(byte[] programBinary, bool hasFragmentShader, ShaderInfo info); IProgram LoadProgramBinary(byte[] programBinary, bool hasFragmentShader, ShaderInfo info);

View File

@@ -338,6 +338,11 @@ namespace Ryujinx.Graphics.GAL.Multithreading
return box.Result; return box.Result;
} }
public ulong GetCurrentSync()
{
return _baseRenderer.GetCurrentSync();
}
public HardwareInfo GetHardwareInfo() public HardwareInfo GetHardwareInfo()
{ {
return _baseRenderer.GetHardwareInfo(); return _baseRenderer.GetHardwareInfo();

View File

@@ -69,6 +69,12 @@ namespace Ryujinx.Graphics.Gpu
/// </summary> /// </summary>
internal List<Action> SyncpointActions { get; } internal List<Action> SyncpointActions { get; }
/// <summary>
/// Buffer migrations that are currently in-flight. These are checked whenever sync is created to determine if buffer migration
/// copies have completed on the GPU, and their data can be freed.
/// </summary>
internal List<BufferMigration> BufferMigrations { get; }
/// <summary> /// <summary>
/// Queue with deferred actions that must run on the render thread. /// Queue with deferred actions that must run on the render thread.
/// </summary> /// </summary>
@@ -90,6 +96,7 @@ namespace Ryujinx.Graphics.Gpu
public event Action<ShaderCacheState, int, int> ShaderCacheStateChanged; public event Action<ShaderCacheState, int, int> ShaderCacheStateChanged;
private Thread _gpuThread; private Thread _gpuThread;
private bool _pendingSync;
/// <summary> /// <summary>
/// Creates a new instance of the GPU emulation context. /// Creates a new instance of the GPU emulation context.
@@ -109,6 +116,7 @@ namespace Ryujinx.Graphics.Gpu
SyncActions = new List<Action>(); SyncActions = new List<Action>();
SyncpointActions = new List<Action>(); SyncpointActions = new List<Action>();
BufferMigrations = new List<BufferMigration>();
DeferredActions = new Queue<Action>(); DeferredActions = new Queue<Action>();
@@ -273,6 +281,17 @@ namespace Ryujinx.Graphics.Gpu
SequenceNumber++; SequenceNumber++;
} }
/// <summary>
/// Registers a buffer migration. These are checked to see if they can be disposed when the sync number increases,
/// and the migration copy has completed.
/// </summary>
/// <param name="migration">The buffer migration</param>
internal void RegisterBufferMigration(BufferMigration migration)
{
BufferMigrations.Add(migration);
_pendingSync = true;
}
/// <summary> /// <summary>
/// Registers an action to be performed the next time a syncpoint is incremented. /// Registers an action to be performed the next time a syncpoint is incremented.
/// This will also ensure a host sync object is created, and <see cref="SyncNumber"/> is incremented. /// This will also ensure a host sync object is created, and <see cref="SyncNumber"/> is incremented.
@@ -288,6 +307,7 @@ namespace Ryujinx.Graphics.Gpu
else else
{ {
SyncActions.Add(action); SyncActions.Add(action);
_pendingSync = true;
} }
} }
@@ -298,7 +318,24 @@ namespace Ryujinx.Graphics.Gpu
/// <param name="syncpoint">True if host sync is being created by a syncpoint</param> /// <param name="syncpoint">True if host sync is being created by a syncpoint</param>
public void CreateHostSyncIfNeeded(bool syncpoint) public void CreateHostSyncIfNeeded(bool syncpoint)
{ {
if (SyncActions.Count > 0 || (syncpoint && SyncpointActions.Count > 0)) if (BufferMigrations.Count > 0)
{
ulong currentSyncNumber = Renderer.GetCurrentSync();
for (int i = 0; i < BufferMigrations.Count; i++)
{
BufferMigration migration = BufferMigrations[i];
long diff = (long)(currentSyncNumber - migration.SyncNumber);
if (diff >= 0)
{
migration.Dispose();
BufferMigrations.RemoveAt(i--);
}
}
}
if (_pendingSync || (syncpoint && SyncpointActions.Count > 0))
{ {
Renderer.CreateSync(SyncNumber); Renderer.CreateSync(SyncNumber);
@@ -317,6 +354,8 @@ namespace Ryujinx.Graphics.Gpu
SyncActions.Clear(); SyncActions.Clear();
SyncpointActions.Clear(); SyncpointActions.Clear();
} }
_pendingSync = false;
} }
/// <summary> /// <summary>

View File

@@ -65,6 +65,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
private bool _useGranular; private bool _useGranular;
private bool _syncActionRegistered; private bool _syncActionRegistered;
private int _referenceCount = 1;
/// <summary> /// <summary>
/// Creates a new instance of the buffer. /// Creates a new instance of the buffer.
/// </summary> /// </summary>
@@ -229,7 +231,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
{ {
if (_modifiedRanges == null) if (_modifiedRanges == null)
{ {
_modifiedRanges = new BufferModifiedRangeList(_context); _modifiedRanges = new BufferModifiedRangeList(_context, this, Flush);
} }
} }
@@ -290,7 +292,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="from">The buffer to inherit from</param> /// <param name="from">The buffer to inherit from</param>
public void InheritModifiedRanges(Buffer from) public void InheritModifiedRanges(Buffer from)
{ {
if (from._modifiedRanges != null) if (from._modifiedRanges != null && from._modifiedRanges.HasRanges)
{ {
if (from._syncActionRegistered && !_syncActionRegistered) if (from._syncActionRegistered && !_syncActionRegistered)
{ {
@@ -310,19 +312,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
} }
}; };
if (_modifiedRanges == null) EnsureRangeList();
{
_modifiedRanges = from._modifiedRanges;
_modifiedRanges.ReregisterRanges(registerRangeAction);
from._modifiedRanges = null;
}
else
{
_modifiedRanges.InheritRanges(from._modifiedRanges, registerRangeAction); _modifiedRanges.InheritRanges(from._modifiedRanges, registerRangeAction);
} }
} }
}
/// <summary> /// <summary>
/// Determine if a given region of the buffer has been modified, and must be flushed. /// Determine if a given region of the buffer has been modified, and must be flushed.
@@ -456,7 +450,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (ranges != null) if (ranges != null)
{ {
(address, size) = PageAlign(address, size); (address, size) = PageAlign(address, size);
ranges.WaitForAndGetRanges(address, size, Flush); ranges.WaitForAndFlushRanges(address, size);
} }
}, true); }, true);
} }
@@ -508,6 +502,25 @@ namespace Ryujinx.Graphics.Gpu.Memory
UnmappedSequence++; UnmappedSequence++;
} }
/// <summary>
/// Increments the buffer reference count.
/// </summary>
public void IncrementReferenceCount()
{
_referenceCount++;
}
/// <summary>
/// Decrements the buffer reference count.
/// </summary>
public void DecrementReferenceCount()
{
if (--_referenceCount == 0)
{
DisposeData();
}
}
/// <summary> /// <summary>
/// Disposes the host buffer's data, not its tracking handles. /// Disposes the host buffer's data, not its tracking handles.
/// </summary> /// </summary>
@@ -528,7 +541,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
_memoryTrackingGranular?.Dispose(); _memoryTrackingGranular?.Dispose();
_memoryTracking?.Dispose(); _memoryTracking?.Dispose();
DisposeData(); DecrementReferenceCount();
} }
} }
} }

View File

@@ -273,7 +273,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
buffer.CopyTo(newBuffer, dstOffset); buffer.CopyTo(newBuffer, dstOffset);
newBuffer.InheritModifiedRanges(buffer); newBuffer.InheritModifiedRanges(buffer);
buffer.DisposeData(); buffer.DecrementReferenceCount();
} }
newBuffer.SynchronizeMemory(address, newSize); newBuffer.SynchronizeMemory(address, newSize);

View File

@@ -0,0 +1,125 @@
using System;
namespace Ryujinx.Graphics.Gpu.Memory
{
/// <summary>
/// A record of when buffer data was copied from one buffer to another, along with the SyncNumber when the migration will be complete.
/// Keeps the source buffer alive for data flushes until the migration is complete.
/// </summary>
internal class BufferMigration : IDisposable
{
/// <summary>
/// The offset for the migrated region.
/// </summary>
private readonly ulong _offset;
/// <summary>
/// The size for the migrated region.
/// </summary>
private readonly ulong _size;
/// <summary>
/// The buffer that was migrated from.
/// </summary>
private readonly Buffer _buffer;
/// <summary>
/// The source range action, to be called on overlap with an unreached sync number.
/// </summary>
private readonly Action<ulong, ulong> _sourceRangeAction;
/// <summary>
/// The source range list.
/// </summary>
private readonly BufferModifiedRangeList _source;
/// <summary>
/// The destination range list. This range list must be updated when flushing the source.
/// </summary>
public readonly BufferModifiedRangeList Destination;
/// <summary>
/// The sync number that needs to be reached for this migration to be removed. This is set to the pending sync number on creation.
/// </summary>
public readonly ulong SyncNumber;
/// <summary>
/// Creates a record for a buffer migration.
/// </summary>
/// <param name="buffer">The source buffer for this migration</param>
/// <param name="sourceRangeAction">The flush action for the source buffer</param>
/// <param name="source">The modified range list for the source buffer</param>
/// <param name="dest">The modified range list for the destination buffer</param>
/// <param name="syncNumber">The sync number for when the migration is complete</param>
public BufferMigration(
Buffer buffer,
Action<ulong, ulong> sourceRangeAction,
BufferModifiedRangeList source,
BufferModifiedRangeList dest,
ulong syncNumber)
{
_offset = buffer.Address;
_size = buffer.Size;
_buffer = buffer;
_sourceRangeAction = sourceRangeAction;
_source = source;
Destination = dest;
SyncNumber = syncNumber;
}
/// <summary>
/// Determine if the given range overlaps this migration, and has not been completed yet.
/// </summary>
/// <param name="offset">Start offset</param>
/// <param name="size">Range size</param>
/// <param name="syncNumber">The sync number that was waited on</param>
/// <returns>True if overlapping and in progress, false otherwise</returns>
public bool Overlaps(ulong offset, ulong size, ulong syncNumber)
{
ulong end = offset + size;
ulong destEnd = _offset + _size;
long syncDiff = (long)(syncNumber - SyncNumber); // syncNumber is less if the copy has not completed.
return !(end <= _offset || offset >= destEnd) && syncDiff < 0;
}
/// <summary>
/// Determine if the given range matches this migration.
/// </summary>
/// <param name="offset">Start offset</param>
/// <param name="size">Range size</param>
/// <returns>True if the range exactly matches, false otherwise</returns>
public bool FullyMatches(ulong offset, ulong size)
{
return _offset == offset && _size == size;
}
/// <summary>
/// Perform the migration source's range action on the range provided, clamped to the bounds of the source buffer.
/// </summary>
/// <param name="offset">Start offset</param>
/// <param name="size">Range size</param>
/// <param name="syncNumber">Current sync number</param>
/// <param name="parent">The modified range list that originally owned this range</param>
public void RangeActionWithMigration(ulong offset, ulong size, ulong syncNumber, BufferModifiedRangeList parent)
{
ulong end = offset + size;
end = Math.Min(_offset + _size, end);
offset = Math.Max(_offset, offset);
size = end - offset;
_source.RangeActionWithMigration(offset, size, syncNumber, parent, _sourceRangeAction);
}
/// <summary>
/// Removes this reference to the range list, potentially allowing for the source buffer to be disposed.
/// </summary>
public void Dispose()
{
Destination.RemoveMigration(this);
_buffer.DecrementReferenceCount();
}
}
}

View File

@@ -1,6 +1,8 @@
using Ryujinx.Common.Pools; using Ryujinx.Common.Logging;
using Ryujinx.Common.Pools;
using Ryujinx.Memory.Range; using Ryujinx.Memory.Range;
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace Ryujinx.Graphics.Gpu.Memory namespace Ryujinx.Graphics.Gpu.Memory
@@ -30,17 +32,24 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary> /// </summary>
public ulong SyncNumber { get; internal set; } public ulong SyncNumber { get; internal set; }
/// <summary>
/// The range list that originally owned this range.
/// </summary>
public BufferModifiedRangeList Parent { get; internal set; }
/// <summary> /// <summary>
/// Creates a new instance of a modified range. /// Creates a new instance of a modified range.
/// </summary> /// </summary>
/// <param name="address">Start address of the range</param> /// <param name="address">Start address of the range</param>
/// <param name="size">Size of the range in bytes</param> /// <param name="size">Size of the range in bytes</param>
/// <param name="syncNumber">The GPU sync number at the time of creation</param> /// <param name="syncNumber">The GPU sync number at the time of creation</param>
public BufferModifiedRange(ulong address, ulong size, ulong syncNumber) /// <param name="parent">The range list that owns this range</param>
public BufferModifiedRange(ulong address, ulong size, ulong syncNumber, BufferModifiedRangeList parent)
{ {
Address = address; Address = address;
Size = size; Size = size;
SyncNumber = syncNumber; SyncNumber = syncNumber;
Parent = parent;
} }
/// <summary> /// <summary>
@@ -63,16 +72,39 @@ namespace Ryujinx.Graphics.Gpu.Memory
private const int BackingInitialSize = 8; private const int BackingInitialSize = 8;
private GpuContext _context; private GpuContext _context;
private Buffer _parent;
private Action<ulong, ulong> _flushAction;
private List<BufferMigration> _sources;
private BufferMigration _migrationTarget;
private object _lock = new object(); private object _lock = new object();
/// <summary>
/// Whether the modified range list has any entries or not.
/// </summary>
public bool HasRanges
{
get
{
lock (_lock)
{
return Count > 0;
}
}
}
/// <summary> /// <summary>
/// Creates a new instance of a modified range list. /// Creates a new instance of a modified range list.
/// </summary> /// </summary>
/// <param name="context">GPU context that the buffer range list belongs to</param> /// <param name="context">GPU context that the buffer range list belongs to</param>
public BufferModifiedRangeList(GpuContext context) : base(BackingInitialSize) /// <param name="parent">The parent buffer that owns this range list</param>
/// <param name="flushAction">The flush action for the parent buffer</param>
public BufferModifiedRangeList(GpuContext context, Buffer parent, Action<ulong, ulong> flushAction) : base(BackingInitialSize)
{ {
_context = context; _context = context;
_parent = parent;
_flushAction = flushAction;
} }
/// <summary> /// <summary>
@@ -142,6 +174,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
{ {
// Region already exists. Just update the existing sync number. // Region already exists. Just update the existing sync number.
overlap.SyncNumber = syncNumber; overlap.SyncNumber = syncNumber;
overlap.Parent = this;
return; return;
} }
@@ -152,18 +185,18 @@ namespace Ryujinx.Graphics.Gpu.Memory
{ {
// A split item must be created behind this overlap. // A split item must be created behind this overlap.
Add(new BufferModifiedRange(overlap.Address, address - overlap.Address, overlap.SyncNumber)); Add(new BufferModifiedRange(overlap.Address, address - overlap.Address, overlap.SyncNumber, overlap.Parent));
} }
if (overlap.Address < endAddress && overlap.EndAddress > endAddress) if (overlap.Address < endAddress && overlap.EndAddress > endAddress)
{ {
// A split item must be created after this overlap. // A split item must be created after this overlap.
Add(new BufferModifiedRange(endAddress, overlap.EndAddress - endAddress, overlap.SyncNumber)); Add(new BufferModifiedRange(endAddress, overlap.EndAddress - endAddress, overlap.SyncNumber, overlap.Parent));
} }
} }
Add(new BufferModifiedRange(address, size, syncNumber)); Add(new BufferModifiedRange(address, size, syncNumber, this));
} }
} }
@@ -207,9 +240,102 @@ namespace Ryujinx.Graphics.Gpu.Memory
} }
} }
/// <summary>
/// Performs the given range action, or one from a migration that overlaps and has not synced yet.
/// </summary>
/// <param name="offset">The offset to pass to the action</param>
/// <param name="size">The size to pass to the action</param>
/// <param name="syncNumber">The sync number that has been reached</param>
/// <param name="parent">The modified range list that originally owned this range</param>
/// <param name="rangeAction">The action to perform</param>
public void RangeActionWithMigration(ulong offset, ulong size, ulong syncNumber, BufferModifiedRangeList parent, Action<ulong, ulong> rangeAction)
{
bool firstSource = true;
if (parent != this)
{
lock (_lock)
{
if (_sources != null)
{
foreach (BufferMigration source in _sources)
{
if (source.Overlaps(offset, size, syncNumber))
{
if (firstSource && !source.FullyMatches(offset, size))
{
// Perform this buffer's action first. The migrations will run after.
rangeAction(offset, size);
}
source.RangeActionWithMigration(offset, size, syncNumber, parent);
firstSource = false;
}
}
}
}
}
if (firstSource)
{
// No overlapping migrations, or they are not meant for this range, flush the data using the given action.
rangeAction(offset, size);
}
}
/// <summary>
/// Removes modified ranges ready by the sync number from the list, and flushes their buffer data within a given address range.
/// </summary>
/// <param name="overlaps">Overlapping ranges to check</param>
/// <param name="rangeCount">Number of overlapping ranges</param>
/// <param name="highestDiff">The highest difference between an overlapping range's sync number and the current one</param>
/// <param name="currentSync">The current sync number</param>
/// <param name="address">The start address of the flush range</param>
/// <param name="endAddress">The end address of the flush range</param>
private void RemoveRangesAndFlush(
BufferModifiedRange[] overlaps,
int rangeCount,
long highestDiff,
ulong currentSync,
ulong address,
ulong endAddress)
{
lock (_lock)
{
if (_migrationTarget == null)
{
ulong waitSync = currentSync + (ulong)highestDiff;
for (int i = 0; i < rangeCount; i++)
{
BufferModifiedRange overlap = overlaps[i];
long diff = (long)(overlap.SyncNumber - currentSync);
if (diff <= highestDiff)
{
ulong clampAddress = Math.Max(address, overlap.Address);
ulong clampEnd = Math.Min(endAddress, overlap.EndAddress);
ClearPart(overlap, clampAddress, clampEnd);
RangeActionWithMigration(clampAddress, clampEnd - clampAddress, waitSync, overlap.Parent, _flushAction);
}
}
return;
}
}
// There is a migration target to call instead. This can't be changed after set so accessing it outside the lock is fine.
_migrationTarget.Destination.RemoveRangesAndFlush(overlaps, rangeCount, highestDiff, currentSync, address, endAddress);
}
/// <summary> /// <summary>
/// Gets modified ranges within the specified region, waits on ones from a previous sync number, /// Gets modified ranges within the specified region, waits on ones from a previous sync number,
/// and then fires the given action for each range individually. /// and then fires the flush action for each range individually.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// This function assumes it is called from the background thread. /// This function assumes it is called from the background thread.
@@ -218,8 +344,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </remarks> /// </remarks>
/// <param name="address">Start address to query</param> /// <param name="address">Start address to query</param>
/// <param name="size">Size to query</param> /// <param name="size">Size to query</param>
/// <param name="rangeAction">The action to call for each modified range</param> public void WaitForAndFlushRanges(ulong address, ulong size)
public void WaitForAndGetRanges(ulong address, ulong size, Action<ulong, ulong> rangeAction)
{ {
ulong endAddress = address + size; ulong endAddress = address + size;
ulong currentSync = _context.SyncNumber; ulong currentSync = _context.SyncNumber;
@@ -230,11 +355,24 @@ namespace Ryujinx.Graphics.Gpu.Memory
// Range list must be consistent for this operation // Range list must be consistent for this operation
lock (_lock) lock (_lock)
{
if (_migrationTarget != null)
{
rangeCount = -1;
}
else
{ {
rangeCount = FindOverlapsNonOverlapping(address, size, ref overlaps); rangeCount = FindOverlapsNonOverlapping(address, size, ref overlaps);
} }
}
if (rangeCount == 0) if (rangeCount == -1)
{
_migrationTarget.Destination.WaitForAndFlushRanges(address, size);
return;
}
else if (rangeCount == 0)
{ {
return; return;
} }
@@ -264,93 +402,59 @@ namespace Ryujinx.Graphics.Gpu.Memory
// Wait for the syncpoint. // Wait for the syncpoint.
_context.Renderer.WaitSync(currentSync + (ulong)highestDiff); _context.Renderer.WaitSync(currentSync + (ulong)highestDiff);
// Flush and remove all regions with the older syncpoint. RemoveRangesAndFlush(overlaps, rangeCount, highestDiff, currentSync, address, endAddress);
lock (_lock)
{
for (int i = 0; i < rangeCount; i++)
{
BufferModifiedRange overlap = overlaps[i];
long diff = (long)(overlap.SyncNumber - currentSync);
if (diff <= highestDiff)
{
ulong clampAddress = Math.Max(address, overlap.Address);
ulong clampEnd = Math.Min(endAddress, overlap.EndAddress);
ClearPart(overlap, clampAddress, clampEnd);
rangeAction(clampAddress, clampEnd - clampAddress);
}
}
}
} }
/// <summary> /// <summary>
/// Inherit ranges from another modified range list. /// Inherit ranges from another modified range list.
/// </summary> /// </summary>
/// <param name="ranges">The range list to inherit from</param> /// <param name="ranges">The range list to inherit from</param>
/// <param name="rangeAction">The action to call for each modified range</param> /// <param name="registerRangeAction">The action to call for each modified range</param>
public void InheritRanges(BufferModifiedRangeList ranges, Action<ulong, ulong> rangeAction) public void InheritRanges(BufferModifiedRangeList ranges, Action<ulong, ulong> registerRangeAction)
{ {
BufferModifiedRange[] inheritRanges; BufferModifiedRange[] inheritRanges;
lock (ranges._lock) lock (ranges._lock)
{ {
BufferMigration migration = new(ranges._parent, ranges._flushAction, ranges, this, _context.SyncNumber);
ranges._parent.IncrementReferenceCount();
ranges._migrationTarget = migration;
_context.RegisterBufferMigration(migration);
inheritRanges = ranges.ToArray(); inheritRanges = ranges.ToArray();
}
lock (_lock) lock (_lock)
{ {
(_sources ??= new List<BufferMigration>()).Add(migration);
foreach (BufferModifiedRange range in inheritRanges) foreach (BufferModifiedRange range in inheritRanges)
{ {
Add(range); Add(range);
} }
} }
}
ulong currentSync = _context.SyncNumber; ulong currentSync = _context.SyncNumber;
foreach (BufferModifiedRange range in inheritRanges) foreach (BufferModifiedRange range in inheritRanges)
{ {
if (range.SyncNumber != currentSync) if (range.SyncNumber != currentSync)
{ {
rangeAction(range.Address, range.Size); registerRangeAction(range.Address, range.Size);
} }
} }
} }
/// <summary> /// <summary>
/// Calls the given action for modified ranges that aren't from the current sync number. /// Removes a source buffer migration, indicating its copy has completed.
/// </summary> /// </summary>
/// <param name="rangeAction">The action to call for each modified range</param> /// <param name="migration">The migration to remove</param>
public void ReregisterRanges(Action<ulong, ulong> rangeAction) public void RemoveMigration(BufferMigration migration)
{ {
ref var ranges = ref ThreadStaticArray<BufferModifiedRange>.Get();
int count;
// Range list must be consistent for this operation.
lock (_lock) lock (_lock)
{ {
count = Count; _sources.Remove(migration);
if (ranges.Length < count)
{
Array.Resize(ref ranges, count);
}
int i = 0;
foreach (BufferModifiedRange range in this)
{
ranges[i++] = range;
}
}
ulong currentSync = _context.SyncNumber;
for (int i = 0; i < count; i++)
{
BufferModifiedRange range = ranges[i];
if (range.SyncNumber != currentSync)
{
rangeAction(range.Address, range.Size);
}
} }
} }
@@ -362,12 +466,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (overlap.Address < address) if (overlap.Address < address)
{ {
Add(new BufferModifiedRange(overlap.Address, address - overlap.Address, overlap.SyncNumber)); Add(new BufferModifiedRange(overlap.Address, address - overlap.Address, overlap.SyncNumber, overlap.Parent));
} }
if (overlap.EndAddress > endAddress) if (overlap.EndAddress > endAddress)
{ {
Add(new BufferModifiedRange(endAddress, overlap.EndAddress - endAddress, overlap.SyncNumber)); Add(new BufferModifiedRange(endAddress, overlap.EndAddress - endAddress, overlap.SyncNumber, overlap.Parent));
} }
} }

View File

@@ -238,6 +238,11 @@ namespace Ryujinx.Graphics.OpenGL
_sync.Wait(id); _sync.Wait(id);
} }
public ulong GetCurrentSync()
{
return _sync.GetCurrent();
}
public void Screenshot() public void Screenshot()
{ {
_window.ScreenCaptureRequested = true; _window.ScreenCaptureRequested = true;

View File

@@ -40,6 +40,37 @@ namespace Ryujinx.Graphics.OpenGL
} }
} }
public ulong GetCurrent()
{
lock (_handles)
{
ulong lastHandle = _firstHandle;
foreach (SyncHandle handle in _handles)
{
lock (handle)
{
if (handle.Handle == IntPtr.Zero)
{
continue;
}
if (handle.ID > lastHandle)
{
WaitSyncStatus syncResult = GL.ClientWaitSync(handle.Handle, _syncFlags, 0);
if (syncResult == WaitSyncStatus.AlreadySignaled)
{
lastHandle = handle.ID;
}
}
}
}
return lastHandle;
}
}
public void Wait(ulong id) public void Wait(ulong id)
{ {
SyncHandle result = null; SyncHandle result = null;

View File

@@ -12,12 +12,12 @@ namespace Ryujinx.Graphics.Vulkan
private const int MaxUpdateBufferSize = 0x10000; private const int MaxUpdateBufferSize = 0x10000;
public const AccessFlags DefaultAccessFlags = public const AccessFlags DefaultAccessFlags =
AccessFlags.AccessIndirectCommandReadBit | AccessFlags.IndirectCommandReadBit |
AccessFlags.AccessShaderReadBit | AccessFlags.ShaderReadBit |
AccessFlags.AccessShaderWriteBit | AccessFlags.ShaderWriteBit |
AccessFlags.AccessTransferReadBit | AccessFlags.TransferReadBit |
AccessFlags.AccessTransferWriteBit | AccessFlags.TransferWriteBit |
AccessFlags.AccessUniformReadBit; AccessFlags.UniformReadBit;
private readonly VulkanRenderer _gd; private readonly VulkanRenderer _gd;
private readonly Device _device; private readonly Device _device;
@@ -87,9 +87,9 @@ namespace Ryujinx.Graphics.Vulkan
_gd.Api.CmdPipelineBarrier( _gd.Api.CmdPipelineBarrier(
commandBuffer, commandBuffer,
PipelineStageFlags.PipelineStageAllCommandsBit, PipelineStageFlags.AllCommandsBit,
PipelineStageFlags.PipelineStageAllCommandsBit, PipelineStageFlags.AllCommandsBit,
DependencyFlags.DependencyDeviceGroupBit, DependencyFlags.DeviceGroupBit,
1, 1,
memoryBarrier, memoryBarrier,
0, 0,
@@ -273,9 +273,9 @@ namespace Ryujinx.Graphics.Vulkan
cbs.CommandBuffer, cbs.CommandBuffer,
dstBuffer, dstBuffer,
BufferHolder.DefaultAccessFlags, BufferHolder.DefaultAccessFlags,
AccessFlags.AccessTransferWriteBit, AccessFlags.TransferWriteBit,
PipelineStageFlags.PipelineStageAllCommandsBit, PipelineStageFlags.AllCommandsBit,
PipelineStageFlags.PipelineStageTransferBit, PipelineStageFlags.TransferBit,
dstOffset, dstOffset,
data.Length); data.Length);
@@ -293,10 +293,10 @@ namespace Ryujinx.Graphics.Vulkan
_gd, _gd,
cbs.CommandBuffer, cbs.CommandBuffer,
dstBuffer, dstBuffer,
AccessFlags.AccessTransferWriteBit, AccessFlags.TransferWriteBit,
BufferHolder.DefaultAccessFlags, BufferHolder.DefaultAccessFlags,
PipelineStageFlags.PipelineStageTransferBit, PipelineStageFlags.TransferBit,
PipelineStageFlags.PipelineStageAllCommandsBit, PipelineStageFlags.AllCommandsBit,
dstOffset, dstOffset,
data.Length); data.Length);
@@ -320,9 +320,9 @@ namespace Ryujinx.Graphics.Vulkan
cbs.CommandBuffer, cbs.CommandBuffer,
dstBuffer, dstBuffer,
BufferHolder.DefaultAccessFlags, BufferHolder.DefaultAccessFlags,
AccessFlags.AccessTransferWriteBit, AccessFlags.TransferWriteBit,
PipelineStageFlags.PipelineStageAllCommandsBit, PipelineStageFlags.AllCommandsBit,
PipelineStageFlags.PipelineStageTransferBit, PipelineStageFlags.TransferBit,
dstOffset, dstOffset,
size); size);
@@ -334,10 +334,10 @@ namespace Ryujinx.Graphics.Vulkan
gd, gd,
cbs.CommandBuffer, cbs.CommandBuffer,
dstBuffer, dstBuffer,
AccessFlags.AccessTransferWriteBit, AccessFlags.TransferWriteBit,
BufferHolder.DefaultAccessFlags, BufferHolder.DefaultAccessFlags,
PipelineStageFlags.PipelineStageTransferBit, PipelineStageFlags.TransferBit,
PipelineStageFlags.PipelineStageAllCommandsBit, PipelineStageFlags.AllCommandsBit,
dstOffset, dstOffset,
size); size);
} }

View File

@@ -10,28 +10,28 @@ namespace Ryujinx.Graphics.Vulkan
class BufferManager : IDisposable class BufferManager : IDisposable
{ {
private const MemoryPropertyFlags DefaultBufferMemoryFlags = private const MemoryPropertyFlags DefaultBufferMemoryFlags =
MemoryPropertyFlags.MemoryPropertyHostVisibleBit | MemoryPropertyFlags.HostVisibleBit |
MemoryPropertyFlags.MemoryPropertyHostCoherentBit | MemoryPropertyFlags.HostCoherentBit |
MemoryPropertyFlags.MemoryPropertyHostCachedBit; MemoryPropertyFlags.HostCachedBit;
private const MemoryPropertyFlags DeviceLocalBufferMemoryFlags = private const MemoryPropertyFlags DeviceLocalBufferMemoryFlags =
MemoryPropertyFlags.MemoryPropertyDeviceLocalBit; MemoryPropertyFlags.DeviceLocalBit;
private const MemoryPropertyFlags FlushableDeviceLocalBufferMemoryFlags = private const MemoryPropertyFlags FlushableDeviceLocalBufferMemoryFlags =
MemoryPropertyFlags.MemoryPropertyHostVisibleBit | MemoryPropertyFlags.HostVisibleBit |
MemoryPropertyFlags.MemoryPropertyHostCoherentBit | MemoryPropertyFlags.HostCoherentBit |
MemoryPropertyFlags.MemoryPropertyDeviceLocalBit; MemoryPropertyFlags.DeviceLocalBit;
private const BufferUsageFlags DefaultBufferUsageFlags = private const BufferUsageFlags DefaultBufferUsageFlags =
BufferUsageFlags.BufferUsageTransferSrcBit | BufferUsageFlags.TransferSrcBit |
BufferUsageFlags.BufferUsageTransferDstBit | BufferUsageFlags.TransferDstBit |
BufferUsageFlags.BufferUsageUniformTexelBufferBit | BufferUsageFlags.UniformTexelBufferBit |
BufferUsageFlags.BufferUsageStorageTexelBufferBit | BufferUsageFlags.StorageTexelBufferBit |
BufferUsageFlags.BufferUsageUniformBufferBit | BufferUsageFlags.UniformBufferBit |
BufferUsageFlags.BufferUsageStorageBufferBit | BufferUsageFlags.StorageBufferBit |
BufferUsageFlags.BufferUsageIndexBufferBit | BufferUsageFlags.IndexBufferBit |
BufferUsageFlags.BufferUsageVertexBufferBit | BufferUsageFlags.VertexBufferBit |
BufferUsageFlags.BufferUsageTransformFeedbackBufferBitExt; BufferUsageFlags.TransformFeedbackBufferBitExt;
private readonly PhysicalDevice _physicalDevice; private readonly PhysicalDevice _physicalDevice;
private readonly Device _device; private readonly Device _device;
@@ -76,11 +76,11 @@ namespace Ryujinx.Graphics.Vulkan
if (forConditionalRendering && gd.Capabilities.SupportsConditionalRendering) if (forConditionalRendering && gd.Capabilities.SupportsConditionalRendering)
{ {
usage |= BufferUsageFlags.BufferUsageConditionalRenderingBitExt; usage |= BufferUsageFlags.ConditionalRenderingBitExt;
} }
else if (gd.Capabilities.SupportsIndirectParameters) else if (gd.Capabilities.SupportsIndirectParameters)
{ {
usage |= BufferUsageFlags.BufferUsageIndirectBufferBit; usage |= BufferUsageFlags.IndirectBufferBit;
} }
var bufferCreateInfo = new BufferCreateInfo() var bufferCreateInfo = new BufferCreateInfo()

View File

@@ -71,8 +71,8 @@ namespace Ryujinx.Graphics.Vulkan
{ {
SType = StructureType.CommandPoolCreateInfo, SType = StructureType.CommandPoolCreateInfo,
QueueFamilyIndex = queueFamilyIndex, QueueFamilyIndex = queueFamilyIndex,
Flags = CommandPoolCreateFlags.CommandPoolCreateTransientBit | Flags = CommandPoolCreateFlags.TransientBit |
CommandPoolCreateFlags.CommandPoolCreateResetCommandBufferBit CommandPoolCreateFlags.ResetCommandBufferBit
}; };
api.CreateCommandPool(device, commandPoolCreateInfo, null, out _pool).ThrowOnError(); api.CreateCommandPool(device, commandPoolCreateInfo, null, out _pool).ThrowOnError();

View File

@@ -229,7 +229,7 @@ namespace Ryujinx.Graphics.Vulkan
} }
else if (texture is TextureView view) else if (texture is TextureView view)
{ {
view.Storage.InsertBarrier(cbs, AccessFlags.AccessShaderReadBit, stage.ConvertToPipelineStageFlags()); view.Storage.InsertBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
_textureRefs[binding] = view.GetImageView(); _textureRefs[binding] = view.GetImageView();
_samplerRefs[binding] = ((SamplerHolder)sampler)?.GetSampler(); _samplerRefs[binding] = ((SamplerHolder)sampler)?.GetSampler();

View File

@@ -12,12 +12,12 @@ namespace Ryujinx.Graphics.Vulkan
{ {
return stage switch return stage switch
{ {
ShaderStage.Vertex => ShaderStageFlags.ShaderStageVertexBit, ShaderStage.Vertex => ShaderStageFlags.VertexBit,
ShaderStage.Geometry => ShaderStageFlags.ShaderStageGeometryBit, ShaderStage.Geometry => ShaderStageFlags.GeometryBit,
ShaderStage.TessellationControl => ShaderStageFlags.ShaderStageTessellationControlBit, ShaderStage.TessellationControl => ShaderStageFlags.TessellationControlBit,
ShaderStage.TessellationEvaluation => ShaderStageFlags.ShaderStageTessellationEvaluationBit, ShaderStage.TessellationEvaluation => ShaderStageFlags.TessellationEvaluationBit,
ShaderStage.Fragment => ShaderStageFlags.ShaderStageFragmentBit, ShaderStage.Fragment => ShaderStageFlags.FragmentBit,
ShaderStage.Compute => ShaderStageFlags.ShaderStageComputeBit, ShaderStage.Compute => ShaderStageFlags.ComputeBit,
_ => LogInvalidAndReturn(stage, nameof(ShaderStage), (ShaderStageFlags)0) _ => LogInvalidAndReturn(stage, nameof(ShaderStage), (ShaderStageFlags)0)
}; };
} }
@@ -26,12 +26,12 @@ namespace Ryujinx.Graphics.Vulkan
{ {
return stage switch return stage switch
{ {
ShaderStage.Vertex => PipelineStageFlags.PipelineStageVertexShaderBit, ShaderStage.Vertex => PipelineStageFlags.VertexShaderBit,
ShaderStage.Geometry => PipelineStageFlags.PipelineStageGeometryShaderBit, ShaderStage.Geometry => PipelineStageFlags.GeometryShaderBit,
ShaderStage.TessellationControl => PipelineStageFlags.PipelineStageTessellationControlShaderBit, ShaderStage.TessellationControl => PipelineStageFlags.TessellationControlShaderBit,
ShaderStage.TessellationEvaluation => PipelineStageFlags.PipelineStageTessellationEvaluationShaderBit, ShaderStage.TessellationEvaluation => PipelineStageFlags.TessellationEvaluationShaderBit,
ShaderStage.Fragment => PipelineStageFlags.PipelineStageFragmentShaderBit, ShaderStage.Fragment => PipelineStageFlags.FragmentShaderBit,
ShaderStage.Compute => PipelineStageFlags.PipelineStageComputeShaderBit, ShaderStage.Compute => PipelineStageFlags.ComputeShaderBit,
_ => LogInvalidAndReturn(stage, nameof(ShaderStage), (PipelineStageFlags)0) _ => LogInvalidAndReturn(stage, nameof(ShaderStage), (PipelineStageFlags)0)
}; };
} }
@@ -112,10 +112,10 @@ namespace Ryujinx.Graphics.Vulkan
{ {
return face switch return face switch
{ {
Face.Back => CullModeFlags.CullModeBackBit, Face.Back => CullModeFlags.BackBit,
Face.Front => CullModeFlags.CullModeFrontBit, Face.Front => CullModeFlags.FrontBit,
Face.FrontAndBack => CullModeFlags.CullModeFrontAndBack, Face.FrontAndBack => CullModeFlags.FrontAndBack,
_ => LogInvalidAndReturn(face, nameof(Face), CullModeFlags.CullModeBackBit) _ => LogInvalidAndReturn(face, nameof(Face), CullModeFlags.BackBit)
}; };
} }
@@ -223,14 +223,14 @@ namespace Ryujinx.Graphics.Vulkan
{ {
Target.Texture1D or Target.Texture1D or
Target.Texture1DArray or Target.Texture1DArray or
Target.TextureBuffer => ImageType.ImageType1D, Target.TextureBuffer => ImageType.Type1D,
Target.Texture2D or Target.Texture2D or
Target.Texture2DArray or Target.Texture2DArray or
Target.Texture2DMultisample or Target.Texture2DMultisample or
Target.Cubemap or Target.Cubemap or
Target.CubemapArray => ImageType.ImageType2D, Target.CubemapArray => ImageType.Type2D,
Target.Texture3D => ImageType.ImageType3D, Target.Texture3D => ImageType.Type3D,
_ => LogInvalidAndReturn(target, nameof(Target), ImageType.ImageType2D) _ => LogInvalidAndReturn(target, nameof(Target), ImageType.Type2D)
}; };
} }
@@ -238,14 +238,14 @@ namespace Ryujinx.Graphics.Vulkan
{ {
return target switch return target switch
{ {
Target.Texture1D => ImageViewType.ImageViewType1D, Target.Texture1D => ImageViewType.Type1D,
Target.Texture2D or Target.Texture2DMultisample => ImageViewType.ImageViewType2D, Target.Texture2D or Target.Texture2DMultisample => ImageViewType.Type2D,
Target.Texture3D => ImageViewType.ImageViewType3D, Target.Texture3D => ImageViewType.Type3D,
Target.Texture1DArray => ImageViewType.ImageViewType1DArray, Target.Texture1DArray => ImageViewType.Type1DArray,
Target.Texture2DArray => ImageViewType.ImageViewType2DArray, Target.Texture2DArray => ImageViewType.Type2DArray,
Target.Cubemap => ImageViewType.Cube, Target.Cubemap => ImageViewType.TypeCube,
Target.CubemapArray => ImageViewType.CubeArray, Target.CubemapArray => ImageViewType.TypeCubeArray,
_ => LogInvalidAndReturn(target, nameof(Target), ImageViewType.ImageViewType2D) _ => LogInvalidAndReturn(target, nameof(Target), ImageViewType.Type2D)
}; };
} }
@@ -253,12 +253,12 @@ namespace Ryujinx.Graphics.Vulkan
{ {
return format switch return format switch
{ {
GAL.Format.D16Unorm or GAL.Format.D32Float => ImageAspectFlags.ImageAspectDepthBit, GAL.Format.D16Unorm or GAL.Format.D32Float => ImageAspectFlags.DepthBit,
GAL.Format.S8Uint => ImageAspectFlags.ImageAspectStencilBit, GAL.Format.S8Uint => ImageAspectFlags.StencilBit,
GAL.Format.D24UnormS8Uint or GAL.Format.D24UnormS8Uint or
GAL.Format.D32FloatS8Uint or GAL.Format.D32FloatS8Uint or
GAL.Format.S8UintD24Unorm => ImageAspectFlags.ImageAspectDepthBit | ImageAspectFlags.ImageAspectStencilBit, GAL.Format.S8UintD24Unorm => ImageAspectFlags.DepthBit | ImageAspectFlags.StencilBit,
_ => ImageAspectFlags.ImageAspectColorBit _ => ImageAspectFlags.ColorBit
}; };
} }
@@ -266,12 +266,12 @@ namespace Ryujinx.Graphics.Vulkan
{ {
return format switch return format switch
{ {
GAL.Format.D16Unorm or GAL.Format.D32Float => ImageAspectFlags.ImageAspectDepthBit, GAL.Format.D16Unorm or GAL.Format.D32Float => ImageAspectFlags.DepthBit,
GAL.Format.S8Uint => ImageAspectFlags.ImageAspectStencilBit, GAL.Format.S8Uint => ImageAspectFlags.StencilBit,
GAL.Format.D24UnormS8Uint or GAL.Format.D24UnormS8Uint or
GAL.Format.D32FloatS8Uint or GAL.Format.D32FloatS8Uint or
GAL.Format.S8UintD24Unorm => depthStencilMode == DepthStencilMode.Stencil ? ImageAspectFlags.ImageAspectStencilBit : ImageAspectFlags.ImageAspectDepthBit, GAL.Format.S8UintD24Unorm => depthStencilMode == DepthStencilMode.Stencil ? ImageAspectFlags.StencilBit : ImageAspectFlags.DepthBit,
_ => ImageAspectFlags.ImageAspectColorBit _ => ImageAspectFlags.ColorBit
}; };
} }

View File

@@ -83,22 +83,22 @@ namespace Ryujinx.Graphics.Vulkan
{ {
var format = FormatTable.GetFormat(srcFormat); var format = FormatTable.GetFormat(srcFormat);
var requiredFeatures = FormatFeatureFlags.FormatFeatureSampledImageBit | var requiredFeatures = FormatFeatureFlags.SampledImageBit |
FormatFeatureFlags.FormatFeatureTransferSrcBit | FormatFeatureFlags.TransferSrcBit |
FormatFeatureFlags.FormatFeatureTransferDstBit; FormatFeatureFlags.TransferDstBit;
if (srcFormat.IsDepthOrStencil()) if (srcFormat.IsDepthOrStencil())
{ {
requiredFeatures |= FormatFeatureFlags.FormatFeatureDepthStencilAttachmentBit; requiredFeatures |= FormatFeatureFlags.DepthStencilAttachmentBit;
} }
else if (srcFormat.IsRtColorCompatible()) else if (srcFormat.IsRtColorCompatible())
{ {
requiredFeatures |= FormatFeatureFlags.FormatFeatureColorAttachmentBit; requiredFeatures |= FormatFeatureFlags.ColorAttachmentBit;
} }
if (srcFormat.IsImageCompatible()) if (srcFormat.IsImageCompatible())
{ {
requiredFeatures |= FormatFeatureFlags.FormatFeatureStorageImageBit; requiredFeatures |= FormatFeatureFlags.StorageImageBit;
} }
if (!OptimalFormatSupports(requiredFeatures, srcFormat) || (IsD24S8(srcFormat) && VulkanConfiguration.ForceD24S8Unsupported)) if (!OptimalFormatSupports(requiredFeatures, srcFormat) || (IsD24S8(srcFormat) && VulkanConfiguration.ForceD24S8Unsupported))
@@ -125,7 +125,7 @@ namespace Ryujinx.Graphics.Vulkan
{ {
var format = FormatTable.GetFormat(srcFormat); var format = FormatTable.GetFormat(srcFormat);
if (!BufferFormatSupports(FormatFeatureFlags.FormatFeatureVertexBufferBit, srcFormat) || if (!BufferFormatSupports(FormatFeatureFlags.VertexBufferBit, srcFormat) ||
(IsRGB16IntFloat(srcFormat) && VulkanConfiguration.ForceRGB16IntFloatUnsupported)) (IsRGB16IntFloat(srcFormat) && VulkanConfiguration.ForceRGB16IntFloatUnsupported))
{ {
// The format is not supported. Can we convert it to an alternative format? // The format is not supported. Can we convert it to an alternative format?

View File

@@ -190,14 +190,14 @@ namespace Ryujinx.Graphics.Vulkan
for (int index = 0; index < _colors.Length; index++) for (int index = 0; index < _colors.Length; index++)
{ {
_colors[index].Storage.SetModification( _colors[index].Storage.SetModification(
AccessFlags.AccessColorAttachmentWriteBit, AccessFlags.ColorAttachmentWriteBit,
PipelineStageFlags.PipelineStageColorAttachmentOutputBit); PipelineStageFlags.ColorAttachmentOutputBit);
} }
} }
_depthStencil?.Storage.SetModification( _depthStencil?.Storage.SetModification(
AccessFlags.AccessDepthStencilAttachmentWriteBit, AccessFlags.DepthStencilAttachmentWriteBit,
PipelineStageFlags.PipelineStageColorAttachmentOutputBit); PipelineStageFlags.ColorAttachmentOutputBit);
} }
} }
} }

View File

@@ -349,8 +349,8 @@ namespace Ryujinx.Graphics.Vulkan
var srcBuffer = srcBufferAuto.Get(cbs, srcOffset, size).Value; var srcBuffer = srcBufferAuto.Get(cbs, srcOffset, size).Value;
var dstBuffer = dstBufferAuto.Get(cbs, 0, newSize).Value; var dstBuffer = dstBufferAuto.Get(cbs, 0, newSize).Value;
var access = supportsUint8 ? AccessFlags.AccessShaderWriteBit : AccessFlags.AccessTransferWriteBit; var access = supportsUint8 ? AccessFlags.ShaderWriteBit : AccessFlags.TransferWriteBit;
var stage = supportsUint8 ? PipelineStageFlags.PipelineStageComputeShaderBit : PipelineStageFlags.PipelineStageTransferBit; var stage = supportsUint8 ? PipelineStageFlags.ComputeShaderBit : PipelineStageFlags.TransferBit;
BufferHolder.InsertBufferBarrier( BufferHolder.InsertBufferBarrier(
gd, gd,
@@ -358,7 +358,7 @@ namespace Ryujinx.Graphics.Vulkan
dstBuffer, dstBuffer,
BufferHolder.DefaultAccessFlags, BufferHolder.DefaultAccessFlags,
access, access,
PipelineStageFlags.PipelineStageAllCommandsBit, PipelineStageFlags.AllCommandsBit,
stage, stage,
0, 0,
newSize); newSize);
@@ -420,7 +420,7 @@ namespace Ryujinx.Graphics.Vulkan
access, access,
BufferHolder.DefaultAccessFlags, BufferHolder.DefaultAccessFlags,
stage, stage,
PipelineStageFlags.PipelineStageAllCommandsBit, PipelineStageFlags.AllCommandsBit,
0, 0,
newSize); newSize);
} }
@@ -484,9 +484,9 @@ namespace Ryujinx.Graphics.Vulkan
cbs.CommandBuffer, cbs.CommandBuffer,
dstBuffer, dstBuffer,
BufferHolder.DefaultAccessFlags, BufferHolder.DefaultAccessFlags,
AccessFlags.AccessTransferWriteBit, AccessFlags.TransferWriteBit,
PipelineStageFlags.PipelineStageAllCommandsBit, PipelineStageFlags.AllCommandsBit,
PipelineStageFlags.PipelineStageTransferBit, PipelineStageFlags.TransferBit,
0, 0,
convertedCount * outputIndexSize); convertedCount * outputIndexSize);
@@ -499,10 +499,10 @@ namespace Ryujinx.Graphics.Vulkan
gd, gd,
cbs.CommandBuffer, cbs.CommandBuffer,
dstBuffer, dstBuffer,
AccessFlags.AccessTransferWriteBit, AccessFlags.TransferWriteBit,
BufferHolder.DefaultAccessFlags, BufferHolder.DefaultAccessFlags,
PipelineStageFlags.PipelineStageTransferBit, PipelineStageFlags.TransferBit,
PipelineStageFlags.PipelineStageAllCommandsBit, PipelineStageFlags.AllCommandsBit,
0, 0,
convertedCount * outputIndexSize); convertedCount * outputIndexSize);
} }
@@ -548,10 +548,10 @@ namespace Ryujinx.Graphics.Vulkan
cbs.CommandBuffer, cbs.CommandBuffer,
src.GetImage().Get(cbs).Value, src.GetImage().Get(cbs).Value,
TextureStorage.DefaultAccessMask, TextureStorage.DefaultAccessMask,
AccessFlags.AccessShaderReadBit, AccessFlags.ShaderReadBit,
PipelineStageFlags.PipelineStageAllCommandsBit, PipelineStageFlags.AllCommandsBit,
PipelineStageFlags.PipelineStageComputeShaderBit, PipelineStageFlags.ComputeShaderBit,
ImageAspectFlags.ImageAspectColorBit, ImageAspectFlags.ColorBit,
src.FirstLayer + srcLayer, src.FirstLayer + srcLayer,
src.FirstLevel, src.FirstLevel,
depth, depth,
@@ -610,11 +610,11 @@ namespace Ryujinx.Graphics.Vulkan
gd.Api, gd.Api,
cbs.CommandBuffer, cbs.CommandBuffer,
dst.GetImage().Get(cbs).Value, dst.GetImage().Get(cbs).Value,
AccessFlags.AccessShaderWriteBit, AccessFlags.ShaderWriteBit,
TextureStorage.DefaultAccessMask, TextureStorage.DefaultAccessMask,
PipelineStageFlags.PipelineStageComputeShaderBit, PipelineStageFlags.ComputeShaderBit,
PipelineStageFlags.PipelineStageAllCommandsBit, PipelineStageFlags.AllCommandsBit,
ImageAspectFlags.ImageAspectColorBit, ImageAspectFlags.ColorBit,
dst.FirstLayer + dstLayer, dst.FirstLayer + dstLayer,
dst.FirstLevel, dst.FirstLevel,
depth, depth,
@@ -770,9 +770,9 @@ namespace Ryujinx.Graphics.Vulkan
cbs.CommandBuffer, cbs.CommandBuffer,
srcIndirectBuffer.GetBuffer().Get(cbs, srcIndirectBufferOffset, indirectDataSize).Value, srcIndirectBuffer.GetBuffer().Get(cbs, srcIndirectBufferOffset, indirectDataSize).Value,
BufferHolder.DefaultAccessFlags, BufferHolder.DefaultAccessFlags,
AccessFlags.AccessShaderReadBit, AccessFlags.ShaderReadBit,
PipelineStageFlags.PipelineStageAllCommandsBit, PipelineStageFlags.AllCommandsBit,
PipelineStageFlags.PipelineStageComputeShaderBit, PipelineStageFlags.ComputeShaderBit,
srcIndirectBufferOffset, srcIndirectBufferOffset,
indirectDataSize); indirectDataSize);
@@ -786,10 +786,10 @@ namespace Ryujinx.Graphics.Vulkan
gd, gd,
cbs.CommandBuffer, cbs.CommandBuffer,
patternBufferAuto.Get(cbs, ParamsIndirectDispatchOffset, ParamsIndirectDispatchSize).Value, patternBufferAuto.Get(cbs, ParamsIndirectDispatchOffset, ParamsIndirectDispatchSize).Value,
AccessFlags.AccessShaderWriteBit, AccessFlags.ShaderWriteBit,
AccessFlags.AccessIndirectCommandReadBit, AccessFlags.IndirectCommandReadBit,
PipelineStageFlags.PipelineStageComputeShaderBit, PipelineStageFlags.ComputeShaderBit,
PipelineStageFlags.PipelineStageDrawIndirectBit, PipelineStageFlags.DrawIndirectBit,
ParamsIndirectDispatchOffset, ParamsIndirectDispatchOffset,
ParamsIndirectDispatchSize); ParamsIndirectDispatchSize);
@@ -798,9 +798,9 @@ namespace Ryujinx.Graphics.Vulkan
cbs.CommandBuffer, cbs.CommandBuffer,
dstBuffer, dstBuffer,
BufferHolder.DefaultAccessFlags, BufferHolder.DefaultAccessFlags,
AccessFlags.AccessTransferWriteBit, AccessFlags.TransferWriteBit,
PipelineStageFlags.PipelineStageAllCommandsBit, PipelineStageFlags.AllCommandsBit,
PipelineStageFlags.PipelineStageTransferBit, PipelineStageFlags.TransferBit,
0, 0,
convertedCount * outputIndexSize); convertedCount * outputIndexSize);
@@ -814,10 +814,10 @@ namespace Ryujinx.Graphics.Vulkan
gd, gd,
cbs.CommandBuffer, cbs.CommandBuffer,
dstBuffer, dstBuffer,
AccessFlags.AccessTransferWriteBit, AccessFlags.TransferWriteBit,
BufferHolder.DefaultAccessFlags, BufferHolder.DefaultAccessFlags,
PipelineStageFlags.PipelineStageTransferBit, PipelineStageFlags.TransferBit,
PipelineStageFlags.PipelineStageAllCommandsBit, PipelineStageFlags.AllCommandsBit,
0, 0,
convertedCount * outputIndexSize); convertedCount * outputIndexSize);

View File

@@ -33,7 +33,7 @@ namespace Ryujinx.Graphics.Vulkan
return default; return default;
} }
bool map = flags.HasFlag(MemoryPropertyFlags.MemoryPropertyHostVisibleBit); bool map = flags.HasFlag(MemoryPropertyFlags.HostVisibleBit);
return Allocate(memoryTypeIndex, requirements.Size, requirements.Alignment, map); return Allocate(memoryTypeIndex, requirements.Size, requirements.Alignment, map);
} }

View File

@@ -128,14 +128,14 @@ namespace Ryujinx.Graphics.Vulkan
MemoryBarrier memoryBarrier = new MemoryBarrier() MemoryBarrier memoryBarrier = new MemoryBarrier()
{ {
SType = StructureType.MemoryBarrier, SType = StructureType.MemoryBarrier,
SrcAccessMask = AccessFlags.AccessMemoryReadBit | AccessFlags.AccessMemoryWriteBit, SrcAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit,
DstAccessMask = AccessFlags.AccessMemoryReadBit | AccessFlags.AccessMemoryWriteBit DstAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit
}; };
Gd.Api.CmdPipelineBarrier( Gd.Api.CmdPipelineBarrier(
CommandBuffer, CommandBuffer,
PipelineStageFlags.PipelineStageFragmentShaderBit, PipelineStageFlags.FragmentShaderBit,
PipelineStageFlags.PipelineStageFragmentShaderBit, PipelineStageFlags.FragmentShaderBit,
0, 0,
1, 1,
memoryBarrier, memoryBarrier,
@@ -161,9 +161,9 @@ namespace Ryujinx.Graphics.Vulkan
Cbs.CommandBuffer, Cbs.CommandBuffer,
dst, dst,
BufferHolder.DefaultAccessFlags, BufferHolder.DefaultAccessFlags,
AccessFlags.AccessTransferWriteBit, AccessFlags.TransferWriteBit,
PipelineStageFlags.PipelineStageAllCommandsBit, PipelineStageFlags.AllCommandsBit,
PipelineStageFlags.PipelineStageTransferBit, PipelineStageFlags.TransferBit,
offset, offset,
size); size);
@@ -173,10 +173,10 @@ namespace Ryujinx.Graphics.Vulkan
Gd, Gd,
Cbs.CommandBuffer, Cbs.CommandBuffer,
dst, dst,
AccessFlags.AccessTransferWriteBit, AccessFlags.TransferWriteBit,
BufferHolder.DefaultAccessFlags, BufferHolder.DefaultAccessFlags,
PipelineStageFlags.PipelineStageTransferBit, PipelineStageFlags.TransferBit,
PipelineStageFlags.PipelineStageAllCommandsBit, PipelineStageFlags.AllCommandsBit,
offset, offset,
size); size);
} }
@@ -196,7 +196,7 @@ namespace Ryujinx.Graphics.Vulkan
BeginRenderPass(); BeginRenderPass();
var clearValue = new ClearValue(new ClearColorValue(color.Red, color.Green, color.Blue, color.Alpha)); var clearValue = new ClearValue(new ClearColorValue(color.Red, color.Green, color.Blue, color.Alpha));
var attachment = new ClearAttachment(ImageAspectFlags.ImageAspectColorBit, (uint)index, clearValue); var attachment = new ClearAttachment(ImageAspectFlags.ColorBit, (uint)index, clearValue);
var clearRect = FramebufferParams.GetClearRect(ClearScissor, layer, layerCount); var clearRect = FramebufferParams.GetClearRect(ClearScissor, layer, layerCount);
Gd.Api.CmdClearAttachments(CommandBuffer, 1, &attachment, 1, &clearRect); Gd.Api.CmdClearAttachments(CommandBuffer, 1, &attachment, 1, &clearRect);
@@ -219,11 +219,11 @@ namespace Ryujinx.Graphics.Vulkan
BeginRenderPass(); BeginRenderPass();
var clearValue = new ClearValue(null, new ClearDepthStencilValue(depthValue, (uint)stencilValue)); var clearValue = new ClearValue(null, new ClearDepthStencilValue(depthValue, (uint)stencilValue));
var flags = depthMask ? ImageAspectFlags.ImageAspectDepthBit : 0; var flags = depthMask ? ImageAspectFlags.DepthBit : 0;
if (stencilMask != 0) if (stencilMask != 0)
{ {
flags |= ImageAspectFlags.ImageAspectStencilBit; flags |= ImageAspectFlags.StencilBit;
} }
var attachment = new ClearAttachment(flags, 0, clearValue); var attachment = new ClearAttachment(flags, 0, clearValue);
@@ -238,13 +238,13 @@ namespace Ryujinx.Graphics.Vulkan
{ {
SType = StructureType.MemoryBarrier, SType = StructureType.MemoryBarrier,
SrcAccessMask = BufferHolder.DefaultAccessFlags, SrcAccessMask = BufferHolder.DefaultAccessFlags,
DstAccessMask = AccessFlags.AccessIndirectCommandReadBit DstAccessMask = AccessFlags.IndirectCommandReadBit
}; };
Gd.Api.CmdPipelineBarrier( Gd.Api.CmdPipelineBarrier(
CommandBuffer, CommandBuffer,
PipelineStageFlags.PipelineStageAllCommandsBit, PipelineStageFlags.AllCommandsBit,
PipelineStageFlags.PipelineStageDrawIndirectBit, PipelineStageFlags.DrawIndirectBit,
0, 0,
1, 1,
memoryBarrier, memoryBarrier,
@@ -624,7 +624,7 @@ namespace Ryujinx.Graphics.Vulkan
var oldViewports = DynamicState.Viewports; var oldViewports = DynamicState.Viewports;
var oldViewportsCount = _newState.ViewportsCount; var oldViewportsCount = _newState.ViewportsCount;
_newState.CullMode = CullModeFlags.CullModeNone; _newState.CullMode = CullModeFlags.None;
_newState.StencilTestEnable = false; _newState.StencilTestEnable = false;
_newState.DepthTestEnable = false; _newState.DepthTestEnable = false;
_newState.DepthWriteEnable = false; _newState.DepthWriteEnable = false;
@@ -737,7 +737,7 @@ namespace Ryujinx.Graphics.Vulkan
public void SetFaceCulling(bool enable, Face face) public void SetFaceCulling(bool enable, Face face)
{ {
_newState.CullMode = enable ? face.Convert() : CullModeFlags.CullModeNone; _newState.CullMode = enable ? face.Convert() : CullModeFlags.None;
SignalStateChange(); SignalStateChange();
} }
@@ -1200,14 +1200,14 @@ namespace Ryujinx.Graphics.Vulkan
MemoryBarrier memoryBarrier = new MemoryBarrier() MemoryBarrier memoryBarrier = new MemoryBarrier()
{ {
SType = StructureType.MemoryBarrier, SType = StructureType.MemoryBarrier,
SrcAccessMask = AccessFlags.AccessMemoryReadBit | AccessFlags.AccessMemoryWriteBit, SrcAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit,
DstAccessMask = AccessFlags.AccessMemoryReadBit | AccessFlags.AccessMemoryWriteBit DstAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit
}; };
Gd.Api.CmdPipelineBarrier( Gd.Api.CmdPipelineBarrier(
CommandBuffer, CommandBuffer,
PipelineStageFlags.PipelineStageFragmentShaderBit, PipelineStageFlags.FragmentShaderBit,
PipelineStageFlags.PipelineStageFragmentShaderBit, PipelineStageFlags.FragmentShaderBit,
0, 0,
1, 1,
memoryBarrier, memoryBarrier,

View File

@@ -6,8 +6,8 @@ namespace Ryujinx.Graphics.Vulkan
{ {
static class PipelineConverter static class PipelineConverter
{ {
private const AccessFlags SubpassSrcAccessMask = AccessFlags.AccessMemoryReadBit | AccessFlags.AccessMemoryWriteBit | AccessFlags.AccessColorAttachmentWriteBit; private const AccessFlags SubpassSrcAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit | AccessFlags.ColorAttachmentWriteBit;
private const AccessFlags SubpassDstAccessMask = AccessFlags.AccessMemoryReadBit | AccessFlags.AccessMemoryWriteBit | AccessFlags.AccessShaderReadBit; private const AccessFlags SubpassDstAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit | AccessFlags.ShaderReadBit;
public static unsafe DisposableRenderPass ToRenderPass(this ProgramPipelineState state, VulkanRenderer gd, Device device) public static unsafe DisposableRenderPass ToRenderPass(this ProgramPipelineState state, VulkanRenderer gd, Device device)
{ {
@@ -129,8 +129,8 @@ namespace Ryujinx.Graphics.Vulkan
return new SubpassDependency( return new SubpassDependency(
0, 0,
0, 0,
PipelineStageFlags.PipelineStageAllGraphicsBit, PipelineStageFlags.AllGraphicsBit,
PipelineStageFlags.PipelineStageAllGraphicsBit, PipelineStageFlags.AllGraphicsBit,
SubpassSrcAccessMask, SubpassSrcAccessMask,
SubpassDstAccessMask, SubpassDstAccessMask,
0); 0);
@@ -143,8 +143,8 @@ namespace Ryujinx.Graphics.Vulkan
null, null,
0, 0,
0, 0,
PipelineStageFlags.PipelineStageAllGraphicsBit, PipelineStageFlags.AllGraphicsBit,
PipelineStageFlags.PipelineStageAllGraphicsBit, PipelineStageFlags.AllGraphicsBit,
SubpassSrcAccessMask, SubpassSrcAccessMask,
SubpassDstAccessMask, SubpassDstAccessMask,
0); 0);
@@ -157,7 +157,7 @@ namespace Ryujinx.Graphics.Vulkan
// It is assumed that Dynamic State is enabled when this conversion is used. // It is assumed that Dynamic State is enabled when this conversion is used.
pipeline.CullMode = state.CullEnable ? state.CullMode.Convert() : CullModeFlags.CullModeNone; pipeline.CullMode = state.CullEnable ? state.CullMode.Convert() : CullModeFlags.None;
pipeline.DepthBoundsTestEnable = false; // Not implemented. pipeline.DepthBoundsTestEnable = false; // Not implemented.

View File

@@ -145,12 +145,12 @@ namespace Ryujinx.Graphics.Vulkan
private void RecordStencilMasks(Vk api, CommandBuffer commandBuffer) private void RecordStencilMasks(Vk api, CommandBuffer commandBuffer)
{ {
api.CmdSetStencilCompareMask(commandBuffer, StencilFaceFlags.StencilFaceBackBit, _backCompareMask); api.CmdSetStencilCompareMask(commandBuffer, StencilFaceFlags.FaceBackBit, _backCompareMask);
api.CmdSetStencilWriteMask(commandBuffer, StencilFaceFlags.StencilFaceBackBit, _backWriteMask); api.CmdSetStencilWriteMask(commandBuffer, StencilFaceFlags.FaceBackBit, _backWriteMask);
api.CmdSetStencilReference(commandBuffer, StencilFaceFlags.StencilFaceBackBit, _backReference); api.CmdSetStencilReference(commandBuffer, StencilFaceFlags.FaceBackBit, _backReference);
api.CmdSetStencilCompareMask(commandBuffer, StencilFaceFlags.StencilFaceFrontBit, _frontCompareMask); api.CmdSetStencilCompareMask(commandBuffer, StencilFaceFlags.FaceFrontBit, _frontCompareMask);
api.CmdSetStencilWriteMask(commandBuffer, StencilFaceFlags.StencilFaceFrontBit, _frontWriteMask); api.CmdSetStencilWriteMask(commandBuffer, StencilFaceFlags.FaceFrontBit, _frontWriteMask);
api.CmdSetStencilReference(commandBuffer, StencilFaceFlags.StencilFaceFrontBit, _frontReference); api.CmdSetStencilReference(commandBuffer, StencilFaceFlags.FaceFrontBit, _frontReference);
} }
private void RecordViewport(Vk api, CommandBuffer commandBuffer) private void RecordViewport(Vk api, CommandBuffer commandBuffer)

View File

@@ -125,7 +125,7 @@ namespace Ryujinx.Graphics.Vulkan
if (Gd.Capabilities.SupportsConditionalRendering) if (Gd.Capabilities.SupportsConditionalRendering)
{ {
var buffer = evt.GetBuffer().Get(Cbs, 0, sizeof(long)).Value; var buffer = evt.GetBuffer().Get(Cbs, 0, sizeof(long)).Value;
var flags = isEqual ? ConditionalRenderingFlagsEXT.ConditionalRenderingInvertedBitExt : 0; var flags = isEqual ? ConditionalRenderingFlagsEXT.InvertedBitExt : 0;
var conditionalRenderingBeginInfo = new ConditionalRenderingBeginInfoEXT() var conditionalRenderingBeginInfo = new ConditionalRenderingBeginInfoEXT()
{ {

View File

@@ -8,9 +8,9 @@ namespace Ryujinx.Graphics.Vulkan
static class PipelineLayoutFactory static class PipelineLayoutFactory
{ {
private const ShaderStageFlags SupportBufferStages = private const ShaderStageFlags SupportBufferStages =
ShaderStageFlags.ShaderStageVertexBit | ShaderStageFlags.VertexBit |
ShaderStageFlags.ShaderStageFragmentBit | ShaderStageFlags.FragmentBit |
ShaderStageFlags.ShaderStageComputeBit; ShaderStageFlags.ComputeBit;
public static unsafe DescriptorSetLayout[] Create(VulkanRenderer gd, Device device, uint stages, bool usePd, out PipelineLayout layout) public static unsafe DescriptorSetLayout[] Create(VulkanRenderer gd, Device device, uint stages, bool usePd, out PipelineLayout layout)
{ {
@@ -42,11 +42,11 @@ namespace Ryujinx.Graphics.Vulkan
var stageFlags = stage switch var stageFlags = stage switch
{ {
1 => ShaderStageFlags.ShaderStageFragmentBit, 1 => ShaderStageFlags.FragmentBit,
2 => ShaderStageFlags.ShaderStageGeometryBit, 2 => ShaderStageFlags.GeometryBit,
3 => ShaderStageFlags.ShaderStageTessellationControlBit, 3 => ShaderStageFlags.TessellationControlBit,
4 => ShaderStageFlags.ShaderStageTessellationEvaluationBit, 4 => ShaderStageFlags.TessellationEvaluationBit,
_ => ShaderStageFlags.ShaderStageVertexBit | ShaderStageFlags.ShaderStageComputeBit _ => ShaderStageFlags.VertexBit | ShaderStageFlags.ComputeBit
}; };
void Set(DescriptorSetLayoutBinding* bindings, int maxPerStage, DescriptorType type, int start, int skip) void Set(DescriptorSetLayoutBinding* bindings, int maxPerStage, DescriptorType type, int start, int skip)
@@ -93,7 +93,7 @@ namespace Ryujinx.Graphics.Vulkan
SType = StructureType.DescriptorSetLayoutCreateInfo, SType = StructureType.DescriptorSetLayoutCreateInfo,
PBindings = uLayoutBindings, PBindings = uLayoutBindings,
BindingCount = (uint)uCount, BindingCount = (uint)uCount,
Flags = usePd ? DescriptorSetLayoutCreateFlags.DescriptorSetLayoutCreatePushDescriptorBitKhr : 0 Flags = usePd ? DescriptorSetLayoutCreateFlags.PushDescriptorBitKhr : 0
}; };
var sDescriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo() var sDescriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo()

View File

@@ -41,7 +41,7 @@ namespace Ryujinx.Graphics.Vulkan.Queries
if (_isSupported) if (_isSupported)
{ {
QueryPipelineStatisticFlags flags = type == CounterType.PrimitivesGenerated ? QueryPipelineStatisticFlags flags = type == CounterType.PrimitivesGenerated ?
QueryPipelineStatisticFlags.QueryPipelineStatisticGeometryShaderPrimitivesBit : 0; QueryPipelineStatisticFlags.GeometryShaderPrimitivesBit : 0;
var queryPoolCreateInfo = new QueryPoolCreateInfo() var queryPoolCreateInfo = new QueryPoolCreateInfo()
{ {
@@ -175,11 +175,11 @@ namespace Ryujinx.Graphics.Vulkan.Queries
{ {
var buffer = _buffer.GetBuffer(cbs.CommandBuffer, true).Get(cbs, 0, sizeof(long)).Value; var buffer = _buffer.GetBuffer(cbs.CommandBuffer, true).Get(cbs, 0, sizeof(long)).Value;
QueryResultFlags flags = QueryResultFlags.QueryResultWaitBit; QueryResultFlags flags = QueryResultFlags.ResultWaitBit;
if (!_result32Bit) if (!_result32Bit)
{ {
flags |= QueryResultFlags.QueryResult64Bit; flags |= QueryResultFlags.Result64Bit;
} }
_api.CmdCopyQueryPoolResults( _api.CmdCopyQueryPoolResults(

View File

@@ -15,9 +15,9 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.7.5" /> <PackageReference Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.7.5" />
<PackageReference Include="shaderc.net" Version="0.1.0" /> <PackageReference Include="shaderc.net" Version="0.1.0" />
<PackageReference Include="Silk.NET.Vulkan" Version="2.10.1" /> <PackageReference Include="Silk.NET.Vulkan" Version="2.16.0" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.10.1" /> <PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.10.1" /> <PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />
<PackageReference Include="System.IO.FileSystem.Primitives" Version="4.3.0" /> <PackageReference Include="System.IO.FileSystem.Primitives" Version="4.3.0" />
<PackageReference Include="System.Net.NameResolution" Version="4.3.0" /> <PackageReference Include="System.Net.NameResolution" Version="4.3.0" />
<PackageReference Include="System.Threading.ThreadPool" Version="4.3.0" /> <PackageReference Include="System.Threading.ThreadPool" Version="4.3.0" />

View File

@@ -82,14 +82,14 @@ namespace Ryujinx.Graphics.Vulkan
stages |= 1u << shader.StageFlags switch stages |= 1u << shader.StageFlags switch
{ {
ShaderStageFlags.ShaderStageFragmentBit => 1, ShaderStageFlags.FragmentBit => 1,
ShaderStageFlags.ShaderStageGeometryBit => 2, ShaderStageFlags.GeometryBit => 2,
ShaderStageFlags.ShaderStageTessellationControlBit => 3, ShaderStageFlags.TessellationControlBit => 3,
ShaderStageFlags.ShaderStageTessellationEvaluationBit => 4, ShaderStageFlags.TessellationEvaluationBit => 4,
_ => 0 _ => 0
}; };
if (shader.StageFlags == ShaderStageFlags.ShaderStageComputeBit) if (shader.StageFlags == ShaderStageFlags.ComputeBit)
{ {
IsCompute = true; IsCompute = true;
} }

View File

@@ -11,6 +11,7 @@ namespace Ryujinx.Graphics.Vulkan
{ {
public ulong ID; public ulong ID;
public MultiFenceHolder Waitable; public MultiFenceHolder Waitable;
public bool Signalled;
} }
private ulong _firstHandle = 0; private ulong _firstHandle = 0;
@@ -45,6 +46,37 @@ namespace Ryujinx.Graphics.Vulkan
} }
} }
public ulong GetCurrent()
{
lock (_handles)
{
ulong lastHandle = _firstHandle;
foreach (SyncHandle handle in _handles)
{
lock (handle)
{
if (handle.Waitable == null)
{
continue;
}
if (handle.ID > lastHandle)
{
bool signaled = handle.Signalled || handle.Waitable.WaitForFences(_gd.Api, _device, 0);
if (signaled)
{
lastHandle = handle.ID;
handle.Signalled = true;
}
}
}
}
return lastHandle;
}
}
public void Wait(ulong id) public void Wait(ulong id)
{ {
SyncHandle result = null; SyncHandle result = null;
@@ -75,11 +107,15 @@ namespace Ryujinx.Graphics.Vulkan
return; return;
} }
bool signaled = result.Waitable.WaitForFences(_gd.Api, _device, 1000000000); bool signaled = result.Signalled || result.Waitable.WaitForFences(_gd.Api, _device, 1000000000);
if (!signaled) if (!signaled)
{ {
Logger.Error?.PrintMsg(LogClass.Gpu, $"VK Sync Object {result.ID} failed to signal within 1000ms. Continuing..."); Logger.Error?.PrintMsg(LogClass.Gpu, $"VK Sync Object {result.ID} failed to signal within 1000ms. Continuing...");
} }
else
{
result.Signalled = true;
}
} }
} }
} }

View File

@@ -60,9 +60,9 @@ namespace Ryujinx.Graphics.Vulkan
commandBuffer, commandBuffer,
srcImage, srcImage,
TextureStorage.DefaultAccessMask, TextureStorage.DefaultAccessMask,
AccessFlags.AccessTransferReadBit, AccessFlags.TransferReadBit,
PipelineStageFlags.PipelineStageAllCommandsBit, PipelineStageFlags.AllCommandsBit,
PipelineStageFlags.PipelineStageTransferBit, PipelineStageFlags.TransferBit,
srcAspectFlags, srcAspectFlags,
srcLayer, srcLayer,
srcLevel, srcLevel,
@@ -103,10 +103,10 @@ namespace Ryujinx.Graphics.Vulkan
api, api,
commandBuffer, commandBuffer,
dstImage, dstImage,
AccessFlags.AccessTransferWriteBit, AccessFlags.TransferWriteBit,
TextureStorage.DefaultAccessMask, TextureStorage.DefaultAccessMask,
PipelineStageFlags.PipelineStageTransferBit, PipelineStageFlags.TransferBit,
PipelineStageFlags.PipelineStageAllCommandsBit, PipelineStageFlags.AllCommandsBit,
dstAspectFlags, dstAspectFlags,
dstLayer, dstLayer,
dstLevel, dstLevel,
@@ -285,9 +285,9 @@ namespace Ryujinx.Graphics.Vulkan
commandBuffer, commandBuffer,
srcImage, srcImage,
TextureStorage.DefaultAccessMask, TextureStorage.DefaultAccessMask,
AccessFlags.AccessTransferReadBit, AccessFlags.TransferReadBit,
PipelineStageFlags.PipelineStageAllCommandsBit, PipelineStageFlags.AllCommandsBit,
PipelineStageFlags.PipelineStageTransferBit, PipelineStageFlags.TransferBit,
srcAspect, srcAspect,
srcViewLayer + srcLayer, srcViewLayer + srcLayer,
srcViewLevel + srcLevel, srcViewLevel + srcLevel,
@@ -345,10 +345,10 @@ namespace Ryujinx.Graphics.Vulkan
api, api,
commandBuffer, commandBuffer,
dstImage, dstImage,
AccessFlags.AccessTransferWriteBit, AccessFlags.TransferWriteBit,
TextureStorage.DefaultAccessMask, TextureStorage.DefaultAccessMask,
PipelineStageFlags.PipelineStageTransferBit, PipelineStageFlags.TransferBit,
PipelineStageFlags.PipelineStageAllCommandsBit, PipelineStageFlags.AllCommandsBit,
dstAspect, dstAspect,
dstViewLayer + dstLayer, dstViewLayer + dstLayer,
dstViewLevel + dstLevel, dstViewLevel + dstLevel,
@@ -370,8 +370,8 @@ namespace Ryujinx.Graphics.Vulkan
{ {
SType = StructureType.SubpassDescriptionDepthStencilResolve, SType = StructureType.SubpassDescriptionDepthStencilResolve,
PDepthStencilResolveAttachment = &dsResolveAttachmentReference, PDepthStencilResolveAttachment = &dsResolveAttachmentReference,
DepthResolveMode = ResolveModeFlags.ResolveModeSampleZeroBit, DepthResolveMode = ResolveModeFlags.SampleZeroBit,
StencilResolveMode = ResolveModeFlags.ResolveModeSampleZeroBit StencilResolveMode = ResolveModeFlags.SampleZeroBit
}; };
var subpass = new SubpassDescription2() var subpass = new SubpassDescription2()

View File

@@ -12,22 +12,22 @@ namespace Ryujinx.Graphics.Vulkan
class TextureStorage : IDisposable class TextureStorage : IDisposable
{ {
private const MemoryPropertyFlags DefaultImageMemoryFlags = private const MemoryPropertyFlags DefaultImageMemoryFlags =
MemoryPropertyFlags.MemoryPropertyDeviceLocalBit; MemoryPropertyFlags.DeviceLocalBit;
private const ImageUsageFlags DefaultUsageFlags = private const ImageUsageFlags DefaultUsageFlags =
ImageUsageFlags.ImageUsageSampledBit | ImageUsageFlags.SampledBit |
ImageUsageFlags.ImageUsageTransferSrcBit | ImageUsageFlags.TransferSrcBit |
ImageUsageFlags.ImageUsageTransferDstBit; ImageUsageFlags.TransferDstBit;
public const AccessFlags DefaultAccessMask = public const AccessFlags DefaultAccessMask =
AccessFlags.AccessShaderReadBit | AccessFlags.ShaderReadBit |
AccessFlags.AccessShaderWriteBit | AccessFlags.ShaderWriteBit |
AccessFlags.AccessColorAttachmentReadBit | AccessFlags.ColorAttachmentReadBit |
AccessFlags.AccessColorAttachmentWriteBit | AccessFlags.ColorAttachmentWriteBit |
AccessFlags.AccessDepthStencilAttachmentReadBit | AccessFlags.DepthStencilAttachmentReadBit |
AccessFlags.AccessDepthStencilAttachmentWriteBit | AccessFlags.DepthStencilAttachmentWriteBit |
AccessFlags.AccessTransferReadBit | AccessFlags.TransferReadBit |
AccessFlags.AccessTransferWriteBit; AccessFlags.TransferWriteBit;
private readonly VulkanRenderer _gd; private readonly VulkanRenderer _gd;
@@ -83,32 +83,32 @@ namespace Ryujinx.Graphics.Vulkan
if (info.Format.IsDepthOrStencil()) if (info.Format.IsDepthOrStencil())
{ {
usage |= ImageUsageFlags.ImageUsageDepthStencilAttachmentBit; usage |= ImageUsageFlags.DepthStencilAttachmentBit;
} }
else if (info.Format.IsRtColorCompatible()) else if (info.Format.IsRtColorCompatible())
{ {
usage |= ImageUsageFlags.ImageUsageColorAttachmentBit; usage |= ImageUsageFlags.ColorAttachmentBit;
} }
if (info.Format.IsImageCompatible()) if (info.Format.IsImageCompatible())
{ {
usage |= ImageUsageFlags.ImageUsageStorageBit; usage |= ImageUsageFlags.StorageBit;
} }
var flags = ImageCreateFlags.ImageCreateMutableFormatBit; var flags = ImageCreateFlags.CreateMutableFormatBit;
// This flag causes mipmapped texture arrays to break on AMD GCN, so for that copy dependencies are forced for aliasing as cube. // This flag causes mipmapped texture arrays to break on AMD GCN, so for that copy dependencies are forced for aliasing as cube.
bool isCube = info.Target == Target.Cubemap || info.Target == Target.CubemapArray; bool isCube = info.Target == Target.Cubemap || info.Target == Target.CubemapArray;
bool cubeCompatible = gd.IsAmdGcn ? isCube : (info.Width == info.Height && layers >= 6); bool cubeCompatible = gd.IsAmdGcn ? isCube : (info.Width == info.Height && layers >= 6);
if (type == ImageType.ImageType2D && cubeCompatible) if (type == ImageType.Type2D && cubeCompatible)
{ {
flags |= ImageCreateFlags.ImageCreateCubeCompatibleBit; flags |= ImageCreateFlags.CreateCubeCompatibleBit;
} }
if (type == ImageType.ImageType3D) if (type == ImageType.Type3D)
{ {
flags |= ImageCreateFlags.ImageCreate2DArrayCompatibleBit; flags |= ImageCreateFlags.Create2DArrayCompatibleBit;
} }
var imageCreateInfo = new ImageCreateInfo() var imageCreateInfo = new ImageCreateInfo()
@@ -290,8 +290,8 @@ namespace Ryujinx.Graphics.Vulkan
_gd.Api.CmdPipelineBarrier( _gd.Api.CmdPipelineBarrier(
cbs.CommandBuffer, cbs.CommandBuffer,
PipelineStageFlags.PipelineStageTopOfPipeBit, PipelineStageFlags.TopOfPipeBit,
PipelineStageFlags.PipelineStageAllCommandsBit, PipelineStageFlags.AllCommandsBit,
0, 0,
0, 0,
null, null,
@@ -308,9 +308,9 @@ namespace Ryujinx.Graphics.Vulkan
public static SampleCountFlags ConvertToSampleCountFlags(uint samples) public static SampleCountFlags ConvertToSampleCountFlags(uint samples)
{ {
if (samples == 0 || samples > (uint)SampleCountFlags.SampleCount64Bit) if (samples == 0 || samples > (uint)SampleCountFlags.Count64Bit)
{ {
return SampleCountFlags.SampleCount1Bit; return SampleCountFlags.Count1Bit;
} }
// Round up to the nearest power of two. // Round up to the nearest power of two.
@@ -428,7 +428,7 @@ namespace Ryujinx.Graphics.Vulkan
public void InsertBarrier(CommandBufferScoped cbs, AccessFlags dstAccessFlags, PipelineStageFlags dstStageFlags) public void InsertBarrier(CommandBufferScoped cbs, AccessFlags dstAccessFlags, PipelineStageFlags dstStageFlags)
{ {
if (_lastModificationAccess != AccessFlags.AccessNoneKhr) if (_lastModificationAccess != AccessFlags.NoneKhr)
{ {
ImageAspectFlags aspectFlags; ImageAspectFlags aspectFlags;
@@ -436,20 +436,20 @@ namespace Ryujinx.Graphics.Vulkan
{ {
if (_info.Format == GAL.Format.S8Uint) if (_info.Format == GAL.Format.S8Uint)
{ {
aspectFlags = ImageAspectFlags.ImageAspectStencilBit; aspectFlags = ImageAspectFlags.StencilBit;
} }
else if (_info.Format == GAL.Format.D16Unorm || _info.Format == GAL.Format.D32Float) else if (_info.Format == GAL.Format.D16Unorm || _info.Format == GAL.Format.D32Float)
{ {
aspectFlags = ImageAspectFlags.ImageAspectDepthBit; aspectFlags = ImageAspectFlags.DepthBit;
} }
else else
{ {
aspectFlags = ImageAspectFlags.ImageAspectDepthBit | ImageAspectFlags.ImageAspectStencilBit; aspectFlags = ImageAspectFlags.DepthBit | ImageAspectFlags.StencilBit;
} }
} }
else else
{ {
aspectFlags = ImageAspectFlags.ImageAspectColorBit; aspectFlags = ImageAspectFlags.ColorBit;
} }
TextureView.InsertImageBarrier( TextureView.InsertImageBarrier(
@@ -466,7 +466,7 @@ namespace Ryujinx.Graphics.Vulkan
_info.GetLayers(), _info.GetLayers(),
_info.Levels); _info.Levels);
_lastModificationAccess = AccessFlags.AccessNoneKhr; _lastModificationAccess = AccessFlags.NoneKhr;
} }
} }

View File

@@ -126,7 +126,7 @@ namespace Ryujinx.Graphics.Vulkan
{ {
subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, levels, (uint)firstLayer, (uint)info.Depth); subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, levels, (uint)firstLayer, (uint)info.Depth);
_imageView2dArray = CreateImageView(identityComponentMapping, subresourceRange, ImageViewType.ImageViewType2DArray); _imageView2dArray = CreateImageView(identityComponentMapping, subresourceRange, ImageViewType.Type2DArray);
} }
Valid = true; Valid = true;
@@ -322,8 +322,8 @@ namespace Ryujinx.Graphics.Vulkan
return; return;
} }
else if (_gd.FormatCapabilities.OptimalFormatSupports(FormatFeatureFlags.FormatFeatureBlitSrcBit, srcFormat) && else if (_gd.FormatCapabilities.OptimalFormatSupports(FormatFeatureFlags.BlitSrcBit, srcFormat) &&
_gd.FormatCapabilities.OptimalFormatSupports(FormatFeatureFlags.FormatFeatureBlitDstBit, dstFormat)) _gd.FormatCapabilities.OptimalFormatSupports(FormatFeatureFlags.BlitDstBit, dstFormat))
{ {
TextureCopy.Blit( TextureCopy.Blit(
_gd.Api, _gd.Api,
@@ -402,8 +402,8 @@ namespace Ryujinx.Graphics.Vulkan
layers, layers,
levels, levels,
linearFilter, linearFilter,
ImageAspectFlags.ImageAspectColorBit, ImageAspectFlags.ColorBit,
ImageAspectFlags.ImageAspectColorBit); ImageAspectFlags.ColorBit);
} }
private static void BlitDepthStencilWithBuffer( private static void BlitDepthStencilWithBuffer(
@@ -471,10 +471,10 @@ namespace Ryujinx.Graphics.Vulkan
gd, gd,
cbs.CommandBuffer, cbs.CommandBuffer,
srcTempBuffer.GetBuffer().Get(cbs, 0, srcSize).Value, srcTempBuffer.GetBuffer().Get(cbs, 0, srcSize).Value,
AccessFlags.AccessTransferWriteBit, AccessFlags.TransferWriteBit,
AccessFlags.AccessTransferReadBit, AccessFlags.TransferReadBit,
PipelineStageFlags.PipelineStageTransferBit, PipelineStageFlags.TransferBit,
PipelineStageFlags.PipelineStageTransferBit, PipelineStageFlags.TransferBit,
0, 0,
srcSize); srcSize);
@@ -498,10 +498,10 @@ namespace Ryujinx.Graphics.Vulkan
gd.Api, gd.Api,
cbs.CommandBuffer, cbs.CommandBuffer,
srcTemp.GetImage().Get(cbs).Value, srcTemp.GetImage().Get(cbs).Value,
AccessFlags.AccessTransferWriteBit, AccessFlags.TransferWriteBit,
AccessFlags.AccessTransferReadBit, AccessFlags.TransferReadBit,
PipelineStageFlags.PipelineStageTransferBit, PipelineStageFlags.TransferBit,
PipelineStageFlags.PipelineStageTransferBit, PipelineStageFlags.TransferBit,
aspectFlags, aspectFlags,
0, 0,
0, 0,
@@ -531,10 +531,10 @@ namespace Ryujinx.Graphics.Vulkan
gd.Api, gd.Api,
cbs.CommandBuffer, cbs.CommandBuffer,
dstTemp.GetImage().Get(cbs).Value, dstTemp.GetImage().Get(cbs).Value,
AccessFlags.AccessTransferWriteBit, AccessFlags.TransferWriteBit,
AccessFlags.AccessTransferReadBit, AccessFlags.TransferReadBit,
PipelineStageFlags.PipelineStageTransferBit, PipelineStageFlags.TransferBit,
PipelineStageFlags.PipelineStageTransferBit, PipelineStageFlags.TransferBit,
aspectFlags, aspectFlags,
0, 0,
0, 0,
@@ -561,10 +561,10 @@ namespace Ryujinx.Graphics.Vulkan
gd, gd,
cbs.CommandBuffer, cbs.CommandBuffer,
dstTempBuffer.GetBuffer().Get(cbs, 0, dstSize).Value, dstTempBuffer.GetBuffer().Get(cbs, 0, dstSize).Value,
AccessFlags.AccessTransferWriteBit, AccessFlags.TransferWriteBit,
AccessFlags.AccessTransferReadBit, AccessFlags.TransferReadBit,
PipelineStageFlags.PipelineStageTransferBit, PipelineStageFlags.TransferBit,
PipelineStageFlags.PipelineStageTransferBit, PipelineStageFlags.TransferBit,
0, 0,
dstSize); dstSize);
@@ -585,8 +585,8 @@ namespace Ryujinx.Graphics.Vulkan
false); false);
} }
SlowBlit(d32SrcStorage, d32DstStorage, ImageAspectFlags.ImageAspectDepthBit); SlowBlit(d32SrcStorage, d32DstStorage, ImageAspectFlags.DepthBit);
SlowBlit(s8SrcStorage, s8DstStorage, ImageAspectFlags.ImageAspectStencilBit); SlowBlit(s8SrcStorage, s8DstStorage, ImageAspectFlags.StencilBit);
} }
public static unsafe void InsertImageBarrier( public static unsafe void InsertImageBarrier(
@@ -631,7 +631,7 @@ namespace Ryujinx.Graphics.Vulkan
private bool SupportsBlitFromD32FS8ToD32FAndS8() private bool SupportsBlitFromD32FS8ToD32FAndS8()
{ {
var formatFeatureFlags = FormatFeatureFlags.FormatFeatureBlitSrcBit | FormatFeatureFlags.FormatFeatureBlitDstBit; var formatFeatureFlags = FormatFeatureFlags.BlitSrcBit | FormatFeatureFlags.BlitDstBit;
return _gd.FormatCapabilities.OptimalFormatSupports(formatFeatureFlags, GAL.Format.D32Float) && return _gd.FormatCapabilities.OptimalFormatSupports(formatFeatureFlags, GAL.Format.D32Float) &&
_gd.FormatCapabilities.OptimalFormatSupports(formatFeatureFlags, GAL.Format.S8Uint); _gd.FormatCapabilities.OptimalFormatSupports(formatFeatureFlags, GAL.Format.S8Uint);
} }
@@ -903,9 +903,9 @@ namespace Ryujinx.Graphics.Vulkan
var aspectFlags = Info.Format.ConvertAspectFlags(); var aspectFlags = Info.Format.ConvertAspectFlags();
if (aspectFlags == (ImageAspectFlags.ImageAspectDepthBit | ImageAspectFlags.ImageAspectStencilBit)) if (aspectFlags == (ImageAspectFlags.DepthBit | ImageAspectFlags.StencilBit))
{ {
aspectFlags = ImageAspectFlags.ImageAspectDepthBit; aspectFlags = ImageAspectFlags.DepthBit;
} }
var sl = new ImageSubresourceLayers( var sl = new ImageSubresourceLayers(
@@ -962,9 +962,9 @@ namespace Ryujinx.Graphics.Vulkan
{ {
var aspectFlags = Info.Format.ConvertAspectFlags(); var aspectFlags = Info.Format.ConvertAspectFlags();
if (aspectFlags == (ImageAspectFlags.ImageAspectDepthBit | ImageAspectFlags.ImageAspectStencilBit)) if (aspectFlags == (ImageAspectFlags.DepthBit | ImageAspectFlags.StencilBit))
{ {
aspectFlags = ImageAspectFlags.ImageAspectDepthBit; aspectFlags = ImageAspectFlags.DepthBit;
} }
var sl = new ImageSubresourceLayers(aspectFlags, (uint)(FirstLevel + dstLevel), (uint)(FirstLayer + dstLayer), 1); var sl = new ImageSubresourceLayers(aspectFlags, (uint)(FirstLevel + dstLevel), (uint)(FirstLayer + dstLayer), 1);

View File

@@ -159,20 +159,20 @@ namespace Ryujinx.Graphics.Vulkan
} }
} }
if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityErrorBitExt)) if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt))
{ {
Logger.Error?.Print(LogClass.Gpu, msg); Logger.Error?.Print(LogClass.Gpu, msg);
//throw new Exception(msg); //throw new Exception(msg);
} }
else if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityWarningBitExt)) else if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.WarningBitExt))
{ {
Logger.Warning?.Print(LogClass.Gpu, msg); Logger.Warning?.Print(LogClass.Gpu, msg);
} }
else if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityInfoBitExt)) else if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.InfoBitExt))
{ {
Logger.Info?.Print(LogClass.Gpu, msg); Logger.Info?.Print(LogClass.Gpu, msg);
} }
else // if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityVerboseBitExt)) else // if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.VerboseBitExt))
{ {
Logger.Debug?.Print(LogClass.Gpu, msg); Logger.Debug?.Print(LogClass.Gpu, msg);
} }
@@ -317,7 +317,7 @@ namespace Ryujinx.Graphics.Vulkan
internal static uint FindSuitableQueueFamily(Vk api, PhysicalDevice physicalDevice, SurfaceKHR surface, out uint queueCount) internal static uint FindSuitableQueueFamily(Vk api, PhysicalDevice physicalDevice, SurfaceKHR surface, out uint queueCount)
{ {
const QueueFlags RequiredFlags = QueueFlags.QueueGraphicsBit | QueueFlags.QueueComputeBit; const QueueFlags RequiredFlags = QueueFlags.GraphicsBit | QueueFlags.ComputeBit;
var khrSurface = new KhrSurface(api.Context); var khrSurface = new KhrSurface(api.Context);
@@ -561,24 +561,24 @@ namespace Ryujinx.Graphics.Vulkan
var filterLogType = logLevel switch var filterLogType = logLevel switch
{ {
GraphicsDebugLevel.Error => DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypeValidationBitExt, GraphicsDebugLevel.Error => DebugUtilsMessageTypeFlagsEXT.ValidationBitExt,
GraphicsDebugLevel.Slowdowns => DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypeValidationBitExt | GraphicsDebugLevel.Slowdowns => DebugUtilsMessageTypeFlagsEXT.ValidationBitExt |
DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypePerformanceBitExt, DebugUtilsMessageTypeFlagsEXT.PerformanceBitExt,
GraphicsDebugLevel.All => DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypeGeneralBitExt | GraphicsDebugLevel.All => DebugUtilsMessageTypeFlagsEXT.GeneralBitExt |
DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypeValidationBitExt | DebugUtilsMessageTypeFlagsEXT.ValidationBitExt |
DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypePerformanceBitExt, DebugUtilsMessageTypeFlagsEXT.PerformanceBitExt,
_ => throw new ArgumentException($"Invalid log level \"{logLevel}\".") _ => throw new ArgumentException($"Invalid log level \"{logLevel}\".")
}; };
var filterLogSeverity = logLevel switch var filterLogSeverity = logLevel switch
{ {
GraphicsDebugLevel.Error => DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityErrorBitExt, GraphicsDebugLevel.Error => DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt,
GraphicsDebugLevel.Slowdowns => DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityErrorBitExt | GraphicsDebugLevel.Slowdowns => DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt |
DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityWarningBitExt, DebugUtilsMessageSeverityFlagsEXT.WarningBitExt,
GraphicsDebugLevel.All => DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityInfoBitExt | GraphicsDebugLevel.All => DebugUtilsMessageSeverityFlagsEXT.InfoBitExt |
DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityWarningBitExt | DebugUtilsMessageSeverityFlagsEXT.WarningBitExt |
DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityVerboseBitExt | DebugUtilsMessageSeverityFlagsEXT.VerboseBitExt |
DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityErrorBitExt, DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt,
_ => throw new ArgumentException($"Invalid log level \"{logLevel}\".") _ => throw new ArgumentException($"Invalid log level \"{logLevel}\".")
}; };

View File

@@ -341,11 +341,11 @@ namespace Ryujinx.Graphics.Vulkan
public unsafe Capabilities GetCapabilities() public unsafe Capabilities GetCapabilities()
{ {
FormatFeatureFlags compressedFormatFeatureFlags = FormatFeatureFlags compressedFormatFeatureFlags =
FormatFeatureFlags.FormatFeatureSampledImageBit | FormatFeatureFlags.SampledImageBit |
FormatFeatureFlags.FormatFeatureSampledImageFilterLinearBit | FormatFeatureFlags.SampledImageFilterLinearBit |
FormatFeatureFlags.FormatFeatureBlitSrcBit | FormatFeatureFlags.BlitSrcBit |
FormatFeatureFlags.FormatFeatureTransferSrcBit | FormatFeatureFlags.TransferSrcBit |
FormatFeatureFlags.FormatFeatureTransferDstBit; FormatFeatureFlags.TransferDstBit;
bool supportsBc123CompressionFormat = FormatCapabilities.OptimalFormatsSupport(compressedFormatFeatureFlags, bool supportsBc123CompressionFormat = FormatCapabilities.OptimalFormatsSupport(compressedFormatFeatureFlags,
GAL.Format.Bc1RgbaSrgb, GAL.Format.Bc1RgbaSrgb,
@@ -565,6 +565,11 @@ namespace Ryujinx.Graphics.Vulkan
_syncManager.Wait(id); _syncManager.Wait(id);
} }
public ulong GetCurrentSync()
{
return _syncManager.GetCurrent();
}
public void Screenshot() public void Screenshot()
{ {
_window.ScreenCaptureRequested = true; _window.ScreenCaptureRequested = true;

View File

@@ -109,11 +109,11 @@ namespace Ryujinx.Graphics.Vulkan
ImageFormat = surfaceFormat.Format, ImageFormat = surfaceFormat.Format,
ImageColorSpace = surfaceFormat.ColorSpace, ImageColorSpace = surfaceFormat.ColorSpace,
ImageExtent = extent, ImageExtent = extent,
ImageUsage = ImageUsageFlags.ImageUsageColorAttachmentBit | ImageUsageFlags.ImageUsageTransferDstBit, ImageUsage = ImageUsageFlags.ColorAttachmentBit | ImageUsageFlags.TransferDstBit,
ImageSharingMode = SharingMode.Exclusive, ImageSharingMode = SharingMode.Exclusive,
ImageArrayLayers = 1, ImageArrayLayers = 1,
PreTransform = capabilities.CurrentTransform, PreTransform = capabilities.CurrentTransform,
CompositeAlpha = CompositeAlphaFlagsKHR.CompositeAlphaOpaqueBitKhr, CompositeAlpha = CompositeAlphaFlagsKHR.OpaqueBitKhr,
PresentMode = ChooseSwapPresentMode(presentModes, _vsyncEnabled), PresentMode = ChooseSwapPresentMode(presentModes, _vsyncEnabled),
Clipped = true, Clipped = true,
OldSwapchain = oldSwapchain OldSwapchain = oldSwapchain
@@ -146,7 +146,7 @@ namespace Ryujinx.Graphics.Vulkan
ComponentSwizzle.B, ComponentSwizzle.B,
ComponentSwizzle.A); ComponentSwizzle.A);
var aspectFlags = ImageAspectFlags.ImageAspectColorBit; var aspectFlags = ImageAspectFlags.ColorBit;
var subresourceRange = new ImageSubresourceRange(aspectFlags, 0, 1, 0, 1); var subresourceRange = new ImageSubresourceRange(aspectFlags, 0, 1, 0, 1);
@@ -154,7 +154,7 @@ namespace Ryujinx.Graphics.Vulkan
{ {
SType = StructureType.ImageViewCreateInfo, SType = StructureType.ImageViewCreateInfo,
Image = swapchainImage, Image = swapchainImage,
ViewType = ImageViewType.ImageViewType2D, ViewType = ImageViewType.Type2D,
Format = format, Format = format,
Components = componentMapping, Components = componentMapping,
SubresourceRange = subresourceRange SubresourceRange = subresourceRange
@@ -168,12 +168,12 @@ namespace Ryujinx.Graphics.Vulkan
{ {
if (availableFormats.Length == 1 && availableFormats[0].Format == VkFormat.Undefined) if (availableFormats.Length == 1 && availableFormats[0].Format == VkFormat.Undefined)
{ {
return new SurfaceFormatKHR(VkFormat.B8G8R8A8Unorm, ColorSpaceKHR.ColorspaceSrgbNonlinearKhr); return new SurfaceFormatKHR(VkFormat.B8G8R8A8Unorm, ColorSpaceKHR.PaceSrgbNonlinearKhr);
} }
foreach (var format in availableFormats) foreach (var format in availableFormats)
{ {
if (format.Format == VkFormat.B8G8R8A8Unorm && format.ColorSpace == ColorSpaceKHR.ColorspaceSrgbNonlinearKhr) if (format.Format == VkFormat.B8G8R8A8Unorm && format.ColorSpace == ColorSpaceKHR.PaceSrgbNonlinearKhr)
{ {
return format; return format;
} }
@@ -184,21 +184,21 @@ namespace Ryujinx.Graphics.Vulkan
private static PresentModeKHR ChooseSwapPresentMode(PresentModeKHR[] availablePresentModes, bool vsyncEnabled) private static PresentModeKHR ChooseSwapPresentMode(PresentModeKHR[] availablePresentModes, bool vsyncEnabled)
{ {
if (!vsyncEnabled && availablePresentModes.Contains(PresentModeKHR.PresentModeImmediateKhr)) if (!vsyncEnabled && availablePresentModes.Contains(PresentModeKHR.ImmediateKhr))
{ {
return PresentModeKHR.PresentModeImmediateKhr; return PresentModeKHR.ImmediateKhr;
} }
else if (availablePresentModes.Contains(PresentModeKHR.PresentModeMailboxKhr)) else if (availablePresentModes.Contains(PresentModeKHR.MailboxKhr))
{ {
return PresentModeKHR.PresentModeMailboxKhr; return PresentModeKHR.MailboxKhr;
} }
else if (availablePresentModes.Contains(PresentModeKHR.PresentModeFifoKhr)) else if (availablePresentModes.Contains(PresentModeKHR.FifoKhr))
{ {
return PresentModeKHR.PresentModeFifoKhr; return PresentModeKHR.FifoKhr;
} }
else else
{ {
return PresentModeKHR.PresentModeFifoKhr; return PresentModeKHR.FifoKhr;
} }
} }
@@ -254,7 +254,7 @@ namespace Ryujinx.Graphics.Vulkan
cbs.CommandBuffer, cbs.CommandBuffer,
swapchainImage, swapchainImage,
0, 0,
AccessFlags.AccessTransferWriteBit, AccessFlags.TransferWriteBit,
ImageLayout.Undefined, ImageLayout.Undefined,
ImageLayout.General); ImageLayout.General);
@@ -339,7 +339,7 @@ namespace Ryujinx.Graphics.Vulkan
_gd.CommandBufferPool.Return( _gd.CommandBufferPool.Return(
cbs, cbs,
stackalloc[] { _imageAvailableSemaphore }, stackalloc[] { _imageAvailableSemaphore },
stackalloc[] { PipelineStageFlags.PipelineStageColorAttachmentOutputBit }, stackalloc[] { PipelineStageFlags.ColorAttachmentOutputBit },
stackalloc[] { _renderFinishedSemaphore }); stackalloc[] { _renderFinishedSemaphore });
// TODO: Present queue. // TODO: Present queue.
@@ -373,7 +373,7 @@ namespace Ryujinx.Graphics.Vulkan
ImageLayout srcLayout, ImageLayout srcLayout,
ImageLayout dstLayout) ImageLayout dstLayout)
{ {
var subresourceRange = new ImageSubresourceRange(ImageAspectFlags.ImageAspectColorBit, 0, 1, 0, 1); var subresourceRange = new ImageSubresourceRange(ImageAspectFlags.ColorBit, 0, 1, 0, 1);
var barrier = new ImageMemoryBarrier() var barrier = new ImageMemoryBarrier()
{ {
@@ -390,8 +390,8 @@ namespace Ryujinx.Graphics.Vulkan
_gd.Api.CmdPipelineBarrier( _gd.Api.CmdPipelineBarrier(
commandBuffer, commandBuffer,
PipelineStageFlags.PipelineStageTopOfPipeBit, PipelineStageFlags.TopOfPipeBit,
PipelineStageFlags.PipelineStageAllCommandsBit, PipelineStageFlags.AllCommandsBit,
0, 0,
0, 0,
null, null,

View File

@@ -69,7 +69,7 @@ namespace Ryujinx.SDL2.Common
if (SDL_Init(SdlInitFlags) != 0) if (SDL_Init(SdlInitFlags) != 0)
{ {
string errorMessage = $"SDL2 initlaization failed with error \"{SDL_GetError()}\""; string errorMessage = $"SDL2 initialization failed with error \"{SDL_GetError()}\"";
Logger.Error?.Print(LogClass.Application, errorMessage); Logger.Error?.Print(LogClass.Application, errorMessage);

View File

@@ -444,7 +444,10 @@ namespace Ryujinx.Ui.App.Common
continue; continue;
} }
ApplicationMetadata appMetadata = LoadAndSaveMetaData(titleId); ApplicationMetadata appMetadata = LoadAndSaveMetaData(titleId, appMetadata =>
{
appMetadata.Title = titleName;
});
if (appMetadata.LastPlayed != "Never" && !DateTime.TryParse(appMetadata.LastPlayed, out _)) if (appMetadata.LastPlayed != "Never" && !DateTime.TryParse(appMetadata.LastPlayed, out _))
{ {

View File

@@ -2,6 +2,7 @@
{ {
public class ApplicationMetadata public class ApplicationMetadata
{ {
public string Title { get; set; }
public bool Favorite { get; set; } public bool Favorite { get; set; }
public double TimePlayed { get; set; } public double TimePlayed { get; set; }
public string LastPlayed { get; set; } = "Never"; public string LastPlayed { get; set; } = "Never";

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>Ryujinx</string>
<key>CFBundleGetInfoString</key>
<string>Ryujinx</string>
<key>CFBundleIconFile</key>
<string>Ryujinx.icns</string>
<key>CFBundleTypeExtensions</key>
<array>
<string>nca</string>
<string>nro</string>
<string>nso</string>
<string>nsp</string>
<string>xci</string>
</array>
<key>CFBundleIdentifier</key>
<string>org.ryujinx.Ryujinx</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleLongVersionString</key>
<string>%%RYUJINX_BUILD_VERSION%%-%%RYUJINX_BUILD_GIT_HASH%%"</string>
<key>CFBundleName</key>
<string>Ryujinx</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.1.0</string>
<key>NSHighResolutionCapable</key>
<true/>
<key>CSResourcesFileMapped</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2018 - 2022 Ryujinx Team and Contributors.</string>
<key>LSMinimumSystemVersion</key>
<string>11.0</string>
</dict>
</plist>

Binary file not shown.

View File

@@ -0,0 +1,609 @@
import argparse
import hashlib
import os
from pathlib import Path
import platform
import shutil
import struct
import subprocess
from typing import List, Optional, Tuple
parser = argparse.ArgumentParser(description="Fixup for MacOS application bundle")
parser.add_argument("input_directory", help="Input directory (Application path)")
parser.add_argument("executable_sub_path", help="Main executable sub path")
# Use Apple LLVM on Darwin, otherwise standard LLVM.
if platform.system() == "Darwin":
OTOOL = "otool"
INSTALL_NAME_TOOL = "install_name_tool"
else:
OTOOL = shutil.which("llvm-otool")
if OTOOL is None:
for llvm_ver in [15, 14, 13]:
otool_path = shutil.which(f"llvm-otool-{llvm_ver}")
if otool_path is not None:
OTOOL = otool_path
INSTALL_NAME_TOOL = shutil.which(f"llvm-install-name-tool-{llvm_ver}")
break
else:
INSTALL_NAME_TOOL = shutil.which("llvm-install-name-tool")
args = parser.parse_args()
def get_dylib_id(dylib_path: Path) -> str:
res = subprocess.check_output([OTOOL, "-D", str(dylib_path.absolute())]).decode(
"utf-8"
)
return res.split("\n")[1]
def get_dylib_dependencies(dylib_path: Path) -> List[str]:
output = (
subprocess.check_output([OTOOL, "-L", str(dylib_path.absolute())])
.decode("utf-8")
.split("\n")[1:]
)
res = []
for line in output:
line = line.strip()
index = line.find(" (compatibility version ")
if index == -1:
continue
line = line[:index]
res.append(line)
return res
def replace_dylib_id(dylib_path: Path, new_id: str):
subprocess.check_call(
[INSTALL_NAME_TOOL, "-id", new_id, str(dylib_path.absolute())]
)
def change_dylib_link(dylib_path: Path, old: str, new: str):
subprocess.check_call(
[INSTALL_NAME_TOOL, "-change", old, new, str(dylib_path.absolute())]
)
def add_dylib_rpath(dylib_path: Path, rpath: str):
subprocess.check_call(
[INSTALL_NAME_TOOL, "-add_rpath", rpath, str(dylib_path.absolute())]
)
def fixup_dylib(
dylib_path: Path,
replacement_path: str,
search_path: List[str],
content_directory: Path,
):
dylib_id = get_dylib_id(dylib_path)
new_dylib_id = replacement_path + "/" + os.path.basename(dylib_id)
replace_dylib_id(dylib_path, new_dylib_id)
dylib_dependencies = get_dylib_dependencies(dylib_path)
dylib_new_mapping = {}
for dylib_dependency in dylib_dependencies:
if (
not dylib_dependency.startswith("@executable_path")
and not dylib_dependency.startswith("/usr/lib")
and not dylib_dependency.startswith("/System/Library")
):
dylib_dependency_name = os.path.basename(dylib_dependency)
library_found = False
for library_base_path in search_path:
lib_path = Path(os.path.join(library_base_path, dylib_dependency_name))
if lib_path.exists():
target_replacement_path = get_path_related_to_target_exec(
content_directory, lib_path
)
dylib_new_mapping[dylib_dependency] = (
target_replacement_path
+ "/"
+ os.path.basename(dylib_dependency)
)
library_found = True
if not library_found:
raise Exception(
f"{dylib_id}: Cannot find dependency {dylib_dependency_name} for fixup"
)
for key in dylib_new_mapping:
change_dylib_link(dylib_path, key, dylib_new_mapping[key])
FILE_TYPE_ASSEMBLY = 1
ALIGN_REQUIREMENTS = 4096
def parse_embedded_string(data: bytes) -> Tuple[bytes, str]:
first_byte = data[0]
if (first_byte & 0x80) == 0:
size = first_byte
data = data[1:]
else:
second_byte = data[1]
assert (second_byte & 0x80) == 0
size = (second_byte << 7) | (first_byte & 0x7F)
data = data[2:]
res = data[:size].decode("utf-8")
data = data[size:]
return (data, res)
def write_embedded_string(file, string: str):
raw_str = string.encode("utf-8")
raw_str_len = len(raw_str)
assert raw_str_len < 0x7FFF
if raw_str_len > 0x7F:
file.write(struct.pack("b", raw_str_len & 0x7F | 0x80))
file.write(struct.pack("b", raw_str_len >> 7))
else:
file.write(struct.pack("b", raw_str_len))
file.write(raw_str)
class BundleFileEntry(object):
offset: int
size: int
compressed_size: int
file_type: int
relative_path: str
data: bytes
def __init__(
self,
offset: int,
size: int,
compressed_size: int,
file_type: int,
relative_path: str,
data: bytes,
) -> None:
self.offset = offset
self.size = size
self.compressed_size = compressed_size
self.file_type = file_type
self.relative_path = relative_path
self.data = data
def write(self, file):
self.offset = file.tell()
if (
self.file_type == FILE_TYPE_ASSEMBLY
and (self.offset % ALIGN_REQUIREMENTS) != 0
):
padding_size = ALIGN_REQUIREMENTS - (self.offset % ALIGN_REQUIREMENTS)
file.write(b"\0" * padding_size)
self.offset += padding_size
file.write(self.data)
def write_header(self, file):
file.write(
struct.pack(
"QQQb", self.offset, self.size, self.compressed_size, self.file_type
)
)
write_embedded_string(file, self.relative_path)
class BundleManifest(object):
major: int
minor: int
bundle_id: str
deps_json: BundleFileEntry
runtimeconfig_json: BundleFileEntry
flags: int
files: List[BundleFileEntry]
def __init__(
self,
major: int,
minor: int,
bundle_id: str,
deps_json: BundleFileEntry,
runtimeconfig_json: BundleFileEntry,
flags: int,
files: List[BundleFileEntry],
) -> None:
self.major = major
self.minor = minor
self.bundle_id = bundle_id
self.deps_json = deps_json
self.runtimeconfig_json = runtimeconfig_json
self.flags = flags
self.files = files
def write(self, file) -> int:
for bundle_file in self.files:
bundle_file.write(file)
bundle_header_offset = file.tell()
file.write(struct.pack("iiI", self.major, self.minor, len(self.files)))
write_embedded_string(file, self.bundle_id)
if self.deps_json is not None:
deps_json_location_offset = self.deps_json.offset
deps_json_location_size = self.deps_json.size
else:
deps_json_location_offset = 0
deps_json_location_size = 0
if self.runtimeconfig_json is not None:
runtimeconfig_json_location_offset = self.runtimeconfig_json.offset
runtimeconfig_json_location_size = self.runtimeconfig_json.size
else:
runtimeconfig_json_location_offset = 0
runtimeconfig_json_location_size = 0
file.write(
struct.pack("qq", deps_json_location_offset, deps_json_location_size)
)
file.write(
struct.pack(
"qq",
runtimeconfig_json_location_offset,
runtimeconfig_json_location_size,
)
)
file.write(struct.pack("q", self.flags))
for bundle_file in self.files:
bundle_file.write_header(file)
return bundle_header_offset
def read_file_entry(
raw_data: bytes, header_bytes: bytes
) -> Tuple[bytes, BundleFileEntry]:
(
offset,
size,
compressed_size,
file_type,
) = struct.unpack("QQQb", header_bytes[:0x19])
(header_bytes, relative_path) = parse_embedded_string(header_bytes[0x19:])
target_size = compressed_size
if target_size == 0:
target_size = size
return (
header_bytes,
BundleFileEntry(
offset,
size,
compressed_size,
file_type,
relative_path,
raw_data[offset : offset + target_size],
),
)
def get_dotnet_bundle_data(data: bytes) -> Optional[Tuple[int, int, BundleManifest]]:
offset = data.find(hashlib.sha256(b".net core bundle\n").digest())
if offset == -1:
return None
raw_header_offset = data[offset - 8 : offset]
(header_offset,) = struct.unpack("q", raw_header_offset)
header_bytes = data[header_offset:]
(
major,
minor,
files_count,
) = struct.unpack("iiI", header_bytes[:0xC])
header_bytes = header_bytes[0xC:]
(header_bytes, bundle_id) = parse_embedded_string(header_bytes)
# v2 header
(
deps_json_location_offset,
deps_json_location_size,
) = struct.unpack("qq", header_bytes[:0x10])
(
runtimeconfig_json_location_offset,
runtimeconfig_json_location_size,
) = struct.unpack("qq", header_bytes[0x10:0x20])
(flags,) = struct.unpack("q", header_bytes[0x20:0x28])
header_bytes = header_bytes[0x28:]
files = []
deps_json = None
runtimeconfig_json = None
for _ in range(files_count):
(header_bytes, file_entry) = read_file_entry(data, header_bytes)
files.append(file_entry)
if file_entry.offset == deps_json_location_offset:
deps_json = file_entry
elif file_entry.offset == runtimeconfig_json_location_offset:
runtimeconfig_json = file_entry
file_entry = files[0]
return (
file_entry.offset,
header_offset,
BundleManifest(
major, minor, bundle_id, deps_json, runtimeconfig_json, flags, files
),
)
LC_SYMTAB = 0x2
LC_SEGMENT_64 = 0x19
LC_CODE_SIGNATURE = 0x1D
def fixup_linkedit(file, data: bytes, new_size: int):
offset = 0
(
macho_magic,
macho_cputype,
macho_cpusubtype,
macho_filetype,
macho_ncmds,
macho_sizeofcmds,
macho_flags,
macho_reserved,
) = struct.unpack("IiiIIIII", data[offset : offset + 0x20])
offset += 0x20
linkedit_offset = None
symtab_offset = None
codesign_offset = None
for _ in range(macho_ncmds):
(cmd, cmdsize) = struct.unpack("II", data[offset : offset + 8])
if cmd == LC_SEGMENT_64:
(
cmd,
cmdsize,
segname_raw,
vmaddr,
vmsize,
fileoff,
filesize,
maxprot,
initprot,
nsects,
flags,
) = struct.unpack("II16sQQQQiiII", data[offset : offset + 72])
segname = segname_raw.decode("utf-8").split("\0")[0]
if segname == "__LINKEDIT":
linkedit_offset = offset
elif cmd == LC_SYMTAB:
symtab_offset = offset
elif cmd == LC_CODE_SIGNATURE:
codesign_offset = offset
offset += cmdsize
pass
assert linkedit_offset is not None and symtab_offset is not None
# If there is a codesign section, clean it up.
if codesign_offset is not None:
(
codesign_cmd,
codesign_cmdsize,
codesign_dataoff,
codesign_datasize,
) = struct.unpack("IIII", data[codesign_offset : codesign_offset + 16])
file.seek(codesign_offset)
file.write(b"\0" * codesign_cmdsize)
macho_ncmds -= 1
macho_sizeofcmds -= codesign_cmdsize
file.seek(0)
file.write(
struct.pack(
"IiiIIIII",
macho_magic,
macho_cputype,
macho_cpusubtype,
macho_filetype,
macho_ncmds,
macho_sizeofcmds,
macho_flags,
macho_reserved,
)
)
file.seek(codesign_dataoff)
file.write(b"\0" * codesign_datasize)
(
symtab_cmd,
symtab_cmdsize,
symtab_symoff,
symtab_nsyms,
symtab_stroff,
symtab_strsize,
) = struct.unpack("IIIIII", data[symtab_offset : symtab_offset + 24])
symtab_strsize = new_size - symtab_stroff
new_symtab = struct.pack(
"IIIIII",
symtab_cmd,
symtab_cmdsize,
symtab_symoff,
symtab_nsyms,
symtab_stroff,
symtab_strsize,
)
file.seek(symtab_offset)
file.write(new_symtab)
(
linkedit_cmd,
linkedit_cmdsize,
linkedit_segname_raw,
linkedit_vmaddr,
linkedit_vmsize,
linkedit_fileoff,
linkedit_filesize,
linkedit_maxprot,
linkedit_initprot,
linkedit_nsects,
linkedit_flags,
) = struct.unpack("II16sQQQQiiII", data[linkedit_offset : linkedit_offset + 72])
linkedit_filesize = new_size - linkedit_fileoff
linkedit_vmsize = linkedit_filesize
new_linkedit = struct.pack(
"II16sQQQQiiII",
linkedit_cmd,
linkedit_cmdsize,
linkedit_segname_raw,
linkedit_vmaddr,
linkedit_vmsize,
linkedit_fileoff,
linkedit_filesize,
linkedit_maxprot,
linkedit_initprot,
linkedit_nsects,
linkedit_flags,
)
file.seek(linkedit_offset)
file.write(new_linkedit)
def write_bundle_data(
output,
old_bundle_base_offset: int,
new_bundle_base_offset: int,
bundle: BundleManifest,
) -> int:
# Write bundle data
bundle_header_offset = bundle.write(output)
total_size = output.tell()
# Patch the header position
offset = file_data.find(hashlib.sha256(b".net core bundle\n").digest())
output.seek(offset - 8)
output.write(struct.pack("q", bundle_header_offset))
return total_size - new_bundle_base_offset
input_directory: Path = Path(args.input_directory)
content_directory: Path = Path(os.path.join(args.input_directory, "Contents"))
executable_path: Path = Path(os.path.join(content_directory, args.executable_sub_path))
def get_path_related_to_other_path(a: Path, b: Path) -> str:
temp = b
parts = []
while temp != a:
temp = temp.parent
parts.append(temp.name)
parts.remove(parts[-1])
parts.reverse()
return "/".join(parts)
def get_path_related_to_target_exec(input_directory: Path, path: Path):
return "@executable_path/../" + get_path_related_to_other_path(
input_directory, path
)
search_path = [
Path(os.path.join(content_directory, "Frameworks")),
Path(os.path.join(content_directory, "Resources/lib")),
]
for path in content_directory.rglob("**/*.dylib"):
current_search_path = [path.parent]
current_search_path.extend(search_path)
fixup_dylib(
path,
get_path_related_to_target_exec(content_directory, path),
current_search_path,
content_directory,
)
for path in content_directory.rglob("**/*.so"):
current_search_path = [path.parent]
current_search_path.extend(search_path)
fixup_dylib(
path,
get_path_related_to_target_exec(content_directory, path),
current_search_path,
content_directory,
)
with open(executable_path, "rb") as input:
file_data = input.read()
(bundle_base_offset, bundle_header_offset, bundle) = get_dotnet_bundle_data(file_data)
add_dylib_rpath(executable_path, "@executable_path/../Frameworks/")
# Recent "vanilla" version of LLVM (LLVM 13 and upper) seems to really dislike how .NET package its assemblies.
# As a result, after execution of install_name_tool it will have "fixed" the symtab resulting in a missing .NET bundle...
# To mitigate that, we check if the bundle offset inside the binary is valid after install_name_tool and readd .NET bundle if not.
output_file_size = os.stat(executable_path).st_size
if output_file_size < bundle_header_offset:
print("LLVM broke the .NET bundle, readding bundle data...")
with open(executable_path, "r+b") as output:
file_data = output.read()
bundle_data_size = write_bundle_data(
output, bundle_base_offset, output_file_size, bundle
)
# Now patch the __LINKEDIT section
new_size = output_file_size + bundle_data_size
fixup_linkedit(output, file_data, new_size)

View File

@@ -0,0 +1,95 @@
import argparse
import os
from pathlib import Path
import platform
import shutil
import subprocess
parser = argparse.ArgumentParser(
description="Construct Universal dylibs for nuget package"
)
parser.add_argument(
"arm64_input_directory", help="ARM64 Input directory containing dylibs"
)
parser.add_argument(
"x86_64_input_directory", help="x86_64 Input directory containing dylibs"
)
parser.add_argument("output_directory", help="Output directory")
parser.add_argument("rglob", help="rglob")
args = parser.parse_args()
# Use Apple LLVM on Darwin, otherwise standard LLVM.
if platform.system() == "Darwin":
LIPO = "lipo"
else:
LIPO = shutil.which("llvm-lipo")
if LIPO is None:
for llvm_ver in [15, 14, 13]:
lipo_path = shutil.which(f"llvm-lipo-{llvm_ver}")
if lipo_path is not None:
LIPO = lipo_path
break
if LIPO is None:
raise Exception("Cannot find a valid location for LLVM lipo!")
arm64_input_directory: Path = Path(args.arm64_input_directory)
x86_64_input_directory: Path = Path(args.x86_64_input_directory)
output_directory: Path = Path(args.output_directory)
rglob = args.rglob
def get_new_name(
input_directory: Path, output_directory: str, input_dylib_path: Path
) -> Path:
input_component = str(input_dylib_path).replace(str(input_directory), "")[1:]
return Path(os.path.join(output_directory, input_component))
def is_fat_file(dylib_path: Path) -> str:
res = subprocess.check_output([LIPO, "-info", str(dylib_path.absolute())]).decode(
"utf-8"
)
return not res.split("\n")[0].startswith("Non-fat file")
def construct_universal_dylib(
arm64_input_dylib_path: Path, x86_64_input_dylib_path: Path, output_dylib_path: Path
):
if output_dylib_path.exists() or output_dylib_path.is_symlink():
os.remove(output_dylib_path)
os.makedirs(output_dylib_path.parent, exist_ok=True)
if arm64_input_dylib_path.is_symlink():
os.symlink(
os.path.basename(arm64_input_dylib_path.resolve()), output_dylib_path
)
else:
if is_fat_file(arm64_input_dylib_path) or not x86_64_input_dylib_path.exists():
with open(output_dylib_path, "wb") as dst:
with open(arm64_input_dylib_path, "rb") as src:
dst.write(src.read())
else:
subprocess.check_call(
[
LIPO,
str(arm64_input_dylib_path.absolute()),
str(x86_64_input_dylib_path.absolute()),
"-output",
str(output_dylib_path.absolute()),
"-create",
]
)
print(rglob)
for path in arm64_input_directory.rglob("**/*.dylib"):
construct_universal_dylib(
path,
get_new_name(arm64_input_directory, x86_64_input_directory, path),
get_new_name(arm64_input_directory, output_directory, path),
)

View File

@@ -0,0 +1,51 @@
#!/bin/bash
set -e
PUBLISH_DIRECTORY=$1
OUTPUT_DIRECTORY=$2
ENTITLEMENTS_FILE_PATH=$3
APP_BUNDLE_DIRECTORY=$OUTPUT_DIRECTORY/Ryujinx.app
rm -rf $APP_BUNDLE_DIRECTORY
mkdir -p $APP_BUNDLE_DIRECTORY/Contents
mkdir $APP_BUNDLE_DIRECTORY/Contents/Frameworks
mkdir $APP_BUNDLE_DIRECTORY/Contents/MacOS
mkdir $APP_BUNDLE_DIRECTORY/Contents/Resources
# Copy executables first
cp $PUBLISH_DIRECTORY/Ryujinx.Ava $APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx
chmod u+x $APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx
# Then all libraries
cp $PUBLISH_DIRECTORY/*.dylib $APP_BUNDLE_DIRECTORY/Contents/Frameworks
# Then resources
cp Info.plist $APP_BUNDLE_DIRECTORY/Contents
cp Ryujinx.icns $APP_BUNDLE_DIRECTORY/Contents/Resources/Ryujinx.icns
cp -r $PUBLISH_DIRECTORY/THIRDPARTY.md $APP_BUNDLE_DIRECTORY/Contents/Resources
echo -n "APPL????" > $APP_BUNDLE_DIRECTORY/Contents/PkgInfo
# Fixup libraries and executable
python3 bundle_fix_up.py $APP_BUNDLE_DIRECTORY MacOS/Ryujinx
# Now sign it
if ! [ -x "$(command -v codesign)" ];
then
if ! [ -x "$(command -v rcodesign)" ];
then
echo "Cannot find rcodesign on your system, please install rcodesign."
exit 1
fi
# NOTE: Currently require https://github.com/indygreg/apple-platform-rs/pull/44 to work on other OSes.
# cargo install --git "https://github.com/marysaka/apple-platform-rs" --branch "fix/adhoc-app-bundle" apple-codesign --bin "rcodesign"
echo "Usign rcodesign for ad-hoc signing"
rcodesign sign --entitlements-xml-path $ENTITLEMENTS_FILE_PATH $APP_BUNDLE_DIRECTORY
else
echo "Usign codesign for ad-hoc signing"
codesign --entitlements $ENTITLEMENTS_FILE_PATH -f --deep -s - $APP_BUNDLE_DIRECTORY
fi

View File

@@ -0,0 +1,105 @@
#!/bin/bash
set -e
if [ "$#" -ne 6 ]; then
echo "usage <BASE_DIR> <TEMP_DIRECTORY> <OUTPUT_DIRECTORY> <ENTITLEMENTS_FILE_PATH> <VERSION> <SOURCE_REVISION_ID>"
exit 1
fi
mkdir -p $1
mkdir -p $2
mkdir -p $3
BASE_DIR=$(readlink -f $1)
TEMP_DIRECTORY=$(readlink -f $2)
OUTPUT_DIRECTORY=$(readlink -f $3)
ENTITLEMENTS_FILE_PATH=$(readlink -f $4)
VERSION=$5
SOURCE_REVISION_ID=$6
RELEASE_TAR_FILE_NAME=Ryujinx-$VERSION-macos_universal.app.tar
ARM64_APP_BUNDLE=$TEMP_DIRECTORY/output_arm64/Ryujinx.app
X64_APP_BUNDLE=$TEMP_DIRECTORY/output_x64/Ryujinx.app
UNIVERSAL_APP_BUNDLE=$OUTPUT_DIRECTORY/Ryujinx.app
EXECUTABLE_SUB_PATH=Contents/MacOS/Ryujinx
rm -rf $TEMP_DIRECTORY
mkdir -p $TEMP_DIRECTORY
DOTNET_COMMON_ARGS="-p:DebugType=embedded -p:Version=$VERSION -p:SourceRevisionId=$SOURCE_REVISION_ID -p:ExtraDefineConstants=DISABLE_UPDATER --self-contained true"
dotnet restore
dotnet build -c Release Ryujinx.Ava
dotnet publish -c Release -r osx-arm64 -o $TEMP_DIRECTORY/publish_arm64 $DOTNET_COMMON_ARGS Ryujinx.Ava
dotnet publish -c Release -r osx-x64 -o $TEMP_DIRECTORY/publish_x64 $DOTNET_COMMON_ARGS Ryujinx.Ava
# Get ride of the support library for ARMeilleur for x64 (that's only for arm64)
rm -rf $TEMP_DIRECTORY/publish_x64/libarmeilleure-jitsupport.dylib
# Get ride of libsoundio from arm64 builds as we don't have a arm64 variant
# TODO: remove this once done
rm -rf $TEMP_DIRECTORY/publish_arm64/libsoundio.dylib
pushd $BASE_DIR/distribution/macos
./create_app_bundle.sh $TEMP_DIRECTORY/publish_x64 $TEMP_DIRECTORY/output_x64 $ENTITLEMENTS_FILE_PATH
./create_app_bundle.sh $TEMP_DIRECTORY/publish_arm64 $TEMP_DIRECTORY/output_arm64 $ENTITLEMENTS_FILE_PATH
popd
rm -rf $UNIVERSAL_APP_BUNDLE
mkdir -p $OUTPUT_DIRECTORY
# Let's copy one of the two different app bundle and remove the executable
cp -R $ARM64_APP_BUNDLE $UNIVERSAL_APP_BUNDLE
rm $UNIVERSAL_APP_BUNDLE/$EXECUTABLE_SUB_PATH
# Make it libraries universal
python3 $BASE_DIR/distribution/macos/construct_universal_dylib.py $ARM64_APP_BUNDLE $X64_APP_BUNDLE $UNIVERSAL_APP_BUNDLE "**/*.dylib"
if ! [ -x "$(command -v lipo)" ];
then
if ! [ -x "$(command -v llvm-lipo-14)" ];
then
LIPO=llvm-lipo
else
LIPO=llvm-lipo-14
fi
else
LIPO=lipo
fi
# Make it the executable universal
$LIPO $ARM64_APP_BUNDLE/$EXECUTABLE_SUB_PATH $X64_APP_BUNDLE/$EXECUTABLE_SUB_PATH -output $UNIVERSAL_APP_BUNDLE/$EXECUTABLE_SUB_PATH -create
# Patch up the Info.plist to have appropriate version
sed -r -i.bck "s/\%\%RYUJINX_BUILD_VERSION\%\%/$VERSION/g;" $UNIVERSAL_APP_BUNDLE/Contents/Info.plist
sed -r -i.bck "s/\%\%RYUJINX_BUILD_GIT_HASH\%\%/$SOURCE_REVISION_ID/g;" $UNIVERSAL_APP_BUNDLE/Contents/Info.plist
rm $UNIVERSAL_APP_BUNDLE/Contents/Info.plist.bck
# Now sign it
if ! [ -x "$(command -v codesign)" ];
then
if ! [ -x "$(command -v rcodesign)" ];
then
echo "Cannot find rcodesign on your system, please install rcodesign."
exit 1
fi
# NOTE: Currently require https://github.com/indygreg/apple-platform-rs/pull/44 to work on other OSes.
# cargo install --git "https://github.com/marysaka/apple-platform-rs" --branch "fix/adhoc-app-bundle" apple-codesign --bin "rcodesign"
echo "Usign rcodesign for ad-hoc signing"
rcodesign sign --entitlements-xml-path $ENTITLEMENTS_FILE_PATH $UNIVERSAL_APP_BUNDLE
else
echo "Usign codesign for ad-hoc signing"
codesign --entitlements $ENTITLEMENTS_FILE_PATH -f --deep -s - $UNIVERSAL_APP_BUNDLE
fi
echo "Creating archive"
pushd $OUTPUT_DIRECTORY
tar --exclude "Ryujinx.app/Contents/MacOS/Ryujinx" -cvf $RELEASE_TAR_FILE_NAME Ryujinx.app 1> /dev/null
python3 $BASE_DIR/distribution/misc/add_tar_exec.py $RELEASE_TAR_FILE_NAME "Ryujinx.app/Contents/MacOS/Ryujinx" "Ryujinx.app/Contents/MacOS/Ryujinx"
gzip -9 < $RELEASE_TAR_FILE_NAME > $RELEASE_TAR_FILE_NAME.gz
rm $RELEASE_TAR_FILE_NAME
popd
echo "Done"

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.cs.disable-executable-page-protection</key>
<true/>
<key>com.apple.security.cs.debugger</key>
<true/>
<key>com.apple.security.get-task-allow</key>
<true/>
<key>com.apple.security.hypervisor</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,24 @@
import argparse
from io import BytesIO
import tarfile
parser = argparse.ArgumentParser(
description="Add the main binary to a tar and force it to be executable"
)
parser.add_argument("input_tar_file", help="input tar file")
parser.add_argument("main_binary_path", help="Main executable path")
parser.add_argument("main_binary_tar_path", help="Main executable tar path")
args = parser.parse_args()
input_tar_file = args.input_tar_file
main_binary_path = args.main_binary_path
main_binary_tar_path = args.main_binary_tar_path
with open(main_binary_path, "rb") as f:
with tarfile.open(input_tar_file, "a") as tar:
data = f.read()
tar_info = tarfile.TarInfo(main_binary_tar_path)
tar_info.mode = 0o755
tar_info.size = len(data)
tar.addfile(tar_info, BytesIO(data))