Compare commits

...

18 Commits

Author SHA1 Message Date
sunshineinabox
4352cf80cf Merge 48f9ada59b into c06f16c5e6 2025-01-23 22:27:45 -06:00
Evan Husted
c06f16c5e6 infra: chore: Raise minimum required Windows 10 version
Inspired by the breakages covered in #409
2025-01-23 17:39:34 -06:00
Evan Husted
7829fd8ee7 misc: chore: OS + CPU arch helpers 2025-01-23 16:58:48 -06:00
Evan Husted
33079422fe misc: chore: code cleanups 2025-01-23 16:47:11 -06:00
Evan Husted
f81cb093fc misc: chore: Change references of GreemDev/Ryujinx to Ryubing/Ryujinx 2025-01-23 16:27:49 -06:00
Evan Husted
dc0c7a2912 infra: clarify how stable builds are made now 2025-01-23 16:27:25 -06:00
Evan Husted
1fbee5a584 infra: Metal label 2025-01-23 14:39:36 -06:00
Evan Husted
c140e9b23c UI: Localize LED color & hide it until it's functional
Also moved IgnoreApplet to the System config section object.
2025-01-23 00:48:42 -06:00
Evan Husted
9c8055440e HLE: TryAdd firmware NCAs 2025-01-22 23:58:11 -06:00
Evan Husted
c03cd50fa3 UI: Add the ability to change a DualSense/DualShock 4's LED color.
Not functional yet. This is the UI & persistence side of #572.
2025-01-22 19:53:39 -06:00
Evan Husted
069f630776 docs: compat: boots: ENDER MAGNOLIA: Bloom in the Mist 2025-01-22 18:00:14 -06:00
Evan Husted
13d411e4de misc: chore: also ToLower the titleID for the OpenShaderDirectory button 2025-01-22 08:54:39 -06:00
Evan Husted
9f53b07491 misc: chore: Fix shader cache & CPU cache being in different folders on non-Windows
fixes #565
2025-01-22 08:52:21 -06:00
Evan Husted
cd8113dadf misc: chore: Collapse adding a game/autoload dir into a single reusable method. 2025-01-21 18:59:56 -06:00
Evan Husted
9089c4ffe5 misc: chore: Multi/Single file/folder picker extensions (for convenience)
The result of these extensions is an empty Optional when the user hits Cancel on the shown file picker.
2025-01-21 18:59:19 -06:00
Evan Husted
fe9d8d05bd UI: Fixed the Amiibo keybind only working when the UI had been updated. 2025-01-21 18:00:51 -06:00
Evan Husted
880a8ae748 misc: chore: Remove duplicated styling blocks in MainMenuBarView in favor of a reusable Avalonia Style. 2025-01-21 17:50:55 -06:00
sunshineinabox
48f9ada59b Support VK_EXT_extended_dynamic_state and VK_EXT_extended_dynamic_state2 2024-11-21 17:53:52 -08:00
69 changed files with 1868 additions and 880 deletions

4
.github/labeler.yml vendored
View File

@@ -18,6 +18,10 @@ gpu:
- changed-files:
- any-glob-to-any-file: ['src/Ryujinx.Graphics.Vulkan/**', 'src/Spv.Generator/**']
'graphics-backend:metal':
- changed-files:
- any-glob-to-any-file: ['src/Ryujinx.Graphics.Metal/**', 'src/Ryujinx.Graphics.Metal.SharpMetalExtensions/**']
gui:
- changed-files:
- any-glob-to-any-file: ['src/Ryujinx/**', 'src/Ryujinx.UI.Common/**', 'src/Ryujinx.UI.LocaleGenerator/**']

View File

@@ -54,12 +54,13 @@ failing to meet this requirement may result in a poor gameplay experience or une
## Latest build
Stable builds are made every so often onto a separate "release" branch that then gets put into the releases you know and love.
Stable builds are made every so often, based on the `master` branch, that then gets put into the releases you know and love.
These stable builds exist so that the end user can get a more **enjoyable and stable experience**.
They are released every month or so, to ensure consistent updates, while not being an annoying amount of individual updates to download over the course of that month.
You can find the latest stable release [here](https://github.com/GreemDev/Ryujinx/releases/latest).
Canary builds are compiled automatically for each commit on the master branch.
Canary builds are compiled automatically for each commit on the `master` branch.
While we strive to ensure optimal stability and performance prior to pushing an update, these builds **may be unstable or completely broken**.
These canary builds are only recommended for experienced users.
@@ -109,7 +110,7 @@ If you are planning to contribute or just want to learn more about this project
- **Configuration**
The emulator has settings for enabling or disabling some logging, remapping controllers, and more.
You can configure all of them through the graphical interface or manually through the config file, `Config.json`, found in the user folder which can be accessed by clicking `Open Ryujinx Folder` under the File menu in the GUI.
You can configure all of them through the graphical interface or manually through the config file, `Config.json`, found in the Ryujinx data folder which can be accessed by clicking `Open Ryujinx Folder` under the File menu in the GUI.
## License

View File

@@ -1070,6 +1070,7 @@
010017B0102A8000,"Emma: Lost in Memories",nvdec,playable,2021-01-28 16:19:10
010068300E08E000,"Enchanted in the Moonlight - Kiryu, Chikage & Yukinojo -",gpu;nvdec,ingame,2022-11-20 16:18:45
01007A4008486000,"Enchanting Mahjong Match",gpu,ingame,2020-04-17 22:01:31
0100EF901E552000,"ENDER MAGNOLIA: Bloom in the Mist",deadlock,boots,2025-01-22 17:59:00
01004F3011F92000,"Endless Fables: Dark Moor",gpu;nvdec,ingame,2021-03-07 15:31:03
010067B017588000,"Endless Ocean™ Luminous",services-horizon;crash,ingame,2024-05-30 02:05:57
0100B8700BD14000,"Energy Cycle Edge",services,ingame,2021-11-30 05:02:31
1 title_id game_name labels status last_updated
1070 010017B0102A8000 Emma: Lost in Memories nvdec playable 2021-01-28 16:19:10
1071 010068300E08E000 Enchanted in the Moonlight - Kiryu, Chikage & Yukinojo - gpu;nvdec ingame 2022-11-20 16:18:45
1072 01007A4008486000 Enchanting Mahjong Match gpu ingame 2020-04-17 22:01:31
1073 0100EF901E552000 ENDER MAGNOLIA: Bloom in the Mist deadlock boots 2025-01-22 17:59:00
1074 01004F3011F92000 Endless Fables: Dark Moor gpu;nvdec ingame 2021-03-07 15:31:03
1075 010067B017588000 Endless Ocean™ Luminous services-horizon;crash ingame 2024-05-30 02:05:57
1076 0100B8700BD14000 Energy Cycle Edge services ingame 2021-11-30 05:02:31

View File

@@ -78,5 +78,10 @@ namespace Ryujinx.Common.Configuration.Hid.Controller
/// Controller Rumble Settings
/// </summary>
public RumbleConfigController Rumble { get; set; }
/// <summary>
/// Controller LED Settings
/// </summary>
public LedConfigController Led { get; set; }
}
}

View File

@@ -0,0 +1,15 @@
namespace Ryujinx.Common.Configuration.Hid.Controller
{
public class LedConfigController
{
/// <summary>
/// Packed RGB int of the color
/// </summary>
public uint LedColor { get; set; }
/// <summary>
/// Enable LED color changing by the emulator
/// </summary>
public bool EnableLed { get; set; }
}
}

View File

@@ -0,0 +1,23 @@
using System;
using System.Runtime.InteropServices;
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable InconsistentNaming
namespace Ryujinx.Common.Helper
{
public static class RunningPlatform
{
public static bool IsMacOS => OperatingSystem.IsMacOS();
public static bool IsWindows => OperatingSystem.IsWindows();
public static bool IsLinux => OperatingSystem.IsLinux();
public static bool IsIntelMac => IsMacOS && RuntimeInformation.OSArchitecture is Architecture.X64;
public static bool IsArmMac => IsMacOS && RuntimeInformation.OSArchitecture is Architecture.Arm64;
public static bool IsX64Windows => IsWindows && (RuntimeInformation.OSArchitecture is Architecture.X64);
public static bool IsArmWindows => IsWindows && (RuntimeInformation.OSArchitecture is Architecture.Arm64);
public static bool IsX64Linux => IsLinux && (RuntimeInformation.OSArchitecture is Architecture.X64);
public static bool IsArmLinux => IsLinux && (RuntimeInformation.OSArchitecture is Architecture.Arm64);
}
}

View File

@@ -1,5 +1,6 @@
using Gommon;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Helper;
using System;
using System.Linq;
using System.Runtime.InteropServices;
@@ -8,7 +9,7 @@ namespace Ryujinx.Common
{
public static class TitleIDs
{
public static ReactiveObject<Optional<string>> CurrentApplication { get; set; } = new();
public static ReactiveObject<Optional<string>> CurrentApplication { get; } = new();
public static GraphicsBackend SelectGraphicsBackend(string titleId, GraphicsBackend currentBackend)
{
@@ -21,7 +22,7 @@ namespace Ryujinx.Common
return currentBackend;
}
if (!(OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture is Architecture.Arm64))
if (!RunningPlatform.IsArmMac)
return GraphicsBackend.Vulkan;
return GreatMetalTitles.ContainsIgnoreCase(titleId) ? GraphicsBackend.Metal : GraphicsBackend.Vulkan;

View File

@@ -50,6 +50,10 @@ namespace Ryujinx.Graphics.GAL
public readonly bool SupportsViewportSwizzle;
public readonly bool SupportsIndirectParameters;
public readonly bool SupportsDepthClipControl;
public readonly bool SupportsExtendedDynamicState;
public readonly bool SupportsExtendedDynamicState2;
public readonly bool SupportsLogicOpDynamicState;
public readonly bool SupportsPatchControlPointsDynamicState;
public readonly int UniformBufferSetIndex;
public readonly int StorageBufferSetIndex;
@@ -118,6 +122,10 @@ namespace Ryujinx.Graphics.GAL
bool supportsViewportSwizzle,
bool supportsIndirectParameters,
bool supportsDepthClipControl,
bool supportsExtendedDynamicState,
bool supportsExtendedDynamicState2,
bool supportsLogicOpDynamicState,
bool supportsPatchControlPointsDynamicState,
int uniformBufferSetIndex,
int storageBufferSetIndex,
int textureSetIndex,
@@ -180,6 +188,10 @@ namespace Ryujinx.Graphics.GAL
SupportsViewportSwizzle = supportsViewportSwizzle;
SupportsIndirectParameters = supportsIndirectParameters;
SupportsDepthClipControl = supportsDepthClipControl;
SupportsExtendedDynamicState = supportsExtendedDynamicState;
SupportsExtendedDynamicState2 = supportsExtendedDynamicState2;
SupportsLogicOpDynamicState = supportsLogicOpDynamicState;
SupportsPatchControlPointsDynamicState = supportsPatchControlPointsDynamicState;
UniformBufferSetIndex = uniformBufferSetIndex;
StorageBufferSetIndex = storageBufferSetIndex;
TextureSetIndex = textureSetIndex;

View File

@@ -2,6 +2,7 @@ namespace Ryujinx.Graphics.GAL
{
public enum Face
{
None = 0,
Front = 0x404,
Back = 0x405,
FrontAndBack = 0x408,

View File

@@ -50,9 +50,9 @@ namespace Ryujinx.Graphics.GAL
void SetDepthBias(PolygonModeMask enables, float factor, float units, float clamp);
void SetDepthClamp(bool clamp);
void SetDepthMode(DepthMode mode);
void SetDepthTest(DepthTestDescriptor depthTest);
void SetDepthTest(DepthTestDescriptor depthTest, bool signalChange = true);
void SetFaceCulling(bool enable, Face face);
void SetFaceCulling(Face face);
void SetFrontFace(FrontFace frontFace);
@@ -75,16 +75,16 @@ namespace Ryujinx.Graphics.GAL
void SetPrimitiveRestart(bool enable, int index);
void SetPrimitiveTopology(PrimitiveTopology topology);
void SetPrimitiveTopology(PrimitiveTopology topology, bool signalChange = true);
void SetProgram(IProgram program);
void SetProgram(IProgram program, bool signalChange = true);
void SetRasterizerDiscard(bool discard);
void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMask);
void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMask, bool signalChange = true);
void SetRenderTargets(ITexture[] colors, ITexture depthStencil);
void SetScissors(ReadOnlySpan<Rectangle<int>> regions);
void SetScissors(ReadOnlySpan<Rectangle<int>> regions, bool signalChange = true);
void SetStencilTest(StencilTestDescriptor stencilTest);
@@ -102,7 +102,7 @@ namespace Ryujinx.Graphics.GAL
void SetVertexAttribs(ReadOnlySpan<VertexAttribDescriptor> vertexAttribs);
void SetVertexBuffers(ReadOnlySpan<VertexBufferDescriptor> vertexBuffers);
void SetViewports(ReadOnlySpan<Viewport> viewports);
void SetViewports(ReadOnlySpan<Viewport> viewports, bool signalChange = true);
void TextureBarrier();
void TextureBarrierTiled();

View File

@@ -3,18 +3,16 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands
struct SetFaceCullingCommand : IGALCommand, IGALCommand<SetFaceCullingCommand>
{
public readonly CommandType CommandType => CommandType.SetFaceCulling;
private bool _enable;
private Face _face;
public void Set(bool enable, Face face)
public void Set(Face face)
{
_enable = enable;
_face = face;
}
public static void Run(ref SetFaceCullingCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
renderer.Pipeline.SetFaceCulling(command._enable, command._face);
renderer.Pipeline.SetFaceCulling(command._face);
}
}
}

View File

@@ -159,15 +159,15 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand();
}
public void SetDepthTest(DepthTestDescriptor depthTest)
public void SetDepthTest(DepthTestDescriptor depthTest, bool signalChange = true)
{
_renderer.New<SetDepthTestCommand>().Set(depthTest);
_renderer.QueueCommand();
}
public void SetFaceCulling(bool enable, Face face)
public void SetFaceCulling(Face face)
{
_renderer.New<SetFaceCullingCommand>().Set(enable, face);
_renderer.New<SetFaceCullingCommand>().Set(face);
_renderer.QueueCommand();
}
@@ -243,13 +243,13 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand();
}
public void SetPrimitiveTopology(PrimitiveTopology topology)
public void SetPrimitiveTopology(PrimitiveTopology topology, bool signalChange = true)
{
_renderer.New<SetPrimitiveTopologyCommand>().Set(topology);
_renderer.QueueCommand();
}
public void SetProgram(IProgram program)
public void SetProgram(IProgram program, bool signalChange = true)
{
_renderer.New<SetProgramCommand>().Set(Ref(program));
_renderer.QueueCommand();
@@ -261,7 +261,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand();
}
public void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMask)
public void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMask, bool signalChange = true)
{
_renderer.New<SetRenderTargetColorMasksCommand>().Set(_renderer.CopySpan(componentMask));
_renderer.QueueCommand();
@@ -273,7 +273,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand();
}
public void SetScissors(ReadOnlySpan<Rectangle<int>> scissors)
public void SetScissors(ReadOnlySpan<Rectangle<int>> scissors, bool signalChange = true)
{
_renderer.New<SetScissorsCommand>().Set(_renderer.CopySpan(scissors));
_renderer.QueueCommand();
@@ -339,7 +339,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand();
}
public void SetViewports(ReadOnlySpan<Viewport> viewports)
public void SetViewports(ReadOnlySpan<Viewport> viewports, bool signalChange = true)
{
_renderer.New<SetViewportsCommand>().Set(_renderer.CopySpan(viewports));
_renderer.QueueCommand();

View File

@@ -51,11 +51,12 @@ namespace Ryujinx.Graphics.GAL
public StencilTestDescriptor StencilTest;
public FrontFace FrontFace;
public Face CullMode;
public bool CullEnable;
public PolygonModeMask BiasEnable;
public float LineWidth;
public bool AlphaToCoverageEnable;
public bool AlphaToOneEnable;
// TODO: Polygon mode.
public bool DepthClampEnable;
public bool RasterizerDiscard;
@@ -63,6 +64,9 @@ namespace Ryujinx.Graphics.GAL
public bool PrimitiveRestartEnable;
public uint PatchControlPoints;
public float DepthBiasUnits;
public float DepthBiasFactor;
public DepthMode DepthMode;
public void SetVertexAttribs(ReadOnlySpan<VertexAttribDescriptor> vertexAttribs)

View File

@@ -854,6 +854,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
enables |= (depthBias.FillEnable ? PolygonModeMask.Fill : 0);
_pipeline.BiasEnable = enables;
_pipeline.DepthBiasUnits = units / 2f;
_pipeline.DepthBiasFactor = factor;
_context.Renderer.Pipeline.SetDepthBias(enables, factor, units / 2f, clamp);
}
@@ -1026,7 +1029,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
float width = _state.State.LineWidthSmooth;
bool smooth = _state.State.LineSmoothEnable;
_pipeline.LineWidth = width;
_context.Renderer.Pipeline.SetLineParameters(width, smooth);
}
@@ -1196,9 +1198,16 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
var yControl = _state.State.YControl;
var face = _state.State.FaceState;
_pipeline.CullEnable = face.CullEnable;
_pipeline.CullMode = face.CullFace;
_context.Renderer.Pipeline.SetFaceCulling(face.CullEnable, face.CullFace);
if (face.CullEnable)
{
_pipeline.CullMode = face.CullFace;
_context.Renderer.Pipeline.SetFaceCulling(face.CullFace);
}
else
{
_pipeline.CullMode = Face.None;
_context.Renderer.Pipeline.SetFaceCulling(Face.None);
}
UpdateFrontFace(yControl, face.FrontFace);
}
@@ -1388,6 +1397,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
bool alphaToCoverageEnable = (_state.State.MultisampleControl & 1) != 0;
bool alphaToOneEnable = (_state.State.MultisampleControl & 0x10) != 0;
_pipeline.AlphaToCoverageEnable = alphaToCoverageEnable;
_pipeline.AlphaToOneEnable = alphaToOneEnable;
_context.Renderer.Pipeline.SetMultisampleState(new MultisampleDescriptor(
alphaToCoverageEnable,
_state.State.AlphaToCoverageDitherEnable,

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 = 7353;
private const uint CodeGenVersion = 6877;
private const string SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data";

View File

@@ -23,6 +23,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
private readonly CancellationToken _cancellationToken;
private readonly Action<ShaderCacheState, int, int> _stateChangeCallback;
private readonly HashSet<ProgramPipelineState> _pipelineStateSet = new();
/// <summary>
/// Indicates if the cache should be loaded.
/// </summary>
@@ -233,10 +235,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
for (int index = 0; index < ThreadCount; index++)
{
workThreads[index] = new Thread(ProcessAsyncQueue)
{
Name = $"GPU.AsyncTranslationThread.{index}",
};
workThreads[index] = new Thread(ProcessAsyncQueue) { Name = $"GPU.AsyncTranslationThread.{index}", };
}
int programCount = _hostStorage.GetProgramCount();
@@ -306,6 +305,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
using var streams = _hostStorage.GetOutputStreams(_context);
int packagedShaders = 0;
ProgramPipelineState currentPipelineState = default;
foreach (var kv in _programList)
{
if (!Active)
@@ -315,12 +316,53 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
(CachedShaderProgram program, byte[] binaryCode) = kv.Value;
_hostStorage.AddShader(_context, program, binaryCode, streams);
if (program.SpecializationState.PipelineState.HasValue && _context.Capabilities.SupportsExtendedDynamicState)
{
currentPipelineState = program.SpecializationState.PipelineState.Value;
_stateChangeCallback(ShaderCacheState.Packaging, ++packagedShaders, _programList.Count);
if (_context.Capabilities.SupportsExtendedDynamicState)
{
currentPipelineState.StencilTest = default;
currentPipelineState.CullMode = 0;
currentPipelineState.FrontFace = 0;
currentPipelineState.DepthTest = default;
currentPipelineState.Topology = ConvertToClass(currentPipelineState.Topology);
}
if (_context.Capabilities.SupportsExtendedDynamicState2)
{
currentPipelineState.PrimitiveRestartEnable = false;
currentPipelineState.BiasEnable = 0;
currentPipelineState.RasterizerDiscard = false;
}
if (_context.Capabilities.SupportsLogicOpDynamicState)
{
currentPipelineState.LogicOp = 0;
}
if (_context.Capabilities.SupportsPatchControlPointsDynamicState)
{
currentPipelineState.PatchControlPoints = 0;
}
}
if (!_pipelineStateSet.Contains(currentPipelineState) ||
!_context.Capabilities.SupportsExtendedDynamicState ||
!program.SpecializationState.PipelineState.HasValue)
{
_hostStorage.AddShader(_context, program, binaryCode, streams);
_stateChangeCallback(ShaderCacheState.Packaging, ++packagedShaders, _programList.Count);
if (_context.Capabilities.SupportsExtendedDynamicState)
{
_pipelineStateSet.Add(currentPipelineState);
}
}
}
Logger.Info?.Print(LogClass.Gpu, $"Rebuilt {_programList.Count} shaders successfully.");
Logger.Info?.Print(LogClass.Gpu, $"Rebuilt {packagedShaders} shaders successfully.");
}
else
{
@@ -344,6 +386,29 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
_stateChangeCallback(ShaderCacheState.Loaded, programCount, programCount);
}
private PrimitiveTopology ConvertToClass(PrimitiveTopology topology)
{
return topology switch
{
PrimitiveTopology.Points => PrimitiveTopology.Points,
PrimitiveTopology.Lines or
PrimitiveTopology.LineStrip or
PrimitiveTopology.LinesAdjacency or
PrimitiveTopology.LineStripAdjacency => PrimitiveTopology.Lines,
PrimitiveTopology.Triangles or
PrimitiveTopology.TriangleStrip or
PrimitiveTopology.TriangleFan or
PrimitiveTopology.TrianglesAdjacency or
PrimitiveTopology.TriangleStripAdjacency or
PrimitiveTopology.Polygon => PrimitiveTopology.TriangleStrip,
PrimitiveTopology.Patches => PrimitiveTopology.Patches,
PrimitiveTopology.Quads => PrimitiveTopology.Quads,
PrimitiveTopology.QuadStrip => PrimitiveTopology.QuadStrip,
PrimitiveTopology.LineLoop => PrimitiveTopology.LineLoop,
_ => PrimitiveTopology.TriangleStrip,
};
}
/// <summary>
/// Enqueues a host program for compilation.
/// </summary>

View File

@@ -118,7 +118,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
private static string GetDiskCachePath()
{
return GraphicsConfig.EnableShaderCache && GraphicsConfig.TitleId != null
? Path.Combine(AppDataManager.GamesDirPath, GraphicsConfig.TitleId, "cache", "shader")
? Path.Combine(AppDataManager.GamesDirPath, GraphicsConfig.TitleId.ToLower(), "cache", "shader")
: null;
}

View File

@@ -191,6 +191,10 @@ namespace Ryujinx.Graphics.OpenGL
supportsViewportSwizzle: HwCapabilities.SupportsViewportSwizzle,
supportsIndirectParameters: HwCapabilities.SupportsIndirectParameters,
supportsDepthClipControl: true,
supportsExtendedDynamicState: false,
supportsExtendedDynamicState2: false,
supportsLogicOpDynamicState: false,
supportsPatchControlPointsDynamicState: false,
uniformBufferSetIndex: 0,
storageBufferSetIndex: 1,
textureSetIndex: 2,

View File

@@ -898,7 +898,7 @@ namespace Ryujinx.Graphics.OpenGL
}
}
public void SetDepthTest(DepthTestDescriptor depthTest)
public void SetDepthTest(DepthTestDescriptor depthTest, bool signalChange = true)
{
if (depthTest.TestEnable)
{
@@ -915,11 +915,11 @@ namespace Ryujinx.Graphics.OpenGL
_depthTestEnable = depthTest.TestEnable;
}
public void SetFaceCulling(bool enable, Face face)
public void SetFaceCulling(Face face)
{
_cullEnable = enable;
_cullEnable = face != Face.None;
if (!enable)
if (!_cullEnable)
{
GL.Disable(EnableCap.CullFace);
return;
@@ -1107,12 +1107,12 @@ namespace Ryujinx.Graphics.OpenGL
GL.Enable(EnableCap.PrimitiveRestart);
}
public void SetPrimitiveTopology(PrimitiveTopology topology)
public void SetPrimitiveTopology(PrimitiveTopology topology, bool signalChange = true)
{
_primitiveType = topology.Convert();
}
public void SetProgram(IProgram program)
public void SetProgram(IProgram program, bool signalChange = true)
{
Program prg = (Program)program;
@@ -1154,7 +1154,7 @@ namespace Ryujinx.Graphics.OpenGL
_rasterizerDiscard = discard;
}
public void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMasks)
public void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMasks, bool signalChange = true)
{
_componentMasks = 0;
@@ -1195,7 +1195,7 @@ namespace Ryujinx.Graphics.OpenGL
_framebuffer.SetDrawBuffers(colors.Length);
}
public void SetScissors(ReadOnlySpan<Rectangle<int>> regions)
public void SetScissors(ReadOnlySpan<Rectangle<int>> regions, bool signalChange = true)
{
int count = Math.Min(regions.Length, Constants.MaxViewports);
@@ -1388,7 +1388,7 @@ namespace Ryujinx.Graphics.OpenGL
_vertexArray.SetVertexBuffers(vertexBuffers);
}
public void SetViewports(ReadOnlySpan<Viewport> viewports)
public void SetViewports(ReadOnlySpan<Viewport> viewports, bool signalChange = true)
{
Array.Resize(ref _viewportArray, viewports.Length * 4);
Array.Resize(ref _depthRangeArray, viewports.Length * 2);

View File

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

View File

@@ -238,6 +238,7 @@ namespace Ryujinx.Graphics.Vulkan
Face.Back => CullModeFlags.BackBit,
Face.Front => CullModeFlags.FrontBit,
Face.FrontAndBack => CullModeFlags.FrontAndBack,
Face.None => CullModeFlags.None,
_ => LogInvalidAndReturn(face, nameof(Face), CullModeFlags.BackBit),
};
}
@@ -310,6 +311,25 @@ namespace Ryujinx.Graphics.Vulkan
};
}
public static PrimitiveTopology ConvertToClass(this PrimitiveTopology topology)
{
return topology switch
{
PrimitiveTopology.PointList => PrimitiveTopology.PointList,
PrimitiveTopology.LineList or
PrimitiveTopology.LineStrip or
PrimitiveTopology.LineListWithAdjacency or
PrimitiveTopology.LineStripWithAdjacency => PrimitiveTopology.LineList,
PrimitiveTopology.TriangleList or
PrimitiveTopology.TriangleStrip or
PrimitiveTopology.TriangleFan or
PrimitiveTopology.TriangleListWithAdjacency or
PrimitiveTopology.TriangleStripWithAdjacency => PrimitiveTopology.TriangleStrip,
PrimitiveTopology.PatchList => PrimitiveTopology.PatchList,
_ => LogInvalidAndReturn(topology, nameof(PrimitiveTopology), PrimitiveTopology.TriangleStrip),
};
}
public static StencilOp Convert(this GAL.StencilOp op)
{
return op switch

View File

@@ -31,6 +31,7 @@ namespace Ryujinx.Graphics.Vulkan
public readonly bool SupportsShaderStorageImageMultisample;
public readonly bool SupportsConditionalRendering;
public readonly bool SupportsExtendedDynamicState;
public readonly PhysicalDeviceExtendedDynamicState2FeaturesEXT SupportsExtendedDynamicState2;
public readonly bool SupportsMultiView;
public readonly bool SupportsNullDescriptors;
public readonly bool SupportsPushDescriptors;
@@ -46,6 +47,7 @@ namespace Ryujinx.Graphics.Vulkan
public readonly bool SupportsViewportArray2;
public readonly bool SupportsHostImportedMemory;
public readonly bool SupportsDepthClipControl;
public readonly bool SupportsWideLines;
public readonly bool SupportsAttachmentFeedbackLoop;
public readonly bool SupportsDynamicAttachmentFeedbackLoop;
public readonly uint SubgroupSize;
@@ -54,6 +56,7 @@ namespace Ryujinx.Graphics.Vulkan
public readonly uint VertexBufferAlignment;
public readonly uint SubTexelPrecisionBits;
public readonly ulong MinResourceAlignment;
public readonly uint MaxTessellationPatchSize;
public HardwareCapabilities(
bool supportsIndexTypeUint8,
@@ -71,6 +74,8 @@ namespace Ryujinx.Graphics.Vulkan
bool supportsShaderStorageImageMultisample,
bool supportsConditionalRendering,
bool supportsExtendedDynamicState,
PhysicalDeviceExtendedDynamicState2FeaturesEXT supportsExtendedDynamicState2,
uint maxTessellationPatchSize,
bool supportsMultiView,
bool supportsNullDescriptors,
bool supportsPushDescriptors,
@@ -86,6 +91,7 @@ namespace Ryujinx.Graphics.Vulkan
bool supportsViewportArray2,
bool supportsHostImportedMemory,
bool supportsDepthClipControl,
bool supportsWideLines,
bool supportsAttachmentFeedbackLoop,
bool supportsDynamicAttachmentFeedbackLoop,
uint subgroupSize,
@@ -110,6 +116,8 @@ namespace Ryujinx.Graphics.Vulkan
SupportsShaderStorageImageMultisample = supportsShaderStorageImageMultisample;
SupportsConditionalRendering = supportsConditionalRendering;
SupportsExtendedDynamicState = supportsExtendedDynamicState;
SupportsExtendedDynamicState2 = supportsExtendedDynamicState2;
MaxTessellationPatchSize = maxTessellationPatchSize;
SupportsMultiView = supportsMultiView;
SupportsNullDescriptors = supportsNullDescriptors;
SupportsPushDescriptors = supportsPushDescriptors;
@@ -125,6 +133,7 @@ namespace Ryujinx.Graphics.Vulkan
SupportsViewportArray2 = supportsViewportArray2;
SupportsHostImportedMemory = supportsHostImportedMemory;
SupportsDepthClipControl = supportsDepthClipControl;
SupportsWideLines = supportsWideLines;
SupportsAttachmentFeedbackLoop = supportsAttachmentFeedbackLoop;
SupportsDynamicAttachmentFeedbackLoop = supportsDynamicAttachmentFeedbackLoop;
SubgroupSize = subgroupSize;

View File

@@ -429,35 +429,35 @@ namespace Ryujinx.Graphics.Vulkan
if (dstIsDepthOrStencil)
{
_pipeline.SetProgram(src.Info.Target.IsMultisample() ? _programDepthBlitMs : _programDepthBlit);
_pipeline.SetDepthTest(new DepthTestDescriptor(true, true, CompareOp.Always));
_pipeline.SetProgram(src.Info.Target.IsMultisample() ? _programDepthBlitMs : _programDepthBlit, false);
_pipeline.SetDepthTest(new DepthTestDescriptor(true, true, CompareOp.Always), false);
}
else if (src.Info.Target.IsMultisample())
{
_pipeline.SetProgram(_programColorBlitMs);
_pipeline.SetProgram(_programColorBlitMs, false);
}
else if (clearAlpha)
{
_pipeline.SetProgram(_programColorBlitClearAlpha);
_pipeline.SetProgram(_programColorBlitClearAlpha, false);
}
else
{
_pipeline.SetProgram(_programColorBlit);
_pipeline.SetProgram(_programColorBlit, false);
}
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) });
_pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, false);
_pipeline.SetRenderTargetColorMasks(new uint[] { 0xf }, false);
_pipeline.SetScissors(stackalloc Rectangle<int>[] { new Rectangle<int>(0, 0, dstWidth, dstHeight) }, false);
if (clearAlpha)
{
_pipeline.ClearRenderTargetColor(0, 0, 1, new ColorF(0f, 0f, 0f, 1f));
}
_pipeline.SetViewports(viewports);
_pipeline.SetViewports(viewports, false);
_pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
_pipeline.Draw(4, 1, 0, 0);
@@ -524,10 +524,10 @@ namespace Ryujinx.Graphics.Vulkan
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);
_pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, false);
_pipeline.SetScissors(stackalloc Rectangle<int>[] { new Rectangle<int>(0, 0, dstWidth, dstHeight) }, false);
_pipeline.SetViewports(viewports, false);
_pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip, false);
var aspectFlags = src.Info.Format.ConvertAspectFlags();
@@ -589,12 +589,12 @@ namespace Ryujinx.Graphics.Vulkan
if (isDepth)
{
_pipeline.SetProgram(src.Info.Target.IsMultisample() ? _programDepthBlitMs : _programDepthBlit);
_pipeline.SetProgram(src.Info.Target.IsMultisample() ? _programDepthBlitMs : _programDepthBlit, false);
_pipeline.SetDepthTest(new DepthTestDescriptor(true, true, CompareOp.Always));
}
else
{
_pipeline.SetProgram(src.Info.Target.IsMultisample() ? _programStencilBlitMs : _programStencilBlit);
_pipeline.SetProgram(src.Info.Target.IsMultisample() ? _programStencilBlitMs : _programStencilBlit, false);
_pipeline.SetStencilTest(CreateStencilTestDescriptor(true));
}
@@ -684,11 +684,11 @@ namespace Ryujinx.Graphics.Vulkan
program = _programColorClearF;
}
_pipeline.SetProgram(program);
_pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight);
_pipeline.SetRenderTargetColorMasks(new[] { componentMask });
_pipeline.SetViewports(viewports);
_pipeline.SetScissors(stackalloc Rectangle<int>[] { scissor });
_pipeline.SetProgram(program, false);
_pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, false);
_pipeline.SetRenderTargetColorMasks(new[] { componentMask }, false);
_pipeline.SetViewports(viewports, false);
_pipeline.SetScissors(stackalloc Rectangle<int>[] { scissor }, false);
_pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
_pipeline.Draw(4, 1, 0, 0);
_pipeline.Finish();
@@ -731,12 +731,12 @@ namespace Ryujinx.Graphics.Vulkan
0f,
1f);
_pipeline.SetProgram(_programDepthStencilClear);
_pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight);
_pipeline.SetViewports(viewports);
_pipeline.SetScissors(stackalloc Rectangle<int>[] { scissor });
_pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
_pipeline.SetDepthTest(new DepthTestDescriptor(true, depthMask, CompareOp.Always));
_pipeline.SetProgram(_programDepthStencilClear, false);
_pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, false);
_pipeline.SetViewports(viewports, false);
_pipeline.SetScissors(stackalloc Rectangle<int>[] { scissor }, false);
_pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip, false);
_pipeline.SetDepthTest(new DepthTestDescriptor(true, depthMask, CompareOp.Always), false);
_pipeline.SetStencilTest(CreateStencilTestDescriptor(stencilMask != 0, stencilValue, 0xff, stencilMask));
_pipeline.Draw(4, 1, 0, 0);
_pipeline.Finish();
@@ -794,8 +794,8 @@ namespace Ryujinx.Graphics.Vulkan
0f,
1f);
pipeline.SetProgram(_programColorBlit);
pipeline.SetViewports(viewports);
pipeline.SetProgram(_programColorBlit, false);
pipeline.SetViewports(viewports, false);
pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
pipeline.Draw(4, 1, 0, 0);
@@ -1129,16 +1129,16 @@ namespace Ryujinx.Graphics.Vulkan
0f,
1f);
_pipeline.SetScissors(stackalloc Rectangle<int>[] { new Rectangle<int>(0, 0, dst.Width, dst.Height) });
_pipeline.SetViewports(viewports);
_pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
_pipeline.SetScissors(stackalloc Rectangle<int>[] { new Rectangle<int>(0, 0, dst.Width, dst.Height) }, false);
_pipeline.SetViewports(viewports, false);
_pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip, false);
for (int z = 0; z < depth; z++)
{
var srcView = Create2DLayerView(src, srcLayer + z, 0);
var dstView = Create2DLayerView(dst, dstLayer + z, 0);
_pipeline.SetRenderTarget(dstView, (uint)dst.Width, (uint)dst.Height);
_pipeline.SetRenderTarget(dstView, (uint)dst.Width, (uint)dst.Height, false);
CopyMSDraw(srcView, aspectFlags, fromMS: true);
@@ -1251,9 +1251,9 @@ namespace Ryujinx.Graphics.Vulkan
1f);
_pipeline.SetRenderTargetColorMasks(new uint[] { 0xf });
_pipeline.SetScissors(stackalloc Rectangle<int>[] { new Rectangle<int>(0, 0, dst.Width, dst.Height) });
_pipeline.SetViewports(viewports);
_pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
_pipeline.SetScissors(stackalloc Rectangle<int>[] { new Rectangle<int>(0, 0, dst.Width, dst.Height) }, false);
_pipeline.SetViewports(viewports, false);
_pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip, false);
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, buffer.Range) });
@@ -1264,7 +1264,7 @@ namespace Ryujinx.Graphics.Vulkan
var srcView = Create2DLayerView(src, srcLayer + z, 0);
var dstView = Create2DLayerView(dst, dstLayer + z, 0);
_pipeline.SetRenderTarget(dstView, (uint)dst.Width, (uint)dst.Height);
_pipeline.SetRenderTarget(dstView, (uint)dst.Width, (uint)dst.Height, false);
CopyMSDraw(srcView, aspectFlags, fromMS: false);
@@ -1281,7 +1281,7 @@ namespace Ryujinx.Graphics.Vulkan
}
else
{
_pipeline.SetProgram(_programColorDrawToMs);
_pipeline.SetProgram(_programColorDrawToMs, false);
var format = GetFormat(src.Info.BytesPerPixel);
var vkFormat = FormatTable.GetFormat(format);
@@ -1358,12 +1358,12 @@ namespace Ryujinx.Graphics.Vulkan
if (isDepth)
{
_pipeline.SetProgram(fromMS ? _programDepthDrawToNonMs : _programDepthDrawToMs);
_pipeline.SetProgram(fromMS ? _programDepthDrawToNonMs : _programDepthDrawToMs, false);
_pipeline.SetDepthTest(new DepthTestDescriptor(true, true, CompareOp.Always));
}
else
{
_pipeline.SetProgram(fromMS ? _programStencilDrawToNonMs : _programStencilDrawToMs);
_pipeline.SetProgram(fromMS ? _programStencilDrawToNonMs : _programStencilDrawToMs, false);
_pipeline.SetStencilTest(CreateStencilTestDescriptor(true));
}

View File

@@ -1,3 +1,5 @@
using Ryujinx.Common.Logging;
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader;
using Silk.NET.Vulkan;
@@ -74,6 +76,7 @@ namespace Ryujinx.Graphics.Vulkan
private readonly BufferState[] _transformFeedbackBuffers;
private readonly VertexBufferState[] _vertexBuffers;
private ulong _vertexBuffersDirty;
private bool _bindingsSet;
protected Rectangle<int> ClearScissor;
private readonly VertexBufferUpdater _vertexBufferUpdater;
@@ -87,6 +90,9 @@ namespace Ryujinx.Graphics.Vulkan
private bool _tfEnabled;
private bool _tfActive;
private readonly bool _supportExtDynamic;
private readonly bool _supportExtDynamic2;
private FeedbackLoopAspects _feedbackLoop;
private bool _passWritesDepthStencil;
@@ -94,6 +100,8 @@ namespace Ryujinx.Graphics.Vulkan
public ulong DrawCount { get; private set; }
public bool RenderPassActive { get; private set; }
private readonly int[] _vertexBufferBindings;
public unsafe PipelineBase(VulkanRenderer gd, Device device)
{
Gd = gd;
@@ -126,7 +134,19 @@ namespace Ryujinx.Graphics.Vulkan
_storedBlend = new PipelineColorBlendAttachmentState[Constants.MaxRenderTargets];
_newState.Initialize();
_supportExtDynamic = gd.Capabilities.SupportsExtendedDynamicState;
_supportExtDynamic2 = gd.Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2;
_bindingsSet = false;
_vertexBufferBindings = new int[Constants.MaxVertexBuffers];
for (int i = 0; i < Constants.MaxVertexBuffers; i++)
{
_vertexBufferBindings[i] = i + 1;
}
_newState.Initialize(gd.Capabilities);
}
public void Initialize()
@@ -632,19 +652,46 @@ namespace Ryujinx.Graphics.Vulkan
{
if (texture is TextureView srcTexture)
{
var oldCullMode = _newState.CullMode;
var oldStencilTestEnable = _newState.StencilTestEnable;
var oldDepthTestEnable = _newState.DepthTestEnable;
var oldDepthWriteEnable = _newState.DepthWriteEnable;
var oldViewports = DynamicState.Viewports;
var oldViewportsCount = _newState.ViewportsCount;
var oldTopology = _topology;
CullModeFlags oldCullMode;
bool oldStencilTestEnable;
bool oldDepthTestEnable;
bool oldDepthWriteEnable;
PrimitiveTopology oldTopology;
Array16<Silk.NET.Vulkan.Viewport> oldViewports = DynamicState.Viewports;
uint oldViewportsCount;
_newState.CullMode = CullModeFlags.None;
_newState.StencilTestEnable = false;
_newState.DepthTestEnable = false;
_newState.DepthWriteEnable = false;
SignalStateChange();
if (_supportExtDynamic)
{
oldCullMode = DynamicState.CullMode;
oldStencilTestEnable = DynamicState.StencilTestEnable;
oldDepthTestEnable = DynamicState.DepthTestEnable;
oldDepthWriteEnable = DynamicState.DepthWriteEnable;
oldTopology = _topology;
oldViewportsCount = DynamicState.ViewportsCount;
}
else
{
oldCullMode = _newState.CullMode;
oldStencilTestEnable = _newState.StencilTestEnable;
oldDepthTestEnable = _newState.DepthTestEnable;
oldDepthWriteEnable = _newState.DepthWriteEnable;
oldTopology = _topology;
oldViewportsCount = _newState.ViewportsCount;
}
if (_supportExtDynamic)
{
DynamicState.SetCullMode(CullModeFlags.None);
DynamicState.SetDepthTestBool(false, false);
DynamicState.SetStencilTest(false);
}
else
{
_newState.CullMode = CullModeFlags.None;
_newState.StencilTestEnable = false;
_newState.DepthTestEnable = false;
_newState.DepthWriteEnable = false;
}
Gd.HelperShader.DrawTexture(
Gd,
@@ -654,16 +701,24 @@ namespace Ryujinx.Graphics.Vulkan
srcRegion,
dstRegion);
_newState.CullMode = oldCullMode;
_newState.StencilTestEnable = oldStencilTestEnable;
_newState.DepthTestEnable = oldDepthTestEnable;
_newState.DepthWriteEnable = oldDepthWriteEnable;
if (_supportExtDynamic)
{
DynamicState.SetCullMode(oldCullMode);
DynamicState.SetStencilTest(oldStencilTestEnable);
DynamicState.SetDepthTestBool(oldDepthTestEnable, oldDepthWriteEnable);
}
else
{
_newState.CullMode = oldCullMode;
_newState.StencilTestEnable = oldStencilTestEnable;
_newState.DepthTestEnable = oldDepthTestEnable;
_newState.DepthWriteEnable = oldDepthWriteEnable;
_newState.ViewportsCount = oldViewportsCount;
}
SetPrimitiveTopology(oldTopology);
DynamicState.SetViewports(ref oldViewports, oldViewportsCount);
_newState.ViewportsCount = oldViewportsCount;
SignalStateChange();
}
}
@@ -792,48 +847,102 @@ namespace Ryujinx.Graphics.Vulkan
public void SetDepthBias(PolygonModeMask enables, float factor, float units, float clamp)
{
DynamicState.SetDepthBias(factor, units, clamp);
bool depthBiasEnable = (enables != 0) && (factor != 0 && units != 0);
bool changed = false;
_newState.DepthBiasEnable = enables != 0;
SignalStateChange();
}
if (_supportExtDynamic2)
{
DynamicState.SetDepthBiasEnable(depthBiasEnable);
}
else if (_newState.DepthBiasEnable != depthBiasEnable)
{
_newState.DepthBiasEnable = depthBiasEnable;
changed = true;
}
public void SetDepthClamp(bool clamp)
{
_newState.DepthClampEnable = clamp;
SignalStateChange();
}
if (depthBiasEnable)
{
DynamicState.SetDepthBias(factor, units, clamp);
}
public void SetDepthMode(DepthMode mode)
{
bool oldMode = _newState.DepthMode;
_newState.DepthMode = mode == DepthMode.MinusOneToOne;
if (_newState.DepthMode != oldMode)
if (changed)
{
SignalStateChange();
}
}
public void SetDepthTest(DepthTestDescriptor depthTest)
public void SetDepthClamp(bool clamp)
{
_newState.DepthTestEnable = depthTest.TestEnable;
_newState.DepthWriteEnable = depthTest.WriteEnable;
_newState.DepthCompareOp = depthTest.Func.Convert();
_newState.DepthClampEnable = clamp;
UpdatePassDepthStencil();
SignalStateChange();
}
public void SetFaceCulling(bool enable, Face face)
public void SetDepthMode(DepthMode mode)
{
_newState.CullMode = enable ? face.Convert() : CullModeFlags.None;
bool newMode = mode == DepthMode.MinusOneToOne;
if (_newState.DepthMode == newMode)
{
return;
}
_newState.DepthMode = newMode;
SignalStateChange();
}
public void SetDepthTest(DepthTestDescriptor depthTest, bool signalChange = true)
{
if (_supportExtDynamic)
{
DynamicState.SetDepthTestBool(depthTest.TestEnable, depthTest.WriteEnable);
if (depthTest.TestEnable)
{
DynamicState.SetDepthTestCompareOp(depthTest.Func.Convert());
}
}
else
{
_newState.DepthTestEnable = depthTest.TestEnable;
_newState.DepthWriteEnable = depthTest.WriteEnable;
_newState.DepthCompareOp = depthTest.Func.Convert();
if (signalChange)
{
SignalStateChange();
}
}
UpdatePassDepthStencil();
}
public void SetFaceCulling(Face face)
{
if (_supportExtDynamic)
{
DynamicState.SetCullMode(face.Convert());
}
else
{
_newState.CullMode = face.Convert();
SignalStateChange();
}
}
public void SetFrontFace(FrontFace frontFace)
{
_newState.FrontFace = frontFace.Convert();
SignalStateChange();
if (_supportExtDynamic)
{
DynamicState.SetFrontFace(frontFace.Convert());
}
else
{
_newState.FrontFace = frontFace.Convert();
SignalStateChange();
}
}
public void SetImage(ShaderStage stage, int binding, ITexture image)
@@ -872,28 +981,56 @@ namespace Ryujinx.Graphics.Vulkan
public void SetLineParameters(float width, bool smooth)
{
_newState.LineWidth = width;
SignalStateChange();
if (!Gd.IsMoltenVk)
{
DynamicState.SetLineWidth(Gd.Capabilities.SupportsWideLines ? width : 1.0f);
}
}
public void SetLogicOpState(bool enable, LogicalOp op)
{
// Vendors other than NVIDIA have a bug where it enables logical operations even for float formats,
// so we need to force disable them here.
bool logicOpEnable = enable && (Gd.Vendor == Vendor.Nvidia || _newState.Internal.LogicOpsAllowed);
_newState.LogicOpEnable = enable;
_newState.LogicOp = op.Convert();
if (Gd.Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2LogicOp)
{
if (logicOpEnable)
{
DynamicState.SetLogicOp(op.Convert());
}
}
else
{
_newState.LogicOp = op.Convert();
}
SignalStateChange();
}
public void SetMultisampleState(MultisampleDescriptor multisample)
{
_newState.AlphaToCoverageEnable = multisample.AlphaToCoverageEnable;
_newState.AlphaToOneEnable = multisample.AlphaToOneEnable;
SignalStateChange();
}
public void SetPatchParameters(int vertices, ReadOnlySpan<float> defaultOuterLevel, ReadOnlySpan<float> defaultInnerLevel)
{
_newState.PatchControlPoints = (uint)vertices;
SignalStateChange();
if (Gd.Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2PatchControlPoints)
{
DynamicState.SetPatchControlPoints((uint)vertices);
}
else
{
_newState.PatchControlPoints = (uint)vertices;
SignalStateChange();
}
// TODO: Default levels (likely needs emulation on shaders?)
}
@@ -910,12 +1047,21 @@ namespace Ryujinx.Graphics.Vulkan
public void SetPrimitiveRestart(bool enable, int index)
{
_newState.PrimitiveRestartEnable = enable;
if (_supportExtDynamic2)
{
DynamicState.SetPrimitiveRestartEnable(enable);
}
else
{
_newState.PrimitiveRestartEnable = enable;
SignalStateChange();
}
// TODO: What to do about the index?
SignalStateChange();
}
public void SetPrimitiveTopology(PrimitiveTopology topology)
public void SetPrimitiveTopology(PrimitiveTopology topology, bool signalChange = true)
{
_topology = topology;
@@ -923,10 +1069,18 @@ namespace Ryujinx.Graphics.Vulkan
_newState.Topology = vkTopology;
SignalStateChange();
if (_supportExtDynamic)
{
DynamicState.SetPrimitiveTopology(vkTopology);
}
if (signalChange)
{
SignalStateChange();
}
}
public void SetProgram(IProgram program)
public void SetProgram(IProgram program, bool signalChange = true)
{
var internalProgram = (ShaderCollection)program;
var stages = internalProgram.GetInfos();
@@ -942,7 +1096,10 @@ namespace Ryujinx.Graphics.Vulkan
stages.CopyTo(_newState.Stages.AsSpan()[..stages.Length]);
SignalStateChange();
if (signalChange)
{
SignalStateChange();
}
if (internalProgram.IsCompute)
{
@@ -968,18 +1125,26 @@ namespace Ryujinx.Graphics.Vulkan
public void SetRasterizerDiscard(bool discard)
{
_newState.RasterizerDiscardEnable = discard;
SignalStateChange();
if (_supportExtDynamic2)
{
DynamicState.SetRasterizerDiscard(discard);
}
else
{
_newState.RasterizerDiscardEnable = discard;
SignalStateChange();
}
if (!discard && Gd.IsQualcommProprietary)
{
// On Adreno, enabling rasterizer discard somehow corrupts the viewport state.
// Force it to be updated on next use to work around this bug.
DynamicState.ForceAllDirty();
DynamicState.ForceAllDirty(Gd);
}
}
public void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMask)
public void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMask, bool signalChange = true)
{
int count = Math.Min(Constants.MaxRenderTargets, componentMask.Length);
int writtenAttachments = 0;
@@ -1019,7 +1184,10 @@ namespace Ryujinx.Graphics.Vulkan
}
else
{
SignalStateChange();
if (signalChange)
{
SignalStateChange();
}
if (writtenAttachments != _writtenAttachmentCount)
{
@@ -1043,7 +1211,7 @@ namespace Ryujinx.Graphics.Vulkan
SetRenderTargetsInternal(colors, depthStencil, Gd.IsTBDR);
}
public void SetScissors(ReadOnlySpan<Rectangle<int>> regions)
public void SetScissors(ReadOnlySpan<Rectangle<int>> regions, bool signalChange = true)
{
int maxScissors = Gd.Capabilities.SupportsMultiView ? Constants.MaxViewports : 1;
int count = Math.Min(maxScissors, regions.Length);
@@ -1052,6 +1220,8 @@ namespace Ryujinx.Graphics.Vulkan
ClearScissor = regions[0];
}
DynamicState.ScissorsCount = count;
for (int i = 0; i < count; i++)
{
var region = regions[i];
@@ -1061,34 +1231,56 @@ namespace Ryujinx.Graphics.Vulkan
DynamicState.SetScissor(i, new Rect2D(offset, extent));
}
DynamicState.ScissorsCount = count;
if (!_supportExtDynamic)
{
_newState.ScissorsCount = (uint)count;
_newState.ScissorsCount = (uint)count;
SignalStateChange();
if (signalChange)
{
SignalStateChange();
}
}
}
public void SetStencilTest(StencilTestDescriptor stencilTest)
{
DynamicState.SetStencilMasks(
(uint)stencilTest.BackFuncMask,
if (_supportExtDynamic)
{
DynamicState.SetStencilTestandOp(
stencilTest.BackSFail.Convert(),
stencilTest.BackDpPass.Convert(),
stencilTest.BackDpFail.Convert(),
stencilTest.BackFunc.Convert(),
stencilTest.FrontSFail.Convert(),
stencilTest.FrontDpPass.Convert(),
stencilTest.FrontDpFail.Convert(),
stencilTest.FrontFunc.Convert(),
stencilTest.TestEnable);
UpdatePassDepthStencil();
}
else
{
_newState.StencilBackFailOp = stencilTest.BackSFail.Convert();
_newState.StencilBackPassOp = stencilTest.BackDpPass.Convert();
_newState.StencilBackDepthFailOp = stencilTest.BackDpFail.Convert();
_newState.StencilBackCompareOp = stencilTest.BackFunc.Convert();
_newState.StencilFrontFailOp = stencilTest.FrontSFail.Convert();
_newState.StencilFrontPassOp = stencilTest.FrontDpPass.Convert();
_newState.StencilFrontDepthFailOp = stencilTest.FrontDpFail.Convert();
_newState.StencilFrontCompareOp = stencilTest.FrontFunc.Convert();
_newState.StencilTestEnable = stencilTest.TestEnable;
UpdatePassDepthStencil();
SignalStateChange();
}
DynamicState.SetStencilMask((uint)stencilTest.BackFuncMask,
(uint)stencilTest.BackMask,
(uint)stencilTest.BackFuncRef,
(uint)stencilTest.FrontFuncMask,
(uint)stencilTest.FrontMask,
(uint)stencilTest.FrontFuncRef);
_newState.StencilTestEnable = stencilTest.TestEnable;
_newState.StencilBackFailOp = stencilTest.BackSFail.Convert();
_newState.StencilBackPassOp = stencilTest.BackDpPass.Convert();
_newState.StencilBackDepthFailOp = stencilTest.BackDpFail.Convert();
_newState.StencilBackCompareOp = stencilTest.BackFunc.Convert();
_newState.StencilFrontFailOp = stencilTest.FrontSFail.Convert();
_newState.StencilFrontPassOp = stencilTest.FrontDpPass.Convert();
_newState.StencilFrontDepthFailOp = stencilTest.FrontDpFail.Convert();
_newState.StencilFrontCompareOp = stencilTest.FrontFunc.Convert();
UpdatePassDepthStencil();
SignalStateChange();
}
public void SetStorageBuffers(ReadOnlySpan<BufferAssignment> buffers)
@@ -1207,12 +1399,24 @@ namespace Ryujinx.Graphics.Vulkan
{
int count = Math.Min(Constants.MaxVertexBuffers, vertexBuffers.Length);
_newState.Internal.VertexBindingDescriptions[0] = new VertexInputBindingDescription(0, 0, VertexInputRate.Vertex);
int validCount = 1;
if (!_bindingsSet)
{
_newState.Internal.VertexBindingDescriptions[0] = new VertexInputBindingDescription(0, _supportExtDynamic && (!Gd.IsMoltenVk || Gd.SupportsMTL31) ? null : 0, VertexInputRate.Vertex);
for (int i = 1; i < count; i++)
{
_newState.Internal.VertexBindingDescriptions[i] = new VertexInputBindingDescription((uint)i);
}
_bindingsSet = true;
}
BufferHandle lastHandle = default;
Auto<DisposableBuffer> lastBuffer = default;
bool vertexBindingDescriptionChanged = false;
bool vertexDescriptionCountChanged = false;
for (int i = 0; i < count; i++)
{
@@ -1231,13 +1435,32 @@ namespace Ryujinx.Graphics.Vulkan
if (vb != null)
{
int binding = i + 1;
int descriptorIndex = validCount++;
_newState.Internal.VertexBindingDescriptions[descriptorIndex] = new VertexInputBindingDescription(
(uint)binding,
(uint)vertexBuffer.Stride,
inputRate);
if (_supportExtDynamic && (!Gd.IsMoltenVk || Gd.SupportsMTL31))
{
if (_newState.Internal.VertexBindingDescriptions[descriptorIndex].InputRate != inputRate ||
_newState.Internal.VertexBindingDescriptions[descriptorIndex].Binding != _vertexBufferBindings[i])
{
_newState.Internal.VertexBindingDescriptions[descriptorIndex].InputRate = inputRate;
_newState.Internal.VertexBindingDescriptions[descriptorIndex].Binding = (uint)_vertexBufferBindings[i];
vertexBindingDescriptionChanged = true;
}
}
else
{
if (_newState.Internal.VertexBindingDescriptions[descriptorIndex].InputRate != inputRate ||
_newState.Internal.VertexBindingDescriptions[descriptorIndex].Stride != vertexBuffer.Stride ||
_newState.Internal.VertexBindingDescriptions[descriptorIndex].Binding != _vertexBufferBindings[i])
{
_newState.Internal.VertexBindingDescriptions[descriptorIndex].Binding = (uint)_vertexBufferBindings[i];
_newState.Internal.VertexBindingDescriptions[descriptorIndex].Stride = (uint)vertexBuffer.Stride;
_newState.Internal.VertexBindingDescriptions[descriptorIndex].InputRate = inputRate;
vertexBindingDescriptionChanged = true;
}
}
int vbSize = vertexBuffer.Buffer.Size;
@@ -1253,7 +1476,7 @@ namespace Ryujinx.Graphics.Vulkan
}
}
ref var buffer = ref _vertexBuffers[binding];
ref var buffer = ref _vertexBuffers[_vertexBufferBindings[i]];
int oldScalarAlign = buffer.AttributeScalarAlignment;
if (Gd.Capabilities.VertexBufferAlignment < 2 &&
@@ -1270,7 +1493,7 @@ namespace Ryujinx.Graphics.Vulkan
vbSize,
vertexBuffer.Stride);
buffer.BindVertexBuffer(Gd, Cbs, (uint)binding, ref _newState, _vertexBufferUpdater);
buffer.BindVertexBuffer(Gd, Cbs, (uint)_vertexBufferBindings[i], ref _newState, _vertexBufferUpdater);
}
}
else
@@ -1286,7 +1509,7 @@ namespace Ryujinx.Graphics.Vulkan
vbSize,
vertexBuffer.Stride);
_vertexBuffersDirty |= 1UL << binding;
_vertexBuffersDirty |= 1UL << _vertexBufferBindings[i];
}
buffer.AttributeScalarAlignment = oldScalarAlign;
@@ -1296,11 +1519,19 @@ namespace Ryujinx.Graphics.Vulkan
_vertexBufferUpdater.Commit(Cbs);
_newState.VertexBindingDescriptionsCount = (uint)validCount;
SignalStateChange();
if (_newState.VertexBindingDescriptionsCount != validCount)
{
_newState.VertexBindingDescriptionsCount = (uint)validCount;
vertexDescriptionCountChanged = true;
}
if (vertexDescriptionCountChanged || vertexBindingDescriptionChanged)
{
SignalStateChange();
}
}
public void SetViewports(ReadOnlySpan<Viewport> viewports)
public void SetViewports(ReadOnlySpan<Viewport> viewports, bool signalChange = true)
{
int maxViewports = Gd.Capabilities.SupportsMultiView ? Constants.MaxViewports : 1;
int count = Math.Min(maxViewports, viewports.Length);
@@ -1325,8 +1556,15 @@ namespace Ryujinx.Graphics.Vulkan
Clamp(viewport.DepthFar)));
}
_newState.ViewportsCount = (uint)count;
SignalStateChange();
if (!_supportExtDynamic)
{
_newState.ViewportsCount = (uint)count;
if (signalChange)
{
SignalStateChange();
}
}
}
public void SwapBuffer(Auto<DisposableBuffer> from, Auto<DisposableBuffer> to)
@@ -1375,7 +1613,7 @@ namespace Ryujinx.Graphics.Vulkan
_vertexBuffersDirty = ulong.MaxValue >> (64 - _vertexBuffers.Length);
_descriptorSetUpdater.SignalCommandBufferChange();
DynamicState.ForceAllDirty();
DynamicState.ForceAllDirty(Gd);
_currentPipelineHandle = 0;
}
@@ -1529,9 +1767,10 @@ namespace Ryujinx.Graphics.Vulkan
{
if (Gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop)
{
_newState.FeedbackLoopDynamicState = true;
DynamicState.SetFeedbackLoop(aspects);
}
else
else if (Gd.Capabilities.SupportsAttachmentFeedbackLoop)
{
_newState.FeedbackLoopAspects = aspects;
}
@@ -1591,7 +1830,14 @@ namespace Ryujinx.Graphics.Vulkan
}
// Stencil test being enabled doesn't necessarily mean a write, but it's not critical to check.
_passWritesDepthStencil |= (_newState.DepthTestEnable && _newState.DepthWriteEnable) || _newState.StencilTestEnable;
if (_supportExtDynamic)
{
_passWritesDepthStencil |= (DynamicState.DepthTestEnable && DynamicState.DepthWriteEnable) || DynamicState.StencilTestEnable;
}
else
{
_passWritesDepthStencil |= (_newState.DepthTestEnable && _newState.DepthWriteEnable) || _newState.StencilTestEnable;
}
}
private bool RecreateGraphicsPipelineIfNeeded()

View File

@@ -4,6 +4,7 @@ using Silk.NET.Vulkan;
using System;
using Format = Silk.NET.Vulkan.Format;
using PolygonMode = Silk.NET.Vulkan.PolygonMode;
using PrimitiveTopology = Ryujinx.Graphics.GAL.PrimitiveTopology;
namespace Ryujinx.Graphics.Vulkan
{
@@ -160,64 +161,84 @@ namespace Ryujinx.Graphics.Vulkan
public static PipelineState ToVulkanPipelineState(this ProgramPipelineState state, VulkanRenderer gd)
{
var extendedDynamicState2 = gd.Capabilities.SupportsExtendedDynamicState2;
var extendedDynamicState = gd.Capabilities.SupportsExtendedDynamicState;
PipelineState pipeline = new();
pipeline.Initialize();
pipeline.Initialize(gd.Capabilities);
// It is assumed that Dynamic State is enabled when this conversion is used.
pipeline.CullMode = state.CullEnable ? state.CullMode.Convert() : CullModeFlags.None;
pipeline.DepthBoundsTestEnable = false; // Not implemented.
pipeline.DepthClampEnable = state.DepthClampEnable;
pipeline.DepthTestEnable = state.DepthTest.TestEnable;
pipeline.DepthWriteEnable = state.DepthTest.WriteEnable;
pipeline.DepthCompareOp = state.DepthTest.Func.Convert();
pipeline.AlphaToCoverageEnable = state.AlphaToCoverageEnable;
pipeline.AlphaToOneEnable = state.AlphaToOneEnable;
pipeline.DepthMode = state.DepthMode == DepthMode.MinusOneToOne;
pipeline.FrontFace = state.FrontFace.Convert();
pipeline.HasDepthStencil = state.DepthStencilEnable;
pipeline.LineWidth = state.LineWidth;
pipeline.LogicOpEnable = state.LogicOpEnable;
pipeline.LogicOp = state.LogicOp.Convert();
pipeline.PatchControlPoints = state.PatchControlPoints;
pipeline.PolygonMode = PolygonMode.Fill; // Not implemented.
pipeline.PrimitiveRestartEnable = state.PrimitiveRestartEnable;
pipeline.RasterizerDiscardEnable = state.RasterizerDiscard;
pipeline.SamplesCount = (uint)state.SamplesCount;
if (gd.Capabilities.SupportsMultiView)
{
pipeline.ScissorsCount = Constants.MaxViewports;
pipeline.ViewportsCount = Constants.MaxViewports;
}
else
{
pipeline.ScissorsCount = 1;
pipeline.ViewportsCount = 1;
}
pipeline.DepthBiasEnable = state.BiasEnable != 0;
// Stencil masks and ref are dynamic, so are 0 in the Vulkan pipeline.
pipeline.StencilFrontFailOp = state.StencilTest.FrontSFail.Convert();
pipeline.StencilFrontPassOp = state.StencilTest.FrontDpPass.Convert();
pipeline.StencilFrontDepthFailOp = state.StencilTest.FrontDpFail.Convert();
pipeline.StencilFrontCompareOp = state.StencilTest.FrontFunc.Convert();
pipeline.StencilBackFailOp = state.StencilTest.BackSFail.Convert();
pipeline.StencilBackPassOp = state.StencilTest.BackDpPass.Convert();
pipeline.StencilBackDepthFailOp = state.StencilTest.BackDpFail.Convert();
pipeline.StencilBackCompareOp = state.StencilTest.BackFunc.Convert();
pipeline.StencilTestEnable = state.StencilTest.TestEnable;
pipeline.Topology = gd.TopologyRemap(state.Topology).Convert();
if (!extendedDynamicState)
{
pipeline.DepthCompareOp = state.DepthTest.Func.Convert();
pipeline.CullMode = state.CullMode.Convert();
pipeline.DepthTestEnable = state.DepthTest.TestEnable;
pipeline.DepthWriteEnable = state.DepthTest.WriteEnable;
pipeline.FrontFace = state.FrontFace.Convert();
if (gd.Capabilities.SupportsMultiView)
{
pipeline.ScissorsCount = Constants.MaxViewports;
pipeline.ViewportsCount = Constants.MaxViewports;
}
else
{
pipeline.ScissorsCount = 1;
pipeline.ViewportsCount = 1;
}
pipeline.StencilTestEnable = state.StencilTest.TestEnable;
pipeline.StencilFrontFailOp = state.StencilTest.FrontSFail.Convert();
pipeline.StencilFrontPassOp = state.StencilTest.FrontDpPass.Convert();
pipeline.StencilFrontDepthFailOp = state.StencilTest.FrontDpFail.Convert();
pipeline.StencilFrontCompareOp = state.StencilTest.FrontFunc.Convert();
pipeline.StencilBackFailOp = state.StencilTest.BackSFail.Convert();
pipeline.StencilBackPassOp = state.StencilTest.BackDpPass.Convert();
pipeline.StencilBackDepthFailOp = state.StencilTest.BackDpFail.Convert();
pipeline.StencilBackCompareOp = state.StencilTest.BackFunc.Convert();
}
if (!extendedDynamicState2.ExtendedDynamicState2)
{
pipeline.PrimitiveRestartEnable = state.PrimitiveRestartEnable;
pipeline.RasterizerDiscardEnable = state.RasterizerDiscard;
pipeline.DepthBiasEnable = (state.BiasEnable != 0) &&
(state.DepthBiasFactor != 0 && state.DepthBiasUnits != 0);
}
if (!extendedDynamicState2.ExtendedDynamicState2LogicOp)
{
pipeline.LogicOp = state.LogicOp.Convert();
}
if (!extendedDynamicState2.ExtendedDynamicState2PatchControlPoints)
{
pipeline.PatchControlPoints = state.PatchControlPoints;
}
pipeline.SamplesCount = (uint)state.SamplesCount;
pipeline.LogicOpEnable = state.LogicOpEnable;
int vaCount = Math.Min(Constants.MaxVertexAttributes, state.VertexAttribCount);
int vbCount = Math.Min(Constants.MaxVertexBuffers, state.VertexBufferCount);
@@ -241,7 +262,7 @@ namespace Ryujinx.Graphics.Vulkan
}
int descriptorIndex = 1;
pipeline.Internal.VertexBindingDescriptions[0] = new VertexInputBindingDescription(0, 0, VertexInputRate.Vertex);
pipeline.Internal.VertexBindingDescriptions[0] = new VertexInputBindingDescription(0, extendedDynamicState && (!gd.IsMoltenVk || gd.SupportsMTL31) ? null : 0, VertexInputRate.Vertex);
for (int i = 0; i < vbCount; i++)
{
@@ -261,7 +282,7 @@ namespace Ryujinx.Graphics.Vulkan
// TODO: Support divisor > 1
pipeline.Internal.VertexBindingDescriptions[descriptorIndex++] = new VertexInputBindingDescription(
(uint)i + 1,
(uint)alignedStride,
extendedDynamicState && (!gd.IsMoltenVk || gd.SupportsMTL31) ? null : (uint)alignedStride,
inputRate);
}
}

View File

@@ -1,6 +1,8 @@
using Ryujinx.Common.Memory;
using Silk.NET.Vulkan;
using Silk.NET.Vulkan.Extensions.EXT;
using System;
using System.Numerics;
namespace Ryujinx.Graphics.Vulkan
{
@@ -9,6 +11,7 @@ namespace Ryujinx.Graphics.Vulkan
private float _depthBiasSlopeFactor;
private float _depthBiasConstantFactor;
private float _depthBiasClamp;
private bool _depthBiasEnable;
public int ScissorsCount;
private Array16<Rect2D> _scissors;
@@ -20,6 +23,23 @@ namespace Ryujinx.Graphics.Vulkan
private uint _frontWriteMask;
private uint _frontReference;
private StencilOp _backFailOp;
private StencilOp _backPassOp;
private StencilOp _backDepthFailOp;
private CompareOp _backCompareOp;
private StencilOp _frontFailOp;
private StencilOp _frontPassOp;
private StencilOp _frontDepthFailOp;
private CompareOp _frontCompareOp;
private float _lineWidth;
public bool StencilTestEnable;
public bool DepthTestEnable;
public bool DepthWriteEnable;
private CompareOp _depthCompareOp;
private Array4<float> _blendConstants;
private FeedbackLoopAspects _feedbackLoopAspects;
@@ -27,6 +47,20 @@ namespace Ryujinx.Graphics.Vulkan
public uint ViewportsCount;
public Array16<Viewport> Viewports;
public CullModeFlags CullMode;
private FrontFace _frontFace;
private bool _discard;
private LogicOp _logicOp;
private uint _patchControlPoints;
public PrimitiveTopology Topology;
private bool _primitiveRestartEnable;
[Flags]
private enum DirtyFlags
{
None = 0,
@@ -36,7 +70,21 @@ namespace Ryujinx.Graphics.Vulkan
Stencil = 1 << 3,
Viewport = 1 << 4,
FeedbackLoop = 1 << 5,
All = Blend | DepthBias | Scissor | Stencil | Viewport | FeedbackLoop,
CullMode = 1 << 6,
FrontFace = 1 << 7,
DepthTestBool = 1 << 8,
DepthTestCompareOp = 1 << 9,
StencilTestEnableAndStencilOp = 1 << 10,
LineWidth = 1 << 11,
RasterDiscard = 1 << 12,
LogicOp = 1 << 13,
PatchControlPoints = 1 << 14,
PrimitiveRestart = 1 << 15,
PrimitiveTopology = 1 << 16,
DepthBiasEnable = 1 << 17,
Standard = Blend | DepthBias | Scissor | Stencil | Viewport,
Extended = CullMode | FrontFace | DepthTestBool | DepthTestCompareOp | StencilTestEnableAndStencilOp | PrimitiveTopology,
Extended2 = RasterDiscard | PrimitiveRestart | DepthBiasEnable,
}
private DirtyFlags _dirty;
@@ -47,7 +95,6 @@ namespace Ryujinx.Graphics.Vulkan
_blendConstants[1] = g;
_blendConstants[2] = b;
_blendConstants[3] = a;
_dirty |= DirtyFlags.Blend;
}
@@ -60,15 +107,64 @@ namespace Ryujinx.Graphics.Vulkan
_dirty |= DirtyFlags.DepthBias;
}
public void SetDepthBiasEnable(bool enable)
{
_depthBiasEnable = enable;
_dirty |= DirtyFlags.DepthBiasEnable;
}
public void SetScissor(int index, Rect2D scissor)
{
_scissors[index] = scissor;
_dirty |= DirtyFlags.Scissor;
}
public void SetStencilMasks(
uint backCompareMask,
public void SetDepthTestBool(bool testEnable, bool writeEnable)
{
DepthTestEnable = testEnable;
DepthWriteEnable = writeEnable;
_dirty |= DirtyFlags.DepthTestBool;
}
public void SetDepthTestCompareOp(CompareOp depthTestOp)
{
_depthCompareOp = depthTestOp;
_dirty |= DirtyFlags.DepthTestCompareOp;
}
public void SetStencilTestandOp(
StencilOp backFailOp,
StencilOp backPassOp,
StencilOp backDepthFailOp,
CompareOp backCompareOp,
StencilOp frontFailOp,
StencilOp frontPassOp,
StencilOp frontDepthFailOp,
CompareOp frontCompareOp,
bool stencilTestEnable)
{
_backFailOp = backFailOp;
_backPassOp = backPassOp;
_backDepthFailOp = backDepthFailOp;
_backCompareOp = backCompareOp;
_frontFailOp = frontFailOp;
_frontPassOp = frontPassOp;
_frontDepthFailOp = frontDepthFailOp;
_frontCompareOp = frontCompareOp;
StencilTestEnable = stencilTestEnable;
_dirty |= DirtyFlags.StencilTestEnableAndStencilOp;
}
public void SetStencilTest(bool stencilTestEnable)
{
StencilTestEnable = stencilTestEnable;
_dirty |= DirtyFlags.StencilTestEnableAndStencilOp;
}
public void SetStencilMask(uint backCompareMask,
uint backWriteMask,
uint backReference,
uint frontCompareMask,
@@ -81,28 +177,46 @@ namespace Ryujinx.Graphics.Vulkan
_frontCompareMask = frontCompareMask;
_frontWriteMask = frontWriteMask;
_frontReference = frontReference;
_dirty |= DirtyFlags.Stencil;
}
public void SetViewport(int index, Viewport viewport)
{
Viewports[index] = viewport;
_dirty |= DirtyFlags.Viewport;
}
public void SetViewports(ref Array16<Viewport> viewports, uint viewportsCount)
{
Viewports = viewports;
ViewportsCount = viewportsCount;
if (ViewportsCount != 0)
if (!Viewports.Equals(viewports) || ViewportsCount != viewportsCount)
{
_dirty |= DirtyFlags.Viewport;
Viewports = viewports;
ViewportsCount = viewportsCount;
if (ViewportsCount != 0)
{
_dirty |= DirtyFlags.Viewport;
}
}
}
public void SetCullMode(CullModeFlags cullMode)
{
CullMode = cullMode;
_dirty |= DirtyFlags.CullMode;
}
public void SetFrontFace(FrontFace frontFace)
{
_frontFace = frontFace;
_dirty |= DirtyFlags.FrontFace;
}
public void SetLineWidth(float width)
{
_lineWidth = width;
_dirty |= DirtyFlags.LineWidth;
}
public void SetFeedbackLoop(FeedbackLoopAspects aspects)
{
_feedbackLoopAspects = aspects;
@@ -110,43 +224,149 @@ namespace Ryujinx.Graphics.Vulkan
_dirty |= DirtyFlags.FeedbackLoop;
}
public void ForceAllDirty()
public void SetRasterizerDiscard(bool discard)
{
_dirty = DirtyFlags.All;
_discard = discard;
_dirty |= DirtyFlags.RasterDiscard;
}
public void SetPrimitiveRestartEnable(bool primitiveRestart)
{
_primitiveRestartEnable = primitiveRestart;
_dirty |= DirtyFlags.PrimitiveRestart;
}
public void SetPrimitiveTopology(PrimitiveTopology primitiveTopology)
{
Topology = primitiveTopology;
_dirty |= DirtyFlags.PrimitiveTopology;
}
public void SetLogicOp(LogicOp op)
{
_logicOp = op;
_dirty |= DirtyFlags.LogicOp;
}
public void SetPatchControlPoints(uint points)
{
_patchControlPoints = points;
_dirty |= DirtyFlags.PatchControlPoints;
}
public void ForceAllDirty(VulkanRenderer gd)
{
_dirty = DirtyFlags.Standard;
if (gd.Capabilities.SupportsExtendedDynamicState)
{
_dirty |= DirtyFlags.Extended;
}
if (gd.Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2)
{
_dirty |= DirtyFlags.Extended2;
if (gd.Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2LogicOp)
{
_dirty |= DirtyFlags.LogicOp;
}
if (gd.Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2PatchControlPoints)
{
_dirty |= DirtyFlags.PatchControlPoints;
}
}
if (!gd.IsMoltenVk)
{
_dirty |= DirtyFlags.LineWidth;
}
if (gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop)
{
_dirty |= DirtyFlags.FeedbackLoop;
}
}
public void ReplayIfDirty(VulkanRenderer gd, CommandBuffer commandBuffer)
{
Vk api = gd.Api;
if (_dirty.HasFlag(DirtyFlags.Blend))
if (_dirty == DirtyFlags.None)
{
RecordBlend(api, commandBuffer);
return;
}
if (_dirty.HasFlag(DirtyFlags.DepthBias))
{
RecordDepthBias(api, commandBuffer);
}
var api = gd.Api;
var extendedStateApi = gd.ExtendedDynamicStateApi;
var extendedState2Api = gd.ExtendedDynamicState2Api;
var dynamicFeedbackLoopApi = gd.DynamicFeedbackLoopApi;
if (_dirty.HasFlag(DirtyFlags.Scissor))
{
RecordScissor(api, commandBuffer);
}
DirtyFlags dirtyFlags = _dirty;
if (_dirty.HasFlag(DirtyFlags.Stencil))
while (dirtyFlags != DirtyFlags.None)
{
RecordStencilMasks(api, commandBuffer);
}
int bitIndex = BitOperations.TrailingZeroCount((uint)dirtyFlags);
DirtyFlags currentFlag = (DirtyFlags)(1 << bitIndex);
if (_dirty.HasFlag(DirtyFlags.Viewport))
{
RecordViewport(api, commandBuffer);
}
switch (currentFlag)
{
case DirtyFlags.Blend:
RecordBlend(api, commandBuffer);
break;
case DirtyFlags.DepthBias:
RecordDepthBias(api, commandBuffer);
break;
case DirtyFlags.Scissor:
RecordScissor(gd, commandBuffer);
break;
case DirtyFlags.Stencil:
RecordStencil(api, commandBuffer);
break;
case DirtyFlags.Viewport:
RecordViewport(gd, commandBuffer);
break;
case DirtyFlags.FeedbackLoop:
RecordFeedbackLoop(dynamicFeedbackLoopApi, commandBuffer);
break;
case DirtyFlags.CullMode:
RecordCullMode(extendedStateApi, commandBuffer);
break;
case DirtyFlags.FrontFace:
RecordFrontFace(extendedStateApi, commandBuffer);
break;
case DirtyFlags.DepthTestBool:
RecordDepthTestBool(extendedStateApi, commandBuffer);
break;
case DirtyFlags.DepthTestCompareOp:
RecordDepthTestCompareOp(extendedStateApi, commandBuffer);
break;
case DirtyFlags.StencilTestEnableAndStencilOp:
RecordStencilTestAndOp(extendedStateApi, commandBuffer);
break;
case DirtyFlags.LineWidth:
RecordLineWidth(api, commandBuffer);
break;
case DirtyFlags.RasterDiscard:
RecordRasterizationDiscard(extendedState2Api, commandBuffer);
break;
case DirtyFlags.LogicOp:
RecordLogicOp(extendedState2Api, commandBuffer);
break;
case DirtyFlags.PatchControlPoints:
RecordPatchControlPoints(extendedState2Api, commandBuffer);
break;
case DirtyFlags.PrimitiveRestart:
RecordPrimitiveRestartEnable(gd, commandBuffer);
break;
case DirtyFlags.PrimitiveTopology:
RecordPrimitiveTopology(extendedStateApi, commandBuffer);
break;
case DirtyFlags.DepthBiasEnable:
RecordDepthBiasEnable(extendedState2Api, commandBuffer);
break;
}
if (_dirty.HasFlag(DirtyFlags.FeedbackLoop) && gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop)
{
RecordFeedbackLoop(gd.DynamicFeedbackLoopApi, commandBuffer);
dirtyFlags &= ~currentFlag;
}
_dirty = DirtyFlags.None;
@@ -162,15 +382,27 @@ namespace Ryujinx.Graphics.Vulkan
api.CmdSetDepthBias(commandBuffer, _depthBiasConstantFactor, _depthBiasClamp, _depthBiasSlopeFactor);
}
private void RecordScissor(Vk api, CommandBuffer commandBuffer)
private readonly void RecordDepthBiasEnable(ExtExtendedDynamicState2 gd, CommandBuffer commandBuffer)
{
gd.CmdSetDepthBiasEnable(commandBuffer, _depthBiasEnable);
}
private void RecordScissor(VulkanRenderer gd, CommandBuffer commandBuffer)
{
if (ScissorsCount != 0)
{
api.CmdSetScissor(commandBuffer, 0, (uint)ScissorsCount, _scissors.AsSpan());
if (gd.Capabilities.SupportsExtendedDynamicState)
{
gd.ExtendedDynamicStateApi.CmdSetScissorWithCount(commandBuffer, (uint)ScissorsCount, _scissors.AsSpan());
}
else
{
gd.Api.CmdSetScissor(commandBuffer, 0, (uint)ScissorsCount, _scissors.AsSpan());
}
}
}
private readonly void RecordStencilMasks(Vk api, CommandBuffer commandBuffer)
private readonly void RecordStencil(Vk api, CommandBuffer commandBuffer)
{
api.CmdSetStencilCompareMask(commandBuffer, StencilFaceFlags.FaceBackBit, _backCompareMask);
api.CmdSetStencilWriteMask(commandBuffer, StencilFaceFlags.FaceBackBit, _backWriteMask);
@@ -180,12 +412,107 @@ namespace Ryujinx.Graphics.Vulkan
api.CmdSetStencilReference(commandBuffer, StencilFaceFlags.FaceFrontBit, _frontReference);
}
private void RecordViewport(Vk api, CommandBuffer commandBuffer)
private readonly void RecordStencilTestAndOp(ExtExtendedDynamicState api, CommandBuffer commandBuffer)
{
if (ViewportsCount != 0)
api.CmdSetStencilTestEnable(commandBuffer, StencilTestEnable);
api.CmdSetStencilOp(commandBuffer, StencilFaceFlags.FaceBackBit, _backFailOp, _backPassOp, _backDepthFailOp, _backCompareOp);
api.CmdSetStencilOp(commandBuffer, StencilFaceFlags.FaceFrontBit, _frontFailOp, _frontPassOp, _frontDepthFailOp, _frontCompareOp);
}
private void RecordViewport(VulkanRenderer gd, CommandBuffer commandBuffer)
{
if (ViewportsCount == 0)
{
api.CmdSetViewport(commandBuffer, 0, ViewportsCount, Viewports.AsSpan());
return;
}
if (gd.Capabilities.SupportsExtendedDynamicState)
{
gd.ExtendedDynamicStateApi.CmdSetViewportWithCount(commandBuffer, ViewportsCount, Viewports.AsSpan());
}
else
{
gd.Api.CmdSetViewport(commandBuffer, 0, ViewportsCount, Viewports.AsSpan());
}
}
private readonly void RecordCullMode(ExtExtendedDynamicState api, CommandBuffer commandBuffer)
{
api.CmdSetCullMode(commandBuffer, CullMode);
}
private readonly void RecordFrontFace(ExtExtendedDynamicState api, CommandBuffer commandBuffer)
{
api.CmdSetFrontFace(commandBuffer, _frontFace);
}
private readonly void RecordDepthTestBool(ExtExtendedDynamicState api, CommandBuffer commandBuffer)
{
api.CmdSetDepthTestEnable(commandBuffer, DepthTestEnable);
api.CmdSetDepthWriteEnable(commandBuffer, DepthWriteEnable);
}
private readonly void RecordDepthTestCompareOp(ExtExtendedDynamicState api, CommandBuffer commandBuffer)
{
api.CmdSetDepthCompareOp(commandBuffer, _depthCompareOp);
}
private readonly void RecordRasterizationDiscard(ExtExtendedDynamicState2 extendedDynamicState2Api, CommandBuffer commandBuffer)
{
extendedDynamicState2Api.CmdSetRasterizerDiscardEnable(commandBuffer, _discard);
}
private readonly void RecordPrimitiveRestartEnable(VulkanRenderer gd, CommandBuffer commandBuffer)
{
bool primitiveRestartEnable = _primitiveRestartEnable;
bool topologySupportsRestart;
if (gd.Capabilities.SupportsPrimitiveTopologyListRestart)
{
topologySupportsRestart = gd.Capabilities.SupportsPrimitiveTopologyPatchListRestart ||
Topology != PrimitiveTopology.PatchList;
}
else
{
topologySupportsRestart = Topology == PrimitiveTopology.LineStrip ||
Topology == PrimitiveTopology.TriangleStrip ||
Topology == PrimitiveTopology.TriangleFan ||
Topology == PrimitiveTopology.LineStripWithAdjacency ||
Topology == PrimitiveTopology.TriangleStripWithAdjacency;
}
primitiveRestartEnable &= topologySupportsRestart;
// Cannot disable primitiveRestartEnable for these Topologies on MacOS.
if (gd.IsMoltenVk)
{
primitiveRestartEnable = true;
}
gd.ExtendedDynamicState2Api.CmdSetPrimitiveRestartEnable(commandBuffer, primitiveRestartEnable);
}
private readonly void RecordPrimitiveTopology(ExtExtendedDynamicState extendedDynamicStateApi, CommandBuffer commandBuffer)
{
extendedDynamicStateApi.CmdSetPrimitiveTopology(commandBuffer, Topology);
}
private readonly void RecordLogicOp(ExtExtendedDynamicState2 extendedDynamicState2Api, CommandBuffer commandBuffer)
{
extendedDynamicState2Api.CmdSetLogicOp(commandBuffer, _logicOp);
}
private readonly void RecordPatchControlPoints(ExtExtendedDynamicState2 extendedDynamicState2Api, CommandBuffer commandBuffer)
{
extendedDynamicState2Api.CmdSetPatchControlPoints(commandBuffer, _patchControlPoints);
}
private readonly void RecordLineWidth(Vk api, CommandBuffer commandBuffer)
{
api.CmdSetLineWidth(commandBuffer, _lineWidth);
}
private readonly void RecordFeedbackLoop(ExtAttachmentFeedbackLoopDynamicState api, CommandBuffer commandBuffer)

View File

@@ -9,11 +9,15 @@ namespace Ryujinx.Graphics.Vulkan
{
}
public void SetRenderTarget(TextureView view, uint width, uint height)
public void SetRenderTarget(TextureView view, uint width, uint height, bool signalChange = true)
{
CreateFramebuffer(view, width, height);
CreateRenderPass();
SignalStateChange();
if (signalChange)
{
SignalStateChange();
}
}
private void CreateFramebuffer(TextureView view, uint width, uint height)

File diff suppressed because it is too large Load Diff

View File

@@ -11,23 +11,17 @@ namespace Ryujinx.Graphics.Vulkan
{
public ulong Id0;
public ulong Id1;
public ulong Id2;
public ulong Id3;
public ulong Id4;
public ulong Id5;
public ulong Id6;
public ulong Id7;
public ulong Id8;
private readonly uint VertexAttributeDescriptionsCount => (byte)((Id5 >> 38) & 0xFF);
private readonly uint VertexBindingDescriptionsCount => (byte)((Id5 >> 46) & 0xFF);
private readonly uint ColorBlendAttachmentStateCount => (byte)((Id6 >> 8) & 0xFF);
private readonly bool HasDepthStencil => ((Id6 >> 63) & 0x1) != 0UL;
private readonly uint VertexAttributeDescriptionsCount => (byte)((Id0 >> 38) & 0xFF);
private readonly uint VertexBindingDescriptionsCount => (byte)((Id0 >> 46) & 0xFF);
private readonly uint ColorBlendAttachmentStateCount => (byte)((Id1 >> 8) & 0xFF);
private readonly bool HasDepthStencil => ((Id1 >> 63) & 0x1) != 0UL;
public Array32<VertexInputAttributeDescription> VertexAttributeDescriptions;
public Array33<VertexInputBindingDescription> VertexBindingDescriptions;
public Array32<VertexInputBindingDescription> VertexBindingDescriptions;
public Array8<PipelineColorBlendAttachmentState> ColorBlendAttachmentState;
public Array9<Format> AttachmentFormats;
public uint AttachmentIntegerFormatMask;
@@ -40,9 +34,7 @@ namespace Ryujinx.Graphics.Vulkan
public bool Equals(ref PipelineUid other)
{
if (!Unsafe.As<ulong, Vector256<byte>>(ref Id0).Equals(Unsafe.As<ulong, Vector256<byte>>(ref other.Id0)) ||
!Unsafe.As<ulong, Vector256<byte>>(ref Id4).Equals(Unsafe.As<ulong, Vector256<byte>>(ref other.Id4)) ||
!Unsafe.As<ulong, Vector128<byte>>(ref Id7).Equals(Unsafe.As<ulong, Vector128<byte>>(ref other.Id7)))
if (!Unsafe.As<ulong, Vector256<byte>>(ref Id0).Equals(Unsafe.As<ulong, Vector256<byte>>(ref other.Id0)))
{
return false;
}
@@ -80,12 +72,7 @@ namespace Ryujinx.Graphics.Vulkan
ulong hash64 = Id0 * 23 ^
Id1 * 23 ^
Id2 * 23 ^
Id3 * 23 ^
Id4 * 23 ^
Id5 * 23 ^
Id6 * 23 ^
Id7 * 23 ^
Id8 * 23;
Id3 * 23;
for (int i = 0; i < (int)VertexAttributeDescriptionsCount; i++)
{

View File

@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using PrimitiveTopology = Silk.NET.Vulkan.PrimitiveTopology;
namespace Ryujinx.Graphics.Vulkan
{
@@ -538,7 +539,7 @@ namespace Ryujinx.Graphics.Vulkan
public void CreateBackgroundComputePipeline()
{
PipelineState pipeline = new();
pipeline.Initialize();
pipeline.Initialize(_gd.Capabilities);
pipeline.Stages[0] = _shaders[0].GetInfo();
pipeline.StagesCount = 1;

View File

@@ -73,7 +73,10 @@ namespace Ryujinx.Graphics.Vulkan
_buffer = autoBuffer;
state.Internal.VertexBindingDescriptions[DescriptorIndex].Stride = (uint)stride;
if (!gd.Capabilities.SupportsExtendedDynamicState)
{
state.Internal.VertexBindingDescriptions[DescriptorIndex].Stride = (uint)stride;
}
}
return;
@@ -81,8 +84,11 @@ namespace Ryujinx.Graphics.Vulkan
autoBuffer = gd.BufferManager.GetBuffer(cbs.CommandBuffer, _handle, false, out int size);
// The original stride must be reapplied in case it was rewritten.
state.Internal.VertexBindingDescriptions[DescriptorIndex].Stride = (uint)_stride;
if (!gd.Capabilities.SupportsExtendedDynamicState)
{
// The original stride must be reapplied in case it was rewritten.
state.Internal.VertexBindingDescriptions[DescriptorIndex].Stride = (uint)_stride;
}
if (_offset >= size)
{

View File

@@ -51,7 +51,7 @@ namespace Ryujinx.Graphics.Vulkan
{
if (_count != 0)
{
if (_gd.Capabilities.SupportsExtendedDynamicState)
if (_gd.Capabilities.SupportsExtendedDynamicState && (_gd.SupportsMTL31 || !_gd.IsMoltenVk))
{
_gd.ExtendedDynamicStateApi.CmdBindVertexBuffers2(
cbs.CommandBuffer,

View File

@@ -23,6 +23,7 @@ namespace Ryujinx.Graphics.Vulkan
private static readonly string[] _desirableExtensions = {
ExtConditionalRendering.ExtensionName,
ExtExtendedDynamicState.ExtensionName,
ExtExtendedDynamicState2.ExtensionName,
ExtTransformFeedback.ExtensionName,
KhrDrawIndirectCount.ExtensionName,
KhrPushDescriptor.ExtensionName,
@@ -314,6 +315,17 @@ namespace Ryujinx.Graphics.Vulkan
features2.PNext = &supportedFeaturesCustomBorderColor;
}
PhysicalDeviceExtendedDynamicState2FeaturesEXT supportedFeaturesExtExtendedDynamicState2 = new()
{
SType = StructureType.PhysicalDeviceExtendedDynamicState2FeaturesExt,
PNext = features2.PNext,
};
if (physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState2.ExtensionName))
{
features2.PNext = &supportedFeaturesExtExtendedDynamicState2;
}
PhysicalDevicePrimitiveTopologyListRestartFeaturesEXT supportedFeaturesPrimitiveTopologyListRestart = new()
{
SType = StructureType.PhysicalDevicePrimitiveTopologyListRestartFeaturesExt,
@@ -416,6 +428,7 @@ namespace Ryujinx.Graphics.Vulkan
TessellationShader = supportedFeatures.TessellationShader,
VertexPipelineStoresAndAtomics = supportedFeatures.VertexPipelineStoresAndAtomics,
RobustBufferAccess = useRobustBufferAccess,
WideLines = supportedFeatures.WideLines,
SampleRateShading = supportedFeatures.SampleRateShading,
};
@@ -473,6 +486,20 @@ namespace Ryujinx.Graphics.Vulkan
pExtendedFeatures = &featuresExtendedDynamicState;
if (physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState2.ExtensionName))
{
var featuresExtendedDynamicState2 = new PhysicalDeviceExtendedDynamicState2FeaturesEXT()
{
SType = StructureType.PhysicalDeviceExtendedDynamicState2FeaturesExt,
PNext = pExtendedFeatures,
ExtendedDynamicState2 = supportedFeaturesExtExtendedDynamicState2.ExtendedDynamicState2,
ExtendedDynamicState2LogicOp = supportedFeaturesExtExtendedDynamicState2.ExtendedDynamicState2LogicOp,
ExtendedDynamicState2PatchControlPoints = supportedFeaturesExtExtendedDynamicState2.ExtendedDynamicState2PatchControlPoints,
};
pExtendedFeatures = &featuresExtendedDynamicState2;
}
var featuresVk11 = new PhysicalDeviceVulkan11Features
{
SType = StructureType.PhysicalDeviceVulkan11Features,

View File

@@ -39,6 +39,7 @@ namespace Ryujinx.Graphics.Vulkan
internal KhrSwapchain SwapchainApi { get; private set; }
internal ExtConditionalRendering ConditionalRenderingApi { get; private set; }
internal ExtExtendedDynamicState ExtendedDynamicStateApi { get; private set; }
internal ExtExtendedDynamicState2 ExtendedDynamicState2Api { get; private set; }
internal KhrPushDescriptor PushDescriptorApi { get; private set; }
internal ExtTransformFeedback TransformFeedbackApi { get; private set; }
internal KhrDrawIndirectCount DrawIndirectCountApi { get; private set; }
@@ -94,6 +95,7 @@ namespace Ryujinx.Graphics.Vulkan
internal bool IsIntelArc { get; private set; }
internal bool IsQualcommProprietary { get; private set; }
internal bool IsMoltenVk { get; private set; }
internal bool SupportsMTL31 { get; private set; }
internal bool IsTBDR { get; private set; }
internal bool IsSharedMemory { get; private set; }
@@ -119,6 +121,8 @@ namespace Ryujinx.Graphics.Vulkan
// Any device running on MacOS is using MoltenVK, even Intel and AMD vendors.
if (IsMoltenVk = OperatingSystem.IsMacOS())
MVKInitialization.Initialize();
SupportsMTL31 = OperatingSystem.IsMacOSVersionAtLeast(14);
}
public static VulkanRenderer Create(
@@ -141,6 +145,11 @@ namespace Ryujinx.Graphics.Vulkan
ExtendedDynamicStateApi = extendedDynamicStateApi;
}
if (Api.TryGetDeviceExtension(_instance.Instance, _device, out ExtExtendedDynamicState2 extendedDynamicState2Api))
{
ExtendedDynamicState2Api = extendedDynamicState2Api;
}
if (Api.TryGetDeviceExtension(_instance.Instance, _device, out KhrPushDescriptor pushDescriptorApi))
{
PushDescriptorApi = pushDescriptorApi;
@@ -235,6 +244,11 @@ namespace Ryujinx.Graphics.Vulkan
SType = StructureType.PhysicalDevicePrimitiveTopologyListRestartFeaturesExt,
};
PhysicalDeviceExtendedDynamicState2FeaturesEXT featuresExtendedDynamicState2 = new()
{
SType = StructureType.PhysicalDeviceExtendedDynamicState2FeaturesExt,
};
PhysicalDeviceRobustness2FeaturesEXT featuresRobustness2 = new()
{
SType = StructureType.PhysicalDeviceRobustness2FeaturesExt,
@@ -275,6 +289,12 @@ namespace Ryujinx.Graphics.Vulkan
features2.PNext = &featuresPrimitiveTopologyListRestart;
}
if (_physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState2.ExtensionName))
{
featuresExtendedDynamicState2.PNext = features2.PNext;
features2.PNext = &featuresExtendedDynamicState2;
}
if (_physicalDevice.IsDeviceExtensionPresent("VK_EXT_robustness2"))
{
featuresRobustness2.PNext = features2.PNext;
@@ -408,6 +428,10 @@ namespace Ryujinx.Graphics.Vulkan
properties.Limits.FramebufferDepthSampleCounts &
properties.Limits.FramebufferStencilSampleCounts;
// Temporarily disable this, can be added back at a later date, make it easy to re-enable.
// Disabled because currently causing Device Lost error on NVIDIA.
featuresExtendedDynamicState2.ExtendedDynamicState2PatchControlPoints = false;
Capabilities = new HardwareCapabilities(
_physicalDevice.IsDeviceExtensionPresent("VK_EXT_index_type_uint8"),
supportsCustomBorderColor,
@@ -424,6 +448,8 @@ namespace Ryujinx.Graphics.Vulkan
features2.Features.ShaderStorageImageMultisample,
_physicalDevice.IsDeviceExtensionPresent(ExtConditionalRendering.ExtensionName),
_physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState.ExtensionName),
featuresExtendedDynamicState2,
_physicalDevice.PhysicalDeviceProperties.Limits.MaxTessellationPatchSize,
features2.Features.MultiViewport && !(IsMoltenVk && Vendor == Vendor.Amd), // Workaround for AMD on MoltenVK issue
featuresRobustness2.NullDescriptor || IsMoltenVk,
supportsPushDescriptors && !IsMoltenVk,
@@ -439,6 +465,7 @@ namespace Ryujinx.Graphics.Vulkan
_physicalDevice.IsDeviceExtensionPresent("VK_NV_viewport_array2"),
_physicalDevice.IsDeviceExtensionPresent(ExtExternalMemoryHost.ExtensionName),
supportsDepthClipControl && featuresDepthClipControl.DepthClipControl,
_physicalDevice.PhysicalDeviceFeatures.WideLines,
supportsAttachmentFeedbackLoop && featuresAttachmentFeedbackLoop.AttachmentFeedbackLoopLayout,
supportsDynamicAttachmentFeedbackLoop && featuresDynamicAttachmentFeedbackLoop.AttachmentFeedbackLoopDynamicState,
propertiesSubgroup.SubgroupSize,
@@ -774,6 +801,10 @@ namespace Ryujinx.Graphics.Vulkan
supportsViewportSwizzle: false,
supportsIndirectParameters: true,
supportsDepthClipControl: Capabilities.SupportsDepthClipControl,
supportsExtendedDynamicState: Capabilities.SupportsExtendedDynamicState,
supportsExtendedDynamicState2: Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2,
supportsLogicOpDynamicState: Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2LogicOp,
supportsPatchControlPointsDynamicState: Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2PatchControlPoints,
uniformBufferSetIndex: PipelineBase.UniformSetIndex,
storageBufferSetIndex: PipelineBase.StorageSetIndex,
textureSetIndex: PipelineBase.TextureSetIndex,

View File

@@ -710,9 +710,8 @@ namespace Ryujinx.HLE.FileSystem
{
updateNcasItem.Add((nca.Header.ContentType, entry.FullName));
}
else
else if (updateNcas.TryAdd(nca.Header.TitleId, new List<(NcaContentType, string)>()))
{
updateNcas.Add(nca.Header.TitleId, new List<(NcaContentType, string)>());
updateNcas[nca.Header.TitleId].Add((nca.Header.ContentType, entry.FullName));
}
}
@@ -898,9 +897,8 @@ namespace Ryujinx.HLE.FileSystem
{
updateNcasItem.Add((nca.Header.ContentType, entry.FullPath));
}
else
else if (updateNcas.TryAdd(nca.Header.TitleId, new List<(NcaContentType, string)>()))
{
updateNcas.Add(nca.Header.TitleId, new List<(NcaContentType, string)>());
updateNcas[nca.Header.TitleId].Add((nca.Header.ContentType, entry.FullPath));
}

View File

@@ -659,7 +659,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
if (string.IsNullOrWhiteSpace(filePath))
{
throw new InvalidSystemResourceException("JIT (010000000000003B) system title not found! The JIT will not work, provide the system archive to fix this error. (See https://github.com/GreemDev/Ryujinx#requirements for more information)");
throw new InvalidSystemResourceException("JIT (010000000000003B) system title not found! The JIT will not work, provide the system archive to fix this error. (See https://github.com/Ryubing/Ryujinx#requirements for more information)");
}
context.Device.LoadNca(filePath);

View File

@@ -105,7 +105,7 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pl
titleName = "Unknown";
}
throw new InvalidSystemResourceException($"{titleName} ({fontTitle:x8}) system title not found! This font will not work, provide the system archive to fix this error. (See https://github.com/GreemDev/Ryujinx#requirements for more information)");
throw new InvalidSystemResourceException($"{titleName} ({fontTitle:x8}) system title not found! This font will not work, provide the system archive to fix this error. (See https://github.com/Ryubing/Ryujinx#requirements for more information)");
}
}
else

View File

@@ -161,5 +161,20 @@ namespace Ryujinx.HLE
{
return 1000 / _frameRate[FrameTypeGame];
}
public string FormatGameFrameRate()
{
double frameRate = GetGameFrameRate();
double frameTime = GetGameFrameTime();
return $"{frameRate:00.00} FPS ({frameTime:00.00}ms)";
}
public string FormatFifoPercent()
{
double fifoPercent = GetFifoPercent();
return $"FIFO: {fifoPercent:00.00}%";
}
}
}

View File

@@ -34,8 +34,8 @@ namespace Ryujinx.HLE
public int CpuCoresCount = 4; //Switch 1 has 4 cores
public VSyncMode VSyncMode { get; set; } = VSyncMode.Switch;
public bool CustomVSyncIntervalEnabled { get; set; } = false;
public VSyncMode VSyncMode { get; set; }
public bool CustomVSyncIntervalEnabled { get; set; }
public int CustomVSyncInterval { get; set; }
public long TargetVSyncInterval { get; set; } = 60;

View File

@@ -1,6 +1,7 @@
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Logging;
using SDL2;
using System;
using System.Collections.Generic;
using System.Numerics;
@@ -118,6 +119,11 @@ namespace Ryujinx.Input.SDL2
result |= GamepadFeaturesFlag.Rumble;
}
if (SDL_GameControllerHasLED(_gamepadHandle) == SDL_bool.SDL_TRUE)
{
result |= GamepadFeaturesFlag.Led;
}
return result;
}

View File

@@ -24,5 +24,10 @@ namespace Ryujinx.Input
/// <remarks>Also named sixaxis</remarks>
/// </summary>
Motion,
/// <summary>
/// The LED on the back of modern PlayStation controllers (DualSense &amp; DualShock 4).
/// </summary>
Led,
}
}

View File

@@ -1151,8 +1151,8 @@ namespace Ryujinx.Ava
LocaleManager.Instance[LocaleKeys.VolumeShort] + $": {(int)(Device.GetVolume() * 100)}%",
dockedMode,
ConfigurationState.Instance.Graphics.AspectRatio.Value.ToText(),
$"{Device.Statistics.GetGameFrameRate():00.00} FPS ({Device.Statistics.GetGameFrameTime():00.00} ms)",
$"FIFO: {Device.Statistics.GetFifoPercent():00.00} %",
Device.Statistics.FormatGameFrameRate(),
Device.Statistics.FormatFifoPercent(),
_displayCount));
}

View File

@@ -0,0 +1,13 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style Selector="MenuItem.withCheckbox Viewbox#PART_IconPresenter">
<Setter Property="MaxHeight" Value="36" />
<Setter Property="MinHeight" Value="36" />
<Setter Property="MaxWidth" Value="36" />
<Setter Property="MinWidth" Value="36" />
</Style>
<Style Selector="MenuItem.withCheckbox ContentPresenter#PART_HeaderPresenter">
<Setter Property="Padding" Value="-10,0,0,0" />
</Style>
</Styles>

View File

@@ -7622,6 +7622,31 @@
"zh_TW": "陀螺儀無感帶:"
}
},
{
"ID": "ControllerSettingsLedColor",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Custom LED",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "ControllerSettingsSave",
"Translations": {

View File

@@ -1,6 +1,6 @@
using Avalonia.Controls.Notifications;
using Avalonia.Platform.Storage;
using Avalonia.Threading;
using Gommon;
using LibHac;
using LibHac.Account;
using LibHac.Common;
@@ -15,6 +15,7 @@ using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.Utilities;
using Ryujinx.Ava.Utilities.Configuration;
using Ryujinx.Common.Helper;
using Ryujinx.Common.Logging;
@@ -418,35 +419,27 @@ namespace Ryujinx.Ava.Common
public static async Task ExtractAoc(IStorageProvider storageProvider, string updateFilePath, string updateName)
{
var result = await storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
Optional<IStorageFolder> result = await storageProvider.OpenSingleFolderPickerAsync(new FolderPickerOpenOptions
{
Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle],
AllowMultiple = false,
Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle]
});
if (result.Count == 0)
{
return;
}
ExtractAoc(result[0].Path.LocalPath, updateFilePath, updateName);
if (!result.HasValue) return;
ExtractAoc(result.Value.Path.LocalPath, updateFilePath, updateName);
}
public static async Task ExtractSection(IStorageProvider storageProvider, NcaSectionType ncaSectionType, string titleFilePath, string titleName, int programIndex = 0)
{
var result = await storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
Optional<IStorageFolder> result = await storageProvider.OpenSingleFolderPickerAsync(new FolderPickerOpenOptions
{
Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle],
AllowMultiple = false,
Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle]
});
if (result.Count == 0)
{
return;
}
if (!result.HasValue) return;
ExtractSection(result[0].Path.LocalPath, ncaSectionType, titleFilePath, titleName, programIndex);
ExtractSection(result.Value.Path.LocalPath, ncaSectionType, titleFilePath, titleName, programIndex);
}
public static (Result? result, bool canceled) CopyDirectory(FileSystemClient fs, string sourcePath, string destPath, CancellationToken token)

View File

@@ -149,7 +149,7 @@ namespace Ryujinx.Headless
IgnoreMissingServices = configurationState.System.IgnoreMissingServices;
if (NeedsOverride(nameof(IgnoreControllerApplet)))
IgnoreControllerApplet = configurationState.IgnoreApplet;
IgnoreControllerApplet = configurationState.System.IgnoreApplet;
return;

View File

@@ -47,9 +47,9 @@ namespace Ryujinx.Ava
{
Version = ReleaseInformation.Version;
if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134))
if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 19041))
{
_ = MessageBoxA(nint.Zero, "You are running an outdated version of Windows.\n\nRyujinx supports Windows 10 version 1803 and newer.\n", $"Ryujinx {Version}", MbIconwarning);
_ = MessageBoxA(nint.Zero, "You are running an outdated version of Windows.\n\nRyujinx supports Windows 10 version 20H1 and newer.\n", $"Ryujinx {Version}", MbIconwarning);
}
PreviewerDetached = true;

View File

@@ -123,12 +123,13 @@
<Generator>MSBuild:Compile</Generator>
</AvaloniaResource>
<AvaloniaResource Include="Assets\Styles\Styles.xaml" />
<AvaloniaResource Include="Assets\Styles\CheckboxMenuItemStyle.axaml" />
</ItemGroup>
<ItemGroup>
<None Remove="Assets\locales.json" />
<None Remove="Assets\Styles\Styles.xaml" />
<None Remove="Assets\Styles\Themes.xaml" />
<None Remove="Assets\Styles\CheckboxMenuItemStyle.xaml" />
<None Remove="Assets\Icons\Controller_JoyConLeft.svg" />
<None Remove="Assets\Icons\Controller_JoyConPair.svg" />
<None Remove="Assets\Icons\Controller_JoyConRight.svg" />
@@ -150,6 +151,7 @@
</EmbeddedResource>
<EmbeddedResource Include="Assets\locales.json" />
<EmbeddedResource Include="Assets\Styles\Styles.xaml" />
<EmbeddedResource Include="Assets\Styles\CheckboxMenuItemStyle.axaml" />
<EmbeddedResource Include="Assets\Icons\Controller_JoyConLeft.svg" />
<EmbeddedResource Include="Assets\Icons\Controller_JoyConPair.svg" />
<EmbeddedResource Include="Assets\Icons\Controller_JoyConRight.svg" />

View File

@@ -16,5 +16,6 @@
<Application.Styles>
<sty:FluentAvaloniaTheme PreferUserAccentColor="True" PreferSystemTheme="False" />
<StyleInclude Source="/Assets/Styles/Styles.xaml" />
<StyleInclude Source="/Assets/Styles/CheckboxMenuItemStyle.axaml"/>
</Application.Styles>
</Application>

View File

@@ -6,7 +6,6 @@ using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.ViewModels.Input;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Ava.Utilities.Configuration;
using Ryujinx.Common;
@@ -42,7 +41,7 @@ namespace Ryujinx.Ava.UI.Applet
bool okPressed = false;
if (ConfigurationState.Instance.IgnoreApplet)
if (ConfigurationState.Instance.System.IgnoreApplet)
return false;
Dispatcher.UIThread.InvokeAsync(async () =>

View File

@@ -251,7 +251,7 @@ namespace Ryujinx.Ava.UI.Controls
{
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
{
string shaderCacheDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "shader");
string shaderCacheDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString.ToLower(), "cache", "shader");
if (!Directory.Exists(shaderCacheDir))
{

View File

@@ -1,3 +1,4 @@
using Avalonia.Media;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller;
@@ -387,6 +388,30 @@ namespace Ryujinx.Ava.UI.Models.Input
}
}
private bool _enableLedChanging;
public bool EnableLedChanging
{
get => _enableLedChanging;
set
{
_enableLedChanging = value;
OnPropertyChanged();
}
}
private Color _ledColor;
public Color LedColor
{
get => _ledColor;
set
{
_ledColor = value;
OnPropertyChanged();
}
}
private bool _enableMotion;
public bool EnableMotion
{
@@ -483,12 +508,23 @@ namespace Ryujinx.Ava.UI.Models.Input
WeakRumble = controllerInput.Rumble.WeakRumble;
StrongRumble = controllerInput.Rumble.StrongRumble;
}
if (controllerInput.Led != null)
{
EnableLedChanging = controllerInput.Led.EnableLed;
uint rawColor = controllerInput.Led.LedColor;
byte alpha = (byte)(rawColor >> 24);
byte red = (byte)(rawColor >> 16);
byte green = (byte)(rawColor >> 8);
byte blue = (byte)(rawColor % 256);
LedColor = new Color(alpha, red, green, blue);
}
}
}
public InputConfig GetConfig()
{
var config = new StandardControllerInputConfig
StandardControllerInputConfig config = new()
{
Id = Id,
Backend = InputBackendType.GamepadSDL2,
@@ -540,6 +576,11 @@ namespace Ryujinx.Ava.UI.Models.Input
WeakRumble = WeakRumble,
StrongRumble = StrongRumble,
},
Led = new LedConfigController
{
EnableLed = EnableLedChanging,
LedColor = LedColor.ToUInt32()
},
Version = InputConfig.CurrentVersion,
DeadzoneLeft = DeadzoneLeft,
DeadzoneRight = DeadzoneRight,

View File

@@ -432,7 +432,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{
try
{
HttpResponseMessage response = await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, "https://raw.githubusercontent.com/GreemDev/Ryujinx/refs/heads/master/assets/amiibo/Amiibo.json"));
HttpResponseMessage response = await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, "https://raw.githubusercontent.com/Ryubing/Ryujinx/refs/heads/master/assets/amiibo/Amiibo.json"));
if (response.IsSuccessStatusCode)
{
@@ -451,7 +451,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{
try
{
HttpResponseMessage response = await _httpClient.GetAsync($"https://raw.githubusercontent.com/GreemDev/Ryujinx/refs/heads/master/assets/amiibo/Amiibo.json");
HttpResponseMessage response = await _httpClient.GetAsync($"https://raw.githubusercontent.com/Ryubing/Ryujinx/refs/heads/master/assets/amiibo/Amiibo.json");
if (response.IsSuccessStatusCode)
{

View File

@@ -37,7 +37,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
[ObservableProperty] private SvgImage _image;
public readonly InputViewModel ParentModel;
public InputViewModel ParentModel { get; }
public ControllerInputViewModel(InputViewModel model, GamepadInputConfig config)
{

View File

@@ -69,6 +69,9 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public bool IsRight { get; set; }
public bool IsLeft { get; set; }
public bool HasLed => false; //temporary
//SelectedGamepad.Features.HasFlag(GamepadFeaturesFlag.Led);
public bool IsModified { get; set; }
public event Action NotifyChangesEvent;

View File

@@ -1662,10 +1662,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public async Task OpenAmiiboWindow()
{
if (!IsAmiiboRequested)
return;
if (AppHost.Device.System.SearchingForAmiibo(out int deviceId))
if (AppHost.Device.System.SearchingForAmiibo(out int deviceId) && IsGameRunning)
{
string titleId = AppHost.Device.Processes.ActiveApplication.ProgramIdText.ToUpper();
AmiiboWindow window = new(ShowAll, LastScannedAmiiboId, titleId);
@@ -1683,10 +1680,7 @@ namespace Ryujinx.Ava.UI.ViewModels
}
public async Task OpenBinFile()
{
if (!IsAmiiboRequested)
return;
if (AppHost.Device.System.SearchingForAmiibo(out int deviceId))
if (AppHost.Device.System.SearchingForAmiibo(out _) && IsGameRunning)
{
var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{

View File

@@ -488,7 +488,6 @@ namespace Ryujinx.Ava.UI.ViewModels
EnableDiscordIntegration = config.EnableDiscordIntegration;
CheckUpdatesOnStart = config.CheckUpdatesOnStart;
ShowConfirmExit = config.ShowConfirmExit;
IgnoreApplet = config.IgnoreApplet;
RememberWindowState = config.RememberWindowState;
ShowTitleBar = config.ShowTitleBar;
HideCursor = (int)config.HideCursor.Value;
@@ -532,6 +531,7 @@ namespace Ryujinx.Ava.UI.ViewModels
EnableFsIntegrityChecks = config.System.EnableFsIntegrityChecks;
DramSize = config.System.DramSize;
IgnoreMissingServices = config.System.IgnoreMissingServices;
IgnoreApplet = config.System.IgnoreApplet;
// CPU
EnablePptc = config.System.EnablePtc;
@@ -591,7 +591,6 @@ namespace Ryujinx.Ava.UI.ViewModels
config.EnableDiscordIntegration.Value = EnableDiscordIntegration;
config.CheckUpdatesOnStart.Value = CheckUpdatesOnStart;
config.ShowConfirmExit.Value = ShowConfirmExit;
config.IgnoreApplet.Value = IgnoreApplet;
config.RememberWindowState.Value = RememberWindowState;
config.ShowTitleBar.Value = ShowTitleBar;
config.HideCursor.Value = (HideCursorMode)HideCursor;
@@ -632,12 +631,10 @@ namespace Ryujinx.Ava.UI.ViewModels
}
config.System.SystemTimeOffset.Value = Convert.ToInt64((CurrentDate.ToUnixTimeSeconds() + CurrentTime.TotalSeconds) - DateTimeOffset.Now.ToUnixTimeSeconds());
config.Graphics.VSyncMode.Value = VSyncMode;
config.Graphics.EnableCustomVSyncInterval.Value = EnableCustomVSyncInterval;
config.Graphics.CustomVSyncInterval.Value = CustomVSyncInterval;
config.System.EnableFsIntegrityChecks.Value = EnableFsIntegrityChecks;
config.System.DramSize.Value = DramSize;
config.System.IgnoreMissingServices.Value = IgnoreMissingServices;
config.System.IgnoreApplet.Value = IgnoreApplet;
// CPU
config.System.EnablePtc.Value = EnablePptc;
@@ -646,6 +643,9 @@ namespace Ryujinx.Ava.UI.ViewModels
config.System.UseHypervisor.Value = UseHypervisor;
// Graphics
config.Graphics.VSyncMode.Value = VSyncMode;
config.Graphics.EnableCustomVSyncInterval.Value = EnableCustomVSyncInterval;
config.Graphics.CustomVSyncInterval.Value = CustomVSyncInterval;
config.Graphics.GraphicsBackend.Value = (GraphicsBackend)GraphicsBackendIndex;
config.Graphics.PreferredGpu.Value = _gpuIds.ElementAtOrDefault(PreferredGpuIndex);
config.Graphics.EnableShaderCache.Value = EnableShaderCache;

View File

@@ -4,6 +4,7 @@
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels.Input"
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
@@ -486,6 +487,37 @@
</Button>
</Grid>
</Border>
<Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
CornerRadius="5"
HorizontalAlignment="Stretch"
Margin="0,-1,0,0">
<Grid IsVisible="{Binding ParentModel.HasLed}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<CheckBox
Margin="10"
MinWidth="0"
Grid.Column="0"
IsChecked="{Binding Config.EnableLedChanging, Mode=TwoWay}">
<TextBlock Text="{ext:Locale ControllerSettingsLedColor}" />
</CheckBox>
<ui:ColorPickerButton
Grid.Column="1"
Margin="10"
IsMoreButtonVisible="False"
UseColorPalette="False"
UseColorTriangle="False"
UseColorWheel="False"
ShowAcceptDismissButtons="False"
IsAlphaEnabled="False"
Color="{Binding Config.LedColor, Mode=TwoWay}">
</ui:ColorPickerButton>
</Grid>
</Border>
</StackPanel>
</StackPanel>
<!-- Right Controls -->

View File

@@ -4,14 +4,11 @@ using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using DiscordRPC;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels.Input;
using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Logging;
using Ryujinx.Input;
using Ryujinx.Input.Assigner;
using System;
using StickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
namespace Ryujinx.Ava.UI.Views.Input

View File

@@ -81,25 +81,16 @@
<MenuItem
Command="{Binding ToggleFullscreen}"
Header="{ext:Locale MenuBarOptionsToggleFullscreen}"
Classes="withCheckbox"
Padding="0"
Icon="{ext:Icon fa-solid fa-expand}"
InputGesture="F11">
<MenuItem.Styles>
<Style Selector="Viewbox#PART_IconPresenter">
<Setter Property="MaxHeight" Value="36" />
<Setter Property="MinHeight" Value="36" />
<Setter Property="MaxWidth" Value="36" />
<Setter Property="MinWidth" Value="36" />
</Style>
<Style Selector="ContentPresenter#PART_HeaderPresenter">
<Setter Property="Padding" Value="-10,0,0,0" />
</Style>
</MenuItem.Styles>
</MenuItem>
<MenuItem
Padding="0"
Command="{Binding ToggleStartGamesInFullscreen}"
Header="{ext:Locale MenuBarOptionsStartGamesInFullscreen}">
Header="{ext:Locale MenuBarOptionsStartGamesInFullscreen}"
Classes="withCheckbox">
<MenuItem.Icon>
<CheckBox
MinWidth="{DynamicResource CheckBoxSize}"
@@ -107,22 +98,12 @@
IsChecked="{Binding StartGamesInFullscreen, Mode=TwoWay}"
Padding="0" />
</MenuItem.Icon>
<MenuItem.Styles>
<Style Selector="Viewbox#PART_IconPresenter">
<Setter Property="MaxHeight" Value="36" />
<Setter Property="MinHeight" Value="36" />
<Setter Property="MaxWidth" Value="36" />
<Setter Property="MinWidth" Value="36" />
</Style>
<Style Selector="ContentPresenter#PART_HeaderPresenter">
<Setter Property="Padding" Value="-10,0,0,0" />
</Style>
</MenuItem.Styles>
</MenuItem>
<MenuItem
Padding="0"
Command="{Binding ToggleStartGamesWithoutUI}"
Header="{ext:Locale MenuBarOptionsStartGamesWithoutUI}">
Header="{ext:Locale MenuBarOptionsStartGamesWithoutUI}"
Classes="withCheckbox">
<MenuItem.Icon>
<CheckBox
MinWidth="{DynamicResource CheckBoxSize}"
@@ -130,23 +111,13 @@
IsChecked="{Binding StartGamesWithoutUI, Mode=TwoWay}"
Padding="0" />
</MenuItem.Icon>
<MenuItem.Styles>
<Style Selector="Viewbox#PART_IconPresenter">
<Setter Property="MaxHeight" Value="36" />
<Setter Property="MinHeight" Value="36" />
<Setter Property="MaxWidth" Value="36" />
<Setter Property="MinWidth" Value="36" />
</Style>
<Style Selector="ContentPresenter#PART_HeaderPresenter">
<Setter Property="Padding" Value="-10,0,0,0" />
</Style>
</MenuItem.Styles>
</MenuItem>
<MenuItem
Padding="0"
IsVisible="{Binding ShowConsoleVisible}"
Command="{Binding ToggleShowConsole}"
Header="{ext:Locale MenuBarOptionsShowConsole}">
Header="{ext:Locale MenuBarOptionsShowConsole}"
Classes="withCheckbox">
<MenuItem.Icon>
<CheckBox
MinWidth="{DynamicResource CheckBoxSize}"
@@ -154,35 +125,14 @@
IsChecked="{Binding ShowConsole, Mode=TwoWay}"
Padding="0" />
</MenuItem.Icon>
<MenuItem.Styles>
<Style Selector="Viewbox#PART_IconPresenter">
<Setter Property="MaxHeight" Value="36" />
<Setter Property="MinHeight" Value="36" />
<Setter Property="MaxWidth" Value="36" />
<Setter Property="MinWidth" Value="36" />
</Style>
<Style Selector="ContentPresenter#PART_HeaderPresenter">
<Setter Property="Padding" Value="-10,0,0,0" />
</Style>
</MenuItem.Styles>
</MenuItem>
<Separator/>
<MenuItem
Name="ChangeLanguageMenuItem"
Padding="0"
Header="{ext:Locale MenuBarOptionsChangeLanguage}"
Icon="{ext:Icon fa-solid fa-language}">
<MenuItem.Styles>
<Style Selector="Viewbox#PART_IconPresenter">
<Setter Property="MaxHeight" Value="36" />
<Setter Property="MinHeight" Value="36" />
<Setter Property="MaxWidth" Value="36" />
<Setter Property="MinWidth" Value="36" />
</Style>
<Style Selector="ContentPresenter#PART_HeaderPresenter">
<Setter Property="Padding" Value="-10,0,0,0" />
</Style>
</MenuItem.Styles>
Icon="{ext:Icon fa-solid fa-language}"
Classes="withCheckbox">
</MenuItem>
<MenuItem
Name="ToggleFileTypesMenuItem"
@@ -194,18 +144,8 @@
Padding="0"
Header="{ext:Locale MenuBarOptionsSettings}"
Icon="{ext:Icon fa-solid fa-gear}"
ToolTip.Tip="{ext:Locale OpenSettingsTooltip}">
<MenuItem.Styles>
<Style Selector="Viewbox#PART_IconPresenter">
<Setter Property="MaxHeight" Value="36" />
<Setter Property="MinHeight" Value="36" />
<Setter Property="MaxWidth" Value="36" />
<Setter Property="MinWidth" Value="36" />
</Style>
<Style Selector="ContentPresenter#PART_HeaderPresenter">
<Setter Property="Padding" Value="-10,0,0,0" />
</Style>
</MenuItem.Styles>
ToolTip.Tip="{ext:Locale OpenSettingsTooltip}"
Classes="withCheckbox">
</MenuItem>
<MenuItem
Command="{Binding ManageProfiles}"
@@ -213,18 +153,8 @@
Header="{ext:Locale MenuBarOptionsManageUserProfiles}"
Icon="{ext:Icon mdi-account}"
IsEnabled="{Binding EnableNonGameRunningControls}"
ToolTip.Tip="{ext:Locale OpenProfileManagerTooltip}">
<MenuItem.Styles>
<Style Selector="Viewbox#PART_IconPresenter">
<Setter Property="MaxHeight" Value="36" />
<Setter Property="MinHeight" Value="36" />
<Setter Property="MaxWidth" Value="36" />
<Setter Property="MinWidth" Value="36" />
</Style>
<Style Selector="ContentPresenter#PART_HeaderPresenter">
<Setter Property="Padding" Value="-10,0,0,0" />
</Style>
</MenuItem.Styles>
ToolTip.Tip="{ext:Locale OpenProfileManagerTooltip}"
Classes="withCheckbox">
</MenuItem>
</MenuItem>
<MenuItem
@@ -334,19 +264,19 @@
Name="FaqMenuItem"
Header="{ext:Locale MenuBarHelpFaq}"
Icon="{ext:Icon fa-github}"
CommandParameter="https://github.com/GreemDev/Ryujinx/wiki/FAQ-and-Troubleshooting"
CommandParameter="https://github.com/Ryubing/Ryujinx/wiki/FAQ-and-Troubleshooting"
ToolTip.Tip="{ext:Locale MenuBarHelpFaqTooltip}" />
<MenuItem
Name="SetupGuideMenuItem"
Header="{ext:Locale MenuBarHelpSetup}"
Icon="{ext:Icon fa-github}"
CommandParameter="https://github.com/GreemDev/Ryujinx/wiki/Ryujinx-Setup-&amp;-Configuration-Guide"
CommandParameter="https://github.com/Ryubing/Ryujinx/wiki/Ryujinx-Setup-&amp;-Configuration-Guide"
ToolTip.Tip="{ext:Locale MenuBarHelpSetupTooltip}" />
<MenuItem
Name="LdnGuideMenuItem"
Header="{ext:Locale MenuBarHelpMultiplayer}"
Icon="{ext:Icon fa-github}"
CommandParameter="https://github.com/GreemDev/Ryujinx/wiki/Multiplayer%E2%80%90(LDN%E2%80%90Local%E2%80%90Wireless)%E2%80%90Guide"
CommandParameter="https://github.com/Ryubing/Ryujinx/wiki/Multiplayer%E2%80%90(LDN%E2%80%90Local%E2%80%90Wireless)%E2%80%90Guide"
ToolTip.Tip="{ext:Locale MenuBarHelpMultiplayerTooltip}" />
</MenuItem>
</MenuItem>

View File

@@ -114,8 +114,7 @@
Grid.Column="1"
MinWidth="90"
Margin="10,0,0,0"
ToolTip.Tip="{ext:Locale AddGameDirTooltip}"
Click="AddGameDirButton_OnClick">
ToolTip.Tip="{ext:Locale AddGameDirTooltip}">
<TextBlock HorizontalAlignment="Center"
Text="{ext:Locale SettingsTabGeneralAdd}" />
</Button>
@@ -168,8 +167,7 @@
Grid.Column="1"
MinWidth="90"
Margin="10,0,0,0"
ToolTip.Tip="{ext:Locale AddAutoloadDirTooltip}"
Click="AddAutoloadDirButton_OnClick">
ToolTip.Tip="{ext:Locale AddAutoloadDirTooltip}">
<TextBlock HorizontalAlignment="Center"
Text="{ext:Locale SettingsTabGeneralAdd}" />
</Button>

View File

@@ -1,12 +1,17 @@
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Platform.Storage;
using Avalonia.VisualTree;
using Gommon;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.Utilities;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Views.Settings
{
@@ -18,31 +23,39 @@ namespace Ryujinx.Ava.UI.Views.Settings
{
InitializeComponent();
ShowTitleBarBox.IsVisible = OperatingSystem.IsWindows();
AddGameDirButton.Command =
Commands.Create(() => AddDirButton(GameDirPathBox, ViewModel.GameDirectories, true));
AddAutoloadDirButton.Command =
Commands.Create(() => AddDirButton(AutoloadDirPathBox, ViewModel.AutoloadDirectories, false));
}
private async void AddGameDirButton_OnClick(object sender, RoutedEventArgs e)
private async Task AddDirButton(TextBox addDirBox, AvaloniaList<string> directories, bool isGameList)
{
string path = GameDirPathBox.Text;
string path = addDirBox.Text;
if (!string.IsNullOrWhiteSpace(path) && Directory.Exists(path) && !ViewModel.GameDirectories.Contains(path))
if (!string.IsNullOrWhiteSpace(path) && Directory.Exists(path) && !directories.Contains(path))
{
ViewModel.GameDirectories.Add(path);
ViewModel.GameDirectoryChanged = true;
directories.Add(path);
addDirBox.Clear();
if (isGameList)
ViewModel.GameDirectoryChanged = true;
else
ViewModel.AutoloadDirectoryChanged = true;
}
else
{
if (this.GetVisualRoot() is Window window)
{
var result = await window.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
AllowMultiple = false,
});
Optional<IStorageFolder> folder = await RyujinxApp.MainWindow.ViewModel.StorageProvider.OpenSingleFolderPickerAsync();
if (result.Count > 0)
{
ViewModel.GameDirectories.Add(result[0].Path.LocalPath);
if (folder.HasValue)
{
directories.Add(folder.Value.Path.LocalPath);
if (isGameList)
ViewModel.GameDirectoryChanged = true;
}
else
ViewModel.AutoloadDirectoryChanged = true;
}
}
}
@@ -63,33 +76,6 @@ namespace Ryujinx.Ava.UI.Views.Settings
}
}
private async void AddAutoloadDirButton_OnClick(object sender, RoutedEventArgs e)
{
string path = AutoloadDirPathBox.Text;
if (!string.IsNullOrWhiteSpace(path) && Directory.Exists(path) && !ViewModel.AutoloadDirectories.Contains(path))
{
ViewModel.AutoloadDirectories.Add(path);
ViewModel.AutoloadDirectoryChanged = true;
}
else
{
if (this.GetVisualRoot() is Window window)
{
var result = await window.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
AllowMultiple = false,
});
if (result.Count > 0)
{
ViewModel.AutoloadDirectories.Add(result[0].Path.LocalPath);
ViewModel.AutoloadDirectoryChanged = true;
}
}
}
}
private void RemoveAutoloadDirButton_OnClick(object sender, RoutedEventArgs e)
{
int oldIndex = AutoloadDirsList.SelectedIndex;

View File

@@ -182,7 +182,7 @@
HorizontalAlignment="Left"
Background="Transparent"
Click="Button_OnClick"
Tag="https://github.com/GreemDev/Ryujinx/graphs/contributors?type=a">
Tag="https://github.com/Ryubing/Ryujinx/graphs/contributors?type=a">
<TextBlock
FontSize="10"
Text="{ext:Locale AboutRyujinxContributorsButtonHeader}"

View File

@@ -736,9 +736,7 @@ namespace Ryujinx.Ava.UI.Windows
});
}
private static bool _intelMacWarningShown = !(OperatingSystem.IsMacOS() &&
(RuntimeInformation.OSArchitecture == Architecture.X64 ||
RuntimeInformation.OSArchitecture == Architecture.X86));
private static bool _intelMacWarningShown = !RunningPlatform.IsIntelMac;
public static async Task ShowIntelMacWarningAsync()
{

View File

@@ -17,7 +17,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
/// <summary>
/// The current version of the file format
/// </summary>
public const int CurrentVersion = 60;
public const int CurrentVersion = 61;
/// <summary>
/// Version of the configuration file format

View File

@@ -45,7 +45,6 @@ namespace Ryujinx.Ava.Utilities.Configuration
EnableDiscordIntegration.Value = cff.EnableDiscordIntegration;
CheckUpdatesOnStart.Value = cff.CheckUpdatesOnStart;
ShowConfirmExit.Value = cff.ShowConfirmExit;
IgnoreApplet.Value = cff.IgnoreApplet;
RememberWindowState.Value = cff.RememberWindowState;
ShowTitleBar.Value = cff.ShowTitleBar;
EnableHardwareAcceleration.Value = cff.EnableHardwareAcceleration;
@@ -97,6 +96,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
System.MemoryManagerMode.Value = cff.MemoryManagerMode;
System.DramSize.Value = cff.DramSize;
System.IgnoreMissingServices.Value = cff.IgnoreMissingServices;
System.IgnoreApplet.Value = cff.IgnoreApplet;
System.UseHypervisor.Value = cff.UseHypervisor;
UI.GuiColumns.FavColumn.Value = cff.GuiColumns.FavColumn;
@@ -263,15 +263,12 @@ namespace Ryujinx.Ava.Utilities.Configuration
}),
(30, static cff =>
{
foreach (InputConfig config in cff.InputConfig)
foreach (StandardControllerInputConfig config in cff.InputConfig.OfType<StandardControllerInputConfig>())
{
if (config is StandardControllerInputConfig controllerConfig)
config.Rumble = new RumbleConfigController
{
controllerConfig.Rumble = new RumbleConfigController
{
EnableRumble = false, StrongRumble = 1f, WeakRumble = 1f,
};
}
EnableRumble = false, StrongRumble = 1f, WeakRumble = 1f,
};
}
}),
(31, static cff => cff.BackendThreading = BackendThreading.Auto),
@@ -416,7 +413,18 @@ namespace Ryujinx.Ava.Utilities.Configuration
// so as a compromise users who want to use it will simply need to re-enable it once after updating.
cff.IgnoreApplet = false;
}),
(60, static cff => cff.StartNoUI = false)
(60, static cff => cff.StartNoUI = false),
(61, static cff =>
{
foreach (StandardControllerInputConfig config in cff.InputConfig.OfType<StandardControllerInputConfig>())
{
config.Led = new LedConfigController
{
EnableLed = false,
LedColor = 328189
};
}
})
);
}
}

View File

@@ -366,6 +366,11 @@ namespace Ryujinx.Ava.Utilities.Configuration
/// Enable or disable ignoring missing services
/// </summary>
public ReactiveObject<bool> IgnoreMissingServices { get; private set; }
/// <summary>
/// Ignore Controller Applet
/// </summary>
public ReactiveObject<bool> IgnoreApplet { get; private set; }
/// <summary>
/// Uses Hypervisor over JIT if available
@@ -404,6 +409,8 @@ namespace Ryujinx.Ava.Utilities.Configuration
DramSize.LogChangesToValue(nameof(DramSize));
IgnoreMissingServices = new ReactiveObject<bool>();
IgnoreMissingServices.LogChangesToValue(nameof(IgnoreMissingServices));
IgnoreApplet = new ReactiveObject<bool>();
IgnoreApplet.LogChangesToValue(nameof(IgnoreApplet));
AudioVolume = new ReactiveObject<float>();
AudioVolume.LogChangesToValue(nameof(AudioVolume));
UseHypervisor = new ReactiveObject<bool>();
@@ -745,11 +752,6 @@ namespace Ryujinx.Ava.Utilities.Configuration
/// </summary>
public ReactiveObject<bool> ShowConfirmExit { get; private set; }
/// <summary>
/// Ignore Applet
/// </summary>
public ReactiveObject<bool> IgnoreApplet { get; private set; }
/// <summary>
/// Enables or disables save window size, position and state on close.
/// </summary>
@@ -782,8 +784,6 @@ namespace Ryujinx.Ava.Utilities.Configuration
EnableDiscordIntegration = new ReactiveObject<bool>();
CheckUpdatesOnStart = new ReactiveObject<bool>();
ShowConfirmExit = new ReactiveObject<bool>();
IgnoreApplet = new ReactiveObject<bool>();
IgnoreApplet.LogChangesToValue(nameof(IgnoreApplet));
RememberWindowState = new ReactiveObject<bool>();
ShowTitleBar = new ReactiveObject<bool>();
EnableHardwareAcceleration = new ReactiveObject<bool>();

View File

@@ -56,7 +56,6 @@ namespace Ryujinx.Ava.Utilities.Configuration
EnableDiscordIntegration = EnableDiscordIntegration,
CheckUpdatesOnStart = CheckUpdatesOnStart,
ShowConfirmExit = ShowConfirmExit,
IgnoreApplet = IgnoreApplet,
RememberWindowState = RememberWindowState,
ShowTitleBar = ShowTitleBar,
EnableHardwareAcceleration = EnableHardwareAcceleration,
@@ -78,6 +77,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
MemoryManagerMode = System.MemoryManagerMode,
DramSize = System.DramSize,
IgnoreMissingServices = System.IgnoreMissingServices,
IgnoreApplet = System.IgnoreApplet,
UseHypervisor = System.UseHypervisor,
GuiColumns = new GuiColumns
{
@@ -176,7 +176,6 @@ namespace Ryujinx.Ava.Utilities.Configuration
EnableDiscordIntegration.Value = true;
CheckUpdatesOnStart.Value = true;
ShowConfirmExit.Value = true;
IgnoreApplet.Value = false;
RememberWindowState.Value = true;
ShowTitleBar.Value = !OperatingSystem.IsWindows();
EnableHardwareAcceleration.Value = true;
@@ -200,6 +199,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
System.MemoryManagerMode.Value = MemoryManagerMode.HostMappedUnsafe;
System.DramSize.Value = MemoryConfiguration.MemoryConfiguration4GiB;
System.IgnoreMissingServices.Value = false;
System.IgnoreApplet.Value = false;
System.UseHypervisor.Value = true;
Multiplayer.LanInterfaceId.Value = "0";
Multiplayer.Mode.Value = MultiplayerMode.Disabled;

View File

@@ -0,0 +1,47 @@
using Avalonia.Platform.Storage;
using Gommon;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Ryujinx.Ava.Utilities
{
public static class StorageProviderExtensions
{
public static async Task<Optional<IStorageFolder>> OpenSingleFolderPickerAsync(this IStorageProvider storageProvider, FolderPickerOpenOptions openOptions = null) =>
await storageProvider.OpenFolderPickerAsync(FixOpenOptions(openOptions, false))
.Then(folders => folders.FindFirst());
public static async Task<Optional<IStorageFile>> OpenSingleFilePickerAsync(this IStorageProvider storageProvider, FilePickerOpenOptions openOptions = null) =>
await storageProvider.OpenFilePickerAsync(FixOpenOptions(openOptions, false))
.Then(files => files.FindFirst());
public static async Task<Optional<IReadOnlyList<IStorageFolder>>> OpenMultiFolderPickerAsync(this IStorageProvider storageProvider, FolderPickerOpenOptions openOptions = null) =>
await storageProvider.OpenFolderPickerAsync(FixOpenOptions(openOptions, true))
.Then(folders => folders.Count > 0 ? Optional.Of(folders) : default);
public static async Task<Optional<IReadOnlyList<IStorageFile>>> OpenMultiFilePickerAsync(this IStorageProvider storageProvider, FilePickerOpenOptions openOptions = null) =>
await storageProvider.OpenFilePickerAsync(FixOpenOptions(openOptions, true))
.Then(files => files.Count > 0 ? Optional.Of(files) : default);
private static FilePickerOpenOptions FixOpenOptions(this FilePickerOpenOptions openOptions, bool allowMultiple)
{
if (openOptions is null)
return new FilePickerOpenOptions { AllowMultiple = allowMultiple };
openOptions.AllowMultiple = allowMultiple;
return openOptions;
}
private static FolderPickerOpenOptions FixOpenOptions(this FolderPickerOpenOptions openOptions, bool allowMultiple)
{
if (openOptions is null)
return new FolderPickerOpenOptions { AllowMultiple = allowMultiple };
openOptions.AllowMultiple = allowMultiple;
return openOptions;
}
}
}