diff --git a/ARMeilleure/Instructions/InstEmitMemoryHelper.cs b/ARMeilleure/Instructions/InstEmitMemoryHelper.cs
index 3e6d80fad3..8186c3c2a5 100644
--- a/ARMeilleure/Instructions/InstEmitMemoryHelper.cs
+++ b/ARMeilleure/Instructions/InstEmitMemoryHelper.cs
@@ -320,12 +320,9 @@ namespace ARMeilleure.Instructions
}
while (bit < context.Memory.AddressSpaceBits);
- if (!context.Memory.HasWriteWatchSupport)
- {
- Operand hasFlagSet = context.BitwiseAnd(pte, Const((long)MemoryManager.PteFlagsMask));
+ Operand hasFlagSet = context.BitwiseAnd(pte, Const((long)MemoryManager.PteFlagsMask));
- context.BranchIfTrue(lblFallbackPath, hasFlagSet);
- }
+ context.BranchIfTrue(lblFallbackPath, hasFlagSet);
Operand pageOffset = context.BitwiseAnd(address, Const(address.Type, MemoryManager.PageMask));
diff --git a/ARMeilleure/Memory/IMemory.cs b/ARMeilleure/Memory/IMemory.cs
deleted file mode 100644
index 0c3849c07f..0000000000
--- a/ARMeilleure/Memory/IMemory.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-namespace ARMeilleure.Memory
-{
- public interface IMemory
- {
- sbyte ReadSByte(long position);
-
- short ReadInt16(long position);
-
- int ReadInt32(long position);
-
- long ReadInt64(long position);
-
- byte ReadByte(long position);
-
- ushort ReadUInt16(long position);
-
- uint ReadUInt32(long position);
-
- ulong ReadUInt64(long position);
-
- void WriteSByte(long position, sbyte value);
-
- void WriteInt16(long position, short value);
-
- void WriteInt32(long position, int value);
-
- void WriteInt64(long position, long value);
-
- void WriteByte(long position, byte value);
-
- void WriteUInt16(long position, ushort value);
-
- void WriteUInt32(long position, uint value);
-
- void WriteUInt64(long position, ulong value);
- }
-}
\ No newline at end of file
diff --git a/ARMeilleure/Memory/MemoryManagement.cs b/ARMeilleure/Memory/MemoryManagement.cs
index bf0bd02ce0..e299ae49da 100644
--- a/ARMeilleure/Memory/MemoryManagement.cs
+++ b/ARMeilleure/Memory/MemoryManagement.cs
@@ -6,8 +6,6 @@ namespace ARMeilleure.Memory
{
public static class MemoryManagement
{
- public static bool HasWriteWatchSupport => RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
-
public static IntPtr Allocate(ulong size)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
@@ -88,27 +86,5 @@ namespace ARMeilleure.Memory
throw new PlatformNotSupportedException();
}
}
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static bool GetModifiedPages(
- IntPtr address,
- IntPtr size,
- IntPtr[] addresses,
- out ulong count)
- {
- // This is only supported on windows, but returning
- // false (failed) is also valid for platforms without
- // write tracking support on the OS.
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- {
- return MemoryManagementWindows.GetModifiedPages(address, size, addresses, out count);
- }
- else
- {
- count = 0;
-
- return false;
- }
- }
}
}
\ No newline at end of file
diff --git a/ARMeilleure/Memory/MemoryManagementWindows.cs b/ARMeilleure/Memory/MemoryManagementWindows.cs
index c1a84c95b9..ae64b5c62b 100644
--- a/ARMeilleure/Memory/MemoryManagementWindows.cs
+++ b/ARMeilleure/Memory/MemoryManagementWindows.cs
@@ -36,12 +36,6 @@ namespace ARMeilleure.Memory
WriteCombineModifierflag = 0x400
}
- private enum WriteWatchFlags : uint
- {
- None = 0,
- Reset = 1
- }
-
[DllImport("kernel32.dll")]
private static extern IntPtr VirtualAlloc(
IntPtr lpAddress,
@@ -62,15 +56,6 @@ namespace ARMeilleure.Memory
IntPtr dwSize,
AllocationType dwFreeType);
- [DllImport("kernel32.dll")]
- private static extern int GetWriteWatch(
- WriteWatchFlags dwFlags,
- IntPtr lpBaseAddress,
- IntPtr dwRegionSize,
- IntPtr[] lpAddresses,
- ref ulong lpdwCount,
- out uint lpdwGranularity);
-
public static IntPtr Allocate(IntPtr size)
{
const AllocationType flags =
@@ -130,27 +115,5 @@ namespace ARMeilleure.Memory
{
return VirtualFree(address, IntPtr.Zero, AllocationType.Release);
}
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static bool GetModifiedPages(
- IntPtr address,
- IntPtr size,
- IntPtr[] addresses,
- out ulong count)
- {
- ulong pagesCount = (ulong)addresses.Length;
-
- int result = GetWriteWatch(
- WriteWatchFlags.Reset,
- address,
- size,
- addresses,
- ref pagesCount,
- out uint granularity);
-
- count = pagesCount;
-
- return result == 0;
- }
}
}
\ No newline at end of file
diff --git a/ARMeilleure/Memory/MemoryManager.cs b/ARMeilleure/Memory/MemoryManager.cs
index 56718ad671..562747f567 100644
--- a/ARMeilleure/Memory/MemoryManager.cs
+++ b/ARMeilleure/Memory/MemoryManager.cs
@@ -1,5 +1,6 @@
using ARMeilleure.State;
using System;
+using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
@@ -13,8 +14,6 @@ namespace ARMeilleure.Memory
public const int PageSize = 1 << PageBits;
public const int PageMask = PageSize - 1;
- private const long PteFlagNotModified = 1;
-
internal const long PteFlagsMask = 7;
public IntPtr Ram { get; private set; }
@@ -29,8 +28,6 @@ namespace ARMeilleure.Memory
internal int PtLevelSize { get; }
internal int PtLevelMask { get; }
- public bool HasWriteWatchSupport => MemoryManagement.HasWriteWatchSupport;
-
public int AddressSpaceBits { get; }
public long AddressSpaceSize { get; }
@@ -107,6 +104,11 @@ namespace ARMeilleure.Memory
ptr = (byte*)ptrUlong;
}
+ if (ptr == null)
+ {
+ return IntPtr.Zero;
+ }
+
return new IntPtr(ptr + (position & PageMask));
}
@@ -123,10 +125,7 @@ namespace ARMeilleure.Memory
if ((ptrUlong & PteFlagsMask) != 0)
{
- if ((ptrUlong & PteFlagNotModified) != 0)
- {
- ClearPtEntryFlag(position, PteFlagNotModified);
- }
+ ClearPtEntryFlag(position, PteFlagsMask);
ptrUlong &= ~(ulong)PteFlagsMask;
@@ -254,119 +253,60 @@ namespace ARMeilleure.Memory
return ptePtr;
}
- public bool IsRegionModified(long position, long size)
+ public unsafe (ulong, ulong)[] GetModifiedRanges(ulong address, ulong size, int id)
{
- if (!HasWriteWatchSupport)
+ ulong idMask = 1UL << id;
+
+ List<(ulong, ulong)> ranges = new List<(ulong, ulong)>();
+
+ ulong endAddress = (address + size + PageMask) & ~(ulong)PageMask;
+
+ address &= ~(ulong)PageMask;
+
+ ulong currAddr = address;
+ ulong currSize = 0;
+
+ while (address < endAddress)
{
- return IsRegionModifiedFallback(position, size);
- }
-
- IntPtr address = Translate(position);
-
- IntPtr baseAddr = address;
- IntPtr expectedAddr = address;
-
- long pendingPages = 0;
-
- long pages = size / PageSize;
-
- bool modified = false;
-
- bool IsAnyPageModified()
- {
- IntPtr pendingSize = new IntPtr(pendingPages * PageSize);
-
- IntPtr[] addresses = new IntPtr[pendingPages];
-
- bool result = GetModifiedPages(baseAddr, pendingSize, addresses, out ulong count);
-
- if (result)
- {
- return count != 0;
- }
- else
- {
- return true;
- }
- }
-
- while (pages-- > 0)
- {
- if (address != expectedAddr)
- {
- modified |= IsAnyPageModified();
-
- baseAddr = address;
-
- pendingPages = 0;
- }
-
- expectedAddr = address + PageSize;
-
- pendingPages++;
-
- if (pages == 0)
+ // If the address is invalid, we stop and consider all the remaining memory
+ // as not modified (since the address is invalid, we can't check, and technically
+ // the memory doesn't exist).
+ if (!IsValidPosition((long)address))
{
break;
}
- position += PageSize;
+ byte* ptr = ((byte**)_pageTable)[address >> PageBits];
- address = Translate(position);
- }
+ ulong ptrUlong = (ulong)ptr;
- if (pendingPages != 0)
- {
- modified |= IsAnyPageModified();
- }
-
- return modified;
- }
-
- private unsafe bool IsRegionModifiedFallback(long position, long size)
- {
- long endAddr = (position + size + PageMask) & ~PageMask;
-
- bool modified = false;
-
- while ((ulong)position < (ulong)endAddr)
- {
- if (IsValidPosition(position))
+ if ((ptrUlong & idMask) == 0)
{
- byte* ptr = ((byte**)_pageTable)[position >> PageBits];
+ // Modified.
+ currSize += PageSize;
- ulong ptrUlong = (ulong)ptr;
-
- if ((ptrUlong & PteFlagNotModified) == 0)
- {
- modified = true;
-
- SetPtEntryFlag(position, PteFlagNotModified);
- }
+ SetPtEntryFlag((long)address, (long)idMask);
}
else
{
- modified = true;
+ if (currSize != 0)
+ {
+ ranges.Add((currAddr, currSize));
+ }
+
+ currAddr = address + PageSize;
+ currSize = 0;
}
- position += PageSize;
+ address += PageSize;
}
- return modified;
- }
-
- public bool TryGetHostAddress(long position, long size, out IntPtr ptr)
- {
- if (IsContiguous(position, size))
+ if (currSize != 0)
{
- ptr = (IntPtr)Translate(position);
-
- return true;
+ ranges.Add((currAddr, currSize));
}
- ptr = IntPtr.Zero;
-
- return false;
+ return ranges.ToArray();
}
private bool IsContiguous(long position, long size)
@@ -612,41 +552,6 @@ namespace ARMeilleure.Memory
return data;
}
- public void ReadBytes(long position, byte[] data, int startIndex, int size)
- {
- // Note: This will be moved later.
- long endAddr = position + size;
-
- if ((ulong)size > int.MaxValue)
- {
- throw new ArgumentOutOfRangeException(nameof(size));
- }
-
- if ((ulong)endAddr < (ulong)position)
- {
- throw new ArgumentOutOfRangeException(nameof(position));
- }
-
- int offset = startIndex;
-
- while ((ulong)position < (ulong)endAddr)
- {
- long pageLimit = (position + PageSize) & ~(long)PageMask;
-
- if ((ulong)pageLimit > (ulong)endAddr)
- {
- pageLimit = endAddr;
- }
-
- int copySize = (int)(pageLimit - position);
-
- Marshal.Copy(Translate(position), data, offset, copySize);
-
- position += copySize;
- offset += copySize;
- }
- }
-
public void WriteSByte(long position, sbyte value)
{
WriteByte(position, (byte)value);
@@ -746,53 +651,6 @@ namespace ARMeilleure.Memory
}
}
- public void WriteBytes(long position, byte[] data, int startIndex, int size)
- {
- // Note: This will be moved later.
- long endAddr = position + size;
-
- if ((ulong)endAddr < (ulong)position)
- {
- throw new ArgumentOutOfRangeException(nameof(position));
- }
-
- int offset = startIndex;
-
- while ((ulong)position < (ulong)endAddr)
- {
- long pageLimit = (position + PageSize) & ~(long)PageMask;
-
- if ((ulong)pageLimit > (ulong)endAddr)
- {
- pageLimit = endAddr;
- }
-
- int copySize = (int)(pageLimit - position);
-
- Marshal.Copy(data, offset, Translate(position), copySize);
-
- position += copySize;
- offset += copySize;
- }
- }
-
- public void CopyBytes(long src, long dst, long size)
- {
- // Note: This will be moved later.
- if (IsContiguous(src, size) &&
- IsContiguous(dst, size))
- {
- byte* srcPtr = (byte*)Translate(src);
- byte* dstPtr = (byte*)Translate(dst);
-
- Buffer.MemoryCopy(srcPtr, dstPtr, size, size);
- }
- else
- {
- WriteBytes(dst, ReadBytes(src, size));
- }
- }
-
public void Dispose()
{
Dispose(true);
diff --git a/README.md b/README.md
index 80bfb6fd96..65fc95c5f3 100644
--- a/README.md
+++ b/README.md
@@ -43,10 +43,6 @@ The latest automatic build for Windows, macOS, and Linux can be found on the [Of
Everything on the Switch is encrypted, so if you want to run anything other than homebrew, you have to dump encryption keys from your console. To get more information please take a look at our [Keys Documentation](KEYS.md).
- - **FFmpeg Dependencies**
-
- Ryujinx has a basic implementation of `NVDEC`, a video decoder used by the Switch's GPU. Many games include videos that use it, so you need to download [Zeranoe's FFmpeg Builds](http://ffmpeg.zeranoe.com/builds/) for **Shared** linking and your computer's operating system. When it's done, extract the contents of the `bin` folder directly into your Ryujinx folder.
-
- **System Titles**
Some of our System Module implementations, like `time`, require [System Data Archives](https://switchbrew.org/wiki/Title_list#System_Data_Archives). You can install them by mounting your nand partition using [HacDiskMount](https://switchtools.sshnuke.net/) and copying the content to `Ryujinx/nand/system`.
diff --git a/Ryujinx.Common/PerformanceCounter.cs b/Ryujinx.Common/PerformanceCounter.cs
index 4c8ae6a75a..1c2782e34b 100644
--- a/Ryujinx.Common/PerformanceCounter.cs
+++ b/Ryujinx.Common/PerformanceCounter.cs
@@ -1,9 +1,11 @@
-using System.Diagnostics;
+using System.Diagnostics;
namespace Ryujinx.Common
{
public static class PerformanceCounter
{
+ private static double _ticksToNs;
+
///
/// Represents the number of ticks in 1 day.
///
@@ -53,6 +55,19 @@ namespace Ryujinx.Common
}
}
+ ///
+ /// Gets the number of nanoseconds elapsed since the system started.
+ ///
+ public static long ElapsedNanoseconds
+ {
+ get
+ {
+ long timestamp = Stopwatch.GetTimestamp();
+
+ return (long)(timestamp * _ticksToNs);
+ }
+ }
+
static PerformanceCounter()
{
TicksPerMillisecond = Stopwatch.Frequency / 1000;
@@ -60,6 +75,8 @@ namespace Ryujinx.Common
TicksPerMinute = TicksPerSecond * 60;
TicksPerHour = TicksPerMinute * 60;
TicksPerDay = TicksPerHour * 24;
+
+ _ticksToNs = 1000000000.0 / (double)Stopwatch.Frequency;
}
}
-}
+}
\ No newline at end of file
diff --git a/Ryujinx.Common/Utilities/Buffers.cs b/Ryujinx.Common/Utilities/Buffers.cs
new file mode 100644
index 0000000000..d614bfc43b
--- /dev/null
+++ b/Ryujinx.Common/Utilities/Buffers.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Common.Utilities
+{
+ [DebuggerDisplay("{ToString()}")]
+ [StructLayout(LayoutKind.Sequential, Size = 16)]
+ public struct Buffer16
+ {
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy0;
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy1;
+
+ public byte this[int i]
+ {
+ get => Bytes[i];
+ set => Bytes[i] = value;
+ }
+
+ public Span Bytes => SpanHelpers.AsByteSpan(ref this);
+
+ // Prevent a defensive copy by changing the read-only in reference to a reference with Unsafe.AsRef()
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator Span(in Buffer16 value)
+ {
+ return SpanHelpers.AsByteSpan(ref Unsafe.AsRef(in value));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator ReadOnlySpan(in Buffer16 value)
+ {
+ return SpanHelpers.AsReadOnlyByteSpan(ref Unsafe.AsRef(in value));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ref T As() where T : unmanaged
+ {
+ if (Unsafe.SizeOf() > (uint)Unsafe.SizeOf())
+ {
+ throw new ArgumentException();
+ }
+
+ return ref MemoryMarshal.GetReference(AsSpan());
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Span AsSpan() where T : unmanaged
+ {
+ return SpanHelpers.AsSpan(ref this);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly ReadOnlySpan AsReadOnlySpan() where T : unmanaged
+ {
+ return SpanHelpers.AsReadOnlySpan(ref Unsafe.AsRef(in this));
+ }
+ }
+}
diff --git a/Ryujinx.Common/Utilities/EmbeddedResources.cs b/Ryujinx.Common/Utilities/EmbeddedResources.cs
new file mode 100644
index 0000000000..97f349c192
--- /dev/null
+++ b/Ryujinx.Common/Utilities/EmbeddedResources.cs
@@ -0,0 +1,148 @@
+using System;
+using System.IO;
+using System.Reflection;
+using System.Threading.Tasks;
+
+namespace Ryujinx.Common
+{
+ public static class EmbeddedResources
+ {
+ private readonly static Assembly ResourceAssembly;
+
+ static EmbeddedResources()
+ {
+ ResourceAssembly = Assembly.GetAssembly(typeof(EmbeddedResources));
+ }
+
+ public static byte[] Read(string filename)
+ {
+ var (assembly, path) = ResolveManifestPath(filename);
+
+ return Read(assembly, path);
+ }
+
+ public static Task ReadAsync(string filename)
+ {
+ var (assembly, path) = ResolveManifestPath(filename);
+
+ return ReadAsync(assembly, path);
+ }
+
+ public static byte[] Read(Assembly assembly, string filename)
+ {
+ using (var stream = GetStream(assembly, filename))
+ {
+ if (stream == null)
+ {
+ return null;
+ }
+
+ using (var mem = new MemoryStream())
+ {
+ stream.CopyTo(mem);
+
+ return mem.ToArray();
+ }
+ }
+ }
+
+ public async static Task ReadAsync(Assembly assembly, string filename)
+ {
+ using (var stream = GetStream(assembly, filename))
+ {
+ if (stream == null)
+ {
+ return null;
+ }
+
+ using (var mem = new MemoryStream())
+ {
+ await stream.CopyToAsync(mem);
+
+ return mem.ToArray();
+ }
+ }
+ }
+
+ public static string ReadAllText(string filename)
+ {
+ var (assembly, path) = ResolveManifestPath(filename);
+
+ return ReadAllText(assembly, path);
+ }
+
+ public static Task ReadAllTextAsync(string filename)
+ {
+ var (assembly, path) = ResolveManifestPath(filename);
+
+ return ReadAllTextAsync(assembly, path);
+ }
+
+ public static string ReadAllText(Assembly assembly, string filename)
+ {
+ using (var stream = GetStream(assembly, filename))
+ {
+ if (stream == null)
+ {
+ return null;
+ }
+
+ using (var reader = new StreamReader(stream))
+ {
+ return reader.ReadToEnd();
+ }
+ }
+ }
+
+ public async static Task ReadAllTextAsync(Assembly assembly, string filename)
+ {
+ using (var stream = GetStream(assembly, filename))
+ {
+ if (stream == null)
+ {
+ return null;
+ }
+
+ using (var reader = new StreamReader(stream))
+ {
+ return await reader.ReadToEndAsync();
+ }
+ }
+ }
+
+ public static Stream GetStream(string filename)
+ {
+ var (assembly, _) = ResolveManifestPath(filename);
+
+ return GetStream(assembly, filename);
+ }
+
+ public static Stream GetStream(Assembly assembly, string filename)
+ {
+ var namespace_ = assembly.GetName().Name;
+ var manifestUri = namespace_ + "." + filename.Replace('/', '.');
+
+ var stream = assembly.GetManifestResourceStream(manifestUri);
+
+ return stream;
+ }
+
+ private static (Assembly, string) ResolveManifestPath(string filename)
+ {
+ var segments = filename.Split(new[] { '/' }, 2, StringSplitOptions.RemoveEmptyEntries);
+
+ if (segments.Length >= 2)
+ {
+ foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
+ {
+ if (assembly.GetName().Name == segments[0])
+ {
+ return (assembly, segments[1]);
+ }
+ }
+ }
+
+ return (ResourceAssembly, filename);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Common/Utilities/SpanHelpers.cs b/Ryujinx.Common/Utilities/SpanHelpers.cs
new file mode 100644
index 0000000000..84c130233f
--- /dev/null
+++ b/Ryujinx.Common/Utilities/SpanHelpers.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Common.Utilities
+{
+ public static class SpanHelpers
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Span CreateSpan(ref T reference, int length)
+ {
+ return MemoryMarshal.CreateSpan(ref reference, length);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Span AsSpan(ref T reference) where T : unmanaged
+ {
+ return CreateSpan(ref reference, 1);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Span AsSpan(ref TStruct reference)
+ where TStruct : unmanaged where TSpan : unmanaged
+ {
+ return CreateSpan(ref Unsafe.As(ref reference),
+ Unsafe.SizeOf() / Unsafe.SizeOf());
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Span AsByteSpan(ref T reference) where T : unmanaged
+ {
+ return CreateSpan(ref Unsafe.As(ref reference), Unsafe.SizeOf());
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlySpan CreateReadOnlySpan(ref T reference, int length)
+ {
+ return MemoryMarshal.CreateReadOnlySpan(ref reference, length);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlySpan AsReadOnlySpan(ref T reference) where T : unmanaged
+ {
+ return CreateReadOnlySpan(ref reference, 1);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlySpan AsReadOnlySpan(ref TStruct reference)
+ where TStruct : unmanaged where TSpan : unmanaged
+ {
+ return CreateReadOnlySpan(ref Unsafe.As(ref reference),
+ Unsafe.SizeOf() / Unsafe.SizeOf());
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlySpan AsReadOnlyByteSpan(ref T reference) where T : unmanaged
+ {
+ return CreateReadOnlySpan(ref Unsafe.As(ref reference), Unsafe.SizeOf());
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.GAL/AddressMode.cs b/Ryujinx.Graphics.GAL/AddressMode.cs
new file mode 100644
index 0000000000..153925b108
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/AddressMode.cs
@@ -0,0 +1,14 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum AddressMode
+ {
+ Repeat,
+ MirroredRepeat,
+ ClampToEdge,
+ ClampToBorder,
+ Clamp,
+ MirrorClampToEdge,
+ MirrorClampToBorder,
+ MirrorClamp
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.GAL/BlendDescriptor.cs b/Ryujinx.Graphics.GAL/BlendDescriptor.cs
new file mode 100644
index 0000000000..b35a0169dd
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/BlendDescriptor.cs
@@ -0,0 +1,32 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public struct BlendDescriptor
+ {
+ public bool Enable { get; }
+
+ public BlendOp ColorOp { get; }
+ public BlendFactor ColorSrcFactor { get; }
+ public BlendFactor ColorDstFactor { get; }
+ public BlendOp AlphaOp { get; }
+ public BlendFactor AlphaSrcFactor { get; }
+ public BlendFactor AlphaDstFactor { get; }
+
+ public BlendDescriptor(
+ bool enable,
+ BlendOp colorOp,
+ BlendFactor colorSrcFactor,
+ BlendFactor colorDstFactor,
+ BlendOp alphaOp,
+ BlendFactor alphaSrcFactor,
+ BlendFactor alphaDstFactor)
+ {
+ Enable = enable;
+ ColorOp = colorOp;
+ ColorSrcFactor = colorSrcFactor;
+ ColorDstFactor = colorDstFactor;
+ AlphaOp = alphaOp;
+ AlphaSrcFactor = alphaSrcFactor;
+ AlphaDstFactor = alphaDstFactor;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.GAL/BlendFactor.cs b/Ryujinx.Graphics.GAL/BlendFactor.cs
new file mode 100644
index 0000000000..135873e992
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/BlendFactor.cs
@@ -0,0 +1,41 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum BlendFactor
+ {
+ Zero = 1,
+ One,
+ SrcColor,
+ OneMinusSrcColor,
+ SrcAlpha,
+ OneMinusSrcAlpha,
+ DstAlpha,
+ OneMinusDstAlpha,
+ DstColor,
+ OneMinusDstColor,
+ SrcAlphaSaturate,
+ Src1Color = 0x10,
+ OneMinusSrc1Color,
+ Src1Alpha,
+ OneMinusSrc1Alpha,
+ ConstantColor = 0xc001,
+ OneMinusConstantColor,
+ ConstantAlpha,
+ OneMinusConstantAlpha,
+
+ ZeroGl = 0x4000,
+ OneGl = 0x4001,
+ SrcColorGl = 0x4300,
+ OneMinusSrcColorGl = 0x4301,
+ SrcAlphaGl = 0x4302,
+ OneMinusSrcAlphaGl = 0x4303,
+ DstAlphaGl = 0x4304,
+ OneMinusDstAlphaGl = 0x4305,
+ DstColorGl = 0x4306,
+ OneMinusDstColorGl = 0x4307,
+ SrcAlphaSaturateGl = 0x4308,
+ Src1ColorGl = 0xc900,
+ OneMinusSrc1ColorGl = 0xc901,
+ Src1AlphaGl = 0xc902,
+ OneMinusSrc1AlphaGl = 0xc903
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.GAL/BlendOp.cs b/Ryujinx.Graphics.GAL/BlendOp.cs
new file mode 100644
index 0000000000..de1ab67d01
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/BlendOp.cs
@@ -0,0 +1,17 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum BlendOp
+ {
+ Add = 1,
+ Subtract,
+ ReverseSubtract,
+ Minimum,
+ Maximum,
+
+ AddGl = 0x8006,
+ SubtractGl = 0x8007,
+ ReverseSubtractGl = 0x8008,
+ MinimumGl = 0x800a,
+ MaximumGl = 0x800b
+ }
+}
diff --git a/Ryujinx.Graphics.GAL/BufferRange.cs b/Ryujinx.Graphics.GAL/BufferRange.cs
new file mode 100644
index 0000000000..a35636aa73
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/BufferRange.cs
@@ -0,0 +1,21 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public struct BufferRange
+ {
+ private static BufferRange _empty = new BufferRange(null, 0, 0);
+
+ public BufferRange Empty => _empty;
+
+ public IBuffer Buffer { get; }
+
+ public int Offset { get; }
+ public int Size { get; }
+
+ public BufferRange(IBuffer buffer, int offset, int size)
+ {
+ Buffer = buffer;
+ Offset = offset;
+ Size = size;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.GAL/Capabilities.cs b/Ryujinx.Graphics.GAL/Capabilities.cs
new file mode 100644
index 0000000000..b75ceb94f3
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/Capabilities.cs
@@ -0,0 +1,23 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public struct Capabilities
+ {
+ public bool SupportsAstcCompression { get; }
+ public bool SupportsNonConstantTextureOffset { get; }
+
+ public int MaximumComputeSharedMemorySize { get; }
+ public int StorageBufferOffsetAlignment { get; }
+
+ public Capabilities(
+ bool supportsAstcCompression,
+ bool supportsNonConstantTextureOffset,
+ int maximumComputeSharedMemorySize,
+ int storageBufferOffsetAlignment)
+ {
+ SupportsAstcCompression = supportsAstcCompression;
+ SupportsNonConstantTextureOffset = supportsNonConstantTextureOffset;
+ MaximumComputeSharedMemorySize = maximumComputeSharedMemorySize;
+ StorageBufferOffsetAlignment = storageBufferOffsetAlignment;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.GAL/ColorF.cs b/Ryujinx.Graphics.GAL/ColorF.cs
new file mode 100644
index 0000000000..2e971a62c8
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/ColorF.cs
@@ -0,0 +1,18 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public struct ColorF
+ {
+ public float Red { get; }
+ public float Green { get; }
+ public float Blue { get; }
+ public float Alpha { get; }
+
+ public ColorF(float red, float green, float blue, float alpha)
+ {
+ Red = red;
+ Green = green;
+ Blue = blue;
+ Alpha = alpha;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.GAL/CompareMode.cs b/Ryujinx.Graphics.GAL/CompareMode.cs
new file mode 100644
index 0000000000..7a64d9bb9a
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/CompareMode.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum CompareMode
+ {
+ None,
+ CompareRToTexture
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.GAL/CompareOp.cs b/Ryujinx.Graphics.GAL/CompareOp.cs
new file mode 100644
index 0000000000..358ed2b46c
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/CompareOp.cs
@@ -0,0 +1,23 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum CompareOp
+ {
+ Never = 1,
+ Less,
+ Equal,
+ LessOrEqual,
+ Greater,
+ NotEqual,
+ GreaterOrEqual,
+ Always,
+
+ NeverGl = 0x200,
+ LessGl = 0x201,
+ EqualGl = 0x202,
+ LessOrEqualGl = 0x203,
+ GreaterGl = 0x204,
+ NotEqualGl = 0x205,
+ GreaterOrEqualGl = 0x206,
+ AlwaysGl = 0x207,
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.GAL/CounterType.cs b/Ryujinx.Graphics.GAL/CounterType.cs
new file mode 100644
index 0000000000..9d7b3b9816
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/CounterType.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum CounterType
+ {
+ SamplesPassed,
+ PrimitivesGenerated,
+ TransformFeedbackPrimitivesWritten
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.GAL/DepthMode.cs b/Ryujinx.Graphics.GAL/DepthMode.cs
new file mode 100644
index 0000000000..aafbb65a61
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/DepthMode.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum DepthMode
+ {
+ MinusOneToOne,
+ ZeroToOne
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.GAL/DepthStencilMode.cs b/Ryujinx.Graphics.GAL/DepthStencilMode.cs
new file mode 100644
index 0000000000..e80d0d4b36
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/DepthStencilMode.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum DepthStencilMode
+ {
+ Depth,
+ Stencil
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.GAL/DepthStencilState.cs b/Ryujinx.Graphics.GAL/DepthStencilState.cs
new file mode 100644
index 0000000000..d81e84360d
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/DepthStencilState.cs
@@ -0,0 +1,47 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public struct DepthStencilState
+ {
+ public bool DepthTestEnable { get; }
+ public bool DepthWriteEnable { get; }
+ public bool StencilTestEnable { get; }
+
+ public CompareOp DepthFunc { get; }
+ public CompareOp StencilFrontFunc { get; }
+ public StencilOp StencilFrontSFail { get; }
+ public StencilOp StencilFrontDpPass { get; }
+ public StencilOp StencilFrontDpFail { get; }
+ public CompareOp StencilBackFunc { get; }
+ public StencilOp StencilBackSFail { get; }
+ public StencilOp StencilBackDpPass { get; }
+ public StencilOp StencilBackDpFail { get; }
+
+ public DepthStencilState(
+ bool depthTestEnable,
+ bool depthWriteEnable,
+ bool stencilTestEnable,
+ CompareOp depthFunc,
+ CompareOp stencilFrontFunc,
+ StencilOp stencilFrontSFail,
+ StencilOp stencilFrontDpPass,
+ StencilOp stencilFrontDpFail,
+ CompareOp stencilBackFunc,
+ StencilOp stencilBackSFail,
+ StencilOp stencilBackDpPass,
+ StencilOp stencilBackDpFail)
+ {
+ DepthTestEnable = depthTestEnable;
+ DepthWriteEnable = depthWriteEnable;
+ StencilTestEnable = stencilTestEnable;
+ DepthFunc = depthFunc;
+ StencilFrontFunc = stencilFrontFunc;
+ StencilFrontSFail = stencilFrontSFail;
+ StencilFrontDpPass = stencilFrontDpPass;
+ StencilFrontDpFail = stencilFrontDpFail;
+ StencilBackFunc = stencilBackFunc;
+ StencilBackSFail = stencilBackSFail;
+ StencilBackDpPass = stencilBackDpPass;
+ StencilBackDpFail = stencilBackDpFail;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.GAL/DepthTestDescriptor.cs b/Ryujinx.Graphics.GAL/DepthTestDescriptor.cs
new file mode 100644
index 0000000000..c835e94141
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/DepthTestDescriptor.cs
@@ -0,0 +1,20 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public struct DepthTestDescriptor
+ {
+ public bool TestEnable { get; }
+ public bool WriteEnable { get; }
+
+ public CompareOp Func { get; }
+
+ public DepthTestDescriptor(
+ bool testEnable,
+ bool writeEnable,
+ CompareOp func)
+ {
+ TestEnable = testEnable;
+ WriteEnable = writeEnable;
+ Func = func;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.GAL/Extents2D.cs b/Ryujinx.Graphics.GAL/Extents2D.cs
new file mode 100644
index 0000000000..e9e26af411
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/Extents2D.cs
@@ -0,0 +1,18 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public struct Extents2D
+ {
+ public int X1 { get; }
+ public int Y1 { get; }
+ public int X2 { get; }
+ public int Y2 { get; }
+
+ public Extents2D(int x1, int y1, int x2, int y2)
+ {
+ X1 = x1;
+ Y1 = y1;
+ X2 = x2;
+ Y2 = y2;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/GalCullFace.cs b/Ryujinx.Graphics.GAL/Face.cs
similarity index 61%
rename from Ryujinx.Graphics/Gal/GalCullFace.cs
rename to Ryujinx.Graphics.GAL/Face.cs
index 4ab3e1742c..0587641fab 100644
--- a/Ryujinx.Graphics/Gal/GalCullFace.cs
+++ b/Ryujinx.Graphics.GAL/Face.cs
@@ -1,9 +1,9 @@
-namespace Ryujinx.Graphics.Gal
+namespace Ryujinx.Graphics.GAL
{
- public enum GalCullFace
+ public enum Face
{
Front = 0x404,
Back = 0x405,
FrontAndBack = 0x408
}
-}
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.GAL/Format.cs b/Ryujinx.Graphics.GAL/Format.cs
new file mode 100644
index 0000000000..0221746b32
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/Format.cs
@@ -0,0 +1,218 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum Format
+ {
+ R8Unorm,
+ R8Snorm,
+ R8Uint,
+ R8Sint,
+ R16Float,
+ R16Unorm,
+ R16Snorm,
+ R16Uint,
+ R16Sint,
+ R32Float,
+ R32Uint,
+ R32Sint,
+ R8G8Unorm,
+ R8G8Snorm,
+ R8G8Uint,
+ R8G8Sint,
+ R16G16Float,
+ R16G16Unorm,
+ R16G16Snorm,
+ R16G16Uint,
+ R16G16Sint,
+ R32G32Float,
+ R32G32Uint,
+ R32G32Sint,
+ R8G8B8Unorm,
+ R8G8B8Snorm,
+ R8G8B8Uint,
+ R8G8B8Sint,
+ R16G16B16Float,
+ R16G16B16Unorm,
+ R16G16B16Snorm,
+ R16G16B16Uint,
+ R16G16B16Sint,
+ R32G32B32Float,
+ R32G32B32Uint,
+ R32G32B32Sint,
+ R8G8B8A8Unorm,
+ R8G8B8A8Snorm,
+ R8G8B8A8Uint,
+ R8G8B8A8Sint,
+ R16G16B16A16Float,
+ R16G16B16A16Unorm,
+ R16G16B16A16Snorm,
+ R16G16B16A16Uint,
+ R16G16B16A16Sint,
+ R32G32B32A32Float,
+ R32G32B32A32Uint,
+ R32G32B32A32Sint,
+ S8Uint,
+ D16Unorm,
+ D24X8Unorm,
+ D32Float,
+ D24UnormS8Uint,
+ D32FloatS8Uint,
+ R8G8B8X8Srgb,
+ R8G8B8A8Srgb,
+ R4G4B4A4Unorm,
+ R5G5B5X1Unorm,
+ R5G5B5A1Unorm,
+ R5G6B5Unorm,
+ R10G10B10A2Unorm,
+ R10G10B10A2Uint,
+ R11G11B10Float,
+ R9G9B9E5Float,
+ Bc1RgbUnorm,
+ Bc1RgbaUnorm,
+ Bc2Unorm,
+ Bc3Unorm,
+ Bc1RgbSrgb,
+ Bc1RgbaSrgb,
+ Bc2Srgb,
+ Bc3Srgb,
+ Bc4Unorm,
+ Bc4Snorm,
+ Bc5Unorm,
+ Bc5Snorm,
+ Bc7Unorm,
+ Bc7Srgb,
+ Bc6HSfloat,
+ Bc6HUfloat,
+ R8Uscaled,
+ R8Sscaled,
+ R16Uscaled,
+ R16Sscaled,
+ R32Uscaled,
+ R32Sscaled,
+ R8G8Uscaled,
+ R8G8Sscaled,
+ R16G16Uscaled,
+ R16G16Sscaled,
+ R32G32Uscaled,
+ R32G32Sscaled,
+ R8G8B8Uscaled,
+ R8G8B8Sscaled,
+ R16G16B16Uscaled,
+ R16G16B16Sscaled,
+ R32G32B32Uscaled,
+ R32G32B32Sscaled,
+ R8G8B8A8Uscaled,
+ R8G8B8A8Sscaled,
+ R16G16B16A16Uscaled,
+ R16G16B16A16Sscaled,
+ R32G32B32A32Uscaled,
+ R32G32B32A32Sscaled,
+ R10G10B10A2Snorm,
+ R10G10B10A2Sint,
+ R10G10B10A2Uscaled,
+ R10G10B10A2Sscaled,
+ R8G8B8X8Unorm,
+ R8G8B8X8Snorm,
+ R8G8B8X8Uint,
+ R8G8B8X8Sint,
+ R16G16B16X16Float,
+ R16G16B16X16Unorm,
+ R16G16B16X16Snorm,
+ R16G16B16X16Uint,
+ R16G16B16X16Sint,
+ R32G32B32X32Float,
+ R32G32B32X32Uint,
+ R32G32B32X32Sint,
+ Astc4x4Unorm,
+ Astc5x4Unorm,
+ Astc5x5Unorm,
+ Astc6x5Unorm,
+ Astc6x6Unorm,
+ Astc8x5Unorm,
+ Astc8x6Unorm,
+ Astc8x8Unorm,
+ Astc10x5Unorm,
+ Astc10x6Unorm,
+ Astc10x8Unorm,
+ Astc10x10Unorm,
+ Astc12x10Unorm,
+ Astc12x12Unorm,
+ Astc4x4Srgb,
+ Astc5x4Srgb,
+ Astc5x5Srgb,
+ Astc6x5Srgb,
+ Astc6x6Srgb,
+ Astc8x5Srgb,
+ Astc8x6Srgb,
+ Astc8x8Srgb,
+ Astc10x5Srgb,
+ Astc10x6Srgb,
+ Astc10x8Srgb,
+ Astc10x10Srgb,
+ Astc12x10Srgb,
+ Astc12x12Srgb,
+ B5G6R5Unorm,
+ B5G5R5X1Unorm,
+ B5G5R5A1Unorm,
+ A1B5G5R5Unorm,
+ B8G8R8X8Unorm,
+ B8G8R8A8Unorm,
+ B8G8R8X8Srgb,
+ B8G8R8A8Srgb
+ }
+
+ public static class FormatExtensions
+ {
+ public static bool IsAstc(this Format format)
+ {
+ return format.IsAstcUnorm() || format.IsAstcSrgb();
+ }
+
+ public static bool IsAstcUnorm(this Format format)
+ {
+ switch (format)
+ {
+ case Format.Astc4x4Unorm:
+ case Format.Astc5x4Unorm:
+ case Format.Astc5x5Unorm:
+ case Format.Astc6x5Unorm:
+ case Format.Astc6x6Unorm:
+ case Format.Astc8x5Unorm:
+ case Format.Astc8x6Unorm:
+ case Format.Astc8x8Unorm:
+ case Format.Astc10x5Unorm:
+ case Format.Astc10x6Unorm:
+ case Format.Astc10x8Unorm:
+ case Format.Astc10x10Unorm:
+ case Format.Astc12x10Unorm:
+ case Format.Astc12x12Unorm:
+ return true;
+ }
+
+ return false;
+ }
+
+ public static bool IsAstcSrgb(this Format format)
+ {
+ switch (format)
+ {
+ case Format.Astc4x4Srgb:
+ case Format.Astc5x4Srgb:
+ case Format.Astc5x5Srgb:
+ case Format.Astc6x5Srgb:
+ case Format.Astc6x6Srgb:
+ case Format.Astc8x5Srgb:
+ case Format.Astc8x6Srgb:
+ case Format.Astc8x8Srgb:
+ case Format.Astc10x5Srgb:
+ case Format.Astc10x6Srgb:
+ case Format.Astc10x8Srgb:
+ case Format.Astc10x10Srgb:
+ case Format.Astc12x10Srgb:
+ case Format.Astc12x12Srgb:
+ return true;
+ }
+
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.GAL/FrontFace.cs b/Ryujinx.Graphics.GAL/FrontFace.cs
new file mode 100644
index 0000000000..aa6bfdc5b7
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/FrontFace.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum FrontFace
+ {
+ Clockwise = 0x900,
+ CounterClockwise = 0x901
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.GAL/IBuffer.cs b/Ryujinx.Graphics.GAL/IBuffer.cs
new file mode 100644
index 0000000000..000efd67aa
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/IBuffer.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Ryujinx.Graphics.GAL
+{
+ public interface IBuffer : IDisposable
+ {
+ void CopyTo(IBuffer destination, int srcOffset, int dstOffset, int size);
+
+ byte[] GetData(int offset, int size);
+
+ void SetData(Span data);
+
+ void SetData(int offset, Span data);
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.GAL/IPipeline.cs b/Ryujinx.Graphics.GAL/IPipeline.cs
new file mode 100644
index 0000000000..1a502913b7
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/IPipeline.cs
@@ -0,0 +1,71 @@
+using Ryujinx.Graphics.Shader;
+
+namespace Ryujinx.Graphics.GAL
+{
+ public interface IPipeline
+ {
+ void ClearRenderTargetColor(int index, uint componentMask, ColorF color);
+
+ void ClearRenderTargetDepthStencil(
+ float depthValue,
+ bool depthMask,
+ int stencilValue,
+ int stencilMask);
+
+ void DispatchCompute(int groupsX, int groupsY, int groupsZ);
+
+ void Draw(int vertexCount, int instanceCount, int firstVertex, int firstInstance);
+ void DrawIndexed(
+ int indexCount,
+ int instanceCount,
+ int firstIndex,
+ int firstVertex,
+ int firstInstance);
+
+ void SetBlendState(int index, BlendDescriptor blend);
+
+ void SetBlendColor(ColorF color);
+
+ void SetDepthBias(PolygonModeMask enables, float factor, float units, float clamp);
+
+ void SetDepthMode(DepthMode mode);
+
+ void SetDepthTest(DepthTestDescriptor depthTest);
+
+ void SetFaceCulling(bool enable, Face face);
+
+ void SetFrontFace(FrontFace frontFace);
+
+ void SetIndexBuffer(BufferRange buffer, IndexType type);
+
+ void SetImage(int index, ShaderStage stage, ITexture texture);
+
+ void SetPrimitiveRestart(bool enable, int index);
+
+ void SetPrimitiveTopology(PrimitiveTopology topology);
+
+ void SetProgram(IProgram program);
+
+ void SetRenderTargetColorMasks(uint[] componentMask);
+
+ void SetRenderTargets(ITexture[] colors, ITexture depthStencil);
+
+ void SetSampler(int index, ShaderStage stage, ISampler sampler);
+
+ void SetStencilTest(StencilTestDescriptor stencilTest);
+
+ void SetStorageBuffer(int index, ShaderStage stage, BufferRange buffer);
+
+ void SetTexture(int index, ShaderStage stage, ITexture texture);
+
+ void SetUniformBuffer(int index, ShaderStage stage, BufferRange buffer);
+
+ void SetVertexAttribs(VertexAttribDescriptor[] vertexAttribs);
+ void SetVertexBuffers(VertexBufferDescriptor[] vertexBuffers);
+
+ void SetViewports(int first, Viewport[] viewports);
+
+ void TextureBarrier();
+ void TextureBarrierTiled();
+ }
+}
diff --git a/Ryujinx.Graphics.GAL/IProgram.cs b/Ryujinx.Graphics.GAL/IProgram.cs
new file mode 100644
index 0000000000..ef44fc47eb
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/IProgram.cs
@@ -0,0 +1,6 @@
+using System;
+
+namespace Ryujinx.Graphics.GAL
+{
+ public interface IProgram : IDisposable { }
+}
diff --git a/Ryujinx.Graphics.GAL/IRenderer.cs b/Ryujinx.Graphics.GAL/IRenderer.cs
new file mode 100644
index 0000000000..1139ba0609
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/IRenderer.cs
@@ -0,0 +1,31 @@
+using Ryujinx.Graphics.Shader;
+using System;
+
+namespace Ryujinx.Graphics.GAL
+{
+ public interface IRenderer : IDisposable
+ {
+ IPipeline Pipeline { get; }
+
+ IWindow Window { get; }
+
+ IShader CompileShader(ShaderProgram shader);
+
+ IBuffer CreateBuffer(int size);
+
+ IProgram CreateProgram(IShader[] shaders);
+
+ ISampler CreateSampler(SamplerCreateInfo info);
+ ITexture CreateTexture(TextureCreateInfo info);
+
+ void FlushPipelines();
+
+ Capabilities GetCapabilities();
+
+ ulong GetCounter(CounterType type);
+
+ void Initialize();
+
+ void ResetCounter(CounterType type);
+ }
+}
diff --git a/Ryujinx.Graphics.GAL/ISampler.cs b/Ryujinx.Graphics.GAL/ISampler.cs
new file mode 100644
index 0000000000..3aefc6a780
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/ISampler.cs
@@ -0,0 +1,6 @@
+using System;
+
+namespace Ryujinx.Graphics.GAL
+{
+ public interface ISampler : IDisposable { }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.GAL/IShader.cs b/Ryujinx.Graphics.GAL/IShader.cs
new file mode 100644
index 0000000000..be24adcdaa
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/IShader.cs
@@ -0,0 +1,6 @@
+using System;
+
+namespace Ryujinx.Graphics.GAL
+{
+ public interface IShader : IDisposable { }
+}
diff --git a/Ryujinx.Graphics.GAL/ITexture.cs b/Ryujinx.Graphics.GAL/ITexture.cs
new file mode 100644
index 0000000000..f5bc1b4705
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/ITexture.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace Ryujinx.Graphics.GAL
+{
+ public interface ITexture : IDisposable
+ {
+ void CopyTo(ITexture destination, int firstLayer, int firstLevel);
+ void CopyTo(ITexture destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter);
+
+ ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel);
+
+ byte[] GetData();
+
+ void SetData(Span data);
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.GAL/IWindow.cs b/Ryujinx.Graphics.GAL/IWindow.cs
new file mode 100644
index 0000000000..369f7b9a67
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/IWindow.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public interface IWindow
+ {
+ void Present(ITexture texture, ImageCrop crop);
+
+ void SetSize(int width, int height);
+ }
+}
diff --git a/Ryujinx.Graphics.GAL/ImageCrop.cs b/Ryujinx.Graphics.GAL/ImageCrop.cs
new file mode 100644
index 0000000000..4428eee972
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/ImageCrop.cs
@@ -0,0 +1,28 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public struct ImageCrop
+ {
+ public int Left { get; }
+ public int Right { get; }
+ public int Top { get; }
+ public int Bottom { get; }
+ public bool FlipX { get; }
+ public bool FlipY { get; }
+
+ public ImageCrop(
+ int left,
+ int right,
+ int top,
+ int bottom,
+ bool flipX,
+ bool flipY)
+ {
+ Left = left;
+ Right = right;
+ Top = top;
+ Bottom = bottom;
+ FlipX = flipX;
+ FlipY = flipY;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.GAL/IndexType.cs b/Ryujinx.Graphics.GAL/IndexType.cs
new file mode 100644
index 0000000000..4abf28d9c4
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/IndexType.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum IndexType
+ {
+ UByte,
+ UShort,
+ UInt
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.GAL/MagFilter.cs b/Ryujinx.Graphics.GAL/MagFilter.cs
new file mode 100644
index 0000000000..f20d095e7e
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/MagFilter.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum MagFilter
+ {
+ Nearest = 1,
+ Linear
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.GAL/MinFilter.cs b/Ryujinx.Graphics.GAL/MinFilter.cs
new file mode 100644
index 0000000000..b7a0740be9
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/MinFilter.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum MinFilter
+ {
+ Nearest = 1,
+ Linear,
+ NearestMipmapNearest,
+ LinearMipmapNearest,
+ NearestMipmapLinear,
+ LinearMipmapLinear
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.GAL/PolygonModeMask.cs b/Ryujinx.Graphics.GAL/PolygonModeMask.cs
new file mode 100644
index 0000000000..514b423172
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/PolygonModeMask.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Ryujinx.Graphics.GAL
+{
+ [Flags]
+ public enum PolygonModeMask
+ {
+ Point = 1 << 0,
+ Line = 1 << 1,
+ Fill = 1 << 2
+ }
+}
diff --git a/Ryujinx.Graphics.GAL/PrimitiveTopology.cs b/Ryujinx.Graphics.GAL/PrimitiveTopology.cs
new file mode 100644
index 0000000000..ed567a68a8
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/PrimitiveTopology.cs
@@ -0,0 +1,21 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum PrimitiveTopology
+ {
+ Points,
+ Lines,
+ LineLoop,
+ LineStrip,
+ Triangles,
+ TriangleStrip,
+ TriangleFan,
+ Quads,
+ QuadStrip,
+ Polygon,
+ LinesAdjacency,
+ LineStripAdjacency,
+ TrianglesAdjacency,
+ TriangleStripAdjacency,
+ Patches
+ }
+}
diff --git a/Ryujinx.Graphics.GAL/RectangleF.cs b/Ryujinx.Graphics.GAL/RectangleF.cs
new file mode 100644
index 0000000000..c58aabf0e8
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/RectangleF.cs
@@ -0,0 +1,18 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public struct RectangleF
+ {
+ public float X { get; }
+ public float Y { get; }
+ public float Width { get; }
+ public float Height { get; }
+
+ public RectangleF(float x, float y, float width, float height)
+ {
+ X = x;
+ Y = y;
+ Width = width;
+ Height = height;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.GAL/Ryujinx.Graphics.GAL.csproj b/Ryujinx.Graphics.GAL/Ryujinx.Graphics.GAL.csproj
new file mode 100644
index 0000000000..3f2df4092e
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/Ryujinx.Graphics.GAL.csproj
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+ netcoreapp3.0
+ win-x64;osx-x64;linux-x64
+
+
+
diff --git a/Ryujinx.Graphics.GAL/SamplerCreateInfo.cs b/Ryujinx.Graphics.GAL/SamplerCreateInfo.cs
new file mode 100644
index 0000000000..33d80af5c6
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/SamplerCreateInfo.cs
@@ -0,0 +1,50 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public struct SamplerCreateInfo
+ {
+ public MinFilter MinFilter { get; }
+ public MagFilter MagFilter { get; }
+
+ public AddressMode AddressU { get; }
+ public AddressMode AddressV { get; }
+ public AddressMode AddressP { get; }
+
+ public CompareMode CompareMode { get; }
+ public CompareOp CompareOp { get; }
+
+ public ColorF BorderColor { get; }
+
+ public float MinLod { get; }
+ public float MaxLod { get; }
+ public float MipLodBias { get; }
+ public float MaxAnisotropy { get; }
+
+ public SamplerCreateInfo(
+ MinFilter minFilter,
+ MagFilter magFilter,
+ AddressMode addressU,
+ AddressMode addressV,
+ AddressMode addressP,
+ CompareMode compareMode,
+ CompareOp compareOp,
+ ColorF borderColor,
+ float minLod,
+ float maxLod,
+ float mipLodBias,
+ float maxAnisotropy)
+ {
+ MinFilter = minFilter;
+ MagFilter = magFilter;
+ AddressU = addressU;
+ AddressV = addressV;
+ AddressP = addressP;
+ CompareMode = compareMode;
+ CompareOp = compareOp;
+ BorderColor = borderColor;
+ MinLod = minLod;
+ MaxLod = maxLod;
+ MipLodBias = mipLodBias;
+ MaxAnisotropy = maxAnisotropy;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.GAL/StencilOp.cs b/Ryujinx.Graphics.GAL/StencilOp.cs
new file mode 100644
index 0000000000..f0ac829e67
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/StencilOp.cs
@@ -0,0 +1,14 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum StencilOp
+ {
+ Keep = 1,
+ Zero,
+ Replace,
+ IncrementAndClamp,
+ DecrementAndClamp,
+ Invert,
+ IncrementAndWrap,
+ DecrementAndWrap
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.GAL/StencilTestDescriptor.cs b/Ryujinx.Graphics.GAL/StencilTestDescriptor.cs
new file mode 100644
index 0000000000..8c9d1644aa
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/StencilTestDescriptor.cs
@@ -0,0 +1,56 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public struct StencilTestDescriptor
+ {
+ public bool TestEnable { get; }
+
+ public CompareOp FrontFunc { get; }
+ public StencilOp FrontSFail { get; }
+ public StencilOp FrontDpPass { get; }
+ public StencilOp FrontDpFail { get; }
+ public int FrontFuncRef { get; }
+ public int FrontFuncMask { get; }
+ public int FrontMask { get; }
+ public CompareOp BackFunc { get; }
+ public StencilOp BackSFail { get; }
+ public StencilOp BackDpPass { get; }
+ public StencilOp BackDpFail { get; }
+ public int BackFuncRef { get; }
+ public int BackFuncMask { get; }
+ public int BackMask { get; }
+
+ public StencilTestDescriptor(
+ bool testEnable,
+ CompareOp frontFunc,
+ StencilOp frontSFail,
+ StencilOp frontDpPass,
+ StencilOp frontDpFail,
+ int frontFuncRef,
+ int frontFuncMask,
+ int frontMask,
+ CompareOp backFunc,
+ StencilOp backSFail,
+ StencilOp backDpPass,
+ StencilOp backDpFail,
+ int backFuncRef,
+ int backFuncMask,
+ int backMask)
+ {
+ TestEnable = testEnable;
+ FrontFunc = frontFunc;
+ FrontSFail = frontSFail;
+ FrontDpPass = frontDpPass;
+ FrontDpFail = frontDpFail;
+ FrontFuncRef = frontFuncRef;
+ FrontFuncMask = frontFuncMask;
+ FrontMask = frontMask;
+ BackFunc = backFunc;
+ BackSFail = backSFail;
+ BackDpPass = backDpPass;
+ BackDpFail = backDpFail;
+ BackFuncRef = backFuncRef;
+ BackFuncMask = backFuncMask;
+ BackMask = backMask;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.GAL/SwizzleComponent.cs b/Ryujinx.Graphics.GAL/SwizzleComponent.cs
new file mode 100644
index 0000000000..a405bd139b
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/SwizzleComponent.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum SwizzleComponent
+ {
+ Zero,
+ One,
+ Red,
+ Green,
+ Blue,
+ Alpha
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.GAL/Target.cs b/Ryujinx.Graphics.GAL/Target.cs
new file mode 100644
index 0000000000..73f77f4370
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/Target.cs
@@ -0,0 +1,17 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum Target
+ {
+ Texture1D,
+ Texture2D,
+ Texture3D,
+ Texture1DArray,
+ Texture2DArray,
+ Texture2DMultisample,
+ Texture2DMultisampleArray,
+ Rectangle,
+ Cubemap,
+ CubemapArray,
+ TextureBuffer
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.GAL/TextureCreateInfo.cs b/Ryujinx.Graphics.GAL/TextureCreateInfo.cs
new file mode 100644
index 0000000000..d74ac62dd2
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/TextureCreateInfo.cs
@@ -0,0 +1,120 @@
+using Ryujinx.Common;
+using System;
+
+namespace Ryujinx.Graphics.GAL
+{
+ public struct TextureCreateInfo
+ {
+ public int Width { get; }
+ public int Height { get; }
+ public int Depth { get; }
+ public int Levels { get; }
+ public int Samples { get; }
+ public int BlockWidth { get; }
+ public int BlockHeight { get; }
+ public int BytesPerPixel { get; }
+
+ public bool IsCompressed => (BlockWidth | BlockHeight) != 1;
+
+ public Format Format { get; }
+
+ public DepthStencilMode DepthStencilMode { get; }
+
+ public Target Target { get; }
+
+ public SwizzleComponent SwizzleR { get; }
+ public SwizzleComponent SwizzleG { get; }
+ public SwizzleComponent SwizzleB { get; }
+ public SwizzleComponent SwizzleA { get; }
+
+ public TextureCreateInfo(
+ int width,
+ int height,
+ int depth,
+ int levels,
+ int samples,
+ int blockWidth,
+ int blockHeight,
+ int bytesPerPixel,
+ Format format,
+ DepthStencilMode depthStencilMode,
+ Target target,
+ SwizzleComponent swizzleR,
+ SwizzleComponent swizzleG,
+ SwizzleComponent swizzleB,
+ SwizzleComponent swizzleA)
+ {
+ Width = width;
+ Height = height;
+ Depth = depth;
+ Levels = levels;
+ Samples = samples;
+ BlockWidth = blockWidth;
+ BlockHeight = blockHeight;
+ BytesPerPixel = bytesPerPixel;
+ Format = format;
+ DepthStencilMode = depthStencilMode;
+ Target = target;
+ SwizzleR = swizzleR;
+ SwizzleG = swizzleG;
+ SwizzleB = swizzleB;
+ SwizzleA = swizzleA;
+ }
+
+ public readonly int GetMipSize(int level)
+ {
+ return GetMipStride(level) * GetLevelHeight(level) * GetLevelDepth(level);
+ }
+
+ public readonly int GetMipSize2D(int level)
+ {
+ return GetMipStride(level) * GetLevelHeight(level);
+ }
+
+ public readonly int GetMipStride(int level)
+ {
+ return BitUtils.AlignUp(GetLevelWidth(level) * BytesPerPixel, 4);
+ }
+
+ private readonly int GetLevelWidth(int level)
+ {
+ return BitUtils.DivRoundUp(GetLevelSize(Width, level), BlockWidth);
+ }
+
+ private readonly int GetLevelHeight(int level)
+ {
+ return BitUtils.DivRoundUp(GetLevelSize(Height, level), BlockHeight);
+ }
+
+ private readonly int GetLevelDepth(int level)
+ {
+ return Target == Target.Texture3D ? GetLevelSize(Depth, level) : GetLayers();
+ }
+
+ public readonly int GetDepthOrLayers()
+ {
+ return Target == Target.Texture3D ? Depth : GetLayers();
+ }
+
+ public readonly int GetLayers()
+ {
+ if (Target == Target.Texture2DArray ||
+ Target == Target.Texture2DMultisampleArray ||
+ Target == Target.CubemapArray)
+ {
+ return Depth;
+ }
+ else if (Target == Target.Cubemap)
+ {
+ return 6;
+ }
+
+ return 1;
+ }
+
+ private static int GetLevelSize(int size, int level)
+ {
+ return Math.Max(1, size >> level);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.GAL/TextureReleaseCallback.cs b/Ryujinx.Graphics.GAL/TextureReleaseCallback.cs
new file mode 100644
index 0000000000..c058df2bb2
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/TextureReleaseCallback.cs
@@ -0,0 +1,4 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public delegate void TextureReleaseCallback(object context);
+}
diff --git a/Ryujinx.Graphics.GAL/VertexAttribDescriptor.cs b/Ryujinx.Graphics.GAL/VertexAttribDescriptor.cs
new file mode 100644
index 0000000000..34daff57b6
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/VertexAttribDescriptor.cs
@@ -0,0 +1,17 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public struct VertexAttribDescriptor
+ {
+ public int BufferIndex { get; }
+ public int Offset { get; }
+
+ public Format Format { get; }
+
+ public VertexAttribDescriptor(int bufferIndex, int offset, Format format)
+ {
+ BufferIndex = bufferIndex;
+ Offset = offset;
+ Format = format;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.GAL/VertexBufferDescriptor.cs b/Ryujinx.Graphics.GAL/VertexBufferDescriptor.cs
new file mode 100644
index 0000000000..bcd3b28f5d
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/VertexBufferDescriptor.cs
@@ -0,0 +1,17 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public struct VertexBufferDescriptor
+ {
+ public BufferRange Buffer { get; }
+
+ public int Stride { get; }
+ public int Divisor { get; }
+
+ public VertexBufferDescriptor(BufferRange buffer, int stride, int divisor)
+ {
+ Buffer = buffer;
+ Stride = stride;
+ Divisor = divisor;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.GAL/Viewport.cs b/Ryujinx.Graphics.GAL/Viewport.cs
new file mode 100644
index 0000000000..d9d6e20a42
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/Viewport.cs
@@ -0,0 +1,33 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public struct Viewport
+ {
+ public RectangleF Region { get; }
+
+ public ViewportSwizzle SwizzleX { get; }
+ public ViewportSwizzle SwizzleY { get; }
+ public ViewportSwizzle SwizzleZ { get; }
+ public ViewportSwizzle SwizzleW { get; }
+
+ public float DepthNear { get; }
+ public float DepthFar { get; }
+
+ public Viewport(
+ RectangleF region,
+ ViewportSwizzle swizzleX,
+ ViewportSwizzle swizzleY,
+ ViewportSwizzle swizzleZ,
+ ViewportSwizzle swizzleW,
+ float depthNear,
+ float depthFar)
+ {
+ Region = region;
+ SwizzleX = swizzleX;
+ SwizzleY = swizzleY;
+ SwizzleZ = swizzleZ;
+ SwizzleW = swizzleW;
+ DepthNear = depthNear;
+ DepthFar = depthFar;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.GAL/ViewportSwizzle.cs b/Ryujinx.Graphics.GAL/ViewportSwizzle.cs
new file mode 100644
index 0000000000..5f04bf87d3
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/ViewportSwizzle.cs
@@ -0,0 +1,14 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum ViewportSwizzle
+ {
+ PositiveX,
+ NegativeX,
+ PositiveY,
+ NegativeY,
+ PositiveZ,
+ NegativeZ,
+ PositiveW,
+ NegativeW
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/ClassId.cs b/Ryujinx.Graphics.Gpu/ClassId.cs
new file mode 100644
index 0000000000..be4ebe4b68
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/ClassId.cs
@@ -0,0 +1,14 @@
+namespace Ryujinx.Graphics.Gpu
+{
+ ///
+ /// GPU engine class ID.
+ ///
+ enum ClassId
+ {
+ Engine2D = 0x902d,
+ Engine3D = 0xb197,
+ EngineCompute = 0xb1c0,
+ EngineInline2Memory = 0xa140,
+ EngineDma = 0xb0b5
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Constants.cs b/Ryujinx.Graphics.Gpu/Constants.cs
new file mode 100644
index 0000000000..65cd8846d4
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Constants.cs
@@ -0,0 +1,48 @@
+namespace Ryujinx.Graphics.Gpu
+{
+ ///
+ /// Common Maxwell GPU constants.
+ ///
+ static class Constants
+ {
+ ///
+ /// Maximum number of compute uniform buffers.
+ ///
+ public const int TotalCpUniformBuffers = 8;
+
+ ///
+ /// Maximum number of compute storage buffers (this is an API limitation).
+ ///
+ public const int TotalCpStorageBuffers = 16;
+
+ ///
+ /// Maximum number of graphics uniform buffers.
+ ///
+ public const int TotalGpUniformBuffers = 18;
+
+ ///
+ /// Maximum number of graphics storage buffers (this is an API limitation).
+ ///
+ public const int TotalGpStorageBuffers = 16;
+
+ ///
+ /// Maximum number of render target color buffers.
+ ///
+ public const int TotalRenderTargets = 8;
+
+ ///
+ /// Number of shader stages.
+ ///
+ public const int ShaderStages = 5;
+
+ ///
+ /// Maximum number of vertex buffers.
+ ///
+ public const int TotalVertexBuffers = 16;
+
+ ///
+ /// Maximum number of viewports.
+ ///
+ public const int TotalViewports = 8;
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/DmaPusher.cs b/Ryujinx.Graphics.Gpu/DmaPusher.cs
similarity index 66%
rename from Ryujinx.Graphics/DmaPusher.cs
rename to Ryujinx.Graphics.Gpu/DmaPusher.cs
index 74a32a4a20..1c85686a52 100644
--- a/Ryujinx.Graphics/DmaPusher.cs
+++ b/Ryujinx.Graphics.Gpu/DmaPusher.cs
@@ -1,16 +1,21 @@
-using Ryujinx.Graphics.Memory;
using System.Collections.Concurrent;
using System.Threading;
-namespace Ryujinx.Graphics
+namespace Ryujinx.Graphics.Gpu
{
+ ///
+ /// GPU DMA pusher, used to push commands to the GPU.
+ ///
public class DmaPusher
{
- private ConcurrentQueue<(NvGpuVmm, long)> _ibBuffer;
+ private ConcurrentQueue _ibBuffer;
- private long _dmaPut;
- private long _dmaGet;
+ private ulong _dmaPut;
+ private ulong _dmaGet;
+ ///
+ /// Internal GPFIFO state.
+ ///
private struct DmaState
{
public int Method;
@@ -29,47 +34,64 @@ namespace Ryujinx.Graphics
private bool _ibEnable;
private bool _nonMain;
- private long _dmaMGet;
+ private ulong _dmaMGet;
- private NvGpuVmm _vmm;
-
- private NvGpu _gpu;
+ private GpuContext _context;
private AutoResetEvent _event;
- public DmaPusher(NvGpu gpu)
+ ///
+ /// Creates a new instance of the GPU DMA pusher.
+ ///
+ /// GPU context that the pusher belongs to
+ internal DmaPusher(GpuContext context)
{
- _gpu = gpu;
+ _context = context;
- _ibBuffer = new ConcurrentQueue<(NvGpuVmm, long)>();
+ _ibBuffer = new ConcurrentQueue();
_ibEnable = true;
_event = new AutoResetEvent(false);
}
- public void Push(NvGpuVmm vmm, long entry)
+ ///
+ /// Pushes a GPFIFO entry.
+ ///
+ /// GPFIFO entry
+ public void Push(ulong entry)
{
- _ibBuffer.Enqueue((vmm, entry));
+ _ibBuffer.Enqueue(entry);
_event.Set();
}
+ ///
+ /// Waits until commands are pushed to the FIFO.
+ ///
+ /// True if commands were received, false if wait timed out
public bool WaitForCommands()
{
return _event.WaitOne(8);
}
+ ///
+ /// Processes commands pushed to the FIFO.
+ ///
public void DispatchCalls()
{
while (Step());
}
+ ///
+ /// Processes a single command on the FIFO.
+ ///
+ /// True if the FIFO still has commands to be processed, false otherwise
private bool Step()
{
if (_dmaGet != _dmaPut)
{
- int word = _vmm.ReadInt32(_dmaGet);
+ int word = _context.MemoryAccessor.ReadInt32(_dmaGet);
_dmaGet += 4;
@@ -148,20 +170,14 @@ namespace Ryujinx.Graphics
}
}
}
- else if (_ibEnable && _ibBuffer.TryDequeue(out (NvGpuVmm Vmm, long Entry) tuple))
+ else if (_ibEnable && _ibBuffer.TryDequeue(out ulong entry))
{
- _vmm = tuple.Vmm;
-
- long entry = tuple.Entry;
-
- int length = (int)(entry >> 42) & 0x1fffff;
+ ulong length = (entry >> 42) & 0x1fffff;
_dmaGet = entry & 0xfffffffffc;
_dmaPut = _dmaGet + length * 4;
- _nonMain = (entry & (1L << 41)) != 0;
-
- _gpu.ResourceManager.ClearPbCache();
+ _nonMain = (entry & (1UL << 41)) != 0;
}
else
{
@@ -171,6 +187,10 @@ namespace Ryujinx.Graphics
return true;
}
+ ///
+ /// Sets current non-immediate method call state.
+ ///
+ /// Compressed method word
private void SetNonImmediateState(int word)
{
_state.Method = (word >> 0) & 0x1fff;
@@ -178,9 +198,13 @@ namespace Ryujinx.Graphics
_state.MethodCount = (word >> 16) & 0x1fff;
}
+ ///
+ /// Forwards the method call to GPU engines.
+ ///
+ /// Call argument
private void CallMethod(int argument)
{
- _gpu.Fifo.CallMethod(_vmm, new GpuMethodCall(
+ _context.Fifo.CallMethod(new MethodParams(
_state.Method,
argument,
_state.SubChannel,
diff --git a/Ryujinx.Graphics.Gpu/Engine/Compute.cs b/Ryujinx.Graphics.Gpu/Engine/Compute.cs
new file mode 100644
index 0000000000..1f52467126
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Engine/Compute.cs
@@ -0,0 +1,141 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.Image;
+using Ryujinx.Graphics.Gpu.Shader;
+using Ryujinx.Graphics.Gpu.State;
+using Ryujinx.Graphics.Shader;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+ partial class Methods
+ {
+ ///
+ /// Dispatches compute work.
+ ///
+ /// Current GPU state
+ /// Method call argument
+ public void Dispatch(GpuState state, int argument)
+ {
+ uint dispatchParamsAddress = (uint)state.Get(MethodOffset.DispatchParamsAddress);
+
+ var dispatchParams = _context.MemoryAccessor.Read((ulong)dispatchParamsAddress << 8);
+
+ GpuVa shaderBaseAddress = state.Get(MethodOffset.ShaderBaseAddress);
+
+ ulong shaderGpuVa = shaderBaseAddress.Pack() + (uint)dispatchParams.ShaderOffset;
+
+ // Note: A size of 0 is also invalid, the size must be at least 1.
+ int sharedMemorySize = Math.Clamp(dispatchParams.SharedMemorySize & 0xffff, 1, _context.Capabilities.MaximumComputeSharedMemorySize);
+
+ ComputeShader cs = ShaderCache.GetComputeShader(
+ shaderGpuVa,
+ sharedMemorySize,
+ dispatchParams.UnpackBlockSizeX(),
+ dispatchParams.UnpackBlockSizeY(),
+ dispatchParams.UnpackBlockSizeZ());
+
+ _context.Renderer.Pipeline.SetProgram(cs.HostProgram);
+
+ var samplerPool = state.Get(MethodOffset.SamplerPoolState);
+
+ TextureManager.SetComputeSamplerPool(samplerPool.Address.Pack(), samplerPool.MaximumId, dispatchParams.SamplerIndex);
+
+ var texturePool = state.Get(MethodOffset.TexturePoolState);
+
+ TextureManager.SetComputeTexturePool(texturePool.Address.Pack(), texturePool.MaximumId);
+
+ TextureManager.SetComputeTextureBufferIndex(state.Get(MethodOffset.TextureBufferIndex));
+
+ ShaderProgramInfo info = cs.Shader.Program.Info;
+
+ uint sbEnableMask = 0;
+ uint ubEnableMask = dispatchParams.UnpackUniformBuffersEnableMask();
+
+ for (int index = 0; index < dispatchParams.UniformBuffers.Length; index++)
+ {
+ if ((ubEnableMask & (1 << index)) == 0)
+ {
+ continue;
+ }
+
+ ulong gpuVa = dispatchParams.UniformBuffers[index].PackAddress();
+ ulong size = dispatchParams.UniformBuffers[index].UnpackSize();
+
+ BufferManager.SetComputeUniformBuffer(index, gpuVa, size);
+ }
+
+ for (int index = 0; index < info.SBuffers.Count; index++)
+ {
+ BufferDescriptor sb = info.SBuffers[index];
+
+ sbEnableMask |= 1u << sb.Slot;
+
+ ulong sbDescAddress = BufferManager.GetComputeUniformBufferAddress(0);
+
+ int sbDescOffset = 0x310 + sb.Slot * 0x10;
+
+ sbDescAddress += (ulong)sbDescOffset;
+
+ Span sbDescriptorData = _context.PhysicalMemory.Read(sbDescAddress, 0x10);
+
+ SbDescriptor sbDescriptor = MemoryMarshal.Cast(sbDescriptorData)[0];
+
+ BufferManager.SetComputeStorageBuffer(sb.Slot, sbDescriptor.PackAddress(), (uint)sbDescriptor.Size);
+ }
+
+ ubEnableMask = 0;
+
+ for (int index = 0; index < info.CBuffers.Count; index++)
+ {
+ ubEnableMask |= 1u << info.CBuffers[index].Slot;
+ }
+
+ BufferManager.SetComputeStorageBufferEnableMask(sbEnableMask);
+ BufferManager.SetComputeUniformBufferEnableMask(ubEnableMask);
+
+ var textureBindings = new TextureBindingInfo[info.Textures.Count];
+
+ for (int index = 0; index < info.Textures.Count; index++)
+ {
+ var descriptor = info.Textures[index];
+
+ Target target = GetTarget(descriptor.Type);
+
+ if (descriptor.IsBindless)
+ {
+ textureBindings[index] = new TextureBindingInfo(target, descriptor.CbufOffset, descriptor.CbufSlot);
+ }
+ else
+ {
+ textureBindings[index] = new TextureBindingInfo(target, descriptor.HandleIndex);
+ }
+ }
+
+ TextureManager.SetComputeTextures(textureBindings);
+
+ var imageBindings = new TextureBindingInfo[info.Images.Count];
+
+ for (int index = 0; index < info.Images.Count; index++)
+ {
+ var descriptor = info.Images[index];
+
+ Target target = GetTarget(descriptor.Type);
+
+ imageBindings[index] = new TextureBindingInfo(target, descriptor.HandleIndex);
+ }
+
+ TextureManager.SetComputeImages(imageBindings);
+
+ BufferManager.CommitComputeBindings();
+ TextureManager.CommitComputeBindings();
+
+ _context.Renderer.Pipeline.DispatchCompute(
+ dispatchParams.UnpackGridSizeX(),
+ dispatchParams.UnpackGridSizeY(),
+ dispatchParams.UnpackGridSizeZ());
+
+ UpdateShaderState(state);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Engine/ComputeParams.cs b/Ryujinx.Graphics.Gpu/Engine/ComputeParams.cs
new file mode 100644
index 0000000000..c19b43d81e
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Engine/ComputeParams.cs
@@ -0,0 +1,173 @@
+using Ryujinx.Graphics.Gpu.State;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+ ///
+ /// Compute uniform buffer parameters.
+ ///
+ struct UniformBufferParams
+ {
+ public int AddressLow;
+ public int AddressHighAndSize;
+
+ ///
+ /// Packs the split address to a 64-bits integer.
+ ///
+ /// Uniform buffer GPU virtual address
+ public ulong PackAddress()
+ {
+ return (uint)AddressLow | ((ulong)(AddressHighAndSize & 0xff) << 32);
+ }
+
+ ///
+ /// Unpacks the uniform buffer size in bytes.
+ ///
+ /// Uniform buffer size in bytes
+ public ulong UnpackSize()
+ {
+ return (ulong)((AddressHighAndSize >> 15) & 0x1ffff);
+ }
+ }
+
+ ///
+ /// Compute dispatch parameters.
+ ///
+ struct ComputeParams
+ {
+ public int Unknown0;
+ public int Unknown1;
+ public int Unknown2;
+ public int Unknown3;
+ public int Unknown4;
+ public int Unknown5;
+ public int Unknown6;
+ public int Unknown7;
+ public int ShaderOffset;
+ public int Unknown9;
+ public int Unknown10;
+ public SamplerIndex SamplerIndex;
+ public int GridSizeX;
+ public int GridSizeYZ;
+ public int Unknown14;
+ public int Unknown15;
+ public int Unknown16;
+ public int SharedMemorySize;
+ public int BlockSizeX;
+ public int BlockSizeYZ;
+ public int UniformBuffersConfig;
+ public int Unknown21;
+ public int Unknown22;
+ public int Unknown23;
+ public int Unknown24;
+ public int Unknown25;
+ public int Unknown26;
+ public int Unknown27;
+ public int Unknown28;
+
+ private UniformBufferParams _uniformBuffer0;
+ private UniformBufferParams _uniformBuffer1;
+ private UniformBufferParams _uniformBuffer2;
+ private UniformBufferParams _uniformBuffer3;
+ private UniformBufferParams _uniformBuffer4;
+ private UniformBufferParams _uniformBuffer5;
+ private UniformBufferParams _uniformBuffer6;
+ private UniformBufferParams _uniformBuffer7;
+
+ ///
+ /// Uniform buffer parameters.
+ ///
+ public Span UniformBuffers
+ {
+ get
+ {
+ return MemoryMarshal.CreateSpan(ref _uniformBuffer0, 8);
+ }
+ }
+
+ public int Unknown45;
+ public int Unknown46;
+ public int Unknown47;
+ public int Unknown48;
+ public int Unknown49;
+ public int Unknown50;
+ public int Unknown51;
+ public int Unknown52;
+ public int Unknown53;
+ public int Unknown54;
+ public int Unknown55;
+ public int Unknown56;
+ public int Unknown57;
+ public int Unknown58;
+ public int Unknown59;
+ public int Unknown60;
+ public int Unknown61;
+ public int Unknown62;
+ public int Unknown63;
+
+ ///
+ /// Unpacks the work group X size.
+ ///
+ /// Work group X size
+ public int UnpackGridSizeX()
+ {
+ return GridSizeX & 0x7fffffff;
+ }
+
+ ///
+ /// Unpacks the work group Y size.
+ ///
+ /// Work group Y size
+ public int UnpackGridSizeY()
+ {
+ return GridSizeYZ & 0xffff;
+ }
+
+ ///
+ /// Unpacks the work group Z size.
+ ///
+ /// Work group Z size
+ public int UnpackGridSizeZ()
+ {
+ return (GridSizeYZ >> 16) & 0xffff;
+ }
+
+ ///
+ /// Unpacks the local group X size.
+ ///
+ /// Local group X size
+ public int UnpackBlockSizeX()
+ {
+ return (BlockSizeX >> 16) & 0xffff;
+ }
+
+ ///
+ /// Unpacks the local group Y size.
+ ///
+ /// Local group Y size
+ public int UnpackBlockSizeY()
+ {
+ return BlockSizeYZ & 0xffff;
+ }
+
+ ///
+ /// Unpacks the local group Z size.
+ ///
+ /// Local group Z size
+ public int UnpackBlockSizeZ()
+ {
+ return (BlockSizeYZ >> 16) & 0xffff;
+ }
+
+ ///
+ /// Unpacks the uniform buffers enable mask.
+ /// Each bit set on the mask indicates that the respective buffer index is enabled.
+ ///
+ /// Uniform buffers enable mask
+ public uint UnpackUniformBuffersEnableMask()
+ {
+ return (uint)UniformBuffersConfig & 0xff;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Engine/Inline2Memory.cs b/Ryujinx.Graphics.Gpu/Engine/Inline2Memory.cs
new file mode 100644
index 0000000000..7943239568
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Engine/Inline2Memory.cs
@@ -0,0 +1,130 @@
+using Ryujinx.Common;
+using Ryujinx.Graphics.Gpu.State;
+using Ryujinx.Graphics.Texture;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+ partial class Methods
+ {
+ private Inline2MemoryParams _params;
+
+ private bool _isLinear;
+
+ private int _offset;
+ private int _size;
+
+ private bool _finished;
+
+ private int[] _buffer;
+
+ ///
+ /// Launches Inline-to-Memory engine DMA copy.
+ ///
+ /// Current GPU state
+ /// Method call argument
+ public void LaunchDma(GpuState state, int argument)
+ {
+ _params = state.Get(MethodOffset.I2mParams);
+
+ _isLinear = (argument & 1) != 0;
+
+ _offset = 0;
+ _size = _params.LineLengthIn * _params.LineCount;
+
+ int count = BitUtils.DivRoundUp(_size, 4);
+
+ if (_buffer == null || _buffer.Length < count)
+ {
+ _buffer = new int[count];
+ }
+
+ ulong dstBaseAddress = _context.MemoryManager.Translate(_params.DstAddress.Pack());
+
+ _context.Methods.TextureManager.Flush(dstBaseAddress, (ulong)_size);
+
+ _finished = false;
+ }
+
+ ///
+ /// Pushes a word of data to the Inline-to-Memory engine.
+ ///
+ /// Current GPU state
+ /// Method call argument
+ public void LoadInlineData(GpuState state, int argument)
+ {
+ if (!_finished)
+ {
+ _buffer[_offset++] = argument;
+
+ if (_offset * 4 >= _size)
+ {
+ FinishTransfer();
+ }
+ }
+ }
+
+ ///
+ /// Performs actual copy of the inline data after the transfer is finished.
+ ///
+ private void FinishTransfer()
+ {
+ Span data = MemoryMarshal.Cast(_buffer).Slice(0, _size);
+
+ if (_isLinear && _params.LineCount == 1)
+ {
+ ulong address = _context.MemoryManager.Translate( _params.DstAddress.Pack());
+
+ _context.PhysicalMemory.Write(address, data);
+ }
+ else
+ {
+ var dstCalculator = new OffsetCalculator(
+ _params.DstWidth,
+ _params.DstHeight,
+ _params.DstStride,
+ _isLinear,
+ _params.DstMemoryLayout.UnpackGobBlocksInY(),
+ 1);
+
+ int srcOffset = 0;
+
+ ulong dstBaseAddress = _context.MemoryManager.Translate(_params.DstAddress.Pack());
+
+ for (int y = _params.DstY; y < _params.DstY + _params.LineCount; y++)
+ {
+ int x1 = _params.DstX;
+ int x2 = _params.DstX + _params.LineLengthIn;
+ int x2Trunc = _params.DstX + BitUtils.AlignDown(_params.LineLengthIn, 16);
+
+ int x;
+
+ for (x = x1; x < x2Trunc; x += 16, srcOffset += 16)
+ {
+ int dstOffset = dstCalculator.GetOffset(x, y);
+
+ ulong dstAddress = dstBaseAddress + (ulong)dstOffset;
+
+ Span pixel = data.Slice(srcOffset, 16);
+
+ _context.PhysicalMemory.Write(dstAddress, pixel);
+ }
+
+ for (; x < x2; x++, srcOffset++)
+ {
+ int dstOffset = dstCalculator.GetOffset(x, y);
+
+ ulong dstAddress = dstBaseAddress + (ulong)dstOffset;
+
+ Span pixel = data.Slice(srcOffset, 1);
+
+ _context.PhysicalMemory.Write(dstAddress, pixel);
+ }
+ }
+ }
+
+ _finished = true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodClear.cs b/Ryujinx.Graphics.Gpu/Engine/MethodClear.cs
new file mode 100644
index 0000000000..dfa7b12ed3
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Engine/MethodClear.cs
@@ -0,0 +1,62 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.State;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+ partial class Methods
+ {
+ ///
+ /// Clears the current color and depth-stencil buffers.
+ /// Which buffers should be cleared is also specified on the argument.
+ ///
+ /// Current GPU state
+ /// Method call argument
+ private void Clear(GpuState state, int argument)
+ {
+ UpdateRenderTargetState(state, useControl: false);
+
+ TextureManager.CommitGraphicsBindings();
+
+ bool clearDepth = (argument & 1) != 0;
+ bool clearStencil = (argument & 2) != 0;
+
+ uint componentMask = (uint)((argument >> 2) & 0xf);
+
+ int index = (argument >> 6) & 0xf;
+
+ if (componentMask != 0)
+ {
+ var clearColor = state.Get(MethodOffset.ClearColors);
+
+ ColorF color = new ColorF(
+ clearColor.Red,
+ clearColor.Green,
+ clearColor.Blue,
+ clearColor.Alpha);
+
+ _context.Renderer.Pipeline.ClearRenderTargetColor(index, componentMask, color);
+ }
+
+ if (clearDepth || clearStencil)
+ {
+ float depthValue = state.Get(MethodOffset.ClearDepthValue);
+ int stencilValue = state.Get (MethodOffset.ClearStencilValue);
+
+ int stencilMask = 0;
+
+ if (clearStencil)
+ {
+ stencilMask = state.Get(MethodOffset.StencilTestState).FrontMask;
+ }
+
+ _context.Renderer.Pipeline.ClearRenderTargetDepthStencil(
+ depthValue,
+ clearDepth,
+ stencilValue,
+ stencilMask);
+ }
+
+ UpdateRenderTargetState(state, useControl: true);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodCopyBuffer.cs b/Ryujinx.Graphics.Gpu/Engine/MethodCopyBuffer.cs
new file mode 100644
index 0000000000..6b6742ff10
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Engine/MethodCopyBuffer.cs
@@ -0,0 +1,80 @@
+using Ryujinx.Graphics.Gpu.State;
+using Ryujinx.Graphics.Texture;
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+ partial class Methods
+ {
+ ///
+ /// Performs a buffer to buffer, or buffer to texture copy.
+ ///
+ /// Current GPU state
+ /// Method call argument
+ private void CopyBuffer(GpuState state, int argument)
+ {
+ var cbp = state.Get(MethodOffset.CopyBufferParams);
+
+ var swizzle = state.Get(MethodOffset.CopyBufferSwizzle);
+
+ bool srcLinear = (argument & (1 << 7)) != 0;
+ bool dstLinear = (argument & (1 << 8)) != 0;
+ bool copy2D = (argument & (1 << 9)) != 0;
+
+ int size = cbp.XCount;
+
+ if (size == 0)
+ {
+ return;
+ }
+
+ if (copy2D)
+ {
+ // Buffer to texture copy.
+ int srcBpp = swizzle.UnpackSrcComponentsCount() * swizzle.UnpackComponentSize();
+ int dstBpp = swizzle.UnpackDstComponentsCount() * swizzle.UnpackComponentSize();
+
+ var dst = state.Get(MethodOffset.CopyBufferDstTexture);
+ var src = state.Get(MethodOffset.CopyBufferSrcTexture);
+
+ var srcCalculator = new OffsetCalculator(
+ src.Width,
+ src.Height,
+ cbp.SrcStride,
+ srcLinear,
+ src.MemoryLayout.UnpackGobBlocksInY(),
+ srcBpp);
+
+ var dstCalculator = new OffsetCalculator(
+ dst.Width,
+ dst.Height,
+ cbp.DstStride,
+ dstLinear,
+ dst.MemoryLayout.UnpackGobBlocksInY(),
+ dstBpp);
+
+ ulong srcBaseAddress = _context.MemoryManager.Translate(cbp.SrcAddress.Pack());
+ ulong dstBaseAddress = _context.MemoryManager.Translate(cbp.DstAddress.Pack());
+
+ for (int y = 0; y < cbp.YCount; y++)
+ for (int x = 0; x < cbp.XCount; x++)
+ {
+ int srcOffset = srcCalculator.GetOffset(src.RegionX + x, src.RegionY + y);
+ int dstOffset = dstCalculator.GetOffset(dst.RegionX + x, dst.RegionY + y);
+
+ ulong srcAddress = srcBaseAddress + (ulong)srcOffset;
+ ulong dstAddress = dstBaseAddress + (ulong)dstOffset;
+
+ Span pixel = _context.PhysicalMemory.Read(srcAddress, (ulong)srcBpp);
+
+ _context.PhysicalMemory.Write(dstAddress, pixel);
+ }
+ }
+ else
+ {
+ // Buffer to buffer copy.
+ BufferManager.CopyBuffer(cbp.SrcAddress, cbp.DstAddress, (uint)size);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodCopyTexture.cs b/Ryujinx.Graphics.Gpu/Engine/MethodCopyTexture.cs
new file mode 100644
index 0000000000..8d1b2b714f
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Engine/MethodCopyTexture.cs
@@ -0,0 +1,100 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.State;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+ using Texture = Image.Texture;
+
+ partial class Methods
+ {
+ ///
+ /// Performs a texture to texture copy.
+ ///
+ /// Current GPU state
+ /// Method call argument
+ private void CopyTexture(GpuState state, int argument)
+ {
+ var dstCopyTexture = state.Get(MethodOffset.CopyDstTexture);
+ var srcCopyTexture = state.Get(MethodOffset.CopySrcTexture);
+
+ Texture srcTexture = TextureManager.FindOrCreateTexture(srcCopyTexture);
+
+ if (srcTexture == null)
+ {
+ return;
+ }
+
+ // When the source texture that was found has a depth format,
+ // we must enforce the target texture also has a depth format,
+ // as copies between depth and color formats are not allowed.
+ if (srcTexture.Format == Format.D32Float)
+ {
+ dstCopyTexture.Format = RtFormat.D32Float;
+ }
+
+ Texture dstTexture = TextureManager.FindOrCreateTexture(dstCopyTexture);
+
+ if (dstTexture == null)
+ {
+ return;
+ }
+
+ var control = state.Get(MethodOffset.CopyTextureControl);
+
+ var region = state.Get(MethodOffset.CopyRegion);
+
+ int srcX1 = (int)(region.SrcXF >> 32);
+ int srcY1 = (int)(region.SrcYF >> 32);
+
+ int srcX2 = (int)((region.SrcXF + region.SrcWidthRF * region.DstWidth) >> 32);
+ int srcY2 = (int)((region.SrcYF + region.SrcHeightRF * region.DstHeight) >> 32);
+
+ int dstX1 = region.DstX;
+ int dstY1 = region.DstY;
+
+ int dstX2 = region.DstX + region.DstWidth;
+ int dstY2 = region.DstY + region.DstHeight;
+
+ Extents2D srcRegion = new Extents2D(
+ srcX1 / srcTexture.Info.SamplesInX,
+ srcY1 / srcTexture.Info.SamplesInY,
+ srcX2 / srcTexture.Info.SamplesInX,
+ srcY2 / srcTexture.Info.SamplesInY);
+
+ Extents2D dstRegion = new Extents2D(
+ dstX1 / dstTexture.Info.SamplesInX,
+ dstY1 / dstTexture.Info.SamplesInY,
+ dstX2 / dstTexture.Info.SamplesInX,
+ dstY2 / dstTexture.Info.SamplesInY);
+
+ bool linearFilter = control.UnpackLinearFilter();
+
+ srcTexture.HostTexture.CopyTo(dstTexture.HostTexture, srcRegion, dstRegion, linearFilter);
+
+ // For an out of bounds copy, we must ensure that the copy wraps to the next line,
+ // so for a copy from a 64x64 texture, in the region [32, 96[, there are 32 pixels that are
+ // outside the bounds of the texture. We fill the destination with the first 32 pixels
+ // of the next line on the source texture.
+ // This can be emulated with 2 copies (the first copy handles the region inside the bounds,
+ // the second handles the region outside of the bounds).
+ // We must also extend the source texture by one line to ensure we can wrap on the last line.
+ // This is required by the (guest) OpenGL driver.
+ if (srcRegion.X2 > srcTexture.Info.Width)
+ {
+ srcCopyTexture.Height++;
+
+ srcTexture = TextureManager.FindOrCreateTexture(srcCopyTexture);
+
+ srcRegion = new Extents2D(
+ srcRegion.X1 - srcTexture.Info.Width,
+ srcRegion.Y1 + 1,
+ srcRegion.X2 - srcTexture.Info.Width,
+ srcRegion.Y2 + 1);
+
+ srcTexture.HostTexture.CopyTo(dstTexture.HostTexture, srcRegion, dstRegion, linearFilter);
+ }
+
+ dstTexture.Modified = true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodDraw.cs b/Ryujinx.Graphics.Gpu/Engine/MethodDraw.cs
new file mode 100644
index 0000000000..b13cc9cab3
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Engine/MethodDraw.cs
@@ -0,0 +1,169 @@
+using Ryujinx.Graphics.Gpu.State;
+using Ryujinx.Graphics.Gpu.Image;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+ partial class Methods
+ {
+ private bool _drawIndexed;
+
+ private int _firstIndex;
+ private int _indexCount;
+
+ private bool _instancedDrawPending;
+ private bool _instancedIndexed;
+
+ private int _instancedFirstIndex;
+ private int _instancedFirstVertex;
+ private int _instancedFirstInstance;
+ private int _instancedIndexCount;
+ private int _instancedDrawStateFirst;
+ private int _instancedDrawStateCount;
+
+ private int _instanceIndex;
+
+ ///
+ /// Primitive type of the current draw.
+ ///
+ public PrimitiveType PrimitiveType { get; private set; }
+
+ ///
+ /// Finishes draw call.
+ /// This draws geometry on the bound buffers based on the current GPU state.
+ ///
+ /// Current GPU state
+ /// Method call argument
+ private void DrawEnd(GpuState state, int argument)
+ {
+ if (_instancedDrawPending)
+ {
+ _drawIndexed = false;
+
+ return;
+ }
+
+ UpdateState(state);
+
+ bool instanced = _vsUsesInstanceId || _isAnyVbInstanced;
+
+ if (instanced)
+ {
+ _instancedDrawPending = true;
+
+ _instancedIndexed = _drawIndexed;
+
+ _instancedFirstIndex = _firstIndex;
+ _instancedFirstVertex = state.Get(MethodOffset.FirstVertex);
+ _instancedFirstInstance = state.Get(MethodOffset.FirstInstance);
+
+ _instancedIndexCount = _indexCount;
+
+ var drawState = state.Get(MethodOffset.VertexBufferDrawState);
+
+ _instancedDrawStateFirst = drawState.First;
+ _instancedDrawStateCount = drawState.Count;
+
+ _drawIndexed = false;
+
+ return;
+ }
+
+ int firstInstance = state.Get(MethodOffset.FirstInstance);
+
+ if (_drawIndexed)
+ {
+ _drawIndexed = false;
+
+ int firstVertex = state.Get(MethodOffset.FirstVertex);
+
+ _context.Renderer.Pipeline.DrawIndexed(
+ _indexCount,
+ 1,
+ _firstIndex,
+ firstVertex,
+ firstInstance);
+ }
+ else
+ {
+ var drawState = state.Get(MethodOffset.VertexBufferDrawState);
+
+ _context.Renderer.Pipeline.Draw(
+ drawState.Count,
+ 1,
+ drawState.First,
+ firstInstance);
+ }
+ }
+
+ ///
+ /// Starts draw.
+ /// This sets primitive type and instanced draw parameters.
+ ///
+ /// Current GPU state
+ /// Method call argument
+ private void DrawBegin(GpuState state, int argument)
+ {
+ if ((argument & (1 << 26)) != 0)
+ {
+ _instanceIndex++;
+ }
+ else if ((argument & (1 << 27)) == 0)
+ {
+ PerformDeferredDraws();
+
+ _instanceIndex = 0;
+ }
+
+ PrimitiveType type = (PrimitiveType)(argument & 0xffff);
+
+ _context.Renderer.Pipeline.SetPrimitiveTopology(type.Convert());
+
+ PrimitiveType = type;
+ }
+
+ ///
+ /// Sets the index buffer count.
+ /// This also sets internal state that indicates that the next draw is an indexed draw.
+ ///
+ /// Current GPU state
+ /// Method call argument
+ private void SetIndexBufferCount(GpuState state, int argument)
+ {
+ _drawIndexed = true;
+ }
+
+ ///
+ /// Perform any deferred draws.
+ /// This is used for instanced draws.
+ /// Since each instance is a separate draw, we defer the draw and accumulate the instance count.
+ /// Once we detect the last instanced draw, then we perform the host instanced draw,
+ /// with the accumulated instance count.
+ ///
+ public void PerformDeferredDraws()
+ {
+ // Perform any pending instanced draw.
+ if (_instancedDrawPending)
+ {
+ _instancedDrawPending = false;
+
+ if (_instancedIndexed)
+ {
+ _context.Renderer.Pipeline.DrawIndexed(
+ _instancedIndexCount,
+ _instanceIndex + 1,
+ _instancedFirstIndex,
+ _instancedFirstVertex,
+ _instancedFirstInstance);
+ }
+ else
+ {
+ _context.Renderer.Pipeline.Draw(
+ _instancedDrawStateCount,
+ _instanceIndex + 1,
+ _instancedDrawStateFirst,
+ _instancedFirstInstance);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodReport.cs b/Ryujinx.Graphics.Gpu/Engine/MethodReport.cs
new file mode 100644
index 0000000000..173989c388
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Engine/MethodReport.cs
@@ -0,0 +1,129 @@
+using Ryujinx.Common;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.State;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+ partial class Methods
+ {
+ private const int NsToTicksFractionNumerator = 384;
+ private const int NsToTicksFractionDenominator = 625;
+
+ private ulong _runningCounter;
+
+ ///
+ /// Writes a GPU counter to guest memory.
+ ///
+ /// Current GPU state
+ /// Method call argument
+ private void Report(GpuState state, int argument)
+ {
+ ReportMode mode = (ReportMode)(argument & 3);
+
+ ReportCounterType type = (ReportCounterType)((argument >> 23) & 0x1f);
+
+ switch (mode)
+ {
+ case ReportMode.Semaphore: ReportSemaphore(state); break;
+ case ReportMode.Counter: ReportCounter(state, type); break;
+ }
+ }
+
+ ///
+ /// Writes a GPU semaphore value to guest memory.
+ ///
+ /// Current GPU state
+ private void ReportSemaphore(GpuState state)
+ {
+ var rs = state.Get(MethodOffset.ReportState);
+
+ _context.MemoryAccessor.Write(rs.Address.Pack(), rs.Payload);
+
+ _context.AdvanceSequence();
+ }
+
+ ///
+ /// Packed GPU counter data (including GPU timestamp) in memory.
+ ///
+ private struct CounterData
+ {
+ public ulong Counter;
+ public ulong Timestamp;
+ }
+
+ ///
+ /// Writes a GPU counter to guest memory.
+ /// This also writes the current timestamp value.
+ ///
+ /// Current GPU state
+ /// Counter to be written to memory
+ private void ReportCounter(GpuState state, ReportCounterType type)
+ {
+ CounterData counterData = new CounterData();
+
+ ulong counter = 0;
+
+ switch (type)
+ {
+ case ReportCounterType.Zero:
+ counter = 0;
+ break;
+ case ReportCounterType.SamplesPassed:
+ counter = _context.Renderer.GetCounter(CounterType.SamplesPassed);
+ break;
+ case ReportCounterType.PrimitivesGenerated:
+ counter = _context.Renderer.GetCounter(CounterType.PrimitivesGenerated);
+ break;
+ case ReportCounterType.TransformFeedbackPrimitivesWritten:
+ counter = _context.Renderer.GetCounter(CounterType.TransformFeedbackPrimitivesWritten);
+ break;
+ }
+
+ ulong ticks;
+
+ if (GraphicsConfig.FastGpuTime)
+ {
+ ticks = _runningCounter++;
+ }
+ else
+ {
+ ticks = ConvertNanosecondsToTicks((ulong)PerformanceCounter.ElapsedNanoseconds);
+ }
+
+ counterData.Counter = counter;
+ counterData.Timestamp = ticks;
+
+ Span counterDataSpan = MemoryMarshal.CreateSpan(ref counterData, 1);
+
+ Span data = MemoryMarshal.Cast(counterDataSpan);
+
+ var rs = state.Get(MethodOffset.ReportState);
+
+ _context.MemoryAccessor.Write(rs.Address.Pack(), data);
+ }
+
+ ///
+ /// Converts a nanoseconds timestamp value to Maxwell time ticks.
+ ///
+ ///
+ /// The frequency is 614400000 Hz.
+ ///
+ /// Timestamp in nanoseconds
+ /// Maxwell ticks
+ private static ulong ConvertNanosecondsToTicks(ulong nanoseconds)
+ {
+ // We need to divide first to avoid overflows.
+ // We fix up the result later by calculating the difference and adding
+ // that to the result.
+ ulong divided = nanoseconds / NsToTicksFractionDenominator;
+
+ ulong rounded = divided * NsToTicksFractionDenominator;
+
+ ulong errorBias = (nanoseconds - rounded) * NsToTicksFractionNumerator / NsToTicksFractionDenominator;
+
+ return divided * NsToTicksFractionNumerator + errorBias;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodResetCounter.cs b/Ryujinx.Graphics.Gpu/Engine/MethodResetCounter.cs
new file mode 100644
index 0000000000..79f8e1e8ae
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Engine/MethodResetCounter.cs
@@ -0,0 +1,31 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.State;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+ partial class Methods
+ {
+ ///
+ /// Resets the value of an internal GPU counter back to zero.
+ ///
+ /// Current GPU state
+ /// Method call argument
+ private void ResetCounter(GpuState state, int argument)
+ {
+ ResetCounterType type = (ResetCounterType)argument;
+
+ switch (type)
+ {
+ case ResetCounterType.SamplesPassed:
+ _context.Renderer.ResetCounter(CounterType.SamplesPassed);
+ break;
+ case ResetCounterType.PrimitivesGenerated:
+ _context.Renderer.ResetCounter(CounterType.PrimitivesGenerated);
+ break;
+ case ResetCounterType.TransformFeedbackPrimitivesWritten:
+ _context.Renderer.ResetCounter(CounterType.TransformFeedbackPrimitivesWritten);
+ break;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferBind.cs b/Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferBind.cs
new file mode 100644
index 0000000000..3fee1fcf8b
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferBind.cs
@@ -0,0 +1,83 @@
+using Ryujinx.Graphics.Gpu.State;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+ partial class Methods
+ {
+ ///
+ /// Binds a uniform buffer for the vertex shader stage.
+ ///
+ /// Current GPU state
+ /// Method call argument
+ private void UniformBufferBindVertex(GpuState state, int argument)
+ {
+ UniformBufferBind(state, argument, ShaderType.Vertex);
+ }
+
+ ///
+ /// Binds a uniform buffer for the tessellation control shader stage.
+ ///
+ /// Current GPU state
+ /// Method call argument
+ private void UniformBufferBindTessControl(GpuState state, int argument)
+ {
+ UniformBufferBind(state, argument, ShaderType.TessellationControl);
+ }
+
+ ///
+ /// Binds a uniform buffer for the tessellation evaluation shader stage.
+ ///
+ /// Current GPU state
+ /// Method call argument
+ private void UniformBufferBindTessEvaluation(GpuState state, int argument)
+ {
+ UniformBufferBind(state, argument, ShaderType.TessellationEvaluation);
+ }
+
+ ///
+ /// Binds a uniform buffer for the geometry shader stage.
+ ///
+ /// Current GPU state
+ /// Method call argument
+ private void UniformBufferBindGeometry(GpuState state, int argument)
+ {
+ UniformBufferBind(state, argument, ShaderType.Geometry);
+ }
+
+ ///
+ /// Binds a uniform buffer for the fragment shader stage.
+ ///
+ /// Current GPU state
+ /// Method call argument
+ private void UniformBufferBindFragment(GpuState state, int argument)
+ {
+ UniformBufferBind(state, argument, ShaderType.Fragment);
+ }
+
+ ///
+ ///Binds a uniform buffer for the specified shader stage.
+ ///
+ /// Current GPU state
+ /// Method call argument
+ /// Shader stage that will access the uniform buffer
+ private void UniformBufferBind(GpuState state, int argument, ShaderType type)
+ {
+ bool enable = (argument & 1) != 0;
+
+ int index = (argument >> 4) & 0x1f;
+
+ if (enable)
+ {
+ var uniformBuffer = state.Get(MethodOffset.UniformBufferState);
+
+ ulong address = uniformBuffer.Address.Pack();
+
+ BufferManager.SetGraphicsUniformBuffer((int)type, index, address, (uint)uniformBuffer.Size);
+ }
+ else
+ {
+ BufferManager.SetGraphicsUniformBuffer((int)type, index, 0, 0);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferUpdate.cs b/Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferUpdate.cs
new file mode 100644
index 0000000000..524f5e0399
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferUpdate.cs
@@ -0,0 +1,23 @@
+using Ryujinx.Graphics.Gpu.State;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+ partial class Methods
+ {
+ ///
+ /// Updates the uniform buffer data with inline data.
+ ///
+ /// Current GPU state
+ /// New uniform buffer data word
+ private void UniformBufferUpdate(GpuState state, int argument)
+ {
+ var uniformBuffer = state.Get(MethodOffset.UniformBufferState);
+
+ _context.MemoryAccessor.Write(uniformBuffer.Address.Pack() + (uint)uniformBuffer.Offset, argument);
+
+ state.SetUniformBufferOffset(uniformBuffer.Offset + 4);
+
+ _context.AdvanceSequence();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Engine/Methods.cs b/Ryujinx.Graphics.Gpu/Engine/Methods.cs
new file mode 100644
index 0000000000..90935b34c0
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Engine/Methods.cs
@@ -0,0 +1,890 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.Image;
+using Ryujinx.Graphics.Gpu.Memory;
+using Ryujinx.Graphics.Gpu.Shader;
+using Ryujinx.Graphics.Gpu.State;
+using Ryujinx.Graphics.Shader;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+ using Texture = Image.Texture;
+
+ ///
+ /// GPU method implementations.
+ ///
+ partial class Methods
+ {
+ private readonly GpuContext _context;
+ private readonly ShaderProgramInfo[] _currentProgramInfo;
+
+ ///
+ /// In-memory shader cache.
+ ///
+ public ShaderCache ShaderCache { get; }
+
+ ///
+ /// GPU buffer manager.
+ ///
+ public BufferManager BufferManager { get; }
+
+ ///
+ /// GPU texture manager.
+ ///
+ public TextureManager TextureManager { get; }
+
+ private bool _isAnyVbInstanced;
+ private bool _vsUsesInstanceId;
+
+ ///
+ /// Creates a new instance of the GPU methods class.
+ ///
+ /// GPU context
+ public Methods(GpuContext context)
+ {
+ _context = context;
+
+ ShaderCache = new ShaderCache(_context);
+
+ _currentProgramInfo = new ShaderProgramInfo[Constants.ShaderStages];
+
+ BufferManager = new BufferManager(context);
+ TextureManager = new TextureManager(context);
+ }
+
+ ///
+ /// Register callback for GPU method calls that triggers an action on the GPU.
+ ///
+ /// GPU state where the triggers will be registered
+ public void RegisterCallbacks(GpuState state)
+ {
+ state.RegisterCallback(MethodOffset.LaunchDma, LaunchDma);
+ state.RegisterCallback(MethodOffset.LoadInlineData, LoadInlineData);
+
+ state.RegisterCallback(MethodOffset.Dispatch, Dispatch);
+
+ state.RegisterCallback(MethodOffset.CopyBuffer, CopyBuffer);
+ state.RegisterCallback(MethodOffset.CopyTexture, CopyTexture);
+
+ state.RegisterCallback(MethodOffset.TextureBarrier, TextureBarrier);
+ state.RegisterCallback(MethodOffset.InvalidateTextures, InvalidateTextures);
+ state.RegisterCallback(MethodOffset.TextureBarrierTiled, TextureBarrierTiled);
+
+ state.RegisterCallback(MethodOffset.ResetCounter, ResetCounter);
+
+ state.RegisterCallback(MethodOffset.DrawEnd, DrawEnd);
+ state.RegisterCallback(MethodOffset.DrawBegin, DrawBegin);
+
+ state.RegisterCallback(MethodOffset.IndexBufferCount, SetIndexBufferCount);
+
+ state.RegisterCallback(MethodOffset.Clear, Clear);
+
+ state.RegisterCallback(MethodOffset.Report, Report);
+
+ state.RegisterCallback(MethodOffset.UniformBufferUpdateData, 16, UniformBufferUpdate);
+
+ state.RegisterCallback(MethodOffset.UniformBufferBindVertex, UniformBufferBindVertex);
+ state.RegisterCallback(MethodOffset.UniformBufferBindTessControl, UniformBufferBindTessControl);
+ state.RegisterCallback(MethodOffset.UniformBufferBindTessEvaluation, UniformBufferBindTessEvaluation);
+ state.RegisterCallback(MethodOffset.UniformBufferBindGeometry, UniformBufferBindGeometry);
+ state.RegisterCallback(MethodOffset.UniformBufferBindFragment, UniformBufferBindFragment);
+ }
+
+ ///
+ /// Updates host state based on the current guest GPU state.
+ ///
+ /// Guest GPU state
+ private void UpdateState(GpuState state)
+ {
+ // Shaders must be the first one to be updated if modified, because
+ // some of the other state depends on information from the currently
+ // bound shaders.
+ if (state.QueryModified(MethodOffset.ShaderBaseAddress, MethodOffset.ShaderState))
+ {
+ UpdateShaderState(state);
+ }
+
+ if (state.QueryModified(MethodOffset.RtColorState,
+ MethodOffset.RtDepthStencilState,
+ MethodOffset.RtControl,
+ MethodOffset.RtDepthStencilSize,
+ MethodOffset.RtDepthStencilEnable))
+ {
+ UpdateRenderTargetState(state, useControl: true);
+ }
+
+ if (state.QueryModified(MethodOffset.DepthTestEnable,
+ MethodOffset.DepthWriteEnable,
+ MethodOffset.DepthTestFunc))
+ {
+ UpdateDepthTestState(state);
+ }
+
+ if (state.QueryModified(MethodOffset.DepthMode, MethodOffset.ViewportTransform, MethodOffset.ViewportExtents))
+ {
+ UpdateViewportTransform(state);
+ }
+
+ if (state.QueryModified(MethodOffset.DepthBiasState,
+ MethodOffset.DepthBiasFactor,
+ MethodOffset.DepthBiasUnits,
+ MethodOffset.DepthBiasClamp))
+ {
+ UpdateDepthBiasState(state);
+ }
+
+ if (state.QueryModified(MethodOffset.StencilBackMasks,
+ MethodOffset.StencilTestState,
+ MethodOffset.StencilBackTestState))
+ {
+ UpdateStencilTestState(state);
+ }
+
+ // Pools.
+ if (state.QueryModified(MethodOffset.SamplerPoolState, MethodOffset.SamplerIndex))
+ {
+ UpdateSamplerPoolState(state);
+ }
+
+ if (state.QueryModified(MethodOffset.TexturePoolState))
+ {
+ UpdateTexturePoolState(state);
+ }
+
+ // Input assembler state.
+ if (state.QueryModified(MethodOffset.VertexAttribState))
+ {
+ UpdateVertexAttribState(state);
+ }
+
+ if (state.QueryModified(MethodOffset.PrimitiveRestartState))
+ {
+ UpdatePrimitiveRestartState(state);
+ }
+
+ if (state.QueryModified(MethodOffset.IndexBufferState))
+ {
+ UpdateIndexBufferState(state);
+ }
+
+ if (state.QueryModified(MethodOffset.VertexBufferDrawState,
+ MethodOffset.VertexBufferInstanced,
+ MethodOffset.VertexBufferState,
+ MethodOffset.VertexBufferEndAddress))
+ {
+ UpdateVertexBufferState(state);
+ }
+
+ if (state.QueryModified(MethodOffset.FaceState))
+ {
+ UpdateFaceState(state);
+ }
+
+ if (state.QueryModified(MethodOffset.RtColorMaskShared, MethodOffset.RtColorMask))
+ {
+ UpdateRtColorMask(state);
+ }
+
+ if (state.QueryModified(MethodOffset.BlendIndependent,
+ MethodOffset.BlendStateCommon,
+ MethodOffset.BlendEnableCommon,
+ MethodOffset.BlendEnable,
+ MethodOffset.BlendState))
+ {
+ UpdateBlendState(state);
+ }
+
+ CommitBindings();
+ }
+
+ ///
+ /// Ensures that the bindings are visible to the host GPU.
+ /// Note: this actually performs the binding using the host graphics API.
+ ///
+ private void CommitBindings()
+ {
+ UpdateStorageBuffers();
+
+ BufferManager.CommitBindings();
+ TextureManager.CommitGraphicsBindings();
+ }
+
+ ///
+ /// Updates storage buffer bindings.
+ ///
+ private void UpdateStorageBuffers()
+ {
+ for (int stage = 0; stage < _currentProgramInfo.Length; stage++)
+ {
+ ShaderProgramInfo info = _currentProgramInfo[stage];
+
+ if (info == null)
+ {
+ continue;
+ }
+
+ for (int index = 0; index < info.SBuffers.Count; index++)
+ {
+ BufferDescriptor sb = info.SBuffers[index];
+
+ ulong sbDescAddress = BufferManager.GetGraphicsUniformBufferAddress(stage, 0);
+
+ int sbDescOffset = 0x110 + stage * 0x100 + sb.Slot * 0x10;
+
+ sbDescAddress += (ulong)sbDescOffset;
+
+ Span sbDescriptorData = _context.PhysicalMemory.Read(sbDescAddress, 0x10);
+
+ SbDescriptor sbDescriptor = MemoryMarshal.Cast(sbDescriptorData)[0];
+
+ BufferManager.SetGraphicsStorageBuffer(stage, sb.Slot, sbDescriptor.PackAddress(), (uint)sbDescriptor.Size);
+ }
+ }
+ }
+
+ ///
+ /// Updates render targets (color and depth-stencil buffers) based on current render target state.
+ ///
+ /// Current GPU state
+ /// Use draw buffers information from render target control register
+ private void UpdateRenderTargetState(GpuState state, bool useControl)
+ {
+ var rtControl = state.Get(MethodOffset.RtControl);
+
+ int count = useControl ? rtControl.UnpackCount() : Constants.TotalRenderTargets;
+
+ var msaaMode = state.Get(MethodOffset.RtMsaaMode);
+
+ int samplesInX = msaaMode.SamplesInX();
+ int samplesInY = msaaMode.SamplesInY();
+
+ for (int index = 0; index < Constants.TotalRenderTargets; index++)
+ {
+ int rtIndex = useControl ? rtControl.UnpackPermutationIndex(index) : index;
+
+ var colorState = state.Get(MethodOffset.RtColorState, rtIndex);
+
+ if (index >= count || !IsRtEnabled(colorState))
+ {
+ TextureManager.SetRenderTargetColor(index, null);
+
+ continue;
+ }
+
+ Texture color = TextureManager.FindOrCreateTexture(colorState, samplesInX, samplesInY);
+
+ TextureManager.SetRenderTargetColor(index, color);
+
+ if (color != null)
+ {
+ color.Modified = true;
+ }
+ }
+
+ bool dsEnable = state.Get(MethodOffset.RtDepthStencilEnable);
+
+ Texture depthStencil = null;
+
+ if (dsEnable)
+ {
+ var dsState = state.Get(MethodOffset.RtDepthStencilState);
+ var dsSize = state.Get (MethodOffset.RtDepthStencilSize);
+
+ depthStencil = TextureManager.FindOrCreateTexture(dsState, dsSize, samplesInX, samplesInY);
+ }
+
+ TextureManager.SetRenderTargetDepthStencil(depthStencil);
+
+ if (depthStencil != null)
+ {
+ depthStencil.Modified = true;
+ }
+ }
+
+ ///
+ /// Checks if a render target color buffer is used.
+ ///
+ /// Color buffer information
+ /// True if the specified buffer is enabled/used, false otherwise
+ private static bool IsRtEnabled(RtColorState colorState)
+ {
+ // Colors are disabled by writing 0 to the format.
+ return colorState.Format != 0 && colorState.WidthOrStride != 0;
+ }
+
+ ///
+ /// Updates host depth test state based on current GPU state.
+ ///
+ /// Current GPU state
+ private void UpdateDepthTestState(GpuState state)
+ {
+ _context.Renderer.Pipeline.SetDepthTest(new DepthTestDescriptor(
+ state.Get(MethodOffset.DepthTestEnable),
+ state.Get(MethodOffset.DepthWriteEnable),
+ state.Get(MethodOffset.DepthTestFunc)));
+ }
+
+ ///
+ /// Updates host viewport transform and clipping state based on current GPU state.
+ ///
+ /// Current GPU state
+ private void UpdateViewportTransform(GpuState state)
+ {
+ DepthMode depthMode = state.Get(MethodOffset.DepthMode);
+
+ _context.Renderer.Pipeline.SetDepthMode(depthMode);
+
+ bool flipY = (state.Get(MethodOffset.YControl) & 1) != 0;
+
+ float yFlip = flipY ? -1 : 1;
+
+ Viewport[] viewports = new Viewport[Constants.TotalViewports];
+
+ for (int index = 0; index < Constants.TotalViewports; index++)
+ {
+ var transform = state.Get(MethodOffset.ViewportTransform, index);
+ var extents = state.Get (MethodOffset.ViewportExtents, index);
+
+ float x = transform.TranslateX - MathF.Abs(transform.ScaleX);
+ float y = transform.TranslateY - MathF.Abs(transform.ScaleY);
+
+ float width = transform.ScaleX * 2;
+ float height = transform.ScaleY * 2 * yFlip;
+
+ RectangleF region = new RectangleF(x, y, width, height);
+
+ viewports[index] = new Viewport(
+ region,
+ transform.UnpackSwizzleX(),
+ transform.UnpackSwizzleY(),
+ transform.UnpackSwizzleZ(),
+ transform.UnpackSwizzleW(),
+ extents.DepthNear,
+ extents.DepthFar);
+ }
+
+ _context.Renderer.Pipeline.SetViewports(0, viewports);
+ }
+
+ ///
+ /// Updates host depth bias (also called polygon offset) state based on current GPU state.
+ ///
+ /// Current GPU state
+ private void UpdateDepthBiasState(GpuState state)
+ {
+ var depthBias = state.Get(MethodOffset.DepthBiasState);
+
+ float factor = state.Get(MethodOffset.DepthBiasFactor);
+ float units = state.Get(MethodOffset.DepthBiasUnits);
+ float clamp = state.Get(MethodOffset.DepthBiasClamp);
+
+ PolygonModeMask enables;
+
+ enables = (depthBias.PointEnable ? PolygonModeMask.Point : 0);
+ enables |= (depthBias.LineEnable ? PolygonModeMask.Line : 0);
+ enables |= (depthBias.FillEnable ? PolygonModeMask.Fill : 0);
+
+ _context.Renderer.Pipeline.SetDepthBias(enables, factor, units, clamp);
+ }
+
+ ///
+ /// Updates host stencil test state based on current GPU state.
+ ///
+ /// Current GPU state
+ private void UpdateStencilTestState(GpuState state)
+ {
+ var backMasks = state.Get (MethodOffset.StencilBackMasks);
+ var test = state.Get (MethodOffset.StencilTestState);
+ var backTest = state.Get(MethodOffset.StencilBackTestState);
+
+ CompareOp backFunc;
+ StencilOp backSFail;
+ StencilOp backDpPass;
+ StencilOp backDpFail;
+ int backFuncRef;
+ int backFuncMask;
+ int backMask;
+
+ if (backTest.TwoSided)
+ {
+ backFunc = backTest.BackFunc;
+ backSFail = backTest.BackSFail;
+ backDpPass = backTest.BackDpPass;
+ backDpFail = backTest.BackDpFail;
+ backFuncRef = backMasks.FuncRef;
+ backFuncMask = backMasks.FuncMask;
+ backMask = backMasks.Mask;
+ }
+ else
+ {
+ backFunc = test.FrontFunc;
+ backSFail = test.FrontSFail;
+ backDpPass = test.FrontDpPass;
+ backDpFail = test.FrontDpFail;
+ backFuncRef = test.FrontFuncRef;
+ backFuncMask = test.FrontFuncMask;
+ backMask = test.FrontMask;
+ }
+
+ _context.Renderer.Pipeline.SetStencilTest(new StencilTestDescriptor(
+ test.Enable,
+ test.FrontFunc,
+ test.FrontSFail,
+ test.FrontDpPass,
+ test.FrontDpFail,
+ test.FrontFuncRef,
+ test.FrontFuncMask,
+ test.FrontMask,
+ backFunc,
+ backSFail,
+ backDpPass,
+ backDpFail,
+ backFuncRef,
+ backFuncMask,
+ backMask));
+ }
+
+ ///
+ /// Updates current sampler pool address and size based on guest GPU state.
+ ///
+ /// Current GPU state
+ private void UpdateSamplerPoolState(GpuState state)
+ {
+ var texturePool = state.Get(MethodOffset.TexturePoolState);
+ var samplerPool = state.Get(MethodOffset.SamplerPoolState);
+
+ var samplerIndex = state.Get(MethodOffset.SamplerIndex);
+
+ int maximumId = samplerIndex == SamplerIndex.ViaHeaderIndex
+ ? texturePool.MaximumId
+ : samplerPool.MaximumId;
+
+ TextureManager.SetGraphicsSamplerPool(samplerPool.Address.Pack(), maximumId, samplerIndex);
+ }
+
+ ///
+ /// Updates current texture pool address and size based on guest GPU state.
+ ///
+ /// Current GPU state
+ private void UpdateTexturePoolState(GpuState state)
+ {
+ var texturePool = state.Get(MethodOffset.TexturePoolState);
+
+ TextureManager.SetGraphicsTexturePool(texturePool.Address.Pack(), texturePool.MaximumId);
+
+ TextureManager.SetGraphicsTextureBufferIndex(state.Get(MethodOffset.TextureBufferIndex));
+ }
+
+ ///
+ /// Updates host vertex attributes based on guest GPU state.
+ ///
+ /// Current GPU state
+ private void UpdateVertexAttribState(GpuState state)
+ {
+ VertexAttribDescriptor[] vertexAttribs = new VertexAttribDescriptor[16];
+
+ for (int index = 0; index < 16; index++)
+ {
+ var vertexAttrib = state.Get(MethodOffset.VertexAttribState, index);
+
+ if (!FormatTable.TryGetAttribFormat(vertexAttrib.UnpackFormat(), out Format format))
+ {
+ Logger.PrintDebug(LogClass.Gpu, $"Invalid attribute format 0x{vertexAttrib.UnpackFormat():X}.");
+
+ format = Format.R32G32B32A32Float;
+ }
+
+ vertexAttribs[index] = new VertexAttribDescriptor(
+ vertexAttrib.UnpackBufferIndex(),
+ vertexAttrib.UnpackOffset(),
+ format);
+ }
+
+ _context.Renderer.Pipeline.SetVertexAttribs(vertexAttribs);
+ }
+
+ ///
+ /// Updates host primitive restart based on guest GPU state.
+ ///
+ /// Current GPU state
+ private void UpdatePrimitiveRestartState(GpuState state)
+ {
+ PrimitiveRestartState primitiveRestart = state.Get(MethodOffset.PrimitiveRestartState);
+
+ _context.Renderer.Pipeline.SetPrimitiveRestart(
+ primitiveRestart.Enable,
+ primitiveRestart.Index);
+ }
+
+ ///
+ /// Updates host index buffer binding based on guest GPU state.
+ ///
+ /// Current GPU state
+ private void UpdateIndexBufferState(GpuState state)
+ {
+ var indexBuffer = state.Get(MethodOffset.IndexBufferState);
+
+ _firstIndex = indexBuffer.First;
+ _indexCount = indexBuffer.Count;
+
+ if (_indexCount == 0)
+ {
+ return;
+ }
+
+ ulong gpuVa = indexBuffer.Address.Pack();
+
+ // Do not use the end address to calculate the size, because
+ // the result may be much larger than the real size of the index buffer.
+ ulong size = (ulong)(_firstIndex + _indexCount);
+
+ switch (indexBuffer.Type)
+ {
+ case IndexType.UShort: size *= 2; break;
+ case IndexType.UInt: size *= 4; break;
+ }
+
+ BufferManager.SetIndexBuffer(gpuVa, size, indexBuffer.Type);
+
+ // The index buffer affects the vertex buffer size calculation, we
+ // need to ensure that they are updated.
+ UpdateVertexBufferState(state);
+ }
+
+ ///
+ /// Updates host vertex buffer bindings based on guest GPU state.
+ ///
+ /// Current GPU state
+ private void UpdateVertexBufferState(GpuState state)
+ {
+ _isAnyVbInstanced = false;
+
+ for (int index = 0; index < 16; index++)
+ {
+ var vertexBuffer = state.Get(MethodOffset.VertexBufferState, index);
+
+ if (!vertexBuffer.UnpackEnable())
+ {
+ BufferManager.SetVertexBuffer(index, 0, 0, 0, 0);
+
+ continue;
+ }
+
+ GpuVa endAddress = state.Get(MethodOffset.VertexBufferEndAddress, index);
+
+ ulong address = vertexBuffer.Address.Pack();
+
+ int stride = vertexBuffer.UnpackStride();
+
+ bool instanced = state.Get(MethodOffset.VertexBufferInstanced + index);
+
+ int divisor = instanced ? vertexBuffer.Divisor : 0;
+
+ _isAnyVbInstanced |= divisor != 0;
+
+ ulong size;
+
+ if (_drawIndexed || stride == 0 || instanced)
+ {
+ // 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;
+ }
+ else
+ {
+ // For non-indexed draws, we can guess the size from the vertex count
+ // and stride.
+ int firstInstance = state.Get(MethodOffset.FirstInstance);
+
+ var drawState = state.Get(MethodOffset.VertexBufferDrawState);
+
+ size = (ulong)((firstInstance + drawState.First + drawState.Count) * stride);
+ }
+
+ BufferManager.SetVertexBuffer(index, address, size, stride, divisor);
+ }
+ }
+
+ ///
+ /// Updates host face culling and orientation based on guest GPU state.
+ ///
+ /// Current GPU state
+ private void UpdateFaceState(GpuState state)
+ {
+ var face = state.Get(MethodOffset.FaceState);
+
+ _context.Renderer.Pipeline.SetFaceCulling(face.CullEnable, face.CullFace);
+
+ _context.Renderer.Pipeline.SetFrontFace(face.FrontFace);
+ }
+
+ ///
+ /// Updates host render target color masks, based on guest GPU state.
+ /// This defines which color channels are written to each color buffer.
+ ///
+ /// Current GPU state
+ private void UpdateRtColorMask(GpuState state)
+ {
+ bool rtColorMaskShared = state.Get(MethodOffset.RtColorMaskShared);
+
+ uint[] componentMasks = new uint[Constants.TotalRenderTargets];
+
+ for (int index = 0; index < Constants.TotalRenderTargets; index++)
+ {
+ var colorMask = state.Get(MethodOffset.RtColorMask, rtColorMaskShared ? 0 : index);
+
+ uint componentMask;
+
+ componentMask = (colorMask.UnpackRed() ? 1u : 0u);
+ componentMask |= (colorMask.UnpackGreen() ? 2u : 0u);
+ componentMask |= (colorMask.UnpackBlue() ? 4u : 0u);
+ componentMask |= (colorMask.UnpackAlpha() ? 8u : 0u);
+
+ componentMasks[index] = componentMask;
+ }
+
+ _context.Renderer.Pipeline.SetRenderTargetColorMasks(componentMasks);
+ }
+
+ ///
+ /// Updates host render target color buffer blending state, based on guest state.
+ ///
+ /// Current GPU state
+ private void UpdateBlendState(GpuState state)
+ {
+ bool blendIndependent = state.Get(MethodOffset.BlendIndependent);
+
+ for (int index = 0; index < 8; index++)
+ {
+ BlendDescriptor descriptor;
+
+ if (blendIndependent)
+ {
+ bool enable = state.Get (MethodOffset.BlendEnable, index);
+ var blend = state.Get(MethodOffset.BlendState, index);
+
+ descriptor = new BlendDescriptor(
+ enable,
+ blend.ColorOp,
+ blend.ColorSrcFactor,
+ blend.ColorDstFactor,
+ blend.AlphaOp,
+ blend.AlphaSrcFactor,
+ blend.AlphaDstFactor);
+ }
+ else
+ {
+ bool enable = state.Get (MethodOffset.BlendEnable, 0);
+ var blend = state.Get(MethodOffset.BlendStateCommon);
+
+ descriptor = new BlendDescriptor(
+ enable,
+ blend.ColorOp,
+ blend.ColorSrcFactor,
+ blend.ColorDstFactor,
+ blend.AlphaOp,
+ blend.AlphaSrcFactor,
+ blend.AlphaDstFactor);
+ }
+
+ _context.Renderer.Pipeline.SetBlendState(index, descriptor);
+ }
+ }
+
+ ///
+ /// Storage buffer address and size information.
+ ///
+ private struct SbDescriptor
+ {
+ public uint AddressLow;
+ public uint AddressHigh;
+ public int Size;
+ public int Padding;
+
+ public ulong PackAddress()
+ {
+ return AddressLow | ((ulong)AddressHigh << 32);
+ }
+ }
+
+ ///
+ /// Updates host shaders based on the guest GPU state.
+ ///
+ /// Current GPU state
+ private void UpdateShaderState(GpuState state)
+ {
+ ShaderAddresses addresses = new ShaderAddresses();
+
+ Span addressesSpan = MemoryMarshal.CreateSpan(ref addresses, 1);
+
+ Span addressesArray = MemoryMarshal.Cast(addressesSpan);
+
+ ulong baseAddress = state.Get(MethodOffset.ShaderBaseAddress).Pack();
+
+ for (int index = 0; index < 6; index++)
+ {
+ var shader = state.Get(MethodOffset.ShaderState, index);
+
+ if (!shader.UnpackEnable() && index != 1)
+ {
+ continue;
+ }
+
+ addressesArray[index] = baseAddress + shader.Offset;
+ }
+
+ GraphicsShader gs = ShaderCache.GetGraphicsShader(state, addresses);
+
+ _vsUsesInstanceId = gs.Shaders[0]?.Program.Info.UsesInstanceId ?? false;
+
+ for (int stage = 0; stage < Constants.ShaderStages; stage++)
+ {
+ ShaderProgramInfo info = gs.Shaders[stage]?.Program.Info;
+
+ _currentProgramInfo[stage] = info;
+
+ if (info == null)
+ {
+ continue;
+ }
+
+ var textureBindings = new TextureBindingInfo[info.Textures.Count];
+
+ for (int index = 0; index < info.Textures.Count; index++)
+ {
+ var descriptor = info.Textures[index];
+
+ Target target = GetTarget(descriptor.Type);
+
+ if (descriptor.IsBindless)
+ {
+ textureBindings[index] = new TextureBindingInfo(target, descriptor.CbufSlot, descriptor.CbufOffset);
+ }
+ else
+ {
+ textureBindings[index] = new TextureBindingInfo(target, descriptor.HandleIndex);
+ }
+ }
+
+ TextureManager.SetGraphicsTextures(stage, textureBindings);
+
+ var imageBindings = new TextureBindingInfo[info.Images.Count];
+
+ for (int index = 0; index < info.Images.Count; index++)
+ {
+ var descriptor = info.Images[index];
+
+ Target target = GetTarget(descriptor.Type);
+
+ imageBindings[index] = new TextureBindingInfo(target, descriptor.HandleIndex);
+ }
+
+ TextureManager.SetGraphicsImages(stage, imageBindings);
+
+ uint sbEnableMask = 0;
+ uint ubEnableMask = 0;
+
+ for (int index = 0; index < info.SBuffers.Count; index++)
+ {
+ sbEnableMask |= 1u << info.SBuffers[index].Slot;
+ }
+
+ for (int index = 0; index < info.CBuffers.Count; index++)
+ {
+ ubEnableMask |= 1u << info.CBuffers[index].Slot;
+ }
+
+ BufferManager.SetGraphicsStorageBufferEnableMask(stage, sbEnableMask);
+ BufferManager.SetGraphicsUniformBufferEnableMask(stage, ubEnableMask);
+ }
+
+ _context.Renderer.Pipeline.SetProgram(gs.HostProgram);
+ }
+
+ ///
+ /// Gets texture target from a sampler type.
+ ///
+ /// Sampler type
+ /// Texture target value
+ private static Target GetTarget(SamplerType type)
+ {
+ type &= ~(SamplerType.Indexed | SamplerType.Shadow);
+
+ switch (type)
+ {
+ case SamplerType.Texture1D:
+ return Target.Texture1D;
+
+ case SamplerType.TextureBuffer:
+ return Target.TextureBuffer;
+
+ case SamplerType.Texture1D | SamplerType.Array:
+ return Target.Texture1DArray;
+
+ case SamplerType.Texture2D:
+ return Target.Texture2D;
+
+ case SamplerType.Texture2D | SamplerType.Array:
+ return Target.Texture2DArray;
+
+ case SamplerType.Texture2D | SamplerType.Multisample:
+ return Target.Texture2DMultisample;
+
+ case SamplerType.Texture2D | SamplerType.Multisample | SamplerType.Array:
+ return Target.Texture2DMultisampleArray;
+
+ case SamplerType.Texture3D:
+ return Target.Texture3D;
+
+ case SamplerType.TextureCube:
+ return Target.Cubemap;
+
+ case SamplerType.TextureCube | SamplerType.Array:
+ return Target.CubemapArray;
+ }
+
+ Logger.PrintWarning(LogClass.Gpu, $"Invalid sampler type \"{type}\".");
+
+ return Target.Texture2D;
+ }
+
+ ///
+ /// Issues a texture barrier.
+ /// This waits until previous texture writes from the GPU to finish, before
+ /// performing new operations with said textures.
+ ///
+ /// Current GPU state (unused)
+ /// Method call argument (unused)
+ private void TextureBarrier(GpuState state, int argument)
+ {
+ _context.Renderer.Pipeline.TextureBarrier();
+ }
+
+ ///
+ /// Invalidates all modified textures on the cache.
+ ///
+ /// Current GPU state (unused)
+ /// Method call argument (unused)
+ private void InvalidateTextures(GpuState state, int argument)
+ {
+ TextureManager.Flush();
+ }
+
+ ///
+ /// Issues a texture barrier.
+ /// This waits until previous texture writes from the GPU to finish, before
+ /// performing new operations with said textures.
+ /// This performs a per-tile wait, it is only valid if both the previous write
+ /// and current access has the same access patterns.
+ /// This may be faster than the regular barrier on tile-based rasterizers.
+ ///
+ /// Current GPU state (unused)
+ /// Method call argument (unused)
+ private void TextureBarrierTiled(GpuState state, int argument)
+ {
+ _context.Renderer.Pipeline.TextureBarrierTiled();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/GpuContext.cs b/Ryujinx.Graphics.Gpu/GpuContext.cs
new file mode 100644
index 0000000000..b644c54ddb
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/GpuContext.cs
@@ -0,0 +1,121 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.Engine;
+using Ryujinx.Graphics.Gpu.Memory;
+using System;
+
+namespace Ryujinx.Graphics.Gpu
+{
+ ///
+ /// GPU emulation context.
+ ///
+ public sealed class GpuContext : IDisposable
+ {
+ ///
+ /// Host renderer.
+ ///
+ public IRenderer Renderer { get; }
+
+ ///
+ /// Physical memory access (it actually accesses the process memory, not actual physical memory).
+ ///
+ internal PhysicalMemory PhysicalMemory { get; private set; }
+
+ ///
+ /// GPU memory manager.
+ ///
+ public MemoryManager MemoryManager { get; }
+
+ ///
+ /// GPU memory accessor.
+ ///
+ public MemoryAccessor MemoryAccessor { get; }
+
+ ///
+ /// GPU engine methods processing.
+ ///
+ internal Methods Methods { get; }
+
+ ///
+ /// GPU commands FIFO.
+ ///
+ internal NvGpuFifo Fifo { get; }
+
+ ///
+ /// DMA pusher.
+ ///
+ public DmaPusher DmaPusher { get; }
+
+ ///
+ /// Presentation window.
+ ///
+ public Window Window { get; }
+
+ ///
+ /// Internal sequence number, used to avoid needless resource data updates
+ /// in the middle of a command buffer before synchronizations.
+ ///
+ internal int SequenceNumber { get; private set; }
+
+ private readonly Lazy _caps;
+
+ ///
+ /// Host hardware capabilities.
+ ///
+ internal Capabilities Capabilities => _caps.Value;
+
+ ///
+ /// Creates a new instance of the GPU emulation context.
+ ///
+ /// Host renderer
+ public GpuContext(IRenderer renderer)
+ {
+ Renderer = renderer;
+
+ MemoryManager = new MemoryManager();
+
+ MemoryAccessor = new MemoryAccessor(this);
+
+ Methods = new Methods(this);
+
+ Fifo = new NvGpuFifo(this);
+
+ DmaPusher = new DmaPusher(this);
+
+ Window = new Window(this);
+
+ _caps = new Lazy(Renderer.GetCapabilities);
+ }
+
+ ///
+ /// Advances internal sequence number.
+ /// This forces the update of any modified GPU resource.
+ ///
+ internal void AdvanceSequence()
+ {
+ SequenceNumber++;
+ }
+
+ ///
+ /// Sets the process memory manager, after the application process is initialized.
+ /// This is required for any GPU memory access.
+ ///
+ /// CPU memory manager
+ public void SetVmm(ARMeilleure.Memory.MemoryManager cpuMemory)
+ {
+ PhysicalMemory = new PhysicalMemory(cpuMemory);
+ }
+
+ ///
+ /// Disposes all GPU resources currently cached.
+ /// It's an error to push any GPU commands after disposal.
+ /// Additionally, the GPU commands FIFO must be empty for disposal,
+ /// and processing of all commands must have finished.
+ ///
+ public void Dispose()
+ {
+ Methods.ShaderCache.Dispose();
+ Methods.BufferManager.Dispose();
+ Methods.TextureManager.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/GraphicsConfig.cs b/Ryujinx.Graphics.Gpu/GraphicsConfig.cs
new file mode 100644
index 0000000000..468d3a3426
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/GraphicsConfig.cs
@@ -0,0 +1,21 @@
+namespace Ryujinx.Graphics.Gpu
+{
+ ///
+ /// General GPU and graphics configuration.
+ ///
+ public static class GraphicsConfig
+ {
+ ///
+ /// Base directory used to write shader code dumps.
+ /// Set to null to disable code dumping.
+ ///
+ public static string ShadersDumpPath;
+
+ ///
+ /// Fast GPU time calculates the internal GPU time ticks as if the GPU was capable of
+ /// processing commands almost instantly, instead of using the host timer.
+ /// This can avoid lower resolution on some games when GPU performance is poor.
+ ///
+ public static bool FastGpuTime = true;
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs b/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs
new file mode 100644
index 0000000000..d66eab93f0
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs
@@ -0,0 +1,87 @@
+using System.Collections;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ ///
+ /// A texture cache that automatically removes older textures that are not used for some time.
+ /// The cache works with a rotated list with a fixed size. When new textures are added, the
+ /// old ones at the bottom of the list are deleted.
+ ///
+ class AutoDeleteCache : IEnumerable
+ {
+ private const int MaxCapacity = 2048;
+
+ private readonly LinkedList _textures;
+
+ ///
+ /// Creates a new instance of the automatic deletion cache.
+ ///
+ public AutoDeleteCache()
+ {
+ _textures = new LinkedList();
+ }
+
+ ///
+ /// Adds a new texture to the cache, even if the texture added is already on the cache.
+ ///
+ ///
+ /// Using this method is only recommended if you know that the texture is not yet on the cache,
+ /// otherwise it would store the same texture more than once.
+ ///
+ /// The texture to be added to the cache
+ public void Add(Texture texture)
+ {
+ texture.IncrementReferenceCount();
+
+ texture.CacheNode = _textures.AddLast(texture);
+
+ if (_textures.Count > MaxCapacity)
+ {
+ Texture oldestTexture = _textures.First.Value;
+
+ _textures.RemoveFirst();
+
+ oldestTexture.DecrementReferenceCount();
+
+ oldestTexture.CacheNode = null;
+ }
+ }
+
+ ///
+ /// Adds a new texture to the cache, or just moves it to the top of the list if the
+ /// texture is already on the cache.
+ ///
+ ///
+ /// Moving the texture to the top of the list prevents it from being deleted,
+ /// as the textures on the bottom of the list are deleted when new ones are added.
+ ///
+ /// The texture to be added, or moved to the top
+ public void Lift(Texture texture)
+ {
+ if (texture.CacheNode != null)
+ {
+ if (texture.CacheNode != _textures.Last)
+ {
+ _textures.Remove(texture.CacheNode);
+
+ texture.CacheNode = _textures.AddLast(texture);
+ }
+ }
+ else
+ {
+ Add(texture);
+ }
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ return _textures.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return _textures.GetEnumerator();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/FormatInfo.cs b/Ryujinx.Graphics.Gpu/Image/FormatInfo.cs
new file mode 100644
index 0000000000..12f3aecbbd
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/FormatInfo.cs
@@ -0,0 +1,65 @@
+using Ryujinx.Graphics.GAL;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ ///
+ /// Represents texture format information.
+ ///
+ struct FormatInfo
+ {
+ ///
+ /// A default, generic RGBA8 texture format.
+ ///
+ public static FormatInfo Default { get; } = new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4);
+
+ ///
+ /// The format of the texture data.
+ ///
+ public Format Format { get; }
+
+ ///
+ /// The block width for compressed formats.
+ ///
+ ///
+ /// Must be 1 for non-compressed formats.
+ ///
+ public int BlockWidth { get; }
+
+ ///
+ /// The block height for compressed formats.
+ ///
+ ///
+ /// Must be 1 for non-compressed formats.
+ ///
+ public int BlockHeight { get; }
+
+ ///
+ /// The number of bytes occupied by a single pixel in memory of the texture data.
+ ///
+ public int BytesPerPixel { get; }
+
+ ///
+ /// Whenever or not the texture format is a compressed format. Determined from block size.
+ ///
+ public bool IsCompressed => (BlockWidth | BlockHeight) != 1;
+
+ ///
+ /// Constructs the texture format info structure.
+ ///
+ /// The format of the texture data
+ /// The block width for compressed formats. Must be 1 for non-compressed formats
+ /// The block height for compressed formats. Must be 1 for non-compressed formats
+ /// The number of bytes occupied by a single pixel in memory of the texture data
+ public FormatInfo(
+ Format format,
+ int blockWidth,
+ int blockHeight,
+ int bytesPerPixel)
+ {
+ Format = format;
+ BlockWidth = blockWidth;
+ BlockHeight = blockHeight;
+ BytesPerPixel = bytesPerPixel;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/FormatTable.cs b/Ryujinx.Graphics.Gpu/Image/FormatTable.cs
new file mode 100644
index 0000000000..517caecaf8
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/FormatTable.cs
@@ -0,0 +1,217 @@
+using Ryujinx.Graphics.GAL;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ ///
+ /// Contains format tables, for texture and vertex attribute formats.
+ ///
+ static class FormatTable
+ {
+ private static Dictionary _textureFormats = new Dictionary()
+ {
+ { 0x2491d, new FormatInfo(Format.R8Unorm, 1, 1, 1) },
+ { 0x1249d, new FormatInfo(Format.R8Snorm, 1, 1, 1) },
+ { 0x4921d, new FormatInfo(Format.R8Uint, 1, 1, 1) },
+ { 0x36d9d, new FormatInfo(Format.R8Sint, 1, 1, 1) },
+ { 0x7ff9b, new FormatInfo(Format.R16Float, 1, 1, 2) },
+ { 0x2491b, new FormatInfo(Format.R16Unorm, 1, 1, 2) },
+ { 0x1249b, new FormatInfo(Format.R16Snorm, 1, 1, 2) },
+ { 0x4921b, new FormatInfo(Format.R16Uint, 1, 1, 2) },
+ { 0x36d9b, new FormatInfo(Format.R16Sint, 1, 1, 2) },
+ { 0x7ff8f, new FormatInfo(Format.R32Float, 1, 1, 4) },
+ { 0x4920f, new FormatInfo(Format.R32Uint, 1, 1, 4) },
+ { 0x36d8f, new FormatInfo(Format.R32Sint, 1, 1, 4) },
+ { 0x24918, new FormatInfo(Format.R8G8Unorm, 1, 1, 2) },
+ { 0x12498, new FormatInfo(Format.R8G8Snorm, 1, 1, 2) },
+ { 0x49218, new FormatInfo(Format.R8G8Uint, 1, 1, 2) },
+ { 0x36d98, new FormatInfo(Format.R8G8Sint, 1, 1, 2) },
+ { 0x7ff8c, new FormatInfo(Format.R16G16Float, 1, 1, 4) },
+ { 0x2490c, new FormatInfo(Format.R16G16Unorm, 1, 1, 4) },
+ { 0x1248c, new FormatInfo(Format.R16G16Snorm, 1, 1, 4) },
+ { 0x4920c, new FormatInfo(Format.R16G16Uint, 1, 1, 4) },
+ { 0x36d8c, new FormatInfo(Format.R16G16Sint, 1, 1, 4) },
+ { 0x7ff84, new FormatInfo(Format.R32G32Float, 1, 1, 8) },
+ { 0x49204, new FormatInfo(Format.R32G32Uint, 1, 1, 8) },
+ { 0x36d84, new FormatInfo(Format.R32G32Sint, 1, 1, 8) },
+ { 0x7ff82, new FormatInfo(Format.R32G32B32Float, 1, 1, 12) },
+ { 0x49202, new FormatInfo(Format.R32G32B32Uint, 1, 1, 12) },
+ { 0x36d82, new FormatInfo(Format.R32G32B32Sint, 1, 1, 12) },
+ { 0x24908, new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4) },
+ { 0x12488, new FormatInfo(Format.R8G8B8A8Snorm, 1, 1, 4) },
+ { 0x49208, new FormatInfo(Format.R8G8B8A8Uint, 1, 1, 4) },
+ { 0x36d88, new FormatInfo(Format.R8G8B8A8Sint, 1, 1, 4) },
+ { 0x7ff83, new FormatInfo(Format.R16G16B16A16Float, 1, 1, 8) },
+ { 0x24903, new FormatInfo(Format.R16G16B16A16Unorm, 1, 1, 8) },
+ { 0x12483, new FormatInfo(Format.R16G16B16A16Snorm, 1, 1, 8) },
+ { 0x49203, new FormatInfo(Format.R16G16B16A16Uint, 1, 1, 8) },
+ { 0x36d83, new FormatInfo(Format.R16G16B16A16Sint, 1, 1, 8) },
+ { 0x7ff81, new FormatInfo(Format.R32G32B32A32Float, 1, 1, 16) },
+ { 0x49201, new FormatInfo(Format.R32G32B32A32Uint, 1, 1, 16) },
+ { 0x36d81, new FormatInfo(Format.R32G32B32A32Sint, 1, 1, 16) },
+ { 0x2493a, new FormatInfo(Format.D16Unorm, 1, 1, 2) },
+ { 0x7ffaf, new FormatInfo(Format.D32Float, 1, 1, 4) },
+ { 0x24a29, new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4) },
+ { 0x253b0, new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8) },
+ { 0xa4908, new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4) },
+ { 0x24912, new FormatInfo(Format.R4G4B4A4Unorm, 1, 1, 2) },
+ { 0x24914, new FormatInfo(Format.R5G5B5A1Unorm, 1, 1, 2) },
+ { 0x24915, new FormatInfo(Format.R5G6B5Unorm, 1, 1, 2) },
+ { 0x24909, new FormatInfo(Format.R10G10B10A2Unorm, 1, 1, 4) },
+ { 0x49209, new FormatInfo(Format.R10G10B10A2Uint, 1, 1, 4) },
+ { 0x7ffa1, new FormatInfo(Format.R11G11B10Float, 1, 1, 4) },
+ { 0x7ffa0, new FormatInfo(Format.R9G9B9E5Float, 1, 1, 4) },
+ { 0x24924, new FormatInfo(Format.Bc1RgbaUnorm, 4, 4, 8) },
+ { 0x24925, new FormatInfo(Format.Bc2Unorm, 4, 4, 16) },
+ { 0x24926, new FormatInfo(Format.Bc3Unorm, 4, 4, 16) },
+ { 0xa4924, new FormatInfo(Format.Bc1RgbaSrgb, 4, 4, 8) },
+ { 0xa4925, new FormatInfo(Format.Bc2Srgb, 4, 4, 16) },
+ { 0xa4926, new FormatInfo(Format.Bc3Srgb, 4, 4, 16) },
+ { 0x24927, new FormatInfo(Format.Bc4Unorm, 4, 4, 8) },
+ { 0x124a7, new FormatInfo(Format.Bc4Snorm, 4, 4, 8) },
+ { 0x24928, new FormatInfo(Format.Bc5Unorm, 4, 4, 16) },
+ { 0x124a8, new FormatInfo(Format.Bc5Snorm, 4, 4, 16) },
+ { 0x24917, new FormatInfo(Format.Bc7Unorm, 4, 4, 16) },
+ { 0xa4917, new FormatInfo(Format.Bc7Srgb, 4, 4, 16) },
+ { 0x7ff90, new FormatInfo(Format.Bc6HSfloat, 4, 4, 16) },
+ { 0x7ff91, new FormatInfo(Format.Bc6HUfloat, 4, 4, 16) },
+ { 0x24940, new FormatInfo(Format.Astc4x4Unorm, 4, 4, 16) },
+ { 0x24950, new FormatInfo(Format.Astc5x4Unorm, 5, 4, 16) },
+ { 0x24941, new FormatInfo(Format.Astc5x5Unorm, 5, 5, 16) },
+ { 0x24951, new FormatInfo(Format.Astc6x5Unorm, 6, 5, 16) },
+ { 0x24942, new FormatInfo(Format.Astc6x6Unorm, 6, 6, 16) },
+ { 0x24955, new FormatInfo(Format.Astc8x5Unorm, 8, 5, 16) },
+ { 0x24952, new FormatInfo(Format.Astc8x6Unorm, 8, 6, 16) },
+ { 0x24944, new FormatInfo(Format.Astc8x8Unorm, 8, 8, 16) },
+ { 0x24956, new FormatInfo(Format.Astc10x5Unorm, 10, 5, 16) },
+ { 0x24957, new FormatInfo(Format.Astc10x6Unorm, 10, 6, 16) },
+ { 0x24953, new FormatInfo(Format.Astc10x8Unorm, 10, 8, 16) },
+ { 0x24945, new FormatInfo(Format.Astc10x10Unorm, 10, 10, 16) },
+ { 0x24954, new FormatInfo(Format.Astc12x10Unorm, 12, 10, 16) },
+ { 0x24946, new FormatInfo(Format.Astc12x12Unorm, 12, 12, 16) },
+ { 0xa4940, new FormatInfo(Format.Astc4x4Srgb, 4, 4, 16) },
+ { 0xa4950, new FormatInfo(Format.Astc5x4Srgb, 5, 4, 16) },
+ { 0xa4941, new FormatInfo(Format.Astc5x5Srgb, 5, 5, 16) },
+ { 0xa4951, new FormatInfo(Format.Astc6x5Srgb, 6, 5, 16) },
+ { 0xa4942, new FormatInfo(Format.Astc6x6Srgb, 6, 6, 16) },
+ { 0xa4955, new FormatInfo(Format.Astc8x5Srgb, 8, 5, 16) },
+ { 0xa4952, new FormatInfo(Format.Astc8x6Srgb, 8, 6, 16) },
+ { 0xa4944, new FormatInfo(Format.Astc8x8Srgb, 8, 8, 16) },
+ { 0xa4956, new FormatInfo(Format.Astc10x5Srgb, 10, 5, 16) },
+ { 0xa4957, new FormatInfo(Format.Astc10x6Srgb, 10, 6, 16) },
+ { 0xa4953, new FormatInfo(Format.Astc10x8Srgb, 10, 8, 16) },
+ { 0xa4945, new FormatInfo(Format.Astc10x10Srgb, 10, 10, 16) },
+ { 0xa4954, new FormatInfo(Format.Astc12x10Srgb, 12, 10, 16) },
+ { 0xa4946, new FormatInfo(Format.Astc12x12Srgb, 12, 12, 16) },
+ { 0x24913, new FormatInfo(Format.A1B5G5R5Unorm, 1, 1, 2) }
+ };
+
+ private static Dictionary _attribFormats = new Dictionary()
+ {
+ { 0x13a00000, Format.R8Unorm },
+ { 0x0ba00000, Format.R8Snorm },
+ { 0x23a00000, Format.R8Uint },
+ { 0x1ba00000, Format.R8Sint },
+ { 0x3b600000, Format.R16Float },
+ { 0x13600000, Format.R16Unorm },
+ { 0x0b600000, Format.R16Snorm },
+ { 0x23600000, Format.R16Uint },
+ { 0x1b600000, Format.R16Sint },
+ { 0x3a400000, Format.R32Float },
+ { 0x22400000, Format.R32Uint },
+ { 0x1a400000, Format.R32Sint },
+ { 0x13000000, Format.R8G8Unorm },
+ { 0x0b000000, Format.R8G8Snorm },
+ { 0x23000000, Format.R8G8Uint },
+ { 0x1b000000, Format.R8G8Sint },
+ { 0x39e00000, Format.R16G16Float },
+ { 0x11e00000, Format.R16G16Unorm },
+ { 0x09e00000, Format.R16G16Snorm },
+ { 0x21e00000, Format.R16G16Uint },
+ { 0x19e00000, Format.R16G16Sint },
+ { 0x38800000, Format.R32G32Float },
+ { 0x20800000, Format.R32G32Uint },
+ { 0x18800000, Format.R32G32Sint },
+ { 0x12600000, Format.R8G8B8Unorm },
+ { 0x0a600000, Format.R8G8B8Snorm },
+ { 0x22600000, Format.R8G8B8Uint },
+ { 0x1a600000, Format.R8G8B8Sint },
+ { 0x38a00000, Format.R16G16B16Float },
+ { 0x10a00000, Format.R16G16B16Unorm },
+ { 0x08a00000, Format.R16G16B16Snorm },
+ { 0x20a00000, Format.R16G16B16Uint },
+ { 0x18a00000, Format.R16G16B16Sint },
+ { 0x38400000, Format.R32G32B32Float },
+ { 0x20400000, Format.R32G32B32Uint },
+ { 0x18400000, Format.R32G32B32Sint },
+ { 0x11400000, Format.R8G8B8A8Unorm },
+ { 0x09400000, Format.R8G8B8A8Snorm },
+ { 0x21400000, Format.R8G8B8A8Uint },
+ { 0x19400000, Format.R8G8B8A8Sint },
+ { 0x38600000, Format.R16G16B16A16Float },
+ { 0x10600000, Format.R16G16B16A16Unorm },
+ { 0x08600000, Format.R16G16B16A16Snorm },
+ { 0x20600000, Format.R16G16B16A16Uint },
+ { 0x18600000, Format.R16G16B16A16Sint },
+ { 0x38200000, Format.R32G32B32A32Float },
+ { 0x20200000, Format.R32G32B32A32Uint },
+ { 0x18200000, Format.R32G32B32A32Sint },
+ { 0x16000000, Format.R10G10B10A2Unorm },
+ { 0x26000000, Format.R10G10B10A2Uint },
+ { 0x3e200000, Format.R11G11B10Float },
+ { 0x2ba00000, Format.R8Uscaled },
+ { 0x33a00000, Format.R8Sscaled },
+ { 0x2b600000, Format.R16Uscaled },
+ { 0x33600000, Format.R16Sscaled },
+ { 0x2a400000, Format.R32Uscaled },
+ { 0x32400000, Format.R32Sscaled },
+ { 0x2b000000, Format.R8G8Uscaled },
+ { 0x33000000, Format.R8G8Sscaled },
+ { 0x29e00000, Format.R16G16Uscaled },
+ { 0x31e00000, Format.R16G16Sscaled },
+ { 0x28800000, Format.R32G32Uscaled },
+ { 0x30800000, Format.R32G32Sscaled },
+ { 0x2a600000, Format.R8G8B8Uscaled },
+ { 0x32600000, Format.R8G8B8Sscaled },
+ { 0x28a00000, Format.R16G16B16Uscaled },
+ { 0x30a00000, Format.R16G16B16Sscaled },
+ { 0x28400000, Format.R32G32B32Uscaled },
+ { 0x30400000, Format.R32G32B32Sscaled },
+ { 0x29400000, Format.R8G8B8A8Uscaled },
+ { 0x31400000, Format.R8G8B8A8Sscaled },
+ { 0x28600000, Format.R16G16B16A16Uscaled },
+ { 0x30600000, Format.R16G16B16A16Sscaled },
+ { 0x28200000, Format.R32G32B32A32Uscaled },
+ { 0x30200000, Format.R32G32B32A32Sscaled },
+ { 0x0e000000, Format.R10G10B10A2Snorm },
+ { 0x1e000000, Format.R10G10B10A2Sint },
+ { 0x2e000000, Format.R10G10B10A2Uscaled },
+ { 0x36000000, Format.R10G10B10A2Sscaled }
+ };
+
+ ///
+ /// Try getting the texture format from an encoded format integer from the Maxwell texture descriptor.
+ ///
+ /// The encoded format integer from the texture descriptor
+ /// Indicates if the format is a sRGB format
+ /// The output texture format
+ /// True if the format is valid, false otherwise
+ public static bool TryGetTextureFormat(uint encoded, bool isSrgb, out FormatInfo format)
+ {
+ encoded |= (isSrgb ? 1u << 19 : 0u);
+
+ return _textureFormats.TryGetValue(encoded, out format);
+ }
+
+ ///
+ /// Try getting the vertex attribute format from an encoded format integer from Maxwell attribute registers.
+ ///
+ /// The encoded format integer from the attribute registers
+ /// The output vertex attribute format
+ /// True if the format is valid, false otherwise
+ public static bool TryGetAttribFormat(uint encoded, out Format format)
+ {
+ return _attribFormats.TryGetValue(encoded, out format);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/Pool.cs b/Ryujinx.Graphics.Gpu/Image/Pool.cs
new file mode 100644
index 0000000000..7cf06d0b01
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/Pool.cs
@@ -0,0 +1,143 @@
+using Ryujinx.Graphics.Gpu.Memory;
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ ///
+ /// Represents a pool of GPU resources, such as samplers or textures.
+ ///
+ /// Type of the GPU resource
+ abstract class Pool : IDisposable
+ {
+ protected const int DescriptorSize = 0x20;
+
+ protected GpuContext Context;
+
+ protected T[] Items;
+
+ ///
+ /// The maximum ID value of resources on the pool (inclusive).
+ ///
+ ///
+ /// The maximum amount of resources on the pool is equal to this value plus one.
+ ///
+ public int MaximumId { get; }
+
+ ///
+ /// The address of the pool in guest memory.
+ ///
+ public ulong Address { get; }
+
+ ///
+ /// The size of the pool in bytes.
+ ///
+ public ulong Size { get; }
+
+ public Pool(GpuContext context, ulong address, int maximumId)
+ {
+ Context = context;
+ MaximumId = maximumId;
+
+ int count = maximumId + 1;
+
+ ulong size = (ulong)(uint)count * DescriptorSize;;
+
+ Items = new T[count];
+
+ Address = address;
+ Size = size;
+ }
+
+ ///
+ /// Gets the GPU resource with the given ID.
+ ///
+ /// ID of the resource. This is effectively a zero-based index
+ /// The GPU resource with the given ID
+ public abstract T Get(int id);
+
+ ///
+ /// Synchronizes host memory with guest memory.
+ /// This causes invalidation of pool entries,
+ /// if a modification of entries by the CPU is detected.
+ ///
+ public void SynchronizeMemory()
+ {
+ (ulong, ulong)[] modifiedRanges = Context.PhysicalMemory.GetModifiedRanges(Address, Size, ResourceName.TexturePool);
+
+ for (int index = 0; index < modifiedRanges.Length; index++)
+ {
+ (ulong mAddress, ulong mSize) = modifiedRanges[index];
+
+ if (mAddress < Address)
+ {
+ mAddress = Address;
+ }
+
+ ulong maxSize = Address + Size - mAddress;
+
+ if (mSize > maxSize)
+ {
+ mSize = maxSize;
+ }
+
+ InvalidateRangeImpl(mAddress, mSize);
+ }
+ }
+
+ ///
+ /// Invalidates a range of memory of the GPU resource pool.
+ /// Entries that falls inside the speicified range will be invalidated,
+ /// causing all the data to be reloaded from guest memory.
+ ///
+ /// The start address of the range to invalidate
+ /// The size of the range to invalidate
+ public void InvalidateRange(ulong address, ulong size)
+ {
+ ulong endAddress = address + size;
+
+ ulong texturePoolEndAddress = Address + Size;
+
+ // If the range being invalidated is not overlapping the texture pool range,
+ // then we don't have anything to do, exit early.
+ if (address >= texturePoolEndAddress || endAddress <= Address)
+ {
+ return;
+ }
+
+ if (address < Address)
+ {
+ address = Address;
+ }
+
+ if (endAddress > texturePoolEndAddress)
+ {
+ endAddress = texturePoolEndAddress;
+ }
+
+ size = endAddress - address;
+
+ InvalidateRangeImpl(address, size);
+ }
+
+ protected abstract void InvalidateRangeImpl(ulong address, ulong size);
+
+ protected abstract void Delete(T item);
+
+ ///
+ /// Performs the disposal of all resources stored on the pool.
+ /// It's an error to try using the pool after disposal.
+ ///
+ public void Dispose()
+ {
+ if (Items != null)
+ {
+ for (int index = 0; index < Items.Length; index++)
+ {
+ Delete(Items[index]);
+ }
+
+ Items = null;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/ReductionFilter.cs b/Ryujinx.Graphics.Gpu/Image/ReductionFilter.cs
new file mode 100644
index 0000000000..1f7d9b0703
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/ReductionFilter.cs
@@ -0,0 +1,15 @@
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ ///
+ /// Represents a filter used with texture minification linear filtering.
+ ///
+ ///
+ /// This feature is only supported on NVIDIA GPUs.
+ ///
+ enum ReductionFilter
+ {
+ Average,
+ Minimum,
+ Maximum
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/Image/Sampler.cs b/Ryujinx.Graphics.Gpu/Image/Sampler.cs
new file mode 100644
index 0000000000..23c6160e39
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/Sampler.cs
@@ -0,0 +1,64 @@
+using Ryujinx.Graphics.GAL;
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ ///
+ /// Cached sampler entry for sampler pools.
+ ///
+ class Sampler : IDisposable
+ {
+ ///
+ /// Host sampler object.
+ ///
+ public ISampler HostSampler { get; }
+
+ ///
+ /// Creates a new instance of the cached sampler.
+ ///
+ /// The GPU context the sampler belongs to
+ /// The Maxwell sampler descriptor
+ public Sampler(GpuContext context, SamplerDescriptor descriptor)
+ {
+ MinFilter minFilter = descriptor.UnpackMinFilter();
+ MagFilter magFilter = descriptor.UnpackMagFilter();
+
+ AddressMode addressU = descriptor.UnpackAddressU();
+ AddressMode addressV = descriptor.UnpackAddressV();
+ AddressMode addressP = descriptor.UnpackAddressP();
+
+ CompareMode compareMode = descriptor.UnpackCompareMode();
+ CompareOp compareOp = descriptor.UnpackCompareOp();
+
+ ColorF color = new ColorF(0, 0, 0, 0);
+
+ float minLod = descriptor.UnpackMinLod();
+ float maxLod = descriptor.UnpackMaxLod();
+ float mipLodBias = descriptor.UnpackMipLodBias();
+
+ float maxAnisotropy = descriptor.UnpackMaxAnisotropy();
+
+ HostSampler = context.Renderer.CreateSampler(new SamplerCreateInfo(
+ minFilter,
+ magFilter,
+ addressU,
+ addressV,
+ addressP,
+ compareMode,
+ compareOp,
+ color,
+ minLod,
+ maxLod,
+ mipLodBias,
+ maxAnisotropy));
+ }
+
+ ///
+ /// Disposes the host sampler object.
+ ///
+ public void Dispose()
+ {
+ HostSampler.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/SamplerDescriptor.cs b/Ryujinx.Graphics.Gpu/Image/SamplerDescriptor.cs
new file mode 100644
index 0000000000..80b1b70fd5
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/SamplerDescriptor.cs
@@ -0,0 +1,237 @@
+using Ryujinx.Graphics.GAL;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ ///
+ /// Maxwell sampler descriptor structure.
+ /// This structure defines the sampler descriptor as it is packed on the GPU sampler pool region.
+ ///
+ struct SamplerDescriptor
+ {
+ private static readonly float[] _f5ToF32ConversionLut = new float[]
+ {
+ 0.0f,
+ 0.055555556f,
+ 0.1f,
+ 0.13636364f,
+ 0.16666667f,
+ 0.1923077f,
+ 0.21428572f,
+ 0.23333333f,
+ 0.25f,
+ 0.2777778f,
+ 0.3f,
+ 0.3181818f,
+ 0.33333334f,
+ 0.34615386f,
+ 0.35714287f,
+ 0.36666667f,
+ 0.375f,
+ 0.3888889f,
+ 0.4f,
+ 0.4090909f,
+ 0.41666666f,
+ 0.42307693f,
+ 0.42857143f,
+ 0.43333334f,
+ 0.4375f,
+ 0.44444445f,
+ 0.45f,
+ 0.45454547f,
+ 0.45833334f,
+ 0.46153846f,
+ 0.4642857f,
+ 0.46666667f
+ };
+
+ private static readonly float[] _maxAnisotropyLut = new float[]
+ {
+ 1, 2, 4, 6, 8, 10, 12, 16
+ };
+
+ private const float Frac8ToF32 = 1.0f / 256.0f;
+
+ public uint Word0;
+ public uint Word1;
+ public uint Word2;
+ public uint Word3;
+ public uint BorderColorR;
+ public uint BorderColorG;
+ public uint BorderColorB;
+ public uint BorderColorA;
+
+ ///
+ /// Unpacks the texture wrap mode along the X axis.
+ ///
+ /// The texture wrap mode enum
+ public AddressMode UnpackAddressU()
+ {
+ return (AddressMode)(Word0 & 7);
+ }
+
+ //
+ /// Unpacks the texture wrap mode along the Y axis.
+ ///
+ /// The texture wrap mode enum
+ public AddressMode UnpackAddressV()
+ {
+ return (AddressMode)((Word0 >> 3) & 7);
+ }
+
+ //
+ /// Unpacks the texture wrap mode along the Z axis.
+ ///
+ /// The texture wrap mode enum
+ public AddressMode UnpackAddressP()
+ {
+ return (AddressMode)((Word0 >> 6) & 7);
+ }
+
+ ///
+ /// Unpacks the compare mode used for depth comparison on the shader, for
+ /// depth buffer texture.
+ /// This is only relevant for shaders with shadow samplers.
+ ///
+ /// The depth comparison mode enum
+ public CompareMode UnpackCompareMode()
+ {
+ return (CompareMode)((Word0 >> 9) & 1);
+ }
+
+ ///
+ /// Unpacks the compare operation used for depth comparison on the shader, for
+ /// depth buffer texture.
+ /// This is only relevant for shaders with shadow samplers.
+ ///
+ /// The depth comparison operation enum
+ public CompareOp UnpackCompareOp()
+ {
+ return (CompareOp)(((Word0 >> 10) & 7) + 1);
+ }
+
+ ///
+ /// Unpacks and converts the maximum anisotropy value used for texture anisotropic filtering.
+ ///
+ /// The maximum anisotropy
+ public float UnpackMaxAnisotropy()
+ {
+ return _maxAnisotropyLut[(Word0 >> 20) & 7];
+ }
+
+ ///
+ /// Unpacks the texture magnification filter.
+ /// This defines the filtering used when the texture covers an area on the screen
+ /// that is larger than the texture size.
+ ///
+ /// The magnification filter
+ public MagFilter UnpackMagFilter()
+ {
+ return (MagFilter)(Word1 & 3);
+ }
+
+ ///
+ /// Unpacks the texture minification filter.
+ /// This defines the filtering used when the texture covers an area on the screen
+ /// that is smaller than the texture size.
+ ///
+ /// The minification filter
+ public MinFilter UnpackMinFilter()
+ {
+ SamplerMinFilter minFilter = (SamplerMinFilter)((Word1 >> 4) & 3);
+ SamplerMipFilter mipFilter = (SamplerMipFilter)((Word1 >> 6) & 3);
+
+ return ConvertFilter(minFilter, mipFilter);
+ }
+
+ ///
+ /// Converts two minification and filter enum, to a single minification enum,
+ /// including mipmap filtering information, as expected from the host API.
+ ///
+ /// The minification filter
+ /// The mipmap level filter
+ /// The combined, host API compatible filter enum
+ private static MinFilter ConvertFilter(SamplerMinFilter minFilter, SamplerMipFilter mipFilter)
+ {
+ switch (mipFilter)
+ {
+ case SamplerMipFilter.None:
+ switch (minFilter)
+ {
+ case SamplerMinFilter.Nearest: return MinFilter.Nearest;
+ case SamplerMinFilter.Linear: return MinFilter.Linear;
+ }
+ break;
+
+ case SamplerMipFilter.Nearest:
+ switch (minFilter)
+ {
+ case SamplerMinFilter.Nearest: return MinFilter.NearestMipmapNearest;
+ case SamplerMinFilter.Linear: return MinFilter.LinearMipmapNearest;
+ }
+ break;
+
+ case SamplerMipFilter.Linear:
+ switch (minFilter)
+ {
+ case SamplerMinFilter.Nearest: return MinFilter.NearestMipmapLinear;
+ case SamplerMinFilter.Linear: return MinFilter.LinearMipmapLinear;
+ }
+ break;
+ }
+
+ return MinFilter.Nearest;
+ }
+
+ ///
+ /// Unpacks the reduction filter, used with texture minification linear filtering.
+ /// This describes how the final value will be computed from neighbouring pixels.
+ ///
+ /// The reduction filter
+ public ReductionFilter UnpackReductionFilter()
+ {
+ return (ReductionFilter)((Word1 >> 10) & 3);
+ }
+
+ ///
+ /// Unpacks the level-of-detail bias value.
+ /// This is a bias added to the level-of-detail value as computed by the GPU, used to select
+ /// which mipmap level to use from a given texture.
+ ///
+ /// The level-of-detail bias value
+ public float UnpackMipLodBias()
+ {
+ int fixedValue = (int)(Word1 >> 12) & 0x1fff;
+
+ fixedValue = (fixedValue << 19) >> 19;
+
+ return fixedValue * Frac8ToF32;
+ }
+
+ ///
+ /// Unpacks the level-of-detail snap value.
+ ///
+ /// The level-of-detail snap value
+ public float UnpackLodSnap()
+ {
+ return _f5ToF32ConversionLut[(Word1 >> 26) & 0x1f];
+ }
+
+ ///
+ /// Unpacks the minimum level-of-detail value.
+ ///
+ /// The minimum level-of-detail value
+ public float UnpackMinLod()
+ {
+ return (Word2 & 0xfff) * Frac8ToF32;
+ }
+
+ ///
+ /// Unpacks the maximum level-of-detail value.
+ ///
+ /// The maximum level-of-detail value
+ public float UnpackMaxLod()
+ {
+ return ((Word2 >> 12) & 0xfff) * Frac8ToF32;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/Image/SamplerMinFilter.cs b/Ryujinx.Graphics.Gpu/Image/SamplerMinFilter.cs
new file mode 100644
index 0000000000..17beb12933
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/SamplerMinFilter.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ ///
+ /// Sampler texture minification filter.
+ ///
+ enum SamplerMinFilter
+ {
+ Nearest = 1,
+ Linear
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/Image/SamplerMipFilter.cs b/Ryujinx.Graphics.Gpu/Image/SamplerMipFilter.cs
new file mode 100644
index 0000000000..319d419609
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/SamplerMipFilter.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ ///
+ /// Sampler texture mipmap level filter.
+ ///
+ enum SamplerMipFilter
+ {
+ None = 1,
+ Nearest,
+ Linear
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs b/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs
new file mode 100644
index 0000000000..f10f800cd7
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ ///
+ /// Sampler pool.
+ ///
+ class SamplerPool : Pool
+ {
+ private int _sequenceNumber;
+
+ ///
+ /// Constructs a new instance of the sampler pool.
+ ///
+ /// GPU context that the sampler pool belongs to
+ /// Address of the sampler pool in guest memory
+ /// Maximum sampler ID of the sampler pool (equal to maximum samplers minus one)
+ public SamplerPool(GpuContext context, ulong address, int maximumId) : base(context, address, maximumId) { }
+
+ ///
+ /// Gets the sampler with the given ID.
+ ///
+ /// ID of the sampler. This is effectively a zero-based index
+ /// The sampler with the given ID
+ public override Sampler Get(int id)
+ {
+ if ((uint)id >= Items.Length)
+ {
+ return null;
+ }
+
+ if (_sequenceNumber != Context.SequenceNumber)
+ {
+ _sequenceNumber = Context.SequenceNumber;
+
+ SynchronizeMemory();
+ }
+
+ Sampler sampler = Items[id];
+
+ if (sampler == null)
+ {
+ ulong address = Address + (ulong)(uint)id * DescriptorSize;
+
+ Span data = Context.PhysicalMemory.Read(address, DescriptorSize);
+
+ SamplerDescriptor descriptor = MemoryMarshal.Cast(data)[0];
+
+ sampler = new Sampler(Context, descriptor);
+
+ Items[id] = sampler;
+ }
+
+ return sampler;
+ }
+
+ ///
+ /// Implementation of the sampler pool range invalidation.
+ ///
+ /// Start address of the range of the sampler pool
+ /// Size of the range being invalidated
+ protected override void InvalidateRangeImpl(ulong address, ulong size)
+ {
+ ulong endAddress = address + size;
+
+ for (; address < endAddress; address += DescriptorSize)
+ {
+ int id = (int)((address - Address) / DescriptorSize);
+
+ Sampler sampler = Items[id];
+
+ if (sampler != null)
+ {
+ sampler.Dispose();
+
+ Items[id] = null;
+ }
+ }
+ }
+
+ ///
+ /// Deletes a given sampler pool entry.
+ /// The host memory used by the sampler is released by the driver.
+ ///
+ /// The entry to be deleted
+ protected override void Delete(Sampler item)
+ {
+ item?.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs
new file mode 100644
index 0000000000..e33de1fa4e
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/Texture.cs
@@ -0,0 +1,1025 @@
+using Ryujinx.Common;
+using Ryujinx.Common.Logging;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.Memory;
+using Ryujinx.Graphics.Texture;
+using Ryujinx.Graphics.Texture.Astc;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ ///
+ /// Represents a cached GPU texture.
+ ///
+ class Texture : IRange, IDisposable
+ {
+ private GpuContext _context;
+
+ private SizeInfo _sizeInfo;
+
+ ///
+ /// Texture format.
+ ///
+ public Format Format => Info.FormatInfo.Format;
+
+ ///
+ /// Texture information.
+ ///
+ public TextureInfo Info { get; private set; }
+
+ private int _depth;
+ private int _layers;
+ private readonly int _firstLayer;
+ private readonly int _firstLevel;
+
+ private bool _hasData;
+
+ private ITexture _arrayViewTexture;
+ private Target _arrayViewTarget;
+
+ private Texture _viewStorage;
+
+ private List _views;
+
+ ///
+ /// Host texture.
+ ///
+ public ITexture HostTexture { get; private set; }
+
+ ///
+ /// Intrusive linked list node used on the auto deletion texture cache.
+ ///
+ public LinkedListNode CacheNode { get; set; }
+
+ ///
+ /// Texture data modified by the GPU.
+ ///
+ public bool Modified { get; set; }
+
+ ///
+ /// Start address of the texture in guest memory.
+ ///
+ public ulong Address => Info.Address;
+
+ ///
+ /// End address of the texture in guest memory.
+ ///
+ public ulong EndAddress => Info.Address + Size;
+
+ ///
+ /// Texture size in bytes.
+ ///
+ public ulong Size => (ulong)_sizeInfo.TotalSize;
+
+ private int _referenceCount;
+
+ private int _sequenceNumber;
+
+ ///
+ /// Constructs a new instance of the cached GPU texture.
+ ///
+ /// GPU context that the texture belongs to
+ /// Texture information
+ /// Size information of the texture
+ /// The first layer of the texture, or 0 if the texture has no parent
+ /// The first mipmap level of the texture, or 0 if the texture has no parent
+ private Texture(
+ GpuContext context,
+ TextureInfo info,
+ SizeInfo sizeInfo,
+ int firstLayer,
+ int firstLevel)
+ {
+ InitializeTexture(context, info, sizeInfo);
+
+ _firstLayer = firstLayer;
+ _firstLevel = firstLevel;
+
+ _hasData = true;
+ }
+
+ ///
+ /// Constructs a new instance of the cached GPU texture.
+ ///
+ /// GPU context that the texture belongs to
+ /// Texture information
+ /// Size information of the texture
+ public Texture(GpuContext context, TextureInfo info, SizeInfo sizeInfo)
+ {
+ InitializeTexture(context, info, sizeInfo);
+
+ TextureCreateInfo createInfo = TextureManager.GetCreateInfo(info, context.Capabilities);
+
+ HostTexture = _context.Renderer.CreateTexture(createInfo);
+ }
+
+ ///
+ /// Common texture initialization method.
+ /// This sets the context, info and sizeInfo fields.
+ /// Other fields are initialized with their default values.
+ ///
+ /// GPU context that the texture belongs to
+ /// Texture information
+ /// Size information of the texture
+ private void InitializeTexture(GpuContext context, TextureInfo info, SizeInfo sizeInfo)
+ {
+ _context = context;
+ _sizeInfo = sizeInfo;
+
+ SetInfo(info);
+
+ _viewStorage = this;
+
+ _views = new List();
+ }
+
+ ///
+ /// Create a texture view from this texture.
+ /// A texture view is defined as a child texture, from a sub-range of their parent texture.
+ /// For example, the initial layer and mipmap level of the view can be defined, so the texture
+ /// will start at the given layer/level of the parent texture.
+ ///
+ /// Child texture information
+ /// Child texture size information
+ /// Start layer of the child texture on the parent texture
+ /// Start mipmap level of the child texture on the parent texture
+ /// The child texture
+ public Texture CreateView(TextureInfo info, SizeInfo sizeInfo, int firstLayer, int firstLevel)
+ {
+ Texture texture = new Texture(
+ _context,
+ info,
+ sizeInfo,
+ _firstLayer + firstLayer,
+ _firstLevel + firstLevel);
+
+ TextureCreateInfo createInfo = TextureManager.GetCreateInfo(info, _context.Capabilities);
+
+ texture.HostTexture = HostTexture.CreateView(createInfo, firstLayer, firstLevel);
+
+ _viewStorage.AddView(texture);
+
+ return texture;
+ }
+
+ ///
+ /// Adds a child texture to this texture.
+ ///
+ /// The child texture
+ private void AddView(Texture texture)
+ {
+ _views.Add(texture);
+
+ texture._viewStorage = this;
+ }
+
+ ///
+ /// Removes a child texture from this texture.
+ ///
+ /// The child texture
+ private void RemoveView(Texture texture)
+ {
+ _views.Remove(texture);
+
+ texture._viewStorage = null;
+
+ DeleteIfNotUsed();
+ }
+
+ ///
+ /// Changes the texture size.
+ ///
+ ///
+ /// This operation may also change the size of all mipmap levels, including from the parent
+ /// and other possible child textures, to ensure that all sizes are consistent.
+ ///
+ /// The new texture width
+ /// The new texture height
+ /// The new texture depth (for 3D textures) or layers (for layered textures)
+ public void ChangeSize(int width, int height, int depthOrLayers)
+ {
+ width <<= _firstLevel;
+ height <<= _firstLevel;
+
+ if (Info.Target == Target.Texture3D)
+ {
+ depthOrLayers <<= _firstLevel;
+ }
+ else
+ {
+ depthOrLayers = _viewStorage.Info.DepthOrLayers;
+ }
+
+ _viewStorage.RecreateStorageOrView(width, height, depthOrLayers);
+
+ foreach (Texture view in _viewStorage._views)
+ {
+ int viewWidth = Math.Max(1, width >> view._firstLevel);
+ int viewHeight = Math.Max(1, height >> view._firstLevel);
+
+ int viewDepthOrLayers;
+
+ if (view.Info.Target == Target.Texture3D)
+ {
+ viewDepthOrLayers = Math.Max(1, depthOrLayers >> view._firstLevel);
+ }
+ else
+ {
+ viewDepthOrLayers = view.Info.DepthOrLayers;
+ }
+
+ view.RecreateStorageOrView(viewWidth, viewHeight, viewDepthOrLayers);
+ }
+ }
+
+ ///
+ /// Recreates the texture storage (or view, in the case of child textures) of this texture.
+ /// This allows recreating the texture with a new size.
+ /// A copy is automatically performed from the old to the new texture.
+ ///
+ /// The new texture width
+ /// The new texture height
+ /// The new texture depth (for 3D textures) or layers (for layered textures)
+ private void RecreateStorageOrView(int width, int height, int depthOrLayers)
+ {
+ SetInfo(new TextureInfo(
+ Info.Address,
+ width,
+ height,
+ depthOrLayers,
+ Info.Levels,
+ Info.SamplesInX,
+ Info.SamplesInY,
+ Info.Stride,
+ Info.IsLinear,
+ Info.GobBlocksInY,
+ Info.GobBlocksInZ,
+ Info.GobBlocksInTileX,
+ Info.Target,
+ Info.FormatInfo,
+ Info.DepthStencilMode,
+ Info.SwizzleR,
+ Info.SwizzleG,
+ Info.SwizzleB,
+ Info.SwizzleA));
+
+ TextureCreateInfo createInfo = TextureManager.GetCreateInfo(Info, _context.Capabilities);
+
+ if (_viewStorage != this)
+ {
+ ReplaceStorage(_viewStorage.HostTexture.CreateView(createInfo, _firstLayer, _firstLevel));
+ }
+ else
+ {
+ ITexture newStorage = _context.Renderer.CreateTexture(createInfo);
+
+ HostTexture.CopyTo(newStorage, 0, 0);
+
+ ReplaceStorage(newStorage);
+ }
+ }
+
+ ///
+ /// Synchronizes guest and host memory.
+ /// This will overwrite the texture data with the texture data on the guest memory, if a CPU
+ /// modification is detected.
+ /// Be aware that this can cause texture data written by the GPU to be lost, this is just a
+ /// one way copy (from CPU owned to GPU owned memory).
+ ///
+ public void SynchronizeMemory()
+ {
+ if (_sequenceNumber == _context.SequenceNumber && _hasData)
+ {
+ return;
+ }
+
+ _sequenceNumber = _context.SequenceNumber;
+
+ bool modified = _context.PhysicalMemory.GetModifiedRanges(Address, Size, ResourceName.Texture).Length != 0;
+
+ if (!modified && _hasData)
+ {
+ return;
+ }
+
+ Span data = _context.PhysicalMemory.Read(Address, Size);
+
+ if (Info.IsLinear)
+ {
+ data = LayoutConverter.ConvertLinearStridedToLinear(
+ Info.Width,
+ Info.Height,
+ Info.FormatInfo.BlockWidth,
+ Info.FormatInfo.BlockHeight,
+ Info.Stride,
+ Info.FormatInfo.BytesPerPixel,
+ data);
+ }
+ else
+ {
+ data = LayoutConverter.ConvertBlockLinearToLinear(
+ Info.Width,
+ Info.Height,
+ _depth,
+ Info.Levels,
+ _layers,
+ Info.FormatInfo.BlockWidth,
+ Info.FormatInfo.BlockHeight,
+ Info.FormatInfo.BytesPerPixel,
+ Info.GobBlocksInY,
+ Info.GobBlocksInZ,
+ Info.GobBlocksInTileX,
+ _sizeInfo,
+ data);
+ }
+
+ if (!_context.Capabilities.SupportsAstcCompression && Info.FormatInfo.Format.IsAstc())
+ {
+ if (!AstcDecoder.TryDecodeToRgba8(
+ data.ToArray(),
+ Info.FormatInfo.BlockWidth,
+ Info.FormatInfo.BlockHeight,
+ Info.Width,
+ Info.Height,
+ _depth,
+ Info.Levels,
+ out Span decoded))
+ {
+ string texInfo = $"{Info.Target} {Info.FormatInfo.Format} {Info.Width}x{Info.Height}x{Info.DepthOrLayers} levels {Info.Levels}";
+
+ Logger.PrintDebug(LogClass.Gpu, $"Invalid ASTC texture at 0x{Info.Address:X} ({texInfo}).");
+ }
+
+ data = decoded;
+ }
+
+ HostTexture.SetData(data);
+
+ _hasData = true;
+ }
+
+ ///
+ /// Flushes the texture data.
+ /// This causes the texture data to be written back to guest memory.
+ /// If the texture was written by the GPU, this includes all modification made by the GPU
+ /// up to this point.
+ /// Be aware that this is an expensive operation, avoid calling it unless strictly needed.
+ /// This may cause data corruption if the memory is already being used for something else on the CPU side.
+ ///
+ public void Flush()
+ {
+ Span data = HostTexture.GetData();
+
+ if (Info.IsLinear)
+ {
+ data = LayoutConverter.ConvertLinearToLinearStrided(
+ Info.Width,
+ Info.Height,
+ Info.FormatInfo.BlockWidth,
+ Info.FormatInfo.BlockHeight,
+ Info.Stride,
+ Info.FormatInfo.BytesPerPixel,
+ data);
+ }
+ else
+ {
+ data = LayoutConverter.ConvertLinearToBlockLinear(
+ Info.Width,
+ Info.Height,
+ _depth,
+ Info.Levels,
+ _layers,
+ Info.FormatInfo.BlockWidth,
+ Info.FormatInfo.BlockHeight,
+ Info.FormatInfo.BytesPerPixel,
+ Info.GobBlocksInY,
+ Info.GobBlocksInZ,
+ Info.GobBlocksInTileX,
+ _sizeInfo,
+ data);
+ }
+
+ _context.PhysicalMemory.Write(Address, data);
+ }
+
+ ///
+ /// Performs a comparison of this texture information, with the specified texture information.
+ /// This performs a strict comparison, used to check if two textures are equal.
+ ///
+ /// Texture information to compare with
+ /// Comparison flags
+ /// True if the textures are strictly equal or similar, false otherwise
+ public bool IsPerfectMatch(TextureInfo info, TextureSearchFlags flags)
+ {
+ if (!FormatMatches(info, (flags & TextureSearchFlags.Strict) != 0))
+ {
+ return false;
+ }
+
+ if (!LayoutMatches(info))
+ {
+ return false;
+ }
+
+ if (!SizeMatches(info, (flags & TextureSearchFlags.Strict) == 0))
+ {
+ return false;
+ }
+
+ if ((flags & TextureSearchFlags.Sampler) != 0)
+ {
+ if (!SamplerParamsMatches(info))
+ {
+ return false;
+ }
+ }
+
+ if ((flags & TextureSearchFlags.IgnoreMs) != 0)
+ {
+ bool msTargetCompatible = Info.Target == Target.Texture2DMultisample && info.Target == Target.Texture2D;
+
+ if (!msTargetCompatible && !TargetAndSamplesCompatible(info))
+ {
+ return false;
+ }
+ }
+ else if (!TargetAndSamplesCompatible(info))
+ {
+ return false;
+ }
+
+ return Info.Address == info.Address && Info.Levels == info.Levels;
+ }
+
+ ///
+ /// Checks if the texture format matches with the specified texture information.
+ ///
+ /// Texture information to compare with
+ /// True to perform a strict comparison (formats must be exactly equal)
+ /// True if the format matches, with the given comparison rules
+ private bool FormatMatches(TextureInfo info, bool strict)
+ {
+ // D32F and R32F texture have the same representation internally,
+ // however the R32F format is used to sample from depth textures.
+ if (Info.FormatInfo.Format == Format.D32Float && info.FormatInfo.Format == Format.R32Float && !strict)
+ {
+ return true;
+ }
+
+ return Info.FormatInfo.Format == info.FormatInfo.Format;
+ }
+
+ ///
+ /// Checks if the texture layout specified matches with this texture layout.
+ /// The layout information is composed of the Stride for linear textures, or GOB block size
+ /// for block linear textures.
+ ///
+ /// Texture information to compare with
+ /// True if the layout matches, false otherwise
+ private bool LayoutMatches(TextureInfo info)
+ {
+ if (Info.IsLinear != info.IsLinear)
+ {
+ return false;
+ }
+
+ // For linear textures, gob block sizes are ignored.
+ // For block linear textures, the stride is ignored.
+ if (info.IsLinear)
+ {
+ return Info.Stride == info.Stride;
+ }
+ else
+ {
+ return Info.GobBlocksInY == info.GobBlocksInY &&
+ Info.GobBlocksInZ == info.GobBlocksInZ;
+ }
+ }
+
+ ///
+ /// Checks if the texture sizes of the supplied texture information matches this texture.
+ ///
+ /// Texture information to compare with
+ /// True if the size matches, false otherwise
+ public bool SizeMatches(TextureInfo info)
+ {
+ return SizeMatches(info, alignSizes: false);
+ }
+
+ ///
+ /// Checks if the texture sizes of the supplied texture information matches the given level of
+ /// this texture.
+ ///
+ /// Texture information to compare with
+ /// Mipmap level of this texture to compare with
+ /// True if the size matches with the level, false otherwise
+ public bool SizeMatches(TextureInfo info, int level)
+ {
+ return Math.Max(1, Info.Width >> level) == info.Width &&
+ Math.Max(1, Info.Height >> level) == info.Height &&
+ Math.Max(1, Info.GetDepth() >> level) == info.GetDepth();
+ }
+
+ ///
+ /// Checks if the texture sizes of the supplied texture information matches this texture.
+ ///
+ /// Texture information to compare with
+ /// True to align the sizes according to the texture layout for comparison
+ /// True if the sizes matches, false otherwise
+ private bool SizeMatches(TextureInfo info, bool alignSizes)
+ {
+ if (Info.GetLayers() != info.GetLayers())
+ {
+ return false;
+ }
+
+ if (alignSizes)
+ {
+ Size size0 = GetAlignedSize(Info);
+ Size size1 = GetAlignedSize(info);
+
+ return size0.Width == size1.Width &&
+ size0.Height == size1.Height &&
+ size0.Depth == size1.Depth;
+ }
+ else
+ {
+ return Info.Width == info.Width &&
+ Info.Height == info.Height &&
+ Info.GetDepth() == info.GetDepth();
+ }
+ }
+
+ ///
+ /// Checks if the texture shader sampling parameters matches.
+ ///
+ /// Texture information to compare with
+ /// True if the texture shader sampling parameters matches, false otherwise
+ private bool SamplerParamsMatches(TextureInfo info)
+ {
+ return Info.DepthStencilMode == info.DepthStencilMode &&
+ Info.SwizzleR == info.SwizzleR &&
+ Info.SwizzleG == info.SwizzleG &&
+ Info.SwizzleB == info.SwizzleB &&
+ Info.SwizzleA == info.SwizzleA;
+ }
+
+ ///
+ /// Check if the texture target and samples count (for multisampled textures) matches.
+ ///
+ /// Texture information to compare with
+ /// True if the texture target and samples count matches, false otherwise
+ private bool TargetAndSamplesCompatible(TextureInfo info)
+ {
+ return Info.Target == info.Target &&
+ Info.SamplesInX == info.SamplesInX &&
+ Info.SamplesInY == info.SamplesInY;
+ }
+
+ ///
+ /// Check if it's possible to create a view, with the given parameters, from this texture.
+ ///
+ /// Texture view information
+ /// Texture view size
+ /// Texture view initial layer on this texture
+ /// Texture view first mipmap level on this texture
+ /// True if a view with the given parameters can be created from this texture, false otherwise
+ public bool IsViewCompatible(
+ TextureInfo info,
+ ulong size,
+ out int firstLayer,
+ out int firstLevel)
+ {
+ return IsViewCompatible(info, size, isCopy: false, out firstLayer, out firstLevel);
+ }
+
+ ///
+ /// Check if it's possible to create a view, with the given parameters, from this texture.
+ ///
+ /// Texture view information
+ /// Texture view size
+ /// True to check for copy compability, instead of view compatibility
+ /// Texture view initial layer on this texture
+ /// Texture view first mipmap level on this texture
+ /// True if a view with the given parameters can be created from this texture, false otherwise
+ public bool IsViewCompatible(
+ TextureInfo info,
+ ulong size,
+ bool isCopy,
+ out int firstLayer,
+ out int firstLevel)
+ {
+ // Out of range.
+ if (info.Address < Address || info.Address + size > EndAddress)
+ {
+ firstLayer = 0;
+ firstLevel = 0;
+
+ return false;
+ }
+
+ int offset = (int)(info.Address - Address);
+
+ if (!_sizeInfo.FindView(offset, (int)size, out firstLayer, out firstLevel))
+ {
+ return false;
+ }
+
+ if (!ViewLayoutCompatible(info, firstLevel))
+ {
+ return false;
+ }
+
+ if (!ViewFormatCompatible(info))
+ {
+ return false;
+ }
+
+ if (!ViewSizeMatches(info, firstLevel, isCopy))
+ {
+ return false;
+ }
+
+ if (!ViewTargetCompatible(info, isCopy))
+ {
+ return false;
+ }
+
+ return Info.SamplesInX == info.SamplesInX &&
+ Info.SamplesInY == info.SamplesInY;
+ }
+
+ ///
+ /// Check if it's possible to create a view with the specified layout.
+ /// The layout information is composed of the Stride for linear textures, or GOB block size
+ /// for block linear textures.
+ ///
+ /// Texture information of the texture view
+ /// Start level of the texture view, in relation with this texture
+ /// True if the layout is compatible, false otherwise
+ private bool ViewLayoutCompatible(TextureInfo info, int level)
+ {
+ if (Info.IsLinear != info.IsLinear)
+ {
+ return false;
+ }
+
+ // For linear textures, gob block sizes are ignored.
+ // For block linear textures, the stride is ignored.
+ if (info.IsLinear)
+ {
+ int width = Math.Max(1, Info.Width >> level);
+
+ int stride = width * Info.FormatInfo.BytesPerPixel;
+
+ stride = BitUtils.AlignUp(stride, 32);
+
+ return stride == info.Stride;
+ }
+ else
+ {
+ int height = Math.Max(1, Info.Height >> level);
+ int depth = Math.Max(1, Info.GetDepth() >> level);
+
+ (int gobBlocksInY, int gobBlocksInZ) = SizeCalculator.GetMipGobBlockSizes(
+ height,
+ depth,
+ Info.FormatInfo.BlockHeight,
+ Info.GobBlocksInY,
+ Info.GobBlocksInZ);
+
+ return gobBlocksInY == info.GobBlocksInY &&
+ gobBlocksInZ == info.GobBlocksInZ;
+ }
+ }
+
+ ///
+ /// Checks if the view format is compatible with this texture format.
+ /// In general, the formats are considered compatible if the bytes per pixel values are equal,
+ /// but there are more complex rules for some formats, like compressed or depth-stencil formats.
+ /// This follows the host API copy compatibility rules.
+ ///
+ /// Texture information of the texture view
+ /// True if the formats are compatible, false otherwise
+ private bool ViewFormatCompatible(TextureInfo info)
+ {
+ return TextureCompatibility.FormatCompatible(Info.FormatInfo, info.FormatInfo);
+ }
+
+ ///
+ /// Checks if the size of a given texture view is compatible with this texture.
+ ///
+ /// Texture information of the texture view
+ /// Mipmap level of the texture view in relation to this texture
+ /// True to check for copy compatibility rather than view compatibility
+ /// True if the sizes are compatible, false otherwise
+ private bool ViewSizeMatches(TextureInfo info, int level, bool isCopy)
+ {
+ Size size = GetAlignedSize(Info, level);
+
+ Size otherSize = GetAlignedSize(info);
+
+ // For copies, we can copy a subset of the 3D texture slices,
+ // so the depth may be different in this case.
+ if (!isCopy && info.Target == Target.Texture3D && size.Depth != otherSize.Depth)
+ {
+ return false;
+ }
+
+ return size.Width == otherSize.Width &&
+ size.Height == otherSize.Height;
+ }
+
+ ///
+ /// Check if the target of the specified texture view information is compatible with this
+ /// texture.
+ /// This follows the host API target compatibility rules.
+ ///
+ /// Texture information of the texture view
+ /// True to check for copy rather than view compatibility
+ /// True if the targets are compatible, false otherwise
+ private bool ViewTargetCompatible(TextureInfo info, bool isCopy)
+ {
+ switch (Info.Target)
+ {
+ case Target.Texture1D:
+ case Target.Texture1DArray:
+ return info.Target == Target.Texture1D ||
+ info.Target == Target.Texture1DArray;
+
+ case Target.Texture2D:
+ return info.Target == Target.Texture2D ||
+ info.Target == Target.Texture2DArray;
+
+ case Target.Texture2DArray:
+ case Target.Cubemap:
+ case Target.CubemapArray:
+ return info.Target == Target.Texture2D ||
+ info.Target == Target.Texture2DArray ||
+ info.Target == Target.Cubemap ||
+ info.Target == Target.CubemapArray;
+
+ case Target.Texture2DMultisample:
+ case Target.Texture2DMultisampleArray:
+ return info.Target == Target.Texture2DMultisample ||
+ info.Target == Target.Texture2DMultisampleArray;
+
+ case Target.Texture3D:
+ return info.Target == Target.Texture3D ||
+ (info.Target == Target.Texture2D && isCopy);
+ }
+
+ return false;
+ }
+
+ ///
+ /// Gets the aligned sizes of the specified texture information.
+ /// The alignment depends on the texture layout and format bytes per pixel.
+ ///
+ /// Texture information to calculate the aligned size from
+ /// Mipmap level for texture views
+ /// The aligned texture size
+ private static Size GetAlignedSize(TextureInfo info, int level = 0)
+ {
+ int width = Math.Max(1, info.Width >> level);
+ int height = Math.Max(1, info.Height >> level);
+
+ if (info.IsLinear)
+ {
+ return SizeCalculator.GetLinearAlignedSize(
+ width,
+ height,
+ info.FormatInfo.BlockWidth,
+ info.FormatInfo.BlockHeight,
+ info.FormatInfo.BytesPerPixel);
+ }
+ else
+ {
+ int depth = Math.Max(1, info.GetDepth() >> level);
+
+ (int gobBlocksInY, int gobBlocksInZ) = SizeCalculator.GetMipGobBlockSizes(
+ height,
+ depth,
+ info.FormatInfo.BlockHeight,
+ info.GobBlocksInY,
+ info.GobBlocksInZ);
+
+ return SizeCalculator.GetBlockLinearAlignedSize(
+ width,
+ height,
+ depth,
+ info.FormatInfo.BlockWidth,
+ info.FormatInfo.BlockHeight,
+ info.FormatInfo.BytesPerPixel,
+ gobBlocksInY,
+ gobBlocksInZ,
+ info.GobBlocksInTileX);
+ }
+ }
+
+ ///
+ /// Gets a texture of the specified target type from this texture.
+ /// This can be used to get an array texture from a non-array texture and vice-versa.
+ /// If this texture and the requested targets are equal, then this texture Host texture is returned directly.
+ ///
+ /// The desired target type
+ /// A view of this texture with the requested target, or null if the target is invalid for this texture
+ public ITexture GetTargetTexture(Target target)
+ {
+ if (target == Info.Target)
+ {
+ return HostTexture;
+ }
+
+ if (_arrayViewTexture == null && IsSameDimensionsTarget(target))
+ {
+ 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,
+ Info.DepthStencilMode,
+ target,
+ Info.SwizzleR,
+ Info.SwizzleG,
+ Info.SwizzleB,
+ Info.SwizzleA);
+
+ ITexture viewTexture = HostTexture.CreateView(createInfo, 0, 0);
+
+ _arrayViewTexture = viewTexture;
+ _arrayViewTarget = target;
+
+ return viewTexture;
+ }
+ else if (_arrayViewTarget == target)
+ {
+ return _arrayViewTexture;
+ }
+
+ return null;
+ }
+
+ ///
+ /// Check if this texture and the specified target have the same number of dimensions.
+ /// For the purposes of this comparison, 2D and 2D Multisample textures are not considered to have
+ /// the same number of dimensions. Same for Cubemap and 3D textures.
+ ///
+ /// The target to compare with
+ /// True if both targets have the same number of dimensions, false otherwise
+ private bool IsSameDimensionsTarget(Target target)
+ {
+ switch (Info.Target)
+ {
+ case Target.Texture1D:
+ case Target.Texture1DArray:
+ return target == Target.Texture1D ||
+ target == Target.Texture1DArray;
+
+ case Target.Texture2D:
+ case Target.Texture2DArray:
+ return target == Target.Texture2D ||
+ target == Target.Texture2DArray;
+
+ case Target.Cubemap:
+ case Target.CubemapArray:
+ return target == Target.Cubemap ||
+ target == Target.CubemapArray;
+
+ case Target.Texture2DMultisample:
+ case Target.Texture2DMultisampleArray:
+ return target == Target.Texture2DMultisample ||
+ target == Target.Texture2DMultisampleArray;
+
+ case Target.Texture3D:
+ return target == Target.Texture3D;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Replaces view texture information.
+ /// This should only be used for child textures with a parent.
+ ///
+ /// The parent texture
+ /// The new view texture information
+ /// The new host texture
+ public void ReplaceView(Texture parent, TextureInfo info, ITexture hostTexture)
+ {
+ ReplaceStorage(hostTexture);
+
+ parent._viewStorage.AddView(this);
+
+ SetInfo(info);
+ }
+
+ ///
+ /// Sets the internal texture information structure.
+ ///
+ /// The new texture information
+ private void SetInfo(TextureInfo info)
+ {
+ Info = info;
+
+ _depth = info.GetDepth();
+ _layers = info.GetLayers();
+ }
+
+ ///
+ /// Replaces the host texture, while disposing of the old one if needed.
+ ///
+ /// The new host texture
+ private void ReplaceStorage(ITexture hostTexture)
+ {
+ DisposeTextures();
+
+ HostTexture = hostTexture;
+ }
+
+ ///
+ /// Checks if the texture overlaps with a memory range.
+ ///
+ /// Start address of the range
+ /// Size of the range
+ /// True if the texture overlaps with the range, false otherwise
+ public bool OverlapsWith(ulong address, ulong size)
+ {
+ return Address < address + size && address < EndAddress;
+ }
+
+ ///
+ /// Increments the texture reference count.
+ ///
+ public void IncrementReferenceCount()
+ {
+ _referenceCount++;
+ }
+
+ ///
+ /// Decrements the texture reference count.
+ /// When the reference count hits zero, the texture may be deleted and can't be used anymore.
+ ///
+ public void DecrementReferenceCount()
+ {
+ int newRefCount = --_referenceCount;
+
+ if (newRefCount == 0)
+ {
+ if (_viewStorage != this)
+ {
+ _viewStorage.RemoveView(this);
+ }
+
+ _context.Methods.TextureManager.RemoveTextureFromCache(this);
+ }
+
+ Debug.Assert(newRefCount >= 0);
+
+ DeleteIfNotUsed();
+ }
+
+ ///
+ /// Delete the texture if it is not used anymore.
+ /// The texture is considered unused when the reference count is zero,
+ /// and it has no child views.
+ ///
+ private void DeleteIfNotUsed()
+ {
+ // We can delete the texture as long it is not being used
+ // in any cache (the reference count is 0 in this case), and
+ // also all views that may be created from this texture were
+ // already deleted (views count is 0).
+ if (_referenceCount == 0 && _views.Count == 0)
+ {
+ DisposeTextures();
+ }
+ }
+
+ ///
+ /// Performs texture disposal, deleting the texture.
+ ///
+ private void DisposeTextures()
+ {
+ HostTexture.Dispose();
+
+ _arrayViewTexture?.Dispose();
+ _arrayViewTexture = null;
+ }
+
+ ///
+ /// Performs texture disposal, deleting the texture.
+ ///
+ public void Dispose()
+ {
+ DisposeTextures();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs b/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs
new file mode 100644
index 0000000000..b4793f58ff
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs
@@ -0,0 +1,73 @@
+using Ryujinx.Graphics.GAL;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ ///
+ /// Texture binding information.
+ /// This is used for textures that needs to be accessed from shaders.
+ ///
+ struct TextureBindingInfo
+ {
+ ///
+ /// Shader sampler target type.
+ ///
+ public Target Target { get; }
+
+ ///
+ /// Shader texture handle.
+ /// This is an index into the texture constant buffer.
+ ///
+ public int Handle { get; }
+
+ ///
+ /// Indicates if the texture is a bindless texture.
+ ///
+ ///
+ /// For those textures, Handle is ignored.
+ ///
+ public bool IsBindless { get; }
+
+ ///
+ /// Constant buffer slot with the bindless texture handle, for bindless texture.
+ ///
+ public int CbufSlot { get; }
+
+ ///
+ /// Constant buffer offset of the bindless texture handle, for bindless texture.
+ ///
+ public int CbufOffset { get; }
+
+ ///
+ /// Constructs the texture binding information structure.
+ ///
+ /// The shader sampler target type
+ /// The shader texture handle (read index into the texture constant buffer)
+ public TextureBindingInfo(Target target, int handle)
+ {
+ Target = target;
+ Handle = handle;
+
+ IsBindless = false;
+
+ CbufSlot = 0;
+ CbufOffset = 0;
+ }
+
+ ///
+ /// Constructs the bindless texture binding information structure.
+ ///
+ /// The shader sampler target type
+ /// Constant buffer slot where the bindless texture handle is located
+ /// Constant buffer offset of the bindless texture handle
+ public TextureBindingInfo(Target target, int cbufSlot, int cbufOffset)
+ {
+ Target = target;
+ Handle = 0;
+
+ IsBindless = true;
+
+ CbufSlot = cbufSlot;
+ CbufOffset = cbufOffset;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs
new file mode 100644
index 0000000000..984d45a952
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs
@@ -0,0 +1,367 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.State;
+using Ryujinx.Graphics.Shader;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ ///
+ /// Texture bindings manager.
+ ///
+ class TextureBindingsManager
+ {
+ private GpuContext _context;
+
+ private bool _isCompute;
+
+ private SamplerPool _samplerPool;
+
+ private SamplerIndex _samplerIndex;
+
+ private ulong _texturePoolAddress;
+ private int _texturePoolMaximumId;
+
+ private TexturePoolCache _texturePoolCache;
+
+ private TextureBindingInfo[][] _textureBindings;
+ private TextureBindingInfo[][] _imageBindings;
+
+ private struct TextureStatePerStage
+ {
+ public ITexture Texture;
+ public ISampler Sampler;
+ }
+
+ private TextureStatePerStage[][] _textureState;
+ private TextureStatePerStage[][] _imageState;
+
+ private int _textureBufferIndex;
+
+ private bool _rebind;
+
+ ///
+ /// Constructs a new instance of the texture bindings manager.
+ ///
+ /// The GPU context that the texture bindings manager belongs to
+ /// Texture pools cache used to get texture pools from
+ /// True if the bindings manager is used for the compute engine
+ public TextureBindingsManager(GpuContext context, TexturePoolCache texturePoolCache, bool isCompute)
+ {
+ _context = context;
+ _texturePoolCache = texturePoolCache;
+ _isCompute = isCompute;
+
+ int stages = isCompute ? 1 : Constants.ShaderStages;
+
+ _textureBindings = new TextureBindingInfo[stages][];
+ _imageBindings = new TextureBindingInfo[stages][];
+
+ _textureState = new TextureStatePerStage[stages][];
+ _imageState = new TextureStatePerStage[stages][];
+ }
+
+ ///
+ /// Binds textures for a given shader stage.
+ ///
+ /// Shader stage number, or 0 for compute shaders
+ /// Texture bindings
+ public void SetTextures(int stage, TextureBindingInfo[] bindings)
+ {
+ _textureBindings[stage] = bindings;
+
+ _textureState[stage] = new TextureStatePerStage[bindings.Length];
+ }
+
+ ///
+ /// Binds images for a given shader stage.
+ ///
+ /// Shader stage number, or 0 for compute shaders
+ /// Image bindings
+ public void SetImages(int stage, TextureBindingInfo[] bindings)
+ {
+ _imageBindings[stage] = bindings;
+
+ _imageState[stage] = new TextureStatePerStage[bindings.Length];
+ }
+
+ ///
+ /// Sets the textures constant buffer index.
+ /// The constant buffer specified holds the texture handles.
+ ///
+ /// Constant buffer index
+ public void SetTextureBufferIndex(int index)
+ {
+ _textureBufferIndex = index;
+ }
+
+ ///
+ /// Sets the current texture sampler pool to be used.
+ ///
+ /// Start GPU virtual address of the pool
+ /// Maximum ID of the pool (total count minus one)
+ /// Type of the sampler pool indexing used for bound samplers
+ public void SetSamplerPool(ulong gpuVa, int maximumId, SamplerIndex samplerIndex)
+ {
+ ulong address = _context.MemoryManager.Translate(gpuVa);
+
+ if (_samplerPool != null)
+ {
+ if (_samplerPool.Address == address && _samplerPool.MaximumId >= maximumId)
+ {
+ return;
+ }
+
+ _samplerPool.Dispose();
+ }
+
+ _samplerPool = new SamplerPool(_context, address, maximumId);
+
+ _samplerIndex = samplerIndex;
+ }
+
+ ///
+ /// Sets the current texture pool to be used.
+ ///
+ /// Start GPU virtual address of the pool
+ /// Maximum ID of the pool (total count minus one)
+ public void SetTexturePool(ulong gpuVa, int maximumId)
+ {
+ ulong address = _context.MemoryManager.Translate(gpuVa);
+
+ _texturePoolAddress = address;
+ _texturePoolMaximumId = maximumId;
+ }
+
+ ///
+ /// Ensures that the bindings are visible to the host GPU.
+ /// Note: this actually performs the binding using the host graphics API.
+ ///
+ public void CommitBindings()
+ {
+ TexturePool texturePool = _texturePoolCache.FindOrCreate(
+ _texturePoolAddress,
+ _texturePoolMaximumId);
+
+ if (_isCompute)
+ {
+ CommitTextureBindings(texturePool, ShaderStage.Compute, 0);
+ CommitImageBindings (texturePool, ShaderStage.Compute, 0);
+ }
+ else
+ {
+ for (ShaderStage stage = ShaderStage.Vertex; stage <= ShaderStage.Fragment; stage++)
+ {
+ int stageIndex = (int)stage - 1;
+
+ CommitTextureBindings(texturePool, stage, stageIndex);
+ CommitImageBindings (texturePool, stage, stageIndex);
+ }
+ }
+
+ _rebind = false;
+ }
+
+ ///
+ /// Ensures that the texture bindings are visible to the host GPU.
+ /// Note: this actually performs the binding using the host graphics API.
+ ///
+ /// The current texture pool
+ /// The shader stage using the textures to be bound
+ /// The stage number of the specified shader stage
+ private void CommitTextureBindings(TexturePool pool, ShaderStage stage, int stageIndex)
+ {
+ if (_textureBindings[stageIndex] == null)
+ {
+ return;
+ }
+
+ for (int index = 0; index < _textureBindings[stageIndex].Length; index++)
+ {
+ TextureBindingInfo binding = _textureBindings[stageIndex][index];
+
+ int packedId;
+
+ if (binding.IsBindless)
+ {
+ ulong address;
+
+ var bufferManager = _context.Methods.BufferManager;
+
+ if (_isCompute)
+ {
+ address = bufferManager.GetComputeUniformBufferAddress(binding.CbufSlot);
+ }
+ else
+ {
+ address = bufferManager.GetGraphicsUniformBufferAddress(stageIndex, binding.CbufSlot);
+ }
+
+ packedId = MemoryMarshal.Cast(_context.PhysicalMemory.Read(address + (ulong)binding.CbufOffset * 4, 4))[0];
+ }
+ else
+ {
+ packedId = ReadPackedId(stageIndex, binding.Handle);
+ }
+
+ int textureId = UnpackTextureId(packedId);
+ int samplerId;
+
+ if (_samplerIndex == SamplerIndex.ViaHeaderIndex)
+ {
+ samplerId = textureId;
+ }
+ else
+ {
+ samplerId = UnpackSamplerId(packedId);
+ }
+
+ Texture texture = pool.Get(textureId);
+
+ ITexture hostTexture = texture?.GetTargetTexture(binding.Target);
+
+ if (_textureState[stageIndex][index].Texture != hostTexture || _rebind)
+ {
+ _textureState[stageIndex][index].Texture = hostTexture;
+
+ _context.Renderer.Pipeline.SetTexture(index, stage, hostTexture);
+ }
+
+ Sampler sampler = _samplerPool.Get(samplerId);
+
+ ISampler hostSampler = sampler?.HostSampler;
+
+ if (_textureState[stageIndex][index].Sampler != hostSampler || _rebind)
+ {
+ _textureState[stageIndex][index].Sampler = hostSampler;
+
+ _context.Renderer.Pipeline.SetSampler(index, stage, hostSampler);
+ }
+ }
+ }
+
+ ///
+ /// Ensures that the image bindings are visible to the host GPU.
+ /// Note: this actually performs the binding using the host graphics API.
+ ///
+ /// The current texture pool
+ /// The shader stage using the textures to be bound
+ /// The stage number of the specified shader stage
+ private void CommitImageBindings(TexturePool pool, ShaderStage stage, int stageIndex)
+ {
+ if (_imageBindings[stageIndex] == null)
+ {
+ return;
+ }
+
+ for (int index = 0; index < _imageBindings[stageIndex].Length; index++)
+ {
+ TextureBindingInfo binding = _imageBindings[stageIndex][index];
+
+ int packedId = ReadPackedId(stageIndex, binding.Handle);
+
+ int textureId = UnpackTextureId(packedId);
+
+ Texture texture = pool.Get(textureId);
+
+ ITexture hostTexture = texture?.GetTargetTexture(binding.Target);
+
+ if (_imageState[stageIndex][index].Texture != hostTexture || _rebind)
+ {
+ _imageState[stageIndex][index].Texture = hostTexture;
+
+ _context.Renderer.Pipeline.SetImage(index, stage, hostTexture);
+ }
+ }
+ }
+
+ ///
+ /// Gets the texture descriptor for a given texture handle.
+ ///
+ /// The current GPU state
+ /// The stage number where the texture is bound
+ /// The texture handle
+ /// The texture descriptor for the specified texture
+ public TextureDescriptor GetTextureDescriptor(GpuState state, int stageIndex, int handle)
+ {
+ int packedId = ReadPackedId(stageIndex, handle);
+
+ int textureId = UnpackTextureId(packedId);
+
+ var poolState = state.Get(MethodOffset.TexturePoolState);
+
+ ulong poolAddress = _context.MemoryManager.Translate(poolState.Address.Pack());
+
+ TexturePool texturePool = _texturePoolCache.FindOrCreate(poolAddress, poolState.MaximumId);
+
+ return texturePool.GetDescriptor(textureId);
+ }
+
+ ///
+ /// Reads a packed texture and sampler ID (basically, the real texture handle)
+ /// from the texture constant buffer.
+ ///
+ /// The number of the shader stage where the texture is bound
+ /// A word offset of the handle on the buffer (the "fake" shader handle)
+ /// The packed texture and sampler ID (the real texture handle)
+ private int ReadPackedId(int stageIndex, int wordOffset)
+ {
+ ulong address;
+
+ var bufferManager = _context.Methods.BufferManager;
+
+ if (_isCompute)
+ {
+ address = bufferManager.GetComputeUniformBufferAddress(_textureBufferIndex);
+ }
+ else
+ {
+ address = bufferManager.GetGraphicsUniformBufferAddress(stageIndex, _textureBufferIndex);
+ }
+
+ address += (uint)wordOffset * 4;
+
+ return BitConverter.ToInt32(_context.PhysicalMemory.Read(address, 4));
+ }
+
+ ///
+ /// Unpacks the texture ID from the real texture handle.
+ ///
+ /// The real texture handle
+ /// The texture ID
+ private static int UnpackTextureId(int packedId)
+ {
+ return (packedId >> 0) & 0xfffff;
+ }
+
+ ///
+ /// Unpacks the sampler ID from the real texture handle.
+ ///
+ /// The real texture handle
+ /// The sampler ID
+ private static int UnpackSamplerId(int packedId)
+ {
+ return (packedId >> 20) & 0xfff;
+ }
+
+ ///
+ /// Invalidates a range of memory on all GPU resource pools (both texture and sampler pools).
+ ///
+ /// Start address of the range to invalidate
+ /// Size of the range to invalidate
+ public void InvalidatePoolRange(ulong address, ulong size)
+ {
+ _samplerPool?.InvalidateRange(address, size);
+
+ _texturePoolCache.InvalidateRange(address, size);
+ }
+
+ ///
+ /// Force all bound textures and images to be rebound the next time CommitBindings is called.
+ ///
+ public void Rebind()
+ {
+ _rebind = true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs b/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs
new file mode 100644
index 0000000000..408f6e2ac2
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs
@@ -0,0 +1,115 @@
+using Ryujinx.Graphics.GAL;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ ///
+ /// Texture format compatibility checks.
+ ///
+ static class TextureCompatibility
+ {
+ private enum FormatClass
+ {
+ Unclassified,
+ BCn64,
+ BCn128,
+ Bc1Rgb,
+ Bc1Rgba,
+ Bc2,
+ Bc3,
+ Bc4,
+ Bc5,
+ Bc6,
+ Bc7
+ }
+
+ ///
+ /// Checks if two formats are compatible, according to the host API copy format compatibility rules.
+ ///
+ /// First comparand
+ /// Second comparand
+ /// True if the formats are compatible, false otherwise
+ public static bool FormatCompatible(FormatInfo lhs, FormatInfo rhs)
+ {
+ if (IsDsFormat(lhs.Format) || IsDsFormat(rhs.Format))
+ {
+ return lhs.Format == rhs.Format;
+ }
+
+ if (lhs.Format.IsAstc() || rhs.Format.IsAstc())
+ {
+ return lhs.Format == rhs.Format;
+ }
+
+ if (lhs.IsCompressed && rhs.IsCompressed)
+ {
+ FormatClass lhsClass = GetFormatClass(lhs.Format);
+ FormatClass rhsClass = GetFormatClass(rhs.Format);
+
+ return lhsClass == rhsClass;
+ }
+ else
+ {
+ return lhs.BytesPerPixel == rhs.BytesPerPixel;
+ }
+ }
+
+ ///
+ /// Gets the texture format class, for compressed textures, or Unclassified otherwise.
+ ///
+ /// The format
+ /// Format class
+ private static FormatClass GetFormatClass(Format format)
+ {
+ switch (format)
+ {
+ case Format.Bc1RgbSrgb:
+ case Format.Bc1RgbUnorm:
+ return FormatClass.Bc1Rgb;
+ case Format.Bc1RgbaSrgb:
+ case Format.Bc1RgbaUnorm:
+ return FormatClass.Bc1Rgba;
+ case Format.Bc2Srgb:
+ case Format.Bc2Unorm:
+ return FormatClass.Bc2;
+ case Format.Bc3Srgb:
+ case Format.Bc3Unorm:
+ return FormatClass.Bc3;
+ case Format.Bc4Snorm:
+ case Format.Bc4Unorm:
+ return FormatClass.Bc4;
+ case Format.Bc5Snorm:
+ case Format.Bc5Unorm:
+ return FormatClass.Bc5;
+ case Format.Bc6HSfloat:
+ case Format.Bc6HUfloat:
+ return FormatClass.Bc6;
+ case Format.Bc7Srgb:
+ case Format.Bc7Unorm:
+ return FormatClass.Bc7;
+ }
+
+ return FormatClass.Unclassified;
+ }
+
+ ///
+ /// Checks if the format is a depth-stencil texture format.
+ ///
+ /// Format to check
+ /// True if the format is a depth-stencil format (including depth only), false otherwise
+ private static bool IsDsFormat(Format format)
+ {
+ switch (format)
+ {
+ case Format.D16Unorm:
+ case Format.D24X8Unorm:
+ case Format.D24UnormS8Uint:
+ case Format.D32Float:
+ case Format.D32FloatS8Uint:
+ case Format.S8Uint:
+ return true;
+ }
+
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureComponent.cs b/Ryujinx.Graphics.Gpu/Image/TextureComponent.cs
new file mode 100644
index 0000000000..359069bcfa
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TextureComponent.cs
@@ -0,0 +1,43 @@
+using Ryujinx.Graphics.GAL;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ ///
+ /// Texture swizzle color component.
+ ///
+ enum TextureComponent
+ {
+ Zero = 0,
+ Red = 2,
+ Green = 3,
+ Blue = 4,
+ Alpha = 5,
+ OneSI = 6,
+ OneF = 7
+ }
+
+ static class TextureComponentConverter
+ {
+ ///
+ /// Converts the texture swizzle color component enum to the respective Graphics Abstraction Layer enum.
+ ///
+ /// Texture swizzle color component
+ /// Converted enum
+ public static SwizzleComponent Convert(this TextureComponent component)
+ {
+ switch (component)
+ {
+ case TextureComponent.Zero: return SwizzleComponent.Zero;
+ case TextureComponent.Red: return SwizzleComponent.Red;
+ case TextureComponent.Green: return SwizzleComponent.Green;
+ case TextureComponent.Blue: return SwizzleComponent.Blue;
+ case TextureComponent.Alpha: return SwizzleComponent.Alpha;
+ case TextureComponent.OneSI:
+ case TextureComponent.OneF:
+ return SwizzleComponent.One;
+ }
+
+ return SwizzleComponent.Zero;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs b/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs
new file mode 100644
index 0000000000..aebf4abf7a
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs
@@ -0,0 +1,229 @@
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ ///
+ /// Maxwell texture descriptor, as stored on the GPU texture pool memory region.
+ ///
+ struct TextureDescriptor
+ {
+ public uint Word0;
+ public uint Word1;
+ public uint Word2;
+ public uint Word3;
+ public uint Word4;
+ public uint Word5;
+ public uint Word6;
+ public uint Word7;
+
+ ///
+ /// Unpacks Maxwell texture format integer.
+ ///
+ /// The texture format integer
+ public uint UnpackFormat()
+ {
+ return Word0 & 0x8007ffff;
+ }
+
+ ///
+ /// Unpacks the swizzle component for the texture red color channel.
+ ///
+ /// The swizzle component
+ public TextureComponent UnpackSwizzleR()
+ {
+ return(TextureComponent)((Word0 >> 19) & 7);
+ }
+
+ ///
+ /// Unpacks the swizzle component for the texture green color channel.
+ ///
+ /// The swizzle component
+ public TextureComponent UnpackSwizzleG()
+ {
+ return(TextureComponent)((Word0 >> 22) & 7);
+ }
+
+ ///
+ /// Unpacks the swizzle component for the texture blue color channel.
+ ///
+ /// The swizzle component
+ public TextureComponent UnpackSwizzleB()
+ {
+ return(TextureComponent)((Word0 >> 25) & 7);
+ }
+
+ ///
+ /// Unpacks the swizzle component for the texture alpha color channel.
+ ///
+ /// The swizzle component
+ public TextureComponent UnpackSwizzleA()
+ {
+ return(TextureComponent)((Word0 >> 28) & 7);
+ }
+
+ ///
+ /// Unpacks the 40-bits texture GPU virtual address.
+ ///
+ /// The GPU virtual address
+ public ulong UnpackAddress()
+ {
+ return Word1 | ((ulong)(Word2 & 0xffff) << 32);
+ }
+
+ ///
+ /// Unpacks texture descriptor type for this texture descriptor.
+ /// This defines the texture layout, among other things.
+ ///
+ /// The texture descriptor type
+ public TextureDescriptorType UnpackTextureDescriptorType()
+ {
+ return (TextureDescriptorType)((Word2 >> 21) & 7);
+ }
+
+ ///
+ /// Unpacks the texture stride (bytes per line) for linear textures only.
+ /// Always 32-bytes aligned.
+ ///
+ /// The linear texture stride
+ public int UnpackStride()
+ {
+ return (int)(Word3 & 0xffff) << 5;
+ }
+
+ ///
+ /// Unpacks the GOB block size in X (width) for block linear textures.
+ /// Must be always 1, ignored by the GPU.
+ ///
+ /// THe GOB block X size
+ public int UnpackGobBlocksInX()
+ {
+ return 1 << (int)(Word3 & 7);
+ }
+
+ ///
+ /// Unpacks the GOB block size in Y (height) for block linear textures.
+ /// Must be always a power of 2, with a maximum value of 32.
+ ///
+ /// THe GOB block Y size
+ public int UnpackGobBlocksInY()
+ {
+ return 1 << (int)((Word3 >> 3) & 7);
+ }
+
+ ///
+ /// Unpacks the GOB block size in Z (depth) for block linear textures.
+ /// Must be always a power of 2, with a maximum value of 32.
+ /// Must be 1 for any texture target other than 3D textures.
+ ///
+ /// The GOB block Z size
+ public int UnpackGobBlocksInZ()
+ {
+ return 1 << (int)((Word3 >> 6) & 7);
+ }
+
+ ///
+ /// Number of GOB blocks per tile in the X direction.
+ /// This is only used for sparse textures, should be 1 otherwise.
+ ///
+ /// The number of GOB blocks per tile
+ public int UnpackGobBlocksInTileX()
+ {
+ return 1 << (int)((Word3 >> 10) & 7);
+ }
+
+ ///
+ /// Unpacks the number of mipmap levels of the texture.
+ ///
+ /// The number of mipmap levels
+ public int UnpackLevels()
+ {
+ return (int)(Word3 >> 28) + 1;
+ }
+
+ ///
+ /// Unpack the base level texture width size.
+ ///
+ /// The texture width
+ public int UnpackWidth()
+ {
+ return (int)(Word4 & 0xffff) + 1;
+ }
+
+ ///
+ /// Unpacks the texture sRGB format flag.
+ ///
+ /// True if the texture is sRGB, false otherwise
+ public bool UnpackSrgb()
+ {
+ return (Word4 & (1 << 22)) != 0;
+ }
+
+ ///
+ /// Unpacks the texture target.
+ ///
+ /// The texture target
+ public TextureTarget UnpackTextureTarget()
+ {
+ return (TextureTarget)((Word4 >> 23) & 0xf);
+ }
+
+ ///
+ /// Unpack the base level texture height size, or array layers for 1D array textures.
+ /// Should be ignored for 1D or buffer textures.
+ ///
+ /// The texture height or layers count
+ public int UnpackHeight()
+ {
+ return (int)(Word5 & 0xffff) + 1;
+ }
+
+ ///
+ /// Unpack the base level texture depth size, number of array layers or cubemap faces.
+ /// The meaning of this value depends on the texture target.
+ ///
+ /// The texture depth, layer or faces count
+ public int UnpackDepth()
+ {
+ return (int)((Word5 >> 16) & 0x3fff) + 1;
+ }
+
+ ///
+ /// Unpacks the texture coordinates normalized flag.
+ /// When this is true, texture coordinates are expected to be in the [0, 1] range on the shader.
+ /// When this is false, texture coordinates are expected to be in the [0, W], [0, H] and [0, D] range.
+ /// It must be set to false (by the guest driver) for rectangle textures.
+ ///
+ /// The texture coordinates normalized flag
+ public bool UnpackTextureCoordNormalized()
+ {
+ return (Word5 & (1 << 31)) != 0;
+ }
+
+ ///
+ /// Unpacks the base mipmap level of the texture.
+ ///
+ /// The base mipmap level of the texture
+ public int UnpackBaseLevel()
+ {
+ return (int)(Word7 & 0xf);
+ }
+
+ ///
+ /// Unpacks the maximum mipmap level (inclusive) of the texture.
+ /// Usually equal to Levels minus 1.
+ ///
+ /// The maximum mipmap level (inclusive) of the texture
+ public int UnpackMaxLevelInclusive()
+ {
+ return (int)((Word7 >> 4) & 0xf);
+ }
+
+ ///
+ /// Unpacks the multisampled texture samples count in each direction.
+ /// Must be ignored for non-multisample textures.
+ ///
+ /// The multisample counts enum
+ public TextureMsaaMode UnpackTextureMsaaMode()
+ {
+ return (TextureMsaaMode)((Word7 >> 8) & 0xf);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureDescriptorType.cs b/Ryujinx.Graphics.Gpu/Image/TextureDescriptorType.cs
new file mode 100644
index 0000000000..8e7d40bbe1
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TextureDescriptorType.cs
@@ -0,0 +1,16 @@
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ ///
+ /// The texture descriptor type.
+ /// This specifies the texture memory layout.
+ /// The texture descriptor structure depends on the type.
+ ///
+ enum TextureDescriptorType
+ {
+ Buffer,
+ LinearColorKey,
+ Linear,
+ BlockLinear,
+ BlockLinearColorKey
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs b/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs
new file mode 100644
index 0000000000..2bcafd727f
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs
@@ -0,0 +1,207 @@
+using Ryujinx.Graphics.GAL;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ ///
+ /// Texture information.
+ ///
+ struct TextureInfo
+ {
+ ///
+ /// Address of the texture in guest memory.
+ ///
+ public ulong Address { get; }
+
+ ///
+ /// The width of the texture.
+ ///
+ public int Width { get; }
+
+ ///
+ /// The height of the texture, or layers count for 1D array textures.
+ ///
+ public int Height { get; }
+
+ ///
+ /// The depth of the texture (for 3D textures), or layers count for array textures.
+ ///
+ public int DepthOrLayers { get; }
+
+ ///
+ /// The number of mipmap levels of the texture.
+ ///
+ public int Levels { get; }
+
+ ///
+ /// The number of samples in the X direction for multisampled textures.
+ ///
+ public int SamplesInX { get; }
+
+ ///
+ /// The number of samples in the Y direction for multisampled textures.
+ ///
+ public int SamplesInY { get; }
+
+ ///
+ /// The number of bytes per line for linear textures.
+ ///
+ public int Stride { get; }
+
+ ///
+ /// Indicates whenever or not the texture is a linear texture.
+ ///
+ public bool IsLinear { get; }
+
+ ///
+ /// GOB blocks in the Y direction, for block linear textures.
+ ///
+ public int GobBlocksInY { get; }
+
+ ///
+ /// GOB blocks in the Z direction, for block linear textures.
+ ///
+ public int GobBlocksInZ { get; }
+
+ ///
+ /// Number of GOB blocks per tile in the X direction, for block linear textures.
+ ///
+ public int GobBlocksInTileX { get; }
+
+ ///
+ /// Total number of samples for multisampled textures.
+ ///
+ public int Samples => SamplesInX * SamplesInY;
+
+ ///
+ /// Texture target type.
+ ///
+ public Target Target { get; }
+
+ ///
+ /// Texture format information.
+ ///
+ public FormatInfo FormatInfo { get; }
+
+ ///
+ /// Depth-stencil mode of the texture. This defines whenever the depth or stencil value is read from shaders,
+ /// for depth-stencil texture formats.
+ ///
+ public DepthStencilMode DepthStencilMode { get; }
+
+ ///
+ /// Texture swizzle for the red color channel.
+ ///
+ public SwizzleComponent SwizzleR { get; }
+ ///
+ /// Texture swizzle for the green color channel.
+ ///
+ public SwizzleComponent SwizzleG { get; }
+ ///
+ /// Texture swizzle for the blue color channel.
+ ///
+ public SwizzleComponent SwizzleB { get; }
+ ///
+ /// Texture swizzle for the alpha color channel.
+ ///
+ public SwizzleComponent SwizzleA { get; }
+
+ ///
+ /// Constructs the texture information structure.
+ ///
+ /// The address of the texture
+ /// The width of the texture
+ /// The height or the texture
+ /// The depth or layers count of the texture
+ /// The amount of mipmap levels of the texture
+ /// The number of samples in the X direction for multisample textures, should be 1 otherwise
+ /// The number of samples in the Y direction for multisample textures, should be 1 otherwise
+ /// The stride for linear textures
+ /// Whenever the texture is linear or block linear
+ /// Number of GOB blocks in the Y direction
+ /// Number of GOB blocks in the Z direction
+ /// Number of GOB blocks per tile in the X direction
+ /// Texture target type
+ /// Texture format information
+ /// Depth-stencil mode
+ /// Swizzle for the red color channel
+ /// Swizzle for the green color channel
+ /// Swizzle for the blue color channel
+ /// Swizzle for the alpha color channel
+ public TextureInfo(
+ ulong address,
+ int width,
+ int height,
+ int depthOrLayers,
+ int levels,
+ int samplesInX,
+ int samplesInY,
+ int stride,
+ bool isLinear,
+ int gobBlocksInY,
+ int gobBlocksInZ,
+ int gobBlocksInTileX,
+ Target target,
+ FormatInfo formatInfo,
+ DepthStencilMode depthStencilMode = DepthStencilMode.Depth,
+ SwizzleComponent swizzleR = SwizzleComponent.Red,
+ SwizzleComponent swizzleG = SwizzleComponent.Green,
+ SwizzleComponent swizzleB = SwizzleComponent.Blue,
+ SwizzleComponent swizzleA = SwizzleComponent.Alpha)
+ {
+ Address = address;
+ Width = width;
+ Height = height;
+ DepthOrLayers = depthOrLayers;
+ Levels = levels;
+ SamplesInX = samplesInX;
+ SamplesInY = samplesInY;
+ Stride = stride;
+ IsLinear = isLinear;
+ GobBlocksInY = gobBlocksInY;
+ GobBlocksInZ = gobBlocksInZ;
+ GobBlocksInTileX = gobBlocksInTileX;
+ Target = target;
+ FormatInfo = formatInfo;
+ DepthStencilMode = depthStencilMode;
+ SwizzleR = swizzleR;
+ SwizzleG = swizzleG;
+ SwizzleB = swizzleB;
+ SwizzleA = swizzleA;
+ }
+
+ ///
+ /// Gets the real texture depth.
+ /// Returns 1 for any target other than 3D textures.
+ ///
+ /// Texture depth
+ public int GetDepth()
+ {
+ return Target == Target.Texture3D ? DepthOrLayers : 1;
+ }
+
+ ///
+ /// Gets the number of layers of the texture.
+ /// Returns 1 for non-array textures, 6 for cubemap textures, and layer faces for cubemap array textures.
+ ///
+ /// The number of texture layers
+ public int GetLayers()
+ {
+ if (Target == Target.Texture2DArray || Target == Target.Texture2DMultisampleArray)
+ {
+ return DepthOrLayers;
+ }
+ else if (Target == Target.CubemapArray)
+ {
+ return DepthOrLayers * 6;
+ }
+ else if (Target == Target.Cubemap)
+ {
+ return 6;
+ }
+ else
+ {
+ return 1;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
new file mode 100644
index 0000000000..1572719118
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
@@ -0,0 +1,779 @@
+using Ryujinx.Common;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.Image;
+using Ryujinx.Graphics.Gpu.Memory;
+using Ryujinx.Graphics.Gpu.State;
+using Ryujinx.Graphics.Texture;
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ ///
+ /// Texture manager.
+ ///
+ class TextureManager : IDisposable
+ {
+ private const int OverlapsBufferInitialCapacity = 10;
+ private const int OverlapsBufferMaxCapacity = 10000;
+
+ private readonly GpuContext _context;
+
+ private readonly TextureBindingsManager _cpBindingsManager;
+ private readonly TextureBindingsManager _gpBindingsManager;
+
+ private readonly Texture[] _rtColors;
+
+ private Texture _rtDepthStencil;
+
+ private readonly ITexture[] _rtHostColors;
+
+ private ITexture _rtHostDs;
+
+ private readonly RangeList _textures;
+
+ private Texture[] _textureOverlaps;
+
+ private readonly AutoDeleteCache _cache;
+
+ ///
+ /// Constructs a new instance of the texture manager.
+ ///
+ /// The GPU context that the texture manager belongs to
+ public TextureManager(GpuContext context)
+ {
+ _context = context;
+
+ TexturePoolCache texturePoolCache = new TexturePoolCache(context);
+
+ _cpBindingsManager = new TextureBindingsManager(context, texturePoolCache, isCompute: true);
+ _gpBindingsManager = new TextureBindingsManager(context, texturePoolCache, isCompute: false);
+
+ _rtColors = new Texture[Constants.TotalRenderTargets];
+
+ _rtHostColors = new ITexture[Constants.TotalRenderTargets];
+
+ _textures = new RangeList();
+
+ _textureOverlaps = new Texture[OverlapsBufferInitialCapacity];
+
+ _cache = new AutoDeleteCache();
+ }
+
+ ///
+ /// Sets texture bindings on the compute pipeline.
+ ///
+ /// The texture bindings
+ public void SetComputeTextures(TextureBindingInfo[] bindings)
+ {
+ _cpBindingsManager.SetTextures(0, bindings);
+ }
+
+ ///
+ /// Sets texture bindings on the graphics pipeline.
+ ///
+ /// The index of the shader stage to bind the textures
+ /// The texture bindings
+ public void SetGraphicsTextures(int stage, TextureBindingInfo[] bindings)
+ {
+ _gpBindingsManager.SetTextures(stage, bindings);
+ }
+
+ ///
+ /// Sets image bindings on the compute pipeline.
+ ///
+ /// The image bindings
+ public void SetComputeImages(TextureBindingInfo[] bindings)
+ {
+ _cpBindingsManager.SetImages(0, bindings);
+ }
+
+ ///
+ /// Sets image bindings on the graphics pipeline.
+ ///
+ /// The index of the shader stage to bind the images
+ /// The image bindings
+ public void SetGraphicsImages(int stage, TextureBindingInfo[] bindings)
+ {
+ _gpBindingsManager.SetImages(stage, bindings);
+ }
+
+ ///
+ /// Sets the texture constant buffer index on the compute pipeline.
+ ///
+ /// The texture constant buffer index
+ public void SetComputeTextureBufferIndex(int index)
+ {
+ _cpBindingsManager.SetTextureBufferIndex(index);
+ }
+
+ ///
+ /// Sets the texture constant buffer index on the graphics pipeline.
+ ///
+ /// The texture constant buffer index
+ public void SetGraphicsTextureBufferIndex(int index)
+ {
+ _gpBindingsManager.SetTextureBufferIndex(index);
+ }
+
+ ///
+ /// Sets the current sampler pool on the compute pipeline.
+ ///
+ /// The start GPU virtual address of the sampler pool
+ /// The maximum ID of the sampler pool
+ /// The indexing type of the sampler pool
+ public void SetComputeSamplerPool(ulong gpuVa, int maximumId, SamplerIndex samplerIndex)
+ {
+ _cpBindingsManager.SetSamplerPool(gpuVa, maximumId, samplerIndex);
+ }
+
+ ///
+ /// Sets the current sampler pool on the graphics pipeline.
+ ///
+ /// The start GPU virtual address of the sampler pool
+ /// The maximum ID of the sampler pool
+ /// The indexing type of the sampler pool
+ public void SetGraphicsSamplerPool(ulong gpuVa, int maximumId, SamplerIndex samplerIndex)
+ {
+ _gpBindingsManager.SetSamplerPool(gpuVa, maximumId, samplerIndex);
+ }
+
+ ///
+ /// Sets the current texture pool on the compute pipeline.
+ ///
+ /// The start GPU virtual address of the texture pool
+ /// The maximum ID of the texture pool
+ public void SetComputeTexturePool(ulong gpuVa, int maximumId)
+ {
+ _cpBindingsManager.SetTexturePool(gpuVa, maximumId);
+ }
+
+ ///
+ /// Sets the current texture pool on the graphics pipeline.
+ ///
+ /// The start GPU virtual address of the texture pool
+ /// The maximum ID of the texture pool
+ public void SetGraphicsTexturePool(ulong gpuVa, int maximumId)
+ {
+ _gpBindingsManager.SetTexturePool(gpuVa, maximumId);
+ }
+
+ ///
+ /// Sets the render target color buffer.
+ ///
+ /// The index of the color buffer to set (up to 8)
+ /// The color buffer texture
+ public void SetRenderTargetColor(int index, Texture color)
+ {
+ _rtColors[index] = color;
+ }
+
+ ///
+ /// Sets the render target depth-stencil buffer.
+ ///
+ /// The depth-stencil buffer texture
+ public void SetRenderTargetDepthStencil(Texture depthStencil)
+ {
+ _rtDepthStencil = depthStencil;
+ }
+
+ ///
+ /// Commits bindings on the compute pipeline.
+ ///
+ public void CommitComputeBindings()
+ {
+ // 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();
+ _gpBindingsManager.Rebind();
+ }
+
+ ///
+ /// Commits bindings on the graphics pipeline.
+ ///
+ public void CommitGraphicsBindings()
+ {
+ _gpBindingsManager.CommitBindings();
+
+ UpdateRenderTargets();
+ }
+
+ ///
+ /// Gets a texture descriptor used on the graphics pipeline.
+ ///
+ /// Current GPU state
+ /// Index of the shader stage where the texture is bound
+ /// Shader "fake" handle of the texture
+ /// The texture descriptor
+ public TextureDescriptor GetGraphicsTextureDescriptor(GpuState state, int stageIndex, int handle)
+ {
+ return _gpBindingsManager.GetTextureDescriptor(state, stageIndex, handle);
+ }
+
+ ///
+ /// Update host framebuffer attachments based on currently bound render target buffers.
+ ///
+ private void UpdateRenderTargets()
+ {
+ bool anyChanged = false;
+
+ if (_rtHostDs != _rtDepthStencil?.HostTexture)
+ {
+ _rtHostDs = _rtDepthStencil?.HostTexture;
+
+ anyChanged = true;
+ }
+
+ for (int index = 0; index < _rtColors.Length; index++)
+ {
+ ITexture hostTexture = _rtColors[index]?.HostTexture;
+
+ if (_rtHostColors[index] != hostTexture)
+ {
+ _rtHostColors[index] = hostTexture;
+
+ anyChanged = true;
+ }
+ }
+
+ if (anyChanged)
+ {
+ _context.Renderer.Pipeline.SetRenderTargets(_rtHostColors, _rtHostDs);
+ }
+ }
+
+ ///
+ /// Tries to find an existing texture, or create a new one if not found.
+ ///
+ /// Copy texture to find or create
+ /// The texture
+ public Texture FindOrCreateTexture(CopyTexture copyTexture)
+ {
+ ulong address = _context.MemoryManager.Translate(copyTexture.Address.Pack());
+
+ if (address == MemoryManager.BadAddress)
+ {
+ return null;
+ }
+
+ int gobBlocksInY = copyTexture.MemoryLayout.UnpackGobBlocksInY();
+ int gobBlocksInZ = copyTexture.MemoryLayout.UnpackGobBlocksInZ();
+
+ FormatInfo formatInfo = copyTexture.Format.Convert();
+
+ int width;
+
+ if (copyTexture.LinearLayout)
+ {
+ width = copyTexture.Stride / formatInfo.BytesPerPixel;
+ }
+ else
+ {
+ width = copyTexture.Width;
+ }
+
+ TextureInfo info = new TextureInfo(
+ address,
+ width,
+ copyTexture.Height,
+ copyTexture.Depth,
+ 1,
+ 1,
+ 1,
+ copyTexture.Stride,
+ copyTexture.LinearLayout,
+ gobBlocksInY,
+ gobBlocksInZ,
+ 1,
+ Target.Texture2D,
+ formatInfo);
+
+ Texture texture = FindOrCreateTexture(info, TextureSearchFlags.IgnoreMs);
+
+ texture.SynchronizeMemory();
+
+ return texture;
+ }
+
+ ///
+ /// Tries to find an existing texture, or create a new one if not found.
+ ///
+ /// Color buffer texture to find or create
+ /// Number of samples in the X direction, for MSAA
+ /// Number of samples in the Y direction, for MSAA
+ /// The texture
+ public Texture FindOrCreateTexture(RtColorState colorState, int samplesInX, int samplesInY)
+ {
+ ulong address = _context.MemoryManager.Translate(colorState.Address.Pack());
+
+ if (address == MemoryManager.BadAddress)
+ {
+ return null;
+ }
+
+ bool isLinear = colorState.MemoryLayout.UnpackIsLinear();
+
+ int gobBlocksInY = colorState.MemoryLayout.UnpackGobBlocksInY();
+ int gobBlocksInZ = colorState.MemoryLayout.UnpackGobBlocksInZ();
+
+ Target target;
+
+ if (colorState.MemoryLayout.UnpackIsTarget3D())
+ {
+ target = Target.Texture3D;
+ }
+ else if ((samplesInX | samplesInY) != 1)
+ {
+ target = colorState.Depth > 1
+ ? Target.Texture2DMultisampleArray
+ : Target.Texture2DMultisample;
+ }
+ else
+ {
+ target = colorState.Depth > 1
+ ? Target.Texture2DArray
+ : Target.Texture2D;
+ }
+
+ FormatInfo formatInfo = colorState.Format.Convert();
+
+ int width, stride;
+
+ // For linear textures, the width value is actually the stride.
+ // We can easily get the width by dividing the stride by the bpp,
+ // since the stride is the total number of bytes occupied by a
+ // line. The stride should also meet alignment constraints however,
+ // so the width we get here is the aligned width.
+ if (isLinear)
+ {
+ width = colorState.WidthOrStride / formatInfo.BytesPerPixel;
+ stride = colorState.WidthOrStride;
+ }
+ else
+ {
+ width = colorState.WidthOrStride;
+ stride = 0;
+ }
+
+ TextureInfo info = new TextureInfo(
+ address,
+ width,
+ colorState.Height,
+ colorState.Depth,
+ 1,
+ samplesInX,
+ samplesInY,
+ stride,
+ isLinear,
+ gobBlocksInY,
+ gobBlocksInZ,
+ 1,
+ target,
+ formatInfo);
+
+ Texture texture = FindOrCreateTexture(info);
+
+ texture.SynchronizeMemory();
+
+ return texture;
+ }
+
+ ///
+ /// Tries to find an existing texture, or create a new one if not found.
+ ///
+ /// Depth-stencil buffer texture to find or create
+ /// Size of the depth-stencil texture
+ /// Number of samples in the X direction, for MSAA
+ /// Number of samples in the Y direction, for MSAA
+ /// The texture
+ public Texture FindOrCreateTexture(RtDepthStencilState dsState, Size3D size, int samplesInX, int samplesInY)
+ {
+ ulong address = _context.MemoryManager.Translate(dsState.Address.Pack());
+
+ if (address == MemoryManager.BadAddress)
+ {
+ return null;
+ }
+
+ int gobBlocksInY = dsState.MemoryLayout.UnpackGobBlocksInY();
+ int gobBlocksInZ = dsState.MemoryLayout.UnpackGobBlocksInZ();
+
+ Target target = (samplesInX | samplesInY) != 1
+ ? Target.Texture2DMultisample
+ : Target.Texture2D;
+
+ FormatInfo formatInfo = dsState.Format.Convert();
+
+ TextureInfo info = new TextureInfo(
+ address,
+ size.Width,
+ size.Height,
+ size.Depth,
+ 1,
+ samplesInX,
+ samplesInY,
+ 0,
+ false,
+ gobBlocksInY,
+ gobBlocksInZ,
+ 1,
+ target,
+ formatInfo);
+
+ Texture texture = FindOrCreateTexture(info);
+
+ texture.SynchronizeMemory();
+
+ return texture;
+ }
+
+ ///
+ /// Tries to find an existing texture, or create a new one if not found.
+ ///
+ /// Texture information of the texture to be found or created
+ /// The texture search flags, defines texture comparison rules
+ /// The texture
+ public Texture FindOrCreateTexture(TextureInfo info, TextureSearchFlags flags = TextureSearchFlags.None)
+ {
+ bool isSamplerTexture = (flags & TextureSearchFlags.Sampler) != 0;
+
+ // Try to find a perfect texture match, with the same address and parameters.
+ int sameAddressOverlapsCount = _textures.FindOverlaps(info.Address, ref _textureOverlaps);
+
+ for (int index = 0; index < sameAddressOverlapsCount; index++)
+ {
+ Texture overlap = _textureOverlaps[index];
+
+ if (overlap.IsPerfectMatch(info, flags))
+ {
+ if (!isSamplerTexture)
+ {
+ // If not a sampler texture, it is managed by the auto delete
+ // cache, ensure that it is on the "top" of the list to avoid
+ // deletion.
+ _cache.Lift(overlap);
+ }
+ else if (!overlap.SizeMatches(info))
+ {
+ // If this is used for sampling, the size must match,
+ // otherwise the shader would sample garbage data.
+ // To fix that, we create a new texture with the correct
+ // size, and copy the data from the old one to the new one.
+ overlap.ChangeSize(info.Width, info.Height, info.DepthOrLayers);
+ }
+
+ return overlap;
+ }
+ }
+
+ // Calculate texture sizes, used to find all overlapping textures.
+ SizeInfo sizeInfo;
+
+ if (info.IsLinear)
+ {
+ sizeInfo = SizeCalculator.GetLinearTextureSize(
+ info.Stride,
+ info.Height,
+ info.FormatInfo.BlockHeight);
+ }
+ else
+ {
+ sizeInfo = SizeCalculator.GetBlockLinearTextureSize(
+ info.Width,
+ info.Height,
+ info.GetDepth(),
+ info.Levels,
+ info.GetLayers(),
+ info.FormatInfo.BlockWidth,
+ info.FormatInfo.BlockHeight,
+ info.FormatInfo.BytesPerPixel,
+ info.GobBlocksInY,
+ info.GobBlocksInZ,
+ info.GobBlocksInTileX);
+ }
+
+ // Find view compatible matches.
+ ulong size = (ulong)sizeInfo.TotalSize;
+
+ int overlapsCount = _textures.FindOverlaps(info.Address, size, ref _textureOverlaps);
+
+ Texture texture = null;
+
+ for (int index = 0; index < overlapsCount; index++)
+ {
+ Texture overlap = _textureOverlaps[index];
+
+ if (overlap.IsViewCompatible(info, size, out int firstLayer, out int firstLevel))
+ {
+ if (!isSamplerTexture)
+ {
+ info = AdjustSizes(overlap, info, firstLevel);
+ }
+
+ texture = overlap.CreateView(info, sizeInfo, firstLayer, firstLevel);
+
+ // The size only matters (and is only really reliable) when the
+ // texture is used on a sampler, because otherwise the size will be
+ // aligned.
+ if (!overlap.SizeMatches(info, firstLevel) && isSamplerTexture)
+ {
+ texture.ChangeSize(info.Width, info.Height, info.DepthOrLayers);
+ }
+
+ break;
+ }
+ }
+
+ // No match, create a new texture.
+ if (texture == null)
+ {
+ texture = new Texture(_context, info, sizeInfo);
+
+ // We need to synchronize before copying the old view data to the texture,
+ // otherwise the copied data would be overwritten by a future synchronization.
+ texture.SynchronizeMemory();
+
+ for (int index = 0; index < overlapsCount; index++)
+ {
+ Texture overlap = _textureOverlaps[index];
+
+ if (texture.IsViewCompatible(overlap.Info, overlap.Size, out int firstLayer, out int firstLevel))
+ {
+ TextureInfo overlapInfo = AdjustSizes(texture, overlap.Info, firstLevel);
+
+ TextureCreateInfo createInfo = GetCreateInfo(overlapInfo, _context.Capabilities);
+
+ ITexture newView = texture.HostTexture.CreateView(createInfo, firstLayer, firstLevel);
+
+ overlap.HostTexture.CopyTo(newView, 0, 0);
+
+ overlap.ReplaceView(texture, overlapInfo, newView);
+ }
+ }
+
+ // If the texture is a 3D texture, we need to additionally copy any slice
+ // of the 3D texture to the newly created 3D texture.
+ if (info.Target == Target.Texture3D)
+ {
+ for (int index = 0; index < overlapsCount; index++)
+ {
+ Texture overlap = _textureOverlaps[index];
+
+ if (texture.IsViewCompatible(
+ overlap.Info,
+ overlap.Size,
+ isCopy: true,
+ out int firstLayer,
+ out int firstLevel))
+ {
+ overlap.HostTexture.CopyTo(texture.HostTexture, firstLayer, firstLevel);
+ }
+ }
+ }
+ }
+
+ // Sampler textures are managed by the texture pool, all other textures
+ // are managed by the auto delete cache.
+ if (!isSamplerTexture)
+ {
+ _cache.Add(texture);
+ }
+
+ _textures.Add(texture);
+
+ ShrinkOverlapsBufferIfNeeded();
+
+ return texture;
+ }
+
+ ///
+ /// Resizes the temporary buffer used for range list intersection results, if it has grown too much.
+ ///
+ private void ShrinkOverlapsBufferIfNeeded()
+ {
+ if (_textureOverlaps.Length > OverlapsBufferMaxCapacity)
+ {
+ Array.Resize(ref _textureOverlaps, OverlapsBufferMaxCapacity);
+ }
+ }
+
+ ///
+ /// Adjusts the size of the texture information for a given mipmap level,
+ /// based on the size of a parent texture.
+ ///
+ /// The parent texture
+ /// The texture information to be adjusted
+ /// The first level of the texture view
+ /// The adjusted texture information with the new size
+ private static TextureInfo AdjustSizes(Texture parent, TextureInfo info, int firstLevel)
+ {
+ // When the texture is used as view of another texture, we must
+ // ensure that the sizes are valid, otherwise data uploads would fail
+ // (and the size wouldn't match the real size used on the host API).
+ // Given a parent texture from where the view is created, we have the
+ // following rules:
+ // - The view size must be equal to the parent size, divided by (2 ^ l),
+ // where l is the first mipmap level of the view. The division result must
+ // be rounded down, and the result must be clamped to 1.
+ // - If the parent format is compressed, and the view format isn't, the
+ // view size is calculated as above, but the width and height of the
+ // view must be also divided by the compressed format block width and height.
+ // - If the parent format is not compressed, and the view is, the view
+ // size is calculated as described on the first point, but the width and height
+ // of the view must be also multiplied by the block width and height.
+ int width = Math.Max(1, parent.Info.Width >> firstLevel);
+ int height = Math.Max(1, parent.Info.Height >> firstLevel);
+
+ if (parent.Info.FormatInfo.IsCompressed && !info.FormatInfo.IsCompressed)
+ {
+ width = BitUtils.DivRoundUp(width, parent.Info.FormatInfo.BlockWidth);
+ height = BitUtils.DivRoundUp(height, parent.Info.FormatInfo.BlockHeight);
+ }
+ else if (!parent.Info.FormatInfo.IsCompressed && info.FormatInfo.IsCompressed)
+ {
+ width *= info.FormatInfo.BlockWidth;
+ height *= info.FormatInfo.BlockHeight;
+ }
+
+ int depthOrLayers;
+
+ if (info.Target == Target.Texture3D)
+ {
+ depthOrLayers = Math.Max(1, parent.Info.DepthOrLayers >> firstLevel);
+ }
+ else
+ {
+ depthOrLayers = info.DepthOrLayers;
+ }
+
+ return new TextureInfo(
+ info.Address,
+ width,
+ height,
+ depthOrLayers,
+ info.Levels,
+ info.SamplesInX,
+ info.SamplesInY,
+ info.Stride,
+ info.IsLinear,
+ info.GobBlocksInY,
+ info.GobBlocksInZ,
+ info.GobBlocksInTileX,
+ info.Target,
+ info.FormatInfo,
+ info.DepthStencilMode,
+ info.SwizzleR,
+ info.SwizzleG,
+ info.SwizzleB,
+ info.SwizzleA);
+ }
+
+
+ ///
+ /// Gets a texture creation information from texture information.
+ /// This can be used to create new host textures.
+ ///
+ /// Texture information
+ /// GPU capabilities
+ /// The texture creation information
+ public static TextureCreateInfo GetCreateInfo(TextureInfo info, Capabilities caps)
+ {
+ FormatInfo formatInfo = info.FormatInfo;
+
+ if (!caps.SupportsAstcCompression)
+ {
+ if (formatInfo.Format.IsAstcUnorm())
+ {
+ formatInfo = new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4);
+ }
+ else if (formatInfo.Format.IsAstcSrgb())
+ {
+ formatInfo = new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4);
+ }
+ }
+
+ int width = info.Width / info.SamplesInX;
+ int height = info.Height / info.SamplesInY;
+
+ int depth = info.GetDepth() * info.GetLayers();
+
+ return new TextureCreateInfo(
+ width,
+ height,
+ depth,
+ info.Levels,
+ info.Samples,
+ formatInfo.BlockWidth,
+ formatInfo.BlockHeight,
+ formatInfo.BytesPerPixel,
+ formatInfo.Format,
+ info.DepthStencilMode,
+ info.Target,
+ info.SwizzleR,
+ info.SwizzleG,
+ info.SwizzleB,
+ info.SwizzleA);
+ }
+
+ ///
+ /// Flushes all the textures in the cache that have been modified since the last call.
+ ///
+ public void Flush()
+ {
+ foreach (Texture texture in _cache)
+ {
+ if (texture.Info.IsLinear && texture.Modified)
+ {
+ texture.Flush();
+
+ texture.Modified = false;
+ }
+ }
+ }
+
+ ///
+ /// Flushes the textures in the cache inside a given range that have been modified since the last call.
+ ///
+ /// The range start address
+ /// The range size
+ public void Flush(ulong address, ulong size)
+ {
+ foreach (Texture texture in _cache)
+ {
+ if (texture.OverlapsWith(address, size) && texture.Modified)
+ {
+ texture.Flush();
+
+ texture.Modified = false;
+ }
+ }
+ }
+
+ ///
+ /// Removes a texture from the cache.
+ ///
+ ///
+ /// This only removes the texture from the internal list, not from the auto-deletion cache.
+ /// It may still have live references after the removal.
+ ///
+ /// The texture to be removed
+ public void RemoveTextureFromCache(Texture texture)
+ {
+ _textures.Remove(texture);
+ }
+
+ ///
+ /// Disposes all textures in the cache.
+ /// It's an error to use the texture manager after disposal.
+ ///
+ public void Dispose()
+ {
+ foreach (Texture texture in _textures)
+ {
+ texture.Dispose();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureMsaaMode.cs b/Ryujinx.Graphics.Gpu/Image/TextureMsaaMode.cs
new file mode 100644
index 0000000000..0461888f11
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TextureMsaaMode.cs
@@ -0,0 +1,68 @@
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ ///
+ /// Multisampled texture samples count.
+ ///
+ enum TextureMsaaMode
+ {
+ Ms1x1 = 0,
+ Ms2x2 = 2,
+ Ms4x2 = 4,
+ Ms2x1 = 5,
+ Ms4x4 = 6
+ }
+
+ static class TextureMsaaModeConverter
+ {
+ ///
+ /// Returns the total number of samples from the MSAA mode.
+ ///
+ /// The MSAA mode
+ /// The total number of samples
+ public static int SamplesCount(this TextureMsaaMode msaaMode)
+ {
+ return msaaMode switch
+ {
+ TextureMsaaMode.Ms2x1 => 2,
+ TextureMsaaMode.Ms2x2 => 4,
+ TextureMsaaMode.Ms4x2 => 8,
+ TextureMsaaMode.Ms4x4 => 16,
+ _ => 1
+ };
+ }
+
+ ///
+ /// Returns the number of samples in the X direction from the MSAA mode.
+ ///
+ /// The MSAA mode
+ /// The number of samples in the X direction
+ public static int SamplesInX(this TextureMsaaMode msaaMode)
+ {
+ return msaaMode switch
+ {
+ TextureMsaaMode.Ms2x1 => 2,
+ TextureMsaaMode.Ms2x2 => 2,
+ TextureMsaaMode.Ms4x2 => 4,
+ TextureMsaaMode.Ms4x4 => 4,
+ _ => 1
+ };
+ }
+
+ ///
+ /// Returns the number of samples in the Y direction from the MSAA mode.
+ ///
+ /// The MSAA mode
+ /// The number of samples in the Y direction
+ public static int SamplesInY(this TextureMsaaMode msaaMode)
+ {
+ return msaaMode switch
+ {
+ TextureMsaaMode.Ms2x1 => 1,
+ TextureMsaaMode.Ms2x2 => 2,
+ TextureMsaaMode.Ms4x2 => 2,
+ TextureMsaaMode.Ms4x4 => 4,
+ _ => 1
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/TexturePool.cs b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
new file mode 100644
index 0000000000..f6aede7979
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
@@ -0,0 +1,265 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.Memory;
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ ///
+ /// Texture pool.
+ ///
+ class TexturePool : Pool
+ {
+ private int _sequenceNumber;
+
+ ///
+ /// Intrusive linked list node used on the texture pool cache.
+ ///
+ public LinkedListNode CacheNode { get; set; }
+
+ ///
+ /// Constructs a new instance of the texture pool.
+ ///
+ /// GPU context that the texture pool belongs to
+ /// Address of the texture pool in guest memory
+ /// Maximum texture ID of the texture pool (equal to maximum textures minus one)
+ public TexturePool(GpuContext context, ulong address, int maximumId) : base(context, address, maximumId) { }
+
+ ///
+ /// Gets the texture with the given ID.
+ ///
+ /// ID of the texture. This is effectively a zero-based index
+ /// The texture with the given ID
+ public override Texture Get(int id)
+ {
+ if ((uint)id >= Items.Length)
+ {
+ return null;
+ }
+
+ if (_sequenceNumber != Context.SequenceNumber)
+ {
+ _sequenceNumber = Context.SequenceNumber;
+
+ SynchronizeMemory();
+ }
+
+ Texture texture = Items[id];
+
+ if (texture == null)
+ {
+ TextureDescriptor descriptor = GetDescriptor(id);
+
+ TextureInfo info = GetInfo(descriptor);
+
+ // Bad address. We can't add a texture with a invalid address
+ // to the cache.
+ if (info.Address == MemoryManager.BadAddress)
+ {
+ return null;
+ }
+
+ texture = Context.Methods.TextureManager.FindOrCreateTexture(info, TextureSearchFlags.Sampler);
+
+ texture.IncrementReferenceCount();
+
+ Items[id] = texture;
+ }
+ else
+ {
+ // Memory is automatically synchronized on texture creation.
+ texture.SynchronizeMemory();
+ }
+
+ return texture;
+ }
+
+ ///
+ /// Gets the texture descriptor from a given texture ID.
+ ///
+ /// ID of the texture. This is effectively a zero-based index
+ /// The texture descriptor
+ public TextureDescriptor GetDescriptor(int id)
+ {
+ ulong address = Address + (ulong)(uint)id * DescriptorSize;
+
+ Span data = Context.PhysicalMemory.Read(address, DescriptorSize);
+
+ return MemoryMarshal.Cast(data)[0];
+ }
+
+ ///
+ /// Implementation of the texture pool range invalidation.
+ ///
+ /// Start address of the range of the texture pool
+ /// Size of the range being invalidated
+ protected override void InvalidateRangeImpl(ulong address, ulong size)
+ {
+ ulong endAddress = address + size;
+
+ for (; address < endAddress; address += DescriptorSize)
+ {
+ int id = (int)((address - Address) / DescriptorSize);
+
+ Texture texture = Items[id];
+
+ if (texture != null)
+ {
+ Span data = Context.PhysicalMemory.Read(address, DescriptorSize);
+
+ TextureDescriptor descriptor = MemoryMarshal.Cast(data)[0];
+
+ // If the descriptors are the same, the texture is the same,
+ // we don't need to remove as it was not modified. Just continue.
+ if (texture.IsPerfectMatch(GetInfo(descriptor), TextureSearchFlags.Strict))
+ {
+ continue;
+ }
+
+ texture.DecrementReferenceCount();
+
+ Items[id] = null;
+ }
+ }
+ }
+
+ ///
+ /// Gets texture information from a texture descriptor.
+ ///
+ /// The texture descriptor
+ /// The texture information
+ private TextureInfo GetInfo(TextureDescriptor descriptor)
+ {
+ ulong address = Context.MemoryManager.Translate(descriptor.UnpackAddress());
+
+ int width = descriptor.UnpackWidth();
+ int height = descriptor.UnpackHeight();
+ int depthOrLayers = descriptor.UnpackDepth();
+ int levels = descriptor.UnpackLevels();
+
+ TextureMsaaMode msaaMode = descriptor.UnpackTextureMsaaMode();
+
+ int samplesInX = msaaMode.SamplesInX();
+ int samplesInY = msaaMode.SamplesInY();
+
+ int stride = descriptor.UnpackStride();
+
+ TextureDescriptorType descriptorType = descriptor.UnpackTextureDescriptorType();
+
+ bool isLinear = descriptorType == TextureDescriptorType.Linear;
+
+ Target target = descriptor.UnpackTextureTarget().Convert((samplesInX | samplesInY) != 1);
+
+ uint format = descriptor.UnpackFormat();
+ bool srgb = descriptor.UnpackSrgb();
+
+ if (!FormatTable.TryGetTextureFormat(format, srgb, out FormatInfo formatInfo))
+ {
+ Logger.PrintDebug(LogClass.Gpu, $"Invalid texture format 0x{format:X} (sRGB: {srgb}).");
+
+ formatInfo = FormatInfo.Default;
+ }
+
+ int gobBlocksInY = descriptor.UnpackGobBlocksInY();
+ int gobBlocksInZ = descriptor.UnpackGobBlocksInZ();
+
+ int gobBlocksInTileX = descriptor.UnpackGobBlocksInTileX();
+
+ SwizzleComponent swizzleR = descriptor.UnpackSwizzleR().Convert();
+ SwizzleComponent swizzleG = descriptor.UnpackSwizzleG().Convert();
+ SwizzleComponent swizzleB = descriptor.UnpackSwizzleB().Convert();
+ SwizzleComponent swizzleA = descriptor.UnpackSwizzleA().Convert();
+
+ DepthStencilMode depthStencilMode = GetDepthStencilMode(
+ formatInfo.Format,
+ swizzleR,
+ swizzleG,
+ swizzleB,
+ swizzleA);
+
+ return new TextureInfo(
+ address,
+ width,
+ height,
+ depthOrLayers,
+ levels,
+ samplesInX,
+ samplesInY,
+ stride,
+ isLinear,
+ gobBlocksInY,
+ gobBlocksInZ,
+ gobBlocksInTileX,
+ target,
+ formatInfo,
+ depthStencilMode,
+ swizzleR,
+ swizzleG,
+ swizzleB,
+ swizzleA);
+ }
+
+ ///
+ /// Gets the texture depth-stencil mode, based on the swizzle components of each color channel.
+ /// The depth-stencil mode is determined based on how the driver sets those parameters.
+ ///
+ /// The format of the texture
+ /// The texture swizzle components
+ /// The depth-stencil mode
+ private static DepthStencilMode GetDepthStencilMode(Format format, params SwizzleComponent[] components)
+ {
+ // R = Depth, G = Stencil.
+ // On 24-bits depth formats, this is inverted (Stencil is R etc).
+ // NVN setup:
+ // For depth, A is set to 1.0f, the other components are set to Depth.
+ // For stencil, all components are set to Stencil.
+ SwizzleComponent component = components[0];
+
+ for (int index = 1; index < 4 && !IsRG(component); index++)
+ {
+ component = components[index];
+ }
+
+ if (!IsRG(component))
+ {
+ return DepthStencilMode.Depth;
+ }
+
+ if (format == Format.D24X8Unorm || format == Format.D24UnormS8Uint)
+ {
+ return component == SwizzleComponent.Red
+ ? DepthStencilMode.Stencil
+ : DepthStencilMode.Depth;
+ }
+ else
+ {
+ return component == SwizzleComponent.Red
+ ? DepthStencilMode.Depth
+ : DepthStencilMode.Stencil;
+ }
+ }
+
+ ///
+ /// Checks if the swizzle component is equal to the red or green channels.
+ ///
+ /// The swizzle component to check
+ /// True if the swizzle component is equal to the red or green, false otherwise
+ private static bool IsRG(SwizzleComponent component)
+ {
+ return component == SwizzleComponent.Red ||
+ component == SwizzleComponent.Green;
+ }
+
+ ///
+ /// Decrements the reference count of the texture.
+ /// This indicates that the texture pool is not using it anymore.
+ ///
+ /// The texture to be deleted
+ protected override void Delete(Texture item)
+ {
+ item?.DecrementReferenceCount();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/TexturePoolCache.cs b/Ryujinx.Graphics.Gpu/Image/TexturePoolCache.cs
new file mode 100644
index 0000000000..2e5c2b5d3c
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TexturePoolCache.cs
@@ -0,0 +1,91 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ ///
+ /// Texture pool cache.
+ /// This can keep multiple texture pools, and return the current one as needed.
+ /// It is useful for applications that uses multiple texture pools.
+ ///
+ class TexturePoolCache
+ {
+ private const int MaxCapacity = 4;
+
+ private GpuContext _context;
+
+ private LinkedList _pools;
+
+ ///
+ /// Constructs a new instance of the texture pool.
+ ///
+ /// GPU context that the texture pool belongs to
+ public TexturePoolCache(GpuContext context)
+ {
+ _context = context;
+
+ _pools = new LinkedList();
+ }
+
+ ///
+ /// Finds a cache texture pool, or creates a new one if not found.
+ ///
+ /// Start address of the texture pool
+ /// Maximum ID of the texture pool
+ /// The found or newly created texture pool
+ public TexturePool FindOrCreate(ulong address, int maximumId)
+ {
+ TexturePool pool;
+
+ // First we try to find the pool.
+ for (LinkedListNode node = _pools.First; node != null; node = node.Next)
+ {
+ pool = node.Value;
+
+ if (pool.Address == address)
+ {
+ if (pool.CacheNode != _pools.Last)
+ {
+ _pools.Remove(pool.CacheNode);
+
+ pool.CacheNode = _pools.AddLast(pool);
+ }
+
+ return pool;
+ }
+ }
+
+ // If not found, create a new one.
+ pool = new TexturePool(_context, address, maximumId);
+
+ pool.CacheNode = _pools.AddLast(pool);
+
+ if (_pools.Count > MaxCapacity)
+ {
+ TexturePool oldestPool = _pools.First.Value;
+
+ _pools.RemoveFirst();
+
+ oldestPool.Dispose();
+
+ oldestPool.CacheNode = null;
+ }
+
+ return pool;
+ }
+
+ ///
+ /// Invalidates a memory range of all intersecting texture pools on the cache.
+ ///
+ /// Start address of the range to invalidate
+ /// Size of the range to invalidate
+ public void InvalidateRange(ulong address, ulong size)
+ {
+ for (LinkedListNode node = _pools.First; node != null; node = node.Next)
+ {
+ TexturePool pool = node.Value;
+
+ pool.InvalidateRange(address, size);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureSearchFlags.cs b/Ryujinx.Graphics.Gpu/Image/TextureSearchFlags.cs
new file mode 100644
index 0000000000..daf726f1d2
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TextureSearchFlags.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ ///
+ /// Texture search flags, defines texture information comparison rules.
+ ///
+ [Flags]
+ enum TextureSearchFlags
+ {
+ None = 0,
+ IgnoreMs = 1 << 0,
+ Strict = 1 << 1 | Sampler,
+ Sampler = 1 << 2
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureTarget.cs b/Ryujinx.Graphics.Gpu/Image/TextureTarget.cs
new file mode 100644
index 0000000000..301fc87b2b
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TextureTarget.cs
@@ -0,0 +1,58 @@
+using Ryujinx.Graphics.GAL;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ ///
+ /// Texture target.
+ ///
+ enum TextureTarget
+ {
+ Texture1D,
+ Texture2D,
+ Texture3D,
+ Cubemap,
+ Texture1DArray,
+ Texture2DArray,
+ TextureBuffer,
+ Texture2DRect,
+ CubemapArray
+ }
+
+ static class TextureTargetConverter
+ {
+ ///
+ /// Converts the texture target enum to a host compatible, Graphics Abstraction Layer enum.
+ ///
+ /// The target enum to convert
+ /// True if the texture is a multisampled texture
+ /// The host compatible texture target
+ public static Target Convert(this TextureTarget target, bool isMultisample)
+ {
+ if (isMultisample)
+ {
+ switch (target)
+ {
+ case TextureTarget.Texture2D: return Target.Texture2DMultisample;
+ case TextureTarget.Texture2DArray: return Target.Texture2DMultisampleArray;
+ }
+ }
+ else
+ {
+ switch (target)
+ {
+ case TextureTarget.Texture1D: return Target.Texture1D;
+ case TextureTarget.Texture2D: return Target.Texture2D;
+ case TextureTarget.Texture2DRect: return Target.Texture2D;
+ case TextureTarget.Texture3D: return Target.Texture3D;
+ case TextureTarget.Texture1DArray: return Target.Texture1DArray;
+ case TextureTarget.Texture2DArray: return Target.Texture2DArray;
+ case TextureTarget.Cubemap: return Target.Cubemap;
+ case TextureTarget.CubemapArray: return Target.CubemapArray;
+ case TextureTarget.TextureBuffer: return Target.TextureBuffer;
+ }
+ }
+
+ return Target.Texture1D;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Graphics3d/MacroInterpreter.cs b/Ryujinx.Graphics.Gpu/MacroInterpreter.cs
similarity index 69%
rename from Ryujinx.Graphics/Graphics3d/MacroInterpreter.cs
rename to Ryujinx.Graphics.Gpu/MacroInterpreter.cs
index 9a6206fa4a..c853da73fb 100644
--- a/Ryujinx.Graphics/Graphics3d/MacroInterpreter.cs
+++ b/Ryujinx.Graphics.Gpu/MacroInterpreter.cs
@@ -1,10 +1,13 @@
using Ryujinx.Common.Logging;
-using Ryujinx.Graphics.Memory;
+using Ryujinx.Graphics.Gpu.State;
using System;
using System.Collections.Generic;
-namespace Ryujinx.Graphics.Graphics3d
+namespace Ryujinx.Graphics.Gpu
{
+ ///
+ /// Macro code interpreter.
+ ///
class MacroInterpreter
{
private enum AssignmentOperation
@@ -42,9 +45,6 @@ namespace Ryujinx.Graphics.Graphics3d
BitwiseNotAnd = 12
}
- private NvGpuFifo _pFifo;
- private INvGpuEngine _engine;
-
public Queue Fifo { get; private set; }
private int[] _gprs;
@@ -60,17 +60,24 @@ namespace Ryujinx.Graphics.Graphics3d
private int _pc;
- public MacroInterpreter(NvGpuFifo pFifo, INvGpuEngine engine)
+ ///
+ /// Creates a new instance of the macro code interpreter.
+ ///
+ public MacroInterpreter()
{
- _pFifo = pFifo;
- _engine = engine;
-
Fifo = new Queue();
_gprs = new int[8];
}
- public void Execute(NvGpuVmm vmm, int[] mme, int position, int param)
+ ///
+ /// Executes a macro program until it exits.
+ ///
+ /// Code of the program to execute
+ /// Start position to execute
+ /// Optional argument passed to the program, 0 if not used
+ /// Current GPU state
+ public void Execute(int[] mme, int position, int param, GpuState state)
{
Reset();
@@ -80,13 +87,17 @@ namespace Ryujinx.Graphics.Graphics3d
FetchOpCode(mme);
- while (Step(vmm, mme));
+ while (Step(mme, state));
// Due to the delay slot, we still need to execute
// one more instruction before we actually exit.
- Step(vmm, mme);
+ Step(mme, state);
}
+ ///
+ /// Resets the internal interpreter state.
+ /// Call each time you run a new program.
+ ///
private void Reset()
{
for (int index = 0; index < _gprs.Length; index++)
@@ -100,7 +111,13 @@ namespace Ryujinx.Graphics.Graphics3d
_carry = false;
}
- private bool Step(NvGpuVmm vmm, int[] mme)
+ ///
+ /// Executes a single instruction of the program.
+ ///
+ /// Program code to execute
+ /// Current GPU state
+ /// True to continue execution, false if the program exited
+ private bool Step(int[] mme, GpuState state)
{
int baseAddr = _pc - 1;
@@ -111,7 +128,7 @@ namespace Ryujinx.Graphics.Graphics3d
// Operation produces a value.
AssignmentOperation asgOp = (AssignmentOperation)((_opCode >> 4) & 7);
- int result = GetAluResult();
+ int result = GetAluResult(state);
switch (asgOp)
{
@@ -146,7 +163,7 @@ namespace Ryujinx.Graphics.Graphics3d
{
SetDstGpr(FetchParam());
- Send(vmm, result);
+ Send(state, result);
break;
}
@@ -156,7 +173,7 @@ namespace Ryujinx.Graphics.Graphics3d
{
SetDstGpr(result);
- Send(vmm, result);
+ Send(state, result);
break;
}
@@ -178,7 +195,7 @@ namespace Ryujinx.Graphics.Graphics3d
SetMethAddr(result);
- Send(vmm, FetchParam());
+ Send(state, FetchParam());
break;
}
@@ -190,7 +207,7 @@ namespace Ryujinx.Graphics.Graphics3d
SetMethAddr(result);
- Send(vmm, (result >> 12) & 0x3f);
+ Send(state, (result >> 12) & 0x3f);
break;
}
@@ -225,14 +242,22 @@ namespace Ryujinx.Graphics.Graphics3d
return !exit;
}
+ ///
+ /// Fetches a single operation code from the program code.
+ ///
+ /// Program code
private void FetchOpCode(int[] mme)
{
_opCode = _pipeOp;
-
_pipeOp = mme[_pc++];
}
- private int GetAluResult()
+ ///
+ /// Gets the result of the current Arithmetic and Logic unit operation.
+ ///
+ /// Current GPU state
+ /// Operation result
+ private int GetAluResult(GpuState state)
{
AluOperation op = (AluOperation)(_opCode & 7);
@@ -296,13 +321,20 @@ namespace Ryujinx.Graphics.Graphics3d
case AluOperation.ReadImmediate:
{
- return Read(GetGprA() + GetImm());
+ return Read(state, GetGprA() + GetImm());
}
}
throw new ArgumentException(nameof(_opCode));
}
+ ///
+ /// Gets the result of an Arithmetic and Logic operation using registers.
+ ///
+ /// Arithmetic and Logic unit operation with registers
+ /// First operand value
+ /// Second operand value
+ /// Operation result
private int GetAluResult(AluRegOperation aluOp, int a, int b)
{
switch (aluOp)
@@ -353,43 +385,70 @@ namespace Ryujinx.Graphics.Graphics3d
throw new ArgumentOutOfRangeException(nameof(aluOp));
}
+ ///
+ /// Extracts a 32-bits signed integer constant from the current operation code.
+ ///
+ /// The 32-bits immediate value encoded at the current operation code
private int GetImm()
{
// Note: The immediate is signed, the sign-extension is intended here.
return _opCode >> 14;
}
+ ///
+ /// Sets the current method address, for method calls.
+ ///
+ /// Packed address and increment value
private void SetMethAddr(int value)
{
_methAddr = (value >> 0) & 0xfff;
_methIncr = (value >> 12) & 0x3f;
}
+ ///
+ /// Sets the destination register value.
+ ///
+ /// Value to set (usually the operation result)
private void SetDstGpr(int value)
{
_gprs[(_opCode >> 8) & 7] = value;
}
+ ///
+ /// Gets first operand value from the respective register.
+ ///
+ /// Operand value
private int GetGprA()
{
return GetGprValue((_opCode >> 11) & 7);
}
+ ///
+ /// Gets second operand value from the respective register.
+ ///
+ /// Operand value
private int GetGprB()
{
return GetGprValue((_opCode >> 14) & 7);
}
+ ///
+ /// Gets the value from a register, or 0 if the R0 register is specified.
+ ///
+ /// Index of the register
+ /// Register value
private int GetGprValue(int index)
{
return index != 0 ? _gprs[index] : 0;
}
+ ///
+ /// Fetches a call argument from the call argument FIFO.
+ ///
+ /// The call argument, or 0 if the FIFO is empty
private int FetchParam()
{
- int value;
-
- if (!Fifo.TryDequeue(out value))
+ if (!Fifo.TryDequeue(out int value))
{
Logger.PrintWarning(LogClass.Gpu, "Macro attempted to fetch an inexistent argument.");
@@ -399,16 +458,27 @@ namespace Ryujinx.Graphics.Graphics3d
return value;
}
- private int Read(int reg)
+ ///
+ /// Reads data from a GPU register.
+ ///
+ /// Current GPU state
+ /// Register offset to read
+ /// GPU register value
+ private int Read(GpuState state, int reg)
{
- return _engine.Registers[reg];
+ return state.Read(reg);
}
- private void Send(NvGpuVmm vmm, int value)
+ ///
+ /// Performs a GPU method call.
+ ///
+ /// Current GPU state
+ /// Call argument
+ private void Send(GpuState state, int value)
{
- GpuMethodCall methCall = new GpuMethodCall(_methAddr, value);
+ MethodParams meth = new MethodParams(_methAddr, value);
- _engine.CallMethod(vmm, methCall);
+ state.CallMethod(meth);
_methAddr += _methIncr;
}
diff --git a/Ryujinx.Graphics.Gpu/Memory/Buffer.cs b/Ryujinx.Graphics.Gpu/Memory/Buffer.cs
new file mode 100644
index 0000000000..4210ecb982
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Memory/Buffer.cs
@@ -0,0 +1,171 @@
+using Ryujinx.Graphics.GAL;
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+ ///
+ /// Buffer, used to store vertex and index data, uniform and storage buffers, and others.
+ ///
+ class Buffer : IRange, IDisposable
+ {
+ private readonly GpuContext _context;
+
+ ///
+ /// Host buffer object.
+ ///
+ public IBuffer HostBuffer { get; }
+
+ ///
+ /// Start address of the buffer in guest memory.
+ ///
+ public ulong Address { get; }
+
+ ///
+ /// Size of the buffer in bytes.
+ ///
+ public ulong Size { get; }
+
+ ///
+ /// End address of the buffer in guest memory.
+ ///
+ public ulong EndAddress => Address + Size;
+
+ private int[] _sequenceNumbers;
+
+ ///
+ /// Creates a new instance of the buffer.
+ ///
+ /// GPU context that the buffer belongs to
+ /// Start address of the buffer
+ /// Size of the buffer in bytes
+ public Buffer(GpuContext context, ulong address, ulong size)
+ {
+ _context = context;
+ Address = address;
+ Size = size;
+
+ HostBuffer = context.Renderer.CreateBuffer((int)size);
+
+ _sequenceNumbers = new int[size / MemoryManager.PageSize];
+
+ Invalidate();
+ }
+
+ ///
+ /// Gets a sub-range from the buffer.
+ ///
+ ///
+ /// This can be used to bind and use sub-ranges of the buffer on the host API.
+ ///
+ /// Start address of the sub-range, must be greater than or equal to the buffer address
+ /// Size in bytes of the sub-range, must be less than or equal to the buffer size
+ /// The buffer sub-range
+ public BufferRange GetRange(ulong address, ulong size)
+ {
+ int offset = (int)(address - Address);
+
+ return new BufferRange(HostBuffer, offset, (int)size);
+ }
+
+ ///
+ /// Checks if a given range overlaps with the buffer.
+ ///
+ /// Start address of the range
+ /// Size in bytes of the range
+ /// True if the range overlaps, false otherwise
+ public bool OverlapsWith(ulong address, ulong size)
+ {
+ return Address < address + size && address < EndAddress;
+ }
+
+ ///
+ /// Performs guest to host memory synchronization of the buffer data.
+ ///
+ ///
+ /// This causes the buffer data to be overwritten if a write was detected from the CPU,
+ /// since the last call to this method.
+ ///
+ /// Start address of the range to synchronize
+ /// Size in bytes of the range to synchronize
+ public void SynchronizeMemory(ulong address, ulong size)
+ {
+ int currentSequenceNumber = _context.SequenceNumber;
+
+ bool needsSync = false;
+
+ ulong buffOffset = address - Address;
+
+ ulong buffEndOffset = (buffOffset + size + MemoryManager.PageMask) & ~MemoryManager.PageMask;
+
+ int startIndex = (int)(buffOffset / MemoryManager.PageSize);
+ int endIndex = (int)(buffEndOffset / MemoryManager.PageSize);
+
+ for (int index = startIndex; index < endIndex; index++)
+ {
+ if (_sequenceNumbers[index] != currentSequenceNumber)
+ {
+ _sequenceNumbers[index] = currentSequenceNumber;
+
+ needsSync = true;
+ }
+ }
+
+ if (!needsSync)
+ {
+ return;
+ }
+
+ (ulong, ulong)[] modifiedRanges = _context.PhysicalMemory.GetModifiedRanges(address, size, ResourceName.Buffer);
+
+ for (int index = 0; index < modifiedRanges.Length; index++)
+ {
+ (ulong mAddress, ulong mSize) = modifiedRanges[index];
+
+ int offset = (int)(mAddress - Address);
+
+ HostBuffer.SetData(offset, _context.PhysicalMemory.Read(mAddress, mSize));
+ }
+ }
+
+ ///
+ /// Performs copy of all the buffer data from one buffer to another.
+ ///
+ /// The destination buffer to copy the data into
+ /// The offset of the destination buffer to copy into
+ public void CopyTo(Buffer destination, int dstOffset)
+ {
+ HostBuffer.CopyTo(destination.HostBuffer, 0, dstOffset, (int)Size);
+ }
+
+ ///
+ /// Flushes a range of the buffer.
+ /// This writes the range data back into guest memory.
+ ///
+ /// Start address of the range
+ /// Size in bytes of the range
+ public void Flush(ulong address, ulong size)
+ {
+ int offset = (int)(address - Address);
+
+ byte[] data = HostBuffer.GetData(offset, (int)size);
+
+ _context.PhysicalMemory.Write(address, data);
+ }
+
+ ///
+ /// Invalidates all the buffer data, causing it to be read from guest memory.
+ ///
+ public void Invalidate()
+ {
+ HostBuffer.SetData(0, _context.PhysicalMemory.Read(Address, Size));
+ }
+
+ ///
+ /// Disposes the host buffer.
+ ///
+ public void Dispose()
+ {
+ HostBuffer.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs b/Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs
new file mode 100644
index 0000000000..42500342fc
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+ ///
+ /// Memory range used for buffers.
+ ///
+ struct BufferBounds
+ {
+ public ulong Address;
+ public ulong Size;
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs b/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs
new file mode 100644
index 0000000000..0acbd94ad0
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs
@@ -0,0 +1,701 @@
+using Ryujinx.Common;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.State;
+using Ryujinx.Graphics.Shader;
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+ ///
+ /// Buffer manager.
+ ///
+ class BufferManager
+ {
+ private const int OverlapsBufferInitialCapacity = 10;
+ private const int OverlapsBufferMaxCapacity = 10000;
+
+ private const ulong BufferAlignmentSize = 0x1000;
+ private const ulong BufferAlignmentMask = BufferAlignmentSize - 1;
+
+ private GpuContext _context;
+
+ private RangeList _buffers;
+
+ private Buffer[] _bufferOverlaps;
+
+ private IndexBuffer _indexBuffer;
+
+ private VertexBuffer[] _vertexBuffers;
+
+ private class BuffersPerStage
+ {
+ public uint EnableMask { get; set; }
+
+ public BufferBounds[] Buffers { get; }
+
+ public BuffersPerStage(int count)
+ {
+ Buffers = new BufferBounds[count];
+ }
+
+ public void Bind(int index, ulong address, ulong size)
+ {
+ Buffers[index].Address = address;
+ Buffers[index].Size = size;
+ }
+ }
+
+ private BuffersPerStage _cpStorageBuffers;
+ private BuffersPerStage _cpUniformBuffers;
+ private BuffersPerStage[] _gpStorageBuffers;
+ private BuffersPerStage[] _gpUniformBuffers;
+
+ private bool _gpStorageBuffersDirty;
+ private bool _gpUniformBuffersDirty;
+
+ private bool _indexBufferDirty;
+ private bool _vertexBuffersDirty;
+ private uint _vertexBuffersEnableMask;
+
+ private bool _rebind;
+
+ ///
+ /// Creates a new instance of the buffer manager.
+ ///
+ /// The GPU context that the buffer manager belongs to
+ public BufferManager(GpuContext context)
+ {
+ _context = context;
+
+ _buffers = new RangeList();
+
+ _bufferOverlaps = new Buffer[OverlapsBufferInitialCapacity];
+
+ _vertexBuffers = new VertexBuffer[Constants.TotalVertexBuffers];
+
+ _cpStorageBuffers = new BuffersPerStage(Constants.TotalCpStorageBuffers);
+ _cpUniformBuffers = new BuffersPerStage(Constants.TotalCpUniformBuffers);
+
+ _gpStorageBuffers = new BuffersPerStage[Constants.ShaderStages];
+ _gpUniformBuffers = new BuffersPerStage[Constants.ShaderStages];
+
+ for (int index = 0; index < Constants.ShaderStages; index++)
+ {
+ _gpStorageBuffers[index] = new BuffersPerStage(Constants.TotalGpStorageBuffers);
+ _gpUniformBuffers[index] = new BuffersPerStage(Constants.TotalGpUniformBuffers);
+ }
+ }
+
+ ///
+ /// Sets the memory range with the index buffer data, to be used for subsequent draw calls.
+ ///
+ /// Start GPU virtual address of the index buffer
+ /// Size, in bytes, of the index buffer
+ /// Type of each index buffer element
+ public void SetIndexBuffer(ulong gpuVa, ulong size, IndexType type)
+ {
+ ulong address = TranslateAndCreateBuffer(gpuVa, size);
+
+ _indexBuffer.Address = address;
+ _indexBuffer.Size = size;
+ _indexBuffer.Type = type;
+
+ _indexBufferDirty = true;
+ }
+
+ ///
+ /// Sets the memory range with vertex buffer data, to be used for subsequent draw calls.
+ ///
+ /// Index of the vertex buffer (up to 16)
+ /// GPU virtual address of the buffer
+ /// Size in bytes of the buffer
+ /// Stride of the buffer, defined as the number of bytes of each vertex
+ /// Vertex divisor of the buffer, for instanced draws
+ public void SetVertexBuffer(int index, ulong gpuVa, ulong size, int stride, int divisor)
+ {
+ ulong address = TranslateAndCreateBuffer(gpuVa, size);
+
+ _vertexBuffers[index].Address = address;
+ _vertexBuffers[index].Size = size;
+ _vertexBuffers[index].Stride = stride;
+ _vertexBuffers[index].Divisor = divisor;
+
+ _vertexBuffersDirty = true;
+
+ if (address != 0)
+ {
+ _vertexBuffersEnableMask |= 1u << index;
+ }
+ else
+ {
+ _vertexBuffersEnableMask &= ~(1u << index);
+ }
+ }
+
+ ///
+ /// Sets a storage buffer on the compute pipeline.
+ /// Storage buffers can be read and written to on shaders.
+ ///
+ /// Index of the storage buffer
+ /// Start GPU virtual address of the buffer
+ /// Size in bytes of the storage buffer
+ public void SetComputeStorageBuffer(int index, ulong gpuVa, ulong size)
+ {
+ size += gpuVa & ((ulong)_context.Capabilities.StorageBufferOffsetAlignment - 1);
+
+ gpuVa = BitUtils.AlignDown(gpuVa, _context.Capabilities.StorageBufferOffsetAlignment);
+
+ ulong address = TranslateAndCreateBuffer(gpuVa, size);
+
+ _cpStorageBuffers.Bind(index, address, size);
+ }
+
+ ///
+ /// Sets a storage buffer on the graphics pipeline.
+ /// Storage buffers can be read and written to on shaders.
+ ///
+ /// Index of the shader stage
+ /// Index of the storage buffer
+ /// Start GPU virtual address of the buffer
+ /// Size in bytes of the storage buffer
+ public void SetGraphicsStorageBuffer(int stage, int index, ulong gpuVa, ulong size)
+ {
+ size += gpuVa & ((ulong)_context.Capabilities.StorageBufferOffsetAlignment - 1);
+
+ gpuVa = BitUtils.AlignDown(gpuVa, _context.Capabilities.StorageBufferOffsetAlignment);
+
+ ulong address = TranslateAndCreateBuffer(gpuVa, size);
+
+ if (_gpStorageBuffers[stage].Buffers[index].Address != address ||
+ _gpStorageBuffers[stage].Buffers[index].Size != size)
+ {
+ _gpStorageBuffersDirty = true;
+ }
+
+ _gpStorageBuffers[stage].Bind(index, address, size);
+ }
+
+ ///
+ /// Sets a uniform buffer on the compute pipeline.
+ /// Uniform buffers are read-only from shaders, and have a small capacity.
+ ///
+ /// Index of the uniform buffer
+ /// Start GPU virtual address of the buffer
+ /// Size in bytes of the storage buffer
+ public void SetComputeUniformBuffer(int index, ulong gpuVa, ulong size)
+ {
+ ulong address = TranslateAndCreateBuffer(gpuVa, size);
+
+ _cpUniformBuffers.Bind(index, address, size);
+ }
+
+ ///
+ /// Sets a uniform buffer on the graphics pipeline.
+ /// Uniform buffers are read-only from shaders, and have a small capacity.
+ ///
+ /// Index of the shader stage
+ /// Index of the uniform buffer
+ /// Start GPU virtual address of the buffer
+ /// Size in bytes of the storage buffer
+ public void SetGraphicsUniformBuffer(int stage, int index, ulong gpuVa, ulong size)
+ {
+ ulong address = TranslateAndCreateBuffer(gpuVa, size);
+
+ _gpUniformBuffers[stage].Bind(index, address, size);
+
+ _gpUniformBuffersDirty = true;
+ }
+
+ ///
+ /// Sets the enabled storage buffers mask on the compute pipeline.
+ /// Each bit set on the mask indicates that the respective buffer index is enabled.
+ ///
+ /// Buffer enable mask
+ public void SetComputeStorageBufferEnableMask(uint mask)
+ {
+ _cpStorageBuffers.EnableMask = mask;
+ }
+
+ ///
+ /// Sets the enabled storage buffers mask on the graphics pipeline.
+ /// Each bit set on the mask indicates that the respective buffer index is enabled.
+ ///
+ /// Index of the shader stage
+ /// Buffer enable mask
+ public void SetGraphicsStorageBufferEnableMask(int stage, uint mask)
+ {
+ _gpStorageBuffers[stage].EnableMask = mask;
+
+ _gpStorageBuffersDirty = true;
+ }
+
+ ///
+ /// Sets the enabled uniform buffers mask on the compute pipeline.
+ /// Each bit set on the mask indicates that the respective buffer index is enabled.
+ ///
+ /// Buffer enable mask
+ public void SetComputeUniformBufferEnableMask(uint mask)
+ {
+ _cpUniformBuffers.EnableMask = mask;
+ }
+
+ ///
+ /// Sets the enabled uniform buffers mask on the graphics pipeline.
+ /// Each bit set on the mask indicates that the respective buffer index is enabled.
+ ///
+ /// Index of the shader stage
+ /// Buffer enable mask
+ public void SetGraphicsUniformBufferEnableMask(int stage, uint mask)
+ {
+ _gpUniformBuffers[stage].EnableMask = mask;
+
+ _gpUniformBuffersDirty = true;
+ }
+
+ ///
+ /// Performs address translation of the GPU virtual address, and creates a
+ /// new buffer, if needed, for the specified range.
+ ///
+ /// Start GPU virtual address of the buffer
+ /// Size in bytes of the buffer
+ /// CPU virtual address of the buffer, after address translation
+ private ulong TranslateAndCreateBuffer(ulong gpuVa, ulong size)
+ {
+ if (gpuVa == 0)
+ {
+ return 0;
+ }
+
+ ulong address = _context.MemoryManager.Translate(gpuVa);
+
+ if (address == MemoryManager.BadAddress)
+ {
+ return 0;
+ }
+
+ ulong endAddress = address + size;
+
+ ulong alignedAddress = address & ~BufferAlignmentMask;
+
+ ulong alignedEndAddress = (endAddress + BufferAlignmentMask) & ~BufferAlignmentMask;
+
+ // The buffer must have the size of at least one page.
+ if (alignedEndAddress == alignedAddress)
+ {
+ alignedEndAddress += BufferAlignmentSize;
+ }
+
+ CreateBuffer(alignedAddress, alignedEndAddress - alignedAddress);
+
+ return address;
+ }
+
+ ///
+ /// Creates a new buffer for the specified range, if needed.
+ /// If a buffer where this range can be fully contained already exists,
+ /// then the creation of a new buffer is not necessary.
+ ///
+ /// Address of the buffer in guest memory
+ /// Size in bytes of the buffer
+ private void CreateBuffer(ulong address, ulong size)
+ {
+ int overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref _bufferOverlaps);
+
+ if (overlapsCount != 0)
+ {
+ // The buffer already exists. We can just return the existing buffer
+ // if the buffer we need is fully contained inside the overlapping buffer.
+ // Otherwise, we must delete the overlapping buffers and create a bigger buffer
+ // that fits all the data we need. We also need to copy the contents from the
+ // old buffer(s) to the new buffer.
+ ulong endAddress = address + size;
+
+ if (_bufferOverlaps[0].Address > address || _bufferOverlaps[0].EndAddress < endAddress)
+ {
+ for (int index = 0; index < overlapsCount; index++)
+ {
+ Buffer buffer = _bufferOverlaps[index];
+
+ address = Math.Min(address, buffer.Address);
+ endAddress = Math.Max(endAddress, buffer.EndAddress);
+
+ buffer.SynchronizeMemory(buffer.Address, buffer.Size);
+
+ _buffers.Remove(buffer);
+ }
+
+ Buffer newBuffer = new Buffer(_context, address, endAddress - address);
+
+ _buffers.Add(newBuffer);
+
+ for (int index = 0; index < overlapsCount; index++)
+ {
+ Buffer buffer = _bufferOverlaps[index];
+
+ int dstOffset = (int)(buffer.Address - newBuffer.Address);
+
+ buffer.CopyTo(newBuffer, dstOffset);
+
+ buffer.Dispose();
+ }
+
+ _rebind = true;
+ }
+ }
+ else
+ {
+ // No overlap, just create a new buffer.
+ Buffer buffer = new Buffer(_context, address, size);
+
+ _buffers.Add(buffer);
+ }
+
+ ShrinkOverlapsBufferIfNeeded();
+ }
+
+ ///
+ /// Resizes the temporary buffer used for range list intersection results, if it has grown too much.
+ ///
+ private void ShrinkOverlapsBufferIfNeeded()
+ {
+ if (_bufferOverlaps.Length > OverlapsBufferMaxCapacity)
+ {
+ Array.Resize(ref _bufferOverlaps, OverlapsBufferMaxCapacity);
+ }
+ }
+
+ ///
+ /// Gets the address of the compute uniform buffer currently bound at the given index.
+ ///
+ /// Index of the uniform buffer binding
+ /// The uniform buffer address, or an undefined value if the buffer is not currently bound
+ public ulong GetComputeUniformBufferAddress(int index)
+ {
+ return _cpUniformBuffers.Buffers[index].Address;
+ }
+
+ ///
+ /// Gets the address of the graphics uniform buffer currently bound at the given index.
+ ///
+ /// Index of the shader stage
+ /// Index of the uniform buffer binding
+ /// The uniform buffer address, or an undefined value if the buffer is not currently bound
+ public ulong GetGraphicsUniformBufferAddress(int stage, int index)
+ {
+ return _gpUniformBuffers[stage].Buffers[index].Address;
+ }
+
+ ///
+ /// Ensures that the compute engine bindings are visible to the host GPU.
+ /// Note: this actually performs the binding using the host graphics API.
+ ///
+ public void CommitComputeBindings()
+ {
+ uint enableMask = _cpStorageBuffers.EnableMask;
+
+ for (int index = 0; (enableMask >> index) != 0; index++)
+ {
+ if ((enableMask & (1u << index)) == 0)
+ {
+ continue;
+ }
+
+ BufferBounds bounds = _cpStorageBuffers.Buffers[index];
+
+ if (bounds.Address == 0)
+ {
+ continue;
+ }
+
+ BufferRange buffer = GetBufferRange(bounds.Address, bounds.Size);
+
+ _context.Renderer.Pipeline.SetStorageBuffer(index, ShaderStage.Compute, buffer);
+ }
+
+ enableMask = _cpUniformBuffers.EnableMask;
+
+ for (int index = 0; (enableMask >> index) != 0; index++)
+ {
+ if ((enableMask & (1u << index)) == 0)
+ {
+ continue;
+ }
+
+ BufferBounds bounds = _cpUniformBuffers.Buffers[index];
+
+ if (bounds.Address == 0)
+ {
+ continue;
+ }
+
+ BufferRange buffer = GetBufferRange(bounds.Address, bounds.Size);
+
+ _context.Renderer.Pipeline.SetUniformBuffer(index, ShaderStage.Compute, buffer);
+ }
+
+ // Force rebind after doing compute work.
+ _rebind = true;
+ }
+
+ ///
+ /// Ensures that the graphics engine bindings are visible to the host GPU.
+ /// Note: this actually performs the binding using the host graphics API.
+ ///
+ public void CommitBindings()
+ {
+ if (_indexBufferDirty || _rebind)
+ {
+ _indexBufferDirty = false;
+
+ if (_indexBuffer.Address != 0)
+ {
+ BufferRange buffer = GetBufferRange(_indexBuffer.Address, _indexBuffer.Size);
+
+ _context.Renderer.Pipeline.SetIndexBuffer(buffer, _indexBuffer.Type);
+ }
+ }
+ else if (_indexBuffer.Address != 0)
+ {
+ SynchronizeBufferRange(_indexBuffer.Address, _indexBuffer.Size);
+ }
+
+ uint vbEnableMask = _vertexBuffersEnableMask;
+
+ if (_vertexBuffersDirty || _rebind)
+ {
+ _vertexBuffersDirty = false;
+
+ VertexBufferDescriptor[] vertexBuffers = new VertexBufferDescriptor[Constants.TotalVertexBuffers];
+
+ for (int index = 0; (vbEnableMask >> index) != 0; index++)
+ {
+ VertexBuffer vb = _vertexBuffers[index];
+
+ if (vb.Address == 0)
+ {
+ continue;
+ }
+
+ BufferRange buffer = GetBufferRange(vb.Address, vb.Size);
+
+ vertexBuffers[index] = new VertexBufferDescriptor(buffer, vb.Stride, vb.Divisor);
+ }
+
+ _context.Renderer.Pipeline.SetVertexBuffers(vertexBuffers);
+ }
+ else
+ {
+ for (int index = 0; (vbEnableMask >> index) != 0; index++)
+ {
+ VertexBuffer vb = _vertexBuffers[index];
+
+ if (vb.Address == 0)
+ {
+ continue;
+ }
+
+ SynchronizeBufferRange(vb.Address, vb.Size);
+ }
+ }
+
+ if (_gpStorageBuffersDirty || _rebind)
+ {
+ _gpStorageBuffersDirty = false;
+
+ BindBuffers(_gpStorageBuffers, isStorage: true);
+ }
+ else
+ {
+ UpdateBuffers(_gpStorageBuffers);
+ }
+
+ if (_gpUniformBuffersDirty || _rebind)
+ {
+ _gpUniformBuffersDirty = false;
+
+ BindBuffers(_gpUniformBuffers, isStorage: false);
+ }
+ else
+ {
+ UpdateBuffers(_gpUniformBuffers);
+ }
+
+ _rebind = false;
+ }
+
+ ///
+ /// Bind respective buffer bindings on the host API.
+ ///
+ /// Bindings to bind
+ /// True to bind as storage buffer, false to bind as uniform buffers
+ private void BindBuffers(BuffersPerStage[] bindings, bool isStorage)
+ {
+ BindOrUpdateBuffers(bindings, bind: true, isStorage);
+ }
+
+ ///
+ /// Updates data for the already bound buffer bindings.
+ ///
+ /// Bindings to update
+ private void UpdateBuffers(BuffersPerStage[] bindings)
+ {
+ BindOrUpdateBuffers(bindings, bind: false);
+ }
+
+ ///
+ /// This binds buffers into the host API, or updates data for already bound buffers.
+ ///
+ /// Bindings to bind or update
+ /// True to bind, false to update
+ /// True to bind as storage buffer, false to bind as uniform buffer
+ private void BindOrUpdateBuffers(BuffersPerStage[] bindings, bool bind, bool isStorage = false)
+ {
+ for (ShaderStage stage = ShaderStage.Vertex; stage <= ShaderStage.Fragment; stage++)
+ {
+ uint enableMask = bindings[(int)stage - 1].EnableMask;
+
+ if (enableMask == 0)
+ {
+ continue;
+ }
+
+ for (int index = 0; (enableMask >> index) != 0; index++)
+ {
+ if ((enableMask & (1u << index)) == 0)
+ {
+ continue;
+ }
+
+ BufferBounds bounds = bindings[(int)stage - 1].Buffers[index];
+
+ if (bounds.Address == 0)
+ {
+ continue;
+ }
+
+ if (bind)
+ {
+ BindBuffer(index, stage, bounds, isStorage);
+ }
+ else
+ {
+ SynchronizeBufferRange(bounds.Address, bounds.Size);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Binds a buffer on the host API.
+ ///
+ /// Index to bind the buffer into
+ /// Shader stage to bind the buffer into
+ /// Buffer address and size
+ /// True to bind as storage buffer, false to bind as uniform buffer
+ private void BindBuffer(int index, ShaderStage stage, BufferBounds bounds, bool isStorage)
+ {
+ BufferRange buffer = GetBufferRange(bounds.Address, bounds.Size);
+
+ if (isStorage)
+ {
+ _context.Renderer.Pipeline.SetStorageBuffer(index, stage, buffer);
+ }
+ else
+ {
+ _context.Renderer.Pipeline.SetUniformBuffer(index, stage, buffer);
+ }
+ }
+
+ ///
+ /// Copy a buffer data from a given address to another.
+ ///
+ ///
+ /// This does a GPU side copy.
+ ///
+ /// GPU virtual address of the copy source
+ /// GPU virtual address of the copy destination
+ /// Size in bytes of the copy
+ public void CopyBuffer(GpuVa srcVa, GpuVa dstVa, ulong size)
+ {
+ ulong srcAddress = TranslateAndCreateBuffer(srcVa.Pack(), size);
+ ulong dstAddress = TranslateAndCreateBuffer(dstVa.Pack(), size);
+
+ Buffer srcBuffer = GetBuffer(srcAddress, size);
+ Buffer dstBuffer = GetBuffer(dstAddress, size);
+
+ int srcOffset = (int)(srcAddress - srcBuffer.Address);
+ int dstOffset = (int)(dstAddress - dstBuffer.Address);
+
+ srcBuffer.HostBuffer.CopyTo(
+ dstBuffer.HostBuffer,
+ srcOffset,
+ dstOffset,
+ (int)size);
+
+ dstBuffer.Flush(dstAddress, size);
+ }
+
+ ///
+ /// Gets a buffer sub-range for a given memory range.
+ ///
+ /// Start address of the memory range
+ /// Size in bytes of the memory range
+ /// The buffer sub-range for the given range
+ private BufferRange GetBufferRange(ulong address, ulong size)
+ {
+ return GetBuffer(address, size).GetRange(address, size);
+ }
+
+ ///
+ /// Gets a buffer for a given memory range.
+ /// A buffer overlapping with the specified range is assumed to already exist on the cache.
+ ///
+ /// Start address of the memory range
+ /// Size in bytes of the memory range
+ /// The buffer where the range is fully contained
+ private Buffer GetBuffer(ulong address, ulong size)
+ {
+ Buffer buffer;
+
+ if (size != 0)
+ {
+ buffer = _buffers.FindFirstOverlap(address, size);
+
+ buffer.SynchronizeMemory(address, size);
+ }
+ else
+ {
+ buffer = _buffers.FindFirstOverlap(address, 1);
+ }
+
+ return buffer;
+ }
+
+ ///
+ /// Performs guest to host memory synchronization of a given memory range.
+ ///
+ /// Start address of the memory range
+ /// Size in bytes of the memory range
+ private void SynchronizeBufferRange(ulong address, ulong size)
+ {
+ if (size != 0)
+ {
+ Buffer buffer = _buffers.FindFirstOverlap(address, size);
+
+ buffer.SynchronizeMemory(address, size);
+ }
+ }
+
+ ///
+ /// Disposes all buffers in the cache.
+ /// It's an error to use the buffer manager after disposal.
+ ///
+ public void Dispose()
+ {
+ foreach (Buffer buffer in _buffers)
+ {
+ buffer.Dispose();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Memory/IRange.cs b/Ryujinx.Graphics.Gpu/Memory/IRange.cs
new file mode 100644
index 0000000000..9d5eee0b0d
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Memory/IRange.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+ ///
+ /// Range of memory.
+ ///
+ interface IRange
+ {
+ ulong Address { get; }
+ ulong Size { get; }
+
+ bool OverlapsWith(ulong address, ulong size);
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Memory/IndexBuffer.cs b/Ryujinx.Graphics.Gpu/Memory/IndexBuffer.cs
new file mode 100644
index 0000000000..7765e89943
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Memory/IndexBuffer.cs
@@ -0,0 +1,15 @@
+using Ryujinx.Graphics.GAL;
+
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+ ///
+ /// GPU Index Buffer information.
+ ///
+ struct IndexBuffer
+ {
+ public ulong Address;
+ public ulong Size;
+
+ public IndexType Type;
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Memory/MemoryAccessor.cs b/Ryujinx.Graphics.Gpu/Memory/MemoryAccessor.cs
new file mode 100644
index 0000000000..1877933317
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Memory/MemoryAccessor.cs
@@ -0,0 +1,124 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+ ///
+ /// GPU mapped memory accessor.
+ ///
+ public class MemoryAccessor
+ {
+ private GpuContext _context;
+
+ ///
+ /// Creates a new instance of the GPU memory accessor.
+ ///
+ /// GPU context that the memory accessor belongs to
+ public MemoryAccessor(GpuContext context)
+ {
+ _context = context;
+ }
+
+ ///
+ /// Reads a byte array from GPU mapped memory.
+ ///
+ /// GPU virtual address where the data is located
+ /// Size of the data in bytes
+ /// Byte array with the data
+ public byte[] ReadBytes(ulong gpuVa, ulong size)
+ {
+ return Read(gpuVa, size).ToArray();
+ }
+
+ ///
+ /// Reads data from GPU mapped memory.
+ /// This reads as much data as possible, up to the specified maximum size.
+ ///
+ /// GPU virtual address where the data is located
+ /// Maximum size of the data
+ /// The data at the specified memory location
+ public Span Read(ulong gpuVa, ulong maxSize)
+ {
+ ulong processVa = _context.MemoryManager.Translate(gpuVa);
+
+ ulong size = Math.Min(_context.MemoryManager.GetSubSize(gpuVa), maxSize);
+
+ return _context.PhysicalMemory.Read(processVa, size);
+ }
+
+ ///
+ /// Reads a structure from GPU mapped memory.
+ ///
+ /// Type of the structure
+ /// GPU virtual address where the structure is located
+ /// The structure at the specified memory location
+ public T Read(ulong gpuVa) where T : struct
+ {
+ ulong processVa = _context.MemoryManager.Translate(gpuVa);
+
+ ulong size = (uint)Marshal.SizeOf();
+
+ return MemoryMarshal.Cast(_context.PhysicalMemory.Read(processVa, size))[0];
+ }
+
+ ///
+ /// Reads a 32-bits signed integer from GPU mapped memory.
+ ///
+ /// GPU virtual address where the value is located
+ /// The value at the specified memory location
+ public int ReadInt32(ulong gpuVa)
+ {
+ ulong processVa = _context.MemoryManager.Translate(gpuVa);
+
+ return BitConverter.ToInt32(_context.PhysicalMemory.Read(processVa, 4));
+ }
+
+ ///
+ /// Reads a 64-bits unsigned integer from GPU mapped memory.
+ ///
+ /// GPU virtual address where the value is located
+ /// The value at the specified memory location
+ public ulong ReadUInt64(ulong gpuVa)
+ {
+ ulong processVa = _context.MemoryManager.Translate(gpuVa);
+
+ return BitConverter.ToUInt64(_context.PhysicalMemory.Read(processVa, 8));
+ }
+
+ ///
+ /// Reads a 8-bits unsigned integer from GPU mapped memory.
+ ///
+ /// GPU virtual address where the value is located
+ /// The value to be written
+ public void WriteByte(ulong gpuVa, byte value)
+ {
+ ulong processVa = _context.MemoryManager.Translate(gpuVa);
+
+ _context.PhysicalMemory.Write(processVa, MemoryMarshal.CreateSpan(ref value, 1));
+ }
+
+ ///
+ /// Writes a 32-bits signed integer to GPU mapped memory.
+ ///
+ /// GPU virtual address to write the value into
+ /// The value to be written
+ public void Write(ulong gpuVa, int value)
+ {
+ ulong processVa = _context.MemoryManager.Translate(gpuVa);
+
+ _context.PhysicalMemory.Write(processVa, BitConverter.GetBytes(value));
+ }
+
+ ///
+ /// Writes data to GPU mapped memory.
+ ///
+ /// GPU virtual address to write the data into
+ /// The data to be written
+ public void Write(ulong gpuVa, Span data)
+ {
+ ulong processVa = _context.MemoryManager.Translate(gpuVa);
+
+ _context.PhysicalMemory.Write(processVa, data);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs b/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs
new file mode 100644
index 0000000000..2fc315c3cd
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs
@@ -0,0 +1,352 @@
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+ ///
+ /// GPU memory manager.
+ ///
+ public class MemoryManager
+ {
+ private const ulong AddressSpaceSize = 1UL << 40;
+
+ public const ulong BadAddress = ulong.MaxValue;
+
+ private const int PtLvl0Bits = 14;
+ private const int PtLvl1Bits = 14;
+ public const int PtPageBits = 12;
+
+ private const ulong PtLvl0Size = 1UL << PtLvl0Bits;
+ private const ulong PtLvl1Size = 1UL << PtLvl1Bits;
+ public const ulong PageSize = 1UL << PtPageBits;
+
+ private const ulong PtLvl0Mask = PtLvl0Size - 1;
+ private const ulong PtLvl1Mask = PtLvl1Size - 1;
+ public const ulong PageMask = PageSize - 1;
+
+ private const int PtLvl0Bit = PtPageBits + PtLvl1Bits;
+ private const int PtLvl1Bit = PtPageBits;
+
+ private const ulong PteUnmapped = 0xffffffff_ffffffff;
+ private const ulong PteReserved = 0xffffffff_fffffffe;
+
+ private ulong[][] _pageTable;
+
+ ///
+ /// Creates a new instance of the GPU memory manager.
+ ///
+ public MemoryManager()
+ {
+ _pageTable = new ulong[PtLvl0Size][];
+ }
+
+ ///
+ /// Maps a given range of pages to the specified CPU virtual address.
+ ///
+ ///
+ /// All addresses and sizes must be page aligned.
+ ///
+ /// CPU virtual address to map into
+ /// GPU virtual address to be mapped
+ /// Size in bytes of the mapping
+ /// GPU virtual address of the mapping
+ public ulong Map(ulong pa, ulong va, ulong size)
+ {
+ lock (_pageTable)
+ {
+ for (ulong offset = 0; offset < size; offset += PageSize)
+ {
+ SetPte(va + offset, pa + offset);
+ }
+ }
+
+ return va;
+ }
+
+ ///
+ /// Maps a given range of pages to an allocated GPU virtual address.
+ /// The memory is automatically allocated by the memory manager.
+ ///
+ /// CPU virtual address to map into
+ /// Size in bytes of the mapping
+ /// GPU virtual address where the range was mapped, or an all ones mask in case of failure
+ public ulong Map(ulong pa, ulong size)
+ {
+ lock (_pageTable)
+ {
+ ulong va = GetFreePosition(size);
+
+ if (va != PteUnmapped)
+ {
+ for (ulong offset = 0; offset < size; offset += PageSize)
+ {
+ SetPte(va + offset, pa + offset);
+ }
+ }
+
+ return va;
+ }
+ }
+
+ ///
+ /// Maps a given range of pages to an allocated GPU virtual address.
+ /// The memory is automatically allocated by the memory manager.
+ /// This also ensures that the mapping is always done in the first 4GB of GPU address space.
+ ///
+ /// CPU virtual address to map into
+ /// Size in bytes of the mapping
+ /// GPU virtual address where the range was mapped, or an all ones mask in case of failure
+ public ulong MapLow(ulong pa, ulong size)
+ {
+ lock (_pageTable)
+ {
+ ulong va = GetFreePosition(size, 1, PageSize);
+
+ if (va != PteUnmapped && va <= uint.MaxValue && (va + size) <= uint.MaxValue)
+ {
+ for (ulong offset = 0; offset < size; offset += PageSize)
+ {
+ SetPte(va + offset, pa + offset);
+ }
+ }
+ else
+ {
+ va = PteUnmapped;
+ }
+
+ return va;
+ }
+ }
+
+ ///
+ /// Reserves memory at a fixed GPU memory location.
+ /// This prevents the reserved region from being used for memory allocation for map.
+ ///
+ /// GPU virtual address to reserve
+ /// Size in bytes of the reservation
+ /// GPU virtual address of the reservation, or an all ones mask in case of failure
+ public ulong ReserveFixed(ulong va, ulong size)
+ {
+ lock (_pageTable)
+ {
+ for (ulong offset = 0; offset < size; offset += PageSize)
+ {
+ if (IsPageInUse(va + offset))
+ {
+ return PteUnmapped;
+ }
+ }
+
+ for (ulong offset = 0; offset < size; offset += PageSize)
+ {
+ SetPte(va + offset, PteReserved);
+ }
+ }
+
+ return va;
+ }
+
+ ///
+ /// Reserves memory at any GPU memory location.
+ ///
+ /// Size in bytes of the reservation
+ /// Reservation address alignment in bytes
+ /// GPU virtual address of the reservation, or an all ones mask in case of failure
+ public ulong Reserve(ulong size, ulong alignment)
+ {
+ lock (_pageTable)
+ {
+ ulong address = GetFreePosition(size, alignment);
+
+ if (address != PteUnmapped)
+ {
+ for (ulong offset = 0; offset < size; offset += PageSize)
+ {
+ SetPte(address + offset, PteReserved);
+ }
+ }
+
+ return address;
+ }
+ }
+
+ ///
+ /// Frees memory that was previously allocated by a map or reserved.
+ ///
+ /// GPU virtual address to free
+ /// Size in bytes of the region being freed
+ public void Free(ulong va, ulong size)
+ {
+ lock (_pageTable)
+ {
+ for (ulong offset = 0; offset < size; offset += PageSize)
+ {
+ SetPte(va + offset, PteUnmapped);
+ }
+ }
+ }
+
+ ///
+ /// Gets the address of an unused (free) region of the specified size.
+ ///
+ /// Size of the region in bytes
+ /// Required alignment of the region address in bytes
+ /// Start address of the search on the address space
+ /// GPU virtual address of the allocation, or an all ones mask in case of failure
+ private ulong GetFreePosition(ulong size, ulong alignment = 1, ulong start = 1UL << 32)
+ {
+ // Note: Address 0 is not considered valid by the driver,
+ // when 0 is returned it's considered a mapping error.
+ ulong address = start;
+ ulong freeSize = 0;
+
+ if (alignment == 0)
+ {
+ alignment = 1;
+ }
+
+ alignment = (alignment + PageMask) & ~PageMask;
+
+ while (address + freeSize < AddressSpaceSize)
+ {
+ if (!IsPageInUse(address + freeSize))
+ {
+ freeSize += PageSize;
+
+ if (freeSize >= size)
+ {
+ return address;
+ }
+ }
+ else
+ {
+ address += freeSize + PageSize;
+ freeSize = 0;
+
+ ulong remainder = address % alignment;
+
+ if (remainder != 0)
+ {
+ address = (address - remainder) + alignment;
+ }
+ }
+ }
+
+ return PteUnmapped;
+ }
+
+ ///
+ /// Gets the number of mapped or reserved pages on a given region.
+ ///
+ /// Start GPU virtual address of the region
+ /// Mapped size in bytes of the specified region
+ internal ulong GetSubSize(ulong gpuVa)
+ {
+ ulong size = 0;
+
+ while (GetPte(gpuVa + size) != PteUnmapped)
+ {
+ size += PageSize;
+ }
+
+ return size;
+ }
+
+ ///
+ /// Translates a GPU virtual address to a CPU virtual address.
+ ///
+ /// GPU virtual address to be translated
+ /// CPU virtual address
+ public ulong Translate(ulong gpuVa)
+ {
+ ulong baseAddress = GetPte(gpuVa);
+
+ if (baseAddress == PteUnmapped || baseAddress == PteReserved)
+ {
+ return PteUnmapped;
+ }
+
+ return baseAddress + (gpuVa & PageMask);
+ }
+
+ ///
+ /// Checks if a given memory region is currently unmapped.
+ ///
+ /// Start GPU virtual address of the region
+ /// Size in bytes of the region
+ /// True if the region is unmapped (free), false otherwise
+ public bool IsRegionFree(ulong gpuVa, ulong size)
+ {
+ for (ulong offset = 0; offset < size; offset += PageSize)
+ {
+ if (IsPageInUse(gpuVa + offset))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ ///
+ /// Checks if a given memory page is mapped or reserved.
+ ///
+ /// GPU virtual address of the page
+ /// True if the page is mapped or reserved, false otherwise
+ private bool IsPageInUse(ulong gpuVa)
+ {
+ if (gpuVa >> PtLvl0Bits + PtLvl1Bits + PtPageBits != 0)
+ {
+ return false;
+ }
+
+ ulong l0 = (gpuVa >> PtLvl0Bit) & PtLvl0Mask;
+ ulong l1 = (gpuVa >> PtLvl1Bit) & PtLvl1Mask;
+
+ if (_pageTable[l0] == null)
+ {
+ return false;
+ }
+
+ return _pageTable[l0][l1] != PteUnmapped;
+ }
+
+ ///
+ /// Gets the Page Table entry for a given GPU virtual address.
+ ///
+ /// GPU virtual address
+ /// Page table entry (CPU virtual address)
+ private ulong GetPte(ulong gpuVa)
+ {
+ ulong l0 = (gpuVa >> PtLvl0Bit) & PtLvl0Mask;
+ ulong l1 = (gpuVa >> PtLvl1Bit) & PtLvl1Mask;
+
+ if (_pageTable[l0] == null)
+ {
+ return PteUnmapped;
+ }
+
+ return _pageTable[l0][l1];
+ }
+
+ ///
+ /// Sets a Page Table entry at a given GPU virtual address.
+ ///
+ /// GPU virtual address
+ /// Page table entry (CPU virtual address)
+ private void SetPte(ulong gpuVa, ulong pte)
+ {
+ ulong l0 = (gpuVa >> PtLvl0Bit) & PtLvl0Mask;
+ ulong l1 = (gpuVa >> PtLvl1Bit) & PtLvl1Mask;
+
+ if (_pageTable[l0] == null)
+ {
+ _pageTable[l0] = new ulong[PtLvl1Size];
+
+ for (ulong index = 0; index < PtLvl1Size; index++)
+ {
+ _pageTable[l0][index] = PteUnmapped;
+ }
+ }
+
+ _pageTable[l0][l1] = pte;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs b/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs
new file mode 100644
index 0000000000..71384df237
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs
@@ -0,0 +1,57 @@
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+ using CpuMemoryManager = ARMeilleure.Memory.MemoryManager;
+
+ ///
+ /// Represents physical memory, accessible from the GPU.
+ /// This is actually working CPU virtual addresses, of memory mapped on the application process.
+ ///
+ class PhysicalMemory
+ {
+ private readonly CpuMemoryManager _cpuMemory;
+
+ ///
+ /// Creates a new instance of the physical memory.
+ ///
+ /// CPU memory manager of the application process
+ public PhysicalMemory(CpuMemoryManager cpuMemory)
+ {
+ _cpuMemory = cpuMemory;
+ }
+
+ ///
+ /// Reads data from the application process.
+ ///
+ /// Address to be read
+ /// Size in bytes to be read
+ /// The data at the specified memory location
+ public Span Read(ulong address, ulong size)
+ {
+ return _cpuMemory.ReadBytes((long)address, (long)size);
+ }
+
+ ///
+ /// Writes data to the application process.
+ ///
+ /// Address to write into
+ /// Data to be written
+ public void Write(ulong address, Span data)
+ {
+ _cpuMemory.WriteBytes((long)address, data.ToArray());
+ }
+
+ ///
+ /// Gets the modified ranges for a given range of the application process mapped memory.
+ ///
+ /// Start address of the range
+ /// Size, in bytes, of the range
+ /// Name of the GPU resource being checked
+ /// Ranges, composed of address and size, modified by the application process, form the CPU
+ public (ulong, ulong)[] GetModifiedRanges(ulong address, ulong size, ResourceName name)
+ {
+ return _cpuMemory.GetModifiedRanges(address, size, (int)name);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Memory/RangeList.cs b/Ryujinx.Graphics.Gpu/Memory/RangeList.cs
new file mode 100644
index 0000000000..6af440c054
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Memory/RangeList.cs
@@ -0,0 +1,343 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+ ///
+ /// List of GPU resources with data on guest memory.
+ ///
+ /// Type of the GPU resource
+ class RangeList : IEnumerable where T : IRange
+ {
+ private const int ArrayGrowthSize = 32;
+
+ private readonly List _items;
+
+ ///
+ /// Creates a new GPU resources list.
+ ///
+ public RangeList()
+ {
+ _items = new List();
+ }
+
+ ///
+ /// Adds a new item to the list.
+ ///
+ /// The item to be added
+ public void Add(T item)
+ {
+ int index = BinarySearch(item.Address);
+
+ if (index < 0)
+ {
+ index = ~index;
+ }
+
+ _items.Insert(index, item);
+ }
+
+ ///
+ /// Removes an item from the list.
+ ///
+ /// The item to be removed
+ /// True if the item was removed, or false if it was not found
+ public bool Remove(T item)
+ {
+ int index = BinarySearch(item.Address);
+
+ if (index >= 0)
+ {
+ while (index > 0 && _items[index - 1].Address == item.Address)
+ {
+ index--;
+ }
+
+ while (index < _items.Count)
+ {
+ if (_items[index].Equals(item))
+ {
+ _items.RemoveAt(index);
+
+ return true;
+ }
+
+ if (_items[index].Address > item.Address)
+ {
+ break;
+ }
+
+ index++;
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// Gets the first item on the list overlapping in memory with the specified item.
+ ///
+ ///
+ /// Despite the name, this has no ordering guarantees of the returned item.
+ /// It only ensures that the item returned overlaps the specified item.
+ ///
+ /// Item to check for overlaps
+ /// The overlapping item, or the default value for the type if none found
+ public T FindFirstOverlap(T item)
+ {
+ return FindFirstOverlap(item.Address, item.Size);
+ }
+
+ ///
+ /// Gets the first item on the list overlapping the specified memory range.
+ ///
+ ///
+ /// Despite the name, this has no ordering guarantees of the returned item.
+ /// It only ensures that the item returned overlaps the specified memory range.
+ ///
+ /// Start address of the range
+ /// Size in bytes of the range
+ /// The overlapping item, or the default value for the type if none found
+ public T FindFirstOverlap(ulong address, ulong size)
+ {
+ int index = BinarySearch(address, size);
+
+ if (index < 0)
+ {
+ return default(T);
+ }
+
+ return _items[index];
+ }
+
+ ///
+ /// Gets all items overlapping with the specified item in memory.
+ ///
+ /// Item to check for overlaps
+ /// Output array where matches will be written. It is automatically resized to fit the results
+ /// The number of overlapping items found
+ public int FindOverlaps(T item, ref T[] output)
+ {
+ return FindOverlaps(item.Address, item.Size, ref output);
+ }
+
+ ///
+ /// Gets all items on the list overlapping the specified memory range.
+ ///
+ /// Start address of the range
+ /// Size in bytes of the range
+ /// Output array where matches will be written. It is automatically resized to fit the results
+ /// The number of overlapping items found
+ public int FindOverlaps(ulong address, ulong size, ref T[] output)
+ {
+ int outputIndex = 0;
+
+ ulong endAddress = address + size;
+
+ lock (_items)
+ {
+ foreach (T item in _items)
+ {
+ if (item.Address >= endAddress)
+ {
+ break;
+ }
+
+ if (item.OverlapsWith(address, size))
+ {
+ if (outputIndex == output.Length)
+ {
+ Array.Resize(ref output, outputIndex + ArrayGrowthSize);
+ }
+
+ output[outputIndex++] = item;
+ }
+ }
+ }
+
+ return outputIndex;
+ }
+
+ ///
+ /// Gets all items overlapping with the specified item in memory.
+ ///
+ ///
+ /// This method only returns correct results if none of the items on the list overlaps with
+ /// each other. If that is not the case, this method should not be used.
+ /// This method is faster than the regular method to find all overlaps.
+ ///
+ /// Item to check for overlaps
+ /// Output array where matches will be written. It is automatically resized to fit the results
+ /// The number of overlapping items found
+ public int FindOverlapsNonOverlapping(T item, ref T[] output)
+ {
+ return FindOverlapsNonOverlapping(item.Address, item.Size, ref output);
+ }
+
+ ///
+ /// Gets all items on the list overlapping the specified memory range.
+ ///
+ ///
+ /// This method only returns correct results if none of the items on the list overlaps with
+ /// each other. If that is not the case, this method should not be used.
+ /// This method is faster than the regular method to find all overlaps.
+ ///
+ /// Start address of the range
+ /// Size in bytes of the range
+ /// Output array where matches will be written. It is automatically resized to fit the results
+ /// The number of overlapping items found
+ public int FindOverlapsNonOverlapping(ulong address, ulong size, ref T[] output)
+ {
+ // This is a bit faster than FindOverlaps, but only works
+ // when none of the items on the list overlaps with each other.
+ int outputIndex = 0;
+
+ int index = BinarySearch(address, size);
+
+ if (index >= 0)
+ {
+ while (index > 0 && _items[index - 1].OverlapsWith(address, size))
+ {
+ index--;
+ }
+
+ do
+ {
+ if (outputIndex == output.Length)
+ {
+ Array.Resize(ref output, outputIndex + ArrayGrowthSize);
+ }
+
+ output[outputIndex++] = _items[index++];
+ }
+ while (index < _items.Count && _items[index].OverlapsWith(address, size));
+ }
+
+ return outputIndex;
+ }
+
+ ///
+ /// Gets all items on the list with the specified memory address.
+ ///
+ /// Address to find
+ /// Output array where matches will be written. It is automatically resized to fit the results
+ /// The number of matches found
+ public int FindOverlaps(ulong address, ref T[] output)
+ {
+ int index = BinarySearch(address);
+
+ int outputIndex = 0;
+
+ if (index >= 0)
+ {
+ while (index > 0 && _items[index - 1].Address == address)
+ {
+ index--;
+ }
+
+ while (index < _items.Count)
+ {
+ T overlap = _items[index++];
+
+ if (overlap.Address != address)
+ {
+ break;
+ }
+
+ if (outputIndex == output.Length)
+ {
+ Array.Resize(ref output, outputIndex + ArrayGrowthSize);
+ }
+
+ output[outputIndex++] = overlap;
+ }
+ }
+
+ return outputIndex;
+ }
+
+ ///
+ /// Performs binary search on the internal list of items.
+ ///
+ /// Address to find
+ /// List index of the item, or complement index of nearest item with lower value on the list
+ private int BinarySearch(ulong address)
+ {
+ int left = 0;
+ int right = _items.Count - 1;
+
+ while (left <= right)
+ {
+ int range = right - left;
+
+ int middle = left + (range >> 1);
+
+ T item = _items[middle];
+
+ if (item.Address == address)
+ {
+ return middle;
+ }
+
+ if (address < item.Address)
+ {
+ right = middle - 1;
+ }
+ else
+ {
+ left = middle + 1;
+ }
+ }
+
+ return ~left;
+ }
+
+ ///
+ /// Performs binary search for items overlapping a given memory range.
+ ///
+ /// Start address of the range
+ /// Size in bytes of the range
+ /// List index of the item, or complement index of nearest item with lower value on the list
+ private int BinarySearch(ulong address, ulong size)
+ {
+ int left = 0;
+ int right = _items.Count - 1;
+
+ while (left <= right)
+ {
+ int range = right - left;
+
+ int middle = left + (range >> 1);
+
+ T item = _items[middle];
+
+ if (item.OverlapsWith(address, size))
+ {
+ return middle;
+ }
+
+ if (address < item.Address)
+ {
+ right = middle - 1;
+ }
+ else
+ {
+ left = middle + 1;
+ }
+ }
+
+ return ~left;
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ return _items.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return _items.GetEnumerator();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Memory/ResourceName.cs b/Ryujinx.Graphics.Gpu/Memory/ResourceName.cs
new file mode 100644
index 0000000000..c3d2dc77a1
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Memory/ResourceName.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+ ///
+ /// Name of a GPU resource.
+ ///
+ public enum ResourceName
+ {
+ Buffer,
+ Texture,
+ TexturePool,
+ SamplerPool
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Memory/VertexBuffer.cs b/Ryujinx.Graphics.Gpu/Memory/VertexBuffer.cs
new file mode 100644
index 0000000000..8f08912513
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Memory/VertexBuffer.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+ ///
+ /// GPU Vertex Buffer information.
+ ///
+ struct VertexBuffer
+ {
+ public ulong Address;
+ public ulong Size;
+ public int Stride;
+ public int Divisor;
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/MethodParams.cs b/Ryujinx.Graphics.Gpu/MethodParams.cs
new file mode 100644
index 0000000000..dd60f77cd0
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/MethodParams.cs
@@ -0,0 +1,52 @@
+namespace Ryujinx.Graphics
+{
+ ///
+ /// Method call parameters.
+ ///
+ struct MethodParams
+ {
+ ///
+ /// Method offset.
+ ///
+ public int Method { get; }
+
+ ///
+ /// Method call argument.
+ ///
+ public int Argument { get; }
+
+ ///
+ /// Sub-channel where the call should be sent.
+ ///
+ public int SubChannel { get; }
+
+ ///
+ /// For multiple calls to the same method, this is the remaining calls count.
+ ///
+ public int MethodCount { get; }
+
+ ///
+ /// Indicates if the current call is the last one from a batch of calls to the same method.
+ ///
+ public bool IsLastCall => MethodCount <= 1;
+
+ ///
+ /// Constructs the method call parameters structure.
+ ///
+ /// Method offset
+ /// Method call argument
+ /// Optional sub-channel where the method should be sent (not required for macro calls)
+ /// Optional remaining calls count (not required for macro calls)
+ public MethodParams(
+ int method,
+ int argument,
+ int subChannel = 0,
+ int methodCount = 0)
+ {
+ Method = method;
+ Argument = argument;
+ SubChannel = subChannel;
+ MethodCount = methodCount;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/NvGpuFifo.cs b/Ryujinx.Graphics.Gpu/NvGpuFifo.cs
new file mode 100644
index 0000000000..853e5dfdd3
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/NvGpuFifo.cs
@@ -0,0 +1,219 @@
+using Ryujinx.Graphics.Gpu.State;
+
+namespace Ryujinx.Graphics.Gpu
+{
+ ///
+ /// GPU commands FIFO.
+ ///
+ class NvGpuFifo
+ {
+ private const int MacrosCount = 0x80;
+ private const int MacroIndexMask = MacrosCount - 1;
+
+ // Note: The size of the macro memory is unknown, we just make
+ // a guess here and use 256kb as the size. Increase if needed.
+ private const int MmeWords = 256 * 256;
+
+ private GpuContext _context;
+
+ ///
+ /// Cached GPU macro program.
+ ///
+ private struct CachedMacro
+ {
+ ///
+ /// Word offset of the code on the code memory.
+ ///
+ public int Position { get; }
+
+ private bool _executionPending;
+ private int _argument;
+
+ private MacroInterpreter _interpreter;
+
+ ///
+ /// Creates a new instance of the GPU cached macro program.
+ ///
+ /// Macro code start position
+ public CachedMacro(int position)
+ {
+ Position = position;
+
+ _executionPending = false;
+ _argument = 0;
+
+ _interpreter = new MacroInterpreter();
+ }
+
+ ///
+ /// Sets the first argument for the macro call.
+ ///
+ /// First argument
+ public void StartExecution(int argument)
+ {
+ _argument = argument;
+
+ _executionPending = true;
+ }
+
+ ///
+ /// Starts executing the macro program code.
+ ///
+ /// Program code
+ /// Current GPU state
+ public void Execute(int[] mme, GpuState state)
+ {
+ if (_executionPending)
+ {
+ _executionPending = false;
+
+ _interpreter?.Execute(mme, Position, _argument, state);
+ }
+ }
+
+ ///
+ /// Pushes an argument to the macro call argument FIFO.
+ ///
+ /// Argument to be pushed
+ public void PushArgument(int argument)
+ {
+ _interpreter?.Fifo.Enqueue(argument);
+ }
+ }
+
+ private int _currMacroPosition;
+ private int _currMacroBindIndex;
+
+ private CachedMacro[] _macros;
+
+ private int[] _mme;
+
+ ///
+ /// GPU sub-channel information.
+ ///
+ private class SubChannel
+ {
+ ///
+ /// Sub-channel GPU state.
+ ///
+ public GpuState State { get; }
+
+ ///
+ /// Engine bound to the sub-channel.
+ ///
+ public ClassId Class { get; set; }
+
+ ///
+ /// Creates a new instance of the GPU sub-channel.
+ ///
+ public SubChannel()
+ {
+ State = new GpuState();
+ }
+ }
+
+ private SubChannel[] _subChannels;
+
+ ///
+ /// Creates a new instance of the GPU commands FIFO.
+ ///
+ /// GPU emulation context
+ public NvGpuFifo(GpuContext context)
+ {
+ _context = context;
+
+ _macros = new CachedMacro[MacrosCount];
+
+ _mme = new int[MmeWords];
+
+ _subChannels = new SubChannel[8];
+
+ for (int index = 0; index < _subChannels.Length; index++)
+ {
+ _subChannels[index] = new SubChannel();
+
+ context.Methods.RegisterCallbacks(_subChannels[index].State);
+ }
+ }
+
+ ///
+ /// Calls a GPU method.
+ ///
+ /// GPU method call parameters
+ public void CallMethod(MethodParams meth)
+ {
+ if ((NvGpuFifoMeth)meth.Method == NvGpuFifoMeth.BindChannel)
+ {
+ _subChannels[meth.SubChannel].Class = (ClassId)meth.Argument;
+ }
+ else if (meth.Method < 0x60)
+ {
+ switch ((NvGpuFifoMeth)meth.Method)
+ {
+ case NvGpuFifoMeth.WaitForIdle:
+ {
+ _context.Methods.PerformDeferredDraws();
+
+ _context.Renderer.FlushPipelines();
+
+ break;
+ }
+
+ case NvGpuFifoMeth.SetMacroUploadAddress:
+ {
+ _currMacroPosition = meth.Argument;
+
+ break;
+ }
+
+ case NvGpuFifoMeth.SendMacroCodeData:
+ {
+ _mme[_currMacroPosition++] = meth.Argument;
+
+ break;
+ }
+
+ case NvGpuFifoMeth.SetMacroBindingIndex:
+ {
+ _currMacroBindIndex = meth.Argument;
+
+ break;
+ }
+
+ case NvGpuFifoMeth.BindMacro:
+ {
+ int position = meth.Argument;
+
+ _macros[_currMacroBindIndex++] = new CachedMacro(position);
+
+ break;
+ }
+ }
+ }
+ else if (meth.Method < 0xe00)
+ {
+ _subChannels[meth.SubChannel].State.CallMethod(meth);
+ }
+ else
+ {
+ int macroIndex = (meth.Method >> 1) & MacroIndexMask;
+
+ if ((meth.Method & 1) != 0)
+ {
+ _macros[macroIndex].PushArgument(meth.Argument);
+ }
+ else
+ {
+ _macros[macroIndex].StartExecution(meth.Argument);
+ }
+
+ if (meth.IsLastCall)
+ {
+ _macros[macroIndex].Execute(_mme, _subChannels[meth.SubChannel].State);
+
+ _context.Methods.PerformDeferredDraws();
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Graphics3d/NvGpuFifoMeth.cs b/Ryujinx.Graphics.Gpu/NvGpuFifoMeth.cs
similarity index 59%
rename from Ryujinx.Graphics/Graphics3d/NvGpuFifoMeth.cs
rename to Ryujinx.Graphics.Gpu/NvGpuFifoMeth.cs
index 9bf528b3f3..89023407e9 100644
--- a/Ryujinx.Graphics/Graphics3d/NvGpuFifoMeth.cs
+++ b/Ryujinx.Graphics.Gpu/NvGpuFifoMeth.cs
@@ -1,8 +1,12 @@
-namespace Ryujinx.Graphics.Graphics3d
+namespace Ryujinx.Graphics.Gpu
{
+ ///
+ /// GPU commands FIFO processor commands.
+ ///
enum NvGpuFifoMeth
{
BindChannel = 0,
+ WaitForIdle = 0x44,
SetMacroUploadAddress = 0x45,
SendMacroCodeData = 0x46,
SetMacroBindingIndex = 0x47,
diff --git a/Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj b/Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj
new file mode 100644
index 0000000000..b9751508ef
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+ netcoreapp3.0
+ win-x64;osx-x64;linux-x64
+
+
+
diff --git a/Ryujinx.Graphics.Gpu/Shader/CachedShader.cs b/Ryujinx.Graphics.Gpu/Shader/CachedShader.cs
new file mode 100644
index 0000000000..f849404564
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/CachedShader.cs
@@ -0,0 +1,37 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Shader;
+
+namespace Ryujinx.Graphics.Gpu.Shader
+{
+ ///
+ /// Cached shader code for a single shader stage.
+ ///
+ class CachedShader
+ {
+ ///
+ /// Shader program containing translated code.
+ ///
+ public ShaderProgram Program { get; }
+
+ ///
+ /// Host shader object.
+ ///
+ public IShader HostShader { get; set; }
+
+ ///
+ /// Maxwell binary shader code.
+ ///
+ public int[] Code { get; }
+
+ ///
+ /// Creates a new instace of the cached shader.
+ ///
+ /// Shader program
+ /// Maxwell binary shader code
+ public CachedShader(ShaderProgram program, int[] code)
+ {
+ Program = program;
+ Code = code;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Shader/ComputeShader.cs b/Ryujinx.Graphics.Gpu/Shader/ComputeShader.cs
new file mode 100644
index 0000000000..fcc38d0488
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/ComputeShader.cs
@@ -0,0 +1,31 @@
+using Ryujinx.Graphics.GAL;
+
+namespace Ryujinx.Graphics.Gpu.Shader
+{
+ ///
+ /// Cached compute shader code.
+ ///
+ class ComputeShader
+ {
+ ///
+ /// Host shader program object.
+ ///
+ public IProgram HostProgram { get; }
+
+ ///
+ /// Cached shader.
+ ///
+ public CachedShader Shader { get; }
+
+ ///
+ /// Creates a new instance of the compute shader.
+ ///
+ /// Host shader program
+ /// Cached shader
+ public ComputeShader(IProgram hostProgram, CachedShader shader)
+ {
+ HostProgram = hostProgram;
+ Shader = shader;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Shader/GraphicsShader.cs b/Ryujinx.Graphics.Gpu/Shader/GraphicsShader.cs
new file mode 100644
index 0000000000..e348f304f3
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/GraphicsShader.cs
@@ -0,0 +1,28 @@
+using Ryujinx.Graphics.GAL;
+
+namespace Ryujinx.Graphics.Gpu.Shader
+{
+ ///
+ /// Cached graphics shader code for all stages.
+ ///
+ class GraphicsShader
+ {
+ ///
+ /// Host shader program object.
+ ///
+ public IProgram HostProgram { get; set; }
+
+ ///
+ /// Compiled shader for each shader stage.
+ ///
+ public CachedShader[] Shaders { get; }
+
+ ///
+ /// Creates a new instance of cached graphics shader.
+ ///
+ public GraphicsShader()
+ {
+ Shaders = new CachedShader[Constants.ShaderStages];
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderAddresses.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderAddresses.cs
new file mode 100644
index 0000000000..76ea32482d
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/ShaderAddresses.cs
@@ -0,0 +1,51 @@
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Shader
+{
+ ///
+ /// Shader code addresses in memory for each shader stage.
+ ///
+ struct ShaderAddresses : IEquatable
+ {
+ public ulong VertexA;
+ public ulong Vertex;
+ public ulong TessControl;
+ public ulong TessEvaluation;
+ public ulong Geometry;
+ public ulong Fragment;
+
+ ///
+ /// Check if the addresses are equal.
+ ///
+ /// Shader addresses structure to compare with
+ /// True if they are equal, false otherwise
+ public override bool Equals(object other)
+ {
+ return other is ShaderAddresses addresses && Equals(addresses);
+ }
+
+ ///
+ /// Check if the addresses are equal.
+ ///
+ /// Shader addresses structure to compare with
+ /// True if they are equal, false otherwise
+ public bool Equals(ShaderAddresses other)
+ {
+ return VertexA == other.VertexA &&
+ Vertex == other.Vertex &&
+ TessControl == other.TessControl &&
+ TessEvaluation == other.TessEvaluation &&
+ Geometry == other.Geometry &&
+ Fragment == other.Fragment;
+ }
+
+ ///
+ /// Computes hash code from the addresses.
+ ///
+ /// Hash code
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(VertexA, Vertex, TessControl, TessEvaluation, Geometry, Fragment);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
new file mode 100644
index 0000000000..548a7e07ab
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
@@ -0,0 +1,532 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.Image;
+using Ryujinx.Graphics.Gpu.State;
+using Ryujinx.Graphics.Shader;
+using Ryujinx.Graphics.Shader.Translation;
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Shader
+{
+ using TextureDescriptor = Image.TextureDescriptor;
+
+ ///
+ /// Memory cache of shader code.
+ ///
+ class ShaderCache : IDisposable
+ {
+ private const int MaxProgramSize = 0x100000;
+
+ private const TranslationFlags DefaultFlags = TranslationFlags.DebugMode;
+
+ private GpuContext _context;
+
+ private ShaderDumper _dumper;
+
+ private Dictionary> _cpPrograms;
+
+ private Dictionary> _gpPrograms;
+
+ ///
+ /// Creates a new instance of the shader cache.
+ ///
+ /// GPU context that the shader cache belongs to
+ public ShaderCache(GpuContext context)
+ {
+ _context = context;
+
+ _dumper = new ShaderDumper();
+
+ _cpPrograms = new Dictionary>();
+
+ _gpPrograms = new Dictionary>();
+ }
+
+ ///
+ /// Gets a compute shader from the cache.
+ ///
+ ///
+ /// This automatically translates, compiles and adds the code to the cache if not present.
+ ///
+ /// GPU virtual address of the binary shader code
+ /// Shared memory size of the compute shader
+ /// Local group size X of the computer shader
+ /// Local group size Y of the computer shader
+ /// Local group size Z of the computer shader
+ /// Compiled compute shader code
+ public ComputeShader GetComputeShader(ulong gpuVa, int sharedMemorySize, int localSizeX, int localSizeY, int localSizeZ)
+ {
+ bool isCached = _cpPrograms.TryGetValue(gpuVa, out List list);
+
+ if (isCached)
+ {
+ foreach (ComputeShader cachedCpShader in list)
+ {
+ if (!IsShaderDifferent(cachedCpShader, gpuVa))
+ {
+ return cachedCpShader;
+ }
+ }
+ }
+
+ CachedShader shader = TranslateComputeShader(gpuVa, sharedMemorySize, localSizeX, localSizeY, localSizeZ);
+
+ shader.HostShader = _context.Renderer.CompileShader(shader.Program);
+
+ IProgram hostProgram = _context.Renderer.CreateProgram(new IShader[] { shader.HostShader });
+
+ ComputeShader cpShader = new ComputeShader(hostProgram, shader);
+
+ if (!isCached)
+ {
+ list = new List();
+
+ _cpPrograms.Add(gpuVa, list);
+ }
+
+ list.Add(cpShader);
+
+ return cpShader;
+ }
+
+ ///
+ /// Gets a graphics shader program from the shader cache.
+ /// This includes all the specified shader stages.
+ ///
+ ///
+ /// This automatically translates, compiles and adds the code to the cache if not present.
+ ///
+ /// Current GPU state
+ /// Addresses of the shaders for each stage
+ /// Compiled graphics shader code
+ public GraphicsShader GetGraphicsShader(GpuState state, ShaderAddresses addresses)
+ {
+ bool isCached = _gpPrograms.TryGetValue(addresses, out List list);
+
+ if (isCached)
+ {
+ foreach (GraphicsShader cachedGpShaders in list)
+ {
+ if (!IsShaderDifferent(cachedGpShaders, addresses))
+ {
+ return cachedGpShaders;
+ }
+ }
+ }
+
+ GraphicsShader gpShaders = new GraphicsShader();
+
+ if (addresses.VertexA != 0)
+ {
+ gpShaders.Shaders[0] = TranslateGraphicsShader(state, ShaderStage.Vertex, addresses.Vertex, addresses.VertexA);
+ }
+ else
+ {
+ gpShaders.Shaders[0] = TranslateGraphicsShader(state, ShaderStage.Vertex, addresses.Vertex);
+ }
+
+ gpShaders.Shaders[1] = TranslateGraphicsShader(state, ShaderStage.TessellationControl, addresses.TessControl);
+ gpShaders.Shaders[2] = TranslateGraphicsShader(state, ShaderStage.TessellationEvaluation, addresses.TessEvaluation);
+ gpShaders.Shaders[3] = TranslateGraphicsShader(state, ShaderStage.Geometry, addresses.Geometry);
+ gpShaders.Shaders[4] = TranslateGraphicsShader(state, ShaderStage.Fragment, addresses.Fragment);
+
+ BackpropQualifiers(gpShaders);
+
+ List hostShaders = new List();
+
+ for (int stage = 0; stage < gpShaders.Shaders.Length; stage++)
+ {
+ ShaderProgram program = gpShaders.Shaders[stage]?.Program;
+
+ if (program == null)
+ {
+ continue;
+ }
+
+ IShader hostShader = _context.Renderer.CompileShader(program);
+
+ gpShaders.Shaders[stage].HostShader = hostShader;
+
+ hostShaders.Add(hostShader);
+ }
+
+ gpShaders.HostProgram = _context.Renderer.CreateProgram(hostShaders.ToArray());
+
+ if (!isCached)
+ {
+ list = new List();
+
+ _gpPrograms.Add(addresses, list);
+ }
+
+ list.Add(gpShaders);
+
+ return gpShaders;
+ }
+
+ ///
+ /// Checks if compute shader code in memory is different from the cached shader.
+ ///
+ /// Cached compute shader
+ /// GPU virtual address of the shader code in memory
+ /// True if the code is different, false otherwise
+ private bool IsShaderDifferent(ComputeShader cpShader, ulong gpuVa)
+ {
+ return IsShaderDifferent(cpShader.Shader, gpuVa);
+ }
+
+ ///
+ /// Checks if graphics shader code from all stages in memory is different from the cached shaders.
+ ///
+ /// Cached graphics shaders
+ /// GPU virtual addresses of all enabled shader stages
+ /// True if the code is different, false otherwise
+ private bool IsShaderDifferent(GraphicsShader gpShaders, ShaderAddresses addresses)
+ {
+ for (int stage = 0; stage < gpShaders.Shaders.Length; stage++)
+ {
+ CachedShader shader = gpShaders.Shaders[stage];
+
+ ulong gpuVa = 0;
+
+ switch (stage)
+ {
+ case 0: gpuVa = addresses.Vertex; break;
+ case 1: gpuVa = addresses.TessControl; break;
+ case 2: gpuVa = addresses.TessEvaluation; break;
+ case 3: gpuVa = addresses.Geometry; break;
+ case 4: gpuVa = addresses.Fragment; break;
+ }
+
+ if (IsShaderDifferent(shader, gpuVa))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// Checks if the code of the specified cached shader is different from the code in memory.
+ ///
+ /// Cached shader to compare with
+ /// GPU virtual address of the binary shader code
+ /// True if the code is different, false otherwise
+ private bool IsShaderDifferent(CachedShader shader, ulong gpuVa)
+ {
+ if (shader == null)
+ {
+ return false;
+ }
+
+ for (int index = 0; index < shader.Code.Length; index++)
+ {
+ if (_context.MemoryAccessor.ReadInt32(gpuVa + (ulong)index * 4) != shader.Code[index])
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// Translates the binary Maxwell shader code to something that the host API accepts.
+ ///
+ /// GPU virtual address of the binary shader code
+ /// Shared memory size of the compute shader
+ /// Local group size X of the computer shader
+ /// Local group size Y of the computer shader
+ /// Local group size Z of the computer shader
+ /// Compiled compute shader code
+ private CachedShader TranslateComputeShader(ulong gpuVa, int sharedMemorySize, int localSizeX, int localSizeY, int localSizeZ)
+ {
+ if (gpuVa == 0)
+ {
+ return null;
+ }
+
+ int QueryInfo(QueryInfoName info, int index)
+ {
+ return info switch
+ {
+ QueryInfoName.ComputeLocalSizeX => localSizeX,
+ QueryInfoName.ComputeLocalSizeY => localSizeY,
+ QueryInfoName.ComputeLocalSizeZ => localSizeZ,
+ QueryInfoName.ComputeSharedMemorySize => sharedMemorySize,
+ _ => QueryInfoCommon(info)
+ };
+ }
+
+ TranslatorCallbacks callbacks = new TranslatorCallbacks(QueryInfo, PrintLog);
+
+ ShaderProgram program;
+
+ Span code = _context.MemoryAccessor.Read(gpuVa, MaxProgramSize);
+
+ program = Translator.Translate(code, callbacks, DefaultFlags | TranslationFlags.Compute);
+
+ int[] codeCached = MemoryMarshal.Cast(code.Slice(0, program.Size)).ToArray();
+
+ _dumper.Dump(code, compute: true, out string fullPath, out string codePath);
+
+ if (fullPath != null && codePath != null)
+ {
+ program.Prepend("// " + codePath);
+ program.Prepend("// " + fullPath);
+ }
+
+ return new CachedShader(program, codeCached);
+ }
+
+ ///
+ /// Translates the binary Maxwell shader code to something that the host API accepts.
+ ///
+ ///
+ /// This will combine the "Vertex A" and "Vertex B" shader stages, if specified, into one shader.
+ ///
+ /// Current GPU state
+ /// Shader stage
+ /// GPU virtual address of the shader code
+ /// Optional GPU virtual address of the "Vertex A" shader code
+ /// Compiled graphics shader code
+ private CachedShader TranslateGraphicsShader(GpuState state, ShaderStage stage, ulong gpuVa, ulong gpuVaA = 0)
+ {
+ if (gpuVa == 0)
+ {
+ return null;
+ }
+
+ int QueryInfo(QueryInfoName info, int index)
+ {
+ return info switch
+ {
+ QueryInfoName.IsTextureBuffer => Convert.ToInt32(QueryIsTextureBuffer(state, (int)stage - 1, index)),
+ QueryInfoName.IsTextureRectangle => Convert.ToInt32(QueryIsTextureRectangle(state, (int)stage - 1, index)),
+ QueryInfoName.PrimitiveTopology => (int)GetPrimitiveTopology(),
+ _ => QueryInfoCommon(info)
+ };
+ }
+
+ TranslatorCallbacks callbacks = new TranslatorCallbacks(QueryInfo, PrintLog);
+
+ ShaderProgram program;
+
+ int[] codeCached = null;
+
+ if (gpuVaA != 0)
+ {
+ Span codeA = _context.MemoryAccessor.Read(gpuVaA, MaxProgramSize);
+ Span codeB = _context.MemoryAccessor.Read(gpuVa, MaxProgramSize);
+
+ program = Translator.Translate(codeA, codeB, callbacks, DefaultFlags);
+
+ // TODO: We should also take "codeA" into account.
+ codeCached = MemoryMarshal.Cast(codeB.Slice(0, program.Size)).ToArray();
+
+ _dumper.Dump(codeA, compute: false, out string fullPathA, out string codePathA);
+ _dumper.Dump(codeB, compute: false, out string fullPathB, out string codePathB);
+
+ if (fullPathA != null && fullPathB != null && codePathA != null && codePathB != null)
+ {
+ program.Prepend("// " + codePathB);
+ program.Prepend("// " + fullPathB);
+ program.Prepend("// " + codePathA);
+ program.Prepend("// " + fullPathA);
+ }
+ }
+ else
+ {
+ Span code = _context.MemoryAccessor.Read(gpuVa, MaxProgramSize);
+
+ program = Translator.Translate(code, callbacks, DefaultFlags);
+
+ codeCached = MemoryMarshal.Cast(code.Slice(0, program.Size)).ToArray();
+
+ _dumper.Dump(code, compute: false, out string fullPath, out string codePath);
+
+ if (fullPath != null && codePath != null)
+ {
+ program.Prepend("// " + codePath);
+ program.Prepend("// " + fullPath);
+ }
+ }
+
+ ulong address = _context.MemoryManager.Translate(gpuVa);
+
+ return new CachedShader(program, codeCached);
+ }
+
+ ///
+ /// Performs backwards propagation of interpolation qualifiers or later shader stages input,
+ /// to ealier shader stages output.
+ /// This is required by older versions of OpenGL (pre-4.3).
+ ///
+ /// Graphics shader cached code
+ private void BackpropQualifiers(GraphicsShader program)
+ {
+ ShaderProgram fragmentShader = program.Shaders[4]?.Program;
+
+ bool isFirst = true;
+
+ for (int stage = 3; stage >= 0; stage--)
+ {
+ if (program.Shaders[stage] == null)
+ {
+ continue;
+ }
+
+ // We need to iterate backwards, since we do name replacement,
+ // and it would otherwise replace a subset of the longer names.
+ for (int attr = 31; attr >= 0; attr--)
+ {
+ string iq = fragmentShader?.Info.InterpolationQualifiers[attr].ToGlslQualifier() ?? string.Empty;
+
+ if (isFirst && !string.IsNullOrEmpty(iq))
+ {
+ program.Shaders[stage].Program.Replace($"{DefineNames.OutQualifierPrefixName}{attr}", iq);
+ }
+ else
+ {
+ program.Shaders[stage].Program.Replace($"{DefineNames.OutQualifierPrefixName}{attr} ", string.Empty);
+ }
+ }
+
+ isFirst = false;
+ }
+ }
+
+ ///
+ /// Gets the primitive topology for the current draw.
+ /// This is required by geometry shaders.
+ ///
+ /// Primitive topology
+ private InputTopology GetPrimitiveTopology()
+ {
+ switch (_context.Methods.PrimitiveType)
+ {
+ case PrimitiveType.Points:
+ return InputTopology.Points;
+ case PrimitiveType.Lines:
+ case PrimitiveType.LineLoop:
+ case PrimitiveType.LineStrip:
+ return InputTopology.Lines;
+ case PrimitiveType.LinesAdjacency:
+ case PrimitiveType.LineStripAdjacency:
+ return InputTopology.LinesAdjacency;
+ case PrimitiveType.Triangles:
+ case PrimitiveType.TriangleStrip:
+ case PrimitiveType.TriangleFan:
+ return InputTopology.Triangles;
+ case PrimitiveType.TrianglesAdjacency:
+ case PrimitiveType.TriangleStripAdjacency:
+ return InputTopology.TrianglesAdjacency;
+ }
+
+ return InputTopology.Points;
+ }
+
+ ///
+ /// Check if the target of a given texture is texture buffer.
+ /// This is required as 1D textures and buffer textures shares the same sampler type on binary shader code,
+ /// but not on GLSL.
+ ///
+ /// Current GPU state
+ /// Index of the shader stage
+ /// Index of the texture (this is the shader "fake" handle)
+ /// True if the texture is a buffer texture, false otherwise
+ private bool QueryIsTextureBuffer(GpuState state, int stageIndex, int index)
+ {
+ return GetTextureDescriptor(state, stageIndex, index).UnpackTextureTarget() == TextureTarget.TextureBuffer;
+ }
+
+ ///
+ /// Check if the target of a given texture is texture rectangle.
+ /// This is required as 2D textures and rectangle textures shares the same sampler type on binary shader code,
+ /// but not on GLSL.
+ ///
+ /// Current GPU state
+ /// Index of the shader stage
+ /// Index of the texture (this is the shader "fake" handle)
+ /// True if the texture is a rectangle texture, false otherwise
+ private bool QueryIsTextureRectangle(GpuState state, int stageIndex, int index)
+ {
+ var descriptor = GetTextureDescriptor(state, stageIndex, index);
+
+ TextureTarget target = descriptor.UnpackTextureTarget();
+
+ bool is2DTexture = target == TextureTarget.Texture2D ||
+ target == TextureTarget.Texture2DRect;
+
+ return !descriptor.UnpackTextureCoordNormalized() && is2DTexture;
+ }
+
+ ///
+ /// Gets the texture descriptor for a given texture on the pool.
+ ///
+ /// Current GPU state
+ /// Index of the shader stage
+ /// Index of the texture (this is the shader "fake" handle)
+ /// Texture descriptor
+ private TextureDescriptor GetTextureDescriptor(GpuState state, int stageIndex, int index)
+ {
+ return _context.Methods.TextureManager.GetGraphicsTextureDescriptor(state, stageIndex, index);
+ }
+
+ ///
+ /// Returns information required by both compute and graphics shader compilation.
+ ///
+ /// Information queried
+ /// Requested information
+ private int QueryInfoCommon(QueryInfoName info)
+ {
+ return info switch
+ {
+ QueryInfoName.StorageBufferOffsetAlignment => _context.Capabilities.StorageBufferOffsetAlignment,
+ QueryInfoName.SupportsNonConstantTextureOffset => Convert.ToInt32(_context.Capabilities.SupportsNonConstantTextureOffset),
+ _ => 0
+ };
+ }
+
+ ///
+ /// Prints a warning from the shader code translator.
+ ///
+ /// Warning message
+ private static void PrintLog(string message)
+ {
+ Logger.PrintWarning(LogClass.Gpu, $"Shader translator: {message}");
+ }
+
+ ///
+ /// Disposes the shader cache, deleting all the cached shaders.
+ /// It's an error to use the shader cache after disposal.
+ ///
+ public void Dispose()
+ {
+ foreach (List list in _cpPrograms.Values)
+ {
+ foreach (ComputeShader shader in list)
+ {
+ shader.HostProgram.Dispose();
+ shader.Shader?.HostShader.Dispose();
+ }
+ }
+
+ foreach (List list in _gpPrograms.Values)
+ {
+ foreach (GraphicsShader shader in list)
+ {
+ shader.HostProgram.Dispose();
+
+ foreach (CachedShader cachedShader in shader.Shaders)
+ {
+ cachedShader?.HostShader.Dispose();
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderDumper.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderDumper.cs
new file mode 100644
index 0000000000..3be75564bf
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/ShaderDumper.cs
@@ -0,0 +1,131 @@
+using Ryujinx.Graphics.Shader.Translation;
+using System;
+using System.IO;
+
+namespace Ryujinx.Graphics.Gpu.Shader
+{
+ ///
+ /// Shader dumper, writes binary shader code to disk.
+ ///
+ class ShaderDumper
+ {
+ private string _runtimeDir;
+ private string _dumpPath;
+ private int _dumpIndex;
+
+ public int CurrentDumpIndex => _dumpIndex;
+
+ public ShaderDumper()
+ {
+ _dumpIndex = 1;
+ }
+
+ ///
+ /// Dumps shader code to disk.
+ ///
+ /// Code to be dumped
+ /// True for compute shader code, false for graphics shader code
+ /// Output path for the shader code with header included
+ /// Output path for the shader code without header
+ public void Dump(Span code, bool compute, out string fullPath, out string codePath)
+ {
+ _dumpPath = GraphicsConfig.ShadersDumpPath;
+
+ if (string.IsNullOrWhiteSpace(_dumpPath))
+ {
+ fullPath = null;
+ codePath = null;
+
+ return;
+ }
+
+ string fileName = "Shader" + _dumpIndex.ToString("d4") + ".bin";
+
+ fullPath = Path.Combine(FullDir(), fileName);
+ codePath = Path.Combine(CodeDir(), fileName);
+
+ _dumpIndex++;
+
+ code = Translator.ExtractCode(code, compute, out int headerSize);
+
+ using (MemoryStream stream = new MemoryStream(code.ToArray()))
+ {
+ BinaryReader codeReader = new BinaryReader(stream);
+
+ using (FileStream fullFile = File.Create(fullPath))
+ using (FileStream codeFile = File.Create(codePath))
+ {
+ BinaryWriter fullWriter = new BinaryWriter(fullFile);
+ BinaryWriter codeWriter = new BinaryWriter(codeFile);
+
+ fullWriter.Write(codeReader.ReadBytes(headerSize));
+
+ byte[] temp = codeReader.ReadBytes(code.Length - headerSize);
+
+ fullWriter.Write(temp);
+ codeWriter.Write(temp);
+
+ // Align to meet nvdisasm requirements.
+ while (codeFile.Length % 0x20 != 0)
+ {
+ codeWriter.Write(0);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Returns the output directory for shader code with header.
+ ///
+ /// Directory path
+ private string FullDir()
+ {
+ return CreateAndReturn(Path.Combine(DumpDir(), "Full"));
+ }
+
+ ///
+ /// Returns the output directory for shader code without header.
+ ///
+ /// Directory path
+ private string CodeDir()
+ {
+ return CreateAndReturn(Path.Combine(DumpDir(), "Code"));
+ }
+
+ ///
+ /// Returns the full output directory for the current shader dump.
+ ///
+ /// Directory path
+ private string DumpDir()
+ {
+ if (string.IsNullOrEmpty(_runtimeDir))
+ {
+ int index = 1;
+
+ do
+ {
+ _runtimeDir = Path.Combine(_dumpPath, "Dumps" + index.ToString("d2"));
+
+ index++;
+ }
+ while (Directory.Exists(_runtimeDir));
+
+ Directory.CreateDirectory(_runtimeDir);
+ }
+
+ return _runtimeDir;
+ }
+
+ ///
+ /// Creates a new specified directory if needed.
+ ///
+ /// The directory to create
+ /// The same directory passed to the method
+ private static string CreateAndReturn(string dir)
+ {
+ Directory.CreateDirectory(dir);
+
+ return dir;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/State/BlendState.cs b/Ryujinx.Graphics.Gpu/State/BlendState.cs
new file mode 100644
index 0000000000..609bcc2937
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/BlendState.cs
@@ -0,0 +1,19 @@
+using Ryujinx.Graphics.GAL;
+
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// Color buffer blending parameters.
+ ///
+ struct BlendState
+ {
+ public Boolean32 SeparateAlpha;
+ public BlendOp ColorOp;
+ public BlendFactor ColorSrcFactor;
+ public BlendFactor ColorDstFactor;
+ public BlendOp AlphaOp;
+ public BlendFactor AlphaSrcFactor;
+ public BlendFactor AlphaDstFactor;
+ public uint Padding;
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/BlendStateCommon.cs b/Ryujinx.Graphics.Gpu/State/BlendStateCommon.cs
new file mode 100644
index 0000000000..a11cd5b10b
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/BlendStateCommon.cs
@@ -0,0 +1,19 @@
+using Ryujinx.Graphics.GAL;
+
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// Color buffer blending parameters, shared by all color buffers.
+ ///
+ struct BlendStateCommon
+ {
+ public Boolean32 SeparateAlpha;
+ public BlendOp ColorOp;
+ public BlendFactor ColorSrcFactor;
+ public BlendFactor ColorDstFactor;
+ public BlendOp AlphaOp;
+ public BlendFactor AlphaSrcFactor;
+ public uint Unknown0x1354;
+ public BlendFactor AlphaDstFactor;
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/Boolean32.cs b/Ryujinx.Graphics.Gpu/State/Boolean32.cs
new file mode 100644
index 0000000000..ff701d9e46
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/Boolean32.cs
@@ -0,0 +1,15 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// Boolean value, stored as a 32-bits integer in memory.
+ ///
+ struct Boolean32
+ {
+ private uint _value;
+
+ public static implicit operator bool(Boolean32 value)
+ {
+ return (value._value & 1) != 0;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/ClearColors.cs b/Ryujinx.Graphics.Gpu/State/ClearColors.cs
new file mode 100644
index 0000000000..b9f7b68476
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/ClearColors.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// Color buffer clear color.
+ ///
+ struct ClearColors
+ {
+ public float Red;
+ public float Green;
+ public float Blue;
+ public float Alpha;
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/Condition.cs b/Ryujinx.Graphics.Gpu/State/Condition.cs
new file mode 100644
index 0000000000..5afdbe3edb
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/Condition.cs
@@ -0,0 +1,14 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// Condition for conditional rendering.
+ ///
+ enum Condition
+ {
+ Never,
+ Always,
+ ResultNonZero,
+ Equal,
+ NotEqual
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/ConditionState.cs b/Ryujinx.Graphics.Gpu/State/ConditionState.cs
new file mode 100644
index 0000000000..bad266d9e6
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/ConditionState.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// Condition parameters for conditional rendering.
+ ///
+ struct ConditionState
+ {
+ public GpuVa Address;
+ public Condition Condition;
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/CopyBufferParams.cs b/Ryujinx.Graphics.Gpu/State/CopyBufferParams.cs
new file mode 100644
index 0000000000..7393c9692b
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/CopyBufferParams.cs
@@ -0,0 +1,15 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// Buffer to buffer copy parameters.
+ ///
+ struct CopyBufferParams
+ {
+ public GpuVa SrcAddress;
+ public GpuVa DstAddress;
+ public int SrcStride;
+ public int DstStride;
+ public int XCount;
+ public int YCount;
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/State/CopyBufferSwizzle.cs b/Ryujinx.Graphics.Gpu/State/CopyBufferSwizzle.cs
new file mode 100644
index 0000000000..5b3d7076d9
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/CopyBufferSwizzle.cs
@@ -0,0 +1,37 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// Buffer to buffer copy vector swizzle parameters.
+ ///
+ struct CopyBufferSwizzle
+ {
+ public uint Swizzle;
+
+ ///
+ /// Unpacks the size of each vector component of the copy.
+ ///
+ /// Vector component size
+ public int UnpackComponentSize()
+ {
+ return (int)((Swizzle >> 16) & 3) + 1;
+ }
+
+ ///
+ /// Unpacks the number of components of the source vector of the copy.
+ ///
+ /// Number of vector components
+ public int UnpackSrcComponentsCount()
+ {
+ return (int)((Swizzle >> 20) & 7) + 1;
+ }
+
+ ///
+ /// Unpacks the number of components of the destination vector of the copy.
+ ///
+ /// Number of vector components
+ public int UnpackDstComponentsCount()
+ {
+ return (int)((Swizzle >> 24) & 7) + 1;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/State/CopyBufferTexture.cs b/Ryujinx.Graphics.Gpu/State/CopyBufferTexture.cs
new file mode 100644
index 0000000000..98d113a354
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/CopyBufferTexture.cs
@@ -0,0 +1,16 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// Buffer to texture copy parameters.
+ ///
+ struct CopyBufferTexture
+ {
+ public MemoryLayout MemoryLayout;
+ public int Width;
+ public int Height;
+ public int Depth;
+ public int RegionZ;
+ public ushort RegionX;
+ public ushort RegionY;
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/State/CopyRegion.cs b/Ryujinx.Graphics.Gpu/State/CopyRegion.cs
new file mode 100644
index 0000000000..a256594b35
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/CopyRegion.cs
@@ -0,0 +1,17 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// Texture copy region.
+ ///
+ struct CopyRegion
+ {
+ public int DstX;
+ public int DstY;
+ public int DstWidth;
+ public int DstHeight;
+ public long SrcWidthRF;
+ public long SrcHeightRF;
+ public long SrcXF;
+ public long SrcYF;
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/State/CopyTexture.cs b/Ryujinx.Graphics.Gpu/State/CopyTexture.cs
new file mode 100644
index 0000000000..df723d0d12
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/CopyTexture.cs
@@ -0,0 +1,18 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// Texture to texture (with optional resizing) copy parameters.
+ ///
+ struct CopyTexture
+ {
+ public RtFormat Format;
+ public Boolean32 LinearLayout;
+ public MemoryLayout MemoryLayout;
+ public int Depth;
+ public int Layer;
+ public int Stride;
+ public int Width;
+ public int Height;
+ public GpuVa Address;
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/State/CopyTextureControl.cs b/Ryujinx.Graphics.Gpu/State/CopyTextureControl.cs
new file mode 100644
index 0000000000..cfc64fc45d
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/CopyTextureControl.cs
@@ -0,0 +1,15 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// Texture to texture copy control.
+ ///
+ struct CopyTextureControl
+ {
+ public uint Packed;
+
+ public bool UnpackLinearFilter()
+ {
+ return (Packed & (1u << 4)) != 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/State/DepthBiasState.cs b/Ryujinx.Graphics.Gpu/State/DepthBiasState.cs
new file mode 100644
index 0000000000..752706252b
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/DepthBiasState.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// Depth bias (also called polygon offset) parameters.
+ ///
+ struct DepthBiasState
+ {
+ public Boolean32 PointEnable;
+ public Boolean32 LineEnable;
+ public Boolean32 FillEnable;
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/FaceState.cs b/Ryujinx.Graphics.Gpu/State/FaceState.cs
new file mode 100644
index 0000000000..10dec3b91f
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/FaceState.cs
@@ -0,0 +1,14 @@
+using Ryujinx.Graphics.GAL;
+
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// Face culling and orientation parameters.
+ ///
+ struct FaceState
+ {
+ public Boolean32 CullEnable;
+ public FrontFace FrontFace;
+ public Face CullFace;
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/GpuState.cs b/Ryujinx.Graphics.Gpu/State/GpuState.cs
new file mode 100644
index 0000000000..c6052f4a65
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/GpuState.cs
@@ -0,0 +1,289 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// GPU state.
+ ///
+ class GpuState
+ {
+ private const int RegistersCount = 0xe00;
+
+ public delegate void MethodCallback(GpuState state, int argument);
+
+ private int[] _backingMemory;
+
+ ///
+ /// GPU register information.
+ ///
+ private struct Register
+ {
+ public MethodCallback Callback;
+
+ public MethodOffset BaseOffset;
+
+ public int Stride;
+ public int Count;
+
+ public bool Modified;
+ }
+
+ private Register[] _registers;
+
+ ///
+ /// Creates a new instance of the GPU state.
+ ///
+ public GpuState()
+ {
+ _backingMemory = new int[RegistersCount];
+
+ _registers = new Register[RegistersCount];
+
+ for (int index = 0; index < _registers.Length; index++)
+ {
+ _registers[index].BaseOffset = (MethodOffset)index;
+ _registers[index].Stride = 1;
+ _registers[index].Count = 1;
+ _registers[index].Modified = true;
+ }
+
+ foreach (var item in GpuStateTable.Table)
+ {
+ int totalRegs = item.Size * item.Count;
+
+ for (int regOffset = 0; regOffset < totalRegs; regOffset++)
+ {
+ int index = (int)item.Offset + regOffset;
+
+ _registers[index].BaseOffset = item.Offset;
+ _registers[index].Stride = item.Size;
+ _registers[index].Count = item.Count;
+ }
+ }
+
+ InitializeDefaultState();
+ }
+
+ ///
+ /// Calls a GPU method, using this state.
+ ///
+ /// The GPU method to be called
+ public void CallMethod(MethodParams meth)
+ {
+ Register register = _registers[meth.Method];
+
+ if (_backingMemory[meth.Method] != meth.Argument)
+ {
+ _registers[(int)register.BaseOffset].Modified = true;
+ }
+
+ _backingMemory[meth.Method] = meth.Argument;
+
+ register.Callback?.Invoke(this, meth.Argument);
+ }
+
+ ///
+ /// Reads data from a GPU register at the given offset.
+ ///
+ /// Offset to be read
+ /// Data at the register
+ public int Read(int offset)
+ {
+ return _backingMemory[offset];
+ }
+
+ ///
+ /// Writes an offset value at the uniform buffer offset register.
+ ///
+ /// The offset to be written
+ public void SetUniformBufferOffset(int offset)
+ {
+ _backingMemory[(int)MethodOffset.UniformBufferState + 3] = offset;
+ }
+
+ ///
+ /// Initializes registers with the default state.
+ ///
+ private void InitializeDefaultState()
+ {
+ // Depth ranges.
+ for (int index = 0; index < 8; index++)
+ {
+ _backingMemory[(int)MethodOffset.ViewportExtents + index * 4 + 2] = 0;
+ _backingMemory[(int)MethodOffset.ViewportExtents + index * 4 + 3] = 0x3F800000;
+ }
+
+ // Viewport transform enable.
+ _backingMemory[(int)MethodOffset.ViewportTransformEnable] = 1;
+
+ // Default front stencil mask.
+ _backingMemory[0x4e7] = 0xff;
+
+ // Default color mask.
+ for (int index = 0; index < Constants.TotalRenderTargets; index++)
+ {
+ _backingMemory[(int)MethodOffset.RtColorMask + index] = 0x1111;
+ }
+ }
+
+ ///
+ /// Registers a callback that is called every time a GPU method, or methods are called.
+ ///
+ /// Offset of the method
+ /// Word count of the methods region
+ /// Calllback to be called
+ public void RegisterCallback(MethodOffset offset, int count, MethodCallback callback)
+ {
+ for (int index = 0; index < count; index++)
+ {
+ _registers[(int)offset + index].Callback = callback;
+ }
+ }
+
+ ///
+ /// Registers a callback that is called every time a GPU method is called.
+ ///
+ /// Offset of the method
+ /// Calllback to be called
+ public void RegisterCallback(MethodOffset offset, MethodCallback callback)
+ {
+ _registers[(int)offset].Callback = callback;
+ }
+
+ ///
+ /// Checks if a given register has been modified since the last call to this method.
+ ///
+ /// Register offset
+ /// True if modified, false otherwise
+ public bool QueryModified(MethodOffset offset)
+ {
+ bool modified = _registers[(int)offset].Modified;
+
+ _registers[(int)offset].Modified = false;
+
+ return modified;
+ }
+
+ ///
+ /// Checks if two registers have been modified since the last call to this method.
+ ///
+ /// First register offset
+ /// Second register offset
+ /// True if any register was modified, false otherwise
+ public bool QueryModified(MethodOffset m1, MethodOffset m2)
+ {
+ bool modified = _registers[(int)m1].Modified ||
+ _registers[(int)m2].Modified;
+
+ _registers[(int)m1].Modified = false;
+ _registers[(int)m2].Modified = false;
+
+ return modified;
+ }
+
+ ///
+ /// Checks if two registers have been modified since the last call to this method.
+ ///
+ /// First register offset
+ /// Second register offset
+ /// Third register offset
+ /// True if any register was modified, false otherwise
+ public bool QueryModified(MethodOffset m1, MethodOffset m2, MethodOffset m3)
+ {
+ bool modified = _registers[(int)m1].Modified ||
+ _registers[(int)m2].Modified ||
+ _registers[(int)m3].Modified;
+
+ _registers[(int)m1].Modified = false;
+ _registers[(int)m2].Modified = false;
+ _registers[(int)m3].Modified = false;
+
+ return modified;
+ }
+
+ ///
+ /// Checks if two registers have been modified since the last call to this method.
+ ///
+ /// First register offset
+ /// Second register offset
+ /// Third register offset
+ /// Fourth register offset
+ /// True if any register was modified, false otherwise
+ public bool QueryModified(MethodOffset m1, MethodOffset m2, MethodOffset m3, MethodOffset m4)
+ {
+ bool modified = _registers[(int)m1].Modified ||
+ _registers[(int)m2].Modified ||
+ _registers[(int)m3].Modified ||
+ _registers[(int)m4].Modified;
+
+ _registers[(int)m1].Modified = false;
+ _registers[(int)m2].Modified = false;
+ _registers[(int)m3].Modified = false;
+ _registers[(int)m4].Modified = false;
+
+ return modified;
+ }
+
+ ///
+ /// Checks if two registers have been modified since the last call to this method.
+ ///
+ /// First register offset
+ /// Second register offset
+ /// Third register offset
+ /// Fourth register offset
+ /// Fifth register offset
+ /// True if any register was modified, false otherwise
+ public bool QueryModified(
+ MethodOffset m1,
+ MethodOffset m2,
+ MethodOffset m3,
+ MethodOffset m4,
+ MethodOffset m5)
+ {
+ bool modified = _registers[(int)m1].Modified ||
+ _registers[(int)m2].Modified ||
+ _registers[(int)m3].Modified ||
+ _registers[(int)m4].Modified ||
+ _registers[(int)m5].Modified;
+
+ _registers[(int)m1].Modified = false;
+ _registers[(int)m2].Modified = false;
+ _registers[(int)m3].Modified = false;
+ _registers[(int)m4].Modified = false;
+ _registers[(int)m5].Modified = false;
+
+ return modified;
+ }
+
+ ///
+ /// Gets indexed data from a given register offset.
+ ///
+ /// Type of the data
+ /// Register offset
+ /// Index for indexed data
+ /// The data at the specified location
+ public T Get(MethodOffset offset, int index) where T : struct
+ {
+ Register register = _registers[(int)offset];
+
+ if ((uint)index >= register.Count)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index));
+ }
+
+ return Get(offset + index * register.Stride);
+ }
+
+ ///
+ /// Gets data from a given register offset.
+ ///
+ /// Type of the data
+ /// Register offset
+ /// The data at the specified location
+ public T Get(MethodOffset offset) where T : struct
+ {
+ return MemoryMarshal.Cast(_backingMemory.AsSpan().Slice((int)offset))[0];
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/GpuStateTable.cs b/Ryujinx.Graphics.Gpu/State/GpuStateTable.cs
new file mode 100644
index 0000000000..db8d141e77
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/GpuStateTable.cs
@@ -0,0 +1,81 @@
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// GPU State item sizes table.
+ ///
+ static class GpuStateTable
+ {
+ ///
+ /// GPU state table item, with size for structures, and count for indexed state data.
+ ///
+ public struct TableItem
+ {
+ ///
+ /// Offset of the data.
+ ///
+ public MethodOffset Offset { get; }
+
+ ///
+ /// Size in words.
+ ///
+ public int Size { get; }
+
+ ///
+ /// Count for indexed data, or 1 if not indexed.
+ ///
+ public int Count { get; }
+
+ ///
+ /// Constructs the table item structure.
+ ///
+ /// Data offset
+ /// Data type
+ /// Data count, for indexed data
+ public TableItem(MethodOffset offset, Type type, int count)
+ {
+ int sizeInBytes = Marshal.SizeOf(type);
+
+ Debug.Assert((sizeInBytes & 3) == 0);
+
+ Offset = offset;
+ Size = sizeInBytes / 4;
+ Count = count;
+ }
+ }
+
+ ///
+ /// Table of GPU state structure sizes and counts.
+ ///
+ public static TableItem[] Table = new TableItem[]
+ {
+ new TableItem(MethodOffset.RtColorState, typeof(RtColorState), 8),
+ new TableItem(MethodOffset.ViewportTransform, typeof(ViewportTransform), 8),
+ new TableItem(MethodOffset.ViewportExtents, typeof(ViewportExtents), 8),
+ new TableItem(MethodOffset.VertexBufferDrawState, typeof(VertexBufferDrawState), 1),
+ new TableItem(MethodOffset.DepthBiasState, typeof(DepthBiasState), 1),
+ new TableItem(MethodOffset.StencilBackMasks, typeof(StencilBackMasks), 1),
+ new TableItem(MethodOffset.RtDepthStencilState, typeof(RtDepthStencilState), 1),
+ new TableItem(MethodOffset.VertexAttribState, typeof(VertexAttribState), 16),
+ new TableItem(MethodOffset.RtDepthStencilSize, typeof(Size3D), 1),
+ new TableItem(MethodOffset.BlendEnable, typeof(Boolean32), 8),
+ new TableItem(MethodOffset.StencilTestState, typeof(StencilTestState), 1),
+ new TableItem(MethodOffset.SamplerPoolState, typeof(PoolState), 1),
+ new TableItem(MethodOffset.TexturePoolState, typeof(PoolState), 1),
+ new TableItem(MethodOffset.StencilBackTestState, typeof(StencilBackTestState), 1),
+ new TableItem(MethodOffset.ShaderBaseAddress, typeof(GpuVa), 1),
+ new TableItem(MethodOffset.PrimitiveRestartState, typeof(PrimitiveRestartState), 1),
+ new TableItem(MethodOffset.IndexBufferState, typeof(IndexBufferState), 1),
+ new TableItem(MethodOffset.VertexBufferInstanced, typeof(Boolean32), 16),
+ new TableItem(MethodOffset.FaceState, typeof(FaceState), 1),
+ new TableItem(MethodOffset.RtColorMask, typeof(RtColorMask), 8),
+ new TableItem(MethodOffset.VertexBufferState, typeof(VertexBufferState), 16),
+ new TableItem(MethodOffset.BlendState, typeof(BlendState), 8),
+ new TableItem(MethodOffset.VertexBufferEndAddress, typeof(GpuVa), 16),
+ new TableItem(MethodOffset.ShaderState, typeof(ShaderState), 6),
+ };
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/State/GpuVa.cs b/Ryujinx.Graphics.Gpu/State/GpuVa.cs
new file mode 100644
index 0000000000..76a2fddfd2
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/GpuVa.cs
@@ -0,0 +1,20 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// Split GPU virtual address.
+ ///
+ struct GpuVa
+ {
+ public uint High;
+ public uint Low;
+
+ ///
+ /// Packs the split address into a 64-bits address value.
+ ///
+ /// The 64-bits address value
+ public ulong Pack()
+ {
+ return Low | ((ulong)High << 32);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/IndexBufferState.cs b/Ryujinx.Graphics.Gpu/State/IndexBufferState.cs
new file mode 100644
index 0000000000..b7868bb9fe
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/IndexBufferState.cs
@@ -0,0 +1,17 @@
+using Ryujinx.Graphics.GAL;
+
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// GPU index buffer state.
+ /// This is used on indexed draws.
+ ///
+ struct IndexBufferState
+ {
+ public GpuVa Address;
+ public GpuVa EndAddress;
+ public IndexType Type;
+ public int First;
+ public int Count;
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/Inline2MemoryParams.cs b/Ryujinx.Graphics.Gpu/State/Inline2MemoryParams.cs
new file mode 100644
index 0000000000..45555be20b
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/Inline2MemoryParams.cs
@@ -0,0 +1,20 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// Inline-to-memory copy parameters.
+ ///
+ struct Inline2MemoryParams
+ {
+ public int LineLengthIn;
+ public int LineCount;
+ public GpuVa DstAddress;
+ public int DstStride;
+ public MemoryLayout DstMemoryLayout;
+ public int DstWidth;
+ public int DstHeight;
+ public int DstDepth;
+ public int DstZ;
+ public int DstX;
+ public int DstY;
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/State/MemoryLayout.cs b/Ryujinx.Graphics.Gpu/State/MemoryLayout.cs
new file mode 100644
index 0000000000..9b72b097c9
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/MemoryLayout.cs
@@ -0,0 +1,35 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// Memory layout parameters, for block linear textures.
+ ///
+ struct MemoryLayout
+ {
+ public uint Packed;
+
+ public int UnpackGobBlocksInX()
+ {
+ return 1 << (int)(Packed & 0xf);
+ }
+
+ public int UnpackGobBlocksInY()
+ {
+ return 1 << (int)((Packed >> 4) & 0xf);
+ }
+
+ public int UnpackGobBlocksInZ()
+ {
+ return 1 << (int)((Packed >> 8) & 0xf);
+ }
+
+ public bool UnpackIsLinear()
+ {
+ return (Packed & 0x1000) != 0;
+ }
+
+ public bool UnpackIsTarget3D()
+ {
+ return (Packed & 0x10000) != 0;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/MethodOffset.cs b/Ryujinx.Graphics.Gpu/State/MethodOffset.cs
new file mode 100644
index 0000000000..904cf8ff49
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/MethodOffset.cs
@@ -0,0 +1,92 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// GPU method offset.
+ ///
+ ///
+ /// This is indexed in 32 bits word.
+ ///
+ enum MethodOffset
+ {
+ I2mParams = 0x60,
+ LaunchDma = 0x6c,
+ LoadInlineData = 0x6d,
+ CopyDstTexture = 0x80,
+ CopySrcTexture = 0x8c,
+ DispatchParamsAddress = 0xad,
+ Dispatch = 0xaf,
+ CopyBuffer = 0xc0,
+ CopyBufferParams = 0x100,
+ CopyBufferSwizzle = 0x1c2,
+ CopyBufferDstTexture = 0x1c3,
+ CopyBufferSrcTexture = 0x1ca,
+ RtColorState = 0x200,
+ CopyTextureControl = 0x223,
+ CopyRegion = 0x22c,
+ CopyTexture = 0x237,
+ ViewportTransform = 0x280,
+ ViewportExtents = 0x300,
+ VertexBufferDrawState = 0x35d,
+ DepthMode = 0x35f,
+ ClearColors = 0x360,
+ ClearDepthValue = 0x364,
+ ClearStencilValue = 0x368,
+ DepthBiasState = 0x370,
+ TextureBarrier = 0x378,
+ StencilBackMasks = 0x3d5,
+ InvalidateTextures = 0x3dd,
+ TextureBarrierTiled = 0x3df,
+ RtColorMaskShared = 0x3e4,
+ RtDepthStencilState = 0x3f8,
+ VertexAttribState = 0x458,
+ RtControl = 0x487,
+ RtDepthStencilSize = 0x48a,
+ SamplerIndex = 0x48d,
+ DepthTestEnable = 0x4b3,
+ BlendIndependent = 0x4b9,
+ DepthWriteEnable = 0x4ba,
+ DepthTestFunc = 0x4c3,
+ BlendStateCommon = 0x4cf,
+ BlendEnableCommon = 0x4d7,
+ BlendEnable = 0x4d8,
+ StencilTestState = 0x4e0,
+ YControl = 0x4eb,
+ FirstVertex = 0x50d,
+ FirstInstance = 0x50e,
+ ResetCounter = 0x54c,
+ RtDepthStencilEnable = 0x54e,
+ ConditionState = 0x554,
+ SamplerPoolState = 0x557,
+ DepthBiasFactor = 0x55b,
+ TexturePoolState = 0x55d,
+ StencilBackTestState = 0x565,
+ DepthBiasUnits = 0x56f,
+ RtMsaaMode = 0x574,
+ ShaderBaseAddress = 0x582,
+ DrawEnd = 0x585,
+ DrawBegin = 0x586,
+ PrimitiveRestartState = 0x591,
+ IndexBufferState = 0x5f2,
+ IndexBufferCount = 0x5f8,
+ DepthBiasClamp = 0x61f,
+ VertexBufferInstanced = 0x620,
+ FaceState = 0x646,
+ ViewportTransformEnable = 0x64b,
+ Clear = 0x674,
+ RtColorMask = 0x680,
+ ReportState = 0x6c0,
+ Report = 0x6c3,
+ VertexBufferState = 0x700,
+ BlendState = 0x780,
+ VertexBufferEndAddress = 0x7c0,
+ ShaderState = 0x800,
+ UniformBufferState = 0x8e0,
+ UniformBufferUpdateData = 0x8e4,
+ UniformBufferBindVertex = 0x904,
+ UniformBufferBindTessControl = 0x90c,
+ UniformBufferBindTessEvaluation = 0x914,
+ UniformBufferBindGeometry = 0x91c,
+ UniformBufferBindFragment = 0x924,
+ TextureBufferIndex = 0x982
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/State/PoolState.cs b/Ryujinx.Graphics.Gpu/State/PoolState.cs
new file mode 100644
index 0000000000..eeb5691867
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/PoolState.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// Texture or sampler pool state.
+ ///
+ struct PoolState
+ {
+ public GpuVa Address;
+ public int MaximumId;
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/State/PrimitiveRestartState.cs b/Ryujinx.Graphics.Gpu/State/PrimitiveRestartState.cs
new file mode 100644
index 0000000000..96795083ee
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/PrimitiveRestartState.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// Primitive restart state.
+ ///
+ struct PrimitiveRestartState
+ {
+ public Boolean32 Enable;
+ public int Index;
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/PrimitiveTopology.cs b/Ryujinx.Graphics.Gpu/State/PrimitiveTopology.cs
new file mode 100644
index 0000000000..340991c2e3
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/PrimitiveTopology.cs
@@ -0,0 +1,57 @@
+using Ryujinx.Graphics.GAL;
+
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// Draw primitive type.
+ ///
+ enum PrimitiveType
+ {
+ Points,
+ Lines,
+ LineLoop,
+ LineStrip,
+ Triangles,
+ TriangleStrip,
+ TriangleFan,
+ Quads,
+ QuadStrip,
+ Polygon,
+ LinesAdjacency,
+ LineStripAdjacency,
+ TrianglesAdjacency,
+ TriangleStripAdjacency,
+ Patches
+ }
+
+ static class PrimitiveTypeConverter
+ {
+ ///
+ /// Converts the primitive type into something that can be used with the host API.
+ ///
+ /// The primitive type to convert
+ /// A host compatible enum value
+ public static PrimitiveTopology Convert(this PrimitiveType type)
+ {
+ return type switch
+ {
+ PrimitiveType.Points => PrimitiveTopology.Points,
+ PrimitiveType.Lines => PrimitiveTopology.Lines,
+ PrimitiveType.LineLoop => PrimitiveTopology.LineLoop,
+ PrimitiveType.LineStrip => PrimitiveTopology.LineStrip,
+ PrimitiveType.Triangles => PrimitiveTopology.Triangles,
+ PrimitiveType.TriangleStrip => PrimitiveTopology.TriangleStrip,
+ PrimitiveType.TriangleFan => PrimitiveTopology.TriangleFan,
+ PrimitiveType.Quads => PrimitiveTopology.Quads,
+ PrimitiveType.QuadStrip => PrimitiveTopology.QuadStrip,
+ PrimitiveType.Polygon => PrimitiveTopology.Polygon,
+ PrimitiveType.LinesAdjacency => PrimitiveTopology.LinesAdjacency,
+ PrimitiveType.LineStripAdjacency => PrimitiveTopology.LineStripAdjacency,
+ PrimitiveType.TrianglesAdjacency => PrimitiveTopology.TrianglesAdjacency,
+ PrimitiveType.TriangleStripAdjacency => PrimitiveTopology.TriangleStripAdjacency,
+ PrimitiveType.Patches => PrimitiveTopology.Patches,
+ _ => PrimitiveTopology.Triangles
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/State/ReportCounterType.cs b/Ryujinx.Graphics.Gpu/State/ReportCounterType.cs
new file mode 100644
index 0000000000..cface55d03
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/ReportCounterType.cs
@@ -0,0 +1,28 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// Counter type for GPU counter reporting.
+ ///
+ enum ReportCounterType
+ {
+ Zero = 0,
+ InputVertices = 1,
+ InputPrimitives = 3,
+ VertexShaderInvocations = 5,
+ GeometryShaderInvocations = 7,
+ GeometryShaderPrimitives = 9,
+ TransformFeedbackPrimitivesWritten = 0xb,
+ ClipperInputPrimitives = 0xf,
+ ClipperOutputPrimitives = 0x11,
+ PrimitivesGenerated = 0x12,
+ FragmentShaderInvocations = 0x13,
+ SamplesPassed = 0x15,
+ TessControlShaderInvocations = 0x1b,
+ TessEvaluationShaderInvocations = 0x1d,
+ TessEvaluationShaderPrimitives = 0x1f,
+ ZcullStats0 = 0x2a,
+ ZcullStats1 = 0x2c,
+ ZcullStats2 = 0x2e,
+ ZcullStats3 = 0x30
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/State/ReportMode.cs b/Ryujinx.Graphics.Gpu/State/ReportMode.cs
new file mode 100644
index 0000000000..e557f4ca4a
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/ReportMode.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// GPU counter report mode.
+ ///
+ enum ReportMode
+ {
+ Semaphore = 0,
+ Counter = 2
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/State/ReportState.cs b/Ryujinx.Graphics.Gpu/State/ReportState.cs
new file mode 100644
index 0000000000..7430ea3161
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/ReportState.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// GPU counter report state.
+ ///
+ struct ReportState
+ {
+ public GpuVa Address;
+ public int Payload;
+ public uint Control;
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/ResetCounterType.cs b/Ryujinx.Graphics.Gpu/State/ResetCounterType.cs
new file mode 100644
index 0000000000..aaf575e1b4
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/ResetCounterType.cs
@@ -0,0 +1,24 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// Counter type for GPU counter reset.
+ ///
+ enum ResetCounterType
+ {
+ SamplesPassed = 1,
+ ZcullStats = 2,
+ TransformFeedbackPrimitivesWritten = 0x10,
+ InputVertices = 0x12,
+ InputPrimitives = 0x13,
+ VertexShaderInvocations = 0x15,
+ TessControlShaderInvocations = 0x16,
+ TessEvaluationShaderInvocations = 0x17,
+ TessEvaluationShaderPrimitives = 0x18,
+ GeometryShaderInvocations = 0x1a,
+ GeometryShaderPrimitives = 0x1b,
+ ClipperInputPrimitives = 0x1c,
+ ClipperOutputPrimitives = 0x1d,
+ FragmentShaderInvocations = 0x1e,
+ PrimitivesGenerated = 0x1f
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/State/RtColorMask.cs b/Ryujinx.Graphics.Gpu/State/RtColorMask.cs
new file mode 100644
index 0000000000..4aa5c33132
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/RtColorMask.cs
@@ -0,0 +1,47 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// Render target color buffer mask.
+ /// This defines which color channels are written to the color buffer.
+ ///
+ struct RtColorMask
+ {
+ public uint Packed;
+
+ ///
+ /// Unpacks red channel enable.
+ ///
+ /// True to write the new red channel color, false to keep the old value
+ public bool UnpackRed()
+ {
+ return (Packed & 0x1) != 0;
+ }
+
+ ///
+ /// Unpacks green channel enable.
+ ///
+ /// True to write the new green channel color, false to keep the old value
+ public bool UnpackGreen()
+ {
+ return (Packed & 0x10) != 0;
+ }
+
+ ///
+ /// Unpacks blue channel enable.
+ ///
+ /// True to write the new blue channel color, false to keep the old value
+ public bool UnpackBlue()
+ {
+ return (Packed & 0x100) != 0;
+ }
+
+ ///
+ /// Unpacks alpha channel enable.
+ ///
+ /// True to write the new alpha channel color, false to keep the old value
+ public bool UnpackAlpha()
+ {
+ return (Packed & 0x1000) != 0;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/RtColorState.cs b/Ryujinx.Graphics.Gpu/State/RtColorState.cs
new file mode 100644
index 0000000000..9261526a25
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/RtColorState.cs
@@ -0,0 +1,24 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// Render target color buffer state.
+ ///
+ struct RtColorState
+ {
+ public GpuVa Address;
+ public int WidthOrStride;
+ public int Height;
+ public RtFormat Format;
+ public MemoryLayout MemoryLayout;
+ public int Depth;
+ public int LayerSize;
+ public int BaseLayer;
+ public int Unknown0x24;
+ public int Padding0;
+ public int Padding1;
+ public int Padding2;
+ public int Padding3;
+ public int Padding4;
+ public int Padding5;
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/RtControl.cs b/Ryujinx.Graphics.Gpu/State/RtControl.cs
new file mode 100644
index 0000000000..2e28660bf6
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/RtControl.cs
@@ -0,0 +1,29 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// Render target draw buffers control.
+ ///
+ struct RtControl
+ {
+ public uint Packed;
+
+ ///
+ /// Unpacks the number of active draw buffers.
+ ///
+ /// Number of active draw buffers
+ public int UnpackCount()
+ {
+ return (int)(Packed & 0xf);
+ }
+
+ ///
+ /// Unpacks the color attachment index for a given draw buffer.
+ ///
+ /// Index of the draw buffer
+ /// Attachment index
+ public int UnpackPermutationIndex(int index)
+ {
+ return (int)((Packed >> (4 + index * 3)) & 7);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/RtDepthStencilState.cs b/Ryujinx.Graphics.Gpu/State/RtDepthStencilState.cs
new file mode 100644
index 0000000000..abc28fca14
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/RtDepthStencilState.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// Render target depth-stencil buffer state.
+ ///
+ struct RtDepthStencilState
+ {
+ public GpuVa Address;
+ public RtFormat Format;
+ public MemoryLayout MemoryLayout;
+ public int LayerSize;
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/RtFormat.cs b/Ryujinx.Graphics.Gpu/State/RtFormat.cs
new file mode 100644
index 0000000000..ffd2492b64
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/RtFormat.cs
@@ -0,0 +1,148 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.Image;
+
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// Render target buffer texture format.
+ ///
+ enum RtFormat
+ {
+ D32Float = 0xa,
+ D16Unorm = 0x13,
+ D24UnormS8Uint = 0x14,
+ D24Unorm = 0x15,
+ S8UintD24Unorm = 0x16,
+ S8Uint = 0x17,
+ D32FloatS8Uint = 0x19,
+ R32G32B32A32Float = 0xc0,
+ R32G32B32A32Sint = 0xc1,
+ R32G32B32A32Uint = 0xc2,
+ R32G32B32X32Float = 0xc3,
+ R32G32B32X32Sint = 0xc4,
+ R32G32B32X32Uint = 0xc5,
+ R16G16B16X16Unorm = 0xc6,
+ R16G16B16X16Snorm = 0xc7,
+ R16G16B16X16Sint = 0xc8,
+ R16G16B16X16Uint = 0xc9,
+ R16G16B16A16Float = 0xca,
+ R32G32Float = 0xcb,
+ R32G32Sint = 0xcc,
+ R32G32Uint = 0xcd,
+ R16G16B16X16Float = 0xce,
+ B8G8R8A8Unorm = 0xcf,
+ B8G8R8A8Srgb = 0xd0,
+ R10G10B10A2Unorm = 0xd1,
+ R10G10B10A2Uint = 0xd2,
+ R8G8B8A8Unorm = 0xd5,
+ R8G8B8A8Srgb = 0xd6,
+ R8G8B8X8Snorm = 0xd7,
+ R8G8B8X8Sint = 0xd8,
+ R8G8B8X8Uint = 0xd9,
+ R16G16Unorm = 0xda,
+ R16G16Snorm = 0xdb,
+ R16G16Sint = 0xdc,
+ R16G16Uint = 0xdd,
+ R16G16Float = 0xde,
+ R11G11B10Float = 0xe0,
+ R32Sint = 0xe3,
+ R32Uint = 0xe4,
+ R32Float = 0xe5,
+ B8G8R8X8Unorm = 0xe6,
+ B8G8R8X8Srgb = 0xe7,
+ B5G6R5Unorm = 0xe8,
+ B5G5R5A1Unorm = 0xe9,
+ R8G8Unorm = 0xea,
+ R8G8Snorm = 0xeb,
+ R8G8Sint = 0xec,
+ R8G8Uint = 0xed,
+ R16Unorm = 0xee,
+ R16Snorm = 0xef,
+ R16Sint = 0xf0,
+ R16Uint = 0xf1,
+ R16Float = 0xf2,
+ R8Unorm = 0xf3,
+ R8Snorm = 0xf4,
+ R8Sint = 0xf5,
+ R8Uint = 0xf6,
+ B5G5R5X1Unorm = 0xf8,
+ R8G8B8X8Unorm = 0xf9,
+ R8G8B8X8Srgb = 0xfa
+ }
+
+ static class RtFormatConverter
+ {
+ ///
+ /// Converts the render target buffer texture format to a host compatible format.
+ ///
+ /// Render target format
+ /// Host compatible format enum value
+ public static FormatInfo Convert(this RtFormat format)
+ {
+ return format switch
+ {
+ RtFormat.D32Float => new FormatInfo(Format.D32Float, 1, 1, 4),
+ RtFormat.D16Unorm => new FormatInfo(Format.D16Unorm, 1, 1, 2),
+ RtFormat.D24UnormS8Uint => new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4),
+ RtFormat.D24Unorm => new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4),
+ RtFormat.S8UintD24Unorm => new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4),
+ RtFormat.S8Uint => new FormatInfo(Format.S8Uint, 1, 1, 1),
+ RtFormat.D32FloatS8Uint => new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8),
+ RtFormat.R32G32B32A32Float => new FormatInfo(Format.R32G32B32A32Float, 1, 1, 16),
+ RtFormat.R32G32B32A32Sint => new FormatInfo(Format.R32G32B32A32Sint, 1, 1, 16),
+ RtFormat.R32G32B32A32Uint => new FormatInfo(Format.R32G32B32A32Uint, 1, 1, 16),
+ RtFormat.R32G32B32X32Float => new FormatInfo(Format.R32G32B32A32Float, 1, 1, 16),
+ RtFormat.R32G32B32X32Sint => new FormatInfo(Format.R32G32B32A32Sint, 1, 1, 16),
+ RtFormat.R32G32B32X32Uint => new FormatInfo(Format.R32G32B32A32Uint, 1, 1, 16),
+ RtFormat.R16G16B16X16Unorm => new FormatInfo(Format.R16G16B16A16Unorm, 1, 1, 8),
+ RtFormat.R16G16B16X16Snorm => new FormatInfo(Format.R16G16B16A16Snorm, 1, 1, 8),
+ RtFormat.R16G16B16X16Sint => new FormatInfo(Format.R16G16B16A16Sint, 1, 1, 8),
+ RtFormat.R16G16B16X16Uint => new FormatInfo(Format.R16G16B16A16Uint, 1, 1, 8),
+ RtFormat.R16G16B16A16Float => new FormatInfo(Format.R16G16B16A16Float, 1, 1, 8),
+ RtFormat.R32G32Float => new FormatInfo(Format.R32G32Float, 1, 1, 8),
+ RtFormat.R32G32Sint => new FormatInfo(Format.R32G32Sint, 1, 1, 8),
+ RtFormat.R32G32Uint => new FormatInfo(Format.R32G32Uint, 1, 1, 8),
+ RtFormat.R16G16B16X16Float => new FormatInfo(Format.R16G16B16A16Float, 1, 1, 8),
+ RtFormat.B8G8R8A8Unorm => new FormatInfo(Format.B8G8R8A8Unorm, 1, 1, 4),
+ RtFormat.B8G8R8A8Srgb => new FormatInfo(Format.B8G8R8A8Srgb, 1, 1, 4),
+ RtFormat.R10G10B10A2Unorm => new FormatInfo(Format.R10G10B10A2Unorm, 1, 1, 4),
+ RtFormat.R10G10B10A2Uint => new FormatInfo(Format.R10G10B10A2Uint, 1, 1, 4),
+ RtFormat.R8G8B8A8Unorm => new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4),
+ RtFormat.R8G8B8A8Srgb => new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4),
+ RtFormat.R8G8B8X8Snorm => new FormatInfo(Format.R8G8B8A8Snorm, 1, 1, 4),
+ RtFormat.R8G8B8X8Sint => new FormatInfo(Format.R8G8B8A8Sint, 1, 1, 4),
+ RtFormat.R8G8B8X8Uint => new FormatInfo(Format.R8G8B8A8Uint, 1, 1, 4),
+ RtFormat.R16G16Unorm => new FormatInfo(Format.R16G16Unorm, 1, 1, 4),
+ RtFormat.R16G16Snorm => new FormatInfo(Format.R16G16Snorm, 1, 1, 4),
+ RtFormat.R16G16Sint => new FormatInfo(Format.R16G16Sint, 1, 1, 4),
+ RtFormat.R16G16Uint => new FormatInfo(Format.R16G16Uint, 1, 1, 4),
+ RtFormat.R16G16Float => new FormatInfo(Format.R16G16Float, 1, 1, 4),
+ RtFormat.R11G11B10Float => new FormatInfo(Format.R11G11B10Float, 1, 1, 4),
+ RtFormat.R32Sint => new FormatInfo(Format.R32Sint, 1, 1, 4),
+ RtFormat.R32Uint => new FormatInfo(Format.R32Uint, 1, 1, 4),
+ RtFormat.R32Float => new FormatInfo(Format.R32Float, 1, 1, 4),
+ RtFormat.B8G8R8X8Unorm => new FormatInfo(Format.B8G8R8A8Unorm, 1, 1, 4),
+ RtFormat.B8G8R8X8Srgb => new FormatInfo(Format.B8G8R8A8Srgb, 1, 1, 4),
+ RtFormat.B5G6R5Unorm => new FormatInfo(Format.B5G6R5Unorm, 1, 1, 2),
+ RtFormat.B5G5R5A1Unorm => new FormatInfo(Format.B5G5R5A1Unorm, 1, 1, 2),
+ RtFormat.R8G8Unorm => new FormatInfo(Format.R8G8Unorm, 1, 1, 2),
+ RtFormat.R8G8Snorm => new FormatInfo(Format.R8G8Snorm, 1, 1, 2),
+ RtFormat.R8G8Sint => new FormatInfo(Format.R8G8Sint, 1, 1, 2),
+ RtFormat.R8G8Uint => new FormatInfo(Format.R8G8Uint, 1, 1, 2),
+ RtFormat.R16Unorm => new FormatInfo(Format.R16Unorm, 1, 1, 2),
+ RtFormat.R16Snorm => new FormatInfo(Format.R16Snorm, 1, 1, 2),
+ RtFormat.R16Sint => new FormatInfo(Format.R16Sint, 1, 1, 2),
+ RtFormat.R16Uint => new FormatInfo(Format.R16Uint, 1, 1, 2),
+ RtFormat.R16Float => new FormatInfo(Format.R16Float, 1, 1, 2),
+ RtFormat.R8Unorm => new FormatInfo(Format.R8Unorm, 1, 1, 1),
+ RtFormat.R8Snorm => new FormatInfo(Format.R8Snorm, 1, 1, 1),
+ RtFormat.R8Sint => new FormatInfo(Format.R8Sint, 1, 1, 1),
+ RtFormat.R8Uint => new FormatInfo(Format.R8Uint, 1, 1, 1),
+ RtFormat.B5G5R5X1Unorm => new FormatInfo(Format.B5G5R5X1Unorm, 1, 1, 2),
+ RtFormat.R8G8B8X8Unorm => new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4),
+ RtFormat.R8G8B8X8Srgb => new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4),
+ _ => FormatInfo.Default
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/State/SamplerIndex.cs b/Ryujinx.Graphics.Gpu/State/SamplerIndex.cs
new file mode 100644
index 0000000000..c2aaff43f1
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/SamplerIndex.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// Sampler pool indexing mode.
+ ///
+ enum SamplerIndex
+ {
+ Independently = 0,
+ ViaHeaderIndex = 1
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/State/ShaderState.cs b/Ryujinx.Graphics.Gpu/State/ShaderState.cs
new file mode 100644
index 0000000000..62c7ed4dab
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/ShaderState.cs
@@ -0,0 +1,35 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// Graphics shader stage state.
+ ///
+ struct ShaderState
+ {
+ public uint Control;
+ public uint Offset;
+ public uint Unknown0x8;
+ public int MaxRegisters;
+ public ShaderType Type;
+ public uint Unknown0x14;
+ public uint Unknown0x18;
+ public uint Unknown0x1c;
+ public uint Unknown0x20;
+ public uint Unknown0x24;
+ public uint Unknown0x28;
+ public uint Unknown0x2c;
+ public uint Unknown0x30;
+ public uint Unknown0x34;
+ public uint Unknown0x38;
+ public uint Unknown0x3c;
+
+ ///
+ /// Unpacks shader enable information.
+ /// Must be ignored for vertex shaders, those are always enabled.
+ ///
+ /// True if the stage is enabled, false otherwise
+ public bool UnpackEnable()
+ {
+ return (Control & 1) != 0;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/ShaderType.cs b/Ryujinx.Graphics.Gpu/State/ShaderType.cs
new file mode 100644
index 0000000000..58506821de
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/ShaderType.cs
@@ -0,0 +1,14 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// Shader stage name.
+ ///
+ enum ShaderType
+ {
+ Vertex,
+ TessellationControl,
+ TessellationEvaluation,
+ Geometry,
+ Fragment
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/Size3D.cs b/Ryujinx.Graphics.Gpu/State/Size3D.cs
new file mode 100644
index 0000000000..0fa3314a3d
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/Size3D.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// 3D, 2D or 1D texture size.
+ ///
+ struct Size3D
+ {
+ public int Width;
+ public int Height;
+ public int Depth;
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/StencilBackMasks.cs b/Ryujinx.Graphics.Gpu/State/StencilBackMasks.cs
new file mode 100644
index 0000000000..f9ee613f4c
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/StencilBackMasks.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// Stencil test masks for back tests.
+ ///
+ struct StencilBackMasks
+ {
+ public int FuncRef;
+ public int Mask;
+ public int FuncMask;
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/StencilBackTestState.cs b/Ryujinx.Graphics.Gpu/State/StencilBackTestState.cs
new file mode 100644
index 0000000000..0169b5e777
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/StencilBackTestState.cs
@@ -0,0 +1,16 @@
+using Ryujinx.Graphics.GAL;
+
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// Stencil back test state.
+ ///
+ struct StencilBackTestState
+ {
+ public Boolean32 TwoSided;
+ public StencilOp BackSFail;
+ public StencilOp BackDpFail;
+ public StencilOp BackDpPass;
+ public CompareOp BackFunc;
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/StencilTestState.cs b/Ryujinx.Graphics.Gpu/State/StencilTestState.cs
new file mode 100644
index 0000000000..a50de6e6c6
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/StencilTestState.cs
@@ -0,0 +1,19 @@
+using Ryujinx.Graphics.GAL;
+
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// Stencil front test state and masks.
+ ///
+ struct StencilTestState
+ {
+ public Boolean32 Enable;
+ public StencilOp FrontSFail;
+ public StencilOp FrontDpFail;
+ public StencilOp FrontDpPass;
+ public CompareOp FrontFunc;
+ public int FrontFuncRef;
+ public int FrontFuncMask;
+ public int FrontMask;
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/UniformBufferState.cs b/Ryujinx.Graphics.Gpu/State/UniformBufferState.cs
new file mode 100644
index 0000000000..15fe556e9d
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/UniformBufferState.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// Uniform buffer state for the uniform buffer currently being modified.
+ ///
+ struct UniformBufferState
+ {
+ public int Size;
+ public GpuVa Address;
+ public int Offset;
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/VertexAttribState.cs b/Ryujinx.Graphics.Gpu/State/VertexAttribState.cs
new file mode 100644
index 0000000000..897da79753
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/VertexAttribState.cs
@@ -0,0 +1,37 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// Vertex buffer attribute state.
+ ///
+ struct VertexAttribState
+ {
+ public uint Attribute;
+
+ ///
+ /// Unpacks the index of the vertex buffer this attribute belongs to.
+ ///
+ /// Vertex buffer index
+ public int UnpackBufferIndex()
+ {
+ return (int)(Attribute & 0x1f);
+ }
+
+ ///
+ /// Unpacks the offset, in bytes, of the attribute on the vertex buffer.
+ ///
+ /// Attribute offset in bytes
+ public int UnpackOffset()
+ {
+ return (int)((Attribute >> 7) & 0x3fff);
+ }
+
+ ///
+ /// Unpacks the Maxwell attribute format integer.
+ ///
+ /// Attribute format integer
+ public uint UnpackFormat()
+ {
+ return Attribute & 0x3fe00000;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/VertexBufferDrawState.cs b/Ryujinx.Graphics.Gpu/State/VertexBufferDrawState.cs
new file mode 100644
index 0000000000..b1c9a087aa
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/VertexBufferDrawState.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// Draw state for non-indexed draws.
+ ///
+ struct VertexBufferDrawState
+ {
+ public int First;
+ public int Count;
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/VertexBufferState.cs b/Ryujinx.Graphics.Gpu/State/VertexBufferState.cs
new file mode 100644
index 0000000000..e514f2a90a
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/VertexBufferState.cs
@@ -0,0 +1,30 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// Vertex buffer state.
+ ///
+ struct VertexBufferState
+ {
+ public uint Control;
+ public GpuVa Address;
+ public int Divisor;
+
+ ///
+ /// Vertex buffer stride, defined as the number of bytes occupied by each vertex in memory.
+ ///
+ /// Vertex buffer stride
+ public int UnpackStride()
+ {
+ return (int)(Control & 0xfff);
+ }
+
+ ///
+ /// Vertex buffer enable.
+ ///
+ /// True if the vertex buffer is enabled, false otherwise
+ public bool UnpackEnable()
+ {
+ return (Control & (1 << 12)) != 0;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/ViewportExtents.cs b/Ryujinx.Graphics.Gpu/State/ViewportExtents.cs
new file mode 100644
index 0000000000..6675c909b9
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/ViewportExtents.cs
@@ -0,0 +1,15 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// Viewport extents for viewport clipping, also includes depth range.
+ ///
+ struct ViewportExtents
+ {
+ public ushort X;
+ public ushort Width;
+ public ushort Y;
+ public ushort Height;
+ public float DepthNear;
+ public float DepthFar;
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/ViewportTransform.cs b/Ryujinx.Graphics.Gpu/State/ViewportTransform.cs
new file mode 100644
index 0000000000..c7db311de9
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/ViewportTransform.cs
@@ -0,0 +1,55 @@
+using Ryujinx.Graphics.GAL;
+
+namespace Ryujinx.Graphics.Gpu.State
+{
+ ///
+ /// Viewport transform parameters, for viewport transformation.
+ ///
+ struct ViewportTransform
+ {
+ public float ScaleX;
+ public float ScaleY;
+ public float ScaleZ;
+ public float TranslateX;
+ public float TranslateY;
+ public float TranslateZ;
+ public uint Swizzle;
+ public uint SubpixelPrecisionBias;
+
+ ///
+ /// Unpacks viewport swizzle of the position X component.
+ ///
+ /// Swizzle enum value
+ public ViewportSwizzle UnpackSwizzleX()
+ {
+ return (ViewportSwizzle)(Swizzle & 7);
+ }
+
+ ///
+ /// Unpacks viewport swizzle of the position Y component.
+ ///
+ /// Swizzle enum value
+ public ViewportSwizzle UnpackSwizzleY()
+ {
+ return (ViewportSwizzle)((Swizzle >> 4) & 7);
+ }
+
+ ///
+ /// Unpacks viewport swizzle of the position Z component.
+ ///
+ /// Swizzle enum value
+ public ViewportSwizzle UnpackSwizzleZ()
+ {
+ return (ViewportSwizzle)((Swizzle >> 8) & 7);
+ }
+
+ ///
+ /// Unpacks viewport swizzle of the position W component.
+ ///
+ /// Swizzle enum value
+ public ViewportSwizzle UnpackSwizzleW()
+ {
+ return (ViewportSwizzle)((Swizzle >> 12) & 7);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/Window.cs b/Ryujinx.Graphics.Gpu/Window.cs
new file mode 100644
index 0000000000..29c3624866
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Window.cs
@@ -0,0 +1,149 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.Image;
+using System;
+using System.Collections.Concurrent;
+
+namespace Ryujinx.Graphics.Gpu
+{
+ using Texture = Image.Texture;
+
+ ///
+ /// GPU image presentation window.
+ ///
+ public class Window
+ {
+ private readonly GpuContext _context;
+
+ ///
+ /// Texture presented on the window.
+ ///
+ private struct PresentationTexture
+ {
+ ///
+ /// Texture information.
+ ///
+ public TextureInfo Info { get; }
+
+ ///
+ /// Texture crop region.
+ ///
+ public ImageCrop Crop { get; }
+
+ ///
+ /// Texture release callback.
+ ///
+ public Action