Merge pull request #1 from gdkchan/vulkan-rel

Vulkan rel
This commit is contained in:
RMED24 2022-06-22 10:37:41 +01:00 committed by GitHub
commit 1100e5e1a6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
273 changed files with 39733 additions and 1502 deletions

View file

@ -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);
}

View file

@ -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();

View file

@ -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" />

View file

@ -135,7 +135,7 @@ namespace Ryujinx.Ava.Ui.Applet
Dispatcher.UIThread.Post(() =>
{
_hiddenTextBox.Clear();
_parent.GlRenderer.Focus();
_parent.RendererControl.Focus();
_parent = null;
});

View 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);
}
}
}
}

View 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);
}
}

View 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;
}
}
}

View 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}\".");
}
}
}

View 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; }
}
}
}

View 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;
}
}
}

View 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();
}
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}
}

View 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);
}
}
}
}

View 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;
}
}

View 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();
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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);
}
}
}

View 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; }
}
}

View 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;
}
}
}

View 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);
}
}
}

View 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; }
}
}

View 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);
}
}
}

View 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];
}
}
}

View file

@ -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();
}
}
}

View 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());
}
}
}
}

View file

@ -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);
}
}
}

View 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());
}
}
}
}

View file

@ -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()

View file

@ -0,0 +1,8 @@
namespace Ryujinx.Common.Configuration
{
public enum GraphicsBackend
{
Vulkan,
OpenGl
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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;

View 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;
}
}
}

View file

@ -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>

View 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;
}
}
}

View file

@ -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);

View file

@ -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);

View file

@ -1,6 +0,0 @@
using System;
namespace Ryujinx.Graphics.GAL
{
public interface IShader : IDisposable { }
}

View file

@ -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) =>

View file

@ -81,10 +81,9 @@
SetRenderTargetColorMasks,
SetRenderTargetScale,
SetRenderTargets,
SetSampler,
SetScissor,
SetStencilTest,
SetTexture,
SetTextureAndSampler,
SetUserClipDistance,
SetVertexAttribs,
SetVertexBuffers,

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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();
}

View file

@ -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>

View file

@ -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);
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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)
{
}
}

View file

@ -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;

View file

@ -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.

View file

@ -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);

View file

@ -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,

View file

@ -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>

View file

@ -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>

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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--;
}

View file

@ -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();
}

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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();

View file

@ -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;
}

View file

@ -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>

View file

@ -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();

View file

@ -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>

View file

@ -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;

View file

@ -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>

View file

@ -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++)
{

View file

@ -35,6 +35,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
HostProgram = hostProgram;
SpecializationState = specializationState;
Shaders = shaders;
SpecializationState.Prepare(shaders);
}
/// <summary>

View file

@ -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>

View file

@ -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()
{

View file

@ -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>

View file

@ -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();

View 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;
}
}
}

View file

@ -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));
}

View file

@ -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();
}
}
}

View file

@ -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()
{

View file

@ -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>

View file

@ -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;
}
}
}

View file

@ -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.

View file

@ -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;

View file

@ -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;

View file

@ -1,4 +1,4 @@
using Ryujinx.Common;
using Ryujinx.Common;
using System;
using System.Collections.Generic;
using System.Threading;

View file

@ -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));

View file

@ -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;
}
}
}
}

View file

@ -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;

View file

@ -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)

View file

@ -1,5 +1,4 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using System;
using System.Threading;

View file

@ -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);

View 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