mirror of
https://git.naxdy.org/Mirror/Ryujinx.git
synced 2025-02-21 00:23:36 +00:00
commit
1100e5e1a6
273 changed files with 39733 additions and 1502 deletions
|
@ -1,5 +1,6 @@
|
||||||
using ARMeilleure.Translation;
|
using ARMeilleure.Translation;
|
||||||
using ARMeilleure.Translation.PTC;
|
using ARMeilleure.Translation.PTC;
|
||||||
|
using Avalonia;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using LibHac.Tools.FsSystem;
|
using LibHac.Tools.FsSystem;
|
||||||
|
@ -13,6 +14,7 @@ using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.Input;
|
using Ryujinx.Ava.Input;
|
||||||
using Ryujinx.Ava.Ui.Controls;
|
using Ryujinx.Ava.Ui.Controls;
|
||||||
using Ryujinx.Ava.Ui.Models;
|
using Ryujinx.Ava.Ui.Models;
|
||||||
|
using Ryujinx.Ava.Ui.Vulkan;
|
||||||
using Ryujinx.Ava.Ui.Windows;
|
using Ryujinx.Ava.Ui.Windows;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
|
@ -22,6 +24,7 @@ using Ryujinx.Graphics.GAL;
|
||||||
using Ryujinx.Graphics.GAL.Multithreading;
|
using Ryujinx.Graphics.GAL.Multithreading;
|
||||||
using Ryujinx.Graphics.Gpu;
|
using Ryujinx.Graphics.Gpu;
|
||||||
using Ryujinx.Graphics.OpenGL;
|
using Ryujinx.Graphics.OpenGL;
|
||||||
|
using Ryujinx.Graphics.Vulkan;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.HOS;
|
using Ryujinx.HLE.HOS;
|
||||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||||
|
@ -590,7 +593,23 @@ namespace Ryujinx.Ava
|
||||||
{
|
{
|
||||||
VirtualFileSystem.ReloadKeySet();
|
VirtualFileSystem.ReloadKeySet();
|
||||||
|
|
||||||
IRenderer renderer = new Renderer();
|
IRenderer renderer;
|
||||||
|
|
||||||
|
if (Program.UseVulkan)
|
||||||
|
{
|
||||||
|
var vulkan = AvaloniaLocator.Current.GetService<VulkanPlatformInterface>();
|
||||||
|
renderer = new VulkanGraphicsDevice(vulkan.Instance.InternalHandle,
|
||||||
|
vulkan.Device.InternalHandle,
|
||||||
|
vulkan.PhysicalDevice.InternalHandle,
|
||||||
|
vulkan.Device.Queue.InternalHandle,
|
||||||
|
vulkan.PhysicalDevice.QueueFamilyIndex,
|
||||||
|
vulkan.Device.Lock);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
renderer = new Renderer();
|
||||||
|
}
|
||||||
|
|
||||||
IHardwareDeviceDriver deviceDriver = new DummyHardwareDeviceDriver();
|
IHardwareDeviceDriver deviceDriver = new DummyHardwareDeviceDriver();
|
||||||
|
|
||||||
BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading;
|
BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading;
|
||||||
|
@ -800,9 +819,12 @@ namespace Ryujinx.Ava
|
||||||
|
|
||||||
_renderer.ScreenCaptured += Renderer_ScreenCaptured;
|
_renderer.ScreenCaptured += Renderer_ScreenCaptured;
|
||||||
|
|
||||||
(_renderer as Renderer).InitializeBackgroundContext(SPBOpenGLContext.CreateBackgroundContext(Renderer.GameContext));
|
if (!Program.UseVulkan)
|
||||||
|
{
|
||||||
|
(_renderer as Renderer).InitializeBackgroundContext(SPBOpenGLContext.CreateBackgroundContext((Renderer as OpenGLRendererControl).GameContext));
|
||||||
|
|
||||||
Renderer.MakeCurrent();
|
Renderer.MakeCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
Device.Gpu.Renderer.Initialize(_glLogLevel);
|
Device.Gpu.Renderer.Initialize(_glLogLevel);
|
||||||
|
|
||||||
|
@ -861,8 +883,6 @@ namespace Ryujinx.Ava
|
||||||
dockedMode += $" ({scale}x)";
|
dockedMode += $" ({scale}x)";
|
||||||
}
|
}
|
||||||
|
|
||||||
string vendor = _renderer is Renderer renderer ? renderer.GpuVendor : "";
|
|
||||||
|
|
||||||
StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs(
|
StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs(
|
||||||
Device.EnableDeviceVsync,
|
Device.EnableDeviceVsync,
|
||||||
Device.GetVolume(),
|
Device.GetVolume(),
|
||||||
|
@ -870,7 +890,7 @@ namespace Ryujinx.Ava
|
||||||
ConfigurationState.Instance.Graphics.AspectRatio.Value.ToText(),
|
ConfigurationState.Instance.Graphics.AspectRatio.Value.ToText(),
|
||||||
LocaleManager.Instance["Game"] + $": {Device.Statistics.GetGameFrameRate():00.00} FPS ({Device.Statistics.GetGameFrameTime():00.00} ms)",
|
LocaleManager.Instance["Game"] + $": {Device.Statistics.GetGameFrameRate():00.00} FPS ({Device.Statistics.GetGameFrameTime():00.00} ms)",
|
||||||
$"FIFO: {Device.Statistics.GetFifoPercent():00.00} %",
|
$"FIFO: {Device.Statistics.GetFifoPercent():00.00} %",
|
||||||
$"GPU: {vendor}"));
|
$"GPU: {_renderer.GetHardwareInfo().GpuVendor}"));
|
||||||
|
|
||||||
Renderer.Present(image);
|
Renderer.Present(image);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ using Avalonia;
|
||||||
using Avalonia.OpenGL;
|
using Avalonia.OpenGL;
|
||||||
using Avalonia.Rendering;
|
using Avalonia.Rendering;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
|
using Ryujinx.Ava.Ui.Backend;
|
||||||
using Ryujinx.Ava.Ui.Controls;
|
using Ryujinx.Ava.Ui.Controls;
|
||||||
using Ryujinx.Ava.Ui.Windows;
|
using Ryujinx.Ava.Ui.Windows;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
|
@ -11,9 +12,12 @@ using Ryujinx.Common.GraphicsDriver;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Common.System;
|
using Ryujinx.Common.System;
|
||||||
using Ryujinx.Common.SystemInfo;
|
using Ryujinx.Common.SystemInfo;
|
||||||
|
using Ryujinx.Graphics.Vulkan;
|
||||||
using Ryujinx.Modules;
|
using Ryujinx.Modules;
|
||||||
using Ryujinx.Ui.Common;
|
using Ryujinx.Ui.Common;
|
||||||
using Ryujinx.Ui.Common.Configuration;
|
using Ryujinx.Ui.Common.Configuration;
|
||||||
|
using Silk.NET.Vulkan.Extensions.EXT;
|
||||||
|
using Silk.NET.Vulkan.Extensions.KHR;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
@ -25,17 +29,20 @@ namespace Ryujinx.Ava
|
||||||
internal class Program
|
internal class Program
|
||||||
{
|
{
|
||||||
public static double WindowScaleFactor { get; set; }
|
public static double WindowScaleFactor { get; set; }
|
||||||
|
public static double ActualScaleFactor { get; set; }
|
||||||
public static string Version { get; private set; }
|
public static string Version { get; private set; }
|
||||||
public static string ConfigurationPath { get; private set; }
|
public static string ConfigurationPath { get; private set; }
|
||||||
public static string CommandLineProfile { get; set; }
|
public static string CommandLineProfile { get; set; }
|
||||||
public static bool PreviewerDetached { get; private set; }
|
public static bool PreviewerDetached { get; private set; }
|
||||||
|
|
||||||
public static RenderTimer RenderTimer { get; private set; }
|
public static RenderTimer RenderTimer { get; private set; }
|
||||||
|
public static bool UseVulkan { get; private set; }
|
||||||
|
|
||||||
[DllImport("user32.dll", SetLastError = true)]
|
[DllImport("user32.dll", SetLastError = true)]
|
||||||
public static extern int MessageBoxA(IntPtr hWnd, string text, string caption, uint type);
|
public static extern int MessageBoxA(IntPtr hWnd, string text, string caption, uint type);
|
||||||
|
|
||||||
private const uint MB_ICONWARNING = 0x30;
|
private const uint MB_ICONWARNING = 0x30;
|
||||||
|
private const int BaseDpi = 96;
|
||||||
|
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
|
@ -66,7 +73,7 @@ namespace Ryujinx.Ava
|
||||||
EnableMultiTouch = true,
|
EnableMultiTouch = true,
|
||||||
EnableIme = true,
|
EnableIme = true,
|
||||||
UseEGL = false,
|
UseEGL = false,
|
||||||
UseGpu = true,
|
UseGpu = !UseVulkan,
|
||||||
GlProfiles = new List<GlVersion>()
|
GlProfiles = new List<GlVersion>()
|
||||||
{
|
{
|
||||||
new GlVersion(GlProfileType.OpenGL, 4, 3)
|
new GlVersion(GlProfileType.OpenGL, 4, 3)
|
||||||
|
@ -75,7 +82,7 @@ namespace Ryujinx.Ava
|
||||||
.With(new Win32PlatformOptions
|
.With(new Win32PlatformOptions
|
||||||
{
|
{
|
||||||
EnableMultitouch = true,
|
EnableMultitouch = true,
|
||||||
UseWgl = true,
|
UseWgl = !UseVulkan,
|
||||||
WglProfiles = new List<GlVersion>()
|
WglProfiles = new List<GlVersion>()
|
||||||
{
|
{
|
||||||
new GlVersion(GlProfileType.OpenGL, 4, 3)
|
new GlVersion(GlProfileType.OpenGL, 4, 3)
|
||||||
|
@ -84,6 +91,19 @@ namespace Ryujinx.Ava
|
||||||
CompositionBackdropCornerRadius = 8f,
|
CompositionBackdropCornerRadius = 8f,
|
||||||
})
|
})
|
||||||
.UseSkia()
|
.UseSkia()
|
||||||
|
.With(new Ui.Vulkan.VulkanOptions()
|
||||||
|
{
|
||||||
|
ApplicationName = "Ryujinx.Graphics.Vulkan",
|
||||||
|
VulkanVersion = new Version(1, 2),
|
||||||
|
MaxQueueCount = 2,
|
||||||
|
PreferDiscreteGpu = true,
|
||||||
|
PreferredDevice = !PreviewerDetached ? "" : ConfigurationState.Instance.Graphics.PreferredGpu.Value,
|
||||||
|
UseDebug = !PreviewerDetached ? false : ConfigurationState.Instance.Logger.GraphicsDebugLevel.Value != GraphicsDebugLevel.None,
|
||||||
|
})
|
||||||
|
.With(new SkiaOptions()
|
||||||
|
{
|
||||||
|
CustomGpuFactory = UseVulkan ? SkiaGpuFactory.CreateVulkanGpu : null
|
||||||
|
})
|
||||||
.AfterSetup(_ =>
|
.AfterSetup(_ =>
|
||||||
{
|
{
|
||||||
AvaloniaLocator.CurrentMutable
|
AvaloniaLocator.CurrentMutable
|
||||||
|
@ -136,9 +156,6 @@ namespace Ryujinx.Ava
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make process DPI aware for proper window sizing on high-res screens.
|
|
||||||
WindowScaleFactor = ForceDpiAware.GetWindowScaleFactor();
|
|
||||||
|
|
||||||
// Delete backup files after updating.
|
// Delete backup files after updating.
|
||||||
Task.Run(Updater.CleanupUpdate);
|
Task.Run(Updater.CleanupUpdate);
|
||||||
|
|
||||||
|
@ -162,6 +179,18 @@ namespace Ryujinx.Ava
|
||||||
|
|
||||||
ReloadConfig();
|
ReloadConfig();
|
||||||
|
|
||||||
|
UseVulkan = PreviewerDetached ? ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Vulkan : false;
|
||||||
|
|
||||||
|
if (UseVulkan)
|
||||||
|
{
|
||||||
|
// With a custom gpu backend, avalonia doesn't enable dpi awareness, so the backend must handle it. This isn't so for the opengl backed,
|
||||||
|
// as that uses avalonia's gpu backend and it's enabled there.
|
||||||
|
ForceDpiAware.Windows();
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowScaleFactor = ForceDpiAware.GetWindowScaleFactor();
|
||||||
|
ActualScaleFactor = ForceDpiAware.GetActualScaleFactor() / BaseDpi;
|
||||||
|
|
||||||
// Logging system information.
|
// Logging system information.
|
||||||
PrintSystemInfo();
|
PrintSystemInfo();
|
||||||
|
|
||||||
|
|
|
@ -28,10 +28,13 @@
|
||||||
<PackageReference Include="jp2masa.Avalonia.Flexbox" Version="0.2.0" />
|
<PackageReference Include="jp2masa.Avalonia.Flexbox" Version="0.2.0" />
|
||||||
<PackageReference Include="DynamicData" Version="7.7.14" />
|
<PackageReference Include="DynamicData" Version="7.7.14" />
|
||||||
<PackageReference Include="FluentAvaloniaUI" Version="1.3.4" />
|
<PackageReference Include="FluentAvaloniaUI" Version="1.3.4" />
|
||||||
|
<PackageReference Include="OpenTK.Core" Version="4.7.2" />
|
||||||
|
|
||||||
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
|
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
|
||||||
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="4.4.0-build9" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
|
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="4.4.0-build9" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
|
||||||
<PackageReference Include="OpenTK.Graphics" Version="4.7.2" />
|
<PackageReference Include="Silk.NET.Vulkan" Version="2.10.1" />
|
||||||
|
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.10.1" />
|
||||||
|
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.10.1" />
|
||||||
<PackageReference Include="SPB" Version="0.0.4-build17" />
|
<PackageReference Include="SPB" Version="0.0.4-build17" />
|
||||||
<PackageReference Include="SharpZipLib" Version="1.3.3" />
|
<PackageReference Include="SharpZipLib" Version="1.3.3" />
|
||||||
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
|
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
|
||||||
|
@ -39,6 +42,7 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Ryujinx.Audio.Backends.SDL2\Ryujinx.Audio.Backends.SDL2.csproj" />
|
<ProjectReference Include="..\Ryujinx.Audio.Backends.SDL2\Ryujinx.Audio.Backends.SDL2.csproj" />
|
||||||
|
<ProjectReference Include="..\Ryujinx.Graphics.Vulkan\Ryujinx.Graphics.Vulkan.csproj" />
|
||||||
<ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" />
|
<ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" />
|
||||||
<ProjectReference Include="..\Ryujinx.Input.SDL2\Ryujinx.Input.SDL2.csproj" />
|
<ProjectReference Include="..\Ryujinx.Input.SDL2\Ryujinx.Input.SDL2.csproj" />
|
||||||
<ProjectReference Include="..\Ryujinx.Audio.Backends.OpenAL\Ryujinx.Audio.Backends.OpenAL.csproj" />
|
<ProjectReference Include="..\Ryujinx.Audio.Backends.OpenAL\Ryujinx.Audio.Backends.OpenAL.csproj" />
|
||||||
|
|
|
@ -135,7 +135,7 @@ namespace Ryujinx.Ava.Ui.Applet
|
||||||
Dispatcher.UIThread.Post(() =>
|
Dispatcher.UIThread.Post(() =>
|
||||||
{
|
{
|
||||||
_hiddenTextBox.Clear();
|
_hiddenTextBox.Clear();
|
||||||
_parent.GlRenderer.Focus();
|
_parent.RendererControl.Focus();
|
||||||
|
|
||||||
_parent = null;
|
_parent = null;
|
||||||
});
|
});
|
||||||
|
|
71
Ryujinx.Ava/Ui/Backend/BackendSurface.cs
Normal file
71
Ryujinx.Ava/Ui/Backend/BackendSurface.cs
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
using Avalonia;
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using static Ryujinx.Ava.Ui.Backend.Interop;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Ui.Backend
|
||||||
|
{
|
||||||
|
public abstract class BackendSurface : IDisposable
|
||||||
|
{
|
||||||
|
protected IntPtr Display => _display;
|
||||||
|
|
||||||
|
private IntPtr _display = IntPtr.Zero;
|
||||||
|
|
||||||
|
[DllImport("libX11.so.6")]
|
||||||
|
public static extern IntPtr XOpenDisplay(IntPtr display);
|
||||||
|
|
||||||
|
[DllImport("libX11.so.6")]
|
||||||
|
public static extern int XCloseDisplay(IntPtr display);
|
||||||
|
|
||||||
|
private PixelSize _currentSize;
|
||||||
|
public IntPtr Handle { get; protected set; }
|
||||||
|
|
||||||
|
public bool IsDisposed { get; private set; }
|
||||||
|
|
||||||
|
public BackendSurface(IntPtr handle)
|
||||||
|
{
|
||||||
|
Handle = handle;
|
||||||
|
|
||||||
|
if (OperatingSystem.IsLinux())
|
||||||
|
{
|
||||||
|
_display = XOpenDisplay(IntPtr.Zero);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PixelSize Size
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
PixelSize size = new PixelSize();
|
||||||
|
if (OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
GetClientRect(Handle, out var rect);
|
||||||
|
size = new PixelSize(rect.right, rect.bottom);
|
||||||
|
}
|
||||||
|
else if (OperatingSystem.IsLinux())
|
||||||
|
{
|
||||||
|
XWindowAttributes attributes = new XWindowAttributes();
|
||||||
|
XGetWindowAttributes(Display, Handle, ref attributes);
|
||||||
|
|
||||||
|
size = new PixelSize(attributes.width, attributes.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
_currentSize = size;
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PixelSize CurrentSize => _currentSize;
|
||||||
|
|
||||||
|
public virtual void Dispose()
|
||||||
|
{
|
||||||
|
IsDisposed = true;
|
||||||
|
|
||||||
|
if (_display != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
XCloseDisplay(_display);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
49
Ryujinx.Ava/Ui/Backend/Interop.cs
Normal file
49
Ryujinx.Ava/Ui/Backend/Interop.cs
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
using FluentAvalonia.Interop;
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Ui.Backend
|
||||||
|
{
|
||||||
|
public static class Interop
|
||||||
|
{
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct XWindowAttributes
|
||||||
|
{
|
||||||
|
public int x;
|
||||||
|
public int y;
|
||||||
|
public int width;
|
||||||
|
public int height;
|
||||||
|
public int border_width;
|
||||||
|
public int depth;
|
||||||
|
public IntPtr visual;
|
||||||
|
public IntPtr root;
|
||||||
|
public int c_class;
|
||||||
|
public int bit_gravity;
|
||||||
|
public int win_gravity;
|
||||||
|
public int backing_store;
|
||||||
|
public IntPtr backing_planes;
|
||||||
|
public IntPtr backing_pixel;
|
||||||
|
public int save_under;
|
||||||
|
public IntPtr colormap;
|
||||||
|
public int map_installed;
|
||||||
|
public int map_state;
|
||||||
|
public IntPtr all_event_masks;
|
||||||
|
public IntPtr your_event_mask;
|
||||||
|
public IntPtr do_not_propagate_mask;
|
||||||
|
public int override_direct;
|
||||||
|
public IntPtr screen;
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
public static extern bool GetClientRect(IntPtr hwnd, out RECT lpRect);
|
||||||
|
|
||||||
|
[DllImport("libX11.so.6")]
|
||||||
|
public static extern int XCloseDisplay(IntPtr display);
|
||||||
|
|
||||||
|
[DllImport("libX11.so.6")]
|
||||||
|
public static extern int XGetWindowAttributes(IntPtr display, IntPtr window, ref XWindowAttributes attributes);
|
||||||
|
|
||||||
|
[DllImport("libX11.so.6")]
|
||||||
|
public static extern IntPtr XOpenDisplay(IntPtr display);
|
||||||
|
}
|
||||||
|
}
|
23
Ryujinx.Ava/Ui/Backend/SkiaGpuFactory.cs
Normal file
23
Ryujinx.Ava/Ui/Backend/SkiaGpuFactory.cs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Skia;
|
||||||
|
using Ryujinx.Ava.Ui.Vulkan;
|
||||||
|
using Ryujinx.Ava.Ui.Backend.Vulkan;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Ui.Backend
|
||||||
|
{
|
||||||
|
public static class SkiaGpuFactory
|
||||||
|
{
|
||||||
|
public static ISkiaGpu CreateVulkanGpu()
|
||||||
|
{
|
||||||
|
var skiaOptions = AvaloniaLocator.Current.GetService<SkiaOptions>() ?? new SkiaOptions();
|
||||||
|
var platformInterface = AvaloniaLocator.Current.GetService<VulkanPlatformInterface>();
|
||||||
|
if (platformInterface == null)
|
||||||
|
{
|
||||||
|
VulkanPlatformInterface.TryInitialize();
|
||||||
|
}
|
||||||
|
var gpu = new VulkanSkiaGpu(skiaOptions.MaxGpuResourceSizeBytes);
|
||||||
|
AvaloniaLocator.CurrentMutable.Bind<VulkanSkiaGpu>().ToConstant(gpu);
|
||||||
|
return gpu;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
Ryujinx.Ava/Ui/Backend/Vulkan/ResultExtensions.cs
Normal file
13
Ryujinx.Ava/Ui/Backend/Vulkan/ResultExtensions.cs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
using System;
|
||||||
|
using Silk.NET.Vulkan;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
|
{
|
||||||
|
public static class ResultExtensions
|
||||||
|
{
|
||||||
|
public static void ThrowOnError(this Result result)
|
||||||
|
{
|
||||||
|
if (result != Result.Success) throw new Exception($"Unexpected API error \"{result}\".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
134
Ryujinx.Ava/Ui/Backend/Vulkan/Skia/VulkanRenderTarget.cs
Normal file
134
Ryujinx.Ava/Ui/Backend/Vulkan/Skia/VulkanRenderTarget.cs
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
using System;
|
||||||
|
using Avalonia.Skia;
|
||||||
|
using Ryujinx.Ava.Ui.Vulkan;
|
||||||
|
using Ryujinx.Ava.Ui.Vulkan.Surfaces;
|
||||||
|
using SkiaSharp;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Ui.Backend.Vulkan
|
||||||
|
{
|
||||||
|
internal class VulkanRenderTarget : ISkiaGpuRenderTarget
|
||||||
|
{
|
||||||
|
public GRContext GrContext { get; set; }
|
||||||
|
|
||||||
|
private readonly VulkanSurfaceRenderTarget _surface;
|
||||||
|
private readonly IVulkanPlatformSurface _vulkanPlatformSurface;
|
||||||
|
|
||||||
|
public VulkanRenderTarget(VulkanPlatformInterface vulkanPlatformInterface,
|
||||||
|
IVulkanPlatformSurface vulkanPlatformSurface)
|
||||||
|
{
|
||||||
|
_surface = vulkanPlatformInterface.CreateRenderTarget(vulkanPlatformSurface);
|
||||||
|
_vulkanPlatformSurface = vulkanPlatformSurface;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_surface.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ISkiaGpuRenderSession BeginRenderingSession()
|
||||||
|
{
|
||||||
|
var session = _surface.BeginDraw(_vulkanPlatformSurface.Scaling);
|
||||||
|
bool success = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var disp = session.Display;
|
||||||
|
var api = session.Api;
|
||||||
|
|
||||||
|
var size = session.Size;
|
||||||
|
var scaling = session.Scaling;
|
||||||
|
if (size.Width <= 0 || size.Height <= 0 || scaling < 0)
|
||||||
|
{
|
||||||
|
size = new Avalonia.PixelSize(1, 1);
|
||||||
|
scaling = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (GrContext)
|
||||||
|
{
|
||||||
|
GrContext.ResetContext();
|
||||||
|
|
||||||
|
var imageInfo = new GRVkImageInfo()
|
||||||
|
{
|
||||||
|
CurrentQueueFamily = disp.QueueFamilyIndex,
|
||||||
|
Format = _surface.ImageFormat,
|
||||||
|
Image = _surface.Image.Handle,
|
||||||
|
ImageLayout = (uint)_surface.Image.CurrentLayout,
|
||||||
|
ImageTiling = (uint)_surface.Image.Tiling,
|
||||||
|
ImageUsageFlags = _surface.UsageFlags,
|
||||||
|
LevelCount = _surface.MipLevels,
|
||||||
|
SampleCount = 1,
|
||||||
|
Protected = false,
|
||||||
|
Alloc = new GRVkAlloc()
|
||||||
|
{
|
||||||
|
Memory = _surface.Image.MemoryHandle,
|
||||||
|
Flags = 0,
|
||||||
|
Offset = 0,
|
||||||
|
Size = _surface.MemorySize
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var renderTarget =
|
||||||
|
new GRBackendRenderTarget((int)size.Width, (int)size.Height, 1,
|
||||||
|
imageInfo);
|
||||||
|
var surface = SKSurface.Create(GrContext, renderTarget,
|
||||||
|
session.IsYFlipped ? GRSurfaceOrigin.TopLeft : GRSurfaceOrigin.BottomLeft,
|
||||||
|
_surface.IsRgba ? SKColorType.Rgba8888 : SKColorType.Bgra8888, SKColorSpace.CreateSrgb());
|
||||||
|
|
||||||
|
if (surface == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"Surface can't be created with the provided render target");
|
||||||
|
}
|
||||||
|
|
||||||
|
success = true;
|
||||||
|
|
||||||
|
return new VulkanGpuSession(GrContext, renderTarget, surface, session);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (!success)
|
||||||
|
session.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsCorrupted { get; }
|
||||||
|
|
||||||
|
internal class VulkanGpuSession : ISkiaGpuRenderSession
|
||||||
|
{
|
||||||
|
private readonly GRBackendRenderTarget _backendRenderTarget;
|
||||||
|
private readonly VulkanSurfaceRenderingSession _vulkanSession;
|
||||||
|
|
||||||
|
public VulkanGpuSession(GRContext grContext,
|
||||||
|
GRBackendRenderTarget backendRenderTarget,
|
||||||
|
SKSurface surface,
|
||||||
|
VulkanSurfaceRenderingSession vulkanSession)
|
||||||
|
{
|
||||||
|
GrContext = grContext;
|
||||||
|
_backendRenderTarget = backendRenderTarget;
|
||||||
|
SkSurface = surface;
|
||||||
|
_vulkanSession = vulkanSession;
|
||||||
|
|
||||||
|
SurfaceOrigin = vulkanSession.IsYFlipped ? GRSurfaceOrigin.TopLeft : GRSurfaceOrigin.BottomLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
lock (_vulkanSession.Display.Lock)
|
||||||
|
{
|
||||||
|
SkSurface.Canvas.Flush();
|
||||||
|
|
||||||
|
SkSurface.Dispose();
|
||||||
|
_backendRenderTarget.Dispose();
|
||||||
|
GrContext.Flush();
|
||||||
|
|
||||||
|
_vulkanSession.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public GRContext GrContext { get; }
|
||||||
|
public SKSurface SkSurface { get; }
|
||||||
|
public double ScaleFactor => _vulkanSession.Scaling;
|
||||||
|
public GRSurfaceOrigin SurfaceOrigin { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
118
Ryujinx.Ava/Ui/Backend/Vulkan/Skia/VulkanSkiaGpu.cs
Normal file
118
Ryujinx.Ava/Ui/Backend/Vulkan/Skia/VulkanSkiaGpu.cs
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Platform;
|
||||||
|
using Avalonia.Skia;
|
||||||
|
using Avalonia.X11;
|
||||||
|
using Ryujinx.Ava.Ui.Vulkan;
|
||||||
|
using Silk.NET.Vulkan;
|
||||||
|
using SkiaSharp;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Ui.Backend.Vulkan
|
||||||
|
{
|
||||||
|
public class VulkanSkiaGpu : ISkiaGpu
|
||||||
|
{
|
||||||
|
private readonly VulkanPlatformInterface _vulkan;
|
||||||
|
private readonly long? _maxResourceBytes;
|
||||||
|
private GRContext _grContext;
|
||||||
|
private GRVkBackendContext _grVkBackend;
|
||||||
|
private bool _initialized;
|
||||||
|
|
||||||
|
public GRContext GrContext { get => _grContext; set => _grContext = value; }
|
||||||
|
|
||||||
|
public VulkanSkiaGpu(long? maxResourceBytes)
|
||||||
|
{
|
||||||
|
_vulkan = AvaloniaLocator.Current.GetService<VulkanPlatformInterface>();
|
||||||
|
_maxResourceBytes = maxResourceBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Initialize()
|
||||||
|
{
|
||||||
|
if (_initialized)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_initialized = true;
|
||||||
|
GRVkGetProcedureAddressDelegate getProc = (string name, IntPtr instanceHandle, IntPtr deviceHandle) =>
|
||||||
|
{
|
||||||
|
IntPtr addr = IntPtr.Zero;
|
||||||
|
|
||||||
|
if (deviceHandle != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
addr = _vulkan.Device.Api.GetDeviceProcAddr(new Device(deviceHandle), name);
|
||||||
|
if (addr != IntPtr.Zero)
|
||||||
|
return addr;
|
||||||
|
|
||||||
|
addr = _vulkan.Device.Api.GetDeviceProcAddr(new Device(_vulkan.Device.Handle), name);
|
||||||
|
|
||||||
|
if (addr != IntPtr.Zero)
|
||||||
|
return addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
addr = _vulkan.Device.Api.GetInstanceProcAddr(new Instance(_vulkan.Instance.Handle), name);
|
||||||
|
|
||||||
|
if (addr == IntPtr.Zero)
|
||||||
|
addr = _vulkan.Device.Api.GetInstanceProcAddr(new Instance(instanceHandle), name);
|
||||||
|
|
||||||
|
return addr;
|
||||||
|
};
|
||||||
|
|
||||||
|
_grVkBackend = new GRVkBackendContext()
|
||||||
|
{
|
||||||
|
VkInstance = _vulkan.Device.Handle,
|
||||||
|
VkPhysicalDevice = _vulkan.PhysicalDevice.Handle,
|
||||||
|
VkDevice = _vulkan.Device.Handle,
|
||||||
|
VkQueue = _vulkan.Device.Queue.Handle,
|
||||||
|
GraphicsQueueIndex = _vulkan.PhysicalDevice.QueueFamilyIndex,
|
||||||
|
GetProcedureAddress = getProc
|
||||||
|
};
|
||||||
|
_grContext = GRContext.CreateVulkan(_grVkBackend);
|
||||||
|
if (_maxResourceBytes.HasValue)
|
||||||
|
{
|
||||||
|
_grContext.SetResourceCacheLimit(_maxResourceBytes.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ISkiaGpuRenderTarget TryCreateRenderTarget(IEnumerable<object> surfaces)
|
||||||
|
{
|
||||||
|
foreach (var surface in surfaces)
|
||||||
|
{
|
||||||
|
VulkanWindowSurface window;
|
||||||
|
|
||||||
|
if (surface is IPlatformHandle handle)
|
||||||
|
{
|
||||||
|
window = new VulkanWindowSurface(handle.Handle);
|
||||||
|
}
|
||||||
|
else if (surface is X11FramebufferSurface x11FramebufferSurface)
|
||||||
|
{
|
||||||
|
// As of Avalonia 0.10.13, an IPlatformHandle isn't passed for linux, so use reflection to otherwise get the window id
|
||||||
|
var xId = (IntPtr)x11FramebufferSurface.GetType().GetField(
|
||||||
|
"_xid",
|
||||||
|
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(x11FramebufferSurface);
|
||||||
|
|
||||||
|
window = new VulkanWindowSurface(xId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var vulkanRenderTarget = new VulkanRenderTarget(_vulkan, window);
|
||||||
|
|
||||||
|
Initialize();
|
||||||
|
|
||||||
|
vulkanRenderTarget.GrContext = _grContext;
|
||||||
|
|
||||||
|
return vulkanRenderTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ISkiaSurface TryCreateSurface(PixelSize size, ISkiaGpuRenderSession session)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
58
Ryujinx.Ava/Ui/Backend/Vulkan/Skia/VulkanSurface.cs
Normal file
58
Ryujinx.Ava/Ui/Backend/Vulkan/Skia/VulkanSurface.cs
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
using Avalonia;
|
||||||
|
using Ryujinx.Ava.Ui.Vulkan;
|
||||||
|
using Ryujinx.Ava.Ui.Vulkan.Surfaces;
|
||||||
|
using Silk.NET.Vulkan;
|
||||||
|
using Silk.NET.Vulkan.Extensions.KHR;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Ui.Backend.Vulkan
|
||||||
|
{
|
||||||
|
internal class VulkanWindowSurface : BackendSurface, IVulkanPlatformSurface
|
||||||
|
{
|
||||||
|
public float Scaling => (float)Program.ActualScaleFactor;
|
||||||
|
|
||||||
|
public PixelSize SurfaceSize => Size;
|
||||||
|
|
||||||
|
public VulkanWindowSurface(IntPtr handle) : base(handle)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe SurfaceKHR CreateSurface(VulkanInstance instance)
|
||||||
|
{
|
||||||
|
if (OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
if (instance.Api.TryGetInstanceExtension(new Instance(instance.Handle), out KhrWin32Surface surfaceExtension))
|
||||||
|
{
|
||||||
|
var createInfo = new Win32SurfaceCreateInfoKHR() { Hinstance = 0, Hwnd = Handle, SType = StructureType.Win32SurfaceCreateInfoKhr };
|
||||||
|
|
||||||
|
surfaceExtension.CreateWin32Surface(new Instance(instance.Handle), createInfo, null, out var surface).ThrowOnError();
|
||||||
|
|
||||||
|
return surface;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (OperatingSystem.IsLinux())
|
||||||
|
{
|
||||||
|
if (instance.Api.TryGetInstanceExtension(new Instance(instance.Handle), out KhrXlibSurface surfaceExtension))
|
||||||
|
{
|
||||||
|
var createInfo = new XlibSurfaceCreateInfoKHR()
|
||||||
|
{
|
||||||
|
SType = StructureType.XlibSurfaceCreateInfoKhr,
|
||||||
|
Dpy = (nint*)Display,
|
||||||
|
Window = Handle
|
||||||
|
};
|
||||||
|
|
||||||
|
surfaceExtension.CreateXlibSurface(new Instance(instance.Handle), createInfo, null, out var surface).ThrowOnError();
|
||||||
|
|
||||||
|
return surface;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new PlatformNotSupportedException("The current platform does not support surface creation.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
base.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
using System;
|
||||||
|
using Avalonia;
|
||||||
|
using Silk.NET.Vulkan;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Ui.Vulkan.Surfaces
|
||||||
|
{
|
||||||
|
public interface IVulkanPlatformSurface : IDisposable
|
||||||
|
{
|
||||||
|
float Scaling { get; }
|
||||||
|
PixelSize SurfaceSize { get; }
|
||||||
|
SurfaceKHR CreateSurface(VulkanInstance instance);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
using System;
|
||||||
|
using Avalonia;
|
||||||
|
using Silk.NET.Vulkan;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Ui.Vulkan.Surfaces
|
||||||
|
{
|
||||||
|
internal class VulkanSurfaceRenderTarget : IDisposable
|
||||||
|
{
|
||||||
|
private readonly VulkanPlatformInterface _platformInterface;
|
||||||
|
|
||||||
|
public bool IsCorrupted { get; set; } = true;
|
||||||
|
private readonly Format _format;
|
||||||
|
|
||||||
|
public VulkanImage Image { get; private set; }
|
||||||
|
|
||||||
|
public uint MipLevels => Image.MipLevels;
|
||||||
|
|
||||||
|
public VulkanSurfaceRenderTarget(VulkanPlatformInterface platformInterface, VulkanSurface surface)
|
||||||
|
{
|
||||||
|
_platformInterface = platformInterface;
|
||||||
|
|
||||||
|
Display = VulkanDisplay.CreateDisplay(platformInterface.Instance, platformInterface.Device,
|
||||||
|
platformInterface.PhysicalDevice, surface);
|
||||||
|
Surface = surface;
|
||||||
|
|
||||||
|
// Skia seems to only create surfaces from images with unorm format
|
||||||
|
|
||||||
|
IsRgba = Display.SurfaceFormat.Format >= Format.R8G8B8A8Unorm &&
|
||||||
|
Display.SurfaceFormat.Format <= Format.R8G8B8A8Srgb;
|
||||||
|
|
||||||
|
_format = IsRgba ? Format.R8G8B8A8Unorm : Format.B8G8R8A8Unorm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsRgba { get; }
|
||||||
|
|
||||||
|
public uint ImageFormat => (uint) _format;
|
||||||
|
|
||||||
|
public ulong MemorySize => Image.MemorySize;
|
||||||
|
|
||||||
|
public VulkanDisplay Display { get; }
|
||||||
|
|
||||||
|
public VulkanSurface Surface { get; }
|
||||||
|
|
||||||
|
public uint UsageFlags => Image.UsageFlags;
|
||||||
|
|
||||||
|
public PixelSize Size { get; private set; }
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_platformInterface.Device.WaitIdle();
|
||||||
|
DestroyImage();
|
||||||
|
Display?.Dispose();
|
||||||
|
Surface?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public VulkanSurfaceRenderingSession BeginDraw(float scaling)
|
||||||
|
{
|
||||||
|
var session = new VulkanSurfaceRenderingSession(Display, _platformInterface.Device, this, scaling);
|
||||||
|
|
||||||
|
if (IsCorrupted)
|
||||||
|
{
|
||||||
|
IsCorrupted = false;
|
||||||
|
DestroyImage();
|
||||||
|
CreateImage();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Image.TransitionLayout(ImageLayout.ColorAttachmentOptimal, AccessFlags.AccessNoneKhr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Invalidate()
|
||||||
|
{
|
||||||
|
IsCorrupted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateImage()
|
||||||
|
{
|
||||||
|
Size = Display.Size;
|
||||||
|
|
||||||
|
Image = new VulkanImage(_platformInterface.Device, _platformInterface.PhysicalDevice, _platformInterface.Device.CommandBufferPool, ImageFormat, Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DestroyImage()
|
||||||
|
{
|
||||||
|
_platformInterface.Device.WaitIdle();
|
||||||
|
Image?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
177
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanCommandBufferPool.cs
Normal file
177
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanCommandBufferPool.cs
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Silk.NET.Vulkan;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
|
{
|
||||||
|
internal class VulkanCommandBufferPool : IDisposable
|
||||||
|
{
|
||||||
|
private readonly VulkanDevice _device;
|
||||||
|
private readonly CommandPool _commandPool;
|
||||||
|
|
||||||
|
private readonly List<VulkanCommandBuffer> _usedCommandBuffers = new();
|
||||||
|
|
||||||
|
public unsafe VulkanCommandBufferPool(VulkanDevice device, VulkanPhysicalDevice physicalDevice)
|
||||||
|
{
|
||||||
|
_device = device;
|
||||||
|
|
||||||
|
var commandPoolCreateInfo = new CommandPoolCreateInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.CommandPoolCreateInfo,
|
||||||
|
Flags = CommandPoolCreateFlags.CommandPoolCreateResetCommandBufferBit,
|
||||||
|
QueueFamilyIndex = physicalDevice.QueueFamilyIndex
|
||||||
|
};
|
||||||
|
|
||||||
|
device.Api.CreateCommandPool(_device.InternalHandle, commandPoolCreateInfo, null, out _commandPool)
|
||||||
|
.ThrowOnError();
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void Dispose()
|
||||||
|
{
|
||||||
|
FreeUsedCommandBuffers();
|
||||||
|
_device.Api.DestroyCommandPool(_device.InternalHandle, _commandPool, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CommandBuffer AllocateCommandBuffer()
|
||||||
|
{
|
||||||
|
var commandBufferAllocateInfo = new CommandBufferAllocateInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.CommandBufferAllocateInfo,
|
||||||
|
CommandPool = _commandPool,
|
||||||
|
CommandBufferCount = 1,
|
||||||
|
Level = CommandBufferLevel.Primary
|
||||||
|
};
|
||||||
|
|
||||||
|
_device.Api.AllocateCommandBuffers(_device.InternalHandle, commandBufferAllocateInfo, out var commandBuffer);
|
||||||
|
|
||||||
|
return commandBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VulkanCommandBuffer CreateCommandBuffer()
|
||||||
|
{
|
||||||
|
return new(_device, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FreeUsedCommandBuffers()
|
||||||
|
{
|
||||||
|
lock (_usedCommandBuffers)
|
||||||
|
{
|
||||||
|
foreach (var usedCommandBuffer in _usedCommandBuffers) usedCommandBuffer.Dispose();
|
||||||
|
|
||||||
|
_usedCommandBuffers.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisposeCommandBuffer(VulkanCommandBuffer commandBuffer)
|
||||||
|
{
|
||||||
|
lock (_usedCommandBuffers)
|
||||||
|
{
|
||||||
|
_usedCommandBuffers.Add(commandBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class VulkanCommandBuffer : IDisposable
|
||||||
|
{
|
||||||
|
private readonly VulkanCommandBufferPool _commandBufferPool;
|
||||||
|
private readonly VulkanDevice _device;
|
||||||
|
private readonly Fence _fence;
|
||||||
|
private bool _hasEnded;
|
||||||
|
private bool _hasStarted;
|
||||||
|
|
||||||
|
public IntPtr Handle => InternalHandle.Handle;
|
||||||
|
|
||||||
|
internal CommandBuffer InternalHandle { get; }
|
||||||
|
|
||||||
|
internal unsafe VulkanCommandBuffer(VulkanDevice device, VulkanCommandBufferPool commandBufferPool)
|
||||||
|
{
|
||||||
|
_device = device;
|
||||||
|
_commandBufferPool = commandBufferPool;
|
||||||
|
|
||||||
|
InternalHandle = _commandBufferPool.AllocateCommandBuffer();
|
||||||
|
|
||||||
|
var fenceCreateInfo = new FenceCreateInfo()
|
||||||
|
{
|
||||||
|
SType = StructureType.FenceCreateInfo,
|
||||||
|
Flags = FenceCreateFlags.FenceCreateSignaledBit
|
||||||
|
};
|
||||||
|
|
||||||
|
device.Api.CreateFence(device.InternalHandle, fenceCreateInfo, null, out _fence);
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void Dispose()
|
||||||
|
{
|
||||||
|
_device.Api.WaitForFences(_device.InternalHandle, 1, _fence, true, ulong.MaxValue);
|
||||||
|
_device.Api.FreeCommandBuffers(_device.InternalHandle, _commandBufferPool._commandPool, 1, InternalHandle);
|
||||||
|
_device.Api.DestroyFence(_device.InternalHandle, _fence, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void BeginRecording()
|
||||||
|
{
|
||||||
|
if (!_hasStarted)
|
||||||
|
{
|
||||||
|
_hasStarted = true;
|
||||||
|
|
||||||
|
var beginInfo = new CommandBufferBeginInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.CommandBufferBeginInfo,
|
||||||
|
Flags = CommandBufferUsageFlags.CommandBufferUsageOneTimeSubmitBit
|
||||||
|
};
|
||||||
|
|
||||||
|
_device.Api.BeginCommandBuffer(InternalHandle, beginInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EndRecording()
|
||||||
|
{
|
||||||
|
if (_hasStarted && !_hasEnded)
|
||||||
|
{
|
||||||
|
_hasEnded = true;
|
||||||
|
|
||||||
|
_device.Api.EndCommandBuffer(InternalHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Submit()
|
||||||
|
{
|
||||||
|
Submit(null, null, null, _fence);
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void Submit(
|
||||||
|
ReadOnlySpan<Semaphore> waitSemaphores,
|
||||||
|
ReadOnlySpan<PipelineStageFlags> waitDstStageMask,
|
||||||
|
ReadOnlySpan<Semaphore> signalSemaphores,
|
||||||
|
Fence? fence = null)
|
||||||
|
{
|
||||||
|
EndRecording();
|
||||||
|
|
||||||
|
if (!fence.HasValue)
|
||||||
|
fence = _fence;
|
||||||
|
|
||||||
|
fixed (Semaphore* pWaitSemaphores = waitSemaphores, pSignalSemaphores = signalSemaphores)
|
||||||
|
{
|
||||||
|
fixed (PipelineStageFlags* pWaitDstStageMask = waitDstStageMask)
|
||||||
|
{
|
||||||
|
var commandBuffer = InternalHandle;
|
||||||
|
var submitInfo = new SubmitInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.SubmitInfo,
|
||||||
|
WaitSemaphoreCount = waitSemaphores != null ? (uint)waitSemaphores.Length : 0,
|
||||||
|
PWaitSemaphores = pWaitSemaphores,
|
||||||
|
PWaitDstStageMask = pWaitDstStageMask,
|
||||||
|
CommandBufferCount = 1,
|
||||||
|
PCommandBuffers = &commandBuffer,
|
||||||
|
SignalSemaphoreCount = signalSemaphores != null ? (uint)signalSemaphores.Length : 0,
|
||||||
|
PSignalSemaphores = pSignalSemaphores,
|
||||||
|
};
|
||||||
|
|
||||||
|
_device.Api.ResetFences(_device.InternalHandle, 1, fence.Value);
|
||||||
|
|
||||||
|
_device.Submit(submitInfo, fence.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_commandBufferPool.DisposeCommandBuffer(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
67
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanDevice.cs
Normal file
67
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanDevice.cs
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
using System;
|
||||||
|
using Silk.NET.Vulkan;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
|
{
|
||||||
|
internal class VulkanDevice : IDisposable
|
||||||
|
{
|
||||||
|
private static object _lock = new object();
|
||||||
|
|
||||||
|
public VulkanDevice(Device apiHandle, VulkanPhysicalDevice physicalDevice, Vk api)
|
||||||
|
{
|
||||||
|
InternalHandle = apiHandle;
|
||||||
|
Api = api;
|
||||||
|
|
||||||
|
api.GetDeviceQueue(apiHandle, physicalDevice.QueueFamilyIndex, 0, out var queue);
|
||||||
|
|
||||||
|
var vulkanQueue = new VulkanQueue(this, queue);
|
||||||
|
Queue = vulkanQueue;
|
||||||
|
|
||||||
|
PresentQueue = vulkanQueue;
|
||||||
|
|
||||||
|
CommandBufferPool = new VulkanCommandBufferPool(this, physicalDevice);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IntPtr Handle => InternalHandle.Handle;
|
||||||
|
|
||||||
|
internal Device InternalHandle { get; }
|
||||||
|
public Vk Api { get; }
|
||||||
|
|
||||||
|
public VulkanQueue Queue { get; private set; }
|
||||||
|
public VulkanQueue PresentQueue { get; }
|
||||||
|
public VulkanCommandBufferPool CommandBufferPool { get; }
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
WaitIdle();
|
||||||
|
CommandBufferPool?.Dispose();
|
||||||
|
Queue = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Submit(SubmitInfo submitInfo, Fence fence = new())
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
Api.QueueSubmit(Queue.InternalHandle, 1, submitInfo, fence);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WaitIdle()
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
Api.DeviceWaitIdle(InternalHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void QueueWaitIdle()
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
Api.QueueWaitIdle(Queue.InternalHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Lock => _lock;
|
||||||
|
}
|
||||||
|
}
|
406
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanDisplay.cs
Normal file
406
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanDisplay.cs
Normal file
|
@ -0,0 +1,406 @@
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using Avalonia;
|
||||||
|
using Ryujinx.Ava.Ui.Vulkan.Surfaces;
|
||||||
|
using Silk.NET.Vulkan;
|
||||||
|
using Silk.NET.Vulkan.Extensions.KHR;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
|
{
|
||||||
|
internal class VulkanDisplay : IDisposable
|
||||||
|
{
|
||||||
|
private static KhrSwapchain _swapchainExtension;
|
||||||
|
private readonly VulkanInstance _instance;
|
||||||
|
private readonly VulkanPhysicalDevice _physicalDevice;
|
||||||
|
private readonly VulkanSemaphorePair _semaphorePair;
|
||||||
|
private uint _nextImage;
|
||||||
|
private readonly VulkanSurface _surface;
|
||||||
|
private SurfaceFormatKHR _surfaceFormat;
|
||||||
|
private SwapchainKHR _swapchain;
|
||||||
|
private Extent2D _swapchainExtent;
|
||||||
|
private Image[] _swapchainImages;
|
||||||
|
private VulkanDevice _device { get; }
|
||||||
|
private ImageView[] _swapchainImageViews = new ImageView[0];
|
||||||
|
|
||||||
|
public VulkanCommandBufferPool CommandBufferPool { get; set; }
|
||||||
|
|
||||||
|
public object Lock => _device.Lock;
|
||||||
|
|
||||||
|
private VulkanDisplay(VulkanInstance instance, VulkanDevice device,
|
||||||
|
VulkanPhysicalDevice physicalDevice, VulkanSurface surface, SwapchainKHR swapchain,
|
||||||
|
Extent2D swapchainExtent)
|
||||||
|
{
|
||||||
|
_instance = instance;
|
||||||
|
_device = device;
|
||||||
|
_physicalDevice = physicalDevice;
|
||||||
|
_swapchain = swapchain;
|
||||||
|
_swapchainExtent = swapchainExtent;
|
||||||
|
_surface = surface;
|
||||||
|
|
||||||
|
CreateSwapchainImages();
|
||||||
|
|
||||||
|
_semaphorePair = new VulkanSemaphorePair(_device);
|
||||||
|
|
||||||
|
CommandBufferPool = new VulkanCommandBufferPool(device, physicalDevice);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PixelSize Size { get; private set; }
|
||||||
|
public uint QueueFamilyIndex => _physicalDevice.QueueFamilyIndex;
|
||||||
|
|
||||||
|
internal SurfaceFormatKHR SurfaceFormat
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_surfaceFormat.Format == Format.Undefined)
|
||||||
|
_surfaceFormat = _surface.GetSurfaceFormat(_physicalDevice);
|
||||||
|
|
||||||
|
return _surfaceFormat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void Dispose()
|
||||||
|
{
|
||||||
|
_device.WaitIdle();
|
||||||
|
_semaphorePair?.Dispose();
|
||||||
|
DestroyCurrentImageViews();
|
||||||
|
_swapchainExtension.DestroySwapchain(_device.InternalHandle, _swapchain, null);
|
||||||
|
CommandBufferPool.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static unsafe SwapchainKHR CreateSwapchain(VulkanInstance instance, VulkanDevice device,
|
||||||
|
VulkanPhysicalDevice physicalDevice, VulkanSurface surface, out Extent2D swapchainExtent,
|
||||||
|
VulkanDisplay oldDisplay = null)
|
||||||
|
{
|
||||||
|
if (_swapchainExtension == null)
|
||||||
|
{
|
||||||
|
instance.Api.TryGetDeviceExtension(instance.InternalHandle, device.InternalHandle, out KhrSwapchain extension);
|
||||||
|
|
||||||
|
_swapchainExtension = extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!surface.CanSurfacePresent(physicalDevice))
|
||||||
|
{
|
||||||
|
Thread.Sleep(16);
|
||||||
|
}
|
||||||
|
|
||||||
|
VulkanSurface.SurfaceExtension.GetPhysicalDeviceSurfaceCapabilities(physicalDevice.InternalHandle,
|
||||||
|
surface.ApiHandle, out var capabilities);
|
||||||
|
|
||||||
|
uint presentModesCount;
|
||||||
|
|
||||||
|
VulkanSurface.SurfaceExtension.GetPhysicalDeviceSurfacePresentModes(physicalDevice.InternalHandle,
|
||||||
|
surface.ApiHandle,
|
||||||
|
&presentModesCount, null);
|
||||||
|
|
||||||
|
var presentModes = new PresentModeKHR[presentModesCount];
|
||||||
|
|
||||||
|
fixed (PresentModeKHR* pPresentModes = presentModes)
|
||||||
|
{
|
||||||
|
VulkanSurface.SurfaceExtension.GetPhysicalDeviceSurfacePresentModes(physicalDevice.InternalHandle,
|
||||||
|
surface.ApiHandle, &presentModesCount, pPresentModes);
|
||||||
|
}
|
||||||
|
|
||||||
|
var imageCount = capabilities.MinImageCount + 1;
|
||||||
|
if (capabilities.MaxImageCount > 0 && imageCount > capabilities.MaxImageCount)
|
||||||
|
imageCount = capabilities.MaxImageCount;
|
||||||
|
|
||||||
|
var surfaceFormat = surface.GetSurfaceFormat(physicalDevice);
|
||||||
|
|
||||||
|
bool supportsIdentityTransform = capabilities.SupportedTransforms.HasFlag(SurfaceTransformFlagsKHR.SurfaceTransformIdentityBitKhr);
|
||||||
|
bool isRotated = capabilities.CurrentTransform.HasFlag(SurfaceTransformFlagsKHR.SurfaceTransformRotate90BitKhr) ||
|
||||||
|
capabilities.CurrentTransform.HasFlag(SurfaceTransformFlagsKHR.SurfaceTransformRotate270BitKhr);
|
||||||
|
|
||||||
|
if (capabilities.CurrentExtent.Width != uint.MaxValue)
|
||||||
|
{
|
||||||
|
swapchainExtent = capabilities.CurrentExtent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var surfaceSize = surface.SurfaceSize;
|
||||||
|
|
||||||
|
var width = Math.Max(capabilities.MinImageExtent.Width,
|
||||||
|
Math.Min(capabilities.MaxImageExtent.Width, (uint)surfaceSize.Width));
|
||||||
|
var height = Math.Max(capabilities.MinImageExtent.Height,
|
||||||
|
Math.Min(capabilities.MaxImageExtent.Height, (uint)surfaceSize.Height));
|
||||||
|
|
||||||
|
swapchainExtent = new Extent2D(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
PresentModeKHR presentMode;
|
||||||
|
var modes = presentModes.ToList();
|
||||||
|
|
||||||
|
if (modes.Contains(PresentModeKHR.PresentModeImmediateKhr))
|
||||||
|
presentMode = PresentModeKHR.PresentModeImmediateKhr;
|
||||||
|
else if (modes.Contains(PresentModeKHR.PresentModeMailboxKhr))
|
||||||
|
presentMode = PresentModeKHR.PresentModeMailboxKhr;
|
||||||
|
else
|
||||||
|
presentMode = PresentModeKHR.PresentModeFifoKhr;
|
||||||
|
|
||||||
|
var compositeAlphaFlags = CompositeAlphaFlagsKHR.CompositeAlphaOpaqueBitKhr;
|
||||||
|
|
||||||
|
if (capabilities.SupportedCompositeAlpha.HasFlag(CompositeAlphaFlagsKHR.CompositeAlphaPostMultipliedBitKhr))
|
||||||
|
{
|
||||||
|
compositeAlphaFlags = CompositeAlphaFlagsKHR.CompositeAlphaPostMultipliedBitKhr;
|
||||||
|
}
|
||||||
|
else if (capabilities.SupportedCompositeAlpha.HasFlag(CompositeAlphaFlagsKHR.CompositeAlphaPreMultipliedBitKhr))
|
||||||
|
{
|
||||||
|
compositeAlphaFlags = CompositeAlphaFlagsKHR.CompositeAlphaPreMultipliedBitKhr;
|
||||||
|
}
|
||||||
|
|
||||||
|
var swapchainCreateInfo = new SwapchainCreateInfoKHR
|
||||||
|
{
|
||||||
|
SType = StructureType.SwapchainCreateInfoKhr,
|
||||||
|
Surface = surface.ApiHandle,
|
||||||
|
MinImageCount = imageCount,
|
||||||
|
ImageFormat = surfaceFormat.Format,
|
||||||
|
ImageColorSpace = surfaceFormat.ColorSpace,
|
||||||
|
ImageExtent = swapchainExtent,
|
||||||
|
ImageUsage =
|
||||||
|
ImageUsageFlags.ImageUsageColorAttachmentBit | ImageUsageFlags.ImageUsageTransferDstBit,
|
||||||
|
ImageSharingMode = SharingMode.Exclusive,
|
||||||
|
ImageArrayLayers = 1,
|
||||||
|
PreTransform = supportsIdentityTransform && isRotated ? SurfaceTransformFlagsKHR.SurfaceTransformIdentityBitKhr : capabilities.CurrentTransform,
|
||||||
|
CompositeAlpha = compositeAlphaFlags,
|
||||||
|
PresentMode = presentMode,
|
||||||
|
Clipped = true,
|
||||||
|
OldSwapchain = oldDisplay?._swapchain ?? new SwapchainKHR()
|
||||||
|
};
|
||||||
|
|
||||||
|
_swapchainExtension.CreateSwapchain(device.InternalHandle, swapchainCreateInfo, null, out var swapchain)
|
||||||
|
.ThrowOnError();
|
||||||
|
|
||||||
|
if (oldDisplay != null)
|
||||||
|
{
|
||||||
|
_swapchainExtension.DestroySwapchain(device.InternalHandle, oldDisplay._swapchain, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return swapchain;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal static VulkanDisplay CreateDisplay(VulkanInstance instance, VulkanDevice device,
|
||||||
|
VulkanPhysicalDevice physicalDevice, VulkanSurface surface)
|
||||||
|
{
|
||||||
|
var swapchain = CreateSwapchain(instance, device, physicalDevice, surface, out var extent);
|
||||||
|
|
||||||
|
return new VulkanDisplay(instance, device, physicalDevice, surface, swapchain, extent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void CreateSwapchainImages()
|
||||||
|
{
|
||||||
|
DestroyCurrentImageViews();
|
||||||
|
|
||||||
|
Size = new PixelSize((int)_swapchainExtent.Width, (int)_swapchainExtent.Height);
|
||||||
|
|
||||||
|
uint imageCount = 0;
|
||||||
|
|
||||||
|
_swapchainExtension.GetSwapchainImages(_device.InternalHandle, _swapchain, &imageCount, null);
|
||||||
|
|
||||||
|
_swapchainImages = new Image[imageCount];
|
||||||
|
|
||||||
|
fixed (Image* pSwapchainImages = _swapchainImages)
|
||||||
|
{
|
||||||
|
_swapchainExtension.GetSwapchainImages(_device.InternalHandle, _swapchain, &imageCount, pSwapchainImages);
|
||||||
|
}
|
||||||
|
|
||||||
|
_swapchainImageViews = new ImageView[imageCount];
|
||||||
|
|
||||||
|
var surfaceFormat = SurfaceFormat;
|
||||||
|
|
||||||
|
for (var i = 0; i < imageCount; i++)
|
||||||
|
{
|
||||||
|
_swapchainImageViews[i] = CreateSwapchainImageView(_swapchainImages[i], surfaceFormat.Format);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void DestroyCurrentImageViews()
|
||||||
|
{
|
||||||
|
if (_swapchainImageViews.Length > 0)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < _swapchainImageViews.Length; i++)
|
||||||
|
{
|
||||||
|
_instance.Api.DestroyImageView(_device.InternalHandle, _swapchainImageViews[i], null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Recreate()
|
||||||
|
{
|
||||||
|
_device.WaitIdle();
|
||||||
|
_swapchain = CreateSwapchain(_instance, _device, _physicalDevice, _surface, out var extent, this);
|
||||||
|
|
||||||
|
_swapchainExtent = extent;
|
||||||
|
|
||||||
|
CreateSwapchainImages();
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe ImageView CreateSwapchainImageView(Image swapchainImage, Format format)
|
||||||
|
{
|
||||||
|
var componentMapping = new ComponentMapping(
|
||||||
|
ComponentSwizzle.Identity,
|
||||||
|
ComponentSwizzle.Identity,
|
||||||
|
ComponentSwizzle.Identity,
|
||||||
|
ComponentSwizzle.Identity);
|
||||||
|
|
||||||
|
var aspectFlags = ImageAspectFlags.ImageAspectColorBit;
|
||||||
|
|
||||||
|
var subresourceRange = new ImageSubresourceRange(aspectFlags, 0, 1, 0, 1);
|
||||||
|
|
||||||
|
var imageCreateInfo = new ImageViewCreateInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.ImageViewCreateInfo,
|
||||||
|
Image = swapchainImage,
|
||||||
|
ViewType = ImageViewType.ImageViewType2D,
|
||||||
|
Format = format,
|
||||||
|
Components = componentMapping,
|
||||||
|
SubresourceRange = subresourceRange
|
||||||
|
};
|
||||||
|
|
||||||
|
_instance.Api.CreateImageView(_device.InternalHandle, imageCreateInfo, null, out var imageView).ThrowOnError();
|
||||||
|
return imageView;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool EnsureSwapchainAvailable()
|
||||||
|
{
|
||||||
|
if (Size != _surface.SurfaceSize)
|
||||||
|
{
|
||||||
|
Recreate();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal VulkanCommandBufferPool.VulkanCommandBuffer StartPresentation(VulkanSurfaceRenderTarget renderTarget)
|
||||||
|
{
|
||||||
|
_nextImage = 0;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var acquireResult = _swapchainExtension.AcquireNextImage(
|
||||||
|
_device.InternalHandle,
|
||||||
|
_swapchain,
|
||||||
|
ulong.MaxValue,
|
||||||
|
_semaphorePair.ImageAvailableSemaphore,
|
||||||
|
new Fence(),
|
||||||
|
ref _nextImage);
|
||||||
|
|
||||||
|
if (acquireResult == Result.ErrorOutOfDateKhr ||
|
||||||
|
acquireResult == Result.SuboptimalKhr)
|
||||||
|
{
|
||||||
|
Recreate();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
acquireResult.ThrowOnError();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var commandBuffer = CommandBufferPool.CreateCommandBuffer();
|
||||||
|
commandBuffer.BeginRecording();
|
||||||
|
|
||||||
|
VulkanMemoryHelper.TransitionLayout(_device, commandBuffer.InternalHandle,
|
||||||
|
_swapchainImages[_nextImage], ImageLayout.Undefined,
|
||||||
|
AccessFlags.AccessNoneKhr,
|
||||||
|
ImageLayout.TransferDstOptimal,
|
||||||
|
AccessFlags.AccessTransferWriteBit,
|
||||||
|
1);
|
||||||
|
|
||||||
|
return commandBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void BlitImageToCurrentImage(VulkanSurfaceRenderTarget renderTarget, CommandBuffer commandBuffer)
|
||||||
|
{
|
||||||
|
VulkanMemoryHelper.TransitionLayout(_device, commandBuffer,
|
||||||
|
renderTarget.Image.InternalHandle.Value, (ImageLayout)renderTarget.Image.CurrentLayout,
|
||||||
|
AccessFlags.AccessNoneKhr,
|
||||||
|
ImageLayout.TransferSrcOptimal,
|
||||||
|
AccessFlags.AccessTransferReadBit,
|
||||||
|
renderTarget.MipLevels);
|
||||||
|
|
||||||
|
var srcBlitRegion = new ImageBlit
|
||||||
|
{
|
||||||
|
SrcOffsets = new ImageBlit.SrcOffsetsBuffer
|
||||||
|
{
|
||||||
|
Element0 = new Offset3D(0, 0, 0),
|
||||||
|
Element1 = new Offset3D(renderTarget.Size.Width, renderTarget.Size.Height, 1),
|
||||||
|
},
|
||||||
|
DstOffsets = new ImageBlit.DstOffsetsBuffer
|
||||||
|
{
|
||||||
|
Element0 = new Offset3D(0, 0, 0),
|
||||||
|
Element1 = new Offset3D(Size.Width, Size.Height, 1),
|
||||||
|
},
|
||||||
|
SrcSubresource = new ImageSubresourceLayers
|
||||||
|
{
|
||||||
|
AspectMask = ImageAspectFlags.ImageAspectColorBit,
|
||||||
|
BaseArrayLayer = 0,
|
||||||
|
LayerCount = 1,
|
||||||
|
MipLevel = 0
|
||||||
|
},
|
||||||
|
DstSubresource = new ImageSubresourceLayers
|
||||||
|
{
|
||||||
|
AspectMask = ImageAspectFlags.ImageAspectColorBit,
|
||||||
|
BaseArrayLayer = 0,
|
||||||
|
LayerCount = 1,
|
||||||
|
MipLevel = 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_device.Api.CmdBlitImage(commandBuffer, renderTarget.Image.InternalHandle.Value,
|
||||||
|
ImageLayout.TransferSrcOptimal,
|
||||||
|
_swapchainImages[_nextImage],
|
||||||
|
ImageLayout.TransferDstOptimal,
|
||||||
|
1,
|
||||||
|
srcBlitRegion,
|
||||||
|
Filter.Linear);
|
||||||
|
|
||||||
|
VulkanMemoryHelper.TransitionLayout(_device, commandBuffer,
|
||||||
|
renderTarget.Image.InternalHandle.Value, ImageLayout.TransferSrcOptimal,
|
||||||
|
AccessFlags.AccessTransferReadBit,
|
||||||
|
(ImageLayout)renderTarget.Image.CurrentLayout,
|
||||||
|
AccessFlags.AccessNoneKhr,
|
||||||
|
renderTarget.MipLevels);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal unsafe void EndPresentation(VulkanCommandBufferPool.VulkanCommandBuffer commandBuffer)
|
||||||
|
{
|
||||||
|
VulkanMemoryHelper.TransitionLayout(_device, commandBuffer.InternalHandle,
|
||||||
|
_swapchainImages[_nextImage], ImageLayout.TransferDstOptimal,
|
||||||
|
AccessFlags.AccessNoneKhr,
|
||||||
|
ImageLayout.PresentSrcKhr,
|
||||||
|
AccessFlags.AccessNoneKhr,
|
||||||
|
1);
|
||||||
|
|
||||||
|
commandBuffer.Submit(
|
||||||
|
new[] { _semaphorePair.ImageAvailableSemaphore },
|
||||||
|
new[] { PipelineStageFlags.PipelineStageColorAttachmentOutputBit },
|
||||||
|
new[] { _semaphorePair.RenderFinishedSemaphore });
|
||||||
|
|
||||||
|
var semaphore = _semaphorePair.RenderFinishedSemaphore;
|
||||||
|
var swapchain = _swapchain;
|
||||||
|
var nextImage = _nextImage;
|
||||||
|
|
||||||
|
Result result;
|
||||||
|
|
||||||
|
var presentInfo = new PresentInfoKHR
|
||||||
|
{
|
||||||
|
SType = StructureType.PresentInfoKhr,
|
||||||
|
WaitSemaphoreCount = 1,
|
||||||
|
PWaitSemaphores = &semaphore,
|
||||||
|
SwapchainCount = 1,
|
||||||
|
PSwapchains = &swapchain,
|
||||||
|
PImageIndices = &nextImage,
|
||||||
|
PResults = &result
|
||||||
|
};
|
||||||
|
|
||||||
|
lock (_device.Lock)
|
||||||
|
{
|
||||||
|
_swapchainExtension.QueuePresent(_device.PresentQueue.InternalHandle, presentInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
CommandBufferPool.FreeUsedCommandBuffers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
167
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanImage.cs
Normal file
167
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanImage.cs
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
using System;
|
||||||
|
using Avalonia;
|
||||||
|
using Silk.NET.Vulkan;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
|
{
|
||||||
|
internal class VulkanImage : IDisposable
|
||||||
|
{
|
||||||
|
private readonly VulkanDevice _device;
|
||||||
|
private readonly VulkanPhysicalDevice _physicalDevice;
|
||||||
|
private readonly VulkanCommandBufferPool _commandBufferPool;
|
||||||
|
private ImageLayout _currentLayout;
|
||||||
|
private AccessFlags _currentAccessFlags;
|
||||||
|
private ImageUsageFlags _imageUsageFlags { get; }
|
||||||
|
private ImageView? _imageView { get; set; }
|
||||||
|
private DeviceMemory _imageMemory { get; set; }
|
||||||
|
|
||||||
|
internal Image? InternalHandle { get; private set; }
|
||||||
|
internal Format Format { get; }
|
||||||
|
internal ImageAspectFlags AspectFlags { get; private set; }
|
||||||
|
|
||||||
|
public ulong Handle => InternalHandle?.Handle ?? 0;
|
||||||
|
public ulong ViewHandle => _imageView?.Handle ?? 0;
|
||||||
|
public uint UsageFlags => (uint)_imageUsageFlags;
|
||||||
|
public ulong MemoryHandle => _imageMemory.Handle;
|
||||||
|
public uint MipLevels { get; private set; }
|
||||||
|
public PixelSize Size { get; }
|
||||||
|
public ulong MemorySize { get; private set; }
|
||||||
|
public uint CurrentLayout => (uint)_currentLayout;
|
||||||
|
|
||||||
|
public VulkanImage(
|
||||||
|
VulkanDevice device,
|
||||||
|
VulkanPhysicalDevice physicalDevice,
|
||||||
|
VulkanCommandBufferPool commandBufferPool,
|
||||||
|
uint format,
|
||||||
|
PixelSize size,
|
||||||
|
uint mipLevels = 0)
|
||||||
|
{
|
||||||
|
_device = device;
|
||||||
|
_physicalDevice = physicalDevice;
|
||||||
|
_commandBufferPool = commandBufferPool;
|
||||||
|
Format = (Format)format;
|
||||||
|
Size = size;
|
||||||
|
MipLevels = mipLevels;
|
||||||
|
_imageUsageFlags =
|
||||||
|
ImageUsageFlags.ImageUsageColorAttachmentBit | ImageUsageFlags.ImageUsageTransferDstBit |
|
||||||
|
ImageUsageFlags.ImageUsageTransferSrcBit | ImageUsageFlags.ImageUsageSampledBit;
|
||||||
|
|
||||||
|
Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void Initialize()
|
||||||
|
{
|
||||||
|
if (!InternalHandle.HasValue)
|
||||||
|
{
|
||||||
|
MipLevels = MipLevels != 0 ? MipLevels : (uint)Math.Floor(Math.Log(Math.Max(Size.Width, Size.Height), 2));
|
||||||
|
|
||||||
|
var imageCreateInfo = new ImageCreateInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.ImageCreateInfo,
|
||||||
|
ImageType = ImageType.ImageType2D,
|
||||||
|
Format = Format,
|
||||||
|
Extent = new Extent3D((uint?)Size.Width, (uint?)Size.Height, 1),
|
||||||
|
MipLevels = MipLevels,
|
||||||
|
ArrayLayers = 1,
|
||||||
|
Samples = SampleCountFlags.SampleCount1Bit,
|
||||||
|
Tiling = Tiling,
|
||||||
|
Usage = _imageUsageFlags,
|
||||||
|
SharingMode = SharingMode.Exclusive,
|
||||||
|
InitialLayout = ImageLayout.Undefined,
|
||||||
|
Flags = ImageCreateFlags.ImageCreateMutableFormatBit
|
||||||
|
};
|
||||||
|
|
||||||
|
_device.Api.CreateImage(_device.InternalHandle, imageCreateInfo, null, out var image).ThrowOnError();
|
||||||
|
InternalHandle = image;
|
||||||
|
|
||||||
|
_device.Api.GetImageMemoryRequirements(_device.InternalHandle, InternalHandle.Value,
|
||||||
|
out var memoryRequirements);
|
||||||
|
|
||||||
|
var memoryAllocateInfo = new MemoryAllocateInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.MemoryAllocateInfo,
|
||||||
|
AllocationSize = memoryRequirements.Size,
|
||||||
|
MemoryTypeIndex = (uint)VulkanMemoryHelper.FindSuitableMemoryTypeIndex(
|
||||||
|
_physicalDevice,
|
||||||
|
memoryRequirements.MemoryTypeBits, MemoryPropertyFlags.MemoryPropertyDeviceLocalBit)
|
||||||
|
};
|
||||||
|
|
||||||
|
_device.Api.AllocateMemory(_device.InternalHandle, memoryAllocateInfo, null,
|
||||||
|
out var imageMemory);
|
||||||
|
|
||||||
|
_imageMemory = imageMemory;
|
||||||
|
|
||||||
|
_device.Api.BindImageMemory(_device.InternalHandle, InternalHandle.Value, _imageMemory, 0);
|
||||||
|
|
||||||
|
MemorySize = memoryRequirements.Size;
|
||||||
|
|
||||||
|
var componentMapping = new ComponentMapping(
|
||||||
|
ComponentSwizzle.Identity,
|
||||||
|
ComponentSwizzle.Identity,
|
||||||
|
ComponentSwizzle.Identity,
|
||||||
|
ComponentSwizzle.Identity);
|
||||||
|
|
||||||
|
AspectFlags = ImageAspectFlags.ImageAspectColorBit;
|
||||||
|
|
||||||
|
var subresourceRange = new ImageSubresourceRange(AspectFlags, 0, MipLevels, 0, 1);
|
||||||
|
|
||||||
|
var imageViewCreateInfo = new ImageViewCreateInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.ImageViewCreateInfo,
|
||||||
|
Image = InternalHandle.Value,
|
||||||
|
ViewType = ImageViewType.ImageViewType2D,
|
||||||
|
Format = Format,
|
||||||
|
Components = componentMapping,
|
||||||
|
SubresourceRange = subresourceRange
|
||||||
|
};
|
||||||
|
|
||||||
|
_device.Api
|
||||||
|
.CreateImageView(_device.InternalHandle, imageViewCreateInfo, null, out var imageView)
|
||||||
|
.ThrowOnError();
|
||||||
|
|
||||||
|
_imageView = imageView;
|
||||||
|
|
||||||
|
_currentLayout = ImageLayout.Undefined;
|
||||||
|
|
||||||
|
TransitionLayout(ImageLayout.ColorAttachmentOptimal, AccessFlags.AccessNoneKhr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImageTiling Tiling => ImageTiling.Optimal;
|
||||||
|
|
||||||
|
internal void TransitionLayout(ImageLayout destinationLayout, AccessFlags destinationAccessFlags)
|
||||||
|
{
|
||||||
|
var commandBuffer = _commandBufferPool.CreateCommandBuffer();
|
||||||
|
commandBuffer.BeginRecording();
|
||||||
|
|
||||||
|
VulkanMemoryHelper.TransitionLayout(_device, commandBuffer.InternalHandle, InternalHandle.Value,
|
||||||
|
_currentLayout,
|
||||||
|
_currentAccessFlags,
|
||||||
|
destinationLayout, destinationAccessFlags,
|
||||||
|
MipLevels);
|
||||||
|
|
||||||
|
commandBuffer.EndRecording();
|
||||||
|
|
||||||
|
commandBuffer.Submit();
|
||||||
|
|
||||||
|
_currentLayout = destinationLayout;
|
||||||
|
_currentAccessFlags = destinationAccessFlags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void TransitionLayout(uint destinationLayout, uint destinationAccessFlags)
|
||||||
|
{
|
||||||
|
TransitionLayout((ImageLayout)destinationLayout, (AccessFlags)destinationAccessFlags);
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void Dispose()
|
||||||
|
{
|
||||||
|
_device.Api.DestroyImageView(_device.InternalHandle, _imageView.Value, null);
|
||||||
|
_device.Api.DestroyImage(_device.InternalHandle, InternalHandle.Value, null);
|
||||||
|
_device.Api.FreeMemory(_device.InternalHandle, _imageMemory, null);
|
||||||
|
|
||||||
|
_imageView = default;
|
||||||
|
InternalHandle = default;
|
||||||
|
_imageMemory = default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
138
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanInstance.cs
Normal file
138
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanInstance.cs
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Silk.NET.Core;
|
||||||
|
using Silk.NET.Vulkan;
|
||||||
|
using Silk.NET.Vulkan.Extensions.EXT;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
|
{
|
||||||
|
public class VulkanInstance : IDisposable
|
||||||
|
{
|
||||||
|
private const string EngineName = "Avalonia Vulkan";
|
||||||
|
|
||||||
|
private VulkanInstance(Instance apiHandle, Vk api)
|
||||||
|
{
|
||||||
|
InternalHandle = apiHandle;
|
||||||
|
Api = api;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IntPtr Handle => InternalHandle.Handle;
|
||||||
|
|
||||||
|
internal Instance InternalHandle { get; }
|
||||||
|
public Vk Api { get; }
|
||||||
|
|
||||||
|
internal static IList<string> RequiredInstanceExtensions
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var extensions = new List<string> { "VK_KHR_surface" };
|
||||||
|
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||||
|
{
|
||||||
|
extensions.Add("VK_KHR_xlib_surface");
|
||||||
|
}
|
||||||
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
extensions.Add("VK_KHR_win32_surface");
|
||||||
|
}
|
||||||
|
|
||||||
|
return extensions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void Dispose()
|
||||||
|
{
|
||||||
|
Api?.DestroyInstance(InternalHandle, null);
|
||||||
|
Api?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static unsafe VulkanInstance Create(VulkanOptions options)
|
||||||
|
{
|
||||||
|
var api = Vk.GetApi();
|
||||||
|
var applicationName = Marshal.StringToHGlobalAnsi(options.ApplicationName);
|
||||||
|
var engineName = Marshal.StringToHGlobalAnsi(EngineName);
|
||||||
|
var enabledExtensions = new List<string>(options.InstanceExtensions);
|
||||||
|
|
||||||
|
enabledExtensions.AddRange(RequiredInstanceExtensions);
|
||||||
|
|
||||||
|
var applicationInfo = new ApplicationInfo
|
||||||
|
{
|
||||||
|
PApplicationName = (byte*)applicationName,
|
||||||
|
ApiVersion = new Version32((uint)options.VulkanVersion.Major, (uint)options.VulkanVersion.Minor,
|
||||||
|
(uint)options.VulkanVersion.Build),
|
||||||
|
PEngineName = (byte*)engineName,
|
||||||
|
EngineVersion = new Version32(1, 0, 0),
|
||||||
|
ApplicationVersion = new Version32(1, 0, 0)
|
||||||
|
};
|
||||||
|
|
||||||
|
var enabledLayers = new HashSet<string>();
|
||||||
|
|
||||||
|
if (options.UseDebug)
|
||||||
|
{
|
||||||
|
enabledExtensions.Add(ExtDebugUtils.ExtensionName);
|
||||||
|
if (IsLayerAvailable(api, "VK_LAYER_KHRONOS_validation"))
|
||||||
|
enabledLayers.Add("VK_LAYER_KHRONOS_validation");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var layer in options.EnabledLayers)
|
||||||
|
enabledLayers.Add(layer);
|
||||||
|
|
||||||
|
var ppEnabledExtensions = stackalloc IntPtr[enabledExtensions.Count];
|
||||||
|
var ppEnabledLayers = stackalloc IntPtr[enabledLayers.Count];
|
||||||
|
|
||||||
|
for (var i = 0; i < enabledExtensions.Count; i++)
|
||||||
|
ppEnabledExtensions[i] = Marshal.StringToHGlobalAnsi(enabledExtensions[i]);
|
||||||
|
|
||||||
|
var layers = enabledLayers.ToList();
|
||||||
|
|
||||||
|
for (var i = 0; i < enabledLayers.Count; i++)
|
||||||
|
ppEnabledLayers[i] = Marshal.StringToHGlobalAnsi(layers[i]);
|
||||||
|
|
||||||
|
var instanceCreateInfo = new InstanceCreateInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.InstanceCreateInfo,
|
||||||
|
PApplicationInfo = &applicationInfo,
|
||||||
|
PpEnabledExtensionNames = (byte**)ppEnabledExtensions,
|
||||||
|
PpEnabledLayerNames = (byte**)ppEnabledLayers,
|
||||||
|
EnabledExtensionCount = (uint)enabledExtensions.Count,
|
||||||
|
EnabledLayerCount = (uint)enabledLayers.Count
|
||||||
|
};
|
||||||
|
|
||||||
|
api.CreateInstance(in instanceCreateInfo, null, out var instance).ThrowOnError();
|
||||||
|
|
||||||
|
Marshal.FreeHGlobal(applicationName);
|
||||||
|
Marshal.FreeHGlobal(engineName);
|
||||||
|
|
||||||
|
for (var i = 0; i < enabledExtensions.Count; i++) Marshal.FreeHGlobal(ppEnabledExtensions[i]);
|
||||||
|
|
||||||
|
for (var i = 0; i < enabledLayers.Count; i++) Marshal.FreeHGlobal(ppEnabledLayers[i]);
|
||||||
|
|
||||||
|
return new VulkanInstance(instance, api);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static unsafe bool IsLayerAvailable(Vk api, string layerName)
|
||||||
|
{
|
||||||
|
uint layerPropertiesCount;
|
||||||
|
|
||||||
|
api.EnumerateInstanceLayerProperties(&layerPropertiesCount, null).ThrowOnError();
|
||||||
|
|
||||||
|
var layerProperties = new LayerProperties[layerPropertiesCount];
|
||||||
|
|
||||||
|
fixed (LayerProperties* pLayerProperties = layerProperties)
|
||||||
|
{
|
||||||
|
api.EnumerateInstanceLayerProperties(&layerPropertiesCount, layerProperties).ThrowOnError();
|
||||||
|
|
||||||
|
for (var i = 0; i < layerPropertiesCount; i++)
|
||||||
|
{
|
||||||
|
var currentLayerName = Marshal.PtrToStringAnsi((IntPtr)pLayerProperties[i].LayerName);
|
||||||
|
|
||||||
|
if (currentLayerName == layerName) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
59
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanMemoryHelper.cs
Normal file
59
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanMemoryHelper.cs
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
using Silk.NET.Vulkan;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
|
{
|
||||||
|
internal static class VulkanMemoryHelper
|
||||||
|
{
|
||||||
|
internal static int FindSuitableMemoryTypeIndex(VulkanPhysicalDevice physicalDevice, uint memoryTypeBits,
|
||||||
|
MemoryPropertyFlags flags)
|
||||||
|
{
|
||||||
|
physicalDevice.Api.GetPhysicalDeviceMemoryProperties(physicalDevice.InternalHandle, out var properties);
|
||||||
|
|
||||||
|
for (var i = 0; i < properties.MemoryTypeCount; i++)
|
||||||
|
{
|
||||||
|
var type = properties.MemoryTypes[i];
|
||||||
|
|
||||||
|
if ((memoryTypeBits & (1 << i)) != 0 && type.PropertyFlags.HasFlag(flags)) return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static unsafe void TransitionLayout(VulkanDevice device,
|
||||||
|
CommandBuffer commandBuffer,
|
||||||
|
Image image,
|
||||||
|
ImageLayout sourceLayout,
|
||||||
|
AccessFlags sourceAccessMask,
|
||||||
|
ImageLayout destinationLayout,
|
||||||
|
AccessFlags destinationAccessMask,
|
||||||
|
uint mipLevels)
|
||||||
|
{
|
||||||
|
var subresourceRange = new ImageSubresourceRange(ImageAspectFlags.ImageAspectColorBit, 0, mipLevels, 0, 1);
|
||||||
|
|
||||||
|
var barrier = new ImageMemoryBarrier
|
||||||
|
{
|
||||||
|
SType = StructureType.ImageMemoryBarrier,
|
||||||
|
SrcAccessMask = sourceAccessMask,
|
||||||
|
DstAccessMask = destinationAccessMask,
|
||||||
|
OldLayout = sourceLayout,
|
||||||
|
NewLayout = destinationLayout,
|
||||||
|
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
||||||
|
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
||||||
|
Image = image,
|
||||||
|
SubresourceRange = subresourceRange
|
||||||
|
};
|
||||||
|
|
||||||
|
device.Api.CmdPipelineBarrier(
|
||||||
|
commandBuffer,
|
||||||
|
PipelineStageFlags.PipelineStageAllCommandsBit,
|
||||||
|
PipelineStageFlags.PipelineStageAllCommandsBit,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
null,
|
||||||
|
0,
|
||||||
|
null,
|
||||||
|
1,
|
||||||
|
barrier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
48
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanOptions.cs
Normal file
48
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanOptions.cs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
|
{
|
||||||
|
public class VulkanOptions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the application name of the Vulkan instance
|
||||||
|
/// </summary>
|
||||||
|
public string ApplicationName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies the Vulkan API version to use
|
||||||
|
/// </summary>
|
||||||
|
public Version VulkanVersion{ get; set; } = new Version(1, 1, 0);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies additional extensions to enable if available on the instance
|
||||||
|
/// </summary>
|
||||||
|
public IList<string> InstanceExtensions { get; set; } = new List<string>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies layers to enable if available on the instance
|
||||||
|
/// </summary>
|
||||||
|
public IList<string> EnabledLayers { get; set; } = new List<string>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables the debug layer
|
||||||
|
/// </summary>
|
||||||
|
public bool UseDebug { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Selects the first suitable discrete GPU available
|
||||||
|
/// </summary>
|
||||||
|
public bool PreferDiscreteGpu { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the device to use if available and suitable.
|
||||||
|
/// </summary>
|
||||||
|
public string PreferredDevice { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Max number of device queues to request
|
||||||
|
/// </summary>
|
||||||
|
public uint MaxQueueCount { get; set; }
|
||||||
|
}
|
||||||
|
}
|
203
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanPhysicalDevice.cs
Normal file
203
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanPhysicalDevice.cs
Normal file
|
@ -0,0 +1,203 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Ryujinx.Graphics.Vulkan;
|
||||||
|
using Silk.NET.Core;
|
||||||
|
using Silk.NET.Vulkan;
|
||||||
|
using Silk.NET.Vulkan.Extensions.KHR;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
|
{
|
||||||
|
public unsafe class VulkanPhysicalDevice
|
||||||
|
{
|
||||||
|
private VulkanPhysicalDevice(PhysicalDevice apiHandle, Vk api, uint queueCount, uint queueFamilyIndex)
|
||||||
|
{
|
||||||
|
InternalHandle = apiHandle;
|
||||||
|
Api = api;
|
||||||
|
QueueCount = queueCount;
|
||||||
|
QueueFamilyIndex = queueFamilyIndex;
|
||||||
|
|
||||||
|
api.GetPhysicalDeviceProperties(apiHandle, out var properties);
|
||||||
|
|
||||||
|
DeviceName = Marshal.PtrToStringAnsi((IntPtr)properties.DeviceName);
|
||||||
|
|
||||||
|
var version = (Version32)properties.ApiVersion;
|
||||||
|
ApiVersion = new Version((int)version.Major, (int)version.Minor, 0, (int)version.Patch);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal PhysicalDevice InternalHandle { get; }
|
||||||
|
internal Vk Api { get; }
|
||||||
|
public uint QueueCount { get; }
|
||||||
|
public uint QueueFamilyIndex { get; }
|
||||||
|
public IntPtr Handle => InternalHandle.Handle;
|
||||||
|
|
||||||
|
public string DeviceName { get; }
|
||||||
|
public Version ApiVersion { get; }
|
||||||
|
|
||||||
|
internal static unsafe VulkanPhysicalDevice FindSuitablePhysicalDevice(VulkanInstance instance,
|
||||||
|
VulkanSurface surface, bool preferDiscreteGpu, string preferredDevice)
|
||||||
|
{
|
||||||
|
uint physicalDeviceCount;
|
||||||
|
|
||||||
|
instance.Api.EnumeratePhysicalDevices(instance.InternalHandle, &physicalDeviceCount, null).ThrowOnError();
|
||||||
|
|
||||||
|
var physicalDevices = new PhysicalDevice[physicalDeviceCount];
|
||||||
|
|
||||||
|
fixed (PhysicalDevice* pPhysicalDevices = physicalDevices)
|
||||||
|
{
|
||||||
|
instance.Api.EnumeratePhysicalDevices(instance.InternalHandle, &physicalDeviceCount, pPhysicalDevices)
|
||||||
|
.ThrowOnError();
|
||||||
|
}
|
||||||
|
|
||||||
|
var physicalDeviceProperties = new Dictionary<PhysicalDevice, PhysicalDeviceProperties>();
|
||||||
|
|
||||||
|
foreach (var physicalDevice in physicalDevices)
|
||||||
|
{
|
||||||
|
instance.Api.GetPhysicalDeviceProperties(physicalDevice, out var properties);
|
||||||
|
physicalDeviceProperties.Add(physicalDevice, properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(preferredDevice))
|
||||||
|
{
|
||||||
|
var physicalDevice = physicalDeviceProperties.FirstOrDefault(x => VulkanInitialization.StringFromIdPair(x.Value.VendorID, x.Value.DeviceID) == preferredDevice);
|
||||||
|
if (physicalDevice.Key.Handle != 0 && IsSuitableDevice(instance.Api, physicalDevice.Key,
|
||||||
|
physicalDevice.Value, surface.ApiHandle, out var queueCount,
|
||||||
|
out var queueFamilyIndex))
|
||||||
|
return new VulkanPhysicalDevice(physicalDevice.Key, instance.Api, queueCount, queueFamilyIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preferDiscreteGpu)
|
||||||
|
{
|
||||||
|
var discreteGpus = physicalDeviceProperties.Where(p => p.Value.DeviceType == PhysicalDeviceType.DiscreteGpu);
|
||||||
|
|
||||||
|
foreach (var gpu in discreteGpus)
|
||||||
|
{
|
||||||
|
if (IsSuitableDevice(
|
||||||
|
instance.Api,
|
||||||
|
gpu.Key,
|
||||||
|
gpu.Value,
|
||||||
|
surface.ApiHandle,
|
||||||
|
out var queueCount,
|
||||||
|
out var queueFamilyIndex))
|
||||||
|
{
|
||||||
|
return new VulkanPhysicalDevice(gpu.Key, instance.Api, queueCount, queueFamilyIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
physicalDeviceProperties.Remove(gpu.Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var physicalDevice in physicalDeviceProperties)
|
||||||
|
if (IsSuitableDevice(
|
||||||
|
instance.Api,
|
||||||
|
physicalDevice.Key,
|
||||||
|
physicalDevice.Value,
|
||||||
|
surface.ApiHandle,
|
||||||
|
out var queueCount,
|
||||||
|
out var queueFamilyIndex))
|
||||||
|
{
|
||||||
|
return new VulkanPhysicalDevice(physicalDevice.Key, instance.Api, queueCount, queueFamilyIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception("No suitable physical device found");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static unsafe bool IsSuitableDevice(Vk api, PhysicalDevice physicalDevice, PhysicalDeviceProperties properties, SurfaceKHR surface,
|
||||||
|
out uint queueCount, out uint familyIndex)
|
||||||
|
{
|
||||||
|
queueCount = 0;
|
||||||
|
familyIndex = 0;
|
||||||
|
|
||||||
|
if (properties.DeviceType == PhysicalDeviceType.Cpu) return false;
|
||||||
|
|
||||||
|
var extensionMatches = 0;
|
||||||
|
uint propertiesCount;
|
||||||
|
|
||||||
|
api.EnumerateDeviceExtensionProperties(physicalDevice, (byte*)null, &propertiesCount, null).ThrowOnError();
|
||||||
|
|
||||||
|
var extensionProperties = new ExtensionProperties[propertiesCount];
|
||||||
|
|
||||||
|
fixed (ExtensionProperties* pExtensionProperties = extensionProperties)
|
||||||
|
{
|
||||||
|
api.EnumerateDeviceExtensionProperties(
|
||||||
|
physicalDevice,
|
||||||
|
(byte*)null,
|
||||||
|
&propertiesCount,
|
||||||
|
pExtensionProperties).ThrowOnError();
|
||||||
|
|
||||||
|
for (var i = 0; i < propertiesCount; i++)
|
||||||
|
{
|
||||||
|
var extensionName = Marshal.PtrToStringAnsi((IntPtr)pExtensionProperties[i].ExtensionName);
|
||||||
|
|
||||||
|
if (VulkanInitialization.RequiredExtensions.Contains(extensionName))
|
||||||
|
{
|
||||||
|
extensionMatches++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extensionMatches == VulkanInitialization.RequiredExtensions.Length)
|
||||||
|
{
|
||||||
|
familyIndex = FindSuitableQueueFamily(api, physicalDevice, surface, out queueCount);
|
||||||
|
|
||||||
|
return familyIndex != uint.MaxValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal unsafe string[] GetSupportedExtensions()
|
||||||
|
{
|
||||||
|
uint propertiesCount;
|
||||||
|
|
||||||
|
Api.EnumerateDeviceExtensionProperties(InternalHandle, (byte*)null, &propertiesCount, null).ThrowOnError();
|
||||||
|
|
||||||
|
var extensionProperties = new ExtensionProperties[propertiesCount];
|
||||||
|
|
||||||
|
fixed (ExtensionProperties* pExtensionProperties = extensionProperties)
|
||||||
|
{
|
||||||
|
Api.EnumerateDeviceExtensionProperties(InternalHandle, (byte*)null, &propertiesCount, pExtensionProperties)
|
||||||
|
.ThrowOnError();
|
||||||
|
}
|
||||||
|
|
||||||
|
return extensionProperties.Select(x => Marshal.PtrToStringAnsi((IntPtr)x.ExtensionName)).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static unsafe uint FindSuitableQueueFamily(Vk api, PhysicalDevice physicalDevice, SurfaceKHR surface,
|
||||||
|
out uint queueCount)
|
||||||
|
{
|
||||||
|
const QueueFlags RequiredFlags = QueueFlags.QueueGraphicsBit | QueueFlags.QueueComputeBit;
|
||||||
|
|
||||||
|
var khrSurface = new KhrSurface(api.Context);
|
||||||
|
|
||||||
|
uint propertiesCount;
|
||||||
|
|
||||||
|
api.GetPhysicalDeviceQueueFamilyProperties(physicalDevice, &propertiesCount, null);
|
||||||
|
|
||||||
|
var properties = new QueueFamilyProperties[propertiesCount];
|
||||||
|
|
||||||
|
fixed (QueueFamilyProperties* pProperties = properties)
|
||||||
|
{
|
||||||
|
api.GetPhysicalDeviceQueueFamilyProperties(physicalDevice, &propertiesCount, pProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint index = 0; index < propertiesCount; index++)
|
||||||
|
{
|
||||||
|
var queueFlags = properties[index].QueueFlags;
|
||||||
|
|
||||||
|
khrSurface.GetPhysicalDeviceSurfaceSupport(physicalDevice, index, surface, out var surfaceSupported)
|
||||||
|
.ThrowOnError();
|
||||||
|
|
||||||
|
if (queueFlags.HasFlag(RequiredFlags) && surfaceSupported)
|
||||||
|
{
|
||||||
|
queueCount = properties[index].QueueCount;
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
queueCount = 0;
|
||||||
|
return uint.MaxValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
84
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanPlatformInterface.cs
Normal file
84
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanPlatformInterface.cs
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
using System;
|
||||||
|
using Avalonia;
|
||||||
|
using Ryujinx.Ava.Ui.Vulkan.Surfaces;
|
||||||
|
using Silk.NET.Vulkan;
|
||||||
|
using Ryujinx.Graphics.Vulkan;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
|
{
|
||||||
|
internal class VulkanPlatformInterface : IDisposable
|
||||||
|
{
|
||||||
|
private static VulkanOptions _options;
|
||||||
|
|
||||||
|
private VulkanPlatformInterface(VulkanInstance instance)
|
||||||
|
{
|
||||||
|
Instance = instance;
|
||||||
|
Api = instance.Api;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VulkanPhysicalDevice PhysicalDevice { get; private set; }
|
||||||
|
public VulkanInstance Instance { get; }
|
||||||
|
public VulkanDevice Device { get; set; }
|
||||||
|
public Vk Api { get; private set; }
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Device?.Dispose();
|
||||||
|
Instance?.Dispose();
|
||||||
|
Api?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static VulkanPlatformInterface TryCreate()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_options = AvaloniaLocator.Current.GetService<VulkanOptions>() ?? new VulkanOptions();
|
||||||
|
|
||||||
|
var instance = VulkanInstance.Create(_options);
|
||||||
|
|
||||||
|
return new VulkanPlatformInterface(instance);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryInitialize()
|
||||||
|
{
|
||||||
|
var feature = TryCreate();
|
||||||
|
if (feature != null)
|
||||||
|
{
|
||||||
|
AvaloniaLocator.CurrentMutable.Bind<VulkanPlatformInterface>().ToConstant(feature);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VulkanSurfaceRenderTarget CreateRenderTarget(IVulkanPlatformSurface platformSurface)
|
||||||
|
{
|
||||||
|
var surface = VulkanSurface.CreateSurface(Instance, platformSurface);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Device == null)
|
||||||
|
{
|
||||||
|
PhysicalDevice = VulkanPhysicalDevice.FindSuitablePhysicalDevice(Instance, surface, _options.PreferDiscreteGpu, _options.PreferredDevice);
|
||||||
|
var device = VulkanInitialization.CreateDevice(Instance.Api,
|
||||||
|
PhysicalDevice.InternalHandle,
|
||||||
|
PhysicalDevice.QueueFamilyIndex,
|
||||||
|
VulkanInitialization.GetSupportedExtensions(Instance.Api, PhysicalDevice.InternalHandle),
|
||||||
|
PhysicalDevice.QueueCount);
|
||||||
|
|
||||||
|
Device = new VulkanDevice(device, PhysicalDevice, Instance.Api);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
surface.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new VulkanSurfaceRenderTarget(this, surface);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanQueue.cs
Normal file
18
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanQueue.cs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
using System;
|
||||||
|
using Silk.NET.Vulkan;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
|
{
|
||||||
|
internal class VulkanQueue
|
||||||
|
{
|
||||||
|
public VulkanQueue(VulkanDevice device, Queue apiHandle)
|
||||||
|
{
|
||||||
|
Device = device;
|
||||||
|
InternalHandle = apiHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VulkanDevice Device { get; }
|
||||||
|
public IntPtr Handle => InternalHandle.Handle;
|
||||||
|
internal Queue InternalHandle { get; }
|
||||||
|
}
|
||||||
|
}
|
32
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanSemaphorePair.cs
Normal file
32
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanSemaphorePair.cs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
using System;
|
||||||
|
using Silk.NET.Vulkan;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
|
{
|
||||||
|
internal class VulkanSemaphorePair : IDisposable
|
||||||
|
{
|
||||||
|
private readonly VulkanDevice _device;
|
||||||
|
|
||||||
|
public unsafe VulkanSemaphorePair(VulkanDevice device)
|
||||||
|
{
|
||||||
|
_device = device;
|
||||||
|
|
||||||
|
var semaphoreCreateInfo = new SemaphoreCreateInfo { SType = StructureType.SemaphoreCreateInfo };
|
||||||
|
|
||||||
|
_device.Api.CreateSemaphore(_device.InternalHandle, semaphoreCreateInfo, null, out var semaphore).ThrowOnError();
|
||||||
|
ImageAvailableSemaphore = semaphore;
|
||||||
|
|
||||||
|
_device.Api.CreateSemaphore(_device.InternalHandle, semaphoreCreateInfo, null, out semaphore).ThrowOnError();
|
||||||
|
RenderFinishedSemaphore = semaphore;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Semaphore ImageAvailableSemaphore { get; }
|
||||||
|
internal Semaphore RenderFinishedSemaphore { get; }
|
||||||
|
|
||||||
|
public unsafe void Dispose()
|
||||||
|
{
|
||||||
|
_device.Api.DestroySemaphore(_device.InternalHandle, ImageAvailableSemaphore, null);
|
||||||
|
_device.Api.DestroySemaphore(_device.InternalHandle, RenderFinishedSemaphore, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
77
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanSurface.cs
Normal file
77
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanSurface.cs
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
using System;
|
||||||
|
using Avalonia;
|
||||||
|
using Ryujinx.Ava.Ui.Vulkan.Surfaces;
|
||||||
|
using Silk.NET.Vulkan;
|
||||||
|
using Silk.NET.Vulkan.Extensions.KHR;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
|
{
|
||||||
|
public class VulkanSurface : IDisposable
|
||||||
|
{
|
||||||
|
private readonly VulkanInstance _instance;
|
||||||
|
private readonly IVulkanPlatformSurface _vulkanPlatformSurface;
|
||||||
|
|
||||||
|
private VulkanSurface(IVulkanPlatformSurface vulkanPlatformSurface, VulkanInstance instance)
|
||||||
|
{
|
||||||
|
_vulkanPlatformSurface = vulkanPlatformSurface;
|
||||||
|
_instance = instance;
|
||||||
|
ApiHandle = vulkanPlatformSurface.CreateSurface(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal SurfaceKHR ApiHandle { get; }
|
||||||
|
|
||||||
|
internal static KhrSurface SurfaceExtension { get; private set; }
|
||||||
|
|
||||||
|
internal PixelSize SurfaceSize => _vulkanPlatformSurface.SurfaceSize;
|
||||||
|
|
||||||
|
public unsafe void Dispose()
|
||||||
|
{
|
||||||
|
SurfaceExtension.DestroySurface(_instance.InternalHandle, ApiHandle, null);
|
||||||
|
_vulkanPlatformSurface.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static VulkanSurface CreateSurface(VulkanInstance instance, IVulkanPlatformSurface vulkanPlatformSurface)
|
||||||
|
{
|
||||||
|
if (SurfaceExtension == null)
|
||||||
|
{
|
||||||
|
instance.Api.TryGetInstanceExtension(instance.InternalHandle, out KhrSurface extension);
|
||||||
|
|
||||||
|
SurfaceExtension = extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new VulkanSurface(vulkanPlatformSurface, instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool CanSurfacePresent(VulkanPhysicalDevice physicalDevice)
|
||||||
|
{
|
||||||
|
SurfaceExtension.GetPhysicalDeviceSurfaceSupport(physicalDevice.InternalHandle, physicalDevice.QueueFamilyIndex, ApiHandle, out var isSupported);
|
||||||
|
|
||||||
|
return isSupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal unsafe SurfaceFormatKHR GetSurfaceFormat(VulkanPhysicalDevice physicalDevice)
|
||||||
|
{
|
||||||
|
uint surfaceFormatsCount;
|
||||||
|
|
||||||
|
SurfaceExtension.GetPhysicalDeviceSurfaceFormats(physicalDevice.InternalHandle, ApiHandle,
|
||||||
|
&surfaceFormatsCount, null);
|
||||||
|
|
||||||
|
var surfaceFormats = new SurfaceFormatKHR[surfaceFormatsCount];
|
||||||
|
|
||||||
|
fixed (SurfaceFormatKHR* pSurfaceFormats = surfaceFormats)
|
||||||
|
{
|
||||||
|
SurfaceExtension.GetPhysicalDeviceSurfaceFormats(physicalDevice.InternalHandle, ApiHandle,
|
||||||
|
&surfaceFormatsCount, pSurfaceFormats);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (surfaceFormats.Length == 1 && surfaceFormats[0].Format == Format.Undefined)
|
||||||
|
return new SurfaceFormatKHR(Format.B8G8R8A8Unorm, ColorSpaceKHR.ColorspaceSrgbNonlinearKhr);
|
||||||
|
foreach (var format in surfaceFormats)
|
||||||
|
if (format.Format == Format.B8G8R8A8Unorm &&
|
||||||
|
format.ColorSpace == ColorSpaceKHR.ColorspaceSrgbNonlinearKhr)
|
||||||
|
return format;
|
||||||
|
|
||||||
|
return surfaceFormats[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
using System;
|
||||||
|
using Avalonia;
|
||||||
|
using Ryujinx.Ava.Ui.Vulkan.Surfaces;
|
||||||
|
using Silk.NET.Vulkan;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
|
{
|
||||||
|
internal class VulkanSurfaceRenderingSession : IDisposable
|
||||||
|
{
|
||||||
|
private readonly VulkanDevice _device;
|
||||||
|
private readonly VulkanSurfaceRenderTarget _renderTarget;
|
||||||
|
private VulkanCommandBufferPool.VulkanCommandBuffer _commandBuffer;
|
||||||
|
|
||||||
|
public VulkanSurfaceRenderingSession(VulkanDisplay display, VulkanDevice device,
|
||||||
|
VulkanSurfaceRenderTarget renderTarget, float scaling)
|
||||||
|
{
|
||||||
|
Display = display;
|
||||||
|
_device = device;
|
||||||
|
_renderTarget = renderTarget;
|
||||||
|
Scaling = scaling;
|
||||||
|
Begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
public VulkanDisplay Display { get; }
|
||||||
|
|
||||||
|
public PixelSize Size => _renderTarget.Size;
|
||||||
|
public Vk Api => _device.Api;
|
||||||
|
|
||||||
|
public float Scaling { get; }
|
||||||
|
|
||||||
|
public bool IsYFlipped { get; } = true;
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_commandBuffer = Display.StartPresentation(_renderTarget);
|
||||||
|
|
||||||
|
Display.BlitImageToCurrentImage(_renderTarget, _commandBuffer.InternalHandle);
|
||||||
|
|
||||||
|
Display.EndPresentation(_commandBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Begin()
|
||||||
|
{
|
||||||
|
if (!Display.EnsureSwapchainAvailable())
|
||||||
|
_renderTarget.Invalidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
192
Ryujinx.Ava/Ui/Controls/OpenGLRendererControl.cs
Normal file
192
Ryujinx.Ava/Ui/Controls/OpenGLRendererControl.cs
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.OpenGL;
|
||||||
|
using Avalonia.Platform;
|
||||||
|
using Avalonia.Rendering.SceneGraph;
|
||||||
|
using Avalonia.Skia;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
using Ryujinx.Common.Configuration;
|
||||||
|
using SkiaSharp;
|
||||||
|
using SPB.Graphics;
|
||||||
|
using SPB.Graphics.OpenGL;
|
||||||
|
using SPB.Platform;
|
||||||
|
using SPB.Windowing;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Ui.Controls
|
||||||
|
{
|
||||||
|
internal class OpenGLRendererControl : RendererControl
|
||||||
|
{
|
||||||
|
public int Major { get; }
|
||||||
|
public int Minor { get; }
|
||||||
|
public OpenGLContextBase GameContext { get; set; }
|
||||||
|
|
||||||
|
public static OpenGLContextBase PrimaryContext => AvaloniaLocator.Current.GetService<IPlatformOpenGlInterface>().PrimaryContext.AsOpenGLContextBase();
|
||||||
|
|
||||||
|
private SwappableNativeWindowBase _gameBackgroundWindow;
|
||||||
|
|
||||||
|
private IntPtr _fence;
|
||||||
|
|
||||||
|
public OpenGLRendererControl(int major, int minor, GraphicsDebugLevel graphicsDebugLevel) : base(graphicsDebugLevel)
|
||||||
|
{
|
||||||
|
Major = major;
|
||||||
|
Minor = minor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void DestroyBackgroundContext()
|
||||||
|
{
|
||||||
|
_image = null;
|
||||||
|
|
||||||
|
if (_fence != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
DrawOperation.Dispose();
|
||||||
|
GL.DeleteSync(_fence);
|
||||||
|
}
|
||||||
|
|
||||||
|
GlDrawOperation.DeleteFramebuffer();
|
||||||
|
|
||||||
|
GameContext?.Dispose();
|
||||||
|
|
||||||
|
_gameBackgroundWindow?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Present(object image)
|
||||||
|
{
|
||||||
|
Dispatcher.UIThread.InvokeAsync(() =>
|
||||||
|
{
|
||||||
|
Image = (int)image;
|
||||||
|
}).Wait();
|
||||||
|
|
||||||
|
if (_fence != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
GL.DeleteSync(_fence);
|
||||||
|
}
|
||||||
|
|
||||||
|
_fence = GL.FenceSync(SyncCondition.SyncGpuCommandsComplete, WaitSyncFlags.None);
|
||||||
|
|
||||||
|
QueueRender();
|
||||||
|
|
||||||
|
_gameBackgroundWindow.SwapBuffers();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void MakeCurrent()
|
||||||
|
{
|
||||||
|
GameContext.MakeCurrent(_gameBackgroundWindow);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void MakeCurrent(SwappableNativeWindowBase window)
|
||||||
|
{
|
||||||
|
GameContext.MakeCurrent(window);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void CreateWindow()
|
||||||
|
{
|
||||||
|
var flags = OpenGLContextFlags.Compat;
|
||||||
|
if (DebugLevel != GraphicsDebugLevel.None)
|
||||||
|
{
|
||||||
|
flags |= OpenGLContextFlags.Debug;
|
||||||
|
}
|
||||||
|
_gameBackgroundWindow = PlatformHelper.CreateOpenGLWindow(FramebufferFormat.Default, 0, 0, 100, 100);
|
||||||
|
_gameBackgroundWindow.Hide();
|
||||||
|
|
||||||
|
GameContext = PlatformHelper.CreateOpenGLContext(FramebufferFormat.Default, Major, Minor, flags, shareContext: PrimaryContext);
|
||||||
|
GameContext.Initialize(_gameBackgroundWindow);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override ICustomDrawOperation CreateDrawOperation()
|
||||||
|
{
|
||||||
|
return new GlDrawOperation(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class GlDrawOperation : ICustomDrawOperation
|
||||||
|
{
|
||||||
|
private static int _framebuffer;
|
||||||
|
|
||||||
|
public Rect Bounds { get; }
|
||||||
|
|
||||||
|
private readonly OpenGLRendererControl _control;
|
||||||
|
|
||||||
|
public GlDrawOperation(OpenGLRendererControl control)
|
||||||
|
{
|
||||||
|
_control = control;
|
||||||
|
Bounds = _control.Bounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose() { }
|
||||||
|
|
||||||
|
public static void DeleteFramebuffer()
|
||||||
|
{
|
||||||
|
if (_framebuffer == 0)
|
||||||
|
{
|
||||||
|
GL.DeleteFramebuffer(_framebuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
_framebuffer = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(ICustomDrawOperation other)
|
||||||
|
{
|
||||||
|
return other is GlDrawOperation operation && Equals(this, operation) && operation.Bounds == Bounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HitTest(Point p)
|
||||||
|
{
|
||||||
|
return Bounds.Contains(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateRenderTarget()
|
||||||
|
{
|
||||||
|
_framebuffer = GL.GenFramebuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Render(IDrawingContextImpl context)
|
||||||
|
{
|
||||||
|
if (_control.Image == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_framebuffer == 0)
|
||||||
|
{
|
||||||
|
CreateRenderTarget();
|
||||||
|
}
|
||||||
|
|
||||||
|
int currentFramebuffer = GL.GetInteger(GetPName.FramebufferBinding);
|
||||||
|
|
||||||
|
var image = _control.Image;
|
||||||
|
var fence = _control._fence;
|
||||||
|
|
||||||
|
GL.BindFramebuffer(FramebufferTarget.Framebuffer, _framebuffer);
|
||||||
|
GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, TextureTarget.Texture2D, (int)image, 0);
|
||||||
|
GL.BindFramebuffer(FramebufferTarget.Framebuffer, currentFramebuffer);
|
||||||
|
|
||||||
|
if (context is not ISkiaDrawingContextImpl skiaDrawingContextImpl)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var imageInfo = new SKImageInfo((int)_control.RenderSize.Width, (int)_control.RenderSize.Height, SKColorType.Rgba8888);
|
||||||
|
var glInfo = new GRGlFramebufferInfo((uint)_framebuffer, SKColorType.Rgba8888.ToGlSizedFormat());
|
||||||
|
|
||||||
|
GL.WaitSync(fence, WaitSyncFlags.None, ulong.MaxValue);
|
||||||
|
|
||||||
|
using var backendTexture = new GRBackendRenderTarget(imageInfo.Width, imageInfo.Height, 1, 0, glInfo);
|
||||||
|
using var surface = SKSurface.Create(skiaDrawingContextImpl.GrContext, backendTexture, GRSurfaceOrigin.BottomLeft, SKColorType.Rgba8888);
|
||||||
|
|
||||||
|
if (surface == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var rect = new Rect(new Point(), _control.RenderSize);
|
||||||
|
|
||||||
|
using var snapshot = surface.Snapshot();
|
||||||
|
skiaDrawingContextImpl.SkCanvas.DrawImage(snapshot, rect.ToSKRect(), _control.Bounds.ToSKRect(), new SKPaint());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,65 +2,45 @@
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Data;
|
using Avalonia.Data;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
using Avalonia.OpenGL;
|
|
||||||
using Avalonia.Platform;
|
|
||||||
using Avalonia.Rendering.SceneGraph;
|
using Avalonia.Rendering.SceneGraph;
|
||||||
using Avalonia.Skia;
|
|
||||||
using Avalonia.Threading;
|
|
||||||
using OpenTK.Graphics.OpenGL;
|
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
using SkiaSharp;
|
|
||||||
using SPB.Graphics;
|
|
||||||
using SPB.Graphics.OpenGL;
|
|
||||||
using SPB.Platform;
|
|
||||||
using SPB.Windowing;
|
using SPB.Windowing;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.Ui.Controls
|
namespace Ryujinx.Ava.Ui.Controls
|
||||||
{
|
{
|
||||||
public class RendererControl : Control
|
public abstract class RendererControl : Control
|
||||||
{
|
{
|
||||||
private int _image;
|
protected object _image;
|
||||||
|
|
||||||
static RendererControl()
|
static RendererControl()
|
||||||
{
|
{
|
||||||
AffectsRender<RendererControl>(ImageProperty);
|
AffectsRender<RendererControl>(ImageProperty);
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly static StyledProperty<int> ImageProperty =
|
public readonly static StyledProperty<object> ImageProperty =
|
||||||
AvaloniaProperty.Register<RendererControl, int>(nameof(Image), 0, inherits: true, defaultBindingMode: BindingMode.TwoWay);
|
AvaloniaProperty.Register<RendererControl, object>(nameof(Image), 0, inherits: true, defaultBindingMode: BindingMode.TwoWay);
|
||||||
|
|
||||||
protected int Image
|
protected object Image
|
||||||
{
|
{
|
||||||
get => _image;
|
get => _image;
|
||||||
set => SetAndRaise(ImageProperty, ref _image, value);
|
set => SetAndRaise(ImageProperty, ref _image, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public event EventHandler<EventArgs> GlInitialized;
|
public event EventHandler<EventArgs> RendererInitialized;
|
||||||
public event EventHandler<Size> SizeChanged;
|
public event EventHandler<Size> SizeChanged;
|
||||||
|
|
||||||
protected Size RenderSize { get; private set; }
|
protected Size RenderSize { get; private set; }
|
||||||
public bool IsStarted { get; private set; }
|
public bool IsStarted { get; private set; }
|
||||||
|
|
||||||
public int Major { get; }
|
|
||||||
public int Minor { get; }
|
|
||||||
public GraphicsDebugLevel DebugLevel { get; }
|
public GraphicsDebugLevel DebugLevel { get; }
|
||||||
public OpenGLContextBase GameContext { get; set; }
|
|
||||||
|
|
||||||
public static OpenGLContextBase PrimaryContext => AvaloniaLocator.Current.GetService<IPlatformOpenGlInterface>().PrimaryContext.AsOpenGLContextBase();
|
|
||||||
|
|
||||||
private SwappableNativeWindowBase _gameBackgroundWindow;
|
|
||||||
|
|
||||||
private bool _isInitialized;
|
private bool _isInitialized;
|
||||||
|
|
||||||
private IntPtr _fence;
|
protected ICustomDrawOperation DrawOperation { get; private set; }
|
||||||
|
|
||||||
private GlDrawOperation _glDrawOperation;
|
public RendererControl(GraphicsDebugLevel graphicsDebugLevel)
|
||||||
|
|
||||||
public RendererControl(int major, int minor, GraphicsDebugLevel graphicsDebugLevel)
|
|
||||||
{
|
{
|
||||||
Major = major;
|
|
||||||
Minor = minor;
|
|
||||||
DebugLevel = graphicsDebugLevel;
|
DebugLevel = graphicsDebugLevel;
|
||||||
IObservable<Rect> resizeObservable = this.GetObservable(BoundsProperty);
|
IObservable<Rect> resizeObservable = this.GetObservable(BoundsProperty);
|
||||||
|
|
||||||
|
@ -69,7 +49,7 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
Focusable = true;
|
Focusable = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Resized(Rect rect)
|
protected void Resized(Rect rect)
|
||||||
{
|
{
|
||||||
SizeChanged?.Invoke(this, rect.Size);
|
SizeChanged?.Invoke(this, rect.Size);
|
||||||
|
|
||||||
|
@ -77,37 +57,40 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
{
|
{
|
||||||
RenderSize = rect.Size * VisualRoot.RenderScaling;
|
RenderSize = rect.Size * VisualRoot.RenderScaling;
|
||||||
|
|
||||||
_glDrawOperation?.Dispose();
|
DrawOperation?.Dispose();
|
||||||
_glDrawOperation = new GlDrawOperation(this);
|
DrawOperation = CreateDrawOperation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract ICustomDrawOperation CreateDrawOperation();
|
||||||
|
protected abstract void CreateWindow();
|
||||||
|
|
||||||
public override void Render(DrawingContext context)
|
public override void Render(DrawingContext context)
|
||||||
{
|
{
|
||||||
if (!_isInitialized)
|
if (!_isInitialized)
|
||||||
{
|
{
|
||||||
CreateWindow();
|
CreateWindow();
|
||||||
|
|
||||||
OnGlInitialized();
|
OnRendererInitialized();
|
||||||
_isInitialized = true;
|
_isInitialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GameContext == null || !IsStarted || Image == 0)
|
if (!IsStarted || Image == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_glDrawOperation != null)
|
if (DrawOperation != null)
|
||||||
{
|
{
|
||||||
context.Custom(_glDrawOperation);
|
context.Custom(DrawOperation);
|
||||||
}
|
}
|
||||||
|
|
||||||
base.Render(context);
|
base.Render(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void OnGlInitialized()
|
protected void OnRendererInitialized()
|
||||||
{
|
{
|
||||||
GlInitialized?.Invoke(this, EventArgs.Empty);
|
RendererInitialized?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void QueueRender()
|
public void QueueRender()
|
||||||
|
@ -115,24 +98,7 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
Program.RenderTimer.TickNow();
|
Program.RenderTimer.TickNow();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Present(object image)
|
internal abstract void Present(object image);
|
||||||
{
|
|
||||||
Dispatcher.UIThread.InvokeAsync(() =>
|
|
||||||
{
|
|
||||||
Image = (int)image;
|
|
||||||
}).Wait();
|
|
||||||
|
|
||||||
if (_fence != IntPtr.Zero)
|
|
||||||
{
|
|
||||||
GL.DeleteSync(_fence);
|
|
||||||
}
|
|
||||||
|
|
||||||
_fence = GL.FenceSync(SyncCondition.SyncGpuCommandsComplete, WaitSyncFlags.None);
|
|
||||||
|
|
||||||
QueueRender();
|
|
||||||
|
|
||||||
_gameBackgroundWindow.SwapBuffers();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void Start()
|
internal void Start()
|
||||||
{
|
{
|
||||||
|
@ -145,132 +111,8 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
IsStarted = false;
|
IsStarted = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DestroyBackgroundContext()
|
public abstract void DestroyBackgroundContext();
|
||||||
{
|
internal abstract void MakeCurrent();
|
||||||
_image = 0;
|
internal abstract void MakeCurrent(SwappableNativeWindowBase window);
|
||||||
|
|
||||||
if (_fence != IntPtr.Zero)
|
|
||||||
{
|
|
||||||
_glDrawOperation.Dispose();
|
|
||||||
GL.DeleteSync(_fence);
|
|
||||||
}
|
|
||||||
|
|
||||||
GlDrawOperation.DeleteFramebuffer();
|
|
||||||
|
|
||||||
GameContext?.Dispose();
|
|
||||||
|
|
||||||
_gameBackgroundWindow?.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void MakeCurrent()
|
|
||||||
{
|
|
||||||
GameContext.MakeCurrent(_gameBackgroundWindow);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void MakeCurrent(SwappableNativeWindowBase window)
|
|
||||||
{
|
|
||||||
GameContext.MakeCurrent(window);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void CreateWindow()
|
|
||||||
{
|
|
||||||
var flags = OpenGLContextFlags.Compat;
|
|
||||||
if (DebugLevel != GraphicsDebugLevel.None)
|
|
||||||
{
|
|
||||||
flags |= OpenGLContextFlags.Debug;
|
|
||||||
}
|
|
||||||
_gameBackgroundWindow = PlatformHelper.CreateOpenGLWindow(FramebufferFormat.Default, 0, 0, 100, 100);
|
|
||||||
_gameBackgroundWindow.Hide();
|
|
||||||
|
|
||||||
GameContext = PlatformHelper.CreateOpenGLContext(FramebufferFormat.Default, Major, Minor, flags, shareContext: PrimaryContext);
|
|
||||||
GameContext.Initialize(_gameBackgroundWindow);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class GlDrawOperation : ICustomDrawOperation
|
|
||||||
{
|
|
||||||
private static int _framebuffer;
|
|
||||||
|
|
||||||
public Rect Bounds { get; }
|
|
||||||
|
|
||||||
private readonly RendererControl _control;
|
|
||||||
|
|
||||||
public GlDrawOperation(RendererControl control)
|
|
||||||
{
|
|
||||||
_control = control;
|
|
||||||
Bounds = _control.Bounds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose() { }
|
|
||||||
|
|
||||||
public static void DeleteFramebuffer()
|
|
||||||
{
|
|
||||||
if (_framebuffer == 0)
|
|
||||||
{
|
|
||||||
GL.DeleteFramebuffer(_framebuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
_framebuffer = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Equals(ICustomDrawOperation other)
|
|
||||||
{
|
|
||||||
return other is GlDrawOperation operation && Equals(this, operation) && operation.Bounds == Bounds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool HitTest(Point p)
|
|
||||||
{
|
|
||||||
return Bounds.Contains(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CreateRenderTarget()
|
|
||||||
{
|
|
||||||
_framebuffer = GL.GenFramebuffer();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Render(IDrawingContextImpl context)
|
|
||||||
{
|
|
||||||
if (_control.Image == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_framebuffer == 0)
|
|
||||||
{
|
|
||||||
CreateRenderTarget();
|
|
||||||
}
|
|
||||||
|
|
||||||
int currentFramebuffer = GL.GetInteger(GetPName.FramebufferBinding);
|
|
||||||
|
|
||||||
var image = _control.Image;
|
|
||||||
var fence = _control._fence;
|
|
||||||
|
|
||||||
GL.BindFramebuffer(FramebufferTarget.Framebuffer, _framebuffer);
|
|
||||||
GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, TextureTarget.Texture2D, image, 0);
|
|
||||||
GL.BindFramebuffer(FramebufferTarget.Framebuffer, currentFramebuffer);
|
|
||||||
|
|
||||||
if (context is not ISkiaDrawingContextImpl skiaDrawingContextImpl)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var imageInfo = new SKImageInfo((int)_control.RenderSize.Width, (int)_control.RenderSize.Height, SKColorType.Rgba8888);
|
|
||||||
var glInfo = new GRGlFramebufferInfo((uint)_framebuffer, SKColorType.Rgba8888.ToGlSizedFormat());
|
|
||||||
|
|
||||||
GL.WaitSync(fence, WaitSyncFlags.None, ulong.MaxValue);
|
|
||||||
|
|
||||||
using var backendTexture = new GRBackendRenderTarget(imageInfo.Width, imageInfo.Height, 1, 0, glInfo);
|
|
||||||
using var surface = SKSurface.Create(skiaDrawingContextImpl.GrContext, backendTexture, GRSurfaceOrigin.BottomLeft, SKColorType.Rgba8888);
|
|
||||||
|
|
||||||
if (surface == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var rect = new Rect(new Point(), _control.RenderSize);
|
|
||||||
|
|
||||||
using var snapshot = surface.Snapshot();
|
|
||||||
skiaDrawingContextImpl.SkCanvas.DrawImage(snapshot, rect.ToSKRect(), _control.Bounds.ToSKRect(), new SKPaint());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
153
Ryujinx.Ava/Ui/Controls/VulkanRendererControl.cs
Normal file
153
Ryujinx.Ava/Ui/Controls/VulkanRendererControl.cs
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Platform;
|
||||||
|
using Avalonia.Rendering.SceneGraph;
|
||||||
|
using Avalonia.Skia;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using Ryujinx.Ava.Ui.Backend.Vulkan;
|
||||||
|
using Ryujinx.Ava.Ui.Vulkan;
|
||||||
|
using Ryujinx.Common.Configuration;
|
||||||
|
using Ryujinx.Graphics.Vulkan;
|
||||||
|
using Silk.NET.Vulkan;
|
||||||
|
using SkiaSharp;
|
||||||
|
using SPB.Windowing;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Ui.Controls
|
||||||
|
{
|
||||||
|
internal class VulkanRendererControl : RendererControl
|
||||||
|
{
|
||||||
|
private VulkanPlatformInterface _platformInterface;
|
||||||
|
|
||||||
|
public VulkanRendererControl(GraphicsDebugLevel graphicsDebugLevel) : base(graphicsDebugLevel)
|
||||||
|
{
|
||||||
|
_platformInterface = AvaloniaLocator.Current.GetService<VulkanPlatformInterface>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void DestroyBackgroundContext()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override ICustomDrawOperation CreateDrawOperation()
|
||||||
|
{
|
||||||
|
return new VulkanDrawOperation(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void CreateWindow()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void MakeCurrent()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void MakeCurrent(SwappableNativeWindowBase window)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Present(object image)
|
||||||
|
{
|
||||||
|
Dispatcher.UIThread.InvokeAsync(() =>
|
||||||
|
{
|
||||||
|
Image = image;
|
||||||
|
}).Wait();
|
||||||
|
|
||||||
|
QueueRender();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class VulkanDrawOperation : ICustomDrawOperation
|
||||||
|
{
|
||||||
|
public Rect Bounds { get; }
|
||||||
|
|
||||||
|
private readonly VulkanRendererControl _control;
|
||||||
|
|
||||||
|
public VulkanDrawOperation(VulkanRendererControl control)
|
||||||
|
{
|
||||||
|
_control = control;
|
||||||
|
Bounds = _control.Bounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(ICustomDrawOperation other)
|
||||||
|
{
|
||||||
|
return other is VulkanDrawOperation operation && Equals(this, operation) && operation.Bounds == Bounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HitTest(Point p)
|
||||||
|
{
|
||||||
|
return Bounds.Contains(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Render(IDrawingContextImpl context)
|
||||||
|
{
|
||||||
|
if (_control.Image == null || _control.RenderSize.Width == 0 || _control.RenderSize.Height == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var image = (PresentImageInfo)_control.Image;
|
||||||
|
|
||||||
|
if (context is not ISkiaDrawingContextImpl skiaDrawingContextImpl)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_control._platformInterface.Device.QueueWaitIdle();
|
||||||
|
|
||||||
|
var gpu = AvaloniaLocator.Current.GetService<VulkanSkiaGpu>();
|
||||||
|
|
||||||
|
var imageInfo = new GRVkImageInfo()
|
||||||
|
{
|
||||||
|
CurrentQueueFamily = _control._platformInterface.PhysicalDevice.QueueFamilyIndex,
|
||||||
|
Format = (uint)Format.R8G8B8A8Unorm,
|
||||||
|
Image = image.Image.Handle,
|
||||||
|
ImageLayout = (uint)ImageLayout.ColorAttachmentOptimal,
|
||||||
|
ImageTiling = (uint)ImageTiling.Optimal,
|
||||||
|
ImageUsageFlags = (uint)(ImageUsageFlags.ImageUsageColorAttachmentBit
|
||||||
|
| ImageUsageFlags.ImageUsageTransferSrcBit
|
||||||
|
| ImageUsageFlags.ImageUsageTransferDstBit),
|
||||||
|
LevelCount = 1,
|
||||||
|
SampleCount = 1,
|
||||||
|
Protected = false,
|
||||||
|
Alloc = new GRVkAlloc()
|
||||||
|
{
|
||||||
|
Memory = image.Memory.Handle,
|
||||||
|
Flags = 0,
|
||||||
|
Offset = image.MemoryOffset,
|
||||||
|
Size = image.MemorySize
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using var backendTexture = new GRBackendRenderTarget(
|
||||||
|
(int)_control.RenderSize.Width,
|
||||||
|
(int)_control.RenderSize.Height,
|
||||||
|
1,
|
||||||
|
imageInfo);
|
||||||
|
|
||||||
|
using var surface = SKSurface.Create(
|
||||||
|
gpu.GrContext,
|
||||||
|
backendTexture,
|
||||||
|
GRSurfaceOrigin.TopLeft,
|
||||||
|
SKColorType.Rgba8888);
|
||||||
|
|
||||||
|
if (surface == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var rect = new Rect(new Point(), _control.RenderSize);
|
||||||
|
|
||||||
|
using var snapshot = surface.Snapshot();
|
||||||
|
skiaDrawingContextImpl.SkCanvas.DrawImage(snapshot, rect.ToSKRect(), _control.Bounds.ToSKRect(), new SKPaint());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -61,7 +61,7 @@ namespace Ryujinx.Ava.Ui.Windows
|
||||||
public AppHost AppHost { get; private set; }
|
public AppHost AppHost { get; private set; }
|
||||||
public InputManager InputManager { get; private set; }
|
public InputManager InputManager { get; private set; }
|
||||||
|
|
||||||
public RendererControl GlRenderer { get; private set; }
|
public RendererControl RendererControl { get; private set; }
|
||||||
public ContentControl ContentFrame { get; private set; }
|
public ContentControl ContentFrame { get; private set; }
|
||||||
public TextBlock LoadStatus { get; private set; }
|
public TextBlock LoadStatus { get; private set; }
|
||||||
public TextBlock FirmwareStatus { get; private set; }
|
public TextBlock FirmwareStatus { get; private set; }
|
||||||
|
@ -257,8 +257,8 @@ namespace Ryujinx.Ava.Ui.Windows
|
||||||
|
|
||||||
_mainViewContent = ContentFrame.Content as Control;
|
_mainViewContent = ContentFrame.Content as Control;
|
||||||
|
|
||||||
GlRenderer = new RendererControl(3, 3, ConfigurationState.Instance.Logger.GraphicsDebugLevel);
|
RendererControl = Program.UseVulkan ? new VulkanRendererControl(ConfigurationState.Instance.Logger.GraphicsDebugLevel) : new OpenGLRendererControl(3, 3, ConfigurationState.Instance.Logger.GraphicsDebugLevel);
|
||||||
AppHost = new AppHost(GlRenderer, InputManager, path, VirtualFileSystem, ContentManager, AccountManager, _userChannelPersistence, this);
|
AppHost = new AppHost(RendererControl, InputManager, path, VirtualFileSystem, ContentManager, AccountManager, _userChannelPersistence, this);
|
||||||
|
|
||||||
if (!AppHost.LoadGuestApplication().Result)
|
if (!AppHost.LoadGuestApplication().Result)
|
||||||
{
|
{
|
||||||
|
@ -282,7 +282,7 @@ namespace Ryujinx.Ava.Ui.Windows
|
||||||
|
|
||||||
private void InitializeGame()
|
private void InitializeGame()
|
||||||
{
|
{
|
||||||
GlRenderer.GlInitialized += GlRenderer_Created;
|
RendererControl.RendererInitialized += GlRenderer_Created;
|
||||||
|
|
||||||
AppHost.StatusUpdatedEvent += Update_StatusBar;
|
AppHost.StatusUpdatedEvent += Update_StatusBar;
|
||||||
AppHost.AppExit += AppHost_AppExit;
|
AppHost.AppExit += AppHost_AppExit;
|
||||||
|
@ -322,14 +322,14 @@ namespace Ryujinx.Ava.Ui.Windows
|
||||||
|
|
||||||
Dispatcher.UIThread.InvokeAsync(() =>
|
Dispatcher.UIThread.InvokeAsync(() =>
|
||||||
{
|
{
|
||||||
ContentFrame.Content = GlRenderer;
|
ContentFrame.Content = RendererControl;
|
||||||
|
|
||||||
if (startFullscreen && WindowState != WindowState.FullScreen)
|
if (startFullscreen && WindowState != WindowState.FullScreen)
|
||||||
{
|
{
|
||||||
ViewModel.ToggleFullscreen();
|
ViewModel.ToggleFullscreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
GlRenderer.Focus();
|
RendererControl.Focus();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -380,8 +380,8 @@ namespace Ryujinx.Ava.Ui.Windows
|
||||||
|
|
||||||
HandleRelaunch();
|
HandleRelaunch();
|
||||||
});
|
});
|
||||||
GlRenderer.GlInitialized -= GlRenderer_Created;
|
RendererControl.RendererInitialized -= GlRenderer_Created;
|
||||||
GlRenderer = null;
|
RendererControl = null;
|
||||||
|
|
||||||
ViewModel.SelectedIcon = null;
|
ViewModel.SelectedIcon = null;
|
||||||
|
|
||||||
|
@ -544,6 +544,7 @@ namespace Ryujinx.Ava.Ui.Windows
|
||||||
GraphicsConfig.MaxAnisotropy = ConfigurationState.Instance.Graphics.MaxAnisotropy;
|
GraphicsConfig.MaxAnisotropy = ConfigurationState.Instance.Graphics.MaxAnisotropy;
|
||||||
GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath;
|
GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath;
|
||||||
GraphicsConfig.EnableShaderCache = ConfigurationState.Instance.Graphics.EnableShaderCache;
|
GraphicsConfig.EnableShaderCache = ConfigurationState.Instance.Graphics.EnableShaderCache;
|
||||||
|
GraphicsConfig.EnableTextureRecompression = ConfigurationState.Instance.Graphics.EnableTextureRecompression;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadHotKeys()
|
public void LoadHotKeys()
|
||||||
|
|
8
Ryujinx.Common/Configuration/GraphicsBackend.cs
Normal file
8
Ryujinx.Common/Configuration/GraphicsBackend.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
namespace Ryujinx.Common.Configuration
|
||||||
|
{
|
||||||
|
public enum GraphicsBackend
|
||||||
|
{
|
||||||
|
Vulkan,
|
||||||
|
OpenGl
|
||||||
|
}
|
||||||
|
}
|
|
@ -640,4 +640,15 @@ namespace Ryujinx.Common.Memory
|
||||||
public ref T this[int index] => ref ToSpan()[index];
|
public ref T this[int index] => ref ToSpan()[index];
|
||||||
public Span<T> ToSpan() => MemoryMarshal.CreateSpan(ref _e0, 64);
|
public Span<T> ToSpan() => MemoryMarshal.CreateSpan(ref _e0, 64);
|
||||||
}
|
}
|
||||||
|
public struct Array73<T> : IArray<T> where T : unmanaged
|
||||||
|
{
|
||||||
|
#pragma warning disable CS0169
|
||||||
|
T _e0;
|
||||||
|
Array64<T> _other;
|
||||||
|
Array8<T> _other2;
|
||||||
|
#pragma warning restore CS0169
|
||||||
|
public int Length => 73;
|
||||||
|
public ref T this[int index] => ref ToSpan()[index];
|
||||||
|
public Span<T> ToSpan() => MemoryMarshal.CreateSpan(ref _e0, 73);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ namespace Ryujinx.Common.System
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static double GetWindowScaleFactor()
|
public static double GetActualScaleFactor()
|
||||||
{
|
{
|
||||||
double userDpiScale = 96.0;
|
double userDpiScale = 96.0;
|
||||||
|
|
||||||
|
@ -84,6 +84,13 @@ namespace Ryujinx.Common.System
|
||||||
Logger.Warning?.Print(LogClass.Application, $"Couldn't determine monitor DPI: {e.Message}");
|
Logger.Warning?.Print(LogClass.Application, $"Couldn't determine monitor DPI: {e.Message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return userDpiScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double GetWindowScaleFactor()
|
||||||
|
{
|
||||||
|
double userDpiScale = GetActualScaleFactor();
|
||||||
|
|
||||||
return Math.Min(userDpiScale / _standardDpiScale, _maxScaleFactor);
|
return Math.Min(userDpiScale / _standardDpiScale, _maxScaleFactor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,11 +11,15 @@ namespace Ryujinx.Graphics.GAL
|
||||||
public readonly bool HasVectorIndexingBug;
|
public readonly bool HasVectorIndexingBug;
|
||||||
|
|
||||||
public readonly bool SupportsAstcCompression;
|
public readonly bool SupportsAstcCompression;
|
||||||
|
public readonly bool SupportsBc123Compression;
|
||||||
|
public readonly bool SupportsBc45Compression;
|
||||||
|
public readonly bool SupportsBc67Compression;
|
||||||
public readonly bool Supports3DTextureCompression;
|
public readonly bool Supports3DTextureCompression;
|
||||||
public readonly bool SupportsBgraFormat;
|
public readonly bool SupportsBgraFormat;
|
||||||
public readonly bool SupportsR4G4Format;
|
public readonly bool SupportsR4G4Format;
|
||||||
public readonly bool SupportsFragmentShaderInterlock;
|
public readonly bool SupportsFragmentShaderInterlock;
|
||||||
public readonly bool SupportsFragmentShaderOrderingIntel;
|
public readonly bool SupportsFragmentShaderOrderingIntel;
|
||||||
|
public readonly bool SupportsGeometryShaderPassthrough;
|
||||||
public readonly bool SupportsImageLoadFormatted;
|
public readonly bool SupportsImageLoadFormatted;
|
||||||
public readonly bool SupportsMismatchingViewFormat;
|
public readonly bool SupportsMismatchingViewFormat;
|
||||||
public readonly bool SupportsNonConstantTextureOffset;
|
public readonly bool SupportsNonConstantTextureOffset;
|
||||||
|
@ -24,6 +28,11 @@ namespace Ryujinx.Graphics.GAL
|
||||||
public readonly bool SupportsViewportSwizzle;
|
public readonly bool SupportsViewportSwizzle;
|
||||||
public readonly bool SupportsIndirectParameters;
|
public readonly bool SupportsIndirectParameters;
|
||||||
|
|
||||||
|
public readonly uint MaximumUniformBuffersPerStage;
|
||||||
|
public readonly uint MaximumStorageBuffersPerStage;
|
||||||
|
public readonly uint MaximumTexturesPerStage;
|
||||||
|
public readonly uint MaximumImagesPerStage;
|
||||||
|
|
||||||
public readonly int MaximumComputeSharedMemorySize;
|
public readonly int MaximumComputeSharedMemorySize;
|
||||||
public readonly float MaximumSupportedAnisotropy;
|
public readonly float MaximumSupportedAnisotropy;
|
||||||
public readonly int StorageBufferOffsetAlignment;
|
public readonly int StorageBufferOffsetAlignment;
|
||||||
|
@ -34,11 +43,15 @@ namespace Ryujinx.Graphics.GAL
|
||||||
bool hasFrontFacingBug,
|
bool hasFrontFacingBug,
|
||||||
bool hasVectorIndexingBug,
|
bool hasVectorIndexingBug,
|
||||||
bool supportsAstcCompression,
|
bool supportsAstcCompression,
|
||||||
|
bool supportsBc123Compression,
|
||||||
|
bool supportsBc45Compression,
|
||||||
|
bool supportsBc67Compression,
|
||||||
bool supports3DTextureCompression,
|
bool supports3DTextureCompression,
|
||||||
bool supportsBgraFormat,
|
bool supportsBgraFormat,
|
||||||
bool supportsR4G4Format,
|
bool supportsR4G4Format,
|
||||||
bool supportsFragmentShaderInterlock,
|
bool supportsFragmentShaderInterlock,
|
||||||
bool supportsFragmentShaderOrderingIntel,
|
bool supportsFragmentShaderOrderingIntel,
|
||||||
|
bool supportsGeometryShaderPassthrough,
|
||||||
bool supportsImageLoadFormatted,
|
bool supportsImageLoadFormatted,
|
||||||
bool supportsMismatchingViewFormat,
|
bool supportsMismatchingViewFormat,
|
||||||
bool supportsNonConstantTextureOffset,
|
bool supportsNonConstantTextureOffset,
|
||||||
|
@ -46,6 +59,10 @@ namespace Ryujinx.Graphics.GAL
|
||||||
bool supportsTextureShadowLod,
|
bool supportsTextureShadowLod,
|
||||||
bool supportsViewportSwizzle,
|
bool supportsViewportSwizzle,
|
||||||
bool supportsIndirectParameters,
|
bool supportsIndirectParameters,
|
||||||
|
uint maximumUniformBuffersPerStage,
|
||||||
|
uint maximumStorageBuffersPerStage,
|
||||||
|
uint maximumTexturesPerStage,
|
||||||
|
uint maximumImagesPerStage,
|
||||||
int maximumComputeSharedMemorySize,
|
int maximumComputeSharedMemorySize,
|
||||||
float maximumSupportedAnisotropy,
|
float maximumSupportedAnisotropy,
|
||||||
int storageBufferOffsetAlignment)
|
int storageBufferOffsetAlignment)
|
||||||
|
@ -55,11 +72,15 @@ namespace Ryujinx.Graphics.GAL
|
||||||
HasFrontFacingBug = hasFrontFacingBug;
|
HasFrontFacingBug = hasFrontFacingBug;
|
||||||
HasVectorIndexingBug = hasVectorIndexingBug;
|
HasVectorIndexingBug = hasVectorIndexingBug;
|
||||||
SupportsAstcCompression = supportsAstcCompression;
|
SupportsAstcCompression = supportsAstcCompression;
|
||||||
|
SupportsBc123Compression = supportsBc123Compression;
|
||||||
|
SupportsBc45Compression = supportsBc45Compression;
|
||||||
|
SupportsBc67Compression = supportsBc67Compression;
|
||||||
Supports3DTextureCompression = supports3DTextureCompression;
|
Supports3DTextureCompression = supports3DTextureCompression;
|
||||||
SupportsBgraFormat = supportsBgraFormat;
|
SupportsBgraFormat = supportsBgraFormat;
|
||||||
SupportsR4G4Format = supportsR4G4Format;
|
SupportsR4G4Format = supportsR4G4Format;
|
||||||
SupportsFragmentShaderInterlock = supportsFragmentShaderInterlock;
|
SupportsFragmentShaderInterlock = supportsFragmentShaderInterlock;
|
||||||
SupportsFragmentShaderOrderingIntel = supportsFragmentShaderOrderingIntel;
|
SupportsFragmentShaderOrderingIntel = supportsFragmentShaderOrderingIntel;
|
||||||
|
SupportsGeometryShaderPassthrough = supportsGeometryShaderPassthrough;
|
||||||
SupportsImageLoadFormatted = supportsImageLoadFormatted;
|
SupportsImageLoadFormatted = supportsImageLoadFormatted;
|
||||||
SupportsMismatchingViewFormat = supportsMismatchingViewFormat;
|
SupportsMismatchingViewFormat = supportsMismatchingViewFormat;
|
||||||
SupportsNonConstantTextureOffset = supportsNonConstantTextureOffset;
|
SupportsNonConstantTextureOffset = supportsNonConstantTextureOffset;
|
||||||
|
@ -67,6 +88,10 @@ namespace Ryujinx.Graphics.GAL
|
||||||
SupportsTextureShadowLod = supportsTextureShadowLod;
|
SupportsTextureShadowLod = supportsTextureShadowLod;
|
||||||
SupportsViewportSwizzle = supportsViewportSwizzle;
|
SupportsViewportSwizzle = supportsViewportSwizzle;
|
||||||
SupportsIndirectParameters = supportsIndirectParameters;
|
SupportsIndirectParameters = supportsIndirectParameters;
|
||||||
|
MaximumUniformBuffersPerStage = maximumUniformBuffersPerStage;
|
||||||
|
MaximumStorageBuffersPerStage = maximumStorageBuffersPerStage;
|
||||||
|
MaximumTexturesPerStage = maximumTexturesPerStage;
|
||||||
|
MaximumImagesPerStage = maximumImagesPerStage;
|
||||||
MaximumComputeSharedMemorySize = maximumComputeSharedMemorySize;
|
MaximumComputeSharedMemorySize = maximumComputeSharedMemorySize;
|
||||||
MaximumSupportedAnisotropy = maximumSupportedAnisotropy;
|
MaximumSupportedAnisotropy = maximumSupportedAnisotropy;
|
||||||
StorageBufferOffsetAlignment = storageBufferOffsetAlignment;
|
StorageBufferOffsetAlignment = storageBufferOffsetAlignment;
|
||||||
|
|
18
Ryujinx.Graphics.GAL/DeviceInfo.cs
Normal file
18
Ryujinx.Graphics.GAL/DeviceInfo.cs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
namespace Ryujinx.Graphics.GAL
|
||||||
|
{
|
||||||
|
public struct DeviceInfo
|
||||||
|
{
|
||||||
|
public readonly string Id;
|
||||||
|
public readonly string Vendor;
|
||||||
|
public readonly string Name;
|
||||||
|
public readonly bool IsDiscrete;
|
||||||
|
|
||||||
|
public DeviceInfo(string id, string vendor, string name, bool isDiscrete)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
Vendor = vendor;
|
||||||
|
Name = name;
|
||||||
|
IsDiscrete = isDiscrete;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -165,6 +165,120 @@ namespace Ryujinx.Graphics.GAL
|
||||||
|
|
||||||
public static class FormatExtensions
|
public static class FormatExtensions
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the texture format is valid to use as image format.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="format">Texture format</param>
|
||||||
|
/// <returns>True if the texture can be used as image, false otherwise</returns>
|
||||||
|
public static bool IsImageCompatible(this Format format)
|
||||||
|
{
|
||||||
|
switch (format)
|
||||||
|
{
|
||||||
|
case Format.R8Unorm:
|
||||||
|
case Format.R8Snorm:
|
||||||
|
case Format.R8Uint:
|
||||||
|
case Format.R8Sint:
|
||||||
|
case Format.R16Float:
|
||||||
|
case Format.R16Unorm:
|
||||||
|
case Format.R16Snorm:
|
||||||
|
case Format.R16Uint:
|
||||||
|
case Format.R16Sint:
|
||||||
|
case Format.R32Float:
|
||||||
|
case Format.R32Uint:
|
||||||
|
case Format.R32Sint:
|
||||||
|
case Format.R8G8Unorm:
|
||||||
|
case Format.R8G8Snorm:
|
||||||
|
case Format.R8G8Uint:
|
||||||
|
case Format.R8G8Sint:
|
||||||
|
case Format.R16G16Float:
|
||||||
|
case Format.R16G16Unorm:
|
||||||
|
case Format.R16G16Snorm:
|
||||||
|
case Format.R16G16Uint:
|
||||||
|
case Format.R16G16Sint:
|
||||||
|
case Format.R32G32Float:
|
||||||
|
case Format.R32G32Uint:
|
||||||
|
case Format.R32G32Sint:
|
||||||
|
case Format.R8G8B8A8Unorm:
|
||||||
|
case Format.R8G8B8A8Snorm:
|
||||||
|
case Format.R8G8B8A8Uint:
|
||||||
|
case Format.R8G8B8A8Sint:
|
||||||
|
case Format.R16G16B16A16Float:
|
||||||
|
case Format.R16G16B16A16Unorm:
|
||||||
|
case Format.R16G16B16A16Snorm:
|
||||||
|
case Format.R16G16B16A16Uint:
|
||||||
|
case Format.R16G16B16A16Sint:
|
||||||
|
case Format.R32G32B32A32Float:
|
||||||
|
case Format.R32G32B32A32Uint:
|
||||||
|
case Format.R32G32B32A32Sint:
|
||||||
|
case Format.R10G10B10A2Unorm:
|
||||||
|
case Format.R10G10B10A2Uint:
|
||||||
|
case Format.R11G11B10Float:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the texture format is valid to use as render target color format.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="format">Texture format</param>
|
||||||
|
/// <returns>True if the texture can be used as render target, false otherwise</returns>
|
||||||
|
public static bool IsRtColorCompatible(this Format format)
|
||||||
|
{
|
||||||
|
switch (format)
|
||||||
|
{
|
||||||
|
case Format.R32G32B32A32Float:
|
||||||
|
case Format.R32G32B32A32Sint:
|
||||||
|
case Format.R32G32B32A32Uint:
|
||||||
|
case Format.R16G16B16A16Unorm:
|
||||||
|
case Format.R16G16B16A16Snorm:
|
||||||
|
case Format.R16G16B16A16Sint:
|
||||||
|
case Format.R16G16B16A16Uint:
|
||||||
|
case Format.R16G16B16A16Float:
|
||||||
|
case Format.R32G32Float:
|
||||||
|
case Format.R32G32Sint:
|
||||||
|
case Format.R32G32Uint:
|
||||||
|
case Format.B8G8R8A8Unorm:
|
||||||
|
case Format.B8G8R8A8Srgb:
|
||||||
|
case Format.R10G10B10A2Unorm:
|
||||||
|
case Format.R10G10B10A2Uint:
|
||||||
|
case Format.R8G8B8A8Unorm:
|
||||||
|
case Format.R8G8B8A8Srgb:
|
||||||
|
case Format.R8G8B8A8Snorm:
|
||||||
|
case Format.R8G8B8A8Sint:
|
||||||
|
case Format.R8G8B8A8Uint:
|
||||||
|
case Format.R16G16Unorm:
|
||||||
|
case Format.R16G16Snorm:
|
||||||
|
case Format.R16G16Sint:
|
||||||
|
case Format.R16G16Uint:
|
||||||
|
case Format.R16G16Float:
|
||||||
|
case Format.R11G11B10Float:
|
||||||
|
case Format.R32Sint:
|
||||||
|
case Format.R32Uint:
|
||||||
|
case Format.R32Float:
|
||||||
|
case Format.B5G6R5Unorm:
|
||||||
|
case Format.B5G5R5A1Unorm:
|
||||||
|
case Format.R8G8Unorm:
|
||||||
|
case Format.R8G8Snorm:
|
||||||
|
case Format.R8G8Sint:
|
||||||
|
case Format.R8G8Uint:
|
||||||
|
case Format.R16Unorm:
|
||||||
|
case Format.R16Snorm:
|
||||||
|
case Format.R16Sint:
|
||||||
|
case Format.R16Uint:
|
||||||
|
case Format.R16Float:
|
||||||
|
case Format.R8Unorm:
|
||||||
|
case Format.R8Snorm:
|
||||||
|
case Format.R8Sint:
|
||||||
|
case Format.R8Uint:
|
||||||
|
case Format.B5G5R5X1Unorm:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks if the texture format is an ASTC format.
|
/// Checks if the texture format is an ASTC format.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
18
Ryujinx.Graphics.GAL/HardwareInfo.cs
Normal file
18
Ryujinx.Graphics.GAL/HardwareInfo.cs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.GAL
|
||||||
|
{
|
||||||
|
public struct HardwareInfo
|
||||||
|
{
|
||||||
|
public string GpuVendor { get; }
|
||||||
|
public string GpuModel { get; }
|
||||||
|
|
||||||
|
public HardwareInfo(string gpuVendor, string gpuModel)
|
||||||
|
{
|
||||||
|
GpuVendor = gpuVendor;
|
||||||
|
GpuModel = gpuModel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
using Ryujinx.Graphics.Shader;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.GAL
|
namespace Ryujinx.Graphics.GAL
|
||||||
|
@ -10,9 +11,10 @@ namespace Ryujinx.Graphics.GAL
|
||||||
|
|
||||||
void ClearBuffer(BufferHandle destination, int offset, int size, uint value);
|
void ClearBuffer(BufferHandle destination, int offset, int size, uint value);
|
||||||
|
|
||||||
void ClearRenderTargetColor(int index, uint componentMask, ColorF color);
|
void ClearRenderTargetColor(int index, int layer, uint componentMask, ColorF color);
|
||||||
|
|
||||||
void ClearRenderTargetDepthStencil(
|
void ClearRenderTargetDepthStencil(
|
||||||
|
int layer,
|
||||||
float depthValue,
|
float depthValue,
|
||||||
bool depthMask,
|
bool depthMask,
|
||||||
int stencilValue,
|
int stencilValue,
|
||||||
|
@ -76,15 +78,13 @@ namespace Ryujinx.Graphics.GAL
|
||||||
void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMask);
|
void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMask);
|
||||||
void SetRenderTargets(ITexture[] colors, ITexture depthStencil);
|
void SetRenderTargets(ITexture[] colors, ITexture depthStencil);
|
||||||
|
|
||||||
void SetSampler(int binding, ISampler sampler);
|
void SetScissors(ReadOnlySpan<Rectangle<int>> regions);
|
||||||
|
|
||||||
void SetScissor(int index, bool enable, int x, int y, int width, int height);
|
|
||||||
|
|
||||||
void SetStencilTest(StencilTestDescriptor stencilTest);
|
void SetStencilTest(StencilTestDescriptor stencilTest);
|
||||||
|
|
||||||
void SetStorageBuffers(int first, ReadOnlySpan<BufferRange> buffers);
|
void SetStorageBuffers(int first, ReadOnlySpan<BufferRange> buffers);
|
||||||
|
|
||||||
void SetTexture(int binding, ITexture texture);
|
void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler);
|
||||||
|
|
||||||
void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers);
|
void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers);
|
||||||
void SetUniformBuffers(int first, ReadOnlySpan<BufferRange> buffers);
|
void SetUniformBuffers(int first, ReadOnlySpan<BufferRange> buffers);
|
||||||
|
|
|
@ -30,6 +30,7 @@ namespace Ryujinx.Graphics.GAL
|
||||||
ReadOnlySpan<byte> GetBufferData(BufferHandle buffer, int offset, int size);
|
ReadOnlySpan<byte> GetBufferData(BufferHandle buffer, int offset, int size);
|
||||||
|
|
||||||
Capabilities GetCapabilities();
|
Capabilities GetCapabilities();
|
||||||
|
HardwareInfo GetHardwareInfo();
|
||||||
|
|
||||||
IProgram LoadProgramBinary(byte[] programBinary, bool hasFragmentShader, ShaderInfo info);
|
IProgram LoadProgramBinary(byte[] programBinary, bool hasFragmentShader, ShaderInfo info);
|
||||||
|
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.GAL
|
|
||||||
{
|
|
||||||
public interface IShader : IDisposable { }
|
|
||||||
}
|
|
|
@ -199,14 +199,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||||
SetRenderTargetScaleCommand.Run(ref GetCommand<SetRenderTargetScaleCommand>(memory), threaded, renderer);
|
SetRenderTargetScaleCommand.Run(ref GetCommand<SetRenderTargetScaleCommand>(memory), threaded, renderer);
|
||||||
_lookup[(int)CommandType.SetRenderTargets] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
|
_lookup[(int)CommandType.SetRenderTargets] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
|
||||||
SetRenderTargetsCommand.Run(ref GetCommand<SetRenderTargetsCommand>(memory), threaded, renderer);
|
SetRenderTargetsCommand.Run(ref GetCommand<SetRenderTargetsCommand>(memory), threaded, renderer);
|
||||||
_lookup[(int)CommandType.SetSampler] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
|
|
||||||
SetSamplerCommand.Run(ref GetCommand<SetSamplerCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.SetScissor] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
|
_lookup[(int)CommandType.SetScissor] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
|
||||||
SetScissorCommand.Run(ref GetCommand<SetScissorCommand>(memory), threaded, renderer);
|
SetScissorsCommand.Run(ref GetCommand<SetScissorsCommand>(memory), threaded, renderer);
|
||||||
_lookup[(int)CommandType.SetStencilTest] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
|
_lookup[(int)CommandType.SetStencilTest] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
|
||||||
SetStencilTestCommand.Run(ref GetCommand<SetStencilTestCommand>(memory), threaded, renderer);
|
SetStencilTestCommand.Run(ref GetCommand<SetStencilTestCommand>(memory), threaded, renderer);
|
||||||
_lookup[(int)CommandType.SetTexture] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
|
_lookup[(int)CommandType.SetTextureAndSampler] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
|
||||||
SetTextureCommand.Run(ref GetCommand<SetTextureCommand>(memory), threaded, renderer);
|
SetTextureAndSamplerCommand.Run(ref GetCommand<SetTextureAndSamplerCommand>(memory), threaded, renderer);
|
||||||
_lookup[(int)CommandType.SetUserClipDistance] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
|
_lookup[(int)CommandType.SetUserClipDistance] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
|
||||||
SetUserClipDistanceCommand.Run(ref GetCommand<SetUserClipDistanceCommand>(memory), threaded, renderer);
|
SetUserClipDistanceCommand.Run(ref GetCommand<SetUserClipDistanceCommand>(memory), threaded, renderer);
|
||||||
_lookup[(int)CommandType.SetVertexAttribs] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
|
_lookup[(int)CommandType.SetVertexAttribs] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
|
||||||
|
|
|
@ -81,10 +81,9 @@
|
||||||
SetRenderTargetColorMasks,
|
SetRenderTargetColorMasks,
|
||||||
SetRenderTargetScale,
|
SetRenderTargetScale,
|
||||||
SetRenderTargets,
|
SetRenderTargets,
|
||||||
SetSampler,
|
|
||||||
SetScissor,
|
SetScissor,
|
||||||
SetStencilTest,
|
SetStencilTest,
|
||||||
SetTexture,
|
SetTextureAndSampler,
|
||||||
SetUserClipDistance,
|
SetUserClipDistance,
|
||||||
SetVertexAttribs,
|
SetVertexAttribs,
|
||||||
SetVertexBuffers,
|
SetVertexBuffers,
|
||||||
|
|
|
@ -4,19 +4,21 @@
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.ClearRenderTargetColor;
|
public CommandType CommandType => CommandType.ClearRenderTargetColor;
|
||||||
private int _index;
|
private int _index;
|
||||||
|
private int _layer;
|
||||||
private uint _componentMask;
|
private uint _componentMask;
|
||||||
private ColorF _color;
|
private ColorF _color;
|
||||||
|
|
||||||
public void Set(int index, uint componentMask, ColorF color)
|
public void Set(int index, int layer, uint componentMask, ColorF color)
|
||||||
{
|
{
|
||||||
_index = index;
|
_index = index;
|
||||||
|
_layer = layer;
|
||||||
_componentMask = componentMask;
|
_componentMask = componentMask;
|
||||||
_color = color;
|
_color = color;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Run(ref ClearRenderTargetColorCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
public static void Run(ref ClearRenderTargetColorCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||||
{
|
{
|
||||||
renderer.Pipeline.ClearRenderTargetColor(command._index, command._componentMask, command._color);
|
renderer.Pipeline.ClearRenderTargetColor(command._index, command._layer, command._componentMask, command._color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,15 @@
|
||||||
struct ClearRenderTargetDepthStencilCommand : IGALCommand
|
struct ClearRenderTargetDepthStencilCommand : IGALCommand
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.ClearRenderTargetDepthStencil;
|
public CommandType CommandType => CommandType.ClearRenderTargetDepthStencil;
|
||||||
|
private int _layer;
|
||||||
private float _depthValue;
|
private float _depthValue;
|
||||||
private bool _depthMask;
|
private bool _depthMask;
|
||||||
private int _stencilValue;
|
private int _stencilValue;
|
||||||
private int _stencilMask;
|
private int _stencilMask;
|
||||||
|
|
||||||
public void Set(float depthValue, bool depthMask, int stencilValue, int stencilMask)
|
public void Set(int layer, float depthValue, bool depthMask, int stencilValue, int stencilMask)
|
||||||
{
|
{
|
||||||
|
_layer = layer;
|
||||||
_depthValue = depthValue;
|
_depthValue = depthValue;
|
||||||
_depthMask = depthMask;
|
_depthMask = depthMask;
|
||||||
_stencilValue = stencilValue;
|
_stencilValue = stencilValue;
|
||||||
|
@ -18,7 +20,7 @@
|
||||||
|
|
||||||
public static void Run(ref ClearRenderTargetDepthStencilCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
public static void Run(ref ClearRenderTargetDepthStencilCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||||
{
|
{
|
||||||
renderer.Pipeline.ClearRenderTargetDepthStencil(command._depthValue, command._depthMask, command._stencilValue, command._stencilMask);
|
renderer.Pipeline.ClearRenderTargetDepthStencil(command._layer, command._depthValue, command._depthMask, command._stencilValue, command._stencilMask);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
|
||||||
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
|
||||||
{
|
|
||||||
struct SetSamplerCommand : IGALCommand
|
|
||||||
{
|
|
||||||
public CommandType CommandType => CommandType.SetSampler;
|
|
||||||
private int _index;
|
|
||||||
private TableRef<ISampler> _sampler;
|
|
||||||
|
|
||||||
public void Set(int index, TableRef<ISampler> sampler)
|
|
||||||
{
|
|
||||||
_index = index;
|
|
||||||
_sampler = sampler;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Run(ref SetSamplerCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
|
||||||
{
|
|
||||||
renderer.Pipeline.SetSampler(command._index, command._sampler.GetAs<ThreadedSampler>(threaded)?.Base);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
|
||||||
{
|
|
||||||
struct SetScissorCommand : IGALCommand
|
|
||||||
{
|
|
||||||
public CommandType CommandType => CommandType.SetScissor;
|
|
||||||
private int _index;
|
|
||||||
private bool _enable;
|
|
||||||
private int _x;
|
|
||||||
private int _y;
|
|
||||||
private int _width;
|
|
||||||
private int _height;
|
|
||||||
|
|
||||||
public void Set(int index, bool enable, int x, int y, int width, int height)
|
|
||||||
{
|
|
||||||
_index = index;
|
|
||||||
_enable = enable;
|
|
||||||
_x = x;
|
|
||||||
_y = y;
|
|
||||||
_width = width;
|
|
||||||
_height = height;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Run(ref SetScissorCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
|
||||||
{
|
|
||||||
renderer.Pipeline.SetScissor(command._index, command._enable, command._x, command._y, command._width, command._height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||||
|
{
|
||||||
|
struct SetScissorsCommand : IGALCommand
|
||||||
|
{
|
||||||
|
public CommandType CommandType => CommandType.SetScissor;
|
||||||
|
private SpanRef<Rectangle<int>> _scissors;
|
||||||
|
|
||||||
|
public void Set(SpanRef<Rectangle<int>> scissors)
|
||||||
|
{
|
||||||
|
_scissors = scissors;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Run(ref SetScissorsCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||||
|
{
|
||||||
|
renderer.Pipeline.SetScissors(command._scissors.Get(threaded));
|
||||||
|
|
||||||
|
command._scissors.Dispose(threaded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||||
|
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
||||||
|
using Ryujinx.Graphics.Shader;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||||
|
{
|
||||||
|
struct SetTextureAndSamplerCommand : IGALCommand
|
||||||
|
{
|
||||||
|
public CommandType CommandType => CommandType.SetTextureAndSampler;
|
||||||
|
private ShaderStage _stage;
|
||||||
|
private int _binding;
|
||||||
|
private TableRef<ITexture> _texture;
|
||||||
|
private TableRef<ISampler> _sampler;
|
||||||
|
|
||||||
|
public void Set(ShaderStage stage, int binding, TableRef<ITexture> texture, TableRef<ISampler> sampler)
|
||||||
|
{
|
||||||
|
_stage = stage;
|
||||||
|
_binding = binding;
|
||||||
|
_texture = texture;
|
||||||
|
_sampler = sampler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Run(ref SetTextureAndSamplerCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||||
|
{
|
||||||
|
renderer.Pipeline.SetTextureAndSampler(command._stage, command._binding, command._texture.GetAs<ThreadedTexture>(threaded)?.Base, command._sampler.GetAs<ThreadedSampler>(threaded)?.Base);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,23 +0,0 @@
|
||||||
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
|
||||||
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
|
||||||
{
|
|
||||||
struct SetTextureCommand : IGALCommand
|
|
||||||
{
|
|
||||||
public CommandType CommandType => CommandType.SetTexture;
|
|
||||||
private int _binding;
|
|
||||||
private TableRef<ITexture> _texture;
|
|
||||||
|
|
||||||
public void Set(int binding, TableRef<ITexture> texture)
|
|
||||||
{
|
|
||||||
_binding = binding;
|
|
||||||
_texture = texture;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Run(ref SetTextureCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
|
||||||
{
|
|
||||||
renderer.Pipeline.SetTexture(command._binding, command._texture.GetAs<ThreadedTexture>(threaded)?.Base);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,7 @@
|
||||||
using Ryujinx.Graphics.GAL.Multithreading.Commands;
|
using Ryujinx.Graphics.GAL.Multithreading.Commands;
|
||||||
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||||
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
||||||
|
using Ryujinx.Graphics.Shader;
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
|
@ -40,15 +41,15 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||||
_renderer.QueueCommand();
|
_renderer.QueueCommand();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClearRenderTargetColor(int index, uint componentMask, ColorF color)
|
public void ClearRenderTargetColor(int index, int layer, uint componentMask, ColorF color)
|
||||||
{
|
{
|
||||||
_renderer.New<ClearRenderTargetColorCommand>().Set(index, componentMask, color);
|
_renderer.New<ClearRenderTargetColorCommand>().Set(index, layer, componentMask, color);
|
||||||
_renderer.QueueCommand();
|
_renderer.QueueCommand();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClearRenderTargetDepthStencil(float depthValue, bool depthMask, int stencilValue, int stencilMask)
|
public void ClearRenderTargetDepthStencil(int layer, float depthValue, bool depthMask, int stencilValue, int stencilMask)
|
||||||
{
|
{
|
||||||
_renderer.New<ClearRenderTargetDepthStencilCommand>().Set(depthValue, depthMask, stencilValue, stencilMask);
|
_renderer.New<ClearRenderTargetDepthStencilCommand>().Set(layer, depthValue, depthMask, stencilValue, stencilMask);
|
||||||
_renderer.QueueCommand();
|
_renderer.QueueCommand();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,15 +245,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||||
_renderer.QueueCommand();
|
_renderer.QueueCommand();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetSampler(int binding, ISampler sampler)
|
public void SetScissors(ReadOnlySpan<Rectangle<int>> scissors)
|
||||||
{
|
{
|
||||||
_renderer.New<SetSamplerCommand>().Set(binding, Ref(sampler));
|
_renderer.New<SetScissorsCommand>().Set(_renderer.CopySpan(scissors));
|
||||||
_renderer.QueueCommand();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetScissor(int index, bool enable, int x, int y, int width, int height)
|
|
||||||
{
|
|
||||||
_renderer.New<SetScissorCommand>().Set(index, enable, x, y, width, height);
|
|
||||||
_renderer.QueueCommand();
|
_renderer.QueueCommand();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,9 +263,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||||
_renderer.QueueCommand();
|
_renderer.QueueCommand();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetTexture(int binding, ITexture texture)
|
public void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler)
|
||||||
{
|
{
|
||||||
_renderer.New<SetTextureCommand>().Set(binding, Ref(texture));
|
_renderer.New<SetTextureAndSamplerCommand>().Set(stage, binding, Ref(texture), Ref(sampler));
|
||||||
_renderer.QueueCommand();
|
_renderer.QueueCommand();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -76,7 +76,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||||
renderer.ScreenCaptured += (object sender, ScreenCaptureImageInfo info) => ScreenCaptured?.Invoke(this, info);
|
renderer.ScreenCaptured += (object sender, ScreenCaptureImageInfo info) => ScreenCaptured?.Invoke(this, info);
|
||||||
|
|
||||||
Pipeline = new ThreadedPipeline(this, renderer.Pipeline);
|
Pipeline = new ThreadedPipeline(this, renderer.Pipeline);
|
||||||
Window = new ThreadedWindow(this, renderer.Window);
|
Window = new ThreadedWindow(this, renderer);
|
||||||
Buffers = new BufferMap();
|
Buffers = new BufferMap();
|
||||||
Sync = new SyncMap();
|
Sync = new SyncMap();
|
||||||
Programs = new ProgramQueue(renderer);
|
Programs = new ProgramQueue(renderer);
|
||||||
|
@ -262,7 +262,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||||
public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info)
|
public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info)
|
||||||
{
|
{
|
||||||
var program = new ThreadedProgram(this);
|
var program = new ThreadedProgram(this);
|
||||||
|
|
||||||
SourceProgramRequest request = new SourceProgramRequest(program, shaders, info);
|
SourceProgramRequest request = new SourceProgramRequest(program, shaders, info);
|
||||||
|
|
||||||
Programs.Add(request);
|
Programs.Add(request);
|
||||||
|
|
||||||
New<CreateProgramCommand>().Set(Ref((IProgramRequest)request));
|
New<CreateProgramCommand>().Set(Ref((IProgramRequest)request));
|
||||||
|
@ -337,6 +339,11 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||||
return box.Result;
|
return box.Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HardwareInfo GetHardwareInfo()
|
||||||
|
{
|
||||||
|
return _baseRenderer.GetHardwareInfo();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initialize the base renderer. Must be called on the render thread.
|
/// Initialize the base renderer. Must be called on the render thread.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -8,12 +8,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||||
public class ThreadedWindow : IWindow
|
public class ThreadedWindow : IWindow
|
||||||
{
|
{
|
||||||
private ThreadedRenderer _renderer;
|
private ThreadedRenderer _renderer;
|
||||||
private IWindow _impl;
|
private IRenderer _impl;
|
||||||
|
|
||||||
public ThreadedWindow(ThreadedRenderer renderer, IWindow window)
|
public ThreadedWindow(ThreadedRenderer renderer, IRenderer impl)
|
||||||
{
|
{
|
||||||
_renderer = renderer;
|
_renderer = renderer;
|
||||||
_impl = window;
|
_impl = impl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Present(ITexture texture, ImageCrop crop, Action<object> swapBuffersCallback)
|
public void Present(ITexture texture, ImageCrop crop, Action<object> swapBuffersCallback)
|
||||||
|
@ -28,7 +28,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||||
|
|
||||||
public void SetSize(int width, int height)
|
public void SetSize(int width, int height)
|
||||||
{
|
{
|
||||||
_impl.SetSize(width, height);
|
_impl.Window.SetSize(width, height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
78
Ryujinx.Graphics.GAL/ProgramPipelineState.cs
Normal file
78
Ryujinx.Graphics.GAL/ProgramPipelineState.cs
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.GAL
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Descriptor for a pipeline buffer binding.
|
||||||
|
/// </summary>
|
||||||
|
public struct BufferPipelineDescriptor
|
||||||
|
{
|
||||||
|
public bool Enable { get; }
|
||||||
|
public int Stride { get; }
|
||||||
|
public int Divisor { get; }
|
||||||
|
|
||||||
|
public BufferPipelineDescriptor(bool enable, int stride, int divisor)
|
||||||
|
{
|
||||||
|
Enable = enable;
|
||||||
|
Stride = stride;
|
||||||
|
Divisor = divisor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// State required for a program to compile shaders.
|
||||||
|
/// </summary>
|
||||||
|
public struct ProgramPipelineState
|
||||||
|
{
|
||||||
|
// Some state is considered always dynamic and should not be included:
|
||||||
|
// - Viewports/Scissors
|
||||||
|
// - Bias values (not enable)
|
||||||
|
|
||||||
|
public int SamplesCount;
|
||||||
|
public Array8<bool> AttachmentEnable;
|
||||||
|
public Array8<Format> AttachmentFormats;
|
||||||
|
public bool DepthStencilEnable;
|
||||||
|
public Format DepthStencilFormat;
|
||||||
|
|
||||||
|
public bool LogicOpEnable;
|
||||||
|
public LogicalOp LogicOp;
|
||||||
|
public Array8<BlendDescriptor> BlendDescriptors;
|
||||||
|
public Array8<uint> ColorWriteMask;
|
||||||
|
|
||||||
|
public int VertexAttribCount;
|
||||||
|
public Array32<VertexAttribDescriptor> VertexAttribs;
|
||||||
|
|
||||||
|
public int VertexBufferCount;
|
||||||
|
public Array32<BufferPipelineDescriptor> VertexBuffers;
|
||||||
|
|
||||||
|
// TODO: Min/max depth bounds.
|
||||||
|
public DepthTestDescriptor DepthTest;
|
||||||
|
public StencilTestDescriptor StencilTest;
|
||||||
|
public FrontFace FrontFace;
|
||||||
|
public Face CullMode;
|
||||||
|
public bool CullEnable;
|
||||||
|
|
||||||
|
public PolygonModeMask BiasEnable;
|
||||||
|
|
||||||
|
public float LineWidth;
|
||||||
|
// TODO: Polygon mode.
|
||||||
|
public bool DepthClampEnable;
|
||||||
|
public bool RasterizerDiscard;
|
||||||
|
public PrimitiveTopology Topology;
|
||||||
|
public bool PrimitiveRestartEnable;
|
||||||
|
public uint PatchControlPoints;
|
||||||
|
|
||||||
|
public void SetVertexAttribs(ReadOnlySpan<VertexAttribDescriptor> vertexAttribs)
|
||||||
|
{
|
||||||
|
VertexAttribCount = vertexAttribs.Length;
|
||||||
|
vertexAttribs.CopyTo(VertexAttribs.ToSpan());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetLogicOpState(bool enable, LogicalOp op)
|
||||||
|
{
|
||||||
|
LogicOp = op;
|
||||||
|
LogicOpEnable = enable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
Ryujinx.Graphics.GAL/Rectangle.cs
Normal file
18
Ryujinx.Graphics.GAL/Rectangle.cs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
namespace Ryujinx.Graphics.GAL
|
||||||
|
{
|
||||||
|
public struct Rectangle<T> where T : unmanaged
|
||||||
|
{
|
||||||
|
public T X { get; }
|
||||||
|
public T Y { get; }
|
||||||
|
public T Width { get; }
|
||||||
|
public T Height { get; }
|
||||||
|
|
||||||
|
public Rectangle(T x, T y, T width, T height)
|
||||||
|
{
|
||||||
|
X = x;
|
||||||
|
Y = y;
|
||||||
|
Width = width;
|
||||||
|
Height = height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
Ryujinx.Graphics.GAL/ShaderBindings.cs
Normal file
24
Ryujinx.Graphics.GAL/ShaderBindings.cs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.GAL
|
||||||
|
{
|
||||||
|
public struct ShaderBindings
|
||||||
|
{
|
||||||
|
public IReadOnlyCollection<int> UniformBufferBindings { get; }
|
||||||
|
public IReadOnlyCollection<int> StorageBufferBindings { get; }
|
||||||
|
public IReadOnlyCollection<int> TextureBindings { get; }
|
||||||
|
public IReadOnlyCollection<int> ImageBindings { get; }
|
||||||
|
|
||||||
|
public ShaderBindings(
|
||||||
|
IReadOnlyCollection<int> uniformBufferBindings,
|
||||||
|
IReadOnlyCollection<int> storageBufferBindings,
|
||||||
|
IReadOnlyCollection<int> textureBindings,
|
||||||
|
IReadOnlyCollection<int> imageBindings)
|
||||||
|
{
|
||||||
|
UniformBufferBindings = uniformBufferBindings;
|
||||||
|
StorageBufferBindings = storageBufferBindings;
|
||||||
|
TextureBindings = textureBindings;
|
||||||
|
ImageBindings = imageBindings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,10 +3,21 @@ namespace Ryujinx.Graphics.GAL
|
||||||
public struct ShaderInfo
|
public struct ShaderInfo
|
||||||
{
|
{
|
||||||
public int FragmentOutputMap { get; }
|
public int FragmentOutputMap { get; }
|
||||||
|
public ProgramPipelineState? State { get; }
|
||||||
|
public bool FromCache { get; set; }
|
||||||
|
|
||||||
public ShaderInfo(int fragmentOutputMap)
|
public ShaderInfo(int fragmentOutputMap, ProgramPipelineState state, bool fromCache = false)
|
||||||
{
|
{
|
||||||
FragmentOutputMap = fragmentOutputMap;
|
FragmentOutputMap = fragmentOutputMap;
|
||||||
|
State = state;
|
||||||
|
FromCache = fromCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ShaderInfo(int fragmentOutputMap, bool fromCache = false)
|
||||||
|
{
|
||||||
|
FragmentOutputMap = fragmentOutputMap;
|
||||||
|
State = null;
|
||||||
|
FromCache = fromCache;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -7,22 +7,24 @@ namespace Ryujinx.Graphics.GAL
|
||||||
{
|
{
|
||||||
public string Code { get; }
|
public string Code { get; }
|
||||||
public byte[] BinaryCode { get; }
|
public byte[] BinaryCode { get; }
|
||||||
|
public ShaderBindings Bindings { get; }
|
||||||
public ShaderStage Stage { get; }
|
public ShaderStage Stage { get; }
|
||||||
public TargetLanguage Language { get; }
|
public TargetLanguage Language { get; }
|
||||||
|
|
||||||
public ShaderSource(string code, byte[] binaryCode, ShaderStage stage, TargetLanguage language)
|
public ShaderSource(string code, byte[] binaryCode, ShaderBindings bindings, ShaderStage stage, TargetLanguage language)
|
||||||
{
|
{
|
||||||
Code = code;
|
Code = code;
|
||||||
BinaryCode = binaryCode;
|
BinaryCode = binaryCode;
|
||||||
|
Bindings = bindings;
|
||||||
Stage = stage;
|
Stage = stage;
|
||||||
Language = language;
|
Language = language;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ShaderSource(string code, ShaderStage stage, TargetLanguage language) : this(code, null, stage, language)
|
public ShaderSource(string code, ShaderBindings bindings, ShaderStage stage, TargetLanguage language) : this(code, null, bindings, stage, language)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public ShaderSource(byte[] binaryCode, ShaderStage stage, TargetLanguage language) : this(null, binaryCode, stage, language)
|
public ShaderSource(byte[] binaryCode, ShaderBindings bindings, ShaderStage stage, TargetLanguage language) : this(null, binaryCode, bindings, stage, language)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ namespace Ryujinx.Graphics.GAL
|
||||||
{
|
{
|
||||||
public struct Viewport
|
public struct Viewport
|
||||||
{
|
{
|
||||||
public RectangleF Region { get; }
|
public Rectangle<float> Region { get; }
|
||||||
|
|
||||||
public ViewportSwizzle SwizzleX { get; }
|
public ViewportSwizzle SwizzleX { get; }
|
||||||
public ViewportSwizzle SwizzleY { get; }
|
public ViewportSwizzle SwizzleY { get; }
|
||||||
|
@ -13,13 +13,13 @@ namespace Ryujinx.Graphics.GAL
|
||||||
public float DepthFar { get; }
|
public float DepthFar { get; }
|
||||||
|
|
||||||
public Viewport(
|
public Viewport(
|
||||||
RectangleF region,
|
Rectangle<float> region,
|
||||||
ViewportSwizzle swizzleX,
|
ViewportSwizzle swizzleX,
|
||||||
ViewportSwizzle swizzleY,
|
ViewportSwizzle swizzleY,
|
||||||
ViewportSwizzle swizzleZ,
|
ViewportSwizzle swizzleZ,
|
||||||
ViewportSwizzle swizzleW,
|
ViewportSwizzle swizzleW,
|
||||||
float depthNear,
|
float depthNear,
|
||||||
float depthFar)
|
float depthFar)
|
||||||
{
|
{
|
||||||
Region = region;
|
Region = region;
|
||||||
SwizzleX = swizzleX;
|
SwizzleX = swizzleX;
|
||||||
|
|
|
@ -40,6 +40,22 @@ namespace Ryujinx.Graphics.Gpu
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const int TotalTransformFeedbackBuffers = 4;
|
public const int TotalTransformFeedbackBuffers = 4;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum number of textures on a single shader stage.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The maximum number of textures is API limited, the hardware supports a unlimited amount.
|
||||||
|
/// </remarks>
|
||||||
|
public const int TotalTextures = 32;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum number of images on a single shader stage.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The maximum number of images is API limited, the hardware supports a unlimited amount.
|
||||||
|
/// </remarks>
|
||||||
|
public const int TotalImages = 8;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Maximum number of render target color buffers.
|
/// Maximum number of render target color buffers.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -53,7 +69,7 @@ namespace Ryujinx.Graphics.Gpu
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Maximum number of vertex attributes.
|
/// Maximum number of vertex attributes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const int TotalVertexAttribs = 16;
|
public const int TotalVertexAttribs = 16; // FIXME: Should be 32, but OpenGL only supports 16.
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Maximum number of vertex buffers.
|
/// Maximum number of vertex buffers.
|
||||||
|
|
|
@ -188,6 +188,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
|
||||||
_channel.BufferManager.SetComputeStorageBufferBindings(info.SBuffers);
|
_channel.BufferManager.SetComputeStorageBufferBindings(info.SBuffers);
|
||||||
_channel.BufferManager.SetComputeUniformBufferBindings(info.CBuffers);
|
_channel.BufferManager.SetComputeUniformBufferBindings(info.CBuffers);
|
||||||
|
|
||||||
|
int maxTextureBinding = -1;
|
||||||
|
int maxImageBinding = -1;
|
||||||
|
|
||||||
TextureBindingInfo[] textureBindings = _channel.TextureManager.RentComputeTextureBindings(info.Textures.Count);
|
TextureBindingInfo[] textureBindings = _channel.TextureManager.RentComputeTextureBindings(info.Textures.Count);
|
||||||
|
|
||||||
for (int index = 0; index < info.Textures.Count; index++)
|
for (int index = 0; index < info.Textures.Count; index++)
|
||||||
|
@ -202,6 +205,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
|
||||||
descriptor.CbufSlot,
|
descriptor.CbufSlot,
|
||||||
descriptor.HandleIndex,
|
descriptor.HandleIndex,
|
||||||
descriptor.Flags);
|
descriptor.Flags);
|
||||||
|
|
||||||
|
if (descriptor.Binding > maxTextureBinding)
|
||||||
|
{
|
||||||
|
maxTextureBinding = descriptor.Binding;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TextureBindingInfo[] imageBindings = _channel.TextureManager.RentComputeImageBindings(info.Images.Count);
|
TextureBindingInfo[] imageBindings = _channel.TextureManager.RentComputeImageBindings(info.Images.Count);
|
||||||
|
@ -220,9 +228,18 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
|
||||||
descriptor.CbufSlot,
|
descriptor.CbufSlot,
|
||||||
descriptor.HandleIndex,
|
descriptor.HandleIndex,
|
||||||
descriptor.Flags);
|
descriptor.Flags);
|
||||||
|
|
||||||
|
if (descriptor.Binding > maxImageBinding)
|
||||||
|
{
|
||||||
|
maxImageBinding = descriptor.Binding;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_channel.TextureManager.CommitComputeBindings();
|
_channel.TextureManager.SetComputeMaxBindings(maxTextureBinding, maxImageBinding);
|
||||||
|
|
||||||
|
// Should never return false for mismatching spec state, since the shader was fetched above.
|
||||||
|
_channel.TextureManager.CommitComputeBindings(cs.SpecializationState);
|
||||||
|
|
||||||
_channel.BufferManager.CommitComputeBindings();
|
_channel.BufferManager.CommitComputeBindings();
|
||||||
|
|
||||||
_context.Renderer.Pipeline.DispatchCompute(qmd.CtaRasterWidth, qmd.CtaRasterHeight, qmd.CtaRasterDepth);
|
_context.Renderer.Pipeline.DispatchCompute(qmd.CtaRasterWidth, qmd.CtaRasterHeight, qmd.CtaRasterDepth);
|
||||||
|
|
|
@ -505,8 +505,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
}
|
}
|
||||||
|
|
||||||
int index = (argument >> 6) & 0xf;
|
int index = (argument >> 6) & 0xf;
|
||||||
|
int layer = (argument >> 10) & 0x3ff;
|
||||||
|
|
||||||
engine.UpdateRenderTargetState(useControl: false, singleUse: index);
|
engine.UpdateRenderTargetState(useControl: false, layered: layer != 0, singleUse: index);
|
||||||
|
|
||||||
// If there is a mismatch on the host clip region and the one explicitly defined by the guest
|
// If there is a mismatch on the host clip region and the one explicitly defined by the guest
|
||||||
// on the screen scissor state, then we need to force only one texture to be bound to avoid
|
// on the screen scissor state, then we need to force only one texture to be bound to avoid
|
||||||
|
@ -558,7 +559,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
scissorH = (int)MathF.Ceiling(scissorH * scale);
|
scissorH = (int)MathF.Ceiling(scissorH * scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
_context.Renderer.Pipeline.SetScissor(0, true, scissorX, scissorY, scissorW, scissorH);
|
Span<Rectangle<int>> scissors = stackalloc Rectangle<int>[1];
|
||||||
|
scissors[0] = new Rectangle<int>(scissorX, scissorY, scissorW, scissorH);
|
||||||
|
|
||||||
|
_context.Renderer.Pipeline.SetScissors(scissors);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clipMismatch)
|
if (clipMismatch)
|
||||||
|
@ -581,7 +585,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
|
|
||||||
ColorF color = new ColorF(clearColor.Red, clearColor.Green, clearColor.Blue, clearColor.Alpha);
|
ColorF color = new ColorF(clearColor.Red, clearColor.Green, clearColor.Blue, clearColor.Alpha);
|
||||||
|
|
||||||
_context.Renderer.Pipeline.ClearRenderTargetColor(index, componentMask, color);
|
_context.Renderer.Pipeline.ClearRenderTargetColor(index, layer, componentMask, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clearDepth || clearStencil)
|
if (clearDepth || clearStencil)
|
||||||
|
@ -602,6 +606,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
}
|
}
|
||||||
|
|
||||||
_context.Renderer.Pipeline.ClearRenderTargetDepthStencil(
|
_context.Renderer.Pipeline.ClearRenderTargetDepthStencil(
|
||||||
|
layer,
|
||||||
depthValue,
|
depthValue,
|
||||||
clearDepth,
|
clearDepth,
|
||||||
stencilValue,
|
stencilValue,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
using Ryujinx.Graphics.Gpu.Engine.Types;
|
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||||
using Ryujinx.Graphics.Gpu.Image;
|
using Ryujinx.Graphics.Gpu.Image;
|
||||||
|
@ -15,11 +16,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
/// </summary>
|
/// </summary>
|
||||||
class StateUpdater
|
class StateUpdater
|
||||||
{
|
{
|
||||||
public const int ShaderStateIndex = 0;
|
public const int ShaderStateIndex = 16;
|
||||||
public const int RasterizerStateIndex = 1;
|
public const int RasterizerStateIndex = 15;
|
||||||
public const int ScissorStateIndex = 2;
|
public const int ScissorStateIndex = 18;
|
||||||
public const int VertexBufferStateIndex = 3;
|
public const int VertexBufferStateIndex = 0;
|
||||||
public const int PrimitiveRestartStateIndex = 4;
|
public const int PrimitiveRestartStateIndex = 12;
|
||||||
|
|
||||||
private readonly GpuContext _context;
|
private readonly GpuContext _context;
|
||||||
private readonly GpuChannel _channel;
|
private readonly GpuChannel _channel;
|
||||||
|
@ -31,6 +32,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
private readonly ShaderProgramInfo[] _currentProgramInfo;
|
private readonly ShaderProgramInfo[] _currentProgramInfo;
|
||||||
private ShaderSpecializationState _shaderSpecState;
|
private ShaderSpecializationState _shaderSpecState;
|
||||||
|
|
||||||
|
private ProgramPipelineState _pipeline;
|
||||||
|
|
||||||
private bool _vtgWritesRtLayer;
|
private bool _vtgWritesRtLayer;
|
||||||
private byte _vsClipDistancesWritten;
|
private byte _vsClipDistancesWritten;
|
||||||
|
|
||||||
|
@ -54,7 +57,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
_drawState = drawState;
|
_drawState = drawState;
|
||||||
_currentProgramInfo = new ShaderProgramInfo[Constants.ShaderStages];
|
_currentProgramInfo = new ShaderProgramInfo[Constants.ShaderStages];
|
||||||
|
|
||||||
// ShaderState must be the first, as other state updates depends on information from the currently bound shader.
|
// ShaderState must be updated after other state updates, as pipeline state is sent to the backend when compiling new shaders.
|
||||||
|
// Render target state must appear after shader state as it depends on information from the currently bound shader.
|
||||||
// Rasterizer and scissor states are checked by render target clear, their indexes
|
// Rasterizer and scissor states are checked by render target clear, their indexes
|
||||||
// must be updated on the constants "RasterizerStateIndex" and "ScissorStateIndex" if modified.
|
// must be updated on the constants "RasterizerStateIndex" and "ScissorStateIndex" if modified.
|
||||||
// The vertex buffer state may be forced dirty when a indexed draw starts, the "VertexBufferStateIndex"
|
// The vertex buffer state may be forced dirty when a indexed draw starts, the "VertexBufferStateIndex"
|
||||||
|
@ -62,53 +66,39 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
// The order of the other state updates doesn't matter.
|
// The order of the other state updates doesn't matter.
|
||||||
_updateTracker = new StateUpdateTracker<ThreedClassState>(new[]
|
_updateTracker = new StateUpdateTracker<ThreedClassState>(new[]
|
||||||
{
|
{
|
||||||
new StateUpdateCallbackEntry(UpdateShaderState,
|
|
||||||
nameof(ThreedClassState.ShaderBaseAddress),
|
|
||||||
nameof(ThreedClassState.ShaderState)),
|
|
||||||
|
|
||||||
new StateUpdateCallbackEntry(UpdateRasterizerState, nameof(ThreedClassState.RasterizeEnable)),
|
|
||||||
|
|
||||||
new StateUpdateCallbackEntry(UpdateScissorState,
|
|
||||||
nameof(ThreedClassState.ScissorState),
|
|
||||||
nameof(ThreedClassState.ScreenScissorState)),
|
|
||||||
|
|
||||||
new StateUpdateCallbackEntry(UpdateVertexBufferState,
|
new StateUpdateCallbackEntry(UpdateVertexBufferState,
|
||||||
nameof(ThreedClassState.VertexBufferDrawState),
|
nameof(ThreedClassState.VertexBufferDrawState),
|
||||||
nameof(ThreedClassState.VertexBufferInstanced),
|
nameof(ThreedClassState.VertexBufferInstanced),
|
||||||
nameof(ThreedClassState.VertexBufferState),
|
nameof(ThreedClassState.VertexBufferState),
|
||||||
nameof(ThreedClassState.VertexBufferEndAddress)),
|
nameof(ThreedClassState.VertexBufferEndAddress)),
|
||||||
|
|
||||||
new StateUpdateCallbackEntry(UpdatePrimitiveRestartState,
|
new StateUpdateCallbackEntry(UpdateVertexAttribState, nameof(ThreedClassState.VertexAttribState)),
|
||||||
nameof(ThreedClassState.PrimitiveRestartDrawArrays),
|
|
||||||
nameof(ThreedClassState.PrimitiveRestartState)),
|
|
||||||
|
|
||||||
new StateUpdateCallbackEntry(UpdateTessellationState,
|
new StateUpdateCallbackEntry(UpdateBlendState,
|
||||||
nameof(ThreedClassState.TessOuterLevel),
|
nameof(ThreedClassState.BlendIndependent),
|
||||||
nameof(ThreedClassState.TessInnerLevel),
|
nameof(ThreedClassState.BlendConstant),
|
||||||
nameof(ThreedClassState.PatchVertices)),
|
nameof(ThreedClassState.BlendStateCommon),
|
||||||
|
nameof(ThreedClassState.BlendEnableCommon),
|
||||||
|
nameof(ThreedClassState.BlendEnable),
|
||||||
|
nameof(ThreedClassState.BlendState)),
|
||||||
|
|
||||||
new StateUpdateCallbackEntry(UpdateTfBufferState, nameof(ThreedClassState.TfBufferState)),
|
new StateUpdateCallbackEntry(UpdateFaceState, nameof(ThreedClassState.FaceState)),
|
||||||
new StateUpdateCallbackEntry(UpdateUserClipState, nameof(ThreedClassState.ClipDistanceEnable)),
|
|
||||||
|
|
||||||
new StateUpdateCallbackEntry(UpdateRenderTargetState,
|
new StateUpdateCallbackEntry(UpdateStencilTestState,
|
||||||
nameof(ThreedClassState.RtColorState),
|
nameof(ThreedClassState.StencilBackMasks),
|
||||||
nameof(ThreedClassState.RtDepthStencilState),
|
nameof(ThreedClassState.StencilTestState),
|
||||||
nameof(ThreedClassState.RtControl),
|
nameof(ThreedClassState.StencilBackTestState)),
|
||||||
nameof(ThreedClassState.RtDepthStencilSize),
|
|
||||||
nameof(ThreedClassState.RtDepthStencilEnable)),
|
|
||||||
|
|
||||||
new StateUpdateCallbackEntry(UpdateDepthClampState, nameof(ThreedClassState.ViewVolumeClipControl)),
|
|
||||||
|
|
||||||
new StateUpdateCallbackEntry(UpdateAlphaTestState,
|
|
||||||
nameof(ThreedClassState.AlphaTestEnable),
|
|
||||||
nameof(ThreedClassState.AlphaTestRef),
|
|
||||||
nameof(ThreedClassState.AlphaTestFunc)),
|
|
||||||
|
|
||||||
new StateUpdateCallbackEntry(UpdateDepthTestState,
|
new StateUpdateCallbackEntry(UpdateDepthTestState,
|
||||||
nameof(ThreedClassState.DepthTestEnable),
|
nameof(ThreedClassState.DepthTestEnable),
|
||||||
nameof(ThreedClassState.DepthWriteEnable),
|
nameof(ThreedClassState.DepthWriteEnable),
|
||||||
nameof(ThreedClassState.DepthTestFunc)),
|
nameof(ThreedClassState.DepthTestFunc)),
|
||||||
|
|
||||||
|
new StateUpdateCallbackEntry(UpdateTessellationState,
|
||||||
|
nameof(ThreedClassState.TessOuterLevel),
|
||||||
|
nameof(ThreedClassState.TessInnerLevel),
|
||||||
|
nameof(ThreedClassState.PatchVertices)),
|
||||||
|
|
||||||
new StateUpdateCallbackEntry(UpdateViewportTransform,
|
new StateUpdateCallbackEntry(UpdateViewportTransform,
|
||||||
nameof(ThreedClassState.DepthMode),
|
nameof(ThreedClassState.DepthMode),
|
||||||
nameof(ThreedClassState.ViewportTransform),
|
nameof(ThreedClassState.ViewportTransform),
|
||||||
|
@ -116,6 +106,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
nameof(ThreedClassState.YControl),
|
nameof(ThreedClassState.YControl),
|
||||||
nameof(ThreedClassState.ViewportTransformEnable)),
|
nameof(ThreedClassState.ViewportTransformEnable)),
|
||||||
|
|
||||||
|
new StateUpdateCallbackEntry(UpdateLogicOpState, nameof(ThreedClassState.LogicOpState)),
|
||||||
|
|
||||||
|
new StateUpdateCallbackEntry(UpdateDepthClampState, nameof(ThreedClassState.ViewVolumeClipControl)),
|
||||||
|
|
||||||
new StateUpdateCallbackEntry(UpdatePolygonMode,
|
new StateUpdateCallbackEntry(UpdatePolygonMode,
|
||||||
nameof(ThreedClassState.PolygonModeFront),
|
nameof(ThreedClassState.PolygonModeFront),
|
||||||
nameof(ThreedClassState.PolygonModeBack)),
|
nameof(ThreedClassState.PolygonModeBack)),
|
||||||
|
@ -126,21 +120,46 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
nameof(ThreedClassState.DepthBiasUnits),
|
nameof(ThreedClassState.DepthBiasUnits),
|
||||||
nameof(ThreedClassState.DepthBiasClamp)),
|
nameof(ThreedClassState.DepthBiasClamp)),
|
||||||
|
|
||||||
new StateUpdateCallbackEntry(UpdateStencilTestState,
|
new StateUpdateCallbackEntry(UpdatePrimitiveRestartState, nameof(ThreedClassState.PrimitiveRestartState)),
|
||||||
nameof(ThreedClassState.StencilBackMasks),
|
|
||||||
nameof(ThreedClassState.StencilTestState),
|
new StateUpdateCallbackEntry(UpdateLineState,
|
||||||
nameof(ThreedClassState.StencilBackTestState)),
|
nameof(ThreedClassState.LineWidthSmooth),
|
||||||
|
nameof(ThreedClassState.LineSmoothEnable)),
|
||||||
|
|
||||||
|
new StateUpdateCallbackEntry(UpdateRtColorMask,
|
||||||
|
nameof(ThreedClassState.RtColorMaskShared),
|
||||||
|
nameof(ThreedClassState.RtColorMask)),
|
||||||
|
|
||||||
|
new StateUpdateCallbackEntry(UpdateRasterizerState, nameof(ThreedClassState.RasterizeEnable)),
|
||||||
|
|
||||||
|
new StateUpdateCallbackEntry(UpdateShaderState,
|
||||||
|
nameof(ThreedClassState.ShaderBaseAddress),
|
||||||
|
nameof(ThreedClassState.ShaderState)),
|
||||||
|
|
||||||
|
new StateUpdateCallbackEntry(UpdateRenderTargetState,
|
||||||
|
nameof(ThreedClassState.RtColorState),
|
||||||
|
nameof(ThreedClassState.RtDepthStencilState),
|
||||||
|
nameof(ThreedClassState.RtControl),
|
||||||
|
nameof(ThreedClassState.RtDepthStencilSize),
|
||||||
|
nameof(ThreedClassState.RtDepthStencilEnable)),
|
||||||
|
|
||||||
|
new StateUpdateCallbackEntry(UpdateScissorState,
|
||||||
|
nameof(ThreedClassState.ScissorState),
|
||||||
|
nameof(ThreedClassState.ScreenScissorState)),
|
||||||
|
|
||||||
|
new StateUpdateCallbackEntry(UpdateTfBufferState, nameof(ThreedClassState.TfBufferState)),
|
||||||
|
new StateUpdateCallbackEntry(UpdateUserClipState, nameof(ThreedClassState.ClipDistanceEnable)),
|
||||||
|
|
||||||
|
new StateUpdateCallbackEntry(UpdateAlphaTestState,
|
||||||
|
nameof(ThreedClassState.AlphaTestEnable),
|
||||||
|
nameof(ThreedClassState.AlphaTestRef),
|
||||||
|
nameof(ThreedClassState.AlphaTestFunc)),
|
||||||
|
|
||||||
new StateUpdateCallbackEntry(UpdateSamplerPoolState,
|
new StateUpdateCallbackEntry(UpdateSamplerPoolState,
|
||||||
nameof(ThreedClassState.SamplerPoolState),
|
nameof(ThreedClassState.SamplerPoolState),
|
||||||
nameof(ThreedClassState.SamplerIndex)),
|
nameof(ThreedClassState.SamplerIndex)),
|
||||||
|
|
||||||
new StateUpdateCallbackEntry(UpdateTexturePoolState, nameof(ThreedClassState.TexturePoolState)),
|
new StateUpdateCallbackEntry(UpdateTexturePoolState, nameof(ThreedClassState.TexturePoolState)),
|
||||||
new StateUpdateCallbackEntry(UpdateVertexAttribState, nameof(ThreedClassState.VertexAttribState)),
|
|
||||||
|
|
||||||
new StateUpdateCallbackEntry(UpdateLineState,
|
|
||||||
nameof(ThreedClassState.LineWidthSmooth),
|
|
||||||
nameof(ThreedClassState.LineSmoothEnable)),
|
|
||||||
|
|
||||||
new StateUpdateCallbackEntry(UpdatePointState,
|
new StateUpdateCallbackEntry(UpdatePointState,
|
||||||
nameof(ThreedClassState.PointSize),
|
nameof(ThreedClassState.PointSize),
|
||||||
|
@ -151,22 +170,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
new StateUpdateCallbackEntry(UpdateIndexBufferState,
|
new StateUpdateCallbackEntry(UpdateIndexBufferState,
|
||||||
nameof(ThreedClassState.IndexBufferState),
|
nameof(ThreedClassState.IndexBufferState),
|
||||||
nameof(ThreedClassState.IndexBufferCount)),
|
nameof(ThreedClassState.IndexBufferCount)),
|
||||||
|
|
||||||
new StateUpdateCallbackEntry(UpdateFaceState, nameof(ThreedClassState.FaceState)),
|
|
||||||
|
|
||||||
new StateUpdateCallbackEntry(UpdateRtColorMask,
|
|
||||||
nameof(ThreedClassState.RtColorMaskShared),
|
|
||||||
nameof(ThreedClassState.RtColorMask)),
|
|
||||||
|
|
||||||
new StateUpdateCallbackEntry(UpdateBlendState,
|
|
||||||
nameof(ThreedClassState.BlendIndependent),
|
|
||||||
nameof(ThreedClassState.BlendConstant),
|
|
||||||
nameof(ThreedClassState.BlendStateCommon),
|
|
||||||
nameof(ThreedClassState.BlendEnableCommon),
|
|
||||||
nameof(ThreedClassState.BlendEnable),
|
|
||||||
nameof(ThreedClassState.BlendState)),
|
|
||||||
|
|
||||||
new StateUpdateCallbackEntry(UpdateLogicOpState, nameof(ThreedClassState.LogicOpState))
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,7 +204,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
// of the shader for the new state.
|
// of the shader for the new state.
|
||||||
if (_shaderSpecState != null)
|
if (_shaderSpecState != null)
|
||||||
{
|
{
|
||||||
if (!_shaderSpecState.MatchesGraphics(_channel, GetPoolState(), GetGraphicsState()))
|
if (!_shaderSpecState.MatchesGraphics(_channel, GetPoolState(), GetGraphicsState(), false))
|
||||||
{
|
{
|
||||||
ForceShaderUpdate();
|
ForceShaderUpdate();
|
||||||
}
|
}
|
||||||
|
@ -275,7 +278,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
{
|
{
|
||||||
UpdateStorageBuffers();
|
UpdateStorageBuffers();
|
||||||
|
|
||||||
_channel.TextureManager.CommitGraphicsBindings();
|
if (!_channel.TextureManager.CommitGraphicsBindings(_shaderSpecState))
|
||||||
|
{
|
||||||
|
// Shader must be reloaded.
|
||||||
|
UpdateShaderState();
|
||||||
|
}
|
||||||
|
|
||||||
_channel.BufferManager.CommitGraphicsBindings();
|
_channel.BufferManager.CommitGraphicsBindings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -315,6 +323,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void UpdateTessellationState()
|
private void UpdateTessellationState()
|
||||||
{
|
{
|
||||||
|
_pipeline.PatchControlPoints = (uint)_state.State.PatchVertices;
|
||||||
|
|
||||||
_context.Renderer.Pipeline.SetPatchParameters(
|
_context.Renderer.Pipeline.SetPatchParameters(
|
||||||
_state.State.PatchVertices,
|
_state.State.PatchVertices,
|
||||||
_state.State.TessOuterLevel.ToSpan(),
|
_state.State.TessOuterLevel.ToSpan(),
|
||||||
|
@ -347,6 +357,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
private void UpdateRasterizerState()
|
private void UpdateRasterizerState()
|
||||||
{
|
{
|
||||||
bool enable = _state.State.RasterizeEnable;
|
bool enable = _state.State.RasterizeEnable;
|
||||||
|
_pipeline.RasterizerDiscard = !enable;
|
||||||
_context.Renderer.Pipeline.SetRasterizerDiscard(!enable);
|
_context.Renderer.Pipeline.SetRasterizerDiscard(!enable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -362,8 +373,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
/// Updates render targets (color and depth-stencil buffers) based on current render target state.
|
/// Updates render targets (color and depth-stencil buffers) based on current render target state.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="useControl">Use draw buffers information from render target control register</param>
|
/// <param name="useControl">Use draw buffers information from render target control register</param>
|
||||||
|
/// <param name="layered">Indicates if the texture is layered</param>
|
||||||
/// <param name="singleUse">If this is not -1, it indicates that only the given indexed target will be used.</param>
|
/// <param name="singleUse">If this is not -1, it indicates that only the given indexed target will be used.</param>
|
||||||
public void UpdateRenderTargetState(bool useControl, int singleUse = -1)
|
public void UpdateRenderTargetState(bool useControl, bool layered = false, int singleUse = -1)
|
||||||
{
|
{
|
||||||
var memoryManager = _channel.MemoryManager;
|
var memoryManager = _channel.MemoryManager;
|
||||||
var rtControl = _state.State.RtControl;
|
var rtControl = _state.State.RtControl;
|
||||||
|
@ -399,7 +411,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
Image.Texture color = memoryManager.Physical.TextureCache.FindOrCreateTexture(
|
Image.Texture color = memoryManager.Physical.TextureCache.FindOrCreateTexture(
|
||||||
memoryManager,
|
memoryManager,
|
||||||
colorState,
|
colorState,
|
||||||
_vtgWritesRtLayer,
|
_vtgWritesRtLayer || layered,
|
||||||
samplesInX,
|
samplesInX,
|
||||||
samplesInY,
|
samplesInY,
|
||||||
sizeHint);
|
sizeHint);
|
||||||
|
@ -433,6 +445,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
memoryManager,
|
memoryManager,
|
||||||
dsState,
|
dsState,
|
||||||
dsSize,
|
dsSize,
|
||||||
|
_vtgWritesRtLayer || layered,
|
||||||
samplesInX,
|
samplesInX,
|
||||||
samplesInY,
|
samplesInY,
|
||||||
sizeHint);
|
sizeHint);
|
||||||
|
@ -486,11 +499,21 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void UpdateScissorState()
|
public void UpdateScissorState()
|
||||||
{
|
{
|
||||||
|
const int MinX = 0;
|
||||||
|
const int MinY = 0;
|
||||||
|
const int MaxW = 0xffff;
|
||||||
|
const int MaxH = 0xffff;
|
||||||
|
|
||||||
|
Span<Rectangle<int>> regions = stackalloc Rectangle<int>[Constants.TotalViewports];
|
||||||
|
|
||||||
for (int index = 0; index < Constants.TotalViewports; index++)
|
for (int index = 0; index < Constants.TotalViewports; index++)
|
||||||
{
|
{
|
||||||
ScissorState scissor = _state.State.ScissorState[index];
|
ScissorState scissor = _state.State.ScissorState[index];
|
||||||
|
|
||||||
bool enable = scissor.Enable && (scissor.X1 != 0 || scissor.Y1 != 0 || scissor.X2 != 0xffff || scissor.Y2 != 0xffff);
|
bool enable = scissor.Enable && (scissor.X1 != MinX ||
|
||||||
|
scissor.Y1 != MinY ||
|
||||||
|
scissor.X2 != MaxW ||
|
||||||
|
scissor.Y2 != MaxH);
|
||||||
|
|
||||||
if (enable)
|
if (enable)
|
||||||
{
|
{
|
||||||
|
@ -520,13 +543,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
height = (int)MathF.Ceiling(height * scale);
|
height = (int)MathF.Ceiling(height * scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
_context.Renderer.Pipeline.SetScissor(index, true, x, y, width, height);
|
regions[index] = new Rectangle<int>(x, y, width, height);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_context.Renderer.Pipeline.SetScissor(index, false, 0, 0, 0, 0);
|
regions[index] = new Rectangle<int>(MinX, MinY, MaxW, MaxH);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_context.Renderer.Pipeline.SetScissors(regions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -536,7 +561,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
private void UpdateDepthClampState()
|
private void UpdateDepthClampState()
|
||||||
{
|
{
|
||||||
ViewVolumeClipControl clip = _state.State.ViewVolumeClipControl;
|
ViewVolumeClipControl clip = _state.State.ViewVolumeClipControl;
|
||||||
_context.Renderer.Pipeline.SetDepthClamp((clip & ViewVolumeClipControl.DepthClampDisabled) == 0);
|
bool clamp = (clip & ViewVolumeClipControl.DepthClampDisabled) == 0;
|
||||||
|
|
||||||
|
_pipeline.DepthClampEnable = clamp;
|
||||||
|
_context.Renderer.Pipeline.SetDepthClamp(clamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -555,10 +583,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void UpdateDepthTestState()
|
private void UpdateDepthTestState()
|
||||||
{
|
{
|
||||||
_context.Renderer.Pipeline.SetDepthTest(new DepthTestDescriptor(
|
DepthTestDescriptor descriptor = new DepthTestDescriptor(
|
||||||
_state.State.DepthTestEnable,
|
_state.State.DepthTestEnable,
|
||||||
_state.State.DepthWriteEnable,
|
_state.State.DepthWriteEnable,
|
||||||
_state.State.DepthTestFunc));
|
_state.State.DepthTestFunc);
|
||||||
|
|
||||||
|
_pipeline.DepthTest = descriptor;
|
||||||
|
_context.Renderer.Pipeline.SetDepthTest(descriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -585,7 +616,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
ref var scissor = ref _state.State.ScreenScissorState;
|
ref var scissor = ref _state.State.ScreenScissorState;
|
||||||
|
|
||||||
float rScale = _channel.TextureManager.RenderTargetScale;
|
float rScale = _channel.TextureManager.RenderTargetScale;
|
||||||
var scissorRect = new RectangleF(0, 0, (scissor.X + scissor.Width) * rScale, (scissor.Y + scissor.Height) * rScale);
|
var scissorRect = new Rectangle<float>(0, 0, (scissor.X + scissor.Width) * rScale, (scissor.Y + scissor.Height) * rScale);
|
||||||
|
|
||||||
viewports[index] = new Viewport(scissorRect, ViewportSwizzle.PositiveX, ViewportSwizzle.PositiveY, ViewportSwizzle.PositiveZ, ViewportSwizzle.PositiveW, 0, 1);
|
viewports[index] = new Viewport(scissorRect, ViewportSwizzle.PositiveX, ViewportSwizzle.PositiveY, ViewportSwizzle.PositiveZ, ViewportSwizzle.PositiveW, 0, 1);
|
||||||
continue;
|
continue;
|
||||||
|
@ -622,7 +653,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
height *= scale;
|
height *= scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
RectangleF region = new RectangleF(x, y, width, height);
|
Rectangle<float> region = new Rectangle<float>(x, y, width, height);
|
||||||
|
|
||||||
ViewportSwizzle swizzleX = transform.UnpackSwizzleX();
|
ViewportSwizzle swizzleX = transform.UnpackSwizzleX();
|
||||||
ViewportSwizzle swizzleY = transform.UnpackSwizzleY();
|
ViewportSwizzle swizzleY = transform.UnpackSwizzleY();
|
||||||
|
@ -642,6 +673,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
viewports[index] = new Viewport(region, swizzleX, swizzleY, swizzleZ, swizzleW, depthNear, depthFar);
|
viewports[index] = new Viewport(region, swizzleX, swizzleY, swizzleZ, swizzleW, depthNear, depthFar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_context.Renderer.Pipeline.SetDepthMode(GetDepthMode());
|
||||||
_context.Renderer.Pipeline.SetViewports(0, viewports, disableTransform);
|
_context.Renderer.Pipeline.SetViewports(0, viewports, disableTransform);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -650,37 +682,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void UpdateDepthMode()
|
private void UpdateDepthMode()
|
||||||
{
|
{
|
||||||
ref var transform = ref _state.State.ViewportTransform[0];
|
_context.Renderer.Pipeline.SetDepthMode(GetDepthMode());
|
||||||
ref var extents = ref _state.State.ViewportExtents[0];
|
|
||||||
|
|
||||||
DepthMode depthMode;
|
|
||||||
|
|
||||||
if (!float.IsInfinity(extents.DepthNear) &&
|
|
||||||
!float.IsInfinity(extents.DepthFar) &&
|
|
||||||
(extents.DepthFar - extents.DepthNear) != 0)
|
|
||||||
{
|
|
||||||
// Try to guess the depth mode being used on the high level API
|
|
||||||
// based on current transform.
|
|
||||||
// It is setup like so by said APIs:
|
|
||||||
// If depth mode is ZeroToOne:
|
|
||||||
// TranslateZ = Near
|
|
||||||
// ScaleZ = Far - Near
|
|
||||||
// If depth mode is MinusOneToOne:
|
|
||||||
// TranslateZ = (Near + Far) / 2
|
|
||||||
// ScaleZ = (Far - Near) / 2
|
|
||||||
// DepthNear/Far are sorted such as that Near is always less than Far.
|
|
||||||
depthMode = extents.DepthNear != transform.TranslateZ &&
|
|
||||||
extents.DepthFar != transform.TranslateZ
|
|
||||||
? DepthMode.MinusOneToOne
|
|
||||||
: DepthMode.ZeroToOne;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// If we can't guess from the viewport transform, then just use the depth mode register.
|
|
||||||
depthMode = (DepthMode)(_state.State.DepthMode & 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
_context.Renderer.Pipeline.SetDepthMode(depthMode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -708,6 +710,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
enables |= (depthBias.LineEnable ? PolygonModeMask.Line : 0);
|
enables |= (depthBias.LineEnable ? PolygonModeMask.Line : 0);
|
||||||
enables |= (depthBias.FillEnable ? PolygonModeMask.Fill : 0);
|
enables |= (depthBias.FillEnable ? PolygonModeMask.Fill : 0);
|
||||||
|
|
||||||
|
_pipeline.BiasEnable = enables;
|
||||||
_context.Renderer.Pipeline.SetDepthBias(enables, factor, units / 2f, clamp);
|
_context.Renderer.Pipeline.SetDepthBias(enables, factor, units / 2f, clamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -749,7 +752,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
backMask = test.FrontMask;
|
backMask = test.FrontMask;
|
||||||
}
|
}
|
||||||
|
|
||||||
_context.Renderer.Pipeline.SetStencilTest(new StencilTestDescriptor(
|
StencilTestDescriptor descriptor = new StencilTestDescriptor(
|
||||||
test.Enable,
|
test.Enable,
|
||||||
test.FrontFunc,
|
test.FrontFunc,
|
||||||
test.FrontSFail,
|
test.FrontSFail,
|
||||||
|
@ -764,7 +767,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
backDpFail,
|
backDpFail,
|
||||||
backFuncRef,
|
backFuncRef,
|
||||||
backFuncMask,
|
backFuncMask,
|
||||||
backMask));
|
backMask);
|
||||||
|
|
||||||
|
_pipeline.StencilTest = descriptor;
|
||||||
|
_context.Renderer.Pipeline.SetStencilTest(descriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -833,6 +839,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
format);
|
format);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_pipeline.SetVertexAttribs(vertexAttribs);
|
||||||
_context.Renderer.Pipeline.SetVertexAttribs(vertexAttribs);
|
_context.Renderer.Pipeline.SetVertexAttribs(vertexAttribs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -844,6 +851,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
float width = _state.State.LineWidthSmooth;
|
float width = _state.State.LineWidthSmooth;
|
||||||
bool smooth = _state.State.LineSmoothEnable;
|
bool smooth = _state.State.LineSmoothEnable;
|
||||||
|
|
||||||
|
_pipeline.LineWidth = width;
|
||||||
_context.Renderer.Pipeline.SetLineParameters(width, smooth);
|
_context.Renderer.Pipeline.SetLineParameters(width, smooth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -870,6 +878,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
PrimitiveRestartState primitiveRestart = _state.State.PrimitiveRestartState;
|
PrimitiveRestartState primitiveRestart = _state.State.PrimitiveRestartState;
|
||||||
bool enable = primitiveRestart.Enable && (_drawState.DrawIndexed || _state.State.PrimitiveRestartDrawArrays);
|
bool enable = primitiveRestart.Enable && (_drawState.DrawIndexed || _state.State.PrimitiveRestartDrawArrays);
|
||||||
|
|
||||||
|
_pipeline.PrimitiveRestartEnable = enable;
|
||||||
_context.Renderer.Pipeline.SetPrimitiveRestart(enable, primitiveRestart.Index);
|
_context.Renderer.Pipeline.SetPrimitiveRestart(enable, primitiveRestart.Index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -916,6 +925,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
|
|
||||||
if (!vertexBuffer.UnpackEnable())
|
if (!vertexBuffer.UnpackEnable())
|
||||||
{
|
{
|
||||||
|
_pipeline.VertexBuffers[index] = new BufferPipelineDescriptor(false, 0, 0);
|
||||||
_channel.BufferManager.SetVertexBuffer(index, 0, 0, 0, 0);
|
_channel.BufferManager.SetVertexBuffer(index, 0, 0, 0, 0);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
|
@ -933,6 +943,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
|
|
||||||
_drawState.IsAnyVbInstanced |= divisor != 0;
|
_drawState.IsAnyVbInstanced |= divisor != 0;
|
||||||
|
|
||||||
|
ulong vbSize = endAddress.Pack() - address + 1;
|
||||||
ulong size;
|
ulong size;
|
||||||
|
|
||||||
if (_drawState.IbStreamer.HasInlineIndexData || _drawState.DrawIndexed || stride == 0 || instanced)
|
if (_drawState.IbStreamer.HasInlineIndexData || _drawState.DrawIndexed || stride == 0 || instanced)
|
||||||
|
@ -940,7 +951,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
// This size may be (much) larger than the real vertex buffer size.
|
// This size may be (much) larger than the real vertex buffer size.
|
||||||
// Avoid calculating it this way, unless we don't have any other option.
|
// Avoid calculating it this way, unless we don't have any other option.
|
||||||
|
|
||||||
size = endAddress.Pack() - address + 1;
|
size = vbSize;
|
||||||
|
|
||||||
if (stride > 0 && indexTypeSmall && _drawState.DrawIndexed && !instanced)
|
if (stride > 0 && indexTypeSmall && _drawState.DrawIndexed && !instanced)
|
||||||
{
|
{
|
||||||
|
@ -964,9 +975,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
|
|
||||||
var drawState = _state.State.VertexBufferDrawState;
|
var drawState = _state.State.VertexBufferDrawState;
|
||||||
|
|
||||||
size = (ulong)((firstInstance + drawState.First + drawState.Count) * stride);
|
size = Math.Min(vbSize, (ulong)((firstInstance + drawState.First + drawState.Count) * stride));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_pipeline.VertexBuffers[index] = new BufferPipelineDescriptor(_channel.MemoryManager.IsMapped(address), stride, divisor);
|
||||||
_channel.BufferManager.SetVertexBuffer(index, address, size, stride, divisor);
|
_channel.BufferManager.SetVertexBuffer(index, address, size, stride, divisor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -979,6 +991,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
var yControl = _state.State.YControl;
|
var yControl = _state.State.YControl;
|
||||||
var face = _state.State.FaceState;
|
var face = _state.State.FaceState;
|
||||||
|
|
||||||
|
_pipeline.CullEnable = face.CullEnable;
|
||||||
|
_pipeline.CullMode = face.CullFace;
|
||||||
_context.Renderer.Pipeline.SetFaceCulling(face.CullEnable, face.CullFace);
|
_context.Renderer.Pipeline.SetFaceCulling(face.CullEnable, face.CullFace);
|
||||||
|
|
||||||
UpdateFrontFace(yControl, face.FrontFace);
|
UpdateFrontFace(yControl, face.FrontFace);
|
||||||
|
@ -998,6 +1012,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
frontFace = frontFace == FrontFace.CounterClockwise ? FrontFace.Clockwise : FrontFace.CounterClockwise;
|
frontFace = frontFace == FrontFace.CounterClockwise ? FrontFace.Clockwise : FrontFace.CounterClockwise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_pipeline.FrontFace = frontFace;
|
||||||
_context.Renderer.Pipeline.SetFrontFace(frontFace);
|
_context.Renderer.Pipeline.SetFrontFace(frontFace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1023,6 +1038,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
componentMask |= (colorMask.UnpackAlpha() ? 8u : 0u);
|
componentMask |= (colorMask.UnpackAlpha() ? 8u : 0u);
|
||||||
|
|
||||||
componentMasks[index] = componentMask;
|
componentMasks[index] = componentMask;
|
||||||
|
_pipeline.ColorWriteMask[index] = componentMask;
|
||||||
}
|
}
|
||||||
|
|
||||||
_context.Renderer.Pipeline.SetRenderTargetColorMasks(componentMasks);
|
_context.Renderer.Pipeline.SetRenderTargetColorMasks(componentMasks);
|
||||||
|
@ -1071,6 +1087,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
blend.AlphaDstFactor);
|
blend.AlphaDstFactor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_pipeline.BlendDescriptors[index] = descriptor;
|
||||||
_context.Renderer.Pipeline.SetBlendState(index, descriptor);
|
_context.Renderer.Pipeline.SetBlendState(index, descriptor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1082,6 +1099,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
{
|
{
|
||||||
LogicalOpState logicOpState = _state.State.LogicOpState;
|
LogicalOpState logicOpState = _state.State.LogicOpState;
|
||||||
|
|
||||||
|
_pipeline.SetLogicOpState(logicOpState.Enable, logicOpState.LogicalOp);
|
||||||
_context.Renderer.Pipeline.SetLogicOpState(logicOpState.Enable, logicOpState.LogicalOp);
|
_context.Renderer.Pipeline.SetLogicOpState(logicOpState.Enable, logicOpState.LogicalOp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1113,7 +1131,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
GpuChannelPoolState poolState = GetPoolState();
|
GpuChannelPoolState poolState = GetPoolState();
|
||||||
GpuChannelGraphicsState graphicsState = GetGraphicsState();
|
GpuChannelGraphicsState graphicsState = GetGraphicsState();
|
||||||
|
|
||||||
CachedShaderProgram gs = shaderCache.GetGraphicsShader(ref _state.State, _channel, poolState, graphicsState, addresses);
|
CachedShaderProgram gs = shaderCache.GetGraphicsShader(ref _state.State, ref _pipeline, _channel, poolState, graphicsState, addresses);
|
||||||
|
|
||||||
_shaderSpecState = gs.SpecializationState;
|
_shaderSpecState = gs.SpecializationState;
|
||||||
|
|
||||||
|
@ -1148,6 +1166,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int maxTextureBinding = -1;
|
||||||
|
int maxImageBinding = -1;
|
||||||
|
|
||||||
Span<TextureBindingInfo> textureBindings = _channel.TextureManager.RentGraphicsTextureBindings(stage, info.Textures.Count);
|
Span<TextureBindingInfo> textureBindings = _channel.TextureManager.RentGraphicsTextureBindings(stage, info.Textures.Count);
|
||||||
|
|
||||||
if (info.UsesRtLayer)
|
if (info.UsesRtLayer)
|
||||||
|
@ -1167,6 +1188,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
descriptor.CbufSlot,
|
descriptor.CbufSlot,
|
||||||
descriptor.HandleIndex,
|
descriptor.HandleIndex,
|
||||||
descriptor.Flags);
|
descriptor.Flags);
|
||||||
|
|
||||||
|
if (descriptor.Binding > maxTextureBinding)
|
||||||
|
{
|
||||||
|
maxTextureBinding = descriptor.Binding;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TextureBindingInfo[] imageBindings = _channel.TextureManager.RentGraphicsImageBindings(stage, info.Images.Count);
|
TextureBindingInfo[] imageBindings = _channel.TextureManager.RentGraphicsImageBindings(stage, info.Images.Count);
|
||||||
|
@ -1185,8 +1211,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
descriptor.CbufSlot,
|
descriptor.CbufSlot,
|
||||||
descriptor.HandleIndex,
|
descriptor.HandleIndex,
|
||||||
descriptor.Flags);
|
descriptor.Flags);
|
||||||
|
|
||||||
|
if (descriptor.Binding > maxImageBinding)
|
||||||
|
{
|
||||||
|
maxImageBinding = descriptor.Binding;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_channel.TextureManager.SetGraphicsMaxBindings(maxTextureBinding, maxImageBinding);
|
||||||
|
|
||||||
_channel.BufferManager.SetGraphicsStorageBufferBindings(stage, info.SBuffers);
|
_channel.BufferManager.SetGraphicsStorageBufferBindings(stage, info.SBuffers);
|
||||||
_channel.BufferManager.SetGraphicsUniformBufferBindings(stage, info.CBuffers);
|
_channel.BufferManager.SetGraphicsUniformBufferBindings(stage, info.CBuffers);
|
||||||
}
|
}
|
||||||
|
@ -1205,11 +1238,67 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
/// <returns>Current GPU channel state</returns>
|
/// <returns>Current GPU channel state</returns>
|
||||||
private GpuChannelGraphicsState GetGraphicsState()
|
private GpuChannelGraphicsState GetGraphicsState()
|
||||||
{
|
{
|
||||||
|
ref var vertexAttribState = ref _state.State.VertexAttribState;
|
||||||
|
|
||||||
|
Array32<AttributeType> attributeTypes = new Array32<AttributeType>();
|
||||||
|
|
||||||
|
for (int location = 0; location < attributeTypes.Length; location++)
|
||||||
|
{
|
||||||
|
attributeTypes[location] = vertexAttribState[location].UnpackType() switch
|
||||||
|
{
|
||||||
|
3 => AttributeType.Sint,
|
||||||
|
4 => AttributeType.Uint,
|
||||||
|
_ => AttributeType.Float
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return new GpuChannelGraphicsState(
|
return new GpuChannelGraphicsState(
|
||||||
_state.State.EarlyZForce,
|
_state.State.EarlyZForce,
|
||||||
_drawState.Topology,
|
_drawState.Topology,
|
||||||
_state.State.TessMode,
|
_state.State.TessMode,
|
||||||
_state.State.ViewportTransformEnable == 0);
|
_state.State.ViewportTransformEnable == 0,
|
||||||
|
GetDepthMode() == DepthMode.MinusOneToOne,
|
||||||
|
_state.State.VertexProgramPointSize,
|
||||||
|
_state.State.PointSize,
|
||||||
|
_state.State.AlphaTestEnable,
|
||||||
|
_state.State.AlphaTestFunc,
|
||||||
|
_state.State.AlphaTestRef,
|
||||||
|
ref attributeTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DepthMode GetDepthMode()
|
||||||
|
{
|
||||||
|
ref var transform = ref _state.State.ViewportTransform[0];
|
||||||
|
ref var extents = ref _state.State.ViewportExtents[0];
|
||||||
|
|
||||||
|
DepthMode depthMode;
|
||||||
|
|
||||||
|
if (!float.IsInfinity(extents.DepthNear) &&
|
||||||
|
!float.IsInfinity(extents.DepthFar) &&
|
||||||
|
(extents.DepthFar - extents.DepthNear) != 0)
|
||||||
|
{
|
||||||
|
// Try to guess the depth mode being used on the high level API
|
||||||
|
// based on current transform.
|
||||||
|
// It is setup like so by said APIs:
|
||||||
|
// If depth mode is ZeroToOne:
|
||||||
|
// TranslateZ = Near
|
||||||
|
// ScaleZ = Far - Near
|
||||||
|
// If depth mode is MinusOneToOne:
|
||||||
|
// TranslateZ = (Near + Far) / 2
|
||||||
|
// ScaleZ = (Far - Near) / 2
|
||||||
|
// DepthNear/Far are sorted such as that Near is always less than Far.
|
||||||
|
depthMode = extents.DepthNear != transform.TranslateZ &&
|
||||||
|
extents.DepthFar != transform.TranslateZ
|
||||||
|
? DepthMode.MinusOneToOne
|
||||||
|
: DepthMode.ZeroToOne;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If we can't guess from the viewport transform, then just use the depth mode register.
|
||||||
|
depthMode = (DepthMode)(_state.State.DepthMode & 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return depthMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -131,10 +131,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
/// Updates render targets (color and depth-stencil buffers) based on current render target state.
|
/// Updates render targets (color and depth-stencil buffers) based on current render target state.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="useControl">Use draw buffers information from render target control register</param>
|
/// <param name="useControl">Use draw buffers information from render target control register</param>
|
||||||
|
/// <param name="layered">Indicates if the texture is layered</param>
|
||||||
/// <param name="singleUse">If this is not -1, it indicates that only the given indexed target will be used.</param>
|
/// <param name="singleUse">If this is not -1, it indicates that only the given indexed target will be used.</param>
|
||||||
public void UpdateRenderTargetState(bool useControl, int singleUse = -1)
|
public void UpdateRenderTargetState(bool useControl, bool layered = false, int singleUse = -1)
|
||||||
{
|
{
|
||||||
_stateUpdater.UpdateRenderTargetState(useControl, singleUse);
|
_stateUpdater.UpdateRenderTargetState(useControl, layered, singleUse);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -311,6 +311,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
{
|
{
|
||||||
return Attribute & 0x3fe00000;
|
return Attribute & 0x3fe00000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unpacks the Maxwell attribute component type.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Attribute component type</returns>
|
||||||
|
public uint UnpackType()
|
||||||
|
{
|
||||||
|
return (Attribute >> 27) & 7;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -759,8 +768,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
public fixed uint Reserved10B0[18];
|
public fixed uint Reserved10B0[18];
|
||||||
public uint ClearFlags;
|
public uint ClearFlags;
|
||||||
public fixed uint Reserved10FC[25];
|
public fixed uint Reserved10FC[25];
|
||||||
public Array16<VertexAttribState> VertexAttribState;
|
public Array32<VertexAttribState> VertexAttribState;
|
||||||
public fixed uint Reserved11A0[31];
|
public fixed uint Reserved11E0[15];
|
||||||
public RtControl RtControl;
|
public RtControl RtControl;
|
||||||
public fixed uint Reserved1220[2];
|
public fixed uint Reserved1220[2];
|
||||||
public Size3D RtDepthStencilSize;
|
public Size3D RtDepthStencilSize;
|
||||||
|
|
|
@ -30,8 +30,8 @@ namespace Ryujinx.Graphics.Gpu
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enables or disables fast 2d engine texture copies entirely on CPU when possible.
|
/// Enables or disables fast 2d engine texture copies entirely on CPU when possible.
|
||||||
/// Reduces stuttering and # of textures in games that copy textures around for streaming,
|
/// Reduces stuttering and # of textures in games that copy textures around for streaming,
|
||||||
/// as textures will not need to be created for the copy, and the data does not need to be
|
/// as textures will not need to be created for the copy, and the data does not need to be
|
||||||
/// flushed from GPU.
|
/// flushed from GPU.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool Fast2DCopy = true;
|
public static bool Fast2DCopy = true;
|
||||||
|
@ -56,5 +56,15 @@ namespace Ryujinx.Graphics.Gpu
|
||||||
/// Enables or disables the shader cache.
|
/// Enables or disables the shader cache.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool EnableShaderCache;
|
public static bool EnableShaderCache;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables or disables shader SPIR-V compilation.
|
||||||
|
/// </summary>
|
||||||
|
public static bool EnableSpirvCompilationOnVulkan = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables or disables recompression of compressed textures that are not natively supported by the host.
|
||||||
|
/// </summary>
|
||||||
|
public static bool EnableTextureRecompression = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
using Ryujinx.Cpu.Tracking;
|
using Ryujinx.Cpu.Tracking;
|
||||||
using Ryujinx.Graphics.Gpu.Memory;
|
using Ryujinx.Graphics.Gpu.Memory;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Gpu.Image
|
namespace Ryujinx.Graphics.Gpu.Image
|
||||||
{
|
{
|
||||||
|
@ -16,6 +17,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
protected GpuContext Context;
|
protected GpuContext Context;
|
||||||
protected PhysicalMemory PhysicalMemory;
|
protected PhysicalMemory PhysicalMemory;
|
||||||
protected int SequenceNumber;
|
protected int SequenceNumber;
|
||||||
|
protected int ModifiedSequenceNumber;
|
||||||
|
|
||||||
protected T1[] Items;
|
protected T1[] Items;
|
||||||
protected T2[] DescriptorCache;
|
protected T2[] DescriptorCache;
|
||||||
|
@ -41,6 +43,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
private readonly CpuMultiRegionHandle _memoryTracking;
|
private readonly CpuMultiRegionHandle _memoryTracking;
|
||||||
private readonly Action<ulong, ulong> _modifiedDelegate;
|
private readonly Action<ulong, ulong> _modifiedDelegate;
|
||||||
|
|
||||||
|
private int _modifiedSequenceOffset;
|
||||||
|
private bool _modified;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new instance of the GPU resource pool.
|
/// Creates a new instance of the GPU resource pool.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -79,6 +84,16 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
return PhysicalMemory.Read<T2>(Address + (ulong)id * DescriptorSize);
|
return PhysicalMemory.Read<T2>(Address + (ulong)id * DescriptorSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a reference to the descriptor for a given ID.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">ID of the descriptor. This is effectively a zero-based index</param>
|
||||||
|
/// <returns>A reference to the descriptor</returns>
|
||||||
|
public ref readonly T2 GetDescriptorRef(int id)
|
||||||
|
{
|
||||||
|
return ref MemoryMarshal.Cast<byte, T2>(PhysicalMemory.GetSpan(Address + (ulong)id * DescriptorSize, DescriptorSize))[0];
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the GPU resource with the given ID.
|
/// Gets the GPU resource with the given ID.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -93,7 +108,13 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void SynchronizeMemory()
|
public void SynchronizeMemory()
|
||||||
{
|
{
|
||||||
|
_modified = false;
|
||||||
_memoryTracking.QueryModified(_modifiedDelegate);
|
_memoryTracking.QueryModified(_modifiedDelegate);
|
||||||
|
|
||||||
|
if (_modified)
|
||||||
|
{
|
||||||
|
UpdateModifiedSequence();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -103,6 +124,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
/// <param name="mSize">Size of the modified region</param>
|
/// <param name="mSize">Size of the modified region</param>
|
||||||
private void RegionModified(ulong mAddress, ulong mSize)
|
private void RegionModified(ulong mAddress, ulong mSize)
|
||||||
{
|
{
|
||||||
|
_modified = true;
|
||||||
|
|
||||||
if (mAddress < Address)
|
if (mAddress < Address)
|
||||||
{
|
{
|
||||||
mAddress = Address;
|
mAddress = Address;
|
||||||
|
@ -118,6 +141,15 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
InvalidateRangeImpl(mAddress, mSize);
|
InvalidateRangeImpl(mAddress, mSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the modified sequence number using the current sequence number and offset,
|
||||||
|
/// indicating that it has been modified.
|
||||||
|
/// </summary>
|
||||||
|
protected void UpdateModifiedSequence()
|
||||||
|
{
|
||||||
|
ModifiedSequenceNumber = SequenceNumber + _modifiedSequenceOffset;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An action to be performed when a precise memory access occurs to this resource.
|
/// An action to be performed when a precise memory access occurs to this resource.
|
||||||
/// Makes sure that the dirty flags are checked.
|
/// Makes sure that the dirty flags are checked.
|
||||||
|
@ -129,6 +161,16 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
{
|
{
|
||||||
if (write && Context.SequenceNumber == SequenceNumber)
|
if (write && Context.SequenceNumber == SequenceNumber)
|
||||||
{
|
{
|
||||||
|
if (ModifiedSequenceNumber == SequenceNumber + _modifiedSequenceOffset)
|
||||||
|
{
|
||||||
|
// The modified sequence number is offset when PreciseActions occur so that
|
||||||
|
// users checking it will see an increment and know the pool has changed since
|
||||||
|
// their last look, even though the main SequenceNumber has not been changed.
|
||||||
|
|
||||||
|
_modifiedSequenceOffset++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force the pool to be checked again the next time it is used.
|
||||||
SequenceNumber--;
|
SequenceNumber--;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
/// </summary>
|
/// </summary>
|
||||||
class Sampler : IDisposable
|
class Sampler : IDisposable
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// True if the sampler is disposed, false otherwise.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsDisposed { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Host sampler object.
|
/// Host sampler object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -101,6 +106,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
IsDisposed = true;
|
||||||
|
|
||||||
_hostSampler.Dispose();
|
_hostSampler.Dispose();
|
||||||
_anisoSampler?.Dispose();
|
_anisoSampler?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
Items[i] = null;
|
Items[i] = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UpdateModifiedSequence();
|
||||||
}
|
}
|
||||||
|
|
||||||
SequenceNumber = Context.SequenceNumber;
|
SequenceNumber = Context.SequenceNumber;
|
||||||
|
@ -71,6 +73,39 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
return sampler;
|
return sampler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the pool was modified, and returns the last sequence number where a modification was detected.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A number that increments each time a modification is detected</returns>
|
||||||
|
public int CheckModified()
|
||||||
|
{
|
||||||
|
if (SequenceNumber != Context.SequenceNumber)
|
||||||
|
{
|
||||||
|
SequenceNumber = Context.SequenceNumber;
|
||||||
|
|
||||||
|
if (_forcedAnisotropy != GraphicsConfig.MaxAnisotropy)
|
||||||
|
{
|
||||||
|
_forcedAnisotropy = GraphicsConfig.MaxAnisotropy;
|
||||||
|
|
||||||
|
for (int i = 0; i < Items.Length; i++)
|
||||||
|
{
|
||||||
|
if (Items[i] != null)
|
||||||
|
{
|
||||||
|
Items[i].Dispose();
|
||||||
|
|
||||||
|
Items[i] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateModifiedSequence();
|
||||||
|
}
|
||||||
|
|
||||||
|
SynchronizeMemory();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ModifiedSequenceNumber;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Implementation of the sampler pool range invalidation.
|
/// Implementation of the sampler pool range invalidation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -100,6 +100,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool AlwaysFlushOnOverlap { get; private set; }
|
public bool AlwaysFlushOnOverlap { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Increments when the host texture is swapped, or when the texture is removed from all pools.
|
||||||
|
/// </summary>
|
||||||
|
public int InvalidatedSequence { get; private set; }
|
||||||
|
|
||||||
private int _depth;
|
private int _depth;
|
||||||
private int _layers;
|
private int _layers;
|
||||||
public int FirstLayer { get; private set; }
|
public int FirstLayer { get; private set; }
|
||||||
|
@ -821,20 +826,25 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
depth,
|
depth,
|
||||||
levels,
|
levels,
|
||||||
layers,
|
layers,
|
||||||
out Span<byte> decoded))
|
out byte[] decoded))
|
||||||
{
|
{
|
||||||
string texInfo = $"{Info.Target} {Info.FormatInfo.Format} {Info.Width}x{Info.Height}x{Info.DepthOrLayers} levels {Info.Levels}";
|
string texInfo = $"{Info.Target} {Info.FormatInfo.Format} {Info.Width}x{Info.Height}x{Info.DepthOrLayers} levels {Info.Levels}";
|
||||||
|
|
||||||
Logger.Debug?.Print(LogClass.Gpu, $"Invalid ASTC texture at 0x{Info.GpuAddress:X} ({texInfo}).");
|
Logger.Debug?.Print(LogClass.Gpu, $"Invalid ASTC texture at 0x{Info.GpuAddress:X} ({texInfo}).");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (GraphicsConfig.EnableTextureRecompression)
|
||||||
|
{
|
||||||
|
decoded = BCnEncoder.EncodeBC7(decoded, width, height, depth, levels, layers);
|
||||||
|
}
|
||||||
|
|
||||||
data = decoded;
|
data = decoded;
|
||||||
}
|
}
|
||||||
else if (!_context.Capabilities.SupportsR4G4Format && Format == Format.R4G4Unorm)
|
else if (!_context.Capabilities.SupportsR4G4Format && Format == Format.R4G4Unorm)
|
||||||
{
|
{
|
||||||
data = PixelConverter.ConvertR4G4ToR4G4B4A4(data);
|
data = PixelConverter.ConvertR4G4ToR4G4B4A4(data);
|
||||||
}
|
}
|
||||||
else if (!_context.Capabilities.Supports3DTextureCompression && Target == Target.Texture3D)
|
else if (!TextureCompatibility.HostSupportsBcFormat(Format, Target, _context.Capabilities))
|
||||||
{
|
{
|
||||||
switch (Format)
|
switch (Format)
|
||||||
{
|
{
|
||||||
|
@ -858,6 +868,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
case Format.Bc5Unorm:
|
case Format.Bc5Unorm:
|
||||||
data = BCnDecoder.DecodeBC5(data, width, height, depth, levels, layers, Format == Format.Bc5Snorm);
|
data = BCnDecoder.DecodeBC5(data, width, height, depth, levels, layers, Format == Format.Bc5Snorm);
|
||||||
break;
|
break;
|
||||||
|
case Format.Bc6HSfloat:
|
||||||
|
case Format.Bc6HUfloat:
|
||||||
|
data = BCnDecoder.DecodeBC6(data, width, height, depth, levels, layers, Format == Format.Bc6HSfloat);
|
||||||
|
break;
|
||||||
|
case Format.Bc7Srgb:
|
||||||
|
case Format.Bc7Unorm:
|
||||||
|
data = BCnDecoder.DecodeBC7(data, width, height, depth, levels, layers);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1211,16 +1229,18 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
|
|
||||||
if (_arrayViewTexture == null && IsSameDimensionsTarget(target))
|
if (_arrayViewTexture == null && IsSameDimensionsTarget(target))
|
||||||
{
|
{
|
||||||
|
FormatInfo formatInfo = TextureCompatibility.ToHostCompatibleFormat(Info, _context.Capabilities);
|
||||||
|
|
||||||
TextureCreateInfo createInfo = new TextureCreateInfo(
|
TextureCreateInfo createInfo = new TextureCreateInfo(
|
||||||
Info.Width,
|
Info.Width,
|
||||||
Info.Height,
|
Info.Height,
|
||||||
target == Target.CubemapArray ? 6 : 1,
|
target == Target.CubemapArray ? 6 : 1,
|
||||||
Info.Levels,
|
Info.Levels,
|
||||||
Info.Samples,
|
Info.Samples,
|
||||||
Info.FormatInfo.BlockWidth,
|
formatInfo.BlockWidth,
|
||||||
Info.FormatInfo.BlockHeight,
|
formatInfo.BlockHeight,
|
||||||
Info.FormatInfo.BytesPerPixel,
|
formatInfo.BytesPerPixel,
|
||||||
Info.FormatInfo.Format,
|
formatInfo.Format,
|
||||||
Info.DepthStencilMode,
|
Info.DepthStencilMode,
|
||||||
target,
|
target,
|
||||||
Info.SwizzleR,
|
Info.SwizzleR,
|
||||||
|
@ -1407,6 +1427,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
DisposeTextures();
|
DisposeTextures();
|
||||||
|
|
||||||
HostTexture = hostTexture;
|
HostTexture = hostTexture;
|
||||||
|
InvalidatedSequence++;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1535,6 +1556,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
|
|
||||||
_poolOwners.Clear();
|
_poolOwners.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InvalidatedSequence++;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
using Ryujinx.Graphics.Gpu.Engine.Types;
|
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||||
|
using Ryujinx.Graphics.Gpu.Memory;
|
||||||
|
using Ryujinx.Graphics.Gpu.Shader;
|
||||||
using Ryujinx.Graphics.Shader;
|
using Ryujinx.Graphics.Shader;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Gpu.Image
|
namespace Ryujinx.Graphics.Gpu.Image
|
||||||
{
|
{
|
||||||
|
@ -31,21 +35,30 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
private readonly TextureBindingInfo[][] _textureBindings;
|
private readonly TextureBindingInfo[][] _textureBindings;
|
||||||
private readonly TextureBindingInfo[][] _imageBindings;
|
private readonly TextureBindingInfo[][] _imageBindings;
|
||||||
|
|
||||||
private struct TextureStatePerStage
|
private struct TextureState
|
||||||
{
|
{
|
||||||
public ITexture Texture;
|
public ITexture Texture;
|
||||||
public ISampler Sampler;
|
public ISampler Sampler;
|
||||||
|
|
||||||
|
public int TextureHandle;
|
||||||
|
public int SamplerHandle;
|
||||||
|
public int InvalidatedSequence;
|
||||||
|
public Texture CachedTexture;
|
||||||
|
public Sampler CachedSampler;
|
||||||
|
public int ScaleIndex;
|
||||||
|
public TextureUsageFlags UsageFlags;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly TextureStatePerStage[][] _textureState;
|
private TextureState[] _textureState;
|
||||||
private readonly TextureStatePerStage[][] _imageState;
|
private TextureState[] _imageState;
|
||||||
|
|
||||||
private int[] _textureBindingsCount;
|
private int[] _textureBindingsCount;
|
||||||
private int[] _imageBindingsCount;
|
private int[] _imageBindingsCount;
|
||||||
|
|
||||||
private int _textureBufferIndex;
|
private int _texturePoolSequence;
|
||||||
|
private int _samplerPoolSequence;
|
||||||
|
|
||||||
private bool _rebind;
|
private int _textureBufferIndex;
|
||||||
|
|
||||||
private readonly float[] _scales;
|
private readonly float[] _scales;
|
||||||
private bool _scaleChanged;
|
private bool _scaleChanged;
|
||||||
|
@ -72,8 +85,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
_textureBindings = new TextureBindingInfo[stages][];
|
_textureBindings = new TextureBindingInfo[stages][];
|
||||||
_imageBindings = new TextureBindingInfo[stages][];
|
_imageBindings = new TextureBindingInfo[stages][];
|
||||||
|
|
||||||
_textureState = new TextureStatePerStage[stages][];
|
_textureState = new TextureState[InitialTextureStateSize];
|
||||||
_imageState = new TextureStatePerStage[stages][];
|
_imageState = new TextureState[InitialImageStateSize];
|
||||||
|
|
||||||
_textureBindingsCount = new int[stages];
|
_textureBindingsCount = new int[stages];
|
||||||
_imageBindingsCount = new int[stages];
|
_imageBindingsCount = new int[stages];
|
||||||
|
@ -82,9 +95,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
{
|
{
|
||||||
_textureBindings[stage] = new TextureBindingInfo[InitialTextureStateSize];
|
_textureBindings[stage] = new TextureBindingInfo[InitialTextureStateSize];
|
||||||
_imageBindings[stage] = new TextureBindingInfo[InitialImageStateSize];
|
_imageBindings[stage] = new TextureBindingInfo[InitialImageStateSize];
|
||||||
|
|
||||||
_textureState[stage] = new TextureStatePerStage[InitialTextureStateSize];
|
|
||||||
_imageState[stage] = new TextureStatePerStage[InitialImageStateSize];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,15 +109,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
if (count > _textureBindings[stage].Length)
|
if (count > _textureBindings[stage].Length)
|
||||||
{
|
{
|
||||||
Array.Resize(ref _textureBindings[stage], count);
|
Array.Resize(ref _textureBindings[stage], count);
|
||||||
Array.Resize(ref _textureState[stage], count);
|
|
||||||
}
|
|
||||||
|
|
||||||
int toClear = Math.Max(_textureBindingsCount[stage], count);
|
|
||||||
TextureStatePerStage[] state = _textureState[stage];
|
|
||||||
|
|
||||||
for (int i = 0; i < toClear; i++)
|
|
||||||
{
|
|
||||||
state[i] = new TextureStatePerStage();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_textureBindingsCount[stage] = count;
|
_textureBindingsCount[stage] = count;
|
||||||
|
@ -126,15 +127,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
if (count > _imageBindings[stage].Length)
|
if (count > _imageBindings[stage].Length)
|
||||||
{
|
{
|
||||||
Array.Resize(ref _imageBindings[stage], count);
|
Array.Resize(ref _imageBindings[stage], count);
|
||||||
Array.Resize(ref _imageState[stage], count);
|
|
||||||
}
|
|
||||||
|
|
||||||
int toClear = Math.Max(_imageBindingsCount[stage], count);
|
|
||||||
TextureStatePerStage[] state = _imageState[stage];
|
|
||||||
|
|
||||||
for (int i = 0; i < toClear; i++)
|
|
||||||
{
|
|
||||||
state[i] = new TextureStatePerStage();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_imageBindingsCount[stage] = count;
|
_imageBindingsCount[stage] = count;
|
||||||
|
@ -142,6 +134,24 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
return _imageBindings[stage];
|
return _imageBindings[stage];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the max binding indexes for textures and images.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="maxTextureBinding">The maximum texture binding</param>
|
||||||
|
/// <param name="maxImageBinding">The maximum image binding</param>
|
||||||
|
public void SetMaxBindings(int maxTextureBinding, int maxImageBinding)
|
||||||
|
{
|
||||||
|
if (maxTextureBinding >= _textureState.Length)
|
||||||
|
{
|
||||||
|
Array.Resize(ref _textureState, maxTextureBinding + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxImageBinding >= _imageState.Length)
|
||||||
|
{
|
||||||
|
Array.Resize(ref _imageState, maxImageBinding + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the textures constant buffer index.
|
/// Sets the textures constant buffer index.
|
||||||
/// The constant buffer specified holds the texture handles.
|
/// The constant buffer specified holds the texture handles.
|
||||||
|
@ -222,18 +232,18 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
/// Updates the texture scale for a given texture or image.
|
/// Updates the texture scale for a given texture or image.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="texture">Start GPU virtual address of the pool</param>
|
/// <param name="texture">Start GPU virtual address of the pool</param>
|
||||||
/// <param name="binding">The related texture binding</param>
|
/// <param name="usageFlags">The related texture usage flags</param>
|
||||||
/// <param name="index">The texture/image binding index</param>
|
/// <param name="index">The texture/image binding index</param>
|
||||||
/// <param name="stage">The active shader stage</param>
|
/// <param name="stage">The active shader stage</param>
|
||||||
/// <returns>True if the given texture has become blacklisted, indicating that its host texture may have changed.</returns>
|
/// <returns>True if the given texture has become blacklisted, indicating that its host texture may have changed.</returns>
|
||||||
private bool UpdateScale(Texture texture, TextureBindingInfo binding, int index, ShaderStage stage)
|
private bool UpdateScale(Texture texture, TextureUsageFlags usageFlags, int index, ShaderStage stage)
|
||||||
{
|
{
|
||||||
float result = 1f;
|
float result = 1f;
|
||||||
bool changed = false;
|
bool changed = false;
|
||||||
|
|
||||||
if ((binding.Flags & TextureUsageFlags.NeedsScaleValue) != 0 && texture != null)
|
if ((usageFlags & TextureUsageFlags.NeedsScaleValue) != 0 && texture != null)
|
||||||
{
|
{
|
||||||
if ((binding.Flags & TextureUsageFlags.ResScaleUnsupported) != 0)
|
if ((usageFlags & TextureUsageFlags.ResScaleUnsupported) != 0)
|
||||||
{
|
{
|
||||||
changed = texture.ScaleMode != TextureScaleMode.Blacklisted;
|
changed = texture.ScaleMode != TextureScaleMode.Blacklisted;
|
||||||
texture.BlacklistScale();
|
texture.BlacklistScale();
|
||||||
|
@ -284,6 +294,35 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
return changed;
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines if the vertex stage requires a scale value.
|
||||||
|
/// </summary>
|
||||||
|
private bool VertexRequiresScale()
|
||||||
|
{
|
||||||
|
bool requiresScale = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < _textureBindingsCount[0]; i++)
|
||||||
|
{
|
||||||
|
if ((_textureBindings[0][i].Flags & TextureUsageFlags.NeedsScaleValue) != 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!requiresScale)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _imageBindingsCount[0]; i++)
|
||||||
|
{
|
||||||
|
if ((_imageBindings[0][i].Flags & TextureUsageFlags.NeedsScaleValue) != 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Uploads texture and image scales to the backend when they are used.
|
/// Uploads texture and image scales to the backend when they are used.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -295,10 +334,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
int fragmentIndex = (int)ShaderStage.Fragment - 1;
|
int fragmentIndex = (int)ShaderStage.Fragment - 1;
|
||||||
int fragmentTotal = _isCompute ? 0 : (_textureBindingsCount[fragmentIndex] + _imageBindingsCount[fragmentIndex]);
|
int fragmentTotal = _isCompute ? 0 : (_textureBindingsCount[fragmentIndex] + _imageBindingsCount[fragmentIndex]);
|
||||||
|
|
||||||
if (total != 0 && fragmentTotal != _lastFragmentTotal)
|
if (total != 0 && fragmentTotal != _lastFragmentTotal && VertexRequiresScale())
|
||||||
{
|
{
|
||||||
// Must update scales in the support buffer if:
|
// Must update scales in the support buffer if:
|
||||||
// - Vertex stage has bindings.
|
// - Vertex stage has bindings that require scale.
|
||||||
// - Fragment stage binding count has been updated since last render scale update.
|
// - Fragment stage binding count has been updated since last render scale update.
|
||||||
|
|
||||||
_scaleChanged = true;
|
_scaleChanged = true;
|
||||||
|
@ -323,7 +362,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
/// Ensures that the bindings are visible to the host GPU.
|
/// Ensures that the bindings are visible to the host GPU.
|
||||||
/// Note: this actually performs the binding using the host graphics API.
|
/// Note: this actually performs the binding using the host graphics API.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void CommitBindings()
|
/// <param name="specState">Specialization state for the bound shader</param>
|
||||||
|
/// <returns>True if all bound textures match the current shader specialiation state, false otherwise</returns>
|
||||||
|
public bool CommitBindings(ShaderSpecializationState specState)
|
||||||
{
|
{
|
||||||
ulong texturePoolAddress = _texturePoolAddress;
|
ulong texturePoolAddress = _texturePoolAddress;
|
||||||
|
|
||||||
|
@ -331,10 +372,38 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
? _texturePoolCache.FindOrCreate(_channel, texturePoolAddress, _texturePoolMaximumId)
|
? _texturePoolCache.FindOrCreate(_channel, texturePoolAddress, _texturePoolMaximumId)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
// Check if the texture pool has been modified since bindings were last committed.
|
||||||
|
// If it wasn't, then it's possible to avoid looking up textures again when the handle remains the same.
|
||||||
|
bool poolModified = false;
|
||||||
|
|
||||||
|
if (texturePool != null)
|
||||||
|
{
|
||||||
|
int texturePoolSequence = texturePool.CheckModified();
|
||||||
|
|
||||||
|
if (_texturePoolSequence != texturePoolSequence)
|
||||||
|
{
|
||||||
|
poolModified = true;
|
||||||
|
_texturePoolSequence = texturePoolSequence;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_samplerPool != null)
|
||||||
|
{
|
||||||
|
int samplerPoolSequence = _samplerPool.CheckModified();
|
||||||
|
|
||||||
|
if (_samplerPoolSequence != samplerPoolSequence)
|
||||||
|
{
|
||||||
|
poolModified = true;
|
||||||
|
_samplerPoolSequence = samplerPoolSequence;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool specStateMatches = true;
|
||||||
|
|
||||||
if (_isCompute)
|
if (_isCompute)
|
||||||
{
|
{
|
||||||
CommitTextureBindings(texturePool, ShaderStage.Compute, 0);
|
specStateMatches &= CommitTextureBindings(texturePool, ShaderStage.Compute, 0, poolModified, specState);
|
||||||
CommitImageBindings (texturePool, ShaderStage.Compute, 0);
|
specStateMatches &= CommitImageBindings(texturePool, ShaderStage.Compute, 0, poolModified, specState);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -342,14 +411,76 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
{
|
{
|
||||||
int stageIndex = (int)stage - 1;
|
int stageIndex = (int)stage - 1;
|
||||||
|
|
||||||
CommitTextureBindings(texturePool, stage, stageIndex);
|
specStateMatches &= CommitTextureBindings(texturePool, stage, stageIndex, poolModified, specState);
|
||||||
CommitImageBindings (texturePool, stage, stageIndex);
|
specStateMatches &= CommitImageBindings(texturePool, stage, stageIndex, poolModified, specState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CommitRenderScale();
|
CommitRenderScale();
|
||||||
|
|
||||||
_rebind = false;
|
return specStateMatches;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fetch the constant buffers used for a texture to cache.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stageIndex">Stage index of the constant buffer</param>
|
||||||
|
/// <param name="cachedTextureBufferIndex">The currently cached texture buffer index</param>
|
||||||
|
/// <param name="cachedSamplerBufferIndex">The currently cached sampler buffer index</param>
|
||||||
|
/// <param name="cachedTextureBuffer">The currently cached texture buffer data</param>
|
||||||
|
/// <param name="cachedSamplerBuffer">The currently cached sampler buffer data</param>
|
||||||
|
/// <param name="textureBufferIndex">The new texture buffer index</param>
|
||||||
|
/// <param name="samplerBufferIndex">The new sampler buffer index</param>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private void UpdateCachedBuffer(
|
||||||
|
int stageIndex,
|
||||||
|
ref int cachedTextureBufferIndex,
|
||||||
|
ref int cachedSamplerBufferIndex,
|
||||||
|
ref ReadOnlySpan<int> cachedTextureBuffer,
|
||||||
|
ref ReadOnlySpan<int> cachedSamplerBuffer,
|
||||||
|
int textureBufferIndex,
|
||||||
|
int samplerBufferIndex)
|
||||||
|
{
|
||||||
|
if (textureBufferIndex != cachedTextureBufferIndex)
|
||||||
|
{
|
||||||
|
ref BufferBounds bounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, textureBufferIndex);
|
||||||
|
|
||||||
|
cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(bounds.Address, (int)bounds.Size));
|
||||||
|
cachedTextureBufferIndex = textureBufferIndex;
|
||||||
|
|
||||||
|
if (samplerBufferIndex == textureBufferIndex)
|
||||||
|
{
|
||||||
|
cachedSamplerBuffer = cachedTextureBuffer;
|
||||||
|
cachedSamplerBufferIndex = samplerBufferIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (samplerBufferIndex != cachedSamplerBufferIndex)
|
||||||
|
{
|
||||||
|
ref BufferBounds bounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, samplerBufferIndex);
|
||||||
|
|
||||||
|
cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(bounds.Address, (int)bounds.Size));
|
||||||
|
cachedSamplerBufferIndex = samplerBufferIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Counts the total number of texture bindings used by all shader stages.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The total amount of textures used</returns>
|
||||||
|
private int GetTextureBindingsCount()
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < _textureBindings.Length; i++)
|
||||||
|
{
|
||||||
|
if (_textureBindings[i] != null)
|
||||||
|
{
|
||||||
|
count += _textureBindings[i].Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -358,13 +489,16 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="pool">The current texture pool</param>
|
/// <param name="pool">The current texture pool</param>
|
||||||
/// <param name="stage">The shader stage using the textures to be bound</param>
|
/// <param name="stage">The shader stage using the textures to be bound</param>
|
||||||
/// <param name="stageIndex">The stage number of the specified shader stage</param>
|
/// <param name="stageIndex">The stage number of the specified shader stage</param
|
||||||
private void CommitTextureBindings(TexturePool pool, ShaderStage stage, int stageIndex)
|
/// <param name="poolModified">True if either the texture or sampler pool was modified, false otherwise</param>
|
||||||
|
/// <param name="specState">Specialization state for the bound shader</param>
|
||||||
|
/// <returns>True if all bound textures match the current shader specialiation state, false otherwise</returns>
|
||||||
|
private bool CommitTextureBindings(TexturePool pool, ShaderStage stage, int stageIndex, bool poolModified, ShaderSpecializationState specState)
|
||||||
{
|
{
|
||||||
int textureCount = _textureBindingsCount[stageIndex];
|
int textureCount = _textureBindingsCount[stageIndex];
|
||||||
if (textureCount == 0)
|
if (textureCount == 0)
|
||||||
{
|
{
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var samplerPool = _samplerPool;
|
var samplerPool = _samplerPool;
|
||||||
|
@ -372,17 +506,27 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
if (pool == null)
|
if (pool == null)
|
||||||
{
|
{
|
||||||
Logger.Error?.Print(LogClass.Gpu, $"Shader stage \"{stage}\" uses textures, but texture pool was not set.");
|
Logger.Error?.Print(LogClass.Gpu, $"Shader stage \"{stage}\" uses textures, but texture pool was not set.");
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool specStateMatches = true;
|
||||||
|
|
||||||
|
int cachedTextureBufferIndex = -1;
|
||||||
|
int cachedSamplerBufferIndex = -1;
|
||||||
|
ReadOnlySpan<int> cachedTextureBuffer = Span<int>.Empty;
|
||||||
|
ReadOnlySpan<int> cachedSamplerBuffer = Span<int>.Empty;
|
||||||
|
|
||||||
for (int index = 0; index < textureCount; index++)
|
for (int index = 0; index < textureCount; index++)
|
||||||
{
|
{
|
||||||
TextureBindingInfo bindingInfo = _textureBindings[stageIndex][index];
|
TextureBindingInfo bindingInfo = _textureBindings[stageIndex][index];
|
||||||
|
TextureUsageFlags usageFlags = bindingInfo.Flags;
|
||||||
|
|
||||||
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex);
|
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex);
|
||||||
|
|
||||||
int packedId = ReadPackedId(stageIndex, bindingInfo.Handle, textureBufferIndex, samplerBufferIndex);
|
UpdateCachedBuffer(stageIndex, ref cachedTextureBufferIndex, ref cachedSamplerBufferIndex, ref cachedTextureBuffer, ref cachedSamplerBuffer, textureBufferIndex, samplerBufferIndex);
|
||||||
int textureId = UnpackTextureId(packedId);
|
|
||||||
|
int packedId = TextureHandle.ReadPackedId(bindingInfo.Handle, cachedTextureBuffer, cachedSamplerBuffer);
|
||||||
|
int textureId = TextureHandle.UnpackTextureId(packedId);
|
||||||
int samplerId;
|
int samplerId;
|
||||||
|
|
||||||
if (_samplerIndex == SamplerIndex.ViaHeaderIndex)
|
if (_samplerIndex == SamplerIndex.ViaHeaderIndex)
|
||||||
|
@ -391,46 +535,80 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
samplerId = UnpackSamplerId(packedId);
|
samplerId = TextureHandle.UnpackSamplerId(packedId);
|
||||||
}
|
}
|
||||||
|
|
||||||
Texture texture = pool.Get(textureId);
|
ref TextureState state = ref _textureState[bindingInfo.Binding];
|
||||||
|
|
||||||
|
if (!poolModified &&
|
||||||
|
state.TextureHandle == textureId &&
|
||||||
|
state.SamplerHandle == samplerId &&
|
||||||
|
state.CachedTexture != null &&
|
||||||
|
state.CachedTexture.InvalidatedSequence == state.InvalidatedSequence &&
|
||||||
|
state.CachedSampler?.IsDisposed != true)
|
||||||
|
{
|
||||||
|
// The texture is already bound.
|
||||||
|
state.CachedTexture.SynchronizeMemory();
|
||||||
|
|
||||||
|
if ((state.ScaleIndex != index || state.UsageFlags != usageFlags) &&
|
||||||
|
UpdateScale(state.CachedTexture, usageFlags, index, stage))
|
||||||
|
{
|
||||||
|
ITexture hostTextureRebind = state.CachedTexture.GetTargetTexture(bindingInfo.Target);
|
||||||
|
|
||||||
|
state.Texture = hostTextureRebind;
|
||||||
|
state.ScaleIndex = index;
|
||||||
|
state.UsageFlags = usageFlags;
|
||||||
|
|
||||||
|
_context.Renderer.Pipeline.SetTextureAndSampler(stage, bindingInfo.Binding, hostTextureRebind, state.Sampler);
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.TextureHandle = textureId;
|
||||||
|
state.SamplerHandle = samplerId;
|
||||||
|
|
||||||
|
ref readonly TextureDescriptor descriptor = ref pool.GetForBinding(textureId, out Texture texture);
|
||||||
|
|
||||||
|
specStateMatches &= specState.MatchesTexture(stage, index, descriptor);
|
||||||
|
|
||||||
|
Sampler sampler = _samplerPool?.Get(samplerId);
|
||||||
|
|
||||||
ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
|
ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
|
||||||
|
ISampler hostSampler = sampler?.GetHostSampler(texture);
|
||||||
|
|
||||||
if (hostTexture != null && texture.Target == Target.TextureBuffer)
|
if (hostTexture != null && texture.Target == Target.TextureBuffer)
|
||||||
{
|
{
|
||||||
// Ensure that the buffer texture is using the correct buffer as storage.
|
// Ensure that the buffer texture is using the correct buffer as storage.
|
||||||
// Buffers are frequently re-created to accomodate larger data, so we need to re-bind
|
// Buffers are frequently re-created to accomodate larger data, so we need to re-bind
|
||||||
// to ensure we're not using a old buffer that was already deleted.
|
// to ensure we're not using a old buffer that was already deleted.
|
||||||
_channel.BufferManager.SetBufferTextureStorage(hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, bindingInfo, bindingInfo.Format, false);
|
_channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, bindingInfo, bindingInfo.Format, false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (_textureState[stageIndex][index].Texture != hostTexture || _rebind)
|
if (state.Texture != hostTexture || state.Sampler != hostSampler)
|
||||||
{
|
{
|
||||||
if (UpdateScale(texture, bindingInfo, index, stage))
|
if (UpdateScale(texture, usageFlags, index, stage))
|
||||||
{
|
{
|
||||||
hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
|
hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
|
||||||
}
|
}
|
||||||
|
|
||||||
_textureState[stageIndex][index].Texture = hostTexture;
|
state.Texture = hostTexture;
|
||||||
|
state.ScaleIndex = index;
|
||||||
|
state.UsageFlags = usageFlags;
|
||||||
|
|
||||||
_context.Renderer.Pipeline.SetTexture(bindingInfo.Binding, hostTexture);
|
state.Sampler = hostSampler;
|
||||||
|
|
||||||
|
_context.Renderer.Pipeline.SetTextureAndSampler(stage, bindingInfo.Binding, hostTexture, hostSampler);
|
||||||
}
|
}
|
||||||
|
|
||||||
Sampler sampler = samplerPool?.Get(samplerId);
|
state.CachedTexture = texture;
|
||||||
|
state.CachedSampler = sampler;
|
||||||
ISampler hostSampler = sampler?.GetHostSampler(texture);
|
state.InvalidatedSequence = texture?.InvalidatedSequence ?? 0;
|
||||||
|
|
||||||
if (_textureState[stageIndex][index].Sampler != hostSampler || _rebind)
|
|
||||||
{
|
|
||||||
_textureState[stageIndex][index].Sampler = hostSampler;
|
|
||||||
|
|
||||||
_context.Renderer.Pipeline.SetSampler(bindingInfo.Binding, hostSampler);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return specStateMatches;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -440,38 +618,90 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
/// <param name="pool">The current texture pool</param>
|
/// <param name="pool">The current texture pool</param>
|
||||||
/// <param name="stage">The shader stage using the textures to be bound</param>
|
/// <param name="stage">The shader stage using the textures to be bound</param>
|
||||||
/// <param name="stageIndex">The stage number of the specified shader stage</param>
|
/// <param name="stageIndex">The stage number of the specified shader stage</param>
|
||||||
private void CommitImageBindings(TexturePool pool, ShaderStage stage, int stageIndex)
|
/// <param name="poolModified">True if either the texture or sampler pool was modified, false otherwise</param>
|
||||||
|
/// <param name="specState">Specialization state for the bound shader</param>
|
||||||
|
/// <returns>True if all bound images match the current shader specialiation state, false otherwise</returns>
|
||||||
|
private bool CommitImageBindings(TexturePool pool, ShaderStage stage, int stageIndex, bool poolModified, ShaderSpecializationState specState)
|
||||||
{
|
{
|
||||||
int imageCount = _imageBindingsCount[stageIndex];
|
int imageCount = _imageBindingsCount[stageIndex];
|
||||||
if (imageCount == 0)
|
if (imageCount == 0)
|
||||||
{
|
{
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pool == null)
|
if (pool == null)
|
||||||
{
|
{
|
||||||
Logger.Error?.Print(LogClass.Gpu, $"Shader stage \"{stage}\" uses images, but texture pool was not set.");
|
Logger.Error?.Print(LogClass.Gpu, $"Shader stage \"{stage}\" uses images, but texture pool was not set.");
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scales for images appear after the texture ones.
|
// Scales for images appear after the texture ones.
|
||||||
int baseScaleIndex = _textureBindingsCount[stageIndex];
|
int baseScaleIndex = _textureBindingsCount[stageIndex];
|
||||||
|
|
||||||
|
int cachedTextureBufferIndex = -1;
|
||||||
|
int cachedSamplerBufferIndex = -1;
|
||||||
|
ReadOnlySpan<int> cachedTextureBuffer = Span<int>.Empty;
|
||||||
|
ReadOnlySpan<int> cachedSamplerBuffer = Span<int>.Empty;
|
||||||
|
|
||||||
|
bool specStateMatches = true;
|
||||||
|
|
||||||
for (int index = 0; index < imageCount; index++)
|
for (int index = 0; index < imageCount; index++)
|
||||||
{
|
{
|
||||||
TextureBindingInfo bindingInfo = _imageBindings[stageIndex][index];
|
TextureBindingInfo bindingInfo = _imageBindings[stageIndex][index];
|
||||||
|
TextureUsageFlags usageFlags = bindingInfo.Flags;
|
||||||
|
int scaleIndex = baseScaleIndex + index;
|
||||||
|
|
||||||
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex);
|
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex);
|
||||||
|
|
||||||
int packedId = ReadPackedId(stageIndex, bindingInfo.Handle, textureBufferIndex, samplerBufferIndex);
|
UpdateCachedBuffer(stageIndex, ref cachedTextureBufferIndex, ref cachedSamplerBufferIndex, ref cachedTextureBuffer, ref cachedSamplerBuffer, textureBufferIndex, samplerBufferIndex);
|
||||||
int textureId = UnpackTextureId(packedId);
|
|
||||||
|
|
||||||
Texture texture = pool.Get(textureId);
|
int packedId = TextureHandle.ReadPackedId(bindingInfo.Handle, cachedTextureBuffer, cachedSamplerBuffer);
|
||||||
|
int textureId = TextureHandle.UnpackTextureId(packedId);
|
||||||
|
|
||||||
ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
|
ref TextureState state = ref _imageState[bindingInfo.Binding];
|
||||||
|
|
||||||
bool isStore = bindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore);
|
bool isStore = bindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore);
|
||||||
|
|
||||||
|
if (!poolModified &&
|
||||||
|
state.TextureHandle == textureId &&
|
||||||
|
state.CachedTexture != null &&
|
||||||
|
state.CachedTexture.InvalidatedSequence == state.InvalidatedSequence)
|
||||||
|
{
|
||||||
|
Texture cachedTexture = state.CachedTexture;
|
||||||
|
|
||||||
|
// The texture is already bound.
|
||||||
|
cachedTexture.SynchronizeMemory();
|
||||||
|
|
||||||
|
if (isStore)
|
||||||
|
{
|
||||||
|
cachedTexture?.SignalModified();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((state.ScaleIndex != index || state.UsageFlags != usageFlags) &&
|
||||||
|
UpdateScale(state.CachedTexture, usageFlags, scaleIndex, stage))
|
||||||
|
{
|
||||||
|
ITexture hostTextureRebind = state.CachedTexture.GetTargetTexture(bindingInfo.Target);
|
||||||
|
|
||||||
|
Format format = bindingInfo.Format == 0 ? cachedTexture.Format : bindingInfo.Format;
|
||||||
|
|
||||||
|
state.Texture = hostTextureRebind;
|
||||||
|
state.ScaleIndex = scaleIndex;
|
||||||
|
state.UsageFlags = usageFlags;
|
||||||
|
|
||||||
|
_context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTextureRebind, format);
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.TextureHandle = textureId;
|
||||||
|
|
||||||
|
ref readonly TextureDescriptor descriptor = ref pool.GetForBinding(textureId, out Texture texture);
|
||||||
|
|
||||||
|
specStateMatches &= specState.MatchesImage(stage, index, descriptor);
|
||||||
|
|
||||||
|
ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
|
||||||
|
|
||||||
if (hostTexture != null && texture.Target == Target.TextureBuffer)
|
if (hostTexture != null && texture.Target == Target.TextureBuffer)
|
||||||
{
|
{
|
||||||
// Ensure that the buffer texture is using the correct buffer as storage.
|
// Ensure that the buffer texture is using the correct buffer as storage.
|
||||||
|
@ -485,7 +715,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
format = texture.Format;
|
format = texture.Format;
|
||||||
}
|
}
|
||||||
|
|
||||||
_channel.BufferManager.SetBufferTextureStorage(hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, bindingInfo, format, true);
|
_channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, bindingInfo, format, true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -494,14 +724,16 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
texture?.SignalModified();
|
texture?.SignalModified();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_imageState[stageIndex][index].Texture != hostTexture || _rebind)
|
if (state.Texture != hostTexture)
|
||||||
{
|
{
|
||||||
if (UpdateScale(texture, bindingInfo, baseScaleIndex + index, stage))
|
if (UpdateScale(texture, usageFlags, scaleIndex, stage))
|
||||||
{
|
{
|
||||||
hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
|
hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
|
||||||
}
|
}
|
||||||
|
|
||||||
_imageState[stageIndex][index].Texture = hostTexture;
|
state.Texture = hostTexture;
|
||||||
|
state.ScaleIndex = scaleIndex;
|
||||||
|
state.UsageFlags = usageFlags;
|
||||||
|
|
||||||
Format format = bindingInfo.Format;
|
Format format = bindingInfo.Format;
|
||||||
|
|
||||||
|
@ -512,8 +744,13 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
|
|
||||||
_context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTexture, format);
|
_context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTexture, format);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state.CachedTexture = texture;
|
||||||
|
state.InvalidatedSequence = texture?.InvalidatedSequence ?? 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return specStateMatches;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -537,7 +774,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(cbufSlot, bufferIndex);
|
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(cbufSlot, bufferIndex);
|
||||||
|
|
||||||
int packedId = ReadPackedId(stageIndex, handle, textureBufferIndex, samplerBufferIndex);
|
int packedId = ReadPackedId(stageIndex, handle, textureBufferIndex, samplerBufferIndex);
|
||||||
int textureId = UnpackTextureId(packedId);
|
int textureId = TextureHandle.UnpackTextureId(packedId);
|
||||||
|
|
||||||
ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa);
|
ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa);
|
||||||
|
|
||||||
|
@ -555,6 +792,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
/// <param name="textureBufferIndex">Index of the constant buffer holding the texture handles</param>
|
/// <param name="textureBufferIndex">Index of the constant buffer holding the texture handles</param>
|
||||||
/// <param name="samplerBufferIndex">Index of the constant buffer holding the sampler handles</param>
|
/// <param name="samplerBufferIndex">Index of the constant buffer holding the sampler handles</param>
|
||||||
/// <returns>The packed texture and sampler ID (the real texture handle)</returns>
|
/// <returns>The packed texture and sampler ID (the real texture handle)</returns>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private int ReadPackedId(int stageIndex, int wordOffset, int textureBufferIndex, int samplerBufferIndex)
|
private int ReadPackedId(int stageIndex, int wordOffset, int textureBufferIndex, int samplerBufferIndex)
|
||||||
{
|
{
|
||||||
(int textureWordOffset, int samplerWordOffset, TextureHandleType handleType) = TextureHandle.UnpackOffsets(wordOffset);
|
(int textureWordOffset, int samplerWordOffset, TextureHandleType handleType) = TextureHandle.UnpackOffsets(wordOffset);
|
||||||
|
@ -590,32 +828,13 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Unpacks the texture ID from the real texture handle.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="packedId">The real texture handle</param>
|
|
||||||
/// <returns>The texture ID</returns>
|
|
||||||
private static int UnpackTextureId(int packedId)
|
|
||||||
{
|
|
||||||
return (packedId >> 0) & 0xfffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Unpacks the sampler ID from the real texture handle.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="packedId">The real texture handle</param>
|
|
||||||
/// <returns>The sampler ID</returns>
|
|
||||||
private static int UnpackSamplerId(int packedId)
|
|
||||||
{
|
|
||||||
return (packedId >> 20) & 0xfff;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Force all bound textures and images to be rebound the next time CommitBindings is called.
|
/// Force all bound textures and images to be rebound the next time CommitBindings is called.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Rebind()
|
public void Rebind()
|
||||||
{
|
{
|
||||||
_rebind = true;
|
Array.Clear(_textureState);
|
||||||
|
Array.Clear(_imageState);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -349,6 +349,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
/// <param name="memoryManager">GPU memory manager where the texture is mapped</param>
|
/// <param name="memoryManager">GPU memory manager where the texture is mapped</param>
|
||||||
/// <param name="dsState">Depth-stencil buffer texture to find or create</param>
|
/// <param name="dsState">Depth-stencil buffer texture to find or create</param>
|
||||||
/// <param name="size">Size of the depth-stencil texture</param>
|
/// <param name="size">Size of the depth-stencil texture</param>
|
||||||
|
/// <param name="layered">Indicates if the texture might be accessed with a non-zero layer index</param>
|
||||||
/// <param name="samplesInX">Number of samples in the X direction, for MSAA</param>
|
/// <param name="samplesInX">Number of samples in the X direction, for MSAA</param>
|
||||||
/// <param name="samplesInY">Number of samples in the Y direction, for MSAA</param>
|
/// <param name="samplesInY">Number of samples in the Y direction, for MSAA</param>
|
||||||
/// <param name="sizeHint">A hint indicating the minimum used size for the texture</param>
|
/// <param name="sizeHint">A hint indicating the minimum used size for the texture</param>
|
||||||
|
@ -357,6 +358,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
MemoryManager memoryManager,
|
MemoryManager memoryManager,
|
||||||
RtDepthStencilState dsState,
|
RtDepthStencilState dsState,
|
||||||
Size3D size,
|
Size3D size,
|
||||||
|
bool layered,
|
||||||
int samplesInX,
|
int samplesInX,
|
||||||
int samplesInY,
|
int samplesInY,
|
||||||
Size sizeHint)
|
Size sizeHint)
|
||||||
|
@ -364,9 +366,24 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
int gobBlocksInY = dsState.MemoryLayout.UnpackGobBlocksInY();
|
int gobBlocksInY = dsState.MemoryLayout.UnpackGobBlocksInY();
|
||||||
int gobBlocksInZ = dsState.MemoryLayout.UnpackGobBlocksInZ();
|
int gobBlocksInZ = dsState.MemoryLayout.UnpackGobBlocksInZ();
|
||||||
|
|
||||||
Target target = (samplesInX | samplesInY) != 1
|
Target target;
|
||||||
? Target.Texture2DMultisample
|
|
||||||
: Target.Texture2D;
|
if (dsState.MemoryLayout.UnpackIsTarget3D())
|
||||||
|
{
|
||||||
|
target = Target.Texture3D;
|
||||||
|
}
|
||||||
|
else if ((samplesInX | samplesInY) != 1)
|
||||||
|
{
|
||||||
|
target = size.Depth > 1 && layered
|
||||||
|
? Target.Texture2DMultisampleArray
|
||||||
|
: Target.Texture2DMultisample;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
target = size.Depth > 1 && layered
|
||||||
|
? Target.Texture2DArray
|
||||||
|
: Target.Texture2D;
|
||||||
|
}
|
||||||
|
|
||||||
FormatInfo formatInfo = dsState.Format.Convert();
|
FormatInfo formatInfo = dsState.Format.Convert();
|
||||||
|
|
||||||
|
|
|
@ -71,11 +71,15 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
{
|
{
|
||||||
if (info.FormatInfo.Format.IsAstcUnorm())
|
if (info.FormatInfo.Format.IsAstcUnorm())
|
||||||
{
|
{
|
||||||
return new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4);
|
return GraphicsConfig.EnableTextureRecompression
|
||||||
|
? new FormatInfo(Format.Bc7Unorm, 4, 4, 16, 4)
|
||||||
|
: new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4);
|
||||||
}
|
}
|
||||||
else if (info.FormatInfo.Format.IsAstcSrgb())
|
else if (info.FormatInfo.Format.IsAstcSrgb())
|
||||||
{
|
{
|
||||||
return new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4, 4);
|
return GraphicsConfig.EnableTextureRecompression
|
||||||
|
? new FormatInfo(Format.Bc7Srgb, 4, 4, 16, 4)
|
||||||
|
: new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4, 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,9 +88,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
return new FormatInfo(Format.R4G4B4A4Unorm, 1, 1, 2, 4);
|
return new FormatInfo(Format.R4G4B4A4Unorm, 1, 1, 2, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!caps.Supports3DTextureCompression && info.Target == Target.Texture3D)
|
if (!HostSupportsBcFormat(info.FormatInfo.Format, info.Target, caps))
|
||||||
{
|
{
|
||||||
// The host API does not support 3D compressed formats.
|
// The host API does not this compressed format.
|
||||||
// We assume software decompression will be done for those textures,
|
// We assume software decompression will be done for those textures,
|
||||||
// and so we adjust the format here to match the decompressor output.
|
// and so we adjust the format here to match the decompressor output.
|
||||||
switch (info.FormatInfo.Format)
|
switch (info.FormatInfo.Format)
|
||||||
|
@ -94,10 +98,12 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
case Format.Bc1RgbaSrgb:
|
case Format.Bc1RgbaSrgb:
|
||||||
case Format.Bc2Srgb:
|
case Format.Bc2Srgb:
|
||||||
case Format.Bc3Srgb:
|
case Format.Bc3Srgb:
|
||||||
|
case Format.Bc7Srgb:
|
||||||
return new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4, 4);
|
return new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4, 4);
|
||||||
case Format.Bc1RgbaUnorm:
|
case Format.Bc1RgbaUnorm:
|
||||||
case Format.Bc2Unorm:
|
case Format.Bc2Unorm:
|
||||||
case Format.Bc3Unorm:
|
case Format.Bc3Unorm:
|
||||||
|
case Format.Bc7Unorm:
|
||||||
return new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4);
|
return new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4);
|
||||||
case Format.Bc4Unorm:
|
case Format.Bc4Unorm:
|
||||||
return new FormatInfo(Format.R8Unorm, 1, 1, 1, 1);
|
return new FormatInfo(Format.R8Unorm, 1, 1, 1, 1);
|
||||||
|
@ -107,12 +113,50 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
return new FormatInfo(Format.R8G8Unorm, 1, 1, 2, 2);
|
return new FormatInfo(Format.R8G8Unorm, 1, 1, 2, 2);
|
||||||
case Format.Bc5Snorm:
|
case Format.Bc5Snorm:
|
||||||
return new FormatInfo(Format.R8G8Snorm, 1, 1, 2, 2);
|
return new FormatInfo(Format.R8G8Snorm, 1, 1, 2, 2);
|
||||||
|
case Format.Bc6HSfloat:
|
||||||
|
case Format.Bc6HUfloat:
|
||||||
|
return new FormatInfo(Format.R16G16B16A16Float, 1, 1, 8, 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return info.FormatInfo;
|
return info.FormatInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the host API supports a given texture compression format of the BC family.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="format">BC format to be checked</param>
|
||||||
|
/// <param name="target">Target usage of the texture</param>
|
||||||
|
/// <param name="caps">Host GPU Capabilities</param>
|
||||||
|
/// <returns>True if the texture host supports the format with the given target usage, false otherwise</returns>
|
||||||
|
public static bool HostSupportsBcFormat(Format format, Target target, Capabilities caps)
|
||||||
|
{
|
||||||
|
bool not3DOr3DCompressionSupported = target != Target.Texture3D || caps.Supports3DTextureCompression;
|
||||||
|
|
||||||
|
switch (format)
|
||||||
|
{
|
||||||
|
case Format.Bc1RgbaSrgb:
|
||||||
|
case Format.Bc1RgbaUnorm:
|
||||||
|
case Format.Bc2Srgb:
|
||||||
|
case Format.Bc2Unorm:
|
||||||
|
case Format.Bc3Srgb:
|
||||||
|
case Format.Bc3Unorm:
|
||||||
|
return caps.SupportsBc123Compression && not3DOr3DCompressionSupported;
|
||||||
|
case Format.Bc4Unorm:
|
||||||
|
case Format.Bc4Snorm:
|
||||||
|
case Format.Bc5Unorm:
|
||||||
|
case Format.Bc5Snorm:
|
||||||
|
return caps.SupportsBc45Compression && not3DOr3DCompressionSupported;
|
||||||
|
case Format.Bc6HSfloat:
|
||||||
|
case Format.Bc6HUfloat:
|
||||||
|
case Format.Bc7Srgb:
|
||||||
|
case Format.Bc7Unorm:
|
||||||
|
return caps.SupportsBc67Compression && not3DOr3DCompressionSupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determines whether a texture can flush its data back to guest memory.
|
/// Determines whether a texture can flush its data back to guest memory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -744,7 +788,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
/// <returns>True if the texture target and samples count matches, false otherwise</returns>
|
/// <returns>True if the texture target and samples count matches, false otherwise</returns>
|
||||||
public static bool TargetAndSamplesCompatible(TextureInfo lhs, TextureInfo rhs)
|
public static bool TargetAndSamplesCompatible(TextureInfo lhs, TextureInfo rhs)
|
||||||
{
|
{
|
||||||
return lhs.Target == rhs.Target &&
|
return lhs.Target == rhs.Target &&
|
||||||
lhs.SamplesInX == rhs.SamplesInX &&
|
lhs.SamplesInX == rhs.SamplesInX &&
|
||||||
lhs.SamplesInY == rhs.SamplesInY;
|
lhs.SamplesInY == rhs.SamplesInY;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
using Ryujinx.Graphics.Gpu.Engine.Types;
|
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||||
|
using Ryujinx.Graphics.Gpu.Shader;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Gpu.Image
|
namespace Ryujinx.Graphics.Gpu.Image
|
||||||
|
@ -10,9 +11,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
class TextureManager : IDisposable
|
class TextureManager : IDisposable
|
||||||
{
|
{
|
||||||
private readonly GpuContext _context;
|
private readonly GpuContext _context;
|
||||||
|
private readonly GpuChannel _channel;
|
||||||
|
|
||||||
private readonly TextureBindingsManager _cpBindingsManager;
|
private readonly TextureBindingsManager _cpBindingsManager;
|
||||||
private readonly TextureBindingsManager _gpBindingsManager;
|
private readonly TextureBindingsManager _gpBindingsManager;
|
||||||
|
private readonly TexturePoolCache _texturePoolCache;
|
||||||
|
|
||||||
private readonly Texture[] _rtColors;
|
private readonly Texture[] _rtColors;
|
||||||
private readonly ITexture[] _rtHostColors;
|
private readonly ITexture[] _rtHostColors;
|
||||||
|
@ -35,6 +38,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
public TextureManager(GpuContext context, GpuChannel channel)
|
public TextureManager(GpuContext context, GpuChannel channel)
|
||||||
{
|
{
|
||||||
_context = context;
|
_context = context;
|
||||||
|
_channel = channel;
|
||||||
|
|
||||||
TexturePoolCache texturePoolCache = new TexturePoolCache(context);
|
TexturePoolCache texturePoolCache = new TexturePoolCache(context);
|
||||||
|
|
||||||
|
@ -43,6 +47,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
|
|
||||||
_cpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, scales, isCompute: true);
|
_cpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, scales, isCompute: true);
|
||||||
_gpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, scales, isCompute: false);
|
_gpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, scales, isCompute: false);
|
||||||
|
_texturePoolCache = texturePoolCache;
|
||||||
|
|
||||||
_rtColors = new Texture[Constants.TotalRenderTargets];
|
_rtColors = new Texture[Constants.TotalRenderTargets];
|
||||||
_rtHostColors = new ITexture[Constants.TotalRenderTargets];
|
_rtHostColors = new ITexture[Constants.TotalRenderTargets];
|
||||||
|
@ -99,6 +104,16 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
_cpBindingsManager.SetTextureBufferIndex(index);
|
_cpBindingsManager.SetTextureBufferIndex(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the max binding indexes on the compute pipeline.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="maxTextureBinding">The maximum texture binding</param>
|
||||||
|
/// <param name="maxImageBinding">The maximum image binding</param>
|
||||||
|
public void SetComputeMaxBindings(int maxTextureBinding, int maxImageBinding)
|
||||||
|
{
|
||||||
|
_cpBindingsManager.SetMaxBindings(maxTextureBinding, maxImageBinding);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the texture constant buffer index on the graphics pipeline.
|
/// Sets the texture constant buffer index on the graphics pipeline.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -108,6 +123,16 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
_gpBindingsManager.SetTextureBufferIndex(index);
|
_gpBindingsManager.SetTextureBufferIndex(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the max binding indexes on the graphics pipeline.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="maxTextureBinding">The maximum texture binding</param>
|
||||||
|
/// <param name="maxImageBinding">The maximum image binding</param>
|
||||||
|
public void SetGraphicsMaxBindings(int maxTextureBinding, int maxImageBinding)
|
||||||
|
{
|
||||||
|
_gpBindingsManager.SetMaxBindings(maxTextureBinding, maxImageBinding);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the current sampler pool on the compute pipeline.
|
/// Sets the current sampler pool on the compute pipeline.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -335,25 +360,48 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Commits bindings on the compute pipeline.
|
/// Commits bindings on the compute pipeline.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void CommitComputeBindings()
|
/// <param name="specState">Specialization state for the bound shader</param>
|
||||||
|
/// <returns>True if all bound textures match the current shader specialization state, false otherwise</returns>
|
||||||
|
public bool CommitComputeBindings(ShaderSpecializationState specState)
|
||||||
{
|
{
|
||||||
// Every time we switch between graphics and compute work,
|
// Every time we switch between graphics and compute work,
|
||||||
// we must rebind everything.
|
// we must rebind everything.
|
||||||
// Since compute work happens less often, we always do that
|
// Since compute work happens less often, we always do that
|
||||||
// before and after the compute dispatch.
|
// before and after the compute dispatch.
|
||||||
_cpBindingsManager.Rebind();
|
_cpBindingsManager.Rebind();
|
||||||
_cpBindingsManager.CommitBindings();
|
bool result = _cpBindingsManager.CommitBindings(specState);
|
||||||
_gpBindingsManager.Rebind();
|
_gpBindingsManager.Rebind();
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Commits bindings on the graphics pipeline.
|
/// Commits bindings on the graphics pipeline.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void CommitGraphicsBindings()
|
/// <param name="specState">Specialization state for the bound shader</param>
|
||||||
|
/// <returns>True if all bound textures match the current shader specialization state, false otherwise</returns>
|
||||||
|
public bool CommitGraphicsBindings(ShaderSpecializationState specState)
|
||||||
{
|
{
|
||||||
_gpBindingsManager.CommitBindings();
|
bool result = _gpBindingsManager.CommitBindings(specState);
|
||||||
|
|
||||||
UpdateRenderTargets();
|
UpdateRenderTargets();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a texture pool from the cache, with the given address and maximum id.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="poolGpuVa">GPU virtual address of the texture pool</param>
|
||||||
|
/// <param name="maximumId">Maximum ID of the texture pool</param>
|
||||||
|
/// <returns>The texture pool</returns>
|
||||||
|
public TexturePool GetTexturePool(ulong poolGpuVa, int maximumId)
|
||||||
|
{
|
||||||
|
ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa);
|
||||||
|
|
||||||
|
TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId);
|
||||||
|
|
||||||
|
return texturePool;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -14,6 +14,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
{
|
{
|
||||||
private readonly GpuChannel _channel;
|
private readonly GpuChannel _channel;
|
||||||
private readonly ConcurrentQueue<Texture> _dereferenceQueue = new ConcurrentQueue<Texture>();
|
private readonly ConcurrentQueue<Texture> _dereferenceQueue = new ConcurrentQueue<Texture>();
|
||||||
|
private TextureDescriptor _defaultDescriptor;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Intrusive linked list node used on the texture pool cache.
|
/// Intrusive linked list node used on the texture pool cache.
|
||||||
|
@ -32,6 +33,62 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
_channel = channel;
|
_channel = channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the texture descripor and texture with the given ID with no bounds check or synchronization.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">ID of the texture. This is effectively a zero-based index</param>
|
||||||
|
/// <param name="texture">The texture with the given ID</param>
|
||||||
|
/// <returns>The texture descriptor with the given ID</returns>
|
||||||
|
private ref readonly TextureDescriptor GetInternal(int id, out Texture texture)
|
||||||
|
{
|
||||||
|
texture = Items[id];
|
||||||
|
|
||||||
|
ref readonly TextureDescriptor descriptor = ref GetDescriptorRef(id);
|
||||||
|
|
||||||
|
if (texture == null)
|
||||||
|
{
|
||||||
|
TextureInfo info = GetInfo(descriptor, out int layerSize);
|
||||||
|
|
||||||
|
ProcessDereferenceQueue();
|
||||||
|
|
||||||
|
texture = PhysicalMemory.TextureCache.FindOrCreateTexture(_channel.MemoryManager, TextureSearchFlags.ForSampler, info, layerSize);
|
||||||
|
|
||||||
|
// If this happens, then the texture address is invalid, we can't add it to the cache.
|
||||||
|
if (texture == null)
|
||||||
|
{
|
||||||
|
return ref descriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
texture.IncrementReferenceCount(this, id);
|
||||||
|
|
||||||
|
Items[id] = texture;
|
||||||
|
|
||||||
|
DescriptorCache[id] = descriptor;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (texture.ChangedSize)
|
||||||
|
{
|
||||||
|
// Texture changed size at one point - it may be a different size than the sampler expects.
|
||||||
|
// This can be triggered when the size is changed by a size hint on copy or draw, but the texture has been sampled before.
|
||||||
|
|
||||||
|
int baseLevel = descriptor.UnpackBaseLevel();
|
||||||
|
int width = Math.Max(1, descriptor.UnpackWidth() >> baseLevel);
|
||||||
|
int height = Math.Max(1, descriptor.UnpackHeight() >> baseLevel);
|
||||||
|
|
||||||
|
if (texture.Info.Width != width || texture.Info.Height != height)
|
||||||
|
{
|
||||||
|
texture.ChangeSize(width, height, texture.Info.DepthOrLayers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Memory is automatically synchronized on texture creation.
|
||||||
|
texture.SynchronizeMemory();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ref descriptor;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the texture with the given ID.
|
/// Gets the texture with the given ID.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -51,56 +108,49 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
SynchronizeMemory();
|
SynchronizeMemory();
|
||||||
}
|
}
|
||||||
|
|
||||||
Texture texture = Items[id];
|
GetInternal(id, out Texture texture);
|
||||||
|
|
||||||
if (texture == null)
|
|
||||||
{
|
|
||||||
TextureDescriptor descriptor = GetDescriptor(id);
|
|
||||||
|
|
||||||
TextureInfo info = GetInfo(descriptor, out int layerSize);
|
|
||||||
|
|
||||||
ProcessDereferenceQueue();
|
|
||||||
|
|
||||||
texture = PhysicalMemory.TextureCache.FindOrCreateTexture(_channel.MemoryManager, TextureSearchFlags.ForSampler, info, layerSize);
|
|
||||||
|
|
||||||
// If this happens, then the texture address is invalid, we can't add it to the cache.
|
|
||||||
if (texture == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
texture.IncrementReferenceCount(this, id);
|
|
||||||
|
|
||||||
Items[id] = texture;
|
|
||||||
|
|
||||||
DescriptorCache[id] = descriptor;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (texture.ChangedSize)
|
|
||||||
{
|
|
||||||
// Texture changed size at one point - it may be a different size than the sampler expects.
|
|
||||||
// This can be triggered when the size is changed by a size hint on copy or draw, but the texture has been sampled before.
|
|
||||||
|
|
||||||
TextureDescriptor descriptor = GetDescriptor(id);
|
|
||||||
|
|
||||||
int baseLevel = descriptor.UnpackBaseLevel();
|
|
||||||
int width = Math.Max(1, descriptor.UnpackWidth() >> baseLevel);
|
|
||||||
int height = Math.Max(1, descriptor.UnpackHeight() >> baseLevel);
|
|
||||||
|
|
||||||
if (texture.Info.Width != width || texture.Info.Height != height)
|
|
||||||
{
|
|
||||||
texture.ChangeSize(width, height, texture.Info.DepthOrLayers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Memory is automatically synchronized on texture creation.
|
|
||||||
texture.SynchronizeMemory();
|
|
||||||
}
|
|
||||||
|
|
||||||
return texture;
|
return texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the texture descriptor and texture with the given ID.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method assumes that the pool has been manually synchronized before doing binding.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="id">ID of the texture. This is effectively a zero-based index</param>
|
||||||
|
/// <param name="texture">The texture with the given ID</param>
|
||||||
|
/// <returns>The texture descriptor with the given ID</returns>
|
||||||
|
public ref readonly TextureDescriptor GetForBinding(int id, out Texture texture)
|
||||||
|
{
|
||||||
|
if ((uint)id >= Items.Length)
|
||||||
|
{
|
||||||
|
texture = null;
|
||||||
|
return ref _defaultDescriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// When getting for binding, assume the pool has already been synchronized.
|
||||||
|
|
||||||
|
return ref GetInternal(id, out texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the pool was modified, and returns the last sequence number where a modification was detected.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A number that increments each time a modification is detected</returns>
|
||||||
|
public int CheckModified()
|
||||||
|
{
|
||||||
|
if (SequenceNumber != Context.SequenceNumber)
|
||||||
|
{
|
||||||
|
SequenceNumber = Context.SequenceNumber;
|
||||||
|
|
||||||
|
SynchronizeMemory();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ModifiedSequenceNumber;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Forcibly remove a texture from this pool's items.
|
/// Forcibly remove a texture from this pool's items.
|
||||||
/// If deferred, the dereference will be queued to occur on the render thread.
|
/// If deferred, the dereference will be queued to occur on the render thread.
|
||||||
|
@ -175,7 +225,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
/// <param name="descriptor">The texture descriptor</param>
|
/// <param name="descriptor">The texture descriptor</param>
|
||||||
/// <param name="layerSize">Layer size for textures using a sub-range of mipmap levels, otherwise 0</param>
|
/// <param name="layerSize">Layer size for textures using a sub-range of mipmap levels, otherwise 0</param>
|
||||||
/// <returns>The texture information</returns>
|
/// <returns>The texture information</returns>
|
||||||
private TextureInfo GetInfo(TextureDescriptor descriptor, out int layerSize)
|
private TextureInfo GetInfo(in TextureDescriptor descriptor, out int layerSize)
|
||||||
{
|
{
|
||||||
int depthOrLayers = descriptor.UnpackDepth();
|
int depthOrLayers = descriptor.UnpackDepth();
|
||||||
int levels = descriptor.UnpackLevels();
|
int levels = descriptor.UnpackLevels();
|
||||||
|
|
|
@ -378,6 +378,25 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
return _gpUniformBuffers[stage].Buffers[index].Address;
|
return _gpUniformBuffers[stage].Buffers[index].Address;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the bounds of the uniform buffer currently bound at the given index.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="isCompute">Indicates whenever the uniform is requested by the 3D or compute engine</param>
|
||||||
|
/// <param name="stage">Index of the shader stage, if the uniform is for the 3D engine</param>
|
||||||
|
/// <param name="index">Index of the uniform buffer binding</param>
|
||||||
|
/// <returns>The uniform buffer bounds, or an undefined value if the buffer is not currently bound</returns>
|
||||||
|
public ref BufferBounds GetUniformBufferBounds(bool isCompute, int stage, int index)
|
||||||
|
{
|
||||||
|
if (isCompute)
|
||||||
|
{
|
||||||
|
return ref _cpUniformBuffers.Buffers[index];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return ref _gpUniformBuffers[stage].Buffers[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ensures that the compute engine bindings are visible to the host GPU.
|
/// Ensures that the compute engine bindings are visible to the host GPU.
|
||||||
/// Note: this actually performs the binding using the host graphics API.
|
/// Note: this actually performs the binding using the host graphics API.
|
||||||
|
@ -416,7 +435,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_context.Renderer.Pipeline.SetTexture(binding.BindingInfo.Binding, binding.Texture);
|
_context.Renderer.Pipeline.SetTextureAndSampler(binding.Stage, binding.BindingInfo.Binding, binding.Texture, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -700,17 +719,25 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the buffer storage of a buffer texture. This will be bound when the buffer manager commits bindings.
|
/// Sets the buffer storage of a buffer texture. This will be bound when the buffer manager commits bindings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="stage">Shader stage accessing the texture</param>
|
||||||
/// <param name="texture">Buffer texture</param>
|
/// <param name="texture">Buffer texture</param>
|
||||||
/// <param name="address">Address of the buffer in memory</param>
|
/// <param name="address">Address of the buffer in memory</param>
|
||||||
/// <param name="size">Size of the buffer in bytes</param>
|
/// <param name="size">Size of the buffer in bytes</param>
|
||||||
/// <param name="bindingInfo">Binding info for the buffer texture</param>
|
/// <param name="bindingInfo">Binding info for the buffer texture</param>
|
||||||
/// <param name="format">Format of the buffer texture</param>
|
/// <param name="format">Format of the buffer texture</param>
|
||||||
/// <param name="isImage">Whether the binding is for an image or a sampler</param>
|
/// <param name="isImage">Whether the binding is for an image or a sampler</param>
|
||||||
public void SetBufferTextureStorage(ITexture texture, ulong address, ulong size, TextureBindingInfo bindingInfo, Format format, bool isImage)
|
public void SetBufferTextureStorage(
|
||||||
|
ShaderStage stage,
|
||||||
|
ITexture texture,
|
||||||
|
ulong address,
|
||||||
|
ulong size,
|
||||||
|
TextureBindingInfo bindingInfo,
|
||||||
|
Format format,
|
||||||
|
bool isImage)
|
||||||
{
|
{
|
||||||
_channel.MemoryManager.Physical.BufferCache.CreateBuffer(address, size);
|
_channel.MemoryManager.Physical.BufferCache.CreateBuffer(address, size);
|
||||||
|
|
||||||
_bufferTextures.Add(new BufferTextureBinding(texture, address, size, bindingInfo, format, isImage));
|
_bufferTextures.Add(new BufferTextureBinding(stage, texture, address, size, bindingInfo, format, isImage));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
using Ryujinx.Graphics.Gpu.Image;
|
using Ryujinx.Graphics.Gpu.Image;
|
||||||
|
using Ryujinx.Graphics.Shader;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Gpu.Memory
|
namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
{
|
{
|
||||||
|
@ -8,6 +9,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
/// </summary>
|
/// </summary>
|
||||||
struct BufferTextureBinding
|
struct BufferTextureBinding
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Shader stage accessing the texture.
|
||||||
|
/// </summary>
|
||||||
|
public ShaderStage Stage { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The buffer texture.
|
/// The buffer texture.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -41,14 +47,23 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new buffer texture binding.
|
/// Create a new buffer texture binding.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="stage">Shader stage accessing the texture</param>
|
||||||
/// <param name="texture">Buffer texture</param>
|
/// <param name="texture">Buffer texture</param>
|
||||||
/// <param name="address">Base address</param>
|
/// <param name="address">Base address</param>
|
||||||
/// <param name="size">Size in bytes</param>
|
/// <param name="size">Size in bytes</param>
|
||||||
/// <param name="bindingInfo">Binding info</param>
|
/// <param name="bindingInfo">Binding info</param>
|
||||||
/// <param name="format">Binding format</param>
|
/// <param name="format">Binding format</param>
|
||||||
/// <param name="isImage">Whether the binding is for an image or a sampler</param>
|
/// <param name="isImage">Whether the binding is for an image or a sampler</param>
|
||||||
public BufferTextureBinding(ITexture texture, ulong address, ulong size, TextureBindingInfo bindingInfo, Format format, bool isImage)
|
public BufferTextureBinding(
|
||||||
|
ShaderStage stage,
|
||||||
|
ITexture texture,
|
||||||
|
ulong address,
|
||||||
|
ulong size,
|
||||||
|
TextureBindingInfo bindingInfo,
|
||||||
|
Format format,
|
||||||
|
bool isImage)
|
||||||
{
|
{
|
||||||
|
Stage = stage;
|
||||||
Texture = texture;
|
Texture = texture;
|
||||||
Address = address;
|
Address = address;
|
||||||
Size = size;
|
Size = size;
|
||||||
|
|
|
@ -252,6 +252,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
Write(va, MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref value, 1)));
|
Write(va, MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref value, 1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void WriteUntracked<T>(ulong va, T value) where T : unmanaged
|
||||||
|
{
|
||||||
|
WriteUntracked(va, MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref value, 1)));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Writes data to GPU mapped memory.
|
/// Writes data to GPU mapped memory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -105,7 +105,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||||
entry.Header.GpuAccessorHeader.ComputeLocalMemorySize,
|
entry.Header.GpuAccessorHeader.ComputeLocalMemorySize,
|
||||||
entry.Header.GpuAccessorHeader.ComputeSharedMemorySize);
|
entry.Header.GpuAccessorHeader.ComputeSharedMemorySize);
|
||||||
|
|
||||||
ShaderSpecializationState specState = new ShaderSpecializationState(computeState);
|
ShaderSpecializationState specState = new ShaderSpecializationState(ref computeState);
|
||||||
|
|
||||||
foreach (var td in entry.TextureDescriptors)
|
foreach (var td in entry.TextureDescriptors)
|
||||||
{
|
{
|
||||||
|
@ -163,11 +163,20 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||||
_ => PrimitiveTopology.Points
|
_ => PrimitiveTopology.Points
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Array32<AttributeType> attributeTypes = default;
|
||||||
|
|
||||||
GpuChannelGraphicsState graphicsState = new GpuChannelGraphicsState(
|
GpuChannelGraphicsState graphicsState = new GpuChannelGraphicsState(
|
||||||
accessorHeader.StateFlags.HasFlag(GuestGpuStateFlags.EarlyZForce),
|
accessorHeader.StateFlags.HasFlag(GuestGpuStateFlags.EarlyZForce),
|
||||||
topology,
|
topology,
|
||||||
tessMode,
|
tessMode,
|
||||||
false);
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
1f,
|
||||||
|
false,
|
||||||
|
CompareOp.Always,
|
||||||
|
0f,
|
||||||
|
ref attributeTypes);
|
||||||
|
|
||||||
TransformFeedbackDescriptor[] tfdNew = null;
|
TransformFeedbackDescriptor[] tfdNew = null;
|
||||||
|
|
||||||
|
@ -189,7 +198,9 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ShaderSpecializationState specState = new ShaderSpecializationState(graphicsState, tfdNew);
|
ProgramPipelineState pipelineState = default;
|
||||||
|
|
||||||
|
ShaderSpecializationState specState = new ShaderSpecializationState(ref graphicsState, ref pipelineState, tfdNew);
|
||||||
|
|
||||||
for (int i = 0; i < entries.Length; i++)
|
for (int i = 0; i < entries.Length; i++)
|
||||||
{
|
{
|
||||||
|
|
|
@ -35,6 +35,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
HostProgram = hostProgram;
|
HostProgram = hostProgram;
|
||||||
SpecializationState = specializationState;
|
SpecializationState = specializationState;
|
||||||
Shaders = shaders;
|
Shaders = shaders;
|
||||||
|
|
||||||
|
SpecializationState.Prepare(shaders);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -83,7 +83,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
{
|
{
|
||||||
_context = context;
|
_context = context;
|
||||||
_hostStorage = hostStorage;
|
_hostStorage = hostStorage;
|
||||||
_fileWriterWorkerQueue = new AsyncWorkQueue<CacheFileOperationTask>(ProcessTask, "Gpu.BackgroundDiskCacheWriter");
|
_fileWriterWorkerQueue = new AsyncWorkQueue<CacheFileOperationTask>(ProcessTask, "GPU.BackgroundDiskCacheWriter");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Graphics.GAL;
|
||||||
using Ryujinx.Graphics.Gpu.Image;
|
using Ryujinx.Graphics.Gpu.Image;
|
||||||
using Ryujinx.Graphics.Shader;
|
using Ryujinx.Graphics.Shader;
|
||||||
|
using Ryujinx.Graphics.Shader.Translation;
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
@ -16,7 +18,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
private readonly ShaderSpecializationState _oldSpecState;
|
private readonly ShaderSpecializationState _oldSpecState;
|
||||||
private readonly ShaderSpecializationState _newSpecState;
|
private readonly ShaderSpecializationState _newSpecState;
|
||||||
private readonly int _stageIndex;
|
private readonly int _stageIndex;
|
||||||
private ResourceCounts _resourceCounts;
|
private readonly bool _isVulkan;
|
||||||
|
private readonly ResourceCounts _resourceCounts;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new instance of the cached GPU state accessor for shader translation.
|
/// Creates a new instance of the cached GPU state accessor for shader translation.
|
||||||
|
@ -34,13 +37,14 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
ShaderSpecializationState oldSpecState,
|
ShaderSpecializationState oldSpecState,
|
||||||
ShaderSpecializationState newSpecState,
|
ShaderSpecializationState newSpecState,
|
||||||
ResourceCounts counts,
|
ResourceCounts counts,
|
||||||
int stageIndex) : base(context)
|
int stageIndex) : base(context, counts, stageIndex)
|
||||||
{
|
{
|
||||||
_data = data;
|
_data = data;
|
||||||
_cb1Data = cb1Data;
|
_cb1Data = cb1Data;
|
||||||
_oldSpecState = oldSpecState;
|
_oldSpecState = oldSpecState;
|
||||||
_newSpecState = newSpecState;
|
_newSpecState = newSpecState;
|
||||||
_stageIndex = stageIndex;
|
_stageIndex = stageIndex;
|
||||||
|
_isVulkan = context.Capabilities.Api == TargetApi.Vulkan;
|
||||||
_resourceCounts = counts;
|
_resourceCounts = counts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,27 +72,33 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public int QueryBindingConstantBuffer(int index)
|
public AlphaTestOp QueryAlphaTestCompare()
|
||||||
{
|
{
|
||||||
return _resourceCounts.UniformBuffersCount++;
|
if (!_isVulkan || !_oldSpecState.GraphicsState.AlphaTestEnable)
|
||||||
|
{
|
||||||
|
return AlphaTestOp.Always;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _oldSpecState.GraphicsState.AlphaTestCompare switch
|
||||||
|
{
|
||||||
|
CompareOp.Never or CompareOp.NeverGl => AlphaTestOp.Never,
|
||||||
|
CompareOp.Less or CompareOp.LessGl => AlphaTestOp.Less,
|
||||||
|
CompareOp.Equal or CompareOp.EqualGl => AlphaTestOp.Equal,
|
||||||
|
CompareOp.LessOrEqual or CompareOp.LessOrEqualGl => AlphaTestOp.LessOrEqual,
|
||||||
|
CompareOp.Greater or CompareOp.GreaterGl => AlphaTestOp.Greater,
|
||||||
|
CompareOp.NotEqual or CompareOp.NotEqualGl => AlphaTestOp.NotEqual,
|
||||||
|
CompareOp.GreaterOrEqual or CompareOp.GreaterOrEqualGl => AlphaTestOp.GreaterOrEqual,
|
||||||
|
_ => AlphaTestOp.Always
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public int QueryBindingStorageBuffer(int index)
|
public float QueryAlphaTestReference() => _oldSpecState.GraphicsState.AlphaTestReference;
|
||||||
{
|
|
||||||
return _resourceCounts.StorageBuffersCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public int QueryBindingTexture(int index)
|
public AttributeType QueryAttributeType(int location)
|
||||||
{
|
{
|
||||||
return _resourceCounts.TexturesCount++;
|
return _oldSpecState.GraphicsState.AttributeTypes[location];
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public int QueryBindingImage(int index)
|
|
||||||
{
|
|
||||||
return _resourceCounts.ImagesCount++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
@ -120,6 +130,18 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
return ConvertToInputTopology(_oldSpecState.GraphicsState.Topology, _oldSpecState.GraphicsState.TessellationMode);
|
return ConvertToInputTopology(_oldSpecState.GraphicsState.Topology, _oldSpecState.GraphicsState.TessellationMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool QueryProgramPointSize()
|
||||||
|
{
|
||||||
|
return _oldSpecState.GraphicsState.ProgramPointSizeEnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public float QueryPointSize()
|
||||||
|
{
|
||||||
|
return _oldSpecState.GraphicsState.PointSize;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool QueryTessCw()
|
public bool QueryTessCw()
|
||||||
{
|
{
|
||||||
|
@ -160,6 +182,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
return _oldSpecState.GetCoordNormalized(_stageIndex, handle, cbufSlot);
|
return _oldSpecState.GetCoordNormalized(_stageIndex, handle, cbufSlot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool QueryTransformDepthMinusOneToOne()
|
||||||
|
{
|
||||||
|
return _oldSpecState.GraphicsState.DepthMode;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool QueryTransformFeedbackEnabled()
|
public bool QueryTransformFeedbackEnabled()
|
||||||
{
|
{
|
||||||
|
|
|
@ -14,7 +14,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
private const uint TocMagic = (byte)'T' | ((byte)'O' << 8) | ((byte)'C' << 16) | ((byte)'G' << 24);
|
private const uint TocMagic = (byte)'T' | ((byte)'O' << 8) | ((byte)'C' << 16) | ((byte)'G' << 24);
|
||||||
|
|
||||||
private const ushort VersionMajor = 1;
|
private const ushort VersionMajor = 1;
|
||||||
private const ushort VersionMinor = 0;
|
private const ushort VersionMinor = 1;
|
||||||
private const uint VersionPacked = ((uint)VersionMajor << 16) | VersionMinor;
|
private const uint VersionPacked = ((uint)VersionMajor << 16) | VersionMinor;
|
||||||
|
|
||||||
private const string TocFileName = "guest.toc";
|
private const string TocFileName = "guest.toc";
|
||||||
|
@ -193,8 +193,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
/// <param name="tocFileStream">Guest TOC file stream</param>
|
/// <param name="tocFileStream">Guest TOC file stream</param>
|
||||||
/// <param name="dataFileStream">Guest data file stream</param>
|
/// <param name="dataFileStream">Guest data file stream</param>
|
||||||
/// <param name="index">Guest shader index</param>
|
/// <param name="index">Guest shader index</param>
|
||||||
/// <returns>Tuple with the guest code and constant buffer 1 data, respectively</returns>
|
/// <returns>Guest code and constant buffer 1 data</returns>
|
||||||
public (byte[], byte[]) LoadShader(Stream tocFileStream, Stream dataFileStream, int index)
|
public GuestCodeAndCbData LoadShader(Stream tocFileStream, Stream dataFileStream, int index)
|
||||||
{
|
{
|
||||||
if (_cache == null || index >= _cache.Length)
|
if (_cache == null || index >= _cache.Length)
|
||||||
{
|
{
|
||||||
|
@ -226,7 +226,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
_cache[index] = (guestCode, cb1Data);
|
_cache[index] = (guestCode, cb1Data);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (guestCode, cb1Data);
|
return new GuestCodeAndCbData(guestCode, cb1Data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
using Ryujinx.Graphics.Shader;
|
using Ryujinx.Graphics.Shader;
|
||||||
|
using Ryujinx.Graphics.Shader.Translation;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
@ -19,9 +20,9 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
private const uint TexdMagic = (byte)'T' | ((byte)'E' << 8) | ((byte)'X' << 16) | ((byte)'D' << 24);
|
private const uint TexdMagic = (byte)'T' | ((byte)'E' << 8) | ((byte)'X' << 16) | ((byte)'D' << 24);
|
||||||
|
|
||||||
private const ushort FileFormatVersionMajor = 1;
|
private const ushort FileFormatVersionMajor = 1;
|
||||||
private const ushort FileFormatVersionMinor = 1;
|
private const ushort FileFormatVersionMinor = 2;
|
||||||
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
||||||
private const uint CodeGenVersion = 1;
|
private const uint CodeGenVersion = 9;
|
||||||
|
|
||||||
private const string SharedTocFileName = "shared.toc";
|
private const string SharedTocFileName = "shared.toc";
|
||||||
private const string SharedDataFileName = "shared.data";
|
private const string SharedDataFileName = "shared.data";
|
||||||
|
@ -77,9 +78,14 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
public ulong Offset;
|
public ulong Offset;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Size.
|
/// Size of uncompressed data.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public uint Size;
|
public uint UncompressedSize;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Size of compressed data.
|
||||||
|
/// </summary>
|
||||||
|
public uint CompressedSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -196,6 +202,14 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
private static string GetHostFileName(GpuContext context)
|
private static string GetHostFileName(GpuContext context)
|
||||||
{
|
{
|
||||||
string apiName = context.Capabilities.Api.ToString().ToLowerInvariant();
|
string apiName = context.Capabilities.Api.ToString().ToLowerInvariant();
|
||||||
|
|
||||||
|
// We are just storing SPIR-V directly on Vulkan, so the code won't change per vendor.
|
||||||
|
// We can just have a single file for all vendors.
|
||||||
|
if (context.Capabilities.Api == TargetApi.Vulkan)
|
||||||
|
{
|
||||||
|
return apiName;
|
||||||
|
}
|
||||||
|
|
||||||
string vendorName = RemoveInvalidCharacters(context.Capabilities.VendorName.ToLowerInvariant());
|
string vendorName = RemoveInvalidCharacters(context.Capabilities.VendorName.ToLowerInvariant());
|
||||||
return $"{apiName}_{vendorName}";
|
return $"{apiName}_{vendorName}";
|
||||||
}
|
}
|
||||||
|
@ -324,7 +338,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
stagesBitMask = 1;
|
stagesBitMask = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
CachedShaderStage[] shaders = new CachedShaderStage[isCompute ? 1 : Constants.ShaderStages + 1];
|
GuestCodeAndCbData?[] guestShaders = new GuestCodeAndCbData?[isCompute ? 1 : Constants.ShaderStages + 1];
|
||||||
|
|
||||||
DataEntryPerStage stageEntry = new DataEntryPerStage();
|
DataEntryPerStage stageEntry = new DataEntryPerStage();
|
||||||
|
|
||||||
|
@ -334,15 +348,11 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
|
|
||||||
dataReader.Read(ref stageEntry);
|
dataReader.Read(ref stageEntry);
|
||||||
|
|
||||||
ShaderProgramInfo info = stageIndex != 0 || isCompute ? ReadShaderProgramInfo(ref dataReader) : null;
|
guestShaders[stageIndex] = _guestStorage.LoadShader(
|
||||||
|
|
||||||
(byte[] guestCode, byte[] cb1Data) = _guestStorage.LoadShader(
|
|
||||||
guestTocFileStream,
|
guestTocFileStream,
|
||||||
guestDataFileStream,
|
guestDataFileStream,
|
||||||
stageEntry.GuestCodeIndex);
|
stageEntry.GuestCodeIndex);
|
||||||
|
|
||||||
shaders[stageIndex] = new CachedShaderStage(info, guestCode, cb1Data);
|
|
||||||
|
|
||||||
stagesBitMask &= ~(1u << stageIndex);
|
stagesBitMask &= ~(1u << stageIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -351,17 +361,38 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
|
|
||||||
if (loadHostCache)
|
if (loadHostCache)
|
||||||
{
|
{
|
||||||
byte[] hostCode = ReadHostCode(context, ref hostTocFileStream, ref hostDataFileStream, programIndex);
|
(byte[] hostCode, CachedShaderStage[] shaders) = ReadHostCode(
|
||||||
|
context,
|
||||||
|
ref hostTocFileStream,
|
||||||
|
ref hostDataFileStream,
|
||||||
|
guestShaders,
|
||||||
|
programIndex);
|
||||||
|
|
||||||
if (hostCode != null)
|
if (hostCode != null)
|
||||||
{
|
{
|
||||||
bool hasFragmentShader = shaders.Length > 5 && shaders[5] != null;
|
bool hasFragmentShader = shaders.Length > 5 && shaders[5] != null;
|
||||||
int fragmentOutputMap = hasFragmentShader ? shaders[5].Info.FragmentOutputMap : -1;
|
int fragmentOutputMap = hasFragmentShader ? shaders[5].Info.FragmentOutputMap : -1;
|
||||||
IProgram hostProgram = context.Renderer.LoadProgramBinary(hostCode, hasFragmentShader, new ShaderInfo(fragmentOutputMap));
|
|
||||||
|
ShaderInfo shaderInfo = specState.PipelineState.HasValue
|
||||||
|
? new ShaderInfo(fragmentOutputMap, specState.PipelineState.Value, fromCache: true)
|
||||||
|
: new ShaderInfo(fragmentOutputMap, fromCache: true);
|
||||||
|
|
||||||
|
IProgram hostProgram;
|
||||||
|
|
||||||
|
if (context.Capabilities.Api == TargetApi.Vulkan)
|
||||||
|
{
|
||||||
|
ShaderSource[] shaderSources = ShaderBinarySerializer.Unpack(shaders, hostCode, isCompute);
|
||||||
|
|
||||||
|
hostProgram = context.Renderer.CreateProgram(shaderSources, shaderInfo);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
hostProgram = context.Renderer.LoadProgramBinary(hostCode, hasFragmentShader, shaderInfo);
|
||||||
|
}
|
||||||
|
|
||||||
CachedShaderProgram program = new CachedShaderProgram(hostProgram, specState, shaders);
|
CachedShaderProgram program = new CachedShaderProgram(hostProgram, specState, shaders);
|
||||||
|
|
||||||
loader.QueueHostProgram(program, hostProgram, programIndex, isCompute);
|
loader.QueueHostProgram(program, hostCode, programIndex, isCompute);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -371,7 +402,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
|
|
||||||
if (!loadHostCache)
|
if (!loadHostCache)
|
||||||
{
|
{
|
||||||
loader.QueueGuestProgram(shaders, specState, programIndex, isCompute);
|
loader.QueueGuestProgram(guestShaders, specState, programIndex, isCompute);
|
||||||
}
|
}
|
||||||
|
|
||||||
loader.CheckCompilation();
|
loader.CheckCompilation();
|
||||||
|
@ -393,9 +424,15 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
/// <param name="context">GPU context</param>
|
/// <param name="context">GPU context</param>
|
||||||
/// <param name="tocFileStream">Host TOC file stream, intialized if needed</param>
|
/// <param name="tocFileStream">Host TOC file stream, intialized if needed</param>
|
||||||
/// <param name="dataFileStream">Host data file stream, initialized if needed</param>
|
/// <param name="dataFileStream">Host data file stream, initialized if needed</param>
|
||||||
|
/// <param name="guestShaders">Guest shader code for each active stage</param>
|
||||||
/// <param name="programIndex">Index of the program on the cache</param>
|
/// <param name="programIndex">Index of the program on the cache</param>
|
||||||
/// <returns>Host binary code, or null if not found</returns>
|
/// <returns>Host binary code, or null if not found</returns>
|
||||||
private byte[] ReadHostCode(GpuContext context, ref Stream tocFileStream, ref Stream dataFileStream, int programIndex)
|
private (byte[], CachedShaderStage[]) ReadHostCode(
|
||||||
|
GpuContext context,
|
||||||
|
ref Stream tocFileStream,
|
||||||
|
ref Stream dataFileStream,
|
||||||
|
GuestCodeAndCbData?[] guestShaders,
|
||||||
|
int programIndex)
|
||||||
{
|
{
|
||||||
if (tocFileStream == null && dataFileStream == null)
|
if (tocFileStream == null && dataFileStream == null)
|
||||||
{
|
{
|
||||||
|
@ -404,7 +441,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
|
|
||||||
if (!File.Exists(tocFilePath) || !File.Exists(dataFilePath))
|
if (!File.Exists(tocFilePath) || !File.Exists(dataFilePath))
|
||||||
{
|
{
|
||||||
return null;
|
return (null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
tocFileStream = DiskCacheCommon.OpenFile(_basePath, GetHostTocFileName(context), writable: false);
|
tocFileStream = DiskCacheCommon.OpenFile(_basePath, GetHostTocFileName(context), writable: false);
|
||||||
|
@ -414,7 +451,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
int offset = Unsafe.SizeOf<TocHeader>() + programIndex * Unsafe.SizeOf<OffsetAndSize>();
|
int offset = Unsafe.SizeOf<TocHeader>() + programIndex * Unsafe.SizeOf<OffsetAndSize>();
|
||||||
if (offset + Unsafe.SizeOf<OffsetAndSize>() > tocFileStream.Length)
|
if (offset + Unsafe.SizeOf<OffsetAndSize>() > tocFileStream.Length)
|
||||||
{
|
{
|
||||||
return null;
|
return (null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((ulong)offset >= (ulong)dataFileStream.Length)
|
if ((ulong)offset >= (ulong)dataFileStream.Length)
|
||||||
|
@ -436,11 +473,33 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
|
|
||||||
dataFileStream.Seek((long)offsetAndSize.Offset, SeekOrigin.Begin);
|
dataFileStream.Seek((long)offsetAndSize.Offset, SeekOrigin.Begin);
|
||||||
|
|
||||||
byte[] hostCode = new byte[offsetAndSize.Size];
|
byte[] hostCode = new byte[offsetAndSize.UncompressedSize];
|
||||||
|
|
||||||
BinarySerializer.ReadCompressed(dataFileStream, hostCode);
|
BinarySerializer.ReadCompressed(dataFileStream, hostCode);
|
||||||
|
|
||||||
return hostCode;
|
CachedShaderStage[] shaders = new CachedShaderStage[guestShaders.Length];
|
||||||
|
BinarySerializer dataReader = new BinarySerializer(dataFileStream);
|
||||||
|
|
||||||
|
dataFileStream.Seek((long)(offsetAndSize.Offset + offsetAndSize.CompressedSize), SeekOrigin.Begin);
|
||||||
|
|
||||||
|
dataReader.BeginCompression();
|
||||||
|
|
||||||
|
for (int index = 0; index < guestShaders.Length; index++)
|
||||||
|
{
|
||||||
|
if (!guestShaders[index].HasValue)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
GuestCodeAndCbData guestShader = guestShaders[index].Value;
|
||||||
|
ShaderProgramInfo info = index != 0 || guestShaders.Length == 1 ? ReadShaderProgramInfo(ref dataReader) : null;
|
||||||
|
|
||||||
|
shaders[index] = new CachedShaderStage(info, guestShader.Code, guestShader.Cb1Data);
|
||||||
|
}
|
||||||
|
|
||||||
|
dataReader.EndCompression();
|
||||||
|
|
||||||
|
return (hostCode, shaders);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -519,8 +578,6 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
stageEntry.GuestCodeIndex = _guestStorage.AddShader(shader.Code, shader.Cb1Data);
|
stageEntry.GuestCodeIndex = _guestStorage.AddShader(shader.Code, shader.Cb1Data);
|
||||||
|
|
||||||
dataWriter.Write(ref stageEntry);
|
dataWriter.Write(ref stageEntry);
|
||||||
|
|
||||||
WriteShaderProgramInfo(ref dataWriter, shader.Info);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
program.SpecializationState.Write(ref dataWriter);
|
program.SpecializationState.Write(ref dataWriter);
|
||||||
|
@ -537,7 +594,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
WriteHostCode(context, hostCode, -1, streams);
|
WriteHostCode(context, hostCode, program.Shaders, streams);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -574,29 +631,14 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
dataFileStream.SetLength(0);
|
dataFileStream.SetLength(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds a host binary shader to the host cache.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// This only modifies the host cache. The shader must already exist in the other caches.
|
|
||||||
/// This method should only be used for rebuilding the host cache after a clear.
|
|
||||||
/// </remarks>
|
|
||||||
/// <param name="context">GPU context</param>
|
|
||||||
/// <param name="hostCode">Host binary code</param>
|
|
||||||
/// <param name="programIndex">Index of the program in the cache</param>
|
|
||||||
public void AddHostShader(GpuContext context, ReadOnlySpan<byte> hostCode, int programIndex)
|
|
||||||
{
|
|
||||||
WriteHostCode(context, hostCode, programIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Writes the host binary code on the host cache.
|
/// Writes the host binary code on the host cache.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="context">GPU context</param>
|
/// <param name="context">GPU context</param>
|
||||||
/// <param name="hostCode">Host binary code</param>
|
/// <param name="hostCode">Host binary code</param>
|
||||||
/// <param name="programIndex">Index of the program in the cache</param>
|
/// <param name="shaders">Shader stages to be added to the host cache</param>
|
||||||
/// <param name="streams">Output streams to use</param>
|
/// <param name="streams">Output streams to use</param>
|
||||||
private void WriteHostCode(GpuContext context, ReadOnlySpan<byte> hostCode, int programIndex, DiskCacheOutputStreams streams = null)
|
private void WriteHostCode(GpuContext context, ReadOnlySpan<byte> hostCode, CachedShaderStage[] shaders, DiskCacheOutputStreams streams = null)
|
||||||
{
|
{
|
||||||
var tocFileStream = streams != null ? streams.HostTocFileStream : DiskCacheCommon.OpenFile(_basePath, GetHostTocFileName(context), writable: true);
|
var tocFileStream = streams != null ? streams.HostTocFileStream : DiskCacheCommon.OpenFile(_basePath, GetHostTocFileName(context), writable: true);
|
||||||
var dataFileStream = streams != null ? streams.HostDataFileStream : DiskCacheCommon.OpenFile(_basePath, GetHostDataFileName(context), writable: true);
|
var dataFileStream = streams != null ? streams.HostDataFileStream : DiskCacheCommon.OpenFile(_basePath, GetHostDataFileName(context), writable: true);
|
||||||
|
@ -607,26 +649,36 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
CreateToc(tocFileStream, ref header, TochMagic, 0);
|
CreateToc(tocFileStream, ref header, TochMagic, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (programIndex == -1)
|
tocFileStream.Seek(0, SeekOrigin.End);
|
||||||
{
|
|
||||||
tocFileStream.Seek(0, SeekOrigin.End);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
tocFileStream.Seek(Unsafe.SizeOf<TocHeader>() + (programIndex * Unsafe.SizeOf<OffsetAndSize>()), SeekOrigin.Begin);
|
|
||||||
}
|
|
||||||
|
|
||||||
dataFileStream.Seek(0, SeekOrigin.End);
|
dataFileStream.Seek(0, SeekOrigin.End);
|
||||||
|
|
||||||
BinarySerializer tocWriter = new BinarySerializer(tocFileStream);
|
BinarySerializer tocWriter = new BinarySerializer(tocFileStream);
|
||||||
|
BinarySerializer dataWriter = new BinarySerializer(dataFileStream);
|
||||||
|
|
||||||
OffsetAndSize offsetAndSize = new OffsetAndSize();
|
OffsetAndSize offsetAndSize = new OffsetAndSize();
|
||||||
offsetAndSize.Offset = (ulong)dataFileStream.Position;
|
offsetAndSize.Offset = (ulong)dataFileStream.Position;
|
||||||
offsetAndSize.Size = (uint)hostCode.Length;
|
offsetAndSize.UncompressedSize = (uint)hostCode.Length;
|
||||||
tocWriter.Write(ref offsetAndSize);
|
|
||||||
|
long dataStartPosition = dataFileStream.Position;
|
||||||
|
|
||||||
BinarySerializer.WriteCompressed(dataFileStream, hostCode, DiskCacheCommon.GetCompressionAlgorithm());
|
BinarySerializer.WriteCompressed(dataFileStream, hostCode, DiskCacheCommon.GetCompressionAlgorithm());
|
||||||
|
|
||||||
|
offsetAndSize.CompressedSize = (uint)(dataFileStream.Position - dataStartPosition);
|
||||||
|
|
||||||
|
tocWriter.Write(ref offsetAndSize);
|
||||||
|
|
||||||
|
dataWriter.BeginCompression(DiskCacheCommon.GetCompressionAlgorithm());
|
||||||
|
|
||||||
|
for (int index = 0; index < shaders.Length; index++)
|
||||||
|
{
|
||||||
|
if (shaders[index] != null)
|
||||||
|
{
|
||||||
|
WriteShaderProgramInfo(ref dataWriter, shaders[index].Info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dataWriter.EndCompression();
|
||||||
|
|
||||||
if (streams == null)
|
if (streams == null)
|
||||||
{
|
{
|
||||||
tocFileStream.Dispose();
|
tocFileStream.Dispose();
|
||||||
|
|
31
Ryujinx.Graphics.Gpu/Shader/DiskCache/GuestCodeAndCbData.cs
Normal file
31
Ryujinx.Graphics.Gpu/Shader/DiskCache/GuestCodeAndCbData.cs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
using Ryujinx.Graphics.Shader;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Guest shader code and constant buffer data accessed by the shader.
|
||||||
|
/// </summary>
|
||||||
|
struct GuestCodeAndCbData
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Maxwell binary shader code.
|
||||||
|
/// </summary>
|
||||||
|
public byte[] Code { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constant buffer 1 data accessed by the shader.
|
||||||
|
/// </summary>
|
||||||
|
public byte[] Cb1Data { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the guest shader code and constant buffer data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="code">Maxwell binary shader code</param>
|
||||||
|
/// <param name="cb1Data">Constant buffer 1 data accessed by the shader</param>
|
||||||
|
public GuestCodeAndCbData(byte[] code, byte[] cb1Data)
|
||||||
|
{
|
||||||
|
Code = code;
|
||||||
|
Cb1Data = cb1Data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -45,9 +45,9 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
public readonly CachedShaderProgram CachedProgram;
|
public readonly CachedShaderProgram CachedProgram;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Host program.
|
/// Optional binary code. If not null, it is used instead of the backend host binary.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly IProgram HostProgram;
|
public readonly byte[] BinaryCode;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Program index.
|
/// Program index.
|
||||||
|
@ -68,19 +68,18 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
/// Creates a new program validation entry.
|
/// Creates a new program validation entry.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="cachedProgram">Cached shader program</param>
|
/// <param name="cachedProgram">Cached shader program</param>
|
||||||
/// <param name="hostProgram">Host program</param>
|
|
||||||
/// <param name="programIndex">Program index</param>
|
/// <param name="programIndex">Program index</param>
|
||||||
/// <param name="isCompute">Indicates if the program is a compute shader</param>
|
/// <param name="isCompute">Indicates if the program is a compute shader</param>
|
||||||
/// <param name="isBinary">Indicates if the program is a host binary shader</param>
|
/// <param name="isBinary">Indicates if the program is a host binary shader</param>
|
||||||
public ProgramEntry(
|
public ProgramEntry(
|
||||||
CachedShaderProgram cachedProgram,
|
CachedShaderProgram cachedProgram,
|
||||||
IProgram hostProgram,
|
byte[] binaryCode,
|
||||||
int programIndex,
|
int programIndex,
|
||||||
bool isCompute,
|
bool isCompute,
|
||||||
bool isBinary)
|
bool isBinary)
|
||||||
{
|
{
|
||||||
CachedProgram = cachedProgram;
|
CachedProgram = cachedProgram;
|
||||||
HostProgram = hostProgram;
|
BinaryCode = binaryCode;
|
||||||
ProgramIndex = programIndex;
|
ProgramIndex = programIndex;
|
||||||
IsCompute = isCompute;
|
IsCompute = isCompute;
|
||||||
IsBinary = isBinary;
|
IsBinary = isBinary;
|
||||||
|
@ -146,9 +145,9 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
private struct AsyncProgramTranslation
|
private struct AsyncProgramTranslation
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Cached shader stages.
|
/// Guest code for each active stage.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly CachedShaderStage[] Shaders;
|
public readonly GuestCodeAndCbData?[] GuestShaders;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Specialization state.
|
/// Specialization state.
|
||||||
|
@ -168,17 +167,17 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new program translation entry.
|
/// Creates a new program translation entry.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="shaders">Cached shader stages</param>
|
/// <param name="guestShaders">Guest code for each active stage</param>
|
||||||
/// <param name="specState">Specialization state</param>
|
/// <param name="specState">Specialization state</param>
|
||||||
/// <param name="programIndex">Program index</param>
|
/// <param name="programIndex">Program index</param>
|
||||||
/// <param name="isCompute">Indicates if the program is a compute shader</param>
|
/// <param name="isCompute">Indicates if the program is a compute shader</param>
|
||||||
public AsyncProgramTranslation(
|
public AsyncProgramTranslation(
|
||||||
CachedShaderStage[] shaders,
|
GuestCodeAndCbData?[] guestShaders,
|
||||||
ShaderSpecializationState specState,
|
ShaderSpecializationState specState,
|
||||||
int programIndex,
|
int programIndex,
|
||||||
bool isCompute)
|
bool isCompute)
|
||||||
{
|
{
|
||||||
Shaders = shaders;
|
GuestShaders = guestShaders;
|
||||||
SpecializationState = specState;
|
SpecializationState = specState;
|
||||||
ProgramIndex = programIndex;
|
ProgramIndex = programIndex;
|
||||||
IsCompute = isCompute;
|
IsCompute = isCompute;
|
||||||
|
@ -188,7 +187,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
private readonly Queue<ProgramEntry> _validationQueue;
|
private readonly Queue<ProgramEntry> _validationQueue;
|
||||||
private readonly ConcurrentQueue<ProgramCompilation> _compilationQueue;
|
private readonly ConcurrentQueue<ProgramCompilation> _compilationQueue;
|
||||||
private readonly BlockingCollection<AsyncProgramTranslation> _asyncTranslationQueue;
|
private readonly BlockingCollection<AsyncProgramTranslation> _asyncTranslationQueue;
|
||||||
private readonly SortedList<int, CachedShaderProgram> _programList;
|
private readonly SortedList<int, (CachedShaderProgram, byte[])> _programList;
|
||||||
|
|
||||||
private int _backendParallelCompileThreads;
|
private int _backendParallelCompileThreads;
|
||||||
private int _compiledCount;
|
private int _compiledCount;
|
||||||
|
@ -220,7 +219,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
_validationQueue = new Queue<ProgramEntry>();
|
_validationQueue = new Queue<ProgramEntry>();
|
||||||
_compilationQueue = new ConcurrentQueue<ProgramCompilation>();
|
_compilationQueue = new ConcurrentQueue<ProgramCompilation>();
|
||||||
_asyncTranslationQueue = new BlockingCollection<AsyncProgramTranslation>(ThreadCount);
|
_asyncTranslationQueue = new BlockingCollection<AsyncProgramTranslation>(ThreadCount);
|
||||||
_programList = new SortedList<int, CachedShaderProgram>();
|
_programList = new SortedList<int, (CachedShaderProgram, byte[])>();
|
||||||
_backendParallelCompileThreads = Math.Min(Environment.ProcessorCount, 8); // Must be kept in sync with the backend code.
|
_backendParallelCompileThreads = Math.Min(Environment.ProcessorCount, 8); // Must be kept in sync with the backend code.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,7 +234,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
{
|
{
|
||||||
workThreads[index] = new Thread(ProcessAsyncQueue)
|
workThreads[index] = new Thread(ProcessAsyncQueue)
|
||||||
{
|
{
|
||||||
Name = $"Gpu.AsyncTranslationThread.{index}"
|
Name = $"GPU.AsyncTranslationThread.{index}"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -287,7 +286,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
|
|
||||||
CheckCompilationBlocking();
|
CheckCompilationBlocking();
|
||||||
|
|
||||||
if (_needsHostRegen)
|
if (_needsHostRegen && Active)
|
||||||
{
|
{
|
||||||
// Rebuild both shared and host cache files.
|
// Rebuild both shared and host cache files.
|
||||||
// Rebuilding shared is required because the shader information returned by the translator
|
// Rebuilding shared is required because the shader information returned by the translator
|
||||||
|
@ -310,8 +309,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
CachedShaderProgram program = kv.Value;
|
(CachedShaderProgram program, byte[] binaryCode) = kv.Value;
|
||||||
_hostStorage.AddShader(_context, program, program.HostProgram.GetBinary(), streams);
|
_hostStorage.AddShader(_context, program, binaryCode, streams);
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Info?.Print(LogClass.Gpu, $"Rebuilt {_programList.Count} shaders successfully.");
|
Logger.Info?.Print(LogClass.Gpu, $"Rebuilt {_programList.Count} shaders successfully.");
|
||||||
|
@ -342,24 +341,31 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
/// Enqueues a host program for compilation.
|
/// Enqueues a host program for compilation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="cachedProgram">Cached program</param>
|
/// <param name="cachedProgram">Cached program</param>
|
||||||
/// <param name="hostProgram">Host program to be compiled</param>
|
/// <param name="binaryCode">Host binary code</param>
|
||||||
/// <param name="programIndex">Program index</param>
|
/// <param name="programIndex">Program index</param>
|
||||||
/// <param name="isCompute">Indicates if the program is a compute shader</param>
|
/// <param name="isCompute">Indicates if the program is a compute shader</param>
|
||||||
public void QueueHostProgram(CachedShaderProgram cachedProgram, IProgram hostProgram, int programIndex, bool isCompute)
|
public void QueueHostProgram(CachedShaderProgram cachedProgram, byte[] binaryCode, int programIndex, bool isCompute)
|
||||||
{
|
{
|
||||||
EnqueueForValidation(new ProgramEntry(cachedProgram, hostProgram, programIndex, isCompute, isBinary: true));
|
EnqueueForValidation(new ProgramEntry(cachedProgram, binaryCode, programIndex, isCompute, isBinary: true));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enqueues a guest program for compilation.
|
/// Enqueues a guest program for compilation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="shaders">Cached shader stages</param>
|
/// <param name="guestShaders">Guest code for each active stage</param>
|
||||||
/// <param name="specState">Specialization state</param>
|
/// <param name="specState">Specialization state</param>
|
||||||
/// <param name="programIndex">Program index</param>
|
/// <param name="programIndex">Program index</param>
|
||||||
/// <param name="isCompute">Indicates if the program is a compute shader</param>
|
/// <param name="isCompute">Indicates if the program is a compute shader</param>
|
||||||
public void QueueGuestProgram(CachedShaderStage[] shaders, ShaderSpecializationState specState, int programIndex, bool isCompute)
|
public void QueueGuestProgram(GuestCodeAndCbData?[] guestShaders, ShaderSpecializationState specState, int programIndex, bool isCompute)
|
||||||
{
|
{
|
||||||
_asyncTranslationQueue.Add(new AsyncProgramTranslation(shaders, specState, programIndex, isCompute));
|
try
|
||||||
|
{
|
||||||
|
AsyncProgramTranslation asyncTranslation = new AsyncProgramTranslation(guestShaders, specState, programIndex, isCompute);
|
||||||
|
_asyncTranslationQueue.Add(asyncTranslation, _cancellationToken);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -374,7 +380,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
// If not yet compiled, do nothing. This avoids blocking to wait for shader compilation.
|
// If not yet compiled, do nothing. This avoids blocking to wait for shader compilation.
|
||||||
while (_validationQueue.TryPeek(out ProgramEntry entry))
|
while (_validationQueue.TryPeek(out ProgramEntry entry))
|
||||||
{
|
{
|
||||||
ProgramLinkStatus result = entry.HostProgram.CheckProgramLink(false);
|
ProgramLinkStatus result = entry.CachedProgram.HostProgram.CheckProgramLink(false);
|
||||||
|
|
||||||
if (result != ProgramLinkStatus.Incomplete)
|
if (result != ProgramLinkStatus.Incomplete)
|
||||||
{
|
{
|
||||||
|
@ -398,7 +404,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
|
|
||||||
while (_validationQueue.TryDequeue(out ProgramEntry entry) && Active)
|
while (_validationQueue.TryDequeue(out ProgramEntry entry) && Active)
|
||||||
{
|
{
|
||||||
ProcessCompiledProgram(ref entry, entry.HostProgram.CheckProgramLink(true), asyncCompile: false);
|
ProcessCompiledProgram(ref entry, entry.CachedProgram.HostProgram.CheckProgramLink(true), asyncCompile: false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -427,7 +433,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
_needsHostRegen = true;
|
_needsHostRegen = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
_programList.Add(entry.ProgramIndex, entry.CachedProgram);
|
_programList.Add(entry.ProgramIndex, (entry.CachedProgram, entry.BinaryCode));
|
||||||
SignalCompiled();
|
SignalCompiled();
|
||||||
}
|
}
|
||||||
else if (entry.IsBinary)
|
else if (entry.IsBinary)
|
||||||
|
@ -436,13 +442,25 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
// we still have a chance to recompile from the guest binary.
|
// we still have a chance to recompile from the guest binary.
|
||||||
CachedShaderProgram program = entry.CachedProgram;
|
CachedShaderProgram program = entry.CachedProgram;
|
||||||
|
|
||||||
|
GuestCodeAndCbData?[] guestShaders = new GuestCodeAndCbData?[program.Shaders.Length];
|
||||||
|
|
||||||
|
for (int index = 0; index < program.Shaders.Length; index++)
|
||||||
|
{
|
||||||
|
CachedShaderStage shader = program.Shaders[index];
|
||||||
|
|
||||||
|
if (shader != null)
|
||||||
|
{
|
||||||
|
guestShaders[index] = new GuestCodeAndCbData(shader.Code, shader.Cb1Data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (asyncCompile)
|
if (asyncCompile)
|
||||||
{
|
{
|
||||||
QueueGuestProgram(program.Shaders, program.SpecializationState, entry.ProgramIndex, entry.IsCompute);
|
QueueGuestProgram(guestShaders, program.SpecializationState, entry.ProgramIndex, entry.IsCompute);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
RecompileFromGuestCode(program.Shaders, program.SpecializationState, entry.ProgramIndex, entry.IsCompute);
|
RecompileFromGuestCode(guestShaders, program.SpecializationState, entry.ProgramIndex, entry.IsCompute);
|
||||||
ProcessCompilationQueue();
|
ProcessCompilationQueue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -476,10 +494,16 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IProgram hostProgram = _context.Renderer.CreateProgram(shaderSources, new ShaderInfo(fragmentOutputMap));
|
ShaderInfo shaderInfo = compilation.SpecializationState.PipelineState.HasValue
|
||||||
|
? new ShaderInfo(fragmentOutputMap, compilation.SpecializationState.PipelineState.Value, fromCache: true)
|
||||||
|
: new ShaderInfo(fragmentOutputMap, fromCache: true);
|
||||||
|
|
||||||
|
IProgram hostProgram = _context.Renderer.CreateProgram(shaderSources, shaderInfo);
|
||||||
CachedShaderProgram program = new CachedShaderProgram(hostProgram, compilation.SpecializationState, compilation.Shaders);
|
CachedShaderProgram program = new CachedShaderProgram(hostProgram, compilation.SpecializationState, compilation.Shaders);
|
||||||
|
|
||||||
EnqueueForValidation(new ProgramEntry(program, hostProgram, compilation.ProgramIndex, compilation.IsCompute, isBinary: false));
|
byte[] binaryCode = _context.Capabilities.Api == TargetApi.Vulkan ? ShaderBinarySerializer.Pack(shaderSources) : hostProgram.GetBinary();
|
||||||
|
|
||||||
|
EnqueueForValidation(new ProgramEntry(program, binaryCode, compilation.ProgramIndex, compilation.IsCompute, isBinary: false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -496,7 +520,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
// Submitting more seems to cause NVIDIA OpenGL driver to crash.
|
// Submitting more seems to cause NVIDIA OpenGL driver to crash.
|
||||||
if (_validationQueue.Count >= _backendParallelCompileThreads && _validationQueue.TryDequeue(out ProgramEntry entry))
|
if (_validationQueue.Count >= _backendParallelCompileThreads && _validationQueue.TryDequeue(out ProgramEntry entry))
|
||||||
{
|
{
|
||||||
ProcessCompiledProgram(ref entry, entry.HostProgram.CheckProgramLink(true), asyncCompile: false);
|
ProcessCompiledProgram(ref entry, entry.CachedProgram.HostProgram.CheckProgramLink(true), asyncCompile: false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -513,7 +537,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
foreach (AsyncProgramTranslation asyncCompilation in _asyncTranslationQueue.GetConsumingEnumerable(ct))
|
foreach (AsyncProgramTranslation asyncCompilation in _asyncTranslationQueue.GetConsumingEnumerable(ct))
|
||||||
{
|
{
|
||||||
RecompileFromGuestCode(
|
RecompileFromGuestCode(
|
||||||
asyncCompilation.Shaders,
|
asyncCompilation.GuestShaders,
|
||||||
asyncCompilation.SpecializationState,
|
asyncCompilation.SpecializationState,
|
||||||
asyncCompilation.ProgramIndex,
|
asyncCompilation.ProgramIndex,
|
||||||
asyncCompilation.IsCompute);
|
asyncCompilation.IsCompute);
|
||||||
|
@ -527,21 +551,21 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Recompiles a program from guest code.
|
/// Recompiles a program from guest code.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="shaders">Shader stages</param>
|
/// <param name="guestShaders">Guest code for each active stage</param>
|
||||||
/// <param name="specState">Specialization state</param>
|
/// <param name="specState">Specialization state</param>
|
||||||
/// <param name="programIndex">Program index</param>
|
/// <param name="programIndex">Program index</param>
|
||||||
/// <param name="isCompute">Indicates if the program is a compute shader</param>
|
/// <param name="isCompute">Indicates if the program is a compute shader</param>
|
||||||
private void RecompileFromGuestCode(CachedShaderStage[] shaders, ShaderSpecializationState specState, int programIndex, bool isCompute)
|
private void RecompileFromGuestCode(GuestCodeAndCbData?[] guestShaders, ShaderSpecializationState specState, int programIndex, bool isCompute)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (isCompute)
|
if (isCompute)
|
||||||
{
|
{
|
||||||
RecompileComputeFromGuestCode(shaders, specState, programIndex);
|
RecompileComputeFromGuestCode(guestShaders, specState, programIndex);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
RecompileGraphicsFromGuestCode(shaders, specState, programIndex);
|
RecompileGraphicsFromGuestCode(guestShaders, specState, programIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (DiskCacheLoadException diskCacheLoadException)
|
catch (DiskCacheLoadException diskCacheLoadException)
|
||||||
|
@ -556,41 +580,47 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Recompiles a graphics program from guest code.
|
/// Recompiles a graphics program from guest code.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="shaders">Shader stages</param>
|
/// <param name="guestShaders">Guest code for each active stage</param>
|
||||||
/// <param name="specState">Specialization state</param>
|
/// <param name="specState">Specialization state</param>
|
||||||
/// <param name="programIndex">Program index</param>
|
/// <param name="programIndex">Program index</param>
|
||||||
private void RecompileGraphicsFromGuestCode(CachedShaderStage[] shaders, ShaderSpecializationState specState, int programIndex)
|
private void RecompileGraphicsFromGuestCode(GuestCodeAndCbData?[] guestShaders, ShaderSpecializationState specState, int programIndex)
|
||||||
{
|
{
|
||||||
ShaderSpecializationState newSpecState = new ShaderSpecializationState(specState.GraphicsState, specState.TransformFeedbackDescriptors);
|
ShaderSpecializationState newSpecState = new ShaderSpecializationState(
|
||||||
|
ref specState.GraphicsState,
|
||||||
|
specState.PipelineState,
|
||||||
|
specState.TransformFeedbackDescriptors);
|
||||||
|
|
||||||
ResourceCounts counts = new ResourceCounts();
|
ResourceCounts counts = new ResourceCounts();
|
||||||
|
|
||||||
TranslatorContext[] translatorContexts = new TranslatorContext[Constants.ShaderStages + 1];
|
TranslatorContext[] translatorContexts = new TranslatorContext[Constants.ShaderStages + 1];
|
||||||
TranslatorContext nextStage = null;
|
TranslatorContext nextStage = null;
|
||||||
|
|
||||||
|
TargetApi api = _context.Capabilities.Api;
|
||||||
|
|
||||||
for (int stageIndex = Constants.ShaderStages - 1; stageIndex >= 0; stageIndex--)
|
for (int stageIndex = Constants.ShaderStages - 1; stageIndex >= 0; stageIndex--)
|
||||||
{
|
{
|
||||||
CachedShaderStage shader = shaders[stageIndex + 1];
|
if (guestShaders[stageIndex + 1].HasValue)
|
||||||
|
|
||||||
if (shader != null)
|
|
||||||
{
|
{
|
||||||
|
GuestCodeAndCbData shader = guestShaders[stageIndex + 1].Value;
|
||||||
|
|
||||||
byte[] guestCode = shader.Code;
|
byte[] guestCode = shader.Code;
|
||||||
byte[] cb1Data = shader.Cb1Data;
|
byte[] cb1Data = shader.Cb1Data;
|
||||||
|
|
||||||
DiskCacheGpuAccessor gpuAccessor = new DiskCacheGpuAccessor(_context, guestCode, cb1Data, specState, newSpecState, counts, stageIndex);
|
DiskCacheGpuAccessor gpuAccessor = new DiskCacheGpuAccessor(_context, guestCode, cb1Data, specState, newSpecState, counts, stageIndex);
|
||||||
TranslatorContext currentStage = DecodeGraphicsShader(gpuAccessor, DefaultFlags, 0);
|
TranslatorContext currentStage = DecodeGraphicsShader(gpuAccessor, api, DefaultFlags, 0);
|
||||||
|
|
||||||
if (nextStage != null)
|
if (nextStage != null)
|
||||||
{
|
{
|
||||||
currentStage.SetNextStage(nextStage);
|
currentStage.SetNextStage(nextStage);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stageIndex == 0 && shaders[0] != null)
|
if (stageIndex == 0 && guestShaders[0].HasValue)
|
||||||
{
|
{
|
||||||
byte[] guestCodeA = shaders[0].Code;
|
byte[] guestCodeA = guestShaders[0].Value.Code;
|
||||||
byte[] cb1DataA = shaders[0].Cb1Data;
|
byte[] cb1DataA = guestShaders[0].Value.Cb1Data;
|
||||||
|
|
||||||
DiskCacheGpuAccessor gpuAccessorA = new DiskCacheGpuAccessor(_context, guestCodeA, cb1DataA, specState, newSpecState, counts, 0);
|
DiskCacheGpuAccessor gpuAccessorA = new DiskCacheGpuAccessor(_context, guestCodeA, cb1DataA, specState, newSpecState, counts, 0);
|
||||||
translatorContexts[0] = DecodeGraphicsShader(gpuAccessorA, DefaultFlags | TranslationFlags.VertexA, 0);
|
translatorContexts[0] = DecodeGraphicsShader(gpuAccessorA, api, DefaultFlags | TranslationFlags.VertexA, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
translatorContexts[stageIndex + 1] = currentStage;
|
translatorContexts[stageIndex + 1] = currentStage;
|
||||||
|
@ -598,6 +628,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CachedShaderStage[] shaders = new CachedShaderStage[guestShaders.Length];
|
||||||
List<ShaderProgram> translatedStages = new List<ShaderProgram>();
|
List<ShaderProgram> translatedStages = new List<ShaderProgram>();
|
||||||
|
|
||||||
for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++)
|
for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++)
|
||||||
|
@ -608,15 +639,15 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
{
|
{
|
||||||
ShaderProgram program;
|
ShaderProgram program;
|
||||||
|
|
||||||
byte[] guestCode = shaders[stageIndex + 1].Code;
|
byte[] guestCode = guestShaders[stageIndex + 1].Value.Code;
|
||||||
byte[] cb1Data = shaders[stageIndex + 1].Cb1Data;
|
byte[] cb1Data = guestShaders[stageIndex + 1].Value.Cb1Data;
|
||||||
|
|
||||||
if (stageIndex == 0 && shaders[0] != null)
|
if (stageIndex == 0 && guestShaders[0].HasValue)
|
||||||
{
|
{
|
||||||
program = currentStage.Translate(translatorContexts[0]);
|
program = currentStage.Translate(translatorContexts[0]);
|
||||||
|
|
||||||
byte[] guestCodeA = shaders[0].Code;
|
byte[] guestCodeA = guestShaders[0].Value.Code;
|
||||||
byte[] cb1DataA = shaders[0].Cb1Data;
|
byte[] cb1DataA = guestShaders[0].Value.Cb1Data;
|
||||||
|
|
||||||
shaders[0] = new CachedShaderStage(null, guestCodeA, cb1DataA);
|
shaders[0] = new CachedShaderStage(null, guestCodeA, cb1DataA);
|
||||||
shaders[1] = new CachedShaderStage(program.Info, guestCode, cb1Data);
|
shaders[1] = new CachedShaderStage(program.Info, guestCode, cb1Data);
|
||||||
|
@ -641,21 +672,21 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Recompiles a compute program from guest code.
|
/// Recompiles a compute program from guest code.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="shaders">Shader stages</param>
|
/// <param name="guestShaders">Guest code for each active stage</param>
|
||||||
/// <param name="specState">Specialization state</param>
|
/// <param name="specState">Specialization state</param>
|
||||||
/// <param name="programIndex">Program index</param>
|
/// <param name="programIndex">Program index</param>
|
||||||
private void RecompileComputeFromGuestCode(CachedShaderStage[] shaders, ShaderSpecializationState specState, int programIndex)
|
private void RecompileComputeFromGuestCode(GuestCodeAndCbData?[] guestShaders, ShaderSpecializationState specState, int programIndex)
|
||||||
{
|
{
|
||||||
CachedShaderStage shader = shaders[0];
|
GuestCodeAndCbData shader = guestShaders[0].Value;
|
||||||
ResourceCounts counts = new ResourceCounts();
|
ResourceCounts counts = new ResourceCounts();
|
||||||
ShaderSpecializationState newSpecState = new ShaderSpecializationState(specState.ComputeState);
|
ShaderSpecializationState newSpecState = new ShaderSpecializationState(ref specState.ComputeState);
|
||||||
DiskCacheGpuAccessor gpuAccessor = new DiskCacheGpuAccessor(_context, shader.Code, shader.Cb1Data, specState, newSpecState, counts, 0);
|
DiskCacheGpuAccessor gpuAccessor = new DiskCacheGpuAccessor(_context, shader.Code, shader.Cb1Data, specState, newSpecState, counts, 0);
|
||||||
|
|
||||||
TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, 0);
|
TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, _context.Capabilities.Api, 0);
|
||||||
|
|
||||||
ShaderProgram program = translatorContext.Translate();
|
ShaderProgram program = translatorContext.Translate();
|
||||||
|
|
||||||
shaders[0] = new CachedShaderStage(program.Info, shader.Code, shader.Cb1Data);
|
CachedShaderStage[] shaders = new[] { new CachedShaderStage(program.Info, shader.Code, shader.Cb1Data) };
|
||||||
|
|
||||||
_compilationQueue.Enqueue(new ProgramCompilation(new[] { program }, shaders, newSpecState, programIndex, isCompute: true));
|
_compilationQueue.Enqueue(new ProgramCompilation(new[] { program }, shaders, newSpecState, programIndex, isCompute: true));
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
using Ryujinx.Graphics.GAL;
|
||||||
|
using Ryujinx.Graphics.Shader.Translation;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
|
{
|
||||||
|
static class ShaderBinarySerializer
|
||||||
|
{
|
||||||
|
public static byte[] Pack(ShaderSource[] sources)
|
||||||
|
{
|
||||||
|
using MemoryStream output = new MemoryStream();
|
||||||
|
using BinaryWriter writer = new BinaryWriter(output);
|
||||||
|
|
||||||
|
for (int i = 0; i < sources.Length; i++)
|
||||||
|
{
|
||||||
|
writer.Write(sources[i].BinaryCode.Length);
|
||||||
|
writer.Write(sources[i].BinaryCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return output.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ShaderSource[] Unpack(CachedShaderStage[] stages, byte[] code, bool compute)
|
||||||
|
{
|
||||||
|
using MemoryStream input = new MemoryStream(code);
|
||||||
|
using BinaryReader reader = new BinaryReader(input);
|
||||||
|
|
||||||
|
List<ShaderSource> output = new List<ShaderSource>();
|
||||||
|
|
||||||
|
for (int i = compute ? 0 : 1; i < stages.Length; i++)
|
||||||
|
{
|
||||||
|
CachedShaderStage stage = stages[i];
|
||||||
|
|
||||||
|
if (stage == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int binaryCodeLength = reader.ReadInt32();
|
||||||
|
byte[] binaryCode = reader.ReadBytes(binaryCodeLength);
|
||||||
|
|
||||||
|
output.Add(new ShaderSource(binaryCode, ShaderCache.GetBindings(stage.Info), stage.Info.Stage, TargetLanguage.Spirv));
|
||||||
|
}
|
||||||
|
|
||||||
|
return output.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Graphics.GAL;
|
||||||
using Ryujinx.Graphics.Gpu.Image;
|
using Ryujinx.Graphics.Gpu.Image;
|
||||||
using Ryujinx.Graphics.Shader;
|
using Ryujinx.Graphics.Shader;
|
||||||
|
using Ryujinx.Graphics.Shader.Translation;
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
@ -15,6 +17,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
private readonly GpuAccessorState _state;
|
private readonly GpuAccessorState _state;
|
||||||
private readonly int _stageIndex;
|
private readonly int _stageIndex;
|
||||||
private readonly bool _compute;
|
private readonly bool _compute;
|
||||||
|
private readonly bool _isVulkan;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new instance of the GPU state accessor for graphics shader translation.
|
/// Creates a new instance of the GPU state accessor for graphics shader translation.
|
||||||
|
@ -23,8 +26,13 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
/// <param name="channel">GPU channel</param>
|
/// <param name="channel">GPU channel</param>
|
||||||
/// <param name="state">Current GPU state</param>
|
/// <param name="state">Current GPU state</param>
|
||||||
/// <param name="stageIndex">Graphics shader stage index (0 = Vertex, 4 = Fragment)</param>
|
/// <param name="stageIndex">Graphics shader stage index (0 = Vertex, 4 = Fragment)</param>
|
||||||
public GpuAccessor(GpuContext context, GpuChannel channel, GpuAccessorState state, int stageIndex) : base(context)
|
public GpuAccessor(
|
||||||
|
GpuContext context,
|
||||||
|
GpuChannel channel,
|
||||||
|
GpuAccessorState state,
|
||||||
|
int stageIndex) : base(context, state.ResourceCounts, stageIndex)
|
||||||
{
|
{
|
||||||
|
_isVulkan = context.Capabilities.Api == TargetApi.Vulkan;
|
||||||
_channel = channel;
|
_channel = channel;
|
||||||
_state = state;
|
_state = state;
|
||||||
_stageIndex = stageIndex;
|
_stageIndex = stageIndex;
|
||||||
|
@ -36,7 +44,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
/// <param name="context">GPU context</param>
|
/// <param name="context">GPU context</param>
|
||||||
/// <param name="channel">GPU channel</param>
|
/// <param name="channel">GPU channel</param>
|
||||||
/// <param name="state">Current GPU state</param>
|
/// <param name="state">Current GPU state</param>
|
||||||
public GpuAccessor(GpuContext context, GpuChannel channel, GpuAccessorState state) : base(context)
|
public GpuAccessor(GpuContext context, GpuChannel channel, GpuAccessorState state) : base(context, state.ResourceCounts, 0)
|
||||||
{
|
{
|
||||||
_channel = channel;
|
_channel = channel;
|
||||||
_state = state;
|
_state = state;
|
||||||
|
@ -67,27 +75,36 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public int QueryBindingConstantBuffer(int index)
|
public AlphaTestOp QueryAlphaTestCompare()
|
||||||
{
|
{
|
||||||
return _state.ResourceCounts.UniformBuffersCount++;
|
if (!_isVulkan || !_state.GraphicsState.AlphaTestEnable)
|
||||||
|
{
|
||||||
|
return AlphaTestOp.Always;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _state.GraphicsState.AlphaTestCompare switch
|
||||||
|
{
|
||||||
|
CompareOp.Never or CompareOp.NeverGl => AlphaTestOp.Never,
|
||||||
|
CompareOp.Less or CompareOp.LessGl => AlphaTestOp.Less,
|
||||||
|
CompareOp.Equal or CompareOp.EqualGl => AlphaTestOp.Equal,
|
||||||
|
CompareOp.LessOrEqual or CompareOp.LessOrEqualGl => AlphaTestOp.LessOrEqual,
|
||||||
|
CompareOp.Greater or CompareOp.GreaterGl => AlphaTestOp.Greater,
|
||||||
|
CompareOp.NotEqual or CompareOp.NotEqualGl => AlphaTestOp.NotEqual,
|
||||||
|
CompareOp.GreaterOrEqual or CompareOp.GreaterOrEqualGl => AlphaTestOp.GreaterOrEqual,
|
||||||
|
_ => AlphaTestOp.Always
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public int QueryBindingStorageBuffer(int index)
|
public float QueryAlphaTestReference()
|
||||||
{
|
{
|
||||||
return _state.ResourceCounts.StorageBuffersCount++;
|
return _state.GraphicsState.AlphaTestReference;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public int QueryBindingTexture(int index)
|
public AttributeType QueryAttributeType(int location)
|
||||||
{
|
{
|
||||||
return _state.ResourceCounts.TexturesCount++;
|
return _state.GraphicsState.AttributeTypes[location];
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public int QueryBindingImage(int index)
|
|
||||||
{
|
|
||||||
return _state.ResourceCounts.ImagesCount++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
@ -123,6 +140,18 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
return ConvertToInputTopology(_state.GraphicsState.Topology, _state.GraphicsState.TessellationMode);
|
return ConvertToInputTopology(_state.GraphicsState.Topology, _state.GraphicsState.TessellationMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool QueryProgramPointSize()
|
||||||
|
{
|
||||||
|
return _state.GraphicsState.ProgramPointSizeEnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public float QueryPointSize()
|
||||||
|
{
|
||||||
|
return _state.GraphicsState.PointSize;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool QueryTessCw()
|
public bool QueryTessCw()
|
||||||
{
|
{
|
||||||
|
@ -192,6 +221,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool QueryTransformDepthMinusOneToOne()
|
||||||
|
{
|
||||||
|
return _state.GraphicsState.DepthMode;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool QueryTransformFeedbackEnabled()
|
public bool QueryTransformFeedbackEnabled()
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
using Ryujinx.Graphics.Gpu.Engine.Threed;
|
using Ryujinx.Graphics.Gpu.Engine.Threed;
|
||||||
using Ryujinx.Graphics.Gpu.Image;
|
using Ryujinx.Graphics.Gpu.Image;
|
||||||
using Ryujinx.Graphics.Shader;
|
using Ryujinx.Graphics.Shader;
|
||||||
|
using Ryujinx.Graphics.Shader.Translation;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Gpu.Shader
|
namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
{
|
{
|
||||||
|
@ -11,74 +13,139 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
class GpuAccessorBase
|
class GpuAccessorBase
|
||||||
{
|
{
|
||||||
private readonly GpuContext _context;
|
private readonly GpuContext _context;
|
||||||
|
private readonly ResourceCounts _resourceCounts;
|
||||||
|
private readonly int _stageIndex;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new GPU accessor.
|
/// Creates a new GPU accessor.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="context">GPU context</param>
|
/// <param name="context">GPU context</param>
|
||||||
public GpuAccessorBase(GpuContext context)
|
public GpuAccessorBase(GpuContext context, ResourceCounts resourceCounts, int stageIndex)
|
||||||
{
|
{
|
||||||
_context = context;
|
_context = context;
|
||||||
|
_resourceCounts = resourceCounts;
|
||||||
|
_stageIndex = stageIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Queries host about the presence of the FrontFacing built-in variable bug.
|
public int QueryBindingConstantBuffer(int index)
|
||||||
/// </summary>
|
{
|
||||||
/// <returns>True if the bug is present on the host device used, false otherwise</returns>
|
if (_context.Capabilities.Api == TargetApi.Vulkan)
|
||||||
|
{
|
||||||
|
return 1 + GetBindingFromIndex(index, _context.Capabilities.MaximumUniformBuffersPerStage, "Uniform buffer");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return _resourceCounts.UniformBuffersCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int QueryBindingStorageBuffer(int index)
|
||||||
|
{
|
||||||
|
if (_context.Capabilities.Api == TargetApi.Vulkan)
|
||||||
|
{
|
||||||
|
return GetBindingFromIndex(index, _context.Capabilities.MaximumStorageBuffersPerStage, "Storage buffer");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return _resourceCounts.StorageBuffersCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int QueryBindingTexture(int index, bool isBuffer)
|
||||||
|
{
|
||||||
|
if (_context.Capabilities.Api == TargetApi.Vulkan)
|
||||||
|
{
|
||||||
|
if (isBuffer)
|
||||||
|
{
|
||||||
|
index += (int)_context.Capabilities.MaximumTexturesPerStage;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetBindingFromIndex(index, _context.Capabilities.MaximumTexturesPerStage * 2, "Texture");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return _resourceCounts.TexturesCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int QueryBindingImage(int index, bool isBuffer)
|
||||||
|
{
|
||||||
|
if (_context.Capabilities.Api == TargetApi.Vulkan)
|
||||||
|
{
|
||||||
|
if (isBuffer)
|
||||||
|
{
|
||||||
|
index += (int)_context.Capabilities.MaximumImagesPerStage;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetBindingFromIndex(index, _context.Capabilities.MaximumImagesPerStage * 2, "Image");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return _resourceCounts.ImagesCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetBindingFromIndex(int index, uint maxPerStage, string resourceName)
|
||||||
|
{
|
||||||
|
if ((uint)index >= maxPerStage)
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Gpu, $"{resourceName} index {index} exceeds per stage limit of {maxPerStage}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetStageIndex() * (int)maxPerStage + index;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetStageIndex()
|
||||||
|
{
|
||||||
|
// This is just a simple remapping to ensure that most frequently used shader stages
|
||||||
|
// have the lowest binding numbers.
|
||||||
|
// This is useful because if we need to run on a system with a low limit on the bindings,
|
||||||
|
// then we can still get most games working as the most common shaders will have low binding numbers.
|
||||||
|
return _stageIndex switch
|
||||||
|
{
|
||||||
|
4 => 1, // Fragment
|
||||||
|
3 => 2, // Geometry
|
||||||
|
1 => 3, // Tessellation control
|
||||||
|
2 => 4, // Tessellation evaluation
|
||||||
|
_ => 0 // Vertex/Compute
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public bool QueryHostHasFrontFacingBug() => _context.Capabilities.HasFrontFacingBug;
|
public bool QueryHostHasFrontFacingBug() => _context.Capabilities.HasFrontFacingBug;
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Queries host about the presence of the vector indexing bug.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>True if the bug is present on the host device used, false otherwise</returns>
|
|
||||||
public bool QueryHostHasVectorIndexingBug() => _context.Capabilities.HasVectorIndexingBug;
|
public bool QueryHostHasVectorIndexingBug() => _context.Capabilities.HasVectorIndexingBug;
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Queries host storage buffer alignment required.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>Host storage buffer alignment in bytes</returns>
|
|
||||||
public int QueryHostStorageBufferOffsetAlignment() => _context.Capabilities.StorageBufferOffsetAlignment;
|
public int QueryHostStorageBufferOffsetAlignment() => _context.Capabilities.StorageBufferOffsetAlignment;
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Queries host support for texture formats with BGRA component order (such as BGRA8).
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>True if BGRA formats are supported, false otherwise</returns>
|
|
||||||
public bool QueryHostSupportsBgraFormat() => _context.Capabilities.SupportsBgraFormat;
|
public bool QueryHostSupportsBgraFormat() => _context.Capabilities.SupportsBgraFormat;
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Queries host support for fragment shader ordering critical sections on the shader code.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>True if fragment shader interlock is supported, false otherwise</returns>
|
|
||||||
public bool QueryHostSupportsFragmentShaderInterlock() => _context.Capabilities.SupportsFragmentShaderInterlock;
|
public bool QueryHostSupportsFragmentShaderInterlock() => _context.Capabilities.SupportsFragmentShaderInterlock;
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Queries host support for fragment shader ordering scoped critical sections on the shader code.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>True if fragment shader ordering is supported, false otherwise</returns>
|
|
||||||
public bool QueryHostSupportsFragmentShaderOrderingIntel() => _context.Capabilities.SupportsFragmentShaderOrderingIntel;
|
public bool QueryHostSupportsFragmentShaderOrderingIntel() => _context.Capabilities.SupportsFragmentShaderOrderingIntel;
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Queries host support for readable images without a explicit format declaration on the shader.
|
public bool QueryHostSupportsGeometryShaderPassthrough() => _context.Capabilities.SupportsGeometryShaderPassthrough;
|
||||||
/// </summary>
|
|
||||||
/// <returns>True if formatted image load is supported, false otherwise</returns>
|
/// <inheritdoc/>
|
||||||
public bool QueryHostSupportsImageLoadFormatted() => _context.Capabilities.SupportsImageLoadFormatted;
|
public bool QueryHostSupportsImageLoadFormatted() => _context.Capabilities.SupportsImageLoadFormatted;
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Queries host GPU non-constant texture offset support.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>True if the GPU and driver supports non-constant texture offsets, false otherwise</returns>
|
|
||||||
public bool QueryHostSupportsNonConstantTextureOffset() => _context.Capabilities.SupportsNonConstantTextureOffset;
|
public bool QueryHostSupportsNonConstantTextureOffset() => _context.Capabilities.SupportsNonConstantTextureOffset;
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Queries host GPU shader ballot support.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>True if the GPU and driver supports shader ballot, false otherwise</returns>
|
|
||||||
public bool QueryHostSupportsShaderBallot() => _context.Capabilities.SupportsShaderBallot;
|
public bool QueryHostSupportsShaderBallot() => _context.Capabilities.SupportsShaderBallot;
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Queries host GPU texture shadow LOD support.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>True if the GPU and driver supports texture shadow LOD, false otherwise</returns>
|
|
||||||
public bool QueryHostSupportsTextureShadowLod() => _context.Capabilities.SupportsTextureShadowLod;
|
public bool QueryHostSupportsTextureShadowLod() => _context.Capabilities.SupportsTextureShadowLod;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
using Ryujinx.Graphics.Gpu.Engine.Threed;
|
using Ryujinx.Graphics.Gpu.Engine.Threed;
|
||||||
|
using Ryujinx.Graphics.Shader;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Gpu.Shader
|
namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
{
|
{
|
||||||
|
@ -30,6 +32,41 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly bool ViewportTransformDisable;
|
public readonly bool ViewportTransformDisable;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Depth mode zero to one or minus one to one.
|
||||||
|
/// </summary>
|
||||||
|
public readonly bool DepthMode;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates if the point size is set on the shader or is fixed.
|
||||||
|
/// </summary>
|
||||||
|
public readonly bool ProgramPointSizeEnable;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Point size if not set from shader.
|
||||||
|
/// </summary>
|
||||||
|
public readonly float PointSize;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates whenever alpha test is enabled.
|
||||||
|
/// </summary>
|
||||||
|
public readonly bool AlphaTestEnable;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When alpha test is enabled, indicates the comparison that decides if the fragment is discarded.
|
||||||
|
/// </summary>
|
||||||
|
public readonly CompareOp AlphaTestCompare;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When alpha test is enabled, indicates the value to compare with the fragment output alpha.
|
||||||
|
/// </summary>
|
||||||
|
public readonly float AlphaTestReference;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Type of the vertex attributes consumed by the shader.
|
||||||
|
/// </summary>
|
||||||
|
public Array32<AttributeType> AttributeTypes;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new GPU graphics state.
|
/// Creates a new GPU graphics state.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -37,12 +74,37 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
/// <param name="topology">Primitive topology</param>
|
/// <param name="topology">Primitive topology</param>
|
||||||
/// <param name="tessellationMode">Tessellation mode</param>
|
/// <param name="tessellationMode">Tessellation mode</param>
|
||||||
/// <param name="viewportTransformDisable">Indicates whenever the viewport transform is disabled</param>
|
/// <param name="viewportTransformDisable">Indicates whenever the viewport transform is disabled</param>
|
||||||
public GpuChannelGraphicsState(bool earlyZForce, PrimitiveTopology topology, TessMode tessellationMode, bool viewportTransformDisable)
|
/// <param name="depthMode">Depth mode zero to one or minus one to one</param>
|
||||||
|
/// <param name="programPointSizeEnable">Indicates if the point size is set on the shader or is fixed</param>
|
||||||
|
/// <param name="pointSize">Point size if not set from shader</param>
|
||||||
|
/// <param name="alphaTestEnable">Indicates whenever alpha test is enabled</param>
|
||||||
|
/// <param name="alphaTestCompare">When alpha test is enabled, indicates the comparison that decides if the fragment is discarded</param>
|
||||||
|
/// <param name="alphaTestReference">When alpha test is enabled, indicates the value to compare with the fragment output alpha</param>
|
||||||
|
/// <param name="attributeTypes">Type of the vertex attributes consumed by the shader</param>
|
||||||
|
public GpuChannelGraphicsState(
|
||||||
|
bool earlyZForce,
|
||||||
|
PrimitiveTopology topology,
|
||||||
|
TessMode tessellationMode,
|
||||||
|
bool viewportTransformDisable,
|
||||||
|
bool depthMode,
|
||||||
|
bool programPointSizeEnable,
|
||||||
|
float pointSize,
|
||||||
|
bool alphaTestEnable,
|
||||||
|
CompareOp alphaTestCompare,
|
||||||
|
float alphaTestReference,
|
||||||
|
ref Array32<AttributeType> attributeTypes)
|
||||||
{
|
{
|
||||||
EarlyZForce = earlyZForce;
|
EarlyZForce = earlyZForce;
|
||||||
Topology = topology;
|
Topology = topology;
|
||||||
TessellationMode = tessellationMode;
|
TessellationMode = tessellationMode;
|
||||||
ViewportTransformDisable = viewportTransformDisable;
|
ViewportTransformDisable = viewportTransformDisable;
|
||||||
|
DepthMode = depthMode;
|
||||||
|
ProgramPointSizeEnable = programPointSizeEnable;
|
||||||
|
PointSize = pointSize;
|
||||||
|
AlphaTestEnable = alphaTestEnable;
|
||||||
|
AlphaTestCompare = alphaTestCompare;
|
||||||
|
AlphaTestReference = alphaTestReference;
|
||||||
|
AttributeTypes = attributeTypes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
using Ryujinx.Graphics.Gpu.Engine.Threed;
|
using Ryujinx.Graphics.Gpu.Engine.Threed;
|
||||||
|
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||||
|
using Ryujinx.Graphics.Gpu.Image;
|
||||||
using Ryujinx.Graphics.Gpu.Memory;
|
using Ryujinx.Graphics.Gpu.Memory;
|
||||||
using Ryujinx.Graphics.Gpu.Shader.Cache;
|
using Ryujinx.Graphics.Gpu.Shader.Cache;
|
||||||
using Ryujinx.Graphics.Gpu.Shader.DiskCache;
|
using Ryujinx.Graphics.Gpu.Shader.DiskCache;
|
||||||
|
@ -8,6 +10,7 @@ using Ryujinx.Graphics.Shader;
|
||||||
using Ryujinx.Graphics.Shader.Translation;
|
using Ryujinx.Graphics.Shader.Translation;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Gpu.Shader
|
namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
|
@ -59,11 +62,13 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
{
|
{
|
||||||
public readonly CachedShaderProgram CachedProgram;
|
public readonly CachedShaderProgram CachedProgram;
|
||||||
public readonly IProgram HostProgram;
|
public readonly IProgram HostProgram;
|
||||||
|
public readonly byte[] BinaryCode;
|
||||||
|
|
||||||
public ProgramToSave(CachedShaderProgram cachedProgram, IProgram hostProgram)
|
public ProgramToSave(CachedShaderProgram cachedProgram, IProgram hostProgram, byte[] binaryCode)
|
||||||
{
|
{
|
||||||
CachedProgram = cachedProgram;
|
CachedProgram = cachedProgram;
|
||||||
HostProgram = hostProgram;
|
HostProgram = hostProgram;
|
||||||
|
BinaryCode = binaryCode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,7 +128,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
{
|
{
|
||||||
if (result == ProgramLinkStatus.Success)
|
if (result == ProgramLinkStatus.Success)
|
||||||
{
|
{
|
||||||
_cacheWriter.AddShader(programToSave.CachedProgram, programToSave.HostProgram.GetBinary());
|
_cacheWriter.AddShader(programToSave.CachedProgram, programToSave.BinaryCode ?? programToSave.HostProgram.GetBinary());
|
||||||
}
|
}
|
||||||
|
|
||||||
_programsToSaveQueue.Dequeue();
|
_programsToSaveQueue.Dequeue();
|
||||||
|
@ -143,7 +148,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
{
|
{
|
||||||
if (_diskCacheHostStorage.CacheEnabled)
|
if (_diskCacheHostStorage.CacheEnabled)
|
||||||
{
|
{
|
||||||
if (!_diskCacheHostStorage.CacheExists())
|
// Migration disabled as Vulkan added a lot of new state,
|
||||||
|
// most migrated shaders would be unused due to the state not matching.
|
||||||
|
/* if (!_diskCacheHostStorage.CacheExists())
|
||||||
{
|
{
|
||||||
// If we don't have a shader cache on the new format, try to perform migration from the old shader cache.
|
// If we don't have a shader cache on the new format, try to perform migration from the old shader cache.
|
||||||
Logger.Info?.Print(LogClass.Gpu, "No shader cache found, trying to migrate from legacy shader cache...");
|
Logger.Info?.Print(LogClass.Gpu, "No shader cache found, trying to migrate from legacy shader cache...");
|
||||||
|
@ -151,7 +158,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
int migrationCount = Migration.MigrateFromLegacyCache(_context, _diskCacheHostStorage);
|
int migrationCount = Migration.MigrateFromLegacyCache(_context, _diskCacheHostStorage);
|
||||||
|
|
||||||
Logger.Info?.Print(LogClass.Gpu, $"Migrated {migrationCount} shaders.");
|
Logger.Info?.Print(LogClass.Gpu, $"Migrated {migrationCount} shaders.");
|
||||||
}
|
} */
|
||||||
|
|
||||||
ParallelDiskCacheLoader loader = new ParallelDiskCacheLoader(
|
ParallelDiskCacheLoader loader = new ParallelDiskCacheLoader(
|
||||||
_context,
|
_context,
|
||||||
|
@ -210,25 +217,67 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
return cpShader;
|
return cpShader;
|
||||||
}
|
}
|
||||||
|
|
||||||
ShaderSpecializationState specState = new ShaderSpecializationState(computeState);
|
ShaderSpecializationState specState = new ShaderSpecializationState(ref computeState);
|
||||||
GpuAccessorState gpuAccessorState = new GpuAccessorState(poolState, computeState, default, specState);
|
GpuAccessorState gpuAccessorState = new GpuAccessorState(poolState, computeState, default, specState);
|
||||||
GpuAccessor gpuAccessor = new GpuAccessor(_context, channel, gpuAccessorState);
|
GpuAccessor gpuAccessor = new GpuAccessor(_context, channel, gpuAccessorState);
|
||||||
|
|
||||||
TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, gpuVa);
|
TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, _context.Capabilities.Api, gpuVa);
|
||||||
|
|
||||||
TranslatedShader translatedShader = TranslateShader(_dumper, channel, translatorContext, cachedGuestCode);
|
TranslatedShader translatedShader = TranslateShader(_dumper, channel, translatorContext, cachedGuestCode);
|
||||||
|
|
||||||
IProgram hostProgram = _context.Renderer.CreateProgram(new ShaderSource[] { CreateShaderSource(translatedShader.Program) }, new ShaderInfo(-1));
|
ShaderSource[] shaderSourcesArray = new ShaderSource[] { CreateShaderSource(translatedShader.Program) };
|
||||||
|
|
||||||
|
IProgram hostProgram = _context.Renderer.CreateProgram(shaderSourcesArray, new ShaderInfo(-1));
|
||||||
|
|
||||||
cpShader = new CachedShaderProgram(hostProgram, specState, translatedShader.Shader);
|
cpShader = new CachedShaderProgram(hostProgram, specState, translatedShader.Shader);
|
||||||
|
|
||||||
_computeShaderCache.Add(cpShader);
|
_computeShaderCache.Add(cpShader);
|
||||||
EnqueueProgramToSave(new ProgramToSave(cpShader, hostProgram));
|
EnqueueProgramToSave(cpShader, hostProgram, shaderSourcesArray);
|
||||||
_cpPrograms[gpuVa] = cpShader;
|
_cpPrograms[gpuVa] = cpShader;
|
||||||
|
|
||||||
return cpShader;
|
return cpShader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdatePipelineInfo(
|
||||||
|
ref ThreedClassState state,
|
||||||
|
ref ProgramPipelineState pipeline,
|
||||||
|
GpuChannelGraphicsState graphicsState,
|
||||||
|
GpuChannel channel)
|
||||||
|
{
|
||||||
|
channel.TextureManager.UpdateRenderTargets();
|
||||||
|
|
||||||
|
var rtControl = state.RtControl;
|
||||||
|
var msaaMode = state.RtMsaaMode;
|
||||||
|
|
||||||
|
pipeline.SamplesCount = msaaMode.SamplesInX() * msaaMode.SamplesInY();
|
||||||
|
|
||||||
|
int count = rtControl.UnpackCount();
|
||||||
|
|
||||||
|
for (int index = 0; index < Constants.TotalRenderTargets; index++)
|
||||||
|
{
|
||||||
|
int rtIndex = rtControl.UnpackPermutationIndex(index);
|
||||||
|
|
||||||
|
var colorState = state.RtColorState[rtIndex];
|
||||||
|
|
||||||
|
if (index >= count || colorState.Format == 0 || colorState.WidthOrStride == 0)
|
||||||
|
{
|
||||||
|
pipeline.AttachmentEnable[index] = false;
|
||||||
|
pipeline.AttachmentFormats[index] = Format.R8G8B8A8Unorm;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pipeline.AttachmentEnable[index] = true;
|
||||||
|
pipeline.AttachmentFormats[index] = colorState.Format.Convert().Format;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pipeline.DepthStencilEnable = state.RtDepthStencilEnable;
|
||||||
|
pipeline.DepthStencilFormat = pipeline.DepthStencilEnable ? state.RtDepthStencilState.Format.Convert().Format : Format.D24UnormS8Uint;
|
||||||
|
|
||||||
|
pipeline.VertexBufferCount = Constants.TotalVertexBuffers;
|
||||||
|
pipeline.Topology = graphicsState.Topology;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a graphics shader program from the shader cache.
|
/// Gets a graphics shader program from the shader cache.
|
||||||
/// This includes all the specified shader stages.
|
/// This includes all the specified shader stages.
|
||||||
|
@ -237,6 +286,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
/// This automatically translates, compiles and adds the code to the cache if not present.
|
/// This automatically translates, compiles and adds the code to the cache if not present.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <param name="state">GPU state</param>
|
/// <param name="state">GPU state</param>
|
||||||
|
/// <param name="pipeline">Pipeline state</param>
|
||||||
/// <param name="channel">GPU channel</param>
|
/// <param name="channel">GPU channel</param>
|
||||||
/// <param name="poolState">Texture pool state</param>
|
/// <param name="poolState">Texture pool state</param>
|
||||||
/// <param name="graphicsState">3D engine state</param>
|
/// <param name="graphicsState">3D engine state</param>
|
||||||
|
@ -244,6 +294,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
/// <returns>Compiled graphics shader code</returns>
|
/// <returns>Compiled graphics shader code</returns>
|
||||||
public CachedShaderProgram GetGraphicsShader(
|
public CachedShaderProgram GetGraphicsShader(
|
||||||
ref ThreedClassState state,
|
ref ThreedClassState state,
|
||||||
|
ref ProgramPipelineState pipeline,
|
||||||
GpuChannel channel,
|
GpuChannel channel,
|
||||||
GpuChannelPoolState poolState,
|
GpuChannelPoolState poolState,
|
||||||
GpuChannelGraphicsState graphicsState,
|
GpuChannelGraphicsState graphicsState,
|
||||||
|
@ -262,7 +313,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
|
|
||||||
TransformFeedbackDescriptor[] transformFeedbackDescriptors = GetTransformFeedbackDescriptors(ref state);
|
TransformFeedbackDescriptor[] transformFeedbackDescriptors = GetTransformFeedbackDescriptors(ref state);
|
||||||
|
|
||||||
ShaderSpecializationState specState = new ShaderSpecializationState(graphicsState, transformFeedbackDescriptors);
|
UpdatePipelineInfo(ref state, ref pipeline, graphicsState, channel);
|
||||||
|
|
||||||
|
ShaderSpecializationState specState = new ShaderSpecializationState(ref graphicsState, ref pipeline, transformFeedbackDescriptors);
|
||||||
GpuAccessorState gpuAccessorState = new GpuAccessorState(poolState, default, graphicsState, specState, transformFeedbackDescriptors);
|
GpuAccessorState gpuAccessorState = new GpuAccessorState(poolState, default, graphicsState, specState, transformFeedbackDescriptors);
|
||||||
|
|
||||||
ReadOnlySpan<ulong> addressesSpan = addresses.AsSpan();
|
ReadOnlySpan<ulong> addressesSpan = addresses.AsSpan();
|
||||||
|
@ -270,6 +323,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
TranslatorContext[] translatorContexts = new TranslatorContext[Constants.ShaderStages + 1];
|
TranslatorContext[] translatorContexts = new TranslatorContext[Constants.ShaderStages + 1];
|
||||||
TranslatorContext nextStage = null;
|
TranslatorContext nextStage = null;
|
||||||
|
|
||||||
|
TargetApi api = _context.Capabilities.Api;
|
||||||
|
|
||||||
for (int stageIndex = Constants.ShaderStages - 1; stageIndex >= 0; stageIndex--)
|
for (int stageIndex = Constants.ShaderStages - 1; stageIndex >= 0; stageIndex--)
|
||||||
{
|
{
|
||||||
ulong gpuVa = addressesSpan[stageIndex + 1];
|
ulong gpuVa = addressesSpan[stageIndex + 1];
|
||||||
|
@ -277,7 +332,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
if (gpuVa != 0)
|
if (gpuVa != 0)
|
||||||
{
|
{
|
||||||
GpuAccessor gpuAccessor = new GpuAccessor(_context, channel, gpuAccessorState, stageIndex);
|
GpuAccessor gpuAccessor = new GpuAccessor(_context, channel, gpuAccessorState, stageIndex);
|
||||||
TranslatorContext currentStage = DecodeGraphicsShader(gpuAccessor, DefaultFlags, gpuVa);
|
TranslatorContext currentStage = DecodeGraphicsShader(gpuAccessor, api, DefaultFlags, gpuVa);
|
||||||
|
|
||||||
if (nextStage != null)
|
if (nextStage != null)
|
||||||
{
|
{
|
||||||
|
@ -286,7 +341,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
|
|
||||||
if (stageIndex == 0 && addresses.VertexA != 0)
|
if (stageIndex == 0 && addresses.VertexA != 0)
|
||||||
{
|
{
|
||||||
translatorContexts[0] = DecodeGraphicsShader(gpuAccessor, DefaultFlags | TranslationFlags.VertexA, addresses.VertexA);
|
translatorContexts[0] = DecodeGraphicsShader(gpuAccessor, api, DefaultFlags | TranslationFlags.VertexA, addresses.VertexA);
|
||||||
}
|
}
|
||||||
|
|
||||||
translatorContexts[stageIndex + 1] = currentStage;
|
translatorContexts[stageIndex + 1] = currentStage;
|
||||||
|
@ -336,13 +391,15 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ShaderSource[] shaderSourcesArray = shaderSources.ToArray();
|
||||||
|
|
||||||
int fragmentOutputMap = shaders[5]?.Info.FragmentOutputMap ?? -1;
|
int fragmentOutputMap = shaders[5]?.Info.FragmentOutputMap ?? -1;
|
||||||
IProgram hostProgram = _context.Renderer.CreateProgram(shaderSources.ToArray(), new ShaderInfo(fragmentOutputMap));
|
IProgram hostProgram = _context.Renderer.CreateProgram(shaderSourcesArray, new ShaderInfo(fragmentOutputMap, pipeline));
|
||||||
|
|
||||||
gpShaders = new CachedShaderProgram(hostProgram, specState, shaders);
|
gpShaders = new CachedShaderProgram(hostProgram, specState, shaders);
|
||||||
|
|
||||||
_graphicsShaderCache.Add(gpShaders);
|
_graphicsShaderCache.Add(gpShaders);
|
||||||
EnqueueProgramToSave(new ProgramToSave(gpShaders, hostProgram));
|
EnqueueProgramToSave(gpShaders, hostProgram, shaderSourcesArray);
|
||||||
_gpPrograms[addresses] = gpShaders;
|
_gpPrograms[addresses] = gpShaders;
|
||||||
|
|
||||||
return gpShaders;
|
return gpShaders;
|
||||||
|
@ -355,7 +412,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
/// <returns>Shader source</returns>
|
/// <returns>Shader source</returns>
|
||||||
public static ShaderSource CreateShaderSource(ShaderProgram program)
|
public static ShaderSource CreateShaderSource(ShaderProgram program)
|
||||||
{
|
{
|
||||||
return new ShaderSource(program.Code, program.BinaryCode, program.Info.Stage, program.Language);
|
return new ShaderSource(program.Code, program.BinaryCode, GetBindings(program.Info), program.Info.Stage, program.Language);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -364,11 +421,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This will not do anything if disk shader cache is disabled.
|
/// This will not do anything if disk shader cache is disabled.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <param name="programToSave">Program to be saved on disk</param>
|
/// <param name="program">Cached shader program</param>
|
||||||
private void EnqueueProgramToSave(ProgramToSave programToSave)
|
/// <param name="hostProgram">Host program</param>
|
||||||
|
/// <param name="sources">Source for each shader stage</param>
|
||||||
|
private void EnqueueProgramToSave(CachedShaderProgram program, IProgram hostProgram, ShaderSource[] sources)
|
||||||
{
|
{
|
||||||
if (_diskCacheHostStorage.CacheEnabled)
|
if (_diskCacheHostStorage.CacheEnabled)
|
||||||
{
|
{
|
||||||
|
byte[] binaryCode = _context.Capabilities.Api == TargetApi.Vulkan ? ShaderBinarySerializer.Pack(sources) : null;
|
||||||
|
ProgramToSave programToSave = new ProgramToSave(program, hostProgram, binaryCode);
|
||||||
|
|
||||||
_programsToSaveQueue.Enqueue(programToSave);
|
_programsToSaveQueue.Enqueue(programToSave);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -418,7 +480,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
{
|
{
|
||||||
if (IsShaderEqual(channel.MemoryManager, cpShader.Shaders[0], gpuVa))
|
if (IsShaderEqual(channel.MemoryManager, cpShader.Shaders[0], gpuVa))
|
||||||
{
|
{
|
||||||
return cpShader.SpecializationState.MatchesCompute(channel, poolState);
|
return cpShader.SpecializationState.MatchesCompute(channel, poolState, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -454,7 +516,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return gpShaders.SpecializationState.MatchesGraphics(channel, poolState, graphicsState);
|
return gpShaders.SpecializationState.MatchesGraphics(channel, poolState, graphicsState, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -480,11 +542,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
/// Decode the binary Maxwell shader code to a translator context.
|
/// Decode the binary Maxwell shader code to a translator context.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="gpuAccessor">GPU state accessor</param>
|
/// <param name="gpuAccessor">GPU state accessor</param>
|
||||||
|
/// <param name="api">Graphics API that will be used with the shader</param>
|
||||||
/// <param name="gpuVa">GPU virtual address of the binary shader code</param>
|
/// <param name="gpuVa">GPU virtual address of the binary shader code</param>
|
||||||
/// <returns>The generated translator context</returns>
|
/// <returns>The generated translator context</returns>
|
||||||
public static TranslatorContext DecodeComputeShader(IGpuAccessor gpuAccessor, ulong gpuVa)
|
public static TranslatorContext DecodeComputeShader(IGpuAccessor gpuAccessor, TargetApi api, ulong gpuVa)
|
||||||
{
|
{
|
||||||
var options = new TranslationOptions(TargetLanguage.Glsl, TargetApi.OpenGL, DefaultFlags | TranslationFlags.Compute);
|
var options = CreateTranslationOptions(api, DefaultFlags | TranslationFlags.Compute);
|
||||||
return Translator.CreateContext(gpuVa, gpuAccessor, options);
|
return Translator.CreateContext(gpuVa, gpuAccessor, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -495,12 +558,13 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
/// This will combine the "Vertex A" and "Vertex B" shader stages, if specified, into one shader.
|
/// This will combine the "Vertex A" and "Vertex B" shader stages, if specified, into one shader.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <param name="gpuAccessor">GPU state accessor</param>
|
/// <param name="gpuAccessor">GPU state accessor</param>
|
||||||
|
/// <param name="api">Graphics API that will be used with the shader</param>
|
||||||
/// <param name="flags">Flags that controls shader translation</param>
|
/// <param name="flags">Flags that controls shader translation</param>
|
||||||
/// <param name="gpuVa">GPU virtual address of the shader code</param>
|
/// <param name="gpuVa">GPU virtual address of the shader code</param>
|
||||||
/// <returns>The generated translator context</returns>
|
/// <returns>The generated translator context</returns>
|
||||||
public static TranslatorContext DecodeGraphicsShader(IGpuAccessor gpuAccessor, TranslationFlags flags, ulong gpuVa)
|
public static TranslatorContext DecodeGraphicsShader(IGpuAccessor gpuAccessor, TargetApi api, TranslationFlags flags, ulong gpuVa)
|
||||||
{
|
{
|
||||||
var options = new TranslationOptions(TargetLanguage.Glsl, TargetApi.OpenGL, flags);
|
var options = CreateTranslationOptions(api, flags);
|
||||||
return Translator.CreateContext(gpuVa, gpuAccessor, options);
|
return Translator.CreateContext(gpuVa, gpuAccessor, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -595,6 +659,29 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ShaderBindings GetBindings(ShaderProgramInfo info)
|
||||||
|
{
|
||||||
|
var uniformBufferBindings = info.CBuffers.Select(x => x.Binding).ToArray();
|
||||||
|
var storageBufferBindings = info.SBuffers.Select(x => x.Binding).ToArray();
|
||||||
|
var textureBindings = info.Textures.Select(x => x.Binding).ToArray();
|
||||||
|
var imageBindings = info.Images.Select(x => x.Binding).ToArray();
|
||||||
|
|
||||||
|
return new ShaderBindings(
|
||||||
|
uniformBufferBindings,
|
||||||
|
storageBufferBindings,
|
||||||
|
textureBindings,
|
||||||
|
imageBindings);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TranslationOptions CreateTranslationOptions(TargetApi api, TranslationFlags flags)
|
||||||
|
{
|
||||||
|
TargetLanguage lang = GraphicsConfig.EnableSpirvCompilationOnVulkan && api == TargetApi.Vulkan
|
||||||
|
? TargetLanguage.Spirv
|
||||||
|
: TargetLanguage.Glsl;
|
||||||
|
|
||||||
|
return new TranslationOptions(lang, api, flags);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Disposes the shader cache, deleting all the cached shaders.
|
/// Disposes the shader cache, deleting all the cached shaders.
|
||||||
/// It's an error to use the shader cache after disposal.
|
/// It's an error to use the shader cache after disposal.
|
||||||
|
|
|
@ -35,7 +35,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
{
|
{
|
||||||
foreach (var entry in _entries)
|
foreach (var entry in _entries)
|
||||||
{
|
{
|
||||||
if (entry.SpecializationState.MatchesGraphics(channel, poolState, graphicsState))
|
if (entry.SpecializationState.MatchesGraphics(channel, poolState, graphicsState, true))
|
||||||
{
|
{
|
||||||
program = entry;
|
program = entry;
|
||||||
return true;
|
return true;
|
||||||
|
@ -57,7 +57,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
{
|
{
|
||||||
foreach (var entry in _entries)
|
foreach (var entry in _entries)
|
||||||
{
|
{
|
||||||
if (entry.SpecializationState.MatchesCompute(channel, poolState))
|
if (entry.SpecializationState.MatchesCompute(channel, poolState, true))
|
||||||
{
|
{
|
||||||
program = entry;
|
program = entry;
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
using Ryujinx.Common.Memory;
|
using Ryujinx.Common.Memory;
|
||||||
|
using Ryujinx.Graphics.Gpu.Image;
|
||||||
|
using Ryujinx.Graphics.Gpu.Memory;
|
||||||
|
using Ryujinx.Graphics.GAL;
|
||||||
using Ryujinx.Graphics.Gpu.Shader.DiskCache;
|
using Ryujinx.Graphics.Gpu.Shader.DiskCache;
|
||||||
using Ryujinx.Graphics.Shader;
|
using Ryujinx.Graphics.Shader;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Gpu.Shader
|
namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
{
|
{
|
||||||
|
@ -14,6 +20,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
private const uint TfbdMagic = (byte)'T' | ((byte)'F' << 8) | ((byte)'B' << 16) | ((byte)'D' << 24);
|
private const uint TfbdMagic = (byte)'T' | ((byte)'F' << 8) | ((byte)'B' << 16) | ((byte)'D' << 24);
|
||||||
private const uint TexkMagic = (byte)'T' | ((byte)'E' << 8) | ((byte)'X' << 16) | ((byte)'K' << 24);
|
private const uint TexkMagic = (byte)'T' | ((byte)'E' << 8) | ((byte)'X' << 16) | ((byte)'K' << 24);
|
||||||
private const uint TexsMagic = (byte)'T' | ((byte)'E' << 8) | ((byte)'X' << 16) | ((byte)'S' << 24);
|
private const uint TexsMagic = (byte)'T' | ((byte)'E' << 8) | ((byte)'X' << 16) | ((byte)'S' << 24);
|
||||||
|
private const uint PgpsMagic = (byte)'P' | ((byte)'G' << 8) | ((byte)'P' << 16) | ((byte)'S' << 24);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Flags indicating GPU state that is used by the shader.
|
/// Flags indicating GPU state that is used by the shader.
|
||||||
|
@ -46,6 +53,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Array5<uint> ConstantBufferUse;
|
public Array5<uint> ConstantBufferUse;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optional pipeline state captured at the time of the shader use.
|
||||||
|
/// </summary>
|
||||||
|
public ProgramPipelineState? PipelineState;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Transform feedback buffers active at the time the shader was compiled.
|
/// Transform feedback buffers active at the time the shader was compiled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -158,6 +170,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly Dictionary<TextureKey, Box<TextureSpecializationState>> _textureSpecialization;
|
private readonly Dictionary<TextureKey, Box<TextureSpecializationState>> _textureSpecialization;
|
||||||
|
private KeyValuePair<TextureKey, Box<TextureSpecializationState>>[] _allTextures;
|
||||||
|
private Box<TextureSpecializationState>[][] _textureByBinding;
|
||||||
|
private Box<TextureSpecializationState>[][] _imageByBinding;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new instance of the shader specialization state.
|
/// Creates a new instance of the shader specialization state.
|
||||||
|
@ -171,7 +186,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
/// Creates a new instance of the shader specialization state.
|
/// Creates a new instance of the shader specialization state.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="state">Current compute engine state</param>
|
/// <param name="state">Current compute engine state</param>
|
||||||
public ShaderSpecializationState(GpuChannelComputeState state) : this()
|
public ShaderSpecializationState(ref GpuChannelComputeState state) : this()
|
||||||
{
|
{
|
||||||
ComputeState = state;
|
ComputeState = state;
|
||||||
_compute = true;
|
_compute = true;
|
||||||
|
@ -182,7 +197,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="state">Current 3D engine state</param>
|
/// <param name="state">Current 3D engine state</param>
|
||||||
/// <param name="descriptors">Optional transform feedback buffers in use, if any</param>
|
/// <param name="descriptors">Optional transform feedback buffers in use, if any</param>
|
||||||
public ShaderSpecializationState(GpuChannelGraphicsState state, TransformFeedbackDescriptor[] descriptors) : this()
|
private ShaderSpecializationState(ref GpuChannelGraphicsState state, TransformFeedbackDescriptor[] descriptors) : this()
|
||||||
{
|
{
|
||||||
GraphicsState = state;
|
GraphicsState = state;
|
||||||
_compute = false;
|
_compute = false;
|
||||||
|
@ -194,6 +209,76 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Prepare the shader specialization state for quick binding lookups.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stages">The shader stages</param>
|
||||||
|
public void Prepare(CachedShaderStage[] stages)
|
||||||
|
{
|
||||||
|
_allTextures = _textureSpecialization.ToArray();
|
||||||
|
|
||||||
|
_textureByBinding = new Box<TextureSpecializationState>[stages.Length][];
|
||||||
|
_imageByBinding = new Box<TextureSpecializationState>[stages.Length][];
|
||||||
|
|
||||||
|
for (int i = 0; i < stages.Length; i++)
|
||||||
|
{
|
||||||
|
CachedShaderStage stage = stages[i];
|
||||||
|
if (stage?.Info != null)
|
||||||
|
{
|
||||||
|
var textures = stage.Info.Textures;
|
||||||
|
var images = stage.Info.Images;
|
||||||
|
|
||||||
|
var texBindings = new Box<TextureSpecializationState>[textures.Count];
|
||||||
|
var imageBindings = new Box<TextureSpecializationState>[images.Count];
|
||||||
|
|
||||||
|
int stageIndex = Math.Max(i - 1, 0); // Don't count VertexA for looking up spec state. No-Op for compute.
|
||||||
|
|
||||||
|
for (int j = 0; j < textures.Count; j++)
|
||||||
|
{
|
||||||
|
var texture = textures[j];
|
||||||
|
texBindings[j] = GetTextureSpecState(stageIndex, texture.HandleIndex, texture.CbufSlot);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int j = 0; j < images.Count; j++)
|
||||||
|
{
|
||||||
|
var image = images[j];
|
||||||
|
imageBindings[j] = GetTextureSpecState(stageIndex, image.HandleIndex, image.CbufSlot);
|
||||||
|
}
|
||||||
|
|
||||||
|
_textureByBinding[i] = texBindings;
|
||||||
|
_imageByBinding[i] = imageBindings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the shader specialization state.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="state">Current 3D engine state</param>
|
||||||
|
/// <param name="pipelineState">Current program pipeline state</param>
|
||||||
|
/// <param name="descriptors">Optional transform feedback buffers in use, if any</param>
|
||||||
|
public ShaderSpecializationState(
|
||||||
|
ref GpuChannelGraphicsState state,
|
||||||
|
ref ProgramPipelineState pipelineState,
|
||||||
|
TransformFeedbackDescriptor[] descriptors) : this(ref state, descriptors)
|
||||||
|
{
|
||||||
|
PipelineState = pipelineState;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the shader specialization state.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="state">Current 3D engine state</param>
|
||||||
|
/// <param name="pipelineState">Current program pipeline state</param>
|
||||||
|
/// <param name="descriptors">Optional transform feedback buffers in use, if any</param>
|
||||||
|
public ShaderSpecializationState(
|
||||||
|
ref GpuChannelGraphicsState state,
|
||||||
|
ProgramPipelineState? pipelineState,
|
||||||
|
TransformFeedbackDescriptor[] descriptors) : this(ref state, descriptors)
|
||||||
|
{
|
||||||
|
PipelineState = pipelineState;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates that the shader accesses the early Z force state.
|
/// Indicates that the shader accesses the early Z force state.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -396,15 +481,38 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
/// <param name="channel">GPU channel</param>
|
/// <param name="channel">GPU channel</param>
|
||||||
/// <param name="poolState">Texture pool state</param>
|
/// <param name="poolState">Texture pool state</param>
|
||||||
/// <param name="graphicsState">Graphics state</param>
|
/// <param name="graphicsState">Graphics state</param>
|
||||||
|
/// <param name="checkTextures">Indicates whether texture descriptors should be checked</param>
|
||||||
/// <returns>True if the state matches, false otherwise</returns>
|
/// <returns>True if the state matches, false otherwise</returns>
|
||||||
public bool MatchesGraphics(GpuChannel channel, GpuChannelPoolState poolState, GpuChannelGraphicsState graphicsState)
|
public bool MatchesGraphics(GpuChannel channel, GpuChannelPoolState poolState, GpuChannelGraphicsState graphicsState, bool checkTextures)
|
||||||
{
|
{
|
||||||
if (graphicsState.ViewportTransformDisable != GraphicsState.ViewportTransformDisable)
|
if (graphicsState.ViewportTransformDisable != GraphicsState.ViewportTransformDisable)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Matches(channel, poolState, isCompute: false);
|
if (graphicsState.DepthMode != GraphicsState.DepthMode)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (graphicsState.AlphaTestEnable != GraphicsState.AlphaTestEnable)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (graphicsState.AlphaTestEnable &&
|
||||||
|
(graphicsState.AlphaTestCompare != GraphicsState.AlphaTestCompare ||
|
||||||
|
graphicsState.AlphaTestReference != GraphicsState.AlphaTestReference))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!graphicsState.AttributeTypes.ToSpan().SequenceEqual(GraphicsState.AttributeTypes.ToSpan()))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Matches(channel, poolState, checkTextures, isCompute: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -412,10 +520,64 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="channel">GPU channel</param>
|
/// <param name="channel">GPU channel</param>
|
||||||
/// <param name="poolState">Texture pool state</param>
|
/// <param name="poolState">Texture pool state</param>
|
||||||
|
/// <param name="checkTextures">Indicates whether texture descriptors should be checked</param>
|
||||||
/// <returns>True if the state matches, false otherwise</returns>
|
/// <returns>True if the state matches, false otherwise</returns>
|
||||||
public bool MatchesCompute(GpuChannel channel, GpuChannelPoolState poolState)
|
public bool MatchesCompute(GpuChannel channel, GpuChannelPoolState poolState, bool checkTextures)
|
||||||
{
|
{
|
||||||
return Matches(channel, poolState, isCompute: true);
|
return Matches(channel, poolState, checkTextures, isCompute: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fetch the constant buffers used for a texture to cache.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="channel">GPU channel</param>
|
||||||
|
/// <param name="isCompute">Indicates whenever the check is requested by the 3D or compute engine</param>
|
||||||
|
/// <param name="cachedTextureBufferIndex">The currently cached texture buffer index</param>
|
||||||
|
/// <param name="cachedSamplerBufferIndex">The currently cached sampler buffer index</param>
|
||||||
|
/// <param name="cachedTextureBuffer">The currently cached texture buffer data</param>
|
||||||
|
/// <param name="cachedSamplerBuffer">The currently cached sampler buffer data</param>
|
||||||
|
/// <param name="cachedStageIndex">The currently cached stage</param>
|
||||||
|
/// <param name="textureBufferIndex">The new texture buffer index</param>
|
||||||
|
/// <param name="samplerBufferIndex">The new sampler buffer index</param>
|
||||||
|
/// <param name="stageIndex">Stage index of the constant buffer</param>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static void UpdateCachedBuffer(
|
||||||
|
GpuChannel channel,
|
||||||
|
bool isCompute,
|
||||||
|
ref int cachedTextureBufferIndex,
|
||||||
|
ref int cachedSamplerBufferIndex,
|
||||||
|
ref ReadOnlySpan<int> cachedTextureBuffer,
|
||||||
|
ref ReadOnlySpan<int> cachedSamplerBuffer,
|
||||||
|
ref int cachedStageIndex,
|
||||||
|
int textureBufferIndex,
|
||||||
|
int samplerBufferIndex,
|
||||||
|
int stageIndex)
|
||||||
|
{
|
||||||
|
bool stageChange = stageIndex != cachedStageIndex;
|
||||||
|
|
||||||
|
if (stageChange || textureBufferIndex != cachedTextureBufferIndex)
|
||||||
|
{
|
||||||
|
ref BufferBounds bounds = ref channel.BufferManager.GetUniformBufferBounds(isCompute, stageIndex, textureBufferIndex);
|
||||||
|
|
||||||
|
cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(channel.MemoryManager.Physical.GetSpan(bounds.Address, (int)bounds.Size));
|
||||||
|
cachedTextureBufferIndex = textureBufferIndex;
|
||||||
|
|
||||||
|
if (samplerBufferIndex == textureBufferIndex)
|
||||||
|
{
|
||||||
|
cachedSamplerBuffer = cachedTextureBuffer;
|
||||||
|
cachedSamplerBufferIndex = samplerBufferIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stageChange || samplerBufferIndex != cachedSamplerBufferIndex)
|
||||||
|
{
|
||||||
|
ref BufferBounds bounds = ref channel.BufferManager.GetUniformBufferBounds(isCompute, stageIndex, samplerBufferIndex);
|
||||||
|
|
||||||
|
cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(channel.MemoryManager.Physical.GetSpan(bounds.Address, (int)bounds.Size));
|
||||||
|
cachedSamplerBufferIndex = samplerBufferIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedStageIndex = stageIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -423,9 +585,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="channel">GPU channel</param>
|
/// <param name="channel">GPU channel</param>
|
||||||
/// <param name="poolState">Texture pool state</param>
|
/// <param name="poolState">Texture pool state</param>
|
||||||
|
/// <param name="checkTextures">Indicates whether texture descriptors should be checked</param>
|
||||||
/// <param name="isCompute">Indicates whenever the check is requested by the 3D or compute engine</param>
|
/// <param name="isCompute">Indicates whenever the check is requested by the 3D or compute engine</param>
|
||||||
/// <returns>True if the state matches, false otherwise</returns>
|
/// <returns>True if the state matches, false otherwise</returns>
|
||||||
private bool Matches(GpuChannel channel, GpuChannelPoolState poolState, bool isCompute)
|
private bool Matches(GpuChannel channel, GpuChannelPoolState poolState, bool checkTextures, bool isCompute)
|
||||||
{
|
{
|
||||||
int constantBufferUsePerStageMask = _constantBufferUsePerStage;
|
int constantBufferUsePerStageMask = _constantBufferUsePerStage;
|
||||||
|
|
||||||
|
@ -445,55 +608,60 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
constantBufferUsePerStageMask &= ~(1 << index);
|
constantBufferUsePerStageMask &= ~(1 << index);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var kv in _textureSpecialization)
|
if (checkTextures)
|
||||||
{
|
{
|
||||||
TextureKey textureKey = kv.Key;
|
TexturePool pool = channel.TextureManager.GetTexturePool(poolState.TexturePoolGpuVa, poolState.TexturePoolMaximumId);
|
||||||
|
|
||||||
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(textureKey.CbufSlot, poolState.TextureBufferIndex);
|
int cachedTextureBufferIndex = -1;
|
||||||
|
int cachedSamplerBufferIndex = -1;
|
||||||
|
int cachedStageIndex = -1;
|
||||||
|
ReadOnlySpan<int> cachedTextureBuffer = Span<int>.Empty;
|
||||||
|
ReadOnlySpan<int> cachedSamplerBuffer = Span<int>.Empty;
|
||||||
|
|
||||||
ulong textureCbAddress;
|
foreach (var kv in _allTextures)
|
||||||
ulong samplerCbAddress;
|
|
||||||
|
|
||||||
if (isCompute)
|
|
||||||
{
|
{
|
||||||
textureCbAddress = channel.BufferManager.GetComputeUniformBufferAddress(textureBufferIndex);
|
TextureKey textureKey = kv.Key;
|
||||||
samplerCbAddress = channel.BufferManager.GetComputeUniformBufferAddress(samplerBufferIndex);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
textureCbAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(textureKey.StageIndex, textureBufferIndex);
|
|
||||||
samplerCbAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(textureKey.StageIndex, samplerBufferIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!channel.MemoryManager.Physical.IsMapped(textureCbAddress) || !channel.MemoryManager.Physical.IsMapped(samplerCbAddress))
|
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(textureKey.CbufSlot, poolState.TextureBufferIndex);
|
||||||
{
|
|
||||||
continue;
|
UpdateCachedBuffer(channel,
|
||||||
|
isCompute,
|
||||||
|
ref cachedTextureBufferIndex,
|
||||||
|
ref cachedSamplerBufferIndex,
|
||||||
|
ref cachedTextureBuffer,
|
||||||
|
ref cachedSamplerBuffer,
|
||||||
|
ref cachedStageIndex,
|
||||||
|
textureBufferIndex,
|
||||||
|
samplerBufferIndex,
|
||||||
|
textureKey.StageIndex);
|
||||||
|
|
||||||
|
int packedId = TextureHandle.ReadPackedId(textureKey.Handle, cachedTextureBuffer, cachedSamplerBuffer);
|
||||||
|
|
||||||
|
int textureId = TextureHandle.UnpackTextureId(packedId);
|
||||||
|
|
||||||
|
ref readonly Image.TextureDescriptor descriptor = ref pool.GetDescriptorRef(textureId);
|
||||||
|
|
||||||
|
if (!MatchesTexture(kv.Value, descriptor))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Image.TextureDescriptor descriptor;
|
return true;
|
||||||
|
}
|
||||||
if (isCompute)
|
|
||||||
{
|
|
||||||
descriptor = channel.TextureManager.GetComputeTextureDescriptor(
|
|
||||||
poolState.TexturePoolGpuVa,
|
|
||||||
poolState.TextureBufferIndex,
|
|
||||||
poolState.TexturePoolMaximumId,
|
|
||||||
textureKey.Handle,
|
|
||||||
textureKey.CbufSlot);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
descriptor = channel.TextureManager.GetGraphicsTextureDescriptor(
|
|
||||||
poolState.TexturePoolGpuVa,
|
|
||||||
poolState.TextureBufferIndex,
|
|
||||||
poolState.TexturePoolMaximumId,
|
|
||||||
textureKey.StageIndex,
|
|
||||||
textureKey.Handle,
|
|
||||||
textureKey.CbufSlot);
|
|
||||||
}
|
|
||||||
|
|
||||||
Box<TextureSpecializationState> specializationState = kv.Value;
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the recorded texture state matches the given texture descriptor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="specializationState">Texture specialization state</param>
|
||||||
|
/// <param name="descriptor">Texture descriptor</param>
|
||||||
|
/// <returns>True if the state matches, false otherwise</returns>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private bool MatchesTexture(Box<TextureSpecializationState> specializationState, in Image.TextureDescriptor descriptor)
|
||||||
|
{
|
||||||
|
if (specializationState != null)
|
||||||
|
{
|
||||||
if (specializationState.Value.QueriedFlags.HasFlag(QueriedTextureStateFlags.CoordNormalized) &&
|
if (specializationState.Value.QueriedFlags.HasFlag(QueriedTextureStateFlags.CoordNormalized) &&
|
||||||
specializationState.Value.CoordNormalized != descriptor.UnpackTextureCoordNormalized())
|
specializationState.Value.CoordNormalized != descriptor.UnpackTextureCoordNormalized())
|
||||||
{
|
{
|
||||||
|
@ -504,6 +672,34 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the recorded texture state for a given texture binding matches a texture descriptor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stage">The shader stage</param>
|
||||||
|
/// <param name="index">The texture index</param>
|
||||||
|
/// <param name="descriptor">Texture descriptor</param>
|
||||||
|
/// <returns>True if the state matches, false otherwise</returns>
|
||||||
|
public bool MatchesTexture(ShaderStage stage, int index, in Image.TextureDescriptor descriptor)
|
||||||
|
{
|
||||||
|
Box<TextureSpecializationState> specializationState = _textureByBinding[(int)stage][index];
|
||||||
|
|
||||||
|
return MatchesTexture(specializationState, descriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the recorded texture state for a given image binding matches a texture descriptor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stage">The shader stage</param>
|
||||||
|
/// <param name="index">The texture index</param>
|
||||||
|
/// <param name="descriptor">Texture descriptor</param>
|
||||||
|
/// <returns>True if the state matches, false otherwise</returns>
|
||||||
|
public bool MatchesImage(ShaderStage stage, int index, in Image.TextureDescriptor descriptor)
|
||||||
|
{
|
||||||
|
Box<TextureSpecializationState> specializationState = _imageByBinding[(int)stage][index];
|
||||||
|
|
||||||
|
return MatchesTexture(specializationState, descriptor);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reads shader specialization state that has been serialized.
|
/// Reads shader specialization state that has been serialized.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -536,6 +732,17 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
constantBufferUsePerStageMask &= ~(1 << index);
|
constantBufferUsePerStageMask &= ~(1 << index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool hasPipelineState = false;
|
||||||
|
|
||||||
|
dataReader.Read(ref hasPipelineState);
|
||||||
|
|
||||||
|
if (hasPipelineState)
|
||||||
|
{
|
||||||
|
ProgramPipelineState pipelineState = default;
|
||||||
|
dataReader.ReadWithMagicAndSize(ref pipelineState, PgpsMagic);
|
||||||
|
specState.PipelineState = pipelineState;
|
||||||
|
}
|
||||||
|
|
||||||
if (specState._queriedState.HasFlag(QueriedStateFlags.TransformFeedback))
|
if (specState._queriedState.HasFlag(QueriedStateFlags.TransformFeedback))
|
||||||
{
|
{
|
||||||
ushort tfCount = 0;
|
ushort tfCount = 0;
|
||||||
|
@ -594,6 +801,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
constantBufferUsePerStageMask &= ~(1 << index);
|
constantBufferUsePerStageMask &= ~(1 << index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool hasPipelineState = PipelineState.HasValue;
|
||||||
|
|
||||||
|
dataWriter.Write(ref hasPipelineState);
|
||||||
|
|
||||||
|
if (hasPipelineState)
|
||||||
|
{
|
||||||
|
ProgramPipelineState pipelineState = PipelineState.Value;
|
||||||
|
dataWriter.WriteWithMagicAndSize(ref pipelineState, PgpsMagic);
|
||||||
|
}
|
||||||
|
|
||||||
if (_queriedState.HasFlag(QueriedStateFlags.TransformFeedback))
|
if (_queriedState.HasFlag(QueriedStateFlags.TransformFeedback))
|
||||||
{
|
{
|
||||||
ushort tfCount = (ushort)TransformFeedbackDescriptors.Length;
|
ushort tfCount = (ushort)TransformFeedbackDescriptors.Length;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
|
@ -88,16 +88,14 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
Add(Format.Bc3Srgb, new FormatInfo(4, false, false, All.CompressedSrgbAlphaS3tcDxt5Ext));
|
Add(Format.Bc3Srgb, new FormatInfo(4, false, false, All.CompressedSrgbAlphaS3tcDxt5Ext));
|
||||||
Add(Format.Bc4Unorm, new FormatInfo(1, true, false, All.CompressedRedRgtc1));
|
Add(Format.Bc4Unorm, new FormatInfo(1, true, false, All.CompressedRedRgtc1));
|
||||||
Add(Format.Bc4Snorm, new FormatInfo(1, true, false, All.CompressedSignedRedRgtc1));
|
Add(Format.Bc4Snorm, new FormatInfo(1, true, false, All.CompressedSignedRedRgtc1));
|
||||||
Add(Format.Bc5Unorm, new FormatInfo(2, true, false, All.CompressedRgRgtc2));
|
Add(Format.Bc5Unorm, new FormatInfo(1, true, false, All.CompressedRgRgtc2));
|
||||||
Add(Format.Bc5Snorm, new FormatInfo(2, true, false, All.CompressedSignedRgRgtc2));
|
Add(Format.Bc5Snorm, new FormatInfo(1, true, false, All.CompressedSignedRgRgtc2));
|
||||||
Add(Format.Bc7Unorm, new FormatInfo(4, true, false, All.CompressedRgbaBptcUnorm));
|
Add(Format.Bc7Unorm, new FormatInfo(1, true, false, All.CompressedRgbaBptcUnorm));
|
||||||
Add(Format.Bc7Srgb, new FormatInfo(4, false, false, All.CompressedSrgbAlphaBptcUnorm));
|
Add(Format.Bc7Srgb, new FormatInfo(1, false, false, All.CompressedSrgbAlphaBptcUnorm));
|
||||||
Add(Format.Bc6HSfloat, new FormatInfo(4, false, false, All.CompressedRgbBptcSignedFloat));
|
Add(Format.Bc6HSfloat, new FormatInfo(1, false, false, All.CompressedRgbBptcSignedFloat));
|
||||||
Add(Format.Bc6HUfloat, new FormatInfo(4, false, false, All.CompressedRgbBptcUnsignedFloat));
|
Add(Format.Bc6HUfloat, new FormatInfo(1, false, false, All.CompressedRgbBptcUnsignedFloat));
|
||||||
Add(Format.Etc2RgbUnorm, new FormatInfo(3, false, false, All.CompressedRgb8Etc2));
|
Add(Format.Etc2RgbaUnorm, new FormatInfo(1, false, false, All.CompressedRgba8Etc2Eac));
|
||||||
Add(Format.Etc2RgbaUnorm, new FormatInfo(4, false, false, All.CompressedRgba8Etc2Eac));
|
Add(Format.Etc2RgbaSrgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Etc2Eac));
|
||||||
Add(Format.Etc2RgbSrgb, new FormatInfo(3, false, false, All.CompressedSrgb8Etc2));
|
|
||||||
Add(Format.Etc2RgbaSrgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Etc2Eac));
|
|
||||||
Add(Format.R8Uscaled, new FormatInfo(1, false, true, All.R8ui, PixelFormat.RedInteger, PixelType.UnsignedByte));
|
Add(Format.R8Uscaled, new FormatInfo(1, false, true, All.R8ui, PixelFormat.RedInteger, PixelType.UnsignedByte));
|
||||||
Add(Format.R8Sscaled, new FormatInfo(1, false, true, All.R8i, PixelFormat.RedInteger, PixelType.Byte));
|
Add(Format.R8Sscaled, new FormatInfo(1, false, true, All.R8i, PixelFormat.RedInteger, PixelType.Byte));
|
||||||
Add(Format.R16Uscaled, new FormatInfo(1, false, true, All.R16ui, PixelFormat.RedInteger, PixelType.UnsignedShort));
|
Add(Format.R16Uscaled, new FormatInfo(1, false, true, All.R16ui, PixelFormat.RedInteger, PixelType.UnsignedShort));
|
||||||
|
@ -138,34 +136,34 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
Add(Format.R32G32B32X32Float, new FormatInfo(4, false, false, All.Rgb32f, PixelFormat.Rgba, PixelType.Float));
|
Add(Format.R32G32B32X32Float, new FormatInfo(4, false, false, All.Rgb32f, PixelFormat.Rgba, PixelType.Float));
|
||||||
Add(Format.R32G32B32X32Uint, new FormatInfo(4, false, false, All.Rgb32ui, PixelFormat.RgbaInteger, PixelType.UnsignedInt));
|
Add(Format.R32G32B32X32Uint, new FormatInfo(4, false, false, All.Rgb32ui, PixelFormat.RgbaInteger, PixelType.UnsignedInt));
|
||||||
Add(Format.R32G32B32X32Sint, new FormatInfo(4, false, false, All.Rgb32i, PixelFormat.RgbaInteger, PixelType.Int));
|
Add(Format.R32G32B32X32Sint, new FormatInfo(4, false, false, All.Rgb32i, PixelFormat.RgbaInteger, PixelType.Int));
|
||||||
Add(Format.Astc4x4Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc4X4Khr));
|
Add(Format.Astc4x4Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc4X4Khr));
|
||||||
Add(Format.Astc5x4Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc5X4Khr));
|
Add(Format.Astc5x4Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc5X4Khr));
|
||||||
Add(Format.Astc5x5Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc5X5Khr));
|
Add(Format.Astc5x5Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc5X5Khr));
|
||||||
Add(Format.Astc6x5Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc6X5Khr));
|
Add(Format.Astc6x5Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc6X5Khr));
|
||||||
Add(Format.Astc6x6Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc6X6Khr));
|
Add(Format.Astc6x6Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc6X6Khr));
|
||||||
Add(Format.Astc8x5Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc8X5Khr));
|
Add(Format.Astc8x5Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc8X5Khr));
|
||||||
Add(Format.Astc8x6Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc8X6Khr));
|
Add(Format.Astc8x6Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc8X6Khr));
|
||||||
Add(Format.Astc8x8Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc8X8Khr));
|
Add(Format.Astc8x8Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc8X8Khr));
|
||||||
Add(Format.Astc10x5Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc10X5Khr));
|
Add(Format.Astc10x5Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc10X5Khr));
|
||||||
Add(Format.Astc10x6Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc10X6Khr));
|
Add(Format.Astc10x6Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc10X6Khr));
|
||||||
Add(Format.Astc10x8Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc10X8Khr));
|
Add(Format.Astc10x8Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc10X8Khr));
|
||||||
Add(Format.Astc10x10Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc10X10Khr));
|
Add(Format.Astc10x10Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc10X10Khr));
|
||||||
Add(Format.Astc12x10Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc12X10Khr));
|
Add(Format.Astc12x10Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc12X10Khr));
|
||||||
Add(Format.Astc12x12Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc12X12Khr));
|
Add(Format.Astc12x12Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc12X12Khr));
|
||||||
Add(Format.Astc4x4Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc4X4Khr));
|
Add(Format.Astc4x4Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc4X4Khr));
|
||||||
Add(Format.Astc5x4Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc5X4Khr));
|
Add(Format.Astc5x4Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc5X4Khr));
|
||||||
Add(Format.Astc5x5Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc5X5Khr));
|
Add(Format.Astc5x5Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc5X5Khr));
|
||||||
Add(Format.Astc6x5Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc6X5Khr));
|
Add(Format.Astc6x5Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc6X5Khr));
|
||||||
Add(Format.Astc6x6Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc6X6Khr));
|
Add(Format.Astc6x6Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc6X6Khr));
|
||||||
Add(Format.Astc8x5Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc8X5Khr));
|
Add(Format.Astc8x5Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc8X5Khr));
|
||||||
Add(Format.Astc8x6Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc8X6Khr));
|
Add(Format.Astc8x6Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc8X6Khr));
|
||||||
Add(Format.Astc8x8Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc8X8Khr));
|
Add(Format.Astc8x8Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc8X8Khr));
|
||||||
Add(Format.Astc10x5Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc10X5Khr));
|
Add(Format.Astc10x5Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc10X5Khr));
|
||||||
Add(Format.Astc10x6Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc10X6Khr));
|
Add(Format.Astc10x6Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc10X6Khr));
|
||||||
Add(Format.Astc10x8Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc10X8Khr));
|
Add(Format.Astc10x8Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc10X8Khr));
|
||||||
Add(Format.Astc10x10Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc10X10Khr));
|
Add(Format.Astc10x10Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc10X10Khr));
|
||||||
Add(Format.Astc12x10Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc12X10Khr));
|
Add(Format.Astc12x10Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc12X10Khr));
|
||||||
Add(Format.Astc12x12Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc12X12Khr));
|
Add(Format.Astc12x12Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc12X12Khr));
|
||||||
Add(Format.B5G6R5Unorm, new FormatInfo(3, true, false, All.Rgb565, PixelFormat.Rgb, PixelType.UnsignedShort565Reversed));
|
Add(Format.B5G6R5Unorm, new FormatInfo(3, true, false, All.Rgb565, PixelFormat.Rgb, PixelType.UnsignedShort565Reversed));
|
||||||
Add(Format.B5G5R5X1Unorm, new FormatInfo(4, true, false, All.Rgb5, PixelFormat.Rgba, PixelType.UnsignedShort1555Reversed));
|
Add(Format.B5G5R5X1Unorm, new FormatInfo(4, true, false, All.Rgb5, PixelFormat.Rgba, PixelType.UnsignedShort1555Reversed));
|
||||||
Add(Format.B5G5R5A1Unorm, new FormatInfo(4, true, false, All.Rgb5A1, PixelFormat.Rgba, PixelType.UnsignedShort1555Reversed));
|
Add(Format.B5G5R5A1Unorm, new FormatInfo(4, true, false, All.Rgb5A1, PixelFormat.Rgba, PixelType.UnsignedShort1555Reversed));
|
||||||
|
|
|
@ -9,10 +9,13 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
class Framebuffer : IDisposable
|
class Framebuffer : IDisposable
|
||||||
{
|
{
|
||||||
public int Handle { get; private set; }
|
public int Handle { get; private set; }
|
||||||
|
private int _clearFbHandle;
|
||||||
|
private bool _clearFbInitialized;
|
||||||
|
|
||||||
private FramebufferAttachment _lastDsAttachment;
|
private FramebufferAttachment _lastDsAttachment;
|
||||||
|
|
||||||
private readonly TextureView[] _colors;
|
private readonly TextureView[] _colors;
|
||||||
|
private TextureView _depthStencil;
|
||||||
|
|
||||||
private int _colorsCount;
|
private int _colorsCount;
|
||||||
private bool _dualSourceBlend;
|
private bool _dualSourceBlend;
|
||||||
|
@ -20,6 +23,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
public Framebuffer()
|
public Framebuffer()
|
||||||
{
|
{
|
||||||
Handle = GL.GenFramebuffer();
|
Handle = GL.GenFramebuffer();
|
||||||
|
_clearFbHandle = GL.GenFramebuffer();
|
||||||
|
|
||||||
_colors = new TextureView[8];
|
_colors = new TextureView[8];
|
||||||
}
|
}
|
||||||
|
@ -55,20 +59,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
|
|
||||||
if (depthStencil != null)
|
if (depthStencil != null)
|
||||||
{
|
{
|
||||||
FramebufferAttachment attachment;
|
FramebufferAttachment attachment = GetAttachment(depthStencil.Format);
|
||||||
|
|
||||||
if (IsPackedDepthStencilFormat(depthStencil.Format))
|
|
||||||
{
|
|
||||||
attachment = FramebufferAttachment.DepthStencilAttachment;
|
|
||||||
}
|
|
||||||
else if (IsDepthOnlyFormat(depthStencil.Format))
|
|
||||||
{
|
|
||||||
attachment = FramebufferAttachment.DepthAttachment;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
attachment = FramebufferAttachment.StencilAttachment;
|
|
||||||
}
|
|
||||||
|
|
||||||
GL.FramebufferTexture(
|
GL.FramebufferTexture(
|
||||||
FramebufferTarget.Framebuffer,
|
FramebufferTarget.Framebuffer,
|
||||||
|
@ -82,6 +73,8 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
{
|
{
|
||||||
_lastDsAttachment = 0;
|
_lastDsAttachment = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_depthStencil = depthStencil;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetDualSourceBlend(bool enable)
|
public void SetDualSourceBlend(bool enable)
|
||||||
|
@ -124,6 +117,22 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
GL.DrawBuffers(colorsCount, drawBuffers);
|
GL.DrawBuffers(colorsCount, drawBuffers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static FramebufferAttachment GetAttachment(Format format)
|
||||||
|
{
|
||||||
|
if (IsPackedDepthStencilFormat(format))
|
||||||
|
{
|
||||||
|
return FramebufferAttachment.DepthStencilAttachment;
|
||||||
|
}
|
||||||
|
else if (IsDepthOnlyFormat(format))
|
||||||
|
{
|
||||||
|
return FramebufferAttachment.DepthAttachment;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return FramebufferAttachment.StencilAttachment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static bool IsPackedDepthStencilFormat(Format format)
|
private static bool IsPackedDepthStencilFormat(Format format)
|
||||||
{
|
{
|
||||||
return format == Format.D24UnormS8Uint ||
|
return format == Format.D24UnormS8Uint ||
|
||||||
|
@ -136,6 +145,78 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
return format == Format.D16Unorm || format == Format.D32Float;
|
return format == Format.D16Unorm || format == Format.D32Float;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void AttachColorLayerForClear(int index, int layer)
|
||||||
|
{
|
||||||
|
TextureView color = _colors[index];
|
||||||
|
|
||||||
|
if (!IsLayered(color))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BindClearFb();
|
||||||
|
GL.FramebufferTextureLayer(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0 + index, color.Handle, 0, layer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DetachColorLayerForClear(int index)
|
||||||
|
{
|
||||||
|
TextureView color = _colors[index];
|
||||||
|
|
||||||
|
if (!IsLayered(color))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GL.FramebufferTexture(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0 + index, 0, 0);
|
||||||
|
Bind();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AttachDepthStencilLayerForClear(int layer)
|
||||||
|
{
|
||||||
|
TextureView depthStencil = _depthStencil;
|
||||||
|
|
||||||
|
if (!IsLayered(depthStencil))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BindClearFb();
|
||||||
|
GL.FramebufferTextureLayer(FramebufferTarget.Framebuffer, GetAttachment(depthStencil.Format), depthStencil.Handle, 0, layer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DetachDepthStencilLayerForClear()
|
||||||
|
{
|
||||||
|
TextureView depthStencil = _depthStencil;
|
||||||
|
|
||||||
|
if (!IsLayered(depthStencil))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GL.FramebufferTexture(FramebufferTarget.Framebuffer, GetAttachment(depthStencil.Format), 0, 0);
|
||||||
|
Bind();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BindClearFb()
|
||||||
|
{
|
||||||
|
GL.BindFramebuffer(FramebufferTarget.Framebuffer, _clearFbHandle);
|
||||||
|
|
||||||
|
if (!_clearFbInitialized)
|
||||||
|
{
|
||||||
|
SetDrawBuffersImpl(Constants.MaxRenderTargets);
|
||||||
|
_clearFbInitialized = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsLayered(TextureView view)
|
||||||
|
{
|
||||||
|
return view != null &&
|
||||||
|
view.Target != Target.Texture1D &&
|
||||||
|
view.Target != Target.Texture2D &&
|
||||||
|
view.Target != Target.Texture2DMultisample &&
|
||||||
|
view.Target != Target.TextureBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (Handle != 0)
|
if (Handle != 0)
|
||||||
|
@ -144,6 +225,13 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
|
|
||||||
Handle = 0;
|
Handle = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_clearFbHandle != 0)
|
||||||
|
{
|
||||||
|
GL.DeleteFramebuffer(_clearFbHandle);
|
||||||
|
|
||||||
|
_clearFbHandle = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
private static readonly Lazy<bool> _supportsDrawTexture = new Lazy<bool>(() => HasExtension("GL_NV_draw_texture"));
|
private static readonly Lazy<bool> _supportsDrawTexture = new Lazy<bool>(() => HasExtension("GL_NV_draw_texture"));
|
||||||
private static readonly Lazy<bool> _supportsFragmentShaderInterlock = new Lazy<bool>(() => HasExtension("GL_ARB_fragment_shader_interlock"));
|
private static readonly Lazy<bool> _supportsFragmentShaderInterlock = new Lazy<bool>(() => HasExtension("GL_ARB_fragment_shader_interlock"));
|
||||||
private static readonly Lazy<bool> _supportsFragmentShaderOrdering = new Lazy<bool>(() => HasExtension("GL_INTEL_fragment_shader_ordering"));
|
private static readonly Lazy<bool> _supportsFragmentShaderOrdering = new Lazy<bool>(() => HasExtension("GL_INTEL_fragment_shader_ordering"));
|
||||||
|
private static readonly Lazy<bool> _supportsGeometryShaderPassthrough = new Lazy<bool>(() => HasExtension("GL_NV_geometry_shader_passthrough"));
|
||||||
private static readonly Lazy<bool> _supportsImageLoadFormatted = new Lazy<bool>(() => HasExtension("GL_EXT_shader_image_load_formatted"));
|
private static readonly Lazy<bool> _supportsImageLoadFormatted = new Lazy<bool>(() => HasExtension("GL_EXT_shader_image_load_formatted"));
|
||||||
private static readonly Lazy<bool> _supportsIndirectParameters = new Lazy<bool>(() => HasExtension("GL_ARB_indirect_parameters"));
|
private static readonly Lazy<bool> _supportsIndirectParameters = new Lazy<bool>(() => HasExtension("GL_ARB_indirect_parameters"));
|
||||||
private static readonly Lazy<bool> _supportsParallelShaderCompile = new Lazy<bool>(() => HasExtension("GL_ARB_parallel_shader_compile"));
|
private static readonly Lazy<bool> _supportsParallelShaderCompile = new Lazy<bool>(() => HasExtension("GL_ARB_parallel_shader_compile"));
|
||||||
|
@ -16,6 +17,9 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
private static readonly Lazy<bool> _supportsQuads = new Lazy<bool>(SupportsQuadsCheck);
|
private static readonly Lazy<bool> _supportsQuads = new Lazy<bool>(SupportsQuadsCheck);
|
||||||
private static readonly Lazy<bool> _supportsSeamlessCubemapPerTexture = new Lazy<bool>(() => HasExtension("GL_ARB_seamless_cubemap_per_texture"));
|
private static readonly Lazy<bool> _supportsSeamlessCubemapPerTexture = new Lazy<bool>(() => HasExtension("GL_ARB_seamless_cubemap_per_texture"));
|
||||||
private static readonly Lazy<bool> _supportsShaderBallot = new Lazy<bool>(() => HasExtension("GL_ARB_shader_ballot"));
|
private static readonly Lazy<bool> _supportsShaderBallot = new Lazy<bool>(() => HasExtension("GL_ARB_shader_ballot"));
|
||||||
|
private static readonly Lazy<bool> _supportsTextureCompressionBptc = new Lazy<bool>(() => HasExtension("GL_EXT_texture_compression_bptc"));
|
||||||
|
private static readonly Lazy<bool> _supportsTextureCompressionRgtc = new Lazy<bool>(() => HasExtension("GL_EXT_texture_compression_rgtc"));
|
||||||
|
private static readonly Lazy<bool> _supportsTextureCompressionS3tc = new Lazy<bool>(() => HasExtension("GL_EXT_texture_compression_s3tc"));
|
||||||
private static readonly Lazy<bool> _supportsTextureShadowLod = new Lazy<bool>(() => HasExtension("GL_EXT_texture_shadow_lod"));
|
private static readonly Lazy<bool> _supportsTextureShadowLod = new Lazy<bool>(() => HasExtension("GL_EXT_texture_shadow_lod"));
|
||||||
private static readonly Lazy<bool> _supportsViewportSwizzle = new Lazy<bool>(() => HasExtension("GL_NV_viewport_swizzle"));
|
private static readonly Lazy<bool> _supportsViewportSwizzle = new Lazy<bool>(() => HasExtension("GL_NV_viewport_swizzle"));
|
||||||
|
|
||||||
|
@ -47,6 +51,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
public static bool SupportsDrawTexture => _supportsDrawTexture.Value;
|
public static bool SupportsDrawTexture => _supportsDrawTexture.Value;
|
||||||
public static bool SupportsFragmentShaderInterlock => _supportsFragmentShaderInterlock.Value;
|
public static bool SupportsFragmentShaderInterlock => _supportsFragmentShaderInterlock.Value;
|
||||||
public static bool SupportsFragmentShaderOrdering => _supportsFragmentShaderOrdering.Value;
|
public static bool SupportsFragmentShaderOrdering => _supportsFragmentShaderOrdering.Value;
|
||||||
|
public static bool SupportsGeometryShaderPassthrough => _supportsGeometryShaderPassthrough.Value;
|
||||||
public static bool SupportsImageLoadFormatted => _supportsImageLoadFormatted.Value;
|
public static bool SupportsImageLoadFormatted => _supportsImageLoadFormatted.Value;
|
||||||
public static bool SupportsIndirectParameters => _supportsIndirectParameters.Value;
|
public static bool SupportsIndirectParameters => _supportsIndirectParameters.Value;
|
||||||
public static bool SupportsParallelShaderCompile => _supportsParallelShaderCompile.Value;
|
public static bool SupportsParallelShaderCompile => _supportsParallelShaderCompile.Value;
|
||||||
|
@ -54,6 +59,9 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
public static bool SupportsQuads => _supportsQuads.Value;
|
public static bool SupportsQuads => _supportsQuads.Value;
|
||||||
public static bool SupportsSeamlessCubemapPerTexture => _supportsSeamlessCubemapPerTexture.Value;
|
public static bool SupportsSeamlessCubemapPerTexture => _supportsSeamlessCubemapPerTexture.Value;
|
||||||
public static bool SupportsShaderBallot => _supportsShaderBallot.Value;
|
public static bool SupportsShaderBallot => _supportsShaderBallot.Value;
|
||||||
|
public static bool SupportsTextureCompressionBptc => _supportsTextureCompressionBptc.Value;
|
||||||
|
public static bool SupportsTextureCompressionRgtc => _supportsTextureCompressionRgtc.Value;
|
||||||
|
public static bool SupportsTextureCompressionS3tc => _supportsTextureCompressionS3tc.Value;
|
||||||
public static bool SupportsTextureShadowLod => _supportsTextureShadowLod.Value;
|
public static bool SupportsTextureShadowLod => _supportsTextureShadowLod.Value;
|
||||||
public static bool SupportsViewportSwizzle => _supportsViewportSwizzle.Value;
|
public static bool SupportsViewportSwizzle => _supportsViewportSwizzle.Value;
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
private CounterQueueEvent _activeConditionalRender;
|
private CounterQueueEvent _activeConditionalRender;
|
||||||
|
|
||||||
private Vector4<int>[] _fpIsBgra = new Vector4<int>[SupportBuffer.FragmentIsBgraCount];
|
private Vector4<int>[] _fpIsBgra = new Vector4<int>[SupportBuffer.FragmentIsBgraCount];
|
||||||
private Vector4<float>[] _renderScale = new Vector4<float>[65];
|
private Vector4<float>[] _renderScale = new Vector4<float>[73];
|
||||||
private int _fragmentScaleCount;
|
private int _fragmentScaleCount;
|
||||||
|
|
||||||
private TextureBase _unit0Texture;
|
private TextureBase _unit0Texture;
|
||||||
|
@ -110,7 +110,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
Buffer.Clear(destination, offset, size, value);
|
Buffer.Clear(destination, offset, size, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClearRenderTargetColor(int index, uint componentMask, ColorF color)
|
public void ClearRenderTargetColor(int index, int layer, uint componentMask, ColorF color)
|
||||||
{
|
{
|
||||||
GL.ColorMask(
|
GL.ColorMask(
|
||||||
index,
|
index,
|
||||||
|
@ -119,14 +119,18 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
(componentMask & 4) != 0,
|
(componentMask & 4) != 0,
|
||||||
(componentMask & 8) != 0);
|
(componentMask & 8) != 0);
|
||||||
|
|
||||||
|
_framebuffer.AttachColorLayerForClear(index, layer);
|
||||||
|
|
||||||
float[] colors = new float[] { color.Red, color.Green, color.Blue, color.Alpha };
|
float[] colors = new float[] { color.Red, color.Green, color.Blue, color.Alpha };
|
||||||
|
|
||||||
GL.ClearBuffer(OpenTK.Graphics.OpenGL.ClearBuffer.Color, index, colors);
|
GL.ClearBuffer(OpenTK.Graphics.OpenGL.ClearBuffer.Color, index, colors);
|
||||||
|
|
||||||
|
_framebuffer.DetachColorLayerForClear(index);
|
||||||
|
|
||||||
RestoreComponentMask(index);
|
RestoreComponentMask(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClearRenderTargetDepthStencil(float depthValue, bool depthMask, int stencilValue, int stencilMask)
|
public void ClearRenderTargetDepthStencil(int layer, float depthValue, bool depthMask, int stencilValue, int stencilMask)
|
||||||
{
|
{
|
||||||
bool stencilMaskChanged =
|
bool stencilMaskChanged =
|
||||||
stencilMask != 0 &&
|
stencilMask != 0 &&
|
||||||
|
@ -144,6 +148,8 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
GL.DepthMask(depthMask);
|
GL.DepthMask(depthMask);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_framebuffer.AttachDepthStencilLayerForClear(layer);
|
||||||
|
|
||||||
if (depthMask && stencilMask != 0)
|
if (depthMask && stencilMask != 0)
|
||||||
{
|
{
|
||||||
GL.ClearBuffer(ClearBufferCombined.DepthStencil, 0, depthValue, stencilValue);
|
GL.ClearBuffer(ClearBufferCombined.DepthStencil, 0, depthValue, stencilValue);
|
||||||
|
@ -157,6 +163,8 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
GL.ClearBuffer(OpenTK.Graphics.OpenGL.ClearBuffer.Stencil, 0, ref stencilValue);
|
GL.ClearBuffer(OpenTK.Graphics.OpenGL.ClearBuffer.Stencil, 0, ref stencilValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_framebuffer.DetachDepthStencilLayerForClear();
|
||||||
|
|
||||||
if (stencilMaskChanged)
|
if (stencilMaskChanged)
|
||||||
{
|
{
|
||||||
GL.StencilMaskSeparate(StencilFace.Front, _stencilFrontMask);
|
GL.StencilMaskSeparate(StencilFace.Front, _stencilFrontMask);
|
||||||
|
@ -597,6 +605,8 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
GL.EndTransformFeedback();
|
GL.EndTransformFeedback();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GL.ClipControl(ClipOrigin.UpperLeft, ClipDepthMode.NegativeOneToOne);
|
||||||
|
|
||||||
_drawTexture.Draw(
|
_drawTexture.Draw(
|
||||||
view,
|
view,
|
||||||
samp,
|
samp,
|
||||||
|
@ -627,6 +637,8 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
{
|
{
|
||||||
GL.BeginTransformFeedback(_tfTopology);
|
GL.BeginTransformFeedback(_tfTopology);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RestoreClipControl();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1094,45 +1106,45 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
_framebuffer.SetDrawBuffers(colors.Length);
|
_framebuffer.SetDrawBuffers(colors.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetSampler(int binding, ISampler sampler)
|
public unsafe void SetScissors(ReadOnlySpan<Rectangle<int>> regions)
|
||||||
{
|
{
|
||||||
if (sampler == null)
|
int count = Math.Min(regions.Length, Constants.MaxViewports);
|
||||||
|
|
||||||
|
int* v = stackalloc int[count * 4];
|
||||||
|
|
||||||
|
for (int index = 0; index < count; index++)
|
||||||
{
|
{
|
||||||
return;
|
int vIndex = index * 4;
|
||||||
}
|
|
||||||
|
|
||||||
Sampler samp = (Sampler)sampler;
|
var region = regions[index];
|
||||||
|
|
||||||
if (binding == 0)
|
bool enabled = (region.X | region.Y) != 0 || region.Width != 0xffff || region.Height != 0xffff;
|
||||||
{
|
uint mask = 1u << index;
|
||||||
_unit0Sampler = samp;
|
|
||||||
}
|
|
||||||
|
|
||||||
samp.Bind(binding);
|
if (enabled)
|
||||||
}
|
|
||||||
|
|
||||||
public void SetScissor(int index, bool enable, int x, int y, int width, int height)
|
|
||||||
{
|
|
||||||
uint mask = 1u << index;
|
|
||||||
|
|
||||||
if (!enable)
|
|
||||||
{
|
|
||||||
if ((_scissorEnables & mask) != 0)
|
|
||||||
{
|
{
|
||||||
_scissorEnables &= ~mask;
|
v[vIndex] = region.X;
|
||||||
GL.Disable(IndexedEnableCap.ScissorTest, index);
|
v[vIndex + 1] = region.Y;
|
||||||
|
v[vIndex + 2] = region.Width;
|
||||||
|
v[vIndex + 3] = region.Height;
|
||||||
|
|
||||||
|
if ((_scissorEnables & mask) == 0)
|
||||||
|
{
|
||||||
|
_scissorEnables |= mask;
|
||||||
|
GL.Enable(IndexedEnableCap.ScissorTest, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if ((_scissorEnables & mask) != 0)
|
||||||
|
{
|
||||||
|
_scissorEnables &= ~mask;
|
||||||
|
GL.Disable(IndexedEnableCap.ScissorTest, index);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((_scissorEnables & mask) == 0)
|
GL.ScissorArray(0, count, v);
|
||||||
{
|
|
||||||
_scissorEnables |= mask;
|
|
||||||
GL.Enable(IndexedEnableCap.ScissorTest, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
GL.ScissorIndexed(index, x, y, width, height);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetStencilTest(StencilTestDescriptor stencilTest)
|
public void SetStencilTest(StencilTestDescriptor stencilTest)
|
||||||
|
@ -1183,23 +1195,31 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
SetBuffers(first, buffers, isStorage: true);
|
SetBuffers(first, buffers, isStorage: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetTexture(int binding, ITexture texture)
|
public void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler)
|
||||||
{
|
{
|
||||||
if (texture == null)
|
if (texture != null)
|
||||||
{
|
{
|
||||||
return;
|
if (binding == 0)
|
||||||
|
{
|
||||||
|
_unit0Texture = (TextureBase)texture;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
((TextureBase)texture).Bind(binding);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Sampler glSampler = (Sampler)sampler;
|
||||||
|
|
||||||
|
glSampler?.Bind(binding);
|
||||||
|
|
||||||
if (binding == 0)
|
if (binding == 0)
|
||||||
{
|
{
|
||||||
_unit0Texture = (TextureBase)texture;
|
_unit0Sampler = glSampler;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
((TextureBase)texture).Bind(binding);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers)
|
public void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers)
|
||||||
{
|
{
|
||||||
if (_tfEnabled)
|
if (_tfEnabled)
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using OpenTK.Graphics.OpenGL;
|
using OpenTK.Graphics.OpenGL;
|
||||||
using Ryujinx.Common.Logging;
|
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
|
@ -87,6 +87,11 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
Buffer.Delete(buffer);
|
Buffer.Delete(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HardwareInfo GetHardwareInfo()
|
||||||
|
{
|
||||||
|
return new HardwareInfo(GpuVendor, GpuRenderer);
|
||||||
|
}
|
||||||
|
|
||||||
public ReadOnlySpan<byte> GetBufferData(BufferHandle buffer, int offset, int size)
|
public ReadOnlySpan<byte> GetBufferData(BufferHandle buffer, int offset, int size)
|
||||||
{
|
{
|
||||||
return Buffer.GetData(this, buffer, offset, size);
|
return Buffer.GetData(this, buffer, offset, size);
|
||||||
|
@ -100,11 +105,15 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
hasFrontFacingBug: HwCapabilities.Vendor == HwCapabilities.GpuVendor.IntelWindows,
|
hasFrontFacingBug: HwCapabilities.Vendor == HwCapabilities.GpuVendor.IntelWindows,
|
||||||
hasVectorIndexingBug: HwCapabilities.Vendor == HwCapabilities.GpuVendor.AmdWindows,
|
hasVectorIndexingBug: HwCapabilities.Vendor == HwCapabilities.GpuVendor.AmdWindows,
|
||||||
supportsAstcCompression: HwCapabilities.SupportsAstcCompression,
|
supportsAstcCompression: HwCapabilities.SupportsAstcCompression,
|
||||||
|
supportsBc123Compression: HwCapabilities.SupportsTextureCompressionS3tc,
|
||||||
|
supportsBc45Compression: HwCapabilities.SupportsTextureCompressionRgtc,
|
||||||
|
supportsBc67Compression: true, // Should check BPTC extension, but for some reason NVIDIA is not exposing the extension.
|
||||||
supports3DTextureCompression: false,
|
supports3DTextureCompression: false,
|
||||||
supportsBgraFormat: false,
|
supportsBgraFormat: false,
|
||||||
supportsR4G4Format: false,
|
supportsR4G4Format: false,
|
||||||
supportsFragmentShaderInterlock: HwCapabilities.SupportsFragmentShaderInterlock,
|
supportsFragmentShaderInterlock: HwCapabilities.SupportsFragmentShaderInterlock,
|
||||||
supportsFragmentShaderOrderingIntel: HwCapabilities.SupportsFragmentShaderOrdering,
|
supportsFragmentShaderOrderingIntel: HwCapabilities.SupportsFragmentShaderOrdering,
|
||||||
|
supportsGeometryShaderPassthrough: HwCapabilities.SupportsGeometryShaderPassthrough,
|
||||||
supportsImageLoadFormatted: HwCapabilities.SupportsImageLoadFormatted,
|
supportsImageLoadFormatted: HwCapabilities.SupportsImageLoadFormatted,
|
||||||
supportsMismatchingViewFormat: HwCapabilities.SupportsMismatchingViewFormat,
|
supportsMismatchingViewFormat: HwCapabilities.SupportsMismatchingViewFormat,
|
||||||
supportsNonConstantTextureOffset: HwCapabilities.SupportsNonConstantTextureOffset,
|
supportsNonConstantTextureOffset: HwCapabilities.SupportsNonConstantTextureOffset,
|
||||||
|
@ -112,6 +121,10 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
supportsTextureShadowLod: HwCapabilities.SupportsTextureShadowLod,
|
supportsTextureShadowLod: HwCapabilities.SupportsTextureShadowLod,
|
||||||
supportsViewportSwizzle: HwCapabilities.SupportsViewportSwizzle,
|
supportsViewportSwizzle: HwCapabilities.SupportsViewportSwizzle,
|
||||||
supportsIndirectParameters: HwCapabilities.SupportsIndirectParameters,
|
supportsIndirectParameters: HwCapabilities.SupportsIndirectParameters,
|
||||||
|
maximumUniformBuffersPerStage: 13, // TODO: Avoid hardcoding those limits here and get from driver?
|
||||||
|
maximumStorageBuffersPerStage: 16,
|
||||||
|
maximumTexturesPerStage: 32,
|
||||||
|
maximumImagesPerStage: 8,
|
||||||
maximumComputeSharedMemorySize: HwCapabilities.MaximumComputeSharedMemorySize,
|
maximumComputeSharedMemorySize: HwCapabilities.MaximumComputeSharedMemorySize,
|
||||||
maximumSupportedAnisotropy: HwCapabilities.MaximumSupportedAnisotropy,
|
maximumSupportedAnisotropy: HwCapabilities.MaximumSupportedAnisotropy,
|
||||||
storageBufferOffsetAlignment: HwCapabilities.StorageBufferOffsetAlignment);
|
storageBufferOffsetAlignment: HwCapabilities.StorageBufferOffsetAlignment);
|
||||||
|
|
14
Ryujinx.Graphics.Shader/AlphaTestOp.cs
Normal file
14
Ryujinx.Graphics.Shader/AlphaTestOp.cs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
namespace Ryujinx.Graphics.Shader
|
||||||
|
{
|
||||||
|
public enum AlphaTestOp
|
||||||
|
{
|
||||||
|
Never = 1,
|
||||||
|
Less,
|
||||||
|
Equal,
|
||||||
|
LessOrEqual,
|
||||||
|
Greater,
|
||||||
|
NotEqual,
|
||||||
|
GreaterOrEqual,
|
||||||
|
Always
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue