Compare commits

..

13 Commits

Author SHA1 Message Date
cstamford
dc0dbc50ab Add support for VK_EXT_depth_clip_control. (#5027)
* Add support for VK_EXT_depth_clip_control.

* Code review feedback

Minor formatting

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

* Check .DepthClipControl to make sure the host actually supports the feature.

* Review feedback: remove Vulkan platform switch, relying on QueryHostSupportsDepthClipControl to drive the behaviour - OpenGL returns true, and any future platforms that don't support the [-1, 1] depth mode can return false for the transformation.

---------

Co-authored-by: gdkchan <gab.dark.100@gmail.com>
2023-05-28 23:31:56 +02:00
Mary
994f4dc77d chore: Update Avalonia to 0.10.21 (#5124) 2023-05-28 23:25:55 +02:00
yell0wsuit
c9e297b74c About window: Add changelog link under ver. number (#5095) 2023-05-28 23:13:40 +02:00
Théo Arrouye
dd514a115c Update LastPlayed date on emulation end. (#5056) 2023-05-28 23:03:27 +02:00
siegmund-heiss-ich
7e0b4bd538 Improve macOS updater (#5064)
* Fix macOS Updater (once again)

* Also fix my brain's issues

* Move set -e that lsof doesn't trigger exit 1

* Resolve yesterdays brain malfunction 2

* Revert "Move set -e that lsof doesn't trigger exit 1"

This reverts commit 589a630610.

* Also check if PID exists

* Remove lsof and instead check for running processes

* Remove empty lines

* Increase max iterations

* Address feedback

* Remove obsolete check for child processes

* Update comments

* Update comments

* I swear this is the last commit

* lsof + ps check
2023-05-28 22:54:30 +02:00
Daniel Shala
378080eb87 Added Custom Path case when saving screenshots (#5086) 2023-05-28 22:44:46 +02:00
Mary
e8f5e97fa4 actions: revert timeout-minutes changes for PR workflow
Varibales aren't exposed to PRs...
2023-05-28 11:34:57 +02:00
Mary
f3873620a3 actions: Workaround YAML limitation for timeout-minutes
Because Github Actions wants an int, we use fromJSON to hack around
this.
2023-05-28 08:10:43 +02:00
TSRBerry
986ac9ff83 Use variables to configure job timeouts (#5123) 2023-05-28 08:02:30 +02:00
jhorv
42b9c1e8fe Ryujinx.Ava: fixes for random hangs on exit (#4827)
* Attempt at fixing hang on exit by ending the WindowNotificationManager notification loop, so that the Thread running it can exit.

* explicitly apply the NotificationManager template to allow the notification loop to begin

* NotificationHelper - remove explicity call to ApplyTemplate(). Change to ManualResetEventSlim so we can cancel the Wait on it.

* add a timeout to AudioRenderSystem.Stop()'s waiting for the termination signal, log a warning if this timeout occurs, and continue execution

* NotifiationHelper - cancel first, the CompleteAdding()

* Remove AudioRenderSystem._terminationEvent, redundant

* NotificationHelper - use host.Closing event to trigger cancellation instead of _notifationManager.DetachedFromLogicalTree

* Change NotificationHelper to use an explicit Thread for background work.  Wait on the cancellationToken's WaitHandle so the Thread doesn't have to deal with async. Wrap foreach in try/catch (OperationCanceledException) to swallow the escaping exception from the GetConsumingEnumerable().

* adjust formatting of AsyncWorkQueue constructor to use object initializers consistently

* use AsyncWorkQueue to do everything I added in SetNotificationManager()

* Revert "use AsyncWorkQueue to do everything I added in SetNotificationManager()"

This reverts commit f0e78366b8776ec8e2fef8ab023c0db1833155d3.

* use AsyncWorkQueue to handle the Thread-related changes previously made to NotificationHelper.SetNotificationHelper(). Wrap it in Lazy<T> and force instantiation in the TemplateApplied event handler to accomodate for the fact that AsyncWorkQueue starts immediately, and the notification dispatch loop was being delayed by _templateAppliedEvent.

* impl changes suggested by AcK77

* impl changes suggested by AcK77 (more)
2023-05-26 23:57:43 +02:00
gdkchan
3b375525fb Force reciprocal operation with value biased by constant to be precise on macOS (#5110)
* Force operations to be precise in some cases on SPIR-V

* Make it a bit more strict, add comments

* Shader cache version bump
2023-05-26 15:19:37 -03:00
gdkchan
e6658c133c Fix resolution scaling of image operation coordinates (#5102)
* Fix resolution scaling of image operation coordinates

* Shader cache version bump
2023-05-25 23:42:49 -03:00
TSRBerry
5b42a4d2c4 Fix mod names (#5088) 2023-05-25 23:41:03 +02:00
36 changed files with 260 additions and 74 deletions

View File

@@ -27,7 +27,7 @@ jobs:
build: build:
name: ${{ matrix.OS_NAME }} (${{ matrix.configuration }}) name: ${{ matrix.OS_NAME }} (${{ matrix.configuration }})
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
timeout-minutes: 35 timeout-minutes: 45
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest, macOS-latest, windows-latest] os: [ubuntu-latest, macOS-latest, windows-latest]
@@ -110,7 +110,7 @@ jobs:
build_macos: build_macos:
name: macOS Universal (${{ matrix.configuration }}) name: macOS Universal (${{ matrix.configuration }})
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 35 timeout-minutes: 45
strategy: strategy:
matrix: matrix:
configuration: [ Debug, Release ] configuration: [ Debug, Release ]

View File

@@ -12,7 +12,7 @@ concurrency: flatpak-release
jobs: jobs:
release: release:
timeout-minutes: 35 timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:

View File

@@ -7,7 +7,7 @@ jobs:
pr_comment: pr_comment:
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 35 timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }}
steps: steps:
- uses: actions/github-script@v6 - uses: actions/github-script@v6
with: with:

View File

@@ -46,7 +46,7 @@ jobs:
release: release:
name: Release ${{ matrix.OS_NAME }} name: Release ${{ matrix.OS_NAME }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
timeout-minutes: 35 timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }}
strategy: strategy:
matrix: matrix:
os: [ ubuntu-latest, windows-latest ] os: [ ubuntu-latest, windows-latest ]
@@ -144,7 +144,7 @@ jobs:
macos_release: macos_release:
name: Release MacOS universal name: Release MacOS universal
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 35 timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3

View File

@@ -3,11 +3,11 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally> <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageVersion Include="Avalonia" Version="0.10.19" /> <PackageVersion Include="Avalonia" Version="0.10.21" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="0.10.19" /> <PackageVersion Include="Avalonia.Controls.DataGrid" Version="0.10.21" />
<PackageVersion Include="Avalonia.Desktop" Version="0.10.19" /> <PackageVersion Include="Avalonia.Desktop" Version="0.10.21" />
<PackageVersion Include="Avalonia.Diagnostics" Version="0.10.19" /> <PackageVersion Include="Avalonia.Diagnostics" Version="0.10.21" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="0.10.19" /> <PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="0.10.21" />
<PackageVersion Include="Avalonia.Svg" Version="0.10.18" /> <PackageVersion Include="Avalonia.Svg" Version="0.10.18" />
<PackageVersion Include="Avalonia.Svg.Skia" Version="0.10.18" /> <PackageVersion Include="Avalonia.Svg.Skia" Version="0.10.18" />
<PackageVersion Include="CommandLineParser" Version="2.9.1" /> <PackageVersion Include="CommandLineParser" Version="2.9.1" />

View File

@@ -25,14 +25,27 @@ error_handler() {
exit 1 exit 1
} }
# Wait for Ryujinx to exit
# NOTE: in case no fds are open, lsof could be returning with a process still living.
# We wait 1s and assume the process stopped after that
lsof -p $APP_PID +r 1 &>/dev/null
sleep 1
trap 'error_handler ${LINENO}' ERR trap 'error_handler ${LINENO}' ERR
# Wait for Ryujinx to exit.
# If the main process is still acitve, we wait for 1 second and check it again.
# After the fifth time checking, this script exits with status 1.
attempt=0
while true; do
if lsof -p $APP_PID +r 1 &>/dev/null || ps -p "$APP_PID" &>/dev/null; then
if [ "$attempt" -eq 4 ]; then
exit 1
fi
sleep 1
else
break
fi
(( attempt++ ))
done
sleep 1
# Now replace and reopen. # Now replace and reopen.
rm -rf "$INSTALL_DIRECTORY" rm -rf "$INSTALL_DIRECTORY"
mv "$NEW_APP_DIRECTORY" "$INSTALL_DIRECTORY" mv "$NEW_APP_DIRECTORY" "$INSTALL_DIRECTORY"

View File

@@ -31,7 +31,6 @@ namespace Ryujinx.Audio.Renderer.Server
private AudioRendererRenderingDevice _renderingDevice; private AudioRendererRenderingDevice _renderingDevice;
private AudioRendererExecutionMode _executionMode; private AudioRendererExecutionMode _executionMode;
private IWritableEvent _systemEvent; private IWritableEvent _systemEvent;
private ManualResetEvent _terminationEvent;
private MemoryPoolState _dspMemoryPoolState; private MemoryPoolState _dspMemoryPoolState;
private VoiceContext _voiceContext; private VoiceContext _voiceContext;
private MixContext _mixContext; private MixContext _mixContext;
@@ -83,7 +82,6 @@ namespace Ryujinx.Audio.Renderer.Server
public AudioRenderSystem(AudioRendererManager manager, IWritableEvent systemEvent) public AudioRenderSystem(AudioRendererManager manager, IWritableEvent systemEvent)
{ {
_manager = manager; _manager = manager;
_terminationEvent = new ManualResetEvent(false);
_dspMemoryPoolState = MemoryPoolState.Create(MemoryPoolState.LocationType.Dsp); _dspMemoryPoolState = MemoryPoolState.Create(MemoryPoolState.LocationType.Dsp);
_voiceContext = new VoiceContext(); _voiceContext = new VoiceContext();
_mixContext = new MixContext(); _mixContext = new MixContext();
@@ -387,11 +385,6 @@ namespace Ryujinx.Audio.Renderer.Server
_isActive = false; _isActive = false;
} }
if (_executionMode == AudioRendererExecutionMode.Auto)
{
_terminationEvent.WaitOne();
}
Logger.Info?.Print(LogClass.AudioRenderer, $"Stopped renderer id {_sessionId}"); Logger.Info?.Print(LogClass.AudioRenderer, $"Stopped renderer id {_sessionId}");
} }
@@ -668,8 +661,6 @@ namespace Ryujinx.Audio.Renderer.Server
{ {
if (_isActive) if (_isActive)
{ {
_terminationEvent.Reset();
if (!_manager.Processor.HasRemainingCommands(_sessionId)) if (!_manager.Processor.HasRemainingCommands(_sessionId))
{ {
GenerateCommandList(out CommandList commands); GenerateCommandList(out CommandList commands);
@@ -686,10 +677,6 @@ namespace Ryujinx.Audio.Renderer.Server
_isDspRunningBehind = true; _isDspRunningBehind = true;
} }
} }
else
{
_terminationEvent.Set();
}
} }
} }
@@ -857,7 +844,6 @@ namespace Ryujinx.Audio.Renderer.Server
} }
_manager.Unregister(this); _manager.Unregister(this);
_terminationEvent.Dispose();
_workBufferMemoryPin.Dispose(); _workBufferMemoryPin.Dispose();
if (MemoryManager is IRefCounted rc) if (MemoryManager is IRefCounted rc)

View File

@@ -270,7 +270,7 @@ namespace Ryujinx.Ava
string directory = AppDataManager.Mode switch string directory = AppDataManager.Mode switch
{ {
AppDataManager.LaunchMode.Portable => Path.Combine(AppDataManager.BaseDirPath, "screenshots"), AppDataManager.LaunchMode.Portable or AppDataManager.LaunchMode.Custom => Path.Combine(AppDataManager.BaseDirPath, "screenshots"),
_ => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "Ryujinx") _ => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "Ryujinx")
}; };

View File

@@ -637,5 +637,7 @@
"SettingsTabNetworkInterface": "Network Interface:", "SettingsTabNetworkInterface": "Network Interface:",
"NetworkInterfaceTooltip": "The network interface used for LAN features", "NetworkInterfaceTooltip": "The network interface used for LAN features",
"NetworkInterfaceDefault": "Default", "NetworkInterfaceDefault": "Default",
"PackagingShaders": "Packaging Shaders" "PackagingShaders": "Packaging Shaders",
"AboutChangelogButton": "View Changelog on GitHub",
"AboutChangelogButtonTooltipMessage": "Click to open the changelog for this version in your default browser."
} }

View File

@@ -3,10 +3,10 @@ using Avalonia.Controls;
using Avalonia.Controls.Notifications; using Avalonia.Controls.Notifications;
using Avalonia.Threading; using Avalonia.Threading;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Common;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Helpers namespace Ryujinx.Ava.UI.Helpers
{ {
@@ -17,7 +17,6 @@ namespace Ryujinx.Ava.UI.Helpers
private static WindowNotificationManager _notificationManager; private static WindowNotificationManager _notificationManager;
private static readonly ManualResetEvent _templateAppliedEvent = new(false);
private static readonly BlockingCollection<Notification> _notifications = new(); private static readonly BlockingCollection<Notification> _notifications = new();
public static void SetNotificationManager(Window host) public static void SetNotificationManager(Window host)
@@ -29,25 +28,31 @@ namespace Ryujinx.Ava.UI.Helpers
Margin = new Thickness(0, 0, 15, 40) Margin = new Thickness(0, 0, 15, 40)
}; };
_notificationManager.TemplateApplied += (sender, args) => var maybeAsyncWorkQueue = new Lazy<AsyncWorkQueue<Notification>>(
{ () => new AsyncWorkQueue<Notification>(notification =>
_templateAppliedEvent.Set();
};
Task.Run(async () =>
{
_templateAppliedEvent.WaitOne();
foreach (var notification in _notifications.GetConsumingEnumerable())
{ {
Dispatcher.UIThread.Post(() => Dispatcher.UIThread.Post(() =>
{ {
_notificationManager.Show(notification); _notificationManager.Show(notification);
}); });
},
"UI.NotificationThread",
_notifications),
LazyThreadSafetyMode.ExecutionAndPublication);
await Task.Delay(NotificationDelayInMs / MaxNotifications); _notificationManager.TemplateApplied += (sender, args) =>
{
// NOTE: Force creation of the AsyncWorkQueue.
_ = maybeAsyncWorkQueue.Value;
};
host.Closing += (sender, args) =>
{
if (maybeAsyncWorkQueue.IsValueCreated)
{
maybeAsyncWorkQueue.Value.Dispose();
} }
}); };
} }
public static void Show(string title, string text, NotificationType type, bool waitingExit = false, Action onClick = null, Action onClose = null) public static void Show(string title, string text, NotificationType type, bool waitingExit = false, Action onClick = null, Action onClose = null)

View File

@@ -1529,6 +1529,8 @@ namespace Ryujinx.Ava.UI.ViewModels
double sessionTimePlayed = DateTime.UtcNow.Subtract(appMetadata.LastPlayed.Value).TotalSeconds; double sessionTimePlayed = DateTime.UtcNow.Subtract(appMetadata.LastPlayed.Value).TotalSeconds;
appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero); appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
} }
appMetadata.LastPlayed = DateTime.UtcNow;
}); });
} }

View File

@@ -72,6 +72,18 @@
LineHeight="12" LineHeight="12"
Text="{Binding Version}" Text="{Binding Version}"
TextAlignment="Center" /> TextAlignment="Center" />
<Button
Padding="5"
HorizontalAlignment="Center"
Background="Transparent"
Click="Button_OnClick"
Tag="https://github.com/Ryujinx/Ryujinx/wiki/Changelog#ryujinx-changelog">
<TextBlock
FontSize="10"
Text="{locale:Locale AboutChangelogButton}"
TextAlignment="Center"
ToolTip.Tip="{locale:Locale AboutChangelogButtonTooltipMessage}" />
</Button>
</StackPanel> </StackPanel>
<StackPanel <StackPanel
Grid.Row="2" Grid.Row="2"

View File

@@ -22,9 +22,11 @@ namespace Ryujinx.Common
_cts = new CancellationTokenSource(); _cts = new CancellationTokenSource();
_queue = collection; _queue = collection;
_workerAction = callback; _workerAction = callback;
_workerThread = new Thread(DoWork) { Name = name }; _workerThread = new Thread(DoWork)
{
_workerThread.IsBackground = true; Name = name,
IsBackground = true
};
_workerThread.Start(); _workerThread.Start();
} }

View File

@@ -39,6 +39,7 @@ namespace Ryujinx.Graphics.GAL
public readonly bool SupportsViewportMask; public readonly bool SupportsViewportMask;
public readonly bool SupportsViewportSwizzle; public readonly bool SupportsViewportSwizzle;
public readonly bool SupportsIndirectParameters; public readonly bool SupportsIndirectParameters;
public readonly bool SupportsDepthClipControl;
public readonly uint MaximumUniformBuffersPerStage; public readonly uint MaximumUniformBuffersPerStage;
public readonly uint MaximumStorageBuffersPerStage; public readonly uint MaximumStorageBuffersPerStage;
@@ -85,6 +86,7 @@ namespace Ryujinx.Graphics.GAL
bool supportsViewportMask, bool supportsViewportMask,
bool supportsViewportSwizzle, bool supportsViewportSwizzle,
bool supportsIndirectParameters, bool supportsIndirectParameters,
bool supportsDepthClipControl,
uint maximumUniformBuffersPerStage, uint maximumUniformBuffersPerStage,
uint maximumStorageBuffersPerStage, uint maximumStorageBuffersPerStage,
uint maximumTexturesPerStage, uint maximumTexturesPerStage,
@@ -127,6 +129,7 @@ namespace Ryujinx.Graphics.GAL
SupportsViewportMask = supportsViewportMask; SupportsViewportMask = supportsViewportMask;
SupportsViewportSwizzle = supportsViewportSwizzle; SupportsViewportSwizzle = supportsViewportSwizzle;
SupportsIndirectParameters = supportsIndirectParameters; SupportsIndirectParameters = supportsIndirectParameters;
SupportsDepthClipControl = supportsDepthClipControl;
MaximumUniformBuffersPerStage = maximumUniformBuffersPerStage; MaximumUniformBuffersPerStage = maximumUniformBuffersPerStage;
MaximumStorageBuffersPerStage = maximumStorageBuffersPerStage; MaximumStorageBuffersPerStage = maximumStorageBuffersPerStage;
MaximumTexturesPerStage = maximumTexturesPerStage; MaximumTexturesPerStage = maximumTexturesPerStage;

View File

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

View File

@@ -165,6 +165,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
public bool QueryHostSupportsViewportMask() => _context.Capabilities.SupportsViewportMask; public bool QueryHostSupportsViewportMask() => _context.Capabilities.SupportsViewportMask;
public bool QueryHostSupportsDepthClipControl() => _context.Capabilities.SupportsDepthClipControl;
/// <summary> /// <summary>
/// Converts a packed Maxwell texture format to the shader translator texture format. /// Converts a packed Maxwell texture format to the shader translator texture format.
/// </summary> /// </summary>

View File

@@ -163,6 +163,7 @@ namespace Ryujinx.Graphics.OpenGL
supportsViewportMask: HwCapabilities.SupportsViewportArray2, supportsViewportMask: HwCapabilities.SupportsViewportArray2,
supportsViewportSwizzle: HwCapabilities.SupportsViewportSwizzle, supportsViewportSwizzle: HwCapabilities.SupportsViewportSwizzle,
supportsIndirectParameters: HwCapabilities.SupportsIndirectParameters, supportsIndirectParameters: HwCapabilities.SupportsIndirectParameters,
supportsDepthClipControl: true,
maximumUniformBuffersPerStage: 13, // TODO: Avoid hardcoding those limits here and get from driver? maximumUniformBuffersPerStage: 13, // TODO: Avoid hardcoding those limits here and get from driver?
maximumStorageBuffersPerStage: 16, maximumStorageBuffersPerStage: 16,
maximumTexturesPerStage: 32, maximumTexturesPerStage: 32,

View File

@@ -2245,7 +2245,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{ {
var result = emitF(context.TypeFP64(), context.GetFP64(src1), context.GetFP64(src2)); var result = emitF(context.TypeFP64(), context.GetFP64(src1), context.GetFP64(src2));
if (!context.Config.GpuAccessor.QueryHostReducedPrecision()) if (!context.Config.GpuAccessor.QueryHostReducedPrecision() || operation.ForcePrecise)
{ {
context.Decorate(result, Decoration.NoContraction); context.Decorate(result, Decoration.NoContraction);
} }
@@ -2256,7 +2256,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{ {
var result = emitF(context.TypeFP32(), context.GetFP32(src1), context.GetFP32(src2)); var result = emitF(context.TypeFP32(), context.GetFP32(src1), context.GetFP32(src2));
if (!context.Config.GpuAccessor.QueryHostReducedPrecision()) if (!context.Config.GpuAccessor.QueryHostReducedPrecision() || operation.ForcePrecise)
{ {
context.Decorate(result, Decoration.NoContraction); context.Decorate(result, Decoration.NoContraction);
} }
@@ -2316,7 +2316,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{ {
var result = emitF(context.TypeFP64(), context.GetFP64(src1), context.GetFP64(src2), context.GetFP64(src3)); var result = emitF(context.TypeFP64(), context.GetFP64(src1), context.GetFP64(src2), context.GetFP64(src3));
if (!context.Config.GpuAccessor.QueryHostReducedPrecision()) if (!context.Config.GpuAccessor.QueryHostReducedPrecision() || operation.ForcePrecise)
{ {
context.Decorate(result, Decoration.NoContraction); context.Decorate(result, Decoration.NoContraction);
} }
@@ -2327,7 +2327,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{ {
var result = emitF(context.TypeFP32(), context.GetFP32(src1), context.GetFP32(src2), context.GetFP32(src3)); var result = emitF(context.TypeFP32(), context.GetFP32(src1), context.GetFP32(src2), context.GetFP32(src3));
if (!context.Config.GpuAccessor.QueryHostReducedPrecision()) if (!context.Config.GpuAccessor.QueryHostReducedPrecision() || operation.ForcePrecise)
{ {
context.Decorate(result, Decoration.NoContraction); context.Decorate(result, Decoration.NoContraction);
} }

View File

@@ -367,6 +367,15 @@ namespace Ryujinx.Graphics.Shader
return true; return true;
} }
/// <summary>
/// Queries whether the host supports depth clip control.
/// </summary>
/// <returns>True if the GPU and driver supports depth clip control, false otherwise</returns>
bool QueryHostSupportsDepthClipControl()
{
return true;
}
/// <summary> /// <summary>
/// Queries the point size from the GPU state, used when it is not explicitly set on the shader. /// Queries the point size from the GPU state, used when it is not explicitly set on the shader.
/// </summary> /// </summary>

View File

@@ -8,6 +8,8 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
public Instruction Inst { get; private set; } public Instruction Inst { get; private set; }
public StorageKind StorageKind { get; } public StorageKind StorageKind { get; }
public bool ForcePrecise { get; set; }
private Operand[] _dests; private Operand[] _dests;
public Operand Dest public Operand Dest

View File

@@ -10,6 +10,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
{ {
public Instruction Inst { get; } public Instruction Inst { get; }
public StorageKind StorageKind { get; } public StorageKind StorageKind { get; }
public bool ForcePrecise { get; }
public int Index { get; } public int Index { get; }
@@ -17,10 +18,11 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
public int SourcesCount => _sources.Length; public int SourcesCount => _sources.Length;
public AstOperation(Instruction inst, StorageKind storageKind, IAstNode[] sources, int sourcesCount) public AstOperation(Instruction inst, StorageKind storageKind, bool forcePrecise, IAstNode[] sources, int sourcesCount)
{ {
Inst = inst; Inst = inst;
StorageKind = storageKind; StorageKind = storageKind;
ForcePrecise = forcePrecise;
_sources = sources; _sources = sources;
for (int index = 0; index < sources.Length; index++) for (int index = 0; index < sources.Length; index++)
@@ -38,12 +40,18 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
Index = 0; Index = 0;
} }
public AstOperation(Instruction inst, StorageKind storageKind, int index, IAstNode[] sources, int sourcesCount) : this(inst, storageKind, sources, sourcesCount) public AstOperation(
Instruction inst,
StorageKind storageKind,
bool forcePrecise,
int index,
IAstNode[] sources,
int sourcesCount) : this(inst, storageKind, forcePrecise, sources, sourcesCount)
{ {
Index = index; Index = index;
} }
public AstOperation(Instruction inst, params IAstNode[] sources) : this(inst, StorageKind.None, sources, sources.Length) public AstOperation(Instruction inst, params IAstNode[] sources) : this(inst, StorageKind.None, false, sources, sources.Length)
{ {
} }

View File

@@ -19,7 +19,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
int cbufSlot, int cbufSlot,
int handle, int handle,
int index, int index,
params IAstNode[] sources) : base(inst, StorageKind.None, index, sources, sources.Length) params IAstNode[] sources) : base(inst, StorageKind.None, false, index, sources, sources.Length)
{ {
Type = type; Type = type;
Format = format; Format = format;

View File

@@ -156,7 +156,13 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
} }
else else
{ {
source = new AstOperation(inst, operation.StorageKind, operation.Index, sources, operation.SourcesCount); source = new AstOperation(
inst,
operation.StorageKind,
operation.ForcePrecise,
operation.Index,
sources,
operation.SourcesCount);
} }
AggregateType destElemType = destType; AggregateType destElemType = destType;
@@ -179,7 +185,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
dest.VarType = destElemType; dest.VarType = destElemType;
context.AddNode(new AstAssignment(dest, new AstOperation(Instruction.VectorExtract, StorageKind.None, new[] { destVec, index }, 2))); context.AddNode(new AstAssignment(dest, new AstOperation(Instruction.VectorExtract, StorageKind.None, false, new[] { destVec, index }, 2)));
} }
} }
else if (operation.Dest != null) else if (operation.Dest != null)
@@ -227,7 +233,13 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
} }
else if (!isCopy) else if (!isCopy)
{ {
source = new AstOperation(inst, operation.StorageKind, operation.Index, sources, operation.SourcesCount); source = new AstOperation(
inst,
operation.StorageKind,
operation.ForcePrecise,
operation.Index,
sources,
operation.SourcesCount);
} }
else else
{ {
@@ -248,7 +260,13 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
} }
else else
{ {
context.AddNode(new AstOperation(inst, operation.StorageKind, operation.Index, sources, operation.SourcesCount)); context.AddNode(new AstOperation(
inst,
operation.StorageKind,
operation.ForcePrecise,
operation.Index,
sources,
operation.SourcesCount));
} }
// Those instructions needs to be emulated by using helper functions, // Those instructions needs to be emulated by using helper functions,

View File

@@ -319,7 +319,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
new AstOperand(OperandType.Constant, elemIndex) new AstOperand(OperandType.Constant, elemIndex)
}; };
return new AstOperation(Instruction.Load, StorageKind.ConstantBuffer, sources, sources.Length); return new AstOperation(Instruction.Load, StorageKind.ConstantBuffer, false, sources, sources.Length);
} }
return GetOperand(operand); return GetOperand(operand);

View File

@@ -246,7 +246,7 @@ namespace Ryujinx.Graphics.Shader.Translation
this.Store(StorageKind.Output, IoVariable.Position, null, Const(1), this.FPFusedMultiplyAdd(y, yScale, negativeOne)); this.Store(StorageKind.Output, IoVariable.Position, null, Const(1), this.FPFusedMultiplyAdd(y, yScale, negativeOne));
} }
if (Config.Options.TargetApi == TargetApi.Vulkan && Config.GpuAccessor.QueryTransformDepthMinusOneToOne()) if (Config.GpuAccessor.QueryTransformDepthMinusOneToOne() && !Config.GpuAccessor.QueryHostSupportsDepthClipControl())
{ {
Operand z = this.Load(StorageKind.Output, IoVariable.Position, null, Const(2)); Operand z = this.Load(StorageKind.Output, IoVariable.Position, null, Const(2));
Operand w = this.Load(StorageKind.Output, IoVariable.Position, null, Const(3)); Operand w = this.Load(StorageKind.Output, IoVariable.Position, null, Const(3));
@@ -283,7 +283,7 @@ namespace Ryujinx.Graphics.Shader.Translation
oldYLocal = null; oldYLocal = null;
} }
if (Config.Options.TargetApi == TargetApi.Vulkan && Config.GpuAccessor.QueryTransformDepthMinusOneToOne()) if (Config.GpuAccessor.QueryTransformDepthMinusOneToOne() && !Config.GpuAccessor.QueryHostSupportsDepthClipControl())
{ {
oldZLocal = Local(); oldZLocal = Local();
this.Copy(oldZLocal, this.Load(StorageKind.Output, IoVariable.Position, null, Const(2))); this.Copy(oldZLocal, this.Load(StorageKind.Output, IoVariable.Position, null, Const(2)));

View File

@@ -14,6 +14,7 @@ namespace Ryujinx.Graphics.Shader.Translation
public static void RunPass(HelperFunctionManager hfm, BasicBlock[] blocks, ShaderConfig config) public static void RunPass(HelperFunctionManager hfm, BasicBlock[] blocks, ShaderConfig config)
{ {
bool isVertexShader = config.Stage == ShaderStage.Vertex; bool isVertexShader = config.Stage == ShaderStage.Vertex;
bool isImpreciseFragmentShader = config.Stage == ShaderStage.Fragment && config.GpuAccessor.QueryHostReducedPrecision();
bool hasConstantBufferDrawParameters = config.GpuAccessor.QueryHasConstantBufferDrawParameters(); bool hasConstantBufferDrawParameters = config.GpuAccessor.QueryHasConstantBufferDrawParameters();
bool hasVectorIndexingBug = config.GpuAccessor.QueryHostHasVectorIndexingBug(); bool hasVectorIndexingBug = config.GpuAccessor.QueryHostHasVectorIndexingBug();
bool supportsSnormBufferTextureFormat = config.GpuAccessor.QueryHostSupportsSnormBufferTextureFormat(); bool supportsSnormBufferTextureFormat = config.GpuAccessor.QueryHostSupportsSnormBufferTextureFormat();
@@ -45,6 +46,11 @@ namespace Ryujinx.Graphics.Shader.Translation
} }
} }
if (isImpreciseFragmentShader)
{
EnableForcePreciseIfNeeded(operation);
}
if (hasVectorIndexingBug) if (hasVectorIndexingBug)
{ {
InsertVectorComponentSelect(node, config); InsertVectorComponentSelect(node, config);
@@ -81,6 +87,25 @@ namespace Ryujinx.Graphics.Shader.Translation
} }
} }
private static void EnableForcePreciseIfNeeded(Operation operation)
{
// There are some cases where a small bias is added to values to prevent division by zero.
// When operating with reduced precision, it is possible for this bias to get rounded to 0
// and cause a division by zero.
// To prevent that, we force those operations to be precise even if the host wants
// imprecise operations for performance.
if (operation.Inst == (Instruction.FP32 | Instruction.Divide) &&
operation.GetSource(0).Type == OperandType.Constant &&
operation.GetSource(0).AsFloat() == 1f &&
operation.GetSource(1).AsgOp is Operation addOp &&
addOp.Inst == (Instruction.FP32 | Instruction.Add) &&
addOp.GetSource(1).Type == OperandType.Constant)
{
addOp.ForcePrecise = true;
}
}
private static void InsertVectorComponentSelect(LinkedListNode<INode> node, ShaderConfig config) private static void InsertVectorComponentSelect(LinkedListNode<INode> node, ShaderConfig config)
{ {
Operation operation = (Operation)node.Value; Operation operation = (Operation)node.Value;
@@ -366,7 +391,7 @@ namespace Ryujinx.Graphics.Shader.Translation
bool isImage = IsImageInstructionWithScale(texOp.Inst); bool isImage = IsImageInstructionWithScale(texOp.Inst);
if ((texOp.Inst == Instruction.TextureSample || isImage) && if ((texOp.Inst == Instruction.TextureSample || isImage) &&
intCoords && (intCoords || isImage) &&
!isBindless && !isBindless &&
!isIndexed && !isIndexed &&
config.Stage.SupportsRenderScale() && config.Stage.SupportsRenderScale() &&

View File

@@ -43,6 +43,7 @@ namespace Ryujinx.Graphics.Vulkan
public readonly bool SupportsGeometryShader; public readonly bool SupportsGeometryShader;
public readonly bool SupportsViewportArray2; public readonly bool SupportsViewportArray2;
public readonly bool SupportsHostImportedMemory; public readonly bool SupportsHostImportedMemory;
public readonly bool SupportsDepthClipControl;
public readonly uint MinSubgroupSize; public readonly uint MinSubgroupSize;
public readonly uint MaxSubgroupSize; public readonly uint MaxSubgroupSize;
public readonly ShaderStageFlags RequiredSubgroupSizeStages; public readonly ShaderStageFlags RequiredSubgroupSizeStages;
@@ -79,6 +80,7 @@ namespace Ryujinx.Graphics.Vulkan
bool supportsGeometryShader, bool supportsGeometryShader,
bool supportsViewportArray2, bool supportsViewportArray2,
bool supportsHostImportedMemory, bool supportsHostImportedMemory,
bool supportsDepthClipControl,
uint minSubgroupSize, uint minSubgroupSize,
uint maxSubgroupSize, uint maxSubgroupSize,
ShaderStageFlags requiredSubgroupSizeStages, ShaderStageFlags requiredSubgroupSizeStages,
@@ -114,6 +116,7 @@ namespace Ryujinx.Graphics.Vulkan
SupportsGeometryShader = supportsGeometryShader; SupportsGeometryShader = supportsGeometryShader;
SupportsViewportArray2 = supportsViewportArray2; SupportsViewportArray2 = supportsViewportArray2;
SupportsHostImportedMemory = supportsHostImportedMemory; SupportsHostImportedMemory = supportsHostImportedMemory;
SupportsDepthClipControl = supportsDepthClipControl;
MinSubgroupSize = minSubgroupSize; MinSubgroupSize = minSubgroupSize;
MaxSubgroupSize = maxSubgroupSize; MaxSubgroupSize = maxSubgroupSize;
RequiredSubgroupSizeStages = requiredSubgroupSizeStages; RequiredSubgroupSizeStages = requiredSubgroupSizeStages;

View File

@@ -813,8 +813,12 @@ namespace Ryujinx.Graphics.Vulkan
public void SetDepthMode(DepthMode mode) public void SetDepthMode(DepthMode mode)
{ {
// Currently this is emulated on the shader, because Vulkan had no support for changing the depth mode. bool oldMode = _newState.DepthMode;
// In the future, we may want to use the VK_EXT_depth_clip_control extension to change it here. _newState.DepthMode = mode == DepthMode.MinusOneToOne;
if (_newState.DepthMode != oldMode)
{
SignalStateChange();
}
} }
public void SetDepthTest(DepthTestDescriptor depthTest) public void SetDepthTest(DepthTestDescriptor depthTest)

View File

@@ -304,6 +304,12 @@ namespace Ryujinx.Graphics.Vulkan
set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFFFFFFCF) | ((ulong)value << 4); set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFFFFFFCF) | ((ulong)value << 4);
} }
public bool DepthMode
{
get => ((Internal.Id9 >> 6) & 0x1) != 0UL;
set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFFFFFFBF) | ((value ? 1UL : 0UL) << 6);
}
public NativeArray<PipelineShaderStageCreateInfo> Stages; public NativeArray<PipelineShaderStageCreateInfo> Stages;
public NativeArray<PipelineShaderStageRequiredSubgroupSizeCreateInfoEXT> StageRequiredSubgroupSizes; public NativeArray<PipelineShaderStageRequiredSubgroupSizeCreateInfoEXT> StageRequiredSubgroupSizes;
public PipelineLayout PipelineLayout; public PipelineLayout PipelineLayout;
@@ -331,6 +337,7 @@ namespace Ryujinx.Graphics.Vulkan
LineWidth = 1f; LineWidth = 1f;
SamplesCount = 1; SamplesCount = 1;
DepthMode = true;
} }
public unsafe Auto<DisposablePipeline> CreateComputePipeline( public unsafe Auto<DisposablePipeline> CreateComputePipeline(
@@ -482,6 +489,17 @@ namespace Ryujinx.Graphics.Vulkan
PScissors = pScissors PScissors = pScissors
}; };
if (gd.Capabilities.SupportsDepthClipControl)
{
var viewportDepthClipControlState = new PipelineViewportDepthClipControlCreateInfoEXT()
{
SType = StructureType.PipelineViewportDepthClipControlCreateInfoExt,
NegativeOneToOne = DepthMode
};
viewportState.PNext = &viewportDepthClipControlState;
}
var multisampleState = new PipelineMultisampleStateCreateInfo var multisampleState = new PipelineMultisampleStateCreateInfo
{ {
SType = StructureType.PipelineMultisampleStateCreateInfo, SType = StructureType.PipelineMultisampleStateCreateInfo,

View File

@@ -41,6 +41,7 @@ namespace Ryujinx.Graphics.Vulkan
"VK_EXT_subgroup_size_control", "VK_EXT_subgroup_size_control",
"VK_NV_geometry_shader_passthrough", "VK_NV_geometry_shader_passthrough",
"VK_NV_viewport_array2", "VK_NV_viewport_array2",
"VK_EXT_depth_clip_control",
"VK_KHR_portability_subset" // As per spec, we should enable this if present. "VK_KHR_portability_subset" // As per spec, we should enable this if present.
}; };
@@ -345,6 +346,17 @@ namespace Ryujinx.Graphics.Vulkan
features2.PNext = &supportedFeaturesRobustness2; features2.PNext = &supportedFeaturesRobustness2;
} }
PhysicalDeviceDepthClipControlFeaturesEXT supportedFeaturesDepthClipControl = new PhysicalDeviceDepthClipControlFeaturesEXT()
{
SType = StructureType.PhysicalDeviceDepthClipControlFeaturesExt,
PNext = features2.PNext
};
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_depth_clip_control"))
{
features2.PNext = &supportedFeaturesDepthClipControl;
}
api.GetPhysicalDeviceFeatures2(physicalDevice.PhysicalDevice, &features2); api.GetPhysicalDeviceFeatures2(physicalDevice.PhysicalDevice, &features2);
var supportedFeatures = features2.Features; var supportedFeatures = features2.Features;
@@ -507,6 +519,21 @@ namespace Ryujinx.Graphics.Vulkan
pExtendedFeatures = &featuresCustomBorderColor; pExtendedFeatures = &featuresCustomBorderColor;
} }
PhysicalDeviceDepthClipControlFeaturesEXT featuresDepthClipControl;
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_depth_clip_control") &&
supportedFeaturesDepthClipControl.DepthClipControl)
{
featuresDepthClipControl = new PhysicalDeviceDepthClipControlFeaturesEXT()
{
SType = StructureType.PhysicalDeviceDepthClipControlFeaturesExt,
PNext = pExtendedFeatures,
DepthClipControl = true
};
pExtendedFeatures = &featuresDepthClipControl;
}
var enabledExtensions = _requiredExtensions.Union(_desirableExtensions.Intersect(physicalDevice.DeviceExtensions)).ToArray(); var enabledExtensions = _requiredExtensions.Union(_desirableExtensions.Intersect(physicalDevice.DeviceExtensions)).ToArray();
IntPtr* ppEnabledExtensions = stackalloc IntPtr[enabledExtensions.Length]; IntPtr* ppEnabledExtensions = stackalloc IntPtr[enabledExtensions.Length];

View File

@@ -216,6 +216,11 @@ namespace Ryujinx.Graphics.Vulkan
SType = StructureType.PhysicalDeviceCustomBorderColorFeaturesExt SType = StructureType.PhysicalDeviceCustomBorderColorFeaturesExt
}; };
PhysicalDeviceDepthClipControlFeaturesEXT featuresDepthClipControl = new PhysicalDeviceDepthClipControlFeaturesEXT()
{
SType = StructureType.PhysicalDeviceDepthClipControlFeaturesExt
};
PhysicalDevicePortabilitySubsetFeaturesKHR featuresPortabilitySubset = new PhysicalDevicePortabilitySubsetFeaturesKHR() PhysicalDevicePortabilitySubsetFeaturesKHR featuresPortabilitySubset = new PhysicalDevicePortabilitySubsetFeaturesKHR()
{ {
SType = StructureType.PhysicalDevicePortabilitySubsetFeaturesKhr SType = StructureType.PhysicalDevicePortabilitySubsetFeaturesKhr
@@ -244,6 +249,14 @@ namespace Ryujinx.Graphics.Vulkan
features2.PNext = &featuresCustomBorderColor; features2.PNext = &featuresCustomBorderColor;
} }
bool supportsDepthClipControl = _physicalDevice.IsDeviceExtensionPresent("VK_EXT_depth_clip_control");
if (supportsDepthClipControl)
{
featuresDepthClipControl.PNext = features2.PNext;
features2.PNext = &featuresDepthClipControl;
}
bool usePortability = _physicalDevice.IsDeviceExtensionPresent("VK_KHR_portability_subset"); bool usePortability = _physicalDevice.IsDeviceExtensionPresent("VK_KHR_portability_subset");
if (usePortability) if (usePortability)
@@ -310,6 +323,7 @@ namespace Ryujinx.Graphics.Vulkan
_physicalDevice.PhysicalDeviceFeatures.GeometryShader, _physicalDevice.PhysicalDeviceFeatures.GeometryShader,
_physicalDevice.IsDeviceExtensionPresent("VK_NV_viewport_array2"), _physicalDevice.IsDeviceExtensionPresent("VK_NV_viewport_array2"),
_physicalDevice.IsDeviceExtensionPresent(ExtExternalMemoryHost.ExtensionName), _physicalDevice.IsDeviceExtensionPresent(ExtExternalMemoryHost.ExtensionName),
supportsDepthClipControl && featuresDepthClipControl.DepthClipControl,
propertiesSubgroupSizeControl.MinSubgroupSize, propertiesSubgroupSizeControl.MinSubgroupSize,
propertiesSubgroupSizeControl.MaxSubgroupSize, propertiesSubgroupSizeControl.MaxSubgroupSize,
propertiesSubgroupSizeControl.RequiredSubgroupSizeStages, propertiesSubgroupSizeControl.RequiredSubgroupSizeStages,
@@ -585,6 +599,7 @@ namespace Ryujinx.Graphics.Vulkan
supportsViewportMask: Capabilities.SupportsViewportArray2, supportsViewportMask: Capabilities.SupportsViewportArray2,
supportsViewportSwizzle: false, supportsViewportSwizzle: false,
supportsIndirectParameters: true, supportsIndirectParameters: true,
supportsDepthClipControl: Capabilities.SupportsDepthClipControl,
maximumUniformBuffersPerStage: Constants.MaxUniformBuffersPerStage, maximumUniformBuffersPerStage: Constants.MaxUniformBuffersPerStage,
maximumStorageBuffersPerStage: Constants.MaxStorageBuffersPerStage, maximumStorageBuffersPerStage: Constants.MaxStorageBuffersPerStage,
maximumTexturesPerStage: Constants.MaxTexturesPerStage, maximumTexturesPerStage: Constants.MaxTexturesPerStage,

View File

@@ -167,12 +167,12 @@ namespace Ryujinx.HLE.HOS
if (StrEquals(RomfsDir, modDir.Name)) if (StrEquals(RomfsDir, modDir.Name))
{ {
mods.RomfsDirs.Add(mod = new Mod<DirectoryInfo>($"<{titleId} RomFs>", modDir)); mods.RomfsDirs.Add(mod = new Mod<DirectoryInfo>(dir.Name, modDir));
types.Append('R'); types.Append('R');
} }
else if (StrEquals(ExefsDir, modDir.Name)) else if (StrEquals(ExefsDir, modDir.Name))
{ {
mods.ExefsDirs.Add(mod = new Mod<DirectoryInfo>($"<{titleId} ExeFs>", modDir)); mods.ExefsDirs.Add(mod = new Mod<DirectoryInfo>(dir.Name, modDir));
types.Append('E'); types.Append('E');
} }
else if (StrEquals(CheatDir, modDir.Name)) else if (StrEquals(CheatDir, modDir.Name))

View File

@@ -1024,6 +1024,8 @@ namespace Ryujinx.Ui
double sessionTimePlayed = DateTime.UtcNow.Subtract(appMetadata.LastPlayed.Value).TotalSeconds; double sessionTimePlayed = DateTime.UtcNow.Subtract(appMetadata.LastPlayed.Value).TotalSeconds;
appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero); appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
} }
appMetadata.LastPlayed = DateTime.UtcNow;
}); });
} }
} }

View File

@@ -381,7 +381,7 @@ namespace Ryujinx.Ui
string filename = $"ryujinx_capture_{currentTime.Year}-{currentTime.Month:D2}-{currentTime.Day:D2}_{currentTime.Hour:D2}-{currentTime.Minute:D2}-{currentTime.Second:D2}.png"; string filename = $"ryujinx_capture_{currentTime.Year}-{currentTime.Month:D2}-{currentTime.Day:D2}_{currentTime.Hour:D2}-{currentTime.Minute:D2}-{currentTime.Second:D2}.png";
string directory = AppDataManager.Mode switch string directory = AppDataManager.Mode switch
{ {
AppDataManager.LaunchMode.Portable => System.IO.Path.Combine(AppDataManager.BaseDirPath, "screenshots"), AppDataManager.LaunchMode.Portable or AppDataManager.LaunchMode.Custom => System.IO.Path.Combine(AppDataManager.BaseDirPath, "screenshots"),
_ => System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "Ryujinx") _ => System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "Ryujinx")
}; };

View File

@@ -48,6 +48,8 @@ namespace Ryujinx.Ui.Windows
private Label _patreonNamesLabel; private Label _patreonNamesLabel;
private ScrolledWindow _patreonNamesScrolled; private ScrolledWindow _patreonNamesScrolled;
private TextView _patreonNamesText; private TextView _patreonNamesText;
private EventBox _changelogEventBox;
private Label _changelogLinkLabel;
private void InitializeComponent() private void InitializeComponent()
{ {
@@ -148,6 +150,23 @@ namespace Ryujinx.Ui.Windows
Margin = 5 Margin = 5
}; };
//
// _changelogEventBox
//
_changelogEventBox = new EventBox();
_changelogEventBox.ButtonPressEvent += ChangelogButton_Pressed;
//
// _changelogLinkLabel
//
_changelogLinkLabel = new Label("View Changelog on GitHub")
{
TooltipText = "Click to open the changelog for this version in your default browser.",
Justify = Justification.Center,
Attributes = new AttrList()
};
_changelogLinkLabel.Attributes.Insert(new Pango.AttrUnderline(Underline.Single));
// //
// _disclaimerLabel // _disclaimerLabel
// //
@@ -464,8 +483,11 @@ namespace Ryujinx.Ui.Windows
_socialBox.Add(_discordEventBox); _socialBox.Add(_discordEventBox);
_socialBox.Add(_twitterEventBox); _socialBox.Add(_twitterEventBox);
_changelogEventBox.Add(_changelogLinkLabel);
_leftBox.Add(_logoBox); _leftBox.Add(_logoBox);
_leftBox.Add(_versionLabel); _leftBox.Add(_versionLabel);
_leftBox.Add(_changelogEventBox);
_leftBox.Add(_disclaimerLabel); _leftBox.Add(_disclaimerLabel);
_leftBox.Add(_amiiboApiLink); _leftBox.Add(_amiiboApiLink);
_leftBox.Add(_socialBox); _leftBox.Add(_socialBox);

View File

@@ -76,5 +76,10 @@ namespace Ryujinx.Ui.Windows
{ {
OpenHelper.OpenUrl("https://github.com/Ryujinx/Ryujinx/graphs/contributors?type=a"); OpenHelper.OpenUrl("https://github.com/Ryujinx/Ryujinx/graphs/contributors?type=a");
} }
private void ChangelogButton_Pressed(object sender, ButtonPressEventArgs args)
{
OpenHelper.OpenUrl("https://github.com/Ryujinx/Ryujinx/wiki/Changelog#ryujinx-changelog");
}
} }
} }