Compare commits

..

18 Commits

Author SHA1 Message Date
31ed061bea Vulkan: Improve texture barrier usage, timing and batching (#6240)
* WIP barrier batch

* Add store op to image usage barrier

* Dispose the barrier batch

* Fix encoding?

* Handle read and write on the load op barrier.

Load op consumes read accesses but does not add one, as the only other operation that can read is another load.

* Simplify null check

* Insert barriers on program change in case stale bindings are reintroduced

* Not sure how I messed this one up

* Improve location of bindings barrier update

This is also important for emergency deferred clear

* Update src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs

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

---------

Co-authored-by: Mary Guillemard <thog@protonmail.com>
2024-02-17 00:21:37 -03:00
4218311e6a Vulkan: Use push descriptors for uniform bindings when possible (#6154)
* Fix Push Descriptors

* Use push descriptor templates

* Use reserved bindings

* Formatting

* Disable when using MVK

("my heart will go on" starts playing as thousands of mac users shed a tear in unison)

* Introduce limit on push descriptor binding number

The bitmask used for updating push descriptors is ulong, so only 64 bindings can be tracked for now.

* Address feedback

* Fix logic for binding rejection

Should only offset limit when reserved bindings are less than the requested one.

* Workaround pascal and older nv bug

* Add GPU number detection for nvidia

* Only do workaround if it's valid to do so.
2024-02-16 21:41:30 -03:00
e37735ed26 Implement X8Z24 texture format (#6315) 2024-02-15 19:06:26 -03:00
74a18b7c18 Fix PermissionLocked check on UnmapProcessCodeMemory (#6314) 2024-02-15 16:16:01 -03:00
74fe814329 Remove Vulkan SubgroupSizeControl enablement code (#6317) 2024-02-15 16:04:30 -03:00
d1a093e5ca Stub VSMS related ioctls (#6313)
* Stub VSMS related ioctls

* Clean up usings
2024-02-15 19:48:22 +01:00
dfb14a5607 Updaters: Fix ARM Linux Updater (#6316)
* Remove Arch Checks

* Fix ARM Linux updater
2024-02-15 10:41:43 +01:00
jcm
904a5ffcb4 Handle exceptions when checking user data directory for symlink (#6304)
Co-authored-by: jcm <butt@butts.com>
2024-02-12 00:10:21 +01:00
jcm
946633276b macOS: Stop storing user data in Documents for some users; fix symlinks (#6241)
* macOS: Stop storing user data in Documents for some users; fix symlinks

* Use SupportedOSPlatform tag, catch exceptions, log warning instead of error

* Provide best path hints to user if symlink fixup fails

---------

Co-authored-by: jcm <butt@butts.com>
2024-02-11 19:04:39 +01:00
baf94e0e3e infra: Force add linux-x64 apphost in flathub nuget source (#6302)
Required when building on the arm64 runner.

Signed-off-by: Mary Guillemard <mary@mary.zone>
2024-02-11 16:45:58 +01:00
cf6201a4a6 infra: Restore Nuget packages for linux-arm64 for Flatpak
Signed-off-by: Mary Guillemard <mary@mary.zone>
2024-02-11 13:21:54 +01:00
18909195d1 Old buttons (#6237) 2024-02-11 03:12:43 +01:00
f06d22d6f0 Infra: Capitalisation Consistency (#6296)
* Rename Ryujinx.UI.Common

* Rename Ryujinx.UI.LocaleGenerator

* Update in Files

AboutWindow

* Configuration State

* Rename projects

* Ryujinx/UI

* Fix build

* Main remaining inconsistencies

* HLE.UI Namespace

* HLE.UI Files

* Namespace

* Ryujinx.UI.Common.Configuration.UI

* Ryujinx.UI.Common,Configuration.UI Files

* More instances
2024-02-11 03:09:18 +01:00
jcm
84d6e8d121 Standardize logging locations across desktop platforms (#6238)
* Standardize logging locations across desktop platforms

* Return null instead of empty literal on exceptions

* Remove LogDirectoryPath from LoggerModule

* Catch exception when creating DirectoryInfo in FileLogTarget

* Remove redundant log path vars, handle exception better, add null check

* Address styling issues

* Remove extra newline, quote file path in log, move directory check to OpenHelper

* Add GetOrCreateLogsDir to get/create log directory during runtime

* misc format changes

* Update src/Ryujinx.Common/Configuration/AppDataManager.cs

---------

Co-authored-by: jcm <butt@butts.com>
Co-authored-by: TSR Berry <20988865+TSRBerry@users.noreply.github.com>
Co-authored-by: Ac_K <Acoustik666@gmail.com>
2024-02-11 02:17:19 +01:00
95c4912d58 Linux: Reorder available executables in Ryujinx.sh (#6171)
* Avoid Ryujinx.Headless.SDL2 as a last resort in Ryujinx.desktop when you
  have more than one executable installed.
2024-02-11 00:57:23 +01:00
356a75af0b Remove ReflectionBinding in Mod Manager (#6280) 2024-02-11 00:52:11 +01:00
4ae9921063 Update Avalonia About Window like requested in PR #6267 (#6278)
* Update About Window like requested in PR #6267

* Feedback

* Apply suggestions from code review

Co-authored-by: Isaac Marovitz <42140194+IsaacMarovitz@users.noreply.github.com>

* Fix indents

---------

Co-authored-by: Isaac Marovitz <42140194+IsaacMarovitz@users.noreply.github.com>
2024-02-11 00:45:14 +01:00
6a8ac389e5 Fix mip offset/size for full 3D texture upload on Vulkan (#6294) 2024-02-11 00:41:17 +01:00
238 changed files with 1988 additions and 1036 deletions

2
.github/labeler.yml vendored
View File

@ -20,7 +20,7 @@ gpu:
gui:
- changed-files:
- any-glob-to-any-file: ['src/Ryujinx/**', 'src/Ryujinx.Ui.Common/**', 'src/Ryujinx.Ui.LocaleGenerator/**', 'src/Ryujinx.Ava/**']
- any-glob-to-any-file: ['src/Ryujinx/**', 'src/Ryujinx.UI.Common/**', 'src/Ryujinx.UI.LocaleGenerator/**', 'src/Ryujinx.Ava/**']
horizon:
- changed-files:

View File

@ -51,38 +51,76 @@ jobs:
- name: Restore Nuget packages
# With .NET 8.0.100, Microsoft.NET.ILLink.Tasks isn't restored by default and only seems to appears when publishing.
# So we just publish to grab the dependencies
run: dotnet publish -c Release -r linux-x64 Ryujinx/${{ env.RYUJINX_PROJECT_FILE }} --self-contained
run: |
dotnet publish -c Release -r linux-x64 Ryujinx/${{ env.RYUJINX_PROJECT_FILE }} --self-contained
dotnet publish -c Release -r linux-arm64 Ryujinx/${{ env.RYUJINX_PROJECT_FILE }} --self-contained
- name: Generate nuget_sources.json
shell: python
run: |
import hashlib
from pathlib import Path
import base64
import binascii
import json
import os
import urllib.request
sources = []
for path in Path(os.environ['NUGET_PACKAGES']).glob('**/*.nupkg.sha512'):
name = path.parent.parent.name
version = path.parent.name
filename = '{}.{}.nupkg'.format(name, version)
url = 'https://api.nuget.org/v3-flatcontainer/{}/{}/{}'.format(name, version, filename)
with path.open() as fp:
sha512 = binascii.hexlify(base64.b64decode(fp.read())).decode('ascii')
def create_source_from_external(name, version):
full_dir_path = Path(os.environ["NUGET_PACKAGES"]).joinpath(name).joinpath(version)
os.makedirs(full_dir_path, exist_ok=True)
sources.append({
'type': 'file',
'url': url,
'sha512': sha512,
'dest': os.environ['NUGET_SOURCES_DESTDIR'],
'dest-filename': filename,
})
filename = "{}.{}.nupkg".format(name, version)
url = "https://api.nuget.org/v3-flatcontainer/{}/{}/{}".format(
name, version, filename
)
with open('flathub/nuget_sources.json', 'w') as fp:
json.dump(sources, fp, indent=4)
print(f"Processing {url}...")
response = urllib.request.urlopen(url)
sha512 = hashlib.sha512(response.read()).hexdigest()
return {
"type": "file",
"url": url,
"sha512": sha512,
"dest": os.environ["NUGET_SOURCES_DESTDIR"],
"dest-filename": filename,
}
has_added_x64_apphost = False
for path in Path(os.environ["NUGET_PACKAGES"]).glob("**/*.nupkg.sha512"):
name = path.parent.parent.name
version = path.parent.name
filename = "{}.{}.nupkg".format(name, version)
url = "https://api.nuget.org/v3-flatcontainer/{}/{}/{}".format(
name, version, filename
)
with path.open() as fp:
sha512 = binascii.hexlify(base64.b64decode(fp.read())).decode("ascii")
sources.append(
{
"type": "file",
"url": url,
"sha512": sha512,
"dest": os.environ["NUGET_SOURCES_DESTDIR"],
"dest-filename": filename,
}
)
# .NET will not add current installed application host to the list, force inject it here.
if not has_added_x64_apphost and name.startswith('microsoft.netcore.app.host'):
sources.append(create_source_from_external("microsoft.netcore.app.host.linux-x64", version))
has_added_x64_apphost = True
with open("flathub/nuget_sources.json", "w") as fp:
json.dump(sources, fp, indent=4)
- name: Update flatpak metadata
id: metadata

View File

@ -71,7 +71,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Nvdec.FFmp
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Ava", "src\Ryujinx.Ava\Ryujinx.Ava.csproj", "{7C1B2721-13DA-4B62-B046-C626605ECCE6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Ui.Common", "src\Ryujinx.Ui.Common\Ryujinx.Ui.Common.csproj", "{BA161CA0-CD65-4E6E-B644-51C8D1E542DC}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.UI.Common", "src\Ryujinx.UI.Common\Ryujinx.UI.Common.csproj", "{BA161CA0-CD65-4E6E-B644-51C8D1E542DC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Generators", "src\Ryujinx.Horizon.Generators\Ryujinx.Horizon.Generators.csproj", "{6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}"
EndProject
@ -79,7 +79,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Vulkan", "
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spv.Generator", "src\Spv.Generator\Spv.Generator.csproj", "{2BCB3D7A-38C0-4FE7-8FDA-374C6AD56D0E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Ui.LocaleGenerator", "src\Ryujinx.Ui.LocaleGenerator\Ryujinx.Ui.LocaleGenerator.csproj", "{77D01AD9-2C98-478E-AE1D-8F7100738FB4}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.UI.LocaleGenerator", "src\Ryujinx.UI.LocaleGenerator\Ryujinx.UI.LocaleGenerator.csproj", "{77D01AD9-2C98-478E-AE1D-8F7100738FB4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Common", "src\Ryujinx.Horizon.Common\Ryujinx.Horizon.Common.csproj", "{77F96ECE-4952-42DB-A528-DED25572A573}"
EndProject

View File

@ -1,14 +1,21 @@
#!/bin/sh
SCRIPT_DIR=$(dirname "$(realpath "$0")")
RYUJINX_BIN="Ryujinx"
if [ -f "$SCRIPT_DIR/Ryujinx.Headless.SDL2" ]; then
RYUJINX_BIN="Ryujinx.Headless.SDL2"
fi
if [ -f "$SCRIPT_DIR/Ryujinx.Ava" ]; then
RYUJINX_BIN="Ryujinx.Ava"
fi
if [ -f "$SCRIPT_DIR/Ryujinx.Headless.SDL2" ]; then
RYUJINX_BIN="Ryujinx.Headless.SDL2"
if [ -f "$SCRIPT_DIR/Ryujinx" ]; then
RYUJINX_BIN="Ryujinx"
fi
if [ -z "$RYUJINX_BIN" ]; then
exit 1
fi
COMMAND="env DOTNET_EnableAlternateStackCheck=1"
@ -17,4 +24,4 @@ if command -v gamemoderun > /dev/null 2>&1; then
COMMAND="$COMMAND gamemoderun"
fi
$COMMAND "$SCRIPT_DIR/$RYUJINX_BIN" "$@"
exec $COMMAND "$SCRIPT_DIR/$RYUJINX_BIN" "$@"

View File

@ -8,8 +8,8 @@ using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Helper;
using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Common.Helper;
using System;
using System.Diagnostics;
@ -42,9 +42,9 @@ namespace Ryujinx.Ava
{
ApplyConfiguredTheme();
ConfigurationState.Instance.Ui.BaseStyle.Event += ThemeChanged_Event;
ConfigurationState.Instance.Ui.CustomThemePath.Event += ThemeChanged_Event;
ConfigurationState.Instance.Ui.EnableCustomTheme.Event += CustomThemeChanged_Event;
ConfigurationState.Instance.UI.BaseStyle.Event += ThemeChanged_Event;
ConfigurationState.Instance.UI.CustomThemePath.Event += ThemeChanged_Event;
ConfigurationState.Instance.UI.EnableCustomTheme.Event += CustomThemeChanged_Event;
}
}
@ -88,13 +88,13 @@ namespace Ryujinx.Ava
{
try
{
string baseStyle = ConfigurationState.Instance.Ui.BaseStyle;
string baseStyle = ConfigurationState.Instance.UI.BaseStyle;
if (string.IsNullOrWhiteSpace(baseStyle))
{
ConfigurationState.Instance.Ui.BaseStyle.Value = "Dark";
ConfigurationState.Instance.UI.BaseStyle.Value = "Dark";
baseStyle = ConfigurationState.Instance.Ui.BaseStyle;
baseStyle = ConfigurationState.Instance.UI.BaseStyle;
}
RequestedThemeVariant = baseStyle switch

View File

@ -34,10 +34,10 @@ using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.Input;
using Ryujinx.Input.HLE;
using Ryujinx.Ui.App.Common;
using Ryujinx.Ui.Common;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Helper;
using Ryujinx.UI.App.Common;
using Ryujinx.UI.Common;
using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Common.Helper;
using Silk.NET.Vulkan;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Png;
@ -1070,7 +1070,7 @@ namespace Ryujinx.Ava
case KeyboardHotkeyState.Screenshot:
ScreenshotRequested = true;
break;
case KeyboardHotkeyState.ShowUi:
case KeyboardHotkeyState.ShowUI:
_viewModel.ShowMenuAndStatusBar = !_viewModel.ShowMenuAndStatusBar;
break;
case KeyboardHotkeyState.Pause:
@ -1160,9 +1160,9 @@ namespace Ryujinx.Ava
{
state = KeyboardHotkeyState.Screenshot;
}
else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUi))
else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUI))
{
state = KeyboardHotkeyState.ShowUi;
state = KeyboardHotkeyState.ShowUI;
}
else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause))
{

View File

@ -73,6 +73,10 @@
"GameListContextMenuCreateShortcut": "Create Application Shortcut",
"GameListContextMenuCreateShortcutToolTip": "Create a Desktop Shortcut that launches the selected Application",
"GameListContextMenuCreateShortcutToolTipMacOS": "Create a shortcut in macOS's Applications folder that launches the selected Application",
"GameListContextMenuOpenModsDirectory": "Open Mods Directory",
"GameListContextMenuOpenModsDirectoryToolTip": "Opens the directory which contains Application's Mods",
"GameListContextMenuOpenSdModsDirectory": "Open Atmosphere Mods Directory",
"GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.",
"StatusBarGamesLoaded": "{0}/{1} Games Loaded",
"StatusBarSystemVersion": "System Version: {0}",
"LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected",
@ -329,8 +333,6 @@
"DialogUpdaterAddingFilesMessage": "Adding New Update...",
"DialogUpdaterCompleteMessage": "Update Complete!",
"DialogUpdaterRestartMessage": "Do you want to restart Ryujinx now?",
"DialogUpdaterArchNotSupportedMessage": "You are not running a supported system architecture!",
"DialogUpdaterArchNotSupportedSubMessage": "(Only x64 systems are supported!)",
"DialogUpdaterNoInternetMessage": "You are not connected to the Internet!",
"DialogUpdaterNoInternetSubMessage": "Please verify that you have a working Internet connection!",
"DialogUpdaterDirtyBuildMessage": "You Cannot update a Dirty build of Ryujinx!",

View File

@ -18,8 +18,8 @@ using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.Ui.App.Common;
using Ryujinx.Ui.Common.Helper;
using Ryujinx.UI.App.Common;
using Ryujinx.UI.Common.Helper;
using System;
using System.Buffers;
using System.IO;

View File

@ -5,7 +5,7 @@ namespace Ryujinx.Ava.Common
None,
ToggleVSync,
Screenshot,
ShowUi,
ShowUI,
Pause,
ToggleMute,
ResScaleUp,

View File

@ -1,7 +1,7 @@
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common;
using Ryujinx.Common.Utilities;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.UI.Common.Configuration;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@ -38,9 +38,9 @@ namespace Ryujinx.Ava.Common.Locale
// If the view is loaded with the UI Previewer detached, then override it with the saved one or default.
if (Program.PreviewerDetached)
{
if (!string.IsNullOrEmpty(ConfigurationState.Instance.Ui.LanguageCode.Value))
if (!string.IsNullOrEmpty(ConfigurationState.Instance.UI.LanguageCode.Value))
{
localeLanguageCode = ConfigurationState.Instance.Ui.LanguageCode.Value;
localeLanguageCode = ConfigurationState.Instance.UI.LanguageCode.Value;
}
else
{

View File

@ -10,8 +10,8 @@ using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.Ui.Common.Helper;
using Ryujinx.Ui.Common.Models.Github;
using Ryujinx.UI.Common.Helper;
using Ryujinx.UI.Common.Models.Github;
using System;
using System.Collections.Generic;
using System.Diagnostics;
@ -68,7 +68,8 @@ namespace Ryujinx.Modules
}
else if (OperatingSystem.IsLinux())
{
_platformExt = "linux_x64.tar.gz";
var arch = RuntimeInformation.OSArchitecture == Architecture.Arm64 ? "arm64" : "x64";
_platformExt = $"linux_{arch}.tar.gz";
}
Version newVersion;
@ -637,20 +638,6 @@ namespace Ryujinx.Modules
public static bool CanUpdate(bool showWarnings)
{
#if !DISABLE_UPDATER
if (RuntimeInformation.OSArchitecture != Architecture.X64 && !OperatingSystem.IsMacOS())
{
if (showWarnings)
{
Dispatcher.UIThread.InvokeAsync(() =>
ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterArchNotSupportedMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterArchNotSupportedSubMessage])
);
}
return false;
}
if (!NetworkInterface.GetIsNetworkAvailable())
{
if (showWarnings)

View File

@ -9,10 +9,10 @@ using Ryujinx.Common.Logging;
using Ryujinx.Common.SystemInterop;
using Ryujinx.Modules;
using Ryujinx.SDL2.Common;
using Ryujinx.Ui.Common;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Helper;
using Ryujinx.Ui.Common.SystemInfo;
using Ryujinx.UI.Common;
using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Common.Helper;
using Ryujinx.UI.Common.SystemInfo;
using System;
using System.IO;
using System.Runtime.InteropServices;

View File

@ -73,8 +73,8 @@
<ProjectReference Include="..\ARMeilleure\ARMeilleure.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.OpenGL\Ryujinx.Graphics.OpenGL.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Gpu\Ryujinx.Graphics.Gpu.csproj" />
<ProjectReference Include="..\Ryujinx.Ui.Common\Ryujinx.Ui.Common.csproj" />
<ProjectReference Include="..\Ryujinx.Ui.LocaleGenerator\Ryujinx.Ui.LocaleGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\Ryujinx.UI.Common\Ryujinx.UI.Common.csproj" />
<ProjectReference Include="..\Ryujinx.UI.LocaleGenerator\Ryujinx.UI.LocaleGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>
<ItemGroup>
@ -103,7 +103,7 @@
</ItemGroup>
<ItemGroup>
<AvaloniaResource Include="Ui\**\*.xaml">
<AvaloniaResource Include="UI\**\*.xaml">
<SubType>Designer</SubType>
</AvaloniaResource>
<AvaloniaResource Include="Assets\Fonts\SegoeFluentIcons.ttf" />

View File

@ -8,26 +8,26 @@ using Ryujinx.Ava.UI.Windows;
using Ryujinx.HLE;
using Ryujinx.HLE.HOS.Applets;
using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types;
using Ryujinx.HLE.Ui;
using Ryujinx.HLE.UI;
using System;
using System.Threading;
namespace Ryujinx.Ava.UI.Applet
{
internal class AvaHostUiHandler : IHostUiHandler
internal class AvaHostUIHandler : IHostUIHandler
{
private readonly MainWindow _parent;
public IHostUiTheme HostUiTheme { get; }
public IHostUITheme HostUITheme { get; }
public AvaHostUiHandler(MainWindow parent)
public AvaHostUIHandler(MainWindow parent)
{
_parent = parent;
HostUiTheme = new AvaloniaHostUiTheme(parent);
HostUITheme = new AvaloniaHostUITheme(parent);
}
public bool DisplayMessageDialog(ControllerAppletUiArgs args)
public bool DisplayMessageDialog(ControllerAppletUIArgs args)
{
ManualResetEvent dialogCloseEvent = new(false);
@ -110,7 +110,7 @@ namespace Ryujinx.Ava.UI.Applet
return okPressed;
}
public bool DisplayInputDialog(SoftwareKeyboardUiArgs args, out string userText)
public bool DisplayInputDialog(SoftwareKeyboardUIArgs args, out string userText)
{
ManualResetEvent dialogCloseEvent = new(false);

View File

@ -5,7 +5,7 @@ using Avalonia.Threading;
using Ryujinx.Ava.Input;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.HLE.Ui;
using Ryujinx.HLE.UI;
using System;
using System.Threading;
using HidKey = Ryujinx.Common.Configuration.Hid.Key;

View File

@ -1,13 +1,13 @@
using Avalonia.Media;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.HLE.Ui;
using Ryujinx.HLE.UI;
using System;
namespace Ryujinx.Ava.UI.Applet
{
class AvaloniaHostUiTheme : IHostUiTheme
class AvaloniaHostUITheme : IHostUITheme
{
public AvaloniaHostUiTheme(MainWindow parent)
public AvaloniaHostUITheme(MainWindow parent)
{
FontFamily = OperatingSystem.IsWindows() && OperatingSystem.IsWindowsVersionAtLeast(10, 0, 22000) ? "Segoe UI Variable" : parent.FontFamily.Name;
DefaultBackgroundColor = BrushToThemeColor(parent.Background);

View File

@ -36,7 +36,7 @@ namespace Ryujinx.Ava.UI.Applet
private readonly MainWindow _mainWindow;
public ControllerAppletDialog(MainWindow mainWindow, ControllerAppletUiArgs args)
public ControllerAppletDialog(MainWindow mainWindow, ControllerAppletUIArgs args)
{
if (args.PlayerCountMin == args.PlayerCountMax)
{
@ -69,7 +69,7 @@ namespace Ryujinx.Ava.UI.Applet
InitializeComponent();
}
public static async Task<UserResult> ShowControllerAppletDialog(MainWindow window, ControllerAppletUiArgs args)
public static async Task<UserResult> ShowControllerAppletDialog(MainWindow window, ControllerAppletUIArgs args)
{
ContentDialog contentDialog = new();
UserResult result = UserResult.Cancel;

View File

@ -34,7 +34,7 @@
Height="80"
MinWidth="50"
Margin="5,10,20,10"
Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" />
Source="resm:Ryujinx.UI.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.UI.Common" />
<TextBlock
Grid.Row="1"
Grid.Column="1"

View File

@ -31,7 +31,7 @@
MinWidth="50"
Margin="5,10,20,10"
VerticalAlignment="Center"
Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" />
Source="resm:Ryujinx.UI.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.UI.Common" />
<TextBlock
Grid.Row="1"
Grid.Column="1"

View File

@ -54,7 +54,7 @@ namespace Ryujinx.Ava.UI.Controls
public string MainText { get; set; } = "";
public string SecondaryText { get; set; } = "";
public static async Task<(UserResult Result, string Input)> ShowInputDialog(string title, SoftwareKeyboardUiArgs args)
public static async Task<(UserResult Result, string Input)> ShowInputDialog(string title, SoftwareKeyboardUIArgs args)
{
ContentDialog contentDialog = new();

View File

@ -51,6 +51,15 @@
Header="{locale:Locale GameListContextMenuManageMod}"
ToolTip.Tip="{locale:Locale GameListContextMenuManageModToolTip}" />
<Separator />
<MenuItem
Click="OpenModsDirectory_Click"
Header="{locale:Locale GameListContextMenuOpenModsDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenModsDirectoryToolTip}" />
<MenuItem
Click="OpenSdModsDirectory_Click"
Header="{locale:Locale GameListContextMenuOpenSdModsDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenSdModsDirectoryToolTip}" />
<Separator />
<MenuItem Header="{locale:Locale GameListContextMenuCacheManagement}">
<MenuItem
Click="PurgePtcCache_Click"

View File

@ -11,8 +11,8 @@ using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common.Configuration;
using Ryujinx.HLE.HOS;
using Ryujinx.Ui.App.Common;
using Ryujinx.Ui.Common.Helper;
using Ryujinx.UI.App.Common;
using Ryujinx.UI.Common.Helper;
using System;
using System.Collections.Generic;
using System.Globalization;
@ -126,6 +126,32 @@ namespace Ryujinx.Ava.UI.Controls
}
}
public void OpenModsDirectory_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (viewModel?.SelectedApplication != null)
{
string modsBasePath = ModLoader.GetModsBasePath();
string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, viewModel.SelectedApplication.TitleId);
OpenHelper.OpenFolder(titleModsPath);
}
}
public void OpenSdModsDirectory_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (viewModel?.SelectedApplication != null)
{
string sdModsBasePath = ModLoader.GetSdModsBasePath();
string titleModsPath = ModLoader.GetApplicationDir(sdModsBasePath, viewModel.SelectedApplication.TitleId);
OpenHelper.OpenFolder(titleModsPath);
}
}
public async void OpenModManager_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;

View File

@ -3,7 +3,7 @@ using Avalonia.Input;
using Avalonia.Interactivity;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ui.App.Common;
using Ryujinx.UI.App.Common;
using System;
namespace Ryujinx.Ava.UI.Controls

View File

@ -3,7 +3,7 @@ using Avalonia.Input;
using Avalonia.Interactivity;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ui.App.Common;
using Ryujinx.UI.App.Common;
using System;
namespace Ryujinx.Ava.UI.Controls

View File

@ -26,7 +26,7 @@
Height="70"
MinWidth="50"
Margin="5,10,20,10"
Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" />
Source="resm:Ryujinx.UI.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.UI.Common" />
<StackPanel
Grid.Row="1"
Grid.Column="1"
@ -39,4 +39,4 @@
VerticalAlignment="Center" />
</StackPanel>
</Grid>
</Window>
</Window>

View File

@ -1,5 +1,5 @@
using Avalonia.Interactivity;
using Ryujinx.Ui.App.Common;
using Ryujinx.UI.App.Common;
namespace Ryujinx.Ava.UI.Helpers
{

View File

@ -383,7 +383,7 @@ namespace Ryujinx.Ava.UI.Helpers
{
result = ContentDialogResult.None;
Logger.Warning?.Print(LogClass.Ui, "Content dialog overlay failed to populate. Default value has been returned.");
Logger.Warning?.Print(LogClass.UI, "Content dialog overlay failed to populate. Default value has been returned.");
}
return result;

View File

@ -1,7 +1,7 @@
using Avalonia.Data.Converters;
using Avalonia.Markup.Xaml;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ui.Common.Helper;
using Ryujinx.UI.Common.Helper;
using System;
using System.Globalization;

View File

@ -39,12 +39,12 @@ namespace Ryujinx.Ava.UI.Helpers
public void Log(AvaLogLevel level, string area, object source, string messageTemplate)
{
GetLog(level)?.PrintMsg(RyuLogClass.Ui, Format(level, area, messageTemplate, source, null));
GetLog(level)?.PrintMsg(RyuLogClass.UI, Format(level, area, messageTemplate, source, null));
}
public void Log(AvaLogLevel level, string area, object source, string messageTemplate, params object[] propertyValues)
{
GetLog(level)?.PrintMsg(RyuLogClass.Ui, Format(level, area, messageTemplate, source, propertyValues));
GetLog(level)?.PrintMsg(RyuLogClass.UI, Format(level, area, messageTemplate, source, propertyValues));
}
private static string Format(AvaLogLevel level, string area, string template, object source, object[] v)

View File

@ -1,6 +1,6 @@
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ui.Common;
using Ryujinx.Ui.Common.Helper;
using Ryujinx.UI.Common;
using Ryujinx.UI.Common.Helper;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Helpers

View File

@ -1,4 +1,4 @@
using Ryujinx.Ui.App.Common;
using Ryujinx.UI.App.Common;
using System;
using System.Collections.Generic;

View File

@ -1,4 +1,4 @@
using Ryujinx.Ui.App.Common;
using Ryujinx.UI.App.Common;
using System;
using System.Collections.Generic;

View File

@ -3,8 +3,8 @@ using LibHac.Ncm;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.HLE.FileSystem;
using Ryujinx.Ui.App.Common;
using Ryujinx.Ui.Common.Helper;
using Ryujinx.UI.App.Common;
using Ryujinx.UI.Common.Helper;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

View File

@ -3,8 +3,8 @@ using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Platform;
using Ryujinx.Common.Configuration;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Helper;
using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Common.Helper;
using SPB.Graphics;
using SPB.Platform;
using SPB.Platform.GLX;

View File

@ -3,7 +3,7 @@ using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.OpenGL;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.UI.Common.Configuration;
using SPB.Graphics;
using SPB.Graphics.Exceptions;
using SPB.Graphics.OpenGL;
@ -75,7 +75,7 @@ namespace Ryujinx.Ava.UI.Renderer
throw;
}
Logger.Warning?.Print(LogClass.Ui, $"Failed to {(!unbind ? "bind" : "unbind")} OpenGL context: {e}");
Logger.Warning?.Print(LogClass.UI, $"Failed to {(!unbind ? "bind" : "unbind")} OpenGL context: {e}");
}
}

View File

@ -1,7 +1,7 @@
using Avalonia;
using Avalonia.Controls;
using Ryujinx.Common.Configuration;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.UI.Common.Configuration;
using System;
namespace Ryujinx.Ava.UI.Renderer

View File

@ -3,7 +3,7 @@ using Avalonia.Platform;
using Avalonia.Threading;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Common.Utilities;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.UI.Common.Configuration;
using System;
using System.Net.Http;
using System.Net.NetworkInformation;
@ -87,19 +87,19 @@ namespace Ryujinx.Ava.UI.ViewModels
{
Version = Program.Version;
if (ConfigurationState.Instance.Ui.BaseStyle.Value == "Light")
if (ConfigurationState.Instance.UI.BaseStyle.Value == "Light")
{
GithubLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_GitHub_Light.png?assembly=Ryujinx.Ui.Common")));
DiscordLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Discord_Light.png?assembly=Ryujinx.Ui.Common")));
PatreonLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Patreon_Light.png?assembly=Ryujinx.Ui.Common")));
TwitterLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Twitter_Light.png?assembly=Ryujinx.Ui.Common")));
GithubLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.UI.Common.Resources.Logo_GitHub_Light.png?assembly=Ryujinx.UI.Common")));
DiscordLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.UI.Common.Resources.Logo_Discord_Light.png?assembly=Ryujinx.UI.Common")));
PatreonLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.UI.Common.Resources.Logo_Patreon_Light.png?assembly=Ryujinx.UI.Common")));
TwitterLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.UI.Common.Resources.Logo_Twitter_Light.png?assembly=Ryujinx.UI.Common")));
}
else
{
GithubLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_GitHub_Dark.png?assembly=Ryujinx.Ui.Common")));
DiscordLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Discord_Dark.png?assembly=Ryujinx.Ui.Common")));
PatreonLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Patreon_Dark.png?assembly=Ryujinx.Ui.Common")));
TwitterLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Twitter_Dark.png?assembly=Ryujinx.Ui.Common")));
GithubLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.UI.Common.Resources.Logo_GitHub_Dark.png?assembly=Ryujinx.UI.Common")));
DiscordLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.UI.Common.Resources.Logo_Discord_Dark.png?assembly=Ryujinx.UI.Common")));
PatreonLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.UI.Common.Resources.Logo_Patreon_Dark.png?assembly=Ryujinx.UI.Common")));
TwitterLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.UI.Common.Resources.Logo_Twitter_Dark.png?assembly=Ryujinx.UI.Common")));
}
Dispatcher.UIThread.InvokeAsync(DownloadPatronsJson);

View File

@ -9,7 +9,7 @@ using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.Ui.Common.Models.Amiibo;
using Ryujinx.UI.Common.Models.Amiibo;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@ -65,7 +65,7 @@ namespace Ryujinx.Ava.UI.ViewModels
_amiiboSeries = new ObservableCollection<string>();
_amiibos = new AvaloniaList<AmiiboApi>();
_amiiboLogoBytes = EmbeddedResources.Read("Ryujinx.Ui.Common/Resources/Logo_Amiibo.png");
_amiiboLogoBytes = EmbeddedResources.Read("Ryujinx.UI.Common/Resources/Logo_Amiibo.png");
_ = LoadContentAsync();
}

View File

@ -19,7 +19,7 @@ using Ryujinx.Common.Configuration.Hid.Keyboard;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.Input;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.UI.Common.Configuration;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@ -35,10 +35,10 @@ namespace Ryujinx.Ava.UI.ViewModels
public class ControllerInputViewModel : BaseModel, IDisposable
{
private const string Disabled = "disabled";
private const string ProControllerResource = "Ryujinx.Ui.Common/Resources/Controller_ProCon.svg";
private const string JoyConPairResource = "Ryujinx.Ui.Common/Resources/Controller_JoyConPair.svg";
private const string JoyConLeftResource = "Ryujinx.Ui.Common/Resources/Controller_JoyConLeft.svg";
private const string JoyConRightResource = "Ryujinx.Ui.Common/Resources/Controller_JoyConRight.svg";
private const string ProControllerResource = "Ryujinx.UI.Common/Resources/Controller_ProCon.svg";
private const string JoyConPairResource = "Ryujinx.UI.Common/Resources/Controller_JoyConPair.svg";
private const string JoyConLeftResource = "Ryujinx.UI.Common/Resources/Controller_JoyConLeft.svg";
private const string JoyConRightResource = "Ryujinx.UI.Common/Resources/Controller_JoyConRight.svg";
private const string KeyboardString = "keyboard";
private const string ControllerString = "controller";
private readonly MainWindow _mainWindow;

View File

@ -25,13 +25,13 @@ using Ryujinx.HLE;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.HLE.Ui;
using Ryujinx.HLE.UI;
using Ryujinx.Input.HLE;
using Ryujinx.Modules;
using Ryujinx.Ui.App.Common;
using Ryujinx.Ui.Common;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Helper;
using Ryujinx.UI.App.Common;
using Ryujinx.UI.Common;
using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Common.Helper;
using SixLabors.ImageSharp.PixelFormats;
using System;
using System.Collections.Generic;
@ -138,7 +138,7 @@ namespace Ryujinx.Ava.UI.ViewModels
InputManager inputManager,
UserChannelPersistence userChannelPersistence,
LibHacHorizonManager libHacHorizonManager,
IHostUiHandler uiHandler,
IHostUIHandler uiHandler,
Action<bool> showLoading,
Action<bool> switchToGameControl,
Action<Control> setMainContent,
@ -685,10 +685,10 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool StartGamesInFullscreen
{
get => ConfigurationState.Instance.Ui.StartFullscreen;
get => ConfigurationState.Instance.UI.StartFullscreen;
set
{
ConfigurationState.Instance.Ui.StartFullscreen.Value = value;
ConfigurationState.Instance.UI.StartFullscreen.Value = value;
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
@ -698,10 +698,10 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool ShowConsole
{
get => ConfigurationState.Instance.Ui.ShowConsole;
get => ConfigurationState.Instance.UI.ShowConsole;
set
{
ConfigurationState.Instance.Ui.ShowConsole.Value = value;
ConfigurationState.Instance.UI.ShowConsole.Value = value;
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
@ -743,10 +743,10 @@ namespace Ryujinx.Ava.UI.ViewModels
public Glyph Glyph
{
get => (Glyph)ConfigurationState.Instance.Ui.GameListViewMode.Value;
get => (Glyph)ConfigurationState.Instance.UI.GameListViewMode.Value;
set
{
ConfigurationState.Instance.Ui.GameListViewMode.Value = (int)value;
ConfigurationState.Instance.UI.GameListViewMode.Value = (int)value;
OnPropertyChanged();
OnPropertyChanged(nameof(IsGrid));
@ -758,9 +758,9 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool ShowNames
{
get => ConfigurationState.Instance.Ui.ShowNames && ConfigurationState.Instance.Ui.GridSize > 1; set
get => ConfigurationState.Instance.UI.ShowNames && ConfigurationState.Instance.UI.GridSize > 1; set
{
ConfigurationState.Instance.Ui.ShowNames.Value = value;
ConfigurationState.Instance.UI.ShowNames.Value = value;
OnPropertyChanged();
OnPropertyChanged(nameof(GridSizeScale));
@ -772,10 +772,10 @@ namespace Ryujinx.Ava.UI.ViewModels
internal ApplicationSort SortMode
{
get => (ApplicationSort)ConfigurationState.Instance.Ui.ApplicationSort.Value;
get => (ApplicationSort)ConfigurationState.Instance.UI.ApplicationSort.Value;
private set
{
ConfigurationState.Instance.Ui.ApplicationSort.Value = (int)value;
ConfigurationState.Instance.UI.ApplicationSort.Value = (int)value;
OnPropertyChanged();
OnPropertyChanged(nameof(SortName));
@ -788,7 +788,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{
get
{
return ConfigurationState.Instance.Ui.GridSize.Value switch
return ConfigurationState.Instance.UI.GridSize.Value switch
{
1 => 78,
2 => 100,
@ -803,7 +803,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{
get
{
return ConfigurationState.Instance.Ui.GridSize.Value switch
return ConfigurationState.Instance.UI.GridSize.Value switch
{
1 => 120,
2 => ShowNames ? 210 : 150,
@ -816,10 +816,10 @@ namespace Ryujinx.Ava.UI.ViewModels
public int GridSizeScale
{
get => ConfigurationState.Instance.Ui.GridSize;
get => ConfigurationState.Instance.UI.GridSize;
set
{
ConfigurationState.Instance.Ui.GridSize.Value = value;
ConfigurationState.Instance.UI.GridSize.Value = value;
if (value < 2)
{
@ -860,10 +860,10 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool IsAscending
{
get => ConfigurationState.Instance.Ui.IsAscendingOrder;
get => ConfigurationState.Instance.UI.IsAscendingOrder;
private set
{
ConfigurationState.Instance.Ui.IsAscendingOrder.Value = value;
ConfigurationState.Instance.UI.IsAscendingOrder.Value = value;
OnPropertyChanged();
OnPropertyChanged(nameof(SortMode));
@ -919,7 +919,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public RendererHost RendererHostControl { get; private set; }
public bool IsClosing { get; set; }
public LibHacHorizonManager LibHacHorizonManager { get; internal set; }
public IHostUiHandler UiHandler { get; internal set; }
public IHostUIHandler UiHandler { get; internal set; }
public bool IsSortedByFavorite => SortMode == ApplicationSort.Favorite;
public bool IsSortedByTitle => SortMode == ApplicationSort.Title;
public bool IsSortedByDeveloper => SortMode == ApplicationSort.Developer;
@ -928,10 +928,10 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool IsSortedByType => SortMode == ApplicationSort.FileType;
public bool IsSortedBySize => SortMode == ApplicationSort.FileSize;
public bool IsSortedByPath => SortMode == ApplicationSort.Path;
public bool IsGridSmall => ConfigurationState.Instance.Ui.GridSize == 1;
public bool IsGridMedium => ConfigurationState.Instance.Ui.GridSize == 2;
public bool IsGridLarge => ConfigurationState.Instance.Ui.GridSize == 3;
public bool IsGridHuge => ConfigurationState.Instance.Ui.GridSize == 4;
public bool IsGridSmall => ConfigurationState.Instance.UI.GridSize == 1;
public bool IsGridMedium => ConfigurationState.Instance.UI.GridSize == 2;
public bool IsGridLarge => ConfigurationState.Instance.UI.GridSize == 3;
public bool IsGridHuge => ConfigurationState.Instance.UI.GridSize == 4;
#endregion
@ -1245,7 +1245,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public void LoadConfigurableHotKeys()
{
if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUi, out var showUiKey))
if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUI, out var showUiKey))
{
ShowUiKey = new KeyGesture(showUiKey);
}
@ -1350,16 +1350,11 @@ namespace Ryujinx.Ava.UI.ViewModels
public void OpenLogsFolder()
{
string logPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs");
if (LoggerModule.LogDirectoryPath != null)
string logPath = AppDataManager.GetOrCreateLogsDir();
if (!string.IsNullOrEmpty(logPath))
{
logPath = LoggerModule.LogDirectoryPath;
OpenHelper.OpenFolder(logPath);
}
new DirectoryInfo(logPath).Create();
OpenHelper.OpenFolder(logPath);
}
public void ToggleDockMode()
@ -1390,7 +1385,7 @@ namespace Ryujinx.Ava.UI.ViewModels
if (Program.PreviewerDetached)
{
ConfigurationState.Instance.Ui.LanguageCode.Value = (string)languageCode;
ConfigurationState.Instance.UI.LanguageCode.Value = (string)languageCode;
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
}
}

View File

@ -16,8 +16,8 @@ using Ryujinx.Common.Logging;
using Ryujinx.Graphics.Vulkan;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.Services.Time.TimeZone;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Configuration.System;
using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Common.Configuration.System;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@ -408,9 +408,9 @@ namespace Ryujinx.Ava.UI.ViewModels
HideCursor = (int)config.HideCursor.Value;
GameDirectories.Clear();
GameDirectories.AddRange(config.Ui.GameDirs.Value);
GameDirectories.AddRange(config.UI.GameDirs.Value);
BaseStyleIndex = config.Ui.BaseStyle == "Light" ? 0 : 1;
BaseStyleIndex = config.UI.BaseStyle == "Light" ? 0 : 1;
// Input
EnableDockedMode = config.System.EnableDockedMode;
@ -494,10 +494,10 @@ namespace Ryujinx.Ava.UI.ViewModels
if (_directoryChanged)
{
List<string> gameDirs = new(GameDirectories);
config.Ui.GameDirs.Value = gameDirs;
config.UI.GameDirs.Value = gameDirs;
}
config.Ui.BaseStyle.Value = BaseStyleIndex == 0 ? "Light" : "Dark";
config.UI.BaseStyle.Value = BaseStyleIndex == 0 ? "Light" : "Dark";
// Input
config.System.EnableDockedMode.Value = EnableDockedMode;

View File

@ -17,7 +17,7 @@ using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.FileSystem;
using Ryujinx.Ui.App.Common;
using Ryujinx.UI.App.Common;
using System;
using System.Collections.Generic;
using System.IO;

View File

@ -10,9 +10,9 @@ using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common;
using Ryujinx.Common.Utilities;
using Ryujinx.Modules;
using Ryujinx.Ui.Common;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Helper;
using Ryujinx.UI.Common;
using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Common.Helper;
using System;
using System.Collections.Generic;
using System.IO;
@ -43,7 +43,7 @@ namespace Ryujinx.Ava.UI.Views.Main
checkBoxes.Add(new CheckBox
{
Content = $".{fileName}",
IsChecked = ((FileTypes)item).GetConfigValue(ConfigurationState.Instance.Ui.ShownFileTypes),
IsChecked = ((FileTypes)item).GetConfigValue(ConfigurationState.Instance.UI.ShownFileTypes),
Command = MiniCommand.Create(() => Window.ToggleFileType(fileName)),
});
}

View File

@ -5,7 +5,7 @@ using Avalonia.Interactivity;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.UI.Common.Configuration;
using System;
namespace Ryujinx.Ava.UI.Views.Main

View File

@ -45,7 +45,7 @@
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysShowUiHotkey}" Width="230" />
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
<TextBlock
Text="{Binding KeyboardHotkeys.ShowUi, Mode=TwoWay, Converter={StaticResource Key}}"
Text="{Binding KeyboardHotkeys.ShowUI, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>

View File

@ -44,31 +44,37 @@
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image
Grid.Column="0"
Height="80"
Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" />
<WrapPanel
Grid.Column="2"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Orientation="Vertical">
<TextBlock
<StackPanel
Grid.Column="1"
Orientation="Horizontal"
HorizontalAlignment="Center"
Spacing="10">
<Image
Height="80"
Source="resm:Ryujinx.UI.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.UI.Common"
HorizontalAlignment="Center"
IsHitTestVisible="True" />
<WrapPanel
HorizontalAlignment="Right"
VerticalAlignment="Center"
FontSize="28"
FontWeight="Bold"
Text="Ryujinx"
TextAlignment="Center"
Width="110" />
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="11"
Text="(REE-YOU-JINX)"
TextAlignment="Center"
Width="110" />
</WrapPanel>
Orientation="Vertical">
<TextBlock
FontSize="28"
FontWeight="Bold"
Text="Ryujinx"
TextAlignment="Start"
Width="110"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
<TextBlock
FontSize="11"
Text="(REE-YOU-JINX)"
TextAlignment="Start"
Width="110"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</WrapPanel>
</StackPanel>
</Grid>
<TextBlock
HorizontalAlignment="Center"

View File

@ -7,7 +7,7 @@ using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ui.Common.Helper;
using Ryujinx.UI.Common.Helper;
using System.Threading.Tasks;
using Button = Avalonia.Controls.Button;

View File

@ -1,7 +1,7 @@
using Avalonia.Interactivity;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ui.Common.Models.Amiibo;
using Ryujinx.UI.Common.Models.Amiibo;
namespace Ryujinx.Ava.UI.Windows
{

View File

@ -3,7 +3,7 @@ using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Models;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
using Ryujinx.Ui.App.Common;
using Ryujinx.UI.App.Common;
using System;
using System.Collections.Generic;
using System.Globalization;

View File

@ -7,7 +7,7 @@ using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.HLE.FileSystem;
using Ryujinx.Ui.Common.Helper;
using Ryujinx.UI.Common.Helper;
using System.Threading.Tasks;
using Button = Avalonia.Controls.Button;

View File

@ -18,10 +18,10 @@ using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.Input.HLE;
using Ryujinx.Input.SDL2;
using Ryujinx.Modules;
using Ryujinx.Ui.App.Common;
using Ryujinx.Ui.Common;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Helper;
using Ryujinx.UI.App.Common;
using Ryujinx.UI.Common;
using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Common.Helper;
using System;
using System.IO;
using System.Runtime.Versioning;
@ -40,7 +40,7 @@ namespace Ryujinx.Ava.UI.Windows
private static bool _deferLoad;
private static string _launchPath;
private static bool _startFullscreen;
internal readonly AvaHostUiHandler UiHandler;
internal readonly AvaHostUIHandler UiHandler;
public VirtualFileSystem VirtualFileSystem { get; private set; }
public ContentManager ContentManager { get; private set; }
@ -69,7 +69,7 @@ namespace Ryujinx.Ava.UI.Windows
InitializeComponent();
Load();
UiHandler = new AvaHostUiHandler(this);
UiHandler = new AvaHostUIHandler(this);
ViewModel.Title = $"Ryujinx {Program.Version}";
@ -319,13 +319,13 @@ namespace Ryujinx.Ava.UI.Windows
private void SetWindowSizePosition()
{
PixelPoint savedPoint = new(ConfigurationState.Instance.Ui.WindowStartup.WindowPositionX,
ConfigurationState.Instance.Ui.WindowStartup.WindowPositionY);
PixelPoint savedPoint = new(ConfigurationState.Instance.UI.WindowStartup.WindowPositionX,
ConfigurationState.Instance.UI.WindowStartup.WindowPositionY);
ViewModel.WindowHeight = ConfigurationState.Instance.Ui.WindowStartup.WindowSizeHeight * Program.WindowScaleFactor;
ViewModel.WindowWidth = ConfigurationState.Instance.Ui.WindowStartup.WindowSizeWidth * Program.WindowScaleFactor;
ViewModel.WindowHeight = ConfigurationState.Instance.UI.WindowStartup.WindowSizeHeight * Program.WindowScaleFactor;
ViewModel.WindowWidth = ConfigurationState.Instance.UI.WindowStartup.WindowSizeWidth * Program.WindowScaleFactor;
ViewModel.WindowState = ConfigurationState.Instance.Ui.WindowStartup.WindowMaximized.Value ? WindowState.Maximized : WindowState.Normal;
ViewModel.WindowState = ConfigurationState.Instance.UI.WindowStartup.WindowMaximized.Value ? WindowState.Maximized : WindowState.Normal;
if (CheckScreenBounds(savedPoint))
{
@ -353,13 +353,13 @@ namespace Ryujinx.Ava.UI.Windows
private void SaveWindowSizePosition()
{
ConfigurationState.Instance.Ui.WindowStartup.WindowSizeHeight.Value = (int)Height;
ConfigurationState.Instance.Ui.WindowStartup.WindowSizeWidth.Value = (int)Width;
ConfigurationState.Instance.UI.WindowStartup.WindowSizeHeight.Value = (int)Height;
ConfigurationState.Instance.UI.WindowStartup.WindowSizeWidth.Value = (int)Width;
ConfigurationState.Instance.Ui.WindowStartup.WindowPositionX.Value = Position.X;
ConfigurationState.Instance.Ui.WindowStartup.WindowPositionY.Value = Position.Y;
ConfigurationState.Instance.UI.WindowStartup.WindowPositionX.Value = Position.X;
ConfigurationState.Instance.UI.WindowStartup.WindowPositionY.Value = Position.Y;
ConfigurationState.Instance.Ui.WindowStartup.WindowMaximized.Value = WindowState == WindowState.Maximized;
ConfigurationState.Instance.UI.WindowStartup.WindowMaximized.Value = WindowState == WindowState.Maximized;
MainWindowViewModel.SaveConfig();
}
@ -512,12 +512,12 @@ namespace Ryujinx.Ava.UI.Windows
_ = fileType switch
{
#pragma warning disable IDE0055 // Disable formatting
"NSP" => ConfigurationState.Instance.Ui.ShownFileTypes.NSP.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NSP,
"PFS0" => ConfigurationState.Instance.Ui.ShownFileTypes.PFS0.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.PFS0,
"XCI" => ConfigurationState.Instance.Ui.ShownFileTypes.XCI.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.XCI,
"NCA" => ConfigurationState.Instance.Ui.ShownFileTypes.NCA.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NCA,
"NRO" => ConfigurationState.Instance.Ui.ShownFileTypes.NRO.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NRO,
"NSO" => ConfigurationState.Instance.Ui.ShownFileTypes.NSO.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NSO,
"NSP" => ConfigurationState.Instance.UI.ShownFileTypes.NSP.Value = !ConfigurationState.Instance.UI.ShownFileTypes.NSP,
"PFS0" => ConfigurationState.Instance.UI.ShownFileTypes.PFS0.Value = !ConfigurationState.Instance.UI.ShownFileTypes.PFS0,
"XCI" => ConfigurationState.Instance.UI.ShownFileTypes.XCI.Value = !ConfigurationState.Instance.UI.ShownFileTypes.XCI,
"NCA" => ConfigurationState.Instance.UI.ShownFileTypes.NCA.Value = !ConfigurationState.Instance.UI.ShownFileTypes.NCA,
"NRO" => ConfigurationState.Instance.UI.ShownFileTypes.NRO.Value = !ConfigurationState.Instance.UI.ShownFileTypes.NRO,
"NSO" => ConfigurationState.Instance.UI.ShownFileTypes.NSO.Value = !ConfigurationState.Instance.UI.ShownFileTypes.NSO,
_ => throw new ArgumentOutOfRangeException(fileType),
#pragma warning restore IDE0055
};
@ -537,7 +537,7 @@ namespace Ryujinx.Ava.UI.Windows
Thread applicationLibraryThread = new(() =>
{
ApplicationLibrary.LoadApplications(ConfigurationState.Instance.Ui.GameDirs, ConfigurationState.Instance.System.Language);
ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs, ConfigurationState.Instance.System.Language);
_isLoading = false;
})

View File

@ -40,14 +40,14 @@
Name="EnableAllButton"
MinWidth="90"
Margin="5"
Command="{ReflectionBinding EnableAll}">
Command="{Binding EnableAll}">
<TextBlock Text="{locale:Locale DlcManagerEnableAllButton}" />
</Button>
<Button
Name="DisableAllButton"
MinWidth="90"
Margin="5"
Command="{ReflectionBinding DisableAll}">
Command="{Binding DisableAll}">
<TextBlock Text="{locale:Locale DlcManagerDisableAllButton}" />
</Button>
</StackPanel>

View File

@ -6,7 +6,7 @@ using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ui.Common.Helper;
using Ryujinx.UI.Common.Helper;
using System.Threading.Tasks;
using Button = Avalonia.Controls.Button;

View File

@ -4,7 +4,7 @@ using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.UI.Common.Configuration;
using System.IO;
using System.Reflection;
@ -19,7 +19,7 @@ namespace Ryujinx.Ava.UI.Windows
WindowStartupLocation = WindowStartupLocation.CenterOwner;
TransparencyLevelHint = new[] { WindowTransparencyLevel.None };
using Stream stream = Assembly.GetAssembly(typeof(ConfigurationState)).GetManifestResourceStream("Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png");
using Stream stream = Assembly.GetAssembly(typeof(ConfigurationState)).GetManifestResourceStream("Ryujinx.UI.Common.Resources.Logo_Ryujinx.png");
Icon = new WindowIcon(stream);
stream.Position = 0;

View File

@ -7,7 +7,7 @@ using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.HLE.FileSystem;
using Ryujinx.Ui.Common.Helper;
using Ryujinx.UI.Common.Helper;
using System.Threading.Tasks;
using Button = Avalonia.Controls.Button;

View File

@ -2,6 +2,7 @@ using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using System;
using System.IO;
using System.Runtime.Versioning;
namespace Ryujinx.Common.Configuration
{
@ -30,6 +31,8 @@ namespace Ryujinx.Common.Configuration
public static string KeysDirPath { get; private set; }
public static string KeysDirPathUser { get; }
public static string LogsDirPath { get; private set; }
public const string DefaultNandDir = "bis";
public const string DefaultSdcardDir = "sdcard";
private const string DefaultModsDir = "mods";
@ -46,15 +49,7 @@ namespace Ryujinx.Common.Configuration
public static void Initialize(string baseDirPath)
{
string appDataPath;
if (OperatingSystem.IsMacOS())
{
appDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Application Support");
}
else
{
appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
}
string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
if (appDataPath.Length == 0)
{
@ -101,26 +96,134 @@ namespace Ryujinx.Common.Configuration
BaseDirPath = Path.GetFullPath(BaseDirPath); // convert relative paths
// NOTE: Moves the Ryujinx folder in `~/.config` to `~/Library/Application Support` if one is found
// and a Ryujinx folder does not already exist in Application Support.
// Also creates a symlink from `~/.config/Ryujinx` to `~/Library/Application Support/Ryujinx` to preserve backwards compatibility.
// This should be removed in the future.
if (OperatingSystem.IsMacOS() && Mode == LaunchMode.UserProfile)
if (IsPathSymlink(BaseDirPath))
{
string oldConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), DefaultBaseDir);
if (Path.Exists(oldConfigPath) && !IsPathSymlink(oldConfigPath) && !Path.Exists(BaseDirPath))
{
FileSystemUtils.MoveDirectory(oldConfigPath, BaseDirPath);
Directory.CreateSymbolicLink(oldConfigPath, BaseDirPath);
}
Logger.Warning?.Print(LogClass.Application, $"Application data directory is a symlink. This may be unintended.");
}
SetupBasePaths();
}
public static string GetOrCreateLogsDir()
{
if (Directory.Exists(LogsDirPath))
{
return LogsDirPath;
}
Logger.Notice.Print(LogClass.Application, "Logging directory not found; attempting to create new logging directory.");
LogsDirPath = SetUpLogsDir();
return LogsDirPath;
}
private static string SetUpLogsDir()
{
string logDir = "";
if (Mode == LaunchMode.Portable)
{
logDir = Path.Combine(BaseDirPath, "Logs");
try
{
Directory.CreateDirectory(logDir);
}
catch
{
Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'");
return null;
}
}
else
{
if (OperatingSystem.IsMacOS())
{
// NOTE: Should evaluate to "~/Library/Logs/Ryujinx/".
logDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Logs", DefaultBaseDir);
try
{
Directory.CreateDirectory(logDir);
}
catch
{
Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'");
logDir = "";
}
if (string.IsNullOrEmpty(logDir))
{
// NOTE: Should evaluate to "~/Library/Application Support/Ryujinx/Logs".
logDir = Path.Combine(BaseDirPath, "Logs");
try
{
Directory.CreateDirectory(logDir);
}
catch
{
Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'");
return null;
}
}
}
else if (OperatingSystem.IsWindows())
{
// NOTE: Should evaluate to a "Logs" directory in whatever directory Ryujinx was launched from.
logDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs");
try
{
Directory.CreateDirectory(logDir);
}
catch
{
Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'");
logDir = "";
}
if (string.IsNullOrEmpty(logDir))
{
// NOTE: Should evaluate to "C:\Users\user\AppData\Roaming\Ryujinx\Logs".
logDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), DefaultBaseDir, "Logs");
try
{
Directory.CreateDirectory(logDir);
}
catch
{
Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'");
return null;
}
}
}
else if (OperatingSystem.IsLinux())
{
// NOTE: Should evaluate to "~/.config/Ryujinx/Logs".
logDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), DefaultBaseDir, "Logs");
try
{
Directory.CreateDirectory(logDir);
}
catch
{
Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'");
return null;
}
}
}
return logDir;
}
private static void SetupBasePaths()
{
Directory.CreateDirectory(BaseDirPath);
LogsDirPath = SetUpLogsDir();
Directory.CreateDirectory(GamesDirPath = Path.Combine(BaseDirPath, GamesDir));
Directory.CreateDirectory(ProfilesDirPath = Path.Combine(BaseDirPath, ProfilesDir));
Directory.CreateDirectory(KeysDirPath = Path.Combine(BaseDirPath, KeysDir));
@ -130,8 +233,91 @@ namespace Ryujinx.Common.Configuration
// Should be removed, when the existence of the old directory isn't checked anymore.
private static bool IsPathSymlink(string path)
{
FileAttributes attributes = File.GetAttributes(path);
return (attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint;
try
{
FileAttributes attributes = File.GetAttributes(path);
return (attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint;
}
catch
{
return false;
}
}
[SupportedOSPlatform("macos")]
public static void FixMacOSConfigurationFolders()
{
string oldConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
".config", DefaultBaseDir);
if (Path.Exists(oldConfigPath) && !IsPathSymlink(oldConfigPath) && !Path.Exists(BaseDirPath))
{
FileSystemUtils.MoveDirectory(oldConfigPath, BaseDirPath);
Directory.CreateSymbolicLink(oldConfigPath, BaseDirPath);
}
string correctApplicationDataDirectoryPath =
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), DefaultBaseDir);
if (IsPathSymlink(correctApplicationDataDirectoryPath))
{
//copy the files somewhere temporarily
string tempPath = Path.Combine(Path.GetTempPath(), DefaultBaseDir);
try
{
FileSystemUtils.CopyDirectory(correctApplicationDataDirectoryPath, tempPath, true);
}
catch (Exception exception)
{
Logger.Error?.Print(LogClass.Application,
$"Critical error copying Ryujinx application data into the temp folder. {exception}");
try
{
FileSystemInfo resolvedDirectoryInfo =
Directory.ResolveLinkTarget(correctApplicationDataDirectoryPath, true);
string resolvedPath = resolvedDirectoryInfo.FullName;
Logger.Error?.Print(LogClass.Application, $"Please manually move your Ryujinx data from {resolvedPath} to {correctApplicationDataDirectoryPath}, and remove the symlink.");
}
catch (Exception symlinkException)
{
Logger.Error?.Print(LogClass.Application, $"Unable to resolve the symlink for Ryujinx application data: {symlinkException}. Follow the symlink at {correctApplicationDataDirectoryPath} and move your data back to the Application Support folder.");
}
return;
}
//delete the symlink
try
{
//This will fail if this is an actual directory, so there is no way we can actually delete user data here.
File.Delete(correctApplicationDataDirectoryPath);
}
catch (Exception exception)
{
Logger.Error?.Print(LogClass.Application,
$"Critical error deleting the Ryujinx application data folder symlink at {correctApplicationDataDirectoryPath}. {exception}");
try
{
FileSystemInfo resolvedDirectoryInfo =
Directory.ResolveLinkTarget(correctApplicationDataDirectoryPath, true);
string resolvedPath = resolvedDirectoryInfo.FullName;
Logger.Error?.Print(LogClass.Application, $"Please manually move your Ryujinx data from {resolvedPath} to {correctApplicationDataDirectoryPath}, and remove the symlink.");
}
catch (Exception symlinkException)
{
Logger.Error?.Print(LogClass.Application, $"Unable to resolve the symlink for Ryujinx application data: {symlinkException}. Follow the symlink at {correctApplicationDataDirectoryPath} and move your data back to the Application Support folder.");
}
return;
}
//put the files back
try
{
FileSystemUtils.CopyDirectory(tempPath, correctApplicationDataDirectoryPath, true);
}
catch (Exception exception)
{
Logger.Error?.Print(LogClass.Application,
$"Critical error copying Ryujinx application data into the correct location. {exception}. Please manually move your application data from {tempPath} to {correctApplicationDataDirectoryPath}.");
}
}
}
public static string GetModsPath() => CustomModsPath ?? Directory.CreateDirectory(Path.Combine(BaseDirPath, DefaultModsDir)).FullName;

View File

@ -6,7 +6,7 @@ namespace Ryujinx.Common.Configuration.Hid
{
public Key ToggleVsync { get; set; }
public Key Screenshot { get; set; }
public Key ShowUi { get; set; }
public Key ShowUI { get; set; }
public Key Pause { get; set; }
public Key ToggleMute { get; set; }
public Key ResScaleUp { get; set; }

View File

@ -70,7 +70,7 @@ namespace Ryujinx.Common.Logging
ServiceVi,
SurfaceFlinger,
TamperMachine,
Ui,
UI,
Vic,
}
}

View File

@ -23,7 +23,18 @@ namespace Ryujinx.Common.Logging.Targets
public static FileStream PrepareLogFile(string path)
{
// Ensure directory is present
DirectoryInfo logDir = new(path);
DirectoryInfo logDir;
try
{
logDir = new DirectoryInfo(path);
}
catch (ArgumentException exception)
{
Logger.Warning?.Print(LogClass.Application, $"Logging directory path ('{path}') was invalid: {exception}");
return null;
}
try
{
logDir.Create();

View File

@ -148,6 +148,7 @@ namespace Ryujinx.Graphics.GAL
B8G8R8A8Unorm,
B8G8R8A8Srgb,
B10G10R10A2Unorm,
X8UintD24Unorm,
}
public static class FormatExtensions
@ -269,6 +270,7 @@ namespace Ryujinx.Graphics.GAL
case Format.D16Unorm:
return 2;
case Format.S8UintD24Unorm:
case Format.X8UintD24Unorm:
case Format.D32Float:
case Format.D24UnormS8Uint:
return 4;
@ -349,6 +351,7 @@ namespace Ryujinx.Graphics.GAL
case Format.D16Unorm:
case Format.D24UnormS8Uint:
case Format.S8UintD24Unorm:
case Format.X8UintD24Unorm:
case Format.D32Float:
case Format.D32FloatS8Uint:
return true;
@ -633,6 +636,7 @@ namespace Ryujinx.Graphics.GAL
case Format.D16Unorm:
case Format.D24UnormS8Uint:
case Format.S8UintD24Unorm:
case Format.X8UintD24Unorm:
case Format.D32Float:
case Format.D32FloatS8Uint:
case Format.S8Uint:

View File

@ -58,7 +58,7 @@ namespace Ryujinx.Graphics.GAL
void SetIndexBuffer(BufferRange buffer, IndexType type);
void SetImage(int binding, ITexture texture, Format imageFormat);
void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat);
void SetLineParameters(float width, bool smooth);

View File

@ -1,17 +1,20 @@
using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources;
using Ryujinx.Graphics.Shader;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
{
struct SetImageCommand : IGALCommand, IGALCommand<SetImageCommand>
{
public readonly CommandType CommandType => CommandType.SetImage;
private ShaderStage _stage;
private int _binding;
private TableRef<ITexture> _texture;
private Format _imageFormat;
public void Set(int binding, TableRef<ITexture> texture, Format imageFormat)
public void Set(ShaderStage stage, int binding, TableRef<ITexture> texture, Format imageFormat)
{
_stage = stage;
_binding = binding;
_texture = texture;
_imageFormat = imageFormat;
@ -19,7 +22,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands
public static void Run(ref SetImageCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
renderer.Pipeline.SetImage(command._binding, command._texture.GetAs<ThreadedTexture>(threaded)?.Base, command._imageFormat);
renderer.Pipeline.SetImage(command._stage, command._binding, command._texture.GetAs<ThreadedTexture>(threaded)?.Base, command._imageFormat);
}
}
}

View File

@ -177,9 +177,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand();
}
public void SetImage(int binding, ITexture texture, Format imageFormat)
public void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat)
{
_renderer.New<SetImageCommand>().Set(binding, Ref(texture), imageFormat);
_renderer.New<SetImageCommand>().Set(stage, binding, Ref(texture), imageFormat);
_renderer.QueueCommand();
}

View File

@ -8,13 +8,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.Types
/// </summary>
enum ZetaFormat
{
D32Float = 0xa,
D16Unorm = 0x13,
D24UnormS8Uint = 0x14,
D24Unorm = 0x15,
S8UintD24Unorm = 0x16,
Zf32 = 0xa,
Z16 = 0x13,
Z24S8 = 0x14,
X8Z24 = 0x15,
S8Z24 = 0x16,
S8Uint = 0x17,
D32FloatS8Uint = 0x19,
Zf32X24S8 = 0x19,
}
static class ZetaFormatConverter
@ -29,14 +29,14 @@ namespace Ryujinx.Graphics.Gpu.Engine.Types
return format switch
{
#pragma warning disable IDE0055 // Disable formatting
ZetaFormat.D32Float => new FormatInfo(Format.D32Float, 1, 1, 4, 1),
ZetaFormat.D16Unorm => new FormatInfo(Format.D16Unorm, 1, 1, 2, 1),
ZetaFormat.D24UnormS8Uint => new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2),
ZetaFormat.D24Unorm => new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 1),
ZetaFormat.S8UintD24Unorm => new FormatInfo(Format.S8UintD24Unorm, 1, 1, 4, 2),
ZetaFormat.S8Uint => new FormatInfo(Format.S8Uint, 1, 1, 1, 1),
ZetaFormat.D32FloatS8Uint => new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2),
_ => FormatInfo.Default,
ZetaFormat.Zf32 => new FormatInfo(Format.D32Float, 1, 1, 4, 1),
ZetaFormat.Z16 => new FormatInfo(Format.D16Unorm, 1, 1, 2, 1),
ZetaFormat.Z24S8 => new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2),
ZetaFormat.X8Z24 => new FormatInfo(Format.X8UintD24Unorm, 1, 1, 4, 1),
ZetaFormat.S8Z24 => new FormatInfo(Format.S8UintD24Unorm, 1, 1, 4, 2),
ZetaFormat.S8Uint => new FormatInfo(Format.S8Uint, 1, 1, 1, 1),
ZetaFormat.Zf32X24S8 => new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2),
_ => FormatInfo.Default,
#pragma warning restore IDE0055
};
}

View File

@ -185,6 +185,7 @@ namespace Ryujinx.Graphics.Gpu.Image
G24R8RUintGUnormBUnormAUnorm = G24R8 | RUint | GUnorm | BUnorm | AUnorm, // 0x24a0e
Z24S8RUintGUnormBUnormAUnorm = Z24S8 | RUint | GUnorm | BUnorm | AUnorm, // 0x24a29
Z24S8RUintGUnormBUintAUint = Z24S8 | RUint | GUnorm | BUint | AUint, // 0x48a29
X8Z24RUnormGUintBUintAUint = X8Z24 | RUnorm | GUint | BUint | AUint, // 0x4912a
S8Z24RUnormGUintBUintAUint = S8Z24 | RUnorm | GUint | BUint | AUint, // 0x4912b
R32B24G8RFloatGUintBUnormAUnorm = R32B24G8 | RFloat | GUint | BUnorm | AUnorm, // 0x25385
Zf32X24S8RFloatGUintBUnormAUnorm = Zf32X24S8 | RFloat | GUint | BUnorm | AUnorm, // 0x253b0
@ -410,6 +411,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{ TextureFormat.G24R8RUintGUnormBUnormAUnorm, new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2) },
{ TextureFormat.Z24S8RUintGUnormBUnormAUnorm, new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2) },
{ TextureFormat.Z24S8RUintGUnormBUintAUint, new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2) },
{ TextureFormat.X8Z24RUnormGUintBUintAUint, new FormatInfo(Format.X8UintD24Unorm, 1, 1, 4, 2) },
{ TextureFormat.S8Z24RUnormGUintBUintAUint, new FormatInfo(Format.S8UintD24Unorm, 1, 1, 4, 2) },
{ TextureFormat.R32B24G8RFloatGUintBUnormAUnorm, new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2) },
{ TextureFormat.Zf32X24S8RFloatGUintBUnormAUnorm, new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2) },

View File

@ -634,7 +634,7 @@ namespace Ryujinx.Graphics.Gpu.Image
state.Texture = hostTextureRebind;
state.ImageFormat = format;
_context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTextureRebind, format);
_context.Renderer.Pipeline.SetImage(stage, bindingInfo.Binding, hostTextureRebind, format);
}
continue;
@ -692,7 +692,7 @@ namespace Ryujinx.Graphics.Gpu.Image
state.ImageFormat = format;
_context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTexture, format);
_context.Renderer.Pipeline.SetImage(stage, bindingInfo.Binding, hostTexture, format);
}
state.CachedTexture = texture;

View File

@ -242,7 +242,8 @@ namespace Ryujinx.Graphics.Gpu.Image
return TextureMatchQuality.FormatAlias;
}
else if ((lhs.FormatInfo.Format == Format.D24UnormS8Uint ||
lhs.FormatInfo.Format == Format.S8UintD24Unorm) && rhs.FormatInfo.Format == Format.B8G8R8A8Unorm)
lhs.FormatInfo.Format == Format.S8UintD24Unorm ||
lhs.FormatInfo.Format == Format.X8UintD24Unorm) && rhs.FormatInfo.Format == Format.B8G8R8A8Unorm)
{
return TextureMatchQuality.FormatAlias;
}

View File

@ -484,7 +484,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (binding.IsImage)
{
_context.Renderer.Pipeline.SetImage(binding.BindingInfo.Binding, binding.Texture, binding.Format);
_context.Renderer.Pipeline.SetImage(binding.Stage, binding.BindingInfo.Binding, binding.Texture, binding.Format);
}
else
{

View File

@ -68,6 +68,7 @@ namespace Ryujinx.Graphics.OpenGL
Add(Format.S8Uint, new FormatInfo(1, false, false, All.StencilIndex8, PixelFormat.StencilIndex, PixelType.UnsignedByte));
Add(Format.D16Unorm, new FormatInfo(1, false, false, All.DepthComponent16, PixelFormat.DepthComponent, PixelType.UnsignedShort));
Add(Format.S8UintD24Unorm, new FormatInfo(1, false, false, All.Depth24Stencil8, PixelFormat.DepthStencil, PixelType.UnsignedInt248));
Add(Format.X8UintD24Unorm, new FormatInfo(1, false, false, All.DepthComponent24, PixelFormat.DepthComponent, PixelType.UnsignedInt));
Add(Format.D32Float, new FormatInfo(1, false, false, All.DepthComponent32f, PixelFormat.DepthComponent, PixelType.Float));
Add(Format.D24UnormS8Uint, new FormatInfo(1, false, false, All.Depth24Stencil8, PixelFormat.DepthStencil, PixelType.UnsignedInt248));
Add(Format.D32FloatS8Uint, new FormatInfo(1, false, false, All.Depth32fStencil8, PixelFormat.DepthStencil, PixelType.Float32UnsignedInt248Rev));
@ -224,5 +225,17 @@ namespace Ryujinx.Graphics.OpenGL
{
return _tableImage[(int)format];
}
public static bool IsPackedDepthStencil(Format format)
{
return format == Format.D24UnormS8Uint ||
format == Format.D32FloatS8Uint ||
format == Format.S8UintD24Unorm;
}
public static bool IsDepthOnly(Format format)
{
return format == Format.D16Unorm || format == Format.D32Float || format == Format.X8UintD24Unorm;
}
}
}

View File

@ -119,11 +119,11 @@ namespace Ryujinx.Graphics.OpenGL
private static FramebufferAttachment GetAttachment(Format format)
{
if (IsPackedDepthStencilFormat(format))
if (FormatTable.IsPackedDepthStencil(format))
{
return FramebufferAttachment.DepthStencilAttachment;
}
else if (IsDepthOnlyFormat(format))
else if (FormatTable.IsDepthOnly(format))
{
return FramebufferAttachment.DepthAttachment;
}
@ -133,18 +133,6 @@ namespace Ryujinx.Graphics.OpenGL
}
}
private static bool IsPackedDepthStencilFormat(Format format)
{
return format == Format.D24UnormS8Uint ||
format == Format.D32FloatS8Uint ||
format == Format.S8UintD24Unorm;
}
private static bool IsDepthOnlyFormat(Format format)
{
return format == Format.D16Unorm || format == Format.D32Float;
}
public int GetColorLayerCount(int index)
{
return _colors[index]?.Info.GetDepthOrLayers() ?? 0;

View File

@ -294,7 +294,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
{
return FramebufferAttachment.DepthStencilAttachment;
}
else if (IsDepthOnly(format))
else if (FormatTable.IsDepthOnly(format))
{
return FramebufferAttachment.DepthAttachment;
}
@ -324,11 +324,11 @@ namespace Ryujinx.Graphics.OpenGL.Image
private static ClearBufferMask GetMask(Format format)
{
if (format == Format.D24UnormS8Uint || format == Format.D32FloatS8Uint || format == Format.S8UintD24Unorm)
if (FormatTable.IsPackedDepthStencil(format))
{
return ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit;
}
else if (IsDepthOnly(format))
else if (FormatTable.IsDepthOnly(format))
{
return ClearBufferMask.DepthBufferBit;
}
@ -342,11 +342,6 @@ namespace Ryujinx.Graphics.OpenGL.Image
}
}
private static bool IsDepthOnly(Format format)
{
return format == Format.D16Unorm || format == Format.D32Float;
}
public TextureView BgraSwap(TextureView from)
{
TextureView to = (TextureView)_renderer.CreateTexture(from.Info);

View File

@ -935,7 +935,7 @@ namespace Ryujinx.Graphics.OpenGL
SetFrontFace(_frontFace = frontFace.Convert());
}
public void SetImage(int binding, ITexture texture, Format imageFormat)
public void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat)
{
if ((uint)binding < SavedImages)
{

View File

@ -0,0 +1,225 @@
using Silk.NET.Vulkan;
using System;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Vulkan
{
internal class BarrierBatch : IDisposable
{
private const int MaxBarriersPerCall = 16;
private readonly VulkanRenderer _gd;
private readonly NativeArray<MemoryBarrier> _memoryBarrierBatch = new(MaxBarriersPerCall);
private readonly NativeArray<BufferMemoryBarrier> _bufferBarrierBatch = new(MaxBarriersPerCall);
private readonly NativeArray<ImageMemoryBarrier> _imageBarrierBatch = new(MaxBarriersPerCall);
private readonly List<BarrierWithStageFlags<MemoryBarrier>> _memoryBarriers = new();
private readonly List<BarrierWithStageFlags<BufferMemoryBarrier>> _bufferBarriers = new();
private readonly List<BarrierWithStageFlags<ImageMemoryBarrier>> _imageBarriers = new();
private int _queuedBarrierCount;
public BarrierBatch(VulkanRenderer gd)
{
_gd = gd;
}
private readonly record struct StageFlags : IEquatable<StageFlags>
{
public readonly PipelineStageFlags Source;
public readonly PipelineStageFlags Dest;
public StageFlags(PipelineStageFlags source, PipelineStageFlags dest)
{
Source = source;
Dest = dest;
}
}
private readonly struct BarrierWithStageFlags<T> where T : unmanaged
{
public readonly StageFlags Flags;
public readonly T Barrier;
public BarrierWithStageFlags(StageFlags flags, T barrier)
{
Flags = flags;
Barrier = barrier;
}
public BarrierWithStageFlags(PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags, T barrier)
{
Flags = new StageFlags(srcStageFlags, dstStageFlags);
Barrier = barrier;
}
}
private void QueueBarrier<T>(List<BarrierWithStageFlags<T>> list, T barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags) where T : unmanaged
{
list.Add(new BarrierWithStageFlags<T>(srcStageFlags, dstStageFlags, barrier));
_queuedBarrierCount++;
}
public void QueueBarrier(MemoryBarrier barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
{
QueueBarrier(_memoryBarriers, barrier, srcStageFlags, dstStageFlags);
}
public void QueueBarrier(BufferMemoryBarrier barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
{
QueueBarrier(_bufferBarriers, barrier, srcStageFlags, dstStageFlags);
}
public void QueueBarrier(ImageMemoryBarrier barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
{
QueueBarrier(_imageBarriers, barrier, srcStageFlags, dstStageFlags);
}
public unsafe void Flush(CommandBuffer cb, bool insideRenderPass, Action endRenderPass)
{
while (_queuedBarrierCount > 0)
{
int memoryCount = 0;
int bufferCount = 0;
int imageCount = 0;
bool hasBarrier = false;
StageFlags flags = default;
static void AddBarriers<T>(
Span<T> target,
ref int queuedBarrierCount,
ref bool hasBarrier,
ref StageFlags flags,
ref int count,
List<BarrierWithStageFlags<T>> list) where T : unmanaged
{
int firstMatch = -1;
for (int i = 0; i < list.Count; i++)
{
BarrierWithStageFlags<T> barrier = list[i];
if (!hasBarrier)
{
flags = barrier.Flags;
hasBarrier = true;
target[count++] = barrier.Barrier;
queuedBarrierCount--;
firstMatch = i;
if (count >= target.Length)
{
break;
}
}
else
{
if (flags.Equals(barrier.Flags))
{
target[count++] = barrier.Barrier;
queuedBarrierCount--;
if (firstMatch == -1)
{
firstMatch = i;
}
if (count >= target.Length)
{
break;
}
}
else
{
// Delete consumed barriers from the first match to the current non-match.
if (firstMatch != -1)
{
int deleteCount = i - firstMatch;
list.RemoveRange(firstMatch, deleteCount);
i -= deleteCount;
firstMatch = -1;
}
}
}
}
if (firstMatch == 0)
{
list.Clear();
}
else if (firstMatch != -1)
{
int deleteCount = list.Count - firstMatch;
list.RemoveRange(firstMatch, deleteCount);
}
}
if (insideRenderPass)
{
// Image barriers queued in the batch are meant to be globally scoped,
// but inside a render pass they're scoped to just the range of the render pass.
// On MoltenVK, we just break the rules and always use image barrier.
// On desktop GPUs, all barriers are globally scoped, so we just replace it with a generic memory barrier.
// TODO: On certain GPUs, we need to split render pass so the barrier scope is global. When this is done,
// notify the resource that it should add a barrier as soon as a render pass ends to avoid this in future.
if (!_gd.IsMoltenVk)
{
foreach (var barrier in _imageBarriers)
{
_memoryBarriers.Add(new BarrierWithStageFlags<MemoryBarrier>(
barrier.Flags,
new MemoryBarrier()
{
SType = StructureType.MemoryBarrier,
SrcAccessMask = barrier.Barrier.SrcAccessMask,
DstAccessMask = barrier.Barrier.DstAccessMask
}));
}
_imageBarriers.Clear();
}
}
AddBarriers(_memoryBarrierBatch.AsSpan(), ref _queuedBarrierCount, ref hasBarrier, ref flags, ref memoryCount, _memoryBarriers);
AddBarriers(_bufferBarrierBatch.AsSpan(), ref _queuedBarrierCount, ref hasBarrier, ref flags, ref bufferCount, _bufferBarriers);
AddBarriers(_imageBarrierBatch.AsSpan(), ref _queuedBarrierCount, ref hasBarrier, ref flags, ref imageCount, _imageBarriers);
if (hasBarrier)
{
PipelineStageFlags srcStageFlags = flags.Source;
if (insideRenderPass)
{
// Inside a render pass, barrier stages can only be from rasterization.
srcStageFlags &= ~PipelineStageFlags.ComputeShaderBit;
}
_gd.Api.CmdPipelineBarrier(
cb,
srcStageFlags,
flags.Dest,
0,
(uint)memoryCount,
_memoryBarrierBatch.Pointer,
(uint)bufferCount,
_bufferBarrierBatch.Pointer,
(uint)imageCount,
_imageBarrierBatch.Pointer);
}
}
}
public void Dispose()
{
_memoryBarrierBatch.Dispose();
_bufferBarrierBatch.Dispose();
_imageBarrierBatch.Dispose();
}
}
}

View File

@ -16,6 +16,7 @@ namespace Ryujinx.Graphics.Vulkan
public const int MaxStorageBufferBindings = MaxStorageBuffersPerStage * MaxShaderStages;
public const int MaxTextureBindings = MaxTexturesPerStage * MaxShaderStages;
public const int MaxImageBindings = MaxImagesPerStage * MaxShaderStages;
public const int MaxPushDescriptorBinding = 64;
public const ulong SparseBufferAlignment = 0x10000;
}

View File

@ -1,19 +1,32 @@
using Ryujinx.Graphics.GAL;
using Silk.NET.Vulkan;
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
namespace Ryujinx.Graphics.Vulkan
{
class DescriptorSetTemplate : IDisposable
{
/// <summary>
/// Renderdoc seems to crash when doing a templated uniform update with count > 1 on a push descriptor.
/// When this is true, consecutive buffers are always updated individually.
/// </summary>
private const bool RenderdocPushCountBug = true;
private readonly VulkanRenderer _gd;
private readonly Device _device;
public readonly DescriptorUpdateTemplate Template;
public readonly int Size;
public unsafe DescriptorSetTemplate(VulkanRenderer gd, Device device, ResourceBindingSegment[] segments, PipelineLayoutCacheEntry plce, PipelineBindPoint pbp, int setIndex)
public unsafe DescriptorSetTemplate(
VulkanRenderer gd,
Device device,
ResourceBindingSegment[] segments,
PipelineLayoutCacheEntry plce,
PipelineBindPoint pbp,
int setIndex)
{
_gd = gd;
_device = device;
@ -137,6 +150,93 @@ namespace Ryujinx.Graphics.Vulkan
Template = result;
}
public unsafe DescriptorSetTemplate(
VulkanRenderer gd,
Device device,
ResourceDescriptorCollection descriptors,
long updateMask,
PipelineLayoutCacheEntry plce,
PipelineBindPoint pbp,
int setIndex)
{
_gd = gd;
_device = device;
// Create a template from the set usages. Assumes the descriptor set is updated in segment order then binding order.
int segmentCount = BitOperations.PopCount((ulong)updateMask);
DescriptorUpdateTemplateEntry* entries = stackalloc DescriptorUpdateTemplateEntry[segmentCount];
int entry = 0;
nuint structureOffset = 0;
void AddBinding(int binding, int count)
{
entries[entry++] = new DescriptorUpdateTemplateEntry()
{
DescriptorType = DescriptorType.UniformBuffer,
DstBinding = (uint)binding,
DescriptorCount = (uint)count,
Offset = structureOffset,
Stride = (nuint)Unsafe.SizeOf<DescriptorBufferInfo>()
};
structureOffset += (nuint)(Unsafe.SizeOf<DescriptorBufferInfo>() * count);
}
int startBinding = 0;
int bindingCount = 0;
foreach (ResourceDescriptor descriptor in descriptors.Descriptors)
{
for (int i = 0; i < descriptor.Count; i++)
{
int binding = descriptor.Binding + i;
if ((updateMask & (1L << binding)) != 0)
{
if (bindingCount > 0 && (RenderdocPushCountBug || startBinding + bindingCount != binding))
{
AddBinding(startBinding, bindingCount);
bindingCount = 0;
}
if (bindingCount == 0)
{
startBinding = binding;
}
bindingCount++;
}
}
}
if (bindingCount > 0)
{
AddBinding(startBinding, bindingCount);
}
Size = (int)structureOffset;
var info = new DescriptorUpdateTemplateCreateInfo()
{
SType = StructureType.DescriptorUpdateTemplateCreateInfo,
DescriptorUpdateEntryCount = (uint)entry,
PDescriptorUpdateEntries = entries,
TemplateType = DescriptorUpdateTemplateType.PushDescriptorsKhr,
DescriptorSetLayout = plce.DescriptorSetLayouts[setIndex],
PipelineBindPoint = pbp,
PipelineLayout = plce.PipelineLayout,
Set = (uint)setIndex,
};
DescriptorUpdateTemplate result;
gd.Api.CreateDescriptorUpdateTemplate(device, &info, null, &result).ThrowOnError();
Template = result;
}
public unsafe void Dispose()
{
_gd.Api.DestroyDescriptorUpdateTemplate(_device, Template, null);

View File

@ -52,11 +52,23 @@ namespace Ryujinx.Graphics.Vulkan
return new DescriptorSetTemplateWriter(new Span<byte>(_data.Pointer, template.Size));
}
public DescriptorSetTemplateWriter Begin(int maxSize)
{
EnsureSize(maxSize);
return new DescriptorSetTemplateWriter(new Span<byte>(_data.Pointer, maxSize));
}
public void Commit(VulkanRenderer gd, Device device, DescriptorSet set)
{
gd.Api.UpdateDescriptorSetWithTemplate(device, set, _activeTemplate.Template, _data.Pointer);
}
public void CommitPushDescriptor(VulkanRenderer gd, CommandBufferScoped cbs, DescriptorSetTemplate template, PipelineLayout layout)
{
gd.PushDescriptorApi.CmdPushDescriptorSetWithTemplate(cbs.CommandBuffer, template.Template, layout, 0, _data.Pointer);
}
public void Dispose()
{
_data?.Dispose();

View File

@ -4,6 +4,7 @@ using Ryujinx.Graphics.Shader;
using Silk.NET.Vulkan;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using CompareOp = Ryujinx.Graphics.GAL.CompareOp;
using Format = Ryujinx.Graphics.GAL.Format;
using SamplerCreateInfo = Ryujinx.Graphics.GAL.SamplerCreateInfo;
@ -34,6 +35,36 @@ namespace Ryujinx.Graphics.Vulkan
}
}
private record struct TextureRef
{
public ShaderStage Stage;
public TextureStorage Storage;
public Auto<DisposableImageView> View;
public Auto<DisposableSampler> Sampler;
public TextureRef(ShaderStage stage, TextureStorage storage, Auto<DisposableImageView> view, Auto<DisposableSampler> sampler)
{
Stage = stage;
Storage = storage;
View = view;
Sampler = sampler;
}
}
private record struct ImageRef
{
public ShaderStage Stage;
public TextureStorage Storage;
public Auto<DisposableImageView> View;
public ImageRef(ShaderStage stage, TextureStorage storage, Auto<DisposableImageView> view)
{
Stage = stage;
Storage = storage;
View = view;
}
}
private readonly VulkanRenderer _gd;
private readonly Device _device;
private readonly PipelineBase _pipeline;
@ -41,9 +72,8 @@ namespace Ryujinx.Graphics.Vulkan
private readonly BufferRef[] _uniformBufferRefs;
private readonly BufferRef[] _storageBufferRefs;
private readonly Auto<DisposableImageView>[] _textureRefs;
private readonly Auto<DisposableSampler>[] _samplerRefs;
private readonly Auto<DisposableImageView>[] _imageRefs;
private readonly TextureRef[] _textureRefs;
private readonly ImageRef[] _imageRefs;
private readonly TextureBuffer[] _bufferTextureRefs;
private readonly TextureBuffer[] _bufferImageRefs;
private readonly Format[] _bufferImageFormats;
@ -61,6 +91,8 @@ namespace Ryujinx.Graphics.Vulkan
private BitMapStruct<Array2<long>> _storageSet;
private BitMapStruct<Array2<long>> _uniformMirrored;
private BitMapStruct<Array2<long>> _storageMirrored;
private readonly int[] _uniformSetPd;
private int _pdSequence = 1;
private bool _updateDescriptorCacheCbIndex;
@ -92,9 +124,8 @@ namespace Ryujinx.Graphics.Vulkan
_uniformBufferRefs = new BufferRef[Constants.MaxUniformBufferBindings];
_storageBufferRefs = new BufferRef[Constants.MaxStorageBufferBindings];
_textureRefs = new Auto<DisposableImageView>[Constants.MaxTextureBindings * 2];
_samplerRefs = new Auto<DisposableSampler>[Constants.MaxTextureBindings * 2];
_imageRefs = new Auto<DisposableImageView>[Constants.MaxImageBindings * 2];
_textureRefs = new TextureRef[Constants.MaxTextureBindings * 2];
_imageRefs = new ImageRef[Constants.MaxImageBindings * 2];
_bufferTextureRefs = new TextureBuffer[Constants.MaxTextureBindings * 2];
_bufferImageRefs = new TextureBuffer[Constants.MaxImageBindings * 2];
_bufferImageFormats = new Format[Constants.MaxImageBindings * 2];
@ -106,6 +137,8 @@ namespace Ryujinx.Graphics.Vulkan
_bufferTextures = new BufferView[Constants.MaxTexturesPerStage];
_bufferImages = new BufferView[Constants.MaxImagesPerStage];
_uniformSetPd = new int[Constants.MaxUniformBufferBindings];
var initialImageInfo = new DescriptorImageInfo
{
ImageLayout = ImageLayout.General,
@ -193,6 +226,7 @@ namespace Ryujinx.Graphics.Vulkan
if (BindingOverlaps(ref info, bindingOffset, offset, size))
{
_uniformSet.Clear(binding);
_uniformSetPd[binding] = 0;
SignalDirty(DirtyFlags.Uniform);
}
}
@ -223,14 +257,68 @@ namespace Ryujinx.Graphics.Vulkan
});
}
public void SetProgram(ShaderCollection program)
public void InsertBindingBarriers(CommandBufferScoped cbs)
{
foreach (ResourceBindingSegment segment in _program.BindingSegments[PipelineBase.TextureSetIndex])
{
if (segment.Type == ResourceType.TextureAndSampler)
{
for (int i = 0; i < segment.Count; i++)
{
ref var texture = ref _textureRefs[segment.Binding + i];
texture.Storage?.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, texture.Stage.ConvertToPipelineStageFlags());
}
}
}
foreach (ResourceBindingSegment segment in _program.BindingSegments[PipelineBase.ImageSetIndex])
{
if (segment.Type == ResourceType.Image)
{
for (int i = 0; i < segment.Count; i++)
{
ref var image = ref _imageRefs[segment.Binding + i];
image.Storage?.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, image.Stage.ConvertToPipelineStageFlags());
}
}
}
}
public void AdvancePdSequence()
{
if (++_pdSequence == 0)
{
_pdSequence = 1;
}
}
public void SetProgram(CommandBufferScoped cbs, ShaderCollection program, bool isBound)
{
if (!program.HasSameLayout(_program))
{
// When the pipeline layout changes, push descriptor bindings are invalidated.
AdvancePdSequence();
if (_gd.IsNvidiaPreTuring && !program.UsePushDescriptors && _program?.UsePushDescriptors == true && isBound)
{
// On older nvidia GPUs, we need to clear out the active push descriptor bindings when switching
// to normal descriptors. Keeping them bound can prevent buffers from binding properly in future.
ClearAndBindUniformBufferPd(cbs);
}
}
_program = program;
_updateDescriptorCacheCbIndex = true;
_dirty = DirtyFlags.All;
}
public void SetImage(int binding, ITexture image, Format imageFormat)
public void SetImage(
CommandBufferScoped cbs,
ShaderStage stage,
int binding,
ITexture image,
Format imageFormat)
{
if (image is TextureBuffer imageBuffer)
{
@ -239,11 +327,13 @@ namespace Ryujinx.Graphics.Vulkan
}
else if (image is TextureView view)
{
_imageRefs[binding] = view.GetView(imageFormat).GetIdentityImageView();
view.Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
_imageRefs[binding] = new(stage, view.Storage, view.GetView(imageFormat).GetIdentityImageView());
}
else
{
_imageRefs[binding] = null;
_imageRefs[binding] = default;
_bufferImageRefs[binding] = null;
_bufferImageFormats[binding] = default;
}
@ -253,7 +343,7 @@ namespace Ryujinx.Graphics.Vulkan
public void SetImage(int binding, Auto<DisposableImageView> image)
{
_imageRefs[binding] = image;
_imageRefs[binding] = new(ShaderStage.Compute, null, image);
SignalDirty(DirtyFlags.Image);
}
@ -338,15 +428,13 @@ namespace Ryujinx.Graphics.Vulkan
}
else if (texture is TextureView view)
{
view.Storage.InsertWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
view.Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
_textureRefs[binding] = view.GetImageView();
_samplerRefs[binding] = ((SamplerHolder)sampler)?.GetSampler();
_textureRefs[binding] = new(stage, view.Storage, view.GetImageView(), ((SamplerHolder)sampler)?.GetSampler());
}
else
{
_textureRefs[binding] = null;
_samplerRefs[binding] = null;
_textureRefs[binding] = default;
_bufferTextureRefs[binding] = null;
}
@ -362,10 +450,9 @@ namespace Ryujinx.Graphics.Vulkan
{
if (texture is TextureView view)
{
view.Storage.InsertWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
view.Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
_textureRefs[binding] = view.GetIdentityImageView();
_samplerRefs[binding] = ((SamplerHolder)sampler)?.GetSampler();
_textureRefs[binding] = new(stage, view.Storage, view.GetIdentityImageView(), ((SamplerHolder)sampler)?.GetSampler());
SignalDirty(DirtyFlags.Texture);
}
@ -402,6 +489,7 @@ namespace Ryujinx.Graphics.Vulkan
if (!currentBufferRef.Equals(newRef) || currentInfo.Range != info.Range)
{
_uniformSet.Clear(index);
_uniformSetPd[index] = 0;
currentInfo = info;
currentBufferRef = newRef;
@ -579,9 +667,10 @@ namespace Ryujinx.Graphics.Vulkan
for (int i = 0; i < count; i++)
{
ref var texture = ref textures[i];
ref var refs = ref _textureRefs[binding + i];
texture.ImageView = _textureRefs[binding + i]?.Get(cbs).Value ?? default;
texture.Sampler = _samplerRefs[binding + i]?.Get(cbs).Value ?? default;
texture.ImageView = refs.View?.Get(cbs).Value ?? default;
texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default;
if (texture.ImageView.Handle == 0)
{
@ -616,7 +705,7 @@ namespace Ryujinx.Graphics.Vulkan
for (int i = 0; i < count; i++)
{
images[i].ImageView = _imageRefs[binding + i]?.Get(cbs).Value ?? default;
images[i].ImageView = _imageRefs[binding + i].View?.Get(cbs).Value ?? default;
}
tu.Push<DescriptorImageInfo>(images[..count]);
@ -671,15 +760,19 @@ namespace Ryujinx.Graphics.Vulkan
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void UpdateAndBindUniformBufferPd(CommandBufferScoped cbs, PipelineBindPoint pbp)
{
int sequence = _pdSequence;
var bindingSegments = _program.BindingSegments[PipelineBase.UniformSetIndex];
var dummyBuffer = _dummyBuffer?.GetBuffer();
long updatedBindings = 0;
DescriptorSetTemplateWriter writer = _templateUpdater.Begin(32 * Unsafe.SizeOf<DescriptorBufferInfo>());
foreach (ResourceBindingSegment segment in bindingSegments)
{
int binding = segment.Binding;
int count = segment.Count;
bool doUpdate = false;
ReadOnlySpan<DescriptorBufferInfo> uniformBuffers = _uniformBuffers;
for (int i = 0; i < count; i++)
{
@ -688,17 +781,58 @@ namespace Ryujinx.Graphics.Vulkan
if (_uniformSet.Set(index))
{
ref BufferRef buffer = ref _uniformBufferRefs[index];
UpdateBuffer(cbs, ref _uniformBuffers[index], ref buffer, dummyBuffer, true);
doUpdate = true;
bool mirrored = UpdateBuffer(cbs, ref _uniformBuffers[index], ref buffer, dummyBuffer, true);
_uniformMirrored.Set(index, mirrored);
}
if (_uniformSetPd[index] != sequence)
{
// Need to set this push descriptor (even if the buffer binding has not changed)
_uniformSetPd[index] = sequence;
updatedBindings |= 1L << index;
writer.Push(MemoryMarshal.CreateReadOnlySpan(ref _uniformBuffers[index], 1));
}
}
}
if (doUpdate)
if (updatedBindings > 0)
{
DescriptorSetTemplate template = _program.GetPushDescriptorTemplate(updatedBindings);
_templateUpdater.CommitPushDescriptor(_gd, cbs, template, _program.PipelineLayout);
}
}
private void ClearAndBindUniformBufferPd(CommandBufferScoped cbs)
{
var bindingSegments = _program.BindingSegments[PipelineBase.UniformSetIndex];
long updatedBindings = 0;
DescriptorSetTemplateWriter writer = _templateUpdater.Begin(32 * Unsafe.SizeOf<DescriptorBufferInfo>());
foreach (ResourceBindingSegment segment in bindingSegments)
{
int binding = segment.Binding;
int count = segment.Count;
for (int i = 0; i < count; i++)
{
ReadOnlySpan<DescriptorBufferInfo> uniformBuffers = _uniformBuffers;
UpdateBuffers(cbs, pbp, binding, uniformBuffers.Slice(binding, count), DescriptorType.UniformBuffer);
int index = binding + i;
updatedBindings |= 1L << index;
var bufferInfo = new DescriptorBufferInfo();
writer.Push(MemoryMarshal.CreateReadOnlySpan(ref bufferInfo, 1));
}
}
if (updatedBindings > 0)
{
DescriptorSetTemplate template = _program.GetPushDescriptorTemplate(updatedBindings);
_templateUpdater.CommitPushDescriptor(_gd, cbs, template, _program.PipelineLayout);
}
}
private void Initialize(CommandBufferScoped cbs, int setIndex, DescriptorSetCollection dsc)
@ -724,6 +858,7 @@ namespace Ryujinx.Graphics.Vulkan
_uniformSet.Clear();
_storageSet.Clear();
AdvancePdSequence();
}
private static void SwapBuffer(BufferRef[] list, Auto<DisposableBuffer> from, Auto<DisposableBuffer> to)

View File

@ -154,7 +154,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
int dispatchY = (height + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, buffer.Range) });
_pipeline.SetImage(0, _intermediaryTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
_pipeline.SetImage(ShaderStage.Compute, 0, _intermediaryTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
_pipeline.ComputeBarrier();

View File

@ -75,7 +75,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
var dispatchX = BitUtils.DivRoundUp(view.Width, IPostProcessingEffect.LocalGroupSize);
var dispatchY = BitUtils.DivRoundUp(view.Height, IPostProcessingEffect.LocalGroupSize);
_pipeline.SetImage(0, _texture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
_pipeline.SetImage(ShaderStage.Compute, 0, _texture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
_pipeline.ComputeBarrier();

View File

@ -219,7 +219,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
buffer.Holder.SetDataUnchecked(buffer.Offset, resolutionBuffer);
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, buffer.Range) });
_pipeline.SetImage(0, _edgeOutputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
_pipeline.SetImage(ShaderStage.Compute, 0, _edgeOutputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
_pipeline.ComputeBarrier();
@ -229,7 +229,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, _edgeOutputTexture, _samplerLinear);
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 3, _areaTexture, _samplerLinear);
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 4, _searchTexture, _samplerLinear);
_pipeline.SetImage(0, _blendOutputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
_pipeline.SetImage(ShaderStage.Compute, 0, _blendOutputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
_pipeline.ComputeBarrier();
@ -238,7 +238,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
_pipeline.Specialize(_specConstants);
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 3, _blendOutputTexture, _samplerLinear);
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, view, _samplerLinear);
_pipeline.SetImage(0, _outputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
_pipeline.SetImage(ShaderStage.Compute, 0, _outputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
_pipeline.ComputeBarrier();

View File

@ -376,7 +376,7 @@ namespace Ryujinx.Graphics.Vulkan
{
return format switch
{
Format.D16Unorm or Format.D32Float => ImageAspectFlags.DepthBit,
Format.D16Unorm or Format.D32Float or Format.X8UintD24Unorm => ImageAspectFlags.DepthBit,
Format.S8Uint => ImageAspectFlags.StencilBit,
Format.D24UnormS8Uint or
Format.D32FloatS8Uint or
@ -389,7 +389,7 @@ namespace Ryujinx.Graphics.Vulkan
{
return format switch
{
Format.D16Unorm or Format.D32Float => ImageAspectFlags.DepthBit,
Format.D16Unorm or Format.D32Float or Format.X8UintD24Unorm => ImageAspectFlags.DepthBit,
Format.S8Uint => ImageAspectFlags.StencilBit,
Format.D24UnormS8Uint or
Format.D32FloatS8Uint or

View File

@ -220,7 +220,7 @@ namespace Ryujinx.Graphics.Vulkan
public static bool IsD24S8(Format format)
{
return format == Format.D24UnormS8Uint || format == Format.S8UintD24Unorm;
return format == Format.D24UnormS8Uint || format == Format.S8UintD24Unorm || format == Format.X8UintD24Unorm;
}
private static bool IsRGB16IntFloat(Format format)

View File

@ -67,6 +67,7 @@ namespace Ryujinx.Graphics.Vulkan
Add(Format.S8Uint, VkFormat.S8Uint);
Add(Format.D16Unorm, VkFormat.D16Unorm);
Add(Format.S8UintD24Unorm, VkFormat.D24UnormS8Uint);
Add(Format.X8UintD24Unorm, VkFormat.X8D24UnormPack32);
Add(Format.D32Float, VkFormat.D32Sfloat);
Add(Format.D24UnormS8Uint, VkFormat.D24UnormS8Uint);
Add(Format.D32FloatS8Uint, VkFormat.D32SfloatS8Uint);

View File

@ -243,41 +243,6 @@ namespace Ryujinx.Graphics.Vulkan
return new Auto<DisposableFramebuffer>(new DisposableFramebuffer(api, _device, framebuffer), null, _attachments);
}
public void UpdateModifications()
{
if (_colors != null)
{
for (int index = 0; index < _colors.Length; index++)
{
_colors[index].Storage.SetModification(
AccessFlags.ColorAttachmentWriteBit,
PipelineStageFlags.ColorAttachmentOutputBit);
}
}
_depthStencil?.Storage.SetModification(
AccessFlags.DepthStencilAttachmentWriteBit,
PipelineStageFlags.LateFragmentTestsBit);
}
public void InsertClearBarrier(CommandBufferScoped cbs, int index)
{
_colorsCanonical?[index]?.Storage?.InsertReadToWriteBarrier(
cbs,
AccessFlags.ColorAttachmentWriteBit,
PipelineStageFlags.ColorAttachmentOutputBit,
insideRenderPass: true);
}
public void InsertClearBarrierDS(CommandBufferScoped cbs)
{
_depthStencil?.Storage?.InsertReadToWriteBarrier(
cbs,
AccessFlags.DepthStencilAttachmentWriteBit,
PipelineStageFlags.LateFragmentTestsBit,
insideRenderPass: true);
}
public TextureView[] GetAttachmentViews()
{
var result = new TextureView[_attachments.Length];
@ -297,23 +262,20 @@ namespace Ryujinx.Graphics.Vulkan
return new RenderPassCacheKey(_depthStencil, _colorsCanonical);
}
public void InsertLoadOpBarriers(CommandBufferScoped cbs)
public void InsertLoadOpBarriers(VulkanRenderer gd, CommandBufferScoped cbs)
{
if (_colors != null)
{
foreach (var color in _colors)
{
// If Clear or DontCare were used, this would need to be write bit.
color.Storage?.InsertWriteToReadBarrier(cbs, AccessFlags.ColorAttachmentReadBit, PipelineStageFlags.ColorAttachmentOutputBit);
color.Storage?.SetModification(AccessFlags.ColorAttachmentWriteBit, PipelineStageFlags.ColorAttachmentOutputBit);
color.Storage?.QueueLoadOpBarrier(cbs, false);
}
}
if (_depthStencil != null)
{
_depthStencil.Storage?.InsertWriteToReadBarrier(cbs, AccessFlags.DepthStencilAttachmentReadBit, PipelineStageFlags.EarlyFragmentTestsBit);
_depthStencil.Storage?.SetModification(AccessFlags.DepthStencilAttachmentWriteBit, PipelineStageFlags.LateFragmentTestsBit);
}
_depthStencil?.Storage?.QueueLoadOpBarrier(cbs, true);
gd.Barriers.Flush(cbs.CommandBuffer, false, null);
}
public (Auto<DisposableRenderPass> renderPass, Auto<DisposableFramebuffer> framebuffer) GetPassAndFramebuffer(

View File

@ -34,6 +34,7 @@ namespace Ryujinx.Graphics.Vulkan
public readonly bool SupportsMultiView;
public readonly bool SupportsNullDescriptors;
public readonly bool SupportsPushDescriptors;
public readonly uint MaxPushDescriptors;
public readonly bool SupportsPrimitiveTopologyListRestart;
public readonly bool SupportsPrimitiveTopologyPatchListRestart;
public readonly bool SupportsTransformFeedback;
@ -71,6 +72,7 @@ namespace Ryujinx.Graphics.Vulkan
bool supportsMultiView,
bool supportsNullDescriptors,
bool supportsPushDescriptors,
uint maxPushDescriptors,
bool supportsPrimitiveTopologyListRestart,
bool supportsPrimitiveTopologyPatchListRestart,
bool supportsTransformFeedback,
@ -107,6 +109,7 @@ namespace Ryujinx.Graphics.Vulkan
SupportsMultiView = supportsMultiView;
SupportsNullDescriptors = supportsNullDescriptors;
SupportsPushDescriptors = supportsPushDescriptors;
MaxPushDescriptors = maxPushDescriptors;
SupportsPrimitiveTopologyListRestart = supportsPrimitiveTopologyListRestart;
SupportsPrimitiveTopologyPatchListRestart = supportsPrimitiveTopologyPatchListRestart;
SupportsTransformFeedback = supportsTransformFeedback;

View File

@ -1039,7 +1039,7 @@ namespace Ryujinx.Graphics.Vulkan
var dstView = Create2DLayerView(dst, dstLayer + z, dstLevel + l);
_pipeline.SetTextureAndSamplerIdentitySwizzle(ShaderStage.Compute, 0, srcView, null);
_pipeline.SetImage(0, dstView, dstFormat);
_pipeline.SetImage(ShaderStage.Compute, 0, dstView, dstFormat);
int dispatchX = (Math.Min(srcView.Info.Width, dstView.Info.Width) + 31) / 32;
int dispatchY = (Math.Min(srcView.Info.Height, dstView.Info.Height) + 31) / 32;
@ -1168,7 +1168,7 @@ namespace Ryujinx.Graphics.Vulkan
var dstView = Create2DLayerView(dst, dstLayer + z, 0);
_pipeline.SetTextureAndSamplerIdentitySwizzle(ShaderStage.Compute, 0, srcView, null);
_pipeline.SetImage(0, dstView, format);
_pipeline.SetImage(ShaderStage.Compute, 0, dstView, format);
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);

View File

@ -36,6 +36,7 @@ namespace Ryujinx.Graphics.Vulkan
private PipelineState _newState;
private bool _graphicsStateDirty;
private bool _computeStateDirty;
private bool _bindingBarriersDirty;
private PrimitiveTopology _topology;
private ulong _currentPipelineHandle;
@ -248,14 +249,14 @@ namespace Ryujinx.Graphics.Vulkan
CreateRenderPass();
}
Gd.Barriers.Flush(Cbs.CommandBuffer, RenderPassActive, EndRenderPassDelegate);
BeginRenderPass();
var clearValue = new ClearValue(new ClearColorValue(color.Red, color.Green, color.Blue, color.Alpha));
var attachment = new ClearAttachment(ImageAspectFlags.ColorBit, (uint)index, clearValue);
var clearRect = FramebufferParams.GetClearRect(ClearScissor, layer, layerCount);
FramebufferParams.InsertClearBarrier(Cbs, index);
Gd.Api.CmdClearAttachments(CommandBuffer, 1, &attachment, 1, &clearRect);
}
@ -286,13 +287,13 @@ namespace Ryujinx.Graphics.Vulkan
CreateRenderPass();
}
Gd.Barriers.Flush(Cbs.CommandBuffer, RenderPassActive, EndRenderPassDelegate);
BeginRenderPass();
var attachment = new ClearAttachment(flags, 0, clearValue);
var clearRect = FramebufferParams.GetClearRect(ClearScissor, layer, layerCount);
FramebufferParams.InsertClearBarrierDS(Cbs);
Gd.Api.CmdClearAttachments(CommandBuffer, 1, &attachment, 1, &clearRect);
}
@ -887,9 +888,9 @@ namespace Ryujinx.Graphics.Vulkan
SignalStateChange();
}
public void SetImage(int binding, ITexture image, Format imageFormat)
public void SetImage(ShaderStage stage, int binding, ITexture image, Format imageFormat)
{
_descriptorSetUpdater.SetImage(binding, image, imageFormat);
_descriptorSetUpdater.SetImage(Cbs, stage, binding, image, imageFormat);
}
public void SetImage(int binding, Auto<DisposableImageView> image)
@ -976,7 +977,8 @@ namespace Ryujinx.Graphics.Vulkan
_program = internalProgram;
_descriptorSetUpdater.SetProgram(internalProgram);
_descriptorSetUpdater.SetProgram(Cbs, internalProgram, _currentPipelineHandle != 0);
_bindingBarriersDirty = true;
_newState.PipelineLayout = internalProgram.PipelineLayout;
_newState.StagesCount = (uint)stages.Length;
@ -1066,7 +1068,6 @@ namespace Ryujinx.Graphics.Vulkan
private void SetRenderTargetsInternal(ITexture[] colors, ITexture depthStencil, bool filterWriteMasked)
{
CreateFramebuffer(colors, depthStencil, filterWriteMasked);
FramebufferParams?.UpdateModifications();
CreateRenderPass();
SignalStateChange();
SignalAttachmentChange();
@ -1520,8 +1521,18 @@ namespace Ryujinx.Graphics.Vulkan
CreatePipeline(PipelineBindPoint.Compute);
_computeStateDirty = false;
Pbp = PipelineBindPoint.Compute;
if (_bindingBarriersDirty)
{
// Stale barriers may have been activated by switching program. Emit any that are relevant.
_descriptorSetUpdater.InsertBindingBarriers(Cbs);
_bindingBarriersDirty = false;
}
}
Gd.Barriers.Flush(Cbs.CommandBuffer, RenderPassActive, EndRenderPassDelegate);
_descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, PipelineBindPoint.Compute);
}
@ -1575,8 +1586,18 @@ namespace Ryujinx.Graphics.Vulkan
_graphicsStateDirty = false;
Pbp = PipelineBindPoint.Graphics;
if (_bindingBarriersDirty)
{
// Stale barriers may have been activated by switching program. Emit any that are relevant.
_descriptorSetUpdater.InsertBindingBarriers(Cbs);
_bindingBarriersDirty = false;
}
}
Gd.Barriers.Flush(Cbs.CommandBuffer, RenderPassActive, EndRenderPassDelegate);
_descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, PipelineBindPoint.Graphics);
return true;
@ -1630,6 +1651,8 @@ namespace Ryujinx.Graphics.Vulkan
{
if (!RenderPassActive)
{
FramebufferParams.InsertLoadOpBarriers(Gd, Cbs);
var renderArea = new Rect2D(null, new Extent2D(FramebufferParams.Width, FramebufferParams.Height));
var clearValue = new ClearValue();

View File

@ -269,6 +269,7 @@ namespace Ryujinx.Graphics.Vulkan
PreloadCbs = null;
}
Gd.Barriers.Flush(Cbs.CommandBuffer, false, null);
CommandBuffer = (Cbs = Gd.CommandBufferPool.ReturnAndRent(Cbs)).CommandBuffer;
Gd.RegisterFlush();

View File

@ -31,6 +31,11 @@ namespace Ryujinx.Graphics.Vulkan
private int _dsLastCbIndex;
private int _dsLastSubmissionCount;
private readonly Dictionary<long, DescriptorSetTemplate> _pdTemplates;
private readonly ResourceDescriptorCollection _pdDescriptors;
private long _lastPdUsage;
private DescriptorSetTemplate _lastPdTemplate;
private PipelineLayoutCacheEntry(VulkanRenderer gd, Device device, int setsCount)
{
_gd = gd;
@ -72,6 +77,12 @@ namespace Ryujinx.Graphics.Vulkan
_consumedDescriptorsPerSet[setIndex] = count;
}
if (usePushDescriptors)
{
_pdDescriptors = setDescriptors[0];
_pdTemplates = new();
}
}
public void UpdateCommandBufferIndex(int commandBufferIndex)
@ -143,10 +154,39 @@ namespace Ryujinx.Graphics.Vulkan
return output[..count];
}
public DescriptorSetTemplate GetPushDescriptorTemplate(PipelineBindPoint pbp, long updateMask)
{
if (_lastPdUsage == updateMask && _lastPdTemplate != null)
{
// Most likely result is that it asks to update the same buffers.
return _lastPdTemplate;
}
if (!_pdTemplates.TryGetValue(updateMask, out DescriptorSetTemplate template))
{
template = new DescriptorSetTemplate(_gd, _device, _pdDescriptors, updateMask, this, pbp, 0);
_pdTemplates.Add(updateMask, template);
}
_lastPdUsage = updateMask;
_lastPdTemplate = template;
return template;
}
protected virtual unsafe void Dispose(bool disposing)
{
if (disposing)
{
if (_pdTemplates != null)
{
foreach (DescriptorSetTemplate template in _pdTemplates.Values)
{
template.Dispose();
}
}
for (int i = 0; i < _dsCache.Length; i++)
{
for (int j = 0; j < _dsCache[i].Length; j++)

View File

@ -108,18 +108,25 @@ namespace Ryujinx.Graphics.Vulkan
_shaders = internalShaders;
bool usePushDescriptors = !isMinimal && VulkanConfiguration.UsePushDescriptors && _gd.Capabilities.SupportsPushDescriptors;
bool usePushDescriptors = !isMinimal &&
VulkanConfiguration.UsePushDescriptors &&
_gd.Capabilities.SupportsPushDescriptors &&
!IsCompute &&
CanUsePushDescriptors(gd, resourceLayout, IsCompute);
_plce = gd.PipelineLayoutCache.GetOrCreate(gd, device, resourceLayout.Sets, usePushDescriptors);
ReadOnlyCollection<ResourceDescriptorCollection> sets = usePushDescriptors ?
BuildPushDescriptorSets(gd, resourceLayout.Sets) : resourceLayout.Sets;
_plce = gd.PipelineLayoutCache.GetOrCreate(gd, device, sets, usePushDescriptors);
HasMinimalLayout = isMinimal;
UsePushDescriptors = usePushDescriptors;
Stages = stages;
ClearSegments = BuildClearSegments(resourceLayout.Sets);
ClearSegments = BuildClearSegments(sets);
BindingSegments = BuildBindingSegments(resourceLayout.SetUsages);
Templates = BuildTemplates();
Templates = BuildTemplates(usePushDescriptors);
_compileTask = Task.CompletedTask;
_firstBackgroundUse = false;
@ -139,6 +146,76 @@ namespace Ryujinx.Graphics.Vulkan
_firstBackgroundUse = !fromCache;
}
private static bool CanUsePushDescriptors(VulkanRenderer gd, ResourceLayout layout, bool isCompute)
{
// If binding 3 is immediately used, use an alternate set of reserved bindings.
ReadOnlyCollection<ResourceUsage> uniformUsage = layout.SetUsages[0].Usages;
bool hasBinding3 = uniformUsage.Any(x => x.Binding == 3);
int[] reserved = isCompute ? Array.Empty<int>() : gd.GetPushDescriptorReservedBindings(hasBinding3);
// Can't use any of the reserved usages.
for (int i = 0; i < uniformUsage.Count; i++)
{
var binding = uniformUsage[i].Binding;
if (reserved.Contains(binding) ||
binding >= Constants.MaxPushDescriptorBinding ||
binding >= gd.Capabilities.MaxPushDescriptors + reserved.Count(id => id < binding))
{
return false;
}
}
return true;
}
private static ReadOnlyCollection<ResourceDescriptorCollection> BuildPushDescriptorSets(
VulkanRenderer gd,
ReadOnlyCollection<ResourceDescriptorCollection> sets)
{
// The reserved bindings were selected when determining if push descriptors could be used.
int[] reserved = gd.GetPushDescriptorReservedBindings(false);
var result = new ResourceDescriptorCollection[sets.Count];
for (int i = 0; i < sets.Count; i++)
{
if (i == 0)
{
// Push descriptors apply here. Remove reserved bindings.
ResourceDescriptorCollection original = sets[i];
var pdUniforms = new ResourceDescriptor[original.Descriptors.Count];
int j = 0;
foreach (ResourceDescriptor descriptor in original.Descriptors)
{
if (reserved.Contains(descriptor.Binding))
{
// If the binding is reserved, set its descriptor count to 0.
pdUniforms[j++] = new ResourceDescriptor(
descriptor.Binding,
0,
descriptor.Type,
descriptor.Stages);
}
else
{
pdUniforms[j++] = descriptor;
}
}
result[i] = new ResourceDescriptorCollection(new(pdUniforms));
}
else
{
result[i] = sets[i];
}
}
return new(result);
}
private static ResourceBindingSegment[][] BuildClearSegments(ReadOnlyCollection<ResourceDescriptorCollection> sets)
{
ResourceBindingSegment[][] segments = new ResourceBindingSegment[sets.Count][];
@ -243,12 +320,18 @@ namespace Ryujinx.Graphics.Vulkan
return segments;
}
private DescriptorSetTemplate[] BuildTemplates()
private DescriptorSetTemplate[] BuildTemplates(bool usePushDescriptors)
{
var templates = new DescriptorSetTemplate[BindingSegments.Length];
for (int setIndex = 0; setIndex < BindingSegments.Length; setIndex++)
{
if (usePushDescriptors && setIndex == 0)
{
// Push descriptors get updated using templates owned by the pipeline layout.
continue;
}
ResourceBindingSegment[] segments = BindingSegments[setIndex];
if (segments != null && segments.Length > 0)
@ -433,6 +516,11 @@ namespace Ryujinx.Graphics.Vulkan
return null;
}
public DescriptorSetTemplate GetPushDescriptorTemplate(long updateMask)
{
return _plce.GetPushDescriptorTemplate(IsCompute ? PipelineBindPoint.Compute : PipelineBindPoint.Graphics, updateMask);
}
public void AddComputePipeline(ref SpecData key, Auto<DisposablePipeline> pipeline)
{
(_computePipelineCache ??= new()).Add(ref key, pipeline);
@ -493,6 +581,11 @@ namespace Ryujinx.Graphics.Vulkan
return _plce.GetNewDescriptorSetCollection(setIndex, out isNew);
}
public bool HasSameLayout(ShaderCollection other)
{
return other != null && _plce == other._plce;
}
protected virtual void Dispose(bool disposing)
{
if (disposing)

View File

@ -154,9 +154,8 @@ namespace Ryujinx.Graphics.Vulkan
{
Format.S8Uint => Format.R8Unorm,
Format.D16Unorm => Format.R16Unorm,
Format.S8UintD24Unorm => Format.R8G8B8A8Unorm,
Format.D24UnormS8Uint or Format.S8UintD24Unorm or Format.X8UintD24Unorm => Format.R8G8B8A8Unorm,
Format.D32Float => Format.R32Float,
Format.D24UnormS8Uint => Format.R8G8B8A8Unorm,
Format.D32FloatS8Uint => Format.R32G32Float,
_ => throw new ArgumentException($"\"{format}\" is not a supported depth or stencil format."),
};
@ -434,99 +433,65 @@ namespace Ryujinx.Graphics.Vulkan
return FormatCapabilities.IsD24S8(Info.Format) && VkFormat == VkFormat.D32SfloatS8Uint;
}
public void SetModification(AccessFlags accessFlags, PipelineStageFlags stage)
public void QueueLoadOpBarrier(CommandBufferScoped cbs, bool depthStencil)
{
_lastModificationAccess = accessFlags;
_lastModificationStage = stage;
}
PipelineStageFlags srcStageFlags = _lastReadStage | _lastModificationStage;
PipelineStageFlags dstStageFlags = depthStencil ?
PipelineStageFlags.EarlyFragmentTestsBit | PipelineStageFlags.LateFragmentTestsBit :
PipelineStageFlags.ColorAttachmentOutputBit;
public void InsertReadToWriteBarrier(CommandBufferScoped cbs, AccessFlags dstAccessFlags, PipelineStageFlags dstStageFlags, bool insideRenderPass)
{
var lastReadStage = _lastReadStage;
AccessFlags srcAccessFlags = _lastModificationAccess | _lastReadAccess;
AccessFlags dstAccessFlags = depthStencil ?
AccessFlags.DepthStencilAttachmentWriteBit | AccessFlags.DepthStencilAttachmentReadBit :
AccessFlags.ColorAttachmentWriteBit | AccessFlags.ColorAttachmentReadBit;
if (insideRenderPass)
if (srcAccessFlags != AccessFlags.None)
{
// We can't have barrier from compute inside a render pass,
// as it is invalid to specify compute in the subpass dependency stage mask.
ImageAspectFlags aspectFlags = Info.Format.ConvertAspectFlags();
ImageMemoryBarrier barrier = TextureView.GetImageBarrier(
_imageAuto.Get(cbs).Value,
srcAccessFlags,
dstAccessFlags,
aspectFlags,
0,
0,
_info.GetLayers(),
_info.Levels);
lastReadStage &= ~PipelineStageFlags.ComputeShaderBit;
}
_gd.Barriers.QueueBarrier(barrier, srcStageFlags, dstStageFlags);
if (lastReadStage != PipelineStageFlags.None)
{
// This would result in a validation error, but is
// required on MoltenVK as the generic barrier results in
// severe texture flickering in some scenarios.
if (_gd.IsMoltenVk)
{
ImageAspectFlags aspectFlags = Info.Format.ConvertAspectFlags();
TextureView.InsertImageBarrier(
_gd.Api,
cbs.CommandBuffer,
_imageAuto.Get(cbs).Value,
_lastReadAccess,
dstAccessFlags,
_lastReadStage,
dstStageFlags,
aspectFlags,
0,
0,
_info.GetLayers(),
_info.Levels);
}
else
{
TextureView.InsertMemoryBarrier(
_gd.Api,
cbs.CommandBuffer,
_lastReadAccess,
dstAccessFlags,
lastReadStage,
dstStageFlags);
}
_lastReadAccess = AccessFlags.None;
_lastReadStage = PipelineStageFlags.None;
_lastReadAccess = AccessFlags.None;
}
_lastModificationStage = depthStencil ?
PipelineStageFlags.LateFragmentTestsBit :
PipelineStageFlags.ColorAttachmentOutputBit;
_lastModificationAccess = depthStencil ?
AccessFlags.DepthStencilAttachmentWriteBit :
AccessFlags.ColorAttachmentWriteBit;
}
public void InsertWriteToReadBarrier(CommandBufferScoped cbs, AccessFlags dstAccessFlags, PipelineStageFlags dstStageFlags)
public void QueueWriteToReadBarrier(CommandBufferScoped cbs, AccessFlags dstAccessFlags, PipelineStageFlags dstStageFlags)
{
_lastReadAccess |= dstAccessFlags;
_lastReadStage |= dstStageFlags;
if (_lastModificationAccess != AccessFlags.None)
{
// This would result in a validation error, but is
// required on MoltenVK as the generic barrier results in
// severe texture flickering in some scenarios.
if (_gd.IsMoltenVk)
{
ImageAspectFlags aspectFlags = Info.Format.ConvertAspectFlags();
TextureView.InsertImageBarrier(
_gd.Api,
cbs.CommandBuffer,
_imageAuto.Get(cbs).Value,
_lastModificationAccess,
dstAccessFlags,
_lastModificationStage,
dstStageFlags,
aspectFlags,
0,
0,
_info.GetLayers(),
_info.Levels);
}
else
{
TextureView.InsertMemoryBarrier(
_gd.Api,
cbs.CommandBuffer,
_lastModificationAccess,
dstAccessFlags,
_lastModificationStage,
dstStageFlags);
}
ImageAspectFlags aspectFlags = Info.Format.ConvertAspectFlags();
ImageMemoryBarrier barrier = TextureView.GetImageBarrier(
_imageAuto.Get(cbs).Value,
_lastModificationAccess,
dstAccessFlags,
aspectFlags,
0,
0,
_info.GetLayers(),
_info.Levels);
_gd.Barriers.QueueBarrier(barrier, _lastModificationStage, dstStageFlags);
_lastModificationAccess = AccessFlags.None;
}

View File

@ -497,6 +497,30 @@ namespace Ryujinx.Graphics.Vulkan
null);
}
public static ImageMemoryBarrier GetImageBarrier(
Image image,
AccessFlags srcAccessMask,
AccessFlags dstAccessMask,
ImageAspectFlags aspectFlags,
int firstLayer,
int firstLevel,
int layers,
int levels)
{
return new()
{
SType = StructureType.ImageMemoryBarrier,
SrcAccessMask = srcAccessMask,
DstAccessMask = dstAccessMask,
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
Image = image,
OldLayout = ImageLayout.General,
NewLayout = ImageLayout.General,
SubresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, (uint)levels, (uint)firstLayer, (uint)layers),
};
}
public static unsafe void InsertImageBarrier(
Vk api,
CommandBuffer commandBuffer,
@ -511,18 +535,15 @@ namespace Ryujinx.Graphics.Vulkan
int layers,
int levels)
{
ImageMemoryBarrier memoryBarrier = new()
{
SType = StructureType.ImageMemoryBarrier,
SrcAccessMask = srcAccessMask,
DstAccessMask = dstAccessMask,
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
Image = image,
OldLayout = ImageLayout.General,
NewLayout = ImageLayout.General,
SubresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, (uint)levels, (uint)firstLayer, (uint)layers),
};
ImageMemoryBarrier memoryBarrier = GetImageBarrier(
image,
srcAccessMask,
dstAccessMask,
aspectFlags,
firstLayer,
firstLevel,
layers,
levels);
api.CmdPipelineBarrier(
commandBuffer,
@ -839,7 +860,9 @@ namespace Ryujinx.Graphics.Vulkan
for (int level = 0; level < levels; level++)
{
int mipSize = GetBufferDataLength(Info.GetMipSize2D(dstLevel + level) * dstLayers);
int mipSize = GetBufferDataLength(is3D && !singleSlice
? Info.GetMipSize(dstLevel + level)
: Info.GetMipSize2D(dstLevel + level) * dstLayers);
int endOffset = offset + mipSize;

View File

@ -20,6 +20,9 @@ namespace Ryujinx.Graphics.Vulkan
[GeneratedRegex("Radeon (((HD|R(5|7|9|X)) )?((M?[2-6]\\d{2}(\\D|$))|([7-8]\\d{3}(\\D|$))|Fury|Nano))|(Pro Duo)")]
public static partial Regex AmdGcnRegex();
[GeneratedRegex("NVIDIA GeForce (R|G)?TX? (\\d{3}\\d?)M?")]
public static partial Regex NvidiaConsumerClassRegex();
public static Vendor FromId(uint id)
{
return id switch

View File

@ -4,7 +4,7 @@ namespace Ryujinx.Graphics.Vulkan
{
public const bool UseFastBufferUpdates = true;
public const bool UseUnsafeBlit = true;
public const bool UsePushDescriptors = false;
public const bool UsePushDescriptors = true;
public const bool ForceD24S8Unsupported = false;
public const bool ForceRGB16IntFloatUnsupported = false;

View File

@ -486,20 +486,6 @@ namespace Ryujinx.Graphics.Vulkan
pExtendedFeatures = &featuresFragmentShaderInterlock;
}
PhysicalDeviceSubgroupSizeControlFeaturesEXT featuresSubgroupSizeControl;
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_subgroup_size_control"))
{
featuresSubgroupSizeControl = new PhysicalDeviceSubgroupSizeControlFeaturesEXT
{
SType = StructureType.PhysicalDeviceSubgroupSizeControlFeaturesExt,
PNext = pExtendedFeatures,
SubgroupSizeControl = true,
};
pExtendedFeatures = &featuresSubgroupSizeControl;
}
PhysicalDeviceCustomBorderColorFeaturesEXT featuresCustomBorderColor;
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_custom_border_color") &&

View File

@ -68,6 +68,8 @@ namespace Ryujinx.Graphics.Vulkan
internal HelperShader HelperShader { get; private set; }
internal PipelineFull PipelineInternal => _pipeline;
internal BarrierBatch Barriers { get; private set; }
public IPipeline Pipeline => _pipeline;
public IWindow Window => _window;
@ -76,10 +78,15 @@ namespace Ryujinx.Graphics.Vulkan
private readonly Func<string[]> _getRequiredExtensions;
private readonly string _preferredGpuId;
private int[] _pdReservedBindings;
private readonly static int[] _pdReservedBindingsNvn = { 3, 18, 21, 36, 30 };
private readonly static int[] _pdReservedBindingsOgl = { 17, 18, 34, 35, 36 };
internal Vendor Vendor { get; private set; }
internal bool IsAmdWindows { get; private set; }
internal bool IsIntelWindows { get; private set; }
internal bool IsAmdGcn { get; private set; }
internal bool IsNvidiaPreTuring { get; private set; }
internal bool IsMoltenVk { get; private set; }
internal bool IsTBDR { get; private set; }
internal bool IsSharedMemory { get; private set; }
@ -191,6 +198,19 @@ namespace Ryujinx.Graphics.Vulkan
SType = StructureType.PhysicalDevicePortabilitySubsetPropertiesKhr,
};
bool supportsPushDescriptors = _physicalDevice.IsDeviceExtensionPresent(KhrPushDescriptor.ExtensionName);
PhysicalDevicePushDescriptorPropertiesKHR propertiesPushDescriptor = new PhysicalDevicePushDescriptorPropertiesKHR()
{
SType = StructureType.PhysicalDevicePushDescriptorPropertiesKhr
};
if (supportsPushDescriptors)
{
propertiesPushDescriptor.PNext = properties2.PNext;
properties2.PNext = &propertiesPushDescriptor;
}
PhysicalDeviceFeatures2 features2 = new()
{
SType = StructureType.PhysicalDeviceFeatures2,
@ -320,7 +340,8 @@ namespace Ryujinx.Graphics.Vulkan
_physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState.ExtensionName),
features2.Features.MultiViewport && !(IsMoltenVk && Vendor == Vendor.Amd), // Workaround for AMD on MoltenVK issue
featuresRobustness2.NullDescriptor || IsMoltenVk,
_physicalDevice.IsDeviceExtensionPresent(KhrPushDescriptor.ExtensionName),
supportsPushDescriptors && !IsMoltenVk,
propertiesPushDescriptor.MaxPushDescriptors,
featuresPrimitiveTopologyListRestart.PrimitiveTopologyListRestart,
featuresPrimitiveTopologyListRestart.PrimitiveTopologyPatchListRestart,
supportsTransformFeedback,
@ -362,6 +383,8 @@ namespace Ryujinx.Graphics.Vulkan
HelperShader = new HelperShader(this, _device);
Barriers = new BarrierBatch(this);
_counters = new Counters(this, _device, _pipeline);
}
@ -400,6 +423,25 @@ namespace Ryujinx.Graphics.Vulkan
_initialized = true;
}
internal int[] GetPushDescriptorReservedBindings(bool isOgl)
{
// The first call of this method determines what push descriptor layout is used for all shaders on this renderer.
// This is chosen to minimize shaders that can't fit their uniforms on the device's max number of push descriptors.
if (_pdReservedBindings == null)
{
if (Capabilities.MaxPushDescriptors <= Constants.MaxUniformBuffersPerStage * 2)
{
_pdReservedBindings = isOgl ? _pdReservedBindingsOgl : _pdReservedBindingsNvn;
}
else
{
_pdReservedBindings = Array.Empty<int>();
}
}
return _pdReservedBindings;
}
public BufferHandle CreateBuffer(int size, BufferAccess access)
{
return BufferManager.CreateWithHandle(this, size, access.HasFlag(BufferAccess.SparseCompatible), access.Convert(), default, access == BufferAccess.Stream);
@ -716,6 +758,20 @@ namespace Ryujinx.Graphics.Vulkan
IsAmdGcn = !IsMoltenVk && Vendor == Vendor.Amd && VendorUtils.AmdGcnRegex().IsMatch(GpuRenderer);
if (Vendor == Vendor.Nvidia)
{
var match = VendorUtils.NvidiaConsumerClassRegex().Match(GpuRenderer);
if (match != null && int.TryParse(match.Groups[2].Value, out int gpuNumber))
{
IsNvidiaPreTuring = gpuNumber < 2000;
}
else if (GpuDriver.Contains("TITAN") && !GpuDriver.Contains("RTX"))
{
IsNvidiaPreTuring = true;
}
}
Logger.Notice.Print(LogClass.Gpu, $"{GpuVendor} {GpuRenderer} ({GpuVersion})");
}
@ -862,6 +918,7 @@ namespace Ryujinx.Graphics.Vulkan
BufferManager.Dispose();
DescriptorSetManager.Dispose();
PipelineLayoutCache.Dispose();
Barriers.Dispose();
MemoryAllocator.Dispose();

View File

@ -7,7 +7,7 @@ using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.HLE.Ui;
using Ryujinx.HLE.UI;
using System;
namespace Ryujinx.HLE
@ -63,7 +63,7 @@ namespace Ryujinx.HLE
/// The handler for various UI related operations needed outside of HLE.
/// </summary>
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
internal readonly IHostUiHandler HostUiHandler;
internal readonly IHostUIHandler HostUIHandler;
/// <summary>
/// Control the memory configuration used by the emulation context.
@ -177,7 +177,7 @@ namespace Ryujinx.HLE
IRenderer gpuRenderer,
IHardwareDeviceDriver audioDeviceDriver,
MemoryConfiguration memoryConfiguration,
IHostUiHandler hostUiHandler,
IHostUIHandler hostUIHandler,
SystemLanguage systemLanguage,
RegionCode region,
bool enableVsync,
@ -204,7 +204,7 @@ namespace Ryujinx.HLE
GpuRenderer = gpuRenderer;
AudioDeviceDriver = audioDeviceDriver;
MemoryConfiguration = memoryConfiguration;
HostUiHandler = hostUiHandler;
HostUIHandler = hostUIHandler;
SystemLanguage = systemLanguage;
Region = region;
EnableVsync = enableVsync;

View File

@ -86,7 +86,7 @@ namespace Ryujinx.HLE.HOS.Applets
PlayerIndex primaryIndex;
while (!_system.Device.Hid.Npads.Validate(playerMin, playerMax, (ControllerType)privateArg.NpadStyleSet, out configuredCount, out primaryIndex))
{
ControllerAppletUiArgs uiArgs = new()
ControllerAppletUIArgs uiArgs = new()
{
PlayerCountMin = playerMin,
PlayerCountMax = playerMax,
@ -95,7 +95,7 @@ namespace Ryujinx.HLE.HOS.Applets
IsDocked = _system.State.DockedMode,
};
if (!_system.Device.UiHandler.DisplayMessageDialog(uiArgs))
if (!_system.Device.UIHandler.DisplayMessageDialog(uiArgs))
{
break;
}

View File

@ -3,7 +3,7 @@ using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.Applets
{
public struct ControllerAppletUiArgs
public struct ControllerAppletUIArgs
{
public int PlayerCountMin;
public int PlayerCountMax;

View File

@ -166,13 +166,13 @@ namespace Ryujinx.HLE.HOS.Applets.Error
string[] buttons = GetButtonsText(module, description, "DlgBtn");
bool showDetails = _horizon.Device.UiHandler.DisplayErrorAppletDialog($"Error Code: {module}-{description:0000}", "\n" + message, buttons);
bool showDetails = _horizon.Device.UIHandler.DisplayErrorAppletDialog($"Error Code: {module}-{description:0000}", "\n" + message, buttons);
if (showDetails)
{
message = GetMessageText(module, description, "FlvMsg");
buttons = GetButtonsText(module, description, "FlvBtn");
_horizon.Device.UiHandler.DisplayErrorAppletDialog($"Details: {module}-{description:0000}", "\n" + message, buttons);
_horizon.Device.UIHandler.DisplayErrorAppletDialog($"Details: {module}-{description:0000}", "\n" + message, buttons);
}
}
@ -200,12 +200,12 @@ namespace Ryujinx.HLE.HOS.Applets.Error
buttons.Add("OK");
bool showDetails = _horizon.Device.UiHandler.DisplayErrorAppletDialog($"Error Number: {applicationErrorArg.ErrorNumber}", "\n" + messageText, buttons.ToArray());
bool showDetails = _horizon.Device.UIHandler.DisplayErrorAppletDialog($"Error Number: {applicationErrorArg.ErrorNumber}", "\n" + messageText, buttons.ToArray());
if (showDetails)
{
buttons.RemoveAt(0);
_horizon.Device.UiHandler.DisplayErrorAppletDialog($"Error Number: {applicationErrorArg.ErrorNumber} (Details)", "\n" + detailsText, buttons.ToArray());
_horizon.Device.UIHandler.DisplayErrorAppletDialog($"Error Number: {applicationErrorArg.ErrorNumber} (Details)", "\n" + detailsText, buttons.ToArray());
}
}

Some files were not shown because too many files have changed in this diff Show More