Compare commits
17 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
933e5144a9 | ||
|
73a42c85c4 | ||
|
39ba11054b | ||
|
c250e3392c | ||
|
e56b069081 | ||
|
204c031fef | ||
|
d9053bbe37 | ||
|
c25e8427aa | ||
|
21a081b185 | ||
|
b540ea80d1 | ||
|
d692a9b83e | ||
|
9677ddaa5d | ||
|
ce92e8cd04 | ||
|
456fc04007 | ||
|
458452279c | ||
|
817b89767a | ||
|
3fb583c98c |
@@ -21,6 +21,10 @@
|
||||
<img src="https://github.com/Ryujinx/Ryujinx/actions/workflows/release.yml/badge.svg"
|
||||
alt="">
|
||||
</a>
|
||||
<a href="https://crwd.in/ryujinx">
|
||||
<img src="https://badges.crowdin.net/ryujinx/localized.svg"
|
||||
alt="">
|
||||
</a>
|
||||
<a href="https://discord.com/invite/VkQYXAZ">
|
||||
<img src="https://img.shields.io/discord/410208534861447168?color=5865F2&label=Ryujinx&logo=discord&logoColor=white"
|
||||
alt="Discord">
|
||||
@@ -48,6 +52,8 @@ See our [Setup & Configuration Guide](https://github.com/Ryujinx/Ryujinx/wiki/Ry
|
||||
For our Local Wireless and LAN builds, see our [Multiplayer: Local Play/Local Wireless Guide
|
||||
](https://github.com/Ryujinx/Ryujinx/wiki/Multiplayer-(LDN-Local-Wireless)-Guide).
|
||||
|
||||
Avalonia UI comes with translations for various languages. See [Crowdin](https://crwd.in/ryujinx) for more information.
|
||||
|
||||
## Latest build
|
||||
|
||||
These builds are compiled automatically for each commit on the master branch. While we strive to ensure optimal stability and performance prior to pushing an update, our automated builds **may be unstable or completely broken.**
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using Ryujinx.Audio.Common;
|
||||
using Ryujinx.Audio.Integration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Memory;
|
||||
using Ryujinx.SDL2.Common;
|
||||
using System;
|
||||
@@ -112,6 +113,9 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||
|
||||
if (device == 0)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application,
|
||||
$"SDL2 open audio device initialization failed with error \"{SDL_GetError()}\"");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -119,6 +123,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||
|
||||
if (!isValid)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, "SDL2 open audio device is not valid");
|
||||
SDL_CloseAudioDevice(device);
|
||||
|
||||
return 0;
|
||||
|
@@ -116,6 +116,11 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||
};
|
||||
}
|
||||
|
||||
public bool HasRemainingCommands(int sessionId)
|
||||
{
|
||||
return _sessionCommandList[sessionId] != null;
|
||||
}
|
||||
|
||||
public void Signal()
|
||||
{
|
||||
_mailbox.SendMessage(MailboxMessage.RenderStart);
|
||||
|
@@ -670,14 +670,21 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
{
|
||||
_terminationEvent.Reset();
|
||||
|
||||
GenerateCommandList(out CommandList commands);
|
||||
if (!_manager.Processor.HasRemainingCommands(_sessionId))
|
||||
{
|
||||
GenerateCommandList(out CommandList commands);
|
||||
|
||||
_manager.Processor.Send(_sessionId,
|
||||
commands,
|
||||
GetMaxAllocatedTimeForDsp(),
|
||||
_appletResourceId);
|
||||
_manager.Processor.Send(_sessionId,
|
||||
commands,
|
||||
GetMaxAllocatedTimeForDsp(),
|
||||
_appletResourceId);
|
||||
|
||||
_systemEvent.Signal();
|
||||
_systemEvent.Signal();
|
||||
}
|
||||
else
|
||||
{
|
||||
_isDspRunningBehind = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@@ -168,6 +168,8 @@
|
||||
"SettingsTabInputDirectKeyboardAccess": "Direkter Tastaturzugriff",
|
||||
"SettingsButtonSave": "Speichern",
|
||||
"SettingsButtonClose": "Schließen",
|
||||
"SettingsButtonOk": "OK",
|
||||
"SettingsButtonCancel": "Abbrechen",
|
||||
"SettingsButtonApply": "Übernehmen",
|
||||
"ControllerSettingsPlayer": "Spieler",
|
||||
"ControllerSettingsPlayer1": "Spieler 1",
|
||||
|
@@ -168,6 +168,8 @@
|
||||
"SettingsTabInputDirectKeyboardAccess": "Άμεση Πρόσβαση στο Πληκτρολόγιο",
|
||||
"SettingsButtonSave": "Αποθήκευση",
|
||||
"SettingsButtonClose": "Κλείσιμο",
|
||||
"SettingsButtonOk": "OK",
|
||||
"SettingsButtonCancel": "Ακύρωση",
|
||||
"SettingsButtonApply": "Εφαρμογή",
|
||||
"ControllerSettingsPlayer": "Παίχτης",
|
||||
"ControllerSettingsPlayer1": "Παίχτης 1",
|
||||
|
@@ -168,6 +168,8 @@
|
||||
"SettingsTabInputDirectKeyboardAccess": "Direct Keyboard Access",
|
||||
"SettingsButtonSave": "Save",
|
||||
"SettingsButtonClose": "Close",
|
||||
"SettingsButtonOk": "OK",
|
||||
"SettingsButtonCancel": "Cancel",
|
||||
"SettingsButtonApply": "Apply",
|
||||
"ControllerSettingsPlayer": "Player",
|
||||
"ControllerSettingsPlayer1": "Player 1",
|
||||
@@ -594,7 +596,18 @@
|
||||
"RyujinxUpdaterMessage": "Do you want to update Ryujinx to the latest version?",
|
||||
"SettingsTabHotkeysVolumeUpHotkey": "Increase Volume:",
|
||||
"SettingsTabHotkeysVolumeDownHotkey": "Decrease Volume:",
|
||||
"VolumeShort": "Vol",
|
||||
"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"
|
||||
}
|
||||
|
@@ -168,6 +168,8 @@
|
||||
"SettingsTabInputDirectKeyboardAccess": "Acceso directo al teclado",
|
||||
"SettingsButtonSave": "Guardar",
|
||||
"SettingsButtonClose": "Cerrar",
|
||||
"SettingsButtonOk": "OK",
|
||||
"SettingsButtonCancel": "Cancelar",
|
||||
"SettingsButtonApply": "Aplicar",
|
||||
"ControllerSettingsPlayer": "Jugador",
|
||||
"ControllerSettingsPlayer1": "Jugador 1",
|
||||
|
@@ -161,6 +161,8 @@
|
||||
"SettingsTabInputDirectKeyboardAccess": "Accès direct au clavier",
|
||||
"SettingsButtonSave": "Enregistrer",
|
||||
"SettingsButtonClose": "Fermer",
|
||||
"SettingsButtonOk": "OK",
|
||||
"SettingsButtonCancel": "Annuler",
|
||||
"SettingsButtonApply": "Appliquer",
|
||||
"ControllerSettingsPlayer": "Joueur",
|
||||
"ControllerSettingsPlayer1": "Joueur 1",
|
||||
|
@@ -168,6 +168,8 @@
|
||||
"SettingsTabInputDirectKeyboardAccess": "Accesso diretto alla tastiera",
|
||||
"SettingsButtonSave": "Salva",
|
||||
"SettingsButtonClose": "Chiudi",
|
||||
"SettingsButtonOk": "OK",
|
||||
"SettingsButtonCancel": "Cancella",
|
||||
"SettingsButtonApply": "Applica",
|
||||
"ControllerSettingsPlayer": "Giocatore",
|
||||
"ControllerSettingsPlayer1": "Giocatore 1",
|
||||
|
@@ -168,6 +168,8 @@
|
||||
"SettingsTabInputDirectKeyboardAccess": "キーボード直接アクセス",
|
||||
"SettingsButtonSave": "セーブ",
|
||||
"SettingsButtonClose": "閉じる",
|
||||
"SettingsButtonOk": "オーケー",
|
||||
"SettingsButtonCancel": "キャンセル",
|
||||
"SettingsButtonApply": "適用",
|
||||
"ControllerSettingsPlayer": "プレイヤー",
|
||||
"ControllerSettingsPlayer1": "プレイヤー 1",
|
||||
|
@@ -167,6 +167,8 @@
|
||||
"SettingsTabInputDirectKeyboardAccess": "직접 키보드 액세스",
|
||||
"SettingsButtonSave": "구하다",
|
||||
"SettingsButtonClose": "출구",
|
||||
"SettingsButtonOk": "좋아",
|
||||
"SettingsButtonCancel": "취소",
|
||||
"SettingsButtonApply": "적용하다",
|
||||
"ControllerSettingsPlayer": "플레이어",
|
||||
"ControllerSettingsPlayer1": "플레이어 1",
|
||||
|
@@ -168,6 +168,8 @@
|
||||
"SettingsTabInputDirectKeyboardAccess": "Bezpośredni Dostęp do Klawiatury",
|
||||
"SettingsButtonSave": "Zapisz",
|
||||
"SettingsButtonClose": "Zamknij",
|
||||
"SettingsButtonOk": "OK",
|
||||
"SettingsButtonCancel": "Anuluj",
|
||||
"SettingsButtonApply": "Zastosuj",
|
||||
"ControllerSettingsPlayer": "Gracz",
|
||||
"ControllerSettingsPlayer1": "Gracz 1",
|
||||
|
@@ -168,6 +168,8 @@
|
||||
"SettingsTabInputDirectKeyboardAccess": "Acesso direto ao teclado",
|
||||
"SettingsButtonSave": "Salvar",
|
||||
"SettingsButtonClose": "Fechar",
|
||||
"SettingsButtonOk": "OK",
|
||||
"SettingsButtonCancel": "Cancelar",
|
||||
"SettingsButtonApply": "Aplicar",
|
||||
"ControllerSettingsPlayer": "Jogador",
|
||||
"ControllerSettingsPlayer1": "Jogador 1",
|
||||
|
@@ -167,6 +167,8 @@
|
||||
"SettingsTabInputDirectKeyboardAccess": "Прямой доступ с клавиатуры",
|
||||
"SettingsButtonSave": "Сохранить",
|
||||
"SettingsButtonClose": "Закрыть",
|
||||
"SettingsButtonOk": "OK",
|
||||
"SettingsButtonCancel": "Отмена",
|
||||
"SettingsButtonApply": "Применить",
|
||||
"ControllerSettingsPlayer": "Игрок",
|
||||
"ControllerSettingsPlayer1": "Игрок 1",
|
||||
|
@@ -168,6 +168,8 @@
|
||||
"SettingsTabInputDirectKeyboardAccess": "Doğrudan Klavye Erişimi",
|
||||
"SettingsButtonSave": "Kaydet",
|
||||
"SettingsButtonClose": "Kapat",
|
||||
"SettingsButtonOk": "Tamam",
|
||||
"SettingsButtonCancel": "İptal",
|
||||
"SettingsButtonApply": "Uygula",
|
||||
"ControllerSettingsPlayer": "Oyuncu",
|
||||
"ControllerSettingsPlayer1": "Oyuncu 1",
|
||||
|
@@ -168,6 +168,8 @@
|
||||
"SettingsTabInputDirectKeyboardAccess": "直通键盘控制",
|
||||
"SettingsButtonSave": "保存",
|
||||
"SettingsButtonClose": "关闭",
|
||||
"SettingsButtonOk": "批准",
|
||||
"SettingsButtonCancel": "取消",
|
||||
"SettingsButtonApply": "应用",
|
||||
"ControllerSettingsPlayer": "玩家",
|
||||
"ControllerSettingsPlayer1": "玩家 1",
|
||||
|
@@ -168,6 +168,8 @@
|
||||
"SettingsTabInputDirectKeyboardAccess": "直通鍵盤控制",
|
||||
"SettingsButtonSave": "儲存",
|
||||
"SettingsButtonClose": "關閉",
|
||||
"SettingsButtonOk": "嘛好",
|
||||
"SettingsButtonCancel": "取消",
|
||||
"SettingsButtonApply": "套用",
|
||||
"ControllerSettingsPlayer": "玩家",
|
||||
"ControllerSettingsPlayer1": "玩家 1",
|
||||
|
@@ -41,6 +41,9 @@
|
||||
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush" Color="{DynamicResource DataGridSelectionColor}" />
|
||||
<SolidColorBrush x:Key="ThemeAccentColorBrush" Color="{DynamicResource SystemAccentColor}" />
|
||||
<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="SystemAccentColorDark1">#FF99b000</Color>
|
||||
<Color x:Key="SystemAccentColorDark2">#FF006d7d</Color>
|
||||
|
@@ -1,7 +1,6 @@
|
||||
<Styles
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
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">
|
||||
<Design.PreviewWith>
|
||||
<Border Height="2000" Padding="20">
|
||||
@@ -269,13 +268,15 @@
|
||||
<Color x:Key="DataGridSelectionColor">#FF00FABB</Color>
|
||||
<Color x:Key="ThemeContentBackgroundColor">#FF2D2D2D</Color>
|
||||
<Color x:Key="ThemeControlBorderColor">#FF505050</Color>
|
||||
<sys:Double x:Key="ScrollBarThickness">15</sys:Double>
|
||||
<sys:Double x:Key="FontSizeSmall">8</sys:Double>
|
||||
<sys:Double x:Key="FontSizeNormal">10</sys:Double>
|
||||
<sys:Double x:Key="FontSize">12</sys:Double>
|
||||
<sys:Double x:Key="FontSizeLarge">15</sys:Double>
|
||||
<sys:Double x:Key="ControlContentThemeFontSize">13</sys:Double>
|
||||
<x:Double x:Key="ScrollBarThickness">15</x:Double>
|
||||
<x:Double x:Key="FontSizeSmall">8</x:Double>
|
||||
<x:Double x:Key="FontSizeNormal">10</x:Double>
|
||||
<x:Double x:Key="FontSize">12</x:Double>
|
||||
<x:Double x:Key="FontSizeLarge">15</x:Double>
|
||||
<x:Double x:Key="ControlContentThemeFontSize">13</x:Double>
|
||||
<x:Double x:Key="MenuItemHeight">26</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>
|
@@ -113,6 +113,11 @@ namespace Ryujinx.Ava.Common
|
||||
return;
|
||||
}
|
||||
|
||||
OpenSaveDir(saveDataId);
|
||||
}
|
||||
|
||||
public static void OpenSaveDir(ulong saveDataId)
|
||||
{
|
||||
string saveRootPath = Path.Combine(_virtualFileSystem.GetNandPath(), $"user/save/{saveDataId:x16}");
|
||||
|
||||
if (!Directory.Exists(saveRootPath))
|
||||
|
@@ -1,8 +1,6 @@
|
||||
using ARMeilleure.Translation.PTC;
|
||||
using Avalonia;
|
||||
using Avalonia.Rendering;
|
||||
using Avalonia.Threading;
|
||||
using Ryujinx.Ava.Ui.Controls;
|
||||
using Ryujinx.Ava.Ui.Windows;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Configuration;
|
||||
@@ -11,6 +9,7 @@ using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.System;
|
||||
using Ryujinx.Common.SystemInfo;
|
||||
using Ryujinx.Modules;
|
||||
using Ryujinx.SDL2.Common;
|
||||
using Ryujinx.Ui.Common;
|
||||
using Ryujinx.Ui.Common.Configuration;
|
||||
using Ryujinx.Ui.Common.Helper;
|
||||
@@ -23,18 +22,15 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
internal class Program
|
||||
{
|
||||
public static double WindowScaleFactor { get; set; }
|
||||
public static double ActualScaleFactor { get; set; }
|
||||
public static string Version { get; private set; }
|
||||
public static string ConfigurationPath { get; private set; }
|
||||
public static bool PreviewerDetached { get; private set; }
|
||||
public static RenderTimer RenderTimer { get; private set; }
|
||||
public static double WindowScaleFactor { get; set; }
|
||||
public static string Version { get; private set; }
|
||||
public static string ConfigurationPath { get; private set; }
|
||||
public static bool PreviewerDetached { get; private set; }
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
public static extern int MessageBoxA(IntPtr hWnd, string text, string caption, uint type);
|
||||
|
||||
private const uint MB_ICONWARNING = 0x30;
|
||||
private const int BaseDpi = 96;
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
@@ -49,11 +45,7 @@ namespace Ryujinx.Ava
|
||||
|
||||
Initialize(args);
|
||||
|
||||
RenderTimer = new RenderTimer();
|
||||
|
||||
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
||||
|
||||
RenderTimer.Dispose();
|
||||
}
|
||||
|
||||
public static AppBuilder BuildAvaloniaApp()
|
||||
@@ -65,7 +57,7 @@ namespace Ryujinx.Ava
|
||||
EnableMultiTouch = true,
|
||||
EnableIme = true,
|
||||
UseEGL = false,
|
||||
UseGpu = false
|
||||
UseGpu = true
|
||||
})
|
||||
.With(new Win32PlatformOptions
|
||||
{
|
||||
@@ -75,12 +67,6 @@ namespace Ryujinx.Ava
|
||||
CompositionBackdropCornerRadius = 8.0f,
|
||||
})
|
||||
.UseSkia()
|
||||
.AfterSetup(_ =>
|
||||
{
|
||||
AvaloniaLocator.CurrentMutable
|
||||
.Bind<IRenderTimer>().ToConstant(RenderTimer)
|
||||
.Bind<IRenderLoop>().ToConstant(new RenderLoop(RenderTimer, Dispatcher.UIThread));
|
||||
})
|
||||
.LogToTrace();
|
||||
}
|
||||
|
||||
@@ -110,12 +96,14 @@ namespace Ryujinx.Ava
|
||||
// Initialize Discord integration.
|
||||
DiscordIntegrationModule.Initialize();
|
||||
|
||||
// Initialize SDL2 driver
|
||||
SDL2Driver.MainThreadDispatcher = action => Dispatcher.UIThread.InvokeAsync(action, DispatcherPriority.Input);
|
||||
|
||||
ReloadConfig();
|
||||
|
||||
ForceDpiAware.Windows();
|
||||
|
||||
WindowScaleFactor = ForceDpiAware.GetWindowScaleFactor();
|
||||
ActualScaleFactor = ForceDpiAware.GetActualScaleFactor() / BaseDpi;
|
||||
|
||||
// Logging system information.
|
||||
PrintSystemInfo();
|
||||
@@ -236,4 +224,4 @@ namespace Ryujinx.Ava
|
||||
Logger.Shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -33,9 +33,9 @@
|
||||
<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.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.Extensions.EXT" Version="2.10.1" />
|
||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.10.1" />
|
||||
<PackageReference Include="Silk.NET.Vulkan" Version="2.16.0" />
|
||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" />
|
||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />
|
||||
<PackageReference Include="SPB" Version="0.0.4-build28" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.4.1" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
|
||||
|
@@ -1,6 +1,7 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using LibHac;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Ui.ViewModels;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
@@ -14,6 +15,8 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
{
|
||||
public AccountManager AccountManager { get; }
|
||||
public ContentManager ContentManager { get; }
|
||||
public VirtualFileSystem VirtualFileSystem { get; }
|
||||
public HorizonClient HorizonClient { get; }
|
||||
public UserProfileViewModel ViewModel { get; set; }
|
||||
|
||||
public NavigationDialogHost()
|
||||
@@ -22,10 +25,12 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
}
|
||||
|
||||
public NavigationDialogHost(AccountManager accountManager, ContentManager contentManager,
|
||||
VirtualFileSystem virtualFileSystem)
|
||||
VirtualFileSystem virtualFileSystem, HorizonClient horizonClient)
|
||||
{
|
||||
AccountManager = accountManager;
|
||||
ContentManager = contentManager;
|
||||
VirtualFileSystem = virtualFileSystem;
|
||||
HorizonClient = horizonClient;
|
||||
ViewModel = new UserProfileViewModel(this);
|
||||
|
||||
|
||||
@@ -54,9 +59,10 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
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
|
||||
{
|
||||
Title = LocaleManager.Instance["UserProfileWindowTitle"],
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -4,11 +4,4 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.RendererHost">
|
||||
<ContentControl
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
Name="View"
|
||||
/>
|
||||
</UserControl>
|
||||
|
@@ -41,7 +41,7 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
{
|
||||
_currentWindow.WindowCreated += CurrentWindow_WindowCreated;
|
||||
_currentWindow.SizeChanged += CurrentWindow_SizeChanged;
|
||||
View.Content = _currentWindow;
|
||||
Content = _currentWindow;
|
||||
}
|
||||
|
||||
public void CreateVulkan()
|
||||
|
102
Ryujinx.Ava/Ui/Controls/SaveManager.axaml
Normal file
102
Ryujinx.Ava/Ui/Controls/SaveManager.axaml
Normal 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>
|
160
Ryujinx.Ava/Ui/Controls/SaveManager.axaml.cs
Normal file
160
Ryujinx.Ava/Ui/Controls/SaveManager.axaml.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -10,6 +10,7 @@
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
|
||||
Margin="0"
|
||||
MinWidth="500"
|
||||
Padding="0"
|
||||
mc:Ignorable="d">
|
||||
<UserControl.Resources>
|
||||
@@ -63,7 +64,7 @@
|
||||
HorizontalAlignment="Stretch"
|
||||
MaxLength="{Binding MaxProfileNameLength}"
|
||||
Text="{Binding Name}" />
|
||||
<TextBlock Text="{Locale:Locale UserProfilesUserId}" />
|
||||
<TextBlock Name="IdText" Text="{Locale:Locale UserProfilesUserId}" />
|
||||
<TextBlock Name="IdLabel" Text="{Binding UserId}" />
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
|
@@ -36,15 +36,8 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
case NavigationMode.New:
|
||||
var args = ((NavigationDialogHost parent, UserProfile profile, bool isNewUser))arg.Parameter;
|
||||
_isNewUser = args.isNewUser;
|
||||
if (!_isNewUser)
|
||||
{
|
||||
_profile = args.profile;
|
||||
TempProfile = new TempProfile(_profile);
|
||||
}
|
||||
else
|
||||
{
|
||||
TempProfile = new TempProfile();
|
||||
}
|
||||
_profile = args.profile;
|
||||
TempProfile = new TempProfile(_profile);
|
||||
|
||||
_parent = args.parent;
|
||||
break;
|
||||
@@ -53,7 +46,8 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
DataContext = TempProfile;
|
||||
|
||||
AddPictureButton.IsVisible = _isNewUser;
|
||||
IdLabel.IsVisible = !_isNewUser;
|
||||
IdLabel.IsVisible = _profile != null;
|
||||
IdText.IsVisible = _profile != null;
|
||||
ChangePictureButton.IsVisible = !_isNewUser;
|
||||
}
|
||||
}
|
||||
@@ -87,7 +81,7 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
return;
|
||||
}
|
||||
|
||||
if (_profile != null)
|
||||
if (_profile != null && !_isNewUser)
|
||||
{
|
||||
_profile.Name = TempProfile.Name;
|
||||
_profile.Image = TempProfile.Image;
|
||||
@@ -97,7 +91,7 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
}
|
||||
else if (_isNewUser)
|
||||
{
|
||||
_parent.AccountManager.AddUser(TempProfile.Name, TempProfile.Image);
|
||||
_parent.AccountManager.AddUser(TempProfile.Name, TempProfile.Image, TempProfile.UserId);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
70
Ryujinx.Ava/Ui/Controls/UserRecoverer.axaml
Normal file
70
Ryujinx.Ava/Ui/Controls/UserRecoverer.axaml
Normal 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>
|
44
Ryujinx.Ava/Ui/Controls/UserRecoverer.axaml.cs
Normal file
44
Ryujinx.Ava/Ui/Controls/UserRecoverer.axaml.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -10,6 +10,7 @@
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
|
||||
d:DesignHeight="450"
|
||||
MinWidth="500"
|
||||
d:DesignWidth="800"
|
||||
mc:Ignorable="d">
|
||||
<UserControl.Resources>
|
||||
@@ -25,6 +26,7 @@
|
||||
</Grid.RowDefinitions>
|
||||
<ListBox
|
||||
Margin="5"
|
||||
MaxHeight="300"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
DoubleTapped="ProfilesList_DoubleTapped"
|
||||
@@ -88,21 +90,56 @@
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
<StackPanel
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
Margin="10,0"
|
||||
HorizontalAlignment="Center"
|
||||
Orientation="Horizontal"
|
||||
Spacing="10">
|
||||
<Button Command="{Binding AddUser}" Content="{Locale:Locale UserProfilesAddNewProfile}" />
|
||||
HorizontalAlignment="Center">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<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}"
|
||||
Content="{Locale:Locale UserProfilesEditProfile}"
|
||||
IsEnabled="{Binding IsSelectedProfiledEditable}" />
|
||||
<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}"
|
||||
Content="{Locale:Locale UserProfilesDeleteSelectedProfile}"
|
||||
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>
|
||||
</UserControl>
|
122
Ryujinx.Ava/Ui/Models/SaveModel.cs
Normal file
122
Ryujinx.Ava/Ui/Models/SaveModel.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -45,9 +45,12 @@ namespace Ryujinx.Ava.Ui.Models
|
||||
{
|
||||
_profile = profile;
|
||||
|
||||
Image = profile.Image;
|
||||
Name = profile.Name;
|
||||
UserId = profile.UserId;
|
||||
if (_profile != null)
|
||||
{
|
||||
Image = profile.Image;
|
||||
Name = profile.Name;
|
||||
UserId = profile.UserId;
|
||||
}
|
||||
}
|
||||
|
||||
public TempProfile(){}
|
||||
|
@@ -1,3 +1,4 @@
|
||||
using Ryujinx.Ava.Ui.Controls;
|
||||
using Ryujinx.Ava.Ui.ViewModels;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using Profile = Ryujinx.HLE.HOS.Services.Account.Acc.UserProfile;
|
||||
@@ -7,6 +8,7 @@ namespace Ryujinx.Ava.Ui.Models
|
||||
public class UserProfile : BaseModel
|
||||
{
|
||||
private readonly Profile _profile;
|
||||
private readonly NavigationDialogHost _owner;
|
||||
private byte[] _image;
|
||||
private string _name;
|
||||
private UserId _userId;
|
||||
@@ -41,9 +43,10 @@ namespace Ryujinx.Ava.Ui.Models
|
||||
}
|
||||
}
|
||||
|
||||
public UserProfile(Profile profile)
|
||||
public UserProfile(Profile profile, NavigationDialogHost owner)
|
||||
{
|
||||
_profile = profile;
|
||||
_owner = owner;
|
||||
|
||||
Image = profile.Image;
|
||||
Name = profile.Name;
|
||||
@@ -57,5 +60,10 @@ namespace Ryujinx.Ava.Ui.Models
|
||||
OnPropertyChanged(nameof(IsOpened));
|
||||
OnPropertyChanged(nameof(Name));
|
||||
}
|
||||
|
||||
public void Recover(UserProfile userProfile)
|
||||
{
|
||||
_owner.Navigate(typeof(UserEditor), (_owner, userProfile, true));
|
||||
}
|
||||
}
|
||||
}
|
@@ -76,6 +76,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
private bool _showAll;
|
||||
private string _lastScannedAmiiboId;
|
||||
private ReadOnlyObservableCollection<ApplicationData> _appsObservableList;
|
||||
public ApplicationLibrary ApplicationLibrary => _owner.ApplicationLibrary;
|
||||
|
||||
public string TitleName { get; internal set; }
|
||||
|
||||
@@ -103,8 +104,8 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_owner.ApplicationLibrary.ApplicationCountUpdated += ApplicationLibrary_ApplicationCountUpdated;
|
||||
_owner.ApplicationLibrary.ApplicationAdded += ApplicationLibrary_ApplicationAdded;
|
||||
ApplicationLibrary.ApplicationCountUpdated += ApplicationLibrary_ApplicationCountUpdated;
|
||||
ApplicationLibrary.ApplicationAdded += ApplicationLibrary_ApplicationAdded;
|
||||
|
||||
Ptc.PtcStateChanged -= ProgressHandler;
|
||||
Ptc.PtcStateChanged += ProgressHandler;
|
||||
@@ -817,7 +818,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
|
||||
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;
|
||||
})
|
||||
@@ -1005,7 +1006,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
|
||||
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()
|
||||
@@ -1098,7 +1099,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
{
|
||||
selection.Favorite = !selection.Favorite;
|
||||
|
||||
_owner.ApplicationLibrary.LoadAndSaveMetaData(selection.TitleId, appMetadata =>
|
||||
ApplicationLibrary.LoadAndSaveMetaData(selection.TitleId, appMetadata =>
|
||||
{
|
||||
appMetadata.Favorite = selection.Favorite;
|
||||
});
|
||||
|
@@ -118,7 +118,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public bool IsMacOS
|
||||
{
|
||||
get => OperatingSystem.IsMacOS();
|
||||
}
|
||||
|
||||
public bool EnableDiscordIntegration { get; set; }
|
||||
public bool CheckUpdatesOnStart { get; set; }
|
||||
public bool ShowConfirmExit { get; set; }
|
||||
@@ -474,11 +479,40 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
MainWindow.UpdateGraphicsConfig();
|
||||
|
||||
_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()
|
||||
{
|
||||
Program.ReloadConfig();
|
||||
}
|
||||
|
||||
public void ApplyButton()
|
||||
{
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
public void OkButton()
|
||||
{
|
||||
SaveSettings();
|
||||
_owner.Close();
|
||||
}
|
||||
|
||||
public void CancelButton()
|
||||
{
|
||||
RevertIfNotSaved();
|
||||
_owner.Close();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,8 +1,14 @@
|
||||
using Avalonia;
|
||||
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.Ui.Controls;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using UserProfile = Ryujinx.Ava.Ui.Models.UserProfile;
|
||||
@@ -19,6 +25,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
public UserProfileViewModel()
|
||||
{
|
||||
Profiles = new ObservableCollection<UserProfile>();
|
||||
LostProfiles = new ObservableCollection<UserProfile>();
|
||||
}
|
||||
|
||||
public UserProfileViewModel(NavigationDialogHost owner) : this()
|
||||
@@ -30,6 +37,8 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
|
||||
public ObservableCollection<UserProfile> Profiles { get; set; }
|
||||
|
||||
public ObservableCollection<UserProfile> LostProfiles { get; set; }
|
||||
|
||||
public UserProfile SelectedProfile
|
||||
{
|
||||
get => _selectedProfile;
|
||||
@@ -65,12 +74,13 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
public void LoadProfiles()
|
||||
{
|
||||
Profiles.Clear();
|
||||
LostProfiles.Clear();
|
||||
|
||||
var profiles = _owner.AccountManager.GetAllUsers().OrderByDescending(x => x.AccountState == AccountState.Open);
|
||||
|
||||
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);
|
||||
@@ -84,6 +94,42 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
_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()
|
||||
@@ -93,6 +139,25 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
_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()
|
||||
{
|
||||
_owner.Navigate(typeof(UserEditor), (this._owner, _highlightedProfile ?? SelectedProfile, false));
|
||||
@@ -134,5 +199,15 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
|
||||
LoadProfiles();
|
||||
}
|
||||
|
||||
public void GoBack()
|
||||
{
|
||||
_owner.GoBack();
|
||||
}
|
||||
|
||||
public void RecoverLostAccounts()
|
||||
{
|
||||
_owner.Navigate(typeof(UserRecoverer), (this._owner, this));
|
||||
}
|
||||
}
|
||||
}
|
@@ -13,7 +13,7 @@
|
||||
Title="Ryujinx"
|
||||
Width="1280"
|
||||
Height="785"
|
||||
MinWidth="1024"
|
||||
MinWidth="1092"
|
||||
MinHeight="680"
|
||||
d:DesignHeight="720"
|
||||
d:DesignWidth="1280"
|
||||
@@ -57,6 +57,8 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel
|
||||
Name="MenuBar"
|
||||
MinHeight="35"
|
||||
Grid.Row="0"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
@@ -549,6 +551,7 @@
|
||||
<Grid
|
||||
Name="StatusBar"
|
||||
Grid.Row="2"
|
||||
MinHeight="30"
|
||||
Height="30"
|
||||
Margin="0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
|
@@ -36,6 +36,7 @@ namespace Ryujinx.Ava.Ui.Windows
|
||||
{
|
||||
public partial class MainWindow : StyleableWindow
|
||||
{
|
||||
internal static MainWindowViewModel MainWindowViewModel { get; private set; }
|
||||
private bool _canUpdate;
|
||||
private bool _isClosing;
|
||||
private bool _isLoading;
|
||||
@@ -81,6 +82,8 @@ namespace Ryujinx.Ava.Ui.Windows
|
||||
{
|
||||
ViewModel = new MainWindowViewModel(this);
|
||||
|
||||
MainWindowViewModel = ViewModel;
|
||||
|
||||
DataContext = ViewModel;
|
||||
|
||||
InitializeComponent();
|
||||
@@ -90,7 +93,9 @@ namespace Ryujinx.Ava.Ui.Windows
|
||||
|
||||
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;
|
||||
|
||||
if (Program.PreviewerDetached)
|
||||
|
@@ -955,16 +955,25 @@
|
||||
Icon="Document" />
|
||||
</ui:NavigationView.MenuItems>
|
||||
</ui:NavigationView>
|
||||
<StackPanel
|
||||
<ReversibleStackPanel
|
||||
Grid.Row="2"
|
||||
Margin="10"
|
||||
Spacing="10"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Right">
|
||||
<Button Content="{locale:Locale SettingsButtonSave}" Click="SaveButton_Clicked" />
|
||||
<Button Content="{locale:Locale SettingsButtonClose}" Click="CloseButton_Clicked" />
|
||||
<Button Content="{locale:Locale SettingsButtonApply}"
|
||||
Click="ApplyButton_Clicked" />
|
||||
</StackPanel>
|
||||
HorizontalAlignment="Right"
|
||||
ReverseOrder="{ReflectionBinding IsMacOS}">
|
||||
<Button
|
||||
HotKey="Enter"
|
||||
Classes="accent"
|
||||
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>
|
||||
</window:StyleableWindow>
|
@@ -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)
|
||||
{
|
||||
ControllerSettings.Dispose();
|
||||
|
@@ -28,9 +28,39 @@ namespace Ryujinx.Common.SystemInfo
|
||||
|
||||
CpuName = $"{cpuName} ; {LogicalCoreCount} logical";
|
||||
RamTotal = totalRAM;
|
||||
RamAvailable = GetVMInfoAvailableMemory();
|
||||
}
|
||||
|
||||
[DllImport("libSystem.dylib", CharSet = CharSet.Ansi, SetLastError = true)]
|
||||
static ulong GetVMInfoAvailableMemory()
|
||||
{
|
||||
var port = mach_host_self();
|
||||
|
||||
uint pageSize = 0;
|
||||
var result = host_page_size(port, ref pageSize);
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"Failed to query Available RAM. host_page_size() error = {result}");
|
||||
return 0;
|
||||
}
|
||||
|
||||
const int flavor = 4; // HOST_VM_INFO64
|
||||
uint count = (uint)(Marshal.SizeOf<VMStatistics64>() / sizeof(int)); // HOST_VM_INFO64_COUNT
|
||||
VMStatistics64 stats = new();
|
||||
result = host_statistics64(port, flavor, ref stats, ref count);
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"Failed to query Available RAM. host_statistics64() error = {result}");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (ulong)(stats.FreeCount + stats.InactiveCount) * pageSize;
|
||||
}
|
||||
|
||||
private const string SystemLibraryName = "libSystem.dylib";
|
||||
|
||||
[DllImport(SystemLibraryName, CharSet = CharSet.Ansi, SetLastError = true)]
|
||||
private static extern int sysctlbyname(string name, IntPtr oldValue, ref ulong oldSize, IntPtr newValue, ulong newValueSize);
|
||||
|
||||
private static int sysctlbyname(string name, IntPtr oldValue, ref ulong oldSize)
|
||||
@@ -85,5 +115,43 @@ namespace Ryujinx.Common.SystemInfo
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
[DllImport(SystemLibraryName, CharSet = CharSet.Ansi, SetLastError = true)]
|
||||
private static extern uint mach_host_self();
|
||||
|
||||
[DllImport(SystemLibraryName, CharSet = CharSet.Ansi, SetLastError = true)]
|
||||
private static extern int host_page_size(uint host, ref uint out_page_size);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 8)]
|
||||
struct VMStatistics64
|
||||
{
|
||||
public uint FreeCount;
|
||||
public uint ActiveCount;
|
||||
public uint InactiveCount;
|
||||
public uint WireCount;
|
||||
public ulong ZeroFillCount;
|
||||
public ulong Reactivations;
|
||||
public ulong Pageins;
|
||||
public ulong Pageouts;
|
||||
public ulong Faults;
|
||||
public ulong CowFaults;
|
||||
public ulong Lookups;
|
||||
public ulong Hits;
|
||||
public ulong Purges;
|
||||
public uint PurgeableCount;
|
||||
public uint SpeculativeCount;
|
||||
public ulong Decompressions;
|
||||
public ulong Compressions;
|
||||
public ulong Swapins;
|
||||
public ulong Swapouts;
|
||||
public uint CompressorPageCount;
|
||||
public uint ThrottledCount;
|
||||
public uint ExternalPageCount;
|
||||
public uint InternalPageCount;
|
||||
public ulong TotalUncompressedPagesInCompressor;
|
||||
}
|
||||
|
||||
[DllImport(SystemLibraryName, CharSet = CharSet.Ansi, SetLastError = true)]
|
||||
private static extern int host_statistics64(uint host_priv, int host_flavor, ref VMStatistics64 host_info64_out, ref uint host_info64_outCnt);
|
||||
}
|
||||
}
|
@@ -29,6 +29,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
ReadOnlySpan<byte> GetBufferData(BufferHandle buffer, int offset, int size);
|
||||
|
||||
Capabilities GetCapabilities();
|
||||
ulong GetCurrentSync();
|
||||
HardwareInfo GetHardwareInfo();
|
||||
|
||||
IProgram LoadProgramBinary(byte[] programBinary, bool hasFragmentShader, ShaderInfo info);
|
||||
|
@@ -338,6 +338,11 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
return box.Result;
|
||||
}
|
||||
|
||||
public ulong GetCurrentSync()
|
||||
{
|
||||
return _baseRenderer.GetCurrentSync();
|
||||
}
|
||||
|
||||
public HardwareInfo GetHardwareInfo()
|
||||
{
|
||||
return _baseRenderer.GetHardwareInfo();
|
||||
|
@@ -69,6 +69,12 @@ namespace Ryujinx.Graphics.Gpu
|
||||
/// </summary>
|
||||
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>
|
||||
/// Queue with deferred actions that must run on the render thread.
|
||||
/// </summary>
|
||||
@@ -90,6 +96,7 @@ namespace Ryujinx.Graphics.Gpu
|
||||
public event Action<ShaderCacheState, int, int> ShaderCacheStateChanged;
|
||||
|
||||
private Thread _gpuThread;
|
||||
private bool _pendingSync;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the GPU emulation context.
|
||||
@@ -109,6 +116,7 @@ namespace Ryujinx.Graphics.Gpu
|
||||
|
||||
SyncActions = new List<Action>();
|
||||
SyncpointActions = new List<Action>();
|
||||
BufferMigrations = new List<BufferMigration>();
|
||||
|
||||
DeferredActions = new Queue<Action>();
|
||||
|
||||
@@ -273,6 +281,17 @@ namespace Ryujinx.Graphics.Gpu
|
||||
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>
|
||||
/// 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.
|
||||
@@ -288,6 +307,7 @@ namespace Ryujinx.Graphics.Gpu
|
||||
else
|
||||
{
|
||||
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>
|
||||
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);
|
||||
|
||||
@@ -317,6 +354,8 @@ namespace Ryujinx.Graphics.Gpu
|
||||
SyncActions.Clear();
|
||||
SyncpointActions.Clear();
|
||||
}
|
||||
|
||||
_pendingSync = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@@ -65,6 +65,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
private bool _useGranular;
|
||||
private bool _syncActionRegistered;
|
||||
|
||||
private int _referenceCount = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the buffer.
|
||||
/// </summary>
|
||||
@@ -229,7 +231,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
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>
|
||||
public void InheritModifiedRanges(Buffer from)
|
||||
{
|
||||
if (from._modifiedRanges != null)
|
||||
if (from._modifiedRanges != null && from._modifiedRanges.HasRanges)
|
||||
{
|
||||
if (from._syncActionRegistered && !_syncActionRegistered)
|
||||
{
|
||||
@@ -310,17 +312,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
}
|
||||
};
|
||||
|
||||
if (_modifiedRanges == null)
|
||||
{
|
||||
_modifiedRanges = from._modifiedRanges;
|
||||
_modifiedRanges.ReregisterRanges(registerRangeAction);
|
||||
EnsureRangeList();
|
||||
|
||||
from._modifiedRanges = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
_modifiedRanges.InheritRanges(from._modifiedRanges, registerRangeAction);
|
||||
}
|
||||
_modifiedRanges.InheritRanges(from._modifiedRanges, registerRangeAction);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -456,7 +450,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
if (ranges != null)
|
||||
{
|
||||
(address, size) = PageAlign(address, size);
|
||||
ranges.WaitForAndGetRanges(address, size, Flush);
|
||||
ranges.WaitForAndFlushRanges(address, size);
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
@@ -508,6 +502,25 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
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>
|
||||
/// Disposes the host buffer's data, not its tracking handles.
|
||||
/// </summary>
|
||||
@@ -528,7 +541,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
_memoryTrackingGranular?.Dispose();
|
||||
_memoryTracking?.Dispose();
|
||||
|
||||
DisposeData();
|
||||
DecrementReferenceCount();
|
||||
}
|
||||
}
|
||||
}
|
@@ -273,7 +273,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
buffer.CopyTo(newBuffer, dstOffset);
|
||||
newBuffer.InheritModifiedRanges(buffer);
|
||||
|
||||
buffer.DisposeData();
|
||||
buffer.DecrementReferenceCount();
|
||||
}
|
||||
|
||||
newBuffer.SynchronizeMemory(address, newSize);
|
||||
|
125
Ryujinx.Graphics.Gpu/Memory/BufferMigration.cs
Normal file
125
Ryujinx.Graphics.Gpu/Memory/BufferMigration.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,6 +1,8 @@
|
||||
using Ryujinx.Common.Pools;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Pools;
|
||||
using Ryujinx.Memory.Range;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Memory
|
||||
@@ -30,17 +32,24 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// </summary>
|
||||
public ulong SyncNumber { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// The range list that originally owned this range.
|
||||
/// </summary>
|
||||
public BufferModifiedRangeList Parent { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of a modified range.
|
||||
/// </summary>
|
||||
/// <param name="address">Start address of the range</param>
|
||||
/// <param name="size">Size of the range in bytes</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;
|
||||
Size = size;
|
||||
SyncNumber = syncNumber;
|
||||
Parent = parent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -63,16 +72,39 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
private const int BackingInitialSize = 8;
|
||||
|
||||
private GpuContext _context;
|
||||
private Buffer _parent;
|
||||
private Action<ulong, ulong> _flushAction;
|
||||
|
||||
private List<BufferMigration> _sources;
|
||||
private BufferMigration _migrationTarget;
|
||||
|
||||
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>
|
||||
/// Creates a new instance of a modified range list.
|
||||
/// </summary>
|
||||
/// <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;
|
||||
_parent = parent;
|
||||
_flushAction = flushAction;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -142,6 +174,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
// Region already exists. Just update the existing sync number.
|
||||
overlap.SyncNumber = syncNumber;
|
||||
overlap.Parent = this;
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -152,18 +185,18 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
// 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>
|
||||
/// 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>
|
||||
/// <remarks>
|
||||
/// This function assumes it is called from the background thread.
|
||||
@@ -218,8 +344,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// </remarks>
|
||||
/// <param name="address">Start address to query</param>
|
||||
/// <param name="size">Size to query</param>
|
||||
/// <param name="rangeAction">The action to call for each modified range</param>
|
||||
public void WaitForAndGetRanges(ulong address, ulong size, Action<ulong, ulong> rangeAction)
|
||||
public void WaitForAndFlushRanges(ulong address, ulong size)
|
||||
{
|
||||
ulong endAddress = address + size;
|
||||
ulong currentSync = _context.SyncNumber;
|
||||
@@ -231,10 +356,23 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
// Range list must be consistent for this operation
|
||||
lock (_lock)
|
||||
{
|
||||
rangeCount = FindOverlapsNonOverlapping(address, size, ref overlaps);
|
||||
if (_migrationTarget != null)
|
||||
{
|
||||
rangeCount = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
rangeCount = FindOverlapsNonOverlapping(address, size, ref overlaps);
|
||||
}
|
||||
}
|
||||
|
||||
if (rangeCount == 0)
|
||||
if (rangeCount == -1)
|
||||
{
|
||||
_migrationTarget.Destination.WaitForAndFlushRanges(address, size);
|
||||
|
||||
return;
|
||||
}
|
||||
else if (rangeCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -264,47 +402,37 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
// Wait for the syncpoint.
|
||||
_context.Renderer.WaitSync(currentSync + (ulong)highestDiff);
|
||||
|
||||
// Flush and remove all regions with the older syncpoint.
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
RemoveRangesAndFlush(overlaps, rangeCount, highestDiff, currentSync, address, endAddress);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inherit ranges from another modified range list.
|
||||
/// </summary>
|
||||
/// <param name="ranges">The range list to inherit from</param>
|
||||
/// <param name="rangeAction">The action to call for each modified range</param>
|
||||
public void InheritRanges(BufferModifiedRangeList ranges, Action<ulong, ulong> rangeAction)
|
||||
/// <param name="registerRangeAction">The action to call for each modified range</param>
|
||||
public void InheritRanges(BufferModifiedRangeList ranges, Action<ulong, ulong> registerRangeAction)
|
||||
{
|
||||
BufferModifiedRange[] inheritRanges;
|
||||
|
||||
lock (ranges._lock)
|
||||
{
|
||||
inheritRanges = ranges.ToArray();
|
||||
}
|
||||
BufferMigration migration = new(ranges._parent, ranges._flushAction, ranges, this, _context.SyncNumber);
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
foreach (BufferModifiedRange range in inheritRanges)
|
||||
ranges._parent.IncrementReferenceCount();
|
||||
ranges._migrationTarget = migration;
|
||||
|
||||
_context.RegisterBufferMigration(migration);
|
||||
|
||||
inheritRanges = ranges.ToArray();
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
Add(range);
|
||||
(_sources ??= new List<BufferMigration>()).Add(migration);
|
||||
|
||||
foreach (BufferModifiedRange range in inheritRanges)
|
||||
{
|
||||
Add(range);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,44 +441,20 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
if (range.SyncNumber != currentSync)
|
||||
{
|
||||
rangeAction(range.Address, range.Size);
|
||||
registerRangeAction(range.Address, range.Size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="rangeAction">The action to call for each modified range</param>
|
||||
public void ReregisterRanges(Action<ulong, ulong> rangeAction)
|
||||
/// <param name="migration">The migration to remove</param>
|
||||
public void RemoveMigration(BufferMigration migration)
|
||||
{
|
||||
ref var ranges = ref ThreadStaticArray<BufferModifiedRange>.Get();
|
||||
int count;
|
||||
|
||||
// Range list must be consistent for this operation.
|
||||
lock (_lock)
|
||||
{
|
||||
count = Count;
|
||||
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);
|
||||
}
|
||||
_sources.Remove(migration);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,12 +466,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
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)
|
||||
{
|
||||
Add(new BufferModifiedRange(endAddress, overlap.EndAddress - endAddress, overlap.SyncNumber));
|
||||
Add(new BufferModifiedRange(endAddress, overlap.EndAddress - endAddress, overlap.SyncNumber, overlap.Parent));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -238,6 +238,11 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
_sync.Wait(id);
|
||||
}
|
||||
|
||||
public ulong GetCurrentSync()
|
||||
{
|
||||
return _sync.GetCurrent();
|
||||
}
|
||||
|
||||
public void Screenshot()
|
||||
{
|
||||
_window.ScreenCaptureRequested = true;
|
||||
|
@@ -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)
|
||||
{
|
||||
SyncHandle result = null;
|
||||
|
@@ -12,12 +12,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private const int MaxUpdateBufferSize = 0x10000;
|
||||
|
||||
public const AccessFlags DefaultAccessFlags =
|
||||
AccessFlags.AccessIndirectCommandReadBit |
|
||||
AccessFlags.AccessShaderReadBit |
|
||||
AccessFlags.AccessShaderWriteBit |
|
||||
AccessFlags.AccessTransferReadBit |
|
||||
AccessFlags.AccessTransferWriteBit |
|
||||
AccessFlags.AccessUniformReadBit;
|
||||
AccessFlags.IndirectCommandReadBit |
|
||||
AccessFlags.ShaderReadBit |
|
||||
AccessFlags.ShaderWriteBit |
|
||||
AccessFlags.TransferReadBit |
|
||||
AccessFlags.TransferWriteBit |
|
||||
AccessFlags.UniformReadBit;
|
||||
|
||||
private readonly VulkanRenderer _gd;
|
||||
private readonly Device _device;
|
||||
@@ -87,9 +87,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
_gd.Api.CmdPipelineBarrier(
|
||||
commandBuffer,
|
||||
PipelineStageFlags.PipelineStageAllCommandsBit,
|
||||
PipelineStageFlags.PipelineStageAllCommandsBit,
|
||||
DependencyFlags.DependencyDeviceGroupBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
DependencyFlags.DeviceGroupBit,
|
||||
1,
|
||||
memoryBarrier,
|
||||
0,
|
||||
@@ -273,9 +273,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
cbs.CommandBuffer,
|
||||
dstBuffer,
|
||||
BufferHolder.DefaultAccessFlags,
|
||||
AccessFlags.AccessTransferWriteBit,
|
||||
PipelineStageFlags.PipelineStageAllCommandsBit,
|
||||
PipelineStageFlags.PipelineStageTransferBit,
|
||||
AccessFlags.TransferWriteBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
PipelineStageFlags.TransferBit,
|
||||
dstOffset,
|
||||
data.Length);
|
||||
|
||||
@@ -293,10 +293,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_gd,
|
||||
cbs.CommandBuffer,
|
||||
dstBuffer,
|
||||
AccessFlags.AccessTransferWriteBit,
|
||||
AccessFlags.TransferWriteBit,
|
||||
BufferHolder.DefaultAccessFlags,
|
||||
PipelineStageFlags.PipelineStageTransferBit,
|
||||
PipelineStageFlags.PipelineStageAllCommandsBit,
|
||||
PipelineStageFlags.TransferBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
dstOffset,
|
||||
data.Length);
|
||||
|
||||
@@ -320,9 +320,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
cbs.CommandBuffer,
|
||||
dstBuffer,
|
||||
BufferHolder.DefaultAccessFlags,
|
||||
AccessFlags.AccessTransferWriteBit,
|
||||
PipelineStageFlags.PipelineStageAllCommandsBit,
|
||||
PipelineStageFlags.PipelineStageTransferBit,
|
||||
AccessFlags.TransferWriteBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
PipelineStageFlags.TransferBit,
|
||||
dstOffset,
|
||||
size);
|
||||
|
||||
@@ -334,10 +334,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
gd,
|
||||
cbs.CommandBuffer,
|
||||
dstBuffer,
|
||||
AccessFlags.AccessTransferWriteBit,
|
||||
AccessFlags.TransferWriteBit,
|
||||
BufferHolder.DefaultAccessFlags,
|
||||
PipelineStageFlags.PipelineStageTransferBit,
|
||||
PipelineStageFlags.PipelineStageAllCommandsBit,
|
||||
PipelineStageFlags.TransferBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
dstOffset,
|
||||
size);
|
||||
}
|
||||
|
@@ -10,28 +10,28 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
class BufferManager : IDisposable
|
||||
{
|
||||
private const MemoryPropertyFlags DefaultBufferMemoryFlags =
|
||||
MemoryPropertyFlags.MemoryPropertyHostVisibleBit |
|
||||
MemoryPropertyFlags.MemoryPropertyHostCoherentBit |
|
||||
MemoryPropertyFlags.MemoryPropertyHostCachedBit;
|
||||
MemoryPropertyFlags.HostVisibleBit |
|
||||
MemoryPropertyFlags.HostCoherentBit |
|
||||
MemoryPropertyFlags.HostCachedBit;
|
||||
|
||||
private const MemoryPropertyFlags DeviceLocalBufferMemoryFlags =
|
||||
MemoryPropertyFlags.MemoryPropertyDeviceLocalBit;
|
||||
MemoryPropertyFlags.DeviceLocalBit;
|
||||
|
||||
private const MemoryPropertyFlags FlushableDeviceLocalBufferMemoryFlags =
|
||||
MemoryPropertyFlags.MemoryPropertyHostVisibleBit |
|
||||
MemoryPropertyFlags.MemoryPropertyHostCoherentBit |
|
||||
MemoryPropertyFlags.MemoryPropertyDeviceLocalBit;
|
||||
MemoryPropertyFlags.HostVisibleBit |
|
||||
MemoryPropertyFlags.HostCoherentBit |
|
||||
MemoryPropertyFlags.DeviceLocalBit;
|
||||
|
||||
private const BufferUsageFlags DefaultBufferUsageFlags =
|
||||
BufferUsageFlags.BufferUsageTransferSrcBit |
|
||||
BufferUsageFlags.BufferUsageTransferDstBit |
|
||||
BufferUsageFlags.BufferUsageUniformTexelBufferBit |
|
||||
BufferUsageFlags.BufferUsageStorageTexelBufferBit |
|
||||
BufferUsageFlags.BufferUsageUniformBufferBit |
|
||||
BufferUsageFlags.BufferUsageStorageBufferBit |
|
||||
BufferUsageFlags.BufferUsageIndexBufferBit |
|
||||
BufferUsageFlags.BufferUsageVertexBufferBit |
|
||||
BufferUsageFlags.BufferUsageTransformFeedbackBufferBitExt;
|
||||
BufferUsageFlags.TransferSrcBit |
|
||||
BufferUsageFlags.TransferDstBit |
|
||||
BufferUsageFlags.UniformTexelBufferBit |
|
||||
BufferUsageFlags.StorageTexelBufferBit |
|
||||
BufferUsageFlags.UniformBufferBit |
|
||||
BufferUsageFlags.StorageBufferBit |
|
||||
BufferUsageFlags.IndexBufferBit |
|
||||
BufferUsageFlags.VertexBufferBit |
|
||||
BufferUsageFlags.TransformFeedbackBufferBitExt;
|
||||
|
||||
private readonly PhysicalDevice _physicalDevice;
|
||||
private readonly Device _device;
|
||||
@@ -76,11 +76,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
if (forConditionalRendering && gd.Capabilities.SupportsConditionalRendering)
|
||||
{
|
||||
usage |= BufferUsageFlags.BufferUsageConditionalRenderingBitExt;
|
||||
usage |= BufferUsageFlags.ConditionalRenderingBitExt;
|
||||
}
|
||||
else if (gd.Capabilities.SupportsIndirectParameters)
|
||||
{
|
||||
usage |= BufferUsageFlags.BufferUsageIndirectBufferBit;
|
||||
usage |= BufferUsageFlags.IndirectBufferBit;
|
||||
}
|
||||
|
||||
var bufferCreateInfo = new BufferCreateInfo()
|
||||
|
@@ -71,8 +71,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
SType = StructureType.CommandPoolCreateInfo,
|
||||
QueueFamilyIndex = queueFamilyIndex,
|
||||
Flags = CommandPoolCreateFlags.CommandPoolCreateTransientBit |
|
||||
CommandPoolCreateFlags.CommandPoolCreateResetCommandBufferBit
|
||||
Flags = CommandPoolCreateFlags.TransientBit |
|
||||
CommandPoolCreateFlags.ResetCommandBufferBit
|
||||
};
|
||||
|
||||
api.CreateCommandPool(device, commandPoolCreateInfo, null, out _pool).ThrowOnError();
|
||||
|
@@ -229,7 +229,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
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();
|
||||
_samplerRefs[binding] = ((SamplerHolder)sampler)?.GetSampler();
|
||||
|
@@ -12,12 +12,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
return stage switch
|
||||
{
|
||||
ShaderStage.Vertex => ShaderStageFlags.ShaderStageVertexBit,
|
||||
ShaderStage.Geometry => ShaderStageFlags.ShaderStageGeometryBit,
|
||||
ShaderStage.TessellationControl => ShaderStageFlags.ShaderStageTessellationControlBit,
|
||||
ShaderStage.TessellationEvaluation => ShaderStageFlags.ShaderStageTessellationEvaluationBit,
|
||||
ShaderStage.Fragment => ShaderStageFlags.ShaderStageFragmentBit,
|
||||
ShaderStage.Compute => ShaderStageFlags.ShaderStageComputeBit,
|
||||
ShaderStage.Vertex => ShaderStageFlags.VertexBit,
|
||||
ShaderStage.Geometry => ShaderStageFlags.GeometryBit,
|
||||
ShaderStage.TessellationControl => ShaderStageFlags.TessellationControlBit,
|
||||
ShaderStage.TessellationEvaluation => ShaderStageFlags.TessellationEvaluationBit,
|
||||
ShaderStage.Fragment => ShaderStageFlags.FragmentBit,
|
||||
ShaderStage.Compute => ShaderStageFlags.ComputeBit,
|
||||
_ => LogInvalidAndReturn(stage, nameof(ShaderStage), (ShaderStageFlags)0)
|
||||
};
|
||||
}
|
||||
@@ -26,12 +26,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
return stage switch
|
||||
{
|
||||
ShaderStage.Vertex => PipelineStageFlags.PipelineStageVertexShaderBit,
|
||||
ShaderStage.Geometry => PipelineStageFlags.PipelineStageGeometryShaderBit,
|
||||
ShaderStage.TessellationControl => PipelineStageFlags.PipelineStageTessellationControlShaderBit,
|
||||
ShaderStage.TessellationEvaluation => PipelineStageFlags.PipelineStageTessellationEvaluationShaderBit,
|
||||
ShaderStage.Fragment => PipelineStageFlags.PipelineStageFragmentShaderBit,
|
||||
ShaderStage.Compute => PipelineStageFlags.PipelineStageComputeShaderBit,
|
||||
ShaderStage.Vertex => PipelineStageFlags.VertexShaderBit,
|
||||
ShaderStage.Geometry => PipelineStageFlags.GeometryShaderBit,
|
||||
ShaderStage.TessellationControl => PipelineStageFlags.TessellationControlShaderBit,
|
||||
ShaderStage.TessellationEvaluation => PipelineStageFlags.TessellationEvaluationShaderBit,
|
||||
ShaderStage.Fragment => PipelineStageFlags.FragmentShaderBit,
|
||||
ShaderStage.Compute => PipelineStageFlags.ComputeShaderBit,
|
||||
_ => LogInvalidAndReturn(stage, nameof(ShaderStage), (PipelineStageFlags)0)
|
||||
};
|
||||
}
|
||||
@@ -112,10 +112,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
return face switch
|
||||
{
|
||||
Face.Back => CullModeFlags.CullModeBackBit,
|
||||
Face.Front => CullModeFlags.CullModeFrontBit,
|
||||
Face.FrontAndBack => CullModeFlags.CullModeFrontAndBack,
|
||||
_ => LogInvalidAndReturn(face, nameof(Face), CullModeFlags.CullModeBackBit)
|
||||
Face.Back => CullModeFlags.BackBit,
|
||||
Face.Front => CullModeFlags.FrontBit,
|
||||
Face.FrontAndBack => CullModeFlags.FrontAndBack,
|
||||
_ => LogInvalidAndReturn(face, nameof(Face), CullModeFlags.BackBit)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -223,14 +223,14 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
Target.Texture1D or
|
||||
Target.Texture1DArray or
|
||||
Target.TextureBuffer => ImageType.ImageType1D,
|
||||
Target.TextureBuffer => ImageType.Type1D,
|
||||
Target.Texture2D or
|
||||
Target.Texture2DArray or
|
||||
Target.Texture2DMultisample or
|
||||
Target.Cubemap or
|
||||
Target.CubemapArray => ImageType.ImageType2D,
|
||||
Target.Texture3D => ImageType.ImageType3D,
|
||||
_ => LogInvalidAndReturn(target, nameof(Target), ImageType.ImageType2D)
|
||||
Target.CubemapArray => ImageType.Type2D,
|
||||
Target.Texture3D => ImageType.Type3D,
|
||||
_ => LogInvalidAndReturn(target, nameof(Target), ImageType.Type2D)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -238,14 +238,14 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
return target switch
|
||||
{
|
||||
Target.Texture1D => ImageViewType.ImageViewType1D,
|
||||
Target.Texture2D or Target.Texture2DMultisample => ImageViewType.ImageViewType2D,
|
||||
Target.Texture3D => ImageViewType.ImageViewType3D,
|
||||
Target.Texture1DArray => ImageViewType.ImageViewType1DArray,
|
||||
Target.Texture2DArray => ImageViewType.ImageViewType2DArray,
|
||||
Target.Cubemap => ImageViewType.Cube,
|
||||
Target.CubemapArray => ImageViewType.CubeArray,
|
||||
_ => LogInvalidAndReturn(target, nameof(Target), ImageViewType.ImageViewType2D)
|
||||
Target.Texture1D => ImageViewType.Type1D,
|
||||
Target.Texture2D or Target.Texture2DMultisample => ImageViewType.Type2D,
|
||||
Target.Texture3D => ImageViewType.Type3D,
|
||||
Target.Texture1DArray => ImageViewType.Type1DArray,
|
||||
Target.Texture2DArray => ImageViewType.Type2DArray,
|
||||
Target.Cubemap => ImageViewType.TypeCube,
|
||||
Target.CubemapArray => ImageViewType.TypeCubeArray,
|
||||
_ => LogInvalidAndReturn(target, nameof(Target), ImageViewType.Type2D)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -253,12 +253,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
return format switch
|
||||
{
|
||||
GAL.Format.D16Unorm or GAL.Format.D32Float => ImageAspectFlags.ImageAspectDepthBit,
|
||||
GAL.Format.S8Uint => ImageAspectFlags.ImageAspectStencilBit,
|
||||
GAL.Format.D16Unorm or GAL.Format.D32Float => ImageAspectFlags.DepthBit,
|
||||
GAL.Format.S8Uint => ImageAspectFlags.StencilBit,
|
||||
GAL.Format.D24UnormS8Uint or
|
||||
GAL.Format.D32FloatS8Uint or
|
||||
GAL.Format.S8UintD24Unorm => ImageAspectFlags.ImageAspectDepthBit | ImageAspectFlags.ImageAspectStencilBit,
|
||||
_ => ImageAspectFlags.ImageAspectColorBit
|
||||
GAL.Format.S8UintD24Unorm => ImageAspectFlags.DepthBit | ImageAspectFlags.StencilBit,
|
||||
_ => ImageAspectFlags.ColorBit
|
||||
};
|
||||
}
|
||||
|
||||
@@ -266,12 +266,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
return format switch
|
||||
{
|
||||
GAL.Format.D16Unorm or GAL.Format.D32Float => ImageAspectFlags.ImageAspectDepthBit,
|
||||
GAL.Format.S8Uint => ImageAspectFlags.ImageAspectStencilBit,
|
||||
GAL.Format.D16Unorm or GAL.Format.D32Float => ImageAspectFlags.DepthBit,
|
||||
GAL.Format.S8Uint => ImageAspectFlags.StencilBit,
|
||||
GAL.Format.D24UnormS8Uint or
|
||||
GAL.Format.D32FloatS8Uint or
|
||||
GAL.Format.S8UintD24Unorm => depthStencilMode == DepthStencilMode.Stencil ? ImageAspectFlags.ImageAspectStencilBit : ImageAspectFlags.ImageAspectDepthBit,
|
||||
_ => ImageAspectFlags.ImageAspectColorBit
|
||||
GAL.Format.S8UintD24Unorm => depthStencilMode == DepthStencilMode.Stencil ? ImageAspectFlags.StencilBit : ImageAspectFlags.DepthBit,
|
||||
_ => ImageAspectFlags.ColorBit
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -83,22 +83,22 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
var format = FormatTable.GetFormat(srcFormat);
|
||||
|
||||
var requiredFeatures = FormatFeatureFlags.FormatFeatureSampledImageBit |
|
||||
FormatFeatureFlags.FormatFeatureTransferSrcBit |
|
||||
FormatFeatureFlags.FormatFeatureTransferDstBit;
|
||||
var requiredFeatures = FormatFeatureFlags.SampledImageBit |
|
||||
FormatFeatureFlags.TransferSrcBit |
|
||||
FormatFeatureFlags.TransferDstBit;
|
||||
|
||||
if (srcFormat.IsDepthOrStencil())
|
||||
{
|
||||
requiredFeatures |= FormatFeatureFlags.FormatFeatureDepthStencilAttachmentBit;
|
||||
requiredFeatures |= FormatFeatureFlags.DepthStencilAttachmentBit;
|
||||
}
|
||||
else if (srcFormat.IsRtColorCompatible())
|
||||
{
|
||||
requiredFeatures |= FormatFeatureFlags.FormatFeatureColorAttachmentBit;
|
||||
requiredFeatures |= FormatFeatureFlags.ColorAttachmentBit;
|
||||
}
|
||||
|
||||
if (srcFormat.IsImageCompatible())
|
||||
{
|
||||
requiredFeatures |= FormatFeatureFlags.FormatFeatureStorageImageBit;
|
||||
requiredFeatures |= FormatFeatureFlags.StorageImageBit;
|
||||
}
|
||||
|
||||
if (!OptimalFormatSupports(requiredFeatures, srcFormat) || (IsD24S8(srcFormat) && VulkanConfiguration.ForceD24S8Unsupported))
|
||||
@@ -125,7 +125,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
var format = FormatTable.GetFormat(srcFormat);
|
||||
|
||||
if (!BufferFormatSupports(FormatFeatureFlags.FormatFeatureVertexBufferBit, srcFormat) ||
|
||||
if (!BufferFormatSupports(FormatFeatureFlags.VertexBufferBit, srcFormat) ||
|
||||
(IsRGB16IntFloat(srcFormat) && VulkanConfiguration.ForceRGB16IntFloatUnsupported))
|
||||
{
|
||||
// The format is not supported. Can we convert it to an alternative format?
|
||||
|
@@ -190,14 +190,14 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
for (int index = 0; index < _colors.Length; index++)
|
||||
{
|
||||
_colors[index].Storage.SetModification(
|
||||
AccessFlags.AccessColorAttachmentWriteBit,
|
||||
PipelineStageFlags.PipelineStageColorAttachmentOutputBit);
|
||||
AccessFlags.ColorAttachmentWriteBit,
|
||||
PipelineStageFlags.ColorAttachmentOutputBit);
|
||||
}
|
||||
}
|
||||
|
||||
_depthStencil?.Storage.SetModification(
|
||||
AccessFlags.AccessDepthStencilAttachmentWriteBit,
|
||||
PipelineStageFlags.PipelineStageColorAttachmentOutputBit);
|
||||
AccessFlags.DepthStencilAttachmentWriteBit,
|
||||
PipelineStageFlags.ColorAttachmentOutputBit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -349,8 +349,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
var srcBuffer = srcBufferAuto.Get(cbs, srcOffset, size).Value;
|
||||
var dstBuffer = dstBufferAuto.Get(cbs, 0, newSize).Value;
|
||||
|
||||
var access = supportsUint8 ? AccessFlags.AccessShaderWriteBit : AccessFlags.AccessTransferWriteBit;
|
||||
var stage = supportsUint8 ? PipelineStageFlags.PipelineStageComputeShaderBit : PipelineStageFlags.PipelineStageTransferBit;
|
||||
var access = supportsUint8 ? AccessFlags.ShaderWriteBit : AccessFlags.TransferWriteBit;
|
||||
var stage = supportsUint8 ? PipelineStageFlags.ComputeShaderBit : PipelineStageFlags.TransferBit;
|
||||
|
||||
BufferHolder.InsertBufferBarrier(
|
||||
gd,
|
||||
@@ -358,7 +358,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
dstBuffer,
|
||||
BufferHolder.DefaultAccessFlags,
|
||||
access,
|
||||
PipelineStageFlags.PipelineStageAllCommandsBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
stage,
|
||||
0,
|
||||
newSize);
|
||||
@@ -420,7 +420,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
access,
|
||||
BufferHolder.DefaultAccessFlags,
|
||||
stage,
|
||||
PipelineStageFlags.PipelineStageAllCommandsBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
0,
|
||||
newSize);
|
||||
}
|
||||
@@ -484,9 +484,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
cbs.CommandBuffer,
|
||||
dstBuffer,
|
||||
BufferHolder.DefaultAccessFlags,
|
||||
AccessFlags.AccessTransferWriteBit,
|
||||
PipelineStageFlags.PipelineStageAllCommandsBit,
|
||||
PipelineStageFlags.PipelineStageTransferBit,
|
||||
AccessFlags.TransferWriteBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
PipelineStageFlags.TransferBit,
|
||||
0,
|
||||
convertedCount * outputIndexSize);
|
||||
|
||||
@@ -499,10 +499,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
gd,
|
||||
cbs.CommandBuffer,
|
||||
dstBuffer,
|
||||
AccessFlags.AccessTransferWriteBit,
|
||||
AccessFlags.TransferWriteBit,
|
||||
BufferHolder.DefaultAccessFlags,
|
||||
PipelineStageFlags.PipelineStageTransferBit,
|
||||
PipelineStageFlags.PipelineStageAllCommandsBit,
|
||||
PipelineStageFlags.TransferBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
0,
|
||||
convertedCount * outputIndexSize);
|
||||
}
|
||||
@@ -548,10 +548,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
cbs.CommandBuffer,
|
||||
src.GetImage().Get(cbs).Value,
|
||||
TextureStorage.DefaultAccessMask,
|
||||
AccessFlags.AccessShaderReadBit,
|
||||
PipelineStageFlags.PipelineStageAllCommandsBit,
|
||||
PipelineStageFlags.PipelineStageComputeShaderBit,
|
||||
ImageAspectFlags.ImageAspectColorBit,
|
||||
AccessFlags.ShaderReadBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
PipelineStageFlags.ComputeShaderBit,
|
||||
ImageAspectFlags.ColorBit,
|
||||
src.FirstLayer + srcLayer,
|
||||
src.FirstLevel,
|
||||
depth,
|
||||
@@ -610,11 +610,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
gd.Api,
|
||||
cbs.CommandBuffer,
|
||||
dst.GetImage().Get(cbs).Value,
|
||||
AccessFlags.AccessShaderWriteBit,
|
||||
AccessFlags.ShaderWriteBit,
|
||||
TextureStorage.DefaultAccessMask,
|
||||
PipelineStageFlags.PipelineStageComputeShaderBit,
|
||||
PipelineStageFlags.PipelineStageAllCommandsBit,
|
||||
ImageAspectFlags.ImageAspectColorBit,
|
||||
PipelineStageFlags.ComputeShaderBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
ImageAspectFlags.ColorBit,
|
||||
dst.FirstLayer + dstLayer,
|
||||
dst.FirstLevel,
|
||||
depth,
|
||||
@@ -770,9 +770,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
cbs.CommandBuffer,
|
||||
srcIndirectBuffer.GetBuffer().Get(cbs, srcIndirectBufferOffset, indirectDataSize).Value,
|
||||
BufferHolder.DefaultAccessFlags,
|
||||
AccessFlags.AccessShaderReadBit,
|
||||
PipelineStageFlags.PipelineStageAllCommandsBit,
|
||||
PipelineStageFlags.PipelineStageComputeShaderBit,
|
||||
AccessFlags.ShaderReadBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
PipelineStageFlags.ComputeShaderBit,
|
||||
srcIndirectBufferOffset,
|
||||
indirectDataSize);
|
||||
|
||||
@@ -786,10 +786,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
gd,
|
||||
cbs.CommandBuffer,
|
||||
patternBufferAuto.Get(cbs, ParamsIndirectDispatchOffset, ParamsIndirectDispatchSize).Value,
|
||||
AccessFlags.AccessShaderWriteBit,
|
||||
AccessFlags.AccessIndirectCommandReadBit,
|
||||
PipelineStageFlags.PipelineStageComputeShaderBit,
|
||||
PipelineStageFlags.PipelineStageDrawIndirectBit,
|
||||
AccessFlags.ShaderWriteBit,
|
||||
AccessFlags.IndirectCommandReadBit,
|
||||
PipelineStageFlags.ComputeShaderBit,
|
||||
PipelineStageFlags.DrawIndirectBit,
|
||||
ParamsIndirectDispatchOffset,
|
||||
ParamsIndirectDispatchSize);
|
||||
|
||||
@@ -798,9 +798,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
cbs.CommandBuffer,
|
||||
dstBuffer,
|
||||
BufferHolder.DefaultAccessFlags,
|
||||
AccessFlags.AccessTransferWriteBit,
|
||||
PipelineStageFlags.PipelineStageAllCommandsBit,
|
||||
PipelineStageFlags.PipelineStageTransferBit,
|
||||
AccessFlags.TransferWriteBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
PipelineStageFlags.TransferBit,
|
||||
0,
|
||||
convertedCount * outputIndexSize);
|
||||
|
||||
@@ -814,10 +814,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
gd,
|
||||
cbs.CommandBuffer,
|
||||
dstBuffer,
|
||||
AccessFlags.AccessTransferWriteBit,
|
||||
AccessFlags.TransferWriteBit,
|
||||
BufferHolder.DefaultAccessFlags,
|
||||
PipelineStageFlags.PipelineStageTransferBit,
|
||||
PipelineStageFlags.PipelineStageAllCommandsBit,
|
||||
PipelineStageFlags.TransferBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
0,
|
||||
convertedCount * outputIndexSize);
|
||||
|
||||
|
@@ -33,7 +33,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return default;
|
||||
}
|
||||
|
||||
bool map = flags.HasFlag(MemoryPropertyFlags.MemoryPropertyHostVisibleBit);
|
||||
bool map = flags.HasFlag(MemoryPropertyFlags.HostVisibleBit);
|
||||
return Allocate(memoryTypeIndex, requirements.Size, requirements.Alignment, map);
|
||||
}
|
||||
|
||||
|
@@ -128,14 +128,14 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
MemoryBarrier memoryBarrier = new MemoryBarrier()
|
||||
{
|
||||
SType = StructureType.MemoryBarrier,
|
||||
SrcAccessMask = AccessFlags.AccessMemoryReadBit | AccessFlags.AccessMemoryWriteBit,
|
||||
DstAccessMask = AccessFlags.AccessMemoryReadBit | AccessFlags.AccessMemoryWriteBit
|
||||
SrcAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit,
|
||||
DstAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit
|
||||
};
|
||||
|
||||
Gd.Api.CmdPipelineBarrier(
|
||||
CommandBuffer,
|
||||
PipelineStageFlags.PipelineStageFragmentShaderBit,
|
||||
PipelineStageFlags.PipelineStageFragmentShaderBit,
|
||||
PipelineStageFlags.FragmentShaderBit,
|
||||
PipelineStageFlags.FragmentShaderBit,
|
||||
0,
|
||||
1,
|
||||
memoryBarrier,
|
||||
@@ -161,9 +161,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Cbs.CommandBuffer,
|
||||
dst,
|
||||
BufferHolder.DefaultAccessFlags,
|
||||
AccessFlags.AccessTransferWriteBit,
|
||||
PipelineStageFlags.PipelineStageAllCommandsBit,
|
||||
PipelineStageFlags.PipelineStageTransferBit,
|
||||
AccessFlags.TransferWriteBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
PipelineStageFlags.TransferBit,
|
||||
offset,
|
||||
size);
|
||||
|
||||
@@ -173,10 +173,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Gd,
|
||||
Cbs.CommandBuffer,
|
||||
dst,
|
||||
AccessFlags.AccessTransferWriteBit,
|
||||
AccessFlags.TransferWriteBit,
|
||||
BufferHolder.DefaultAccessFlags,
|
||||
PipelineStageFlags.PipelineStageTransferBit,
|
||||
PipelineStageFlags.PipelineStageAllCommandsBit,
|
||||
PipelineStageFlags.TransferBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
offset,
|
||||
size);
|
||||
}
|
||||
@@ -196,7 +196,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
BeginRenderPass();
|
||||
|
||||
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);
|
||||
|
||||
Gd.Api.CmdClearAttachments(CommandBuffer, 1, &attachment, 1, &clearRect);
|
||||
@@ -219,11 +219,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
BeginRenderPass();
|
||||
|
||||
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)
|
||||
{
|
||||
flags |= ImageAspectFlags.ImageAspectStencilBit;
|
||||
flags |= ImageAspectFlags.StencilBit;
|
||||
}
|
||||
|
||||
var attachment = new ClearAttachment(flags, 0, clearValue);
|
||||
@@ -238,13 +238,13 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
SType = StructureType.MemoryBarrier,
|
||||
SrcAccessMask = BufferHolder.DefaultAccessFlags,
|
||||
DstAccessMask = AccessFlags.AccessIndirectCommandReadBit
|
||||
DstAccessMask = AccessFlags.IndirectCommandReadBit
|
||||
};
|
||||
|
||||
Gd.Api.CmdPipelineBarrier(
|
||||
CommandBuffer,
|
||||
PipelineStageFlags.PipelineStageAllCommandsBit,
|
||||
PipelineStageFlags.PipelineStageDrawIndirectBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
PipelineStageFlags.DrawIndirectBit,
|
||||
0,
|
||||
1,
|
||||
memoryBarrier,
|
||||
@@ -624,7 +624,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
var oldViewports = DynamicState.Viewports;
|
||||
var oldViewportsCount = _newState.ViewportsCount;
|
||||
|
||||
_newState.CullMode = CullModeFlags.CullModeNone;
|
||||
_newState.CullMode = CullModeFlags.None;
|
||||
_newState.StencilTestEnable = false;
|
||||
_newState.DepthTestEnable = false;
|
||||
_newState.DepthWriteEnable = false;
|
||||
@@ -737,7 +737,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public void SetFaceCulling(bool enable, Face face)
|
||||
{
|
||||
_newState.CullMode = enable ? face.Convert() : CullModeFlags.CullModeNone;
|
||||
_newState.CullMode = enable ? face.Convert() : CullModeFlags.None;
|
||||
SignalStateChange();
|
||||
}
|
||||
|
||||
@@ -1200,14 +1200,14 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
MemoryBarrier memoryBarrier = new MemoryBarrier()
|
||||
{
|
||||
SType = StructureType.MemoryBarrier,
|
||||
SrcAccessMask = AccessFlags.AccessMemoryReadBit | AccessFlags.AccessMemoryWriteBit,
|
||||
DstAccessMask = AccessFlags.AccessMemoryReadBit | AccessFlags.AccessMemoryWriteBit
|
||||
SrcAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit,
|
||||
DstAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit
|
||||
};
|
||||
|
||||
Gd.Api.CmdPipelineBarrier(
|
||||
CommandBuffer,
|
||||
PipelineStageFlags.PipelineStageFragmentShaderBit,
|
||||
PipelineStageFlags.PipelineStageFragmentShaderBit,
|
||||
PipelineStageFlags.FragmentShaderBit,
|
||||
PipelineStageFlags.FragmentShaderBit,
|
||||
0,
|
||||
1,
|
||||
memoryBarrier,
|
||||
|
@@ -6,8 +6,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
static class PipelineConverter
|
||||
{
|
||||
private const AccessFlags SubpassSrcAccessMask = AccessFlags.AccessMemoryReadBit | AccessFlags.AccessMemoryWriteBit | AccessFlags.AccessColorAttachmentWriteBit;
|
||||
private const AccessFlags SubpassDstAccessMask = AccessFlags.AccessMemoryReadBit | AccessFlags.AccessMemoryWriteBit | AccessFlags.AccessShaderReadBit;
|
||||
private const AccessFlags SubpassSrcAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit | AccessFlags.ColorAttachmentWriteBit;
|
||||
private const AccessFlags SubpassDstAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit | AccessFlags.ShaderReadBit;
|
||||
|
||||
public static unsafe DisposableRenderPass ToRenderPass(this ProgramPipelineState state, VulkanRenderer gd, Device device)
|
||||
{
|
||||
@@ -129,8 +129,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return new SubpassDependency(
|
||||
0,
|
||||
0,
|
||||
PipelineStageFlags.PipelineStageAllGraphicsBit,
|
||||
PipelineStageFlags.PipelineStageAllGraphicsBit,
|
||||
PipelineStageFlags.AllGraphicsBit,
|
||||
PipelineStageFlags.AllGraphicsBit,
|
||||
SubpassSrcAccessMask,
|
||||
SubpassDstAccessMask,
|
||||
0);
|
||||
@@ -143,8 +143,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
null,
|
||||
0,
|
||||
0,
|
||||
PipelineStageFlags.PipelineStageAllGraphicsBit,
|
||||
PipelineStageFlags.PipelineStageAllGraphicsBit,
|
||||
PipelineStageFlags.AllGraphicsBit,
|
||||
PipelineStageFlags.AllGraphicsBit,
|
||||
SubpassSrcAccessMask,
|
||||
SubpassDstAccessMask,
|
||||
0);
|
||||
@@ -157,7 +157,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
// 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.
|
||||
|
||||
|
@@ -145,12 +145,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
private void RecordStencilMasks(Vk api, CommandBuffer commandBuffer)
|
||||
{
|
||||
api.CmdSetStencilCompareMask(commandBuffer, StencilFaceFlags.StencilFaceBackBit, _backCompareMask);
|
||||
api.CmdSetStencilWriteMask(commandBuffer, StencilFaceFlags.StencilFaceBackBit, _backWriteMask);
|
||||
api.CmdSetStencilReference(commandBuffer, StencilFaceFlags.StencilFaceBackBit, _backReference);
|
||||
api.CmdSetStencilCompareMask(commandBuffer, StencilFaceFlags.StencilFaceFrontBit, _frontCompareMask);
|
||||
api.CmdSetStencilWriteMask(commandBuffer, StencilFaceFlags.StencilFaceFrontBit, _frontWriteMask);
|
||||
api.CmdSetStencilReference(commandBuffer, StencilFaceFlags.StencilFaceFrontBit, _frontReference);
|
||||
api.CmdSetStencilCompareMask(commandBuffer, StencilFaceFlags.FaceBackBit, _backCompareMask);
|
||||
api.CmdSetStencilWriteMask(commandBuffer, StencilFaceFlags.FaceBackBit, _backWriteMask);
|
||||
api.CmdSetStencilReference(commandBuffer, StencilFaceFlags.FaceBackBit, _backReference);
|
||||
api.CmdSetStencilCompareMask(commandBuffer, StencilFaceFlags.FaceFrontBit, _frontCompareMask);
|
||||
api.CmdSetStencilWriteMask(commandBuffer, StencilFaceFlags.FaceFrontBit, _frontWriteMask);
|
||||
api.CmdSetStencilReference(commandBuffer, StencilFaceFlags.FaceFrontBit, _frontReference);
|
||||
}
|
||||
|
||||
private void RecordViewport(Vk api, CommandBuffer commandBuffer)
|
||||
|
@@ -125,7 +125,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
if (Gd.Capabilities.SupportsConditionalRendering)
|
||||
{
|
||||
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()
|
||||
{
|
||||
|
@@ -8,9 +8,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
static class PipelineLayoutFactory
|
||||
{
|
||||
private const ShaderStageFlags SupportBufferStages =
|
||||
ShaderStageFlags.ShaderStageVertexBit |
|
||||
ShaderStageFlags.ShaderStageFragmentBit |
|
||||
ShaderStageFlags.ShaderStageComputeBit;
|
||||
ShaderStageFlags.VertexBit |
|
||||
ShaderStageFlags.FragmentBit |
|
||||
ShaderStageFlags.ComputeBit;
|
||||
|
||||
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
|
||||
{
|
||||
1 => ShaderStageFlags.ShaderStageFragmentBit,
|
||||
2 => ShaderStageFlags.ShaderStageGeometryBit,
|
||||
3 => ShaderStageFlags.ShaderStageTessellationControlBit,
|
||||
4 => ShaderStageFlags.ShaderStageTessellationEvaluationBit,
|
||||
_ => ShaderStageFlags.ShaderStageVertexBit | ShaderStageFlags.ShaderStageComputeBit
|
||||
1 => ShaderStageFlags.FragmentBit,
|
||||
2 => ShaderStageFlags.GeometryBit,
|
||||
3 => ShaderStageFlags.TessellationControlBit,
|
||||
4 => ShaderStageFlags.TessellationEvaluationBit,
|
||||
_ => ShaderStageFlags.VertexBit | ShaderStageFlags.ComputeBit
|
||||
};
|
||||
|
||||
void Set(DescriptorSetLayoutBinding* bindings, int maxPerStage, DescriptorType type, int start, int skip)
|
||||
@@ -93,7 +93,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
SType = StructureType.DescriptorSetLayoutCreateInfo,
|
||||
PBindings = uLayoutBindings,
|
||||
BindingCount = (uint)uCount,
|
||||
Flags = usePd ? DescriptorSetLayoutCreateFlags.DescriptorSetLayoutCreatePushDescriptorBitKhr : 0
|
||||
Flags = usePd ? DescriptorSetLayoutCreateFlags.PushDescriptorBitKhr : 0
|
||||
};
|
||||
|
||||
var sDescriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo()
|
||||
|
@@ -41,7 +41,7 @@ namespace Ryujinx.Graphics.Vulkan.Queries
|
||||
if (_isSupported)
|
||||
{
|
||||
QueryPipelineStatisticFlags flags = type == CounterType.PrimitivesGenerated ?
|
||||
QueryPipelineStatisticFlags.QueryPipelineStatisticGeometryShaderPrimitivesBit : 0;
|
||||
QueryPipelineStatisticFlags.GeometryShaderPrimitivesBit : 0;
|
||||
|
||||
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;
|
||||
|
||||
QueryResultFlags flags = QueryResultFlags.QueryResultWaitBit;
|
||||
QueryResultFlags flags = QueryResultFlags.ResultWaitBit;
|
||||
|
||||
if (!_result32Bit)
|
||||
{
|
||||
flags |= QueryResultFlags.QueryResult64Bit;
|
||||
flags |= QueryResultFlags.Result64Bit;
|
||||
}
|
||||
|
||||
_api.CmdCopyQueryPoolResults(
|
||||
|
@@ -15,9 +15,9 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.7.5" />
|
||||
<PackageReference Include="shaderc.net" Version="0.1.0" />
|
||||
<PackageReference Include="Silk.NET.Vulkan" Version="2.10.1" />
|
||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.10.1" />
|
||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.10.1" />
|
||||
<PackageReference Include="Silk.NET.Vulkan" Version="2.16.0" />
|
||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" />
|
||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />
|
||||
<PackageReference Include="System.IO.FileSystem.Primitives" Version="4.3.0" />
|
||||
<PackageReference Include="System.Net.NameResolution" Version="4.3.0" />
|
||||
<PackageReference Include="System.Threading.ThreadPool" Version="4.3.0" />
|
||||
|
@@ -82,14 +82,14 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
stages |= 1u << shader.StageFlags switch
|
||||
{
|
||||
ShaderStageFlags.ShaderStageFragmentBit => 1,
|
||||
ShaderStageFlags.ShaderStageGeometryBit => 2,
|
||||
ShaderStageFlags.ShaderStageTessellationControlBit => 3,
|
||||
ShaderStageFlags.ShaderStageTessellationEvaluationBit => 4,
|
||||
ShaderStageFlags.FragmentBit => 1,
|
||||
ShaderStageFlags.GeometryBit => 2,
|
||||
ShaderStageFlags.TessellationControlBit => 3,
|
||||
ShaderStageFlags.TessellationEvaluationBit => 4,
|
||||
_ => 0
|
||||
};
|
||||
|
||||
if (shader.StageFlags == ShaderStageFlags.ShaderStageComputeBit)
|
||||
if (shader.StageFlags == ShaderStageFlags.ComputeBit)
|
||||
{
|
||||
IsCompute = true;
|
||||
}
|
||||
|
@@ -11,6 +11,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
public ulong ID;
|
||||
public MultiFenceHolder Waitable;
|
||||
public bool Signalled;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
SyncHandle result = null;
|
||||
@@ -75,11 +107,15 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return;
|
||||
}
|
||||
|
||||
bool signaled = result.Waitable.WaitForFences(_gd.Api, _device, 1000000000);
|
||||
bool signaled = result.Signalled || result.Waitable.WaitForFences(_gd.Api, _device, 1000000000);
|
||||
if (!signaled)
|
||||
{
|
||||
Logger.Error?.PrintMsg(LogClass.Gpu, $"VK Sync Object {result.ID} failed to signal within 1000ms. Continuing...");
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Signalled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -60,9 +60,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
commandBuffer,
|
||||
srcImage,
|
||||
TextureStorage.DefaultAccessMask,
|
||||
AccessFlags.AccessTransferReadBit,
|
||||
PipelineStageFlags.PipelineStageAllCommandsBit,
|
||||
PipelineStageFlags.PipelineStageTransferBit,
|
||||
AccessFlags.TransferReadBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
PipelineStageFlags.TransferBit,
|
||||
srcAspectFlags,
|
||||
srcLayer,
|
||||
srcLevel,
|
||||
@@ -103,10 +103,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
api,
|
||||
commandBuffer,
|
||||
dstImage,
|
||||
AccessFlags.AccessTransferWriteBit,
|
||||
AccessFlags.TransferWriteBit,
|
||||
TextureStorage.DefaultAccessMask,
|
||||
PipelineStageFlags.PipelineStageTransferBit,
|
||||
PipelineStageFlags.PipelineStageAllCommandsBit,
|
||||
PipelineStageFlags.TransferBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
dstAspectFlags,
|
||||
dstLayer,
|
||||
dstLevel,
|
||||
@@ -285,9 +285,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
commandBuffer,
|
||||
srcImage,
|
||||
TextureStorage.DefaultAccessMask,
|
||||
AccessFlags.AccessTransferReadBit,
|
||||
PipelineStageFlags.PipelineStageAllCommandsBit,
|
||||
PipelineStageFlags.PipelineStageTransferBit,
|
||||
AccessFlags.TransferReadBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
PipelineStageFlags.TransferBit,
|
||||
srcAspect,
|
||||
srcViewLayer + srcLayer,
|
||||
srcViewLevel + srcLevel,
|
||||
@@ -345,10 +345,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
api,
|
||||
commandBuffer,
|
||||
dstImage,
|
||||
AccessFlags.AccessTransferWriteBit,
|
||||
AccessFlags.TransferWriteBit,
|
||||
TextureStorage.DefaultAccessMask,
|
||||
PipelineStageFlags.PipelineStageTransferBit,
|
||||
PipelineStageFlags.PipelineStageAllCommandsBit,
|
||||
PipelineStageFlags.TransferBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
dstAspect,
|
||||
dstViewLayer + dstLayer,
|
||||
dstViewLevel + dstLevel,
|
||||
@@ -370,8 +370,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
SType = StructureType.SubpassDescriptionDepthStencilResolve,
|
||||
PDepthStencilResolveAttachment = &dsResolveAttachmentReference,
|
||||
DepthResolveMode = ResolveModeFlags.ResolveModeSampleZeroBit,
|
||||
StencilResolveMode = ResolveModeFlags.ResolveModeSampleZeroBit
|
||||
DepthResolveMode = ResolveModeFlags.SampleZeroBit,
|
||||
StencilResolveMode = ResolveModeFlags.SampleZeroBit
|
||||
};
|
||||
|
||||
var subpass = new SubpassDescription2()
|
||||
|
@@ -12,22 +12,22 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
class TextureStorage : IDisposable
|
||||
{
|
||||
private const MemoryPropertyFlags DefaultImageMemoryFlags =
|
||||
MemoryPropertyFlags.MemoryPropertyDeviceLocalBit;
|
||||
MemoryPropertyFlags.DeviceLocalBit;
|
||||
|
||||
private const ImageUsageFlags DefaultUsageFlags =
|
||||
ImageUsageFlags.ImageUsageSampledBit |
|
||||
ImageUsageFlags.ImageUsageTransferSrcBit |
|
||||
ImageUsageFlags.ImageUsageTransferDstBit;
|
||||
ImageUsageFlags.SampledBit |
|
||||
ImageUsageFlags.TransferSrcBit |
|
||||
ImageUsageFlags.TransferDstBit;
|
||||
|
||||
public const AccessFlags DefaultAccessMask =
|
||||
AccessFlags.AccessShaderReadBit |
|
||||
AccessFlags.AccessShaderWriteBit |
|
||||
AccessFlags.AccessColorAttachmentReadBit |
|
||||
AccessFlags.AccessColorAttachmentWriteBit |
|
||||
AccessFlags.AccessDepthStencilAttachmentReadBit |
|
||||
AccessFlags.AccessDepthStencilAttachmentWriteBit |
|
||||
AccessFlags.AccessTransferReadBit |
|
||||
AccessFlags.AccessTransferWriteBit;
|
||||
AccessFlags.ShaderReadBit |
|
||||
AccessFlags.ShaderWriteBit |
|
||||
AccessFlags.ColorAttachmentReadBit |
|
||||
AccessFlags.ColorAttachmentWriteBit |
|
||||
AccessFlags.DepthStencilAttachmentReadBit |
|
||||
AccessFlags.DepthStencilAttachmentWriteBit |
|
||||
AccessFlags.TransferReadBit |
|
||||
AccessFlags.TransferWriteBit;
|
||||
|
||||
private readonly VulkanRenderer _gd;
|
||||
|
||||
@@ -83,32 +83,32 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
if (info.Format.IsDepthOrStencil())
|
||||
{
|
||||
usage |= ImageUsageFlags.ImageUsageDepthStencilAttachmentBit;
|
||||
usage |= ImageUsageFlags.DepthStencilAttachmentBit;
|
||||
}
|
||||
else if (info.Format.IsRtColorCompatible())
|
||||
{
|
||||
usage |= ImageUsageFlags.ImageUsageColorAttachmentBit;
|
||||
usage |= ImageUsageFlags.ColorAttachmentBit;
|
||||
}
|
||||
|
||||
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.
|
||||
bool isCube = info.Target == Target.Cubemap || info.Target == Target.CubemapArray;
|
||||
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()
|
||||
@@ -290,8 +290,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
_gd.Api.CmdPipelineBarrier(
|
||||
cbs.CommandBuffer,
|
||||
PipelineStageFlags.PipelineStageTopOfPipeBit,
|
||||
PipelineStageFlags.PipelineStageAllCommandsBit,
|
||||
PipelineStageFlags.TopOfPipeBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
0,
|
||||
0,
|
||||
null,
|
||||
@@ -308,9 +308,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
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.
|
||||
@@ -428,7 +428,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public void InsertBarrier(CommandBufferScoped cbs, AccessFlags dstAccessFlags, PipelineStageFlags dstStageFlags)
|
||||
{
|
||||
if (_lastModificationAccess != AccessFlags.AccessNoneKhr)
|
||||
if (_lastModificationAccess != AccessFlags.NoneKhr)
|
||||
{
|
||||
ImageAspectFlags aspectFlags;
|
||||
|
||||
@@ -436,20 +436,20 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
if (_info.Format == GAL.Format.S8Uint)
|
||||
{
|
||||
aspectFlags = ImageAspectFlags.ImageAspectStencilBit;
|
||||
aspectFlags = ImageAspectFlags.StencilBit;
|
||||
}
|
||||
else if (_info.Format == GAL.Format.D16Unorm || _info.Format == GAL.Format.D32Float)
|
||||
{
|
||||
aspectFlags = ImageAspectFlags.ImageAspectDepthBit;
|
||||
aspectFlags = ImageAspectFlags.DepthBit;
|
||||
}
|
||||
else
|
||||
{
|
||||
aspectFlags = ImageAspectFlags.ImageAspectDepthBit | ImageAspectFlags.ImageAspectStencilBit;
|
||||
aspectFlags = ImageAspectFlags.DepthBit | ImageAspectFlags.StencilBit;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
aspectFlags = ImageAspectFlags.ImageAspectColorBit;
|
||||
aspectFlags = ImageAspectFlags.ColorBit;
|
||||
}
|
||||
|
||||
TextureView.InsertImageBarrier(
|
||||
@@ -466,7 +466,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_info.GetLayers(),
|
||||
_info.Levels);
|
||||
|
||||
_lastModificationAccess = AccessFlags.AccessNoneKhr;
|
||||
_lastModificationAccess = AccessFlags.NoneKhr;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -126,7 +126,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
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;
|
||||
@@ -322,8 +322,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
return;
|
||||
}
|
||||
else if (_gd.FormatCapabilities.OptimalFormatSupports(FormatFeatureFlags.FormatFeatureBlitSrcBit, srcFormat) &&
|
||||
_gd.FormatCapabilities.OptimalFormatSupports(FormatFeatureFlags.FormatFeatureBlitDstBit, dstFormat))
|
||||
else if (_gd.FormatCapabilities.OptimalFormatSupports(FormatFeatureFlags.BlitSrcBit, srcFormat) &&
|
||||
_gd.FormatCapabilities.OptimalFormatSupports(FormatFeatureFlags.BlitDstBit, dstFormat))
|
||||
{
|
||||
TextureCopy.Blit(
|
||||
_gd.Api,
|
||||
@@ -402,8 +402,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
layers,
|
||||
levels,
|
||||
linearFilter,
|
||||
ImageAspectFlags.ImageAspectColorBit,
|
||||
ImageAspectFlags.ImageAspectColorBit);
|
||||
ImageAspectFlags.ColorBit,
|
||||
ImageAspectFlags.ColorBit);
|
||||
}
|
||||
|
||||
private static void BlitDepthStencilWithBuffer(
|
||||
@@ -471,10 +471,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
gd,
|
||||
cbs.CommandBuffer,
|
||||
srcTempBuffer.GetBuffer().Get(cbs, 0, srcSize).Value,
|
||||
AccessFlags.AccessTransferWriteBit,
|
||||
AccessFlags.AccessTransferReadBit,
|
||||
PipelineStageFlags.PipelineStageTransferBit,
|
||||
PipelineStageFlags.PipelineStageTransferBit,
|
||||
AccessFlags.TransferWriteBit,
|
||||
AccessFlags.TransferReadBit,
|
||||
PipelineStageFlags.TransferBit,
|
||||
PipelineStageFlags.TransferBit,
|
||||
0,
|
||||
srcSize);
|
||||
|
||||
@@ -498,10 +498,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
gd.Api,
|
||||
cbs.CommandBuffer,
|
||||
srcTemp.GetImage().Get(cbs).Value,
|
||||
AccessFlags.AccessTransferWriteBit,
|
||||
AccessFlags.AccessTransferReadBit,
|
||||
PipelineStageFlags.PipelineStageTransferBit,
|
||||
PipelineStageFlags.PipelineStageTransferBit,
|
||||
AccessFlags.TransferWriteBit,
|
||||
AccessFlags.TransferReadBit,
|
||||
PipelineStageFlags.TransferBit,
|
||||
PipelineStageFlags.TransferBit,
|
||||
aspectFlags,
|
||||
0,
|
||||
0,
|
||||
@@ -531,10 +531,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
gd.Api,
|
||||
cbs.CommandBuffer,
|
||||
dstTemp.GetImage().Get(cbs).Value,
|
||||
AccessFlags.AccessTransferWriteBit,
|
||||
AccessFlags.AccessTransferReadBit,
|
||||
PipelineStageFlags.PipelineStageTransferBit,
|
||||
PipelineStageFlags.PipelineStageTransferBit,
|
||||
AccessFlags.TransferWriteBit,
|
||||
AccessFlags.TransferReadBit,
|
||||
PipelineStageFlags.TransferBit,
|
||||
PipelineStageFlags.TransferBit,
|
||||
aspectFlags,
|
||||
0,
|
||||
0,
|
||||
@@ -561,10 +561,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
gd,
|
||||
cbs.CommandBuffer,
|
||||
dstTempBuffer.GetBuffer().Get(cbs, 0, dstSize).Value,
|
||||
AccessFlags.AccessTransferWriteBit,
|
||||
AccessFlags.AccessTransferReadBit,
|
||||
PipelineStageFlags.PipelineStageTransferBit,
|
||||
PipelineStageFlags.PipelineStageTransferBit,
|
||||
AccessFlags.TransferWriteBit,
|
||||
AccessFlags.TransferReadBit,
|
||||
PipelineStageFlags.TransferBit,
|
||||
PipelineStageFlags.TransferBit,
|
||||
0,
|
||||
dstSize);
|
||||
|
||||
@@ -585,8 +585,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
false);
|
||||
}
|
||||
|
||||
SlowBlit(d32SrcStorage, d32DstStorage, ImageAspectFlags.ImageAspectDepthBit);
|
||||
SlowBlit(s8SrcStorage, s8DstStorage, ImageAspectFlags.ImageAspectStencilBit);
|
||||
SlowBlit(d32SrcStorage, d32DstStorage, ImageAspectFlags.DepthBit);
|
||||
SlowBlit(s8SrcStorage, s8DstStorage, ImageAspectFlags.StencilBit);
|
||||
}
|
||||
|
||||
public static unsafe void InsertImageBarrier(
|
||||
@@ -631,7 +631,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
private bool SupportsBlitFromD32FS8ToD32FAndS8()
|
||||
{
|
||||
var formatFeatureFlags = FormatFeatureFlags.FormatFeatureBlitSrcBit | FormatFeatureFlags.FormatFeatureBlitDstBit;
|
||||
var formatFeatureFlags = FormatFeatureFlags.BlitSrcBit | FormatFeatureFlags.BlitDstBit;
|
||||
return _gd.FormatCapabilities.OptimalFormatSupports(formatFeatureFlags, GAL.Format.D32Float) &&
|
||||
_gd.FormatCapabilities.OptimalFormatSupports(formatFeatureFlags, GAL.Format.S8Uint);
|
||||
}
|
||||
@@ -903,9 +903,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
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(
|
||||
@@ -962,9 +962,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
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);
|
||||
|
@@ -159,20 +159,20 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityErrorBitExt))
|
||||
if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt))
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Gpu, msg);
|
||||
//throw new Exception(msg);
|
||||
}
|
||||
else if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityWarningBitExt))
|
||||
else if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.WarningBitExt))
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, msg);
|
||||
}
|
||||
else if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityInfoBitExt))
|
||||
else if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.InfoBitExt))
|
||||
{
|
||||
Logger.Info?.Print(LogClass.Gpu, msg);
|
||||
}
|
||||
else // if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityVerboseBitExt))
|
||||
else // if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.VerboseBitExt))
|
||||
{
|
||||
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)
|
||||
{
|
||||
const QueueFlags RequiredFlags = QueueFlags.QueueGraphicsBit | QueueFlags.QueueComputeBit;
|
||||
const QueueFlags RequiredFlags = QueueFlags.GraphicsBit | QueueFlags.ComputeBit;
|
||||
|
||||
var khrSurface = new KhrSurface(api.Context);
|
||||
|
||||
@@ -561,24 +561,24 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
var filterLogType = logLevel switch
|
||||
{
|
||||
GraphicsDebugLevel.Error => DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypeValidationBitExt,
|
||||
GraphicsDebugLevel.Slowdowns => DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypeValidationBitExt |
|
||||
DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypePerformanceBitExt,
|
||||
GraphicsDebugLevel.All => DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypeGeneralBitExt |
|
||||
DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypeValidationBitExt |
|
||||
DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypePerformanceBitExt,
|
||||
GraphicsDebugLevel.Error => DebugUtilsMessageTypeFlagsEXT.ValidationBitExt,
|
||||
GraphicsDebugLevel.Slowdowns => DebugUtilsMessageTypeFlagsEXT.ValidationBitExt |
|
||||
DebugUtilsMessageTypeFlagsEXT.PerformanceBitExt,
|
||||
GraphicsDebugLevel.All => DebugUtilsMessageTypeFlagsEXT.GeneralBitExt |
|
||||
DebugUtilsMessageTypeFlagsEXT.ValidationBitExt |
|
||||
DebugUtilsMessageTypeFlagsEXT.PerformanceBitExt,
|
||||
_ => throw new ArgumentException($"Invalid log level \"{logLevel}\".")
|
||||
};
|
||||
|
||||
var filterLogSeverity = logLevel switch
|
||||
{
|
||||
GraphicsDebugLevel.Error => DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityErrorBitExt,
|
||||
GraphicsDebugLevel.Slowdowns => DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityErrorBitExt |
|
||||
DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityWarningBitExt,
|
||||
GraphicsDebugLevel.All => DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityInfoBitExt |
|
||||
DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityWarningBitExt |
|
||||
DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityVerboseBitExt |
|
||||
DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityErrorBitExt,
|
||||
GraphicsDebugLevel.Error => DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt,
|
||||
GraphicsDebugLevel.Slowdowns => DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt |
|
||||
DebugUtilsMessageSeverityFlagsEXT.WarningBitExt,
|
||||
GraphicsDebugLevel.All => DebugUtilsMessageSeverityFlagsEXT.InfoBitExt |
|
||||
DebugUtilsMessageSeverityFlagsEXT.WarningBitExt |
|
||||
DebugUtilsMessageSeverityFlagsEXT.VerboseBitExt |
|
||||
DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt,
|
||||
_ => throw new ArgumentException($"Invalid log level \"{logLevel}\".")
|
||||
};
|
||||
|
||||
|
@@ -341,11 +341,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
public unsafe Capabilities GetCapabilities()
|
||||
{
|
||||
FormatFeatureFlags compressedFormatFeatureFlags =
|
||||
FormatFeatureFlags.FormatFeatureSampledImageBit |
|
||||
FormatFeatureFlags.FormatFeatureSampledImageFilterLinearBit |
|
||||
FormatFeatureFlags.FormatFeatureBlitSrcBit |
|
||||
FormatFeatureFlags.FormatFeatureTransferSrcBit |
|
||||
FormatFeatureFlags.FormatFeatureTransferDstBit;
|
||||
FormatFeatureFlags.SampledImageBit |
|
||||
FormatFeatureFlags.SampledImageFilterLinearBit |
|
||||
FormatFeatureFlags.BlitSrcBit |
|
||||
FormatFeatureFlags.TransferSrcBit |
|
||||
FormatFeatureFlags.TransferDstBit;
|
||||
|
||||
bool supportsBc123CompressionFormat = FormatCapabilities.OptimalFormatsSupport(compressedFormatFeatureFlags,
|
||||
GAL.Format.Bc1RgbaSrgb,
|
||||
@@ -565,6 +565,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_syncManager.Wait(id);
|
||||
}
|
||||
|
||||
public ulong GetCurrentSync()
|
||||
{
|
||||
return _syncManager.GetCurrent();
|
||||
}
|
||||
|
||||
public void Screenshot()
|
||||
{
|
||||
_window.ScreenCaptureRequested = true;
|
||||
|
@@ -109,11 +109,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
ImageFormat = surfaceFormat.Format,
|
||||
ImageColorSpace = surfaceFormat.ColorSpace,
|
||||
ImageExtent = extent,
|
||||
ImageUsage = ImageUsageFlags.ImageUsageColorAttachmentBit | ImageUsageFlags.ImageUsageTransferDstBit,
|
||||
ImageUsage = ImageUsageFlags.ColorAttachmentBit | ImageUsageFlags.TransferDstBit,
|
||||
ImageSharingMode = SharingMode.Exclusive,
|
||||
ImageArrayLayers = 1,
|
||||
PreTransform = capabilities.CurrentTransform,
|
||||
CompositeAlpha = CompositeAlphaFlagsKHR.CompositeAlphaOpaqueBitKhr,
|
||||
CompositeAlpha = CompositeAlphaFlagsKHR.OpaqueBitKhr,
|
||||
PresentMode = ChooseSwapPresentMode(presentModes, _vsyncEnabled),
|
||||
Clipped = true,
|
||||
OldSwapchain = oldSwapchain
|
||||
@@ -146,7 +146,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
ComponentSwizzle.B,
|
||||
ComponentSwizzle.A);
|
||||
|
||||
var aspectFlags = ImageAspectFlags.ImageAspectColorBit;
|
||||
var aspectFlags = ImageAspectFlags.ColorBit;
|
||||
|
||||
var subresourceRange = new ImageSubresourceRange(aspectFlags, 0, 1, 0, 1);
|
||||
|
||||
@@ -154,7 +154,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
SType = StructureType.ImageViewCreateInfo,
|
||||
Image = swapchainImage,
|
||||
ViewType = ImageViewType.ImageViewType2D,
|
||||
ViewType = ImageViewType.Type2D,
|
||||
Format = format,
|
||||
Components = componentMapping,
|
||||
SubresourceRange = subresourceRange
|
||||
@@ -168,12 +168,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (format.Format == VkFormat.B8G8R8A8Unorm && format.ColorSpace == ColorSpaceKHR.ColorspaceSrgbNonlinearKhr)
|
||||
if (format.Format == VkFormat.B8G8R8A8Unorm && format.ColorSpace == ColorSpaceKHR.PaceSrgbNonlinearKhr)
|
||||
{
|
||||
return format;
|
||||
}
|
||||
@@ -184,21 +184,21 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
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
|
||||
{
|
||||
return PresentModeKHR.PresentModeFifoKhr;
|
||||
return PresentModeKHR.FifoKhr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,7 +254,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
cbs.CommandBuffer,
|
||||
swapchainImage,
|
||||
0,
|
||||
AccessFlags.AccessTransferWriteBit,
|
||||
AccessFlags.TransferWriteBit,
|
||||
ImageLayout.Undefined,
|
||||
ImageLayout.General);
|
||||
|
||||
@@ -339,7 +339,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_gd.CommandBufferPool.Return(
|
||||
cbs,
|
||||
stackalloc[] { _imageAvailableSemaphore },
|
||||
stackalloc[] { PipelineStageFlags.PipelineStageColorAttachmentOutputBit },
|
||||
stackalloc[] { PipelineStageFlags.ColorAttachmentOutputBit },
|
||||
stackalloc[] { _renderFinishedSemaphore });
|
||||
|
||||
// TODO: Present queue.
|
||||
@@ -373,7 +373,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
ImageLayout srcLayout,
|
||||
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()
|
||||
{
|
||||
@@ -390,8 +390,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
_gd.Api.CmdPipelineBarrier(
|
||||
commandBuffer,
|
||||
PipelineStageFlags.PipelineStageTopOfPipeBit,
|
||||
PipelineStageFlags.PipelineStageAllCommandsBit,
|
||||
PipelineStageFlags.TopOfPipeBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
0,
|
||||
0,
|
||||
null,
|
||||
|
@@ -638,16 +638,7 @@ namespace Ryujinx.Headless.SDL2
|
||||
|
||||
Translator.IsReadyForTranslation.Reset();
|
||||
|
||||
Thread windowThread = new Thread(() =>
|
||||
{
|
||||
ExecutionEntrypoint();
|
||||
})
|
||||
{
|
||||
Name = "GUI.WindowThread"
|
||||
};
|
||||
|
||||
windowThread.Start();
|
||||
windowThread.Join();
|
||||
ExecutionEntrypoint();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@@ -168,14 +168,6 @@ namespace Ryujinx.Headless.SDL2
|
||||
|
||||
public void Render()
|
||||
{
|
||||
InitializeWindowRenderer();
|
||||
|
||||
Device.Gpu.Renderer.Initialize(_glLogLevel);
|
||||
|
||||
InitializeRenderer();
|
||||
|
||||
_gpuVendorName = GetGpuVendorName();
|
||||
|
||||
Device.Gpu.Renderer.RunLoop(() =>
|
||||
{
|
||||
Device.Gpu.SetGpuThread();
|
||||
@@ -323,6 +315,14 @@ namespace Ryujinx.Headless.SDL2
|
||||
|
||||
InitializeWindow();
|
||||
|
||||
InitializeWindowRenderer();
|
||||
|
||||
Device.Gpu.Renderer.Initialize(_glLogLevel);
|
||||
|
||||
InitializeRenderer();
|
||||
|
||||
_gpuVendorName = GetGpuVendorName();
|
||||
|
||||
Thread renderLoopThread = new Thread(Render)
|
||||
{
|
||||
Name = "GUI.RenderLoop"
|
||||
|
@@ -28,6 +28,8 @@ namespace Ryujinx.SDL2.Common
|
||||
}
|
||||
}
|
||||
|
||||
public static Action<Action> MainThreadDispatcher { get; set; }
|
||||
|
||||
private const uint SdlInitFlags = SDL_INIT_EVENTS | SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO | SDL_INIT_VIDEO;
|
||||
|
||||
private bool _isRunning;
|
||||
@@ -69,7 +71,7 @@ namespace Ryujinx.SDL2.Common
|
||||
|
||||
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);
|
||||
|
||||
@@ -154,10 +156,13 @@ namespace Ryujinx.SDL2.Common
|
||||
|
||||
while (_isRunning)
|
||||
{
|
||||
while (SDL_PollEvent(out SDL_Event evnt) != 0)
|
||||
MainThreadDispatcher?.Invoke(() =>
|
||||
{
|
||||
HandleSDLEvent(ref evnt);
|
||||
}
|
||||
while (SDL_PollEvent(out SDL_Event evnt) != 0)
|
||||
{
|
||||
HandleSDLEvent(ref evnt);
|
||||
}
|
||||
});
|
||||
|
||||
waitHandle.Wait(WaitTimeMs);
|
||||
}
|
||||
|
@@ -444,7 +444,10 @@ namespace Ryujinx.Ui.App.Common
|
||||
continue;
|
||||
}
|
||||
|
||||
ApplicationMetadata appMetadata = LoadAndSaveMetaData(titleId);
|
||||
ApplicationMetadata appMetadata = LoadAndSaveMetaData(titleId, appMetadata =>
|
||||
{
|
||||
appMetadata.Title = titleName;
|
||||
});
|
||||
|
||||
if (appMetadata.LastPlayed != "Never" && !DateTime.TryParse(appMetadata.LastPlayed, out _))
|
||||
{
|
||||
|
@@ -2,6 +2,7 @@
|
||||
{
|
||||
public class ApplicationMetadata
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public bool Favorite { get; set; }
|
||||
public double TimePlayed { get; set; }
|
||||
public string LastPlayed { get; set; } = "Never";
|
||||
|
@@ -7,6 +7,7 @@ using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.System;
|
||||
using Ryujinx.Common.SystemInfo;
|
||||
using Ryujinx.Modules;
|
||||
using Ryujinx.SDL2.Common;
|
||||
using Ryujinx.Ui;
|
||||
using Ryujinx.Ui.Common;
|
||||
using Ryujinx.Ui.Common.Configuration;
|
||||
@@ -111,6 +112,15 @@ namespace Ryujinx
|
||||
// Initialize Discord integration.
|
||||
DiscordIntegrationModule.Initialize();
|
||||
|
||||
// Initialize SDL2 driver
|
||||
SDL2Driver.MainThreadDispatcher = action =>
|
||||
{
|
||||
Gtk.Application.Invoke(delegate
|
||||
{
|
||||
action();
|
||||
});
|
||||
};
|
||||
|
||||
// Sets ImageSharp Jpeg Encoder Quality.
|
||||
SixLabors.ImageSharp.Configuration.Default.ImageFormatsManager.SetEncoder(JpegFormat.Instance, new JpegEncoder()
|
||||
{
|
||||
|
3
crowdin.yml
Normal file
3
crowdin.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
files:
|
||||
- source: /**/Assets/Locales/en_US.json
|
||||
translation: /**/Assets/Locales/%locale_with_underscore%.json
|
46
distribution/macos/Info.plist
Normal file
46
distribution/macos/Info.plist
Normal 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>
|
BIN
distribution/macos/Ryujinx.icns
Normal file
BIN
distribution/macos/Ryujinx.icns
Normal file
Binary file not shown.
609
distribution/macos/bundle_fix_up.py
Normal file
609
distribution/macos/bundle_fix_up.py
Normal 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)
|
95
distribution/macos/construct_universal_dylib.py
Normal file
95
distribution/macos/construct_universal_dylib.py
Normal 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),
|
||||
)
|
51
distribution/macos/create_app_bundle.sh
Executable file
51
distribution/macos/create_app_bundle.sh
Executable 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
|
||||
|
105
distribution/macos/create_macos_release.sh
Executable file
105
distribution/macos/create_macos_release.sh
Executable 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"
|
23
distribution/macos/entitlements.xml
Normal file
23
distribution/macos/entitlements.xml
Normal 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>
|
24
distribution/misc/add_tar_exec.py
Normal file
24
distribution/misc/add_tar_exec.py
Normal 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))
|
Reference in New Issue
Block a user