mirror of
https://git.naxdy.org/Mirror/Ryujinx.git
synced 2025-02-19 07:43:35 +00:00
commit
1100e5e1a6
273 changed files with 39733 additions and 1502 deletions
|
@ -1,5 +1,6 @@
|
|||
using ARMeilleure.Translation;
|
||||
using ARMeilleure.Translation.PTC;
|
||||
using Avalonia;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Threading;
|
||||
using LibHac.Tools.FsSystem;
|
||||
|
@ -13,6 +14,7 @@ using Ryujinx.Ava.Common.Locale;
|
|||
using Ryujinx.Ava.Input;
|
||||
using Ryujinx.Ava.Ui.Controls;
|
||||
using Ryujinx.Ava.Ui.Models;
|
||||
using Ryujinx.Ava.Ui.Vulkan;
|
||||
using Ryujinx.Ava.Ui.Windows;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Configuration;
|
||||
|
@ -22,6 +24,7 @@ using Ryujinx.Graphics.GAL;
|
|||
using Ryujinx.Graphics.GAL.Multithreading;
|
||||
using Ryujinx.Graphics.Gpu;
|
||||
using Ryujinx.Graphics.OpenGL;
|
||||
using Ryujinx.Graphics.Vulkan;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
|
@ -590,7 +593,23 @@ namespace Ryujinx.Ava
|
|||
{
|
||||
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();
|
||||
|
||||
BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading;
|
||||
|
@ -800,9 +819,12 @@ namespace Ryujinx.Ava
|
|||
|
||||
_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);
|
||||
|
||||
|
@ -861,8 +883,6 @@ namespace Ryujinx.Ava
|
|||
dockedMode += $" ({scale}x)";
|
||||
}
|
||||
|
||||
string vendor = _renderer is Renderer renderer ? renderer.GpuVendor : "";
|
||||
|
||||
StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs(
|
||||
Device.EnableDeviceVsync,
|
||||
Device.GetVolume(),
|
||||
|
@ -870,7 +890,7 @@ namespace Ryujinx.Ava
|
|||
ConfigurationState.Instance.Graphics.AspectRatio.Value.ToText(),
|
||||
LocaleManager.Instance["Game"] + $": {Device.Statistics.GetGameFrameRate():00.00} FPS ({Device.Statistics.GetGameFrameTime():00.00} ms)",
|
||||
$"FIFO: {Device.Statistics.GetFifoPercent():00.00} %",
|
||||
$"GPU: {vendor}"));
|
||||
$"GPU: {_renderer.GetHardwareInfo().GpuVendor}"));
|
||||
|
||||
Renderer.Present(image);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ using Avalonia;
|
|||
using Avalonia.OpenGL;
|
||||
using Avalonia.Rendering;
|
||||
using Avalonia.Threading;
|
||||
using Ryujinx.Ava.Ui.Backend;
|
||||
using Ryujinx.Ava.Ui.Controls;
|
||||
using Ryujinx.Ava.Ui.Windows;
|
||||
using Ryujinx.Common;
|
||||
|
@ -11,9 +12,12 @@ using Ryujinx.Common.GraphicsDriver;
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.System;
|
||||
using Ryujinx.Common.SystemInfo;
|
||||
using Ryujinx.Graphics.Vulkan;
|
||||
using Ryujinx.Modules;
|
||||
using Ryujinx.Ui.Common;
|
||||
using Ryujinx.Ui.Common.Configuration;
|
||||
using Silk.NET.Vulkan.Extensions.EXT;
|
||||
using Silk.NET.Vulkan.Extensions.KHR;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
@ -25,17 +29,20 @@ namespace Ryujinx.Ava
|
|||
internal class Program
|
||||
{
|
||||
public static double WindowScaleFactor { get; set; }
|
||||
public static double ActualScaleFactor { get; set; }
|
||||
public static string Version { get; private set; }
|
||||
public static string ConfigurationPath { get; private set; }
|
||||
public static string CommandLineProfile { get; set; }
|
||||
public static bool PreviewerDetached { get; private set; }
|
||||
|
||||
public static RenderTimer RenderTimer { get; private set; }
|
||||
public static bool UseVulkan { get; private set; }
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
public static extern int MessageBoxA(IntPtr hWnd, string text, string caption, uint type);
|
||||
|
||||
private const uint MB_ICONWARNING = 0x30;
|
||||
private const int BaseDpi = 96;
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
|
@ -66,7 +73,7 @@ namespace Ryujinx.Ava
|
|||
EnableMultiTouch = true,
|
||||
EnableIme = true,
|
||||
UseEGL = false,
|
||||
UseGpu = true,
|
||||
UseGpu = !UseVulkan,
|
||||
GlProfiles = new List<GlVersion>()
|
||||
{
|
||||
new GlVersion(GlProfileType.OpenGL, 4, 3)
|
||||
|
@ -75,7 +82,7 @@ namespace Ryujinx.Ava
|
|||
.With(new Win32PlatformOptions
|
||||
{
|
||||
EnableMultitouch = true,
|
||||
UseWgl = true,
|
||||
UseWgl = !UseVulkan,
|
||||
WglProfiles = new List<GlVersion>()
|
||||
{
|
||||
new GlVersion(GlProfileType.OpenGL, 4, 3)
|
||||
|
@ -84,6 +91,19 @@ namespace Ryujinx.Ava
|
|||
CompositionBackdropCornerRadius = 8f,
|
||||
})
|
||||
.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(_ =>
|
||||
{
|
||||
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.
|
||||
Task.Run(Updater.CleanupUpdate);
|
||||
|
||||
|
@ -162,6 +179,18 @@ namespace Ryujinx.Ava
|
|||
|
||||
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.
|
||||
PrintSystemInfo();
|
||||
|
||||
|
|
|
@ -28,10 +28,13 @@
|
|||
<PackageReference Include="jp2masa.Avalonia.Flexbox" Version="0.2.0" />
|
||||
<PackageReference Include="DynamicData" Version="7.7.14" />
|
||||
<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.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="SharpZipLib" Version="1.3.3" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
|
||||
|
@ -39,6 +42,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<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.SDL2\Ryujinx.Input.SDL2.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Audio.Backends.OpenAL\Ryujinx.Audio.Backends.OpenAL.csproj" />
|
||||
|
|
|
@ -135,7 +135,7 @@ namespace Ryujinx.Ava.Ui.Applet
|
|||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
_hiddenTextBox.Clear();
|
||||
_parent.GlRenderer.Focus();
|
||||
_parent.RendererControl.Focus();
|
||||
|
||||
_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.Data;
|
||||
using Avalonia.Media;
|
||||
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;
|
||||
|
||||
namespace Ryujinx.Ava.Ui.Controls
|
||||
{
|
||||
public class RendererControl : Control
|
||||
public abstract class RendererControl : Control
|
||||
{
|
||||
private int _image;
|
||||
protected object _image;
|
||||
|
||||
static RendererControl()
|
||||
{
|
||||
AffectsRender<RendererControl>(ImageProperty);
|
||||
}
|
||||
|
||||
public readonly static StyledProperty<int> ImageProperty =
|
||||
AvaloniaProperty.Register<RendererControl, int>(nameof(Image), 0, inherits: true, defaultBindingMode: BindingMode.TwoWay);
|
||||
public readonly static StyledProperty<object> ImageProperty =
|
||||
AvaloniaProperty.Register<RendererControl, object>(nameof(Image), 0, inherits: true, defaultBindingMode: BindingMode.TwoWay);
|
||||
|
||||
protected int Image
|
||||
protected object Image
|
||||
{
|
||||
get => _image;
|
||||
set => SetAndRaise(ImageProperty, ref _image, value);
|
||||
}
|
||||
|
||||
public event EventHandler<EventArgs> GlInitialized;
|
||||
public event EventHandler<EventArgs> RendererInitialized;
|
||||
public event EventHandler<Size> SizeChanged;
|
||||
|
||||
protected Size RenderSize { get; private set; }
|
||||
public bool IsStarted { get; private set; }
|
||||
|
||||
public int Major { get; }
|
||||
public int Minor { 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 IntPtr _fence;
|
||||
protected ICustomDrawOperation DrawOperation { get; private set; }
|
||||
|
||||
private GlDrawOperation _glDrawOperation;
|
||||
|
||||
public RendererControl(int major, int minor, GraphicsDebugLevel graphicsDebugLevel)
|
||||
public RendererControl(GraphicsDebugLevel graphicsDebugLevel)
|
||||
{
|
||||
Major = major;
|
||||
Minor = minor;
|
||||
DebugLevel = graphicsDebugLevel;
|
||||
IObservable<Rect> resizeObservable = this.GetObservable(BoundsProperty);
|
||||
|
||||
|
@ -69,7 +49,7 @@ namespace Ryujinx.Ava.Ui.Controls
|
|||
Focusable = true;
|
||||
}
|
||||
|
||||
private void Resized(Rect rect)
|
||||
protected void Resized(Rect rect)
|
||||
{
|
||||
SizeChanged?.Invoke(this, rect.Size);
|
||||
|
||||
|
@ -77,37 +57,40 @@ namespace Ryujinx.Ava.Ui.Controls
|
|||
{
|
||||
RenderSize = rect.Size * VisualRoot.RenderScaling;
|
||||
|
||||
_glDrawOperation?.Dispose();
|
||||
_glDrawOperation = new GlDrawOperation(this);
|
||||
DrawOperation?.Dispose();
|
||||
DrawOperation = CreateDrawOperation();
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract ICustomDrawOperation CreateDrawOperation();
|
||||
protected abstract void CreateWindow();
|
||||
|
||||
public override void Render(DrawingContext context)
|
||||
{
|
||||
if (!_isInitialized)
|
||||
{
|
||||
CreateWindow();
|
||||
|
||||
OnGlInitialized();
|
||||
OnRendererInitialized();
|
||||
_isInitialized = true;
|
||||
}
|
||||
|
||||
if (GameContext == null || !IsStarted || Image == 0)
|
||||
if (!IsStarted || Image == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_glDrawOperation != null)
|
||||
if (DrawOperation != null)
|
||||
{
|
||||
context.Custom(_glDrawOperation);
|
||||
context.Custom(DrawOperation);
|
||||
}
|
||||
|
||||
base.Render(context);
|
||||
}
|
||||
|
||||
protected void OnGlInitialized()
|
||||
protected void OnRendererInitialized()
|
||||
{
|
||||
GlInitialized?.Invoke(this, EventArgs.Empty);
|
||||
RendererInitialized?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public void QueueRender()
|
||||
|
@ -115,24 +98,7 @@ namespace Ryujinx.Ava.Ui.Controls
|
|||
Program.RenderTimer.TickNow();
|
||||
}
|
||||
|
||||
internal 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 abstract void Present(object image);
|
||||
|
||||
internal void Start()
|
||||
{
|
||||
|
@ -145,132 +111,8 @@ namespace Ryujinx.Ava.Ui.Controls
|
|||
IsStarted = false;
|
||||
}
|
||||
|
||||
public void DestroyBackgroundContext()
|
||||
{
|
||||
_image = 0;
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
public abstract void DestroyBackgroundContext();
|
||||
internal abstract void MakeCurrent();
|
||||
internal abstract void MakeCurrent(SwappableNativeWindowBase window);
|
||||
}
|
||||
}
|
||||
}
|
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 InputManager InputManager { get; private set; }
|
||||
|
||||
public RendererControl GlRenderer { get; private set; }
|
||||
public RendererControl RendererControl { get; private set; }
|
||||
public ContentControl ContentFrame { get; private set; }
|
||||
public TextBlock LoadStatus { get; private set; }
|
||||
public TextBlock FirmwareStatus { get; private set; }
|
||||
|
@ -257,8 +257,8 @@ namespace Ryujinx.Ava.Ui.Windows
|
|||
|
||||
_mainViewContent = ContentFrame.Content as Control;
|
||||
|
||||
GlRenderer = new RendererControl(3, 3, ConfigurationState.Instance.Logger.GraphicsDebugLevel);
|
||||
AppHost = new AppHost(GlRenderer, InputManager, path, VirtualFileSystem, ContentManager, AccountManager, _userChannelPersistence, this);
|
||||
RendererControl = Program.UseVulkan ? new VulkanRendererControl(ConfigurationState.Instance.Logger.GraphicsDebugLevel) : new OpenGLRendererControl(3, 3, ConfigurationState.Instance.Logger.GraphicsDebugLevel);
|
||||
AppHost = new AppHost(RendererControl, InputManager, path, VirtualFileSystem, ContentManager, AccountManager, _userChannelPersistence, this);
|
||||
|
||||
if (!AppHost.LoadGuestApplication().Result)
|
||||
{
|
||||
|
@ -282,7 +282,7 @@ namespace Ryujinx.Ava.Ui.Windows
|
|||
|
||||
private void InitializeGame()
|
||||
{
|
||||
GlRenderer.GlInitialized += GlRenderer_Created;
|
||||
RendererControl.RendererInitialized += GlRenderer_Created;
|
||||
|
||||
AppHost.StatusUpdatedEvent += Update_StatusBar;
|
||||
AppHost.AppExit += AppHost_AppExit;
|
||||
|
@ -322,14 +322,14 @@ namespace Ryujinx.Ava.Ui.Windows
|
|||
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
ContentFrame.Content = GlRenderer;
|
||||
ContentFrame.Content = RendererControl;
|
||||
|
||||
if (startFullscreen && WindowState != WindowState.FullScreen)
|
||||
{
|
||||
ViewModel.ToggleFullscreen();
|
||||
}
|
||||
|
||||
GlRenderer.Focus();
|
||||
RendererControl.Focus();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -380,8 +380,8 @@ namespace Ryujinx.Ava.Ui.Windows
|
|||
|
||||
HandleRelaunch();
|
||||
});
|
||||
GlRenderer.GlInitialized -= GlRenderer_Created;
|
||||
GlRenderer = null;
|
||||
RendererControl.RendererInitialized -= GlRenderer_Created;
|
||||
RendererControl = null;
|
||||
|
||||
ViewModel.SelectedIcon = null;
|
||||
|
||||
|
@ -544,6 +544,7 @@ namespace Ryujinx.Ava.Ui.Windows
|
|||
GraphicsConfig.MaxAnisotropy = ConfigurationState.Instance.Graphics.MaxAnisotropy;
|
||||
GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath;
|
||||
GraphicsConfig.EnableShaderCache = ConfigurationState.Instance.Graphics.EnableShaderCache;
|
||||
GraphicsConfig.EnableTextureRecompression = ConfigurationState.Instance.Graphics.EnableTextureRecompression;
|
||||
}
|
||||
|
||||
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 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;
|
||||
|
||||
|
@ -84,6 +84,13 @@ namespace Ryujinx.Common.System
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,11 +11,15 @@ namespace Ryujinx.Graphics.GAL
|
|||
public readonly bool HasVectorIndexingBug;
|
||||
|
||||
public readonly bool SupportsAstcCompression;
|
||||
public readonly bool SupportsBc123Compression;
|
||||
public readonly bool SupportsBc45Compression;
|
||||
public readonly bool SupportsBc67Compression;
|
||||
public readonly bool Supports3DTextureCompression;
|
||||
public readonly bool SupportsBgraFormat;
|
||||
public readonly bool SupportsR4G4Format;
|
||||
public readonly bool SupportsFragmentShaderInterlock;
|
||||
public readonly bool SupportsFragmentShaderOrderingIntel;
|
||||
public readonly bool SupportsGeometryShaderPassthrough;
|
||||
public readonly bool SupportsImageLoadFormatted;
|
||||
public readonly bool SupportsMismatchingViewFormat;
|
||||
public readonly bool SupportsNonConstantTextureOffset;
|
||||
|
@ -24,6 +28,11 @@ namespace Ryujinx.Graphics.GAL
|
|||
public readonly bool SupportsViewportSwizzle;
|
||||
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 float MaximumSupportedAnisotropy;
|
||||
public readonly int StorageBufferOffsetAlignment;
|
||||
|
@ -34,11 +43,15 @@ namespace Ryujinx.Graphics.GAL
|
|||
bool hasFrontFacingBug,
|
||||
bool hasVectorIndexingBug,
|
||||
bool supportsAstcCompression,
|
||||
bool supportsBc123Compression,
|
||||
bool supportsBc45Compression,
|
||||
bool supportsBc67Compression,
|
||||
bool supports3DTextureCompression,
|
||||
bool supportsBgraFormat,
|
||||
bool supportsR4G4Format,
|
||||
bool supportsFragmentShaderInterlock,
|
||||
bool supportsFragmentShaderOrderingIntel,
|
||||
bool supportsGeometryShaderPassthrough,
|
||||
bool supportsImageLoadFormatted,
|
||||
bool supportsMismatchingViewFormat,
|
||||
bool supportsNonConstantTextureOffset,
|
||||
|
@ -46,6 +59,10 @@ namespace Ryujinx.Graphics.GAL
|
|||
bool supportsTextureShadowLod,
|
||||
bool supportsViewportSwizzle,
|
||||
bool supportsIndirectParameters,
|
||||
uint maximumUniformBuffersPerStage,
|
||||
uint maximumStorageBuffersPerStage,
|
||||
uint maximumTexturesPerStage,
|
||||
uint maximumImagesPerStage,
|
||||
int maximumComputeSharedMemorySize,
|
||||
float maximumSupportedAnisotropy,
|
||||
int storageBufferOffsetAlignment)
|
||||
|
@ -55,11 +72,15 @@ namespace Ryujinx.Graphics.GAL
|
|||
HasFrontFacingBug = hasFrontFacingBug;
|
||||
HasVectorIndexingBug = hasVectorIndexingBug;
|
||||
SupportsAstcCompression = supportsAstcCompression;
|
||||
SupportsBc123Compression = supportsBc123Compression;
|
||||
SupportsBc45Compression = supportsBc45Compression;
|
||||
SupportsBc67Compression = supportsBc67Compression;
|
||||
Supports3DTextureCompression = supports3DTextureCompression;
|
||||
SupportsBgraFormat = supportsBgraFormat;
|
||||
SupportsR4G4Format = supportsR4G4Format;
|
||||
SupportsFragmentShaderInterlock = supportsFragmentShaderInterlock;
|
||||
SupportsFragmentShaderOrderingIntel = supportsFragmentShaderOrderingIntel;
|
||||
SupportsGeometryShaderPassthrough = supportsGeometryShaderPassthrough;
|
||||
SupportsImageLoadFormatted = supportsImageLoadFormatted;
|
||||
SupportsMismatchingViewFormat = supportsMismatchingViewFormat;
|
||||
SupportsNonConstantTextureOffset = supportsNonConstantTextureOffset;
|
||||
|
@ -67,6 +88,10 @@ namespace Ryujinx.Graphics.GAL
|
|||
SupportsTextureShadowLod = supportsTextureShadowLod;
|
||||
SupportsViewportSwizzle = supportsViewportSwizzle;
|
||||
SupportsIndirectParameters = supportsIndirectParameters;
|
||||
MaximumUniformBuffersPerStage = maximumUniformBuffersPerStage;
|
||||
MaximumStorageBuffersPerStage = maximumStorageBuffersPerStage;
|
||||
MaximumTexturesPerStage = maximumTexturesPerStage;
|
||||
MaximumImagesPerStage = maximumImagesPerStage;
|
||||
MaximumComputeSharedMemorySize = maximumComputeSharedMemorySize;
|
||||
MaximumSupportedAnisotropy = maximumSupportedAnisotropy;
|
||||
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
|
||||
{
|
||||
/// <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>
|
||||
/// Checks if the texture format is an ASTC format.
|
||||
/// </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;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
|
@ -10,9 +11,10 @@ namespace Ryujinx.Graphics.GAL
|
|||
|
||||
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(
|
||||
int layer,
|
||||
float depthValue,
|
||||
bool depthMask,
|
||||
int stencilValue,
|
||||
|
@ -76,15 +78,13 @@ namespace Ryujinx.Graphics.GAL
|
|||
void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMask);
|
||||
void SetRenderTargets(ITexture[] colors, ITexture depthStencil);
|
||||
|
||||
void SetSampler(int binding, ISampler sampler);
|
||||
|
||||
void SetScissor(int index, bool enable, int x, int y, int width, int height);
|
||||
void SetScissors(ReadOnlySpan<Rectangle<int>> regions);
|
||||
|
||||
void SetStencilTest(StencilTestDescriptor stencilTest);
|
||||
|
||||
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 SetUniformBuffers(int first, ReadOnlySpan<BufferRange> buffers);
|
||||
|
|
|
@ -30,6 +30,7 @@ namespace Ryujinx.Graphics.GAL
|
|||
ReadOnlySpan<byte> GetBufferData(BufferHandle buffer, int offset, int size);
|
||||
|
||||
Capabilities GetCapabilities();
|
||||
HardwareInfo GetHardwareInfo();
|
||||
|
||||
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);
|
||||
_lookup[(int)CommandType.SetRenderTargets] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer 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) =>
|
||||
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) =>
|
||||
SetStencilTestCommand.Run(ref GetCommand<SetStencilTestCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetTexture] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
|
||||
SetTextureCommand.Run(ref GetCommand<SetTextureCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetTextureAndSampler] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
|
||||
SetTextureAndSamplerCommand.Run(ref GetCommand<SetTextureAndSamplerCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetUserClipDistance] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
|
||||
SetUserClipDistanceCommand.Run(ref GetCommand<SetUserClipDistanceCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetVertexAttribs] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
|
||||
|
|
|
@ -81,10 +81,9 @@
|
|||
SetRenderTargetColorMasks,
|
||||
SetRenderTargetScale,
|
||||
SetRenderTargets,
|
||||
SetSampler,
|
||||
SetScissor,
|
||||
SetStencilTest,
|
||||
SetTexture,
|
||||
SetTextureAndSampler,
|
||||
SetUserClipDistance,
|
||||
SetVertexAttribs,
|
||||
SetVertexBuffers,
|
||||
|
|
|
@ -4,19 +4,21 @@
|
|||
{
|
||||
public CommandType CommandType => CommandType.ClearRenderTargetColor;
|
||||
private int _index;
|
||||
private int _layer;
|
||||
private uint _componentMask;
|
||||
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;
|
||||
_layer = layer;
|
||||
_componentMask = componentMask;
|
||||
_color = color;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
public CommandType CommandType => CommandType.ClearRenderTargetDepthStencil;
|
||||
private int _layer;
|
||||
private float _depthValue;
|
||||
private bool _depthMask;
|
||||
private int _stencilValue;
|
||||
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;
|
||||
_depthMask = depthMask;
|
||||
_stencilValue = stencilValue;
|
||||
|
@ -18,7 +20,7 @@
|
|||
|
||||
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.Model;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
|
@ -40,15 +41,15 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
_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();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -244,15 +245,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
_renderer.QueueCommand();
|
||||
}
|
||||
|
||||
public void SetSampler(int binding, ISampler sampler)
|
||||
public void SetScissors(ReadOnlySpan<Rectangle<int>> scissors)
|
||||
{
|
||||
_renderer.New<SetSamplerCommand>().Set(binding, Ref(sampler));
|
||||
_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.New<SetScissorsCommand>().Set(_renderer.CopySpan(scissors));
|
||||
_renderer.QueueCommand();
|
||||
}
|
||||
|
||||
|
@ -268,9 +263,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
_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();
|
||||
}
|
||||
|
||||
|
|
|
@ -76,7 +76,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
renderer.ScreenCaptured += (object sender, ScreenCaptureImageInfo info) => ScreenCaptured?.Invoke(this, info);
|
||||
|
||||
Pipeline = new ThreadedPipeline(this, renderer.Pipeline);
|
||||
Window = new ThreadedWindow(this, renderer.Window);
|
||||
Window = new ThreadedWindow(this, renderer);
|
||||
Buffers = new BufferMap();
|
||||
Sync = new SyncMap();
|
||||
Programs = new ProgramQueue(renderer);
|
||||
|
@ -262,7 +262,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info)
|
||||
{
|
||||
var program = new ThreadedProgram(this);
|
||||
|
||||
SourceProgramRequest request = new SourceProgramRequest(program, shaders, info);
|
||||
|
||||
Programs.Add(request);
|
||||
|
||||
New<CreateProgramCommand>().Set(Ref((IProgramRequest)request));
|
||||
|
@ -337,6 +339,11 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
return box.Result;
|
||||
}
|
||||
|
||||
public HardwareInfo GetHardwareInfo()
|
||||
{
|
||||
return _baseRenderer.GetHardwareInfo();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the base renderer. Must be called on the render thread.
|
||||
/// </summary>
|
||||
|
|
|
@ -8,12 +8,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
public class ThreadedWindow : IWindow
|
||||
{
|
||||
private ThreadedRenderer _renderer;
|
||||
private IWindow _impl;
|
||||
private IRenderer _impl;
|
||||
|
||||
public ThreadedWindow(ThreadedRenderer renderer, IWindow window)
|
||||
public ThreadedWindow(ThreadedRenderer renderer, IRenderer impl)
|
||||
{
|
||||
_renderer = renderer;
|
||||
_impl = window;
|
||||
_impl = impl;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
_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 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;
|
||||
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 byte[] BinaryCode { get; }
|
||||
public ShaderBindings Bindings { get; }
|
||||
public ShaderStage Stage { 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;
|
||||
BinaryCode = binaryCode;
|
||||
Bindings = bindings;
|
||||
Stage = stage;
|
||||
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 RectangleF Region { get; }
|
||||
public Rectangle<float> Region { get; }
|
||||
|
||||
public ViewportSwizzle SwizzleX { get; }
|
||||
public ViewportSwizzle SwizzleY { get; }
|
||||
|
@ -13,13 +13,13 @@ namespace Ryujinx.Graphics.GAL
|
|||
public float DepthFar { get; }
|
||||
|
||||
public Viewport(
|
||||
RectangleF region,
|
||||
ViewportSwizzle swizzleX,
|
||||
ViewportSwizzle swizzleY,
|
||||
ViewportSwizzle swizzleZ,
|
||||
ViewportSwizzle swizzleW,
|
||||
float depthNear,
|
||||
float depthFar)
|
||||
Rectangle<float> region,
|
||||
ViewportSwizzle swizzleX,
|
||||
ViewportSwizzle swizzleY,
|
||||
ViewportSwizzle swizzleZ,
|
||||
ViewportSwizzle swizzleW,
|
||||
float depthNear,
|
||||
float depthFar)
|
||||
{
|
||||
Region = region;
|
||||
SwizzleX = swizzleX;
|
||||
|
|
|
@ -40,6 +40,22 @@ namespace Ryujinx.Graphics.Gpu
|
|||
/// </summary>
|
||||
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>
|
||||
/// Maximum number of render target color buffers.
|
||||
/// </summary>
|
||||
|
@ -53,7 +69,7 @@ namespace Ryujinx.Graphics.Gpu
|
|||
/// <summary>
|
||||
/// Maximum number of vertex attributes.
|
||||
/// </summary>
|
||||
public const int TotalVertexAttribs = 16;
|
||||
public const int TotalVertexAttribs = 16; // FIXME: Should be 32, but OpenGL only supports 16.
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of vertex buffers.
|
||||
|
|
|
@ -188,6 +188,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
|
|||
_channel.BufferManager.SetComputeStorageBufferBindings(info.SBuffers);
|
||||
_channel.BufferManager.SetComputeUniformBufferBindings(info.CBuffers);
|
||||
|
||||
int maxTextureBinding = -1;
|
||||
int maxImageBinding = -1;
|
||||
|
||||
TextureBindingInfo[] textureBindings = _channel.TextureManager.RentComputeTextureBindings(info.Textures.Count);
|
||||
|
||||
for (int index = 0; index < info.Textures.Count; index++)
|
||||
|
@ -202,6 +205,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
|
|||
descriptor.CbufSlot,
|
||||
descriptor.HandleIndex,
|
||||
descriptor.Flags);
|
||||
|
||||
if (descriptor.Binding > maxTextureBinding)
|
||||
{
|
||||
maxTextureBinding = descriptor.Binding;
|
||||
}
|
||||
}
|
||||
|
||||
TextureBindingInfo[] imageBindings = _channel.TextureManager.RentComputeImageBindings(info.Images.Count);
|
||||
|
@ -220,9 +228,18 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
|
|||
descriptor.CbufSlot,
|
||||
descriptor.HandleIndex,
|
||||
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();
|
||||
|
||||
_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 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
|
||||
// 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);
|
||||
}
|
||||
|
||||
_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)
|
||||
|
@ -581,7 +585,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
|
||||
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)
|
||||
|
@ -602,6 +606,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
}
|
||||
|
||||
_context.Renderer.Pipeline.ClearRenderTargetDepthStencil(
|
||||
layer,
|
||||
depthValue,
|
||||
clearDepth,
|
||||
stencilValue,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
|
@ -15,11 +16,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
/// </summary>
|
||||
class StateUpdater
|
||||
{
|
||||
public const int ShaderStateIndex = 0;
|
||||
public const int RasterizerStateIndex = 1;
|
||||
public const int ScissorStateIndex = 2;
|
||||
public const int VertexBufferStateIndex = 3;
|
||||
public const int PrimitiveRestartStateIndex = 4;
|
||||
public const int ShaderStateIndex = 16;
|
||||
public const int RasterizerStateIndex = 15;
|
||||
public const int ScissorStateIndex = 18;
|
||||
public const int VertexBufferStateIndex = 0;
|
||||
public const int PrimitiveRestartStateIndex = 12;
|
||||
|
||||
private readonly GpuContext _context;
|
||||
private readonly GpuChannel _channel;
|
||||
|
@ -31,6 +32,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
private readonly ShaderProgramInfo[] _currentProgramInfo;
|
||||
private ShaderSpecializationState _shaderSpecState;
|
||||
|
||||
private ProgramPipelineState _pipeline;
|
||||
|
||||
private bool _vtgWritesRtLayer;
|
||||
private byte _vsClipDistancesWritten;
|
||||
|
||||
|
@ -54,7 +57,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
_drawState = drawState;
|
||||
_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
|
||||
// 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"
|
||||
|
@ -62,53 +66,39 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
// The order of the other state updates doesn't matter.
|
||||
_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,
|
||||
nameof(ThreedClassState.VertexBufferDrawState),
|
||||
nameof(ThreedClassState.VertexBufferInstanced),
|
||||
nameof(ThreedClassState.VertexBufferState),
|
||||
nameof(ThreedClassState.VertexBufferEndAddress)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdatePrimitiveRestartState,
|
||||
nameof(ThreedClassState.PrimitiveRestartDrawArrays),
|
||||
nameof(ThreedClassState.PrimitiveRestartState)),
|
||||
new StateUpdateCallbackEntry(UpdateVertexAttribState, nameof(ThreedClassState.VertexAttribState)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateTessellationState,
|
||||
nameof(ThreedClassState.TessOuterLevel),
|
||||
nameof(ThreedClassState.TessInnerLevel),
|
||||
nameof(ThreedClassState.PatchVertices)),
|
||||
new StateUpdateCallbackEntry(UpdateBlendState,
|
||||
nameof(ThreedClassState.BlendIndependent),
|
||||
nameof(ThreedClassState.BlendConstant),
|
||||
nameof(ThreedClassState.BlendStateCommon),
|
||||
nameof(ThreedClassState.BlendEnableCommon),
|
||||
nameof(ThreedClassState.BlendEnable),
|
||||
nameof(ThreedClassState.BlendState)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateTfBufferState, nameof(ThreedClassState.TfBufferState)),
|
||||
new StateUpdateCallbackEntry(UpdateUserClipState, nameof(ThreedClassState.ClipDistanceEnable)),
|
||||
new StateUpdateCallbackEntry(UpdateFaceState, nameof(ThreedClassState.FaceState)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateRenderTargetState,
|
||||
nameof(ThreedClassState.RtColorState),
|
||||
nameof(ThreedClassState.RtDepthStencilState),
|
||||
nameof(ThreedClassState.RtControl),
|
||||
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(UpdateStencilTestState,
|
||||
nameof(ThreedClassState.StencilBackMasks),
|
||||
nameof(ThreedClassState.StencilTestState),
|
||||
nameof(ThreedClassState.StencilBackTestState)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateDepthTestState,
|
||||
nameof(ThreedClassState.DepthTestEnable),
|
||||
nameof(ThreedClassState.DepthWriteEnable),
|
||||
nameof(ThreedClassState.DepthTestFunc)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateTessellationState,
|
||||
nameof(ThreedClassState.TessOuterLevel),
|
||||
nameof(ThreedClassState.TessInnerLevel),
|
||||
nameof(ThreedClassState.PatchVertices)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateViewportTransform,
|
||||
nameof(ThreedClassState.DepthMode),
|
||||
nameof(ThreedClassState.ViewportTransform),
|
||||
|
@ -116,6 +106,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
nameof(ThreedClassState.YControl),
|
||||
nameof(ThreedClassState.ViewportTransformEnable)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateLogicOpState, nameof(ThreedClassState.LogicOpState)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateDepthClampState, nameof(ThreedClassState.ViewVolumeClipControl)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdatePolygonMode,
|
||||
nameof(ThreedClassState.PolygonModeFront),
|
||||
nameof(ThreedClassState.PolygonModeBack)),
|
||||
|
@ -126,21 +120,46 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
nameof(ThreedClassState.DepthBiasUnits),
|
||||
nameof(ThreedClassState.DepthBiasClamp)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateStencilTestState,
|
||||
nameof(ThreedClassState.StencilBackMasks),
|
||||
nameof(ThreedClassState.StencilTestState),
|
||||
nameof(ThreedClassState.StencilBackTestState)),
|
||||
new StateUpdateCallbackEntry(UpdatePrimitiveRestartState, nameof(ThreedClassState.PrimitiveRestartState)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateLineState,
|
||||
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,
|
||||
nameof(ThreedClassState.SamplerPoolState),
|
||||
nameof(ThreedClassState.SamplerIndex)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateTexturePoolState, nameof(ThreedClassState.TexturePoolState)),
|
||||
new StateUpdateCallbackEntry(UpdateVertexAttribState, nameof(ThreedClassState.VertexAttribState)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateLineState,
|
||||
nameof(ThreedClassState.LineWidthSmooth),
|
||||
nameof(ThreedClassState.LineSmoothEnable)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdatePointState,
|
||||
nameof(ThreedClassState.PointSize),
|
||||
|
@ -151,22 +170,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
new StateUpdateCallbackEntry(UpdateIndexBufferState,
|
||||
nameof(ThreedClassState.IndexBufferState),
|
||||
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.
|
||||
if (_shaderSpecState != null)
|
||||
{
|
||||
if (!_shaderSpecState.MatchesGraphics(_channel, GetPoolState(), GetGraphicsState()))
|
||||
if (!_shaderSpecState.MatchesGraphics(_channel, GetPoolState(), GetGraphicsState(), false))
|
||||
{
|
||||
ForceShaderUpdate();
|
||||
}
|
||||
|
@ -275,7 +278,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
{
|
||||
UpdateStorageBuffers();
|
||||
|
||||
_channel.TextureManager.CommitGraphicsBindings();
|
||||
if (!_channel.TextureManager.CommitGraphicsBindings(_shaderSpecState))
|
||||
{
|
||||
// Shader must be reloaded.
|
||||
UpdateShaderState();
|
||||
}
|
||||
|
||||
_channel.BufferManager.CommitGraphicsBindings();
|
||||
}
|
||||
|
||||
|
@ -315,6 +323,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
/// </summary>
|
||||
private void UpdateTessellationState()
|
||||
{
|
||||
_pipeline.PatchControlPoints = (uint)_state.State.PatchVertices;
|
||||
|
||||
_context.Renderer.Pipeline.SetPatchParameters(
|
||||
_state.State.PatchVertices,
|
||||
_state.State.TessOuterLevel.ToSpan(),
|
||||
|
@ -347,6 +357,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
private void UpdateRasterizerState()
|
||||
{
|
||||
bool enable = _state.State.RasterizeEnable;
|
||||
_pipeline.RasterizerDiscard = !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.
|
||||
/// </summary>
|
||||
/// <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>
|
||||
public void UpdateRenderTargetState(bool useControl, int singleUse = -1)
|
||||
public void UpdateRenderTargetState(bool useControl, bool layered = false, int singleUse = -1)
|
||||
{
|
||||
var memoryManager = _channel.MemoryManager;
|
||||
var rtControl = _state.State.RtControl;
|
||||
|
@ -399,7 +411,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
Image.Texture color = memoryManager.Physical.TextureCache.FindOrCreateTexture(
|
||||
memoryManager,
|
||||
colorState,
|
||||
_vtgWritesRtLayer,
|
||||
_vtgWritesRtLayer || layered,
|
||||
samplesInX,
|
||||
samplesInY,
|
||||
sizeHint);
|
||||
|
@ -433,6 +445,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
memoryManager,
|
||||
dsState,
|
||||
dsSize,
|
||||
_vtgWritesRtLayer || layered,
|
||||
samplesInX,
|
||||
samplesInY,
|
||||
sizeHint);
|
||||
|
@ -486,11 +499,21 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
/// </summary>
|
||||
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++)
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
@ -520,13 +543,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
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
|
||||
{
|
||||
_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>
|
||||
|
@ -536,7 +561,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
private void UpdateDepthClampState()
|
||||
{
|
||||
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>
|
||||
|
@ -555,10 +583,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
/// </summary>
|
||||
private void UpdateDepthTestState()
|
||||
{
|
||||
_context.Renderer.Pipeline.SetDepthTest(new DepthTestDescriptor(
|
||||
DepthTestDescriptor descriptor = new DepthTestDescriptor(
|
||||
_state.State.DepthTestEnable,
|
||||
_state.State.DepthWriteEnable,
|
||||
_state.State.DepthTestFunc));
|
||||
_state.State.DepthTestFunc);
|
||||
|
||||
_pipeline.DepthTest = descriptor;
|
||||
_context.Renderer.Pipeline.SetDepthTest(descriptor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -585,7 +616,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
ref var scissor = ref _state.State.ScreenScissorState;
|
||||
|
||||
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);
|
||||
continue;
|
||||
|
@ -622,7 +653,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
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 swizzleY = transform.UnpackSwizzleY();
|
||||
|
@ -642,6 +673,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
viewports[index] = new Viewport(region, swizzleX, swizzleY, swizzleZ, swizzleW, depthNear, depthFar);
|
||||
}
|
||||
|
||||
_context.Renderer.Pipeline.SetDepthMode(GetDepthMode());
|
||||
_context.Renderer.Pipeline.SetViewports(0, viewports, disableTransform);
|
||||
}
|
||||
|
||||
|
@ -650,37 +682,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
/// </summary>
|
||||
private void UpdateDepthMode()
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
_context.Renderer.Pipeline.SetDepthMode(depthMode);
|
||||
_context.Renderer.Pipeline.SetDepthMode(GetDepthMode());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -708,6 +710,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
enables |= (depthBias.LineEnable ? PolygonModeMask.Line : 0);
|
||||
enables |= (depthBias.FillEnable ? PolygonModeMask.Fill : 0);
|
||||
|
||||
_pipeline.BiasEnable = enables;
|
||||
_context.Renderer.Pipeline.SetDepthBias(enables, factor, units / 2f, clamp);
|
||||
}
|
||||
|
||||
|
@ -749,7 +752,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
backMask = test.FrontMask;
|
||||
}
|
||||
|
||||
_context.Renderer.Pipeline.SetStencilTest(new StencilTestDescriptor(
|
||||
StencilTestDescriptor descriptor = new StencilTestDescriptor(
|
||||
test.Enable,
|
||||
test.FrontFunc,
|
||||
test.FrontSFail,
|
||||
|
@ -764,7 +767,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
backDpFail,
|
||||
backFuncRef,
|
||||
backFuncMask,
|
||||
backMask));
|
||||
backMask);
|
||||
|
||||
_pipeline.StencilTest = descriptor;
|
||||
_context.Renderer.Pipeline.SetStencilTest(descriptor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -833,6 +839,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
format);
|
||||
}
|
||||
|
||||
_pipeline.SetVertexAttribs(vertexAttribs);
|
||||
_context.Renderer.Pipeline.SetVertexAttribs(vertexAttribs);
|
||||
}
|
||||
|
||||
|
@ -844,6 +851,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
float width = _state.State.LineWidthSmooth;
|
||||
bool smooth = _state.State.LineSmoothEnable;
|
||||
|
||||
_pipeline.LineWidth = width;
|
||||
_context.Renderer.Pipeline.SetLineParameters(width, smooth);
|
||||
}
|
||||
|
||||
|
@ -870,6 +878,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
PrimitiveRestartState primitiveRestart = _state.State.PrimitiveRestartState;
|
||||
bool enable = primitiveRestart.Enable && (_drawState.DrawIndexed || _state.State.PrimitiveRestartDrawArrays);
|
||||
|
||||
_pipeline.PrimitiveRestartEnable = enable;
|
||||
_context.Renderer.Pipeline.SetPrimitiveRestart(enable, primitiveRestart.Index);
|
||||
}
|
||||
|
||||
|
@ -916,6 +925,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
|
||||
if (!vertexBuffer.UnpackEnable())
|
||||
{
|
||||
_pipeline.VertexBuffers[index] = new BufferPipelineDescriptor(false, 0, 0);
|
||||
_channel.BufferManager.SetVertexBuffer(index, 0, 0, 0, 0);
|
||||
|
||||
continue;
|
||||
|
@ -933,6 +943,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
|
||||
_drawState.IsAnyVbInstanced |= divisor != 0;
|
||||
|
||||
ulong vbSize = endAddress.Pack() - address + 1;
|
||||
ulong size;
|
||||
|
||||
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.
|
||||
// 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)
|
||||
{
|
||||
|
@ -964,9 +975,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -979,6 +991,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
var yControl = _state.State.YControl;
|
||||
var face = _state.State.FaceState;
|
||||
|
||||
_pipeline.CullEnable = face.CullEnable;
|
||||
_pipeline.CullMode = face.CullFace;
|
||||
_context.Renderer.Pipeline.SetFaceCulling(face.CullEnable, face.CullFace);
|
||||
|
||||
UpdateFrontFace(yControl, face.FrontFace);
|
||||
|
@ -998,6 +1012,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
frontFace = frontFace == FrontFace.CounterClockwise ? FrontFace.Clockwise : FrontFace.CounterClockwise;
|
||||
}
|
||||
|
||||
_pipeline.FrontFace = frontFace;
|
||||
_context.Renderer.Pipeline.SetFrontFace(frontFace);
|
||||
}
|
||||
|
||||
|
@ -1023,6 +1038,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
componentMask |= (colorMask.UnpackAlpha() ? 8u : 0u);
|
||||
|
||||
componentMasks[index] = componentMask;
|
||||
_pipeline.ColorWriteMask[index] = componentMask;
|
||||
}
|
||||
|
||||
_context.Renderer.Pipeline.SetRenderTargetColorMasks(componentMasks);
|
||||
|
@ -1071,6 +1087,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
blend.AlphaDstFactor);
|
||||
}
|
||||
|
||||
_pipeline.BlendDescriptors[index] = descriptor;
|
||||
_context.Renderer.Pipeline.SetBlendState(index, descriptor);
|
||||
}
|
||||
}
|
||||
|
@ -1082,6 +1099,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
{
|
||||
LogicalOpState logicOpState = _state.State.LogicOpState;
|
||||
|
||||
_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();
|
||||
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;
|
||||
|
||||
|
@ -1148,6 +1166,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
return;
|
||||
}
|
||||
|
||||
int maxTextureBinding = -1;
|
||||
int maxImageBinding = -1;
|
||||
|
||||
Span<TextureBindingInfo> textureBindings = _channel.TextureManager.RentGraphicsTextureBindings(stage, info.Textures.Count);
|
||||
|
||||
if (info.UsesRtLayer)
|
||||
|
@ -1167,6 +1188,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
descriptor.CbufSlot,
|
||||
descriptor.HandleIndex,
|
||||
descriptor.Flags);
|
||||
|
||||
if (descriptor.Binding > maxTextureBinding)
|
||||
{
|
||||
maxTextureBinding = descriptor.Binding;
|
||||
}
|
||||
}
|
||||
|
||||
TextureBindingInfo[] imageBindings = _channel.TextureManager.RentGraphicsImageBindings(stage, info.Images.Count);
|
||||
|
@ -1185,8 +1211,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
descriptor.CbufSlot,
|
||||
descriptor.HandleIndex,
|
||||
descriptor.Flags);
|
||||
|
||||
if (descriptor.Binding > maxImageBinding)
|
||||
{
|
||||
maxImageBinding = descriptor.Binding;
|
||||
}
|
||||
}
|
||||
|
||||
_channel.TextureManager.SetGraphicsMaxBindings(maxTextureBinding, maxImageBinding);
|
||||
|
||||
_channel.BufferManager.SetGraphicsStorageBufferBindings(stage, info.SBuffers);
|
||||
_channel.BufferManager.SetGraphicsUniformBufferBindings(stage, info.CBuffers);
|
||||
}
|
||||
|
@ -1205,11 +1238,67 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
/// <returns>Current GPU channel state</returns>
|
||||
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(
|
||||
_state.State.EarlyZForce,
|
||||
_drawState.Topology,
|
||||
_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>
|
||||
|
|
|
@ -131,10 +131,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
/// Updates render targets (color and depth-stencil buffers) based on current render target state.
|
||||
/// </summary>
|
||||
/// <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>
|
||||
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>
|
||||
|
|
|
@ -311,6 +311,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
{
|
||||
return Attribute & 0x3fe00000;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the Maxwell attribute component type.
|
||||
/// </summary>
|
||||
/// <returns>Attribute component type</returns>
|
||||
public uint UnpackType()
|
||||
{
|
||||
return (Attribute >> 27) & 7;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -759,8 +768,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
public fixed uint Reserved10B0[18];
|
||||
public uint ClearFlags;
|
||||
public fixed uint Reserved10FC[25];
|
||||
public Array16<VertexAttribState> VertexAttribState;
|
||||
public fixed uint Reserved11A0[31];
|
||||
public Array32<VertexAttribState> VertexAttribState;
|
||||
public fixed uint Reserved11E0[15];
|
||||
public RtControl RtControl;
|
||||
public fixed uint Reserved1220[2];
|
||||
public Size3D RtDepthStencilSize;
|
||||
|
|
|
@ -30,8 +30,8 @@ namespace Ryujinx.Graphics.Gpu
|
|||
|
||||
/// <summary>
|
||||
/// 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,
|
||||
/// as textures will not need to be created for the copy, and the data does not need to be
|
||||
/// 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
|
||||
/// flushed from GPU.
|
||||
/// </summary>
|
||||
public static bool Fast2DCopy = true;
|
||||
|
@ -56,5 +56,15 @@ namespace Ryujinx.Graphics.Gpu
|
|||
/// Enables or disables the shader cache.
|
||||
/// </summary>
|
||||
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.Graphics.Gpu.Memory;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
|
@ -16,6 +17,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
protected GpuContext Context;
|
||||
protected PhysicalMemory PhysicalMemory;
|
||||
protected int SequenceNumber;
|
||||
protected int ModifiedSequenceNumber;
|
||||
|
||||
protected T1[] Items;
|
||||
protected T2[] DescriptorCache;
|
||||
|
@ -41,6 +43,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
private readonly CpuMultiRegionHandle _memoryTracking;
|
||||
private readonly Action<ulong, ulong> _modifiedDelegate;
|
||||
|
||||
private int _modifiedSequenceOffset;
|
||||
private bool _modified;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the GPU resource pool.
|
||||
/// </summary>
|
||||
|
@ -79,6 +84,16 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
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>
|
||||
/// Gets the GPU resource with the given ID.
|
||||
/// </summary>
|
||||
|
@ -93,7 +108,13 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// </summary>
|
||||
public void SynchronizeMemory()
|
||||
{
|
||||
_modified = false;
|
||||
_memoryTracking.QueryModified(_modifiedDelegate);
|
||||
|
||||
if (_modified)
|
||||
{
|
||||
UpdateModifiedSequence();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -103,6 +124,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// <param name="mSize">Size of the modified region</param>
|
||||
private void RegionModified(ulong mAddress, ulong mSize)
|
||||
{
|
||||
_modified = true;
|
||||
|
||||
if (mAddress < Address)
|
||||
{
|
||||
mAddress = Address;
|
||||
|
@ -118,6 +141,15 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
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>
|
||||
/// An action to be performed when a precise memory access occurs to this resource.
|
||||
/// Makes sure that the dirty flags are checked.
|
||||
|
@ -129,6 +161,16 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
{
|
||||
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--;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// </summary>
|
||||
class Sampler : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// True if the sampler is disposed, false otherwise.
|
||||
/// </summary>
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Host sampler object.
|
||||
/// </summary>
|
||||
|
@ -101,6 +106,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
IsDisposed = true;
|
||||
|
||||
_hostSampler.Dispose();
|
||||
_anisoSampler?.Dispose();
|
||||
}
|
||||
|
|
|
@ -48,6 +48,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
Items[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateModifiedSequence();
|
||||
}
|
||||
|
||||
SequenceNumber = Context.SequenceNumber;
|
||||
|
@ -71,6 +73,39 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
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>
|
||||
/// Implementation of the sampler pool range invalidation.
|
||||
/// </summary>
|
||||
|
|
|
@ -100,6 +100,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// </summary>
|
||||
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 _layers;
|
||||
public int FirstLayer { get; private set; }
|
||||
|
@ -821,20 +826,25 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
depth,
|
||||
levels,
|
||||
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}";
|
||||
|
||||
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;
|
||||
}
|
||||
else if (!_context.Capabilities.SupportsR4G4Format && Format == Format.R4G4Unorm)
|
||||
{
|
||||
data = PixelConverter.ConvertR4G4ToR4G4B4A4(data);
|
||||
}
|
||||
else if (!_context.Capabilities.Supports3DTextureCompression && Target == Target.Texture3D)
|
||||
else if (!TextureCompatibility.HostSupportsBcFormat(Format, Target, _context.Capabilities))
|
||||
{
|
||||
switch (Format)
|
||||
{
|
||||
|
@ -858,6 +868,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
case Format.Bc5Unorm:
|
||||
data = BCnDecoder.DecodeBC5(data, width, height, depth, levels, layers, Format == Format.Bc5Snorm);
|
||||
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))
|
||||
{
|
||||
FormatInfo formatInfo = TextureCompatibility.ToHostCompatibleFormat(Info, _context.Capabilities);
|
||||
|
||||
TextureCreateInfo createInfo = new TextureCreateInfo(
|
||||
Info.Width,
|
||||
Info.Height,
|
||||
target == Target.CubemapArray ? 6 : 1,
|
||||
Info.Levels,
|
||||
Info.Samples,
|
||||
Info.FormatInfo.BlockWidth,
|
||||
Info.FormatInfo.BlockHeight,
|
||||
Info.FormatInfo.BytesPerPixel,
|
||||
Info.FormatInfo.Format,
|
||||
formatInfo.BlockWidth,
|
||||
formatInfo.BlockHeight,
|
||||
formatInfo.BytesPerPixel,
|
||||
formatInfo.Format,
|
||||
Info.DepthStencilMode,
|
||||
target,
|
||||
Info.SwizzleR,
|
||||
|
@ -1407,6 +1427,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
DisposeTextures();
|
||||
|
||||
HostTexture = hostTexture;
|
||||
InvalidatedSequence++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1535,6 +1556,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
|
||||
_poolOwners.Clear();
|
||||
}
|
||||
|
||||
InvalidatedSequence++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.Gpu.Shader;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
|
@ -31,21 +35,30 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
private readonly TextureBindingInfo[][] _textureBindings;
|
||||
private readonly TextureBindingInfo[][] _imageBindings;
|
||||
|
||||
private struct TextureStatePerStage
|
||||
private struct TextureState
|
||||
{
|
||||
public ITexture Texture;
|
||||
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 readonly TextureStatePerStage[][] _imageState;
|
||||
private TextureState[] _textureState;
|
||||
private TextureState[] _imageState;
|
||||
|
||||
private int[] _textureBindingsCount;
|
||||
private int[] _imageBindingsCount;
|
||||
|
||||
private int _textureBufferIndex;
|
||||
private int _texturePoolSequence;
|
||||
private int _samplerPoolSequence;
|
||||
|
||||
private bool _rebind;
|
||||
private int _textureBufferIndex;
|
||||
|
||||
private readonly float[] _scales;
|
||||
private bool _scaleChanged;
|
||||
|
@ -72,8 +85,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
_textureBindings = new TextureBindingInfo[stages][];
|
||||
_imageBindings = new TextureBindingInfo[stages][];
|
||||
|
||||
_textureState = new TextureStatePerStage[stages][];
|
||||
_imageState = new TextureStatePerStage[stages][];
|
||||
_textureState = new TextureState[InitialTextureStateSize];
|
||||
_imageState = new TextureState[InitialImageStateSize];
|
||||
|
||||
_textureBindingsCount = new int[stages];
|
||||
_imageBindingsCount = new int[stages];
|
||||
|
@ -82,9 +95,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
{
|
||||
_textureBindings[stage] = new TextureBindingInfo[InitialTextureStateSize];
|
||||
_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)
|
||||
{
|
||||
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;
|
||||
|
@ -126,15 +127,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
if (count > _imageBindings[stage].Length)
|
||||
{
|
||||
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;
|
||||
|
@ -142,6 +134,24 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
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>
|
||||
/// Sets the textures constant buffer index.
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <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="stage">The active shader stage</param>
|
||||
/// <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;
|
||||
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;
|
||||
texture.BlacklistScale();
|
||||
|
@ -284,6 +294,35 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
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>
|
||||
/// Uploads texture and image scales to the backend when they are used.
|
||||
/// </summary>
|
||||
|
@ -295,10 +334,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
int fragmentIndex = (int)ShaderStage.Fragment - 1;
|
||||
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:
|
||||
// - Vertex stage has bindings.
|
||||
// - Vertex stage has bindings that require scale.
|
||||
// - Fragment stage binding count has been updated since last render scale update.
|
||||
|
||||
_scaleChanged = true;
|
||||
|
@ -323,7 +362,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// Ensures that the bindings are visible to the host GPU.
|
||||
/// Note: this actually performs the binding using the host graphics API.
|
||||
/// </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;
|
||||
|
||||
|
@ -331,10 +372,38 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
? _texturePoolCache.FindOrCreate(_channel, texturePoolAddress, _texturePoolMaximumId)
|
||||
: 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)
|
||||
{
|
||||
CommitTextureBindings(texturePool, ShaderStage.Compute, 0);
|
||||
CommitImageBindings (texturePool, ShaderStage.Compute, 0);
|
||||
specStateMatches &= CommitTextureBindings(texturePool, ShaderStage.Compute, 0, poolModified, specState);
|
||||
specStateMatches &= CommitImageBindings(texturePool, ShaderStage.Compute, 0, poolModified, specState);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -342,14 +411,76 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
{
|
||||
int stageIndex = (int)stage - 1;
|
||||
|
||||
CommitTextureBindings(texturePool, stage, stageIndex);
|
||||
CommitImageBindings (texturePool, stage, stageIndex);
|
||||
specStateMatches &= CommitTextureBindings(texturePool, stage, stageIndex, poolModified, specState);
|
||||
specStateMatches &= CommitImageBindings(texturePool, stage, stageIndex, poolModified, specState);
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
|
@ -358,13 +489,16 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// </summary>
|
||||
/// <param name="pool">The current texture pool</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>
|
||||
private void CommitTextureBindings(TexturePool pool, ShaderStage stage, int stageIndex)
|
||||
/// <param name="stageIndex">The stage number of the specified shader stage</param
|
||||
/// <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];
|
||||
if (textureCount == 0)
|
||||
{
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
var samplerPool = _samplerPool;
|
||||
|
@ -372,17 +506,27 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
if (pool == null)
|
||||
{
|
||||
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++)
|
||||
{
|
||||
TextureBindingInfo bindingInfo = _textureBindings[stageIndex][index];
|
||||
TextureUsageFlags usageFlags = bindingInfo.Flags;
|
||||
|
||||
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex);
|
||||
|
||||
int packedId = ReadPackedId(stageIndex, bindingInfo.Handle, textureBufferIndex, samplerBufferIndex);
|
||||
int textureId = UnpackTextureId(packedId);
|
||||
UpdateCachedBuffer(stageIndex, ref cachedTextureBufferIndex, ref cachedSamplerBufferIndex, ref cachedTextureBuffer, ref cachedSamplerBuffer, textureBufferIndex, samplerBufferIndex);
|
||||
|
||||
int packedId = TextureHandle.ReadPackedId(bindingInfo.Handle, cachedTextureBuffer, cachedSamplerBuffer);
|
||||
int textureId = TextureHandle.UnpackTextureId(packedId);
|
||||
int samplerId;
|
||||
|
||||
if (_samplerIndex == SamplerIndex.ViaHeaderIndex)
|
||||
|
@ -391,46 +535,80 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
}
|
||||
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);
|
||||
ISampler hostSampler = sampler?.GetHostSampler(texture);
|
||||
|
||||
if (hostTexture != null && texture.Target == Target.TextureBuffer)
|
||||
{
|
||||
// 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
|
||||
// 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
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
_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);
|
||||
|
||||
ISampler hostSampler = sampler?.GetHostSampler(texture);
|
||||
|
||||
if (_textureState[stageIndex][index].Sampler != hostSampler || _rebind)
|
||||
{
|
||||
_textureState[stageIndex][index].Sampler = hostSampler;
|
||||
|
||||
_context.Renderer.Pipeline.SetSampler(bindingInfo.Binding, hostSampler);
|
||||
}
|
||||
state.CachedTexture = texture;
|
||||
state.CachedSampler = sampler;
|
||||
state.InvalidatedSequence = texture?.InvalidatedSequence ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
return specStateMatches;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -440,38 +618,90 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// <param name="pool">The current texture pool</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>
|
||||
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];
|
||||
if (imageCount == 0)
|
||||
{
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pool == null)
|
||||
{
|
||||
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.
|
||||
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++)
|
||||
{
|
||||
TextureBindingInfo bindingInfo = _imageBindings[stageIndex][index];
|
||||
TextureUsageFlags usageFlags = bindingInfo.Flags;
|
||||
int scaleIndex = baseScaleIndex + index;
|
||||
|
||||
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex);
|
||||
|
||||
int packedId = ReadPackedId(stageIndex, bindingInfo.Handle, textureBufferIndex, samplerBufferIndex);
|
||||
int textureId = UnpackTextureId(packedId);
|
||||
UpdateCachedBuffer(stageIndex, ref cachedTextureBufferIndex, ref cachedSamplerBufferIndex, ref cachedTextureBuffer, ref cachedSamplerBuffer, textureBufferIndex, samplerBufferIndex);
|
||||
|
||||
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);
|
||||
|
||||
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)
|
||||
{
|
||||
// Ensure that the buffer texture is using the correct buffer as storage.
|
||||
|
@ -485,7 +715,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
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
|
||||
{
|
||||
|
@ -494,14 +724,16 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
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);
|
||||
}
|
||||
|
||||
_imageState[stageIndex][index].Texture = hostTexture;
|
||||
state.Texture = hostTexture;
|
||||
state.ScaleIndex = scaleIndex;
|
||||
state.UsageFlags = usageFlags;
|
||||
|
||||
Format format = bindingInfo.Format;
|
||||
|
||||
|
@ -512,8 +744,13 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
|
||||
_context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTexture, format);
|
||||
}
|
||||
|
||||
state.CachedTexture = texture;
|
||||
state.InvalidatedSequence = texture?.InvalidatedSequence ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
return specStateMatches;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -537,7 +774,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(cbufSlot, bufferIndex);
|
||||
|
||||
int packedId = ReadPackedId(stageIndex, handle, textureBufferIndex, samplerBufferIndex);
|
||||
int textureId = UnpackTextureId(packedId);
|
||||
int textureId = TextureHandle.UnpackTextureId(packedId);
|
||||
|
||||
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="samplerBufferIndex">Index of the constant buffer holding the sampler handles</param>
|
||||
/// <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)
|
||||
{
|
||||
(int textureWordOffset, int samplerWordOffset, TextureHandleType handleType) = TextureHandle.UnpackOffsets(wordOffset);
|
||||
|
@ -590,32 +828,13 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
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>
|
||||
/// Force all bound textures and images to be rebound the next time CommitBindings is called.
|
||||
/// </summary>
|
||||
public void Rebind()
|
||||
{
|
||||
_rebind = true;
|
||||
Array.Clear(_textureState);
|
||||
Array.Clear(_imageState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -349,6 +349,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// <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="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="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>
|
||||
|
@ -357,6 +358,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
MemoryManager memoryManager,
|
||||
RtDepthStencilState dsState,
|
||||
Size3D size,
|
||||
bool layered,
|
||||
int samplesInX,
|
||||
int samplesInY,
|
||||
Size sizeHint)
|
||||
|
@ -364,9 +366,24 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
int gobBlocksInY = dsState.MemoryLayout.UnpackGobBlocksInY();
|
||||
int gobBlocksInZ = dsState.MemoryLayout.UnpackGobBlocksInZ();
|
||||
|
||||
Target target = (samplesInX | samplesInY) != 1
|
||||
? Target.Texture2DMultisample
|
||||
: Target.Texture2D;
|
||||
Target target;
|
||||
|
||||
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();
|
||||
|
||||
|
|
|
@ -71,11 +71,15 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
{
|
||||
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())
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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,
|
||||
// and so we adjust the format here to match the decompressor output.
|
||||
switch (info.FormatInfo.Format)
|
||||
|
@ -94,10 +98,12 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
case Format.Bc1RgbaSrgb:
|
||||
case Format.Bc2Srgb:
|
||||
case Format.Bc3Srgb:
|
||||
case Format.Bc7Srgb:
|
||||
return new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4, 4);
|
||||
case Format.Bc1RgbaUnorm:
|
||||
case Format.Bc2Unorm:
|
||||
case Format.Bc3Unorm:
|
||||
case Format.Bc7Unorm:
|
||||
return new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4);
|
||||
case Format.Bc4Unorm:
|
||||
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);
|
||||
case Format.Bc5Snorm:
|
||||
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;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// Determines whether a texture can flush its data back to guest memory.
|
||||
/// </summary>
|
||||
|
@ -744,7 +788,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// <returns>True if the texture target and samples count matches, false otherwise</returns>
|
||||
public static bool TargetAndSamplesCompatible(TextureInfo lhs, TextureInfo rhs)
|
||||
{
|
||||
return lhs.Target == rhs.Target &&
|
||||
return lhs.Target == rhs.Target &&
|
||||
lhs.SamplesInX == rhs.SamplesInX &&
|
||||
lhs.SamplesInY == rhs.SamplesInY;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||
using Ryujinx.Graphics.Gpu.Shader;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
@ -10,9 +11,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
class TextureManager : IDisposable
|
||||
{
|
||||
private readonly GpuContext _context;
|
||||
private readonly GpuChannel _channel;
|
||||
|
||||
private readonly TextureBindingsManager _cpBindingsManager;
|
||||
private readonly TextureBindingsManager _gpBindingsManager;
|
||||
private readonly TexturePoolCache _texturePoolCache;
|
||||
|
||||
private readonly Texture[] _rtColors;
|
||||
private readonly ITexture[] _rtHostColors;
|
||||
|
@ -35,6 +38,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
public TextureManager(GpuContext context, GpuChannel channel)
|
||||
{
|
||||
_context = context;
|
||||
_channel = channel;
|
||||
|
||||
TexturePoolCache texturePoolCache = new TexturePoolCache(context);
|
||||
|
||||
|
@ -43,6 +47,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
|
||||
_cpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, scales, isCompute: true);
|
||||
_gpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, scales, isCompute: false);
|
||||
_texturePoolCache = texturePoolCache;
|
||||
|
||||
_rtColors = new Texture[Constants.TotalRenderTargets];
|
||||
_rtHostColors = new ITexture[Constants.TotalRenderTargets];
|
||||
|
@ -99,6 +104,16 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
_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>
|
||||
/// Sets the texture constant buffer index on the graphics pipeline.
|
||||
/// </summary>
|
||||
|
@ -108,6 +123,16 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
_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>
|
||||
/// Sets the current sampler pool on the compute pipeline.
|
||||
/// </summary>
|
||||
|
@ -335,25 +360,48 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// <summary>
|
||||
/// Commits bindings on the compute pipeline.
|
||||
/// </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,
|
||||
// we must rebind everything.
|
||||
// Since compute work happens less often, we always do that
|
||||
// before and after the compute dispatch.
|
||||
_cpBindingsManager.Rebind();
|
||||
_cpBindingsManager.CommitBindings();
|
||||
bool result = _cpBindingsManager.CommitBindings(specState);
|
||||
_gpBindingsManager.Rebind();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Commits bindings on the graphics pipeline.
|
||||
/// </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();
|
||||
|
||||
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>
|
||||
|
|
|
@ -14,6 +14,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
{
|
||||
private readonly GpuChannel _channel;
|
||||
private readonly ConcurrentQueue<Texture> _dereferenceQueue = new ConcurrentQueue<Texture>();
|
||||
private TextureDescriptor _defaultDescriptor;
|
||||
|
||||
/// <summary>
|
||||
/// Intrusive linked list node used on the texture pool cache.
|
||||
|
@ -32,6 +33,62 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
_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>
|
||||
/// Gets the texture with the given ID.
|
||||
/// </summary>
|
||||
|
@ -51,56 +108,49 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
SynchronizeMemory();
|
||||
}
|
||||
|
||||
Texture texture = Items[id];
|
||||
|
||||
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();
|
||||
}
|
||||
GetInternal(id, out Texture 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>
|
||||
/// Forcibly remove a texture from this pool's items.
|
||||
/// 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="layerSize">Layer size for textures using a sub-range of mipmap levels, otherwise 0</param>
|
||||
/// <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 levels = descriptor.UnpackLevels();
|
||||
|
|
|
@ -378,6 +378,25 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
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>
|
||||
/// Ensures that the compute engine bindings are visible to the host GPU.
|
||||
/// Note: this actually performs the binding using the host graphics API.
|
||||
|
@ -416,7 +435,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
}
|
||||
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>
|
||||
/// Sets the buffer storage of a buffer texture. This will be bound when the buffer manager commits bindings.
|
||||
/// </summary>
|
||||
/// <param name="stage">Shader stage accessing the texture</param>
|
||||
/// <param name="texture">Buffer texture</param>
|
||||
/// <param name="address">Address of the buffer in memory</param>
|
||||
/// <param name="size">Size of the buffer in bytes</param>
|
||||
/// <param name="bindingInfo">Binding info for 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>
|
||||
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);
|
||||
|
||||
_bufferTextures.Add(new BufferTextureBinding(texture, address, size, bindingInfo, format, isImage));
|
||||
_bufferTextures.Add(new BufferTextureBinding(stage, texture, address, size, bindingInfo, format, isImage));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
|
@ -8,6 +9,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
/// </summary>
|
||||
struct BufferTextureBinding
|
||||
{
|
||||
/// <summary>
|
||||
/// Shader stage accessing the texture.
|
||||
/// </summary>
|
||||
public ShaderStage Stage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The buffer texture.
|
||||
/// </summary>
|
||||
|
@ -41,14 +47,23 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
/// <summary>
|
||||
/// Create a new buffer texture binding.
|
||||
/// </summary>
|
||||
/// <param name="stage">Shader stage accessing the texture</param>
|
||||
/// <param name="texture">Buffer texture</param>
|
||||
/// <param name="address">Base address</param>
|
||||
/// <param name="size">Size in bytes</param>
|
||||
/// <param name="bindingInfo">Binding info</param>
|
||||
/// <param name="format">Binding format</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;
|
||||
Address = address;
|
||||
Size = size;
|
||||
|
|
|
@ -252,6 +252,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
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>
|
||||
/// Writes data to GPU mapped memory.
|
||||
/// </summary>
|
||||
|
|
|
@ -105,7 +105,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
|||
entry.Header.GpuAccessorHeader.ComputeLocalMemorySize,
|
||||
entry.Header.GpuAccessorHeader.ComputeSharedMemorySize);
|
||||
|
||||
ShaderSpecializationState specState = new ShaderSpecializationState(computeState);
|
||||
ShaderSpecializationState specState = new ShaderSpecializationState(ref computeState);
|
||||
|
||||
foreach (var td in entry.TextureDescriptors)
|
||||
{
|
||||
|
@ -163,11 +163,20 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
|||
_ => PrimitiveTopology.Points
|
||||
};
|
||||
|
||||
Array32<AttributeType> attributeTypes = default;
|
||||
|
||||
GpuChannelGraphicsState graphicsState = new GpuChannelGraphicsState(
|
||||
accessorHeader.StateFlags.HasFlag(GuestGpuStateFlags.EarlyZForce),
|
||||
topology,
|
||||
tessMode,
|
||||
false);
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
1f,
|
||||
false,
|
||||
CompareOp.Always,
|
||||
0f,
|
||||
ref attributeTypes);
|
||||
|
||||
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++)
|
||||
{
|
||||
|
|
|
@ -35,6 +35,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
HostProgram = hostProgram;
|
||||
SpecializationState = specializationState;
|
||||
Shaders = shaders;
|
||||
|
||||
SpecializationState.Prepare(shaders);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -83,7 +83,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
{
|
||||
_context = context;
|
||||
_hostStorage = hostStorage;
|
||||
_fileWriterWorkerQueue = new AsyncWorkQueue<CacheFileOperationTask>(ProcessTask, "Gpu.BackgroundDiskCacheWriter");
|
||||
_fileWriterWorkerQueue = new AsyncWorkQueue<CacheFileOperationTask>(ProcessTask, "GPU.BackgroundDiskCacheWriter");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
|
@ -16,7 +18,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
private readonly ShaderSpecializationState _oldSpecState;
|
||||
private readonly ShaderSpecializationState _newSpecState;
|
||||
private readonly int _stageIndex;
|
||||
private ResourceCounts _resourceCounts;
|
||||
private readonly bool _isVulkan;
|
||||
private readonly ResourceCounts _resourceCounts;
|
||||
|
||||
/// <summary>
|
||||
/// 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 newSpecState,
|
||||
ResourceCounts counts,
|
||||
int stageIndex) : base(context)
|
||||
int stageIndex) : base(context, counts, stageIndex)
|
||||
{
|
||||
_data = data;
|
||||
_cb1Data = cb1Data;
|
||||
_oldSpecState = oldSpecState;
|
||||
_newSpecState = newSpecState;
|
||||
_stageIndex = stageIndex;
|
||||
_isVulkan = context.Capabilities.Api == TargetApi.Vulkan;
|
||||
_resourceCounts = counts;
|
||||
}
|
||||
|
||||
|
@ -68,27 +72,33 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
}
|
||||
|
||||
/// <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/>
|
||||
public int QueryBindingStorageBuffer(int index)
|
||||
{
|
||||
return _resourceCounts.StorageBuffersCount++;
|
||||
}
|
||||
public float QueryAlphaTestReference() => _oldSpecState.GraphicsState.AlphaTestReference;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int QueryBindingTexture(int index)
|
||||
public AttributeType QueryAttributeType(int location)
|
||||
{
|
||||
return _resourceCounts.TexturesCount++;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int QueryBindingImage(int index)
|
||||
{
|
||||
return _resourceCounts.ImagesCount++;
|
||||
return _oldSpecState.GraphicsState.AttributeTypes[location];
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
@ -120,6 +130,18 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
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/>
|
||||
public bool QueryTessCw()
|
||||
{
|
||||
|
@ -160,6 +182,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
return _oldSpecState.GetCoordNormalized(_stageIndex, handle, cbufSlot);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool QueryTransformDepthMinusOneToOne()
|
||||
{
|
||||
return _oldSpecState.GraphicsState.DepthMode;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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 ushort VersionMajor = 1;
|
||||
private const ushort VersionMinor = 0;
|
||||
private const ushort VersionMinor = 1;
|
||||
private const uint VersionPacked = ((uint)VersionMajor << 16) | VersionMinor;
|
||||
|
||||
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="dataFileStream">Guest data file stream</param>
|
||||
/// <param name="index">Guest shader index</param>
|
||||
/// <returns>Tuple with the guest code and constant buffer 1 data, respectively</returns>
|
||||
public (byte[], byte[]) LoadShader(Stream tocFileStream, Stream dataFileStream, int index)
|
||||
/// <returns>Guest code and constant buffer 1 data</returns>
|
||||
public GuestCodeAndCbData LoadShader(Stream tocFileStream, Stream dataFileStream, int index)
|
||||
{
|
||||
if (_cache == null || index >= _cache.Length)
|
||||
{
|
||||
|
@ -226,7 +226,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
_cache[index] = (guestCode, cb1Data);
|
||||
}
|
||||
|
||||
return (guestCode, cb1Data);
|
||||
return new GuestCodeAndCbData(guestCode, cb1Data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
using System;
|
||||
using System.IO;
|
||||
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 ushort FileFormatVersionMajor = 1;
|
||||
private const ushort FileFormatVersionMinor = 1;
|
||||
private const ushort FileFormatVersionMinor = 2;
|
||||
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 SharedDataFileName = "shared.data";
|
||||
|
@ -77,9 +78,14 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
public ulong Offset;
|
||||
|
||||
/// <summary>
|
||||
/// Size.
|
||||
/// Size of uncompressed data.
|
||||
/// </summary>
|
||||
public uint Size;
|
||||
public uint UncompressedSize;
|
||||
|
||||
/// <summary>
|
||||
/// Size of compressed data.
|
||||
/// </summary>
|
||||
public uint CompressedSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -196,6 +202,14 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
private static string GetHostFileName(GpuContext context)
|
||||
{
|
||||
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());
|
||||
return $"{apiName}_{vendorName}";
|
||||
}
|
||||
|
@ -324,7 +338,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
stagesBitMask = 1;
|
||||
}
|
||||
|
||||
CachedShaderStage[] shaders = new CachedShaderStage[isCompute ? 1 : Constants.ShaderStages + 1];
|
||||
GuestCodeAndCbData?[] guestShaders = new GuestCodeAndCbData?[isCompute ? 1 : Constants.ShaderStages + 1];
|
||||
|
||||
DataEntryPerStage stageEntry = new DataEntryPerStage();
|
||||
|
||||
|
@ -334,15 +348,11 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
|
||||
dataReader.Read(ref stageEntry);
|
||||
|
||||
ShaderProgramInfo info = stageIndex != 0 || isCompute ? ReadShaderProgramInfo(ref dataReader) : null;
|
||||
|
||||
(byte[] guestCode, byte[] cb1Data) = _guestStorage.LoadShader(
|
||||
guestShaders[stageIndex] = _guestStorage.LoadShader(
|
||||
guestTocFileStream,
|
||||
guestDataFileStream,
|
||||
stageEntry.GuestCodeIndex);
|
||||
|
||||
shaders[stageIndex] = new CachedShaderStage(info, guestCode, cb1Data);
|
||||
|
||||
stagesBitMask &= ~(1u << stageIndex);
|
||||
}
|
||||
|
||||
|
@ -351,17 +361,38 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
|
||||
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)
|
||||
{
|
||||
bool hasFragmentShader = shaders.Length > 5 && shaders[5] != null;
|
||||
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);
|
||||
|
||||
loader.QueueHostProgram(program, hostProgram, programIndex, isCompute);
|
||||
loader.QueueHostProgram(program, hostCode, programIndex, isCompute);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -371,7 +402,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
|
||||
if (!loadHostCache)
|
||||
{
|
||||
loader.QueueGuestProgram(shaders, specState, programIndex, isCompute);
|
||||
loader.QueueGuestProgram(guestShaders, specState, programIndex, isCompute);
|
||||
}
|
||||
|
||||
loader.CheckCompilation();
|
||||
|
@ -393,9 +424,15 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
/// <param name="context">GPU context</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="guestShaders">Guest shader code for each active stage</param>
|
||||
/// <param name="programIndex">Index of the program on the cache</param>
|
||||
/// <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)
|
||||
{
|
||||
|
@ -404,7 +441,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
|
||||
if (!File.Exists(tocFilePath) || !File.Exists(dataFilePath))
|
||||
{
|
||||
return null;
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
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>();
|
||||
if (offset + Unsafe.SizeOf<OffsetAndSize>() > tocFileStream.Length)
|
||||
{
|
||||
return null;
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
if ((ulong)offset >= (ulong)dataFileStream.Length)
|
||||
|
@ -436,11 +473,33 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
|
||||
dataFileStream.Seek((long)offsetAndSize.Offset, SeekOrigin.Begin);
|
||||
|
||||
byte[] hostCode = new byte[offsetAndSize.Size];
|
||||
byte[] hostCode = new byte[offsetAndSize.UncompressedSize];
|
||||
|
||||
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>
|
||||
|
@ -519,8 +578,6 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
stageEntry.GuestCodeIndex = _guestStorage.AddShader(shader.Code, shader.Cb1Data);
|
||||
|
||||
dataWriter.Write(ref stageEntry);
|
||||
|
||||
WriteShaderProgramInfo(ref dataWriter, shader.Info);
|
||||
}
|
||||
|
||||
program.SpecializationState.Write(ref dataWriter);
|
||||
|
@ -537,7 +594,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
return;
|
||||
}
|
||||
|
||||
WriteHostCode(context, hostCode, -1, streams);
|
||||
WriteHostCode(context, hostCode, program.Shaders, streams);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -574,29 +631,14 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
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>
|
||||
/// Writes the host binary code on the host cache.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context</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>
|
||||
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 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);
|
||||
}
|
||||
|
||||
if (programIndex == -1)
|
||||
{
|
||||
tocFileStream.Seek(0, SeekOrigin.End);
|
||||
}
|
||||
else
|
||||
{
|
||||
tocFileStream.Seek(Unsafe.SizeOf<TocHeader>() + (programIndex * Unsafe.SizeOf<OffsetAndSize>()), SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
tocFileStream.Seek(0, SeekOrigin.End);
|
||||
dataFileStream.Seek(0, SeekOrigin.End);
|
||||
|
||||
BinarySerializer tocWriter = new BinarySerializer(tocFileStream);
|
||||
BinarySerializer dataWriter = new BinarySerializer(dataFileStream);
|
||||
|
||||
OffsetAndSize offsetAndSize = new OffsetAndSize();
|
||||
offsetAndSize.Offset = (ulong)dataFileStream.Position;
|
||||
offsetAndSize.Size = (uint)hostCode.Length;
|
||||
tocWriter.Write(ref offsetAndSize);
|
||||
offsetAndSize.UncompressedSize = (uint)hostCode.Length;
|
||||
|
||||
long dataStartPosition = dataFileStream.Position;
|
||||
|
||||
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)
|
||||
{
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Host program.
|
||||
/// Optional binary code. If not null, it is used instead of the backend host binary.
|
||||
/// </summary>
|
||||
public readonly IProgram HostProgram;
|
||||
public readonly byte[] BinaryCode;
|
||||
|
||||
/// <summary>
|
||||
/// Program index.
|
||||
|
@ -68,19 +68,18 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
/// Creates a new program validation entry.
|
||||
/// </summary>
|
||||
/// <param name="cachedProgram">Cached shader program</param>
|
||||
/// <param name="hostProgram">Host program</param>
|
||||
/// <param name="programIndex">Program index</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>
|
||||
public ProgramEntry(
|
||||
CachedShaderProgram cachedProgram,
|
||||
IProgram hostProgram,
|
||||
byte[] binaryCode,
|
||||
int programIndex,
|
||||
bool isCompute,
|
||||
bool isBinary)
|
||||
{
|
||||
CachedProgram = cachedProgram;
|
||||
HostProgram = hostProgram;
|
||||
BinaryCode = binaryCode;
|
||||
ProgramIndex = programIndex;
|
||||
IsCompute = isCompute;
|
||||
IsBinary = isBinary;
|
||||
|
@ -146,9 +145,9 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
private struct AsyncProgramTranslation
|
||||
{
|
||||
/// <summary>
|
||||
/// Cached shader stages.
|
||||
/// Guest code for each active stage.
|
||||
/// </summary>
|
||||
public readonly CachedShaderStage[] Shaders;
|
||||
public readonly GuestCodeAndCbData?[] GuestShaders;
|
||||
|
||||
/// <summary>
|
||||
/// Specialization state.
|
||||
|
@ -168,17 +167,17 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
/// <summary>
|
||||
/// Creates a new program translation entry.
|
||||
/// </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="programIndex">Program index</param>
|
||||
/// <param name="isCompute">Indicates if the program is a compute shader</param>
|
||||
public AsyncProgramTranslation(
|
||||
CachedShaderStage[] shaders,
|
||||
GuestCodeAndCbData?[] guestShaders,
|
||||
ShaderSpecializationState specState,
|
||||
int programIndex,
|
||||
bool isCompute)
|
||||
{
|
||||
Shaders = shaders;
|
||||
GuestShaders = guestShaders;
|
||||
SpecializationState = specState;
|
||||
ProgramIndex = programIndex;
|
||||
IsCompute = isCompute;
|
||||
|
@ -188,7 +187,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
private readonly Queue<ProgramEntry> _validationQueue;
|
||||
private readonly ConcurrentQueue<ProgramCompilation> _compilationQueue;
|
||||
private readonly BlockingCollection<AsyncProgramTranslation> _asyncTranslationQueue;
|
||||
private readonly SortedList<int, CachedShaderProgram> _programList;
|
||||
private readonly SortedList<int, (CachedShaderProgram, byte[])> _programList;
|
||||
|
||||
private int _backendParallelCompileThreads;
|
||||
private int _compiledCount;
|
||||
|
@ -220,7 +219,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
_validationQueue = new Queue<ProgramEntry>();
|
||||
_compilationQueue = new ConcurrentQueue<ProgramCompilation>();
|
||||
_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.
|
||||
}
|
||||
|
||||
|
@ -235,7 +234,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
{
|
||||
workThreads[index] = new Thread(ProcessAsyncQueue)
|
||||
{
|
||||
Name = $"Gpu.AsyncTranslationThread.{index}"
|
||||
Name = $"GPU.AsyncTranslationThread.{index}"
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -287,7 +286,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
|
||||
CheckCompilationBlocking();
|
||||
|
||||
if (_needsHostRegen)
|
||||
if (_needsHostRegen && Active)
|
||||
{
|
||||
// Rebuild both shared and host cache files.
|
||||
// Rebuilding shared is required because the shader information returned by the translator
|
||||
|
@ -310,8 +309,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
break;
|
||||
}
|
||||
|
||||
CachedShaderProgram program = kv.Value;
|
||||
_hostStorage.AddShader(_context, program, program.HostProgram.GetBinary(), streams);
|
||||
(CachedShaderProgram program, byte[] binaryCode) = kv.Value;
|
||||
_hostStorage.AddShader(_context, program, binaryCode, streams);
|
||||
}
|
||||
|
||||
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.
|
||||
/// </summary>
|
||||
/// <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="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>
|
||||
/// Enqueues a guest program for compilation.
|
||||
/// </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="programIndex">Program index</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>
|
||||
|
@ -374,7 +380,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
// If not yet compiled, do nothing. This avoids blocking to wait for shader compilation.
|
||||
while (_validationQueue.TryPeek(out ProgramEntry entry))
|
||||
{
|
||||
ProgramLinkStatus result = entry.HostProgram.CheckProgramLink(false);
|
||||
ProgramLinkStatus result = entry.CachedProgram.HostProgram.CheckProgramLink(false);
|
||||
|
||||
if (result != ProgramLinkStatus.Incomplete)
|
||||
{
|
||||
|
@ -398,7 +404,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
_programList.Add(entry.ProgramIndex, entry.CachedProgram);
|
||||
_programList.Add(entry.ProgramIndex, (entry.CachedProgram, entry.BinaryCode));
|
||||
SignalCompiled();
|
||||
}
|
||||
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.
|
||||
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)
|
||||
{
|
||||
QueueGuestProgram(program.Shaders, program.SpecializationState, entry.ProgramIndex, entry.IsCompute);
|
||||
QueueGuestProgram(guestShaders, program.SpecializationState, entry.ProgramIndex, entry.IsCompute);
|
||||
}
|
||||
else
|
||||
{
|
||||
RecompileFromGuestCode(program.Shaders, program.SpecializationState, entry.ProgramIndex, entry.IsCompute);
|
||||
RecompileFromGuestCode(guestShaders, program.SpecializationState, entry.ProgramIndex, entry.IsCompute);
|
||||
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);
|
||||
|
||||
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.
|
||||
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))
|
||||
{
|
||||
RecompileFromGuestCode(
|
||||
asyncCompilation.Shaders,
|
||||
asyncCompilation.GuestShaders,
|
||||
asyncCompilation.SpecializationState,
|
||||
asyncCompilation.ProgramIndex,
|
||||
asyncCompilation.IsCompute);
|
||||
|
@ -527,21 +551,21 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
/// <summary>
|
||||
/// Recompiles a program from guest code.
|
||||
/// </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="programIndex">Program index</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
|
||||
{
|
||||
if (isCompute)
|
||||
{
|
||||
RecompileComputeFromGuestCode(shaders, specState, programIndex);
|
||||
RecompileComputeFromGuestCode(guestShaders, specState, programIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
RecompileGraphicsFromGuestCode(shaders, specState, programIndex);
|
||||
RecompileGraphicsFromGuestCode(guestShaders, specState, programIndex);
|
||||
}
|
||||
}
|
||||
catch (DiskCacheLoadException diskCacheLoadException)
|
||||
|
@ -556,41 +580,47 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
/// <summary>
|
||||
/// Recompiles a graphics program from guest code.
|
||||
/// </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="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();
|
||||
|
||||
TranslatorContext[] translatorContexts = new TranslatorContext[Constants.ShaderStages + 1];
|
||||
TranslatorContext nextStage = null;
|
||||
|
||||
TargetApi api = _context.Capabilities.Api;
|
||||
|
||||
for (int stageIndex = Constants.ShaderStages - 1; stageIndex >= 0; stageIndex--)
|
||||
{
|
||||
CachedShaderStage shader = shaders[stageIndex + 1];
|
||||
|
||||
if (shader != null)
|
||||
if (guestShaders[stageIndex + 1].HasValue)
|
||||
{
|
||||
GuestCodeAndCbData shader = guestShaders[stageIndex + 1].Value;
|
||||
|
||||
byte[] guestCode = shader.Code;
|
||||
byte[] cb1Data = shader.Cb1Data;
|
||||
|
||||
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)
|
||||
{
|
||||
currentStage.SetNextStage(nextStage);
|
||||
}
|
||||
|
||||
if (stageIndex == 0 && shaders[0] != null)
|
||||
if (stageIndex == 0 && guestShaders[0].HasValue)
|
||||
{
|
||||
byte[] guestCodeA = shaders[0].Code;
|
||||
byte[] cb1DataA = shaders[0].Cb1Data;
|
||||
byte[] guestCodeA = guestShaders[0].Value.Code;
|
||||
byte[] cb1DataA = guestShaders[0].Value.Cb1Data;
|
||||
|
||||
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;
|
||||
|
@ -598,6 +628,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
}
|
||||
}
|
||||
|
||||
CachedShaderStage[] shaders = new CachedShaderStage[guestShaders.Length];
|
||||
List<ShaderProgram> translatedStages = new List<ShaderProgram>();
|
||||
|
||||
for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++)
|
||||
|
@ -608,15 +639,15 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
{
|
||||
ShaderProgram program;
|
||||
|
||||
byte[] guestCode = shaders[stageIndex + 1].Code;
|
||||
byte[] cb1Data = shaders[stageIndex + 1].Cb1Data;
|
||||
byte[] guestCode = guestShaders[stageIndex + 1].Value.Code;
|
||||
byte[] cb1Data = guestShaders[stageIndex + 1].Value.Cb1Data;
|
||||
|
||||
if (stageIndex == 0 && shaders[0] != null)
|
||||
if (stageIndex == 0 && guestShaders[0].HasValue)
|
||||
{
|
||||
program = currentStage.Translate(translatorContexts[0]);
|
||||
|
||||
byte[] guestCodeA = shaders[0].Code;
|
||||
byte[] cb1DataA = shaders[0].Cb1Data;
|
||||
byte[] guestCodeA = guestShaders[0].Value.Code;
|
||||
byte[] cb1DataA = guestShaders[0].Value.Cb1Data;
|
||||
|
||||
shaders[0] = new CachedShaderStage(null, guestCodeA, cb1DataA);
|
||||
shaders[1] = new CachedShaderStage(program.Info, guestCode, cb1Data);
|
||||
|
@ -641,21 +672,21 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
/// <summary>
|
||||
/// Recompiles a compute program from guest code.
|
||||
/// </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="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();
|
||||
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);
|
||||
|
||||
TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, 0);
|
||||
TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, _context.Capabilities.Api, 0);
|
||||
|
||||
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));
|
||||
}
|
||||
|
|
|
@ -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.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
|
@ -15,6 +17,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
private readonly GpuAccessorState _state;
|
||||
private readonly int _stageIndex;
|
||||
private readonly bool _compute;
|
||||
private readonly bool _isVulkan;
|
||||
|
||||
/// <summary>
|
||||
/// 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="state">Current GPU state</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;
|
||||
_state = state;
|
||||
_stageIndex = stageIndex;
|
||||
|
@ -36,7 +44,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
/// <param name="context">GPU context</param>
|
||||
/// <param name="channel">GPU channel</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;
|
||||
_state = state;
|
||||
|
@ -67,27 +75,36 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
}
|
||||
|
||||
/// <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/>
|
||||
public int QueryBindingStorageBuffer(int index)
|
||||
public float QueryAlphaTestReference()
|
||||
{
|
||||
return _state.ResourceCounts.StorageBuffersCount++;
|
||||
return _state.GraphicsState.AlphaTestReference;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int QueryBindingTexture(int index)
|
||||
public AttributeType QueryAttributeType(int location)
|
||||
{
|
||||
return _state.ResourceCounts.TexturesCount++;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int QueryBindingImage(int index)
|
||||
{
|
||||
return _state.ResourceCounts.ImagesCount++;
|
||||
return _state.GraphicsState.AttributeTypes[location];
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
@ -123,6 +140,18 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
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/>
|
||||
public bool QueryTessCw()
|
||||
{
|
||||
|
@ -192,6 +221,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool QueryTransformDepthMinusOneToOne()
|
||||
{
|
||||
return _state.GraphicsState.DepthMode;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool QueryTransformFeedbackEnabled()
|
||||
{
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Threed;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
|
@ -11,74 +13,139 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
class GpuAccessorBase
|
||||
{
|
||||
private readonly GpuContext _context;
|
||||
private readonly ResourceCounts _resourceCounts;
|
||||
private readonly int _stageIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new GPU accessor.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context</param>
|
||||
public GpuAccessorBase(GpuContext context)
|
||||
public GpuAccessorBase(GpuContext context, ResourceCounts resourceCounts, int stageIndex)
|
||||
{
|
||||
_context = context;
|
||||
_resourceCounts = resourceCounts;
|
||||
_stageIndex = stageIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries host about the presence of the FrontFacing built-in variable bug.
|
||||
/// </summary>
|
||||
/// <returns>True if the bug is present on the host device used, false otherwise</returns>
|
||||
/// <inheritdoc/>
|
||||
public int QueryBindingConstantBuffer(int index)
|
||||
{
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
/// <inheritdoc/>
|
||||
public bool QueryHostHasVectorIndexingBug() => _context.Capabilities.HasVectorIndexingBug;
|
||||
|
||||
/// <summary>
|
||||
/// Queries host storage buffer alignment required.
|
||||
/// </summary>
|
||||
/// <returns>Host storage buffer alignment in bytes</returns>
|
||||
/// <inheritdoc/>
|
||||
public int QueryHostStorageBufferOffsetAlignment() => _context.Capabilities.StorageBufferOffsetAlignment;
|
||||
|
||||
/// <summary>
|
||||
/// Queries host support for texture formats with BGRA component order (such as BGRA8).
|
||||
/// </summary>
|
||||
/// <returns>True if BGRA formats are supported, false otherwise</returns>
|
||||
/// <inheritdoc/>
|
||||
public bool QueryHostSupportsBgraFormat() => _context.Capabilities.SupportsBgraFormat;
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
/// <inheritdoc/>
|
||||
public bool QueryHostSupportsFragmentShaderInterlock() => _context.Capabilities.SupportsFragmentShaderInterlock;
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
/// <inheritdoc/>
|
||||
public bool QueryHostSupportsFragmentShaderOrderingIntel() => _context.Capabilities.SupportsFragmentShaderOrderingIntel;
|
||||
|
||||
/// <summary>
|
||||
/// Queries host support for readable images without a explicit format declaration on the shader.
|
||||
/// </summary>
|
||||
/// <returns>True if formatted image load is supported, false otherwise</returns>
|
||||
/// <inheritdoc/>
|
||||
public bool QueryHostSupportsGeometryShaderPassthrough() => _context.Capabilities.SupportsGeometryShaderPassthrough;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool QueryHostSupportsImageLoadFormatted() => _context.Capabilities.SupportsImageLoadFormatted;
|
||||
|
||||
/// <summary>
|
||||
/// Queries host GPU non-constant texture offset support.
|
||||
/// </summary>
|
||||
/// <returns>True if the GPU and driver supports non-constant texture offsets, false otherwise</returns>
|
||||
/// <inheritdoc/>
|
||||
public bool QueryHostSupportsNonConstantTextureOffset() => _context.Capabilities.SupportsNonConstantTextureOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Queries host GPU shader ballot support.
|
||||
/// </summary>
|
||||
/// <returns>True if the GPU and driver supports shader ballot, false otherwise</returns>
|
||||
/// <inheritdoc/>
|
||||
public bool QueryHostSupportsShaderBallot() => _context.Capabilities.SupportsShaderBallot;
|
||||
|
||||
/// <summary>
|
||||
/// Queries host GPU texture shadow LOD support.
|
||||
/// </summary>
|
||||
/// <returns>True if the GPU and driver supports texture shadow LOD, false otherwise</returns>
|
||||
/// <inheritdoc/>
|
||||
public bool QueryHostSupportsTextureShadowLod() => _context.Capabilities.SupportsTextureShadowLod;
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Threed;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
|
@ -30,6 +32,41 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
/// </summary>
|
||||
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>
|
||||
/// Creates a new GPU graphics state.
|
||||
/// </summary>
|
||||
|
@ -37,12 +74,37 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
/// <param name="topology">Primitive topology</param>
|
||||
/// <param name="tessellationMode">Tessellation mode</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;
|
||||
Topology = topology;
|
||||
TessellationMode = tessellationMode;
|
||||
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.Graphics.GAL;
|
||||
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.Shader.Cache;
|
||||
using Ryujinx.Graphics.Gpu.Shader.DiskCache;
|
||||
|
@ -8,6 +10,7 @@ using Ryujinx.Graphics.Shader;
|
|||
using Ryujinx.Graphics.Shader.Translation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader
|
||||
|
@ -59,11 +62,13 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
{
|
||||
public readonly CachedShaderProgram CachedProgram;
|
||||
public readonly IProgram HostProgram;
|
||||
public readonly byte[] BinaryCode;
|
||||
|
||||
public ProgramToSave(CachedShaderProgram cachedProgram, IProgram hostProgram)
|
||||
public ProgramToSave(CachedShaderProgram cachedProgram, IProgram hostProgram, byte[] binaryCode)
|
||||
{
|
||||
CachedProgram = cachedProgram;
|
||||
HostProgram = hostProgram;
|
||||
BinaryCode = binaryCode;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -123,7 +128,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
{
|
||||
if (result == ProgramLinkStatus.Success)
|
||||
{
|
||||
_cacheWriter.AddShader(programToSave.CachedProgram, programToSave.HostProgram.GetBinary());
|
||||
_cacheWriter.AddShader(programToSave.CachedProgram, programToSave.BinaryCode ?? programToSave.HostProgram.GetBinary());
|
||||
}
|
||||
|
||||
_programsToSaveQueue.Dequeue();
|
||||
|
@ -143,7 +148,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
{
|
||||
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.
|
||||
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);
|
||||
|
||||
Logger.Info?.Print(LogClass.Gpu, $"Migrated {migrationCount} shaders.");
|
||||
}
|
||||
} */
|
||||
|
||||
ParallelDiskCacheLoader loader = new ParallelDiskCacheLoader(
|
||||
_context,
|
||||
|
@ -210,25 +217,67 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
return cpShader;
|
||||
}
|
||||
|
||||
ShaderSpecializationState specState = new ShaderSpecializationState(computeState);
|
||||
ShaderSpecializationState specState = new ShaderSpecializationState(ref computeState);
|
||||
GpuAccessorState gpuAccessorState = new GpuAccessorState(poolState, computeState, default, specState);
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
_computeShaderCache.Add(cpShader);
|
||||
EnqueueProgramToSave(new ProgramToSave(cpShader, hostProgram));
|
||||
EnqueueProgramToSave(cpShader, hostProgram, shaderSourcesArray);
|
||||
_cpPrograms[gpuVa] = 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>
|
||||
/// Gets a graphics shader program from the shader cache.
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
/// <param name="state">GPU state</param>
|
||||
/// <param name="pipeline">Pipeline state</param>
|
||||
/// <param name="channel">GPU channel</param>
|
||||
/// <param name="poolState">Texture pool state</param>
|
||||
/// <param name="graphicsState">3D engine state</param>
|
||||
|
@ -244,6 +294,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
/// <returns>Compiled graphics shader code</returns>
|
||||
public CachedShaderProgram GetGraphicsShader(
|
||||
ref ThreedClassState state,
|
||||
ref ProgramPipelineState pipeline,
|
||||
GpuChannel channel,
|
||||
GpuChannelPoolState poolState,
|
||||
GpuChannelGraphicsState graphicsState,
|
||||
|
@ -262,7 +313,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
|
||||
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);
|
||||
|
||||
ReadOnlySpan<ulong> addressesSpan = addresses.AsSpan();
|
||||
|
@ -270,6 +323,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
TranslatorContext[] translatorContexts = new TranslatorContext[Constants.ShaderStages + 1];
|
||||
TranslatorContext nextStage = null;
|
||||
|
||||
TargetApi api = _context.Capabilities.Api;
|
||||
|
||||
for (int stageIndex = Constants.ShaderStages - 1; stageIndex >= 0; stageIndex--)
|
||||
{
|
||||
ulong gpuVa = addressesSpan[stageIndex + 1];
|
||||
|
@ -277,7 +332,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
if (gpuVa != 0)
|
||||
{
|
||||
GpuAccessor gpuAccessor = new GpuAccessor(_context, channel, gpuAccessorState, stageIndex);
|
||||
TranslatorContext currentStage = DecodeGraphicsShader(gpuAccessor, DefaultFlags, gpuVa);
|
||||
TranslatorContext currentStage = DecodeGraphicsShader(gpuAccessor, api, DefaultFlags, gpuVa);
|
||||
|
||||
if (nextStage != null)
|
||||
{
|
||||
|
@ -286,7 +341,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
|
||||
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;
|
||||
|
@ -336,13 +391,15 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
}
|
||||
}
|
||||
|
||||
ShaderSource[] shaderSourcesArray = shaderSources.ToArray();
|
||||
|
||||
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);
|
||||
|
||||
_graphicsShaderCache.Add(gpShaders);
|
||||
EnqueueProgramToSave(new ProgramToSave(gpShaders, hostProgram));
|
||||
EnqueueProgramToSave(gpShaders, hostProgram, shaderSourcesArray);
|
||||
_gpPrograms[addresses] = gpShaders;
|
||||
|
||||
return gpShaders;
|
||||
|
@ -355,7 +412,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
/// <returns>Shader source</returns>
|
||||
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>
|
||||
|
@ -364,11 +421,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
/// <remarks>
|
||||
/// This will not do anything if disk shader cache is disabled.
|
||||
/// </remarks>
|
||||
/// <param name="programToSave">Program to be saved on disk</param>
|
||||
private void EnqueueProgramToSave(ProgramToSave programToSave)
|
||||
/// <param name="program">Cached shader program</param>
|
||||
/// <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)
|
||||
{
|
||||
byte[] binaryCode = _context.Capabilities.Api == TargetApi.Vulkan ? ShaderBinarySerializer.Pack(sources) : null;
|
||||
ProgramToSave programToSave = new ProgramToSave(program, hostProgram, binaryCode);
|
||||
|
||||
_programsToSaveQueue.Enqueue(programToSave);
|
||||
}
|
||||
}
|
||||
|
@ -418,7 +480,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
{
|
||||
if (IsShaderEqual(channel.MemoryManager, cpShader.Shaders[0], gpuVa))
|
||||
{
|
||||
return cpShader.SpecializationState.MatchesCompute(channel, poolState);
|
||||
return cpShader.SpecializationState.MatchesCompute(channel, poolState, true);
|
||||
}
|
||||
|
||||
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>
|
||||
|
@ -480,11 +542,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
/// Decode the binary Maxwell shader code to a translator context.
|
||||
/// </summary>
|
||||
/// <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>
|
||||
/// <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);
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
/// </remarks>
|
||||
/// <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="gpuVa">GPU virtual address of the shader code</param>
|
||||
/// <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);
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
/// Disposes the shader cache, deleting all the cached shaders.
|
||||
/// 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)
|
||||
{
|
||||
if (entry.SpecializationState.MatchesGraphics(channel, poolState, graphicsState))
|
||||
if (entry.SpecializationState.MatchesGraphics(channel, poolState, graphicsState, true))
|
||||
{
|
||||
program = entry;
|
||||
return true;
|
||||
|
@ -57,7 +57,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
{
|
||||
foreach (var entry in _entries)
|
||||
{
|
||||
if (entry.SpecializationState.MatchesCompute(channel, poolState))
|
||||
if (entry.SpecializationState.MatchesCompute(channel, poolState, true))
|
||||
{
|
||||
program = entry;
|
||||
return true;
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
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.Shader;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
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 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 PgpsMagic = (byte)'P' | ((byte)'G' << 8) | ((byte)'P' << 16) | ((byte)'S' << 24);
|
||||
|
||||
/// <summary>
|
||||
/// Flags indicating GPU state that is used by the shader.
|
||||
|
@ -46,6 +53,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
/// </summary>
|
||||
public Array5<uint> ConstantBufferUse;
|
||||
|
||||
/// <summary>
|
||||
/// Optional pipeline state captured at the time of the shader use.
|
||||
/// </summary>
|
||||
public ProgramPipelineState? PipelineState;
|
||||
|
||||
/// <summary>
|
||||
/// Transform feedback buffers active at the time the shader was compiled.
|
||||
/// </summary>
|
||||
|
@ -158,6 +170,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
}
|
||||
|
||||
private readonly Dictionary<TextureKey, Box<TextureSpecializationState>> _textureSpecialization;
|
||||
private KeyValuePair<TextureKey, Box<TextureSpecializationState>>[] _allTextures;
|
||||
private Box<TextureSpecializationState>[][] _textureByBinding;
|
||||
private Box<TextureSpecializationState>[][] _imageByBinding;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="state">Current compute engine state</param>
|
||||
public ShaderSpecializationState(GpuChannelComputeState state) : this()
|
||||
public ShaderSpecializationState(ref GpuChannelComputeState state) : this()
|
||||
{
|
||||
ComputeState = state;
|
||||
_compute = true;
|
||||
|
@ -182,7 +197,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
/// </summary>
|
||||
/// <param name="state">Current 3D engine state</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;
|
||||
_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>
|
||||
/// Indicates that the shader accesses the early Z force state.
|
||||
/// </summary>
|
||||
|
@ -396,15 +481,38 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
/// <param name="channel">GPU channel</param>
|
||||
/// <param name="poolState">Texture pool 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>
|
||||
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)
|
||||
{
|
||||
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>
|
||||
|
@ -412,10 +520,64 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
/// </summary>
|
||||
/// <param name="channel">GPU channel</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>
|
||||
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>
|
||||
|
@ -423,9 +585,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
/// </summary>
|
||||
/// <param name="channel">GPU channel</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>
|
||||
/// <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;
|
||||
|
||||
|
@ -445,55 +608,60 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
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;
|
||||
ulong samplerCbAddress;
|
||||
|
||||
if (isCompute)
|
||||
foreach (var kv in _allTextures)
|
||||
{
|
||||
textureCbAddress = channel.BufferManager.GetComputeUniformBufferAddress(textureBufferIndex);
|
||||
samplerCbAddress = channel.BufferManager.GetComputeUniformBufferAddress(samplerBufferIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
textureCbAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(textureKey.StageIndex, textureBufferIndex);
|
||||
samplerCbAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(textureKey.StageIndex, samplerBufferIndex);
|
||||
}
|
||||
TextureKey textureKey = kv.Key;
|
||||
|
||||
if (!channel.MemoryManager.Physical.IsMapped(textureCbAddress) || !channel.MemoryManager.Physical.IsMapped(samplerCbAddress))
|
||||
{
|
||||
continue;
|
||||
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(textureKey.CbufSlot, poolState.TextureBufferIndex);
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <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) &&
|
||||
specializationState.Value.CoordNormalized != descriptor.UnpackTextureCoordNormalized())
|
||||
{
|
||||
|
@ -504,6 +672,34 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
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>
|
||||
/// Reads shader specialization state that has been serialized.
|
||||
/// </summary>
|
||||
|
@ -536,6 +732,17 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
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))
|
||||
{
|
||||
ushort tfCount = 0;
|
||||
|
@ -594,6 +801,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
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))
|
||||
{
|
||||
ushort tfCount = (ushort)TransformFeedbackDescriptors.Length;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
|
|
@ -88,16 +88,14 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
Add(Format.Bc3Srgb, new FormatInfo(4, false, false, All.CompressedSrgbAlphaS3tcDxt5Ext));
|
||||
Add(Format.Bc4Unorm, new FormatInfo(1, true, false, All.CompressedRedRgtc1));
|
||||
Add(Format.Bc4Snorm, new FormatInfo(1, true, false, All.CompressedSignedRedRgtc1));
|
||||
Add(Format.Bc5Unorm, new FormatInfo(2, true, false, All.CompressedRgRgtc2));
|
||||
Add(Format.Bc5Snorm, new FormatInfo(2, true, false, All.CompressedSignedRgRgtc2));
|
||||
Add(Format.Bc7Unorm, new FormatInfo(4, true, false, All.CompressedRgbaBptcUnorm));
|
||||
Add(Format.Bc7Srgb, new FormatInfo(4, false, false, All.CompressedSrgbAlphaBptcUnorm));
|
||||
Add(Format.Bc6HSfloat, new FormatInfo(4, false, false, All.CompressedRgbBptcSignedFloat));
|
||||
Add(Format.Bc6HUfloat, new FormatInfo(4, false, false, All.CompressedRgbBptcUnsignedFloat));
|
||||
Add(Format.Etc2RgbUnorm, new FormatInfo(3, false, false, All.CompressedRgb8Etc2));
|
||||
Add(Format.Etc2RgbaUnorm, new FormatInfo(4, false, false, All.CompressedRgba8Etc2Eac));
|
||||
Add(Format.Etc2RgbSrgb, new FormatInfo(3, false, false, All.CompressedSrgb8Etc2));
|
||||
Add(Format.Etc2RgbaSrgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Etc2Eac));
|
||||
Add(Format.Bc5Unorm, new FormatInfo(1, true, false, All.CompressedRgRgtc2));
|
||||
Add(Format.Bc5Snorm, new FormatInfo(1, true, false, All.CompressedSignedRgRgtc2));
|
||||
Add(Format.Bc7Unorm, new FormatInfo(1, true, false, All.CompressedRgbaBptcUnorm));
|
||||
Add(Format.Bc7Srgb, new FormatInfo(1, false, false, All.CompressedSrgbAlphaBptcUnorm));
|
||||
Add(Format.Bc6HSfloat, new FormatInfo(1, false, false, All.CompressedRgbBptcSignedFloat));
|
||||
Add(Format.Bc6HUfloat, new FormatInfo(1, false, false, All.CompressedRgbBptcUnsignedFloat));
|
||||
Add(Format.Etc2RgbaUnorm, new FormatInfo(1, false, false, All.CompressedRgba8Etc2Eac));
|
||||
Add(Format.Etc2RgbaSrgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Etc2Eac));
|
||||
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.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.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.Astc4x4Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc4X4Khr));
|
||||
Add(Format.Astc5x4Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc5X4Khr));
|
||||
Add(Format.Astc5x5Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc5X5Khr));
|
||||
Add(Format.Astc6x5Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc6X5Khr));
|
||||
Add(Format.Astc6x6Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc6X6Khr));
|
||||
Add(Format.Astc8x5Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc8X5Khr));
|
||||
Add(Format.Astc8x6Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc8X6Khr));
|
||||
Add(Format.Astc8x8Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc8X8Khr));
|
||||
Add(Format.Astc10x5Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc10X5Khr));
|
||||
Add(Format.Astc10x6Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc10X6Khr));
|
||||
Add(Format.Astc10x8Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc10X8Khr));
|
||||
Add(Format.Astc10x10Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc10X10Khr));
|
||||
Add(Format.Astc12x10Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc12X10Khr));
|
||||
Add(Format.Astc12x12Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc12X12Khr));
|
||||
Add(Format.Astc4x4Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc4X4Khr));
|
||||
Add(Format.Astc5x4Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc5X4Khr));
|
||||
Add(Format.Astc5x5Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc5X5Khr));
|
||||
Add(Format.Astc6x5Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc6X5Khr));
|
||||
Add(Format.Astc6x6Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc6X6Khr));
|
||||
Add(Format.Astc8x5Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc8X5Khr));
|
||||
Add(Format.Astc8x6Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc8X6Khr));
|
||||
Add(Format.Astc8x8Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc8X8Khr));
|
||||
Add(Format.Astc10x5Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc10X5Khr));
|
||||
Add(Format.Astc10x6Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc10X6Khr));
|
||||
Add(Format.Astc10x8Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc10X8Khr));
|
||||
Add(Format.Astc10x10Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc10X10Khr));
|
||||
Add(Format.Astc12x10Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc12X10Khr));
|
||||
Add(Format.Astc12x12Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc12X12Khr));
|
||||
Add(Format.Astc4x4Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc4X4Khr));
|
||||
Add(Format.Astc5x4Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc5X4Khr));
|
||||
Add(Format.Astc5x5Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc5X5Khr));
|
||||
Add(Format.Astc6x5Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc6X5Khr));
|
||||
Add(Format.Astc6x6Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc6X6Khr));
|
||||
Add(Format.Astc8x5Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc8X5Khr));
|
||||
Add(Format.Astc8x6Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc8X6Khr));
|
||||
Add(Format.Astc8x8Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc8X8Khr));
|
||||
Add(Format.Astc10x5Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc10X5Khr));
|
||||
Add(Format.Astc10x6Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc10X6Khr));
|
||||
Add(Format.Astc10x8Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc10X8Khr));
|
||||
Add(Format.Astc10x10Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc10X10Khr));
|
||||
Add(Format.Astc12x10Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc12X10Khr));
|
||||
Add(Format.Astc12x12Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc12X12Khr));
|
||||
Add(Format.Astc4x4Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc4X4Khr));
|
||||
Add(Format.Astc5x4Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc5X4Khr));
|
||||
Add(Format.Astc5x5Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc5X5Khr));
|
||||
Add(Format.Astc6x5Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc6X5Khr));
|
||||
Add(Format.Astc6x6Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc6X6Khr));
|
||||
Add(Format.Astc8x5Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc8X5Khr));
|
||||
Add(Format.Astc8x6Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc8X6Khr));
|
||||
Add(Format.Astc8x8Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc8X8Khr));
|
||||
Add(Format.Astc10x5Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc10X5Khr));
|
||||
Add(Format.Astc10x6Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc10X6Khr));
|
||||
Add(Format.Astc10x8Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc10X8Khr));
|
||||
Add(Format.Astc10x10Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc10X10Khr));
|
||||
Add(Format.Astc12x10Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc12X10Khr));
|
||||
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.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));
|
||||
|
|
|
@ -9,10 +9,13 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
class Framebuffer : IDisposable
|
||||
{
|
||||
public int Handle { get; private set; }
|
||||
private int _clearFbHandle;
|
||||
private bool _clearFbInitialized;
|
||||
|
||||
private FramebufferAttachment _lastDsAttachment;
|
||||
|
||||
private readonly TextureView[] _colors;
|
||||
private TextureView _depthStencil;
|
||||
|
||||
private int _colorsCount;
|
||||
private bool _dualSourceBlend;
|
||||
|
@ -20,6 +23,7 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
public Framebuffer()
|
||||
{
|
||||
Handle = GL.GenFramebuffer();
|
||||
_clearFbHandle = GL.GenFramebuffer();
|
||||
|
||||
_colors = new TextureView[8];
|
||||
}
|
||||
|
@ -55,20 +59,7 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
|
||||
if (depthStencil != null)
|
||||
{
|
||||
FramebufferAttachment attachment;
|
||||
|
||||
if (IsPackedDepthStencilFormat(depthStencil.Format))
|
||||
{
|
||||
attachment = FramebufferAttachment.DepthStencilAttachment;
|
||||
}
|
||||
else if (IsDepthOnlyFormat(depthStencil.Format))
|
||||
{
|
||||
attachment = FramebufferAttachment.DepthAttachment;
|
||||
}
|
||||
else
|
||||
{
|
||||
attachment = FramebufferAttachment.StencilAttachment;
|
||||
}
|
||||
FramebufferAttachment attachment = GetAttachment(depthStencil.Format);
|
||||
|
||||
GL.FramebufferTexture(
|
||||
FramebufferTarget.Framebuffer,
|
||||
|
@ -82,6 +73,8 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
{
|
||||
_lastDsAttachment = 0;
|
||||
}
|
||||
|
||||
_depthStencil = depthStencil;
|
||||
}
|
||||
|
||||
public void SetDualSourceBlend(bool enable)
|
||||
|
@ -124,6 +117,22 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
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)
|
||||
{
|
||||
return format == Format.D24UnormS8Uint ||
|
||||
|
@ -136,6 +145,78 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
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()
|
||||
{
|
||||
if (Handle != 0)
|
||||
|
@ -144,6 +225,13 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
|
||||
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> _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> _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> _supportsIndirectParameters = new Lazy<bool>(() => HasExtension("GL_ARB_indirect_parameters"));
|
||||
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> _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> _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> _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 SupportsFragmentShaderInterlock => _supportsFragmentShaderInterlock.Value;
|
||||
public static bool SupportsFragmentShaderOrdering => _supportsFragmentShaderOrdering.Value;
|
||||
public static bool SupportsGeometryShaderPassthrough => _supportsGeometryShaderPassthrough.Value;
|
||||
public static bool SupportsImageLoadFormatted => _supportsImageLoadFormatted.Value;
|
||||
public static bool SupportsIndirectParameters => _supportsIndirectParameters.Value;
|
||||
public static bool SupportsParallelShaderCompile => _supportsParallelShaderCompile.Value;
|
||||
|
@ -54,6 +59,9 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
public static bool SupportsQuads => _supportsQuads.Value;
|
||||
public static bool SupportsSeamlessCubemapPerTexture => _supportsSeamlessCubemapPerTexture.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 SupportsViewportSwizzle => _supportsViewportSwizzle.Value;
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
private CounterQueueEvent _activeConditionalRender;
|
||||
|
||||
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 TextureBase _unit0Texture;
|
||||
|
@ -110,7 +110,7 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
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(
|
||||
index,
|
||||
|
@ -119,14 +119,18 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
(componentMask & 4) != 0,
|
||||
(componentMask & 8) != 0);
|
||||
|
||||
_framebuffer.AttachColorLayerForClear(index, layer);
|
||||
|
||||
float[] colors = new float[] { color.Red, color.Green, color.Blue, color.Alpha };
|
||||
|
||||
GL.ClearBuffer(OpenTK.Graphics.OpenGL.ClearBuffer.Color, index, colors);
|
||||
|
||||
_framebuffer.DetachColorLayerForClear(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 =
|
||||
stencilMask != 0 &&
|
||||
|
@ -144,6 +148,8 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
GL.DepthMask(depthMask);
|
||||
}
|
||||
|
||||
_framebuffer.AttachDepthStencilLayerForClear(layer);
|
||||
|
||||
if (depthMask && stencilMask != 0)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
_framebuffer.DetachDepthStencilLayerForClear();
|
||||
|
||||
if (stencilMaskChanged)
|
||||
{
|
||||
GL.StencilMaskSeparate(StencilFace.Front, _stencilFrontMask);
|
||||
|
@ -597,6 +605,8 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
GL.EndTransformFeedback();
|
||||
}
|
||||
|
||||
GL.ClipControl(ClipOrigin.UpperLeft, ClipDepthMode.NegativeOneToOne);
|
||||
|
||||
_drawTexture.Draw(
|
||||
view,
|
||||
samp,
|
||||
|
@ -627,6 +637,8 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
{
|
||||
GL.BeginTransformFeedback(_tfTopology);
|
||||
}
|
||||
|
||||
RestoreClipControl();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1094,45 +1106,45 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
_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)
|
||||
{
|
||||
_unit0Sampler = samp;
|
||||
}
|
||||
bool enabled = (region.X | region.Y) != 0 || region.Width != 0xffff || region.Height != 0xffff;
|
||||
uint mask = 1u << index;
|
||||
|
||||
samp.Bind(binding);
|
||||
}
|
||||
|
||||
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)
|
||||
if (enabled)
|
||||
{
|
||||
_scissorEnables &= ~mask;
|
||||
GL.Disable(IndexedEnableCap.ScissorTest, index);
|
||||
v[vIndex] = region.X;
|
||||
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)
|
||||
{
|
||||
_scissorEnables |= mask;
|
||||
GL.Enable(IndexedEnableCap.ScissorTest, index);
|
||||
}
|
||||
|
||||
GL.ScissorIndexed(index, x, y, width, height);
|
||||
GL.ScissorArray(0, count, v);
|
||||
}
|
||||
|
||||
public void SetStencilTest(StencilTestDescriptor stencilTest)
|
||||
|
@ -1183,23 +1195,31 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
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)
|
||||
{
|
||||
_unit0Texture = (TextureBase)texture;
|
||||
}
|
||||
else
|
||||
{
|
||||
((TextureBase)texture).Bind(binding);
|
||||
_unit0Sampler = glSampler;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers)
|
||||
{
|
||||
if (_tfEnabled)
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using OpenTK.Graphics.OpenGL;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
|
|
@ -87,6 +87,11 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
Buffer.Delete(buffer);
|
||||
}
|
||||
|
||||
public HardwareInfo GetHardwareInfo()
|
||||
{
|
||||
return new HardwareInfo(GpuVendor, GpuRenderer);
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> GetBufferData(BufferHandle buffer, int offset, int size)
|
||||
{
|
||||
return Buffer.GetData(this, buffer, offset, size);
|
||||
|
@ -100,11 +105,15 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
hasFrontFacingBug: HwCapabilities.Vendor == HwCapabilities.GpuVendor.IntelWindows,
|
||||
hasVectorIndexingBug: HwCapabilities.Vendor == HwCapabilities.GpuVendor.AmdWindows,
|
||||
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,
|
||||
supportsBgraFormat: false,
|
||||
supportsR4G4Format: false,
|
||||
supportsFragmentShaderInterlock: HwCapabilities.SupportsFragmentShaderInterlock,
|
||||
supportsFragmentShaderOrderingIntel: HwCapabilities.SupportsFragmentShaderOrdering,
|
||||
supportsGeometryShaderPassthrough: HwCapabilities.SupportsGeometryShaderPassthrough,
|
||||
supportsImageLoadFormatted: HwCapabilities.SupportsImageLoadFormatted,
|
||||
supportsMismatchingViewFormat: HwCapabilities.SupportsMismatchingViewFormat,
|
||||
supportsNonConstantTextureOffset: HwCapabilities.SupportsNonConstantTextureOffset,
|
||||
|
@ -112,6 +121,10 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
supportsTextureShadowLod: HwCapabilities.SupportsTextureShadowLod,
|
||||
supportsViewportSwizzle: HwCapabilities.SupportsViewportSwizzle,
|
||||
supportsIndirectParameters: HwCapabilities.SupportsIndirectParameters,
|
||||
maximumUniformBuffersPerStage: 13, // TODO: Avoid hardcoding those limits here and get from driver?
|
||||
maximumStorageBuffersPerStage: 16,
|
||||
maximumTexturesPerStage: 32,
|
||||
maximumImagesPerStage: 8,
|
||||
maximumComputeSharedMemorySize: HwCapabilities.MaximumComputeSharedMemorySize,
|
||||
maximumSupportedAnisotropy: HwCapabilities.MaximumSupportedAnisotropy,
|
||||
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