mirror of
https://git.ryujinx.app/projects/Kenji-NX.git
synced 2026-10-06 07:28:54 +02:00
Compare commits
35
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0678a06284 | ||
|
|
c62976f290 | ||
|
|
25e88be7f1 | ||
|
|
abc03d7325 | ||
|
|
096e054bb3 | ||
|
|
0f5aea82a0 | ||
|
|
1385c60875 | ||
|
|
a0ad6f2246 | ||
|
|
a412999eeb | ||
|
|
87605ebce2 | ||
|
|
b67e8df788 | ||
|
|
892fcd9444 | ||
|
|
70cc617e31 | ||
|
|
9bf15801fd | ||
|
|
e3150d20bb | ||
|
|
50538d601f | ||
|
|
5d3e392082 | ||
|
|
c57e91cc59 | ||
|
|
0381c7157b | ||
|
|
93b4c53c8a | ||
|
|
ef14467d1f | ||
|
|
e07f333a31 | ||
|
|
fc4c45ef1d | ||
|
|
52c6a1890e | ||
|
|
2288084b53 | ||
|
|
dd519a32b6 | ||
|
|
7be8829b0a | ||
|
|
6225e142fc | ||
|
|
9e3c03a606 | ||
|
|
8e9bcb2a8e | ||
|
|
7101f52c01 | ||
|
|
e80fc00462 | ||
|
|
33cbd29c23 | ||
|
|
cfc7c6039f | ||
|
|
ac6db0fe76 |
+12
-12
@@ -3,25 +3,25 @@
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Avalonia" Version="12.1.1" />
|
||||
<PackageVersion Include="Avalonia" Version="12.1.2" />
|
||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="12.1.2" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="12.1.1" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="12.1.2" />
|
||||
<PackageVersion Include="AvaloniaUI.DiagnosticsSupport" Version="2.2.3" />
|
||||
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="12.1.1" />
|
||||
<PackageVersion Include="Svg.Controls.Avalonia" Version="12.0.0.13" />
|
||||
<PackageVersion Include="Svg.Controls.Skia.Avalonia" Version="12.0.0.13" />
|
||||
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="12.1.2" />
|
||||
<PackageVersion Include="Svg.Controls.Avalonia" Version="12.0.0.17" />
|
||||
<PackageVersion Include="Svg.Controls.Skia.Avalonia" Version="12.0.0.17" />
|
||||
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
||||
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.2" />
|
||||
<PackageVersion Include="Concentus" Version="2.2.2" />
|
||||
<PackageVersion Include="DiscordRichPresence" Version="1.6.1.70" />
|
||||
<PackageVersion Include="DynamicData" Version="9.4.33" />
|
||||
<PackageVersion Include="FluentAvaloniaUI" Version="3.0.2" />
|
||||
<PackageVersion Include="FluentAvaloniaUI" Version="3.1.0" />
|
||||
<PackageVersion Include="Gommon" Version="2.8.1.2" />
|
||||
<PackageVersion Include="Humanizer" Version="3.0.10" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" />
|
||||
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.21.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.8.1" />
|
||||
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.22.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.10.0" />
|
||||
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
|
||||
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
||||
<PackageVersion Include="NetCoreServer" Version="8.0.7" />
|
||||
@@ -36,9 +36,9 @@
|
||||
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.Linux" Version="6.1.4-build6" />
|
||||
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.macOS" Version="5.0.3-build14" />
|
||||
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.Windows" Version="6.1.4-build6" />
|
||||
<PackageVersion Include="Ryujinx.Graphics.Vulkan.MoltenVK" Version="1.4.2-ryujinx.6" />
|
||||
<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.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="SharpZipLib" Version="1.4.2" />
|
||||
<PackageVersion Include="Silk.NET.Shaderc" Version="2.23.0" />
|
||||
@@ -48,7 +48,7 @@
|
||||
<PackageVersion Include="SkiaSharp" Version="3.119.4" />
|
||||
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="3.119.4" />
|
||||
<PackageVersion Include="SPB" Version="0.0.4-build32" />
|
||||
<PackageVersion Include="System.IO.Hashing" Version="10.0.10" />
|
||||
<PackageVersion Include="System.IO.Hashing" Version="10.0.12" />
|
||||
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.1.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -21,6 +21,7 @@
|
||||
<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.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.Texture/Ryujinx.Graphics.Texture.csproj" />
|
||||
<Project Path="src/Ryujinx.Graphics.Vic/Ryujinx.Graphics.Vic.csproj" />
|
||||
|
||||
@@ -32,14 +32,11 @@ namespace ARMeilleure.Translation.PTC
|
||||
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
||||
private const string InnerHeaderMagicString = "PTCihd\0\0";
|
||||
|
||||
private const uint InternalVersion = 7020; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
private const uint InternalVersion = 7031; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
|
||||
private const string ActualDir = "0";
|
||||
private const string BackupDir = "1";
|
||||
|
||||
private const string TitleIdTextDefault = "0000000000000000";
|
||||
private const string DisplayVersionDefault = "0";
|
||||
|
||||
public static readonly Symbol PageTableSymbol = new(SymbolType.Special, 1);
|
||||
public static readonly Symbol CountTableSymbol = new(SymbolType.Special, 2);
|
||||
public static readonly Symbol DispatchStubSymbol = new(SymbolType.Special, 3);
|
||||
@@ -67,8 +64,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
public string TitleIdText { get; private set; }
|
||||
public string DisplayVersion { get; private set; }
|
||||
public PtcCacheInfo CacheInfo { get; private set; }
|
||||
|
||||
private MemoryManagerType _memoryMode;
|
||||
|
||||
@@ -97,8 +93,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
|
||||
_disposed = false;
|
||||
|
||||
TitleIdText = TitleIdTextDefault;
|
||||
DisplayVersion = DisplayVersionDefault;
|
||||
CacheInfo = new PtcCacheInfo(0, null, null, 0, null, "Unknown", "default");
|
||||
|
||||
CachePathActual = string.Empty;
|
||||
CachePathBackup = string.Empty;
|
||||
@@ -106,20 +101,24 @@ namespace ARMeilleure.Translation.PTC
|
||||
Disable();
|
||||
}
|
||||
|
||||
public void Initialize(string titleIdText, string displayVersion, bool enabled, MemoryManagerType memoryMode, string cacheSelector)
|
||||
public void Initialize(PtcCacheInfo cacheInfo, bool enabled, MemoryManagerType memoryMode)
|
||||
{
|
||||
Wait();
|
||||
|
||||
Profiler.Wait();
|
||||
Profiler.ClearEntries();
|
||||
|
||||
Logger.Info?.Print(LogClass.Ptc, $"Initializing Profiled Persistent Translation Cache v{InternalVersion}\n\t\t (title: {titleIdText}, version: '{displayVersion}', selector: '{cacheSelector}', enabled: {enabled}).");
|
||||
CacheInfo = cacheInfo;
|
||||
|
||||
if (!enabled || string.IsNullOrEmpty(titleIdText) || titleIdText == TitleIdTextDefault)
|
||||
Logger.Info?.Print(
|
||||
LogClass.Ptc,
|
||||
$"Initializing Profiled Persistent Translation Cache v{InternalVersion}\n\t\t " +
|
||||
$"(pid: {cacheInfo.ProcessId}, title: {cacheInfo.TitleIdText}, application: {cacheInfo.ApplicationIdText}, " +
|
||||
$"programIndex: {cacheInfo.ProgramIndex}, version: '{cacheInfo.DisplayVersion}', kind: {cacheInfo.ProcessKind}, " +
|
||||
$"selector: '{cacheInfo.CacheSelector}', key: '{cacheInfo.CacheKey}', enabled: {enabled}).");
|
||||
|
||||
if (!enabled || cacheInfo.TitleIdText == PtcCacheInfo.TitleIdTextDefault)
|
||||
{
|
||||
TitleIdText = TitleIdTextDefault;
|
||||
DisplayVersion = DisplayVersionDefault;
|
||||
|
||||
CachePathActual = string.Empty;
|
||||
CachePathBackup = string.Empty;
|
||||
|
||||
@@ -128,12 +127,10 @@ namespace ARMeilleure.Translation.PTC
|
||||
return;
|
||||
}
|
||||
|
||||
TitleIdText = titleIdText;
|
||||
DisplayVersion = !string.IsNullOrEmpty(displayVersion) ? displayVersion : DisplayVersionDefault;
|
||||
_memoryMode = memoryMode;
|
||||
|
||||
string workPathActual = Path.Combine(AppDataManager.GamesDirPath, TitleIdText, "cache", "cpu", ActualDir);
|
||||
string workPathBackup = Path.Combine(AppDataManager.GamesDirPath, TitleIdText, "cache", "cpu", BackupDir);
|
||||
string workPathActual = Path.Combine(AppDataManager.GamesDirPath, CacheInfo.TitleIdText, "cache", "cpu", ActualDir);
|
||||
string workPathBackup = Path.Combine(AppDataManager.GamesDirPath, CacheInfo.TitleIdText, "cache", "cpu", BackupDir);
|
||||
|
||||
if (!Directory.Exists(workPathActual))
|
||||
{
|
||||
@@ -145,8 +142,14 @@ namespace ARMeilleure.Translation.PTC
|
||||
Directory.CreateDirectory(workPathBackup);
|
||||
}
|
||||
|
||||
CachePathActual = Path.Combine(workPathActual, DisplayVersion) + "-" + cacheSelector;
|
||||
CachePathBackup = Path.Combine(workPathBackup, DisplayVersion) + "-" + cacheSelector;
|
||||
CachePathActual = Path.Combine(workPathActual, CacheInfo.DisplayVersion) + "-" + CacheInfo.CacheSelector;
|
||||
CachePathBackup = Path.Combine(workPathBackup, CacheInfo.DisplayVersion) + "-" + CacheInfo.CacheSelector;
|
||||
|
||||
Logger.Info?.Print(
|
||||
LogClass.Ptc,
|
||||
$"PPTC cache owner selected (pid: {CacheInfo.ProcessId}, title: {CacheInfo.TitleIdText}, application: {CacheInfo.ApplicationIdText}, " +
|
||||
$"version: '{CacheInfo.DisplayVersion}', kind: {CacheInfo.ProcessKind}, selector: '{CacheInfo.CacheSelector}', " +
|
||||
$"key: '{CacheInfo.CacheKey}', path: '{CachePathActual}').");
|
||||
|
||||
PreLoad();
|
||||
Profiler.PreLoad();
|
||||
@@ -370,7 +373,12 @@ namespace ARMeilleure.Translation.PTC
|
||||
|
||||
long fileSize = new FileInfo(fileName).Length;
|
||||
|
||||
Logger.Info?.Print(LogClass.Ptc, $"{(isBackup ? "Loaded Backup Translation Cache" : "Loaded Translation Cache")} (size: {fileSize} bytes, translated functions: {GetEntriesCount()}).");
|
||||
Logger.Info?.Print(
|
||||
LogClass.Ptc,
|
||||
$"{(isBackup ? "Loaded Backup Translation Cache" : "Loaded Translation Cache")} " +
|
||||
$"(pid: {CacheInfo.ProcessId}, title: {CacheInfo.TitleIdText}, version: '{CacheInfo.DisplayVersion}', kind: {CacheInfo.ProcessKind}, " +
|
||||
$"selector: '{CacheInfo.CacheSelector}', key: '{CacheInfo.CacheKey}', path: '{fileName}', " +
|
||||
$"size: {fileSize} bytes, translated functions: {GetEntriesCount()}).");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
namespace ARMeilleure.Translation.PTC
|
||||
{
|
||||
public readonly struct PtcCacheInfo
|
||||
{
|
||||
public const string TitleIdTextDefault = "0000000000000000";
|
||||
public const string ApplicationIdTextDefault = "0000000000000000";
|
||||
public const string DisplayVersionDefault = "0";
|
||||
|
||||
public ulong ProcessId { get; }
|
||||
public string TitleIdText { get; }
|
||||
public string ApplicationIdText { get; }
|
||||
public byte ProgramIndex { get; }
|
||||
public string DisplayVersion { get; }
|
||||
public string ProcessKind { get; }
|
||||
public string CacheSelector { get; }
|
||||
|
||||
public string CacheKey => $"{DisplayVersion}-{CacheSelector}";
|
||||
|
||||
public PtcCacheInfo(
|
||||
ulong processId,
|
||||
string titleIdText,
|
||||
string applicationIdText,
|
||||
byte programIndex,
|
||||
string displayVersion,
|
||||
string processKind,
|
||||
string cacheSelector)
|
||||
{
|
||||
ProcessId = processId;
|
||||
TitleIdText = !string.IsNullOrEmpty(titleIdText) ? titleIdText : TitleIdTextDefault;
|
||||
ApplicationIdText = !string.IsNullOrEmpty(applicationIdText) ? applicationIdText : ApplicationIdTextDefault;
|
||||
ProgramIndex = programIndex;
|
||||
DisplayVersion = !string.IsNullOrEmpty(displayVersion) ? displayVersion : DisplayVersionDefault;
|
||||
ProcessKind = processKind ?? string.Empty;
|
||||
CacheSelector = string.IsNullOrEmpty(cacheSelector) ? "default" : cacheSelector;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
{
|
||||
private const string OuterHeaderMagicString = "Pohd\0\0\0\0";
|
||||
|
||||
private const uint InternalVersion = 6698; //! Not to be incremented manually for each change to the ARMeilleure project.
|
||||
private const uint InternalVersion = 7031; //! Not to be incremented manually for each change to the ARMeilleure project.
|
||||
|
||||
private static readonly uint[] _migrateInternalVersions =
|
||||
[
|
||||
@@ -254,7 +254,12 @@ namespace ARMeilleure.Translation.PTC
|
||||
|
||||
long fileSize = new FileInfo(fileName).Length;
|
||||
|
||||
Logger.Info?.Print(LogClass.Ptc, $"{(isBackup ? "Loaded Backup Profiling Info" : "Loaded Profiling Info")} (size: {fileSize} bytes, profiled functions: {ProfiledFuncs.Count}).");
|
||||
Logger.Info?.Print(
|
||||
LogClass.Ptc,
|
||||
$"{(isBackup ? "Loaded Backup Profiling Info" : "Loaded Profiling Info")} " +
|
||||
$"(pid: {_ptc.CacheInfo.ProcessId}, title: {_ptc.CacheInfo.TitleIdText}, version: '{_ptc.CacheInfo.DisplayVersion}', " +
|
||||
$"kind: {_ptc.CacheInfo.ProcessKind}, selector: '{_ptc.CacheInfo.CacheSelector}', key: '{_ptc.CacheInfo.CacheKey}', " +
|
||||
$"path: '{fileName}', size: {fileSize} bytes, profiled functions: {ProfiledFuncs.Count}).");
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -375,7 +380,11 @@ namespace ARMeilleure.Translation.PTC
|
||||
|
||||
if (fileSize != 0L)
|
||||
{
|
||||
Logger.Info?.Print(LogClass.Ptc, $"Saved Profiling Info (size: {fileSize} bytes, profiled functions: {profiledFuncsCount}).");
|
||||
Logger.Info?.Print(
|
||||
LogClass.Ptc,
|
||||
$"Saved Profiling Info (pid: {_ptc.CacheInfo.ProcessId}, title: {_ptc.CacheInfo.TitleIdText}, version: '{_ptc.CacheInfo.DisplayVersion}', " +
|
||||
$"kind: {_ptc.CacheInfo.ProcessKind}, selector: '{_ptc.CacheInfo.CacheSelector}', key: '{_ptc.CacheInfo.CacheKey}', " +
|
||||
$"path: '{fileName}', size: {fileSize} bytes, profiled functions: {profiledFuncsCount}).");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,9 +61,9 @@ namespace ARMeilleure.Translation
|
||||
FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub;
|
||||
}
|
||||
|
||||
public IPtcLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled, string cacheSelector)
|
||||
public IPtcLoadState LoadDiskCache(PtcCacheInfo cacheInfo, bool enabled)
|
||||
{
|
||||
_ptc.Initialize(titleIdText, displayVersion, enabled, Memory.Type, cacheSelector);
|
||||
_ptc.Initialize(cacheInfo, enabled, Memory.Type);
|
||||
return _ptc;
|
||||
}
|
||||
|
||||
@@ -413,10 +413,6 @@ namespace ARMeilleure.Translation
|
||||
|
||||
context.CurrOp = opCode;
|
||||
|
||||
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
|
||||
Operand dispAddressAddr = context.Add(nativeContext, Const((ulong)NativeContext.GetDispatchAddressOffset()));
|
||||
context.Store(dispAddressAddr, Const(opCode.Address));
|
||||
|
||||
bool isLastOp = opcIndex == block.OpCodes.Count - 1;
|
||||
|
||||
if (isLastOp)
|
||||
|
||||
@@ -16,5 +16,10 @@ namespace Ryujinx.Common.Configuration.Hid.Controller
|
||||
/// Enable Rumble
|
||||
/// </summary>
|
||||
public bool EnableRumble { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enable HD Rumble support
|
||||
/// </summary
|
||||
public bool UseHDRumble { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,8 +126,13 @@ namespace Ryujinx.Common
|
||||
|
||||
public static string[] GetAllAvailableResources(string path, string ext = "")
|
||||
{
|
||||
return ResolveManifestPath(path).Item1.GetManifestResourceNames()
|
||||
.Where(r => r.EndsWith(ext))
|
||||
(Assembly assembly, string resourcePath) = ResolveManifestPath(path);
|
||||
|
||||
string manifestPath = assembly.GetName().Name + "." + resourcePath.Replace('/', '.');
|
||||
|
||||
return assembly.GetManifestResourceNames()
|
||||
.Where(r => r.StartsWith(manifestPath + ".", StringComparison.Ordinal))
|
||||
.Where(r => r.EndsWith(ext, StringComparison.Ordinal))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using ARMeilleure.Memory;
|
||||
using ARMeilleure.Translation.PTC;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Cpu.AppleHv
|
||||
@@ -32,7 +33,7 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
{
|
||||
}
|
||||
|
||||
public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled, string cacheSelector)
|
||||
public IDiskCacheLoadState LoadDiskCache(PtcCacheInfo cacheInfo, bool enabled)
|
||||
{
|
||||
return new DummyDiskCacheLoadState();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using ARMeilleure.Translation.PTC;
|
||||
|
||||
namespace Ryujinx.Cpu
|
||||
{
|
||||
@@ -44,11 +45,10 @@ namespace Ryujinx.Cpu
|
||||
/// <remarks>
|
||||
/// If the execution engine is recompiling guest code, this can be used to load cached code from disk.
|
||||
/// </remarks>
|
||||
/// <param name="titleIdText">Title ID of the application in padded hex form</param>
|
||||
/// <param name="displayVersion">Version of the application</param>
|
||||
/// <param name="cacheInfo">Identity and selector for the process-owned disk cache</param>
|
||||
/// <param name="enabled">True if the cache should be loaded from disk if it exists, false otherwise</param>
|
||||
/// <returns>Disk cache load progress reporter and manager</returns>
|
||||
IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled, string cacheSelector);
|
||||
IDiskCacheLoadState LoadDiskCache(PtcCacheInfo cacheInfo, bool enabled);
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that code has been loaded into guest memory, and that it might be executed in the future.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using ARMeilleure.Common;
|
||||
using ARMeilleure.Memory;
|
||||
using ARMeilleure.Translation;
|
||||
using ARMeilleure.Translation.PTC;
|
||||
using Ryujinx.Cpu.Signal;
|
||||
|
||||
namespace Ryujinx.Cpu.Jit
|
||||
@@ -51,9 +52,9 @@ namespace Ryujinx.Cpu.Jit
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled, string cacheSelector)
|
||||
public IDiskCacheLoadState LoadDiskCache(PtcCacheInfo cacheInfo, bool enabled)
|
||||
{
|
||||
return new JitDiskCacheLoadState(_translator.LoadDiskCache(titleIdText, displayVersion, enabled, cacheSelector));
|
||||
return new JitDiskCacheLoadState(_translator.LoadDiskCache(cacheInfo, enabled));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
|
||||
{
|
||||
static class Decoder<T> where T : IInstEmit
|
||||
{
|
||||
public static MultiBlock DecodeMulti(CpuPreset cpuPreset, IMemoryManager memoryManager, ulong address, bool singleBlock, bool isThumb)
|
||||
public static MultiBlock DecodeMulti(CpuPreset cpuPreset, IMemoryManager memoryManager, ulong address, bool isThumb)
|
||||
{
|
||||
List<Block> blocks = [];
|
||||
List<ulong> branchTargets = [];
|
||||
@@ -24,7 +24,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
|
||||
|
||||
blocks.Add(block);
|
||||
|
||||
if (block.IsTruncated || (singleBlock && block.EndsWithBranch) || !HasNextBlock(block, block.EndAddress - 4UL, branchTargets))
|
||||
if (block.IsTruncated || !HasNextBlock(block, block.EndAddress - 4UL, branchTargets))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -227,7 +227,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
||||
|
||||
public static CompiledFunction Compile(CpuPreset cpuPreset, IMemoryManager memoryManager, ulong address, IAddressTable<ulong> funcTable, nint dispatchStubPtr, bool isThumb)
|
||||
{
|
||||
MultiBlock multiBlock = Decoder<InstEmit>.DecodeMulti(cpuPreset, memoryManager, address, singleBlock: true, isThumb);
|
||||
MultiBlock multiBlock = Decoder<InstEmit>.DecodeMulti(cpuPreset, memoryManager, address, isThumb);
|
||||
|
||||
Dictionary<ulong, int> targets = new();
|
||||
|
||||
|
||||
@@ -305,7 +305,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
|
||||
|
||||
public static CompiledFunction Compile(CpuPreset cpuPreset, IMemoryManager memoryManager, ulong address, IAddressTable<ulong> funcTable, nint dispatchStubPtr)
|
||||
{
|
||||
MultiBlock multiBlock = Decoder.DecodeMulti(cpuPreset, memoryManager, address, singleBlock: true);
|
||||
MultiBlock multiBlock = Decoder.DecodeMulti(cpuPreset, memoryManager, address);
|
||||
|
||||
Dictionary<ulong, int> targets = new();
|
||||
List<PendingBranch> pendingBranches = [];
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
|
||||
private const uint NzcvFlags = 0xfu << 28;
|
||||
private const uint CFlag = 0x1u << 29;
|
||||
|
||||
public static MultiBlock DecodeMulti(CpuPreset cpuPreset, IMemoryManager memoryManager, ulong address, bool singleBlock)
|
||||
public static MultiBlock DecodeMulti(CpuPreset cpuPreset, IMemoryManager memoryManager, ulong address)
|
||||
{
|
||||
List<Block> blocks = [];
|
||||
List<ulong> branchTargets = [];
|
||||
@@ -35,7 +35,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
|
||||
|
||||
blocks.Add(block);
|
||||
|
||||
if (block.IsTruncated || (singleBlock && block.EndsWithBranch) || !HasNextBlock(block, block.EndAddress - 4UL, branchTargets))
|
||||
if (block.IsTruncated || !HasNextBlock(block, block.EndAddress - 4UL, branchTargets))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using ARMeilleure.Common;
|
||||
using ARMeilleure.Memory;
|
||||
using ARMeilleure.Translation.PTC;
|
||||
using Ryujinx.Cpu.Jit;
|
||||
using Ryujinx.Cpu.LightningJit.State;
|
||||
|
||||
@@ -46,7 +47,7 @@ namespace Ryujinx.Cpu.LightningJit
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled, string cacheSelector)
|
||||
public IDiskCacheLoadState LoadDiskCache(PtcCacheInfo cacheInfo, bool enabled)
|
||||
{
|
||||
return new DummyDiskCacheLoadState();
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ namespace Ryujinx.Graphics.GAL
|
||||
public readonly bool SupportsGeometryShader;
|
||||
public readonly bool SupportsGeometryShaderPassthrough;
|
||||
public readonly bool SupportsTransformFeedback;
|
||||
public readonly bool SupportsImageBufferPixelAlignment;
|
||||
public readonly bool SupportsImageLoadFormatted;
|
||||
public readonly bool SupportsLayerVertexTessellation;
|
||||
public readonly bool SupportsMismatchingViewFormat;
|
||||
@@ -44,7 +43,6 @@ namespace Ryujinx.Graphics.GAL
|
||||
public readonly bool SupportsShaderBarrierDivergence;
|
||||
public readonly bool SupportsShaderFloat64;
|
||||
public readonly bool SupportsShaderNonUniformIndexing;
|
||||
public readonly bool SupportsTextureBufferPixelAlignment;
|
||||
public readonly bool SupportsTextureGatherOffsets;
|
||||
public readonly bool SupportsTextureShadowLod;
|
||||
public readonly bool SupportsVertexStoreAndAtomics;
|
||||
@@ -53,6 +51,10 @@ namespace Ryujinx.Graphics.GAL
|
||||
public readonly bool SupportsViewportSwizzle;
|
||||
public readonly bool SupportsIndirectParameters;
|
||||
public readonly bool SupportsDepthClipControl;
|
||||
public readonly bool SupportsExtendedDynamicState;
|
||||
public readonly bool SupportsExtendedDynamicState2;
|
||||
public readonly bool SupportsLogicOpDynamicState;
|
||||
public readonly bool SupportsPatchControlPointsDynamicState;
|
||||
|
||||
public readonly int UniformBufferSetIndex;
|
||||
public readonly int StorageBufferSetIndex;
|
||||
@@ -103,7 +105,6 @@ namespace Ryujinx.Graphics.GAL
|
||||
bool supportsGeometryShader,
|
||||
bool supportsGeometryShaderPassthrough,
|
||||
bool supportsTransformFeedback,
|
||||
bool supportsImageBufferPixelAlignment,
|
||||
bool supportsImageLoadFormatted,
|
||||
bool supportsLayerVertexTessellation,
|
||||
bool supportsMismatchingViewFormat,
|
||||
@@ -115,7 +116,6 @@ namespace Ryujinx.Graphics.GAL
|
||||
bool supportsShaderBarrierDivergence,
|
||||
bool supportsShaderFloat64,
|
||||
bool supportsShaderNonUniformIndexing,
|
||||
bool supportsTextureBufferPixelAlignment,
|
||||
bool supportsTextureGatherOffsets,
|
||||
bool supportsTextureShadowLod,
|
||||
bool supportsVertexStoreAndAtomics,
|
||||
@@ -124,6 +124,10 @@ namespace Ryujinx.Graphics.GAL
|
||||
bool supportsViewportSwizzle,
|
||||
bool supportsIndirectParameters,
|
||||
bool supportsDepthClipControl,
|
||||
bool supportsExtendedDynamicState,
|
||||
bool supportsExtendedDynamicState2,
|
||||
bool supportsLogicOpDynamicState,
|
||||
bool supportsPatchControlPointsDynamicState,
|
||||
int uniformBufferSetIndex,
|
||||
int storageBufferSetIndex,
|
||||
int textureSetIndex,
|
||||
@@ -168,7 +172,6 @@ namespace Ryujinx.Graphics.GAL
|
||||
SupportsGeometryShader = supportsGeometryShader;
|
||||
SupportsGeometryShaderPassthrough = supportsGeometryShaderPassthrough;
|
||||
SupportsTransformFeedback = supportsTransformFeedback;
|
||||
SupportsImageBufferPixelAlignment = supportsImageBufferPixelAlignment;
|
||||
SupportsImageLoadFormatted = supportsImageLoadFormatted;
|
||||
SupportsLayerVertexTessellation = supportsLayerVertexTessellation;
|
||||
SupportsMismatchingViewFormat = supportsMismatchingViewFormat;
|
||||
@@ -180,7 +183,6 @@ namespace Ryujinx.Graphics.GAL
|
||||
SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence;
|
||||
SupportsShaderFloat64 = supportsShaderFloat64;
|
||||
SupportsShaderNonUniformIndexing = supportsShaderNonUniformIndexing;
|
||||
SupportsTextureBufferPixelAlignment = supportsTextureBufferPixelAlignment;
|
||||
SupportsTextureGatherOffsets = supportsTextureGatherOffsets;
|
||||
SupportsTextureShadowLod = supportsTextureShadowLod;
|
||||
SupportsVertexStoreAndAtomics = supportsVertexStoreAndAtomics;
|
||||
@@ -189,6 +191,10 @@ namespace Ryujinx.Graphics.GAL
|
||||
SupportsViewportSwizzle = supportsViewportSwizzle;
|
||||
SupportsIndirectParameters = supportsIndirectParameters;
|
||||
SupportsDepthClipControl = supportsDepthClipControl;
|
||||
SupportsExtendedDynamicState = supportsExtendedDynamicState;
|
||||
SupportsExtendedDynamicState2 = supportsExtendedDynamicState2;
|
||||
SupportsLogicOpDynamicState = supportsLogicOpDynamicState;
|
||||
SupportsPatchControlPointsDynamicState = supportsPatchControlPointsDynamicState;
|
||||
UniformBufferSetIndex = uniformBufferSetIndex;
|
||||
StorageBufferSetIndex = storageBufferSetIndex;
|
||||
TextureSetIndex = textureSetIndex;
|
||||
|
||||
@@ -2,6 +2,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public enum Face
|
||||
{
|
||||
None = 0,
|
||||
Front = 0x404,
|
||||
Back = 0x405,
|
||||
FrontAndBack = 0x408,
|
||||
|
||||
@@ -50,9 +50,9 @@ namespace Ryujinx.Graphics.GAL
|
||||
void SetDepthBias(PolygonModeMask enables, float factor, float units, float clamp);
|
||||
void SetDepthClamp(bool clamp);
|
||||
void SetDepthMode(DepthMode mode);
|
||||
void SetDepthTest(DepthTestDescriptor depthTest);
|
||||
void SetDepthTest(DepthTestDescriptor depthTest, bool signalChange = true);
|
||||
|
||||
void SetFaceCulling(bool enable, Face face);
|
||||
void SetFaceCulling(Face face);
|
||||
|
||||
void SetFrontFace(FrontFace frontFace);
|
||||
|
||||
@@ -75,16 +75,16 @@ namespace Ryujinx.Graphics.GAL
|
||||
|
||||
void SetPrimitiveRestart(bool enable, int index);
|
||||
|
||||
void SetPrimitiveTopology(PrimitiveTopology topology);
|
||||
void SetPrimitiveTopology(PrimitiveTopology topology, bool signalChange = true);
|
||||
|
||||
void SetProgram(IProgram program);
|
||||
void SetProgram(IProgram program, bool signalChange = true);
|
||||
|
||||
void SetRasterizerDiscard(bool discard);
|
||||
|
||||
void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMask);
|
||||
void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMask, bool signalChange = true);
|
||||
void SetRenderTargets(Span<ITexture> colors, ITexture depthStencil);
|
||||
|
||||
void SetScissors(ReadOnlySpan<Rectangle<int>> regions);
|
||||
void SetScissors(ReadOnlySpan<Rectangle<int>> regions, bool signalChange = true);
|
||||
|
||||
void SetStencilTest(StencilTestDescriptor stencilTest);
|
||||
|
||||
@@ -102,7 +102,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
void SetVertexAttribs(ReadOnlySpan<VertexAttribDescriptor> vertexAttribs);
|
||||
void SetVertexBuffers(ReadOnlySpan<VertexBufferDescriptor> vertexBuffers);
|
||||
|
||||
void SetViewports(ReadOnlySpan<Viewport> viewports);
|
||||
void SetViewports(ReadOnlySpan<Viewport> viewports, bool signalChange = true);
|
||||
|
||||
void TextureBarrier();
|
||||
void TextureBarrierTiled();
|
||||
|
||||
@@ -3,18 +3,16 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||
struct SetFaceCullingCommand : IGALCommand, IGALCommand<SetFaceCullingCommand>
|
||||
{
|
||||
public readonly CommandType CommandType => CommandType.SetFaceCulling;
|
||||
private bool _enable;
|
||||
private Face _face;
|
||||
|
||||
public void Set(bool enable, Face face)
|
||||
public void Set(Face face)
|
||||
{
|
||||
_enable = enable;
|
||||
_face = face;
|
||||
}
|
||||
|
||||
public static void Run(ref SetFaceCullingCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||
{
|
||||
renderer.Pipeline.SetFaceCulling(command._enable, command._face);
|
||||
renderer.Pipeline.SetFaceCulling(command._face);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,15 +158,15 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
_renderer.QueueCommand();
|
||||
}
|
||||
|
||||
public unsafe void SetDepthTest(DepthTestDescriptor depthTest)
|
||||
public unsafe void SetDepthTest(DepthTestDescriptor depthTest, bool signalChange = true)
|
||||
{
|
||||
_renderer.New<SetDepthTestCommand>()->Set(depthTest);
|
||||
_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();
|
||||
}
|
||||
|
||||
@@ -242,13 +242,13 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
_renderer.QueueCommand();
|
||||
}
|
||||
|
||||
public unsafe void SetPrimitiveTopology(PrimitiveTopology topology)
|
||||
public unsafe void SetPrimitiveTopology(PrimitiveTopology topology, bool signalChange = true)
|
||||
{
|
||||
_renderer.New<SetPrimitiveTopologyCommand>()->Set(topology);
|
||||
_renderer.QueueCommand();
|
||||
}
|
||||
|
||||
public unsafe void SetProgram(IProgram program)
|
||||
public unsafe void SetProgram(IProgram program, bool signalChange = true)
|
||||
{
|
||||
_renderer.New<SetProgramCommand>()->Set(Ref(program));
|
||||
_renderer.QueueCommand();
|
||||
@@ -260,7 +260,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
_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.QueueCommand();
|
||||
@@ -275,7 +275,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
_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.QueueCommand();
|
||||
@@ -341,7 +341,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
_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.QueueCommand();
|
||||
|
||||
@@ -51,11 +51,12 @@ namespace Ryujinx.Graphics.GAL
|
||||
public StencilTestDescriptor StencilTest;
|
||||
public FrontFace FrontFace;
|
||||
public Face CullMode;
|
||||
public bool CullEnable;
|
||||
|
||||
public PolygonModeMask BiasEnable;
|
||||
|
||||
public float LineWidth;
|
||||
public bool AlphaToCoverageEnable;
|
||||
public bool AlphaToOneEnable;
|
||||
|
||||
// TODO: Polygon mode.
|
||||
public bool DepthClampEnable;
|
||||
public bool RasterizerDiscard;
|
||||
@@ -63,6 +64,9 @@ namespace Ryujinx.Graphics.GAL
|
||||
public bool PrimitiveRestartEnable;
|
||||
public uint PatchControlPoints;
|
||||
|
||||
public float DepthBiasUnits;
|
||||
public float DepthBiasFactor;
|
||||
|
||||
public DepthMode DepthMode;
|
||||
|
||||
public void SetVertexAttribs(ReadOnlySpan<VertexAttribDescriptor> vertexAttribs)
|
||||
|
||||
@@ -16,19 +16,18 @@ namespace Ryujinx.Graphics.GAL
|
||||
|
||||
public static class TargetExtensions
|
||||
{
|
||||
public static bool IsMultisample(this Target target)
|
||||
extension(Target target)
|
||||
{
|
||||
return target is Target.Texture2DMultisample or Target.Texture2DMultisampleArray;
|
||||
}
|
||||
public bool IsMultisample => target is Target.Texture2DMultisample or Target.Texture2DMultisampleArray;
|
||||
|
||||
public static bool HasDepthOrLayers(this Target target)
|
||||
{
|
||||
return target is Target.Texture3D
|
||||
or Target.Texture1DArray
|
||||
or Target.Texture2DArray
|
||||
or Target.Texture2DMultisampleArray
|
||||
or Target.Cubemap
|
||||
or Target.CubemapArray;
|
||||
public bool HasDepthOrLayers =>
|
||||
target is
|
||||
Target.Texture3D or
|
||||
Target.Texture1DArray or
|
||||
Target.Texture2DArray or
|
||||
Target.Texture2DMultisampleArray or
|
||||
Target.Cubemap or
|
||||
Target.CubemapArray;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -466,7 +466,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
engine.UpdateState(ulong.MaxValue & ~(1UL << StateUpdater.ShaderStateIndex));
|
||||
|
||||
_channel.TextureManager.SignalRenderTargetsModifiable();
|
||||
_channel.TextureManager.UpdateRenderTargets();
|
||||
|
||||
int textureId = _state.State.DrawTextureTextureId;
|
||||
@@ -804,7 +803,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
int index = (argument >> 6) & 0xf;
|
||||
int layer = (argument >> 10) & 0x3ff;
|
||||
|
||||
RenderTargetUpdateFlags updateFlags = RenderTargetUpdateFlags.SingleColor | RenderTargetUpdateFlags.ForClear;
|
||||
RenderTargetUpdateFlags updateFlags = RenderTargetUpdateFlags.SingleColor;
|
||||
|
||||
if (layer != 0 || layerCount > 1)
|
||||
{
|
||||
|
||||
@@ -38,11 +38,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// </summary>
|
||||
DiscardClip = 1 << 4,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the render target will be used for a clear operation.
|
||||
/// </summary>
|
||||
ForClear = 1 << 5,
|
||||
|
||||
/// <summary>
|
||||
/// Default update flags for draw.
|
||||
/// </summary>
|
||||
|
||||
@@ -45,7 +45,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
private ProgramPipelineState _pipeline;
|
||||
|
||||
private bool _fsReadsFragCoord;
|
||||
private bool _fsAlwaysDiscards;
|
||||
private bool _vsUsesDrawParameters;
|
||||
private bool _vtgWritesRtLayer;
|
||||
private byte _vsClipDistancesWritten;
|
||||
@@ -492,8 +491,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
Span<RtColorState> rtColorStateSpan = _state.State.RtColorState.AsSpan();
|
||||
|
||||
bool rtModifiable = updateFlags.HasFlag(RenderTargetUpdateFlags.ForClear) || !_fsAlwaysDiscards;
|
||||
|
||||
for (int index = 0; index < Constants.TotalRenderTargets; index++)
|
||||
{
|
||||
int rtIndex = useControl ? rtControl.UnpackPermutationIndex(index) : index;
|
||||
@@ -502,7 +499,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
if (index >= count || !IsRtEnabled(colorState) || (singleColor && index != singleUse))
|
||||
{
|
||||
changedScale |= _channel.TextureManager.SetRenderTargetColor(index, null, rtModifiable);
|
||||
changedScale |= _channel.TextureManager.SetRenderTargetColor(index, null);
|
||||
|
||||
continue;
|
||||
}
|
||||
@@ -521,7 +518,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
samplesInY,
|
||||
sizeHint);
|
||||
|
||||
changedScale |= _channel.TextureManager.SetRenderTargetColor(index, color, rtModifiable);
|
||||
changedScale |= _channel.TextureManager.SetRenderTargetColor(index, color);
|
||||
|
||||
if (color != null)
|
||||
{
|
||||
@@ -575,7 +572,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
}
|
||||
}
|
||||
|
||||
changedScale |= _channel.TextureManager.SetRenderTargetDepthStencil(depthStencil, rtModifiable);
|
||||
changedScale |= _channel.TextureManager.SetRenderTargetDepthStencil(depthStencil);
|
||||
|
||||
if (changedScale)
|
||||
{
|
||||
@@ -777,11 +774,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
float width = scaleX * 2;
|
||||
float height = scaleY * 2;
|
||||
|
||||
if (yNegate)
|
||||
{
|
||||
y += _state.State.ScreenScissorState.Height - MathF.Abs(height);
|
||||
}
|
||||
|
||||
float scale = _channel.TextureManager.RenderTargetScale;
|
||||
if (scale != 1f)
|
||||
{
|
||||
@@ -869,6 +861,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
enables |= (depthBias.FillEnable ? PolygonModeMask.Fill : 0);
|
||||
|
||||
_pipeline.BiasEnable = enables;
|
||||
_pipeline.DepthBiasUnits = units / 2f;
|
||||
_pipeline.DepthBiasFactor = factor;
|
||||
|
||||
_context.Renderer.Pipeline.SetDepthBias(enables, factor, units / 2f, clamp);
|
||||
}
|
||||
|
||||
@@ -1042,7 +1037,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
float width = _state.State.LineWidthSmooth;
|
||||
bool smooth = _state.State.LineSmoothEnable;
|
||||
|
||||
_pipeline.LineWidth = width;
|
||||
_context.Renderer.Pipeline.SetLineParameters(width, smooth);
|
||||
}
|
||||
|
||||
@@ -1224,9 +1218,16 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
YControl yControl = _state.State.YControl;
|
||||
FaceState face = _state.State.FaceState;
|
||||
|
||||
_pipeline.CullEnable = face.CullEnable;
|
||||
_pipeline.CullMode = face.CullFace;
|
||||
_context.Renderer.Pipeline.SetFaceCulling(face.CullEnable, face.CullFace);
|
||||
if (face.CullEnable)
|
||||
{
|
||||
_pipeline.CullMode = face.CullFace;
|
||||
_context.Renderer.Pipeline.SetFaceCulling(face.CullFace);
|
||||
}
|
||||
else
|
||||
{
|
||||
_pipeline.CullMode = Face.None;
|
||||
_context.Renderer.Pipeline.SetFaceCulling(Face.None);
|
||||
}
|
||||
|
||||
UpdateFrontFace(yControl, face.FrontFace);
|
||||
}
|
||||
@@ -1424,6 +1425,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
bool alphaToCoverageEnable = (_state.State.MultisampleControl & 1) != 0;
|
||||
bool alphaToOneEnable = (_state.State.MultisampleControl & 0x10) != 0;
|
||||
|
||||
_pipeline.AlphaToCoverageEnable = alphaToCoverageEnable;
|
||||
_pipeline.AlphaToOneEnable = alphaToOneEnable;
|
||||
_context.Renderer.Pipeline.SetMultisampleState(new MultisampleDescriptor(
|
||||
alphaToCoverageEnable,
|
||||
_state.State.AlphaToCoverageDitherEnable,
|
||||
@@ -1525,38 +1528,20 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
_currentProgramInfo[stageIndex] = info;
|
||||
}
|
||||
|
||||
_fsReadsFragCoord = false;
|
||||
|
||||
ShaderProgramInfo fragmentShaderInfo = gs.Shaders[5]?.Info;
|
||||
|
||||
if (fragmentShaderInfo != null)
|
||||
if (gs.Shaders[5]?.Info.UsesFragCoord == true)
|
||||
{
|
||||
if (fragmentShaderInfo.UsesFragCoord)
|
||||
// Make sure we update the viewport size on the support buffer if it will be consumed on the new shader.
|
||||
|
||||
if (!_fsReadsFragCoord && (_state.State.YControl & YControl.NegateY) != 0)
|
||||
{
|
||||
// Make sure we update the viewport size on the support buffer if it will be consumed on the new shader.
|
||||
|
||||
if (!_fsReadsFragCoord && (_state.State.YControl & YControl.NegateY) != 0)
|
||||
{
|
||||
UpdateSupportBufferViewportSize();
|
||||
}
|
||||
|
||||
_fsReadsFragCoord = true;
|
||||
UpdateSupportBufferViewportSize();
|
||||
}
|
||||
|
||||
if (_fsAlwaysDiscards != fragmentShaderInfo.HasUnconditionalDiscard)
|
||||
{
|
||||
_fsAlwaysDiscards = fragmentShaderInfo.HasUnconditionalDiscard;
|
||||
|
||||
if (!_fsAlwaysDiscards)
|
||||
{
|
||||
_channel.TextureManager.RefreshModifiedTextures();
|
||||
_channel.TextureManager.SignalRenderTargetsModifiable();
|
||||
}
|
||||
}
|
||||
_fsReadsFragCoord = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_fsAlwaysDiscards = false;
|
||||
_fsReadsFragCoord = false;
|
||||
}
|
||||
|
||||
if (gs.VertexAsCompute != null)
|
||||
|
||||
@@ -1117,7 +1117,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <returns>True if data was flushed, false otherwise</returns>
|
||||
public bool FlushModified(bool tracked = true)
|
||||
{
|
||||
return TextureCompatibility.CanTextureFlush(this, _context.Capabilities) && Group.FlushModified(this, tracked);
|
||||
return TextureCompatibility.CanTextureFlush(Info, _context.Capabilities) && Group.FlushModified(this, tracked);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1131,7 +1131,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <param name="tracked">Whether or not the flush triggers write tracking. If it doesn't, the texture will not be blacklisted for scaling either.</param>
|
||||
public void Flush(bool tracked)
|
||||
{
|
||||
if (TextureCompatibility.CanTextureFlush(this, _context.Capabilities))
|
||||
if (TextureCompatibility.CanTextureFlush(Info, _context.Capabilities))
|
||||
{
|
||||
FlushTextureDataToGuest(tracked);
|
||||
}
|
||||
@@ -1336,7 +1336,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewTargetCompatible(Info, info, ref caps));
|
||||
|
||||
bool bothMs = Info.Target.IsMultisample() && info.Target.IsMultisample();
|
||||
bool bothMs = Info.Target.IsMultisample && info.Target.IsMultisample;
|
||||
if (bothMs && (Info.SamplesInX != info.SamplesInX || Info.SamplesInY != info.SamplesInY))
|
||||
{
|
||||
result = TextureViewCompatibility.Incompatible;
|
||||
@@ -1572,6 +1572,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <param name="bound">True if the texture has been bound, false if it has been unbound</param>
|
||||
public void SignalModifying(bool bound)
|
||||
{
|
||||
if (bound)
|
||||
{
|
||||
_scaledSetScore = Math.Max(0, _scaledSetScore - 1);
|
||||
}
|
||||
|
||||
if (_modifiedStale || Group.HasCopyDependencies || Group.HasFlushBuffer)
|
||||
{
|
||||
_modifiedStale = false;
|
||||
@@ -1579,18 +1584,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
|
||||
_physicalMemory.TextureCache.Lift(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signals that a render target texture has been either bound or unbound.
|
||||
/// </summary>
|
||||
/// <param name="bound">True if the texture has been bound, false if it has been unbound</param>
|
||||
public void SignalBindingChange(bool bound)
|
||||
{
|
||||
if (bound)
|
||||
{
|
||||
_scaledSetScore = Math.Max(0, _scaledSetScore - 1);
|
||||
|
||||
IncrementReferenceCount();
|
||||
}
|
||||
else
|
||||
|
||||
@@ -4,7 +4,6 @@ using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.Gpu.Shader;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using Ryujinx.Memory.Range;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -68,9 +67,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
private int _lastFragmentTotal;
|
||||
|
||||
private readonly int _bufferTextureAlignment;
|
||||
private readonly int _bufferImageAlignment;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of the texture bindings manager.
|
||||
/// </summary>
|
||||
@@ -112,10 +108,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
|
||||
_textureCounts = [];
|
||||
|
||||
int alignment = context.Capabilities.TextureBufferOffsetAlignment;
|
||||
_bufferTextureAlignment = context.Capabilities.SupportsTextureBufferPixelAlignment ? 0 : alignment;
|
||||
_bufferImageAlignment = context.Capabilities.SupportsImageBufferPixelAlignment ? 0 : alignment;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -529,12 +521,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
if (hostTexture != null && texture.Target == Target.TextureBuffer)
|
||||
{
|
||||
MultiRange range = GetAlignedBufferTextureRange(texture, index, stageIndex, false);
|
||||
|
||||
// Ensure that the buffer texture is using the correct buffer as storage.
|
||||
// Buffers are frequently re-created to accommodate larger data, so we need to re-bind
|
||||
// to ensure we're not using a old buffer that was already deleted.
|
||||
_channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, range, bindingInfo, false);
|
||||
_channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range, bindingInfo, false);
|
||||
|
||||
// Cache is not used for buffer texture, it must always rebind.
|
||||
state.CachedTexture = null;
|
||||
@@ -669,9 +659,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
// Buffers are frequently re-created to accommodate larger data, so we need to re-bind
|
||||
// to ensure we're not using a old buffer that was already deleted.
|
||||
|
||||
MultiRange range = GetAlignedBufferTextureRange(texture, index, stageIndex, true);
|
||||
|
||||
_channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, range, bindingInfo, true);
|
||||
_channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range, bindingInfo, true);
|
||||
|
||||
// Cache is not used for buffer texture, it must always rebind.
|
||||
state.CachedTexture = null;
|
||||
@@ -704,61 +692,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
return specStateMatches;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the aligned address of the texture according to the host requirements.
|
||||
/// </summary>
|
||||
/// <param name="texture">Texture to have its address aligned</param>
|
||||
/// <param name="index">Index of the texture in the shader</param>
|
||||
/// <param name="stageIndex">Index of the shader stage</param>
|
||||
/// <param name="isImage">True if the texture is bound as image, false for sampled textures</param>
|
||||
/// <returns>Aligned address and size of the buffer texture</returns>
|
||||
private MultiRange GetAlignedBufferTextureRange(Texture texture, int index, int stageIndex, bool isImage)
|
||||
{
|
||||
MultiRange range = texture.Range;
|
||||
int alignment = isImage ? _bufferImageAlignment : _bufferTextureAlignment;
|
||||
|
||||
if (alignment != 0)
|
||||
{
|
||||
MemoryRange firstRange = range.GetSubRange(0);
|
||||
ulong misalign = firstRange.Address & ((ulong)alignment - 1);
|
||||
int offset = misalign != 0 ? (int)misalign / texture.Info.FormatInfo.BytesPerPixel : 0;
|
||||
|
||||
if (misalign != 0)
|
||||
{
|
||||
firstRange = new MemoryRange(firstRange.Address - misalign, firstRange.Size + misalign);
|
||||
|
||||
if (range.Count > 1)
|
||||
{
|
||||
MemoryRange[] ranges = new MemoryRange[range.Count];
|
||||
|
||||
ranges[0] = firstRange;
|
||||
|
||||
for (int i = 1; i < range.Count; i++)
|
||||
{
|
||||
ranges[i] = range.GetSubRange(i);
|
||||
}
|
||||
|
||||
range = new MultiRange(ranges);
|
||||
}
|
||||
else
|
||||
{
|
||||
range = new MultiRange(firstRange.Address, firstRange.Size);
|
||||
}
|
||||
}
|
||||
|
||||
if (isImage)
|
||||
{
|
||||
_context.SupportBufferUpdater.UpdateBufferImageOffset(stageIndex, index, offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
_context.SupportBufferUpdater.UpdateBufferTextureOffset(stageIndex, index, offset);
|
||||
}
|
||||
}
|
||||
|
||||
return range;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the texture descriptor for a given texture handle.
|
||||
/// </summary>
|
||||
|
||||
@@ -195,16 +195,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a texture can flush its data back to guest memory.
|
||||
/// </summary>
|
||||
/// <param name="info">Texture that will have its data flushed</param>
|
||||
/// <param name="caps">Host GPU Capabilities</param>
|
||||
/// <returns>True if the texture can flush, false otherwise</returns>
|
||||
public static bool CanTextureFlush(Texture texture, in Capabilities caps)
|
||||
{
|
||||
return !texture.HasImportOverride() && CanTextureFlush(texture.Info, caps);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a texture can flush its data back to guest memory.
|
||||
@@ -212,14 +202,15 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <param name="info">Texture information</param>
|
||||
/// <param name="caps">Host GPU Capabilities</param>
|
||||
/// <returns>True if the texture can flush, false otherwise</returns>
|
||||
private static bool CanTextureFlush(TextureInfo info, in Capabilities caps)
|
||||
public static bool CanTextureFlush(TextureInfo info, Capabilities caps)
|
||||
{
|
||||
if (IsFormatHostIncompatible(info, in caps))
|
||||
if (IsFormatHostIncompatible(info, caps))
|
||||
{
|
||||
return false; // Flushing this format is not supported, as it may have been converted to another host format.
|
||||
}
|
||||
|
||||
if (info.Target is Target.Texture2DMultisample or Target.Texture2DMultisampleArray)
|
||||
if (info.Target is Target.Texture2DMultisample or
|
||||
Target.Texture2DMultisampleArray)
|
||||
{
|
||||
return false; // Flushing multisample textures is not supported, the host does not allow getting their data.
|
||||
}
|
||||
@@ -381,12 +372,15 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
|
||||
// Some APIs align the width for copy and render target textures,
|
||||
// so the width may not match in this case for different uses of the same texture.
|
||||
// so the width may not match for different uses of the same texture.
|
||||
// To account for this, we compare the aligned width here.
|
||||
// We expect height to always match exactly, if the texture is the same.
|
||||
// However, matching block footprints are not sufficient for compressed textures;
|
||||
// their logical dimensions must also match.
|
||||
if (alignedWidthMatches && lhsSize.Height == rhsSize.Height)
|
||||
{
|
||||
return (exact && lhsSize.Width != rhsSize.Width) || lhsSize.Width < rhsSize.Width
|
||||
return ((lhs.FormatInfo.IsCompressed || rhs.FormatInfo.IsCompressed) &&
|
||||
(Math.Max(1, lhs.Width >> level) != rhs.Width || Math.Max(1, lhs.Height >> level) != rhs.Height)) ||
|
||||
(exact && lhsSize.Width != rhsSize.Width) || lhsSize.Width < rhsSize.Width
|
||||
? TextureViewCompatibility.CopyOnly
|
||||
: result;
|
||||
}
|
||||
@@ -397,7 +391,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
return stride == rhs.Stride ? TextureViewCompatibility.CopyOnly : TextureViewCompatibility.LayoutIncompatible;
|
||||
}
|
||||
else if (lhs.Target.IsMultisample() != rhs.Target.IsMultisample() && alignedWidthMatches && lhsAlignedSize.Height == rhsAlignedSize.Height)
|
||||
else if (lhs.Target.IsMultisample != rhs.Target.IsMultisample && alignedWidthMatches && lhsAlignedSize.Height == rhsAlignedSize.Height)
|
||||
{
|
||||
// Copy between multisample and non-multisample textures with mismatching size is allowed,
|
||||
// as long aligned size matches.
|
||||
|
||||
@@ -147,7 +147,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
_allOffsets = size.AllOffsets;
|
||||
_sliceSizes = size.SliceSizes;
|
||||
|
||||
if (Storage.Target.HasDepthOrLayers() && Storage.Info.GetSlices() > GranularLayerThreshold)
|
||||
if (Storage.Target.HasDepthOrLayers && Storage.Info.GetSlices() > GranularLayerThreshold)
|
||||
{
|
||||
_hasLayerViews = true;
|
||||
_hasMipViews = true;
|
||||
@@ -182,7 +182,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
foreach (TextureIncompatibleOverlap overlap in _incompatibleOverlaps)
|
||||
{
|
||||
if (overlap.Compatibility <= TextureViewCompatibility.LayoutIncompatible)
|
||||
// LayoutIncompatible and better may still use a regular texture copy dependency.
|
||||
// Fully Incompatible pairs are not copy compatible in general, but may still qualify for an
|
||||
// exact raw byte copy dependency (checked internally by CreateCopyDependency) when they map
|
||||
// to exactly the same guest memory, such as differently typed/sized aliases of the same data.
|
||||
if (overlap.Compatibility <= TextureViewCompatibility.Incompatible)
|
||||
{
|
||||
CreateCopyDependency(overlap.Group, false, overlap.Compatibility);
|
||||
}
|
||||
@@ -226,7 +230,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Flushes incompatible overlaps if the storage format requires it, and they have been modified.
|
||||
/// This allows unsupported host formats to accept data written to format aliased textures.
|
||||
@@ -324,7 +327,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
FlushIncompatibleOverlapsIfNeeded();
|
||||
|
||||
EvaluateRelevantHandles(texture, (baseHandle, regionCount, split, _) =>
|
||||
EvaluateRelevantHandles(texture, (baseHandle, regionCount, split, bound) =>
|
||||
{
|
||||
bool dirty = false;
|
||||
bool anyModified = false;
|
||||
@@ -479,7 +482,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <param name="texture">The texture to synchronize dependents of</param>
|
||||
public void SynchronizeDependents(Texture texture)
|
||||
{
|
||||
EvaluateRelevantHandles(texture, (baseHandle, regionCount, _, _) =>
|
||||
EvaluateRelevantHandles(texture, (baseHandle, regionCount, split, bound) =>
|
||||
{
|
||||
for (int i = 0; i < regionCount; i++)
|
||||
{
|
||||
@@ -571,7 +574,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
tracked = tracked || ShouldFlushTriggerTracking();
|
||||
bool flushed = false;
|
||||
|
||||
EvaluateRelevantHandles(texture, (baseHandle, regionCount, split, _) =>
|
||||
EvaluateRelevantHandles(texture, (baseHandle, regionCount, split, bound) =>
|
||||
{
|
||||
int startSlice = 0;
|
||||
int endSlice = 0;
|
||||
@@ -652,14 +655,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
if (_flushBuffer == BufferHandle.Null)
|
||||
{
|
||||
if (!TextureCompatibility.CanTextureFlush(Storage, _context.Capabilities))
|
||||
if (!TextureCompatibility.CanTextureFlush(Storage.Info, _context.Capabilities))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool canImport = Storage.Info.IsLinear && Storage.Info.Stride >= Storage.Info.Width * Storage.Info.FormatInfo.BytesPerPixel;
|
||||
|
||||
IntPtr hostPointer = canImport ? _physicalMemory.GetHostPointer(Storage.Range) : 0;
|
||||
nint hostPointer = canImport ? _physicalMemory.GetHostPointer(Storage.Range) : 0;
|
||||
|
||||
if (hostPointer != 0 && _context.Renderer.PrepareHostMapping(hostPointer, Storage.Size))
|
||||
{
|
||||
@@ -716,7 +719,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
ClearIncompatibleOverlaps(texture);
|
||||
|
||||
EvaluateRelevantHandles(texture, (baseHandle, regionCount, _, _) =>
|
||||
EvaluateRelevantHandles(texture, (baseHandle, regionCount, split, bound) =>
|
||||
{
|
||||
for (int i = 0; i < regionCount; i++)
|
||||
{
|
||||
@@ -1049,7 +1052,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
int endOffset = _allOffsets[viewEnd] + _sliceSizes[lastLevel];
|
||||
int size = endOffset - offset;
|
||||
|
||||
List<RegionHandle> result = new();
|
||||
List<RegionHandle> result = [];
|
||||
|
||||
for (int i = 0; i < TextureRange.Count; i++)
|
||||
{
|
||||
@@ -1163,7 +1166,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
SignalAllDirty();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Removes a view from the group, removing it from all overlap lists.
|
||||
/// </summary>
|
||||
@@ -1385,7 +1387,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
if (_is3D)
|
||||
{
|
||||
List<TextureGroupHandle> handlesList = new();
|
||||
List<TextureGroupHandle> handlesList = [];
|
||||
|
||||
for (int i = 0; i < levelHandles; i++)
|
||||
{
|
||||
@@ -1468,15 +1470,15 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
// Get the location of each texture within its storage, so we can find the handles to apply the dependency to.
|
||||
// This can consist of multiple disjoint regions, for example if this is a mip slice of an array texture.
|
||||
|
||||
List<(int BaseHandle, int RegionCount)> targetRange = new();
|
||||
List<(int BaseHandle, int RegionCount)> otherRange = new();
|
||||
List<(int BaseHandle, int RegionCount)> targetRange = [];
|
||||
List<(int BaseHandle, int RegionCount)> otherRange = [];
|
||||
|
||||
EvaluateRelevantHandles(firstLayer, firstLevel, other.Info.GetSlices(), other.Info.Levels, (baseHandle, regionCount, _, _) =>
|
||||
EvaluateRelevantHandles(firstLayer, firstLevel, other.Info.GetSlices(), other.Info.Levels, (baseHandle, regionCount, split, specialData) =>
|
||||
{
|
||||
targetRange.Add((baseHandle, regionCount));
|
||||
return true;
|
||||
}, out _);
|
||||
otherGroup.EvaluateRelevantHandles(other, (baseHandle, regionCount, _, _) =>
|
||||
otherGroup.EvaluateRelevantHandles(other, (baseHandle, regionCount, split, specialData) =>
|
||||
{
|
||||
otherRange.Add((baseHandle, regionCount));
|
||||
return true;
|
||||
@@ -1601,7 +1603,15 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
TextureInfo info = Storage.Info;
|
||||
TextureInfo otherInfo = other.Storage.Info;
|
||||
|
||||
bool textureCopy = TextureCompatibility.ViewLayoutCompatible(info, otherInfo, level, otherLevel) &&
|
||||
// ViewLayoutCompatible/CopySizeMatches only reason about textures with some genuine
|
||||
// format relationship (LayoutIncompatible or better) - they are not aware of, and must
|
||||
// never be used to justify, a plain texture-to-texture copy (which some backends
|
||||
// implement via a reinterpreting view) between fully Incompatible aliases such as a
|
||||
// depth format and an unrelated color format. For Incompatible pairs, only the strict
|
||||
// raw byte copy dependency below (which explicitly excludes depth/stencil formats) may
|
||||
// be used.
|
||||
bool textureCopy = compatibility != TextureViewCompatibility.Incompatible &&
|
||||
TextureCompatibility.ViewLayoutCompatible(info, otherInfo, level, otherLevel) &&
|
||||
TextureCompatibility.CopySizeMatches(info, otherInfo, level, otherLevel);
|
||||
|
||||
if (textureCopy || rawCopy)
|
||||
@@ -1659,9 +1669,12 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
if (!_incompatibleOverlaps.Any(overlap => overlap.Group == other.Group))
|
||||
{
|
||||
if (copy && other.Compatibility <= TextureViewCompatibility.LayoutIncompatible)
|
||||
if (copy && other.Compatibility <= TextureViewCompatibility.Incompatible)
|
||||
{
|
||||
// Any of the group's views may share compatibility, even if the parents do not fully.
|
||||
// Fully Incompatible groups are also let through here, since CreateCopyDependency will
|
||||
// fall back to an exact raw byte copy dependency for them when the strict requirements
|
||||
// for one are met (see CanCreateRawCopyDependency).
|
||||
CreateCopyDependency(other.Group, false, other.Compatibility);
|
||||
}
|
||||
|
||||
@@ -1763,7 +1776,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
}
|
||||
|
||||
if (TextureCompatibility.CanTextureFlush(Storage, _context.Capabilities) && !(inBuffer && _flushBufferImported))
|
||||
if (TextureCompatibility.CanTextureFlush(Storage.Info, _context.Capabilities) && !(inBuffer && _flushBufferImported))
|
||||
{
|
||||
FlushSliceRange(false, handle.BaseSlice, handle.BaseSlice + handle.SliceCount, inBuffer, Storage.GetFlushTexture());
|
||||
}
|
||||
@@ -1803,4 +1816,3 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -717,6 +717,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
|
||||
DeferredCopy = old.DeferredCopy;
|
||||
DeferredCopyRaw = old.DeferredCopyRaw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,39 +19,12 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
private readonly TexturePoolCache _texturePoolCache;
|
||||
private readonly SamplerPoolCache _samplerPoolCache;
|
||||
|
||||
/// <summary>
|
||||
/// Bound render target texture modification report state.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
private enum BindState : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Render target texture has not been signalled for modification, and can't be modified on the next render operations.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Render target texture has been signalled for modification.
|
||||
/// </summary>
|
||||
Bound = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// Render target texture might be modified on the next render operations.
|
||||
/// </summary>
|
||||
Modified = 1 << 1,
|
||||
|
||||
/// <summary>
|
||||
/// Render target texture has been signalled for modification and might be modified on the next render operations.
|
||||
/// </summary>
|
||||
BoundModified = Bound | Modified,
|
||||
}
|
||||
|
||||
private readonly Texture[] _rtColors;
|
||||
private readonly ITexture[] _rtHostColors;
|
||||
private readonly BindState[] _rtColorsBound;
|
||||
private readonly bool[] _rtColorsBound;
|
||||
private Texture _rtDepthStencil;
|
||||
private ITexture _rtHostDs;
|
||||
private BindState _rtDsBound;
|
||||
private bool _rtDsBound;
|
||||
|
||||
public int ClipRegionWidth { get; private set; }
|
||||
public int ClipRegionHeight { get; private set; }
|
||||
@@ -82,7 +55,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
_rtColors = new Texture[Constants.TotalRenderTargets];
|
||||
_rtHostColors = new ITexture[Constants.TotalRenderTargets];
|
||||
_rtColorsBound = new BindState[Constants.TotalRenderTargets];
|
||||
_rtColorsBound = new bool[Constants.TotalRenderTargets];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -178,39 +151,27 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the color buffer to set (up to 8)</param>
|
||||
/// <param name="color">The color buffer texture</param>
|
||||
/// <param name="modified">Indicates if the following render operations will modidify <paramref name="color"/> contents</param>
|
||||
/// <returns>True if render target scale must be updated.</returns>
|
||||
public bool SetRenderTargetColor(int index, Texture color, bool modified)
|
||||
public bool SetRenderTargetColor(int index, Texture color)
|
||||
{
|
||||
bool hasValue = color != null;
|
||||
bool changesScale = (hasValue != (_rtColors[index] != null)) || (hasValue && RenderTargetScale != color.ScaleFactor);
|
||||
|
||||
if (_rtColors[index] != color)
|
||||
{
|
||||
Texture oldColor = _rtColors[index];
|
||||
|
||||
if (oldColor != null)
|
||||
if (_rtColorsBound[index])
|
||||
{
|
||||
if (_rtColorsBound[index].HasFlag(BindState.Bound))
|
||||
{
|
||||
oldColor.SignalModifying(false);
|
||||
}
|
||||
|
||||
oldColor.SignalBindingChange(false);
|
||||
_rtColors[index]?.SignalModifying(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_rtColorsBound[index] = true;
|
||||
}
|
||||
|
||||
_rtColorsBound[index] = modified ? BindState.BoundModified : BindState.None;
|
||||
|
||||
if (color != null)
|
||||
{
|
||||
color.SynchronizeMemory();
|
||||
|
||||
if (modified)
|
||||
{
|
||||
color.SignalModifying(true);
|
||||
}
|
||||
|
||||
color.SignalBindingChange(true);
|
||||
color.SignalModifying(true);
|
||||
}
|
||||
|
||||
_rtColors[index] = color;
|
||||
@@ -223,39 +184,27 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// Sets the render target depth-stencil buffer.
|
||||
/// </summary>
|
||||
/// <param name="depthStencil">The depth-stencil buffer texture</param>
|
||||
/// <param name="modified">Indicates if the following render operations will modidify <paramref name="depthStencil"/> contents</param>
|
||||
/// <returns>True if render target scale must be updated.</returns>
|
||||
public bool SetRenderTargetDepthStencil(Texture depthStencil, bool modified)
|
||||
public bool SetRenderTargetDepthStencil(Texture depthStencil)
|
||||
{
|
||||
bool hasValue = depthStencil != null;
|
||||
bool changesScale = (hasValue != (_rtDepthStencil != null)) || (hasValue && RenderTargetScale != depthStencil.ScaleFactor);
|
||||
|
||||
if (_rtDepthStencil != depthStencil)
|
||||
{
|
||||
Texture oldDepthStencil = _rtDepthStencil;
|
||||
|
||||
if (oldDepthStencil != null)
|
||||
if (_rtDsBound)
|
||||
{
|
||||
if (_rtDsBound.HasFlag(BindState.Bound))
|
||||
{
|
||||
oldDepthStencil.SignalModifying(false);
|
||||
}
|
||||
|
||||
oldDepthStencil.SignalBindingChange(false);
|
||||
_rtDepthStencil?.SignalModifying(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_rtDsBound = true;
|
||||
}
|
||||
|
||||
_rtDsBound = modified ? BindState.BoundModified : BindState.None;
|
||||
|
||||
if (depthStencil != null)
|
||||
{
|
||||
depthStencil.SynchronizeMemory();
|
||||
|
||||
if (modified)
|
||||
{
|
||||
depthStencil.SignalModifying(true);
|
||||
}
|
||||
|
||||
depthStencil.SignalBindingChange(true);
|
||||
depthStencil.SignalModifying(true);
|
||||
}
|
||||
|
||||
_rtDepthStencil = depthStencil;
|
||||
@@ -494,10 +443,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
hostDsTexture = dsTexture.HostTexture;
|
||||
|
||||
if (_rtDsBound == BindState.Modified)
|
||||
if (!_rtDsBound)
|
||||
{
|
||||
dsTexture.SignalModifying(true);
|
||||
_rtDsBound |= BindState.Bound;
|
||||
_rtDsBound = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -521,10 +470,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
hostTexture = texture.HostTexture;
|
||||
|
||||
if (_rtColorsBound[index] == BindState.Modified)
|
||||
if (!_rtColorsBound[index])
|
||||
{
|
||||
texture.SignalModifying(true);
|
||||
_rtColorsBound[index] |= BindState.Bound;
|
||||
_rtColorsBound[index] = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -569,37 +518,24 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
Texture dsTexture = _rtDepthStencil;
|
||||
|
||||
if (dsTexture != null && _rtDsBound.HasFlag(BindState.Bound))
|
||||
if (dsTexture != null && _rtDsBound)
|
||||
{
|
||||
dsTexture.SignalModifying(false);
|
||||
_rtDsBound &= ~BindState.Bound;
|
||||
_rtDsBound = false;
|
||||
}
|
||||
|
||||
for (int index = 0; index < _rtColors.Length; index++)
|
||||
{
|
||||
Texture texture = _rtColors[index];
|
||||
|
||||
if (texture != null && _rtColorsBound[index].HasFlag(BindState.Bound))
|
||||
if (texture != null && _rtColorsBound[index])
|
||||
{
|
||||
texture.SignalModifying(false);
|
||||
_rtColorsBound[index] &= ~BindState.Bound;
|
||||
_rtColorsBound[index] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the currently bound render targets might be modified, if they are used on the next render operation.
|
||||
/// </summary>
|
||||
public void SignalRenderTargetsModifiable()
|
||||
{
|
||||
_rtDsBound |= BindState.Modified;
|
||||
|
||||
for (int index = 0; index < _rtColorsBound.Length; index++)
|
||||
{
|
||||
_rtColorsBound[index] |= BindState.Modified;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forces the texture and sampler pools to be re-loaded from the cache on next use.
|
||||
/// </summary>
|
||||
@@ -636,11 +572,19 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
for (int i = 0; i < _rtColors.Length; i++)
|
||||
{
|
||||
_rtColors[i]?.DecrementReferenceCount();
|
||||
if (_rtColorsBound[i])
|
||||
{
|
||||
_rtColors[i]?.DecrementReferenceCount();
|
||||
}
|
||||
|
||||
_rtColors[i] = null;
|
||||
}
|
||||
|
||||
_rtDepthStencil?.DecrementReferenceCount();
|
||||
if (_rtDsBound)
|
||||
{
|
||||
_rtDepthStencil?.DecrementReferenceCount();
|
||||
}
|
||||
|
||||
_rtDepthStencil = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,38 +129,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the offset used for accessing buffer textures that are not aligned to the host requirements.
|
||||
/// </summary>
|
||||
/// <param name="stageIndex">Index of the shader stage where the texture is used</param>
|
||||
/// <param name="bindingIndex">Index of the texture binding in the shader</param>
|
||||
/// <param name="offset">Offset for the misaligned part of the address</param>
|
||||
public void UpdateBufferTextureOffset(int stageIndex, int bindingIndex, int offset)
|
||||
{
|
||||
if (_data.BufferTextureOffset[stageIndex][bindingIndex].X != offset)
|
||||
{
|
||||
_data.BufferTextureOffset[stageIndex][bindingIndex].X = offset;
|
||||
int index = stageIndex * SupportBuffer.TextureCount + bindingIndex;
|
||||
MarkDirty(SupportBuffer.BufferTextureOffsetOffset + index * sizeof(int) * 4, sizeof(int));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the offset used for accessing buffer images that are not aligned to the host requirements.
|
||||
/// </summary>
|
||||
/// <param name="stageIndex">Index of the shader stage where the image is used</param>
|
||||
/// <param name="bindingIndex">Index of the image binding in the shader</param>
|
||||
/// <param name="offset">Offset for the misaligned part of the address</param>
|
||||
public void UpdateBufferImageOffset(int stageIndex, int bindingIndex, int offset)
|
||||
{
|
||||
if (_data.BufferTextureOffset[stageIndex][bindingIndex].Y != offset)
|
||||
{
|
||||
_data.BufferTextureOffset[stageIndex][bindingIndex].Y = offset;
|
||||
int index = stageIndex * SupportBuffer.TextureCount + bindingIndex;
|
||||
MarkDirty(SupportBuffer.BufferTextureOffsetOffset + index * sizeof(int) * 4 + sizeof(int), sizeof(int));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether the format of a given render target is a BGRA format.
|
||||
/// </summary>
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
private const ushort FileFormatVersionMajor = 1;
|
||||
private const ushort FileFormatVersionMinor = 2;
|
||||
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
||||
private const uint CodeGenVersion = 6371;
|
||||
private const uint CodeGenVersion = 7354;
|
||||
|
||||
private const string SharedTocFileName = "shared.toc";
|
||||
private const string SharedDataFileName = "shared.data";
|
||||
@@ -170,11 +170,6 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
/// </summary>
|
||||
public bool UsesRtLayer;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the fragment shader always discards the fragment, not producing any output for the bound render targets.
|
||||
/// </summary>
|
||||
public bool HasUnconditionalDiscard;
|
||||
|
||||
/// <summary>
|
||||
/// Bit mask with the clip distances written on the vertex stage.
|
||||
/// </summary>
|
||||
@@ -811,7 +806,6 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
dataInfo.UsesInstanceId,
|
||||
dataInfo.UsesDrawParameters,
|
||||
dataInfo.UsesRtLayer,
|
||||
dataInfo.HasUnconditionalDiscard,
|
||||
dataInfo.ClipDistancesWritten,
|
||||
dataInfo.FragmentOutputMap);
|
||||
}
|
||||
@@ -842,7 +836,6 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
UsesInstanceId = info.UsesInstanceId,
|
||||
UsesDrawParameters = info.UsesDrawParameters,
|
||||
UsesRtLayer = info.UsesRtLayer,
|
||||
HasUnconditionalDiscard = info.HasUnconditionalDiscard,
|
||||
ClipDistancesWritten = info.ClipDistancesWritten,
|
||||
FragmentOutputMap = info.FragmentOutputMap,
|
||||
};
|
||||
|
||||
@@ -22,6 +22,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
private readonly CancellationToken _cancellationToken;
|
||||
private readonly Action<ShaderCacheState, int, int> _stateChangeCallback;
|
||||
|
||||
private readonly HashSet<ProgramPipelineState> _pipelineStateSet = new();
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the cache should be loaded.
|
||||
/// </summary>
|
||||
@@ -232,10 +234,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
|
||||
for (int index = 0; index < ThreadCount; index++)
|
||||
{
|
||||
workThreads[index] = new Thread(ProcessAsyncQueue)
|
||||
{
|
||||
Name = $"GPU.AsyncTranslationThread.{index}",
|
||||
};
|
||||
workThreads[index] = new Thread(ProcessAsyncQueue) { Name = $"GPU.AsyncTranslationThread.{index}", };
|
||||
}
|
||||
|
||||
int programCount = _hostStorage.GetProgramCount();
|
||||
@@ -305,6 +304,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
using DiskCacheOutputStreams streams = _hostStorage.GetOutputStreams(_context);
|
||||
|
||||
int packagedShaders = 0;
|
||||
ProgramPipelineState currentPipelineState = default;
|
||||
|
||||
foreach (KeyValuePair<int, (CachedShaderProgram, byte[])> kv in _programList)
|
||||
{
|
||||
if (!Active)
|
||||
@@ -314,12 +315,53 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
|
||||
(CachedShaderProgram program, byte[] binaryCode) = kv.Value;
|
||||
|
||||
_hostStorage.AddShader(_context, program, binaryCode, streams);
|
||||
if (program.SpecializationState.PipelineState.HasValue && _context.Capabilities.SupportsExtendedDynamicState)
|
||||
{
|
||||
currentPipelineState = program.SpecializationState.PipelineState.Value;
|
||||
|
||||
_stateChangeCallback(ShaderCacheState.Packaging, ++packagedShaders, _programList.Count);
|
||||
if (_context.Capabilities.SupportsExtendedDynamicState)
|
||||
{
|
||||
currentPipelineState.StencilTest = default;
|
||||
|
||||
currentPipelineState.CullMode = 0;
|
||||
currentPipelineState.FrontFace = 0;
|
||||
currentPipelineState.DepthTest = default;
|
||||
currentPipelineState.Topology = ConvertToClass(currentPipelineState.Topology);
|
||||
}
|
||||
|
||||
if (_context.Capabilities.SupportsExtendedDynamicState2)
|
||||
{
|
||||
currentPipelineState.PrimitiveRestartEnable = false;
|
||||
currentPipelineState.BiasEnable = 0;
|
||||
currentPipelineState.RasterizerDiscard = false;
|
||||
}
|
||||
|
||||
if (_context.Capabilities.SupportsLogicOpDynamicState)
|
||||
{
|
||||
currentPipelineState.LogicOp = 0;
|
||||
}
|
||||
|
||||
if (_context.Capabilities.SupportsPatchControlPointsDynamicState)
|
||||
{
|
||||
currentPipelineState.PatchControlPoints = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_pipelineStateSet.Contains(currentPipelineState) ||
|
||||
!_context.Capabilities.SupportsExtendedDynamicState ||
|
||||
!program.SpecializationState.PipelineState.HasValue)
|
||||
{
|
||||
_hostStorage.AddShader(_context, program, binaryCode, streams);
|
||||
|
||||
_stateChangeCallback(ShaderCacheState.Packaging, ++packagedShaders, _programList.Count);
|
||||
|
||||
if (_context.Capabilities.SupportsExtendedDynamicState)
|
||||
{
|
||||
_pipelineStateSet.Add(currentPipelineState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Logger.Info?.Print(LogClass.Gpu, $"Rebuilt {_programList.Count} shaders successfully.");
|
||||
Logger.Info?.Print(LogClass.Gpu, $"Rebuilt {packagedShaders} shaders successfully.");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -343,6 +385,29 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
_stateChangeCallback(ShaderCacheState.Loaded, programCount, programCount);
|
||||
}
|
||||
|
||||
private PrimitiveTopology ConvertToClass(PrimitiveTopology topology)
|
||||
{
|
||||
return topology switch
|
||||
{
|
||||
PrimitiveTopology.Points => PrimitiveTopology.Points,
|
||||
PrimitiveTopology.Lines or
|
||||
PrimitiveTopology.LineStrip or
|
||||
PrimitiveTopology.LinesAdjacency or
|
||||
PrimitiveTopology.LineStripAdjacency => PrimitiveTopology.Lines,
|
||||
PrimitiveTopology.Triangles or
|
||||
PrimitiveTopology.TriangleStrip or
|
||||
PrimitiveTopology.TriangleFan or
|
||||
PrimitiveTopology.TrianglesAdjacency or
|
||||
PrimitiveTopology.TriangleStripAdjacency or
|
||||
PrimitiveTopology.Polygon => PrimitiveTopology.TriangleStrip,
|
||||
PrimitiveTopology.Patches => PrimitiveTopology.Patches,
|
||||
PrimitiveTopology.Quads => PrimitiveTopology.Quads,
|
||||
PrimitiveTopology.QuadStrip => PrimitiveTopology.QuadStrip,
|
||||
PrimitiveTopology.LineLoop => PrimitiveTopology.LineLoop,
|
||||
_ => PrimitiveTopology.TriangleStrip,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enqueues a host program for compilation.
|
||||
/// </summary>
|
||||
|
||||
@@ -207,10 +207,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
|
||||
public bool QueryHostSupportsBgraFormat() => _context.Capabilities.SupportsBgraFormat;
|
||||
|
||||
public bool QueryHostSupportsBufferImagePixelAlignment() => _context.Capabilities.SupportsImageBufferPixelAlignment;
|
||||
|
||||
public bool QueryHostSupportsBufferTexturePixelAlignment() => _context.Capabilities.SupportsTextureBufferPixelAlignment;
|
||||
|
||||
public bool QueryHostSupportsFragmentShaderInterlock() => _context.Capabilities.SupportsFragmentShaderInterlock;
|
||||
|
||||
public bool QueryHostSupportsFragmentShaderOrderingIntel() => _context.Capabilities.SupportsFragmentShaderOrderingIntel;
|
||||
|
||||
@@ -430,8 +430,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
|
||||
TranslatorContext lastInVertexPipeline = geometryToCompute ? translatorContexts[4] ?? currentStage : currentStage;
|
||||
|
||||
(program, ShaderProgramInfo vacInfo) = lastInVertexPipeline.GenerateVertexPassthroughForCompute();
|
||||
infoBuilder.AddStageInfoVac(vacInfo);
|
||||
program = lastInVertexPipeline.GenerateVertexPassthroughForCompute();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -536,7 +535,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
private ShaderAsCompute CreateHostVertexAsComputeProgram(ShaderProgram program, TranslatorContext context, bool tfEnabled)
|
||||
{
|
||||
ShaderSource source = new(program.Code, program.BinaryCode, ShaderStage.Compute, program.Language);
|
||||
ShaderInfo info = ShaderInfoBuilder.BuildForVertexAsCompute(_context, program.Info, context.GetVertexAsComputeInfo(), tfEnabled);
|
||||
ShaderInfo info = ShaderInfoBuilder.BuildForVertexAsCompute(_context, program.Info, tfEnabled);
|
||||
|
||||
return new(_context.Renderer.CreateProgram([source], info), program.Info, context.GetResourceReservations());
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
private void PopulateDescriptorAndUsages(ResourceStages stages, ResourceType type, int setIndex, int start, int count, bool write = false)
|
||||
{
|
||||
AddDescriptor(stages, type, setIndex, start, count);
|
||||
// AddUsage(stages, type, setIndex, start, count, write);
|
||||
AddUsage(stages, type, setIndex, start, count, write);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -159,25 +159,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
AddUsage(info.Images, stages, isImage: true);
|
||||
}
|
||||
|
||||
public void AddStageInfoVac(ShaderProgramInfo info)
|
||||
{
|
||||
ResourceStages stages = info.Stage switch
|
||||
{
|
||||
ShaderStage.Compute => ResourceStages.Compute,
|
||||
ShaderStage.Vertex => ResourceStages.Vertex,
|
||||
ShaderStage.TessellationControl => ResourceStages.TessellationControl,
|
||||
ShaderStage.TessellationEvaluation => ResourceStages.TessellationEvaluation,
|
||||
ShaderStage.Geometry => ResourceStages.Geometry,
|
||||
ShaderStage.Fragment => ResourceStages.Fragment,
|
||||
_ => ResourceStages.None,
|
||||
};
|
||||
|
||||
AddUsage(info.CBuffers, stages, isStorage: false);
|
||||
AddUsage(info.SBuffers, stages, isStorage: true);
|
||||
AddUsage(info.Textures, stages, isImage: false);
|
||||
AddUsage(info.Images, stages, isImage: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a resource descriptor to the list of descriptors.
|
||||
/// </summary>
|
||||
@@ -441,11 +422,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// <param name="tfEnabled">Indicates if the graphics shader is used with transform feedback enabled</param>
|
||||
/// <param name="fromCache">True if the compute shader comes from a disk cache, false otherwise</param>
|
||||
/// <returns>Shader information</returns>
|
||||
public static ShaderInfo BuildForVertexAsCompute(GpuContext context, ShaderProgramInfo info, ShaderProgramInfo info2, bool tfEnabled, bool fromCache = false)
|
||||
public static ShaderInfo BuildForVertexAsCompute(GpuContext context, ShaderProgramInfo info, bool tfEnabled, bool fromCache = false)
|
||||
{
|
||||
ShaderInfoBuilder builder = new(context, tfEnabled, vertexAsCompute: true);
|
||||
|
||||
builder.AddStageInfoVac(info2);
|
||||
builder.AddStageInfo(info, vertexAsCompute: true);
|
||||
|
||||
return builder.Build(null, fromCache);
|
||||
|
||||
@@ -20,6 +20,27 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
private int _colorsCount;
|
||||
private bool _dualSourceBlend;
|
||||
|
||||
public bool HasAttachments
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_depthStencil != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int index = 0; index < _colors.Length; index++)
|
||||
{
|
||||
if (_colors[index] != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Framebuffer()
|
||||
{
|
||||
Handle = GL.GenFramebuffer();
|
||||
@@ -34,6 +55,12 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
return Handle;
|
||||
}
|
||||
|
||||
public void SetDefaultSize(int width, int height)
|
||||
{
|
||||
GL.FramebufferParameter(FramebufferTarget.Framebuffer, FramebufferDefaultParameter.FramebufferDefaultWidth, Math.Max(1, width));
|
||||
GL.FramebufferParameter(FramebufferTarget.Framebuffer, FramebufferDefaultParameter.FramebufferDefaultHeight, Math.Max(1, height));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AttachColor(int index, TextureView color)
|
||||
{
|
||||
@@ -105,13 +132,20 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
_colorsCount = colorsCount;
|
||||
}
|
||||
|
||||
private static void SetDrawBuffersImpl(int colorsCount)
|
||||
private void SetDrawBuffersImpl(int colorsCount)
|
||||
{
|
||||
DrawBuffersEnum[] drawBuffers = new DrawBuffersEnum[colorsCount];
|
||||
|
||||
for (int index = 0; index < colorsCount; index++)
|
||||
{
|
||||
drawBuffers[index] = DrawBuffersEnum.ColorAttachment0 + index;
|
||||
if (_colors[index] != null)
|
||||
{
|
||||
drawBuffers[index] = DrawBuffersEnum.ColorAttachment0 + index;
|
||||
}
|
||||
else
|
||||
{
|
||||
drawBuffers[index] = DrawBuffersEnum.None;
|
||||
}
|
||||
}
|
||||
|
||||
GL.DrawBuffers(colorsCount, drawBuffers);
|
||||
|
||||
@@ -116,8 +116,8 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
{
|
||||
TextureView destinationView = (TextureView)destination;
|
||||
|
||||
bool srcIsMultisample = Target.IsMultisample();
|
||||
bool dstIsMultisample = destinationView.Target.IsMultisample();
|
||||
bool srcIsMultisample = Target.IsMultisample;
|
||||
bool dstIsMultisample = destinationView.Target.IsMultisample;
|
||||
|
||||
if (dstIsMultisample != srcIsMultisample && Info.Format.IsDepthOrStencil())
|
||||
{
|
||||
@@ -172,8 +172,8 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
{
|
||||
TextureView destinationView = (TextureView)destination;
|
||||
|
||||
bool srcIsMultisample = Target.IsMultisample();
|
||||
bool dstIsMultisample = destinationView.Target.IsMultisample();
|
||||
bool srcIsMultisample = Target.IsMultisample;
|
||||
bool dstIsMultisample = destinationView.Target.IsMultisample;
|
||||
|
||||
if (dstIsMultisample != srcIsMultisample && Info.Format.IsDepthOrStencil())
|
||||
{
|
||||
@@ -216,7 +216,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
Extents2D srcRegion = new(0, 0, Width, Height);
|
||||
Extents2D dstRegion = new(0, 0, destinationView.Width, destinationView.Height);
|
||||
|
||||
if (destinationView.Target.IsMultisample())
|
||||
if (destinationView.Target.IsMultisample)
|
||||
{
|
||||
TextureView intermmediate = _renderer.TextureCopy.IntermediatePool.GetOrCreateWithAtLeast(
|
||||
Info.Target,
|
||||
|
||||
@@ -174,7 +174,6 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
supportsGeometryShader: true,
|
||||
supportsGeometryShaderPassthrough: HwCapabilities.SupportsGeometryShaderPassthrough,
|
||||
supportsTransformFeedback: true,
|
||||
supportsImageBufferPixelAlignment: false,
|
||||
supportsImageLoadFormatted: HwCapabilities.SupportsImageLoadFormatted,
|
||||
supportsLayerVertexTessellation: HwCapabilities.SupportsShaderViewportLayerArray,
|
||||
supportsMismatchingViewFormat: HwCapabilities.SupportsMismatchingViewFormat,
|
||||
@@ -186,7 +185,6 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
supportsShaderBarrierDivergence: !(intelWindows || intelUnix),
|
||||
supportsShaderFloat64: true,
|
||||
supportsShaderNonUniformIndexing: false,
|
||||
supportsTextureBufferPixelAlignment: false,
|
||||
supportsTextureGatherOffsets: true,
|
||||
supportsTextureShadowLod: HwCapabilities.SupportsTextureShadowLod,
|
||||
supportsVertexStoreAndAtomics: true,
|
||||
@@ -195,6 +193,10 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
supportsViewportSwizzle: HwCapabilities.SupportsViewportSwizzle,
|
||||
supportsIndirectParameters: HwCapabilities.SupportsIndirectParameters,
|
||||
supportsDepthClipControl: true,
|
||||
supportsExtendedDynamicState: false,
|
||||
supportsExtendedDynamicState2: false,
|
||||
supportsLogicOpDynamicState: false,
|
||||
supportsPatchControlPointsDynamicState: false,
|
||||
uniformBufferSetIndex: 0,
|
||||
storageBufferSetIndex: 1,
|
||||
textureSetIndex: 2,
|
||||
|
||||
@@ -898,7 +898,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
}
|
||||
}
|
||||
|
||||
public void SetDepthTest(DepthTestDescriptor depthTest)
|
||||
public void SetDepthTest(DepthTestDescriptor depthTest, bool signalChange = true)
|
||||
{
|
||||
if (depthTest.TestEnable)
|
||||
{
|
||||
@@ -915,11 +915,11 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
_depthTestEnable = depthTest.TestEnable;
|
||||
}
|
||||
|
||||
public void SetFaceCulling(bool enable, Face face)
|
||||
public void SetFaceCulling(Face face)
|
||||
{
|
||||
_cullEnable = enable;
|
||||
_cullEnable = face != Face.None;
|
||||
|
||||
if (!enable)
|
||||
if (!_cullEnable)
|
||||
{
|
||||
GL.Disable(EnableCap.CullFace);
|
||||
return;
|
||||
@@ -1107,12 +1107,12 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
GL.Enable(EnableCap.PrimitiveRestart);
|
||||
}
|
||||
|
||||
public void SetPrimitiveTopology(PrimitiveTopology topology)
|
||||
public void SetPrimitiveTopology(PrimitiveTopology topology, bool signalChange = true)
|
||||
{
|
||||
_primitiveType = topology.Convert();
|
||||
}
|
||||
|
||||
public void SetProgram(IProgram program)
|
||||
public void SetProgram(IProgram program, bool signalChange = true)
|
||||
{
|
||||
Program prg = (Program)program;
|
||||
|
||||
@@ -1154,7 +1154,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
_rasterizerDiscard = discard;
|
||||
}
|
||||
|
||||
public void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMasks)
|
||||
public void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMasks, bool signalChange = true)
|
||||
{
|
||||
_componentMasks = 0;
|
||||
|
||||
@@ -1195,7 +1195,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
_framebuffer.SetDrawBuffers(colors.Length);
|
||||
}
|
||||
|
||||
public void SetScissors(ReadOnlySpan<Rectangle<int>> regions)
|
||||
public void SetScissors(ReadOnlySpan<Rectangle<int>> regions, bool signalChange = true)
|
||||
{
|
||||
int count = Math.Min(regions.Length, Constants.MaxViewports);
|
||||
|
||||
@@ -1388,7 +1388,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
_vertexArray.SetVertexBuffers(vertexBuffers);
|
||||
}
|
||||
|
||||
public void SetViewports(ReadOnlySpan<Viewport> viewports)
|
||||
public void SetViewports(ReadOnlySpan<Viewport> viewports, bool signalChange = true)
|
||||
{
|
||||
Array.Resize(ref _viewportArray, viewports.Length * 4);
|
||||
Array.Resize(ref _depthRangeArray, viewports.Length * 2);
|
||||
@@ -1537,6 +1537,11 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
{
|
||||
DrawCount++;
|
||||
|
||||
if (!_framebuffer.HasAttachments && _viewportArray.Length >= 4)
|
||||
{
|
||||
_framebuffer.SetDefaultSize((int)_viewportArray[2], (int)_viewportArray[3]);
|
||||
}
|
||||
|
||||
_unit0Texture?.Bind(0);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 & 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, ×tamp);
|
||||
|
||||
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>
|
||||
@@ -1790,7 +1790,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
{
|
||||
(_, varType) = IoMap.GetSpirvBuiltIn(ioVariable);
|
||||
|
||||
if (IoMap.IsPerVertexBuiltIn(ioVariable))
|
||||
if (context.Definitions.Stage.IsVtg() && IoMap.IsPerVertexBuiltIn(ioVariable))
|
||||
{
|
||||
perVertexBuiltIn = ioVariable;
|
||||
ioVariable = IoVariable.Position;
|
||||
|
||||
@@ -522,7 +522,6 @@ namespace Ryujinx.Graphics.Shader.Decoders
|
||||
|
||||
enum PMode
|
||||
{
|
||||
Idx = 0,
|
||||
F4e = 1,
|
||||
B4e = 2,
|
||||
Rc8 = 3,
|
||||
|
||||
@@ -234,24 +234,6 @@ namespace Ryujinx.Graphics.Shader
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries host support for buffer image access with the buffer offset aligned to a single pixel rather than a fixed alignment.
|
||||
/// </summary>
|
||||
/// <returns>True if the host supports buffer image access with pixel alignment, false otherwise</returns>
|
||||
bool QueryHostSupportsBufferImagePixelAlignment()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries host support for buffer texture access with the buffer offset aligned to a single pixel rather than a fixed alignment.
|
||||
/// </summary>
|
||||
/// <returns>True if the host supports buffer texture access with pixel alignment, false otherwise</returns>
|
||||
bool QueryHostSupportsBufferTexturePixelAlignment()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries host support for fragment shader ordering critical sections on the shader code.
|
||||
/// </summary>
|
||||
|
||||
@@ -215,6 +215,34 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||
context.TranslatorContext.GpuAccessor.Log("Shader instruction Pret is not implemented.");
|
||||
}
|
||||
|
||||
public static void PrmtR(EmitterContext context)
|
||||
{
|
||||
context.GetOp<InstPrmtR>();
|
||||
|
||||
context.TranslatorContext.GpuAccessor.Log("Shader instruction PrmtR is not implemented.");
|
||||
}
|
||||
|
||||
public static void PrmtI(EmitterContext context)
|
||||
{
|
||||
context.GetOp<InstPrmtI>();
|
||||
|
||||
context.TranslatorContext.GpuAccessor.Log("Shader instruction PrmtI is not implemented.");
|
||||
}
|
||||
|
||||
public static void PrmtC(EmitterContext context)
|
||||
{
|
||||
context.GetOp<InstPrmtC>();
|
||||
|
||||
context.TranslatorContext.GpuAccessor.Log("Shader instruction PrmtC is not implemented.");
|
||||
}
|
||||
|
||||
public static void PrmtRc(EmitterContext context)
|
||||
{
|
||||
context.GetOp<InstPrmtRc>();
|
||||
|
||||
context.TranslatorContext.GpuAccessor.Log("Shader instruction PrmtRc is not implemented.");
|
||||
}
|
||||
|
||||
public static void R2b(EmitterContext context)
|
||||
{
|
||||
context.GetOp<InstR2b>();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Ryujinx.Graphics.Shader.Decoders;
|
||||
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
using System;
|
||||
|
||||
using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper;
|
||||
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
|
||||
|
||||
@@ -37,38 +37,6 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||
context.Copy(GetDest(op.Dest), GetSrcImm(context, op.Imm32));
|
||||
}
|
||||
|
||||
public static void PrmtR(EmitterContext context)
|
||||
{
|
||||
context.GetOp<InstPrmtR>();
|
||||
|
||||
context.TranslatorContext.GpuAccessor.Log("Shader instruction PrmtR is not implemented.");
|
||||
}
|
||||
|
||||
public static void PrmtI(EmitterContext context)
|
||||
{
|
||||
InstPrmtI op = context.GetOp<InstPrmtI>();
|
||||
|
||||
Operand op1 = GetSrcReg(context, op.SrcA);
|
||||
Operand control = GetSrcImm(context, op.Imm20);
|
||||
Operand op2 = GetSrcReg(context, op.SrcC);
|
||||
|
||||
EmitPrmt(context, op1, control, op2, op.PMode, op.Dest);
|
||||
}
|
||||
|
||||
public static void PrmtC(EmitterContext context)
|
||||
{
|
||||
context.GetOp<InstPrmtC>();
|
||||
|
||||
context.TranslatorContext.GpuAccessor.Log("Shader instruction PrmtC is not implemented.");
|
||||
}
|
||||
|
||||
public static void PrmtRc(EmitterContext context)
|
||||
{
|
||||
context.GetOp<InstPrmtRc>();
|
||||
|
||||
context.TranslatorContext.GpuAccessor.Log("Shader instruction PrmtRc is not implemented.");
|
||||
}
|
||||
|
||||
public static void R2pR(EmitterContext context)
|
||||
{
|
||||
InstR2pR op = context.GetOp<InstR2pR>();
|
||||
@@ -201,39 +169,6 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||
context.Copy(GetDest(op.Dest), src);
|
||||
}
|
||||
|
||||
public static void SelR(EmitterContext context)
|
||||
{
|
||||
InstSelR op = context.GetOp<InstSelR>();
|
||||
|
||||
Operand srcA = GetSrcReg(context, op.SrcA);
|
||||
Operand srcB = GetSrcReg(context, op.SrcB);
|
||||
Operand srcPred = GetPredicate(context, op.SrcPred, op.SrcPredInv);
|
||||
|
||||
EmitSel(context, srcA, srcB, srcPred, op.Dest);
|
||||
}
|
||||
|
||||
public static void SelI(EmitterContext context)
|
||||
{
|
||||
InstSelI op = context.GetOp<InstSelI>();
|
||||
|
||||
Operand srcA = GetSrcReg(context, op.SrcA);
|
||||
Operand srcB = GetSrcImm(context, Imm20ToSInt(op.Imm20));
|
||||
Operand srcPred = GetPredicate(context, op.SrcPred, op.SrcPredInv);
|
||||
|
||||
EmitSel(context, srcA, srcB, srcPred, op.Dest);
|
||||
}
|
||||
|
||||
public static void SelC(EmitterContext context)
|
||||
{
|
||||
InstSelC op = context.GetOp<InstSelC>();
|
||||
|
||||
Operand srcA = GetSrcReg(context, op.SrcA);
|
||||
Operand srcB = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset);
|
||||
Operand srcPred = GetPredicate(context, op.SrcPred, op.SrcPredInv);
|
||||
|
||||
EmitSel(context, srcA, srcB, srcPred, op.Dest);
|
||||
}
|
||||
|
||||
private static Operand EmitLoadSubgroupLaneId(EmitterContext context)
|
||||
{
|
||||
if (context.TranslatorContext.GpuAccessor.QueryHostSubgroupSize() <= 32)
|
||||
@@ -280,34 +215,37 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||
}
|
||||
}
|
||||
|
||||
private static void EmitPrmt(EmitterContext context, Operand op1, Operand control, Operand op2, PMode pMode, int rd)
|
||||
public static void SelR(EmitterContext context)
|
||||
{
|
||||
if (pMode == PMode.Idx)
|
||||
{
|
||||
Operand res = Const(0);
|
||||
InstSelR op = context.GetOp<InstSelR>();
|
||||
|
||||
for (int b = 0; b < 4; b++)
|
||||
{
|
||||
Operand sel = context.ShiftRightU32(control, Const(b * 4));
|
||||
Operand byteSel = context.BitwiseAnd(sel, Const(7));
|
||||
Operand copySign = context.BitwiseAnd(sel, Const(8));
|
||||
Operand srcA = GetSrcReg(context, op.SrcA);
|
||||
Operand srcB = GetSrcReg(context, op.SrcB);
|
||||
Operand srcPred = GetPredicate(context, op.SrcPred, op.SrcPredInv);
|
||||
|
||||
Operand srcOp = context.ConditionalSelect(context.BitwiseAnd(byteSel, Const(4)), op2, op1);
|
||||
Operand srcValue = context.ShiftRightU32(srcOp, context.ShiftLeft(context.BitwiseAnd(byteSel, Const(3)), Const(3)));
|
||||
Operand srcSign = context.ShiftRightS32(context.ShiftLeft(srcValue, Const(24)), Const(31));
|
||||
EmitSel(context, srcA, srcB, srcPred, op.Dest);
|
||||
}
|
||||
|
||||
srcValue = context.ConditionalSelect(copySign, srcSign, srcValue);
|
||||
srcValue = context.BitwiseAnd(srcValue, Const(0xff));
|
||||
public static void SelI(EmitterContext context)
|
||||
{
|
||||
InstSelI op = context.GetOp<InstSelI>();
|
||||
|
||||
res = context.BitfieldInsert(res, srcValue, Const(b * 8), Const(8));
|
||||
}
|
||||
Operand srcA = GetSrcReg(context, op.SrcA);
|
||||
Operand srcB = GetSrcImm(context, Imm20ToSInt(op.Imm20));
|
||||
Operand srcPred = GetPredicate(context, op.SrcPred, op.SrcPredInv);
|
||||
|
||||
context.Copy(GetDest(rd), res);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException(pMode.ToString());
|
||||
}
|
||||
EmitSel(context, srcA, srcB, srcPred, op.Dest);
|
||||
}
|
||||
|
||||
public static void SelC(EmitterContext context)
|
||||
{
|
||||
InstSelC op = context.GetOp<InstSelC>();
|
||||
|
||||
Operand srcA = GetSrcReg(context, op.SrcA);
|
||||
Operand srcB = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset);
|
||||
Operand srcPred = GetPredicate(context, op.SrcPred, op.SrcPredInv);
|
||||
|
||||
EmitSel(context, srcA, srcB, srcPred, op.Dest);
|
||||
}
|
||||
|
||||
private static void EmitR2p(EmitterContext context, Operand value, Operand mask, ByteSel byteSel, bool ccpr)
|
||||
|
||||
@@ -23,7 +23,6 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
|
||||
set => _branch = AddSuccessor(_branch, value);
|
||||
}
|
||||
|
||||
public bool HasSuccessor => _branch != null || _next != null;
|
||||
public bool HasBranch => _branch != null;
|
||||
public bool Reachable => Index == 0 || Predecessors.Count != 0;
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ namespace Ryujinx.Graphics.Shader
|
||||
public bool UsesInstanceId { get; }
|
||||
public bool UsesDrawParameters { get; }
|
||||
public bool UsesRtLayer { get; }
|
||||
public bool HasUnconditionalDiscard { get; }
|
||||
public byte ClipDistancesWritten { get; }
|
||||
public int FragmentOutputMap { get; }
|
||||
|
||||
@@ -35,7 +34,6 @@ namespace Ryujinx.Graphics.Shader
|
||||
bool usesInstanceId,
|
||||
bool usesDrawParameters,
|
||||
bool usesRtLayer,
|
||||
bool hasUnconditionalDiscard,
|
||||
byte clipDistancesWritten,
|
||||
int fragmentOutputMap)
|
||||
{
|
||||
@@ -52,7 +50,6 @@ namespace Ryujinx.Graphics.Shader
|
||||
UsesInstanceId = usesInstanceId;
|
||||
UsesDrawParameters = usesDrawParameters;
|
||||
UsesRtLayer = usesRtLayer;
|
||||
HasUnconditionalDiscard = hasUnconditionalDiscard;
|
||||
ClipDistancesWritten = clipDistancesWritten;
|
||||
FragmentOutputMap = fragmentOutputMap;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ namespace Ryujinx.Graphics.Shader
|
||||
RenderScale,
|
||||
TfeOffset,
|
||||
TfeVertexCount,
|
||||
BufferTextureOffset,
|
||||
}
|
||||
|
||||
public struct SupportBuffer
|
||||
@@ -43,13 +42,10 @@ namespace Ryujinx.Graphics.Shader
|
||||
public static readonly int ComputeRenderScaleOffset;
|
||||
public static readonly int TfeOffsetOffset;
|
||||
public static readonly int TfeVertexCountOffset;
|
||||
public static readonly int BufferTextureOffsetOffset;
|
||||
|
||||
public const int FragmentIsBgraCount = 8;
|
||||
public const int TextureCount = 64;
|
||||
|
||||
// One for the render target, 64 for the textures, and 8 for the images.
|
||||
public const int RenderScaleMaxCount = 1 + TextureCount + 8;
|
||||
public const int RenderScaleMaxCount = 1 + 64 + 8;
|
||||
|
||||
private static int OffsetOf<T>(ref SupportBuffer storage, ref T target)
|
||||
{
|
||||
@@ -72,7 +68,6 @@ namespace Ryujinx.Graphics.Shader
|
||||
ComputeRenderScaleOffset = GraphicsRenderScaleOffset + FieldSize;
|
||||
TfeOffsetOffset = OffsetOf(ref instance, ref instance.TfeOffset);
|
||||
TfeVertexCountOffset = OffsetOf(ref instance, ref instance.TfeVertexCount);
|
||||
BufferTextureOffsetOffset = OffsetOf(ref instance, ref instance.BufferTextureOffset);
|
||||
}
|
||||
|
||||
internal static StructureType GetStructureType()
|
||||
@@ -85,8 +80,7 @@ namespace Ryujinx.Graphics.Shader
|
||||
new StructureField(AggregateType.S32, "frag_scale_count"),
|
||||
new StructureField(AggregateType.Array | AggregateType.FP32, "render_scale", RenderScaleMaxCount),
|
||||
new StructureField(AggregateType.Vector4 | AggregateType.S32, "tfe_offset"),
|
||||
new StructureField(AggregateType.S32, "tfe_vertex_count"),
|
||||
new StructureField(AggregateType.Array | AggregateType.Vector2 | AggregateType.S32, "buffer_texture_offset")
|
||||
new StructureField(AggregateType.S32, "tfe_vertex_count")
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -101,7 +95,5 @@ namespace Ryujinx.Graphics.Shader
|
||||
|
||||
public Vector4<int> TfeOffset;
|
||||
public Vector4<int> TfeVertexCount;
|
||||
|
||||
public Array5<Array64<Vector4<int>>> BufferTextureOffset;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,5 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
SharedMemory = 1 << 11,
|
||||
Store = 1 << 12,
|
||||
VtgAsCompute = 1 << 13,
|
||||
UnconditionalDiscard = 1 << 14,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||
|
||||
namespace Ryujinx.Graphics.Shader.Translation
|
||||
{
|
||||
static class FeatureIdentification
|
||||
{
|
||||
public static void RunPass(BasicBlock[] blocks, ShaderStage stage, ref FeatureFlags usedFeatures)
|
||||
{
|
||||
if (stage == ShaderStage.Fragment)
|
||||
{
|
||||
bool endsWithDiscardOnly = true;
|
||||
|
||||
for (int blockIndex = 0; blockIndex < blocks.Length; blockIndex++)
|
||||
{
|
||||
BasicBlock block = blocks[blockIndex];
|
||||
|
||||
if (block.HasSuccessor)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (block.Operations.Count == 0 ||
|
||||
block.Operations.Last.Value is not Operation operation ||
|
||||
operation.Inst != Instruction.Discard)
|
||||
{
|
||||
endsWithDiscardOnly = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (endsWithDiscardOnly)
|
||||
{
|
||||
usedFeatures |= FeatureFlags.UnconditionalDiscard;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,19 +70,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
||||
|
||||
Operand bindlessHandle = texOp.GetSource(0);
|
||||
|
||||
if (bindlessHandle.AsgOp is PhiNode phi)
|
||||
{
|
||||
for (int srcIndex = 0; srcIndex < phi.SourcesCount; srcIndex++)
|
||||
{
|
||||
Operand phiSource = phi.GetSource(srcIndex);
|
||||
|
||||
if (phiSource.AsgOp is not PhiNode && !IsBindlessAccessAllowed(phiSource))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!IsBindlessAccessAllowed(bindlessHandle))
|
||||
if (!IsBindlessAccessAllowed(bindlessHandle))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -100,7 +88,10 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
||||
|
||||
texOp.SetSource(0, textureIndex);
|
||||
|
||||
bool hasSampler = !texOp.Inst.IsImage();
|
||||
// Buffer textures use a TIC/BufferView only and never consume a sampler.
|
||||
// Avoiding a sampler descriptor array here is especially important for bindless
|
||||
// texture buffers, where the array can span the entire sampler pool for no benefit.
|
||||
bool hasSampler = !texOp.Inst.IsImage() && texOp.Type != SamplerType.TextureBuffer;
|
||||
|
||||
SetBindingPair textureSetAndBinding = resourceManager.GetTextureOrImageBinding(
|
||||
texOp.Inst,
|
||||
@@ -143,26 +134,112 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
||||
|
||||
private static bool IsBindlessAccessAllowed(Operand bindlessHandle)
|
||||
{
|
||||
if (bindlessHandle.Type == OperandType.ConstantBuffer)
|
||||
{
|
||||
// Bindless access with handles from constant buffer is allowed.
|
||||
// Walk only SSA merges and integer operations that can transparently construct
|
||||
// or select a packed texture/sampler handle. Do not walk arbitrary operations:
|
||||
// finding an unrelated resource load elsewhere in the SSA graph must not enable
|
||||
// descriptor-array access for this handle.
|
||||
const int MaxVisitedOperands = 256;
|
||||
const int MaxDepth = 64;
|
||||
|
||||
return true;
|
||||
Stack<(Operand Operand, int Depth)> work = new();
|
||||
HashSet<Operand> visited = new();
|
||||
|
||||
work.Push((bindlessHandle, 0));
|
||||
|
||||
while (work.Count != 0 && visited.Count < MaxVisitedOperands)
|
||||
{
|
||||
(Operand operand, int depth) = work.Pop();
|
||||
|
||||
if (operand == null || depth > MaxDepth || !visited.Add(operand))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (operand.Type == OperandType.ConstantBuffer)
|
||||
{
|
||||
// Constant buffers are accepted by the existing direct-handle path and
|
||||
// remain accepted when SSA merges or integer handle arithmetic obscure them.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (operand.AsgOp is PhiNode phi)
|
||||
{
|
||||
for (int index = 0; index < phi.SourcesCount; index++)
|
||||
{
|
||||
Operand source = phi.GetSource(index);
|
||||
|
||||
if (source.Type != OperandType.Undefined)
|
||||
{
|
||||
work.Push((source, depth + 1));
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (operand.AsgOp is not Operation operation)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Instruction inst = operation.Inst & Instruction.Mask;
|
||||
|
||||
if (inst == Instruction.Load)
|
||||
{
|
||||
// Preserve the performance restriction. Static CBUF operands are already
|
||||
// accepted above; accept their dynamically indexed Load form as the same
|
||||
// resource class, plus the existing shader-input and storage-buffer sources.
|
||||
// Other loads remain traversal barriers and cannot authorize a pool array.
|
||||
if (operation.StorageKind == StorageKind.ConstantBuffer ||
|
||||
operation.StorageKind == StorageKind.Input ||
|
||||
operation.StorageKind == StorageKind.StorageBuffer)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!IsHandleConstructionOperation(inst))
|
||||
{
|
||||
// Texture/image operations, arbitrary loads, floating-point conversions,
|
||||
// calls and other unrelated calculations are deliberate traversal barriers.
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int index = 0; index < operation.SourcesCount; index++)
|
||||
{
|
||||
work.Push((operation.GetSource(index), depth + 1));
|
||||
}
|
||||
}
|
||||
|
||||
if (bindlessHandle.AsgOp is not Operation handleOp ||
|
||||
handleOp.Inst != Instruction.Load ||
|
||||
(handleOp.StorageKind != StorageKind.Input && handleOp.StorageKind != StorageKind.StorageBuffer))
|
||||
{
|
||||
// Right now, we only allow bindless access when the handle comes from a shader input or storage buffer.
|
||||
// This is an artificial limitation to prevent it from being used in cases where it
|
||||
// would have a large performance impact of loading all textures in the pool.
|
||||
// It might be removed in the future, if we can mitigate the performance impact.
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
private static bool IsHandleConstructionOperation(Instruction inst)
|
||||
{
|
||||
return inst is
|
||||
Instruction.Add or
|
||||
Instruction.Subtract or
|
||||
Instruction.Multiply or
|
||||
Instruction.MultiplyHighS32 or
|
||||
Instruction.MultiplyHighU32 or
|
||||
Instruction.BitwiseAnd or
|
||||
Instruction.BitwiseExclusiveOr or
|
||||
Instruction.BitwiseNot or
|
||||
Instruction.BitwiseOr or
|
||||
Instruction.BitfieldExtractS32 or
|
||||
Instruction.BitfieldExtractU32 or
|
||||
Instruction.BitfieldInsert or
|
||||
Instruction.ShiftLeft or
|
||||
Instruction.ShiftRightS32 or
|
||||
Instruction.ShiftRightU32 or
|
||||
Instruction.MinimumU32 or
|
||||
Instruction.MaximumU32 or
|
||||
Instruction.ClampU32 or
|
||||
Instruction.ConditionalSelect or
|
||||
Instruction.Copy or
|
||||
Instruction.VectorExtract;
|
||||
}
|
||||
|
||||
private static bool TryConvertBindless(BasicBlock block, ResourceManager resourceManager, IGpuAccessor gpuAccessor, TextureOperation texOp)
|
||||
|
||||
@@ -43,11 +43,6 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
private readonly Dictionary<TextureInfo, TextureMeta> _usedTextures;
|
||||
private readonly Dictionary<TextureInfo, TextureMeta> _usedImages;
|
||||
|
||||
private readonly List<BufferDefinition> _vacConstantBuffers;
|
||||
private readonly List<BufferDefinition> _vacStorageBuffers;
|
||||
private readonly List<TextureDefinition> _vacTextures;
|
||||
private readonly List<TextureDefinition> _vacImages;
|
||||
|
||||
public int LocalMemoryId { get; private set; }
|
||||
public int SharedMemoryId { get; private set; }
|
||||
|
||||
@@ -83,11 +78,6 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
_usedTextures = new();
|
||||
_usedImages = new();
|
||||
|
||||
_vacConstantBuffers = new();
|
||||
_vacStorageBuffers = new();
|
||||
_vacTextures = new();
|
||||
_vacImages = new();
|
||||
|
||||
Properties.AddOrUpdateConstantBuffer(new(BufferLayout.Std140, 0, SupportBuffer.Binding, "support_buffer", SupportBuffer.GetStructureType()));
|
||||
|
||||
LocalMemoryId = -1;
|
||||
@@ -573,76 +563,6 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
return descriptors.ToArray();
|
||||
}
|
||||
|
||||
public ShaderProgramInfo GetVertexAsComputeInfo(bool isVertex = false)
|
||||
{
|
||||
var cbDescriptors = new BufferDescriptor[_vacConstantBuffers.Count];
|
||||
int cbDescriptorIndex = 0;
|
||||
|
||||
foreach (BufferDefinition definition in _vacConstantBuffers)
|
||||
{
|
||||
cbDescriptors[cbDescriptorIndex++] = new BufferDescriptor(definition.Set, definition.Binding, 0, 0, 0, BufferUsageFlags.None);
|
||||
}
|
||||
|
||||
var sbDescriptors = new BufferDescriptor[_vacStorageBuffers.Count];
|
||||
int sbDescriptorIndex = 0;
|
||||
|
||||
foreach (BufferDefinition definition in _vacStorageBuffers)
|
||||
{
|
||||
sbDescriptors[sbDescriptorIndex++] = new BufferDescriptor(definition.Set, definition.Binding, 0, 0, 0, BufferUsageFlags.Write);
|
||||
}
|
||||
|
||||
var tDescriptors = new TextureDescriptor[_vacTextures.Count];
|
||||
int tDescriptorIndex = 0;
|
||||
|
||||
foreach (TextureDefinition definition in _vacTextures)
|
||||
{
|
||||
tDescriptors[tDescriptorIndex++] = new TextureDescriptor(
|
||||
definition.Set,
|
||||
definition.Binding,
|
||||
definition.Type,
|
||||
definition.Format,
|
||||
0,
|
||||
0,
|
||||
definition.ArrayLength,
|
||||
definition.Separate,
|
||||
definition.Flags);
|
||||
}
|
||||
|
||||
var iDescriptors = new TextureDescriptor[_vacImages.Count];
|
||||
int iDescriptorIndex = 0;
|
||||
|
||||
foreach (TextureDefinition definition in _vacImages)
|
||||
{
|
||||
iDescriptors[iDescriptorIndex++] = new TextureDescriptor(
|
||||
definition.Set,
|
||||
definition.Binding,
|
||||
definition.Type,
|
||||
definition.Format,
|
||||
0,
|
||||
0,
|
||||
definition.ArrayLength,
|
||||
definition.Separate,
|
||||
definition.Flags);
|
||||
}
|
||||
|
||||
return new ShaderProgramInfo(
|
||||
cbDescriptors,
|
||||
sbDescriptors,
|
||||
tDescriptors,
|
||||
iDescriptors,
|
||||
isVertex ? ShaderStage.Vertex : ShaderStage.Compute,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
0,
|
||||
0);
|
||||
}
|
||||
|
||||
public bool TryGetCbufSlotAndHandleForTexture(int binding, out int cbufSlot, out int handle)
|
||||
{
|
||||
foreach ((TextureInfo info, TextureMeta meta) in _usedTextures)
|
||||
@@ -707,30 +627,6 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
Properties.AddOrUpdateStorageBuffer(new(BufferLayout.Std430, setIndex, binding, name, type));
|
||||
}
|
||||
|
||||
public void AddVertexAsComputeConstantBuffer(BufferDefinition definition)
|
||||
{
|
||||
_vacConstantBuffers.Add(definition);
|
||||
Properties.AddOrUpdateConstantBuffer(definition);
|
||||
}
|
||||
|
||||
public void AddVertexAsComputeStorageBuffer(BufferDefinition definition)
|
||||
{
|
||||
_vacStorageBuffers.Add(definition);
|
||||
Properties.AddOrUpdateStorageBuffer(definition);
|
||||
}
|
||||
|
||||
public void AddVertexAsComputeTexture(TextureDefinition definition)
|
||||
{
|
||||
_vacTextures.Add(definition);
|
||||
Properties.AddOrUpdateTexture(definition);
|
||||
}
|
||||
|
||||
public void AddVertexAsComputeImage(TextureDefinition definition)
|
||||
{
|
||||
_vacImages.Add(definition);
|
||||
Properties.AddOrUpdateImage(definition);
|
||||
}
|
||||
|
||||
public static string GetShaderStagePrefix(ShaderStage stage)
|
||||
{
|
||||
uint index = (uint)stage;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||
using Ryujinx.Graphics.Shader.Translation.Optimizations;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
|
||||
@@ -28,19 +27,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
||||
|
||||
if (texOp.Type == SamplerType.TextureBuffer && !context.GpuAccessor.QueryHostSupportsSnormBufferTextureFormat())
|
||||
{
|
||||
if (!context.GpuAccessor.QueryHostSupportsSnormBufferTextureFormat())
|
||||
{
|
||||
node = InsertSnormNormalization(node, context.ResourceManager, context.GpuAccessor);
|
||||
}
|
||||
|
||||
if (!context.GpuAccessor.QueryHostSupportsBufferTexturePixelAlignment())
|
||||
{
|
||||
node = InsertCoordOffset(node, context.ResourceManager, context.Stage, isImage: false);
|
||||
}
|
||||
}
|
||||
else if (!context.GpuAccessor.QueryHostSupportsBufferTexturePixelAlignment() && texOp.Inst.IsImage())
|
||||
{
|
||||
node = InsertCoordOffset(node, context.ResourceManager, context.Stage, isImage: true);
|
||||
node = InsertSnormNormalization(node, context.ResourceManager, context.GpuAccessor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -291,87 +278,6 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
||||
return node;
|
||||
}
|
||||
|
||||
private static LinkedListNode<INode> InsertCoordOffset(LinkedListNode<INode> node, ResourceManager resourceManager, ShaderStage stage, bool isImage)
|
||||
{
|
||||
// Some GPUs have fixed alignment requirements for buffer textures.
|
||||
// For those cases, we bind the aligned buffer offset, and apply the remaining offset on the shader.
|
||||
|
||||
TextureOperation texOp = (TextureOperation)node.Value;
|
||||
|
||||
if ((texOp.Type & SamplerType.Mask) != SamplerType.TextureBuffer)
|
||||
{
|
||||
return node;
|
||||
}
|
||||
|
||||
Operand[] sources = new Operand[texOp.SourcesCount];
|
||||
|
||||
for (int i = 0; i < texOp.SourcesCount; i++)
|
||||
{
|
||||
sources[i] = texOp.GetSource(i);
|
||||
}
|
||||
|
||||
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
|
||||
bool isIndexed = resourceManager.IsArrayOfTexturesOrImages(texOp.Binding, isImage);
|
||||
|
||||
int coordsIndex = isBindless || isIndexed ? 1 : 0;
|
||||
|
||||
Operand[] dests = new Operand[texOp.DestsCount];
|
||||
|
||||
for (int i = 0; i < texOp.DestsCount; i++)
|
||||
{
|
||||
dests[i] = texOp.GetDest(i);
|
||||
}
|
||||
|
||||
LinkedListNode<INode> oldNode = node;
|
||||
|
||||
Operand source = sources[coordsIndex];
|
||||
Operand offset = Local();
|
||||
Operand coordPlusOffset = Local();
|
||||
|
||||
int stageIndex = stage switch
|
||||
{
|
||||
ShaderStage.TessellationControl => 1,
|
||||
ShaderStage.TessellationEvaluation => 2,
|
||||
ShaderStage.Geometry => 3,
|
||||
ShaderStage.Fragment => 4,
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
int bindingIndex = isImage
|
||||
? resourceManager.FindImageDescriptorIndex(texOp.Binding)
|
||||
: resourceManager.FindTextureDescriptorIndex(texOp.Binding);
|
||||
|
||||
node.List.AddBefore(node, new Operation(
|
||||
Instruction.Load,
|
||||
StorageKind.ConstantBuffer,
|
||||
offset,
|
||||
Const(SupportBuffer.Binding),
|
||||
Const((int)SupportBufferField.BufferTextureOffset),
|
||||
Const(stageIndex * SupportBuffer.TextureCount + bindingIndex),
|
||||
Const(isImage ? 1 : 0)));
|
||||
|
||||
node.List.AddBefore(node, new Operation(Instruction.Add, coordPlusOffset, source, offset));
|
||||
|
||||
sources[coordsIndex] = coordPlusOffset;
|
||||
|
||||
TextureOperation newTexOp = new(
|
||||
texOp.Inst,
|
||||
texOp.Type,
|
||||
texOp.Format,
|
||||
texOp.Flags,
|
||||
texOp.Set,
|
||||
texOp.Binding,
|
||||
texOp.Index,
|
||||
dests,
|
||||
sources);
|
||||
|
||||
node = node.List.AddBefore(node, newTexOp);
|
||||
|
||||
Utils.DeleteNode(oldNode, texOp);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private static LinkedListNode<INode> InsertConstOffsets(LinkedListNode<INode> node, ResourceManager resourceManager, IGpuAccessor gpuAccessor, ShaderStage stage)
|
||||
{
|
||||
// Non-constant texture offsets are not allowed (according to the spec),
|
||||
|
||||
@@ -301,11 +301,6 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
|
||||
Optimizer.RunPass(context);
|
||||
TransformPasses.RunPass(context);
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
FeatureIdentification.RunPass(cfg.Blocks, Definitions.Stage, ref usedFeatures);
|
||||
}
|
||||
}
|
||||
|
||||
funcs[i] = new Function(cfg.Blocks, $"fun{i}", false, inArgumentsCount, outArgumentsCount);
|
||||
@@ -358,7 +353,6 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
usedFeatures.HasFlag(FeatureFlags.InstanceId),
|
||||
usedFeatures.HasFlag(FeatureFlags.DrawParameters),
|
||||
usedFeatures.HasFlag(FeatureFlags.RtLayer),
|
||||
usedFeatures.HasFlag(FeatureFlags.UnconditionalDiscard),
|
||||
clipDistancesWritten,
|
||||
originalDefinitions.OmapTargets);
|
||||
|
||||
@@ -399,7 +393,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
{
|
||||
int binding = resourceManager.Reservations.GetTfeBufferStorageBufferBinding(i);
|
||||
BufferDefinition tfeDataBuffer = new(BufferLayout.Std430, 1, binding, $"tfe_data{i}", tfeDataStruct);
|
||||
resourceManager.AddVertexAsComputeStorageBuffer(tfeDataBuffer);
|
||||
resourceManager.Properties.AddOrUpdateStorageBuffer(tfeDataBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -407,7 +401,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
{
|
||||
int vertexInfoCbBinding = resourceManager.Reservations.VertexInfoConstantBufferBinding;
|
||||
BufferDefinition vertexInfoBuffer = new(BufferLayout.Std140, 0, vertexInfoCbBinding, "vb_info", VertexInfoBuffer.GetStructureType());
|
||||
resourceManager.AddVertexAsComputeConstantBuffer(vertexInfoBuffer);
|
||||
resourceManager.Properties.AddOrUpdateConstantBuffer(vertexInfoBuffer);
|
||||
|
||||
StructureType vertexOutputStruct = new([
|
||||
new StructureField(AggregateType.Array | AggregateType.FP32, "data", 0)
|
||||
@@ -415,13 +409,13 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
|
||||
int vertexOutputSbBinding = resourceManager.Reservations.VertexOutputStorageBufferBinding;
|
||||
BufferDefinition vertexOutputBuffer = new(BufferLayout.Std430, 1, vertexOutputSbBinding, "vertex_output", vertexOutputStruct);
|
||||
resourceManager.AddVertexAsComputeStorageBuffer(vertexOutputBuffer);
|
||||
resourceManager.Properties.AddOrUpdateStorageBuffer(vertexOutputBuffer);
|
||||
|
||||
if (Stage == ShaderStage.Vertex)
|
||||
{
|
||||
SetBindingPair ibSetAndBinding = resourceManager.Reservations.GetIndexBufferTextureSetAndBinding();
|
||||
TextureDefinition indexBuffer = new(ibSetAndBinding.SetIndex, ibSetAndBinding.Binding, "ib_data", SamplerType.TextureBuffer);
|
||||
resourceManager.AddVertexAsComputeTexture(indexBuffer);
|
||||
resourceManager.Properties.AddOrUpdateTexture(indexBuffer);
|
||||
|
||||
int inputMap = _program.AttributeUsage.UsedInputAttributes;
|
||||
|
||||
@@ -430,7 +424,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
int location = BitOperations.TrailingZeroCount(inputMap);
|
||||
SetBindingPair setAndBinding = resourceManager.Reservations.GetVertexBufferTextureSetAndBinding(location);
|
||||
TextureDefinition vaBuffer = new(setAndBinding.SetIndex, setAndBinding.Binding, $"vb_data{location}", SamplerType.TextureBuffer);
|
||||
resourceManager.AddVertexAsComputeTexture(vaBuffer);
|
||||
resourceManager.Properties.AddOrUpdateTexture(vaBuffer);
|
||||
|
||||
inputMap &= ~(1 << location);
|
||||
}
|
||||
@@ -439,11 +433,11 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
{
|
||||
SetBindingPair trbSetAndBinding = resourceManager.Reservations.GetTopologyRemapBufferTextureSetAndBinding();
|
||||
TextureDefinition remapBuffer = new(trbSetAndBinding.SetIndex, trbSetAndBinding.Binding, "trb_data", SamplerType.TextureBuffer);
|
||||
resourceManager.AddVertexAsComputeTexture(remapBuffer);
|
||||
resourceManager.Properties.AddOrUpdateTexture(remapBuffer);
|
||||
|
||||
int geometryVbOutputSbBinding = resourceManager.Reservations.GeometryVertexOutputStorageBufferBinding;
|
||||
BufferDefinition geometryVbOutputBuffer = new(BufferLayout.Std430, 1, geometryVbOutputSbBinding, "geometry_vb_output", vertexOutputStruct);
|
||||
resourceManager.AddVertexAsComputeStorageBuffer(geometryVbOutputBuffer);
|
||||
resourceManager.Properties.AddOrUpdateStorageBuffer(geometryVbOutputBuffer);
|
||||
|
||||
StructureType geometryIbOutputStruct = new([
|
||||
new StructureField(AggregateType.Array | AggregateType.U32, "data", 0)
|
||||
@@ -451,7 +445,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
|
||||
int geometryIbOutputSbBinding = resourceManager.Reservations.GeometryIndexOutputStorageBufferBinding;
|
||||
BufferDefinition geometryIbOutputBuffer = new(BufferLayout.Std430, 1, geometryIbOutputSbBinding, "geometry_ib_output", geometryIbOutputStruct);
|
||||
resourceManager.AddVertexAsComputeStorageBuffer(geometryIbOutputBuffer);
|
||||
resourceManager.Properties.AddOrUpdateStorageBuffer(geometryIbOutputBuffer);
|
||||
}
|
||||
|
||||
resourceManager.SetVertexAsComputeLocalMemories(Definitions.Stage, Definitions.InputTopology);
|
||||
@@ -484,17 +478,12 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
return new ResourceReservations(GpuAccessor, IsTransformFeedbackEmulated, vertexAsCompute: true, _vertexOutput, ioUsage);
|
||||
}
|
||||
|
||||
public ShaderProgramInfo GetVertexAsComputeInfo()
|
||||
{
|
||||
return CreateResourceManager(true).GetVertexAsComputeInfo();
|
||||
}
|
||||
|
||||
public void SetVertexOutputMapForGeometryAsCompute(TranslatorContext vertexContext)
|
||||
{
|
||||
_vertexOutput = vertexContext._program.GetIoUsage();
|
||||
}
|
||||
|
||||
public (ShaderProgram, ShaderProgramInfo) GenerateVertexPassthroughForCompute()
|
||||
public ShaderProgram GenerateVertexPassthroughForCompute()
|
||||
{
|
||||
AttributeUsage attributeUsage = new(GpuAccessor);
|
||||
ResourceManager resourceManager = new(ShaderStage.Vertex, GpuAccessor);
|
||||
@@ -506,7 +495,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
if (Stage == ShaderStage.Vertex)
|
||||
{
|
||||
BufferDefinition vertexInfoBuffer = new(BufferLayout.Std140, 0, vertexInfoCbBinding, "vb_info", VertexInfoBuffer.GetStructureType());
|
||||
resourceManager.AddVertexAsComputeConstantBuffer(vertexInfoBuffer);
|
||||
resourceManager.Properties.AddOrUpdateConstantBuffer(vertexInfoBuffer);
|
||||
}
|
||||
|
||||
StructureType vertexInputStruct = new([
|
||||
@@ -515,7 +504,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
|
||||
int vertexDataSbBinding = reservations.VertexOutputStorageBufferBinding;
|
||||
BufferDefinition vertexOutputBuffer = new(BufferLayout.Std430, 1, vertexDataSbBinding, "vb_input", vertexInputStruct);
|
||||
resourceManager.AddVertexAsComputeStorageBuffer(vertexOutputBuffer);
|
||||
resourceManager.Properties.AddOrUpdateStorageBuffer(vertexOutputBuffer);
|
||||
|
||||
EmitterContext context = new();
|
||||
|
||||
@@ -573,14 +562,14 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
LastInVertexPipeline = true
|
||||
};
|
||||
|
||||
return (Generate(
|
||||
return Generate(
|
||||
[function],
|
||||
attributeUsage,
|
||||
definitions,
|
||||
definitions,
|
||||
resourceManager,
|
||||
FeatureFlags.None,
|
||||
0), resourceManager.GetVertexAsComputeInfo(isVertex: true));
|
||||
0);
|
||||
}
|
||||
|
||||
public ShaderProgram GenerateGeometryPassthrough()
|
||||
|
||||
@@ -36,7 +36,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
queueLock,
|
||||
_gd.QueueFamilyIndex,
|
||||
_gd.IsQualcommProprietary,
|
||||
_gd.IsTurnip,
|
||||
isLight: true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private readonly Queue _queue;
|
||||
private readonly Lock _queueLock;
|
||||
private readonly bool _concurrentFenceWaitUnsupported;
|
||||
private readonly bool _fenceAlwaysWaits;
|
||||
private readonly CommandPool _pool;
|
||||
private readonly Thread _owner;
|
||||
|
||||
@@ -67,7 +66,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Lock queueLock,
|
||||
uint queueFamilyIndex,
|
||||
bool concurrentFenceWaitUnsupported,
|
||||
bool fenceAlwaysWaits,
|
||||
bool isLight = false)
|
||||
{
|
||||
_api = api;
|
||||
@@ -75,7 +73,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_queue = queue;
|
||||
_queueLock = queueLock;
|
||||
_concurrentFenceWaitUnsupported = concurrentFenceWaitUnsupported;
|
||||
_fenceAlwaysWaits = fenceAlwaysWaits;
|
||||
_owner = Thread.CurrentThread;
|
||||
|
||||
CommandPoolCreateInfo commandPoolCreateInfo = new()
|
||||
@@ -210,7 +207,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
ref ReservedCommandBuffer entry = ref _commandBuffers[index];
|
||||
|
||||
if (wait || !entry.InConsumption || entry.Fence.IsSignaledLazy())
|
||||
if (wait || !entry.InConsumption || entry.Fence.IsSignaled())
|
||||
{
|
||||
WaitAndDecrementRef(index);
|
||||
|
||||
@@ -352,7 +349,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
if (refreshFence)
|
||||
{
|
||||
entry.Fence = new FenceHolder(_api, _device, _concurrentFenceWaitUnsupported, _fenceAlwaysWaits);
|
||||
entry.Fence = new FenceHolder(_api, _device, _concurrentFenceWaitUnsupported);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -255,8 +255,8 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
||||
|
||||
scissors[0] = new Rectangle<int>(0, 0, texture.Width, texture.Height);
|
||||
|
||||
_pipeline.SetRenderTarget(texture, (uint)texture.Width, (uint)texture.Height);
|
||||
_pipeline.SetRenderTargetColorMasks(colorMasks);
|
||||
_pipeline.SetRenderTarget(texture, (uint)texture.Width, (uint)texture.Height, false);
|
||||
_pipeline.SetRenderTargetColorMasks(colorMasks, false);
|
||||
_pipeline.SetScissors(scissors);
|
||||
_pipeline.ClearRenderTargetColor(0, 0, 1, new ColorF(0f, 0f, 0f, 1f));
|
||||
}
|
||||
|
||||
@@ -238,6 +238,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Face.Back => CullModeFlags.BackBit,
|
||||
Face.Front => CullModeFlags.FrontBit,
|
||||
Face.FrontAndBack => CullModeFlags.FrontAndBack,
|
||||
Face.None => CullModeFlags.None,
|
||||
_ => LogInvalidAndReturn(face, nameof(Face), CullModeFlags.BackBit),
|
||||
};
|
||||
}
|
||||
@@ -310,6 +311,25 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
};
|
||||
}
|
||||
|
||||
public static PrimitiveTopology ConvertToClass(this PrimitiveTopology topology)
|
||||
{
|
||||
return topology switch
|
||||
{
|
||||
PrimitiveTopology.PointList => PrimitiveTopology.PointList,
|
||||
PrimitiveTopology.LineList or
|
||||
PrimitiveTopology.LineStrip or
|
||||
PrimitiveTopology.LineListWithAdjacency or
|
||||
PrimitiveTopology.LineStripWithAdjacency => PrimitiveTopology.LineList,
|
||||
PrimitiveTopology.TriangleList or
|
||||
PrimitiveTopology.TriangleStrip or
|
||||
PrimitiveTopology.TriangleFan or
|
||||
PrimitiveTopology.TriangleListWithAdjacency or
|
||||
PrimitiveTopology.TriangleStripWithAdjacency => PrimitiveTopology.TriangleStrip,
|
||||
PrimitiveTopology.PatchList => PrimitiveTopology.PatchList,
|
||||
_ => LogInvalidAndReturn(topology, nameof(PrimitiveTopology), PrimitiveTopology.TriangleStrip),
|
||||
};
|
||||
}
|
||||
|
||||
public static StencilOp Convert(this GAL.StencilOp op)
|
||||
{
|
||||
return op switch
|
||||
|
||||
@@ -12,15 +12,13 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private int _referenceCount;
|
||||
private int _lock;
|
||||
private readonly bool _concurrentWaitUnsupported;
|
||||
private readonly bool _alwaysWaits;
|
||||
private bool _disposed;
|
||||
|
||||
public unsafe FenceHolder(Vk api, Device device, bool concurrentWaitUnsupported, bool alwaysWaits)
|
||||
public unsafe FenceHolder(Vk api, Device device, bool concurrentWaitUnsupported)
|
||||
{
|
||||
_api = api;
|
||||
_device = device;
|
||||
_concurrentWaitUnsupported = concurrentWaitUnsupported;
|
||||
_alwaysWaits = alwaysWaits;
|
||||
|
||||
FenceCreateInfo fenceCreateInfo = new()
|
||||
{
|
||||
@@ -125,16 +123,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSignaledLazy()
|
||||
{
|
||||
if (_alwaysWaits)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return IsSignaled();
|
||||
}
|
||||
|
||||
public bool IsSignaled()
|
||||
{
|
||||
if (_concurrentWaitUnsupported)
|
||||
|
||||
@@ -32,6 +32,29 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
public bool HasDepthStencil { get; private set; }
|
||||
public int ColorAttachmentsCount => AttachmentsCount - (HasDepthStencil ? 1 : 0);
|
||||
|
||||
public bool SetVirtualSize(uint width, uint height, uint layers)
|
||||
{
|
||||
if (AttachmentsCount != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
width = Math.Max(1u, width);
|
||||
height = Math.Max(1u, height);
|
||||
layers = Math.Max(1u, layers);
|
||||
|
||||
if (Width == width && Height == height && Layers == layers)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Width = width;
|
||||
Height = height;
|
||||
Layers = layers;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public FramebufferParams(Device device, TextureView view, uint width, uint height)
|
||||
{
|
||||
Format format = view.Info.Format;
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
public readonly bool SupportsShaderStorageImageMultisample;
|
||||
public readonly bool SupportsConditionalRendering;
|
||||
public readonly bool SupportsExtendedDynamicState;
|
||||
public readonly PhysicalDeviceExtendedDynamicState2FeaturesEXT SupportsExtendedDynamicState2;
|
||||
public readonly bool SupportsMultiView;
|
||||
public readonly bool SupportsNullDescriptors;
|
||||
public readonly bool SupportsPushDescriptors;
|
||||
@@ -46,6 +47,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
public readonly bool SupportsViewportArray2;
|
||||
public readonly bool SupportsHostImportedMemory;
|
||||
public readonly bool SupportsDepthClipControl;
|
||||
public readonly bool SupportsWideLines;
|
||||
public readonly bool SupportsAttachmentFeedbackLoop;
|
||||
public readonly bool SupportsDynamicAttachmentFeedbackLoop;
|
||||
public readonly uint SubgroupSize;
|
||||
@@ -54,6 +56,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
public readonly uint VertexBufferAlignment;
|
||||
public readonly uint SubTexelPrecisionBits;
|
||||
public readonly ulong MinResourceAlignment;
|
||||
public readonly uint MaxTessellationPatchSize;
|
||||
public readonly ulong MinTexelBufferAlignment;
|
||||
|
||||
public HardwareCapabilities(
|
||||
@@ -72,6 +75,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
bool supportsShaderStorageImageMultisample,
|
||||
bool supportsConditionalRendering,
|
||||
bool supportsExtendedDynamicState,
|
||||
PhysicalDeviceExtendedDynamicState2FeaturesEXT supportsExtendedDynamicState2,
|
||||
uint maxTessellationPatchSize,
|
||||
bool supportsMultiView,
|
||||
bool supportsNullDescriptors,
|
||||
bool supportsPushDescriptors,
|
||||
@@ -87,6 +92,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
bool supportsViewportArray2,
|
||||
bool supportsHostImportedMemory,
|
||||
bool supportsDepthClipControl,
|
||||
bool supportsWideLines,
|
||||
bool supportsAttachmentFeedbackLoop,
|
||||
bool supportsDynamicAttachmentFeedbackLoop,
|
||||
uint subgroupSize,
|
||||
@@ -112,6 +118,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
SupportsShaderStorageImageMultisample = supportsShaderStorageImageMultisample;
|
||||
SupportsConditionalRendering = supportsConditionalRendering;
|
||||
SupportsExtendedDynamicState = supportsExtendedDynamicState;
|
||||
SupportsExtendedDynamicState2 = supportsExtendedDynamicState2;
|
||||
MaxTessellationPatchSize = maxTessellationPatchSize;
|
||||
SupportsMultiView = supportsMultiView;
|
||||
SupportsNullDescriptors = supportsNullDescriptors;
|
||||
SupportsPushDescriptors = supportsPushDescriptors;
|
||||
@@ -127,6 +135,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
SupportsViewportArray2 = supportsViewportArray2;
|
||||
SupportsHostImportedMemory = supportsHostImportedMemory;
|
||||
SupportsDepthClipControl = supportsDepthClipControl;
|
||||
SupportsWideLines = supportsWideLines;
|
||||
SupportsAttachmentFeedbackLoop = supportsAttachmentFeedbackLoop;
|
||||
SupportsDynamicAttachmentFeedbackLoop = supportsDynamicAttachmentFeedbackLoop;
|
||||
SubgroupSize = subgroupSize;
|
||||
|
||||
@@ -406,20 +406,20 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
if (dstIsDepthOrStencil)
|
||||
{
|
||||
_pipeline.SetProgram(src.Info.Target.IsMultisample() ? _programDepthBlitMs : _programDepthBlit);
|
||||
_pipeline.SetDepthTest(new DepthTestDescriptor(true, true, CompareOp.Always));
|
||||
_pipeline.SetProgram(src.Info.Target.IsMultisample ? _programDepthBlitMs : _programDepthBlit, false);
|
||||
_pipeline.SetDepthTest(new DepthTestDescriptor(true, true, CompareOp.Always), false);
|
||||
}
|
||||
else if (src.Info.Target.IsMultisample())
|
||||
else if (src.Info.Target.IsMultisample)
|
||||
{
|
||||
_pipeline.SetProgram(_programColorBlitMs);
|
||||
_pipeline.SetProgram(_programColorBlitMs, false);
|
||||
}
|
||||
else if (clearAlpha)
|
||||
{
|
||||
_pipeline.SetProgram(_programColorBlitClearAlpha);
|
||||
_pipeline.SetProgram(_programColorBlitClearAlpha, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_pipeline.SetProgram(_programColorBlit);
|
||||
_pipeline.SetProgram(_programColorBlit, false);
|
||||
}
|
||||
|
||||
int dstWidth = dst.Width;
|
||||
@@ -434,7 +434,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_pipeline.ClearRenderTargetColor(0, 0, 1, new ColorF(0f, 0f, 0f, 1f));
|
||||
}
|
||||
|
||||
_pipeline.SetViewports(viewports);
|
||||
_pipeline.SetViewports(viewports, false);
|
||||
_pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
|
||||
_pipeline.Draw(4, 1, 0, 0);
|
||||
|
||||
@@ -566,12 +566,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
if (isDepth)
|
||||
{
|
||||
_pipeline.SetProgram(src.Info.Target.IsMultisample() ? _programDepthBlitMs : _programDepthBlit);
|
||||
_pipeline.SetProgram(src.Info.Target.IsMultisample ? _programDepthBlitMs : _programDepthBlit, false);
|
||||
_pipeline.SetDepthTest(new DepthTestDescriptor(true, true, CompareOp.Always));
|
||||
}
|
||||
else
|
||||
{
|
||||
_pipeline.SetProgram(src.Info.Target.IsMultisample() ? _programStencilBlitMs : _programStencilBlit);
|
||||
_pipeline.SetProgram(src.Info.Target.IsMultisample ? _programStencilBlitMs : _programStencilBlit, false);
|
||||
_pipeline.SetStencilTest(CreateStencilTestDescriptor(true));
|
||||
}
|
||||
|
||||
@@ -771,8 +771,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
0f,
|
||||
1f);
|
||||
|
||||
pipeline.SetProgram(_programColorBlit);
|
||||
pipeline.SetViewports(viewports);
|
||||
pipeline.SetProgram(_programColorBlit, false);
|
||||
pipeline.SetViewports(viewports, false);
|
||||
pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
|
||||
pipeline.Draw(4, 1, 0, 0);
|
||||
|
||||
@@ -1229,7 +1229,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
TextureView srcView = Create2DLayerView(src, srcLayer + 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);
|
||||
|
||||
@@ -1246,7 +1246,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
else
|
||||
{
|
||||
_pipeline.SetProgram(_programColorDrawToMs);
|
||||
_pipeline.SetProgram(_programColorDrawToMs, false);
|
||||
|
||||
Format format = GetFormat(src.Info.BytesPerPixel);
|
||||
VkFormat vkFormat = FormatTable.GetFormat(format);
|
||||
@@ -1323,12 +1323,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
if (isDepth)
|
||||
{
|
||||
_pipeline.SetProgram(fromMS ? _programDepthDrawToNonMs : _programDepthDrawToMs);
|
||||
_pipeline.SetProgram(fromMS ? _programDepthDrawToNonMs : _programDepthDrawToMs, false);
|
||||
_pipeline.SetDepthTest(new DepthTestDescriptor(true, true, CompareOp.Always));
|
||||
}
|
||||
else
|
||||
{
|
||||
_pipeline.SetProgram(fromMS ? _programStencilDrawToNonMs : _programStencilDrawToMs);
|
||||
_pipeline.SetProgram(fromMS ? _programStencilDrawToNonMs : _programStencilDrawToMs, false);
|
||||
_pipeline.SetStencilTest(CreateStencilTestDescriptor(true));
|
||||
}
|
||||
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,6 +76,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private readonly BufferState[] _transformFeedbackBuffers;
|
||||
private readonly VertexBufferState[] _vertexBuffers;
|
||||
private ulong _vertexBuffersDirty;
|
||||
private bool _bindingsSet;
|
||||
protected Rectangle<int> ClearScissor;
|
||||
|
||||
private readonly VertexBufferUpdater _vertexBufferUpdater;
|
||||
@@ -89,6 +90,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private bool _tfEnabled;
|
||||
private bool _tfActive;
|
||||
|
||||
private readonly bool _supportExtDynamic;
|
||||
private readonly bool _supportExtDynamic2;
|
||||
|
||||
private FeedbackLoopAspects _feedbackLoop;
|
||||
private bool _passWritesDepthStencil;
|
||||
|
||||
@@ -96,6 +100,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
public ulong DrawCount { get; private set; }
|
||||
public bool RenderPassActive { get; private set; }
|
||||
|
||||
private readonly int[] _vertexBufferBindings;
|
||||
|
||||
public unsafe PipelineBase(VulkanRenderer gd, Device device)
|
||||
{
|
||||
Gd = gd;
|
||||
@@ -128,7 +134,19 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
_storedBlend = new PipelineColorBlendAttachmentState[Constants.MaxRenderTargets];
|
||||
|
||||
_newState.Initialize();
|
||||
_supportExtDynamic = gd.Capabilities.SupportsExtendedDynamicState;
|
||||
|
||||
_supportExtDynamic2 = gd.Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2;
|
||||
|
||||
_bindingsSet = false;
|
||||
|
||||
_vertexBufferBindings = new int[Constants.MaxVertexBuffers];
|
||||
for (int i = 0; i < Constants.MaxVertexBuffers; i++)
|
||||
{
|
||||
_vertexBufferBindings[i] = i + 1;
|
||||
}
|
||||
|
||||
_newState.Initialize(gd.Capabilities);
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
@@ -634,19 +652,46 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
if (texture is TextureView srcTexture)
|
||||
{
|
||||
CullModeFlags oldCullMode = _newState.CullMode;
|
||||
bool oldStencilTestEnable = _newState.StencilTestEnable;
|
||||
bool oldDepthTestEnable = _newState.DepthTestEnable;
|
||||
bool oldDepthWriteEnable = _newState.DepthWriteEnable;
|
||||
CullModeFlags oldCullMode;
|
||||
bool oldStencilTestEnable;
|
||||
bool oldDepthTestEnable;
|
||||
bool oldDepthWriteEnable;
|
||||
PrimitiveTopology oldTopology;
|
||||
Array16<Silk.NET.Vulkan.Viewport> oldViewports = DynamicState.Viewports;
|
||||
uint oldViewportsCount = _newState.ViewportsCount;
|
||||
PrimitiveTopology oldTopology = _topology;
|
||||
uint oldViewportsCount;
|
||||
|
||||
_newState.CullMode = CullModeFlags.None;
|
||||
_newState.StencilTestEnable = false;
|
||||
_newState.DepthTestEnable = false;
|
||||
_newState.DepthWriteEnable = false;
|
||||
SignalStateChange();
|
||||
if (_supportExtDynamic)
|
||||
{
|
||||
oldCullMode = DynamicState.CullMode;
|
||||
oldStencilTestEnable = DynamicState.StencilTestEnable;
|
||||
oldDepthTestEnable = DynamicState.DepthTestEnable;
|
||||
oldDepthWriteEnable = DynamicState.DepthWriteEnable;
|
||||
oldTopology = _topology;
|
||||
oldViewportsCount = DynamicState.ViewportsCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
oldCullMode = _newState.CullMode;
|
||||
oldStencilTestEnable = _newState.StencilTestEnable;
|
||||
oldDepthTestEnable = _newState.DepthTestEnable;
|
||||
oldDepthWriteEnable = _newState.DepthWriteEnable;
|
||||
oldTopology = _topology;
|
||||
oldViewportsCount = _newState.ViewportsCount;
|
||||
}
|
||||
|
||||
if (_supportExtDynamic)
|
||||
{
|
||||
DynamicState.SetCullMode(CullModeFlags.None);
|
||||
DynamicState.SetDepthTestBool(false, false);
|
||||
DynamicState.SetStencilTest(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_newState.CullMode = CullModeFlags.None;
|
||||
_newState.StencilTestEnable = false;
|
||||
_newState.DepthTestEnable = false;
|
||||
_newState.DepthWriteEnable = false;
|
||||
}
|
||||
|
||||
Gd.HelperShader.DrawTexture(
|
||||
Gd,
|
||||
@@ -656,16 +701,24 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
srcRegion,
|
||||
dstRegion);
|
||||
|
||||
_newState.CullMode = oldCullMode;
|
||||
_newState.StencilTestEnable = oldStencilTestEnable;
|
||||
_newState.DepthTestEnable = oldDepthTestEnable;
|
||||
_newState.DepthWriteEnable = oldDepthWriteEnable;
|
||||
if (_supportExtDynamic)
|
||||
{
|
||||
DynamicState.SetCullMode(oldCullMode);
|
||||
DynamicState.SetStencilTest(oldStencilTestEnable);
|
||||
DynamicState.SetDepthTestBool(oldDepthTestEnable, oldDepthWriteEnable);
|
||||
}
|
||||
else
|
||||
{
|
||||
_newState.CullMode = oldCullMode;
|
||||
_newState.StencilTestEnable = oldStencilTestEnable;
|
||||
_newState.DepthTestEnable = oldDepthTestEnable;
|
||||
_newState.DepthWriteEnable = oldDepthWriteEnable;
|
||||
_newState.ViewportsCount = oldViewportsCount;
|
||||
}
|
||||
|
||||
SetPrimitiveTopology(oldTopology);
|
||||
|
||||
DynamicState.SetViewports(ref oldViewports, oldViewportsCount);
|
||||
|
||||
_newState.ViewportsCount = oldViewportsCount;
|
||||
SignalStateChange();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -796,53 +849,112 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public void SetDepthBias(PolygonModeMask enables, float factor, float units, float clamp)
|
||||
{
|
||||
DynamicState.SetDepthBias(factor, units, clamp);
|
||||
bool depthBiasEnable = (enables != 0) && (factor != 0 && units != 0);
|
||||
bool changed = false;
|
||||
|
||||
_newState.DepthBiasEnable = enables != 0;
|
||||
SignalStateChange();
|
||||
}
|
||||
if (_supportExtDynamic2)
|
||||
{
|
||||
DynamicState.SetDepthBiasEnable(depthBiasEnable);
|
||||
}
|
||||
else if (_newState.DepthBiasEnable != depthBiasEnable)
|
||||
{
|
||||
_newState.DepthBiasEnable = depthBiasEnable;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
public void SetDepthClamp(bool clamp)
|
||||
{
|
||||
_newState.DepthClampEnable = clamp;
|
||||
SignalStateChange();
|
||||
}
|
||||
if (depthBiasEnable)
|
||||
{
|
||||
DynamicState.SetDepthBias(factor, units, clamp);
|
||||
}
|
||||
|
||||
public void SetDepthMode(DepthMode mode)
|
||||
{
|
||||
bool oldMode = _newState.DepthMode;
|
||||
_newState.DepthMode = mode == DepthMode.MinusOneToOne;
|
||||
if (_newState.DepthMode != oldMode)
|
||||
if (changed)
|
||||
{
|
||||
SignalStateChange();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetDepthTest(DepthTestDescriptor depthTest)
|
||||
public void SetDepthClamp(bool clamp)
|
||||
{
|
||||
_newState.DepthTestEnable = depthTest.TestEnable;
|
||||
_newState.DepthWriteEnable = depthTest.WriteEnable;
|
||||
_newState.DepthCompareOp = depthTest.Func.Convert();
|
||||
_newState.DepthClampEnable = clamp;
|
||||
|
||||
UpdatePassDepthStencil();
|
||||
SignalStateChange();
|
||||
}
|
||||
|
||||
public void SetFaceCulling(bool enable, Face face)
|
||||
public void SetDepthMode(DepthMode mode)
|
||||
{
|
||||
_newState.CullMode = enable ? face.Convert() : CullModeFlags.None;
|
||||
bool newMode = mode == DepthMode.MinusOneToOne;
|
||||
|
||||
if (_newState.DepthMode == newMode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_newState.DepthMode = newMode;
|
||||
|
||||
SignalStateChange();
|
||||
}
|
||||
|
||||
public void SetDepthTest(DepthTestDescriptor depthTest, bool signalChange = true)
|
||||
{
|
||||
if (_supportExtDynamic)
|
||||
{
|
||||
DynamicState.SetDepthTestBool(depthTest.TestEnable, depthTest.WriteEnable);
|
||||
if (depthTest.TestEnable)
|
||||
{
|
||||
DynamicState.SetDepthTestCompareOp(depthTest.Func.Convert());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_newState.DepthTestEnable = depthTest.TestEnable;
|
||||
_newState.DepthWriteEnable = depthTest.WriteEnable;
|
||||
_newState.DepthCompareOp = depthTest.Func.Convert();
|
||||
|
||||
if (signalChange)
|
||||
{
|
||||
SignalStateChange();
|
||||
}
|
||||
}
|
||||
|
||||
UpdatePassDepthStencil();
|
||||
}
|
||||
|
||||
public void SetFaceCulling(Face face)
|
||||
{
|
||||
if (_supportExtDynamic)
|
||||
{
|
||||
DynamicState.SetCullMode(face.Convert());
|
||||
}
|
||||
else
|
||||
{
|
||||
_newState.CullMode = face.Convert();
|
||||
|
||||
SignalStateChange();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetFrontFace(FrontFace frontFace)
|
||||
{
|
||||
_newState.FrontFace = frontFace.Convert();
|
||||
SignalStateChange();
|
||||
if (_supportExtDynamic)
|
||||
{
|
||||
DynamicState.SetFrontFace(frontFace.Convert());
|
||||
}
|
||||
else
|
||||
{
|
||||
_newState.FrontFace = frontFace.Convert();
|
||||
|
||||
SignalStateChange();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetImage(ShaderStage stage, int binding, ITexture image)
|
||||
{
|
||||
_descriptorSetUpdater.SetImage(Cbs, stage, binding, image);
|
||||
|
||||
if (stage == ShaderStage.Fragment && image is TextureView view)
|
||||
{
|
||||
FramebufferParams?.SetVirtualSize((uint)view.Width, (uint)view.Height, (uint)view.Layers);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetImage(int binding, Auto<DisposableImageView> image)
|
||||
@@ -876,28 +988,56 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public void SetLineParameters(float width, bool smooth)
|
||||
{
|
||||
_newState.LineWidth = width;
|
||||
SignalStateChange();
|
||||
if (!Gd.IsMoltenVk)
|
||||
{
|
||||
DynamicState.SetLineWidth(Gd.Capabilities.SupportsWideLines ? width : 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetLogicOpState(bool enable, LogicalOp op)
|
||||
{
|
||||
// Vendors other than NVIDIA have a bug where it enables logical operations even for float formats,
|
||||
// so we need to force disable them here.
|
||||
bool logicOpEnable = enable && (Gd.Vendor == Vendor.Nvidia || _newState.Internal.LogicOpsAllowed);
|
||||
|
||||
_newState.LogicOpEnable = enable;
|
||||
_newState.LogicOp = op.Convert();
|
||||
|
||||
if (Gd.Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2LogicOp)
|
||||
{
|
||||
if (logicOpEnable)
|
||||
{
|
||||
DynamicState.SetLogicOp(op.Convert());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_newState.LogicOp = op.Convert();
|
||||
}
|
||||
|
||||
SignalStateChange();
|
||||
}
|
||||
|
||||
public void SetMultisampleState(MultisampleDescriptor multisample)
|
||||
{
|
||||
_newState.AlphaToCoverageEnable = multisample.AlphaToCoverageEnable;
|
||||
|
||||
_newState.AlphaToOneEnable = multisample.AlphaToOneEnable;
|
||||
|
||||
SignalStateChange();
|
||||
}
|
||||
|
||||
public void SetPatchParameters(int vertices, ReadOnlySpan<float> defaultOuterLevel, ReadOnlySpan<float> defaultInnerLevel)
|
||||
{
|
||||
_newState.PatchControlPoints = (uint)vertices;
|
||||
SignalStateChange();
|
||||
if (Gd.Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2PatchControlPoints)
|
||||
{
|
||||
DynamicState.SetPatchControlPoints((uint)vertices);
|
||||
}
|
||||
else
|
||||
{
|
||||
_newState.PatchControlPoints = (uint)vertices;
|
||||
|
||||
SignalStateChange();
|
||||
}
|
||||
|
||||
// TODO: Default levels (likely needs emulation on shaders?)
|
||||
}
|
||||
@@ -914,12 +1054,21 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public void SetPrimitiveRestart(bool enable, int index)
|
||||
{
|
||||
_newState.PrimitiveRestartEnable = enable;
|
||||
if (_supportExtDynamic2)
|
||||
{
|
||||
DynamicState.SetPrimitiveRestartEnable(enable);
|
||||
}
|
||||
else
|
||||
{
|
||||
_newState.PrimitiveRestartEnable = enable;
|
||||
|
||||
SignalStateChange();
|
||||
}
|
||||
|
||||
// TODO: What to do about the index?
|
||||
SignalStateChange();
|
||||
}
|
||||
|
||||
public void SetPrimitiveTopology(PrimitiveTopology topology)
|
||||
public void SetPrimitiveTopology(PrimitiveTopology topology, bool signalChange = true)
|
||||
{
|
||||
_topology = topology;
|
||||
|
||||
@@ -927,10 +1076,18 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
_newState.Topology = vkTopology;
|
||||
|
||||
SignalStateChange();
|
||||
if (_supportExtDynamic)
|
||||
{
|
||||
DynamicState.SetPrimitiveTopology(vkTopology);
|
||||
}
|
||||
|
||||
if (signalChange)
|
||||
{
|
||||
SignalStateChange();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetProgram(IProgram program)
|
||||
public void SetProgram(IProgram program, bool signalChange = true)
|
||||
{
|
||||
ShaderCollection internalProgram = (ShaderCollection)program;
|
||||
PipelineShaderStageCreateInfo[] stages = internalProgram.GetInfos();
|
||||
@@ -946,7 +1103,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
stages.CopyTo(_newState.Stages.AsSpan()[..stages.Length]);
|
||||
|
||||
SignalStateChange();
|
||||
if (signalChange)
|
||||
{
|
||||
SignalStateChange();
|
||||
}
|
||||
|
||||
if (internalProgram.IsCompute)
|
||||
{
|
||||
@@ -972,18 +1132,26 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public void SetRasterizerDiscard(bool discard)
|
||||
{
|
||||
_newState.RasterizerDiscardEnable = discard;
|
||||
SignalStateChange();
|
||||
if (_supportExtDynamic2)
|
||||
{
|
||||
DynamicState.SetRasterizerDiscard(discard);
|
||||
}
|
||||
else
|
||||
{
|
||||
_newState.RasterizerDiscardEnable = discard;
|
||||
|
||||
SignalStateChange();
|
||||
}
|
||||
|
||||
if (!discard && Gd.IsQualcommProprietary)
|
||||
{
|
||||
// On Adreno, enabling rasterizer discard somehow corrupts the viewport state.
|
||||
// Force it to be updated on next use to work around this bug.
|
||||
DynamicState.ForceAllDirty();
|
||||
DynamicState.ForceAllDirty(Gd);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMask)
|
||||
public void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMask, bool signalChange = true)
|
||||
{
|
||||
int count = Math.Min(Constants.MaxRenderTargets, componentMask.Length);
|
||||
int writtenAttachments = 0;
|
||||
@@ -1025,7 +1193,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
else
|
||||
{
|
||||
SignalStateChange();
|
||||
if (signalChange)
|
||||
{
|
||||
SignalStateChange();
|
||||
}
|
||||
|
||||
if (writtenAttachments != _writtenAttachmentCount)
|
||||
{
|
||||
@@ -1049,7 +1220,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
SetRenderTargetsInternal(colors, depthStencil, Gd.IsTBDR);
|
||||
}
|
||||
|
||||
public void SetScissors(ReadOnlySpan<Rectangle<int>> regions)
|
||||
public void SetScissors(ReadOnlySpan<Rectangle<int>> regions, bool signalChange = true)
|
||||
{
|
||||
int maxScissors = Gd.Capabilities.SupportsMultiView ? Constants.MaxViewports : 1;
|
||||
int count = Math.Min(maxScissors, regions.Length);
|
||||
@@ -1058,6 +1229,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
ClearScissor = regions[0];
|
||||
}
|
||||
|
||||
DynamicState.ScissorsCount = count;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
Rectangle<int> region = regions[i];
|
||||
@@ -1067,34 +1240,56 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
DynamicState.SetScissor(i, new Rect2D(offset, extent));
|
||||
}
|
||||
|
||||
DynamicState.ScissorsCount = count;
|
||||
if (!_supportExtDynamic)
|
||||
{
|
||||
_newState.ScissorsCount = (uint)count;
|
||||
|
||||
_newState.ScissorsCount = (uint)count;
|
||||
SignalStateChange();
|
||||
if (signalChange)
|
||||
{
|
||||
SignalStateChange();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetStencilTest(StencilTestDescriptor stencilTest)
|
||||
{
|
||||
DynamicState.SetStencilMasks(
|
||||
(uint)stencilTest.BackFuncMask,
|
||||
if (_supportExtDynamic)
|
||||
{
|
||||
DynamicState.SetStencilTestandOp(
|
||||
stencilTest.BackSFail.Convert(),
|
||||
stencilTest.BackDpPass.Convert(),
|
||||
stencilTest.BackDpFail.Convert(),
|
||||
stencilTest.BackFunc.Convert(),
|
||||
stencilTest.FrontSFail.Convert(),
|
||||
stencilTest.FrontDpPass.Convert(),
|
||||
stencilTest.FrontDpFail.Convert(),
|
||||
stencilTest.FrontFunc.Convert(),
|
||||
stencilTest.TestEnable);
|
||||
|
||||
UpdatePassDepthStencil();
|
||||
}
|
||||
else
|
||||
{
|
||||
_newState.StencilBackFailOp = stencilTest.BackSFail.Convert();
|
||||
_newState.StencilBackPassOp = stencilTest.BackDpPass.Convert();
|
||||
_newState.StencilBackDepthFailOp = stencilTest.BackDpFail.Convert();
|
||||
_newState.StencilBackCompareOp = stencilTest.BackFunc.Convert();
|
||||
_newState.StencilFrontFailOp = stencilTest.FrontSFail.Convert();
|
||||
_newState.StencilFrontPassOp = stencilTest.FrontDpPass.Convert();
|
||||
_newState.StencilFrontDepthFailOp = stencilTest.FrontDpFail.Convert();
|
||||
_newState.StencilFrontCompareOp = stencilTest.FrontFunc.Convert();
|
||||
_newState.StencilTestEnable = stencilTest.TestEnable;
|
||||
|
||||
UpdatePassDepthStencil();
|
||||
SignalStateChange();
|
||||
}
|
||||
|
||||
DynamicState.SetStencilMask((uint)stencilTest.BackFuncMask,
|
||||
(uint)stencilTest.BackMask,
|
||||
(uint)stencilTest.BackFuncRef,
|
||||
(uint)stencilTest.FrontFuncMask,
|
||||
(uint)stencilTest.FrontMask,
|
||||
(uint)stencilTest.FrontFuncRef);
|
||||
|
||||
_newState.StencilTestEnable = stencilTest.TestEnable;
|
||||
_newState.StencilBackFailOp = stencilTest.BackSFail.Convert();
|
||||
_newState.StencilBackPassOp = stencilTest.BackDpPass.Convert();
|
||||
_newState.StencilBackDepthFailOp = stencilTest.BackDpFail.Convert();
|
||||
_newState.StencilBackCompareOp = stencilTest.BackFunc.Convert();
|
||||
_newState.StencilFrontFailOp = stencilTest.FrontSFail.Convert();
|
||||
_newState.StencilFrontPassOp = stencilTest.FrontDpPass.Convert();
|
||||
_newState.StencilFrontDepthFailOp = stencilTest.FrontDpFail.Convert();
|
||||
_newState.StencilFrontCompareOp = stencilTest.FrontFunc.Convert();
|
||||
|
||||
UpdatePassDepthStencil();
|
||||
SignalStateChange();
|
||||
}
|
||||
|
||||
public void SetStorageBuffers(ReadOnlySpan<BufferAssignment> buffers)
|
||||
@@ -1215,12 +1410,24 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
int count = Math.Min(Constants.MaxVertexBuffers, vertexBuffers.Length);
|
||||
|
||||
_newState.Internal.VertexBindingDescriptions[0] = new VertexInputBindingDescription(0, 0, VertexInputRate.Vertex);
|
||||
|
||||
int validCount = 1;
|
||||
|
||||
if (!_bindingsSet)
|
||||
{
|
||||
_newState.Internal.VertexBindingDescriptions[0] = new VertexInputBindingDescription(0, _supportExtDynamic && (!Gd.IsMoltenVk || Gd.SupportsMTL31) ? null : 0, VertexInputRate.Vertex);
|
||||
|
||||
for (int i = 1; i < count; i++)
|
||||
{
|
||||
_newState.Internal.VertexBindingDescriptions[i] = new VertexInputBindingDescription((uint)i);
|
||||
}
|
||||
|
||||
_bindingsSet = true;
|
||||
}
|
||||
|
||||
BufferHandle lastHandle = default;
|
||||
Auto<DisposableBuffer> lastBuffer = null;
|
||||
bool vertexBindingDescriptionChanged = false;
|
||||
bool vertexDescriptionCountChanged = false;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
@@ -1239,13 +1446,32 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
if (vb != null)
|
||||
{
|
||||
int binding = i + 1;
|
||||
int descriptorIndex = validCount++;
|
||||
|
||||
_newState.Internal.VertexBindingDescriptions[descriptorIndex] = new VertexInputBindingDescription(
|
||||
(uint)binding,
|
||||
(uint)vertexBuffer.Stride,
|
||||
inputRate);
|
||||
if (_supportExtDynamic && (!Gd.IsMoltenVk || Gd.SupportsMTL31))
|
||||
{
|
||||
if (_newState.Internal.VertexBindingDescriptions[descriptorIndex].InputRate != inputRate ||
|
||||
_newState.Internal.VertexBindingDescriptions[descriptorIndex].Binding != _vertexBufferBindings[i])
|
||||
{
|
||||
_newState.Internal.VertexBindingDescriptions[descriptorIndex].InputRate = inputRate;
|
||||
_newState.Internal.VertexBindingDescriptions[descriptorIndex].Binding = (uint)_vertexBufferBindings[i];
|
||||
|
||||
vertexBindingDescriptionChanged = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_newState.Internal.VertexBindingDescriptions[descriptorIndex].InputRate != inputRate ||
|
||||
_newState.Internal.VertexBindingDescriptions[descriptorIndex].Stride != vertexBuffer.Stride ||
|
||||
_newState.Internal.VertexBindingDescriptions[descriptorIndex].Binding != _vertexBufferBindings[i])
|
||||
{
|
||||
_newState.Internal.VertexBindingDescriptions[descriptorIndex].Binding = (uint)_vertexBufferBindings[i];
|
||||
_newState.Internal.VertexBindingDescriptions[descriptorIndex].Stride = (uint)vertexBuffer.Stride;
|
||||
_newState.Internal.VertexBindingDescriptions[descriptorIndex].InputRate = inputRate;
|
||||
|
||||
vertexBindingDescriptionChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
int vbSize = vertexBuffer.Buffer.Size;
|
||||
|
||||
@@ -1261,7 +1487,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
ref VertexBufferState buffer = ref _vertexBuffers[binding];
|
||||
ref VertexBufferState buffer = ref _vertexBuffers[_vertexBufferBindings[i]];
|
||||
int oldScalarAlign = buffer.AttributeScalarAlignment;
|
||||
|
||||
if (Gd.Capabilities.VertexBufferAlignment < 2 &&
|
||||
@@ -1278,7 +1504,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
vbSize,
|
||||
vertexBuffer.Stride);
|
||||
|
||||
buffer.BindVertexBuffer(Gd, Cbs, (uint)binding, ref _newState, _vertexBufferUpdater);
|
||||
buffer.BindVertexBuffer(Gd, Cbs, (uint)_vertexBufferBindings[i], ref _newState, _vertexBufferUpdater);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -1294,7 +1520,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
vbSize,
|
||||
vertexBuffer.Stride);
|
||||
|
||||
_vertexBuffersDirty |= 1UL << binding;
|
||||
_vertexBuffersDirty |= 1UL << _vertexBufferBindings[i];
|
||||
}
|
||||
|
||||
buffer.AttributeScalarAlignment = oldScalarAlign;
|
||||
@@ -1304,11 +1530,19 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
_vertexBufferUpdater.Commit(Cbs);
|
||||
|
||||
_newState.VertexBindingDescriptionsCount = (uint)validCount;
|
||||
SignalStateChange();
|
||||
if (_newState.VertexBindingDescriptionsCount != validCount)
|
||||
{
|
||||
_newState.VertexBindingDescriptionsCount = (uint)validCount;
|
||||
vertexDescriptionCountChanged = true;
|
||||
}
|
||||
|
||||
if (vertexDescriptionCountChanged || vertexBindingDescriptionChanged)
|
||||
{
|
||||
SignalStateChange();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetViewports(ReadOnlySpan<Viewport> viewports)
|
||||
public void SetViewports(ReadOnlySpan<Viewport> viewports, bool signalChange = true)
|
||||
{
|
||||
int maxViewports = Gd.Capabilities.SupportsMultiView ? Constants.MaxViewports : 1;
|
||||
int count = Math.Min(maxViewports, viewports.Length);
|
||||
@@ -1333,8 +1567,15 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Clamp(viewport.DepthFar)));
|
||||
}
|
||||
|
||||
_newState.ViewportsCount = (uint)count;
|
||||
SignalStateChange();
|
||||
if (!_supportExtDynamic)
|
||||
{
|
||||
_newState.ViewportsCount = (uint)count;
|
||||
|
||||
if (signalChange)
|
||||
{
|
||||
SignalStateChange();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SwapBuffer(Auto<DisposableBuffer> from, Auto<DisposableBuffer> to)
|
||||
@@ -1383,7 +1624,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_vertexBuffersDirty = ulong.MaxValue >> (64 - _vertexBuffers.Length);
|
||||
|
||||
_descriptorSetUpdater.SignalCommandBufferChange();
|
||||
DynamicState.ForceAllDirty();
|
||||
DynamicState.ForceAllDirty(Gd);
|
||||
_currentPipelineHandle = 0;
|
||||
}
|
||||
|
||||
@@ -1541,8 +1782,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
if (Gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop)
|
||||
{
|
||||
DynamicState.SetFeedbackLoop(aspects);
|
||||
_newState.FeedbackLoopDynamicState = true;
|
||||
}
|
||||
else
|
||||
else if (Gd.Capabilities.SupportsAttachmentFeedbackLoop)
|
||||
{
|
||||
_newState.FeedbackLoopAspects = aspects;
|
||||
}
|
||||
@@ -1602,11 +1844,36 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
|
||||
// Stencil test being enabled doesn't necessarily mean a write, but it's not critical to check.
|
||||
_passWritesDepthStencil |= (_newState.DepthTestEnable && _newState.DepthWriteEnable) || _newState.StencilTestEnable;
|
||||
if (_supportExtDynamic)
|
||||
{
|
||||
_passWritesDepthStencil |= (DynamicState.DepthTestEnable && DynamicState.DepthWriteEnable) || DynamicState.StencilTestEnable;
|
||||
}
|
||||
else
|
||||
{
|
||||
_passWritesDepthStencil |= (_newState.DepthTestEnable && _newState.DepthWriteEnable) || _newState.StencilTestEnable;
|
||||
}
|
||||
}
|
||||
|
||||
private bool RecreateGraphicsPipelineIfNeeded()
|
||||
{
|
||||
if (FramebufferParams != null &&
|
||||
FramebufferParams.AttachmentsCount == 0 &&
|
||||
FramebufferParams.Width == 1 &&
|
||||
FramebufferParams.Height == 1 &&
|
||||
DynamicState.ViewportsCount != 0)
|
||||
{
|
||||
// An attachmentless fragment pass can reach its first draw before the storage
|
||||
// image descriptor is rebound. At that point the null framebuffer still has
|
||||
// its constructor fallback of 1x1, even though the guest viewport already
|
||||
// describes the real render area. Seed the virtual framebuffer from that
|
||||
// viewport so the first storage-image draw is not clipped to one pixel.
|
||||
Silk.NET.Vulkan.Viewport viewport = DynamicState.Viewports[0];
|
||||
uint width = (uint)Math.Max(1f, Math.Abs(viewport.Width));
|
||||
uint height = (uint)Math.Max(1f, Math.Abs(viewport.Height));
|
||||
|
||||
FramebufferParams.SetVirtualSize(width, height, 1);
|
||||
}
|
||||
|
||||
if (AutoFlush.ShouldFlushDraw(DrawCount))
|
||||
{
|
||||
Gd.FlushAllCommands();
|
||||
|
||||
@@ -5,6 +5,7 @@ using System;
|
||||
using VulkanFormat = Silk.NET.Vulkan.Format;
|
||||
using GALFormat = Ryujinx.Graphics.GAL.Format;
|
||||
using PolygonMode = Silk.NET.Vulkan.PolygonMode;
|
||||
using PrimitiveTopology = Ryujinx.Graphics.GAL.PrimitiveTopology;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
@@ -164,64 +165,84 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public static PipelineState ToVulkanPipelineState(this ProgramPipelineState state, VulkanRenderer gd)
|
||||
{
|
||||
var extendedDynamicState2 = gd.Capabilities.SupportsExtendedDynamicState2;
|
||||
var extendedDynamicState = gd.Capabilities.SupportsExtendedDynamicState;
|
||||
|
||||
PipelineState pipeline = new();
|
||||
pipeline.Initialize();
|
||||
pipeline.Initialize(gd.Capabilities);
|
||||
|
||||
// It is assumed that Dynamic State is enabled when this conversion is used.
|
||||
|
||||
pipeline.CullMode = state.CullEnable ? state.CullMode.Convert() : CullModeFlags.None;
|
||||
|
||||
pipeline.DepthBoundsTestEnable = false; // Not implemented.
|
||||
|
||||
pipeline.DepthClampEnable = state.DepthClampEnable;
|
||||
|
||||
pipeline.DepthTestEnable = state.DepthTest.TestEnable;
|
||||
pipeline.DepthWriteEnable = state.DepthTest.WriteEnable;
|
||||
pipeline.DepthCompareOp = state.DepthTest.Func.Convert();
|
||||
pipeline.AlphaToCoverageEnable = state.AlphaToCoverageEnable;
|
||||
pipeline.AlphaToOneEnable = state.AlphaToOneEnable;
|
||||
|
||||
pipeline.DepthMode = state.DepthMode == DepthMode.MinusOneToOne;
|
||||
|
||||
pipeline.FrontFace = state.FrontFace.Convert();
|
||||
|
||||
pipeline.HasDepthStencil = state.DepthStencilEnable;
|
||||
pipeline.LineWidth = state.LineWidth;
|
||||
pipeline.LogicOpEnable = state.LogicOpEnable;
|
||||
pipeline.LogicOp = state.LogicOp.Convert();
|
||||
|
||||
pipeline.PatchControlPoints = state.PatchControlPoints;
|
||||
pipeline.PolygonMode = PolygonMode.Fill; // Not implemented.
|
||||
pipeline.PrimitiveRestartEnable = state.PrimitiveRestartEnable;
|
||||
pipeline.RasterizerDiscardEnable = state.RasterizerDiscard;
|
||||
pipeline.SamplesCount = (uint)state.SamplesCount;
|
||||
|
||||
if (gd.Capabilities.SupportsMultiView)
|
||||
{
|
||||
pipeline.ScissorsCount = Constants.MaxViewports;
|
||||
pipeline.ViewportsCount = Constants.MaxViewports;
|
||||
}
|
||||
else
|
||||
{
|
||||
pipeline.ScissorsCount = 1;
|
||||
pipeline.ViewportsCount = 1;
|
||||
}
|
||||
|
||||
pipeline.DepthBiasEnable = state.BiasEnable != 0;
|
||||
|
||||
// Stencil masks and ref are dynamic, so are 0 in the Vulkan pipeline.
|
||||
|
||||
pipeline.StencilFrontFailOp = state.StencilTest.FrontSFail.Convert();
|
||||
pipeline.StencilFrontPassOp = state.StencilTest.FrontDpPass.Convert();
|
||||
pipeline.StencilFrontDepthFailOp = state.StencilTest.FrontDpFail.Convert();
|
||||
pipeline.StencilFrontCompareOp = state.StencilTest.FrontFunc.Convert();
|
||||
|
||||
pipeline.StencilBackFailOp = state.StencilTest.BackSFail.Convert();
|
||||
pipeline.StencilBackPassOp = state.StencilTest.BackDpPass.Convert();
|
||||
pipeline.StencilBackDepthFailOp = state.StencilTest.BackDpFail.Convert();
|
||||
pipeline.StencilBackCompareOp = state.StencilTest.BackFunc.Convert();
|
||||
|
||||
pipeline.StencilTestEnable = state.StencilTest.TestEnable;
|
||||
|
||||
pipeline.Topology = gd.TopologyRemap(state.Topology).Convert();
|
||||
|
||||
if (!extendedDynamicState)
|
||||
{
|
||||
pipeline.DepthCompareOp = state.DepthTest.Func.Convert();
|
||||
pipeline.CullMode = state.CullMode.Convert();
|
||||
|
||||
pipeline.DepthTestEnable = state.DepthTest.TestEnable;
|
||||
pipeline.DepthWriteEnable = state.DepthTest.WriteEnable;
|
||||
|
||||
pipeline.FrontFace = state.FrontFace.Convert();
|
||||
|
||||
if (gd.Capabilities.SupportsMultiView)
|
||||
{
|
||||
pipeline.ScissorsCount = Constants.MaxViewports;
|
||||
pipeline.ViewportsCount = Constants.MaxViewports;
|
||||
}
|
||||
else
|
||||
{
|
||||
pipeline.ScissorsCount = 1;
|
||||
pipeline.ViewportsCount = 1;
|
||||
}
|
||||
|
||||
pipeline.StencilTestEnable = state.StencilTest.TestEnable;
|
||||
|
||||
pipeline.StencilFrontFailOp = state.StencilTest.FrontSFail.Convert();
|
||||
pipeline.StencilFrontPassOp = state.StencilTest.FrontDpPass.Convert();
|
||||
pipeline.StencilFrontDepthFailOp = state.StencilTest.FrontDpFail.Convert();
|
||||
pipeline.StencilFrontCompareOp = state.StencilTest.FrontFunc.Convert();
|
||||
|
||||
pipeline.StencilBackFailOp = state.StencilTest.BackSFail.Convert();
|
||||
pipeline.StencilBackPassOp = state.StencilTest.BackDpPass.Convert();
|
||||
pipeline.StencilBackDepthFailOp = state.StencilTest.BackDpFail.Convert();
|
||||
pipeline.StencilBackCompareOp = state.StencilTest.BackFunc.Convert();
|
||||
}
|
||||
|
||||
if (!extendedDynamicState2.ExtendedDynamicState2)
|
||||
{
|
||||
pipeline.PrimitiveRestartEnable = state.PrimitiveRestartEnable;
|
||||
pipeline.RasterizerDiscardEnable = state.RasterizerDiscard;
|
||||
pipeline.DepthBiasEnable = (state.BiasEnable != 0) &&
|
||||
(state.DepthBiasFactor != 0 && state.DepthBiasUnits != 0);
|
||||
}
|
||||
|
||||
if (!extendedDynamicState2.ExtendedDynamicState2LogicOp)
|
||||
{
|
||||
pipeline.LogicOp = state.LogicOp.Convert();
|
||||
}
|
||||
|
||||
if (!extendedDynamicState2.ExtendedDynamicState2PatchControlPoints)
|
||||
{
|
||||
pipeline.PatchControlPoints = state.PatchControlPoints;
|
||||
}
|
||||
|
||||
pipeline.SamplesCount = (uint)state.SamplesCount;
|
||||
|
||||
pipeline.LogicOpEnable = state.LogicOpEnable;
|
||||
|
||||
int vaCount = Math.Min(Constants.MaxVertexAttributes, state.VertexAttribCount);
|
||||
int vbCount = Math.Min(Constants.MaxVertexBuffers, state.VertexBufferCount);
|
||||
|
||||
@@ -247,7 +268,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
|
||||
int descriptorIndex = 1;
|
||||
pipeline.Internal.VertexBindingDescriptions[0] = new VertexInputBindingDescription(0, 0, VertexInputRate.Vertex);
|
||||
pipeline.Internal.VertexBindingDescriptions[0] = new VertexInputBindingDescription(0, extendedDynamicState && (!gd.IsMoltenVk || gd.SupportsMTL31) ? null : 0, VertexInputRate.Vertex);
|
||||
|
||||
Span<BufferPipelineDescriptor> vertexBuffersSpan = state.VertexBuffers.AsSpan();
|
||||
Span<VertexInputBindingDescription> vertexBindingDescriptionsSpan = pipeline.Internal.VertexBindingDescriptions.AsSpan();
|
||||
@@ -270,7 +291,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
// TODO: Support divisor > 1
|
||||
vertexBindingDescriptionsSpan[descriptorIndex++] = new VertexInputBindingDescription(
|
||||
(uint)i + 1,
|
||||
(uint)alignedStride,
|
||||
extendedDynamicState && (!gd.IsMoltenVk || gd.SupportsMTL31) ? null : (uint)alignedStride,
|
||||
inputRate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using Ryujinx.Common.Memory;
|
||||
using Silk.NET.Vulkan;
|
||||
using Silk.NET.Vulkan.Extensions.EXT;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
@@ -10,6 +11,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private float _depthBiasSlopeFactor;
|
||||
private float _depthBiasConstantFactor;
|
||||
private float _depthBiasClamp;
|
||||
private bool _depthBiasEnable;
|
||||
|
||||
public int ScissorsCount;
|
||||
private Array16<Rect2D> _scissors;
|
||||
@@ -21,6 +23,23 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private uint _frontWriteMask;
|
||||
private uint _frontReference;
|
||||
|
||||
private StencilOp _backFailOp;
|
||||
private StencilOp _backPassOp;
|
||||
private StencilOp _backDepthFailOp;
|
||||
private CompareOp _backCompareOp;
|
||||
private StencilOp _frontFailOp;
|
||||
private StencilOp _frontPassOp;
|
||||
private StencilOp _frontDepthFailOp;
|
||||
private CompareOp _frontCompareOp;
|
||||
|
||||
private float _lineWidth;
|
||||
|
||||
public bool StencilTestEnable;
|
||||
|
||||
public bool DepthTestEnable;
|
||||
public bool DepthWriteEnable;
|
||||
private CompareOp _depthCompareOp;
|
||||
|
||||
private Array4<float> _blendConstants;
|
||||
|
||||
private FeedbackLoopAspects _feedbackLoopAspects;
|
||||
@@ -28,6 +47,19 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
public uint ViewportsCount;
|
||||
public Array16<Viewport> Viewports;
|
||||
|
||||
public CullModeFlags CullMode;
|
||||
private FrontFace _frontFace;
|
||||
|
||||
private bool _discard;
|
||||
|
||||
private LogicOp _logicOp;
|
||||
|
||||
private uint _patchControlPoints;
|
||||
|
||||
public PrimitiveTopology Topology;
|
||||
|
||||
private bool _primitiveRestartEnable;
|
||||
|
||||
[Flags]
|
||||
private enum DirtyFlags
|
||||
{
|
||||
@@ -38,7 +70,21 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Stencil = 1 << 3,
|
||||
Viewport = 1 << 4,
|
||||
FeedbackLoop = 1 << 5,
|
||||
All = Blend | DepthBias | Scissor | Stencil | Viewport | FeedbackLoop,
|
||||
CullMode = 1 << 6,
|
||||
FrontFace = 1 << 7,
|
||||
DepthTestBool = 1 << 8,
|
||||
DepthTestCompareOp = 1 << 9,
|
||||
StencilTestEnableAndStencilOp = 1 << 10,
|
||||
LineWidth = 1 << 11,
|
||||
RasterDiscard = 1 << 12,
|
||||
LogicOp = 1 << 13,
|
||||
PatchControlPoints = 1 << 14,
|
||||
PrimitiveRestart = 1 << 15,
|
||||
PrimitiveTopology = 1 << 16,
|
||||
DepthBiasEnable = 1 << 17,
|
||||
Standard = Blend | DepthBias | Scissor | Stencil | Viewport | FeedbackLoop,
|
||||
Extended = CullMode | FrontFace | DepthTestBool | DepthTestCompareOp | StencilTestEnableAndStencilOp | PrimitiveTopology,
|
||||
Extended2 = RasterDiscard | PrimitiveRestart | DepthBiasEnable,
|
||||
}
|
||||
|
||||
private DirtyFlags _dirty;
|
||||
@@ -49,7 +95,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_blendConstants[1] = g;
|
||||
_blendConstants[2] = b;
|
||||
_blendConstants[3] = a;
|
||||
|
||||
_dirty |= DirtyFlags.Blend;
|
||||
}
|
||||
|
||||
@@ -62,15 +107,64 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_dirty |= DirtyFlags.DepthBias;
|
||||
}
|
||||
|
||||
public void SetDepthBiasEnable(bool enable)
|
||||
{
|
||||
_depthBiasEnable = enable;
|
||||
_dirty |= DirtyFlags.DepthBiasEnable;
|
||||
}
|
||||
|
||||
public void SetScissor(int index, Rect2D scissor)
|
||||
{
|
||||
_scissors[index] = scissor;
|
||||
|
||||
_dirty |= DirtyFlags.Scissor;
|
||||
}
|
||||
|
||||
public void SetStencilMasks(
|
||||
uint backCompareMask,
|
||||
public void SetDepthTestBool(bool testEnable, bool writeEnable)
|
||||
{
|
||||
DepthTestEnable = testEnable;
|
||||
DepthWriteEnable = writeEnable;
|
||||
_dirty |= DirtyFlags.DepthTestBool;
|
||||
}
|
||||
|
||||
public void SetDepthTestCompareOp(CompareOp depthTestOp)
|
||||
{
|
||||
_depthCompareOp = depthTestOp;
|
||||
_dirty |= DirtyFlags.DepthTestCompareOp;
|
||||
}
|
||||
|
||||
public void SetStencilTestandOp(
|
||||
StencilOp backFailOp,
|
||||
StencilOp backPassOp,
|
||||
StencilOp backDepthFailOp,
|
||||
CompareOp backCompareOp,
|
||||
StencilOp frontFailOp,
|
||||
StencilOp frontPassOp,
|
||||
StencilOp frontDepthFailOp,
|
||||
CompareOp frontCompareOp,
|
||||
bool stencilTestEnable)
|
||||
{
|
||||
_backFailOp = backFailOp;
|
||||
_backPassOp = backPassOp;
|
||||
_backDepthFailOp = backDepthFailOp;
|
||||
_backCompareOp = backCompareOp;
|
||||
_frontFailOp = frontFailOp;
|
||||
_frontPassOp = frontPassOp;
|
||||
_frontDepthFailOp = frontDepthFailOp;
|
||||
_frontCompareOp = frontCompareOp;
|
||||
|
||||
StencilTestEnable = stencilTestEnable;
|
||||
|
||||
_dirty |= DirtyFlags.StencilTestEnableAndStencilOp;
|
||||
}
|
||||
|
||||
public void SetStencilTest(bool stencilTestEnable)
|
||||
{
|
||||
StencilTestEnable = stencilTestEnable;
|
||||
|
||||
_dirty |= DirtyFlags.StencilTestEnableAndStencilOp;
|
||||
}
|
||||
|
||||
public void SetStencilMask(uint backCompareMask,
|
||||
uint backWriteMask,
|
||||
uint backReference,
|
||||
uint frontCompareMask,
|
||||
@@ -83,28 +177,46 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_frontCompareMask = frontCompareMask;
|
||||
_frontWriteMask = frontWriteMask;
|
||||
_frontReference = frontReference;
|
||||
|
||||
_dirty |= DirtyFlags.Stencil;
|
||||
}
|
||||
|
||||
public void SetViewport(int index, Viewport viewport)
|
||||
{
|
||||
Viewports[index] = viewport;
|
||||
|
||||
_dirty |= DirtyFlags.Viewport;
|
||||
}
|
||||
|
||||
public void SetViewports(ref Array16<Viewport> viewports, uint viewportsCount)
|
||||
{
|
||||
Viewports = viewports;
|
||||
ViewportsCount = viewportsCount;
|
||||
|
||||
if (ViewportsCount != 0)
|
||||
if (!Viewports.Equals(viewports) || ViewportsCount != viewportsCount)
|
||||
{
|
||||
_dirty |= DirtyFlags.Viewport;
|
||||
Viewports = viewports;
|
||||
ViewportsCount = viewportsCount;
|
||||
if (ViewportsCount != 0)
|
||||
{
|
||||
_dirty |= DirtyFlags.Viewport;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetCullMode(CullModeFlags cullMode)
|
||||
{
|
||||
CullMode = cullMode;
|
||||
_dirty |= DirtyFlags.CullMode;
|
||||
}
|
||||
|
||||
public void SetFrontFace(FrontFace frontFace)
|
||||
{
|
||||
_frontFace = frontFace;
|
||||
_dirty |= DirtyFlags.FrontFace;
|
||||
}
|
||||
|
||||
public void SetLineWidth(float width)
|
||||
{
|
||||
_lineWidth = width;
|
||||
_dirty |= DirtyFlags.LineWidth;
|
||||
}
|
||||
|
||||
public void SetFeedbackLoop(FeedbackLoopAspects aspects)
|
||||
{
|
||||
_feedbackLoopAspects = aspects;
|
||||
@@ -112,43 +224,149 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_dirty |= DirtyFlags.FeedbackLoop;
|
||||
}
|
||||
|
||||
public void ForceAllDirty()
|
||||
public void SetRasterizerDiscard(bool discard)
|
||||
{
|
||||
_dirty = DirtyFlags.All;
|
||||
_discard = discard;
|
||||
_dirty |= DirtyFlags.RasterDiscard;
|
||||
}
|
||||
|
||||
public void SetPrimitiveRestartEnable(bool primitiveRestart)
|
||||
{
|
||||
_primitiveRestartEnable = primitiveRestart;
|
||||
_dirty |= DirtyFlags.PrimitiveRestart;
|
||||
}
|
||||
|
||||
public void SetPrimitiveTopology(PrimitiveTopology primitiveTopology)
|
||||
{
|
||||
Topology = primitiveTopology;
|
||||
_dirty |= DirtyFlags.PrimitiveTopology;
|
||||
}
|
||||
|
||||
public void SetLogicOp(LogicOp op)
|
||||
{
|
||||
_logicOp = op;
|
||||
_dirty |= DirtyFlags.LogicOp;
|
||||
}
|
||||
|
||||
public void SetPatchControlPoints(uint points)
|
||||
{
|
||||
_patchControlPoints = points;
|
||||
_dirty |= DirtyFlags.PatchControlPoints;
|
||||
}
|
||||
|
||||
public void ForceAllDirty(VulkanRenderer gd)
|
||||
{
|
||||
_dirty = DirtyFlags.Standard;
|
||||
|
||||
if (gd.Capabilities.SupportsExtendedDynamicState)
|
||||
{
|
||||
_dirty |= DirtyFlags.Extended;
|
||||
}
|
||||
|
||||
if (gd.Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2)
|
||||
{
|
||||
_dirty |= DirtyFlags.Extended2;
|
||||
|
||||
if (gd.Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2LogicOp)
|
||||
{
|
||||
_dirty |= DirtyFlags.LogicOp;
|
||||
}
|
||||
|
||||
if (gd.Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2PatchControlPoints)
|
||||
{
|
||||
_dirty |= DirtyFlags.PatchControlPoints;
|
||||
}
|
||||
}
|
||||
|
||||
if (!gd.IsMoltenVk)
|
||||
{
|
||||
_dirty |= DirtyFlags.LineWidth;
|
||||
}
|
||||
|
||||
if (gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop)
|
||||
{
|
||||
_dirty |= DirtyFlags.FeedbackLoop;
|
||||
}
|
||||
}
|
||||
|
||||
public void ReplayIfDirty(VulkanRenderer gd, CommandBuffer commandBuffer)
|
||||
{
|
||||
Vk api = gd.Api;
|
||||
|
||||
if (_dirty.HasFlag(DirtyFlags.Blend))
|
||||
if (_dirty == DirtyFlags.None)
|
||||
{
|
||||
RecordBlend(api, commandBuffer);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_dirty.HasFlag(DirtyFlags.DepthBias))
|
||||
{
|
||||
RecordDepthBias(api, commandBuffer);
|
||||
}
|
||||
var api = gd.Api;
|
||||
var extendedStateApi = gd.ExtendedDynamicStateApi;
|
||||
var extendedState2Api = gd.ExtendedDynamicState2Api;
|
||||
var dynamicFeedbackLoopApi = gd.DynamicFeedbackLoopApi;
|
||||
|
||||
if (_dirty.HasFlag(DirtyFlags.Scissor))
|
||||
{
|
||||
RecordScissor(api, commandBuffer);
|
||||
}
|
||||
DirtyFlags dirtyFlags = _dirty;
|
||||
|
||||
if (_dirty.HasFlag(DirtyFlags.Stencil))
|
||||
while (dirtyFlags != DirtyFlags.None)
|
||||
{
|
||||
RecordStencilMasks(api, commandBuffer);
|
||||
}
|
||||
int bitIndex = BitOperations.TrailingZeroCount((uint)dirtyFlags);
|
||||
DirtyFlags currentFlag = (DirtyFlags)(1 << bitIndex);
|
||||
|
||||
if (_dirty.HasFlag(DirtyFlags.Viewport))
|
||||
{
|
||||
RecordViewport(api, commandBuffer);
|
||||
}
|
||||
switch (currentFlag)
|
||||
{
|
||||
case DirtyFlags.Blend:
|
||||
RecordBlend(api, commandBuffer);
|
||||
break;
|
||||
case DirtyFlags.DepthBias:
|
||||
RecordDepthBias(api, commandBuffer);
|
||||
break;
|
||||
case DirtyFlags.Scissor:
|
||||
RecordScissor(gd, commandBuffer);
|
||||
break;
|
||||
case DirtyFlags.Stencil:
|
||||
RecordStencil(api, commandBuffer);
|
||||
break;
|
||||
case DirtyFlags.Viewport:
|
||||
RecordViewport(gd, commandBuffer);
|
||||
break;
|
||||
case DirtyFlags.FeedbackLoop:
|
||||
RecordFeedbackLoop(dynamicFeedbackLoopApi, commandBuffer);
|
||||
break;
|
||||
case DirtyFlags.CullMode:
|
||||
RecordCullMode(extendedStateApi, commandBuffer);
|
||||
break;
|
||||
case DirtyFlags.FrontFace:
|
||||
RecordFrontFace(extendedStateApi, commandBuffer);
|
||||
break;
|
||||
case DirtyFlags.DepthTestBool:
|
||||
RecordDepthTestBool(extendedStateApi, commandBuffer);
|
||||
break;
|
||||
case DirtyFlags.DepthTestCompareOp:
|
||||
RecordDepthTestCompareOp(extendedStateApi, commandBuffer);
|
||||
break;
|
||||
case DirtyFlags.StencilTestEnableAndStencilOp:
|
||||
RecordStencilTestAndOp(extendedStateApi, commandBuffer);
|
||||
break;
|
||||
case DirtyFlags.LineWidth:
|
||||
RecordLineWidth(api, commandBuffer);
|
||||
break;
|
||||
case DirtyFlags.RasterDiscard:
|
||||
RecordRasterizationDiscard(extendedState2Api, commandBuffer);
|
||||
break;
|
||||
case DirtyFlags.LogicOp:
|
||||
RecordLogicOp(extendedState2Api, commandBuffer);
|
||||
break;
|
||||
case DirtyFlags.PatchControlPoints:
|
||||
RecordPatchControlPoints(extendedState2Api, commandBuffer);
|
||||
break;
|
||||
case DirtyFlags.PrimitiveRestart:
|
||||
RecordPrimitiveRestartEnable(gd, commandBuffer);
|
||||
break;
|
||||
case DirtyFlags.PrimitiveTopology:
|
||||
RecordPrimitiveTopology(extendedStateApi, commandBuffer);
|
||||
break;
|
||||
case DirtyFlags.DepthBiasEnable:
|
||||
RecordDepthBiasEnable(extendedState2Api, commandBuffer);
|
||||
break;
|
||||
}
|
||||
|
||||
if (_dirty.HasFlag(DirtyFlags.FeedbackLoop) && gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop)
|
||||
{
|
||||
RecordFeedbackLoop(gd.DynamicFeedbackLoopApi, commandBuffer);
|
||||
dirtyFlags &= ~currentFlag;
|
||||
}
|
||||
|
||||
_dirty = DirtyFlags.None;
|
||||
@@ -164,15 +382,27 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
api.CmdSetDepthBias(commandBuffer, _depthBiasConstantFactor, _depthBiasClamp, _depthBiasSlopeFactor);
|
||||
}
|
||||
|
||||
private void RecordScissor(Vk api, CommandBuffer commandBuffer)
|
||||
private readonly void RecordDepthBiasEnable(ExtExtendedDynamicState2 gd, CommandBuffer commandBuffer)
|
||||
{
|
||||
gd.CmdSetDepthBiasEnable(commandBuffer, _depthBiasEnable);
|
||||
}
|
||||
|
||||
private void RecordScissor(VulkanRenderer gd, CommandBuffer commandBuffer)
|
||||
{
|
||||
if (ScissorsCount != 0)
|
||||
{
|
||||
api.CmdSetScissor(commandBuffer, 0, (uint)ScissorsCount, _scissors.AsSpan());
|
||||
if (gd.Capabilities.SupportsExtendedDynamicState)
|
||||
{
|
||||
gd.ExtendedDynamicStateApi.CmdSetScissorWithCount(commandBuffer, (uint)ScissorsCount, _scissors.AsSpan());
|
||||
}
|
||||
else
|
||||
{
|
||||
gd.Api.CmdSetScissor(commandBuffer, 0, (uint)ScissorsCount, _scissors.AsSpan());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly void RecordStencilMasks(Vk api, CommandBuffer commandBuffer)
|
||||
private readonly void RecordStencil(Vk api, CommandBuffer commandBuffer)
|
||||
{
|
||||
api.CmdSetStencilCompareMask(commandBuffer, StencilFaceFlags.FaceBackBit, _backCompareMask);
|
||||
api.CmdSetStencilWriteMask(commandBuffer, StencilFaceFlags.FaceBackBit, _backWriteMask);
|
||||
@@ -182,12 +412,107 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
api.CmdSetStencilReference(commandBuffer, StencilFaceFlags.FaceFrontBit, _frontReference);
|
||||
}
|
||||
|
||||
private void RecordViewport(Vk api, CommandBuffer commandBuffer)
|
||||
private readonly void RecordStencilTestAndOp(ExtExtendedDynamicState api, CommandBuffer commandBuffer)
|
||||
{
|
||||
if (ViewportsCount != 0)
|
||||
api.CmdSetStencilTestEnable(commandBuffer, StencilTestEnable);
|
||||
|
||||
api.CmdSetStencilOp(commandBuffer, StencilFaceFlags.FaceBackBit, _backFailOp, _backPassOp, _backDepthFailOp, _backCompareOp);
|
||||
api.CmdSetStencilOp(commandBuffer, StencilFaceFlags.FaceFrontBit, _frontFailOp, _frontPassOp, _frontDepthFailOp, _frontCompareOp);
|
||||
}
|
||||
|
||||
private void RecordViewport(VulkanRenderer gd, CommandBuffer commandBuffer)
|
||||
{
|
||||
if (ViewportsCount == 0)
|
||||
{
|
||||
api.CmdSetViewport(commandBuffer, 0, ViewportsCount, Viewports.AsSpan());
|
||||
return;
|
||||
}
|
||||
|
||||
if (gd.Capabilities.SupportsExtendedDynamicState)
|
||||
{
|
||||
gd.ExtendedDynamicStateApi.CmdSetViewportWithCount(commandBuffer, ViewportsCount, Viewports.AsSpan());
|
||||
}
|
||||
else
|
||||
{
|
||||
gd.Api.CmdSetViewport(commandBuffer, 0, ViewportsCount, Viewports.AsSpan());
|
||||
}
|
||||
}
|
||||
|
||||
private readonly void RecordCullMode(ExtExtendedDynamicState api, CommandBuffer commandBuffer)
|
||||
{
|
||||
api.CmdSetCullMode(commandBuffer, CullMode);
|
||||
}
|
||||
|
||||
private readonly void RecordFrontFace(ExtExtendedDynamicState api, CommandBuffer commandBuffer)
|
||||
{
|
||||
api.CmdSetFrontFace(commandBuffer, _frontFace);
|
||||
}
|
||||
|
||||
private readonly void RecordDepthTestBool(ExtExtendedDynamicState api, CommandBuffer commandBuffer)
|
||||
{
|
||||
api.CmdSetDepthTestEnable(commandBuffer, DepthTestEnable);
|
||||
|
||||
api.CmdSetDepthWriteEnable(commandBuffer, DepthWriteEnable);
|
||||
}
|
||||
|
||||
private readonly void RecordDepthTestCompareOp(ExtExtendedDynamicState api, CommandBuffer commandBuffer)
|
||||
{
|
||||
api.CmdSetDepthCompareOp(commandBuffer, _depthCompareOp);
|
||||
}
|
||||
|
||||
private readonly void RecordRasterizationDiscard(ExtExtendedDynamicState2 extendedDynamicState2Api, CommandBuffer commandBuffer)
|
||||
{
|
||||
extendedDynamicState2Api.CmdSetRasterizerDiscardEnable(commandBuffer, _discard);
|
||||
}
|
||||
|
||||
private readonly void RecordPrimitiveRestartEnable(VulkanRenderer gd, CommandBuffer commandBuffer)
|
||||
{
|
||||
bool primitiveRestartEnable = _primitiveRestartEnable;
|
||||
|
||||
bool topologySupportsRestart;
|
||||
|
||||
if (gd.Capabilities.SupportsPrimitiveTopologyListRestart)
|
||||
{
|
||||
topologySupportsRestart = gd.Capabilities.SupportsPrimitiveTopologyPatchListRestart ||
|
||||
Topology != PrimitiveTopology.PatchList;
|
||||
}
|
||||
else
|
||||
{
|
||||
topologySupportsRestart = Topology == PrimitiveTopology.LineStrip ||
|
||||
Topology == PrimitiveTopology.TriangleStrip ||
|
||||
Topology == PrimitiveTopology.TriangleFan ||
|
||||
Topology == PrimitiveTopology.LineStripWithAdjacency ||
|
||||
Topology == PrimitiveTopology.TriangleStripWithAdjacency;
|
||||
}
|
||||
|
||||
primitiveRestartEnable &= topologySupportsRestart;
|
||||
|
||||
// Cannot disable primitiveRestartEnable for these Topologies on MacOS.
|
||||
if (gd.IsMoltenVk)
|
||||
{
|
||||
primitiveRestartEnable = true;
|
||||
}
|
||||
|
||||
gd.ExtendedDynamicState2Api.CmdSetPrimitiveRestartEnable(commandBuffer, primitiveRestartEnable);
|
||||
}
|
||||
|
||||
private readonly void RecordPrimitiveTopology(ExtExtendedDynamicState extendedDynamicStateApi, CommandBuffer commandBuffer)
|
||||
{
|
||||
extendedDynamicStateApi.CmdSetPrimitiveTopology(commandBuffer, Topology);
|
||||
}
|
||||
|
||||
private readonly void RecordLogicOp(ExtExtendedDynamicState2 extendedDynamicState2Api, CommandBuffer commandBuffer)
|
||||
{
|
||||
extendedDynamicState2Api.CmdSetLogicOp(commandBuffer, _logicOp);
|
||||
}
|
||||
|
||||
private readonly void RecordPatchControlPoints(ExtExtendedDynamicState2 extendedDynamicState2Api, CommandBuffer commandBuffer)
|
||||
{
|
||||
extendedDynamicState2Api.CmdSetPatchControlPoints(commandBuffer, _patchControlPoints);
|
||||
}
|
||||
|
||||
private readonly void RecordLineWidth(Vk api, CommandBuffer commandBuffer)
|
||||
{
|
||||
api.CmdSetLineWidth(commandBuffer, _lineWidth);
|
||||
}
|
||||
|
||||
private readonly void RecordFeedbackLoop(ExtAttachmentFeedbackLoopDynamicState api, CommandBuffer commandBuffer)
|
||||
|
||||
@@ -8,11 +8,15 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
}
|
||||
|
||||
public void SetRenderTarget(TextureView view, uint width, uint height)
|
||||
public void SetRenderTarget(TextureView view, uint width, uint height, bool signalChange = true)
|
||||
{
|
||||
CreateFramebuffer(view, width, height);
|
||||
CreateRenderPass();
|
||||
SignalStateChange();
|
||||
|
||||
if (signalChange)
|
||||
{
|
||||
SignalStateChange();
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateFramebuffer(TextureView view, uint width, uint height)
|
||||
|
||||
@@ -7,312 +7,258 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
struct PipelineState : IDisposable
|
||||
{
|
||||
private const int MaxDynamicStatesCount = 9;
|
||||
private const int MaxDynamicStatesCount = 23;
|
||||
|
||||
public PipelineUid Internal;
|
||||
|
||||
public float LineWidth
|
||||
public PolygonMode PolygonMode
|
||||
{
|
||||
readonly get => BitConverter.Int32BitsToSingle((int)((Internal.Id0 >> 0) & 0xFFFFFFFF));
|
||||
set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFFF00000000) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 0);
|
||||
readonly get => (PolygonMode)((Internal.Id0 >> 0) & 0x3FFFFFFF);
|
||||
set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFFFC0000000) | ((ulong)value << 0);
|
||||
}
|
||||
|
||||
public float DepthBiasClamp
|
||||
public uint StagesCount
|
||||
{
|
||||
readonly get => BitConverter.Int32BitsToSingle((int)((Internal.Id0 >> 32) & 0xFFFFFFFF));
|
||||
set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFFF) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 32);
|
||||
readonly get => (byte)((Internal.Id0 >> 30) & 0xFF);
|
||||
set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFC03FFFFFFF) | ((ulong)value << 30);
|
||||
}
|
||||
|
||||
public float DepthBiasConstantFactor
|
||||
public uint VertexAttributeDescriptionsCount
|
||||
{
|
||||
readonly get => BitConverter.Int32BitsToSingle((int)((Internal.Id1 >> 0) & 0xFFFFFFFF));
|
||||
set => Internal.Id1 = (Internal.Id1 & 0xFFFFFFFF00000000) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 0);
|
||||
readonly get => (byte)((Internal.Id0 >> 38) & 0xFF);
|
||||
set => Internal.Id0 = (Internal.Id0 & 0xFFFFC03FFFFFFFFF) | ((ulong)value << 38);
|
||||
}
|
||||
|
||||
public float DepthBiasSlopeFactor
|
||||
public uint VertexBindingDescriptionsCount
|
||||
{
|
||||
readonly get => BitConverter.Int32BitsToSingle((int)((Internal.Id1 >> 32) & 0xFFFFFFFF));
|
||||
set => Internal.Id1 = (Internal.Id1 & 0xFFFFFFFF) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 32);
|
||||
readonly get => (byte)((Internal.Id0 >> 46) & 0xFF);
|
||||
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);
|
||||
set => Internal.Id2 = (Internal.Id2 & 0xFFFFFFFF00000000) | ((ulong)value << 0);
|
||||
}
|
||||
|
||||
public uint StencilFrontWriteMask
|
||||
public uint SamplesCount
|
||||
{
|
||||
readonly get => (uint)((Internal.Id2 >> 32) & 0xFFFFFFFF);
|
||||
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
|
||||
{
|
||||
readonly get => ((Internal.Id8 >> 0) & 0x1) != 0UL;
|
||||
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFFE) | ((value ? 1UL : 0UL) << 0);
|
||||
readonly get => ((Internal.Id3 >> 0) & 0x1) != 0UL;
|
||||
set => Internal.Id3 = (Internal.Id3 & 0xFFFFFFFFFFFFFFFE) | ((value ? 1UL : 0UL) << 0);
|
||||
}
|
||||
|
||||
public bool AlphaToOneEnable
|
||||
{
|
||||
readonly get => ((Internal.Id8 >> 1) & 0x1) != 0UL;
|
||||
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFFD) | ((value ? 1UL : 0UL) << 1);
|
||||
readonly get => ((Internal.Id3 >> 1) & 0x1) != 0UL;
|
||||
set => Internal.Id3 = (Internal.Id3 & 0xFFFFFFFFFFFFFFFD) | ((value ? 1UL : 0UL) << 1);
|
||||
}
|
||||
|
||||
public bool AdvancedBlendSrcPreMultiplied
|
||||
{
|
||||
readonly get => ((Internal.Id8 >> 2) & 0x1) != 0UL;
|
||||
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFFB) | ((value ? 1UL : 0UL) << 2);
|
||||
readonly get => ((Internal.Id3 >> 2) & 0x1) != 0UL;
|
||||
set => Internal.Id3 = (Internal.Id3 & 0xFFFFFFFFFFFFFFFB) | ((value ? 1UL : 0UL) << 2);
|
||||
}
|
||||
|
||||
public bool AdvancedBlendDstPreMultiplied
|
||||
{
|
||||
readonly get => ((Internal.Id8 >> 3) & 0x1) != 0UL;
|
||||
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFF7) | ((value ? 1UL : 0UL) << 3);
|
||||
readonly get => ((Internal.Id3 >> 3) & 0x1) != 0UL;
|
||||
set => Internal.Id3 = (Internal.Id3 & 0xFFFFFFFFFFFFFFF7) | ((value ? 1UL : 0UL) << 3);
|
||||
}
|
||||
|
||||
public BlendOverlapEXT AdvancedBlendOverlap
|
||||
{
|
||||
readonly get => (BlendOverlapEXT)((Internal.Id8 >> 4) & 0x3);
|
||||
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFCF) | ((ulong)value << 4);
|
||||
readonly get => (BlendOverlapEXT)((Internal.Id3 >> 4) & 0x3);
|
||||
set => Internal.Id3 = (Internal.Id3 & 0xFFFFFFFFFFFFFFCF) | ((ulong)value << 4);
|
||||
}
|
||||
|
||||
public bool DepthMode
|
||||
{
|
||||
readonly get => ((Internal.Id8 >> 6) & 0x1) != 0UL;
|
||||
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFBF) | ((value ? 1UL : 0UL) << 6);
|
||||
readonly get => ((Internal.Id3 >> 6) & 0x1) != 0UL;
|
||||
set => Internal.Id3 = (Internal.Id3 & 0xFFFFFFFFFFFFFFBF) | ((value ? 1UL : 0UL) << 6);
|
||||
}
|
||||
|
||||
public FeedbackLoopAspects FeedbackLoopAspects
|
||||
{
|
||||
readonly get => (FeedbackLoopAspects)((Internal.Id8 >> 7) & 0x3);
|
||||
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFE7F) | (((ulong)value) << 7);
|
||||
readonly get => (FeedbackLoopAspects)((Internal.Id3 >> 7) & 0x3);
|
||||
set => Internal.Id3 = (Internal.Id3 & 0xFFFFFFFFFFFFFE7F) | (((ulong)value) << 7);
|
||||
}
|
||||
|
||||
public bool HasTessellationControlShader;
|
||||
public bool FeedbackLoopDynamicState;
|
||||
public NativeArray<PipelineShaderStageCreateInfo> Stages;
|
||||
public PipelineLayout PipelineLayout;
|
||||
public SpecData SpecializationData;
|
||||
|
||||
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;
|
||||
Stages = new NativeArray<PipelineShaderStageCreateInfo>(Constants.MaxShaderStages);
|
||||
@@ -321,9 +267,54 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
AdvancedBlendDstPreMultiplied = true;
|
||||
AdvancedBlendOverlap = BlendOverlapEXT.UncorrelatedExt;
|
||||
|
||||
LineWidth = 1f;
|
||||
SamplesCount = 1;
|
||||
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(
|
||||
@@ -341,7 +332,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
SType = StructureType.ComputePipelineCreateInfo,
|
||||
Stage = Stages[0],
|
||||
BasePipelineIndex = -1,
|
||||
Layout = PipelineLayout,
|
||||
};
|
||||
|
||||
@@ -377,6 +367,74 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
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(
|
||||
VulkanRenderer gd,
|
||||
Device device,
|
||||
@@ -385,6 +443,17 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
RenderPass renderPass,
|
||||
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))
|
||||
{
|
||||
return pipeline;
|
||||
@@ -394,7 +463,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
bool isMoltenVk = gd.IsMoltenVk;
|
||||
|
||||
if (isMoltenVk)
|
||||
if (isMoltenVk && !_supportsExtDynamicState)
|
||||
{
|
||||
UpdateVertexAttributeDescriptions(gd);
|
||||
}
|
||||
@@ -408,7 +477,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
SType = StructureType.PipelineVertexInputStateCreateInfo,
|
||||
VertexAttributeDescriptionCount = VertexAttributeDescriptionsCount,
|
||||
PVertexAttributeDescriptions = isMoltenVk ? pVertexAttributeDescriptions2 : pVertexAttributeDescriptions,
|
||||
PVertexAttributeDescriptions = isMoltenVk && !_supportsExtDynamicState ? pVertexAttributeDescriptions2 : pVertexAttributeDescriptions,
|
||||
VertexBindingDescriptionCount = VertexBindingDescriptionsCount,
|
||||
PVertexBindingDescriptions = pVertexBindingDescriptions,
|
||||
};
|
||||
@@ -444,33 +513,22 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
PipelineInputAssemblyStateCreateInfo inputAssemblyState = new()
|
||||
{
|
||||
SType = StructureType.PipelineInputAssemblyStateCreateInfo,
|
||||
PrimitiveRestartEnable = primitiveRestartEnable,
|
||||
Topology = HasTessellationControlShader ? PrimitiveTopology.PatchList : Topology,
|
||||
Topology = Topology,
|
||||
};
|
||||
|
||||
PipelineTessellationStateCreateInfo tessellationState = new()
|
||||
{
|
||||
SType = StructureType.PipelineTessellationStateCreateInfo,
|
||||
PatchControlPoints = PatchControlPoints,
|
||||
};
|
||||
PipelineTessellationStateCreateInfo tessellationState;
|
||||
|
||||
PipelineRasterizationStateCreateInfo rasterizationState = new()
|
||||
{
|
||||
SType = StructureType.PipelineRasterizationStateCreateInfo,
|
||||
DepthClampEnable = DepthClampEnable,
|
||||
RasterizerDiscardEnable = RasterizerDiscardEnable,
|
||||
PolygonMode = PolygonMode,
|
||||
LineWidth = LineWidth,
|
||||
CullMode = CullMode,
|
||||
FrontFace = FrontFace,
|
||||
DepthBiasEnable = DepthBiasEnable,
|
||||
// When widelines feature is not supported it must be 1.0f, this will be ignored if Line Width dynamic state is supported
|
||||
LineWidth = 1.0f,
|
||||
};
|
||||
|
||||
PipelineViewportStateCreateInfo viewportState = new()
|
||||
{
|
||||
SType = StructureType.PipelineViewportStateCreateInfo,
|
||||
ViewportCount = ViewportsCount,
|
||||
ScissorCount = ScissorsCount,
|
||||
};
|
||||
|
||||
if (gd.Capabilities.SupportsDepthClipControl)
|
||||
@@ -494,64 +552,70 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
AlphaToOneEnable = AlphaToOneEnable,
|
||||
};
|
||||
|
||||
StencilOpState stencilFront = new(
|
||||
StencilFrontFailOp,
|
||||
StencilFrontPassOp,
|
||||
StencilFrontDepthFailOp,
|
||||
StencilFrontCompareOp);
|
||||
|
||||
StencilOpState stencilBack = new(
|
||||
StencilBackFailOp,
|
||||
StencilBackPassOp,
|
||||
StencilBackDepthFailOp,
|
||||
StencilBackCompareOp);
|
||||
|
||||
PipelineDepthStencilStateCreateInfo depthStencilState = new()
|
||||
{
|
||||
SType = StructureType.PipelineDepthStencilStateCreateInfo,
|
||||
DepthTestEnable = DepthTestEnable,
|
||||
DepthWriteEnable = DepthWriteEnable,
|
||||
DepthCompareOp = DepthCompareOp,
|
||||
DepthBoundsTestEnable = false,
|
||||
StencilTestEnable = StencilTestEnable,
|
||||
Front = stencilFront,
|
||||
Back = stencilBack,
|
||||
DepthBoundsTestEnable = DepthBoundsTestEnable,
|
||||
};
|
||||
|
||||
uint blendEnables = 0;
|
||||
|
||||
if (gd.IsMoltenVk && Internal.AttachmentIntegerFormatMask != 0)
|
||||
if (!_supportsExtDynamicState)
|
||||
{
|
||||
// Blend can't be enabled for integer formats, so let's make sure it is disabled.
|
||||
uint attachmentIntegerFormatMask = Internal.AttachmentIntegerFormatMask;
|
||||
rasterizationState.CullMode = CullMode;
|
||||
rasterizationState.FrontFace = FrontFace;
|
||||
|
||||
while (attachmentIntegerFormatMask != 0)
|
||||
{
|
||||
int i = BitOperations.TrailingZeroCount(attachmentIntegerFormatMask);
|
||||
viewportState.ViewportCount = ViewportsCount;
|
||||
viewportState.ScissorCount = ScissorsCount;
|
||||
|
||||
if (Internal.ColorBlendAttachmentState[i].BlendEnable)
|
||||
{
|
||||
blendEnables |= 1u << i;
|
||||
}
|
||||
StencilOpState stencilFront = new(
|
||||
StencilFrontFailOp,
|
||||
StencilFrontPassOp,
|
||||
StencilFrontDepthFailOp,
|
||||
StencilFrontCompareOp);
|
||||
|
||||
Internal.ColorBlendAttachmentState[i].BlendEnable = false;
|
||||
attachmentIntegerFormatMask &= ~(1u << i);
|
||||
}
|
||||
StencilOpState stencilBack = new(
|
||||
StencilBackFailOp,
|
||||
StencilBackPassOp,
|
||||
StencilBackDepthFailOp,
|
||||
StencilBackCompareOp);
|
||||
|
||||
depthStencilState.Front = stencilFront;
|
||||
depthStencilState.Back = stencilBack;
|
||||
depthStencilState.StencilTestEnable = StencilTestEnable;
|
||||
depthStencilState.DepthTestEnable = DepthTestEnable;
|
||||
depthStencilState.DepthWriteEnable = DepthWriteEnable;
|
||||
depthStencilState.DepthCompareOp = DepthCompareOp;
|
||||
}
|
||||
|
||||
// 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);
|
||||
if (!_supportsExtDynamicState2.ExtendedDynamicState2)
|
||||
{
|
||||
|
||||
inputAssemblyState.PrimitiveRestartEnable = PrimitiveRestartEnable;
|
||||
rasterizationState.DepthBiasEnable = DepthBiasEnable;
|
||||
rasterizationState.RasterizerDiscardEnable = RasterizerDiscardEnable;
|
||||
}
|
||||
|
||||
if (!gd.Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2PatchControlPoints)
|
||||
{
|
||||
tessellationState = new PipelineTessellationStateCreateInfo
|
||||
{
|
||||
SType = StructureType.PipelineTessellationStateCreateInfo,
|
||||
PatchControlPoints = PatchControlPoints,
|
||||
};
|
||||
}
|
||||
|
||||
PipelineColorBlendStateCreateInfo colorBlendState = new()
|
||||
{
|
||||
SType = StructureType.PipelineColorBlendStateCreateInfo,
|
||||
LogicOpEnable = logicOpEnable,
|
||||
LogicOp = LogicOp,
|
||||
AttachmentCount = ColorBlendAttachmentStateCount,
|
||||
PAttachments = pColorBlendAttachmentState,
|
||||
LogicOpEnable = LogicOpEnable,
|
||||
};
|
||||
|
||||
if (!gd.Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2LogicOp)
|
||||
{
|
||||
colorBlendState.LogicOp = LogicOp;
|
||||
}
|
||||
|
||||
if (!AdvancedBlendSrcPreMultiplied ||
|
||||
!AdvancedBlendDstPreMultiplied ||
|
||||
AdvancedBlendOverlap != BlendOverlapEXT.UncorrelatedExt)
|
||||
@@ -567,66 +631,99 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
colorBlendState.PNext = &colorBlendAdvancedState;
|
||||
}
|
||||
|
||||
bool supportsExtDynamicState = gd.Capabilities.SupportsExtendedDynamicState;
|
||||
bool supportsFeedbackLoopDynamicState = gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop;
|
||||
|
||||
DynamicState* dynamicStates = stackalloc DynamicState[MaxDynamicStatesCount];
|
||||
|
||||
int dynamicStatesCount = 7;
|
||||
uint dynamicStatesCount = 7;
|
||||
|
||||
dynamicStates[0] = DynamicState.Viewport;
|
||||
dynamicStates[1] = DynamicState.Scissor;
|
||||
dynamicStates[2] = DynamicState.DepthBias;
|
||||
dynamicStates[3] = DynamicState.StencilCompareMask;
|
||||
dynamicStates[4] = DynamicState.StencilWriteMask;
|
||||
dynamicStates[5] = DynamicState.StencilReference;
|
||||
dynamicStates[6] = DynamicState.BlendConstants;
|
||||
dynamicStates[2] = DynamicState.StencilCompareMask;
|
||||
dynamicStates[3] = DynamicState.StencilWriteMask;
|
||||
dynamicStates[4] = DynamicState.StencilReference;
|
||||
dynamicStates[5] = DynamicState.BlendConstants;
|
||||
dynamicStates[6] = DynamicState.DepthBias;
|
||||
|
||||
if (supportsExtDynamicState && (gd.SupportsMTL31 || !gd.IsMoltenVk))
|
||||
if (!isMoltenVk)
|
||||
{
|
||||
// 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.
|
||||
dynamicStates[dynamicStatesCount++] = DynamicState.VertexInputBindingStrideExt;
|
||||
//LineWidth dynamic state is only supported on macOS when using Metal Private API on newer version of MoltenVK
|
||||
dynamicStates[dynamicStatesCount++] = DynamicState.LineWidth;
|
||||
}
|
||||
|
||||
if (supportsFeedbackLoopDynamicState)
|
||||
if (_supportsExtDynamicState)
|
||||
{
|
||||
dynamicStates[dynamicStatesCount++] = DynamicState.AttachmentFeedbackLoopEnableExt;
|
||||
if (gd.SupportsMTL31 || !gd.IsMoltenVk)
|
||||
{
|
||||
// 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.
|
||||
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;
|
||||
|
||||
dynamicStates[dynamicStatesCount++] = DynamicState.DepthCompareOpExt;
|
||||
dynamicStates[dynamicStatesCount++] = DynamicState.StencilTestEnableExt;
|
||||
dynamicStates[dynamicStatesCount++] = DynamicState.StencilOpExt;
|
||||
dynamicStates[dynamicStatesCount++] = DynamicState.PrimitiveTopologyExt;
|
||||
}
|
||||
|
||||
PipelineDynamicStateCreateInfo pipelineDynamicStateCreateInfo = new()
|
||||
if (_supportsExtDynamicState2.ExtendedDynamicState2)
|
||||
{
|
||||
SType = StructureType.PipelineDynamicStateCreateInfo,
|
||||
DynamicStateCount = (uint)dynamicStatesCount,
|
||||
PDynamicStates = dynamicStates,
|
||||
};
|
||||
dynamicStates[dynamicStatesCount++] = DynamicState.DepthBiasEnableExt;
|
||||
dynamicStates[dynamicStatesCount++] = DynamicState.RasterizerDiscardEnableExt;
|
||||
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;
|
||||
|
||||
if ((aspects & FeedbackLoopAspects.Color) != 0)
|
||||
{
|
||||
flags |= PipelineCreateFlags.CreateColorAttachmentFeedbackLoopBitExt;
|
||||
pipelineCreateFlags |= PipelineCreateFlags.CreateColorAttachmentFeedbackLoopBitExt;
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
SType = StructureType.GraphicsPipelineCreateInfo,
|
||||
Flags = flags,
|
||||
StageCount = StagesCount,
|
||||
Flags = pipelineCreateFlags,
|
||||
PStages = Stages.Pointer,
|
||||
PVertexInputState = &vertexInputState,
|
||||
PInputAssemblyState = &inputAssemblyState,
|
||||
PTessellationState = &tessellationState,
|
||||
PViewportState = &viewportState,
|
||||
PRasterizationState = &rasterizationState,
|
||||
PMultisampleState = &multisampleState,
|
||||
@@ -637,6 +734,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
RenderPass = renderPass,
|
||||
};
|
||||
|
||||
if (!gd.Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2PatchControlPoints)
|
||||
{
|
||||
pipelineCreateInfo.PTessellationState = &tessellationState;
|
||||
}
|
||||
|
||||
Result result = gd.Api.CreateGraphicsPipelines(device, cache, 1, &pipelineCreateInfo, null, &pipelineHandle);
|
||||
|
||||
if (throwOnError)
|
||||
@@ -649,21 +751,21 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
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));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,23 +11,17 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
public ulong Id0;
|
||||
public ulong Id1;
|
||||
|
||||
public ulong Id2;
|
||||
public ulong Id3;
|
||||
|
||||
public ulong Id4;
|
||||
public ulong Id5;
|
||||
public ulong Id6;
|
||||
|
||||
public ulong Id7;
|
||||
public ulong Id8;
|
||||
|
||||
private readonly uint VertexAttributeDescriptionsCount => (byte)((Id5 >> 38) & 0xFF);
|
||||
private readonly uint VertexBindingDescriptionsCount => (byte)((Id5 >> 46) & 0xFF);
|
||||
private readonly uint ColorBlendAttachmentStateCount => (byte)((Id6 >> 8) & 0xFF);
|
||||
private readonly bool HasDepthStencil => ((Id6 >> 63) & 0x1) != 0UL;
|
||||
private readonly uint VertexAttributeDescriptionsCount => (byte)((Id0 >> 38) & 0xFF);
|
||||
private readonly uint VertexBindingDescriptionsCount => (byte)((Id0 >> 46) & 0xFF);
|
||||
private readonly uint ColorBlendAttachmentStateCount => (byte)((Id1 >> 8) & 0xFF);
|
||||
private readonly bool HasDepthStencil => ((Id1 >> 63) & 0x1) != 0UL;
|
||||
|
||||
public Array32<VertexInputAttributeDescription> VertexAttributeDescriptions;
|
||||
public Array33<VertexInputBindingDescription> VertexBindingDescriptions;
|
||||
public Array32<VertexInputBindingDescription> VertexBindingDescriptions;
|
||||
public Array8<PipelineColorBlendAttachmentState> ColorBlendAttachmentState;
|
||||
public Array9<Format> AttachmentFormats;
|
||||
public uint AttachmentIntegerFormatMask;
|
||||
@@ -40,9 +34,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public bool Equals(ref PipelineUid other)
|
||||
{
|
||||
if (!Unsafe.As<ulong, Vector256<byte>>(ref Id0).Equals(Unsafe.As<ulong, Vector256<byte>>(ref other.Id0)) ||
|
||||
!Unsafe.As<ulong, Vector256<byte>>(ref Id4).Equals(Unsafe.As<ulong, Vector256<byte>>(ref other.Id4)) ||
|
||||
!Unsafe.As<ulong, Vector128<byte>>(ref Id7).Equals(Unsafe.As<ulong, Vector128<byte>>(ref other.Id7)))
|
||||
if (!Unsafe.As<ulong, Vector256<byte>>(ref Id0).Equals(Unsafe.As<ulong, Vector256<byte>>(ref other.Id0)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -80,12 +72,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
ulong hash64 = Id0 * 23 ^
|
||||
Id1 * 23 ^
|
||||
Id2 * 23 ^
|
||||
Id3 * 23 ^
|
||||
Id4 * 23 ^
|
||||
Id5 * 23 ^
|
||||
Id6 * 23 ^
|
||||
Id7 * 23 ^
|
||||
Id8 * 23;
|
||||
Id3 * 23;
|
||||
|
||||
ReadOnlySpan<VertexInputAttributeDescription> vertexAttributeDescriptionsSpan = VertexAttributeDescriptions.AsSpan();
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using PrimitiveTopology = Silk.NET.Vulkan.PrimitiveTopology;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
@@ -60,7 +61,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
private ProgramPipelineState _state;
|
||||
private DisposableRenderPass _dummyRenderPass;
|
||||
private ShaderCompilationRequest _compileRequest;
|
||||
private readonly Task _compileTask;
|
||||
private bool _firstBackgroundUse;
|
||||
|
||||
public ShaderCollection(
|
||||
@@ -139,7 +140,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
// Updating buffer texture bindings using template updates crashes the Adreno driver on Windows.
|
||||
UpdateTexturesWithoutTemplate = gd.IsQualcommProprietary && usesBufferTextures;
|
||||
|
||||
_compileRequest = new ShaderCompilationRequest(Task.CompletedTask);
|
||||
_compileTask = Task.CompletedTask;
|
||||
_firstBackgroundUse = false;
|
||||
}
|
||||
|
||||
@@ -153,9 +154,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
_state = state;
|
||||
|
||||
_compileRequest = gd.ShaderCompilationQueue != null
|
||||
? gd.ShaderCompilationQueue.Add(BackgroundCompilation)
|
||||
: new ShaderCompilationRequest(BackgroundCompilationAsync());
|
||||
_compileTask = BackgroundCompilation();
|
||||
_firstBackgroundUse = !fromCache;
|
||||
}
|
||||
|
||||
@@ -460,25 +459,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return (buffer, texture);
|
||||
}
|
||||
|
||||
private async Task BackgroundCompilationAsync()
|
||||
private async Task BackgroundCompilation()
|
||||
{
|
||||
await Task.WhenAll(_shaders.Select(shader => shader.CompileTask));
|
||||
|
||||
BackgroundCompilationImpl();
|
||||
}
|
||||
|
||||
private void BackgroundCompilation()
|
||||
{
|
||||
foreach (var shader in _shaders)
|
||||
{
|
||||
shader.CompileTask.Wait();
|
||||
}
|
||||
|
||||
BackgroundCompilationImpl();
|
||||
}
|
||||
|
||||
private void BackgroundCompilationImpl()
|
||||
{
|
||||
if (Array.Exists(_shaders, shader => shader.CompileStatus == ProgramLinkStatus.Failure))
|
||||
{
|
||||
LinkStatus = ProgramLinkStatus.Failure;
|
||||
@@ -555,7 +539,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
public void CreateBackgroundComputePipeline()
|
||||
{
|
||||
PipelineState pipeline = new();
|
||||
pipeline.Initialize();
|
||||
pipeline.Initialize(_gd.Capabilities);
|
||||
|
||||
pipeline.Stages[0] = _shaders[0].GetInfo();
|
||||
pipeline.StagesCount = 1;
|
||||
@@ -621,11 +605,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
if (!_compileRequest.IsCompleted)
|
||||
if (!_compileTask.IsCompleted)
|
||||
{
|
||||
if (blocking)
|
||||
{
|
||||
_compileRequest.Wait();
|
||||
_compileTask.Wait();
|
||||
|
||||
if (LinkStatus == ProgramLinkStatus.Failure)
|
||||
{
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
class ShaderCompilationQueue
|
||||
{
|
||||
private const int MaxParallelCompilations = 8;
|
||||
private const int MaxThreadStackSize = 2 * 1024 * 1024; // MB
|
||||
|
||||
private struct Request
|
||||
{
|
||||
public readonly ulong Id;
|
||||
public readonly Action Callback;
|
||||
|
||||
public Request(ulong id, Action callback)
|
||||
{
|
||||
Id = id;
|
||||
Callback = callback;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Thread[] _workerThreads;
|
||||
private readonly CancellationTokenSource _cts;
|
||||
private readonly BlockingCollection<Request>[] _queues;
|
||||
private readonly ulong[] _finishedIds;
|
||||
private ulong _currentId;
|
||||
private int _currentQueueIndex;
|
||||
|
||||
public ShaderCompilationQueue()
|
||||
{
|
||||
_workerThreads = new Thread[MaxParallelCompilations];
|
||||
_queues = new BlockingCollection<Request>[MaxParallelCompilations];
|
||||
_finishedIds = new ulong[MaxParallelCompilations];
|
||||
|
||||
_cts = new CancellationTokenSource();
|
||||
|
||||
for (int i = 0; i < MaxParallelCompilations; i++)
|
||||
{
|
||||
_queues[i] = new BlockingCollection<Request>();
|
||||
|
||||
Thread thread = new Thread(DoWork, MaxThreadStackSize) { Name = $"BackgroundShaderCompiler.{i}" };
|
||||
thread.IsBackground = true;
|
||||
thread.Start(i);
|
||||
|
||||
_workerThreads[i] = thread;
|
||||
}
|
||||
}
|
||||
|
||||
private void DoWork(object threadId)
|
||||
{
|
||||
int queueIndex = (int)threadId;
|
||||
|
||||
try
|
||||
{
|
||||
var queue = _queues[queueIndex];
|
||||
|
||||
foreach (var request in queue.GetConsumingEnumerable(_cts.Token))
|
||||
{
|
||||
request.Callback();
|
||||
|
||||
lock (queue)
|
||||
{
|
||||
_finishedIds[queueIndex] = request.Id;
|
||||
|
||||
Monitor.PulseAll(queue);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public ShaderCompilationRequest Add(Action callback)
|
||||
{
|
||||
ulong newId = Interlocked.Increment(ref _currentId);
|
||||
|
||||
// Let's keep rotating between the queues to increase the chances
|
||||
// that the selected queue thread is currently idle.
|
||||
int queueIndex = Interlocked.Increment(ref _currentQueueIndex) % MaxParallelCompilations;
|
||||
|
||||
_queues[queueIndex].Add(new Request(newId, callback));
|
||||
|
||||
return new ShaderCompilationRequest(this, queueIndex, newId);
|
||||
}
|
||||
|
||||
public void Wait(int queueIndex, ulong id)
|
||||
{
|
||||
var queue = _queues[queueIndex];
|
||||
|
||||
lock (queue)
|
||||
{
|
||||
while (_finishedIds[queueIndex] < id)
|
||||
{
|
||||
Monitor.Wait(queue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsCompleted(int queueIndex, ulong id)
|
||||
{
|
||||
var queue = _queues[queueIndex];
|
||||
|
||||
lock (queue)
|
||||
{
|
||||
return _finishedIds[queueIndex] >= id;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
for (int i = 0; i < MaxParallelCompilations; i++)
|
||||
{
|
||||
_queues[i].CompleteAdding();
|
||||
}
|
||||
|
||||
_cts.Cancel();
|
||||
|
||||
for (int i = 0; i < MaxParallelCompilations; i++)
|
||||
{
|
||||
_workerThreads[i].Join();
|
||||
|
||||
_queues[i].Dispose();
|
||||
}
|
||||
|
||||
_cts.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
struct ShaderCompilationRequest
|
||||
{
|
||||
private readonly Task _task;
|
||||
private readonly ShaderCompilationQueue _queue;
|
||||
private readonly int _queueIndex;
|
||||
private readonly ulong _requestId;
|
||||
|
||||
public bool IsCompleted
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_task != null)
|
||||
{
|
||||
return _task.IsCompleted;
|
||||
}
|
||||
else
|
||||
{
|
||||
return _queue.IsCompleted(_queueIndex, _requestId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ShaderCompilationRequest(Task task)
|
||||
{
|
||||
_task = task;
|
||||
_queue = null;
|
||||
_queueIndex = 0;
|
||||
_requestId = 0;
|
||||
}
|
||||
|
||||
public ShaderCompilationRequest(ShaderCompilationQueue queue, int queueIndex, ulong requestId)
|
||||
{
|
||||
_task = null;
|
||||
_queue = queue;
|
||||
_queueIndex = queueIndex;
|
||||
_requestId = requestId;
|
||||
}
|
||||
|
||||
public void Wait()
|
||||
{
|
||||
if (_task != null)
|
||||
{
|
||||
_task.Wait();
|
||||
}
|
||||
else
|
||||
{
|
||||
_queue.Wait(_queueIndex, _requestId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -266,7 +266,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
public void FreeCompleted()
|
||||
{
|
||||
FenceHolder signalledFence = null;
|
||||
while (_pendingCopies.TryPeek(out PendingCopy pc) && pc.Fence != null && (pc.Fence == signalledFence || pc.Fence.IsSignaledLazy()))
|
||||
while (_pendingCopies.TryPeek(out PendingCopy pc) && pc.Fence != null && (pc.Fence == signalledFence || pc.Fence.IsSignaled()))
|
||||
{
|
||||
signalledFence = pc.Fence; // Already checked - don't need to do it again.
|
||||
PendingCopy dequeued = _pendingCopies.Dequeue();
|
||||
|
||||
@@ -79,7 +79,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_device = device;
|
||||
_info = info;
|
||||
|
||||
bool isMsImageStorageSupported = gd.Capabilities.SupportsShaderStorageImageMultisample || !info.Target.IsMultisample();
|
||||
bool isMsImageStorageSupported = gd.Capabilities.SupportsShaderStorageImageMultisample || !info.Target.IsMultisample;
|
||||
|
||||
VkFormat format = _gd.FormatCapabilities.ConvertToVkFormat(info.Format, isMsImageStorageSupported);
|
||||
uint levels = (uint)info.Levels;
|
||||
@@ -323,7 +323,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
usage |= ImageUsageFlags.ColorAttachmentBit;
|
||||
}
|
||||
|
||||
if (format.IsImageCompatible() && (isMsImageStorageSupported || !target.IsMultisample()))
|
||||
if (format.IsImageCompatible() && (isMsImageStorageSupported || !target.IsMultisample))
|
||||
{
|
||||
usage |= ImageUsageFlags.StorageBit;
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
gd.Textures.Add(this);
|
||||
|
||||
bool isMsImageStorageSupported = gd.Capabilities.SupportsShaderStorageImageMultisample || !info.Target.IsMultisample();
|
||||
bool isMsImageStorageSupported = gd.Capabilities.SupportsShaderStorageImageMultisample || !info.Target.IsMultisample;
|
||||
|
||||
VkFormat format = _gd.FormatCapabilities.ConvertToVkFormat(info.Format, isMsImageStorageSupported);
|
||||
ImageUsageFlags usage = TextureStorage.GetImageUsage(info.Format, info.Target, gd.Capabilities, isMsImageStorageSupported) & storage.UsageFlags;
|
||||
@@ -126,7 +126,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
ImageUsageFlags shaderUsage = ImageUsageFlags.SampledBit;
|
||||
|
||||
if (info.Format.IsImageCompatible() && (_gd.Capabilities.SupportsShaderStorageImageMultisample || !info.Target.IsMultisample()))
|
||||
if (info.Format.IsImageCompatible() && (_gd.Capabilities.SupportsShaderStorageImageMultisample || !info.Target.IsMultisample))
|
||||
{
|
||||
shaderUsage |= ImageUsageFlags.StorageBit;
|
||||
}
|
||||
@@ -225,12 +225,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Image srcImage = src.GetImage().Get(cbs).Value;
|
||||
Image dstImage = dst.GetImage().Get(cbs).Value;
|
||||
|
||||
if (!dst.Info.Target.IsMultisample() && Info.Target.IsMultisample())
|
||||
if (!dst.Info.Target.IsMultisample && Info.Target.IsMultisample)
|
||||
{
|
||||
int layers = Math.Min(Info.GetLayers(), dst.Info.GetLayers() - firstLayer);
|
||||
_gd.HelperShader.CopyMSToNonMS(_gd, cbs, src, dst, 0, firstLayer, layers);
|
||||
}
|
||||
else if (dst.Info.Target.IsMultisample() && !Info.Target.IsMultisample())
|
||||
else if (dst.Info.Target.IsMultisample && !Info.Target.IsMultisample)
|
||||
{
|
||||
int layers = Math.Min(Info.GetLayers(), dst.Info.GetLayers() - firstLayer);
|
||||
_gd.HelperShader.CopyNonMSToMS(_gd, cbs, src, dst, 0, firstLayer, layers);
|
||||
@@ -287,11 +287,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Image srcImage = src.GetImage().Get(cbs).Value;
|
||||
Image dstImage = dst.GetImage().Get(cbs).Value;
|
||||
|
||||
if (!dst.Info.Target.IsMultisample() && Info.Target.IsMultisample())
|
||||
if (!dst.Info.Target.IsMultisample && Info.Target.IsMultisample)
|
||||
{
|
||||
_gd.HelperShader.CopyMSToNonMS(_gd, cbs, src, dst, srcLayer, dstLayer, 1);
|
||||
}
|
||||
else if (dst.Info.Target.IsMultisample() && !Info.Target.IsMultisample())
|
||||
else if (dst.Info.Target.IsMultisample && !Info.Target.IsMultisample)
|
||||
{
|
||||
_gd.HelperShader.CopyNonMSToMS(_gd, cbs, src, dst, srcLayer, dstLayer, 1);
|
||||
}
|
||||
|
||||
@@ -71,7 +71,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
_buffer = autoBuffer;
|
||||
|
||||
state.Internal.VertexBindingDescriptions[DescriptorIndex].Stride = (uint)stride;
|
||||
if (!gd.Capabilities.SupportsExtendedDynamicState)
|
||||
{
|
||||
state.Internal.VertexBindingDescriptions[DescriptorIndex].Stride = (uint)stride;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -79,8 +82,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
autoBuffer = gd.BufferManager.GetBuffer(cbs.CommandBuffer, _handle, false, out int size);
|
||||
|
||||
// The original stride must be reapplied in case it was rewritten.
|
||||
state.Internal.VertexBindingDescriptions[DescriptorIndex].Stride = (uint)_stride;
|
||||
if (!gd.Capabilities.SupportsExtendedDynamicState)
|
||||
{
|
||||
// The original stride must be reapplied in case it was rewritten.
|
||||
state.Internal.VertexBindingDescriptions[DescriptorIndex].Stride = (uint)_stride;
|
||||
}
|
||||
|
||||
if (_offset >= size)
|
||||
{
|
||||
|
||||
@@ -101,7 +101,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_strides[i] = strides[i];
|
||||
}
|
||||
|
||||
if (_gd.Capabilities.SupportsExtendedDynamicState)
|
||||
if (_gd.Capabilities.SupportsExtendedDynamicState && (_gd.SupportsMTL31 || !_gd.IsMoltenVk))
|
||||
{
|
||||
_gd.ExtendedDynamicStateApi.CmdBindVertexBuffers2(
|
||||
cbs.CommandBuffer,
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
[
|
||||
ExtConditionalRendering.ExtensionName,
|
||||
ExtExtendedDynamicState.ExtensionName,
|
||||
ExtExtendedDynamicState2.ExtensionName,
|
||||
ExtTransformFeedback.ExtensionName,
|
||||
KhrDrawIndirectCount.ExtensionName,
|
||||
KhrPushDescriptor.ExtensionName,
|
||||
@@ -319,6 +320,17 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
features2.PNext = &supportedFeaturesCustomBorderColor;
|
||||
}
|
||||
|
||||
PhysicalDeviceExtendedDynamicState2FeaturesEXT supportedFeaturesExtExtendedDynamicState2 = new()
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceExtendedDynamicState2FeaturesExt,
|
||||
PNext = features2.PNext,
|
||||
};
|
||||
|
||||
if (physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState2.ExtensionName))
|
||||
{
|
||||
features2.PNext = &supportedFeaturesExtExtendedDynamicState2;
|
||||
}
|
||||
|
||||
PhysicalDevicePrimitiveTopologyListRestartFeaturesEXT supportedFeaturesPrimitiveTopologyListRestart = new()
|
||||
{
|
||||
SType = StructureType.PhysicalDevicePrimitiveTopologyListRestartFeaturesExt,
|
||||
@@ -443,6 +455,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
TessellationShader = supportedFeatures.TessellationShader,
|
||||
VertexPipelineStoresAndAtomics = supportedFeatures.VertexPipelineStoresAndAtomics,
|
||||
RobustBufferAccess = useRobustBufferAccess,
|
||||
WideLines = supportedFeatures.WideLines,
|
||||
SampleRateShading = supportedFeatures.SampleRateShading,
|
||||
ShaderStorageImageExtendedFormats = supportedFeatures.ShaderStorageImageExtendedFormats,
|
||||
};
|
||||
@@ -495,6 +508,20 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
pExtendedFeatures = &featuresExtendedDynamicState;
|
||||
|
||||
if (physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState2.ExtensionName))
|
||||
{
|
||||
var featuresExtendedDynamicState2 = new PhysicalDeviceExtendedDynamicState2FeaturesEXT()
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceExtendedDynamicState2FeaturesExt,
|
||||
PNext = pExtendedFeatures,
|
||||
ExtendedDynamicState2 = supportedFeaturesExtExtendedDynamicState2.ExtendedDynamicState2,
|
||||
ExtendedDynamicState2LogicOp = supportedFeaturesExtExtendedDynamicState2.ExtendedDynamicState2LogicOp,
|
||||
ExtendedDynamicState2PatchControlPoints = supportedFeaturesExtExtendedDynamicState2.ExtendedDynamicState2PatchControlPoints,
|
||||
};
|
||||
|
||||
pExtendedFeatures = &featuresExtendedDynamicState2;
|
||||
}
|
||||
|
||||
PhysicalDeviceVulkan11Features featuresVk11 = new()
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceVulkan11Features,
|
||||
|
||||
@@ -39,6 +39,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
internal KhrSwapchain SwapchainApi { get; private set; }
|
||||
internal ExtConditionalRendering ConditionalRenderingApi { get; private set; }
|
||||
internal ExtExtendedDynamicState ExtendedDynamicStateApi { get; private set; }
|
||||
internal ExtExtendedDynamicState2 ExtendedDynamicState2Api { get; private set; }
|
||||
internal KhrPushDescriptor PushDescriptorApi { get; private set; }
|
||||
internal ExtTransformFeedback TransformFeedbackApi { get; private set; }
|
||||
internal KhrDrawIndirectCount DrawIndirectCountApi { get; private set; }
|
||||
@@ -55,7 +56,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
internal CommandBufferPool CommandBufferPool { get; private set; }
|
||||
internal PipelineLayoutCache PipelineLayoutCache { get; private set; }
|
||||
internal BackgroundResources BackgroundResources { get; private set; }
|
||||
internal ShaderCompilationQueue ShaderCompilationQueue { get; private set; }
|
||||
internal Action<Action> InterruptAction { get; private set; }
|
||||
internal SyncManager SyncManager { get; private set; }
|
||||
|
||||
@@ -97,7 +97,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
internal bool IsNvidiaPreTuring { get; private set; }
|
||||
internal bool IsIntelArc { get; private set; }
|
||||
internal bool IsQualcommProprietary { get; private set; }
|
||||
internal bool IsTurnip { get; private set; }
|
||||
internal bool IsMoltenVk { get; private set; }
|
||||
internal bool SupportsMTL31 { get; private set; }
|
||||
internal bool IsTBDR { get; private set; }
|
||||
@@ -129,12 +128,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
// Any device running on MacOS is using MoltenVK, even Intel and AMD vendors.
|
||||
IsMoltenVk = true;
|
||||
|
||||
// The default thread stack size on MacOS is low, and can cause stack overflow
|
||||
// on SPIR-V Cross during shader compilation.
|
||||
// As a workaround, we use this custom queue which allows us to specify the stack
|
||||
// size of the threads used for compilation.
|
||||
ShaderCompilationQueue = new ShaderCompilationQueue();
|
||||
}
|
||||
|
||||
SupportsMTL31 = OperatingSystem.IsMacOSVersionAtLeast(14);
|
||||
@@ -154,6 +147,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
ExtendedDynamicStateApi = extendedDynamicStateApi;
|
||||
}
|
||||
|
||||
if (Api.TryGetDeviceExtension(_instance.Instance, _device, out ExtExtendedDynamicState2 extendedDynamicState2Api))
|
||||
{
|
||||
ExtendedDynamicState2Api = extendedDynamicState2Api;
|
||||
}
|
||||
|
||||
if (Api.TryGetDeviceExtension(_instance.Instance, _device, out KhrPushDescriptor pushDescriptorApi))
|
||||
{
|
||||
PushDescriptorApi = pushDescriptorApi;
|
||||
@@ -256,6 +254,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
SType = StructureType.PhysicalDevicePrimitiveTopologyListRestartFeaturesExt,
|
||||
};
|
||||
|
||||
PhysicalDeviceExtendedDynamicState2FeaturesEXT featuresExtendedDynamicState2 = new()
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceExtendedDynamicState2FeaturesExt,
|
||||
};
|
||||
|
||||
PhysicalDeviceRobustness2FeaturesEXT featuresRobustness2 = new()
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceRobustness2FeaturesExt,
|
||||
@@ -296,6 +299,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
features2.PNext = &featuresPrimitiveTopologyListRestart;
|
||||
}
|
||||
|
||||
if (_physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState2.ExtensionName))
|
||||
{
|
||||
featuresExtendedDynamicState2.PNext = features2.PNext;
|
||||
features2.PNext = &featuresExtendedDynamicState2;
|
||||
}
|
||||
|
||||
if (_physicalDevice.IsDeviceExtensionPresent("VK_EXT_robustness2"))
|
||||
{
|
||||
featuresRobustness2.PNext = features2.PNext;
|
||||
@@ -404,8 +413,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
IsFeedbackLoopDevice = IsAmdRdna3;
|
||||
|
||||
IsTurnip = GpuRenderer.StartsWith("Turnip");
|
||||
|
||||
if (Vendor == Vendor.Nvidia)
|
||||
{
|
||||
Match match = VendorUtils.NvidiaConsumerClassRegex().Match(GpuRenderer);
|
||||
@@ -438,6 +445,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
properties.Limits.FramebufferDepthSampleCounts &
|
||||
properties.Limits.FramebufferStencilSampleCounts;
|
||||
|
||||
// Temporarily disable this, can be added back at a later date, make it easy to re-enable.
|
||||
// Disabled because currently causing Device Lost error on NVIDIA.
|
||||
featuresExtendedDynamicState2.ExtendedDynamicState2PatchControlPoints = false;
|
||||
|
||||
Capabilities = new HardwareCapabilities(
|
||||
_physicalDevice.IsDeviceExtensionPresent("VK_EXT_index_type_uint8"),
|
||||
supportsCustomBorderColor,
|
||||
@@ -454,6 +465,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
features2.Features.ShaderStorageImageMultisample,
|
||||
_physicalDevice.IsDeviceExtensionPresent(ExtConditionalRendering.ExtensionName),
|
||||
_physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState.ExtensionName),
|
||||
featuresExtendedDynamicState2,
|
||||
_physicalDevice.PhysicalDeviceProperties.Limits.MaxTessellationPatchSize,
|
||||
features2.Features.MultiViewport && !(IsMoltenVk && Vendor == Vendor.Amd), // Workaround for AMD on MoltenVK issue
|
||||
featuresRobustness2.NullDescriptor || IsMoltenVk,
|
||||
supportsPushDescriptors,
|
||||
@@ -469,6 +482,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_physicalDevice.IsDeviceExtensionPresent("VK_NV_viewport_array2"),
|
||||
_physicalDevice.IsDeviceExtensionPresent(ExtExternalMemoryHost.ExtensionName),
|
||||
supportsDepthClipControl && featuresDepthClipControl.DepthClipControl,
|
||||
_physicalDevice.PhysicalDeviceFeatures.WideLines,
|
||||
supportsAttachmentFeedbackLoop && featuresAttachmentFeedbackLoop.AttachmentFeedbackLoopLayout && IsFeedbackLoopDevice,
|
||||
supportsDynamicAttachmentFeedbackLoop && featuresDynamicAttachmentFeedbackLoop.AttachmentFeedbackLoopDynamicState && IsFeedbackLoopDevice,
|
||||
propertiesSubgroup.SubgroupSize,
|
||||
@@ -486,7 +500,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Api.TryGetDeviceExtension(_instance.Instance, _device, out ExtExternalMemoryHost hostMemoryApi);
|
||||
HostMemoryAllocator = new HostMemoryAllocator(MemoryAllocator, Api, hostMemoryApi, _device);
|
||||
|
||||
CommandBufferPool = new CommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex, IsQualcommProprietary, IsTurnip);
|
||||
CommandBufferPool = new CommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex, IsQualcommProprietary);
|
||||
|
||||
PipelineLayoutCache = new PipelineLayoutCache();
|
||||
|
||||
@@ -787,7 +801,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
supportsGeometryShader: Capabilities.SupportsGeometryShader,
|
||||
supportsGeometryShaderPassthrough: Capabilities.SupportsGeometryShaderPassthrough,
|
||||
supportsTransformFeedback: Capabilities.SupportsTransformFeedback,
|
||||
supportsImageBufferPixelAlignment: false,
|
||||
supportsImageLoadFormatted: features2.Features.ShaderStorageImageReadWithoutFormat,
|
||||
supportsLayerVertexTessellation: featuresVk12.ShaderOutputLayer,
|
||||
supportsMismatchingViewFormat: true,
|
||||
@@ -801,7 +814,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
supportsShaderNonUniformIndexing:
|
||||
featuresVk12.ShaderSampledImageArrayNonUniformIndexing &&
|
||||
featuresVk12.ShaderStorageImageArrayNonUniformIndexing,
|
||||
supportsTextureBufferPixelAlignment: false,
|
||||
supportsTextureGatherOffsets: features2.Features.ShaderImageGatherExtended,
|
||||
supportsTextureShadowLod: false,
|
||||
supportsVertexStoreAndAtomics: features2.Features.VertexPipelineStoresAndAtomics,
|
||||
@@ -810,6 +822,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
supportsViewportSwizzle: false,
|
||||
supportsIndirectParameters: true,
|
||||
supportsDepthClipControl: Capabilities.SupportsDepthClipControl,
|
||||
supportsExtendedDynamicState: Capabilities.SupportsExtendedDynamicState,
|
||||
supportsExtendedDynamicState2: Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2,
|
||||
supportsLogicOpDynamicState: Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2LogicOp,
|
||||
supportsPatchControlPointsDynamicState: Capabilities.SupportsExtendedDynamicState2.ExtendedDynamicState2PatchControlPoints,
|
||||
uniformBufferSetIndex: PipelineBase.UniformSetIndex,
|
||||
storageBufferSetIndex: PipelineBase.StorageSetIndex,
|
||||
textureSetIndex: PipelineBase.TextureSetIndex,
|
||||
@@ -1124,8 +1140,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
SurfaceApi.DestroySurface(_instance.Instance, _surface, null);
|
||||
|
||||
ShaderCompilationQueue?.Dispose();
|
||||
|
||||
Api.DestroyDevice(_device, null);
|
||||
|
||||
_debugMessenger.Dispose();
|
||||
|
||||
@@ -166,13 +166,15 @@ namespace Ryujinx.HLE.HOS.Applets.Error
|
||||
|
||||
string[] buttons = GetButtonsText(module, description, "DlgBtn");
|
||||
|
||||
bool showDetails = _horizon.Device.UIHandler.DisplayErrorAppletDialog($"Error Code: {module}-{description:0000}", "\n" + message, buttons);
|
||||
(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)
|
||||
{
|
||||
message = GetMessageText(module, description, "FlvMsg");
|
||||
buttons = GetButtonsText(module, description, "FlvBtn");
|
||||
|
||||
_horizon.Device.UIHandler.DisplayErrorAppletDialog($"Details: {module}-{description:0000}", "\n" + message, buttons);
|
||||
_horizon.Device.UIHandler.DisplayErrorAppletDialog($"Details: {module}-{description:0000}", "\n" + message, buttons, errorCodeTuple);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,9 +27,19 @@ namespace Ryujinx.HLE.HOS.Applets
|
||||
_normalSession = normalSession;
|
||||
_interactiveSession = interactiveSession;
|
||||
|
||||
// TODO(jduncanator): Parse PlayerSelectConfig from input data
|
||||
_normalSession.Push(BuildResponse());
|
||||
|
||||
UserProfile selected = _system.Device.UIHandler.ShowPlayerSelectDialog();
|
||||
if (selected == null)
|
||||
{
|
||||
_normalSession.Push(BuildResponse());
|
||||
}
|
||||
else if (selected.UserId == new UserId("00000000000000000000000000000080"))
|
||||
{
|
||||
_normalSession.Push(BuildGuestResponse());
|
||||
}
|
||||
else
|
||||
{
|
||||
_normalSession.Push(BuildResponse(selected));
|
||||
}
|
||||
AppletStateChanged?.Invoke(this, null);
|
||||
|
||||
_system.ReturnFocus();
|
||||
@@ -37,16 +47,34 @@ namespace Ryujinx.HLE.HOS.Applets
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
private byte[] BuildResponse()
|
||||
private byte[] BuildResponse(UserProfile selectedUser)
|
||||
{
|
||||
UserProfile currentUser = _system.AccountManager.LastOpenedUser;
|
||||
|
||||
using MemoryStream stream = MemoryStreamManager.Shared.GetStream();
|
||||
using BinaryWriter writer = new(stream);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using ARMeilleure.Memory;
|
||||
using ARMeilleure.Translation.PTC;
|
||||
using Ryujinx.Cpu;
|
||||
using Ryujinx.Graphics.Gpu;
|
||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||
@@ -9,12 +10,10 @@ namespace Ryujinx.HLE.HOS
|
||||
interface IArmProcessContext : IProcessContext
|
||||
{
|
||||
IDiskCacheLoadState Initialize(
|
||||
string titleIdText,
|
||||
string displayVersion,
|
||||
PtcCacheInfo cacheInfo,
|
||||
bool diskCacheEnabled,
|
||||
ulong codeAddress,
|
||||
ulong codeSize,
|
||||
string cacheSelector);
|
||||
ulong codeSize);
|
||||
}
|
||||
|
||||
class ArmProcessContext<T> : IArmProcessContext where T : class, IVirtualMemoryManagerTracked, IMemoryManager
|
||||
@@ -64,15 +63,13 @@ namespace Ryujinx.HLE.HOS
|
||||
}
|
||||
|
||||
public IDiskCacheLoadState Initialize(
|
||||
string titleIdText,
|
||||
string displayVersion,
|
||||
PtcCacheInfo cacheInfo,
|
||||
bool diskCacheEnabled,
|
||||
ulong codeAddress,
|
||||
ulong codeSize,
|
||||
string cacheSelector)
|
||||
ulong codeSize)
|
||||
{
|
||||
_cpuContext.PrepareCodeRange(codeAddress, codeSize);
|
||||
return _cpuContext.LoadDiskCache(titleIdText, displayVersion, diskCacheEnabled, cacheSelector);
|
||||
return _cpuContext.LoadDiskCache(cacheInfo, diskCacheEnabled);
|
||||
}
|
||||
|
||||
public void InvalidateCacheRegion(ulong address, ulong size)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using ARMeilleure.Translation.PTC;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Cpu;
|
||||
@@ -7,6 +8,7 @@ using Ryujinx.Cpu.LightningJit;
|
||||
using Ryujinx.Graphics.Gpu;
|
||||
using Ryujinx.HLE.HOS.Kernel;
|
||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||
using Ryujinx.HLE.Loaders.Processes;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -17,8 +19,10 @@ namespace Ryujinx.HLE.HOS
|
||||
{
|
||||
private readonly ITickSource _tickSource;
|
||||
private readonly GpuContext _gpu;
|
||||
private readonly string _titleIdText;
|
||||
private readonly ulong _programId;
|
||||
private readonly byte _programIndex;
|
||||
private readonly string _displayVersion;
|
||||
private readonly ProcessKind _processKind;
|
||||
private readonly bool _diskCacheEnabled;
|
||||
private readonly string _diskCacheSelector;
|
||||
private readonly ulong _codeAddress;
|
||||
@@ -29,8 +33,10 @@ namespace Ryujinx.HLE.HOS
|
||||
public ArmProcessContextFactory(
|
||||
ITickSource tickSource,
|
||||
GpuContext gpu,
|
||||
string titleIdText,
|
||||
ulong programId,
|
||||
byte programIndex,
|
||||
string displayVersion,
|
||||
ProcessKind processKind,
|
||||
bool diskCacheEnabled,
|
||||
string diskCacheSelector,
|
||||
ulong codeAddress,
|
||||
@@ -38,8 +44,10 @@ namespace Ryujinx.HLE.HOS
|
||||
{
|
||||
_tickSource = tickSource;
|
||||
_gpu = gpu;
|
||||
_titleIdText = titleIdText;
|
||||
_programId = programId;
|
||||
_programIndex = programIndex;
|
||||
_displayVersion = displayVersion;
|
||||
_processKind = processKind;
|
||||
_diskCacheEnabled = diskCacheEnabled;
|
||||
_diskCacheSelector = diskCacheSelector;
|
||||
_codeAddress = codeAddress;
|
||||
@@ -121,8 +129,18 @@ namespace Ryujinx.HLE.HOS
|
||||
}
|
||||
|
||||
string cacheSelector = _diskCacheSelector ?? "default";
|
||||
string programIdText = _programId == 0 ? string.Empty : $"{_programId:x16}";
|
||||
string applicationIdText = _programId == 0 ? string.Empty : $"{_programId & ~0xFul:x16}";
|
||||
PtcCacheInfo cacheInfo = new(
|
||||
pid,
|
||||
programIdText,
|
||||
applicationIdText,
|
||||
_programIndex,
|
||||
_displayVersion,
|
||||
_processKind.ToString(),
|
||||
cacheSelector);
|
||||
|
||||
DiskCacheLoadState = processContext.Initialize(_titleIdText, _displayVersion, _diskCacheEnabled, _codeAddress, _codeSize, cacheSelector);
|
||||
DiskCacheLoadState = processContext.Initialize(cacheInfo, _diskCacheEnabled, _codeAddress, _codeSize);
|
||||
|
||||
return processContext;
|
||||
}
|
||||
|
||||
@@ -26,12 +26,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
|
||||
{
|
||||
MemoryArrange.MemoryArrange4GiB or
|
||||
MemoryArrange.MemoryArrange4GiBSystemDev or
|
||||
MemoryArrange.MemoryArrange6GiBAppletDev => 3152 * MiB,
|
||||
MemoryArrange.MemoryArrange6GiBAppletDev => 3236 * MiB,
|
||||
MemoryArrange.MemoryArrange4GiBAppletDev => 2048 * MiB,
|
||||
MemoryArrange.MemoryArrange6GiB => 4783 * MiB,
|
||||
MemoryArrange.MemoryArrange8GiB => 6831 * MiB,
|
||||
MemoryArrange.MemoryArrange10GiB => 8879 * MiB,
|
||||
MemoryArrange.MemoryArrange12GiB => 10927 * MiB,
|
||||
MemoryArrange.MemoryArrange6GiB => 4867 * MiB,
|
||||
MemoryArrange.MemoryArrange8GiB => 6915 * MiB,
|
||||
MemoryArrange.MemoryArrange10GiB => 8963 * MiB,
|
||||
MemoryArrange.MemoryArrange12GiB => 11011 * MiB,
|
||||
_ => throw new ArgumentException($"Invalid memory arrange \"{arrange}\"."),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -203,7 +203,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
heapRegion.Size = memConfig switch {
|
||||
MemoryConfiguration.MemoryConfiguration8GiB => 0x200000000u,
|
||||
MemoryConfiguration.MemoryConfiguration10GiB when !isHeapLimited => 0x280000000u,
|
||||
MemoryConfiguration.MemoryConfiguration10GiB => 0x200000000u,
|
||||
MemoryConfiguration.MemoryConfiguration12GiB when !isHeapLimited => 0x300000000u,
|
||||
MemoryConfiguration.MemoryConfiguration12GiB => 0x200000000u,
|
||||
_ => 0x180000000u
|
||||
};
|
||||
stackRegion.Size = 0;
|
||||
@@ -238,7 +240,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
heapRegion.Size = memConfig switch {
|
||||
MemoryConfiguration.MemoryConfiguration8GiB => 0x200000000u,
|
||||
MemoryConfiguration.MemoryConfiguration10GiB when !isHeapLimited => 0x280000000u,
|
||||
MemoryConfiguration.MemoryConfiguration10GiB => 0x200000000u,
|
||||
MemoryConfiguration.MemoryConfiguration12GiB when !isHeapLimited => 0x300000000u,
|
||||
MemoryConfiguration.MemoryConfiguration12GiB => 0x200000000u,
|
||||
_ => 0x180000000u
|
||||
};
|
||||
stackRegion.Size = 1UL << (addressSpaceWidth - 8);
|
||||
@@ -257,7 +261,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
heapRegion.Size = memConfig switch {
|
||||
MemoryConfiguration.MemoryConfiguration8GiB => 0x200000000u,
|
||||
MemoryConfiguration.MemoryConfiguration10GiB when !isHeapLimited => 0x280000000u,
|
||||
MemoryConfiguration.MemoryConfiguration10GiB => 0x200000000u,
|
||||
MemoryConfiguration.MemoryConfiguration12GiB when !isHeapLimited => 0x300000000u,
|
||||
MemoryConfiguration.MemoryConfiguration12GiB => 0x200000000u,
|
||||
_ => 0x180000000u
|
||||
};
|
||||
stackRegion.Size = 0x80000000;
|
||||
|
||||
@@ -15,7 +15,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
|
||||
private readonly Dictionary<ulong, List<KThread>> _condVarThreads;
|
||||
private readonly Dictionary<ulong, List<KThread>> _arbiterThreads;
|
||||
private readonly ByDynamicPriority _byDynamicPriority;
|
||||
|
||||
public KAddressArbiter(KernelContext context)
|
||||
{
|
||||
@@ -23,7 +22,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
|
||||
_condVarThreads = [];
|
||||
_arbiterThreads = [];
|
||||
_byDynamicPriority = new ByDynamicPriority();
|
||||
}
|
||||
|
||||
public Result ArbitrateLock(int ownerHandle, ulong mutexAddress, int requesterHandle)
|
||||
@@ -142,14 +140,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
|
||||
if (_condVarThreads.TryGetValue(condVarAddress, out List<KThread> threads))
|
||||
{
|
||||
int i = 0;
|
||||
int i = FindDynamicPriorityFifoInsertionIndex(threads, currentThread);
|
||||
|
||||
|
||||
if (threads.Count > 0)
|
||||
{
|
||||
i = threads.BinarySearch(currentThread, _byDynamicPriority);
|
||||
if (i < 0) i = ~i;
|
||||
}
|
||||
|
||||
threads.Insert(i, currentThread);
|
||||
}
|
||||
else
|
||||
@@ -332,14 +325,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
|
||||
if (_arbiterThreads.TryGetValue(address, out List<KThread> threads))
|
||||
{
|
||||
int i = 0;
|
||||
int i = FindDynamicPriorityFifoInsertionIndex(threads, currentThread);
|
||||
|
||||
|
||||
if (threads.Count > 0)
|
||||
{
|
||||
i = threads.BinarySearch(currentThread, _byDynamicPriority);
|
||||
if (i < 0) i = ~i;
|
||||
}
|
||||
|
||||
threads.Insert(i, currentThread);
|
||||
}
|
||||
else
|
||||
@@ -424,14 +412,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
|
||||
if (_arbiterThreads.TryGetValue(address, out List<KThread> threads))
|
||||
{
|
||||
int i = 0;
|
||||
int i = FindDynamicPriorityFifoInsertionIndex(threads, currentThread);
|
||||
|
||||
|
||||
if (threads.Count > 0)
|
||||
{
|
||||
i = threads.BinarySearch(currentThread, _byDynamicPriority);
|
||||
if (i < 0) i = ~i;
|
||||
}
|
||||
|
||||
threads.Insert(i, currentThread);
|
||||
}
|
||||
else
|
||||
@@ -627,12 +610,28 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
return validCount;
|
||||
}
|
||||
|
||||
private class ByDynamicPriority : IComparer<KThread>
|
||||
private static int FindDynamicPriorityFifoInsertionIndex(List<KThread> threads, KThread currentThread)
|
||||
{
|
||||
public int Compare(KThread x, KThread y)
|
||||
int low = 0;
|
||||
int high = threads.Count;
|
||||
|
||||
// Lower numeric values represent higher priorities. Use upper-bound insertion
|
||||
// to preserve FIFO order among waiters with the same dynamic priority.
|
||||
while (low < high)
|
||||
{
|
||||
return x!.DynamicPriority.CompareTo(y!.DynamicPriority);
|
||||
int middle = low + ((high - low) >> 1);
|
||||
|
||||
if (threads[middle].DynamicPriority <= currentThread.DynamicPriority)
|
||||
{
|
||||
low = middle + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
high = middle;
|
||||
}
|
||||
}
|
||||
|
||||
return low;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -783,8 +783,28 @@ namespace Ryujinx.HLE.HOS
|
||||
}
|
||||
|
||||
List<Cheat> cheats = mods.Cheats;
|
||||
Dictionary<string, ulong> processExes = tamperInfo.BuildIds.Zip(tamperInfo.CodeAddresses, (k, v) => new { k, v })
|
||||
.ToDictionary(x => x.k[..Math.Min(Cheat.CheatIdSize, x.k.Length)], x => x.v);
|
||||
Dictionary<string, ulong> processExes = new();
|
||||
|
||||
foreach ((string buildId, ulong codeAddress) in tamperInfo.BuildIds.Zip(tamperInfo.CodeAddresses))
|
||||
{
|
||||
string normalizedBuildId = buildId[..Math.Min(Cheat.CheatIdSize, buildId.Length)];
|
||||
|
||||
if (processExes.TryGetValue(normalizedBuildId, out ulong existingAddress))
|
||||
{
|
||||
if (existingAddress != codeAddress)
|
||||
{
|
||||
Logger.Warning?.Print(
|
||||
LogClass.ModLoader,
|
||||
$"Duplicate BuildId prefix '{normalizedBuildId}' has different code addresses. " +
|
||||
$"Existing: 0x{existingAddress:X}, duplicate: 0x{codeAddress:X}. " +
|
||||
$"Keeping the first one.");
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
processExes.Add(normalizedBuildId, codeAddress);
|
||||
}
|
||||
|
||||
foreach (Cheat cheat in cheats)
|
||||
{
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 7.8 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user