Compare commits

..

16 Commits

Author SHA1 Message Date
sharmander
a37e2d6e44 Resolve an issue where changes to the main window's positioning could cause the application to crash if a modal was dismissed beforehand. (#6223) 2024-02-06 18:05:32 +01:00
dependabot[bot]
25123232bd nuget: bump SPB from 0.0.4-build28 to 0.0.4-build32 (#6235)
Bumps [SPB](https://github.com/marysaka/SPB) from 0.0.4-build28 to 0.0.4-build32.
- [Release notes](https://github.com/marysaka/SPB/releases)
- [Commits](https://github.com/marysaka/SPB/commits)

---
updated-dependencies:
- dependency-name: SPB
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-05 15:39:45 +01:00
gdkchan
8927e0669f Revert change to skip flush when range size is 0 (#6254) 2024-02-04 18:12:12 -03:00
gdkchan
bbed3b9926 Fix depth compare value for TLD4S shader instruction with offset (#6253)
* Fix depth compare value for TLD4S shader instruction with offset

* Shader cache version bump
2024-02-04 20:58:17 +01:00
gdkchan
24c8b0edc0 Remove component operand for texture gather with depth compare (#6247) 2024-02-04 11:10:45 +01:00
gdkchan
e5066449a5 Limit remote closed session removal to SM service (#6248) 2024-02-03 19:40:09 +01:00
gdkchan
d704bcd93b Ensure SM service won't listen to closed sessions (#6246)
* Ensure SM service won't listen to closed sessions

* PR feedback
2024-02-02 20:56:51 -03:00
riperiperi
c94f0fbb83 Vulkan: Add Render Pass / Framebuffer Cache (#6182)
* Vulkan: Add Render Pass / Framebuffer Cache

Cache is owned by each texture view.

- Window's way of getting framebuffer cache for swapchain images is really messy - it creates a TextureView out of just a vk image view, with invalid info and no storage.

* Clear up limited use of alternate TextureView constructor

* Formatting and messages

* More formatting and messages

I apologize for `_colorsCanonical[index]?.Storage?.InsertReadToWriteBarrier`, the compiler made me do it

* Self review, change GetFramebuffer to GetPassAndFramebuffer

* Avoid allocations on Remove for HashTableSlim

* Member can be readonly

* Generate texture create info for swapchain images

* Improve hashcode

* Remove format, samples, size and isDepthStencil when possible

Tested in a number of games, seems fine.

* Removed load op barriers

These can be introduced later.

* Reintroduce UpdateModifications

Technically meant to be replaced by load op stuff.
2024-01-31 23:49:50 +01:00
dependabot[bot]
d1b30fbe08 nuget: bump Microsoft.IdentityModel.JsonWebTokens from 7.2.0 to 7.3.0 (#6227)
Bumps [Microsoft.IdentityModel.JsonWebTokens](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) from 7.2.0 to 7.3.0.
- [Release notes](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases)
- [Changelog](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/7.2.0...7.3.0)

---
updated-dependencies:
- dependency-name: Microsoft.IdentityModel.JsonWebTokens
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-31 23:46:48 +01:00
TSRBerry
4505a7f162 Fix opening the wrong log directory (#6220) 2024-01-30 17:52:45 +01:00
gdkchan
ccbbaddbcb Fix exception when trying to read output pointer buffer size (#6221)
* Fix exception when trying to read output pointer buffer size

* Better name
2024-01-29 21:19:39 -03:00
Ac_K
8bf102d2cd Cpu: Implement Vpadal and Vrintr instructions (#6185)
* Cpu: Implement Vpadal and Vrintr instructions

This PR superseed last instructions left in #2242.
Since I'm not a CPU guy I've just ported the code and nothing more.
Please be precise during review if there are some changes to be done.

It should fixes #1781

Co-Authored-By: Piyachet Kanda <piyachetk@gmail.com>

* Addresses gdkchan's feedback

* Addresses gdkchan's feedback 2

* Apply suggestions from code review

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

* another fix

* Update InstEmitSimdHelper32.cs

* Correct fix

* Addresses gdkchan's feedback

* Update CpuTestSimdCvt32.cs

---------

Co-authored-by: Piyachet Kanda <piyachetk@gmail.com>
Co-authored-by: gdkchan <gab.dark.100@gmail.com>
2024-01-30 00:51:05 +01:00
Ac_K
2adf031830 deps: Update Avalonia.Svg (#6218)
Updates `Avalonia.Svg` from 11.0.0.10 to 11.0.0.13
Updates `Avalonia.Svg.Skia` from 11.0.0.10 to 11.0.0.13

And reflect update changes as dependabot can't do it.

Superseed #6209
2024-01-29 23:22:42 +01:00
Isaac Marovitz
bb4a28b525 Ava UI: Mod Manager Fixes (Again) (#6187)
* Fix typo + Fix deleting from old dir

* Avoid double enumeration

* Break when parentDir is found

* Fix deleting non subdirectory mods

* Typo
2024-01-29 23:14:19 +01:00
merry
a8fbcdae9f UI: Clarify Create Application Shortcut tooltip text (#6217) 2024-01-29 23:08:24 +01:00
TSRBerry
4e81ab4229 Avalonia: Fix dialog issues caused by 1.1.1105 (#6211)
* Set _contentDialogOverlayWindow to null

* Make CheckLaunchState async
2024-01-29 22:57:20 +01:00
41 changed files with 760 additions and 312 deletions

View File

@@ -8,8 +8,8 @@
<PackageVersion Include="Avalonia.Desktop" Version="11.0.7" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.0.7" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.0.7" />
<PackageVersion Include="Avalonia.Svg" Version="11.0.0.10" />
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.10" />
<PackageVersion Include="Avalonia.Svg" Version="11.0.0.13" />
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.13" />
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="Concentus" Version="1.1.7" />
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
@@ -21,7 +21,7 @@
<PackageVersion Include="LibHac" Version="0.19.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.2.0" />
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.3.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.0" />
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
@@ -45,7 +45,7 @@
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />
<PackageVersion Include="SixLabors.ImageSharp" Version="1.0.4" />
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
<PackageVersion Include="SPB" Version="0.0.4-build28" />
<PackageVersion Include="SPB" Version="0.0.4-build32" />
<PackageVersion Include="System.Drawing.Common" Version="8.0.1" />
<PackageVersion Include="System.IO.Hashing" Version="8.0.0" />
<PackageVersion Include="System.Management" Version="8.0.0" />

View File

@@ -875,6 +875,7 @@ namespace ARMeilleure.Decoders
SetVfp("<<<<11100x10xxxxxxxx101xx1x0xxxx", InstName.Vnmul, InstEmit32.Vnmul_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32);
SetVfp("111111101x1110xxxxxx101x01x0xxxx", InstName.Vrint, InstEmit32.Vrint_RM, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
SetVfp("<<<<11101x110110xxxx101x11x0xxxx", InstName.Vrint, InstEmit32.Vrint_Z, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
SetVfp("<<<<11101x110110xxxx101x01x0xxxx", InstName.Vrintr, InstEmit32.Vrintr_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
SetVfp("<<<<11101x110111xxxx101x01x0xxxx", InstName.Vrintx, InstEmit32.Vrintx_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
SetVfp("<<<<11101x110001xxxx101x11x0xxxx", InstName.Vsqrt, InstEmit32.Vsqrt_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
SetVfp("111111100xxxxxxxxxxx101xx0x0xxxx", InstName.Vsel, InstEmit32.Vsel, OpCode32SimdSel.Create, OpCode32SimdSel.CreateT32);
@@ -995,6 +996,7 @@ namespace ARMeilleure.Decoders
SetAsimd("1111001x1x000xxxxxxx<<x10x01xxxx", InstName.Vorr, InstEmit32.Vorr_II, OpCode32SimdImm.Create, OpCode32SimdImm.CreateT32);
SetAsimd("111100100x<<xxxxxxxx1011x0x1xxxx", InstName.Vpadd, InstEmit32.Vpadd_I, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
SetAsimd("111100110x00xxxxxxxx1101x0x0xxxx", InstName.Vpadd, InstEmit32.Vpadd_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
SetAsimd("111100111x11<<00xxxx0110xxx0xxxx", InstName.Vpadal, InstEmit32.Vpadal, OpCode32SimdCmpZ.Create, OpCode32SimdCmpZ.CreateT32);
SetAsimd("111100111x11<<00xxxx0010xxx0xxxx", InstName.Vpaddl, InstEmit32.Vpaddl, OpCode32SimdCmpZ.Create, OpCode32SimdCmpZ.CreateT32);
SetAsimd("1111001x0x<<xxxxxxxx1010x0x0xxxx", InstName.Vpmax, InstEmit32.Vpmax_I, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
SetAsimd("111100110x00xxxxxxxx1111x0x0xxxx", InstName.Vpmax, InstEmit32.Vpmax_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);

View File

@@ -1115,6 +1115,13 @@ namespace ARMeilleure.Instructions
}
}
public static void Vpadal(ArmEmitterContext context)
{
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
EmitVectorPairwiseTernaryLongOpI32(context, (op1, op2, op3) => context.Add(context.Add(op1, op2), op3), op.Opc != 1);
}
public static void Vpaddl(ArmEmitterContext context)
{
OpCode32Simd op = (OpCode32Simd)context.CurrOp;

View File

@@ -578,6 +578,22 @@ namespace ARMeilleure.Instructions
}
}
// VRINTR (floating-point).
public static void Vrintr_S(ArmEmitterContext context)
{
if (Optimizations.UseAdvSimd)
{
InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, Intrinsic.Arm64FrintiS);
}
else
{
EmitScalarUnaryOpF32(context, (op1) =>
{
return EmitRoundByRMode(context, op1);
});
}
}
// VRINTZ (floating-point).
public static void Vrint_Z(ArmEmitterContext context)
{

View File

@@ -673,6 +673,35 @@ namespace ARMeilleure.Instructions
context.Copy(GetVecA32(op.Qd), res);
}
public static void EmitVectorPairwiseTernaryLongOpI32(ArmEmitterContext context, Func3I emit, bool signed)
{
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
int elems = op.GetBytesCount() >> op.Size;
int pairs = elems >> 1;
Operand res = GetVecA32(op.Qd);
for (int index = 0; index < pairs; index++)
{
int pairIndex = index * 2;
Operand m1 = EmitVectorExtract32(context, op.Qm, op.Im + pairIndex, op.Size, signed);
Operand m2 = EmitVectorExtract32(context, op.Qm, op.Im + pairIndex + 1, op.Size, signed);
if (op.Size == 2)
{
m1 = signed ? context.SignExtend32(OperandType.I64, m1) : context.ZeroExtend32(OperandType.I64, m1);
m2 = signed ? context.SignExtend32(OperandType.I64, m2) : context.ZeroExtend32(OperandType.I64, m2);
}
Operand d1 = EmitVectorExtract32(context, op.Qd, op.Id + index, op.Size + 1, signed);
res = EmitVectorInsert(context, res, emit(m1, m2, d1), op.Id + index, op.Size + 1);
}
context.Copy(GetVecA32(op.Qd), res);
}
// Narrow
public static void EmitVectorUnaryNarrowOp32(ArmEmitterContext context, Func1I emit, bool signed = false)

View File

@@ -637,6 +637,7 @@ namespace ARMeilleure.Instructions
Vorn,
Vorr,
Vpadd,
Vpadal,
Vpaddl,
Vpmax,
Vpmin,
@@ -656,6 +657,7 @@ namespace ARMeilleure.Instructions
Vrintm,
Vrintn,
Vrintp,
Vrintr,
Vrintx,
Vrshr,
Vrshrn,

View File

@@ -72,6 +72,7 @@
"GameListContextMenuExtractDataLogoToolTip": "Extract the Logo section from Application's current config (including updates)",
"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",
"StatusBarGamesLoaded": "{0}/{1} Games Loaded",
"StatusBarSystemVersion": "System Version: {0}",
"LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected",

View File

@@ -9,6 +9,7 @@ using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common;
using Ryujinx.HLE.HOS.Applets;
using Ryujinx.HLE.HOS.Services.Hid;
using System;
using System.Linq;
using System.Threading.Tasks;
@@ -103,7 +104,7 @@ namespace Ryujinx.Ava.UI.Applet
if (!string.IsNullOrWhiteSpace(path))
{
SvgSource source = new();
SvgSource source = new(default(Uri));
source.Load(EmbeddedResources.GetStream(path));

View File

@@ -16,7 +16,7 @@
Click="CreateApplicationShortcut_Click"
Header="{locale:Locale GameListContextMenuCreateShortcut}"
IsEnabled="{Binding CreateShortcutEnabled}"
ToolTip.Tip="{locale:Locale GameListContextMenuCreateShortcutToolTip}" />
ToolTip.Tip="{OnPlatform Default={locale:Locale GameListContextMenuCreateShortcutToolTip}, macOS={locale:Locale GameListContextMenuCreateShortcutToolTipMacOS}}" />
<Separator />
<MenuItem
Click="OpenUserSaveDirectory_Click"

View File

@@ -336,6 +336,11 @@ namespace Ryujinx.Ava.UI.Helpers
void OverlayOnPositionChanged(object sender, PixelPointEventArgs e)
{
if (_contentDialogOverlayWindow is null)
{
return;
}
_contentDialogOverlayWindow.Position = parent.PointToScreen(new Point());
}
@@ -388,6 +393,7 @@ namespace Ryujinx.Ava.UI.Helpers
{
_contentDialogOverlayWindow.Content = null;
_contentDialogOverlayWindow.Close();
_contentDialogOverlayWindow = null;
}
return result;

View File

@@ -17,14 +17,16 @@ namespace Ryujinx.Ava.UI.Models
}
}
public bool InSd { get; }
public string Path { get; }
public string Name { get; }
public ModModel(string path, string name, bool enabled)
public ModModel(string path, string name, bool enabled, bool inSd)
{
Path = path;
Name = name;
Enabled = enabled;
InSd = inSd;
}
}
}

View File

@@ -180,7 +180,7 @@ namespace Ryujinx.Ava.UI.ViewModels
if (!string.IsNullOrWhiteSpace(_controllerImage))
{
SvgSource source = new();
SvgSource source = new(default(Uri));
source.Load(EmbeddedResources.GetStream(_controllerImage));

View File

@@ -1352,9 +1352,9 @@ namespace Ryujinx.Ava.UI.ViewModels
{
string logPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs");
if (ReleaseInformation.IsValid)
if (LoggerModule.LogDirectoryPath != null)
{
logPath = Path.Combine(AppDataManager.BaseDirPath, "Logs");
logPath = LoggerModule.LogDirectoryPath;
}
new DirectoryInfo(logPath).Create();

View File

@@ -102,13 +102,14 @@ namespace Ryujinx.Ava.UI.ViewModels
foreach (var path in modsBasePaths)
{
var inSd = path == ModLoader.GetSdModsBasePath();
var modCache = new ModLoader.ModCache();
ModLoader.QueryContentsDir(modCache, new DirectoryInfo(Path.Combine(path, "contents")), applicationId);
foreach (var mod in modCache.RomfsDirs)
{
var modModel = new ModModel(mod.Path.Parent.FullName, mod.Name, mod.Enabled);
var modModel = new ModModel(mod.Path.Parent.FullName, mod.Name, mod.Enabled, inSd);
if (Mods.All(x => x.Path != mod.Path.Parent.FullName))
{
Mods.Add(modModel);
@@ -117,12 +118,12 @@ namespace Ryujinx.Ava.UI.ViewModels
foreach (var mod in modCache.RomfsContainers)
{
Mods.Add(new ModModel(mod.Path.FullName, mod.Name, mod.Enabled));
Mods.Add(new ModModel(mod.Path.FullName, mod.Name, mod.Enabled, inSd));
}
foreach (var mod in modCache.ExefsDirs)
{
var modModel = new ModModel(mod.Path.Parent.FullName, mod.Name, mod.Enabled);
var modModel = new ModModel(mod.Path.Parent.FullName, mod.Name, mod.Enabled, inSd);
if (Mods.All(x => x.Path != mod.Path.Parent.FullName))
{
Mods.Add(modModel);
@@ -131,7 +132,7 @@ namespace Ryujinx.Ava.UI.ViewModels
foreach (var mod in modCache.ExefsContainers)
{
Mods.Add(new ModModel(mod.Path.FullName, mod.Name, mod.Enabled));
Mods.Add(new ModModel(mod.Path.FullName, mod.Name, mod.Enabled, inSd));
}
}
@@ -183,30 +184,43 @@ namespace Ryujinx.Ava.UI.ViewModels
public void Delete(ModModel model)
{
var modsDir = ModLoader.GetApplicationDir(ModLoader.GetSdModsBasePath(), _applicationId.ToString("x16"));
var parentDir = String.Empty;
var isSubdir = true;
var pathToDelete = model.Path;
var basePath = model.InSd ? ModLoader.GetSdModsBasePath() : ModLoader.GetModsBasePath();
var modsDir = ModLoader.GetApplicationDir(basePath, _applicationId.ToString("x16"));
foreach (var dir in Directory.GetDirectories(modsDir, "*", SearchOption.TopDirectoryOnly))
if (new DirectoryInfo(model.Path).Parent?.FullName == modsDir)
{
if (Directory.GetDirectories(dir, "*", SearchOption.AllDirectories).Contains(model.Path))
isSubdir = false;
}
if (isSubdir)
{
var parentDir = String.Empty;
foreach (var dir in Directory.GetDirectories(modsDir, "*", SearchOption.TopDirectoryOnly))
{
parentDir = dir;
if (Directory.GetDirectories(dir, "*", SearchOption.AllDirectories).Contains(model.Path))
{
parentDir = dir;
break;
}
}
if (parentDir == String.Empty)
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(
LocaleKeys.DialogModDeleteNoParentMessage,
model.Path));
});
return;
}
}
if (parentDir == String.Empty)
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(
LocaleKeys.DialogModDeleteNoParentMessage,
parentDir));
});
return;
}
Logger.Info?.Print(LogClass.Application, $"Deleting mod at \"{model.Path}\"");
Directory.Delete(parentDir, true);
Logger.Info?.Print(LogClass.Application, $"Deleting mod at \"{pathToDelete}\"");
Directory.Delete(pathToDelete, true);
Mods.Remove(model);
OnPropertyChanged(nameof(ModCount));

View File

@@ -263,7 +263,7 @@ namespace Ryujinx.Ava.UI.Windows
}
}
private void CheckLaunchState()
private async Task CheckLaunchState()
{
if (OperatingSystem.IsLinux() && LinuxHelper.VmMaxMapCount < LinuxHelper.RecommendedVmMaxMapCount)
{
@@ -271,23 +271,11 @@ namespace Ryujinx.Ava.UI.Windows
if (LinuxHelper.PkExecPath is not null)
{
Dispatcher.UIThread.Post(async () =>
{
if (OperatingSystem.IsLinux())
{
await ShowVmMaxMapCountDialog();
}
});
await Dispatcher.UIThread.InvokeAsync(ShowVmMaxMapCountDialog);
}
else
{
Dispatcher.UIThread.Post(async () =>
{
if (OperatingSystem.IsLinux())
{
await ShowVmMaxMapCountWarning();
}
});
await Dispatcher.UIThread.InvokeAsync(ShowVmMaxMapCountWarning);
}
}
@@ -304,12 +292,12 @@ namespace Ryujinx.Ava.UI.Windows
{
ShowKeyErrorOnLoad = false;
Dispatcher.UIThread.Post(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys));
await Dispatcher.UIThread.InvokeAsync(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys));
}
if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false))
{
Updater.BeginParse(this, false).ContinueWith(task =>
await Updater.BeginParse(this, false).ContinueWith(task =>
{
Logger.Error?.Print(LogClass.Application, $"Updater Error: {task.Exception}");
}, TaskContinuationOptions.OnlyOnFaulted);
@@ -404,7 +392,9 @@ namespace Ryujinx.Ava.UI.Windows
LoadApplications();
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
CheckLaunchState();
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
}
private void SetMainContent(Control content = null)

View File

@@ -23,7 +23,7 @@ namespace Ryujinx.Common.Logging.Targets
public static FileStream PrepareLogFile(string path)
{
// Ensure directory is present
DirectoryInfo logDir = new(Path.Combine(path, "Logs"));
DirectoryInfo logDir = new(path);
try
{
logDir.Create();

View File

@@ -1630,12 +1630,6 @@ namespace Ryujinx.Graphics.Gpu.Image
return;
}
// If size is zero, we have nothing to flush.
if (size == 0)
{
return;
}
// There is a small gap here where the action is removed but _actionRegistered is still 1.
// In this case it will skip registering the action, but here we are already handling it,
// so there shouldn't be any issue as it's the same handler for all actions.

View File

@@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
private const ushort FileFormatVersionMajor = 1;
private const ushort FileFormatVersionMinor = 2;
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
private const uint CodeGenVersion = 5958;
private const uint CodeGenVersion = 6253;
private const string SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data";

View File

@@ -578,12 +578,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
type = SamplerType.Texture2D;
flags = TextureFlags.Gather;
if (tld4sOp.Dc)
{
sourcesList.Add(Rb());
type |= SamplerType.Shadow;
}
int depthCompareIndex = sourcesList.Count;
if (tld4sOp.Aoffi)
{
@@ -592,7 +587,16 @@ namespace Ryujinx.Graphics.Shader.Instructions
flags |= TextureFlags.Offset;
}
sourcesList.Add(Const((int)tld4sOp.TexComp));
if (tld4sOp.Dc)
{
sourcesList.Insert(depthCompareIndex, Rb());
type |= SamplerType.Shadow;
}
else
{
sourcesList.Add(Const((int)tld4sOp.TexComp));
}
}
else
{

View File

@@ -257,7 +257,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
scissors[0] = new Rectangle<int>(0, 0, texture.Width, texture.Height);
_pipeline.SetRenderTarget(texture.GetImageViewForAttachment(), (uint)texture.Width, (uint)texture.Height, false, texture.VkFormat);
_pipeline.SetRenderTarget(texture, (uint)texture.Width, (uint)texture.Height);
_pipeline.SetRenderTargetColorMasks(colorMasks);
_pipeline.SetScissors(scissors);
_pipeline.ClearRenderTargetColor(0, 0, 1, new ColorF(0f, 0f, 0f, 1f));

View File

@@ -1,5 +1,6 @@
using Ryujinx.Graphics.GAL;
using System;
using System.Collections.Generic;
using VkFormat = Silk.NET.Vulkan.Format;
namespace Ryujinx.Graphics.Vulkan
@@ -7,10 +8,12 @@ namespace Ryujinx.Graphics.Vulkan
static class FormatTable
{
private static readonly VkFormat[] _table;
private static readonly Dictionary<VkFormat, Format> _reverseMap;
static FormatTable()
{
_table = new VkFormat[Enum.GetNames(typeof(Format)).Length];
_reverseMap = new Dictionary<VkFormat, Format>();
#pragma warning disable IDE0055 // Disable formatting
Add(Format.R8Unorm, VkFormat.R8Unorm);
@@ -164,6 +167,7 @@ namespace Ryujinx.Graphics.Vulkan
private static void Add(Format format, VkFormat vkFormat)
{
_table[(int)format] = vkFormat;
_reverseMap[vkFormat] = format;
}
public static VkFormat GetFormat(Format format)
@@ -171,6 +175,16 @@ namespace Ryujinx.Graphics.Vulkan
return _table[(int)format];
}
public static Format GetFormat(VkFormat format)
{
if (!_reverseMap.TryGetValue(format, out Format result))
{
return Format.B8G8R8A8Unorm;
}
return result;
}
public static Format ConvertRgba8SrgbToUnorm(Format format)
{
return format switch

View File

@@ -12,6 +12,8 @@ namespace Ryujinx.Graphics.Vulkan
private readonly Auto<DisposableImageView>[] _attachments;
private readonly TextureView[] _colors;
private readonly TextureView _depthStencil;
private readonly TextureView[] _colorsCanonical;
private readonly TextureView _baseAttachment;
private readonly uint _validColorAttachments;
public uint Width { get; }
@@ -28,25 +30,31 @@ namespace Ryujinx.Graphics.Vulkan
public bool HasDepthStencil { get; }
public int ColorAttachmentsCount => AttachmentsCount - (HasDepthStencil ? 1 : 0);
public FramebufferParams(
Device device,
Auto<DisposableImageView> view,
uint width,
uint height,
uint samples,
bool isDepthStencil,
VkFormat format)
public FramebufferParams(Device device, TextureView view, uint width, uint height)
{
bool isDepthStencil = view.Info.Format.IsDepthOrStencil();
_device = device;
_attachments = new[] { view };
_attachments = new[] { view.GetImageViewForAttachment() };
_validColorAttachments = isDepthStencil ? 0u : 1u;
_baseAttachment = view;
if (isDepthStencil)
{
_depthStencil = view;
}
else
{
_colors = new TextureView[] { view };
_colorsCanonical = _colors;
}
Width = width;
Height = height;
Layers = 1;
AttachmentSamples = new[] { samples };
AttachmentFormats = new[] { format };
AttachmentSamples = new[] { (uint)view.Info.Samples };
AttachmentFormats = new[] { view.VkFormat };
AttachmentIndices = isDepthStencil ? Array.Empty<int>() : new[] { 0 };
AttachmentsCount = 1;
@@ -64,6 +72,7 @@ namespace Ryujinx.Graphics.Vulkan
_attachments = new Auto<DisposableImageView>[count];
_colors = new TextureView[colorsCount];
_colorsCanonical = colors.Select(color => color is TextureView view && view.Valid ? view : null).ToArray();
AttachmentSamples = new uint[count];
AttachmentFormats = new VkFormat[count];
@@ -86,6 +95,7 @@ namespace Ryujinx.Graphics.Vulkan
_attachments[index] = texture.GetImageViewForAttachment();
_colors[index] = texture;
_validColorAttachments |= 1u << bindIndex;
_baseAttachment = texture;
AttachmentSamples[index] = (uint)texture.Info.Samples;
AttachmentFormats[index] = texture.VkFormat;
@@ -115,6 +125,7 @@ namespace Ryujinx.Graphics.Vulkan
{
_attachments[count - 1] = dsTexture.GetImageViewForAttachment();
_depthStencil = dsTexture;
_baseAttachment ??= dsTexture;
AttachmentSamples[count - 1] = (uint)dsTexture.Info.Samples;
AttachmentFormats[count - 1] = dsTexture.VkFormat;
@@ -251,19 +262,11 @@ namespace Ryujinx.Graphics.Vulkan
public void InsertClearBarrier(CommandBufferScoped cbs, int index)
{
if (_colors != null)
{
int realIndex = Array.IndexOf(AttachmentIndices, index);
if (realIndex != -1)
{
_colors[realIndex].Storage?.InsertReadToWriteBarrier(
cbs,
AccessFlags.ColorAttachmentWriteBit,
PipelineStageFlags.ColorAttachmentOutputBit,
insideRenderPass: true);
}
}
_colorsCanonical?[index]?.Storage?.InsertReadToWriteBarrier(
cbs,
AccessFlags.ColorAttachmentWriteBit,
PipelineStageFlags.ColorAttachmentOutputBit,
insideRenderPass: true);
}
public void InsertClearBarrierDS(CommandBufferScoped cbs)
@@ -274,5 +277,61 @@ namespace Ryujinx.Graphics.Vulkan
PipelineStageFlags.LateFragmentTestsBit,
insideRenderPass: true);
}
public TextureView[] GetAttachmentViews()
{
var result = new TextureView[_attachments.Length];
_colors?.CopyTo(result, 0);
if (_depthStencil != null)
{
result[^1] = _depthStencil;
}
return result;
}
public RenderPassCacheKey GetRenderPassCacheKey()
{
return new RenderPassCacheKey(_depthStencil, _colorsCanonical);
}
public void InsertLoadOpBarriers(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);
}
}
if (_depthStencil != null)
{
_depthStencil.Storage?.InsertWriteToReadBarrier(cbs, AccessFlags.DepthStencilAttachmentReadBit, PipelineStageFlags.EarlyFragmentTestsBit);
_depthStencil.Storage?.SetModification(AccessFlags.DepthStencilAttachmentWriteBit, PipelineStageFlags.LateFragmentTestsBit);
}
}
public (Auto<DisposableRenderPass> renderPass, Auto<DisposableFramebuffer> framebuffer) GetPassAndFramebuffer(
VulkanRenderer gd,
Device device,
CommandBufferScoped cbs)
{
return _baseAttachment.GetPassAndFramebuffer(gd, device, cbs, this);
}
public TextureView GetColorView(int index)
{
return _colorsCanonical[index];
}
public TextureView GetDepthStencilView()
{
return _depthStencil;
}
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace Ryujinx.Graphics.Vulkan
{
@@ -20,20 +21,29 @@ namespace Ryujinx.Graphics.Vulkan
public TValue Value;
}
private readonly Entry[][] _hashTable = new Entry[TotalBuckets][];
private struct Bucket
{
public int Length;
public Entry[] Entries;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Span<Entry> AsSpan()
{
return Entries == null ? Span<Entry>.Empty : Entries.AsSpan(0, Length);
}
}
private readonly Bucket[] _hashTable = new Bucket[TotalBuckets];
public IEnumerable<TKey> Keys
{
get
{
foreach (Entry[] bucket in _hashTable)
foreach (Bucket bucket in _hashTable)
{
if (bucket != null)
for (int i = 0; i < bucket.Length; i++)
{
foreach (Entry entry in bucket)
{
yield return entry.Key;
}
yield return bucket.Entries[i].Key;
}
}
}
@@ -43,14 +53,11 @@ namespace Ryujinx.Graphics.Vulkan
{
get
{
foreach (Entry[] bucket in _hashTable)
foreach (Bucket bucket in _hashTable)
{
if (bucket != null)
for (int i = 0; i < bucket.Length; i++)
{
foreach (Entry entry in bucket)
{
yield return entry.Value;
}
yield return bucket.Entries[i].Value;
}
}
}
@@ -68,40 +75,64 @@ namespace Ryujinx.Graphics.Vulkan
int hashCode = key.GetHashCode();
int bucketIndex = hashCode & TotalBucketsMask;
var bucket = _hashTable[bucketIndex];
if (bucket != null)
ref var bucket = ref _hashTable[bucketIndex];
if (bucket.Entries != null)
{
int index = bucket.Length;
Array.Resize(ref _hashTable[bucketIndex], index + 1);
if (index >= bucket.Entries.Length)
{
Array.Resize(ref bucket.Entries, index + 1);
}
_hashTable[bucketIndex][index] = entry;
bucket.Entries[index] = entry;
}
else
{
_hashTable[bucketIndex] = new[]
bucket.Entries = new[]
{
entry,
};
}
bucket.Length++;
}
public bool Remove(ref TKey key)
{
int hashCode = key.GetHashCode();
ref var bucket = ref _hashTable[hashCode & TotalBucketsMask];
var entries = bucket.AsSpan();
for (int i = 0; i < entries.Length; i++)
{
ref var entry = ref entries[i];
if (entry.Hash == hashCode && entry.Key.Equals(ref key))
{
entries[(i + 1)..].CopyTo(entries[i..]);
bucket.Length--;
return true;
}
}
return false;
}
public bool TryGetValue(ref TKey key, out TValue value)
{
int hashCode = key.GetHashCode();
var bucket = _hashTable[hashCode & TotalBucketsMask];
if (bucket != null)
var entries = _hashTable[hashCode & TotalBucketsMask].AsSpan();
for (int i = 0; i < entries.Length; i++)
{
for (int i = 0; i < bucket.Length; i++)
{
ref var entry = ref bucket[i];
ref var entry = ref entries[i];
if (entry.Hash == hashCode && entry.Key.Equals(ref key))
{
value = entry.Value;
return true;
}
if (entry.Hash == hashCode && entry.Key.Equals(ref key))
{
value = entry.Value;
return true;
}
}

View File

@@ -256,17 +256,8 @@ namespace Ryujinx.Graphics.Vulkan
using var cbs = gd.CommandBufferPool.Rent();
var dstFormat = dst.VkFormat;
var dstSamples = dst.Info.Samples;
for (int l = 0; l < levels; l++)
{
int srcWidth = Math.Max(1, src.Width >> l);
int srcHeight = Math.Max(1, src.Height >> l);
int dstWidth = Math.Max(1, dst.Width >> l);
int dstHeight = Math.Max(1, dst.Height >> l);
var mipSrcRegion = new Extents2D(
srcRegion.X1 >> l,
srcRegion.Y1 >> l,
@@ -290,11 +281,7 @@ namespace Ryujinx.Graphics.Vulkan
gd,
cbs,
srcView,
dst.GetImageViewForAttachment(),
dstWidth,
dstHeight,
dstSamples,
dstFormat,
dstView,
mipSrcRegion,
mipDstRegion);
}
@@ -304,12 +291,7 @@ namespace Ryujinx.Graphics.Vulkan
gd,
cbs,
srcView,
dst.GetImageViewForAttachment(),
dstWidth,
dstHeight,
dstSamples,
dstFormat,
false,
dstView,
mipSrcRegion,
mipDstRegion,
linearFilter,
@@ -367,12 +349,7 @@ namespace Ryujinx.Graphics.Vulkan
gd,
cbs,
srcView,
dstView.GetImageViewForAttachment(),
dstView.Width,
dstView.Height,
dstView.Info.Samples,
dstView.VkFormat,
dstView.Info.Format.IsDepthOrStencil(),
dstView,
extents,
extents,
false);
@@ -394,12 +371,7 @@ namespace Ryujinx.Graphics.Vulkan
VulkanRenderer gd,
CommandBufferScoped cbs,
TextureView src,
Auto<DisposableImageView> dst,
int dstWidth,
int dstHeight,
int dstSamples,
VkFormat dstFormat,
bool dstIsDepthOrStencil,
TextureView dst,
Extents2D srcRegion,
Extents2D dstRegion,
bool linearFilter,
@@ -453,6 +425,8 @@ namespace Ryujinx.Graphics.Vulkan
0f,
1f);
bool dstIsDepthOrStencil = dst.Info.Format.IsDepthOrStencil();
if (dstIsDepthOrStencil)
{
_pipeline.SetProgram(src.Info.Target.IsMultisample() ? _programDepthBlitMs : _programDepthBlit);
@@ -471,7 +445,10 @@ namespace Ryujinx.Graphics.Vulkan
_pipeline.SetProgram(_programColorBlit);
}
_pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, (uint)dstSamples, dstIsDepthOrStencil, dstFormat);
int dstWidth = dst.Width;
int dstHeight = dst.Height;
_pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight);
_pipeline.SetRenderTargetColorMasks(new uint[] { 0xf });
_pipeline.SetScissors(stackalloc Rectangle<int>[] { new Rectangle<int>(0, 0, dstWidth, dstHeight) });
@@ -496,11 +473,7 @@ namespace Ryujinx.Graphics.Vulkan
VulkanRenderer gd,
CommandBufferScoped cbs,
TextureView src,
Auto<DisposableImageView> dst,
int dstWidth,
int dstHeight,
int dstSamples,
VkFormat dstFormat,
TextureView dst,
Extents2D srcRegion,
Extents2D dstRegion)
{
@@ -548,7 +521,10 @@ namespace Ryujinx.Graphics.Vulkan
0f,
1f);
_pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, (uint)dstSamples, true, dstFormat);
int dstWidth = dst.Width;
int dstHeight = dst.Height;
_pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight);
_pipeline.SetScissors(stackalloc Rectangle<int>[] { new Rectangle<int>(0, 0, dstWidth, dstHeight) });
_pipeline.SetViewports(viewports);
_pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
@@ -660,12 +636,11 @@ namespace Ryujinx.Graphics.Vulkan
public void Clear(
VulkanRenderer gd,
Auto<DisposableImageView> dst,
TextureView dst,
ReadOnlySpan<float> clearColor,
uint componentMask,
int dstWidth,
int dstHeight,
VkFormat dstFormat,
ComponentType type,
Rectangle<int> scissor)
{
@@ -710,7 +685,7 @@ namespace Ryujinx.Graphics.Vulkan
}
_pipeline.SetProgram(program);
_pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, false, dstFormat);
_pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight);
_pipeline.SetRenderTargetColorMasks(new[] { componentMask });
_pipeline.SetViewports(viewports);
_pipeline.SetScissors(stackalloc Rectangle<int>[] { scissor });
@@ -721,7 +696,7 @@ namespace Ryujinx.Graphics.Vulkan
public void Clear(
VulkanRenderer gd,
Auto<DisposableImageView> dst,
TextureView dst,
float depthValue,
bool depthMask,
int stencilValue,
@@ -757,7 +732,7 @@ namespace Ryujinx.Graphics.Vulkan
1f);
_pipeline.SetProgram(_programDepthStencilClear);
_pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, true, dstFormat);
_pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight);
_pipeline.SetViewports(viewports);
_pipeline.SetScissors(stackalloc Rectangle<int>[] { scissor });
_pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
@@ -1163,12 +1138,7 @@ namespace Ryujinx.Graphics.Vulkan
var srcView = Create2DLayerView(src, srcLayer + z, 0);
var dstView = Create2DLayerView(dst, dstLayer + z, 0);
_pipeline.SetRenderTarget(
dstView.GetImageViewForAttachment(),
(uint)dst.Width,
(uint)dst.Height,
true,
dst.VkFormat);
_pipeline.SetRenderTarget(dstView, (uint)dst.Width, (uint)dst.Height);
CopyMSDraw(srcView, aspectFlags, fromMS: true);
@@ -1294,13 +1264,7 @@ namespace Ryujinx.Graphics.Vulkan
var srcView = Create2DLayerView(src, srcLayer + z, 0);
var dstView = Create2DLayerView(dst, dstLayer + z, 0);
_pipeline.SetRenderTarget(
dstView.GetImageViewForAttachment(),
(uint)dst.Width,
(uint)dst.Height,
(uint)samples,
true,
dst.VkFormat);
_pipeline.SetRenderTarget(dstView, (uint)dst.Width, (uint)dst.Height);
CopyMSDraw(srcView, aspectFlags, fromMS: false);
@@ -1328,13 +1292,7 @@ namespace Ryujinx.Graphics.Vulkan
var dstView = Create2DLayerView(dst, dstLayer + z, 0);
_pipeline.SetTextureAndSamplerIdentitySwizzle(ShaderStage.Fragment, 0, srcView, null);
_pipeline.SetRenderTarget(
dstView.GetView(format).GetImageViewForAttachment(),
(uint)dst.Width,
(uint)dst.Height,
(uint)samples,
false,
vkFormat);
_pipeline.SetRenderTarget(dstView.GetView(format), (uint)dst.Width, (uint)dst.Height);
_pipeline.Draw(4, 1, 0, 0);
@@ -1471,9 +1429,9 @@ namespace Ryujinx.Graphics.Vulkan
};
var info = new TextureCreateInfo(
from.Info.Width,
from.Info.Height,
from.Info.Depth,
Math.Max(1, from.Info.Width >> level),
Math.Max(1, from.Info.Height >> level),
1,
1,
from.Info.Samples,
from.Info.BlockWidth,

View File

@@ -55,6 +55,7 @@ namespace Ryujinx.Graphics.Vulkan
protected FramebufferParams FramebufferParams;
private Auto<DisposableFramebuffer> _framebuffer;
private Auto<DisposableRenderPass> _renderPass;
private RenderPassHolder _nullRenderPass;
private int _writtenAttachmentCount;
private bool _framebufferUsingColorWriteMask;
@@ -1488,98 +1489,22 @@ namespace Ryujinx.Graphics.Vulkan
protected unsafe void CreateRenderPass()
{
const int MaxAttachments = Constants.MaxRenderTargets + 1;
AttachmentDescription[] attachmentDescs = null;
var subpass = new SubpassDescription
{
PipelineBindPoint = PipelineBindPoint.Graphics,
};
AttachmentReference* attachmentReferences = stackalloc AttachmentReference[MaxAttachments];
var hasFramebuffer = FramebufferParams != null;
if (hasFramebuffer && FramebufferParams.AttachmentsCount != 0)
{
attachmentDescs = new AttachmentDescription[FramebufferParams.AttachmentsCount];
for (int i = 0; i < FramebufferParams.AttachmentsCount; i++)
{
attachmentDescs[i] = new AttachmentDescription(
0,
FramebufferParams.AttachmentFormats[i],
TextureStorage.ConvertToSampleCountFlags(Gd.Capabilities.SupportedSampleCounts, FramebufferParams.AttachmentSamples[i]),
AttachmentLoadOp.Load,
AttachmentStoreOp.Store,
AttachmentLoadOp.Load,
AttachmentStoreOp.Store,
ImageLayout.General,
ImageLayout.General);
}
int colorAttachmentsCount = FramebufferParams.ColorAttachmentsCount;
if (colorAttachmentsCount > MaxAttachments - 1)
{
colorAttachmentsCount = MaxAttachments - 1;
}
if (colorAttachmentsCount != 0)
{
int maxAttachmentIndex = FramebufferParams.MaxColorAttachmentIndex;
subpass.ColorAttachmentCount = (uint)maxAttachmentIndex + 1;
subpass.PColorAttachments = &attachmentReferences[0];
// Fill with VK_ATTACHMENT_UNUSED to cover any gaps.
for (int i = 0; i <= maxAttachmentIndex; i++)
{
subpass.PColorAttachments[i] = new AttachmentReference(Vk.AttachmentUnused, ImageLayout.Undefined);
}
for (int i = 0; i < colorAttachmentsCount; i++)
{
int bindIndex = FramebufferParams.AttachmentIndices[i];
subpass.PColorAttachments[bindIndex] = new AttachmentReference((uint)i, ImageLayout.General);
}
}
if (FramebufferParams.HasDepthStencil)
{
uint dsIndex = (uint)FramebufferParams.AttachmentsCount - 1;
subpass.PDepthStencilAttachment = &attachmentReferences[MaxAttachments - 1];
*subpass.PDepthStencilAttachment = new AttachmentReference(dsIndex, ImageLayout.General);
}
}
var subpassDependency = PipelineConverter.CreateSubpassDependency();
fixed (AttachmentDescription* pAttachmentDescs = attachmentDescs)
{
var renderPassCreateInfo = new RenderPassCreateInfo
{
SType = StructureType.RenderPassCreateInfo,
PAttachments = pAttachmentDescs,
AttachmentCount = attachmentDescs != null ? (uint)attachmentDescs.Length : 0,
PSubpasses = &subpass,
SubpassCount = 1,
PDependencies = &subpassDependency,
DependencyCount = 1,
};
Gd.Api.CreateRenderPass(Device, renderPassCreateInfo, null, out var renderPass).ThrowOnError();
_renderPass?.Dispose();
_renderPass = new Auto<DisposableRenderPass>(new DisposableRenderPass(Gd.Api, Device, renderPass));
}
EndRenderPass();
_framebuffer?.Dispose();
_framebuffer = hasFramebuffer ? FramebufferParams.Create(Gd.Api, Cbs, _renderPass) : null;
if (!hasFramebuffer || FramebufferParams.AttachmentsCount == 0)
{
// Use the null framebuffer.
_nullRenderPass ??= new RenderPassHolder(Gd, Device, new RenderPassCacheKey(), FramebufferParams);
_renderPass = _nullRenderPass.GetRenderPass();
_framebuffer = _nullRenderPass.GetFramebuffer(Gd, Cbs, FramebufferParams);
}
else
{
(_renderPass, _framebuffer) = FramebufferParams.GetPassAndFramebuffer(Gd, Device, Cbs);
}
}
protected void SignalStateChange()
@@ -1770,8 +1695,7 @@ namespace Ryujinx.Graphics.Vulkan
{
if (disposing)
{
_renderPass?.Dispose();
_framebuffer?.Dispose();
_nullRenderPass?.Dispose();
_newState.Dispose();
_descriptorSetUpdater.Dispose();
_vertexBufferUpdater.Dispose();

View File

@@ -51,7 +51,7 @@ namespace Ryujinx.Graphics.Vulkan
{
// We can't use CmdClearAttachments if not writing all components,
// because on Vulkan, the pipeline state does not affect clears.
var dstTexture = FramebufferParams.GetAttachment(index);
var dstTexture = FramebufferParams.GetColorView(index);
if (dstTexture == null)
{
return;
@@ -71,7 +71,6 @@ namespace Ryujinx.Graphics.Vulkan
componentMask,
(int)FramebufferParams.Width,
(int)FramebufferParams.Height,
FramebufferParams.AttachmentFormats[index],
FramebufferParams.GetAttachmentComponentType(index),
ClearScissor);
}
@@ -92,7 +91,7 @@ namespace Ryujinx.Graphics.Vulkan
{
// We can't use CmdClearAttachments if not clearing all (mask is all ones, 0xFF) or none (mask is 0) of the stencil bits,
// because on Vulkan, the pipeline state does not affect clears.
var dstTexture = FramebufferParams.GetDepthStencilAttachment();
var dstTexture = FramebufferParams.GetDepthStencilView();
if (dstTexture == null)
{
return;

View File

@@ -9,21 +9,16 @@ namespace Ryujinx.Graphics.Vulkan
{
}
public void SetRenderTarget(Auto<DisposableImageView> view, uint width, uint height, bool isDepthStencil, VkFormat format)
public void SetRenderTarget(TextureView view, uint width, uint height)
{
SetRenderTarget(view, width, height, 1u, isDepthStencil, format);
}
public void SetRenderTarget(Auto<DisposableImageView> view, uint width, uint height, uint samples, bool isDepthStencil, VkFormat format)
{
CreateFramebuffer(view, width, height, samples, isDepthStencil, format);
CreateFramebuffer(view, width, height);
CreateRenderPass();
SignalStateChange();
}
private void CreateFramebuffer(Auto<DisposableImageView> view, uint width, uint height, uint samples, bool isDepthStencil, VkFormat format)
private void CreateFramebuffer(TextureView view, uint width, uint height)
{
FramebufferParams = new FramebufferParams(Device, view, width, height, samples, isDepthStencil, format);
FramebufferParams = new FramebufferParams(Device, view, width, height);
UpdatePipelineAttachmentFormats();
}

View File

@@ -0,0 +1,43 @@
using System;
using System.Linq;
namespace Ryujinx.Graphics.Vulkan
{
internal readonly struct RenderPassCacheKey : IRefEquatable<RenderPassCacheKey>
{
private readonly TextureView _depthStencil;
private readonly TextureView[] _colors;
public RenderPassCacheKey(TextureView depthStencil, TextureView[] colors)
{
_depthStencil = depthStencil;
_colors = colors;
}
public override int GetHashCode()
{
HashCode hc = new();
hc.Add(_depthStencil);
if (_colors != null)
{
foreach (var color in _colors)
{
hc.Add(color);
}
}
return hc.ToHashCode();
}
public bool Equals(ref RenderPassCacheKey other)
{
bool colorsNull = _colors == null;
bool otherNull = other._colors == null;
return other._depthStencil == _depthStencil &&
colorsNull == otherNull &&
(colorsNull || other._colors.SequenceEqual(_colors));
}
}
}

View File

@@ -0,0 +1,180 @@
using Silk.NET.Vulkan;
using System;
namespace Ryujinx.Graphics.Vulkan
{
internal class RenderPassHolder
{
private readonly struct FramebufferCacheKey : IRefEquatable<FramebufferCacheKey>
{
private readonly uint _width;
private readonly uint _height;
private readonly uint _layers;
public FramebufferCacheKey(uint width, uint height, uint layers)
{
_width = width;
_height = height;
_layers = layers;
}
public override int GetHashCode()
{
return HashCode.Combine(_width, _height, _layers);
}
public bool Equals(ref FramebufferCacheKey other)
{
return other._width == _width && other._height == _height && other._layers == _layers;
}
}
private readonly TextureView[] _textures;
private readonly Auto<DisposableRenderPass> _renderPass;
private readonly HashTableSlim<FramebufferCacheKey, Auto<DisposableFramebuffer>> _framebuffers;
private readonly RenderPassCacheKey _key;
public unsafe RenderPassHolder(VulkanRenderer gd, Device device, RenderPassCacheKey key, FramebufferParams fb)
{
// Create render pass using framebuffer params.
const int MaxAttachments = Constants.MaxRenderTargets + 1;
AttachmentDescription[] attachmentDescs = null;
var subpass = new SubpassDescription
{
PipelineBindPoint = PipelineBindPoint.Graphics,
};
AttachmentReference* attachmentReferences = stackalloc AttachmentReference[MaxAttachments];
var hasFramebuffer = fb != null;
if (hasFramebuffer && fb.AttachmentsCount != 0)
{
attachmentDescs = new AttachmentDescription[fb.AttachmentsCount];
for (int i = 0; i < fb.AttachmentsCount; i++)
{
attachmentDescs[i] = new AttachmentDescription(
0,
fb.AttachmentFormats[i],
TextureStorage.ConvertToSampleCountFlags(gd.Capabilities.SupportedSampleCounts, fb.AttachmentSamples[i]),
AttachmentLoadOp.Load,
AttachmentStoreOp.Store,
AttachmentLoadOp.Load,
AttachmentStoreOp.Store,
ImageLayout.General,
ImageLayout.General);
}
int colorAttachmentsCount = fb.ColorAttachmentsCount;
if (colorAttachmentsCount > MaxAttachments - 1)
{
colorAttachmentsCount = MaxAttachments - 1;
}
if (colorAttachmentsCount != 0)
{
int maxAttachmentIndex = fb.MaxColorAttachmentIndex;
subpass.ColorAttachmentCount = (uint)maxAttachmentIndex + 1;
subpass.PColorAttachments = &attachmentReferences[0];
// Fill with VK_ATTACHMENT_UNUSED to cover any gaps.
for (int i = 0; i <= maxAttachmentIndex; i++)
{
subpass.PColorAttachments[i] = new AttachmentReference(Vk.AttachmentUnused, ImageLayout.Undefined);
}
for (int i = 0; i < colorAttachmentsCount; i++)
{
int bindIndex = fb.AttachmentIndices[i];
subpass.PColorAttachments[bindIndex] = new AttachmentReference((uint)i, ImageLayout.General);
}
}
if (fb.HasDepthStencil)
{
uint dsIndex = (uint)fb.AttachmentsCount - 1;
subpass.PDepthStencilAttachment = &attachmentReferences[MaxAttachments - 1];
*subpass.PDepthStencilAttachment = new AttachmentReference(dsIndex, ImageLayout.General);
}
}
var subpassDependency = PipelineConverter.CreateSubpassDependency();
fixed (AttachmentDescription* pAttachmentDescs = attachmentDescs)
{
var renderPassCreateInfo = new RenderPassCreateInfo
{
SType = StructureType.RenderPassCreateInfo,
PAttachments = pAttachmentDescs,
AttachmentCount = attachmentDescs != null ? (uint)attachmentDescs.Length : 0,
PSubpasses = &subpass,
SubpassCount = 1,
PDependencies = &subpassDependency,
DependencyCount = 1,
};
gd.Api.CreateRenderPass(device, renderPassCreateInfo, null, out var renderPass).ThrowOnError();
_renderPass?.Dispose();
_renderPass = new Auto<DisposableRenderPass>(new DisposableRenderPass(gd.Api, device, renderPass));
}
_framebuffers = new HashTableSlim<FramebufferCacheKey, Auto<DisposableFramebuffer>>();
// Register this render pass with all render target views.
var textures = fb.GetAttachmentViews();
foreach (var texture in textures)
{
texture.AddRenderPass(key, this);
}
_textures = textures;
_key = key;
}
public Auto<DisposableFramebuffer> GetFramebuffer(VulkanRenderer gd, CommandBufferScoped cbs, FramebufferParams fb)
{
var key = new FramebufferCacheKey(fb.Width, fb.Height, fb.Layers);
if (!_framebuffers.TryGetValue(ref key, out Auto<DisposableFramebuffer> result))
{
result = fb.Create(gd.Api, cbs, _renderPass);
_framebuffers.Add(ref key, result);
}
return result;
}
public Auto<DisposableRenderPass> GetRenderPass()
{
return _renderPass;
}
public void Dispose()
{
// Dispose all framebuffers
foreach (var fb in _framebuffers.Values)
{
fb.Dispose();
}
// Notify all texture views that this render pass has been disposed.
foreach (var texture in _textures)
{
texture.RemoveRenderPass(_key);
}
}
}
}

View File

@@ -3,6 +3,7 @@ using Ryujinx.Graphics.GAL;
using Silk.NET.Vulkan;
using System;
using System.Collections.Generic;
using System.Linq;
using Format = Ryujinx.Graphics.GAL.Format;
using VkBuffer = Silk.NET.Vulkan.Buffer;
using VkFormat = Silk.NET.Vulkan.Format;
@@ -23,6 +24,8 @@ namespace Ryujinx.Graphics.Vulkan
private readonly TextureCreateInfo _info;
private HashTableSlim<RenderPassCacheKey, RenderPassHolder> _renderPasses;
public TextureCreateInfo Info => _info;
public TextureStorage Storage { get; }
@@ -158,6 +161,26 @@ namespace Ryujinx.Graphics.Vulkan
Valid = true;
}
/// <summary>
/// Create a texture view for an existing swapchain image view.
/// Does not set storage, so only appropriate for swapchain use.
/// </summary>
/// <remarks>Do not use this for normal textures, and make sure uses do not try to read storage.</remarks>
public TextureView(VulkanRenderer gd, Device device, DisposableImageView view, TextureCreateInfo info, VkFormat format)
{
_gd = gd;
_device = device;
_imageView = new Auto<DisposableImageView>(view);
_imageViewDraw = _imageView;
_imageViewIdentity = _imageView;
_info = info;
VkFormat = format;
Valid = true;
}
public Auto<DisposableImage> GetImage()
{
return Storage.GetImage();
@@ -939,6 +962,34 @@ namespace Ryujinx.Graphics.Vulkan
throw new NotImplementedException();
}
public (Auto<DisposableRenderPass> renderPass, Auto<DisposableFramebuffer> framebuffer) GetPassAndFramebuffer(
VulkanRenderer gd,
Device device,
CommandBufferScoped cbs,
FramebufferParams fb)
{
var key = fb.GetRenderPassCacheKey();
if (_renderPasses == null || !_renderPasses.TryGetValue(ref key, out RenderPassHolder rpHolder))
{
rpHolder = new RenderPassHolder(gd, device, key, fb);
}
return (rpHolder.GetRenderPass(), rpHolder.GetFramebuffer(gd, cbs, fb));
}
public void AddRenderPass(RenderPassCacheKey key, RenderPassHolder renderPass)
{
_renderPasses ??= new HashTableSlim<RenderPassCacheKey, RenderPassHolder>();
_renderPasses.Add(ref key, renderPass);
}
public void RemoveRenderPass(RenderPassCacheKey key)
{
_renderPasses.Remove(ref key);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
@@ -948,15 +999,29 @@ namespace Ryujinx.Graphics.Vulkan
if (_gd.Textures.Remove(this))
{
_imageView.Dispose();
_imageViewIdentity.Dispose();
_imageView2dArray?.Dispose();
if (_imageViewIdentity != _imageView)
{
_imageViewIdentity.Dispose();
}
if (_imageViewDraw != _imageViewIdentity)
{
_imageViewDraw.Dispose();
}
Storage.DecrementViewsCount();
if (_renderPasses != null)
{
var renderPasses = _renderPasses.Values.ToArray();
foreach (var pass in renderPasses)
{
pass.Dispose();
}
}
}
}
}

View File

@@ -20,7 +20,7 @@ namespace Ryujinx.Graphics.Vulkan
private SwapchainKHR _swapchain;
private Image[] _swapchainImages;
private Auto<DisposableImageView>[] _swapchainImageViews;
private TextureView[] _swapchainImageViews;
private Semaphore[] _imageAvailableSemaphores;
private Semaphore[] _renderFinishedSemaphores;
@@ -143,6 +143,23 @@ namespace Ryujinx.Graphics.Vulkan
Clipped = true,
};
var textureCreateInfo = new TextureCreateInfo(
_width,
_height,
1,
1,
1,
1,
1,
1,
FormatTable.GetFormat(surfaceFormat.Format),
DepthStencilMode.Depth,
Target.Texture2D,
SwizzleComponent.Red,
SwizzleComponent.Green,
SwizzleComponent.Blue,
SwizzleComponent.Alpha);
_gd.SwapchainApi.CreateSwapchain(_device, swapchainCreateInfo, null, out _swapchain).ThrowOnError();
_gd.SwapchainApi.GetSwapchainImages(_device, _swapchain, &imageCount, null);
@@ -154,11 +171,11 @@ namespace Ryujinx.Graphics.Vulkan
_gd.SwapchainApi.GetSwapchainImages(_device, _swapchain, &imageCount, pSwapchainImages);
}
_swapchainImageViews = new Auto<DisposableImageView>[imageCount];
_swapchainImageViews = new TextureView[imageCount];
for (int i = 0; i < _swapchainImageViews.Length; i++)
{
_swapchainImageViews[i] = CreateSwapchainImageView(_swapchainImages[i], surfaceFormat.Format);
_swapchainImageViews[i] = CreateSwapchainImageView(_swapchainImages[i], surfaceFormat.Format, textureCreateInfo);
}
var semaphoreCreateInfo = new SemaphoreCreateInfo
@@ -181,7 +198,7 @@ namespace Ryujinx.Graphics.Vulkan
}
}
private unsafe Auto<DisposableImageView> CreateSwapchainImageView(Image swapchainImage, VkFormat format)
private unsafe TextureView CreateSwapchainImageView(Image swapchainImage, VkFormat format, TextureCreateInfo info)
{
var componentMapping = new ComponentMapping(
ComponentSwizzle.R,
@@ -204,7 +221,8 @@ namespace Ryujinx.Graphics.Vulkan
};
_gd.Api.CreateImageView(_device, imageCreateInfo, null, out var imageView).ThrowOnError();
return new Auto<DisposableImageView>(new DisposableImageView(_gd.Api, _device, imageView));
return new TextureView(_gd, _device, new DisposableImageView(_gd.Api, _device, imageView), info, format);
}
private static SurfaceFormatKHR ChooseSwapSurfaceFormat(SurfaceFormatKHR[] availableFormats, bool colorSpacePassthroughEnabled)
@@ -406,7 +424,7 @@ namespace Ryujinx.Graphics.Vulkan
_scalingFilter.Run(
view,
cbs,
_swapchainImageViews[nextImage],
_swapchainImageViews[nextImage].GetImageViewForAttachment(),
_format,
_width,
_height,
@@ -421,11 +439,6 @@ namespace Ryujinx.Graphics.Vulkan
cbs,
view,
_swapchainImageViews[nextImage],
_width,
_height,
1,
_format,
false,
new Extents2D(srcX0, srcY0, srcX1, srcY1),
new Extents2D(dstX0, dstY1, dstX1, dstY0),
_isLinear,

View File

@@ -287,6 +287,10 @@ namespace Ryujinx.HLE.HOS.Services
_wakeEvent.WritableEvent.Clear();
}
}
else if (rc == KernelResult.PortRemoteClosed && signaledIndex >= 0 && SmObjectFactory != null)
{
DestroySession(handles[signaledIndex]);
}
_selfProcess.CpuMemory.Write(messagePtr + 0x0, 0);
_selfProcess.CpuMemory.Write(messagePtr + 0x4, 2 << 10);
@@ -299,6 +303,16 @@ namespace Ryujinx.HLE.HOS.Services
Dispose();
}
private void DestroySession(int serverSessionHandle)
{
_context.Syscall.CloseHandle(serverSessionHandle);
if (RemoveSessionObj(serverSessionHandle, out var session))
{
(session as IDisposable)?.Dispose();
}
}
private bool Process(int serverSessionHandle, ulong recvListAddr)
{
IpcMessage request = ReadRequest();
@@ -360,7 +374,7 @@ namespace Ryujinx.HLE.HOS.Services
response.RawData = _responseDataStream.ToArray();
}
else if (request.Type == IpcMessageType.CmifControl ||
request.Type == IpcMessageType.CmifControlWithContext)
request.Type == IpcMessageType.CmifControlWithContext)
{
#pragma warning disable IDE0059 // Remove unnecessary value assignment
uint magic = (uint)_requestDataReader.ReadUInt64();
@@ -412,11 +426,7 @@ namespace Ryujinx.HLE.HOS.Services
}
else if (request.Type == IpcMessageType.CmifCloseSession || request.Type == IpcMessageType.TipcCloseSession)
{
_context.Syscall.CloseHandle(serverSessionHandle);
if (RemoveSessionObj(serverSessionHandle, out var session))
{
(session as IDisposable)?.Dispose();
}
DestroySession(serverSessionHandle);
shouldReply = false;
}
// If the type is past 0xF, we are using TIPC

View File

@@ -427,11 +427,11 @@ namespace Ryujinx.Headless.SDL2
if (!option.DisableFileLog)
{
FileStream logFile = FileLogTarget.PrepareLogFile(AppDomain.CurrentDomain.BaseDirectory);
FileStream logFile = FileLogTarget.PrepareLogFile(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs"));
if (logFile == null)
{
logFile = FileLogTarget.PrepareLogFile(AppDataManager.BaseDirPath);
logFile = FileLogTarget.PrepareLogFile(Path.Combine(AppDataManager.BaseDirPath, "Logs"));
if (logFile == null)
{

View File

@@ -181,6 +181,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
}
Span<uint> dataWords = Span<uint>.Empty;
Span<uint> dataWordsPadded = Span<uint>.Empty;
if (meta.DataWordsCount != 0)
{
@@ -189,6 +190,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
int padding = (dataOffsetAligned - dataOffset) / sizeof(uint);
dataWords = MemoryMarshal.Cast<byte, uint>(data)[padding..meta.DataWordsCount];
dataWordsPadded = MemoryMarshal.Cast<byte, uint>(data)[..meta.DataWordsCount];
data = data[(meta.DataWordsCount * sizeof(uint))..];
}
@@ -209,6 +211,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
ReceiveBuffers = receiveBuffers,
ExchangeBuffers = exchangeBuffers,
DataWords = dataWords,
DataWordsPadded = dataWordsPadded,
ReceiveList = receiveList,
CopyHandles = copyHandles,
MoveHandles = moveHandles,

View File

@@ -9,6 +9,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
public Span<HipcBufferDescriptor> ReceiveBuffers;
public Span<HipcBufferDescriptor> ExchangeBuffers;
public Span<uint> DataWords;
public Span<uint> DataWordsPadded;
public Span<HipcReceiveListEntry> ReceiveList;
public Span<int> CopyHandles;
public Span<int> MoveHandles;

View File

@@ -206,7 +206,7 @@ namespace Ryujinx.Horizon.Sdk.Sf
}
else
{
var data = MemoryMarshal.Cast<uint, byte>(context.Request.Data.DataWords);
var data = MemoryMarshal.Cast<uint, byte>(context.Request.Data.DataWordsPadded);
var recvPointerSizes = MemoryMarshal.Cast<byte, ushort>(data[runtimeMetadata.UnfixedOutPointerSizeOffset..]);
size = recvPointerSizes[unfixedRecvPointerIndex++];

View File

@@ -511,6 +511,45 @@ namespace Ryujinx.Tests.Cpu
CompareAgainstUnicorn();
}
[Test, Pairwise, Description("VRINTR.F<size> <Sd>, <Sm>")]
[Platform(Exclude = "Linux,MacOsX")] // Instruction isn't testable due to Unicorn.
public void Vrintr([Values(0u, 1u)] uint rd,
[Values(0u, 1u)] uint rm,
[Values(2u, 3u)] uint size,
[ValueSource(nameof(_1D_F_))] ulong s0,
[ValueSource(nameof(_1D_F_))] ulong s1,
[ValueSource(nameof(_1D_F_))] ulong s2,
[Values(RMode.Rn, RMode.Rm, RMode.Rp)] RMode rMode)
{
uint opcode = 0xEEB60A40;
V128 v0, v1, v2;
if (size == 2)
{
opcode |= ((rm & 0x1e) >> 1) | ((rm & 0x1) << 5);
opcode |= ((rd & 0x1e) << 11) | ((rd & 0x1) << 22);
v0 = MakeVectorE0E1((uint)BitConverter.SingleToInt32Bits(s0), (uint)BitConverter.SingleToInt32Bits(s0));
v1 = MakeVectorE0E1((uint)BitConverter.SingleToInt32Bits(s1), (uint)BitConverter.SingleToInt32Bits(s0));
v2 = MakeVectorE0E1((uint)BitConverter.SingleToInt32Bits(s2), (uint)BitConverter.SingleToInt32Bits(s1));
}
else
{
opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1);
opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18);
v0 = MakeVectorE0E1((uint)BitConverter.DoubleToInt64Bits(s0), (uint)BitConverter.DoubleToInt64Bits(s0));
v1 = MakeVectorE0E1((uint)BitConverter.DoubleToInt64Bits(s1), (uint)BitConverter.DoubleToInt64Bits(s0));
v2 = MakeVectorE0E1((uint)BitConverter.DoubleToInt64Bits(s2), (uint)BitConverter.DoubleToInt64Bits(s1));
}
opcode |= ((size & 3) << 8);
int fpscr = (int)rMode << (int)Fpcr.RMode;
SingleOpcode(opcode, v0: v0, v1: v1, v2: v2, fpscr: fpscr);
CompareAgainstUnicorn();
}
#endif
}
}

View File

@@ -908,6 +908,44 @@ namespace Ryujinx.Tests.Cpu
CompareAgainstUnicorn();
}
[Test, Pairwise]
public void Vp_Add_Long_Accumulate([Values(0u, 2u, 4u, 8u)] uint rd,
[Values(0u, 2u, 4u, 8u)] uint rm,
[Values(0u, 1u, 2u)] uint size,
[Random(RndCnt)] ulong z,
[Random(RndCnt)] ulong a,
[Random(RndCnt)] ulong b,
[Values] bool q,
[Values] bool unsigned)
{
uint opcode = 0xF3B00600; // VPADAL.S8 D0, Q0
if (q)
{
opcode |= 1 << 6;
rm <<= 1;
rd <<= 1;
}
if (unsigned)
{
opcode |= 1 << 7;
}
opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1);
opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18);
opcode |= size << 18;
V128 v0 = MakeVectorE0E1(z, z);
V128 v1 = MakeVectorE0E1(a, z);
V128 v2 = MakeVectorE0E1(b, z);
SingleOpcode(opcode, v0: v0, v1: v1, v2: v2);
CompareAgainstUnicorn();
}
#endif
}
}

View File

@@ -9,6 +9,8 @@ namespace Ryujinx.Ui.Common.Configuration
{
public static class LoggerModule
{
public static string LogDirectoryPath { get; private set; }
public static void Initialize()
{
ConfigurationState.Instance.Logger.EnableDebug.Event += ReloadEnableDebug;
@@ -82,21 +84,26 @@ namespace Ryujinx.Ui.Common.Configuration
{
if (e.NewValue)
{
FileStream logFile = FileLogTarget.PrepareLogFile(AppDomain.CurrentDomain.BaseDirectory);
string logDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs");
FileStream logFile = FileLogTarget.PrepareLogFile(logDir);
if (logFile == null)
{
logFile = FileLogTarget.PrepareLogFile(AppDataManager.BaseDirPath);
logDir = Path.Combine(AppDataManager.BaseDirPath, "Logs");
logFile = FileLogTarget.PrepareLogFile(logDir);
if (logFile == null)
{
Logger.Error?.Print(LogClass.Application, "No writable log directory available. Make sure either the application directory or the Ryujinx directory is writable.");
LogDirectoryPath = null;
Logger.RemoveTarget("file");
return;
}
}
LogDirectoryPath = logDir;
Logger.AddTarget(new AsyncLogTargetWrapper(
new FileLogTarget("file", logFile),
1000,

View File

@@ -1378,9 +1378,9 @@ namespace Ryujinx.Ui
{
string logPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs");
if (ReleaseInformation.IsValid)
if (LoggerModule.LogDirectoryPath != null)
{
logPath = System.IO.Path.Combine(AppDataManager.BaseDirPath, "Logs");
logPath = LoggerModule.LogDirectoryPath;
}
new DirectoryInfo(logPath).Create();

View File

@@ -1,4 +1,5 @@
using Gtk;
using System;
namespace Ryujinx.Ui.Widgets
{
@@ -193,7 +194,7 @@ namespace Ryujinx.Ui.Widgets
//
_createShortcutMenuItem = new MenuItem("Create Application Shortcut")
{
TooltipText = "Create a Desktop Shortcut that launches the selected Application."
TooltipText = OperatingSystem.IsMacOS() ? "Create a shortcut in macOS's Applications folder that launches the selected Application" : "Create a Desktop Shortcut that launches the selected Application."
};
_createShortcutMenuItem.Activated += CreateShortcut_Clicked;