Author SHA1 Message Date
sunshineinabox 0678a06284 Support VK_EXT_extended_dynamic_state and VK_EXT_extended_dynamic_state2 2026-09-19 23:59:40 -05:00
KeatonTheBot c62976f290 Heap adjustments
- Increase heap limit to 8 GiB for games that crash with > 8 GiB heap. This was inadvertently limited to 6 GiB for 10 & 12 GiB DRAM sizes.
2026-09-19 23:56:11 -05:00
Babib3l 25e88be7f1 Fix ProcessLoader stale PID validation against kernel process table
This PR adresses the following issue : `ProcessLoader.ActiveApplication` could return invalid results when `_latestPid` pointed to a process that no longer existed in the kernel's process table. The original exception path was commented out and bypassed (by sh0inx?) with `GetValueOrDefault` to prevent UI lockups, but this only resolved the symptoms without fixing the root cause.

This was due to sevral factors :
- `_latestPid` was never reset or validated against the actual process state
- ProcessLoader maintained its own `_processesByPid` dictionary separate from the kernel's `KernelContext.Processes`
- No cleanups happened when processes exited or were terminated
- ProcessLoader state could drift out of sync with the kernel process table

**Solution/Fixes**
- Validate` _latestPid` against the kernel process table before returning `ActiveApplication`
- Check process state (Exited/Exiting) and automatically clear stale references
- Add thread-safe cleanup methods (`ClearProcess`, `ClearAllProcesses`)
- Integrate `ClearAllProcesses` into Switch.Dispose for proper shutdown cleanup
- Add warning logs when stale PID is detected and cleared for debugging

**Code Changes**:
- `ProcessLoader.cs`: Add `_pidLock`, update `ActiveApplication` with validation, add cleanup methods
- `Switch.cs`: Call `Processes.ClearAllProcesses()` in Dispose()
2026-09-19 16:52:04 -05:00
Renovate Bot abc03d7325 Update dependency Ryujinx.SDL3-CS to 2026.918.0 (#23)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [Ryujinx.SDL3-CS](https://github.com/Kenji-NX/SDL3-CS) | `2026.707.0` → `2026.918.0` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Ryujinx.SDL3-CS/2026.918.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Ryujinx.SDL3-CS/2026.707.0/2026.918.0?slim=true) |

---

### Release Notes

<details>
<summary>Kenji-NX/SDL3-CS (Ryujinx.SDL3-CS)</summary>

### [`v2026.918.0`](https://github.com/Kenji-NX/SDL3-CS/compare/2026.707.0...2026.918.0)

[Compare Source](https://github.com/Kenji-NX/SDL3-CS/compare/2026.707.0...2026.918.0)

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNDkuNSIsInVwZGF0ZWRJblZlciI6IjQzLjI0OS41IiwidGFyZ2V0QnJhbmNoIjoibWFzdGVyIiwibGFiZWxzIjpbXX0=-->

Reviewed-on: https://git.ryujinx.app/projects/Kenji-NX/pulls/23
2026-09-18 22:41:02 +00:00
Coxxs 096e054bb3 Implement CreateLibraryAppletEx in ILibraryAppletCreator 2026-09-18 00:11:05 -05:00
KeatonTheBot 0f5aea82a0 Raise application pool sizes for DRAM selections (again)
- Fixes Ultracam 20+ (beta) support
2026-09-18 00:11:05 -05:00
Neo 1385c60875 HLE: Update ProcessResult in ProcessLoader.cs
Some games/applications may crash with a specific error:

00:00:00.690 |E| Application : Unhandled exception caught: System.Collections.Generic.KeyNotFoundException: The given key '0' was not present in the dictionary.

(or a different key)

However, this message is misleading and not indicative of the actual error (it masks it).

As an example, the error above was seen when attempting to load a mod onto Yo-kai Watch 1 (base game + update worked). However, after patching the ProcessResult, the resulting error was more revealing:

00:00:00.716 |E| Application : Unhandled exception caught: LibHac.Common.HorizonResultException: ResultFsInvalidCharacter (2002-6004): Error creating LocalFileSystem. at LibHac.FsSystem.LocalFileSystem..ctor(String rootPath)

(and so forth)

The actual error was due to an incorrect folder name in the Mods folder.
2026-09-17 15:18:48 -05:00
GreemDev a0ad6f2246 misc: chore: add direct error code tuple to DisplayErrorAppletDialog
for use when i find the list of error codes -> causes
2026-09-13 21:24:52 -05:00
KeatonTheBot a412999eeb Fix Skip User Profiles
This and `Add the player select applet` fixes commit c9c78841b9.
2026-09-13 20:49:31 -05:00
JacobandGreemDev 87605ebce2 Add the player select applet
This introduces the somewhat completed version of the Player Select
Applet, allowing users to select either a user or a guest from the UI.
Note: Selecting the guest more then once currently does not work.

closes https://github.com/Ryubing/Ryujinx/issues/532

- misc: chore: optimize UserSelectorDialog closed handler

- misc: chore: Rename UserSelectorDialog to ProfileSelectorDialog

Co-authored-by: GreemDev <greemdev@ryujinx.app>
2026-09-13 20:48:20 -05:00
GreemDev b67e8df788 RenderDoc API support 2026-09-13 15:08:11 -05:00
66 changed files with 3242 additions and 638 deletions
+1 -1
View File
@@ -38,7 +38,7 @@
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.Windows" Version="6.1.4-build6" /> <PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.Windows" Version="6.1.4-build6" />
<PackageVersion Include="Ryujinx.Graphics.Vulkan.MoltenVK" Version="1.4.3-ryujinx.1" /> <PackageVersion Include="Ryujinx.Graphics.Vulkan.MoltenVK" Version="1.4.3-ryujinx.1" />
<PackageVersion Include="Ryujinx.LibHac" Version="0.21.0-alpha.133" /> <PackageVersion Include="Ryujinx.LibHac" Version="0.21.0-alpha.133" />
<PackageVersion Include="Ryujinx.SDL3-CS" Version="2026.707.0" /> <PackageVersion Include="Ryujinx.SDL3-CS" Version="2026.918.0" />
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" /> <PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
<PackageVersion Include="SharpZipLib" Version="1.4.2" /> <PackageVersion Include="SharpZipLib" Version="1.4.2" />
<PackageVersion Include="Silk.NET.Shaderc" Version="2.23.0" /> <PackageVersion Include="Silk.NET.Shaderc" Version="2.23.0" />
+1
View File
@@ -21,6 +21,7 @@
<Project Path="src/Ryujinx.Graphics.Nvdec.Vp9/Ryujinx.Graphics.Nvdec.Vp9.csproj" /> <Project Path="src/Ryujinx.Graphics.Nvdec.Vp9/Ryujinx.Graphics.Nvdec.Vp9.csproj" />
<Project Path="src/Ryujinx.Graphics.Nvdec/Ryujinx.Graphics.Nvdec.csproj" /> <Project Path="src/Ryujinx.Graphics.Nvdec/Ryujinx.Graphics.Nvdec.csproj" />
<Project Path="src/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj" /> <Project Path="src/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj" />
<Project Path="src/Ryujinx.Graphics.RenderDocApi/Ryujinx.Graphics.RenderDocApi.csproj" />
<Project Path="src/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj" /> <Project Path="src/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj" />
<Project Path="src/Ryujinx.Graphics.Texture/Ryujinx.Graphics.Texture.csproj" /> <Project Path="src/Ryujinx.Graphics.Texture/Ryujinx.Graphics.Texture.csproj" />
<Project Path="src/Ryujinx.Graphics.Vic/Ryujinx.Graphics.Vic.csproj" /> <Project Path="src/Ryujinx.Graphics.Vic/Ryujinx.Graphics.Vic.csproj" />
+12
View File
@@ -51,6 +51,10 @@ namespace Ryujinx.Graphics.GAL
public readonly bool SupportsViewportSwizzle; public readonly bool SupportsViewportSwizzle;
public readonly bool SupportsIndirectParameters; public readonly bool SupportsIndirectParameters;
public readonly bool SupportsDepthClipControl; 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 UniformBufferSetIndex;
public readonly int StorageBufferSetIndex; public readonly int StorageBufferSetIndex;
@@ -120,6 +124,10 @@ namespace Ryujinx.Graphics.GAL
bool supportsViewportSwizzle, bool supportsViewportSwizzle,
bool supportsIndirectParameters, bool supportsIndirectParameters,
bool supportsDepthClipControl, bool supportsDepthClipControl,
bool supportsExtendedDynamicState,
bool supportsExtendedDynamicState2,
bool supportsLogicOpDynamicState,
bool supportsPatchControlPointsDynamicState,
int uniformBufferSetIndex, int uniformBufferSetIndex,
int storageBufferSetIndex, int storageBufferSetIndex,
int textureSetIndex, int textureSetIndex,
@@ -183,6 +191,10 @@ namespace Ryujinx.Graphics.GAL
SupportsViewportSwizzle = supportsViewportSwizzle; SupportsViewportSwizzle = supportsViewportSwizzle;
SupportsIndirectParameters = supportsIndirectParameters; SupportsIndirectParameters = supportsIndirectParameters;
SupportsDepthClipControl = supportsDepthClipControl; SupportsDepthClipControl = supportsDepthClipControl;
SupportsExtendedDynamicState = supportsExtendedDynamicState;
SupportsExtendedDynamicState2 = supportsExtendedDynamicState2;
SupportsLogicOpDynamicState = supportsLogicOpDynamicState;
SupportsPatchControlPointsDynamicState = supportsPatchControlPointsDynamicState;
UniformBufferSetIndex = uniformBufferSetIndex; UniformBufferSetIndex = uniformBufferSetIndex;
StorageBufferSetIndex = storageBufferSetIndex; StorageBufferSetIndex = storageBufferSetIndex;
TextureSetIndex = textureSetIndex; TextureSetIndex = textureSetIndex;
+1
View File
@@ -2,6 +2,7 @@ namespace Ryujinx.Graphics.GAL
{ {
public enum Face public enum Face
{ {
None = 0,
Front = 0x404, Front = 0x404,
Back = 0x405, Back = 0x405,
FrontAndBack = 0x408, FrontAndBack = 0x408,
+7 -7
View File
@@ -50,9 +50,9 @@ namespace Ryujinx.Graphics.GAL
void SetDepthBias(PolygonModeMask enables, float factor, float units, float clamp); void SetDepthBias(PolygonModeMask enables, float factor, float units, float clamp);
void SetDepthClamp(bool clamp); void SetDepthClamp(bool clamp);
void SetDepthMode(DepthMode mode); 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); void SetFrontFace(FrontFace frontFace);
@@ -75,16 +75,16 @@ namespace Ryujinx.Graphics.GAL
void SetPrimitiveRestart(bool enable, int index); 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 SetRasterizerDiscard(bool discard);
void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMask); void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMask, bool signalChange = true);
void SetRenderTargets(Span<ITexture> colors, ITexture depthStencil); void SetRenderTargets(Span<ITexture> colors, ITexture depthStencil);
void SetScissors(ReadOnlySpan<Rectangle<int>> regions); void SetScissors(ReadOnlySpan<Rectangle<int>> regions, bool signalChange = true);
void SetStencilTest(StencilTestDescriptor stencilTest); void SetStencilTest(StencilTestDescriptor stencilTest);
@@ -102,7 +102,7 @@ namespace Ryujinx.Graphics.GAL
void SetVertexAttribs(ReadOnlySpan<VertexAttribDescriptor> vertexAttribs); void SetVertexAttribs(ReadOnlySpan<VertexAttribDescriptor> vertexAttribs);
void SetVertexBuffers(ReadOnlySpan<VertexBufferDescriptor> vertexBuffers); void SetVertexBuffers(ReadOnlySpan<VertexBufferDescriptor> vertexBuffers);
void SetViewports(ReadOnlySpan<Viewport> viewports); void SetViewports(ReadOnlySpan<Viewport> viewports, bool signalChange = true);
void TextureBarrier(); void TextureBarrier();
void TextureBarrierTiled(); void TextureBarrierTiled();
@@ -3,18 +3,16 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands
struct SetFaceCullingCommand : IGALCommand, IGALCommand<SetFaceCullingCommand> struct SetFaceCullingCommand : IGALCommand, IGALCommand<SetFaceCullingCommand>
{ {
public readonly CommandType CommandType => CommandType.SetFaceCulling; public readonly CommandType CommandType => CommandType.SetFaceCulling;
private bool _enable;
private Face _face; private Face _face;
public void Set(bool enable, Face face) public void Set(Face face)
{ {
_enable = enable;
_face = face; _face = face;
} }
public static void Run(ref SetFaceCullingCommand command, ThreadedRenderer threaded, IRenderer renderer) public static void Run(ref SetFaceCullingCommand command, ThreadedRenderer threaded, IRenderer renderer)
{ {
renderer.Pipeline.SetFaceCulling(command._enable, command._face); renderer.Pipeline.SetFaceCulling(command._face);
} }
} }
} }
@@ -158,15 +158,15 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand(); _renderer.QueueCommand();
} }
public unsafe void SetDepthTest(DepthTestDescriptor depthTest) public unsafe void SetDepthTest(DepthTestDescriptor depthTest, bool signalChange = true)
{ {
_renderer.New<SetDepthTestCommand>()->Set(depthTest); _renderer.New<SetDepthTestCommand>()->Set(depthTest);
_renderer.QueueCommand(); _renderer.QueueCommand();
} }
public unsafe void SetFaceCulling(bool enable, Face face) public unsafe void SetFaceCulling(Face face)
{ {
_renderer.New<SetFaceCullingCommand>()->Set(enable, face); _renderer.New<SetFaceCullingCommand>()->Set(face);
_renderer.QueueCommand(); _renderer.QueueCommand();
} }
@@ -242,13 +242,13 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand(); _renderer.QueueCommand();
} }
public unsafe void SetPrimitiveTopology(PrimitiveTopology topology) public unsafe void SetPrimitiveTopology(PrimitiveTopology topology, bool signalChange = true)
{ {
_renderer.New<SetPrimitiveTopologyCommand>()->Set(topology); _renderer.New<SetPrimitiveTopologyCommand>()->Set(topology);
_renderer.QueueCommand(); _renderer.QueueCommand();
} }
public unsafe void SetProgram(IProgram program) public unsafe void SetProgram(IProgram program, bool signalChange = true)
{ {
_renderer.New<SetProgramCommand>()->Set(Ref(program)); _renderer.New<SetProgramCommand>()->Set(Ref(program));
_renderer.QueueCommand(); _renderer.QueueCommand();
@@ -260,7 +260,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand(); _renderer.QueueCommand();
} }
public unsafe void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMask) public unsafe void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMask, bool signalChange = true)
{ {
_renderer.New<SetRenderTargetColorMasksCommand>()->Set(_renderer.CopySpan(componentMask)); _renderer.New<SetRenderTargetColorMasksCommand>()->Set(_renderer.CopySpan(componentMask));
_renderer.QueueCommand(); _renderer.QueueCommand();
@@ -275,7 +275,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand(); _renderer.QueueCommand();
} }
public unsafe void SetScissors(ReadOnlySpan<Rectangle<int>> scissors) public unsafe void SetScissors(ReadOnlySpan<Rectangle<int>> scissors, bool signalChange = true)
{ {
_renderer.New<SetScissorsCommand>()->Set(_renderer.CopySpan(scissors)); _renderer.New<SetScissorsCommand>()->Set(_renderer.CopySpan(scissors));
_renderer.QueueCommand(); _renderer.QueueCommand();
@@ -341,7 +341,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand(); _renderer.QueueCommand();
} }
public unsafe void SetViewports(ReadOnlySpan<Viewport> viewports) public unsafe void SetViewports(ReadOnlySpan<Viewport> viewports, bool signalChange = true)
{ {
_renderer.New<SetViewportsCommand>()->Set(_renderer.CopySpan(viewports)); _renderer.New<SetViewportsCommand>()->Set(_renderer.CopySpan(viewports));
_renderer.QueueCommand(); _renderer.QueueCommand();
@@ -51,11 +51,12 @@ namespace Ryujinx.Graphics.GAL
public StencilTestDescriptor StencilTest; public StencilTestDescriptor StencilTest;
public FrontFace FrontFace; public FrontFace FrontFace;
public Face CullMode; public Face CullMode;
public bool CullEnable;
public PolygonModeMask BiasEnable; public PolygonModeMask BiasEnable;
public float LineWidth; public bool AlphaToCoverageEnable;
public bool AlphaToOneEnable;
// TODO: Polygon mode. // TODO: Polygon mode.
public bool DepthClampEnable; public bool DepthClampEnable;
public bool RasterizerDiscard; public bool RasterizerDiscard;
@@ -63,6 +64,9 @@ namespace Ryujinx.Graphics.GAL
public bool PrimitiveRestartEnable; public bool PrimitiveRestartEnable;
public uint PatchControlPoints; public uint PatchControlPoints;
public float DepthBiasUnits;
public float DepthBiasFactor;
public DepthMode DepthMode; public DepthMode DepthMode;
public void SetVertexAttribs(ReadOnlySpan<VertexAttribDescriptor> vertexAttribs) public void SetVertexAttribs(ReadOnlySpan<VertexAttribDescriptor> vertexAttribs)
@@ -861,6 +861,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
enables |= (depthBias.FillEnable ? PolygonModeMask.Fill : 0); enables |= (depthBias.FillEnable ? PolygonModeMask.Fill : 0);
_pipeline.BiasEnable = enables; _pipeline.BiasEnable = enables;
_pipeline.DepthBiasUnits = units / 2f;
_pipeline.DepthBiasFactor = factor;
_context.Renderer.Pipeline.SetDepthBias(enables, factor, units / 2f, clamp); _context.Renderer.Pipeline.SetDepthBias(enables, factor, units / 2f, clamp);
} }
@@ -1034,7 +1037,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
float width = _state.State.LineWidthSmooth; float width = _state.State.LineWidthSmooth;
bool smooth = _state.State.LineSmoothEnable; bool smooth = _state.State.LineSmoothEnable;
_pipeline.LineWidth = width;
_context.Renderer.Pipeline.SetLineParameters(width, smooth); _context.Renderer.Pipeline.SetLineParameters(width, smooth);
} }
@@ -1216,9 +1218,16 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
YControl yControl = _state.State.YControl; YControl yControl = _state.State.YControl;
FaceState face = _state.State.FaceState; FaceState face = _state.State.FaceState;
_pipeline.CullEnable = face.CullEnable; if (face.CullEnable)
{
_pipeline.CullMode = face.CullFace; _pipeline.CullMode = face.CullFace;
_context.Renderer.Pipeline.SetFaceCulling(face.CullEnable, face.CullFace); _context.Renderer.Pipeline.SetFaceCulling(face.CullFace);
}
else
{
_pipeline.CullMode = Face.None;
_context.Renderer.Pipeline.SetFaceCulling(Face.None);
}
UpdateFrontFace(yControl, face.FrontFace); UpdateFrontFace(yControl, face.FrontFace);
} }
@@ -1416,6 +1425,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
bool alphaToCoverageEnable = (_state.State.MultisampleControl & 1) != 0; bool alphaToCoverageEnable = (_state.State.MultisampleControl & 1) != 0;
bool alphaToOneEnable = (_state.State.MultisampleControl & 0x10) != 0; bool alphaToOneEnable = (_state.State.MultisampleControl & 0x10) != 0;
_pipeline.AlphaToCoverageEnable = alphaToCoverageEnable;
_pipeline.AlphaToOneEnable = alphaToOneEnable;
_context.Renderer.Pipeline.SetMultisampleState(new MultisampleDescriptor( _context.Renderer.Pipeline.SetMultisampleState(new MultisampleDescriptor(
alphaToCoverageEnable, alphaToCoverageEnable,
_state.State.AlphaToCoverageDitherEnable, _state.State.AlphaToCoverageDitherEnable,
@@ -22,6 +22,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
private readonly CancellationToken _cancellationToken; private readonly CancellationToken _cancellationToken;
private readonly Action<ShaderCacheState, int, int> _stateChangeCallback; private readonly Action<ShaderCacheState, int, int> _stateChangeCallback;
private readonly HashSet<ProgramPipelineState> _pipelineStateSet = new();
/// <summary> /// <summary>
/// Indicates if the cache should be loaded. /// Indicates if the cache should be loaded.
/// </summary> /// </summary>
@@ -232,10 +234,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
for (int index = 0; index < ThreadCount; index++) for (int index = 0; index < ThreadCount; index++)
{ {
workThreads[index] = new Thread(ProcessAsyncQueue) workThreads[index] = new Thread(ProcessAsyncQueue) { Name = $"GPU.AsyncTranslationThread.{index}", };
{
Name = $"GPU.AsyncTranslationThread.{index}",
};
} }
int programCount = _hostStorage.GetProgramCount(); int programCount = _hostStorage.GetProgramCount();
@@ -305,6 +304,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
using DiskCacheOutputStreams streams = _hostStorage.GetOutputStreams(_context); using DiskCacheOutputStreams streams = _hostStorage.GetOutputStreams(_context);
int packagedShaders = 0; int packagedShaders = 0;
ProgramPipelineState currentPipelineState = default;
foreach (KeyValuePair<int, (CachedShaderProgram, byte[])> kv in _programList) foreach (KeyValuePair<int, (CachedShaderProgram, byte[])> kv in _programList)
{ {
if (!Active) if (!Active)
@@ -314,12 +315,53 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
(CachedShaderProgram program, byte[] binaryCode) = kv.Value; (CachedShaderProgram program, byte[] binaryCode) = kv.Value;
if (program.SpecializationState.PipelineState.HasValue && _context.Capabilities.SupportsExtendedDynamicState)
{
currentPipelineState = program.SpecializationState.PipelineState.Value;
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); _hostStorage.AddShader(_context, program, binaryCode, streams);
_stateChangeCallback(ShaderCacheState.Packaging, ++packagedShaders, _programList.Count); _stateChangeCallback(ShaderCacheState.Packaging, ++packagedShaders, _programList.Count);
}
Logger.Info?.Print(LogClass.Gpu, $"Rebuilt {_programList.Count} shaders successfully."); if (_context.Capabilities.SupportsExtendedDynamicState)
{
_pipelineStateSet.Add(currentPipelineState);
}
}
}
Logger.Info?.Print(LogClass.Gpu, $"Rebuilt {packagedShaders} shaders successfully.");
} }
else else
{ {
@@ -343,6 +385,29 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
_stateChangeCallback(ShaderCacheState.Loaded, programCount, programCount); _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> /// <summary>
/// Enqueues a host program for compilation. /// Enqueues a host program for compilation.
/// </summary> /// </summary>
@@ -193,6 +193,10 @@ namespace Ryujinx.Graphics.OpenGL
supportsViewportSwizzle: HwCapabilities.SupportsViewportSwizzle, supportsViewportSwizzle: HwCapabilities.SupportsViewportSwizzle,
supportsIndirectParameters: HwCapabilities.SupportsIndirectParameters, supportsIndirectParameters: HwCapabilities.SupportsIndirectParameters,
supportsDepthClipControl: true, supportsDepthClipControl: true,
supportsExtendedDynamicState: false,
supportsExtendedDynamicState2: false,
supportsLogicOpDynamicState: false,
supportsPatchControlPointsDynamicState: false,
uniformBufferSetIndex: 0, uniformBufferSetIndex: 0,
storageBufferSetIndex: 1, storageBufferSetIndex: 1,
textureSetIndex: 2, textureSetIndex: 2,
+9 -9
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) if (depthTest.TestEnable)
{ {
@@ -915,11 +915,11 @@ namespace Ryujinx.Graphics.OpenGL
_depthTestEnable = depthTest.TestEnable; _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); GL.Disable(EnableCap.CullFace);
return; return;
@@ -1107,12 +1107,12 @@ namespace Ryujinx.Graphics.OpenGL
GL.Enable(EnableCap.PrimitiveRestart); GL.Enable(EnableCap.PrimitiveRestart);
} }
public void SetPrimitiveTopology(PrimitiveTopology topology) public void SetPrimitiveTopology(PrimitiveTopology topology, bool signalChange = true)
{ {
_primitiveType = topology.Convert(); _primitiveType = topology.Convert();
} }
public void SetProgram(IProgram program) public void SetProgram(IProgram program, bool signalChange = true)
{ {
Program prg = (Program)program; Program prg = (Program)program;
@@ -1154,7 +1154,7 @@ namespace Ryujinx.Graphics.OpenGL
_rasterizerDiscard = discard; _rasterizerDiscard = discard;
} }
public void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMasks) public void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMasks, bool signalChange = true)
{ {
_componentMasks = 0; _componentMasks = 0;
@@ -1195,7 +1195,7 @@ namespace Ryujinx.Graphics.OpenGL
_framebuffer.SetDrawBuffers(colors.Length); _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); int count = Math.Min(regions.Length, Constants.MaxViewports);
@@ -1388,7 +1388,7 @@ namespace Ryujinx.Graphics.OpenGL
_vertexArray.SetVertexBuffers(vertexBuffers); _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 _viewportArray, viewports.Length * 4);
Array.Resize(ref _depthRangeArray, viewports.Length * 2); Array.Resize(ref _depthRangeArray, viewports.Length * 2);
@@ -0,0 +1,12 @@
using System;
namespace Ryujinx.Graphics.RenderDocApi
{
public readonly record struct Capture(int Index, string FileName, DateTime Timestamp)
{
public void SetComments(string comments)
{
RenderDoc.SetCaptureFileComments(FileName, comments);
}
}
}
@@ -0,0 +1,100 @@
// ReSharper disable UnusedMember.Global
namespace Ryujinx.Graphics.RenderDocApi
{
public enum CaptureOption
{
/// <summary>
/// specifies whether the application is allowed to enable vsync. Default is on.
/// </summary>
AllowVsync = 0,
/// <summary>
/// specifies whether the application is allowed to enter exclusive fullscreen. Default is on.
/// </summary>
AllowFullscreen = 1,
/// <summary>
/// specifies whether (where possible) API-specific debugging is enabled. Default is off.
/// </summary>
ApiValidation = 2,
/// <summary>
/// specifies whether each API call should save a callstack. Default is off.
/// </summary>
CaptureCallstacks = 3,
/// <summary>
/// specifies whether, if <see cref="CaptureCallstacks"/> is enabled, callstacks are only saved on actions. Default is off.
/// </summary>
CaptureCallstacksOnlyDraws = 4,
/// <summary>
/// specifies a delay in seconds after launching a process to pause, to allow debuggers to attach. <br/>
/// This will only apply to child processes since the delay happens at process startup. Default is 0.
/// </summary>
DelayForDebugger = 5,
/// <summary>
/// specifies whether any mapped memory updates should be bounds-checked for overruns,
/// and uninitialised buffers are initialized to <code>0xDDDDDDDD</code> to catch use of uninitialised data.
/// Only supported on D3D11 and OpenGL. Default is off.
/// </summary>
/// <remarks>
/// This option is only valid for OpenGL and D3D11. Explicit APIs such as D3D12 and Vulkan do
/// not do the same kind of interception &amp; checking, and undefined contents are really undefined.
/// </remarks>
VerifyBufferAccess = 6,
/// <summary>
/// Hooks any system API calls that create child processes, and injects
/// RenderDoc into them recursively with the same options.
/// </summary>
HookIntoChildren = 7,
/// <summary>
/// specifies whether all live resources at the time of capture should be included in the capture,
/// even if they are not referenced by the frame. Default is off.
/// </summary>
RefAllSources = 8,
/// <summary>
/// By default, RenderDoc skips saving initial states for resources where the
/// previous contents don't appear to be used, assuming that writes before
/// reads indicate previous contents aren't used.
/// </summary>
/// <remarks>
/// **NOTE**: As of RenderDoc v1.1 this option has been deprecated. Setting or
/// getting it will be ignored, to allow compatibility with older versions.
/// In v1.1 the option acts as if it's always enabled.
/// </remarks>
SaveAllInitials = 9,
/// <summary>
/// In APIs that allow for the recording of command lists to be replayed later,
/// RenderDoc may choose to not capture command lists before a frame capture is
/// triggered, to reduce overheads. This means any command lists recorded once
/// and replayed many times will not be available and may cause a failure to
/// capture.
/// </summary>
/// <remarks>
/// NOTE: This is only true for APIs where multithreading is difficult or
/// discouraged. Newer APIs like Vulkan and D3D12 will ignore this option
/// and always capture all command lists since the API is heavily oriented
/// around it and the overheads have been reduced by API design.
/// </remarks>
CaptureAllCmdLists = 10,
/// <summary>
/// Mute API debugging output when the <see cref="ApiValidation"/> option is enabled.
/// </summary>
DebugOutputMute = 11,
/// <summary>
/// Allow vendor extensions to be used even when they may be
/// incompatible with RenderDoc and cause corrupted replays or crashes.
/// </summary>
AllowUnsupportedVendorExtensions = 12,
/// <summary>
/// Define a soft memory limit which some APIs may aim to keep overhead under where
/// possible. Anything above this limit will where possible be saved directly to disk during
/// capture.<br/>
/// This will cause increased disk space use (which may cause a capture to fail if disk space is
/// exhausted) as well as slower capture times.
/// <br/><br/>
/// Not all memory allocations may be deferred like this so it is not a guarantee of a memory
/// limit.
/// <br/><br/>
/// Units are in MBs, suggested values would range from 200MB to 1000MB.
/// </summary>
SoftMemoryLimit = 13,
}
}
@@ -0,0 +1,83 @@
// ReSharper disable UnusedMember.Global
namespace Ryujinx.Graphics.RenderDocApi
{
public enum InputButton
{
// '0' - '9' matches ASCII values
Key0 = 0x30,
Key1 = 0x31,
Key2 = 0x32,
Key3 = 0x33,
Key4 = 0x34,
Key5 = 0x35,
Key6 = 0x36,
Key7 = 0x37,
Key8 = 0x38,
Key9 = 0x39,
// 'A' - 'Z' matches ASCII values
A = 0x41,
B = 0x42,
C = 0x43,
D = 0x44,
E = 0x45,
F = 0x46,
G = 0x47,
H = 0x48,
I = 0x49,
J = 0x4A,
K = 0x4B,
L = 0x4C,
M = 0x4D,
N = 0x4E,
O = 0x4F,
P = 0x50,
Q = 0x51,
R = 0x52,
S = 0x53,
T = 0x54,
U = 0x55,
V = 0x56,
W = 0x57,
X = 0x58,
Y = 0x59,
Z = 0x5A,
// leave the rest of the ASCII range free
// in case we want to use it later
NonPrintable = 0x100,
Divide,
Multiply,
Subtract,
Plus,
F1,
F2,
F3,
F4,
F5,
F6,
F7,
F8,
F9,
F10,
F11,
F12,
Home,
End,
Insert,
Delete,
PageUp,
PageDn,
Backspace,
Tab,
PrtScrn,
Pause,
Max,
}
}
@@ -0,0 +1,39 @@
// ReSharper disable UnusedMember.Global
using System;
namespace Ryujinx.Graphics.RenderDocApi
{
[Flags]
public enum OverlayBits
{
/// <summary>
/// This single bit controls whether the overlay is enabled or disabled globally
/// </summary>
Enabled = 1 << 0,
/// <summary>
/// Show the average framerate over several seconds as well as min/max
/// </summary>
FrameRate = 1 << 1,
/// <summary>
/// Show the current frame number
/// </summary>
FrameNumber = 1 << 2,
/// <summary>
/// Show a list of recent captures, and how many captures have been made
/// </summary>
CaptureList = 1 << 3,
/// <summary>
/// Default values for the overlay mask
/// </summary>
Default = Enabled | FrameRate | FrameNumber | CaptureList,
/// <summary>
/// Enable all bits
/// </summary>
All = ~0,
/// <summary>
/// Disable all bits
/// </summary>
None = 0
}
}
@@ -0,0 +1,5 @@
# Ryujinx.Graphics.RenderDocApi
This is a C# binding for RenderDoc's application API.
This is a source-inclusion of https://github.com/utkumaden/RenderdocSharp.
I didn't use the NuGet package as I had a few minor changes I wanted to make, and I want to learn from it as well via hands-on experience.
@@ -0,0 +1,639 @@
using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
namespace Ryujinx.Graphics.RenderDocApi
{
public static unsafe partial class RenderDoc
{
/// <summary>
/// True if the API is available.
/// </summary>
public static bool IsAvailable => Api != null;
/// <summary>
/// Set the minimum version of the API you require.
/// </summary>
/// <remarks>Set this before you do anything else with the RenderDoc API, including <see cref="IsAvailable"/>.</remarks>
public static RenderDocVersion MinimumRequired { get; set; } = RenderDocVersion.Version_1_0_0;
/// <summary>
/// Set to true to assert versions.
/// </summary>
public static bool AssertVersionEnabled { get; set; } = true;
/// <summary>
/// Version of the API available.
/// </summary>
[MemberNotNullWhen(true, nameof(IsAvailable))]
public static Version? Version
{
get
{
if (!IsAvailable)
return null;
int major, minor, build;
Api->GetApiVersion(&major, &minor, &build);
return new Version(major, minor, build);
}
}
/// <summary>
/// The current mask which determines what sections of the overlay render on each window.
/// </summary>
[RenderDocApiVersion(1, 0)]
public static OverlayBits OverlayBits
{
get => Api->GetOverlayBits();
set
{
Api->MaskOverlayBits(~value, value);
}
}
/// <summary>
/// The template for new captures.<br/>
/// The template can either be a relative or absolute path, which determines where captures will be saved and how they will be named.
/// If the path template is 'my_captures/example', then captures saved will be e.g.
/// 'my_captures/example_frame123.rdc' and 'my_captures/example_frame456.rdc'.<br/>
/// Relative paths will be saved relative to the process’s current working directory.<br/>
/// </summary>
/// <remarks>The default template is in a folder controlled by the UI - initially the system temporary folder, and the filename is the executable’s filename.</remarks>
[RenderDocApiVersion(1, 0)]
public static string CaptureFilePathTemplate
{
get
{
byte* ptr = Api->GetCaptureFilePathTemplate();
return Marshal.PtrToStringUTF8((nint)ptr)!;
}
set
{
fixed (byte* ptr = value.ToNullTerminatedByteArray())
{
Api->SetCaptureFilePathTemplate(ptr);
}
}
}
/// <summary>
/// The amount of frame captures that have been made.
/// </summary>
[RenderDocApiVersion(1, 0)]
public static int CaptureCount => Api->GetNumCaptures();
/// <summary>
/// Checks if the RenderDoc UI is currently connected to this process.
/// </summary>
[RenderDocApiVersion(1, 0)]
public static bool IsTargetControlConnected => Api is not null && Api->IsTargetControlConnected() != 0;
/// <summary>
/// Checks if the current frame is capturing.
/// </summary>
[RenderDocApiVersion(1, 0)]
public static bool IsFrameCapturing => Api is not null && Api->IsFrameCapturing() != 0;
/// <summary>
/// Set one of the options for tweaking some behaviors of capturing.
/// </summary>
/// <param name="option">specifies which capture option should be set.</param>
/// <param name="integer">the unsigned integer value to set for the option.</param>
/// <remarks>Note that each option only takes effect from after it is set - so it is advised to set these options as early as possible, ideally before any graphics API has been initialized.</remarks>
/// <returns>
/// true, if the <paramref name="option"/> is valid, and the value set on the option is within valid ranges.<br/>
/// false, if the option is not a <see cref="CaptureOption"/>, or the value is not valid for the option.
/// </returns>
[RenderDocApiVersion(1, 0)]
public static bool SetCaptureOption(CaptureOption option, uint integer)
{
return Api is not null && Api->SetCaptureOptionU32(option, integer) != 0;
}
/// <summary>
/// Set one of the options for tweaking some behaviors of capturing.
/// </summary>
/// <param name="option">specifies which capture option should be set.</param>
/// <param name="boolean">the value to set for the option, converted to a 0 or 1 before setting.</param>
/// <remarks>Note that each option only takes effect from after it is set - so it is advised to set these options as early as possible, ideally before any graphics API has been initialized.</remarks>
/// <returns>
/// true, if the <paramref name="option"/> is valid, and the value set on the option is within valid ranges.<br/>
/// false, if the option is not a <see cref="CaptureOption"/>, or the value is not valid for the option.
/// </returns>
[RenderDocApiVersion(1, 0)]
public static bool SetCaptureOption(CaptureOption option, bool boolean)
=> SetCaptureOption(option, boolean ? 1 : 0);
/// <summary>
/// Set one of the options for tweaking some behaviors of capturing.
/// </summary>
/// <param name="option">specifies which capture option should be set.</param>
/// <param name="single">the floating point value to set for the option.</param>
/// <remarks>Note that each option only takes effect from after it is set - so it is advised to set these options as early as possible, ideally before any graphics API has been initialized.</remarks>
/// <returns>
/// true, if the <paramref name="option"/> is valid, and the value set on the option is within valid ranges.<br/>
/// false, if the option is not a <see cref="CaptureOption"/>, or the value is not valid for the option.
/// </returns>
[RenderDocApiVersion(1, 0)]
public static bool SetCaptureOption(CaptureOption option, float single)
{
return Api is not null && Api->SetCaptureOptionF32(option, single) != 0;
}
/// <summary>
/// Gets the current value of one of the different options in <see cref="CaptureOption"/>, writing it to an out parameter.
/// </summary>
/// <param name="option">specifies which capture option should be retrieved.</param>
/// <param name="integer">the value of the capture option, if the option is a valid <see cref="CaptureOption"/> enum. Otherwise, <see cref="int.MaxValue"/>.</param>
[RenderDocApiVersion(1, 0)]
public static void GetCaptureOption(CaptureOption option, out uint integer)
{
integer = Api->GetCaptureOptionU32(option);
}
/// <summary>
/// Gets the current value of one of the different options in <see cref="CaptureOption"/>, writing it to an out parameter.
/// </summary>
/// <param name="option">specifies which capture option should be retrieved.</param>
/// <param name="single">the value of the capture option, if the option is a valid <see cref="CaptureOption"/> enum. Otherwise, -<see cref="float.MaxValue"/>.</param>
[RenderDocApiVersion(1, 0)]
public static void GetCaptureOption(CaptureOption option, out float single)
{
single = Api->GetCaptureOptionF32(option);
}
/// <summary>
/// Gets the current value of one of the different options in <see cref="CaptureOption"/>,
/// converted to a boolean.
/// </summary>
/// <param name="option">specifies which capture option should be retrieved.</param>
/// <returns>
/// the value of the capture option, converted to bool, if the option is a valid <see cref="CaptureOption"/> enum.
/// Otherwise, returns null.
/// </returns>
[RenderDocApiVersion(1, 0)]
public static bool? GetCaptureOptionBool(CaptureOption option)
{
if (Api is null) return false;
uint returnVal = GetCaptureOptionU32(option);
if (returnVal == uint.MaxValue)
return null;
return returnVal is not 0;
}
/// <summary>
/// Gets the current value of one of the different options in <see cref="CaptureOption"/>.
/// </summary>
/// <param name="option">specifies which capture option should be retrieved.</param>
/// <returns>
/// the value of the capture option, if the option is a valid <see cref="CaptureOption"/> enum.
/// Otherwise, returns <see cref="int.MaxValue"/>.
/// </returns>
[RenderDocApiVersion(1, 0)]
public static uint GetCaptureOptionU32(CaptureOption option) => Api->GetCaptureOptionU32(option);
/// <summary>
/// Gets the current value of one of the different options in <see cref="CaptureOption"/>.
/// </summary>
/// <param name="option">specifies which capture option should be retrieved.</param>
/// <returns>
/// the value of the capture option, if the option is a valid <see cref="CaptureOption"/> enum.
/// Otherwise, returns -<see cref="float.MaxValue"/>.
/// </returns>
[RenderDocApiVersion(1, 0)]
public static float GetCaptureOptionF32(CaptureOption option) => Api->GetCaptureOptionF32(option);
/// <summary>
/// Changes the key bindings in-application for changing the focussed window.
/// </summary>
/// <param name="buttons">lists the keys to bind.</param>
[RenderDocApiVersion(1, 0)]
public static void SetFocusToggleKeys(ReadOnlySpan<InputButton> buttons)
{
if (Api is null) return;
fixed (InputButton* ptr = buttons)
{
Api->SetFocusToggleKeys(ptr, buttons.Length);
}
}
/// <summary>
/// Changes the key bindings in-application for triggering a capture on the current window.
/// </summary>
/// <param name="buttons">lists the keys to bind.</param>
[RenderDocApiVersion(1, 0)]
public static void SetCaptureKeys(ReadOnlySpan<InputButton> buttons)
{
if (Api is null) return;
fixed (InputButton* ptr = buttons)
{
Api->SetCaptureKeys(ptr, buttons.Length);
}
}
/// <summary>
/// Attempts to remove RenderDoc and its hooks from the target process.<br/>
/// It must be called as early as possible in the process, and will have undefined results
/// if any graphics API functions have been called.
/// </summary>
[RenderDocApiVersion(1, 0)]
public static void RemoveHooks()
{
if (Api is null) return;
Api->RemoveHooks();
}
/// <summary>
/// Remove RenderDoc’s crash handler from the target process.<br/>
/// If you have your own crash handler that you want to handle any exceptions,
/// RenderDoc’s handler could interfere; so it can be disabled.
/// </summary>
[RenderDocApiVersion(1, 0)]
public static void UnloadCrashHandler()
{
if (Api is null) return;
Api->UnloadCrashHandler();
}
/// <summary>
/// Trigger a capture as if the user had pressed one of the capture hotkeys.<br/>
/// The capture will be taken from the next frame presented to whichever window is considered current.
/// </summary>
[RenderDocApiVersion(1, 0)]
public static void TriggerCapture()
{
if (Api is null) return;
Api->TriggerCapture();
}
/// <summary>
/// Gets the details of all frame capture in the current session.
/// This simply calls <see cref="GetCapture"/> for each index available as specified by <see cref="CaptureCount"/>.
/// </summary>
/// <returns>An immutable array of structs representing RenderDoc Captures.</returns>
public static ImmutableArray<Capture> GetCaptures()
{
if (Api is null) return [];
int captureCount = CaptureCount;
if (captureCount is 0) return [];
ImmutableArray<Capture>.Builder captures = ImmutableArray.CreateBuilder<Capture>(captureCount);
for (int captureIndex = 0; captureIndex < captureCount; captureIndex++)
{
if (GetCapture(captureIndex) is { } capture)
captures.Add(capture);
}
return captures.DrainToImmutable();
}
/// <summary>
/// Gets the details of a particular frame capture, as specified by an index from 0 to <see cref="CaptureCount"/> - 1.
/// </summary>
/// <param name="index">specifies which capture to return the details of. Must be less than the value returned by <see cref="CaptureCount"/>.</param>
/// <returns>A struct representing a RenderDoc Capture.</returns>
[RenderDocApiVersion(1, 0)]
public static Capture? GetCapture(int index)
{
if (Api is null) return null;
int length = 0;
if (Api->GetCapture(index, null, &length, null) == 0)
{
return null;
}
Span<byte> bytes = stackalloc byte[length + 1];
long timestamp;
fixed (byte* ptr = bytes)
Api->GetCapture(index, ptr, &length, &timestamp);
string fileName = Encoding.UTF8.GetString(bytes[length..]);
return new Capture(index, fileName, DateTimeOffset.FromUnixTimeSeconds(timestamp).DateTime);
}
/// <summary>
/// Determine the closest matching replay UI executable for the current RenderDoc module, and launch it.
/// </summary>
/// <param name="connectTargetControl">if the UI should immediately connect to the application.</param>
/// <param name="commandLine">string to be appended to the command line, e.g. a capture filename. If this parameter is null, the command line will be unmodified.</param>
/// <returns>true if the UI was successfully launched; false otherwise.</returns>
[RenderDocApiVersion(1, 0)]
public static bool LaunchReplayUI(bool connectTargetControl, string? commandLine = null)
{
if (Api is null) return false;
if (commandLine == null)
{
return Api->LaunchReplayUI(connectTargetControl ? 1u : 0u, null) != 0;
}
fixed (byte* ptr = commandLine.ToNullTerminatedByteArray())
{
return Api->LaunchReplayUI(connectTargetControl ? 1u : 0u, ptr) != 0;
}
}
/// <summary>
/// Explicitly sets which window is considered active.<br/>
/// The active window is the one that will be captured when the keybind to trigger a capture is pressed.
/// </summary>
/// <param name="hDevice">a handle to the API ‘device’ object that will be set active. Must be valid.</param>
/// <param name="hWindow">a handle to the platform window handle that will be set active. Must be valid.</param>
[RenderDocApiVersion(1, 0)]
public static void SetActiveWindow(nint hDevice, nint hWindow)
{
if (Api is null) return;
Api->SetActiveWindow((void*)hDevice, (void*)hWindow);
}
/// <summary>
/// Immediately begin a capture for the specified device/window combination.
/// </summary>
/// <param name="hDevice">a handle to the API ‘device’ object that will be set active. May be <see cref="nint.Zero"/> to wildcard match.</param>
/// <param name="hWindow">a handle to the platform window handle that will be set active. May be <see cref="nint.Zero"/> to wildcard match.</param>
[RenderDocApiVersion(1, 0)]
public static void StartFrameCapture(nint hDevice, nint hWindow)
{
if (Api is null) return;
Api->StartFrameCapture((void*)hDevice, (void*)hWindow);
}
/// <summary>
/// Immediately end an active capture for the specified device/window combination.
/// </summary>
/// <param name="hDevice">a handle to the API ‘device’ object that will be set active. May be <see cref="nint.Zero"/> to wildcard match.</param>
/// <param name="hWindow">a handle to the platform window handle that will be set active. May be <see cref="nint.Zero"/> to wildcard match.</param>
/// <returns>true if the capture succeeded; false otherwise.</returns>
[RenderDocApiVersion(1, 0)]
public static bool EndFrameCapture(nint hDevice, nint hWindow)
{
if (Api is null) return false;
return Api->EndFrameCapture((void*)hDevice, (void*)hWindow) != 0;
}
/// <summary>
/// Trigger multiple sequential frame captures as if the user had pressed one of the capture hotkeys before each frame.<br/>
/// The captures will be taken from the next frames presented to whichever window is considered current.<br/>
/// Each capture will be taken independently and saved to a separate file, with no reference to the other frames.
/// </summary>
/// <param name="numFrames">the number of frames to capture.</param>
/// <remarks>Requires RenderDoc API version 1.1</remarks>
[RenderDocApiVersion(1, 1)]
public static void TriggerMultiFrameCapture(uint numFrames)
{
if (Api is null) return;
AssertAtLeast(1, 1);
Api->TriggerMultiFrameCapture(numFrames);
}
/// <summary>
/// Adds an arbitrary comments field to the most recent capture,
/// which will then be displayed in the UI to anyone opening the capture.
/// <br/><br/>
/// This is equivalent to calling <see cref="SetCaptureFileComments"/> with a null first (fileName) parameter.
/// </summary>
/// <param name="comments">the comments to set in the capture file.</param>
/// <remarks>Requires RenderDoc API version 1.2</remarks>
public static void SetMostRecentCaptureFileComments(string comments)
{
if (Api is null) return;
AssertAtLeast(1, 2);
byte[] commentBytes = comments.ToNullTerminatedByteArray();
fixed (byte* pcomment = commentBytes)
{
Api->SetCaptureFileComments((byte*)nint.Zero, pcomment);
}
}
/// <summary>
/// Adds an arbitrary comments field to an existing capture on disk,
/// which will then be displayed in the UI to anyone opening the capture.
/// </summary>
/// <param name="fileName">the path to the capture file to set comments in. If this path is null or an empty string, the most recent capture file that has been created will be used.</param>
/// <param name="comments">the comments to set in the capture file.</param>
/// <remarks>Requires RenderDoc API version 1.2</remarks>
[RenderDocApiVersion(1, 2)]
public static void SetCaptureFileComments(string? fileName, string comments)
{
if (Api is null) return;
AssertAtLeast(1, 2);
byte[] commentBytes = comments.ToNullTerminatedByteArray();
fixed (byte* pcomment = commentBytes)
{
if (fileName is null)
{
Api->SetCaptureFileComments((byte*)nint.Zero, pcomment);
}
else
{
byte[] fileBytes = fileName.ToNullTerminatedByteArray();
fixed (byte* pfile = fileBytes)
{
Api->SetCaptureFileComments(pfile, pcomment);
}
}
}
}
/// <summary>
/// Similar to <see cref="EndFrameCapture"/>, but the capture contents will be discarded immediately, and not processed and written to disk.<br/>
/// This will be more efficient than <see cref="EndFrameCapture"/> if the frame capture is not needed.
/// </summary>
/// <param name="hDevice">a handle to the API ‘device’ object that will be set active. May be <see cref="nint.Zero"/> to wildcard match.</param>
/// <param name="hWindow">a handle to the platform window handle that will be set active. May be <see cref="nint.Zero"/> to wildcard match.</param>
/// <returns>true if the capture was discarded; false if there was an error or no capture was in progress.</returns>
/// <remarks>Requires RenderDoc API version 1.4</remarks>
[RenderDocApiVersion(1, 4)]
public static bool DiscardFrameCapture(nint hDevice, nint hWindow)
{
if (Api is null) return false;
AssertAtLeast(1, 4);
return Api->DiscardFrameCapture((void*)hDevice, (void*)hWindow) != 0;
}
/// <summary>
/// Requests that the currently connected replay UI raise its window to the top.<br/>
/// This is only possible if an instance of the replay UI is currently connected, otherwise this method does nothing.<br/>
/// This can be used in conjunction with <see cref="IsTargetControlConnected"/> and <see cref="LaunchReplayUI"/>,<br/> to intelligently handle showing the UI after making a capture.<br/><br/>
/// Given OS differences, it is not guaranteed that the UI will be successfully raised even if the request is passed on.<br/>
/// On some systems it may only be highlighted or otherwise indicated to the user.
/// </summary>
/// <returns>true if the request was passed onto the UI successfully; false if there is no UI connected or some other error occurred.</returns>
/// <remarks>Requires RenderDoc API version 1.5</remarks>
[RenderDocApiVersion(1, 5)]
public static bool ShowReplayUI()
{
if (Api is null) return false;
AssertAtLeast(1, 5);
return Api->ShowReplayUI() != 0;
}
/// <summary>
/// Sets a given title for the currently in-progress capture, which will be displayed in the UI.<br/>
/// This can be used either with a user-defined capture using a manual start and end,
/// or an automatic capture triggered by <see cref="TriggerCapture"/> or a keypress.<br/>
/// If multiple captures are ongoing at once, the title will be applied to the first capture to end only.<br/>
/// Any subsequent captures will not get any title unless the function is called again.
/// This function can only be called while a capture is in-progress,
/// after <see cref="StartFrameCapture"/> and before <see cref="EndFrameCapture"/>.<br/>
/// If it is called elsewhere it will have no effect.
/// If it is called multiple times within a capture, only the last title will have any effect.
/// </summary>
/// <param name="title">The title to set for the in-progress capture.</param>
/// <remarks>Requires RenderDoc API version 1.6</remarks>
[RenderDocApiVersion(1, 6)]
public static void SetCaptureTitle(string title)
{
if (Api is null) return;
AssertAtLeast(1, 6);
fixed (byte* ptr = title.ToNullTerminatedByteArray())
Api->SetCaptureTitle(ptr);
}
#region Dynamic Library loading
/// <summary>
/// Reload the internal RenderDoc API structure. Useful for manually refreshing <see cref="Api"/> while using process injection.
/// </summary>
/// <param name="ignoreAlreadyLoaded">Ignores the existing API function structure and overwrites it with a re-request.</param>
/// <param name="requiredVersion">The version of the RenderDoc API required by your application.</param>
public static void ReloadApi(bool ignoreAlreadyLoaded = false, RenderDocVersion? requiredVersion = null)
{
if (_loaded && !ignoreAlreadyLoaded)
return;
lock (typeof(RenderDoc))
{
// Prevent double loads.
if (_loaded && !ignoreAlreadyLoaded)
return;
if (requiredVersion.HasValue)
MinimumRequired = requiredVersion.Value;
_loaded = true;
_api = GetApi(MinimumRequired);
if (_api != null)
AssertAtLeast(MinimumRequired);
}
}
private static RenderDocApi* _api = null;
private static bool _loaded;
private static RenderDocApi* Api
{
get
{
ReloadApi();
return _api;
}
}
private static readonly Regex _dynamicLibraryPattern = RenderDocApiDynamicLibraryRegex();
private static RenderDocApi* GetApi(RenderDocVersion minimumRequired = RenderDocVersion.Version_1_0_0)
{
foreach (ProcessModule module in Process.GetCurrentProcess().Modules)
{
string moduleName = module.FileName ?? string.Empty;
if (!_dynamicLibraryPattern.IsMatch(moduleName))
continue;
if (!NativeLibrary.TryLoad(moduleName, out nint moduleHandle))
return null;
if (!NativeLibrary.TryGetExport(moduleHandle, "RENDERDOC_GetAPI", out nint procAddress))
return null;
var RENDERDOC_GetApi = (delegate* unmanaged[Cdecl]<RenderDocVersion, RenderDocApi**, int>)procAddress;
RenderDocApi* api;
return RENDERDOC_GetApi(minimumRequired, &api) != 0 ? api : null;
}
return null;
}
private static void AssertAtLeast(RenderDocVersion rdv, [CallerMemberName] string callee = "")
{
Version ver = rdv.SystemVersion;
AssertAtLeast(ver.Major, ver.Minor, ver.Build, callee);
}
private static void AssertAtLeast(int major, int minor, int patch = 0, [CallerMemberName] string callee = "")
{
if (!AssertVersionEnabled)
return;
if (Version!.Major < major)
goto fail;
if (Version.Major > major)
goto success;
if (Version.Minor < minor)
goto fail;
if (Version.Minor > minor)
goto success;
if (Version.Build < patch)
goto fail;
success:
return;
fail:
Version minVersion =
typeof(RenderDoc).GetMethod(callee)!.GetCustomAttribute<RenderDocApiVersionAttribute>()!.MinVersion;
throw new NotSupportedException(
$"This API was introduced in RenderDoc API {minVersion}. Current API version is {Version}.");
}
private static byte[] ToNullTerminatedByteArray(this string str, Encoding? encoding = null)
{
encoding ??= Encoding.UTF8;
return encoding.GetBytes(str + '\0');
}
[GeneratedRegex(@"(lib)?renderdoc(\.dll|\.so|\.dylib)(\.\d+)?",
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)]
private static partial Regex RenderDocApiDynamicLibraryRegex();
#endregion
}
}
@@ -0,0 +1,51 @@
namespace Ryujinx.Graphics.RenderDocApi
{
#pragma warning disable CS0649
internal unsafe struct RenderDocApi
{
public delegate* unmanaged[Cdecl]<int*, int*, int*, void> GetApiVersion;
public delegate* unmanaged[Cdecl]<CaptureOption, uint, int> SetCaptureOptionU32;
public delegate* unmanaged[Cdecl]<CaptureOption, float, int> SetCaptureOptionF32;
public delegate* unmanaged[Cdecl]<CaptureOption, uint> GetCaptureOptionU32;
public delegate* unmanaged[Cdecl]<CaptureOption, float> GetCaptureOptionF32;
public delegate* unmanaged[Cdecl]<InputButton*, int, void> SetFocusToggleKeys;
public delegate* unmanaged[Cdecl]<InputButton*, int, void> SetCaptureKeys;
public delegate* unmanaged[Cdecl]<OverlayBits> GetOverlayBits;
public delegate* unmanaged[Cdecl]<OverlayBits, OverlayBits, void> MaskOverlayBits;
public delegate* unmanaged[Cdecl]<void> RemoveHooks;
public delegate* unmanaged[Cdecl]<void> UnloadCrashHandler;
public delegate* unmanaged[Cdecl]<byte*, void> SetCaptureFilePathTemplate;
public delegate* unmanaged[Cdecl]<byte*> GetCaptureFilePathTemplate;
public delegate* unmanaged[Cdecl]<int> GetNumCaptures;
public delegate* unmanaged[Cdecl]<int, byte*, int*, long*, uint> GetCapture;
public delegate* unmanaged[Cdecl]<void> TriggerCapture;
public delegate* unmanaged[Cdecl]<uint> IsTargetControlConnected;
public delegate* unmanaged[Cdecl]<uint, byte*, uint> LaunchReplayUI;
public delegate* unmanaged[Cdecl]<void*, void*, void> SetActiveWindow;
public delegate* unmanaged[Cdecl]<void*, void*, void> StartFrameCapture;
public delegate* unmanaged[Cdecl]<uint> IsFrameCapturing;
public delegate* unmanaged[Cdecl]<void*, void*, uint> EndFrameCapture;
// 1.1
public delegate* unmanaged[Cdecl]<uint, void> TriggerMultiFrameCapture;
// 1.2
public delegate* unmanaged[Cdecl]<byte*, byte*, void> SetCaptureFileComments;
// 1.3
public delegate* unmanaged[Cdecl]<void*, void*, uint> DiscardFrameCapture;
// 1.5
public delegate* unmanaged[Cdecl]<uint> ShowReplayUI;
// 1.6
public delegate* unmanaged[Cdecl]<byte*, void> SetCaptureTitle;
}
#pragma warning restore CS0649
}
@@ -0,0 +1,16 @@
using System;
namespace Ryujinx.Graphics.RenderDocApi
{
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property)]
public sealed class RenderDocApiVersionAttribute : Attribute
{
public Version MinVersion { get; }
public RenderDocApiVersionAttribute(int major, int minor, int patch = 0)
{
MinVersion = new Version(major, minor, patch);
}
}
}
@@ -0,0 +1,47 @@
using System;
namespace Ryujinx.Graphics.RenderDocApi
{
public enum RenderDocVersion
{
Version_1_0_0 = 10000,
Version_1_0_1 = 10001,
Version_1_0_2 = 10002,
Version_1_1_0 = 10100,
Version_1_1_1 = 10101,
Version_1_1_2 = 10102,
Version_1_2_0 = 10200,
Version_1_3_0 = 10300,
Version_1_4_0 = 10400,
Version_1_4_1 = 10401,
Version_1_4_2 = 10402,
Version_1_5_0 = 10500,
Version_1_6_0 = 10600,
}
public static partial class Helpers
{
extension(RenderDocVersion rdv)
{
public Version SystemVersion
{
get
{
int i = (int)rdv;
return new (i / 10000, (i % 10000) / 100, i % 100);
}
}
}
extension(Version sv)
{
public RenderDocVersion RenderDocVersion
{
get
{
return (RenderDocVersion)(sv.Major * 10000 + sv.Minor * 100 + sv.Build);
}
}
}
}
}
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
</Project>
@@ -255,8 +255,8 @@ namespace Ryujinx.Graphics.Vulkan.Effects
scissors[0] = new Rectangle<int>(0, 0, texture.Width, texture.Height); scissors[0] = new Rectangle<int>(0, 0, texture.Width, texture.Height);
_pipeline.SetRenderTarget(texture, (uint)texture.Width, (uint)texture.Height); _pipeline.SetRenderTarget(texture, (uint)texture.Width, (uint)texture.Height, false);
_pipeline.SetRenderTargetColorMasks(colorMasks); _pipeline.SetRenderTargetColorMasks(colorMasks, false);
_pipeline.SetScissors(scissors); _pipeline.SetScissors(scissors);
_pipeline.ClearRenderTargetColor(0, 0, 1, new ColorF(0f, 0f, 0f, 1f)); _pipeline.ClearRenderTargetColor(0, 0, 1, new ColorF(0f, 0f, 0f, 1f));
} }
@@ -238,6 +238,7 @@ namespace Ryujinx.Graphics.Vulkan
Face.Back => CullModeFlags.BackBit, Face.Back => CullModeFlags.BackBit,
Face.Front => CullModeFlags.FrontBit, Face.Front => CullModeFlags.FrontBit,
Face.FrontAndBack => CullModeFlags.FrontAndBack, Face.FrontAndBack => CullModeFlags.FrontAndBack,
Face.None => CullModeFlags.None,
_ => LogInvalidAndReturn(face, nameof(Face), CullModeFlags.BackBit), _ => 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) public static StencilOp Convert(this GAL.StencilOp op)
{ {
return op switch return op switch
@@ -31,6 +31,7 @@ namespace Ryujinx.Graphics.Vulkan
public readonly bool SupportsShaderStorageImageMultisample; public readonly bool SupportsShaderStorageImageMultisample;
public readonly bool SupportsConditionalRendering; public readonly bool SupportsConditionalRendering;
public readonly bool SupportsExtendedDynamicState; public readonly bool SupportsExtendedDynamicState;
public readonly PhysicalDeviceExtendedDynamicState2FeaturesEXT SupportsExtendedDynamicState2;
public readonly bool SupportsMultiView; public readonly bool SupportsMultiView;
public readonly bool SupportsNullDescriptors; public readonly bool SupportsNullDescriptors;
public readonly bool SupportsPushDescriptors; public readonly bool SupportsPushDescriptors;
@@ -46,6 +47,7 @@ namespace Ryujinx.Graphics.Vulkan
public readonly bool SupportsViewportArray2; public readonly bool SupportsViewportArray2;
public readonly bool SupportsHostImportedMemory; public readonly bool SupportsHostImportedMemory;
public readonly bool SupportsDepthClipControl; public readonly bool SupportsDepthClipControl;
public readonly bool SupportsWideLines;
public readonly bool SupportsAttachmentFeedbackLoop; public readonly bool SupportsAttachmentFeedbackLoop;
public readonly bool SupportsDynamicAttachmentFeedbackLoop; public readonly bool SupportsDynamicAttachmentFeedbackLoop;
public readonly uint SubgroupSize; public readonly uint SubgroupSize;
@@ -54,6 +56,7 @@ namespace Ryujinx.Graphics.Vulkan
public readonly uint VertexBufferAlignment; public readonly uint VertexBufferAlignment;
public readonly uint SubTexelPrecisionBits; public readonly uint SubTexelPrecisionBits;
public readonly ulong MinResourceAlignment; public readonly ulong MinResourceAlignment;
public readonly uint MaxTessellationPatchSize;
public readonly ulong MinTexelBufferAlignment; public readonly ulong MinTexelBufferAlignment;
public HardwareCapabilities( public HardwareCapabilities(
@@ -72,6 +75,8 @@ namespace Ryujinx.Graphics.Vulkan
bool supportsShaderStorageImageMultisample, bool supportsShaderStorageImageMultisample,
bool supportsConditionalRendering, bool supportsConditionalRendering,
bool supportsExtendedDynamicState, bool supportsExtendedDynamicState,
PhysicalDeviceExtendedDynamicState2FeaturesEXT supportsExtendedDynamicState2,
uint maxTessellationPatchSize,
bool supportsMultiView, bool supportsMultiView,
bool supportsNullDescriptors, bool supportsNullDescriptors,
bool supportsPushDescriptors, bool supportsPushDescriptors,
@@ -87,6 +92,7 @@ namespace Ryujinx.Graphics.Vulkan
bool supportsViewportArray2, bool supportsViewportArray2,
bool supportsHostImportedMemory, bool supportsHostImportedMemory,
bool supportsDepthClipControl, bool supportsDepthClipControl,
bool supportsWideLines,
bool supportsAttachmentFeedbackLoop, bool supportsAttachmentFeedbackLoop,
bool supportsDynamicAttachmentFeedbackLoop, bool supportsDynamicAttachmentFeedbackLoop,
uint subgroupSize, uint subgroupSize,
@@ -112,6 +118,8 @@ namespace Ryujinx.Graphics.Vulkan
SupportsShaderStorageImageMultisample = supportsShaderStorageImageMultisample; SupportsShaderStorageImageMultisample = supportsShaderStorageImageMultisample;
SupportsConditionalRendering = supportsConditionalRendering; SupportsConditionalRendering = supportsConditionalRendering;
SupportsExtendedDynamicState = supportsExtendedDynamicState; SupportsExtendedDynamicState = supportsExtendedDynamicState;
SupportsExtendedDynamicState2 = supportsExtendedDynamicState2;
MaxTessellationPatchSize = maxTessellationPatchSize;
SupportsMultiView = supportsMultiView; SupportsMultiView = supportsMultiView;
SupportsNullDescriptors = supportsNullDescriptors; SupportsNullDescriptors = supportsNullDescriptors;
SupportsPushDescriptors = supportsPushDescriptors; SupportsPushDescriptors = supportsPushDescriptors;
@@ -127,6 +135,7 @@ namespace Ryujinx.Graphics.Vulkan
SupportsViewportArray2 = supportsViewportArray2; SupportsViewportArray2 = supportsViewportArray2;
SupportsHostImportedMemory = supportsHostImportedMemory; SupportsHostImportedMemory = supportsHostImportedMemory;
SupportsDepthClipControl = supportsDepthClipControl; SupportsDepthClipControl = supportsDepthClipControl;
SupportsWideLines = supportsWideLines;
SupportsAttachmentFeedbackLoop = supportsAttachmentFeedbackLoop; SupportsAttachmentFeedbackLoop = supportsAttachmentFeedbackLoop;
SupportsDynamicAttachmentFeedbackLoop = supportsDynamicAttachmentFeedbackLoop; SupportsDynamicAttachmentFeedbackLoop = supportsDynamicAttachmentFeedbackLoop;
SubgroupSize = subgroupSize; SubgroupSize = subgroupSize;
+14 -14
View File
@@ -406,20 +406,20 @@ namespace Ryujinx.Graphics.Vulkan
if (dstIsDepthOrStencil) if (dstIsDepthOrStencil)
{ {
_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)); _pipeline.SetDepthTest(new DepthTestDescriptor(true, true, CompareOp.Always), false);
} }
else if (src.Info.Target.IsMultisample) else if (src.Info.Target.IsMultisample)
{ {
_pipeline.SetProgram(_programColorBlitMs); _pipeline.SetProgram(_programColorBlitMs, false);
} }
else if (clearAlpha) else if (clearAlpha)
{ {
_pipeline.SetProgram(_programColorBlitClearAlpha); _pipeline.SetProgram(_programColorBlitClearAlpha, false);
} }
else else
{ {
_pipeline.SetProgram(_programColorBlit); _pipeline.SetProgram(_programColorBlit, false);
} }
int dstWidth = dst.Width; int dstWidth = dst.Width;
@@ -434,7 +434,7 @@ namespace Ryujinx.Graphics.Vulkan
_pipeline.ClearRenderTargetColor(0, 0, 1, new ColorF(0f, 0f, 0f, 1f)); _pipeline.ClearRenderTargetColor(0, 0, 1, new ColorF(0f, 0f, 0f, 1f));
} }
_pipeline.SetViewports(viewports); _pipeline.SetViewports(viewports, false);
_pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip); _pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
_pipeline.Draw(4, 1, 0, 0); _pipeline.Draw(4, 1, 0, 0);
@@ -566,12 +566,12 @@ namespace Ryujinx.Graphics.Vulkan
if (isDepth) 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)); _pipeline.SetDepthTest(new DepthTestDescriptor(true, true, CompareOp.Always));
} }
else else
{ {
_pipeline.SetProgram(src.Info.Target.IsMultisample ? _programStencilBlitMs : _programStencilBlit); _pipeline.SetProgram(src.Info.Target.IsMultisample ? _programStencilBlitMs : _programStencilBlit, false);
_pipeline.SetStencilTest(CreateStencilTestDescriptor(true)); _pipeline.SetStencilTest(CreateStencilTestDescriptor(true));
} }
@@ -771,8 +771,8 @@ namespace Ryujinx.Graphics.Vulkan
0f, 0f,
1f); 1f);
pipeline.SetProgram(_programColorBlit); pipeline.SetProgram(_programColorBlit, false);
pipeline.SetViewports(viewports); pipeline.SetViewports(viewports, false);
pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip); pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
pipeline.Draw(4, 1, 0, 0); pipeline.Draw(4, 1, 0, 0);
@@ -1229,7 +1229,7 @@ namespace Ryujinx.Graphics.Vulkan
TextureView srcView = Create2DLayerView(src, srcLayer + z, 0); TextureView srcView = Create2DLayerView(src, srcLayer + z, 0);
TextureView dstView = Create2DLayerView(dst, dstLayer + z, 0); TextureView 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); CopyMSDraw(srcView, aspectFlags, fromMS: false);
@@ -1246,7 +1246,7 @@ namespace Ryujinx.Graphics.Vulkan
} }
else else
{ {
_pipeline.SetProgram(_programColorDrawToMs); _pipeline.SetProgram(_programColorDrawToMs, false);
Format format = GetFormat(src.Info.BytesPerPixel); Format format = GetFormat(src.Info.BytesPerPixel);
VkFormat vkFormat = FormatTable.GetFormat(format); VkFormat vkFormat = FormatTable.GetFormat(format);
@@ -1323,12 +1323,12 @@ namespace Ryujinx.Graphics.Vulkan
if (isDepth) if (isDepth)
{ {
_pipeline.SetProgram(fromMS ? _programDepthDrawToNonMs : _programDepthDrawToMs); _pipeline.SetProgram(fromMS ? _programDepthDrawToNonMs : _programDepthDrawToMs, false);
_pipeline.SetDepthTest(new DepthTestDescriptor(true, true, CompareOp.Always)); _pipeline.SetDepthTest(new DepthTestDescriptor(true, true, CompareOp.Always));
} }
else else
{ {
_pipeline.SetProgram(fromMS ? _programStencilDrawToNonMs : _programStencilDrawToMs); _pipeline.SetProgram(fromMS ? _programStencilDrawToNonMs : _programStencilDrawToMs, false);
_pipeline.SetStencilTest(CreateStencilTestDescriptor(true)); _pipeline.SetStencilTest(CreateStencilTestDescriptor(true));
} }
+32
View File
@@ -0,0 +1,32 @@
using Silk.NET.Vulkan;
using System.Runtime.CompilerServices;
namespace Ryujinx.Graphics.Vulkan
{
public static class Helpers
{
extension(Vk api)
{
/// <summary>
/// C# implementation of the RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE macro from the RenderDoc API header, since we cannot use macros from C#.
/// </summary>
/// <returns>The dispatch table pointer, which sits as the first pointer-sized object in the memory pointed to by the <see cref="Vk"/>'s <see cref="Instance"/> pointer.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void* GetRenderDocDevicePointer() =>
api.CurrentInstance is not null
? api.CurrentInstance.Value.GetRenderDocDevicePointer()
: null;
}
extension(Instance instance)
{
/// <summary>
/// C# implementation of the RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE macro from the RenderDoc API header, since we cannot use macros from C#.
/// </summary>
/// <returns>The dispatch table pointer, which sits as the first pointer-sized object in the memory pointed to by the <see cref="Instance"/>'s pointer.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void* GetRenderDocDevicePointer()
=> (*((void**)(instance.Handle)));
}
}
}
+299 -55
View File
@@ -76,6 +76,7 @@ namespace Ryujinx.Graphics.Vulkan
private readonly BufferState[] _transformFeedbackBuffers; private readonly BufferState[] _transformFeedbackBuffers;
private readonly VertexBufferState[] _vertexBuffers; private readonly VertexBufferState[] _vertexBuffers;
private ulong _vertexBuffersDirty; private ulong _vertexBuffersDirty;
private bool _bindingsSet;
protected Rectangle<int> ClearScissor; protected Rectangle<int> ClearScissor;
private readonly VertexBufferUpdater _vertexBufferUpdater; private readonly VertexBufferUpdater _vertexBufferUpdater;
@@ -89,6 +90,9 @@ namespace Ryujinx.Graphics.Vulkan
private bool _tfEnabled; private bool _tfEnabled;
private bool _tfActive; private bool _tfActive;
private readonly bool _supportExtDynamic;
private readonly bool _supportExtDynamic2;
private FeedbackLoopAspects _feedbackLoop; private FeedbackLoopAspects _feedbackLoop;
private bool _passWritesDepthStencil; private bool _passWritesDepthStencil;
@@ -96,6 +100,8 @@ namespace Ryujinx.Graphics.Vulkan
public ulong DrawCount { get; private set; } public ulong DrawCount { get; private set; }
public bool RenderPassActive { get; private set; } public bool RenderPassActive { get; private set; }
private readonly int[] _vertexBufferBindings;
public unsafe PipelineBase(VulkanRenderer gd, Device device) public unsafe PipelineBase(VulkanRenderer gd, Device device)
{ {
Gd = gd; Gd = gd;
@@ -128,7 +134,19 @@ namespace Ryujinx.Graphics.Vulkan
_storedBlend = new PipelineColorBlendAttachmentState[Constants.MaxRenderTargets]; _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() public void Initialize()
@@ -634,19 +652,46 @@ namespace Ryujinx.Graphics.Vulkan
{ {
if (texture is TextureView srcTexture) if (texture is TextureView srcTexture)
{ {
CullModeFlags oldCullMode = _newState.CullMode; CullModeFlags oldCullMode;
bool oldStencilTestEnable = _newState.StencilTestEnable; bool oldStencilTestEnable;
bool oldDepthTestEnable = _newState.DepthTestEnable; bool oldDepthTestEnable;
bool oldDepthWriteEnable = _newState.DepthWriteEnable; bool oldDepthWriteEnable;
PrimitiveTopology oldTopology;
Array16<Silk.NET.Vulkan.Viewport> oldViewports = DynamicState.Viewports; Array16<Silk.NET.Vulkan.Viewport> oldViewports = DynamicState.Viewports;
uint oldViewportsCount = _newState.ViewportsCount; uint oldViewportsCount;
PrimitiveTopology oldTopology = _topology;
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.CullMode = CullModeFlags.None;
_newState.StencilTestEnable = false; _newState.StencilTestEnable = false;
_newState.DepthTestEnable = false; _newState.DepthTestEnable = false;
_newState.DepthWriteEnable = false; _newState.DepthWriteEnable = false;
SignalStateChange(); }
Gd.HelperShader.DrawTexture( Gd.HelperShader.DrawTexture(
Gd, Gd,
@@ -656,16 +701,24 @@ namespace Ryujinx.Graphics.Vulkan
srcRegion, srcRegion,
dstRegion); dstRegion);
if (_supportExtDynamic)
{
DynamicState.SetCullMode(oldCullMode);
DynamicState.SetStencilTest(oldStencilTestEnable);
DynamicState.SetDepthTestBool(oldDepthTestEnable, oldDepthWriteEnable);
}
else
{
_newState.CullMode = oldCullMode; _newState.CullMode = oldCullMode;
_newState.StencilTestEnable = oldStencilTestEnable; _newState.StencilTestEnable = oldStencilTestEnable;
_newState.DepthTestEnable = oldDepthTestEnable; _newState.DepthTestEnable = oldDepthTestEnable;
_newState.DepthWriteEnable = oldDepthWriteEnable; _newState.DepthWriteEnable = oldDepthWriteEnable;
_newState.ViewportsCount = oldViewportsCount;
}
SetPrimitiveTopology(oldTopology); SetPrimitiveTopology(oldTopology);
DynamicState.SetViewports(ref oldViewports, oldViewportsCount); DynamicState.SetViewports(ref oldViewports, oldViewportsCount);
_newState.ViewportsCount = oldViewportsCount;
SignalStateChange();
} }
} }
@@ -796,49 +849,103 @@ namespace Ryujinx.Graphics.Vulkan
public void SetDepthBias(PolygonModeMask enables, float factor, float units, float clamp) 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; if (_supportExtDynamic2)
{
DynamicState.SetDepthBiasEnable(depthBiasEnable);
}
else if (_newState.DepthBiasEnable != depthBiasEnable)
{
_newState.DepthBiasEnable = depthBiasEnable;
changed = true;
}
if (depthBiasEnable)
{
DynamicState.SetDepthBias(factor, units, clamp);
}
if (changed)
{
SignalStateChange(); SignalStateChange();
} }
}
public void SetDepthClamp(bool clamp) public void SetDepthClamp(bool clamp)
{ {
_newState.DepthClampEnable = clamp; _newState.DepthClampEnable = clamp;
SignalStateChange(); SignalStateChange();
} }
public void SetDepthMode(DepthMode mode) public void SetDepthMode(DepthMode mode)
{ {
bool oldMode = _newState.DepthMode; bool newMode = mode == DepthMode.MinusOneToOne;
_newState.DepthMode = mode == DepthMode.MinusOneToOne;
if (_newState.DepthMode != oldMode) if (_newState.DepthMode == newMode)
{ {
SignalStateChange(); return;
}
} }
public void SetDepthTest(DepthTestDescriptor depthTest) _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.DepthTestEnable = depthTest.TestEnable;
_newState.DepthWriteEnable = depthTest.WriteEnable; _newState.DepthWriteEnable = depthTest.WriteEnable;
_newState.DepthCompareOp = depthTest.Func.Convert(); _newState.DepthCompareOp = depthTest.Func.Convert();
UpdatePassDepthStencil(); if (signalChange)
{
SignalStateChange(); SignalStateChange();
} }
}
public void SetFaceCulling(bool enable, Face face) UpdatePassDepthStencil();
}
public void SetFaceCulling(Face face)
{ {
_newState.CullMode = enable ? face.Convert() : CullModeFlags.None; if (_supportExtDynamic)
{
DynamicState.SetCullMode(face.Convert());
}
else
{
_newState.CullMode = face.Convert();
SignalStateChange(); SignalStateChange();
} }
}
public void SetFrontFace(FrontFace frontFace) public void SetFrontFace(FrontFace frontFace)
{
if (_supportExtDynamic)
{
DynamicState.SetFrontFace(frontFace.Convert());
}
else
{ {
_newState.FrontFace = frontFace.Convert(); _newState.FrontFace = frontFace.Convert();
SignalStateChange(); SignalStateChange();
} }
}
public void SetImage(ShaderStage stage, int binding, ITexture image) public void SetImage(ShaderStage stage, int binding, ITexture image)
{ {
@@ -881,28 +988,56 @@ namespace Ryujinx.Graphics.Vulkan
public void SetLineParameters(float width, bool smooth) public void SetLineParameters(float width, bool smooth)
{ {
_newState.LineWidth = width; if (!Gd.IsMoltenVk)
SignalStateChange(); {
DynamicState.SetLineWidth(Gd.Capabilities.SupportsWideLines ? width : 1.0f);
}
} }
public void SetLogicOpState(bool enable, LogicalOp op) 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.LogicOpEnable = enable;
if (Gd.Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2LogicOp)
{
if (logicOpEnable)
{
DynamicState.SetLogicOp(op.Convert());
}
}
else
{
_newState.LogicOp = op.Convert(); _newState.LogicOp = op.Convert();
}
SignalStateChange(); SignalStateChange();
} }
public void SetMultisampleState(MultisampleDescriptor multisample) public void SetMultisampleState(MultisampleDescriptor multisample)
{ {
_newState.AlphaToCoverageEnable = multisample.AlphaToCoverageEnable; _newState.AlphaToCoverageEnable = multisample.AlphaToCoverageEnable;
_newState.AlphaToOneEnable = multisample.AlphaToOneEnable; _newState.AlphaToOneEnable = multisample.AlphaToOneEnable;
SignalStateChange(); SignalStateChange();
} }
public void SetPatchParameters(int vertices, ReadOnlySpan<float> defaultOuterLevel, ReadOnlySpan<float> defaultInnerLevel) public void SetPatchParameters(int vertices, ReadOnlySpan<float> defaultOuterLevel, ReadOnlySpan<float> defaultInnerLevel)
{
if (Gd.Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2PatchControlPoints)
{
DynamicState.SetPatchControlPoints((uint)vertices);
}
else
{ {
_newState.PatchControlPoints = (uint)vertices; _newState.PatchControlPoints = (uint)vertices;
SignalStateChange(); SignalStateChange();
}
// TODO: Default levels (likely needs emulation on shaders?) // TODO: Default levels (likely needs emulation on shaders?)
} }
@@ -918,13 +1053,22 @@ namespace Ryujinx.Graphics.Vulkan
} }
public void SetPrimitiveRestart(bool enable, int index) public void SetPrimitiveRestart(bool enable, int index)
{
if (_supportExtDynamic2)
{
DynamicState.SetPrimitiveRestartEnable(enable);
}
else
{ {
_newState.PrimitiveRestartEnable = enable; _newState.PrimitiveRestartEnable = enable;
// TODO: What to do about the index?
SignalStateChange(); SignalStateChange();
} }
public void SetPrimitiveTopology(PrimitiveTopology topology) // TODO: What to do about the index?
}
public void SetPrimitiveTopology(PrimitiveTopology topology, bool signalChange = true)
{ {
_topology = topology; _topology = topology;
@@ -932,10 +1076,18 @@ namespace Ryujinx.Graphics.Vulkan
_newState.Topology = vkTopology; _newState.Topology = vkTopology;
SignalStateChange(); if (_supportExtDynamic)
{
DynamicState.SetPrimitiveTopology(vkTopology);
} }
public void SetProgram(IProgram program) if (signalChange)
{
SignalStateChange();
}
}
public void SetProgram(IProgram program, bool signalChange = true)
{ {
ShaderCollection internalProgram = (ShaderCollection)program; ShaderCollection internalProgram = (ShaderCollection)program;
PipelineShaderStageCreateInfo[] stages = internalProgram.GetInfos(); PipelineShaderStageCreateInfo[] stages = internalProgram.GetInfos();
@@ -951,7 +1103,10 @@ namespace Ryujinx.Graphics.Vulkan
stages.CopyTo(_newState.Stages.AsSpan()[..stages.Length]); stages.CopyTo(_newState.Stages.AsSpan()[..stages.Length]);
if (signalChange)
{
SignalStateChange(); SignalStateChange();
}
if (internalProgram.IsCompute) if (internalProgram.IsCompute)
{ {
@@ -976,19 +1131,27 @@ namespace Ryujinx.Graphics.Vulkan
} }
public void SetRasterizerDiscard(bool discard) public void SetRasterizerDiscard(bool discard)
{
if (_supportExtDynamic2)
{
DynamicState.SetRasterizerDiscard(discard);
}
else
{ {
_newState.RasterizerDiscardEnable = discard; _newState.RasterizerDiscardEnable = discard;
SignalStateChange(); SignalStateChange();
}
if (!discard && Gd.IsQualcommProprietary) if (!discard && Gd.IsQualcommProprietary)
{ {
// On Adreno, enabling rasterizer discard somehow corrupts the viewport state. // On Adreno, enabling rasterizer discard somehow corrupts the viewport state.
// Force it to be updated on next use to work around this bug. // 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 count = Math.Min(Constants.MaxRenderTargets, componentMask.Length);
int writtenAttachments = 0; int writtenAttachments = 0;
@@ -1029,8 +1192,11 @@ namespace Ryujinx.Graphics.Vulkan
SetRenderTargetsInternal(_preMaskColors, _preMaskDepthStencil, true); SetRenderTargetsInternal(_preMaskColors, _preMaskDepthStencil, true);
} }
else else
{
if (signalChange)
{ {
SignalStateChange(); SignalStateChange();
}
if (writtenAttachments != _writtenAttachmentCount) if (writtenAttachments != _writtenAttachmentCount)
{ {
@@ -1054,7 +1220,7 @@ namespace Ryujinx.Graphics.Vulkan
SetRenderTargetsInternal(colors, depthStencil, Gd.IsTBDR); 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 maxScissors = Gd.Capabilities.SupportsMultiView ? Constants.MaxViewports : 1;
int count = Math.Min(maxScissors, regions.Length); int count = Math.Min(maxScissors, regions.Length);
@@ -1063,6 +1229,8 @@ namespace Ryujinx.Graphics.Vulkan
ClearScissor = regions[0]; ClearScissor = regions[0];
} }
DynamicState.ScissorsCount = count;
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
Rectangle<int> region = regions[i]; Rectangle<int> region = regions[i];
@@ -1072,23 +1240,36 @@ namespace Ryujinx.Graphics.Vulkan
DynamicState.SetScissor(i, new Rect2D(offset, extent)); DynamicState.SetScissor(i, new Rect2D(offset, extent));
} }
DynamicState.ScissorsCount = count; if (!_supportExtDynamic)
{
_newState.ScissorsCount = (uint)count; _newState.ScissorsCount = (uint)count;
if (signalChange)
{
SignalStateChange(); SignalStateChange();
} }
}
}
public void SetStencilTest(StencilTestDescriptor stencilTest) public void SetStencilTest(StencilTestDescriptor stencilTest)
{ {
DynamicState.SetStencilMasks( if (_supportExtDynamic)
(uint)stencilTest.BackFuncMask, {
(uint)stencilTest.BackMask, DynamicState.SetStencilTestandOp(
(uint)stencilTest.BackFuncRef, stencilTest.BackSFail.Convert(),
(uint)stencilTest.FrontFuncMask, stencilTest.BackDpPass.Convert(),
(uint)stencilTest.FrontMask, stencilTest.BackDpFail.Convert(),
(uint)stencilTest.FrontFuncRef); stencilTest.BackFunc.Convert(),
stencilTest.FrontSFail.Convert(),
stencilTest.FrontDpPass.Convert(),
stencilTest.FrontDpFail.Convert(),
stencilTest.FrontFunc.Convert(),
stencilTest.TestEnable);
_newState.StencilTestEnable = stencilTest.TestEnable; UpdatePassDepthStencil();
}
else
{
_newState.StencilBackFailOp = stencilTest.BackSFail.Convert(); _newState.StencilBackFailOp = stencilTest.BackSFail.Convert();
_newState.StencilBackPassOp = stencilTest.BackDpPass.Convert(); _newState.StencilBackPassOp = stencilTest.BackDpPass.Convert();
_newState.StencilBackDepthFailOp = stencilTest.BackDpFail.Convert(); _newState.StencilBackDepthFailOp = stencilTest.BackDpFail.Convert();
@@ -1097,11 +1278,20 @@ namespace Ryujinx.Graphics.Vulkan
_newState.StencilFrontPassOp = stencilTest.FrontDpPass.Convert(); _newState.StencilFrontPassOp = stencilTest.FrontDpPass.Convert();
_newState.StencilFrontDepthFailOp = stencilTest.FrontDpFail.Convert(); _newState.StencilFrontDepthFailOp = stencilTest.FrontDpFail.Convert();
_newState.StencilFrontCompareOp = stencilTest.FrontFunc.Convert(); _newState.StencilFrontCompareOp = stencilTest.FrontFunc.Convert();
_newState.StencilTestEnable = stencilTest.TestEnable;
UpdatePassDepthStencil(); UpdatePassDepthStencil();
SignalStateChange(); SignalStateChange();
} }
DynamicState.SetStencilMask((uint)stencilTest.BackFuncMask,
(uint)stencilTest.BackMask,
(uint)stencilTest.BackFuncRef,
(uint)stencilTest.FrontFuncMask,
(uint)stencilTest.FrontMask,
(uint)stencilTest.FrontFuncRef);
}
public void SetStorageBuffers(ReadOnlySpan<BufferAssignment> buffers) public void SetStorageBuffers(ReadOnlySpan<BufferAssignment> buffers)
{ {
_descriptorSetUpdater.SetStorageBuffers(CommandBuffer, buffers); _descriptorSetUpdater.SetStorageBuffers(CommandBuffer, buffers);
@@ -1220,12 +1410,24 @@ namespace Ryujinx.Graphics.Vulkan
{ {
int count = Math.Min(Constants.MaxVertexBuffers, vertexBuffers.Length); int count = Math.Min(Constants.MaxVertexBuffers, vertexBuffers.Length);
_newState.Internal.VertexBindingDescriptions[0] = new VertexInputBindingDescription(0, 0, VertexInputRate.Vertex);
int validCount = 1; 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; BufferHandle lastHandle = default;
Auto<DisposableBuffer> lastBuffer = null; Auto<DisposableBuffer> lastBuffer = null;
bool vertexBindingDescriptionChanged = false;
bool vertexDescriptionCountChanged = false;
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
@@ -1244,13 +1446,32 @@ namespace Ryujinx.Graphics.Vulkan
if (vb != null) if (vb != null)
{ {
int binding = i + 1;
int descriptorIndex = validCount++; int descriptorIndex = validCount++;
_newState.Internal.VertexBindingDescriptions[descriptorIndex] = new VertexInputBindingDescription( if (_supportExtDynamic && (!Gd.IsMoltenVk || Gd.SupportsMTL31))
(uint)binding, {
(uint)vertexBuffer.Stride, if (_newState.Internal.VertexBindingDescriptions[descriptorIndex].InputRate != 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; int vbSize = vertexBuffer.Buffer.Size;
@@ -1266,7 +1487,7 @@ namespace Ryujinx.Graphics.Vulkan
} }
} }
ref VertexBufferState buffer = ref _vertexBuffers[binding]; ref VertexBufferState buffer = ref _vertexBuffers[_vertexBufferBindings[i]];
int oldScalarAlign = buffer.AttributeScalarAlignment; int oldScalarAlign = buffer.AttributeScalarAlignment;
if (Gd.Capabilities.VertexBufferAlignment < 2 && if (Gd.Capabilities.VertexBufferAlignment < 2 &&
@@ -1283,7 +1504,7 @@ namespace Ryujinx.Graphics.Vulkan
vbSize, vbSize,
vertexBuffer.Stride); vertexBuffer.Stride);
buffer.BindVertexBuffer(Gd, Cbs, (uint)binding, ref _newState, _vertexBufferUpdater); buffer.BindVertexBuffer(Gd, Cbs, (uint)_vertexBufferBindings[i], ref _newState, _vertexBufferUpdater);
} }
} }
else else
@@ -1299,7 +1520,7 @@ namespace Ryujinx.Graphics.Vulkan
vbSize, vbSize,
vertexBuffer.Stride); vertexBuffer.Stride);
_vertexBuffersDirty |= 1UL << binding; _vertexBuffersDirty |= 1UL << _vertexBufferBindings[i];
} }
buffer.AttributeScalarAlignment = oldScalarAlign; buffer.AttributeScalarAlignment = oldScalarAlign;
@@ -1309,11 +1530,19 @@ namespace Ryujinx.Graphics.Vulkan
_vertexBufferUpdater.Commit(Cbs); _vertexBufferUpdater.Commit(Cbs);
if (_newState.VertexBindingDescriptionsCount != validCount)
{
_newState.VertexBindingDescriptionsCount = (uint)validCount; _newState.VertexBindingDescriptionsCount = (uint)validCount;
SignalStateChange(); vertexDescriptionCountChanged = true;
} }
public void SetViewports(ReadOnlySpan<Viewport> viewports) if (vertexDescriptionCountChanged || vertexBindingDescriptionChanged)
{
SignalStateChange();
}
}
public void SetViewports(ReadOnlySpan<Viewport> viewports, bool signalChange = true)
{ {
int maxViewports = Gd.Capabilities.SupportsMultiView ? Constants.MaxViewports : 1; int maxViewports = Gd.Capabilities.SupportsMultiView ? Constants.MaxViewports : 1;
int count = Math.Min(maxViewports, viewports.Length); int count = Math.Min(maxViewports, viewports.Length);
@@ -1338,9 +1567,16 @@ namespace Ryujinx.Graphics.Vulkan
Clamp(viewport.DepthFar))); Clamp(viewport.DepthFar)));
} }
if (!_supportExtDynamic)
{
_newState.ViewportsCount = (uint)count; _newState.ViewportsCount = (uint)count;
if (signalChange)
{
SignalStateChange(); SignalStateChange();
} }
}
}
public void SwapBuffer(Auto<DisposableBuffer> from, Auto<DisposableBuffer> to) public void SwapBuffer(Auto<DisposableBuffer> from, Auto<DisposableBuffer> to)
{ {
@@ -1388,7 +1624,7 @@ namespace Ryujinx.Graphics.Vulkan
_vertexBuffersDirty = ulong.MaxValue >> (64 - _vertexBuffers.Length); _vertexBuffersDirty = ulong.MaxValue >> (64 - _vertexBuffers.Length);
_descriptorSetUpdater.SignalCommandBufferChange(); _descriptorSetUpdater.SignalCommandBufferChange();
DynamicState.ForceAllDirty(); DynamicState.ForceAllDirty(Gd);
_currentPipelineHandle = 0; _currentPipelineHandle = 0;
} }
@@ -1546,8 +1782,9 @@ namespace Ryujinx.Graphics.Vulkan
if (Gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop) if (Gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop)
{ {
DynamicState.SetFeedbackLoop(aspects); DynamicState.SetFeedbackLoop(aspects);
_newState.FeedbackLoopDynamicState = true;
} }
else else if (Gd.Capabilities.SupportsAttachmentFeedbackLoop)
{ {
_newState.FeedbackLoopAspects = aspects; _newState.FeedbackLoopAspects = aspects;
} }
@@ -1607,8 +1844,15 @@ namespace Ryujinx.Graphics.Vulkan
} }
// Stencil test being enabled doesn't necessarily mean a write, but it's not critical to check. // Stencil test being enabled doesn't necessarily mean a write, but it's not critical to check.
if (_supportExtDynamic)
{
_passWritesDepthStencil |= (DynamicState.DepthTestEnable && DynamicState.DepthWriteEnable) || DynamicState.StencilTestEnable;
}
else
{
_passWritesDepthStencil |= (_newState.DepthTestEnable && _newState.DepthWriteEnable) || _newState.StencilTestEnable; _passWritesDepthStencil |= (_newState.DepthTestEnable && _newState.DepthWriteEnable) || _newState.StencilTestEnable;
} }
}
private bool RecreateGraphicsPipelineIfNeeded() private bool RecreateGraphicsPipelineIfNeeded()
{ {
@@ -5,6 +5,7 @@ using System;
using VulkanFormat = Silk.NET.Vulkan.Format; using VulkanFormat = Silk.NET.Vulkan.Format;
using GALFormat = Ryujinx.Graphics.GAL.Format; using GALFormat = Ryujinx.Graphics.GAL.Format;
using PolygonMode = Silk.NET.Vulkan.PolygonMode; using PolygonMode = Silk.NET.Vulkan.PolygonMode;
using PrimitiveTopology = Ryujinx.Graphics.GAL.PrimitiveTopology;
namespace Ryujinx.Graphics.Vulkan namespace Ryujinx.Graphics.Vulkan
{ {
@@ -164,34 +165,37 @@ namespace Ryujinx.Graphics.Vulkan
public static PipelineState ToVulkanPipelineState(this ProgramPipelineState state, VulkanRenderer gd) public static PipelineState ToVulkanPipelineState(this ProgramPipelineState state, VulkanRenderer gd)
{ {
var extendedDynamicState2 = gd.Capabilities.SupportsExtendedDynamicState2;
var extendedDynamicState = gd.Capabilities.SupportsExtendedDynamicState;
PipelineState pipeline = new(); PipelineState pipeline = new();
pipeline.Initialize(); pipeline.Initialize(gd.Capabilities);
// It is assumed that Dynamic State is enabled when this conversion is used. // 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.DepthBoundsTestEnable = false; // Not implemented.
pipeline.DepthClampEnable = state.DepthClampEnable; pipeline.DepthClampEnable = state.DepthClampEnable;
pipeline.DepthTestEnable = state.DepthTest.TestEnable; pipeline.AlphaToCoverageEnable = state.AlphaToCoverageEnable;
pipeline.DepthWriteEnable = state.DepthTest.WriteEnable; pipeline.AlphaToOneEnable = state.AlphaToOneEnable;
pipeline.DepthCompareOp = state.DepthTest.Func.Convert();
pipeline.DepthMode = state.DepthMode == DepthMode.MinusOneToOne; pipeline.DepthMode = state.DepthMode == DepthMode.MinusOneToOne;
pipeline.FrontFace = state.FrontFace.Convert();
pipeline.HasDepthStencil = state.DepthStencilEnable; 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.PolygonMode = PolygonMode.Fill; // Not implemented.
pipeline.PrimitiveRestartEnable = state.PrimitiveRestartEnable;
pipeline.RasterizerDiscardEnable = state.RasterizerDiscard; pipeline.Topology = gd.TopologyRemap(state.Topology).Convert();
pipeline.SamplesCount = (uint)state.SamplesCount;
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) if (gd.Capabilities.SupportsMultiView)
{ {
@@ -204,9 +208,7 @@ namespace Ryujinx.Graphics.Vulkan
pipeline.ViewportsCount = 1; pipeline.ViewportsCount = 1;
} }
pipeline.DepthBiasEnable = state.BiasEnable != 0; pipeline.StencilTestEnable = state.StencilTest.TestEnable;
// Stencil masks and ref are dynamic, so are 0 in the Vulkan pipeline.
pipeline.StencilFrontFailOp = state.StencilTest.FrontSFail.Convert(); pipeline.StencilFrontFailOp = state.StencilTest.FrontSFail.Convert();
pipeline.StencilFrontPassOp = state.StencilTest.FrontDpPass.Convert(); pipeline.StencilFrontPassOp = state.StencilTest.FrontDpPass.Convert();
@@ -217,10 +219,29 @@ namespace Ryujinx.Graphics.Vulkan
pipeline.StencilBackPassOp = state.StencilTest.BackDpPass.Convert(); pipeline.StencilBackPassOp = state.StencilTest.BackDpPass.Convert();
pipeline.StencilBackDepthFailOp = state.StencilTest.BackDpFail.Convert(); pipeline.StencilBackDepthFailOp = state.StencilTest.BackDpFail.Convert();
pipeline.StencilBackCompareOp = state.StencilTest.BackFunc.Convert(); pipeline.StencilBackCompareOp = state.StencilTest.BackFunc.Convert();
}
pipeline.StencilTestEnable = state.StencilTest.TestEnable; if (!extendedDynamicState2.ExtendedDynamicState2)
{
pipeline.PrimitiveRestartEnable = state.PrimitiveRestartEnable;
pipeline.RasterizerDiscardEnable = state.RasterizerDiscard;
pipeline.DepthBiasEnable = (state.BiasEnable != 0) &&
(state.DepthBiasFactor != 0 && state.DepthBiasUnits != 0);
}
pipeline.Topology = gd.TopologyRemap(state.Topology).Convert(); 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 vaCount = Math.Min(Constants.MaxVertexAttributes, state.VertexAttribCount);
int vbCount = Math.Min(Constants.MaxVertexBuffers, state.VertexBufferCount); int vbCount = Math.Min(Constants.MaxVertexBuffers, state.VertexBufferCount);
@@ -247,7 +268,7 @@ namespace Ryujinx.Graphics.Vulkan
} }
int descriptorIndex = 1; 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);
Span<BufferPipelineDescriptor> vertexBuffersSpan = state.VertexBuffers.AsSpan(); Span<BufferPipelineDescriptor> vertexBuffersSpan = state.VertexBuffers.AsSpan();
Span<VertexInputBindingDescription> vertexBindingDescriptionsSpan = pipeline.Internal.VertexBindingDescriptions.AsSpan(); Span<VertexInputBindingDescription> vertexBindingDescriptionsSpan = pipeline.Internal.VertexBindingDescriptions.AsSpan();
@@ -270,7 +291,7 @@ namespace Ryujinx.Graphics.Vulkan
// TODO: Support divisor > 1 // TODO: Support divisor > 1
vertexBindingDescriptionsSpan[descriptorIndex++] = new VertexInputBindingDescription( vertexBindingDescriptionsSpan[descriptorIndex++] = new VertexInputBindingDescription(
(uint)i + 1, (uint)i + 1,
(uint)alignedStride, extendedDynamicState && (!gd.IsMoltenVk || gd.SupportsMTL31) ? null : (uint)alignedStride,
inputRate); inputRate);
} }
} }
@@ -2,6 +2,7 @@ using Ryujinx.Common.Memory;
using Silk.NET.Vulkan; using Silk.NET.Vulkan;
using Silk.NET.Vulkan.Extensions.EXT; using Silk.NET.Vulkan.Extensions.EXT;
using System; using System;
using System.Numerics;
namespace Ryujinx.Graphics.Vulkan namespace Ryujinx.Graphics.Vulkan
{ {
@@ -10,6 +11,7 @@ namespace Ryujinx.Graphics.Vulkan
private float _depthBiasSlopeFactor; private float _depthBiasSlopeFactor;
private float _depthBiasConstantFactor; private float _depthBiasConstantFactor;
private float _depthBiasClamp; private float _depthBiasClamp;
private bool _depthBiasEnable;
public int ScissorsCount; public int ScissorsCount;
private Array16<Rect2D> _scissors; private Array16<Rect2D> _scissors;
@@ -21,6 +23,23 @@ namespace Ryujinx.Graphics.Vulkan
private uint _frontWriteMask; private uint _frontWriteMask;
private uint _frontReference; 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 Array4<float> _blendConstants;
private FeedbackLoopAspects _feedbackLoopAspects; private FeedbackLoopAspects _feedbackLoopAspects;
@@ -28,6 +47,19 @@ namespace Ryujinx.Graphics.Vulkan
public uint ViewportsCount; public uint ViewportsCount;
public Array16<Viewport> Viewports; 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] [Flags]
private enum DirtyFlags private enum DirtyFlags
{ {
@@ -38,7 +70,21 @@ namespace Ryujinx.Graphics.Vulkan
Stencil = 1 << 3, Stencil = 1 << 3,
Viewport = 1 << 4, Viewport = 1 << 4,
FeedbackLoop = 1 << 5, 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 | FeedbackLoop,
Extended = CullMode | FrontFace | DepthTestBool | DepthTestCompareOp | StencilTestEnableAndStencilOp | PrimitiveTopology,
Extended2 = RasterDiscard | PrimitiveRestart | DepthBiasEnable,
} }
private DirtyFlags _dirty; private DirtyFlags _dirty;
@@ -49,7 +95,6 @@ namespace Ryujinx.Graphics.Vulkan
_blendConstants[1] = g; _blendConstants[1] = g;
_blendConstants[2] = b; _blendConstants[2] = b;
_blendConstants[3] = a; _blendConstants[3] = a;
_dirty |= DirtyFlags.Blend; _dirty |= DirtyFlags.Blend;
} }
@@ -62,15 +107,64 @@ namespace Ryujinx.Graphics.Vulkan
_dirty |= DirtyFlags.DepthBias; _dirty |= DirtyFlags.DepthBias;
} }
public void SetDepthBiasEnable(bool enable)
{
_depthBiasEnable = enable;
_dirty |= DirtyFlags.DepthBiasEnable;
}
public void SetScissor(int index, Rect2D scissor) public void SetScissor(int index, Rect2D scissor)
{ {
_scissors[index] = scissor; _scissors[index] = scissor;
_dirty |= DirtyFlags.Scissor; _dirty |= DirtyFlags.Scissor;
} }
public void SetStencilMasks( public void SetDepthTestBool(bool testEnable, bool writeEnable)
uint backCompareMask, {
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 backWriteMask,
uint backReference, uint backReference,
uint frontCompareMask, uint frontCompareMask,
@@ -83,27 +177,45 @@ namespace Ryujinx.Graphics.Vulkan
_frontCompareMask = frontCompareMask; _frontCompareMask = frontCompareMask;
_frontWriteMask = frontWriteMask; _frontWriteMask = frontWriteMask;
_frontReference = frontReference; _frontReference = frontReference;
_dirty |= DirtyFlags.Stencil; _dirty |= DirtyFlags.Stencil;
} }
public void SetViewport(int index, Viewport viewport) public void SetViewport(int index, Viewport viewport)
{ {
Viewports[index] = viewport; Viewports[index] = viewport;
_dirty |= DirtyFlags.Viewport; _dirty |= DirtyFlags.Viewport;
} }
public void SetViewports(ref Array16<Viewport> viewports, uint viewportsCount) public void SetViewports(ref Array16<Viewport> viewports, uint viewportsCount)
{
if (!Viewports.Equals(viewports) || ViewportsCount != viewportsCount)
{ {
Viewports = viewports; Viewports = viewports;
ViewportsCount = viewportsCount; ViewportsCount = viewportsCount;
if (ViewportsCount != 0) if (ViewportsCount != 0)
{ {
_dirty |= DirtyFlags.Viewport; _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) public void SetFeedbackLoop(FeedbackLoopAspects aspects)
{ {
@@ -112,43 +224,149 @@ namespace Ryujinx.Graphics.Vulkan
_dirty |= DirtyFlags.FeedbackLoop; _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) public void ReplayIfDirty(VulkanRenderer gd, CommandBuffer commandBuffer)
{ {
Vk api = gd.Api; if (_dirty == DirtyFlags.None)
if (_dirty.HasFlag(DirtyFlags.Blend))
{ {
return;
}
var api = gd.Api;
var extendedStateApi = gd.ExtendedDynamicStateApi;
var extendedState2Api = gd.ExtendedDynamicState2Api;
var dynamicFeedbackLoopApi = gd.DynamicFeedbackLoopApi;
DirtyFlags dirtyFlags = _dirty;
while (dirtyFlags != DirtyFlags.None)
{
int bitIndex = BitOperations.TrailingZeroCount((uint)dirtyFlags);
DirtyFlags currentFlag = (DirtyFlags)(1 << bitIndex);
switch (currentFlag)
{
case DirtyFlags.Blend:
RecordBlend(api, commandBuffer); RecordBlend(api, commandBuffer);
} break;
case DirtyFlags.DepthBias:
if (_dirty.HasFlag(DirtyFlags.DepthBias))
{
RecordDepthBias(api, commandBuffer); 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.Scissor)) dirtyFlags &= ~currentFlag;
{
RecordScissor(api, commandBuffer);
}
if (_dirty.HasFlag(DirtyFlags.Stencil))
{
RecordStencilMasks(api, commandBuffer);
}
if (_dirty.HasFlag(DirtyFlags.Viewport))
{
RecordViewport(api, commandBuffer);
}
if (_dirty.HasFlag(DirtyFlags.FeedbackLoop) && gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop)
{
RecordFeedbackLoop(gd.DynamicFeedbackLoopApi, commandBuffer);
} }
_dirty = DirtyFlags.None; _dirty = DirtyFlags.None;
@@ -164,15 +382,27 @@ namespace Ryujinx.Graphics.Vulkan
api.CmdSetDepthBias(commandBuffer, _depthBiasConstantFactor, _depthBiasClamp, _depthBiasSlopeFactor); 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) 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.CmdSetStencilCompareMask(commandBuffer, StencilFaceFlags.FaceBackBit, _backCompareMask);
api.CmdSetStencilWriteMask(commandBuffer, StencilFaceFlags.FaceBackBit, _backWriteMask); api.CmdSetStencilWriteMask(commandBuffer, StencilFaceFlags.FaceBackBit, _backWriteMask);
@@ -182,12 +412,107 @@ namespace Ryujinx.Graphics.Vulkan
api.CmdSetStencilReference(commandBuffer, StencilFaceFlags.FaceFrontBit, _frontReference); 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.CmdSetViewport(commandBuffer, 0, ViewportsCount, Viewports.AsSpan()); 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)
{
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) private readonly void RecordFeedbackLoop(ExtAttachmentFeedbackLoopDynamicState api, CommandBuffer commandBuffer)
@@ -8,12 +8,16 @@ 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); CreateFramebuffer(view, width, height);
CreateRenderPass(); CreateRenderPass();
if (signalChange)
{
SignalStateChange(); SignalStateChange();
} }
}
private void CreateFramebuffer(TextureView view, uint width, uint height) private void CreateFramebuffer(TextureView view, uint width, uint height)
{ {
+436 -334
View File
@@ -7,312 +7,258 @@ namespace Ryujinx.Graphics.Vulkan
{ {
struct PipelineState : IDisposable struct PipelineState : IDisposable
{ {
private const int MaxDynamicStatesCount = 9; private const int MaxDynamicStatesCount = 23;
public PipelineUid Internal; public PipelineUid Internal;
public float LineWidth public PolygonMode PolygonMode
{ {
readonly get => BitConverter.Int32BitsToSingle((int)((Internal.Id0 >> 0) & 0xFFFFFFFF)); readonly get => (PolygonMode)((Internal.Id0 >> 0) & 0x3FFFFFFF);
set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFFF00000000) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 0); set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFFFC0000000) | ((ulong)value << 0);
} }
public float DepthBiasClamp public uint StagesCount
{ {
readonly get => BitConverter.Int32BitsToSingle((int)((Internal.Id0 >> 32) & 0xFFFFFFFF)); readonly get => (byte)((Internal.Id0 >> 30) & 0xFF);
set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFFF) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 32); set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFC03FFFFFFF) | ((ulong)value << 30);
} }
public float DepthBiasConstantFactor public uint VertexAttributeDescriptionsCount
{ {
readonly get => BitConverter.Int32BitsToSingle((int)((Internal.Id1 >> 0) & 0xFFFFFFFF)); readonly get => (byte)((Internal.Id0 >> 38) & 0xFF);
set => Internal.Id1 = (Internal.Id1 & 0xFFFFFFFF00000000) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 0); set => Internal.Id0 = (Internal.Id0 & 0xFFFFC03FFFFFFFFF) | ((ulong)value << 38);
} }
public float DepthBiasSlopeFactor public uint VertexBindingDescriptionsCount
{ {
readonly get => BitConverter.Int32BitsToSingle((int)((Internal.Id1 >> 32) & 0xFFFFFFFF)); readonly get => (byte)((Internal.Id0 >> 46) & 0xFF);
set => Internal.Id1 = (Internal.Id1 & 0xFFFFFFFF) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 32); set => Internal.Id0 = (Internal.Id0 & 0xFFC03FFFFFFFFFFF) | ((ulong)value << 46);
} }
public uint StencilFrontCompareMask public uint ViewportsCount
{
readonly get => (byte)((Internal.Id0 >> 54) & 0xFF);
set => Internal.Id0 = (Internal.Id0 & 0xC03FFFFFFFFFFFFF) | ((ulong)value << 54);
}
public uint ScissorsCount
{
readonly get => (byte)((Internal.Id1 >> 0) & 0xFF);
set => Internal.Id1 = (Internal.Id1 & 0xFFFFFFFFFFFFFF00) | ((ulong)value << 0);
}
public uint ColorBlendAttachmentStateCount
{
readonly get => (byte)((Internal.Id1 >> 8) & 0xFF);
set => Internal.Id1 = (Internal.Id1 & 0xFFFFFFFFFFFF00FF) | ((ulong)value << 8);
}
public PrimitiveTopology Topology
{
readonly get => (PrimitiveTopology)((Internal.Id1 >> 16) & 0xF);
set => Internal.Id1 = (Internal.Id1 & 0xFFFFFFFFFFF0FFFF) | ((ulong)value << 16);
}
public LogicOp LogicOp
{
readonly get => (LogicOp)((Internal.Id1 >> 20) & 0xF);
set => Internal.Id1 = (Internal.Id1 & 0xFFFFFFFFFF0FFFFF) | ((ulong)value << 20);
}
public CompareOp DepthCompareOp
{
readonly get => (CompareOp)((Internal.Id1 >> 24) & 0x7);
set => Internal.Id1 = (Internal.Id1 & 0xFFFFFFFFF8FFFFFF) | ((ulong)value << 24);
}
public StencilOp StencilFrontFailOp
{
readonly get => (StencilOp)((Internal.Id1 >> 27) & 0x7);
set => Internal.Id1 = (Internal.Id1 & 0xFFFFFFFFC7FFFFFF) | ((ulong)value << 27);
}
public StencilOp StencilFrontPassOp
{
readonly get => (StencilOp)((Internal.Id1 >> 30) & 0x7);
set => Internal.Id1 = (Internal.Id1 & 0xFFFFFFFE3FFFFFFF) | ((ulong)value << 30);
}
public StencilOp StencilFrontDepthFailOp
{
readonly get => (StencilOp)((Internal.Id1 >> 33) & 0x7);
set => Internal.Id1 = (Internal.Id1 & 0xFFFFFFF1FFFFFFFF) | ((ulong)value << 33);
}
public CompareOp StencilFrontCompareOp
{
readonly get => (CompareOp)((Internal.Id1 >> 36) & 0x7);
set => Internal.Id1 = (Internal.Id1 & 0xFFFFFF8FFFFFFFFF) | ((ulong)value << 36);
}
public StencilOp StencilBackFailOp
{
readonly get => (StencilOp)((Internal.Id1 >> 39) & 0x7);
set => Internal.Id1 = (Internal.Id1 & 0xFFFFFC7FFFFFFFFF) | ((ulong)value << 39);
}
public StencilOp StencilBackPassOp
{
readonly get => (StencilOp)((Internal.Id1 >> 42) & 0x7);
set => Internal.Id1 = (Internal.Id1 & 0xFFFFE3FFFFFFFFFF) | ((ulong)value << 42);
}
public StencilOp StencilBackDepthFailOp
{
readonly get => (StencilOp)((Internal.Id1 >> 45) & 0x7);
set => Internal.Id1 = (Internal.Id1 & 0xFFFF1FFFFFFFFFFF) | ((ulong)value << 45);
}
public CompareOp StencilBackCompareOp
{
readonly get => (CompareOp)((Internal.Id1 >> 48) & 0x7);
set => Internal.Id1 = (Internal.Id1 & 0xFFF8FFFFFFFFFFFF) | ((ulong)value << 48);
}
public CullModeFlags CullMode
{
readonly get => (CullModeFlags)((Internal.Id1 >> 51) & 0x3);
set => Internal.Id1 = (Internal.Id1 & 0xFFE7FFFFFFFFFFFF) | ((ulong)value << 51);
}
public bool PrimitiveRestartEnable
{
readonly get => ((Internal.Id1 >> 53) & 0x1) != 0UL;
set => Internal.Id1 = (Internal.Id1 & 0xFFDFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 53);
}
public bool DepthClampEnable
{
readonly get => ((Internal.Id1 >> 54) & 0x1) != 0UL;
set => Internal.Id1 = (Internal.Id1 & 0xFFBFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 54);
}
public bool RasterizerDiscardEnable
{
readonly get => ((Internal.Id1 >> 55) & 0x1) != 0UL;
set => Internal.Id1 = (Internal.Id1 & 0xFF7FFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 55);
}
public FrontFace FrontFace
{
readonly get => (FrontFace)((Internal.Id1 >> 56) & 0x1);
set => Internal.Id1 = (Internal.Id1 & 0xFEFFFFFFFFFFFFFF) | ((ulong)value << 56);
}
public bool DepthBiasEnable
{
readonly get => ((Internal.Id1 >> 57) & 0x1) != 0UL;
set => Internal.Id1 = (Internal.Id1 & 0xFDFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 57);
}
public bool DepthTestEnable
{
readonly get => ((Internal.Id1 >> 58) & 0x1) != 0UL;
set => Internal.Id1 = (Internal.Id1 & 0xFBFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 58);
}
public bool DepthWriteEnable
{
readonly get => ((Internal.Id1 >> 59) & 0x1) != 0UL;
set => Internal.Id1 = (Internal.Id1 & 0xF7FFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 59);
}
public bool DepthBoundsTestEnable
{
readonly get => ((Internal.Id1 >> 60) & 0x1) != 0UL;
set => Internal.Id1 = (Internal.Id1 & 0xEFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 60);
}
public bool StencilTestEnable
{
readonly get => ((Internal.Id1 >> 61) & 0x1) != 0UL;
set => Internal.Id1 = (Internal.Id1 & 0xDFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 61);
}
public bool LogicOpEnable
{
readonly get => ((Internal.Id1 >> 62) & 0x1) != 0UL;
set => Internal.Id1 = (Internal.Id1 & 0xBFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 62);
}
public bool HasDepthStencil
{
readonly get => ((Internal.Id1 >> 63) & 0x1) != 0UL;
set => Internal.Id1 = (Internal.Id1 & 0x7FFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 63);
}
public uint PatchControlPoints
{ {
readonly get => (uint)((Internal.Id2 >> 0) & 0xFFFFFFFF); readonly get => (uint)((Internal.Id2 >> 0) & 0xFFFFFFFF);
set => Internal.Id2 = (Internal.Id2 & 0xFFFFFFFF00000000) | ((ulong)value << 0); set => Internal.Id2 = (Internal.Id2 & 0xFFFFFFFF00000000) | ((ulong)value << 0);
} }
public uint StencilFrontWriteMask public uint SamplesCount
{ {
readonly get => (uint)((Internal.Id2 >> 32) & 0xFFFFFFFF); readonly get => (uint)((Internal.Id2 >> 32) & 0xFFFFFFFF);
set => Internal.Id2 = (Internal.Id2 & 0xFFFFFFFF) | ((ulong)value << 32); set => Internal.Id2 = (Internal.Id2 & 0xFFFFFFFF) | ((ulong)value << 32);
} }
public uint StencilFrontReference
{
readonly get => (uint)((Internal.Id3 >> 0) & 0xFFFFFFFF);
set => Internal.Id3 = (Internal.Id3 & 0xFFFFFFFF00000000) | ((ulong)value << 0);
}
public uint StencilBackCompareMask
{
readonly get => (uint)((Internal.Id3 >> 32) & 0xFFFFFFFF);
set => Internal.Id3 = (Internal.Id3 & 0xFFFFFFFF) | ((ulong)value << 32);
}
public uint StencilBackWriteMask
{
readonly get => (uint)((Internal.Id4 >> 0) & 0xFFFFFFFF);
set => Internal.Id4 = (Internal.Id4 & 0xFFFFFFFF00000000) | ((ulong)value << 0);
}
public uint StencilBackReference
{
readonly get => (uint)((Internal.Id4 >> 32) & 0xFFFFFFFF);
set => Internal.Id4 = (Internal.Id4 & 0xFFFFFFFF) | ((ulong)value << 32);
}
public PolygonMode PolygonMode
{
readonly get => (PolygonMode)((Internal.Id5 >> 0) & 0x3FFFFFFF);
set => Internal.Id5 = (Internal.Id5 & 0xFFFFFFFFC0000000) | ((ulong)value << 0);
}
public uint StagesCount
{
readonly get => (byte)((Internal.Id5 >> 30) & 0xFF);
set => Internal.Id5 = (Internal.Id5 & 0xFFFFFFC03FFFFFFF) | ((ulong)value << 30);
}
public uint VertexAttributeDescriptionsCount
{
readonly get => (byte)((Internal.Id5 >> 38) & 0xFF);
set => Internal.Id5 = (Internal.Id5 & 0xFFFFC03FFFFFFFFF) | ((ulong)value << 38);
}
public uint VertexBindingDescriptionsCount
{
readonly get => (byte)((Internal.Id5 >> 46) & 0xFF);
set => Internal.Id5 = (Internal.Id5 & 0xFFC03FFFFFFFFFFF) | ((ulong)value << 46);
}
public uint ViewportsCount
{
readonly get => (byte)((Internal.Id5 >> 54) & 0xFF);
set => Internal.Id5 = (Internal.Id5 & 0xC03FFFFFFFFFFFFF) | ((ulong)value << 54);
}
public uint ScissorsCount
{
readonly get => (byte)((Internal.Id6 >> 0) & 0xFF);
set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFFFFFFF00) | ((ulong)value << 0);
}
public uint ColorBlendAttachmentStateCount
{
readonly get => (byte)((Internal.Id6 >> 8) & 0xFF);
set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFFFFF00FF) | ((ulong)value << 8);
}
public PrimitiveTopology Topology
{
readonly get => (PrimitiveTopology)((Internal.Id6 >> 16) & 0xF);
set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFFFF0FFFF) | ((ulong)value << 16);
}
public LogicOp LogicOp
{
readonly get => (LogicOp)((Internal.Id6 >> 20) & 0xF);
set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFFF0FFFFF) | ((ulong)value << 20);
}
public CompareOp DepthCompareOp
{
readonly get => (CompareOp)((Internal.Id6 >> 24) & 0x7);
set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFF8FFFFFF) | ((ulong)value << 24);
}
public StencilOp StencilFrontFailOp
{
readonly get => (StencilOp)((Internal.Id6 >> 27) & 0x7);
set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFC7FFFFFF) | ((ulong)value << 27);
}
public StencilOp StencilFrontPassOp
{
readonly get => (StencilOp)((Internal.Id6 >> 30) & 0x7);
set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFE3FFFFFFF) | ((ulong)value << 30);
}
public StencilOp StencilFrontDepthFailOp
{
readonly get => (StencilOp)((Internal.Id6 >> 33) & 0x7);
set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFF1FFFFFFFF) | ((ulong)value << 33);
}
public CompareOp StencilFrontCompareOp
{
readonly get => (CompareOp)((Internal.Id6 >> 36) & 0x7);
set => Internal.Id6 = (Internal.Id6 & 0xFFFFFF8FFFFFFFFF) | ((ulong)value << 36);
}
public StencilOp StencilBackFailOp
{
readonly get => (StencilOp)((Internal.Id6 >> 39) & 0x7);
set => Internal.Id6 = (Internal.Id6 & 0xFFFFFC7FFFFFFFFF) | ((ulong)value << 39);
}
public StencilOp StencilBackPassOp
{
readonly get => (StencilOp)((Internal.Id6 >> 42) & 0x7);
set => Internal.Id6 = (Internal.Id6 & 0xFFFFE3FFFFFFFFFF) | ((ulong)value << 42);
}
public StencilOp StencilBackDepthFailOp
{
readonly get => (StencilOp)((Internal.Id6 >> 45) & 0x7);
set => Internal.Id6 = (Internal.Id6 & 0xFFFF1FFFFFFFFFFF) | ((ulong)value << 45);
}
public CompareOp StencilBackCompareOp
{
readonly get => (CompareOp)((Internal.Id6 >> 48) & 0x7);
set => Internal.Id6 = (Internal.Id6 & 0xFFF8FFFFFFFFFFFF) | ((ulong)value << 48);
}
public CullModeFlags CullMode
{
readonly get => (CullModeFlags)((Internal.Id6 >> 51) & 0x3);
set => Internal.Id6 = (Internal.Id6 & 0xFFE7FFFFFFFFFFFF) | ((ulong)value << 51);
}
public bool PrimitiveRestartEnable
{
readonly get => ((Internal.Id6 >> 53) & 0x1) != 0UL;
set => Internal.Id6 = (Internal.Id6 & 0xFFDFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 53);
}
public bool DepthClampEnable
{
readonly get => ((Internal.Id6 >> 54) & 0x1) != 0UL;
set => Internal.Id6 = (Internal.Id6 & 0xFFBFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 54);
}
public bool RasterizerDiscardEnable
{
readonly get => ((Internal.Id6 >> 55) & 0x1) != 0UL;
set => Internal.Id6 = (Internal.Id6 & 0xFF7FFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 55);
}
public FrontFace FrontFace
{
readonly get => (FrontFace)((Internal.Id6 >> 56) & 0x1);
set => Internal.Id6 = (Internal.Id6 & 0xFEFFFFFFFFFFFFFF) | ((ulong)value << 56);
}
public bool DepthBiasEnable
{
readonly get => ((Internal.Id6 >> 57) & 0x1) != 0UL;
set => Internal.Id6 = (Internal.Id6 & 0xFDFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 57);
}
public bool DepthTestEnable
{
readonly get => ((Internal.Id6 >> 58) & 0x1) != 0UL;
set => Internal.Id6 = (Internal.Id6 & 0xFBFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 58);
}
public bool DepthWriteEnable
{
readonly get => ((Internal.Id6 >> 59) & 0x1) != 0UL;
set => Internal.Id6 = (Internal.Id6 & 0xF7FFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 59);
}
public bool DepthBoundsTestEnable
{
readonly get => ((Internal.Id6 >> 60) & 0x1) != 0UL;
set => Internal.Id6 = (Internal.Id6 & 0xEFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 60);
}
public bool StencilTestEnable
{
readonly get => ((Internal.Id6 >> 61) & 0x1) != 0UL;
set => Internal.Id6 = (Internal.Id6 & 0xDFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 61);
}
public bool LogicOpEnable
{
readonly get => ((Internal.Id6 >> 62) & 0x1) != 0UL;
set => Internal.Id6 = (Internal.Id6 & 0xBFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 62);
}
public bool HasDepthStencil
{
readonly get => ((Internal.Id6 >> 63) & 0x1) != 0UL;
set => Internal.Id6 = (Internal.Id6 & 0x7FFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 63);
}
public uint PatchControlPoints
{
readonly get => (uint)((Internal.Id7 >> 0) & 0xFFFFFFFF);
set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFF00000000) | ((ulong)value << 0);
}
public uint SamplesCount
{
readonly get => (uint)((Internal.Id7 >> 32) & 0xFFFFFFFF);
set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFF) | ((ulong)value << 32);
}
public bool AlphaToCoverageEnable public bool AlphaToCoverageEnable
{ {
readonly get => ((Internal.Id8 >> 0) & 0x1) != 0UL; readonly get => ((Internal.Id3 >> 0) & 0x1) != 0UL;
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFFE) | ((value ? 1UL : 0UL) << 0); set => Internal.Id3 = (Internal.Id3 & 0xFFFFFFFFFFFFFFFE) | ((value ? 1UL : 0UL) << 0);
} }
public bool AlphaToOneEnable public bool AlphaToOneEnable
{ {
readonly get => ((Internal.Id8 >> 1) & 0x1) != 0UL; readonly get => ((Internal.Id3 >> 1) & 0x1) != 0UL;
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFFD) | ((value ? 1UL : 0UL) << 1); set => Internal.Id3 = (Internal.Id3 & 0xFFFFFFFFFFFFFFFD) | ((value ? 1UL : 0UL) << 1);
} }
public bool AdvancedBlendSrcPreMultiplied public bool AdvancedBlendSrcPreMultiplied
{ {
readonly get => ((Internal.Id8 >> 2) & 0x1) != 0UL; readonly get => ((Internal.Id3 >> 2) & 0x1) != 0UL;
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFFB) | ((value ? 1UL : 0UL) << 2); set => Internal.Id3 = (Internal.Id3 & 0xFFFFFFFFFFFFFFFB) | ((value ? 1UL : 0UL) << 2);
} }
public bool AdvancedBlendDstPreMultiplied public bool AdvancedBlendDstPreMultiplied
{ {
readonly get => ((Internal.Id8 >> 3) & 0x1) != 0UL; readonly get => ((Internal.Id3 >> 3) & 0x1) != 0UL;
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFF7) | ((value ? 1UL : 0UL) << 3); set => Internal.Id3 = (Internal.Id3 & 0xFFFFFFFFFFFFFFF7) | ((value ? 1UL : 0UL) << 3);
} }
public BlendOverlapEXT AdvancedBlendOverlap public BlendOverlapEXT AdvancedBlendOverlap
{ {
readonly get => (BlendOverlapEXT)((Internal.Id8 >> 4) & 0x3); readonly get => (BlendOverlapEXT)((Internal.Id3 >> 4) & 0x3);
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFCF) | ((ulong)value << 4); set => Internal.Id3 = (Internal.Id3 & 0xFFFFFFFFFFFFFFCF) | ((ulong)value << 4);
} }
public bool DepthMode public bool DepthMode
{ {
readonly get => ((Internal.Id8 >> 6) & 0x1) != 0UL; readonly get => ((Internal.Id3 >> 6) & 0x1) != 0UL;
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFBF) | ((value ? 1UL : 0UL) << 6); set => Internal.Id3 = (Internal.Id3 & 0xFFFFFFFFFFFFFFBF) | ((value ? 1UL : 0UL) << 6);
} }
public FeedbackLoopAspects FeedbackLoopAspects public FeedbackLoopAspects FeedbackLoopAspects
{ {
readonly get => (FeedbackLoopAspects)((Internal.Id8 >> 7) & 0x3); readonly get => (FeedbackLoopAspects)((Internal.Id3 >> 7) & 0x3);
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFE7F) | (((ulong)value) << 7); set => Internal.Id3 = (Internal.Id3 & 0xFFFFFFFFFFFFFE7F) | (((ulong)value) << 7);
} }
public bool HasTessellationControlShader; public bool HasTessellationControlShader;
public bool FeedbackLoopDynamicState;
public NativeArray<PipelineShaderStageCreateInfo> Stages; public NativeArray<PipelineShaderStageCreateInfo> Stages;
public PipelineLayout PipelineLayout; public PipelineLayout PipelineLayout;
public SpecData SpecializationData; public SpecData SpecializationData;
private Array32<VertexInputAttributeDescription> _vertexAttributeDescriptions2; private Array32<VertexInputAttributeDescription> _vertexAttributeDescriptions2;
public void Initialize() private bool _supportsExtDynamicState;
private PhysicalDeviceExtendedDynamicState2FeaturesEXT _supportsExtDynamicState2;
private bool _supportsFeedBackLoopDynamicState;
private uint _blendEnables;
public void Initialize(HardwareCapabilities capabilities)
{ {
HasTessellationControlShader = false; HasTessellationControlShader = false;
Stages = new NativeArray<PipelineShaderStageCreateInfo>(Constants.MaxShaderStages); Stages = new NativeArray<PipelineShaderStageCreateInfo>(Constants.MaxShaderStages);
@@ -321,9 +267,54 @@ namespace Ryujinx.Graphics.Vulkan
AdvancedBlendDstPreMultiplied = true; AdvancedBlendDstPreMultiplied = true;
AdvancedBlendOverlap = BlendOverlapEXT.UncorrelatedExt; AdvancedBlendOverlap = BlendOverlapEXT.UncorrelatedExt;
LineWidth = 1f;
SamplesCount = 1;
DepthMode = true; DepthMode = true;
PolygonMode = PolygonMode.Fill;
DepthBoundsTestEnable = false;
_supportsExtDynamicState = capabilities.SupportsExtendedDynamicState;
_supportsExtDynamicState2 = capabilities.SupportsExtendedDynamicState2;
_supportsFeedBackLoopDynamicState = capabilities.SupportsDynamicAttachmentFeedbackLoop;
if (_supportsExtDynamicState)
{
StencilFrontFailOp = 0;
StencilFrontPassOp = 0;
StencilFrontDepthFailOp = 0;
StencilFrontCompareOp = 0;
StencilBackFailOp = 0;
StencilBackPassOp = 0;
StencilBackDepthFailOp = 0;
StencilBackCompareOp = 0;
ViewportsCount = 0;
ScissorsCount = 0;
CullMode = 0;
FrontFace = 0;
DepthTestEnable = false;
DepthWriteEnable = false;
DepthCompareOp = 0;
StencilTestEnable = false;
}
if (_supportsExtDynamicState2.ExtendedDynamicState2)
{
PrimitiveRestartEnable = false;
DepthBiasEnable = false;
RasterizerDiscardEnable = false;
if (_supportsExtDynamicState2.ExtendedDynamicState2LogicOp)
{
LogicOp = 0;
}
if (_supportsExtDynamicState2.ExtendedDynamicState2PatchControlPoints)
{
PatchControlPoints = 0;
}
}
} }
public unsafe Auto<DisposablePipeline> CreateComputePipeline( public unsafe Auto<DisposablePipeline> CreateComputePipeline(
@@ -341,7 +332,6 @@ namespace Ryujinx.Graphics.Vulkan
{ {
SType = StructureType.ComputePipelineCreateInfo, SType = StructureType.ComputePipelineCreateInfo,
Stage = Stages[0], Stage = Stages[0],
BasePipelineIndex = -1,
Layout = PipelineLayout, Layout = PipelineLayout,
}; };
@@ -377,6 +367,74 @@ namespace Ryujinx.Graphics.Vulkan
return pipeline; return pipeline;
} }
private void CheckCapability(VulkanRenderer gd)
{
// Vendors other than NVIDIA have a bug where it enables logical operations even for float formats,
// so we need to force disable them here.
LogicOpEnable = LogicOpEnable && (gd.Vendor == Vendor.Nvidia || Internal.LogicOpsAllowed);
if (!_supportsExtDynamicState)
{
DepthWriteEnable = DepthWriteEnable && DepthTestEnable;
DepthCompareOp = DepthTestEnable ? DepthCompareOp : default;
}
if (!_supportsExtDynamicState2.ExtendedDynamicState2LogicOp)
{
LogicOp = LogicOpEnable ? LogicOp : default;
}
if (!_supportsExtDynamicState2.ExtendedDynamicState2)
{
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;
}
if (_supportsExtDynamicState)
{
Topology = Topology.ConvertToClass();
}
Topology = HasTessellationControlShader ? PrimitiveTopology.PatchList : Topology;
if (gd.IsMoltenVk && Internal.AttachmentIntegerFormatMask != 0)
{
_blendEnables = 0;
// Blend can't be enabled for integer formats, so let's make sure it is disabled.
uint attachmentIntegerFormatMask = Internal.AttachmentIntegerFormatMask;
while (attachmentIntegerFormatMask != 0)
{
int i = BitOperations.TrailingZeroCount(attachmentIntegerFormatMask);
if (Internal.ColorBlendAttachmentState[i].BlendEnable)
{
_blendEnables |= 1u << i;
}
Internal.ColorBlendAttachmentState[i].BlendEnable = false;
attachmentIntegerFormatMask &= ~(1u << i);
}
}
}
public unsafe Auto<DisposablePipeline> CreateGraphicsPipeline( public unsafe Auto<DisposablePipeline> CreateGraphicsPipeline(
VulkanRenderer gd, VulkanRenderer gd,
Device device, Device device,
@@ -385,6 +443,17 @@ namespace Ryujinx.Graphics.Vulkan
RenderPass renderPass, RenderPass renderPass,
bool throwOnError = false) bool throwOnError = false)
{ {
CheckCapability(gd);
// Using patches topology without a tessellation shader is invalid.
// If we find such a case, return null pipeline to skip the draw.
if (Topology == PrimitiveTopology.PatchList && !HasTessellationControlShader)
{
program.AddGraphicsPipeline(ref Internal, null);
return null;
}
if (program.TryGetGraphicsPipeline(ref Internal, out Auto<DisposablePipeline> pipeline)) if (program.TryGetGraphicsPipeline(ref Internal, out Auto<DisposablePipeline> pipeline))
{ {
return pipeline; return pipeline;
@@ -394,7 +463,7 @@ namespace Ryujinx.Graphics.Vulkan
bool isMoltenVk = gd.IsMoltenVk; bool isMoltenVk = gd.IsMoltenVk;
if (isMoltenVk) if (isMoltenVk && !_supportsExtDynamicState)
{ {
UpdateVertexAttributeDescriptions(gd); UpdateVertexAttributeDescriptions(gd);
} }
@@ -408,7 +477,7 @@ namespace Ryujinx.Graphics.Vulkan
{ {
SType = StructureType.PipelineVertexInputStateCreateInfo, SType = StructureType.PipelineVertexInputStateCreateInfo,
VertexAttributeDescriptionCount = VertexAttributeDescriptionsCount, VertexAttributeDescriptionCount = VertexAttributeDescriptionsCount,
PVertexAttributeDescriptions = isMoltenVk ? pVertexAttributeDescriptions2 : pVertexAttributeDescriptions, PVertexAttributeDescriptions = isMoltenVk && !_supportsExtDynamicState ? pVertexAttributeDescriptions2 : pVertexAttributeDescriptions,
VertexBindingDescriptionCount = VertexBindingDescriptionsCount, VertexBindingDescriptionCount = VertexBindingDescriptionsCount,
PVertexBindingDescriptions = pVertexBindingDescriptions, PVertexBindingDescriptions = pVertexBindingDescriptions,
}; };
@@ -444,33 +513,22 @@ namespace Ryujinx.Graphics.Vulkan
PipelineInputAssemblyStateCreateInfo inputAssemblyState = new() PipelineInputAssemblyStateCreateInfo inputAssemblyState = new()
{ {
SType = StructureType.PipelineInputAssemblyStateCreateInfo, SType = StructureType.PipelineInputAssemblyStateCreateInfo,
PrimitiveRestartEnable = primitiveRestartEnable, Topology = Topology,
Topology = HasTessellationControlShader ? PrimitiveTopology.PatchList : Topology,
}; };
PipelineTessellationStateCreateInfo tessellationState = new() PipelineTessellationStateCreateInfo tessellationState;
{
SType = StructureType.PipelineTessellationStateCreateInfo,
PatchControlPoints = PatchControlPoints,
};
PipelineRasterizationStateCreateInfo rasterizationState = new() PipelineRasterizationStateCreateInfo rasterizationState = new()
{ {
SType = StructureType.PipelineRasterizationStateCreateInfo, SType = StructureType.PipelineRasterizationStateCreateInfo,
DepthClampEnable = DepthClampEnable, DepthClampEnable = DepthClampEnable,
RasterizerDiscardEnable = RasterizerDiscardEnable, // When widelines feature is not supported it must be 1.0f, this will be ignored if Line Width dynamic state is supported
PolygonMode = PolygonMode, LineWidth = 1.0f,
LineWidth = LineWidth,
CullMode = CullMode,
FrontFace = FrontFace,
DepthBiasEnable = DepthBiasEnable,
}; };
PipelineViewportStateCreateInfo viewportState = new() PipelineViewportStateCreateInfo viewportState = new()
{ {
SType = StructureType.PipelineViewportStateCreateInfo, SType = StructureType.PipelineViewportStateCreateInfo,
ViewportCount = ViewportsCount,
ScissorCount = ScissorsCount,
}; };
if (gd.Capabilities.SupportsDepthClipControl) if (gd.Capabilities.SupportsDepthClipControl)
@@ -494,6 +552,20 @@ namespace Ryujinx.Graphics.Vulkan
AlphaToOneEnable = AlphaToOneEnable, AlphaToOneEnable = AlphaToOneEnable,
}; };
PipelineDepthStencilStateCreateInfo depthStencilState = new()
{
SType = StructureType.PipelineDepthStencilStateCreateInfo,
DepthBoundsTestEnable = DepthBoundsTestEnable,
};
if (!_supportsExtDynamicState)
{
rasterizationState.CullMode = CullMode;
rasterizationState.FrontFace = FrontFace;
viewportState.ViewportCount = ViewportsCount;
viewportState.ScissorCount = ScissorsCount;
StencilOpState stencilFront = new( StencilOpState stencilFront = new(
StencilFrontFailOp, StencilFrontFailOp,
StencilFrontPassOp, StencilFrontPassOp,
@@ -506,52 +578,44 @@ namespace Ryujinx.Graphics.Vulkan
StencilBackDepthFailOp, StencilBackDepthFailOp,
StencilBackCompareOp); StencilBackCompareOp);
PipelineDepthStencilStateCreateInfo depthStencilState = new() depthStencilState.Front = stencilFront;
depthStencilState.Back = stencilBack;
depthStencilState.StencilTestEnable = StencilTestEnable;
depthStencilState.DepthTestEnable = DepthTestEnable;
depthStencilState.DepthWriteEnable = DepthWriteEnable;
depthStencilState.DepthCompareOp = DepthCompareOp;
}
if (!_supportsExtDynamicState2.ExtendedDynamicState2)
{ {
SType = StructureType.PipelineDepthStencilStateCreateInfo,
DepthTestEnable = DepthTestEnable, inputAssemblyState.PrimitiveRestartEnable = PrimitiveRestartEnable;
DepthWriteEnable = DepthWriteEnable, rasterizationState.DepthBiasEnable = DepthBiasEnable;
DepthCompareOp = DepthCompareOp, rasterizationState.RasterizerDiscardEnable = RasterizerDiscardEnable;
DepthBoundsTestEnable = false, }
StencilTestEnable = StencilTestEnable,
Front = stencilFront, if (!gd.Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2PatchControlPoints)
Back = stencilBack, {
tessellationState = new PipelineTessellationStateCreateInfo
{
SType = StructureType.PipelineTessellationStateCreateInfo,
PatchControlPoints = PatchControlPoints,
}; };
uint blendEnables = 0;
if (gd.IsMoltenVk && Internal.AttachmentIntegerFormatMask != 0)
{
// Blend can't be enabled for integer formats, so let's make sure it is disabled.
uint attachmentIntegerFormatMask = Internal.AttachmentIntegerFormatMask;
while (attachmentIntegerFormatMask != 0)
{
int i = BitOperations.TrailingZeroCount(attachmentIntegerFormatMask);
if (Internal.ColorBlendAttachmentState[i].BlendEnable)
{
blendEnables |= 1u << i;
} }
Internal.ColorBlendAttachmentState[i].BlendEnable = false;
attachmentIntegerFormatMask &= ~(1u << i);
}
}
// 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 = LogicOpEnable && (gd.Vendor == Vendor.Nvidia || Internal.LogicOpsAllowed);
PipelineColorBlendStateCreateInfo colorBlendState = new() PipelineColorBlendStateCreateInfo colorBlendState = new()
{ {
SType = StructureType.PipelineColorBlendStateCreateInfo, SType = StructureType.PipelineColorBlendStateCreateInfo,
LogicOpEnable = logicOpEnable,
LogicOp = LogicOp,
AttachmentCount = ColorBlendAttachmentStateCount, AttachmentCount = ColorBlendAttachmentStateCount,
PAttachments = pColorBlendAttachmentState, PAttachments = pColorBlendAttachmentState,
LogicOpEnable = LogicOpEnable,
}; };
if (!gd.Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2LogicOp)
{
colorBlendState.LogicOp = LogicOp;
}
if (!AdvancedBlendSrcPreMultiplied || if (!AdvancedBlendSrcPreMultiplied ||
!AdvancedBlendDstPreMultiplied || !AdvancedBlendDstPreMultiplied ||
AdvancedBlendOverlap != BlendOverlapEXT.UncorrelatedExt) AdvancedBlendOverlap != BlendOverlapEXT.UncorrelatedExt)
@@ -567,66 +631,99 @@ namespace Ryujinx.Graphics.Vulkan
colorBlendState.PNext = &colorBlendAdvancedState; colorBlendState.PNext = &colorBlendAdvancedState;
} }
bool supportsExtDynamicState = gd.Capabilities.SupportsExtendedDynamicState;
bool supportsFeedbackLoopDynamicState = gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop;
DynamicState* dynamicStates = stackalloc DynamicState[MaxDynamicStatesCount]; DynamicState* dynamicStates = stackalloc DynamicState[MaxDynamicStatesCount];
int dynamicStatesCount = 7; uint dynamicStatesCount = 7;
dynamicStates[0] = DynamicState.Viewport; dynamicStates[0] = DynamicState.Viewport;
dynamicStates[1] = DynamicState.Scissor; dynamicStates[1] = DynamicState.Scissor;
dynamicStates[2] = DynamicState.DepthBias; dynamicStates[2] = DynamicState.StencilCompareMask;
dynamicStates[3] = DynamicState.StencilCompareMask; dynamicStates[3] = DynamicState.StencilWriteMask;
dynamicStates[4] = DynamicState.StencilWriteMask; dynamicStates[4] = DynamicState.StencilReference;
dynamicStates[5] = DynamicState.StencilReference; dynamicStates[5] = DynamicState.BlendConstants;
dynamicStates[6] = DynamicState.BlendConstants; dynamicStates[6] = DynamicState.DepthBias;
if (supportsExtDynamicState && (gd.SupportsMTL31 || !gd.IsMoltenVk)) if (!isMoltenVk)
{
//LineWidth dynamic state is only supported on macOS when using Metal Private API on newer version of MoltenVK
dynamicStates[dynamicStatesCount++] = DynamicState.LineWidth;
}
if (_supportsExtDynamicState)
{
if (gd.SupportsMTL31 || !gd.IsMoltenVk)
{ {
// Requires Metal 3.1 and new MoltenVK, however extended dynamic states extension is not // Requires Metal 3.1 and new MoltenVK, however extended dynamic states extension is not
// available on older versions of MVK, so we can safely check only OS version. // available on older versions of MVK, so we can safely check only OS version.
dynamicStates[dynamicStatesCount++] = DynamicState.VertexInputBindingStrideExt; dynamicStates[dynamicStatesCount++] = DynamicState.VertexInputBindingStrideExt;
} }
dynamicStates[0] = DynamicState.ViewportWithCountExt;
dynamicStates[1] = DynamicState.ScissorWithCountExt;
dynamicStates[dynamicStatesCount++] = DynamicState.CullModeExt;
dynamicStates[dynamicStatesCount++] = DynamicState.FrontFaceExt;
dynamicStates[dynamicStatesCount++] = DynamicState.DepthTestEnableExt;
dynamicStates[dynamicStatesCount++] = DynamicState.DepthWriteEnableExt;
if (supportsFeedbackLoopDynamicState) dynamicStates[dynamicStatesCount++] = DynamicState.DepthCompareOpExt;
{ dynamicStates[dynamicStatesCount++] = DynamicState.StencilTestEnableExt;
dynamicStates[dynamicStatesCount++] = DynamicState.AttachmentFeedbackLoopEnableExt; dynamicStates[dynamicStatesCount++] = DynamicState.StencilOpExt;
dynamicStates[dynamicStatesCount++] = DynamicState.PrimitiveTopologyExt;
} }
PipelineDynamicStateCreateInfo pipelineDynamicStateCreateInfo = new() if (_supportsExtDynamicState2.ExtendedDynamicState2)
{ {
SType = StructureType.PipelineDynamicStateCreateInfo, dynamicStates[dynamicStatesCount++] = DynamicState.DepthBiasEnableExt;
DynamicStateCount = (uint)dynamicStatesCount, dynamicStates[dynamicStatesCount++] = DynamicState.RasterizerDiscardEnableExt;
PDynamicStates = dynamicStates, dynamicStates[dynamicStatesCount++] = DynamicState.PrimitiveRestartEnableExt;
};
PipelineCreateFlags flags = 0; if (_supportsExtDynamicState2.ExtendedDynamicState2LogicOp)
{
dynamicStates[dynamicStatesCount++] = DynamicState.LogicOpExt;
}
if (_supportsExtDynamicState2.ExtendedDynamicState2PatchControlPoints)
{
dynamicStates[dynamicStatesCount++] = DynamicState.PatchControlPointsExt;
}
}
if (gd.Capabilities.SupportsAttachmentFeedbackLoop) PipelineCreateFlags pipelineCreateFlags = 0;
if (gd.Capabilities.SupportsAttachmentFeedbackLoop && !_supportsFeedBackLoopDynamicState)
{ {
FeedbackLoopAspects aspects = FeedbackLoopAspects; FeedbackLoopAspects aspects = FeedbackLoopAspects;
if ((aspects & FeedbackLoopAspects.Color) != 0) if ((aspects & FeedbackLoopAspects.Color) != 0)
{ {
flags |= PipelineCreateFlags.CreateColorAttachmentFeedbackLoopBitExt; pipelineCreateFlags |= PipelineCreateFlags.CreateColorAttachmentFeedbackLoopBitExt;
} }
if ((aspects & FeedbackLoopAspects.Depth) != 0) if ((aspects & FeedbackLoopAspects.Depth) != 0)
{ {
flags |= PipelineCreateFlags.CreateDepthStencilAttachmentFeedbackLoopBitExt; pipelineCreateFlags |= PipelineCreateFlags.CreateDepthStencilAttachmentFeedbackLoopBitExt;
} }
} }
if (_supportsFeedBackLoopDynamicState && FeedbackLoopDynamicState)
{
dynamicStates[dynamicStatesCount++] = DynamicState.AttachmentFeedbackLoopEnableExt;
FeedbackLoopDynamicState = false;
}
PipelineDynamicStateCreateInfo pipelineDynamicStateCreateInfo = new()
{
SType = StructureType.PipelineDynamicStateCreateInfo,
DynamicStateCount = dynamicStatesCount,
PDynamicStates = dynamicStates,
};
GraphicsPipelineCreateInfo pipelineCreateInfo = new() GraphicsPipelineCreateInfo pipelineCreateInfo = new()
{ {
SType = StructureType.GraphicsPipelineCreateInfo, SType = StructureType.GraphicsPipelineCreateInfo,
Flags = flags,
StageCount = StagesCount, StageCount = StagesCount,
Flags = pipelineCreateFlags,
PStages = Stages.Pointer, PStages = Stages.Pointer,
PVertexInputState = &vertexInputState, PVertexInputState = &vertexInputState,
PInputAssemblyState = &inputAssemblyState, PInputAssemblyState = &inputAssemblyState,
PTessellationState = &tessellationState,
PViewportState = &viewportState, PViewportState = &viewportState,
PRasterizationState = &rasterizationState, PRasterizationState = &rasterizationState,
PMultisampleState = &multisampleState, PMultisampleState = &multisampleState,
@@ -637,6 +734,11 @@ namespace Ryujinx.Graphics.Vulkan
RenderPass = renderPass, RenderPass = renderPass,
}; };
if (!gd.Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2PatchControlPoints)
{
pipelineCreateInfo.PTessellationState = &tessellationState;
}
Result result = gd.Api.CreateGraphicsPipelines(device, cache, 1, &pipelineCreateInfo, null, &pipelineHandle); Result result = gd.Api.CreateGraphicsPipelines(device, cache, 1, &pipelineCreateInfo, null, &pipelineHandle);
if (throwOnError) if (throwOnError)
@@ -649,21 +751,21 @@ namespace Ryujinx.Graphics.Vulkan
return null; return null;
} }
// Restore previous blend enable values if we changed it.
while (blendEnables != 0)
{
int i = BitOperations.TrailingZeroCount(blendEnables);
Internal.ColorBlendAttachmentState[i].BlendEnable = true;
blendEnables &= ~(1u << i);
}
} }
pipeline = new Auto<DisposablePipeline>(new DisposablePipeline(gd.Api, device, pipelineHandle)); pipeline = new Auto<DisposablePipeline>(new DisposablePipeline(gd.Api, device, pipelineHandle));
program.AddGraphicsPipeline(ref Internal, pipeline); program.AddGraphicsPipeline(ref Internal, pipeline);
// Restore previous blend enable values if we changed it.
while (_blendEnables != 0)
{
int i = BitOperations.TrailingZeroCount(_blendEnables);
Internal.ColorBlendAttachmentState[i].BlendEnable = true;
_blendEnables &= ~(1u << i);
}
return pipeline; return pipeline;
} }
+8 -21
View File
@@ -11,23 +11,17 @@ namespace Ryujinx.Graphics.Vulkan
{ {
public ulong Id0; public ulong Id0;
public ulong Id1; public ulong Id1;
public ulong Id2; public ulong Id2;
public ulong Id3; public ulong Id3;
public ulong Id4; private readonly uint VertexAttributeDescriptionsCount => (byte)((Id0 >> 38) & 0xFF);
public ulong Id5; private readonly uint VertexBindingDescriptionsCount => (byte)((Id0 >> 46) & 0xFF);
public ulong Id6; private readonly uint ColorBlendAttachmentStateCount => (byte)((Id1 >> 8) & 0xFF);
private readonly bool HasDepthStencil => ((Id1 >> 63) & 0x1) != 0UL;
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;
public Array32<VertexInputAttributeDescription> VertexAttributeDescriptions; public Array32<VertexInputAttributeDescription> VertexAttributeDescriptions;
public Array33<VertexInputBindingDescription> VertexBindingDescriptions; public Array32<VertexInputBindingDescription> VertexBindingDescriptions;
public Array8<PipelineColorBlendAttachmentState> ColorBlendAttachmentState; public Array8<PipelineColorBlendAttachmentState> ColorBlendAttachmentState;
public Array9<Format> AttachmentFormats; public Array9<Format> AttachmentFormats;
public uint AttachmentIntegerFormatMask; public uint AttachmentIntegerFormatMask;
@@ -40,9 +34,7 @@ namespace Ryujinx.Graphics.Vulkan
public bool Equals(ref PipelineUid other) public bool Equals(ref PipelineUid other)
{ {
if (!Unsafe.As<ulong, Vector256<byte>>(ref Id0).Equals(Unsafe.As<ulong, Vector256<byte>>(ref other.Id0)) || 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)))
{ {
return false; return false;
} }
@@ -80,12 +72,7 @@ namespace Ryujinx.Graphics.Vulkan
ulong hash64 = Id0 * 23 ^ ulong hash64 = Id0 * 23 ^
Id1 * 23 ^ Id1 * 23 ^
Id2 * 23 ^ Id2 * 23 ^
Id3 * 23 ^ Id3 * 23;
Id4 * 23 ^
Id5 * 23 ^
Id6 * 23 ^
Id7 * 23 ^
Id8 * 23;
ReadOnlySpan<VertexInputAttributeDescription> vertexAttributeDescriptionsSpan = VertexAttributeDescriptions.AsSpan(); ReadOnlySpan<VertexInputAttributeDescription> vertexAttributeDescriptionsSpan = VertexAttributeDescriptions.AsSpan();
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using PrimitiveTopology = Silk.NET.Vulkan.PrimitiveTopology;
namespace Ryujinx.Graphics.Vulkan namespace Ryujinx.Graphics.Vulkan
{ {
@@ -538,7 +539,7 @@ namespace Ryujinx.Graphics.Vulkan
public void CreateBackgroundComputePipeline() public void CreateBackgroundComputePipeline()
{ {
PipelineState pipeline = new(); PipelineState pipeline = new();
pipeline.Initialize(); pipeline.Initialize(_gd.Capabilities);
pipeline.Stages[0] = _shaders[0].GetInfo(); pipeline.Stages[0] = _shaders[0].GetInfo();
pipeline.StagesCount = 1; pipeline.StagesCount = 1;
@@ -71,16 +71,22 @@ namespace Ryujinx.Graphics.Vulkan
_buffer = autoBuffer; _buffer = autoBuffer;
if (!gd.Capabilities.SupportsExtendedDynamicState)
{
state.Internal.VertexBindingDescriptions[DescriptorIndex].Stride = (uint)stride; state.Internal.VertexBindingDescriptions[DescriptorIndex].Stride = (uint)stride;
} }
}
return; return;
} }
autoBuffer = gd.BufferManager.GetBuffer(cbs.CommandBuffer, _handle, false, out int size); autoBuffer = gd.BufferManager.GetBuffer(cbs.CommandBuffer, _handle, false, out int size);
if (!gd.Capabilities.SupportsExtendedDynamicState)
{
// The original stride must be reapplied in case it was rewritten. // The original stride must be reapplied in case it was rewritten.
state.Internal.VertexBindingDescriptions[DescriptorIndex].Stride = (uint)_stride; state.Internal.VertexBindingDescriptions[DescriptorIndex].Stride = (uint)_stride;
}
if (_offset >= size) if (_offset >= size)
{ {
@@ -101,7 +101,7 @@ namespace Ryujinx.Graphics.Vulkan
_strides[i] = strides[i]; _strides[i] = strides[i];
} }
if (_gd.Capabilities.SupportsExtendedDynamicState) if (_gd.Capabilities.SupportsExtendedDynamicState && (_gd.SupportsMTL31 || !_gd.IsMoltenVk))
{ {
_gd.ExtendedDynamicStateApi.CmdBindVertexBuffers2( _gd.ExtendedDynamicStateApi.CmdBindVertexBuffers2(
cbs.CommandBuffer, cbs.CommandBuffer,
@@ -25,6 +25,7 @@ namespace Ryujinx.Graphics.Vulkan
[ [
ExtConditionalRendering.ExtensionName, ExtConditionalRendering.ExtensionName,
ExtExtendedDynamicState.ExtensionName, ExtExtendedDynamicState.ExtensionName,
ExtExtendedDynamicState2.ExtensionName,
ExtTransformFeedback.ExtensionName, ExtTransformFeedback.ExtensionName,
KhrDrawIndirectCount.ExtensionName, KhrDrawIndirectCount.ExtensionName,
KhrPushDescriptor.ExtensionName, KhrPushDescriptor.ExtensionName,
@@ -319,6 +320,17 @@ namespace Ryujinx.Graphics.Vulkan
features2.PNext = &supportedFeaturesCustomBorderColor; features2.PNext = &supportedFeaturesCustomBorderColor;
} }
PhysicalDeviceExtendedDynamicState2FeaturesEXT supportedFeaturesExtExtendedDynamicState2 = new()
{
SType = StructureType.PhysicalDeviceExtendedDynamicState2FeaturesExt,
PNext = features2.PNext,
};
if (physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState2.ExtensionName))
{
features2.PNext = &supportedFeaturesExtExtendedDynamicState2;
}
PhysicalDevicePrimitiveTopologyListRestartFeaturesEXT supportedFeaturesPrimitiveTopologyListRestart = new() PhysicalDevicePrimitiveTopologyListRestartFeaturesEXT supportedFeaturesPrimitiveTopologyListRestart = new()
{ {
SType = StructureType.PhysicalDevicePrimitiveTopologyListRestartFeaturesExt, SType = StructureType.PhysicalDevicePrimitiveTopologyListRestartFeaturesExt,
@@ -443,6 +455,7 @@ namespace Ryujinx.Graphics.Vulkan
TessellationShader = supportedFeatures.TessellationShader, TessellationShader = supportedFeatures.TessellationShader,
VertexPipelineStoresAndAtomics = supportedFeatures.VertexPipelineStoresAndAtomics, VertexPipelineStoresAndAtomics = supportedFeatures.VertexPipelineStoresAndAtomics,
RobustBufferAccess = useRobustBufferAccess, RobustBufferAccess = useRobustBufferAccess,
WideLines = supportedFeatures.WideLines,
SampleRateShading = supportedFeatures.SampleRateShading, SampleRateShading = supportedFeatures.SampleRateShading,
ShaderStorageImageExtendedFormats = supportedFeatures.ShaderStorageImageExtendedFormats, ShaderStorageImageExtendedFormats = supportedFeatures.ShaderStorageImageExtendedFormats,
}; };
@@ -495,6 +508,20 @@ namespace Ryujinx.Graphics.Vulkan
pExtendedFeatures = &featuresExtendedDynamicState; 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;
}
PhysicalDeviceVulkan11Features featuresVk11 = new() PhysicalDeviceVulkan11Features featuresVk11 = new()
{ {
SType = StructureType.PhysicalDeviceVulkan11Features, SType = StructureType.PhysicalDeviceVulkan11Features,
@@ -39,6 +39,7 @@ namespace Ryujinx.Graphics.Vulkan
internal KhrSwapchain SwapchainApi { get; private set; } internal KhrSwapchain SwapchainApi { get; private set; }
internal ExtConditionalRendering ConditionalRenderingApi { get; private set; } internal ExtConditionalRendering ConditionalRenderingApi { get; private set; }
internal ExtExtendedDynamicState ExtendedDynamicStateApi { get; private set; } internal ExtExtendedDynamicState ExtendedDynamicStateApi { get; private set; }
internal ExtExtendedDynamicState2 ExtendedDynamicState2Api { get; private set; }
internal KhrPushDescriptor PushDescriptorApi { get; private set; } internal KhrPushDescriptor PushDescriptorApi { get; private set; }
internal ExtTransformFeedback TransformFeedbackApi { get; private set; } internal ExtTransformFeedback TransformFeedbackApi { get; private set; }
internal KhrDrawIndirectCount DrawIndirectCountApi { get; private set; } internal KhrDrawIndirectCount DrawIndirectCountApi { get; private set; }
@@ -146,6 +147,11 @@ namespace Ryujinx.Graphics.Vulkan
ExtendedDynamicStateApi = extendedDynamicStateApi; ExtendedDynamicStateApi = extendedDynamicStateApi;
} }
if (Api.TryGetDeviceExtension(_instance.Instance, _device, out ExtExtendedDynamicState2 extendedDynamicState2Api))
{
ExtendedDynamicState2Api = extendedDynamicState2Api;
}
if (Api.TryGetDeviceExtension(_instance.Instance, _device, out KhrPushDescriptor pushDescriptorApi)) if (Api.TryGetDeviceExtension(_instance.Instance, _device, out KhrPushDescriptor pushDescriptorApi))
{ {
PushDescriptorApi = pushDescriptorApi; PushDescriptorApi = pushDescriptorApi;
@@ -248,6 +254,11 @@ namespace Ryujinx.Graphics.Vulkan
SType = StructureType.PhysicalDevicePrimitiveTopologyListRestartFeaturesExt, SType = StructureType.PhysicalDevicePrimitiveTopologyListRestartFeaturesExt,
}; };
PhysicalDeviceExtendedDynamicState2FeaturesEXT featuresExtendedDynamicState2 = new()
{
SType = StructureType.PhysicalDeviceExtendedDynamicState2FeaturesExt,
};
PhysicalDeviceRobustness2FeaturesEXT featuresRobustness2 = new() PhysicalDeviceRobustness2FeaturesEXT featuresRobustness2 = new()
{ {
SType = StructureType.PhysicalDeviceRobustness2FeaturesExt, SType = StructureType.PhysicalDeviceRobustness2FeaturesExt,
@@ -288,6 +299,12 @@ namespace Ryujinx.Graphics.Vulkan
features2.PNext = &featuresPrimitiveTopologyListRestart; features2.PNext = &featuresPrimitiveTopologyListRestart;
} }
if (_physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState2.ExtensionName))
{
featuresExtendedDynamicState2.PNext = features2.PNext;
features2.PNext = &featuresExtendedDynamicState2;
}
if (_physicalDevice.IsDeviceExtensionPresent("VK_EXT_robustness2")) if (_physicalDevice.IsDeviceExtensionPresent("VK_EXT_robustness2"))
{ {
featuresRobustness2.PNext = features2.PNext; featuresRobustness2.PNext = features2.PNext;
@@ -428,6 +445,10 @@ namespace Ryujinx.Graphics.Vulkan
properties.Limits.FramebufferDepthSampleCounts & properties.Limits.FramebufferDepthSampleCounts &
properties.Limits.FramebufferStencilSampleCounts; 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( Capabilities = new HardwareCapabilities(
_physicalDevice.IsDeviceExtensionPresent("VK_EXT_index_type_uint8"), _physicalDevice.IsDeviceExtensionPresent("VK_EXT_index_type_uint8"),
supportsCustomBorderColor, supportsCustomBorderColor,
@@ -444,6 +465,8 @@ namespace Ryujinx.Graphics.Vulkan
features2.Features.ShaderStorageImageMultisample, features2.Features.ShaderStorageImageMultisample,
_physicalDevice.IsDeviceExtensionPresent(ExtConditionalRendering.ExtensionName), _physicalDevice.IsDeviceExtensionPresent(ExtConditionalRendering.ExtensionName),
_physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState.ExtensionName), _physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState.ExtensionName),
featuresExtendedDynamicState2,
_physicalDevice.PhysicalDeviceProperties.Limits.MaxTessellationPatchSize,
features2.Features.MultiViewport && !(IsMoltenVk && Vendor == Vendor.Amd), // Workaround for AMD on MoltenVK issue features2.Features.MultiViewport && !(IsMoltenVk && Vendor == Vendor.Amd), // Workaround for AMD on MoltenVK issue
featuresRobustness2.NullDescriptor || IsMoltenVk, featuresRobustness2.NullDescriptor || IsMoltenVk,
supportsPushDescriptors, supportsPushDescriptors,
@@ -459,6 +482,7 @@ namespace Ryujinx.Graphics.Vulkan
_physicalDevice.IsDeviceExtensionPresent("VK_NV_viewport_array2"), _physicalDevice.IsDeviceExtensionPresent("VK_NV_viewport_array2"),
_physicalDevice.IsDeviceExtensionPresent(ExtExternalMemoryHost.ExtensionName), _physicalDevice.IsDeviceExtensionPresent(ExtExternalMemoryHost.ExtensionName),
supportsDepthClipControl && featuresDepthClipControl.DepthClipControl, supportsDepthClipControl && featuresDepthClipControl.DepthClipControl,
_physicalDevice.PhysicalDeviceFeatures.WideLines,
supportsAttachmentFeedbackLoop && featuresAttachmentFeedbackLoop.AttachmentFeedbackLoopLayout && IsFeedbackLoopDevice, supportsAttachmentFeedbackLoop && featuresAttachmentFeedbackLoop.AttachmentFeedbackLoopLayout && IsFeedbackLoopDevice,
supportsDynamicAttachmentFeedbackLoop && featuresDynamicAttachmentFeedbackLoop.AttachmentFeedbackLoopDynamicState && IsFeedbackLoopDevice, supportsDynamicAttachmentFeedbackLoop && featuresDynamicAttachmentFeedbackLoop.AttachmentFeedbackLoopDynamicState && IsFeedbackLoopDevice,
propertiesSubgroup.SubgroupSize, propertiesSubgroup.SubgroupSize,
@@ -798,6 +822,10 @@ namespace Ryujinx.Graphics.Vulkan
supportsViewportSwizzle: false, supportsViewportSwizzle: false,
supportsIndirectParameters: true, supportsIndirectParameters: true,
supportsDepthClipControl: Capabilities.SupportsDepthClipControl, supportsDepthClipControl: Capabilities.SupportsDepthClipControl,
supportsExtendedDynamicState: Capabilities.SupportsExtendedDynamicState,
supportsExtendedDynamicState2: Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2,
supportsLogicOpDynamicState: Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2LogicOp,
supportsPatchControlPointsDynamicState: Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2PatchControlPoints,
uniformBufferSetIndex: PipelineBase.UniformSetIndex, uniformBufferSetIndex: PipelineBase.UniformSetIndex,
storageBufferSetIndex: PipelineBase.StorageSetIndex, storageBufferSetIndex: PipelineBase.StorageSetIndex,
textureSetIndex: PipelineBase.TextureSetIndex, textureSetIndex: PipelineBase.TextureSetIndex,
@@ -166,13 +166,15 @@ namespace Ryujinx.HLE.HOS.Applets.Error
string[] buttons = GetButtonsText(module, description, "DlgBtn"); string[] buttons = GetButtonsText(module, description, "DlgBtn");
bool showDetails = _horizon.Device.UIHandler.DisplayErrorAppletDialog($"Error Code: {module}-{description:0000}", "\n" + message, buttons); (uint Module, uint Description) errorCodeTuple = (module, uint.Parse(description.ToString("0000")));
bool showDetails = _horizon.Device.UIHandler.DisplayErrorAppletDialog($"Error Code: {module}-{description:0000}", "\n" + message, buttons, errorCodeTuple);
if (showDetails) if (showDetails)
{ {
message = GetMessageText(module, description, "FlvMsg"); message = GetMessageText(module, description, "FlvMsg");
buttons = GetButtonsText(module, description, "FlvBtn"); buttons = GetButtonsText(module, description, "FlvBtn");
_horizon.Device.UIHandler.DisplayErrorAppletDialog($"Details: {module}-{description:0000}", "\n" + message, buttons); _horizon.Device.UIHandler.DisplayErrorAppletDialog($"Details: {module}-{description:0000}", "\n" + message, buttons, errorCodeTuple);
} }
} }
@@ -27,9 +27,19 @@ namespace Ryujinx.HLE.HOS.Applets
_normalSession = normalSession; _normalSession = normalSession;
_interactiveSession = interactiveSession; _interactiveSession = interactiveSession;
// TODO(jduncanator): Parse PlayerSelectConfig from input data UserProfile selected = _system.Device.UIHandler.ShowPlayerSelectDialog();
if (selected == null)
{
_normalSession.Push(BuildResponse()); _normalSession.Push(BuildResponse());
}
else if (selected.UserId == new UserId("00000000000000000000000000000080"))
{
_normalSession.Push(BuildGuestResponse());
}
else
{
_normalSession.Push(BuildResponse(selected));
}
AppletStateChanged?.Invoke(this, null); AppletStateChanged?.Invoke(this, null);
_system.ReturnFocus(); _system.ReturnFocus();
@@ -37,16 +47,34 @@ namespace Ryujinx.HLE.HOS.Applets
return ResultCode.Success; return ResultCode.Success;
} }
private byte[] BuildResponse() private byte[] BuildResponse(UserProfile selectedUser)
{ {
UserProfile currentUser = _system.AccountManager.LastOpenedUser;
using MemoryStream stream = MemoryStreamManager.Shared.GetStream(); using MemoryStream stream = MemoryStreamManager.Shared.GetStream();
using BinaryWriter writer = new(stream); using BinaryWriter writer = new(stream);
writer.Write((ulong)PlayerSelectResult.Success); writer.Write((ulong)PlayerSelectResult.Success);
currentUser.UserId.Write(writer); selectedUser.UserId.Write(writer);
return stream.ToArray();
}
private byte[] BuildGuestResponse()
{
using MemoryStream stream = MemoryStreamManager.Shared.GetStream();
using BinaryWriter writer = new(stream);
writer.Write(new byte());
return stream.ToArray();
}
private byte[] BuildResponse()
{
using MemoryStream stream = MemoryStreamManager.Shared.GetStream();
using BinaryWriter writer = new(stream);
writer.Write((ulong)PlayerSelectResult.Failure);
return stream.ToArray(); return stream.ToArray();
} }
@@ -26,12 +26,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
{ {
MemoryArrange.MemoryArrange4GiB or MemoryArrange.MemoryArrange4GiB or
MemoryArrange.MemoryArrange4GiBSystemDev or MemoryArrange.MemoryArrange4GiBSystemDev or
MemoryArrange.MemoryArrange6GiBAppletDev => 3173 * MiB, MemoryArrange.MemoryArrange6GiBAppletDev => 3236 * MiB,
MemoryArrange.MemoryArrange4GiBAppletDev => 2048 * MiB, MemoryArrange.MemoryArrange4GiBAppletDev => 2048 * MiB,
MemoryArrange.MemoryArrange6GiB => 4804 * MiB, MemoryArrange.MemoryArrange6GiB => 4867 * MiB,
MemoryArrange.MemoryArrange8GiB => 6852 * MiB, MemoryArrange.MemoryArrange8GiB => 6915 * MiB,
MemoryArrange.MemoryArrange10GiB => 8900 * MiB, MemoryArrange.MemoryArrange10GiB => 8963 * MiB,
MemoryArrange.MemoryArrange12GiB => 10948 * MiB, MemoryArrange.MemoryArrange12GiB => 11011 * MiB,
_ => throw new ArgumentException($"Invalid memory arrange \"{arrange}\"."), _ => throw new ArgumentException($"Invalid memory arrange \"{arrange}\"."),
}; };
} }
@@ -203,7 +203,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
heapRegion.Size = memConfig switch { heapRegion.Size = memConfig switch {
MemoryConfiguration.MemoryConfiguration8GiB => 0x200000000u, MemoryConfiguration.MemoryConfiguration8GiB => 0x200000000u,
MemoryConfiguration.MemoryConfiguration10GiB when !isHeapLimited => 0x280000000u, MemoryConfiguration.MemoryConfiguration10GiB when !isHeapLimited => 0x280000000u,
MemoryConfiguration.MemoryConfiguration10GiB => 0x200000000u,
MemoryConfiguration.MemoryConfiguration12GiB when !isHeapLimited => 0x300000000u, MemoryConfiguration.MemoryConfiguration12GiB when !isHeapLimited => 0x300000000u,
MemoryConfiguration.MemoryConfiguration12GiB => 0x200000000u,
_ => 0x180000000u _ => 0x180000000u
}; };
stackRegion.Size = 0; stackRegion.Size = 0;
@@ -238,7 +240,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
heapRegion.Size = memConfig switch { heapRegion.Size = memConfig switch {
MemoryConfiguration.MemoryConfiguration8GiB => 0x200000000u, MemoryConfiguration.MemoryConfiguration8GiB => 0x200000000u,
MemoryConfiguration.MemoryConfiguration10GiB when !isHeapLimited => 0x280000000u, MemoryConfiguration.MemoryConfiguration10GiB when !isHeapLimited => 0x280000000u,
MemoryConfiguration.MemoryConfiguration10GiB => 0x200000000u,
MemoryConfiguration.MemoryConfiguration12GiB when !isHeapLimited => 0x300000000u, MemoryConfiguration.MemoryConfiguration12GiB when !isHeapLimited => 0x300000000u,
MemoryConfiguration.MemoryConfiguration12GiB => 0x200000000u,
_ => 0x180000000u _ => 0x180000000u
}; };
stackRegion.Size = 1UL << (addressSpaceWidth - 8); stackRegion.Size = 1UL << (addressSpaceWidth - 8);
@@ -257,7 +261,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
heapRegion.Size = memConfig switch { heapRegion.Size = memConfig switch {
MemoryConfiguration.MemoryConfiguration8GiB => 0x200000000u, MemoryConfiguration.MemoryConfiguration8GiB => 0x200000000u,
MemoryConfiguration.MemoryConfiguration10GiB when !isHeapLimited => 0x280000000u, MemoryConfiguration.MemoryConfiguration10GiB when !isHeapLimited => 0x280000000u,
MemoryConfiguration.MemoryConfiguration10GiB => 0x200000000u,
MemoryConfiguration.MemoryConfiguration12GiB when !isHeapLimited => 0x300000000u, MemoryConfiguration.MemoryConfiguration12GiB when !isHeapLimited => 0x300000000u,
MemoryConfiguration.MemoryConfiguration12GiB => 0x200000000u,
_ => 0x180000000u _ => 0x180000000u
}; };
stackRegion.Size = 0x80000000; stackRegion.Size = 0x80000000;
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

@@ -21,6 +21,21 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
return ResultCode.Success; return ResultCode.Success;
} }
[CommandCmif(3)] // 20.0.0+
// CreateLibraryAppletEx(u32, u32, u64) -> object<nn::am::service::ILibraryAppletAccessor>
public ResultCode CreateLibraryAppletEx(ServiceCtx context)
{
AppletId appletId = (AppletId)context.RequestData.ReadInt32();
_ = context.RequestData.ReadInt32(); // libraryAppletMode
_ = context.RequestData.ReadUInt64(); // threadId
MakeObject(context, new ILibraryAppletAccessor(appletId, context.Device.System));
return ResultCode.Success;
}
[CommandCmif(10)] [CommandCmif(10)]
// CreateStorage(u64) -> object<nn::am::service::IStorage> // CreateStorage(u64) -> object<nn::am::service::IStorage>
public ResultCode CreateStorage(ServiceCtx context) public ResultCode CreateStorage(ServiceCtx context)
@@ -7,6 +7,7 @@ using LibHac.Tools.Fs;
using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils; using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Graphics.Gpu;
using Ryujinx.HLE.Loaders.Executables; using Ryujinx.HLE.Loaders.Executables;
using Ryujinx.HLE.Loaders.Processes.Extensions; using Ryujinx.HLE.Loaders.Processes.Extensions;
using System.Collections.Concurrent; using System.Collections.Concurrent;
@@ -23,7 +24,62 @@ namespace Ryujinx.HLE.Loaders.Processes
private ulong _latestPid; private ulong _latestPid;
public ProcessResult ActiveApplication => _processesByPid[_latestPid]; private readonly object _pidLock = new();
#nullable enable
public ProcessResult? ActiveApplication
{
get
{
lock (_pidLock)
{
// Check if _latestPid is still valid
if (_latestPid == 0)
{
return null;
}
// Verify process still exists in kernel (authoritative source)
if (!_device.System.KernelContext.Processes.TryGetValue(_latestPid, out HOS.Kernel.Process.KProcess? kernelProcess))
{
// Process no longer exists in kernel, clear stale state
Logger.Warning?.Print(LogClass.Loader,
$"ActiveApplication PID {_latestPid} no longer exists in kernel, clearing stale state");
_processesByPid.TryRemove(_latestPid, out _);
_latestPid = 0;
return null;
}
// Verify process still exists in ProcessLoader's dictionary
if (_processesByPid.TryGetValue(_latestPid, out ProcessResult? processResult))
{
// Additional check: verify process state
if (kernelProcess.State == HOS.Kernel.Process.ProcessState.Exited ||
kernelProcess.State == HOS.Kernel.Process.ProcessState.Exiting)
{
Logger.Warning?.Print(LogClass.Loader,
$"ActiveApplication PID {_latestPid} is in state {kernelProcess.State}, clearing");
_processesByPid.TryRemove(_latestPid, out _);
_latestPid = 0;
return null;
}
return processResult;
}
// Fallback: clear stale PID if not in our dictionary
Logger.Warning?.Print(LogClass.Loader,
$"ActiveApplication PID {_latestPid} not in ProcessLoader dictionary, clearing");
_latestPid = 0;
return null;
}
}
}
#nullable disable
public ProcessLoader(Switch device) public ProcessLoader(Switch device)
{ {
@@ -226,7 +282,7 @@ namespace Ryujinx.HLE.Loaders.Processes
} }
// Explicitly null TitleId to disable the shader cache. // Explicitly null TitleId to disable the shader cache.
Graphics.Gpu.GraphicsConfig.TitleId = null; GraphicsConfig.TitleId = null;
_device.Gpu.HostInitalized.Set(); _device.Gpu.HostInitalized.Set();
ProcessResult processResult = ProcessLoaderHelper.LoadNsos(_device, ProcessResult processResult = ProcessLoaderHelper.LoadNsos(_device,
@@ -265,5 +321,37 @@ namespace Ryujinx.HLE.Loaders.Processes
return false; return false;
} }
/// <summary>
/// Clears a specific process from the ProcessLoader's tracking.
/// This should be called when a process exits or is terminated.
/// </summary>
/// <param name="pid">The process ID to clear</param>
public void ClearProcess(ulong pid)
{
lock (_pidLock)
{
if (_processesByPid.TryRemove(pid, out _))
{
if (_latestPid == pid)
{
_latestPid = 0;
}
}
}
}
/// <summary>
/// Clears all processes from the ProcessLoader's tracking.
/// This should be called during system shutdown.
/// </summary>
public void ClearAllProcesses()
{
lock (_pidLock)
{
_processesByPid.Clear();
_latestPid = 0;
}
}
} }
} }
+1
View File
@@ -59,6 +59,7 @@
<EmbeddedResource Include="HOS\Applets\SoftwareKeyboard\Resources\Icon_BtnB.png" /> <EmbeddedResource Include="HOS\Applets\SoftwareKeyboard\Resources\Icon_BtnB.png" />
<EmbeddedResource Include="HOS\Applets\SoftwareKeyboard\Resources\Icon_KeyF6.png" /> <EmbeddedResource Include="HOS\Applets\SoftwareKeyboard\Resources\Icon_KeyF6.png" />
<EmbeddedResource Include="HOS\Services\Account\Acc\DefaultUserImage.jpg" /> <EmbeddedResource Include="HOS\Services\Account\Acc\DefaultUserImage.jpg" />
<EmbeddedResource Include="HOS\Services\Account\Acc\GuestUserImage.jpg" />
</ItemGroup> </ItemGroup>
</Project> </Project>
+1
View File
@@ -208,6 +208,7 @@ namespace Ryujinx.HLE
{ {
if (disposing) if (disposing)
{ {
Processes.ClearAllProcesses();
System.Dispose(); System.Dispose();
AudioDeviceDriver.Dispose(); AudioDeviceDriver.Dispose();
FileSystem.Dispose(); FileSystem.Dispose();
+8 -1
View File
@@ -1,4 +1,5 @@
using Ryujinx.HLE.HOS.Applets; using Ryujinx.HLE.HOS.Applets;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types; using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types;
namespace Ryujinx.HLE.UI namespace Ryujinx.HLE.UI
@@ -48,7 +49,8 @@ namespace Ryujinx.HLE.UI
/// Displays a Message Dialog box specific to Error Applet and blocks until it is closed. /// Displays a Message Dialog box specific to Error Applet and blocks until it is closed.
/// </summary> /// </summary>
/// <returns>False when OK is pressed, True when another button (Details) is pressed.</returns> /// <returns>False when OK is pressed, True when another button (Details) is pressed.</returns>
bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText); // ReSharper disable once UnusedParameter.Global
bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText, (uint Module, uint Description)? errorCode = null);
/// <summary> /// <summary>
/// Creates a handler to process keyboard inputs into text strings. /// Creates a handler to process keyboard inputs into text strings.
@@ -65,5 +67,10 @@ namespace Ryujinx.HLE.UI
/// Takes a screenshot from the current renderer and saves it in the screenshots folder. /// Takes a screenshot from the current renderer and saves it in the screenshots folder.
/// </summary> /// </summary>
void TakeScreenshot(); void TakeScreenshot();
/// <summary>
/// Displays the player select dialog and returns the selected profile.
/// </summary>
UserProfile ShowPlayerSelectDialog();
} }
} }
@@ -15,6 +15,9 @@ namespace Ryujinx.UI.Common.Helper
public static string OverrideBackendThreading { get; private set; } public static string OverrideBackendThreading { get; private set; }
public static string OverrideHideCursor { get; private set; } public static string OverrideHideCursor { get; private set; }
public static string BaseDirPathArg { get; private set; } public static string BaseDirPathArg { get; private set; }
public static string RenderDocCaptureTitleFormat { get; private set; } =
"{EmuVersion}\n{GuestName} {GuestVersion} {GuestTitleId} {GuestArch}";
public static FilePath FirmwareToInstallPathArg { get; set; } public static FilePath FirmwareToInstallPathArg { get; set; }
public static string Profile { get; private set; } public static string Profile { get; private set; }
public static string LaunchPathArg { get; private set; } public static string LaunchPathArg { get; private set; }
@@ -45,6 +48,20 @@ namespace Ryujinx.UI.Common.Helper
BaseDirPathArg = args[++i]; BaseDirPathArg = args[++i];
arguments.Add(arg);
arguments.Add(args[i]);
break;
case "-rdct":
case "--rd-capture-title-format":
if (i + 1 >= args.Length)
{
Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
continue;
}
RenderDocCaptureTitleFormat = args[++i];
arguments.Add(arg); arguments.Add(arg);
arguments.Add(args[i]); arguments.Add(args[i]);
break; break;
@@ -1,3 +1,4 @@
using Gommon;
using Ryujinx.HLE.Loaders.Processes; using Ryujinx.HLE.Loaders.Processes;
using System; using System;
@@ -26,5 +27,23 @@ namespace Ryujinx.UI.Common.Helper
return appTitle; return appTitle;
} }
public static string FormatRenderDocCaptureTitle(ProcessResult activeProcess, string applicationVersion)
{
if (activeProcess == null)
return string.Empty;
string titleNameSection = string.IsNullOrWhiteSpace(activeProcess.Name) ? string.Empty : activeProcess.Name;
string titleVersionSection = string.IsNullOrWhiteSpace(activeProcess.DisplayVersion) ? string.Empty : $"v{activeProcess.DisplayVersion}";
string titleIdSection = $"({activeProcess.ProgramIdText.ToUpper()})";
string titleArchSection = activeProcess.Is64Bit ? "(64-bit)" : "(32-bit)";
return CommandLineState.RenderDocCaptureTitleFormat
.ReplaceIgnoreCase("{EmuVersion}", applicationVersion)
.ReplaceIgnoreCase("{GuestName}", titleNameSection)
.ReplaceIgnoreCase("{GuestVersion}", titleVersionSection)
.ReplaceIgnoreCase("{GuestTitleId}", titleIdSection)
.ReplaceIgnoreCase("{GuestArch}", titleArchSection);
}
} }
} }
+1
View File
@@ -14,6 +14,7 @@ using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Windows; using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Graphics.RenderDocApi;
using Ryujinx.UI.Common.Configuration; using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Common.Helper; using Ryujinx.UI.Common.Helper;
using System; using System;
+5 -1
View File
@@ -934,5 +934,9 @@
"GameListContextMenuExtractDataAocRomFSToolTip": "Extract the RomFS from a selected DLC file", "GameListContextMenuExtractDataAocRomFSToolTip": "Extract the RomFS from a selected DLC file",
"ExtractAocListHeader": "Select a DLC to Extract", "ExtractAocListHeader": "Select a DLC to Extract",
"SettingsTabSystemSkipUserProfilesManager": "Skip Dialog 'Manage User Profiles'", "SettingsTabSystemSkipUserProfilesManager": "Skip Dialog 'Manage User Profiles'",
"SkipUserProfilesTooltip": "This option skips the 'Manage User Profiles' dialog during gameplay, using a pre-selected profile.\n\nProfile switching is found in 'Settings' - 'Manager User Profiles'. Select the desired profile before loading the game." "SkipUserProfilesTooltip": "This option skips the 'Manage User Profiles' dialog during gameplay, using a pre-selected profile.\n\nProfile switching is found in 'Settings' - 'Manager User Profiles'. Select the desired profile before loading the game.",
"MenuBarActions_StartCapture": "Start RenderDoc Frame Capture",
"MenuBarActions_EndCapture": "End RenderDoc Frame Capture",
"MenuBarActions_DiscardCapture": "Discard RenderDoc Frame Capture",
"MenuBarActions_DiscardCapture_ToolTip": "Ends the currently active RenderDoc Frame Capture, immediately discarding its result."
} }
+5 -1
View File
@@ -807,5 +807,9 @@
"MultiplayerModeDisabled": "Deshabilitar", "MultiplayerModeDisabled": "Deshabilitar",
"MultiplayerModeLdnMitm": "ldn_mitm", "MultiplayerModeLdnMitm": "ldn_mitm",
"SettingsTabSystemSkipUserProfilesManager": "Omitir el Diálogo 'Gestionar Perfiles de Usuario'", "SettingsTabSystemSkipUserProfilesManager": "Omitir el Diálogo 'Gestionar Perfiles de Usuario'",
"SkipUserProfilesTooltip": "Esta opción omite el diálogo de 'Gestionar perfiles de usuario' durante el juego, utilizando un perfil preseleccionado.\n\nEl cambio de perfil se encuentra en 'Configuración' - 'Gestionar perfiles de usuario'. Seleccione el perfil deseado antes de cargar el juego." "SkipUserProfilesTooltip": "Esta opción omite el diálogo de 'Gestionar perfiles de usuario' durante el juego, utilizando un perfil preseleccionado.\n\nEl cambio de perfil se encuentra en 'Configuración' - 'Gestionar perfiles de usuario'. Seleccione el perfil deseado antes de cargar el juego.",
"MenuBarActions_StartCapture": "Iniciar una captura de fotograma de RenderDoc",
"MenuBarActions_EndCapture": "Detener la captura de fotograma de RenderDoc",
"MenuBarActions_DiscardCapture": "Descartar la captura de fotograma de RenderDoc",
"MenuBarActions_DiscardCapture_ToolTip": "Finaliza la captura de fotograma de RenderDoc actualmente activa y descarta inmediatamente su resultado."
} }
+5 -1
View File
@@ -828,5 +828,9 @@
"GameListContextMenuExtractDataAocRomFSToolTip": "Extraire les RomFS d'un fichier DLC choisi", "GameListContextMenuExtractDataAocRomFSToolTip": "Extraire les RomFS d'un fichier DLC choisi",
"ExtractAocListHeader": "Choisissez un DLC à extraire", "ExtractAocListHeader": "Choisissez un DLC à extraire",
"SettingsTabSystemSkipUserProfilesManager": "Ignorer la Boîte de Dialogue « Gérer les Profils d'Utilisateurs »", "SettingsTabSystemSkipUserProfilesManager": "Ignorer la Boîte de Dialogue « Gérer les Profils d'Utilisateurs »",
"SkipUserProfilesTooltip": "Cette option permet d'éviter le dialogue du 'Gérer les profils d'utilisateurs' pendant le jeu, en utilisant un profil pré-sélectionné.\n\nLa sélection du profil se trouve dans 'Paramètres' - 'Gérer les profils d'utilisateurs'. Sélectionnez le profil souhaité avant de charger la partie." "SkipUserProfilesTooltip": "Cette option permet d'éviter le dialogue du 'Gérer les profils d'utilisateurs' pendant le jeu, en utilisant un profil pré-sélectionné.\n\nLa sélection du profil se trouve dans 'Paramètres' - 'Gérer les profils d'utilisateurs'. Sélectionnez le profil souhaité avant de charger la partie.",
"MenuBarActions_StartCapture": "Démarrer une capture de trame RenderDoc",
"MenuBarActions_EndCapture": "Arrêter la capture de trame RenderDoc",
"MenuBarActions_DiscardCapture": "Supprimer la capture de trame RenderDoc",
"MenuBarActions_DiscardCapture_ToolTip": "Met fin à la capture de trame RenderDoc en cours, en supprimant immédiatement son résultat."
} }
+8 -1
View File
@@ -10,6 +10,7 @@ using Ryujinx.Graphics.GAL.Multithreading;
using Ryujinx.Graphics.Gpu; using Ryujinx.Graphics.Gpu;
using Ryujinx.Graphics.OpenGL; using Ryujinx.Graphics.OpenGL;
using Ryujinx.HLE.HOS.Applets; using Ryujinx.HLE.HOS.Applets;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types; using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types;
using Ryujinx.HLE.Loaders.Processes; using Ryujinx.HLE.Loaders.Processes;
using Ryujinx.HLE.UI; using Ryujinx.HLE.UI;
@@ -28,6 +29,7 @@ using static SDL.SDL3;
using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing; using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing;
using ScalingFilter = Ryujinx.Common.Configuration.ScalingFilter; using ScalingFilter = Ryujinx.Common.Configuration.ScalingFilter;
using Switch = Ryujinx.HLE.Switch; using Switch = Ryujinx.HLE.Switch;
using UserProfile = Ryujinx.HLE.HOS.Services.Account.Acc.UserProfile;
namespace Ryujinx.Headless namespace Ryujinx.Headless
{ {
@@ -531,7 +533,7 @@ namespace Ryujinx.Headless
Exit(); Exit();
} }
public bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText) public bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText, (uint Module, uint Description)? errorCode = null)
{ {
SDL_MessageBoxButtonData[] buttons = new SDL_MessageBoxButtonData[buttonsText.Length]; SDL_MessageBoxButtonData[] buttons = new SDL_MessageBoxButtonData[buttonsText.Length];
@@ -590,5 +592,10 @@ namespace Ryujinx.Headless
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public UserProfile ShowPlayerSelectDialog()
{
return AccountSaveDataManager.GetLastUsedUser();
}
} }
} }
+1
View File
@@ -8,6 +8,7 @@ using Ryujinx.Common.GraphicsDriver;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Common.SystemInterop; using Ryujinx.Common.SystemInterop;
using Ryujinx.Common.Utilities; using Ryujinx.Common.Utilities;
using Ryujinx.Graphics.RenderDocApi;
using Ryujinx.Graphics.Vulkan.MoltenVK; using Ryujinx.Graphics.Vulkan.MoltenVK;
using Ryujinx.Headless; using Ryujinx.Headless;
using Ryujinx.Modules; using Ryujinx.Modules;
+7
View File
@@ -56,6 +56,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Ryujinx.Graphics.RenderDocApi\Ryujinx.Graphics.RenderDocApi.csproj" />
<ProjectReference Include="..\Ryujinx.Audio.Backends.SDL3\Ryujinx.Audio.Backends.SDL3.csproj" /> <ProjectReference Include="..\Ryujinx.Audio.Backends.SDL3\Ryujinx.Audio.Backends.SDL3.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Vulkan\Ryujinx.Graphics.Vulkan.csproj" /> <ProjectReference Include="..\Ryujinx.Graphics.Vulkan\Ryujinx.Graphics.Vulkan.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.OpenGL\Ryujinx.Graphics.OpenGL.csproj" /> <ProjectReference Include="..\Ryujinx.Graphics.OpenGL\Ryujinx.Graphics.OpenGL.csproj" />
@@ -168,4 +169,10 @@
<ItemGroup> <ItemGroup>
<TrimmerRootDescriptor Include="TrimmerRootDescriptor.xml" /> <TrimmerRootDescriptor Include="TrimmerRootDescriptor.xml" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Compile Update="UI\Applet\UserSelectorDialog.axaml.cs">
<DependentUpon>UserSelectorDialog.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
</ItemGroup>
</Project> </Project>
+59 -3
View File
@@ -1,17 +1,23 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Threading; using Avalonia.Threading;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using Gommon;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls; using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.Windows; using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common;
using Ryujinx.HLE; using Ryujinx.HLE;
using Ryujinx.HLE.HOS.Applets; using Ryujinx.HLE.HOS.Applets;
using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard; using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types; using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types;
using Ryujinx.HLE.UI; using Ryujinx.HLE.UI;
using Ryujinx.UI.Common.Configuration; using Ryujinx.UI.Common.Configuration;
using System; using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading; using System.Threading;
namespace Ryujinx.Ava.UI.Applet namespace Ryujinx.Ava.UI.Applet
@@ -215,7 +221,7 @@ namespace Ryujinx.Ava.UI.Applet
_parent.ViewModel.AppHost?.Stop(); _parent.ViewModel.AppHost?.Stop();
} }
public bool DisplayErrorAppletDialog(string title, string message, string[] buttons) public bool DisplayErrorAppletDialog(string title, string message, string[] buttons, (uint Module, uint Description)? errorCode = null)
{ {
ManualResetEvent dialogCloseEvent = new(false); ManualResetEvent dialogCloseEvent = new(false);
@@ -256,9 +262,59 @@ namespace Ryujinx.Ava.UI.Applet
return showDetails; return showDetails;
} }
public IDynamicTextInputHandler CreateDynamicTextInputHandler() public IDynamicTextInputHandler CreateDynamicTextInputHandler() => new AvaloniaDynamicTextInputHandler(_parent);
public UserProfile ShowPlayerSelectDialog()
{ {
return new AvaloniaDynamicTextInputHandler(_parent); UserId selected = UserId.Null;
byte[] defaultGuestImage = EmbeddedResources.Read("Ryujinx.HLE/HOS/Services/Account/Acc/GuestUserImage.jpg");
UserProfile guest = new UserProfile(new UserId("00000000000000000000000000000080"), "Guest", defaultGuestImage);
ManualResetEvent dialogCloseEvent = new(false);
Dispatcher.UIThread.InvokeAsync(async () =>
{
ObservableCollection<BaseModel> profiles = [];
NavigationDialogHost nav = new();
_parent.AccountManager.GetAllUsers()
.OrderBy(x => x.Name)
.ForEach(profile => profiles.Add(new Models.UserProfile(profile, nav)));
profiles.Add(new Models.UserProfile(guest, nav));
ProfileSelectorDialogViewModel viewModel = new()
{
Profiles = profiles,
SelectedUserId = _parent.AccountManager.LastOpenedUser.UserId
};
(selected, _) = await ProfileSelectorDialog.ShowInputDialog(viewModel);
dialogCloseEvent.Set();
});
dialogCloseEvent.WaitOne();
UserProfile profile = _parent.AccountManager.LastOpenedUser;
if (selected == guest.UserId)
{
profile = guest;
}
else if (selected == UserId.Null)
{
profile = null;
}
else
{
foreach (UserProfile p in _parent.AccountManager.GetAllUsers())
{
if (p.UserId == selected)
{
profile = p;
break;
}
}
}
return profile;
} }
public void TakeScreenshot() public void TakeScreenshot()
@@ -0,0 +1,121 @@
<UserControl
x:Class="Ryujinx.Ava.UI.Applet.ProfileSelectorDialog"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
d:DesignHeight="450"
MinWidth="500"
d:DesignWidth="800"
mc:Ignorable="d"
Focusable="True"
x:DataType="viewModels:ProfileSelectorDialogViewModel">
<UserControl.Resources>
<helpers:BitmapArrayValueConverter x:Key="ByteImage" />
</UserControl.Resources>
<Design.DataContext>
<viewModels:ProfileSelectorDialogViewModel />
</Design.DataContext>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Border
CornerRadius="5"
BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
BorderThickness="1">
<ListBox
MaxHeight="300"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Background="Transparent"
ItemsSource="{Binding Profiles}"
SelectionChanged="ProfilesList_SelectionChanged">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel
HorizontalAlignment="Left"
VerticalAlignment="Center"
Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Margin" Value="5 5 0 5" />
<Setter Property="CornerRadius" Value="5" />
</Style>
<Style Selector="Rectangle#SelectionIndicator">
<Setter Property="Opacity" Value="0" />
</Style>
</ListBox.Styles>
<ListBox.DataTemplates>
<DataTemplate
DataType="models:UserProfile">
<Grid
PointerEntered="Grid_PointerEntered"
PointerExited="Grid_OnPointerExited">
<Border
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ClipToBounds="True"
CornerRadius="5"
Background="{Binding BackgroundColor}">
<StackPanel
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Image
Width="96"
Height="96"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Source="{Binding Image, Converter={StaticResource ByteImage}}" />
<TextBlock
HorizontalAlignment="Stretch"
MaxWidth="90"
Text="{Binding Name}"
TextAlignment="Center"
TextWrapping="Wrap"
TextTrimming="CharacterEllipsis"
MaxLines="2"
Margin="5" />
</StackPanel>
</Border>
</Grid>
</DataTemplate>
<DataTemplate
DataType="viewModels:BaseModel">
<Panel
Height="118"
Width="96">
<Panel.Styles>
<Style Selector="Panel">
<Setter Property="Background" Value="{DynamicResource ListBoxBackground}" />
</Style>
</Panel.Styles>
</Panel>
</DataTemplate>
</ListBox.DataTemplates>
</ListBox>
</Border>
<StackPanel
Grid.Row="1"
Margin="0 24 0 0"
HorizontalAlignment="Left"
Orientation="Horizontal"
Spacing="10">
</StackPanel>
</Grid>
</UserControl>
@@ -0,0 +1,125 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.UI.Common.Configuration;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using UserProfile = Ryujinx.Ava.UI.Models.UserProfile;
using UserProfileSft = Ryujinx.HLE.HOS.Services.Account.Acc.UserProfile;
namespace Ryujinx.Ava.UI.Applet
{
public partial class ProfileSelectorDialog : UserControl
{
public ProfileSelectorDialogViewModel ViewModel { get; set; }
public ProfileSelectorDialog(ProfileSelectorDialogViewModel viewModel)
{
DataContext = ViewModel = viewModel;
InitializeComponent();
}
private void Grid_PointerEntered(object sender, PointerEventArgs e)
{
if (sender is Grid { DataContext: UserProfile profile })
{
profile.IsPointerOver = true;
}
}
private void Grid_OnPointerExited(object sender, PointerEventArgs e)
{
if (sender is Grid { DataContext: UserProfile profile })
{
profile.IsPointerOver = false;
}
}
private void ProfilesList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (sender is ListBox listBox)
{
int selectedIndex = listBox.SelectedIndex;
if (selectedIndex >= 0 && selectedIndex < ViewModel.Profiles.Count)
{
if (ViewModel.Profiles[selectedIndex] is UserProfile userProfile)
{
ViewModel.SelectedUserId = userProfile.UserId;
Logger.Info?.Print(LogClass.UI, $"Selected: {userProfile.UserId}", "ProfileSelector");
ObservableCollection<BaseModel> newProfiles = [];
foreach (BaseModel item in ViewModel.Profiles)
{
if (item is UserProfile originalItem)
{
UserProfileSft profile = new(originalItem.UserId, originalItem.Name, originalItem.Image);
if (profile.UserId == ViewModel.SelectedUserId)
{
profile.AccountState = AccountState.Open;
}
newProfiles.Add(new UserProfile(profile, new NavigationDialogHost()));
}
}
ViewModel.Profiles = newProfiles;
}
}
}
}
public static async Task<(UserId Id, bool Result)> ShowInputDialog(ProfileSelectorDialogViewModel viewModel)
{
if (ConfigurationState.Instance.System.SkipUserProfilesManager)
{
UserId defaultId = viewModel.SelectedUserId;
return (defaultId, true);
}
FAContentDialog contentDialog = new()
{
Title = LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle],
PrimaryButtonText = LocaleManager.Instance[LocaleKeys.Continue],
SecondaryButtonText = string.Empty,
CloseButtonText = LocaleManager.Instance[LocaleKeys.Cancel],
Content = new ProfileSelectorDialog(viewModel),
Padding = new Thickness(0)
};
UserId result = UserId.Null;
bool input = false;
contentDialog.Closed += Handler;
await ContentDialogHelper.ShowAsync(contentDialog);
return (result, input);
void Handler(FAContentDialog sender, FAContentDialogClosedEventArgs eventArgs)
{
if (eventArgs.Result == FAContentDialogResult.Primary)
{
result = viewModel.SelectedUserId;
input = true;
}
else
{
result = UserId.Null;
input = false;
}
}
}
}
}
+58 -2
View File
@@ -4,6 +4,9 @@ using Avalonia.Platform;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.UI.Common.Configuration; using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Common.Helper; using Ryujinx.UI.Common.Helper;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.RenderDocApi;
using Ryujinx.HLE;
using SPB.Graphics; using SPB.Graphics;
using SPB.Platform; using SPB.Platform;
using SPB.Platform.GLX; using SPB.Platform.GLX;
@@ -30,6 +33,7 @@ namespace Ryujinx.Ava.UI.Renderer
protected nint MetalLayer { get; set; } protected nint MetalLayer { get; set; }
public delegate void UpdateBoundsCallbackDelegate(Rect rect); public delegate void UpdateBoundsCallbackDelegate(Rect rect);
private UpdateBoundsCallbackDelegate _updateBoundsCallback; private UpdateBoundsCallbackDelegate _updateBoundsCallback;
public event EventHandler<nint> WindowCreated; public event EventHandler<nint> WindowCreated;
@@ -46,6 +50,55 @@ namespace Ryujinx.Ava.UI.Renderer
protected virtual void OnWindowDestroyed() { } protected virtual void OnWindowDestroyed() { }
public bool ToggleRenderDocCapture(Switch device)
{
if (!RenderDoc.IsAvailable) return false;
if (RenderDoc.IsFrameCapturing)
{
if (EndRenderDocCapture())
{
Logger.Info?.Print(LogClass.Application, "Ended RenderDoc capture.");
return true;
}
}
else if (StartRenderDocCapture(device))
{
Logger.Info?.Print(LogClass.Application, "Starting RenderDoc capture.");
return true;
}
return false;
}
public bool StartRenderDocCapture(Switch device)
{
if (!RenderDoc.IsAvailable) return false;
if (RenderDoc.IsFrameCapturing) return false;
RenderDoc.StartFrameCapture(nint.Zero, WindowHandle);
RenderDoc.SetCaptureTitle(TitleHelper.FormatRenderDocCaptureTitle(device.Processes.ActiveApplication, Program.Version));
return true;
}
public bool EndRenderDocCapture()
{
if (!RenderDoc.IsAvailable) return false;
if (!RenderDoc.IsFrameCapturing) return false;
return RenderDoc.IsFrameCapturing && RenderDoc.EndFrameCapture(nint.Zero, WindowHandle);
}
public bool DiscardRenderDocCapture()
{
if (!RenderDoc.IsAvailable) return false;
if (!RenderDoc.IsFrameCapturing) return false;
return RenderDoc.IsFrameCapturing && RenderDoc.DiscardFrameCapture(nint.Zero, WindowHandle);
}
protected virtual void OnWindowDestroying() protected virtual void OnWindowDestroying()
{ {
WindowHandle = nint.Zero; WindowHandle = nint.Zero;
@@ -124,7 +177,9 @@ namespace Ryujinx.Ava.UI.Renderer
} }
else else
{ {
X11Window = PlatformHelper.CreateOpenGLWindow(new FramebufferFormat(new ColorFormat(8, 8, 8, 0), 16, 0, ColorFormat.Zero, 0, 2, false), 0, 0, 100, 100) as GLXWindow; X11Window = PlatformHelper.CreateOpenGLWindow(
new FramebufferFormat(new ColorFormat(8, 8, 8, 0), 16, 0, ColorFormat.Zero, 0, 2, false), 0, 0, 100,
100) as GLXWindow;
} }
if (X11Window != null) if (X11Window != null)
@@ -164,7 +219,8 @@ namespace Ryujinx.Ava.UI.Renderer
RegisterClassEx(ref wndClassEx); RegisterClassEx(ref wndClassEx);
WindowHandle = CreateWindowEx(0, _className, "NativeWindow", WindowStyles.WsChild, 0, 0, 640, 480, control.Handle, nint.Zero, nint.Zero, nint.Zero); WindowHandle = CreateWindowEx(0, _className, "NativeWindow", WindowStyles.WsChild, 0, 0, 640, 480,
control.Handle, nint.Zero, nint.Zero, nint.Zero);
SetWindowLongPtrW(control.Handle, GWLP_WNDPROC, wndClassEx.lpfnWndProc); SetWindowLongPtrW(control.Handle, GWLP_WNDPROC, wndClassEx.lpfnWndProc);
@@ -7,6 +7,7 @@ using Avalonia.Platform.Storage;
using Avalonia.Threading; using Avalonia.Threading;
using System.Runtime.Versioning; using System.Runtime.Versioning;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using DynamicData; using DynamicData;
using DynamicData.Binding; using DynamicData.Binding;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
@@ -25,6 +26,7 @@ using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities; using Ryujinx.Common.Utilities;
using Ryujinx.Cpu; using Ryujinx.Cpu;
using Ryujinx.Graphics.RenderDocApi;
using Ryujinx.HLE; using Ryujinx.HLE;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS; using Ryujinx.HLE.HOS;
@@ -1833,6 +1835,31 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
public void ReloadRenderDocApi()
{
RenderDoc.ReloadApi(ignoreAlreadyLoaded: true);
OnPropertyChanged(nameof(ShowStartCaptureButton));
OnPropertyChanged(nameof(ShowEndCaptureButton));
OnPropertyChanged(nameof(RenderDocIsAvailable));
if (RenderDoc.IsAvailable)
RenderDocIsCapturing = RenderDoc.IsFrameCapturing;
NotificationHelper.ShowInformation(
"RenderDoc API reloaded",
RenderDoc.IsAvailable ? "RenderDoc is now available." : "RenderDoc is no longer available."
);
}
public void ToggleCapture()
{
if (ShowLoadProgress) return;
AppHost.RendererHost.EmbeddedWindow.ToggleRenderDocCapture(AppHost.Device);
RenderDocIsCapturing = RenderDoc.IsFrameCapturing;
}
public void ToggleFullscreen() public void ToggleFullscreen()
{ {
if (Environment.TickCount64 - LastFullscreenToggle < HotKeyPressDelayMs) if (Environment.TickCount64 - LastFullscreenToggle < HotKeyPressDelayMs)
@@ -2112,5 +2139,25 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
#endregion #endregion
#region Context Menu commands
public bool ShowStartCaptureButton => !RenderDocIsCapturing && RenderDoc.IsAvailable;
public bool ShowEndCaptureButton => RenderDocIsCapturing && RenderDoc.IsAvailable;
public static bool RenderDocIsAvailable => RenderDoc.IsAvailable;
public bool RenderDocIsCapturing
{
get;
set
{
field = value;
OnPropertyChanged();
OnPropertyChanged(nameof(ShowStartCaptureButton));
OnPropertyChanged(nameof(ShowEndCaptureButton));
}
}
#endregion
} }
} }
@@ -0,0 +1,14 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using System.Collections.ObjectModel;
namespace Ryujinx.Ava.UI.ViewModels
{
public partial class ProfileSelectorDialogViewModel : BaseModel
{
[ObservableProperty] private UserId _selectedUserId;
[ObservableProperty] private ObservableCollection<BaseModel> _profiles = [];
}
}
@@ -227,6 +227,26 @@
Click="OpenCheatManagerForCurrentApp" Click="OpenCheatManagerForCurrentApp"
Header="{locale:Locale GameListContextMenuManageCheat}" Header="{locale:Locale GameListContextMenuManageCheat}"
IsEnabled="{Binding IsGameRunning}" /> IsEnabled="{Binding IsGameRunning}" />
<Separator IsVisible="{Binding RenderDocIsAvailable}" />
<MenuItem
Click="StartRenderDocCapture_Click"
IsVisible="{Binding ShowStartCaptureButton}"
CommandParameter="{Binding}"
Header="{locale:Locale MenuBarActions_StartCapture}"
IsEnabled="{Binding IsGameRunning}" />
<MenuItem
Click="EndRenderDocCapture_Click"
IsVisible="{Binding ShowEndCaptureButton}"
CommandParameter="{Binding}"
Header="{locale:Locale MenuBarActions_EndCapture}"
IsEnabled="{Binding IsGameRunning}" />
<MenuItem
Click="DiscardRenderDocCapture_Click"
IsVisible="{Binding ShowEndCaptureButton}"
CommandParameter="{Binding}"
Header="{locale:Locale MenuBarActions_DiscardCapture}"
ToolTip.Tip="{locale:Locale MenuBarActions_DiscardCapture_ToolTip}"
IsEnabled="{Binding IsGameRunning}" />
</MenuItem> </MenuItem>
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarTools}"> <MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarTools}">
<MenuItem Header="{locale:Locale MenuBarToolsInstallKeys}" IsEnabled="{Binding EnableNonGameRunningControls}"> <MenuItem Header="{locale:Locale MenuBarToolsInstallKeys}" IsEnabled="{Binding EnableNonGameRunningControls}">
@@ -9,7 +9,9 @@ using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.Windows; using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities; using Ryujinx.Common.Utilities;
using Ryujinx.Graphics.RenderDocApi;
using Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption; using Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption;
using Ryujinx.Modules; using Ryujinx.Modules;
using Ryujinx.UI.App.Common; using Ryujinx.UI.App.Common;
@@ -266,6 +268,52 @@ namespace Ryujinx.Ava.UI.Views.Main
} }
} }
public void StartRenderDocCapture_Click(object sender, RoutedEventArgs args)
{
MainWindowViewModel viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (!RenderDoc.IsFrameCapturing && RenderDoc.IsAvailable && viewModel is not { ShowLoadProgress: true })
{
if (viewModel != null && viewModel.AppHost.RendererHost
.EmbeddedWindow.StartRenderDocCapture(viewModel.AppHost.Device))
{
Logger.Info?.Print(LogClass.Application, "Starting RenderDoc capture.");
}
}
viewModel?.RenderDocIsCapturing = RenderDoc.IsFrameCapturing;
}
public void EndRenderDocCapture_Click(object sender, RoutedEventArgs args)
{
MainWindowViewModel viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (RenderDoc.IsFrameCapturing && RenderDoc.IsAvailable && viewModel is not { ShowLoadProgress: true })
{
if (viewModel != null && viewModel.AppHost.RendererHost.EmbeddedWindow.EndRenderDocCapture())
{
Logger.Info?.Print(LogClass.Application, "Ended RenderDoc capture.");
}
}
viewModel?.RenderDocIsCapturing = RenderDoc.IsFrameCapturing;
}
public void DiscardRenderDocCapture_Click(object sender, RoutedEventArgs args)
{
MainWindowViewModel viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (RenderDoc.IsFrameCapturing && RenderDoc.IsAvailable && viewModel is not { ShowLoadProgress: true })
{
if (viewModel != null && viewModel.AppHost.RendererHost.EmbeddedWindow.DiscardRenderDocCapture())
{
Logger.Info?.Print(LogClass.Application, "Discarded RenderDoc capture.");
}
}
viewModel?.RenderDocIsCapturing = RenderDoc.IsFrameCapturing;
}
public async void CheckForUpdates(object sender, RoutedEventArgs e) public async void CheckForUpdates(object sender, RoutedEventArgs e)
{ {
if (Updater.CanUpdate(true)) if (Updater.CanUpdate(true))
+2
View File
@@ -43,6 +43,8 @@
<KeyBinding Gesture="Ctrl+B" Command="{Binding OpenBinFile}" /> <KeyBinding Gesture="Ctrl+B" Command="{Binding OpenBinFile}" />
<KeyBinding Gesture="Ctrl+," Command="{Binding OpenSettings}" /> <KeyBinding Gesture="Ctrl+," Command="{Binding OpenSettings}" />
<KeyBinding Gesture="Ctrl+R" Command="{Binding RestartEmulation}" /> <KeyBinding Gesture="Ctrl+R" Command="{Binding RestartEmulation}" />
<KeyBinding Gesture="Ctrl+Shift+R" Command="{Binding ReloadRenderDocApi}" />
<KeyBinding Gesture="Ctrl+Shift+C" Command="{Binding ToggleCapture}" />
</Window.KeyBindings> </Window.KeyBindings>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid.RowDefinitions> <Grid.RowDefinitions>