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 Callback { get; } + + /// + /// User defined object, passed to the release callback. + /// + public object UserObj { get; } + + /// + /// Creates a new instance of the presentation texture. + /// + /// Information of the texture to be presented + /// Texture crop region + /// Texture release callback + /// User defined object passed to the release callback, can be used to identify the texture + public PresentationTexture( + TextureInfo info, + ImageCrop crop, + Action callback, + object userObj) + { + Info = info; + Crop = crop; + Callback = callback; + UserObj = userObj; + } + } + + private readonly ConcurrentQueue _frameQueue; + + /// + /// Creates a new instance of the GPU presentation window. + /// + /// GPU emulation context + public Window(GpuContext context) + { + _context = context; + + _frameQueue = new ConcurrentQueue(); + } + + /// + /// Enqueues a frame for presentation. + /// This method is thread safe and can be called from any thread. + /// When the texture is presented and not needed anymore, the release callback is called. + /// It's an error to modify the texture after calling this method, before the release callback is called. + /// + /// CPU virtual address of the texture data + /// Texture width + /// Texture height + /// Texture stride for linear texture, should be zero otherwise + /// Indicates if the texture is linear, normally false + /// GOB blocks in the Y direction, for block linear textures + /// Texture format + /// Texture format bytes per pixel (must match the format) + /// Texture crop region + /// Texture release callback + /// User defined object passed to the release callback + public void EnqueueFrameThreadSafe( + ulong address, + int width, + int height, + int stride, + bool isLinear, + int gobBlocksInY, + Format format, + int bytesPerPixel, + ImageCrop crop, + Action callback, + object userObj) + { + FormatInfo formatInfo = new FormatInfo(format, 1, 1, bytesPerPixel); + + TextureInfo info = new TextureInfo( + address, + width, + height, + 1, + 1, + 1, + 1, + stride, + isLinear, + gobBlocksInY, + 1, + 1, + Target.Texture2D, + formatInfo); + + _frameQueue.Enqueue(new PresentationTexture(info, crop, callback, userObj)); + } + + /// + /// Presents a texture on the queue. + /// If the queue is empty, then no texture is presented. + /// + /// Callback method to call when a new texture should be presented on the screen + public void Present(Action swapBuffersCallback) + { + _context.AdvanceSequence(); + + if (_frameQueue.TryDequeue(out PresentationTexture pt)) + { + Texture texture = _context.Methods.TextureManager.FindOrCreateTexture(pt.Info); + + texture.SynchronizeMemory(); + + _context.Renderer.Window.Present(texture.HostTexture, pt.Crop); + + swapBuffersCallback(); + + pt.Callback(pt.UserObj); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/CdmaProcessor.cs b/Ryujinx.Graphics.Nvdec/CdmaProcessor.cs similarity index 77% rename from Ryujinx.Graphics/CdmaProcessor.cs rename to Ryujinx.Graphics.Nvdec/CdmaProcessor.cs index 4ff12fbf50..c54a95f9b0 100644 --- a/Ryujinx.Graphics/CdmaProcessor.cs +++ b/Ryujinx.Graphics.Nvdec/CdmaProcessor.cs @@ -1,4 +1,6 @@ -using Ryujinx.Graphics.Memory; +using Ryujinx.Graphics.Gpu; +using Ryujinx.Graphics.VDec; +using Ryujinx.Graphics.Vic; using System.Collections.Generic; namespace Ryujinx.Graphics @@ -8,14 +10,16 @@ namespace Ryujinx.Graphics private const int MethSetMethod = 0x10; private const int MethSetData = 0x11; - private NvGpu _gpu; + private readonly VideoDecoder _videoDecoder; + private readonly VideoImageComposer _videoImageComposer; - public CdmaProcessor(NvGpu gpu) + public CdmaProcessor() { - _gpu = gpu; + _videoDecoder = new VideoDecoder(); + _videoImageComposer = new VideoImageComposer(_videoDecoder); } - public void PushCommands(NvGpuVmm vmm, int[] cmdBuffer) + public void PushCommands(GpuContext gpu, int[] cmdBuffer) { List commands = new List(); @@ -66,10 +70,10 @@ namespace Ryujinx.Graphics } } - ProcessCommands(vmm, commands.ToArray()); + ProcessCommands(gpu, commands.ToArray()); } - private void ProcessCommands(NvGpuVmm vmm, ChCommand[] commands) + private void ProcessCommands(GpuContext gpu, ChCommand[] commands) { int methodOffset = 0; @@ -83,11 +87,11 @@ namespace Ryujinx.Graphics { if (command.ClassId == ChClassId.NvDec) { - _gpu.VideoDecoder.Process(vmm, methodOffset, command.Arguments); + _videoDecoder.Process(gpu, methodOffset, command.Arguments); } else if (command.ClassId == ChClassId.GraphicsVic) { - _gpu.VideoImageComposer.Process(vmm, methodOffset, command.Arguments); + _videoImageComposer.Process(gpu, methodOffset, command.Arguments); } break; diff --git a/Ryujinx.Graphics/ChClassId.cs b/Ryujinx.Graphics.Nvdec/ChClassId.cs similarity index 100% rename from Ryujinx.Graphics/ChClassId.cs rename to Ryujinx.Graphics.Nvdec/ChClassId.cs diff --git a/Ryujinx.Graphics/ChCommandEntry.cs b/Ryujinx.Graphics.Nvdec/ChCommandEntry.cs similarity index 100% rename from Ryujinx.Graphics/ChCommandEntry.cs rename to Ryujinx.Graphics.Nvdec/ChCommandEntry.cs diff --git a/Ryujinx.Graphics/ChSubmissionMode.cs b/Ryujinx.Graphics.Nvdec/ChSubmissionMode.cs similarity index 100% rename from Ryujinx.Graphics/ChSubmissionMode.cs rename to Ryujinx.Graphics.Nvdec/ChSubmissionMode.cs diff --git a/Ryujinx.Graphics.Nvdec/Ryujinx.Graphics.Nvdec.csproj b/Ryujinx.Graphics.Nvdec/Ryujinx.Graphics.Nvdec.csproj new file mode 100644 index 0000000000..63289e53d4 --- /dev/null +++ b/Ryujinx.Graphics.Nvdec/Ryujinx.Graphics.Nvdec.csproj @@ -0,0 +1,24 @@ + + + + netcoreapp3.0 + win-x64;osx-x64;linux-x64 + + + + true + + + + true + + + + + + + + + + + diff --git a/Ryujinx.Graphics/VDec/BitStreamWriter.cs b/Ryujinx.Graphics.Nvdec/VDec/BitStreamWriter.cs similarity index 100% rename from Ryujinx.Graphics/VDec/BitStreamWriter.cs rename to Ryujinx.Graphics.Nvdec/VDec/BitStreamWriter.cs diff --git a/Ryujinx.Graphics/VDec/DecoderHelper.cs b/Ryujinx.Graphics.Nvdec/VDec/DecoderHelper.cs similarity index 100% rename from Ryujinx.Graphics/VDec/DecoderHelper.cs rename to Ryujinx.Graphics.Nvdec/VDec/DecoderHelper.cs diff --git a/Ryujinx.Graphics/VDec/FFmpeg.cs b/Ryujinx.Graphics.Nvdec/VDec/FFmpeg.cs similarity index 100% rename from Ryujinx.Graphics/VDec/FFmpeg.cs rename to Ryujinx.Graphics.Nvdec/VDec/FFmpeg.cs diff --git a/Ryujinx.Graphics/VDec/FFmpegFrame.cs b/Ryujinx.Graphics.Nvdec/VDec/FFmpegFrame.cs similarity index 100% rename from Ryujinx.Graphics/VDec/FFmpegFrame.cs rename to Ryujinx.Graphics.Nvdec/VDec/FFmpegFrame.cs diff --git a/Ryujinx.Graphics/VDec/H264BitStreamWriter.cs b/Ryujinx.Graphics.Nvdec/VDec/H264BitStreamWriter.cs similarity index 100% rename from Ryujinx.Graphics/VDec/H264BitStreamWriter.cs rename to Ryujinx.Graphics.Nvdec/VDec/H264BitStreamWriter.cs diff --git a/Ryujinx.Graphics/VDec/H264Decoder.cs b/Ryujinx.Graphics.Nvdec/VDec/H264Decoder.cs similarity index 100% rename from Ryujinx.Graphics/VDec/H264Decoder.cs rename to Ryujinx.Graphics.Nvdec/VDec/H264Decoder.cs diff --git a/Ryujinx.Graphics/VDec/H264Matrices.cs b/Ryujinx.Graphics.Nvdec/VDec/H264Matrices.cs similarity index 100% rename from Ryujinx.Graphics/VDec/H264Matrices.cs rename to Ryujinx.Graphics.Nvdec/VDec/H264Matrices.cs diff --git a/Ryujinx.Graphics/VDec/H264ParameterSets.cs b/Ryujinx.Graphics.Nvdec/VDec/H264ParameterSets.cs similarity index 100% rename from Ryujinx.Graphics/VDec/H264ParameterSets.cs rename to Ryujinx.Graphics.Nvdec/VDec/H264ParameterSets.cs diff --git a/Ryujinx.Graphics/VDec/VideoCodec.cs b/Ryujinx.Graphics.Nvdec/VDec/VideoCodec.cs similarity index 100% rename from Ryujinx.Graphics/VDec/VideoCodec.cs rename to Ryujinx.Graphics.Nvdec/VDec/VideoCodec.cs diff --git a/Ryujinx.Graphics.Nvdec/VDec/VideoDecoder.cs b/Ryujinx.Graphics.Nvdec/VDec/VideoDecoder.cs new file mode 100644 index 0000000000..b4a89d8f3d --- /dev/null +++ b/Ryujinx.Graphics.Nvdec/VDec/VideoDecoder.cs @@ -0,0 +1,266 @@ +using Ryujinx.Graphics.Gpu; +using Ryujinx.Graphics.Vic; +using System; + +namespace Ryujinx.Graphics.VDec +{ + unsafe class VideoDecoder + { + private H264Decoder _h264Decoder; + private Vp9Decoder _vp9Decoder; + + private VideoCodec _currentVideoCodec; + + private ulong _decoderContextAddress; + private ulong _frameDataAddress; + private ulong _vpxCurrLumaAddress; + private ulong _vpxRef0LumaAddress; + private ulong _vpxRef1LumaAddress; + private ulong _vpxRef2LumaAddress; + private ulong _vpxCurrChromaAddress; + private ulong _vpxRef0ChromaAddress; + private ulong _vpxRef1ChromaAddress; + private ulong _vpxRef2ChromaAddress; + private ulong _vpxProbTablesAddress; + + public VideoDecoder() + { + _h264Decoder = new H264Decoder(); + _vp9Decoder = new Vp9Decoder(); + } + + public void Process(GpuContext gpu, int methodOffset, int[] arguments) + { + VideoDecoderMeth method = (VideoDecoderMeth)methodOffset; + + switch (method) + { + case VideoDecoderMeth.SetVideoCodec: SetVideoCodec(arguments); break; + case VideoDecoderMeth.Execute: Execute(gpu); break; + case VideoDecoderMeth.SetDecoderCtxAddr: SetDecoderCtxAddr(arguments); break; + case VideoDecoderMeth.SetFrameDataAddr: SetFrameDataAddr(arguments); break; + case VideoDecoderMeth.SetVpxCurrLumaAddr: SetVpxCurrLumaAddr(arguments); break; + case VideoDecoderMeth.SetVpxRef0LumaAddr: SetVpxRef0LumaAddr(arguments); break; + case VideoDecoderMeth.SetVpxRef1LumaAddr: SetVpxRef1LumaAddr(arguments); break; + case VideoDecoderMeth.SetVpxRef2LumaAddr: SetVpxRef2LumaAddr(arguments); break; + case VideoDecoderMeth.SetVpxCurrChromaAddr: SetVpxCurrChromaAddr(arguments); break; + case VideoDecoderMeth.SetVpxRef0ChromaAddr: SetVpxRef0ChromaAddr(arguments); break; + case VideoDecoderMeth.SetVpxRef1ChromaAddr: SetVpxRef1ChromaAddr(arguments); break; + case VideoDecoderMeth.SetVpxRef2ChromaAddr: SetVpxRef2ChromaAddr(arguments); break; + case VideoDecoderMeth.SetVpxProbTablesAddr: SetVpxProbTablesAddr(arguments); break; + } + } + + private void SetVideoCodec(int[] arguments) + { + _currentVideoCodec = (VideoCodec)arguments[0]; + } + + private void Execute(GpuContext gpu) + { + if (_currentVideoCodec == VideoCodec.H264) + { + int frameDataSize = gpu.MemoryAccessor.ReadInt32(_decoderContextAddress + 0x48); + + H264ParameterSets Params = gpu.MemoryAccessor.Read(_decoderContextAddress + 0x58); + + H264Matrices matrices = new H264Matrices() + { + ScalingMatrix4 = gpu.MemoryAccessor.ReadBytes(_decoderContextAddress + 0x1c0, 6 * 16), + ScalingMatrix8 = gpu.MemoryAccessor.ReadBytes(_decoderContextAddress + 0x220, 2 * 64) + }; + + byte[] frameData = gpu.MemoryAccessor.ReadBytes(_frameDataAddress, (ulong)frameDataSize); + + _h264Decoder.Decode(Params, matrices, frameData); + } + else if (_currentVideoCodec == VideoCodec.Vp9) + { + int frameDataSize = gpu.MemoryAccessor.ReadInt32(_decoderContextAddress + 0x30); + + Vp9FrameKeys keys = new Vp9FrameKeys() + { + CurrKey = (long)gpu.MemoryManager.Translate(_vpxCurrLumaAddress), + Ref0Key = (long)gpu.MemoryManager.Translate(_vpxRef0LumaAddress), + Ref1Key = (long)gpu.MemoryManager.Translate(_vpxRef1LumaAddress), + Ref2Key = (long)gpu.MemoryManager.Translate(_vpxRef2LumaAddress) + }; + + Vp9FrameHeader header = gpu.MemoryAccessor.Read(_decoderContextAddress + 0x48); + + Vp9ProbabilityTables probs = new Vp9ProbabilityTables() + { + SegmentationTreeProbs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x387, 0x7), + SegmentationPredProbs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x38e, 0x3), + Tx8x8Probs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x470, 0x2), + Tx16x16Probs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x472, 0x4), + Tx32x32Probs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x476, 0x6), + CoefProbs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x5a0, 0x900), + SkipProbs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x537, 0x3), + InterModeProbs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x400, 0x1c), + InterpFilterProbs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x52a, 0x8), + IsInterProbs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x41c, 0x4), + CompModeProbs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x532, 0x5), + SingleRefProbs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x580, 0xa), + CompRefProbs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x58a, 0x5), + YModeProbs0 = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x480, 0x20), + YModeProbs1 = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x47c, 0x4), + PartitionProbs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x4e0, 0x40), + MvJointProbs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x53b, 0x3), + MvSignProbs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x53e, 0x3), + MvClassProbs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x54c, 0x14), + MvClass0BitProbs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x540, 0x3), + MvBitsProbs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x56c, 0x14), + MvClass0FrProbs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x560, 0xc), + MvFrProbs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x542, 0x6), + MvClass0HpProbs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x548, 0x2), + MvHpProbs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x54a, 0x2) + }; + + byte[] frameData = gpu.MemoryAccessor.ReadBytes(_frameDataAddress, (ulong)frameDataSize); + + _vp9Decoder.Decode(keys, header, probs, frameData); + } + else + { + ThrowUnimplementedCodec(); + } + } + + private void SetDecoderCtxAddr(int[] arguments) + { + _decoderContextAddress = GetAddress(arguments); + } + + private void SetFrameDataAddr(int[] arguments) + { + _frameDataAddress = GetAddress(arguments); + } + + private void SetVpxCurrLumaAddr(int[] arguments) + { + _vpxCurrLumaAddress = GetAddress(arguments); + } + + private void SetVpxRef0LumaAddr(int[] arguments) + { + _vpxRef0LumaAddress = GetAddress(arguments); + } + + private void SetVpxRef1LumaAddr(int[] arguments) + { + _vpxRef1LumaAddress = GetAddress(arguments); + } + + private void SetVpxRef2LumaAddr(int[] arguments) + { + _vpxRef2LumaAddress = GetAddress(arguments); + } + + private void SetVpxCurrChromaAddr(int[] arguments) + { + _vpxCurrChromaAddress = GetAddress(arguments); + } + + private void SetVpxRef0ChromaAddr(int[] arguments) + { + _vpxRef0ChromaAddress = GetAddress(arguments); + } + + private void SetVpxRef1ChromaAddr(int[] arguments) + { + _vpxRef1ChromaAddress = GetAddress(arguments); + } + + private void SetVpxRef2ChromaAddr(int[] arguments) + { + _vpxRef2ChromaAddress = GetAddress(arguments); + } + + private void SetVpxProbTablesAddr(int[] arguments) + { + _vpxProbTablesAddress = GetAddress(arguments); + } + + private static ulong GetAddress(int[] arguments) + { + return (ulong)(uint)arguments[0] << 8; + } + + internal void CopyPlanes(GpuContext gpu, SurfaceOutputConfig outputConfig) + { + switch (outputConfig.PixelFormat) + { + case SurfacePixelFormat.Rgba8: CopyPlanesRgba8 (gpu, outputConfig); break; + case SurfacePixelFormat.Yuv420P: CopyPlanesYuv420P(gpu, outputConfig); break; + + default: ThrowUnimplementedPixelFormat(outputConfig.PixelFormat); break; + } + } + + private void CopyPlanesRgba8(GpuContext gpu, SurfaceOutputConfig outputConfig) + { + FFmpegFrame frame = FFmpegWrapper.GetFrameRgba(); + + if ((frame.Width | frame.Height) == 0) + { + return; + } + + throw new NotImplementedException(); + } + + private void CopyPlanesYuv420P(GpuContext gpu, SurfaceOutputConfig outputConfig) + { + FFmpegFrame frame = FFmpegWrapper.GetFrame(); + + if ((frame.Width | frame.Height) == 0) + { + return; + } + + int halfSrcWidth = frame.Width / 2; + + int halfWidth = frame.Width / 2; + int halfHeight = frame.Height / 2; + + int alignedWidth = (outputConfig.SurfaceWidth + 0xff) & ~0xff; + + for (int y = 0; y < frame.Height; y++) + { + int src = y * frame.Width; + int dst = y * alignedWidth; + + int size = frame.Width; + + for (int offset = 0; offset < size; offset++) + { + gpu.MemoryAccessor.WriteByte(outputConfig.SurfaceLumaAddress + (ulong)dst + (ulong)offset, *(frame.LumaPtr + src + offset)); + } + } + + // Copy chroma data from both channels with interleaving. + for (int y = 0; y < halfHeight; y++) + { + int src = y * halfSrcWidth; + int dst = y * alignedWidth; + + for (int x = 0; x < halfWidth; x++) + { + gpu.MemoryAccessor.WriteByte(outputConfig.SurfaceChromaUAddress + (ulong)dst + (ulong)x * 2 + 0, *(frame.ChromaBPtr + src + x)); + gpu.MemoryAccessor.WriteByte(outputConfig.SurfaceChromaUAddress + (ulong)dst + (ulong)x * 2 + 1, *(frame.ChromaRPtr + src + x)); + } + } + } + + private void ThrowUnimplementedCodec() + { + throw new NotImplementedException($"Codec \"{_currentVideoCodec}\" is not supported!"); + } + + private void ThrowUnimplementedPixelFormat(SurfacePixelFormat pixelFormat) + { + throw new NotImplementedException($"Pixel format \"{pixelFormat}\" is not supported!"); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/VDec/VideoDecoderMeth.cs b/Ryujinx.Graphics.Nvdec/VDec/VideoDecoderMeth.cs similarity index 100% rename from Ryujinx.Graphics/VDec/VideoDecoderMeth.cs rename to Ryujinx.Graphics.Nvdec/VDec/VideoDecoderMeth.cs diff --git a/Ryujinx.Graphics/VDec/Vp9Decoder.cs b/Ryujinx.Graphics.Nvdec/VDec/Vp9Decoder.cs similarity index 100% rename from Ryujinx.Graphics/VDec/Vp9Decoder.cs rename to Ryujinx.Graphics.Nvdec/VDec/Vp9Decoder.cs diff --git a/Ryujinx.Graphics/VDec/Vp9FrameHeader.cs b/Ryujinx.Graphics.Nvdec/VDec/Vp9FrameHeader.cs similarity index 100% rename from Ryujinx.Graphics/VDec/Vp9FrameHeader.cs rename to Ryujinx.Graphics.Nvdec/VDec/Vp9FrameHeader.cs diff --git a/Ryujinx.Graphics/VDec/Vp9FrameKeys.cs b/Ryujinx.Graphics.Nvdec/VDec/Vp9FrameKeys.cs similarity index 100% rename from Ryujinx.Graphics/VDec/Vp9FrameKeys.cs rename to Ryujinx.Graphics.Nvdec/VDec/Vp9FrameKeys.cs diff --git a/Ryujinx.Graphics/VDec/Vp9ProbabilityTables.cs b/Ryujinx.Graphics.Nvdec/VDec/Vp9ProbabilityTables.cs similarity index 100% rename from Ryujinx.Graphics/VDec/Vp9ProbabilityTables.cs rename to Ryujinx.Graphics.Nvdec/VDec/Vp9ProbabilityTables.cs diff --git a/Ryujinx.Graphics/VDec/VpxBitStreamWriter.cs b/Ryujinx.Graphics.Nvdec/VDec/VpxBitStreamWriter.cs similarity index 100% rename from Ryujinx.Graphics/VDec/VpxBitStreamWriter.cs rename to Ryujinx.Graphics.Nvdec/VDec/VpxBitStreamWriter.cs diff --git a/Ryujinx.Graphics/VDec/VpxRangeEncoder.cs b/Ryujinx.Graphics.Nvdec/VDec/VpxRangeEncoder.cs similarity index 100% rename from Ryujinx.Graphics/VDec/VpxRangeEncoder.cs rename to Ryujinx.Graphics.Nvdec/VDec/VpxRangeEncoder.cs diff --git a/Ryujinx.Graphics/Vic/StructUnpacker.cs b/Ryujinx.Graphics.Nvdec/Vic/StructUnpacker.cs similarity index 88% rename from Ryujinx.Graphics/Vic/StructUnpacker.cs rename to Ryujinx.Graphics.Nvdec/Vic/StructUnpacker.cs index 6b6b9795c7..4957e6b63c 100644 --- a/Ryujinx.Graphics/Vic/StructUnpacker.cs +++ b/Ryujinx.Graphics.Nvdec/Vic/StructUnpacker.cs @@ -1,18 +1,18 @@ -using Ryujinx.Graphics.Memory; +using Ryujinx.Graphics.Gpu.Memory; using System; namespace Ryujinx.Graphics.Vic { class StructUnpacker { - private NvGpuVmm _vmm; + private MemoryAccessor _vmm; - private long _position; + private ulong _position; private ulong _buffer; private int _buffPos; - public StructUnpacker(NvGpuVmm vmm, long position) + public StructUnpacker(MemoryAccessor vmm, ulong position) { _vmm = vmm; _position = position; diff --git a/Ryujinx.Graphics/Vic/SurfaceOutputConfig.cs b/Ryujinx.Graphics.Nvdec/Vic/SurfaceOutputConfig.cs similarity index 73% rename from Ryujinx.Graphics/Vic/SurfaceOutputConfig.cs rename to Ryujinx.Graphics.Nvdec/Vic/SurfaceOutputConfig.cs index bdd55fc7c8..bcb01e70b7 100644 --- a/Ryujinx.Graphics/Vic/SurfaceOutputConfig.cs +++ b/Ryujinx.Graphics.Nvdec/Vic/SurfaceOutputConfig.cs @@ -8,18 +8,18 @@ namespace Ryujinx.Graphics.Vic public int SurfaceHeight; public int GobBlockHeight; - public long SurfaceLumaAddress; - public long SurfaceChromaUAddress; - public long SurfaceChromaVAddress; + public ulong SurfaceLumaAddress; + public ulong SurfaceChromaUAddress; + public ulong SurfaceChromaVAddress; public SurfaceOutputConfig( SurfacePixelFormat pixelFormat, int surfaceWidth, int surfaceHeight, int gobBlockHeight, - long outputSurfaceLumaAddress, - long outputSurfaceChromaUAddress, - long outputSurfaceChromaVAddress) + ulong outputSurfaceLumaAddress, + ulong outputSurfaceChromaUAddress, + ulong outputSurfaceChromaVAddress) { PixelFormat = pixelFormat; SurfaceWidth = surfaceWidth; diff --git a/Ryujinx.Graphics/Vic/SurfacePixelFormat.cs b/Ryujinx.Graphics.Nvdec/Vic/SurfacePixelFormat.cs similarity index 100% rename from Ryujinx.Graphics/Vic/SurfacePixelFormat.cs rename to Ryujinx.Graphics.Nvdec/Vic/SurfacePixelFormat.cs diff --git a/Ryujinx.Graphics/Vic/VideoImageComposer.cs b/Ryujinx.Graphics.Nvdec/Vic/VideoImageComposer.cs similarity index 55% rename from Ryujinx.Graphics/Vic/VideoImageComposer.cs rename to Ryujinx.Graphics.Nvdec/Vic/VideoImageComposer.cs index e05bcfdb65..39e18fa697 100644 --- a/Ryujinx.Graphics/Vic/VideoImageComposer.cs +++ b/Ryujinx.Graphics.Nvdec/Vic/VideoImageComposer.cs @@ -1,52 +1,39 @@ -using Ryujinx.Graphics.Memory; +using Ryujinx.Graphics.Gpu; +using Ryujinx.Graphics.VDec; namespace Ryujinx.Graphics.Vic { class VideoImageComposer { - private NvGpu _gpu; + private ulong _configStructAddress; + private ulong _outputSurfaceLumaAddress; + private ulong _outputSurfaceChromaUAddress; + private ulong _outputSurfaceChromaVAddress; - private long _configStructAddress; - private long _outputSurfaceLumaAddress; - private long _outputSurfaceChromaUAddress; - private long _outputSurfaceChromaVAddress; + private VideoDecoder _vdec; - public VideoImageComposer(NvGpu gpu) + public VideoImageComposer(VideoDecoder vdec) { - _gpu = gpu; + _vdec = vdec; } - public void Process(NvGpuVmm vmm, int methodOffset, int[] arguments) + public void Process(GpuContext gpu, int methodOffset, int[] arguments) { VideoImageComposerMeth method = (VideoImageComposerMeth)methodOffset; switch (method) { - case VideoImageComposerMeth.Execute: - Execute(vmm, arguments); - break; - - case VideoImageComposerMeth.SetConfigStructOffset: - SetConfigStructOffset(vmm, arguments); - break; - - case VideoImageComposerMeth.SetOutputSurfaceLumaOffset: - SetOutputSurfaceLumaOffset(vmm, arguments); - break; - - case VideoImageComposerMeth.SetOutputSurfaceChromaUOffset: - SetOutputSurfaceChromaUOffset(vmm, arguments); - break; - - case VideoImageComposerMeth.SetOutputSurfaceChromaVOffset: - SetOutputSurfaceChromaVOffset(vmm, arguments); - break; + case VideoImageComposerMeth.Execute: Execute(gpu); break; + case VideoImageComposerMeth.SetConfigStructOffset: SetConfigStructOffset(arguments); break; + case VideoImageComposerMeth.SetOutputSurfaceLumaOffset: SetOutputSurfaceLumaOffset(arguments); break; + case VideoImageComposerMeth.SetOutputSurfaceChromaUOffset: SetOutputSurfaceChromaUOffset(arguments); break; + case VideoImageComposerMeth.SetOutputSurfaceChromaVOffset: SetOutputSurfaceChromaVOffset(arguments); break; } } - private void Execute(NvGpuVmm vmm, int[] arguments) + private void Execute(GpuContext gpu) { - StructUnpacker unpacker = new StructUnpacker(vmm, _configStructAddress + 0x20); + StructUnpacker unpacker = new StructUnpacker(gpu.MemoryAccessor, _configStructAddress + 0x20); SurfacePixelFormat pixelFormat = (SurfacePixelFormat)unpacker.Read(7); @@ -76,32 +63,32 @@ namespace Ryujinx.Graphics.Vic _outputSurfaceChromaUAddress, _outputSurfaceChromaVAddress); - _gpu.VideoDecoder.CopyPlanes(vmm, outputConfig); + _vdec.CopyPlanes(gpu, outputConfig); } - private void SetConfigStructOffset(NvGpuVmm vmm, int[] arguments) + private void SetConfigStructOffset(int[] arguments) { _configStructAddress = GetAddress(arguments); } - private void SetOutputSurfaceLumaOffset(NvGpuVmm vmm, int[] arguments) + private void SetOutputSurfaceLumaOffset(int[] arguments) { _outputSurfaceLumaAddress = GetAddress(arguments); } - private void SetOutputSurfaceChromaUOffset(NvGpuVmm vmm, int[] arguments) + private void SetOutputSurfaceChromaUOffset(int[] arguments) { _outputSurfaceChromaUAddress = GetAddress(arguments); } - private void SetOutputSurfaceChromaVOffset(NvGpuVmm vmm, int[] arguments) + private void SetOutputSurfaceChromaVOffset(int[] arguments) { _outputSurfaceChromaVAddress = GetAddress(arguments); } - private static long GetAddress(int[] arguments) + private static ulong GetAddress(int[] arguments) { - return (long)(uint)arguments[0] << 8; + return (ulong)(uint)arguments[0] << 8; } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Vic/VideoImageComposerMeth.cs b/Ryujinx.Graphics.Nvdec/Vic/VideoImageComposerMeth.cs similarity index 100% rename from Ryujinx.Graphics/Vic/VideoImageComposerMeth.cs rename to Ryujinx.Graphics.Nvdec/Vic/VideoImageComposerMeth.cs diff --git a/Ryujinx.Graphics.OpenGL/Buffer.cs b/Ryujinx.Graphics.OpenGL/Buffer.cs new file mode 100644 index 0000000000..b86719ceb4 --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/Buffer.cs @@ -0,0 +1,74 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.GAL; +using System; + +namespace Ryujinx.Graphics.OpenGL +{ + class Buffer : IBuffer + { + public int Handle { get; } + + public Buffer(int size) + { + Handle = GL.GenBuffer(); + + GL.BindBuffer(BufferTarget.CopyWriteBuffer, Handle); + GL.BufferData(BufferTarget.CopyWriteBuffer, size, IntPtr.Zero, BufferUsageHint.DynamicDraw); + } + + public void CopyTo(IBuffer destination, int srcOffset, int dstOffset, int size) + { + GL.BindBuffer(BufferTarget.CopyReadBuffer, Handle); + GL.BindBuffer(BufferTarget.CopyWriteBuffer, ((Buffer)destination).Handle); + + GL.CopyBufferSubData( + BufferTarget.CopyReadBuffer, + BufferTarget.CopyWriteBuffer, + (IntPtr)srcOffset, + (IntPtr)dstOffset, + (IntPtr)size); + } + + public byte[] GetData(int offset, int size) + { + GL.BindBuffer(BufferTarget.CopyReadBuffer, Handle); + + byte[] data = new byte[size]; + + GL.GetBufferSubData(BufferTarget.CopyReadBuffer, (IntPtr)offset, size, data); + + return data; + } + + public void SetData(Span data) + { + unsafe + { + GL.BindBuffer(BufferTarget.CopyWriteBuffer, Handle); + + fixed (byte* ptr = data) + { + GL.BufferData(BufferTarget.CopyWriteBuffer, data.Length, (IntPtr)ptr, BufferUsageHint.DynamicDraw); + } + } + } + + public void SetData(int offset, Span data) + { + GL.BindBuffer(BufferTarget.CopyWriteBuffer, Handle); + + unsafe + { + fixed (byte* ptr = data) + { + GL.BufferSubData(BufferTarget.CopyWriteBuffer, (IntPtr)offset, data.Length, (IntPtr)ptr); + } + } + } + + public void Dispose() + { + GL.DeleteBuffer(Handle); + } + } +} diff --git a/Ryujinx.Graphics.OpenGL/Counters.cs b/Ryujinx.Graphics.OpenGL/Counters.cs new file mode 100644 index 0000000000..e82a040f0e --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/Counters.cs @@ -0,0 +1,77 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.GAL; +using System; + +namespace Ryujinx.Graphics.OpenGL +{ + class Counters + { + private int[] _queryObjects; + + private ulong[] _accumulatedCounters; + + public Counters() + { + int count = Enum.GetNames(typeof(CounterType)).Length; + + _queryObjects = new int[count]; + + _accumulatedCounters = new ulong[count]; + } + + public void Initialize() + { + for (int index = 0; index < _queryObjects.Length; index++) + { + int handle = GL.GenQuery(); + + _queryObjects[index] = handle; + + CounterType type = (CounterType)index; + + GL.BeginQuery(GetTarget(type), handle); + } + } + + public ulong GetCounter(CounterType type) + { + UpdateAccumulatedCounter(type); + + return _accumulatedCounters[(int)type]; + } + + public void ResetCounter(CounterType type) + { + UpdateAccumulatedCounter(type); + + _accumulatedCounters[(int)type] = 0; + } + + private void UpdateAccumulatedCounter(CounterType type) + { + int handle = _queryObjects[(int)type]; + + QueryTarget target = GetTarget(type); + + GL.EndQuery(target); + + GL.GetQueryObject(handle, GetQueryObjectParam.QueryResult, out long result); + + _accumulatedCounters[(int)type] += (ulong)result; + + GL.BeginQuery(target, handle); + } + + private static QueryTarget GetTarget(CounterType type) + { + switch (type) + { + case CounterType.SamplesPassed: return QueryTarget.SamplesPassed; + case CounterType.PrimitivesGenerated: return QueryTarget.PrimitivesGenerated; + case CounterType.TransformFeedbackPrimitivesWritten: return QueryTarget.TransformFeedbackPrimitivesWritten; + } + + return QueryTarget.SamplesPassed; + } + } +} diff --git a/Ryujinx.Graphics.OpenGL/Debugger.cs b/Ryujinx.Graphics.OpenGL/Debugger.cs new file mode 100644 index 0000000000..f34a5048a1 --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/Debugger.cs @@ -0,0 +1,48 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Common.Logging; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.OpenGL +{ + public static class Debugger + { + private static DebugProc _debugCallback; + + public static void Initialize() + { + GL.Enable(EnableCap.DebugOutputSynchronous); + + GL.DebugMessageControl(DebugSourceControl.DontCare, DebugTypeControl.DontCare, DebugSeverityControl.DontCare, 0, (int[])null, true); + + _debugCallback = GLDebugHandler; + + GL.DebugMessageCallback(_debugCallback, IntPtr.Zero); + } + + private static void GLDebugHandler( + DebugSource source, + DebugType type, + int id, + DebugSeverity severity, + int length, + IntPtr message, + IntPtr userParam) + { + string fullMessage = $"{type} {severity} {source} {Marshal.PtrToStringAnsi(message)}"; + + switch (type) + { + case DebugType.DebugTypeError: + Logger.PrintDebug(LogClass.Gpu, fullMessage); + break; + case DebugType.DebugTypePerformance: + Logger.PrintWarning(LogClass.Gpu, fullMessage); + break; + default: + Logger.PrintDebug(LogClass.Gpu, fullMessage); + break; + } + } + } +} diff --git a/Ryujinx.Graphics.OpenGL/EnumConversion.cs b/Ryujinx.Graphics.OpenGL/EnumConversion.cs new file mode 100644 index 0000000000..0d5ea823b3 --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/EnumConversion.cs @@ -0,0 +1,420 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.OpenGL +{ + static class EnumConversion + { + public static TextureWrapMode Convert(this AddressMode mode) + { + switch (mode) + { + case AddressMode.Clamp: + return TextureWrapMode.Clamp; + case AddressMode.Repeat: + return TextureWrapMode.Repeat; + case AddressMode.MirrorClamp: + return (TextureWrapMode)ExtTextureMirrorClamp.MirrorClampExt; + case AddressMode.MirrorClampToEdge: + return (TextureWrapMode)ExtTextureMirrorClamp.MirrorClampToEdgeExt; + case AddressMode.MirrorClampToBorder: + return (TextureWrapMode)ExtTextureMirrorClamp.MirrorClampToBorderExt; + case AddressMode.ClampToBorder: + return TextureWrapMode.ClampToBorder; + case AddressMode.MirroredRepeat: + return TextureWrapMode.MirroredRepeat; + case AddressMode.ClampToEdge: + return TextureWrapMode.ClampToEdge; + } + + Logger.PrintDebug(LogClass.Gpu, $"Invalid {nameof(AddressMode)} enum value: {mode}."); + + return TextureWrapMode.Clamp; + } + + public static All Convert(this BlendFactor factor) + { + switch (factor) + { + case BlendFactor.Zero: + case BlendFactor.ZeroGl: + return All.Zero; + case BlendFactor.One: + case BlendFactor.OneGl: + return All.One; + case BlendFactor.SrcColor: + case BlendFactor.SrcColorGl: + return All.SrcColor; + case BlendFactor.OneMinusSrcColor: + case BlendFactor.OneMinusSrcColorGl: + return All.OneMinusSrcColor; + case BlendFactor.SrcAlpha: + case BlendFactor.SrcAlphaGl: + return All.SrcAlpha; + case BlendFactor.OneMinusSrcAlpha: + case BlendFactor.OneMinusSrcAlphaGl: + return All.OneMinusSrcAlpha; + case BlendFactor.DstAlpha: + case BlendFactor.DstAlphaGl: + return All.DstAlpha; + case BlendFactor.OneMinusDstAlpha: + case BlendFactor.OneMinusDstAlphaGl: + return All.OneMinusDstAlpha; + case BlendFactor.DstColor: + case BlendFactor.DstColorGl: + return All.DstColor; + case BlendFactor.OneMinusDstColor: + case BlendFactor.OneMinusDstColorGl: + return All.OneMinusDstColor; + case BlendFactor.SrcAlphaSaturate: + case BlendFactor.SrcAlphaSaturateGl: + return All.SrcAlphaSaturate; + case BlendFactor.Src1Color: + case BlendFactor.Src1ColorGl: + return All.Src1Color; + case BlendFactor.OneMinusSrc1Color: + case BlendFactor.OneMinusSrc1ColorGl: + return All.OneMinusSrc1Color; + case BlendFactor.Src1Alpha: + case BlendFactor.Src1AlphaGl: + return All.Src1Alpha; + case BlendFactor.OneMinusSrc1Alpha: + case BlendFactor.OneMinusSrc1AlphaGl: + return All.OneMinusSrc1Alpha; + case BlendFactor.ConstantColor: + return All.ConstantColor; + case BlendFactor.OneMinusConstantColor: + return All.OneMinusConstantColor; + case BlendFactor.ConstantAlpha: + return All.ConstantAlpha; + case BlendFactor.OneMinusConstantAlpha: + return All.OneMinusConstantAlpha; + } + + Logger.PrintDebug(LogClass.Gpu, $"Invalid {nameof(BlendFactor)} enum value: {factor}."); + + return All.Zero; + } + + public static BlendEquationMode Convert(this BlendOp op) + { + switch (op) + { + case BlendOp.Add: + case BlendOp.AddGl: + return BlendEquationMode.FuncAdd; + case BlendOp.Subtract: + case BlendOp.SubtractGl: + return BlendEquationMode.FuncSubtract; + case BlendOp.ReverseSubtract: + case BlendOp.ReverseSubtractGl: + return BlendEquationMode.FuncReverseSubtract; + case BlendOp.Minimum: + case BlendOp.MinimumGl: + return BlendEquationMode.Min; + case BlendOp.Maximum: + case BlendOp.MaximumGl: + return BlendEquationMode.Max; + } + + Logger.PrintDebug(LogClass.Gpu, $"Invalid {nameof(BlendOp)} enum value: {op}."); + + return BlendEquationMode.FuncAdd; + } + + public static TextureCompareMode Convert(this CompareMode mode) + { + switch (mode) + { + case CompareMode.None: + return TextureCompareMode.None; + case CompareMode.CompareRToTexture: + return TextureCompareMode.CompareRToTexture; + } + + Logger.PrintDebug(LogClass.Gpu, $"Invalid {nameof(CompareMode)} enum value: {mode}."); + + return TextureCompareMode.None; + } + + public static All Convert(this CompareOp op) + { + switch (op) + { + case CompareOp.Never: + case CompareOp.NeverGl: + return All.Never; + case CompareOp.Less: + case CompareOp.LessGl: + return All.Less; + case CompareOp.Equal: + case CompareOp.EqualGl: + return All.Equal; + case CompareOp.LessOrEqual: + case CompareOp.LessOrEqualGl: + return All.Lequal; + case CompareOp.Greater: + case CompareOp.GreaterGl: + return All.Greater; + case CompareOp.NotEqual: + case CompareOp.NotEqualGl: + return All.Notequal; + case CompareOp.GreaterOrEqual: + case CompareOp.GreaterOrEqualGl: + return All.Gequal; + case CompareOp.Always: + case CompareOp.AlwaysGl: + return All.Always; + } + + Logger.PrintDebug(LogClass.Gpu, $"Invalid {nameof(CompareOp)} enum value: {op}."); + + return All.Never; + } + + public static ClipDepthMode Convert(this DepthMode mode) + { + switch (mode) + { + case DepthMode.MinusOneToOne: + return ClipDepthMode.NegativeOneToOne; + case DepthMode.ZeroToOne: + return ClipDepthMode.ZeroToOne; + } + + Logger.PrintDebug(LogClass.Gpu, $"Invalid {nameof(DepthMode)} enum value: {mode}."); + + return ClipDepthMode.NegativeOneToOne; + } + + public static All Convert(this DepthStencilMode mode) + { + switch (mode) + { + case DepthStencilMode.Depth: + return All.Depth; + case DepthStencilMode.Stencil: + return All.Stencil; + } + + Logger.PrintDebug(LogClass.Gpu, $"Invalid {nameof(DepthStencilMode)} enum value: {mode}."); + + return All.Depth; + } + + public static CullFaceMode Convert(this Face face) + { + switch (face) + { + case Face.Back: + return CullFaceMode.Back; + case Face.Front: + return CullFaceMode.Front; + case Face.FrontAndBack: + return CullFaceMode.FrontAndBack; + } + + Logger.PrintDebug(LogClass.Gpu, $"Invalid {nameof(Face)} enum value: {face}."); + + return CullFaceMode.Back; + } + + public static FrontFaceDirection Convert(this FrontFace frontFace) + { + switch (frontFace) + { + case FrontFace.Clockwise: + return FrontFaceDirection.Cw; + case FrontFace.CounterClockwise: + return FrontFaceDirection.Ccw; + } + + Logger.PrintDebug(LogClass.Gpu, $"Invalid {nameof(FrontFace)} enum value: {frontFace}."); + + return FrontFaceDirection.Cw; + } + + public static DrawElementsType Convert(this IndexType type) + { + switch (type) + { + case IndexType.UByte: + return DrawElementsType.UnsignedByte; + case IndexType.UShort: + return DrawElementsType.UnsignedShort; + case IndexType.UInt: + return DrawElementsType.UnsignedInt; + } + + Logger.PrintDebug(LogClass.Gpu, $"Invalid {nameof(IndexType)} enum value: {type}."); + + return DrawElementsType.UnsignedByte; + } + + public static TextureMagFilter Convert(this MagFilter filter) + { + switch (filter) + { + case MagFilter.Nearest: + return TextureMagFilter.Nearest; + case MagFilter.Linear: + return TextureMagFilter.Linear; + } + + Logger.PrintDebug(LogClass.Gpu, $"Invalid {nameof(MagFilter)} enum value: {filter}."); + + return TextureMagFilter.Nearest; + } + + public static TextureMinFilter Convert(this MinFilter filter) + { + switch (filter) + { + case MinFilter.Nearest: + return TextureMinFilter.Nearest; + case MinFilter.Linear: + return TextureMinFilter.Linear; + case MinFilter.NearestMipmapNearest: + return TextureMinFilter.NearestMipmapNearest; + case MinFilter.LinearMipmapNearest: + return TextureMinFilter.LinearMipmapNearest; + case MinFilter.NearestMipmapLinear: + return TextureMinFilter.NearestMipmapLinear; + case MinFilter.LinearMipmapLinear: + return TextureMinFilter.LinearMipmapLinear; + } + + Logger.PrintDebug(LogClass.Gpu, $"Invalid {nameof(MinFilter)} enum value: {filter}."); + + return TextureMinFilter.Nearest; + } + + public static PrimitiveType Convert(this PrimitiveTopology topology) + { + switch (topology) + { + case PrimitiveTopology.Points: + return PrimitiveType.Points; + case PrimitiveTopology.Lines: + return PrimitiveType.Lines; + case PrimitiveTopology.LineLoop: + return PrimitiveType.LineLoop; + case PrimitiveTopology.LineStrip: + return PrimitiveType.LineStrip; + case PrimitiveTopology.Triangles: + return PrimitiveType.Triangles; + case PrimitiveTopology.TriangleStrip: + return PrimitiveType.TriangleStrip; + case PrimitiveTopology.TriangleFan: + return PrimitiveType.TriangleFan; + case PrimitiveTopology.Quads: + return PrimitiveType.Quads; + case PrimitiveTopology.QuadStrip: + return PrimitiveType.QuadStrip; + case PrimitiveTopology.Polygon: + return PrimitiveType.Polygon; + case PrimitiveTopology.LinesAdjacency: + return PrimitiveType.LinesAdjacency; + case PrimitiveTopology.LineStripAdjacency: + return PrimitiveType.LineStripAdjacency; + case PrimitiveTopology.TrianglesAdjacency: + return PrimitiveType.TrianglesAdjacency; + case PrimitiveTopology.TriangleStripAdjacency: + return PrimitiveType.TriangleStripAdjacency; + case PrimitiveTopology.Patches: + return PrimitiveType.Patches; + } + + Logger.PrintDebug(LogClass.Gpu, $"Invalid {nameof(PrimitiveTopology)} enum value: {topology}."); + + return PrimitiveType.Points; + } + + public static OpenTK.Graphics.OpenGL.StencilOp Convert(this GAL.StencilOp op) + { + switch (op) + { + case GAL.StencilOp.Keep: + return OpenTK.Graphics.OpenGL.StencilOp.Keep; + case GAL.StencilOp.Zero: + return OpenTK.Graphics.OpenGL.StencilOp.Zero; + case GAL.StencilOp.Replace: + return OpenTK.Graphics.OpenGL.StencilOp.Replace; + case GAL.StencilOp.IncrementAndClamp: + return OpenTK.Graphics.OpenGL.StencilOp.Incr; + case GAL.StencilOp.DecrementAndClamp: + return OpenTK.Graphics.OpenGL.StencilOp.Decr; + case GAL.StencilOp.Invert: + return OpenTK.Graphics.OpenGL.StencilOp.Invert; + case GAL.StencilOp.IncrementAndWrap: + return OpenTK.Graphics.OpenGL.StencilOp.IncrWrap; + case GAL.StencilOp.DecrementAndWrap: + return OpenTK.Graphics.OpenGL.StencilOp.DecrWrap; + } + + Logger.PrintDebug(LogClass.Gpu, $"Invalid {nameof(GAL.StencilOp)} enum value: {op}."); + + return OpenTK.Graphics.OpenGL.StencilOp.Keep; + } + + public static All Convert(this SwizzleComponent swizzleComponent) + { + switch (swizzleComponent) + { + case SwizzleComponent.Zero: + return All.Zero; + case SwizzleComponent.One: + return All.One; + case SwizzleComponent.Red: + return All.Red; + case SwizzleComponent.Green: + return All.Green; + case SwizzleComponent.Blue: + return All.Blue; + case SwizzleComponent.Alpha: + return All.Alpha; + } + + Logger.PrintDebug(LogClass.Gpu, $"Invalid {nameof(SwizzleComponent)} enum value: {swizzleComponent}."); + + return All.Zero; + } + + public static ImageTarget ConvertToImageTarget(this Target target) + { + return (ImageTarget)target.Convert(); + } + + public static TextureTarget Convert(this Target target) + { + switch (target) + { + case Target.Texture1D: + return TextureTarget.Texture1D; + case Target.Texture2D: + return TextureTarget.Texture2D; + case Target.Texture3D: + return TextureTarget.Texture3D; + case Target.Texture1DArray: + return TextureTarget.Texture1DArray; + case Target.Texture2DArray: + return TextureTarget.Texture2DArray; + case Target.Texture2DMultisample: + return TextureTarget.Texture2DMultisample; + case Target.Rectangle: + return TextureTarget.TextureRectangle; + case Target.Cubemap: + return TextureTarget.TextureCubeMap; + case Target.CubemapArray: + return TextureTarget.TextureCubeMapArray; + case Target.TextureBuffer: + return TextureTarget.TextureBuffer; + } + + Logger.PrintDebug(LogClass.Gpu, $"Invalid {nameof(Target)} enum value: {target}."); + + return TextureTarget.Texture2D; + } + } +} diff --git a/Ryujinx.Graphics.OpenGL/FormatInfo.cs b/Ryujinx.Graphics.OpenGL/FormatInfo.cs new file mode 100644 index 0000000000..6aa70691d3 --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/FormatInfo.cs @@ -0,0 +1,45 @@ +using OpenTK.Graphics.OpenGL; + +namespace Ryujinx.Graphics.OpenGL +{ + struct FormatInfo + { + public int Components { get; } + public bool Normalized { get; } + public bool Scaled { get; } + + public PixelInternalFormat PixelInternalFormat { get; } + public PixelFormat PixelFormat { get; } + public PixelType PixelType { get; } + + public bool IsCompressed { get; } + + public FormatInfo( + int components, + bool normalized, + bool scaled, + All pixelInternalFormat, + PixelFormat pixelFormat, + PixelType pixelType) + { + Components = components; + Normalized = normalized; + Scaled = scaled; + PixelInternalFormat = (PixelInternalFormat)pixelInternalFormat; + PixelFormat = pixelFormat; + PixelType = pixelType; + IsCompressed = false; + } + + public FormatInfo(int components, bool normalized, bool scaled, All pixelFormat) + { + Components = components; + Normalized = normalized; + Scaled = scaled; + PixelInternalFormat = 0; + PixelFormat = (PixelFormat)pixelFormat; + PixelType = 0; + IsCompressed = true; + } + } +} diff --git a/Ryujinx.Graphics.OpenGL/FormatTable.cs b/Ryujinx.Graphics.OpenGL/FormatTable.cs new file mode 100644 index 0000000000..b178553cd8 --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/FormatTable.cs @@ -0,0 +1,183 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.GAL; +using System; + +namespace Ryujinx.Graphics.OpenGL +{ + struct FormatTable + { + private static FormatInfo[] _table; + + static FormatTable() + { + _table = new FormatInfo[Enum.GetNames(typeof(Format)).Length]; + + Add(Format.R8Unorm, new FormatInfo(1, true, false, All.R8, PixelFormat.Red, PixelType.UnsignedByte)); + Add(Format.R8Snorm, new FormatInfo(1, true, false, All.R8Snorm, PixelFormat.Red, PixelType.Byte)); + Add(Format.R8Uint, new FormatInfo(1, false, false, All.R8ui, PixelFormat.RedInteger, PixelType.UnsignedByte)); + Add(Format.R8Sint, new FormatInfo(1, false, false, All.R8i, PixelFormat.RedInteger, PixelType.Byte)); + Add(Format.R16Float, new FormatInfo(1, false, false, All.R16f, PixelFormat.Red, PixelType.HalfFloat)); + Add(Format.R16Unorm, new FormatInfo(1, true, false, All.R16, PixelFormat.Red, PixelType.UnsignedShort)); + Add(Format.R16Snorm, new FormatInfo(1, true, false, All.R16Snorm, PixelFormat.Red, PixelType.Short)); + Add(Format.R16Uint, new FormatInfo(1, false, false, All.R16ui, PixelFormat.RedInteger, PixelType.UnsignedShort)); + Add(Format.R16Sint, new FormatInfo(1, false, false, All.R16i, PixelFormat.RedInteger, PixelType.Short)); + Add(Format.R32Float, new FormatInfo(1, false, false, All.R32f, PixelFormat.Red, PixelType.Float)); + Add(Format.R32Uint, new FormatInfo(1, false, false, All.R32ui, PixelFormat.RedInteger, PixelType.UnsignedInt)); + Add(Format.R32Sint, new FormatInfo(1, false, false, All.R32i, PixelFormat.RedInteger, PixelType.Int)); + Add(Format.R8G8Unorm, new FormatInfo(2, true, false, All.Rg8, PixelFormat.Rg, PixelType.UnsignedByte)); + Add(Format.R8G8Snorm, new FormatInfo(2, true, false, All.Rg8Snorm, PixelFormat.Rg, PixelType.Byte)); + Add(Format.R8G8Uint, new FormatInfo(2, false, false, All.Rg8ui, PixelFormat.RgInteger, PixelType.UnsignedByte)); + Add(Format.R8G8Sint, new FormatInfo(2, false, false, All.Rg8i, PixelFormat.RgInteger, PixelType.Byte)); + Add(Format.R16G16Float, new FormatInfo(2, false, false, All.Rg16f, PixelFormat.Rg, PixelType.HalfFloat)); + Add(Format.R16G16Unorm, new FormatInfo(2, true, false, All.Rg16, PixelFormat.Rg, PixelType.UnsignedShort)); + Add(Format.R16G16Snorm, new FormatInfo(2, true, false, All.Rg16Snorm, PixelFormat.Rg, PixelType.Short)); + Add(Format.R16G16Uint, new FormatInfo(2, false, false, All.Rg16ui, PixelFormat.RgInteger, PixelType.UnsignedShort)); + Add(Format.R16G16Sint, new FormatInfo(2, false, false, All.Rg16i, PixelFormat.RgInteger, PixelType.Short)); + Add(Format.R32G32Float, new FormatInfo(2, false, false, All.Rg32f, PixelFormat.Rg, PixelType.Float)); + Add(Format.R32G32Uint, new FormatInfo(2, false, false, All.Rg32ui, PixelFormat.RgInteger, PixelType.UnsignedInt)); + Add(Format.R32G32Sint, new FormatInfo(2, false, false, All.Rg32i, PixelFormat.RgInteger, PixelType.Int)); + Add(Format.R8G8B8Unorm, new FormatInfo(3, true, false, All.Rgb8, PixelFormat.Rgb, PixelType.UnsignedByte)); + Add(Format.R8G8B8Snorm, new FormatInfo(3, true, false, All.Rgb8Snorm, PixelFormat.Rgb, PixelType.Byte)); + Add(Format.R8G8B8Uint, new FormatInfo(3, false, false, All.Rgb8ui, PixelFormat.RgbInteger, PixelType.UnsignedByte)); + Add(Format.R8G8B8Sint, new FormatInfo(3, false, false, All.Rgb8i, PixelFormat.RgbInteger, PixelType.Byte)); + Add(Format.R16G16B16Float, new FormatInfo(3, false, false, All.Rgb16f, PixelFormat.Rgb, PixelType.HalfFloat)); + Add(Format.R16G16B16Unorm, new FormatInfo(3, true, false, All.Rgb16, PixelFormat.Rgb, PixelType.UnsignedShort)); + Add(Format.R16G16B16Snorm, new FormatInfo(3, true, false, All.Rgb16Snorm, PixelFormat.Rgb, PixelType.Short)); + Add(Format.R16G16B16Uint, new FormatInfo(3, false, false, All.Rgb16ui, PixelFormat.RgbInteger, PixelType.UnsignedShort)); + Add(Format.R16G16B16Sint, new FormatInfo(3, false, false, All.Rgb16i, PixelFormat.RgbInteger, PixelType.Short)); + Add(Format.R32G32B32Float, new FormatInfo(3, false, false, All.Rgb32f, PixelFormat.Rgb, PixelType.Float)); + Add(Format.R32G32B32Uint, new FormatInfo(3, false, false, All.Rgb32ui, PixelFormat.RgbInteger, PixelType.UnsignedInt)); + Add(Format.R32G32B32Sint, new FormatInfo(3, false, false, All.Rgb32i, PixelFormat.RgbInteger, PixelType.Int)); + Add(Format.R8G8B8A8Unorm, new FormatInfo(4, true, false, All.Rgba8, PixelFormat.Rgba, PixelType.UnsignedByte)); + Add(Format.R8G8B8A8Snorm, new FormatInfo(4, true, false, All.Rgba8Snorm, PixelFormat.Rgba, PixelType.Byte)); + Add(Format.R8G8B8A8Uint, new FormatInfo(4, false, false, All.Rgba8ui, PixelFormat.RgbaInteger, PixelType.UnsignedByte)); + Add(Format.R8G8B8A8Sint, new FormatInfo(4, false, false, All.Rgba8i, PixelFormat.RgbaInteger, PixelType.Byte)); + Add(Format.R16G16B16A16Float, new FormatInfo(4, false, false, All.Rgba16f, PixelFormat.Rgba, PixelType.HalfFloat)); + Add(Format.R16G16B16A16Unorm, new FormatInfo(4, true, false, All.Rgba16, PixelFormat.Rgba, PixelType.UnsignedShort)); + Add(Format.R16G16B16A16Snorm, new FormatInfo(4, true, false, All.Rgba16Snorm, PixelFormat.Rgba, PixelType.Short)); + Add(Format.R16G16B16A16Uint, new FormatInfo(4, false, false, All.Rgba16ui, PixelFormat.RgbaInteger, PixelType.UnsignedShort)); + Add(Format.R16G16B16A16Sint, new FormatInfo(4, false, false, All.Rgba16i, PixelFormat.RgbaInteger, PixelType.Short)); + Add(Format.R32G32B32A32Float, new FormatInfo(4, false, false, All.Rgba32f, PixelFormat.Rgba, PixelType.Float)); + Add(Format.R32G32B32A32Uint, new FormatInfo(4, false, false, All.Rgba32ui, PixelFormat.RgbaInteger, PixelType.UnsignedInt)); + Add(Format.R32G32B32A32Sint, new FormatInfo(4, false, false, All.Rgba32i, PixelFormat.RgbaInteger, PixelType.Int)); + Add(Format.S8Uint, new FormatInfo(1, false, false, All.StencilIndex8, PixelFormat.StencilIndex, PixelType.UnsignedByte)); + Add(Format.D16Unorm, new FormatInfo(1, false, false, All.DepthComponent16, PixelFormat.DepthComponent, PixelType.UnsignedShort)); + Add(Format.D24X8Unorm, new FormatInfo(1, false, false, All.DepthComponent24, PixelFormat.DepthComponent, PixelType.UnsignedInt)); + Add(Format.D32Float, new FormatInfo(1, false, false, All.DepthComponent32f, PixelFormat.DepthComponent, PixelType.Float)); + Add(Format.D24UnormS8Uint, new FormatInfo(1, false, false, All.Depth24Stencil8, PixelFormat.DepthStencil, PixelType.UnsignedInt248)); + Add(Format.D32FloatS8Uint, new FormatInfo(1, false, false, All.Depth32fStencil8, PixelFormat.DepthStencil, PixelType.Float32UnsignedInt248Rev)); + Add(Format.R8G8B8X8Srgb, new FormatInfo(4, false, false, All.Srgb8, PixelFormat.Rgba, PixelType.UnsignedByte)); + Add(Format.R8G8B8A8Srgb, new FormatInfo(4, false, false, All.Srgb8Alpha8, PixelFormat.Rgba, PixelType.UnsignedByte)); + Add(Format.R4G4B4A4Unorm, new FormatInfo(4, true, false, All.Rgba4, PixelFormat.Rgba, PixelType.UnsignedShort4444Reversed)); + Add(Format.R5G5B5X1Unorm, new FormatInfo(4, true, false, All.Rgb5, PixelFormat.Rgb, PixelType.UnsignedShort1555Reversed)); + Add(Format.R5G5B5A1Unorm, new FormatInfo(4, true, false, All.Rgb5A1, PixelFormat.Rgba, PixelType.UnsignedShort1555Reversed)); + Add(Format.R5G6B5Unorm, new FormatInfo(3, true, false, All.Rgb565, PixelFormat.Rgb, PixelType.UnsignedShort565Reversed)); + Add(Format.R10G10B10A2Unorm, new FormatInfo(4, true, false, All.Rgb10A2, PixelFormat.Rgba, PixelType.UnsignedInt2101010Reversed)); + Add(Format.R10G10B10A2Uint, new FormatInfo(4, false, false, All.Rgb10A2ui, PixelFormat.RgbaInteger, PixelType.UnsignedInt2101010Reversed)); + Add(Format.R11G11B10Float, new FormatInfo(3, false, false, All.R11fG11fB10f, PixelFormat.Rgb, PixelType.UnsignedInt10F11F11FRev)); + Add(Format.R9G9B9E5Float, new FormatInfo(3, false, false, All.Rgb9E5, PixelFormat.Rgb, PixelType.UnsignedInt5999Rev)); + Add(Format.Bc1RgbUnorm, new FormatInfo(2, true, false, All.CompressedRgbS3tcDxt1Ext)); + Add(Format.Bc1RgbaUnorm, new FormatInfo(1, true, false, All.CompressedRgbaS3tcDxt1Ext)); + Add(Format.Bc2Unorm, new FormatInfo(1, true, false, All.CompressedRgbaS3tcDxt3Ext)); + Add(Format.Bc3Unorm, new FormatInfo(1, true, false, All.CompressedRgbaS3tcDxt5Ext)); + Add(Format.Bc1RgbSrgb, new FormatInfo(2, false, false, All.CompressedSrgbS3tcDxt1Ext)); + Add(Format.Bc1RgbaSrgb, new FormatInfo(1, true, false, All.CompressedSrgbAlphaS3tcDxt1Ext)); + Add(Format.Bc2Srgb, new FormatInfo(1, false, false, All.CompressedSrgbAlphaS3tcDxt3Ext)); + Add(Format.Bc3Srgb, new FormatInfo(1, false, false, All.CompressedSrgbAlphaS3tcDxt5Ext)); + Add(Format.Bc4Unorm, new FormatInfo(1, true, false, All.CompressedRedRgtc1)); + Add(Format.Bc4Snorm, new FormatInfo(1, true, false, All.CompressedSignedRedRgtc1)); + Add(Format.Bc5Unorm, new FormatInfo(1, true, false, All.CompressedRgRgtc2)); + Add(Format.Bc5Snorm, new FormatInfo(1, true, false, All.CompressedSignedRgRgtc2)); + Add(Format.Bc7Unorm, new FormatInfo(1, true, false, All.CompressedRgbaBptcUnorm)); + Add(Format.Bc7Srgb, new FormatInfo(1, false, false, All.CompressedSrgbAlphaBptcUnorm)); + Add(Format.Bc6HSfloat, new FormatInfo(1, false, false, All.CompressedRgbBptcSignedFloat)); + Add(Format.Bc6HUfloat, new FormatInfo(1, false, false, All.CompressedRgbBptcUnsignedFloat)); + Add(Format.R8Uscaled, new FormatInfo(1, false, true, All.R8ui, PixelFormat.RedInteger, PixelType.UnsignedByte)); + Add(Format.R8Sscaled, new FormatInfo(1, false, true, All.R8i, PixelFormat.RedInteger, PixelType.Byte)); + Add(Format.R16Uscaled, new FormatInfo(1, false, true, All.R16ui, PixelFormat.RedInteger, PixelType.UnsignedShort)); + Add(Format.R16Sscaled, new FormatInfo(1, false, true, All.R16i, PixelFormat.RedInteger, PixelType.Short)); + Add(Format.R32Uscaled, new FormatInfo(1, false, true, All.R32ui, PixelFormat.RedInteger, PixelType.UnsignedInt)); + Add(Format.R32Sscaled, new FormatInfo(1, false, true, All.R32i, PixelFormat.RedInteger, PixelType.Int)); + Add(Format.R8G8Uscaled, new FormatInfo(2, false, true, All.Rg8ui, PixelFormat.RgInteger, PixelType.UnsignedByte)); + Add(Format.R8G8Sscaled, new FormatInfo(2, false, true, All.Rg8i, PixelFormat.RgInteger, PixelType.Byte)); + Add(Format.R16G16Uscaled, new FormatInfo(2, false, true, All.Rg16ui, PixelFormat.RgInteger, PixelType.UnsignedShort)); + Add(Format.R16G16Sscaled, new FormatInfo(2, false, true, All.Rg16i, PixelFormat.RgInteger, PixelType.Short)); + Add(Format.R32G32Uscaled, new FormatInfo(2, false, true, All.Rg32ui, PixelFormat.RgInteger, PixelType.UnsignedInt)); + Add(Format.R32G32Sscaled, new FormatInfo(2, false, true, All.Rg32i, PixelFormat.RgInteger, PixelType.Int)); + Add(Format.R8G8B8Uscaled, new FormatInfo(3, false, true, All.Rgb8ui, PixelFormat.RgbInteger, PixelType.UnsignedByte)); + Add(Format.R8G8B8Sscaled, new FormatInfo(3, false, true, All.Rgb8i, PixelFormat.RgbInteger, PixelType.Byte)); + Add(Format.R16G16B16Uscaled, new FormatInfo(3, false, true, All.Rgb16ui, PixelFormat.RgbInteger, PixelType.UnsignedShort)); + Add(Format.R16G16B16Sscaled, new FormatInfo(3, false, true, All.Rgb16i, PixelFormat.RgbInteger, PixelType.Short)); + Add(Format.R32G32B32Uscaled, new FormatInfo(3, false, true, All.Rgb32ui, PixelFormat.RgbInteger, PixelType.UnsignedInt)); + Add(Format.R32G32B32Sscaled, new FormatInfo(3, false, true, All.Rgb32i, PixelFormat.RgbInteger, PixelType.Int)); + Add(Format.R8G8B8A8Uscaled, new FormatInfo(4, false, true, All.Rgba8ui, PixelFormat.RgbaInteger, PixelType.UnsignedByte)); + Add(Format.R8G8B8A8Sscaled, new FormatInfo(4, false, true, All.Rgba8i, PixelFormat.RgbaInteger, PixelType.Byte)); + Add(Format.R16G16B16A16Uscaled, new FormatInfo(4, false, true, All.Rgba16ui, PixelFormat.RgbaInteger, PixelType.UnsignedShort)); + Add(Format.R16G16B16A16Sscaled, new FormatInfo(4, false, true, All.Rgba16i, PixelFormat.RgbaInteger, PixelType.Short)); + Add(Format.R32G32B32A32Uscaled, new FormatInfo(4, false, true, All.Rgba32ui, PixelFormat.RgbaInteger, PixelType.UnsignedInt)); + Add(Format.R32G32B32A32Sscaled, new FormatInfo(4, false, true, All.Rgba32i, PixelFormat.RgbaInteger, PixelType.Int)); + Add(Format.R10G10B10A2Snorm, new FormatInfo(4, true, false, All.Rgb10A2, PixelFormat.Rgba, (PixelType)All.Int2101010Rev)); + Add(Format.R10G10B10A2Sint, new FormatInfo(4, false, false, All.Rgb10A2, PixelFormat.RgbaInteger, (PixelType)All.Int2101010Rev)); + Add(Format.R10G10B10A2Uscaled, new FormatInfo(4, false, true, All.Rgb10A2ui, PixelFormat.RgbaInteger, PixelType.UnsignedInt2101010Reversed)); + Add(Format.R10G10B10A2Sscaled, new FormatInfo(4, false, true, All.Rgb10A2, PixelFormat.RgbaInteger, PixelType.UnsignedInt2101010Reversed)); + Add(Format.R8G8B8X8Unorm, new FormatInfo(4, true, false, All.Rgb8, PixelFormat.Rgba, PixelType.UnsignedByte)); + Add(Format.R8G8B8X8Snorm, new FormatInfo(4, true, false, All.Rgb8Snorm, PixelFormat.Rgba, PixelType.Byte)); + Add(Format.R8G8B8X8Uint, new FormatInfo(4, false, false, All.Rgb8ui, PixelFormat.RgbaInteger, PixelType.UnsignedByte)); + Add(Format.R8G8B8X8Sint, new FormatInfo(4, false, false, All.Rgb8i, PixelFormat.RgbaInteger, PixelType.Byte)); + Add(Format.R16G16B16X16Float, new FormatInfo(4, false, false, All.Rgb16f, PixelFormat.Rgba, PixelType.HalfFloat)); + Add(Format.R16G16B16X16Unorm, new FormatInfo(4, true, false, All.Rgb16, PixelFormat.Rgba, PixelType.UnsignedShort)); + Add(Format.R16G16B16X16Snorm, new FormatInfo(4, true, false, All.Rgb16Snorm, PixelFormat.Rgba, PixelType.Short)); + Add(Format.R16G16B16X16Uint, new FormatInfo(4, false, false, All.Rgb16ui, PixelFormat.RgbaInteger, PixelType.UnsignedShort)); + Add(Format.R16G16B16X16Sint, new FormatInfo(4, false, false, All.Rgb16i, PixelFormat.RgbaInteger, PixelType.Short)); + Add(Format.R32G32B32X32Float, new FormatInfo(4, false, false, All.Rgb32f, PixelFormat.Rgba, PixelType.Float)); + Add(Format.R32G32B32X32Uint, new FormatInfo(4, false, false, All.Rgb32ui, PixelFormat.RgbaInteger, PixelType.UnsignedInt)); + Add(Format.R32G32B32X32Sint, new FormatInfo(4, false, false, All.Rgb32i, PixelFormat.RgbaInteger, PixelType.Int)); + Add(Format.Astc4x4Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc4X4Khr)); + Add(Format.Astc5x4Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc5X4Khr)); + Add(Format.Astc5x5Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc5X5Khr)); + Add(Format.Astc6x5Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc6X5Khr)); + Add(Format.Astc6x6Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc6X6Khr)); + Add(Format.Astc8x5Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc8X5Khr)); + Add(Format.Astc8x6Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc8X6Khr)); + Add(Format.Astc8x8Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc8X8Khr)); + Add(Format.Astc10x5Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc10X5Khr)); + Add(Format.Astc10x6Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc10X6Khr)); + Add(Format.Astc10x8Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc10X8Khr)); + Add(Format.Astc10x10Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc10X10Khr)); + Add(Format.Astc12x10Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc12X10Khr)); + Add(Format.Astc12x12Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc12X12Khr)); + Add(Format.Astc4x4Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc4X4Khr)); + Add(Format.Astc5x4Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc5X4Khr)); + Add(Format.Astc5x5Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc5X5Khr)); + Add(Format.Astc6x5Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc6X5Khr)); + Add(Format.Astc6x6Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc6X6Khr)); + Add(Format.Astc8x5Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc8X5Khr)); + Add(Format.Astc8x6Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc8X6Khr)); + Add(Format.Astc8x8Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc8X8Khr)); + Add(Format.Astc10x5Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc10X5Khr)); + Add(Format.Astc10x6Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc10X6Khr)); + Add(Format.Astc10x8Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc10X8Khr)); + Add(Format.Astc10x10Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc10X10Khr)); + Add(Format.Astc12x10Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc12X10Khr)); + Add(Format.Astc12x12Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc12X12Khr)); + Add(Format.B5G6R5Unorm, new FormatInfo(3, true, false, All.Rgb565, PixelFormat.Rgb, PixelType.UnsignedShort565)); + Add(Format.B5G5R5X1Unorm, new FormatInfo(4, true, false, All.Rgb5, PixelFormat.Bgra, PixelType.UnsignedShort5551)); + Add(Format.B5G5R5A1Unorm, new FormatInfo(4, true, false, All.Rgb5A1, PixelFormat.Bgra, PixelType.UnsignedShort5551)); + Add(Format.A1B5G5R5Unorm, new FormatInfo(4, true, false, All.Rgb5A1, PixelFormat.Bgra, PixelType.UnsignedShort1555Reversed)); + Add(Format.B8G8R8X8Unorm, new FormatInfo(4, true, false, All.Rgba8, PixelFormat.Bgra, PixelType.UnsignedByte)); + Add(Format.B8G8R8A8Unorm, new FormatInfo(4, true, false, All.Rgba8, PixelFormat.Bgra, PixelType.UnsignedByte)); + Add(Format.B8G8R8X8Srgb, new FormatInfo(4, false, false, All.Srgb8, PixelFormat.BgraInteger, PixelType.UnsignedByte)); + Add(Format.B8G8R8A8Srgb, new FormatInfo(4, false, false, All.Srgb8Alpha8, PixelFormat.BgraInteger, PixelType.UnsignedByte)); + } + + private static void Add(Format format, FormatInfo info) + { + _table[(int)format] = info; + } + + public static FormatInfo GetFormatInfo(Format format) + { + return _table[(int)format]; + } + } +} diff --git a/Ryujinx.Graphics.OpenGL/Framebuffer.cs b/Ryujinx.Graphics.OpenGL/Framebuffer.cs new file mode 100644 index 0000000000..d416bd03ff --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/Framebuffer.cs @@ -0,0 +1,106 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.GAL; +using System; + +namespace Ryujinx.Graphics.OpenGL +{ + class Framebuffer : IDisposable + { + public int Handle { get; private set; } + + private FramebufferAttachment _lastDsAttachment; + + public Framebuffer() + { + Handle = GL.GenFramebuffer(); + } + + public void Bind() + { + GL.BindFramebuffer(FramebufferTarget.Framebuffer, Handle); + } + + public void AttachColor(int index, TextureView color) + { + GL.FramebufferTexture( + FramebufferTarget.Framebuffer, + FramebufferAttachment.ColorAttachment0 + index, + color?.Handle ?? 0, + 0); + } + + public void AttachDepthStencil(TextureView depthStencil) + { + // Detach the last depth/stencil buffer if there is any. + if (_lastDsAttachment != 0) + { + GL.FramebufferTexture(FramebufferTarget.Framebuffer, _lastDsAttachment, 0, 0); + } + + if (depthStencil != null) + { + FramebufferAttachment attachment; + + if (IsPackedDepthStencilFormat(depthStencil.Format)) + { + attachment = FramebufferAttachment.DepthStencilAttachment; + } + else if (IsDepthOnlyFormat(depthStencil.Format)) + { + attachment = FramebufferAttachment.DepthAttachment; + } + else + { + attachment = FramebufferAttachment.StencilAttachment; + } + + GL.FramebufferTexture( + FramebufferTarget.Framebuffer, + attachment, + depthStencil.Handle, + 0); + + _lastDsAttachment = attachment; + } + else + { + _lastDsAttachment = 0; + } + } + + public void SetDrawBuffers(int colorsCount) + { + DrawBuffersEnum[] drawBuffers = new DrawBuffersEnum[colorsCount]; + + for (int index = 0; index < colorsCount; index++) + { + drawBuffers[index] = DrawBuffersEnum.ColorAttachment0 + index; + } + + GL.DrawBuffers(colorsCount, drawBuffers); + } + + private static bool IsPackedDepthStencilFormat(Format format) + { + return format == Format.D24UnormS8Uint || + format == Format.D32FloatS8Uint; + } + + private static bool IsDepthOnlyFormat(Format format) + { + return format == Format.D16Unorm || + format == Format.D24X8Unorm || + format == Format.D32Float; + } + + public void Dispose() + { + if (Handle != 0) + { + GL.DeleteFramebuffer(Handle); + + Handle = 0; + } + } + } +} diff --git a/Ryujinx.Graphics.OpenGL/HwCapabilities.cs b/Ryujinx.Graphics.OpenGL/HwCapabilities.cs new file mode 100644 index 0000000000..f97bd2ea47 --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/HwCapabilities.cs @@ -0,0 +1,46 @@ +using OpenTK.Graphics.OpenGL; +using System; + +namespace Ryujinx.Graphics.OpenGL +{ + static class HwCapabilities + { + private static Lazy _supportsAstcCompression = new Lazy(() => HasExtension("GL_KHR_texture_compression_astc_ldr")); + + private static Lazy _maximumComputeSharedMemorySize = new Lazy(() => GetLimit(All.MaxComputeSharedMemorySize)); + private static Lazy _storageBufferOffsetAlignment = new Lazy(() => GetLimit(All.ShaderStorageBufferOffsetAlignment)); + + private static Lazy _isNvidiaDriver = new Lazy(() => IsNvidiaDriver()); + + public static bool SupportsAstcCompression => _supportsAstcCompression.Value; + public static bool SupportsNonConstantTextureOffset => _isNvidiaDriver.Value; + + public static int MaximumComputeSharedMemorySize => _maximumComputeSharedMemorySize.Value; + public static int StorageBufferOffsetAlignment => _storageBufferOffsetAlignment.Value; + + private static bool HasExtension(string name) + { + int numExtensions = GL.GetInteger(GetPName.NumExtensions); + + for (int extension = 0; extension < numExtensions; extension++) + { + if (GL.GetString(StringNameIndexed.Extensions, extension) == name) + { + return true; + } + } + + return false; + } + + private static int GetLimit(All name) + { + return GL.GetInteger((GetPName)name); + } + + private static bool IsNvidiaDriver() + { + return GL.GetString(StringName.Vendor).Equals("NVIDIA Corporation"); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.OpenGL/Pipeline.cs b/Ryujinx.Graphics.OpenGL/Pipeline.cs new file mode 100644 index 0000000000..669cfe3e99 --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/Pipeline.cs @@ -0,0 +1,874 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Shader; +using System; + +namespace Ryujinx.Graphics.OpenGL +{ + class Pipeline : IPipeline, IDisposable + { + private Program _program; + + private VertexArray _vertexArray; + private Framebuffer _framebuffer; + + private IntPtr _indexBaseOffset; + + private DrawElementsType _elementsType; + + private PrimitiveType _primitiveType; + + private int _stencilFrontMask; + private bool _depthMask; + private bool _depthTest; + private bool _hasDepthBuffer; + + private TextureView _unit0Texture; + + private ClipOrigin _clipOrigin; + private ClipDepthMode _clipDepthMode; + + private uint[] _componentMasks; + + internal Pipeline() + { + _clipOrigin = ClipOrigin.LowerLeft; + _clipDepthMode = ClipDepthMode.NegativeOneToOne; + } + + public void ClearRenderTargetColor(int index, uint componentMask, ColorF color) + { + GL.ColorMask( + index, + (componentMask & 1) != 0, + (componentMask & 2) != 0, + (componentMask & 4) != 0, + (componentMask & 8) != 0); + + float[] colors = new float[] { color.Red, color.Green, color.Blue, color.Alpha }; + + GL.ClearBuffer(ClearBuffer.Color, index, colors); + + RestoreComponentMask(index); + } + + public void ClearRenderTargetDepthStencil(float depthValue, bool depthMask, int stencilValue, int stencilMask) + { + bool stencilMaskChanged = + stencilMask != 0 && + stencilMask != _stencilFrontMask; + + bool depthMaskChanged = depthMask && depthMask != _depthMask; + + if (stencilMaskChanged) + { + GL.StencilMaskSeparate(StencilFace.Front, stencilMask); + } + + if (depthMaskChanged) + { + GL.DepthMask(depthMask); + } + + if (depthMask && stencilMask != 0) + { + GL.ClearBuffer(ClearBufferCombined.DepthStencil, 0, depthValue, stencilValue); + } + else if (depthMask) + { + GL.ClearBuffer(ClearBuffer.Depth, 0, ref depthValue); + } + else if (stencilMask != 0) + { + GL.ClearBuffer(ClearBuffer.Stencil, 0, ref stencilValue); + } + + if (stencilMaskChanged) + { + GL.StencilMaskSeparate(StencilFace.Front, _stencilFrontMask); + } + + if (depthMaskChanged) + { + GL.DepthMask(_depthMask); + } + } + + public void DispatchCompute(int groupsX, int groupsY, int groupsZ) + { + if (!_program.IsLinked) + { + Logger.PrintDebug(LogClass.Gpu, "Dispatch error, shader not linked."); + + return; + } + + PrepareForDispatch(); + + GL.DispatchCompute(groupsX, groupsY, groupsZ); + } + + public void Draw(int vertexCount, int instanceCount, int firstVertex, int firstInstance) + { + if (!_program.IsLinked) + { + Logger.PrintDebug(LogClass.Gpu, "Draw error, shader not linked."); + + return; + } + + PrepareForDraw(); + + if (_primitiveType == PrimitiveType.Quads) + { + DrawQuadsImpl(vertexCount, instanceCount, firstVertex, firstInstance); + } + else if (_primitiveType == PrimitiveType.QuadStrip) + { + DrawQuadStripImpl(vertexCount, instanceCount, firstVertex, firstInstance); + } + else + { + DrawImpl(vertexCount, instanceCount, firstVertex, firstInstance); + } + } + + private void DrawQuadsImpl( + int vertexCount, + int instanceCount, + int firstVertex, + int firstInstance) + { + // TODO: Instanced rendering. + int quadsCount = vertexCount / 4; + + int[] firsts = new int[quadsCount]; + int[] counts = new int[quadsCount]; + + for (int quadIndex = 0; quadIndex < quadsCount; quadIndex++) + { + firsts[quadIndex] = firstVertex + quadIndex * 4; + counts[quadIndex] = 4; + } + + GL.MultiDrawArrays( + PrimitiveType.TriangleFan, + firsts, + counts, + quadsCount); + } + + private void DrawQuadStripImpl( + int vertexCount, + int instanceCount, + int firstVertex, + int firstInstance) + { + // TODO: Instanced rendering. + int quadsCount = (vertexCount - 2) / 2; + + int[] firsts = new int[quadsCount]; + int[] counts = new int[quadsCount]; + + firsts[0] = firstVertex; + counts[0] = 4; + + for (int quadIndex = 1; quadIndex < quadsCount; quadIndex++) + { + firsts[quadIndex] = firstVertex + quadIndex * 2; + counts[quadIndex] = 4; + } + + GL.MultiDrawArrays( + PrimitiveType.TriangleFan, + firsts, + counts, + quadsCount); + } + + private void DrawImpl( + int vertexCount, + int instanceCount, + int firstVertex, + int firstInstance) + { + if (firstInstance == 0 && instanceCount == 1) + { + GL.DrawArrays(_primitiveType, firstVertex, vertexCount); + } + else if (firstInstance == 0) + { + GL.DrawArraysInstanced(_primitiveType, firstVertex, vertexCount, instanceCount); + } + else + { + GL.DrawArraysInstancedBaseInstance( + _primitiveType, + firstVertex, + vertexCount, + instanceCount, + firstInstance); + } + } + + public void DrawIndexed( + int indexCount, + int instanceCount, + int firstIndex, + int firstVertex, + int firstInstance) + { + if (!_program.IsLinked) + { + Logger.PrintDebug(LogClass.Gpu, "Draw error, shader not linked."); + + return; + } + + PrepareForDraw(); + + int indexElemSize = 1; + + switch (_elementsType) + { + case DrawElementsType.UnsignedShort: indexElemSize = 2; break; + case DrawElementsType.UnsignedInt: indexElemSize = 4; break; + } + + IntPtr indexBaseOffset = _indexBaseOffset + firstIndex * indexElemSize; + + if (_primitiveType == PrimitiveType.Quads) + { + DrawQuadsIndexedImpl( + indexCount, + instanceCount, + indexBaseOffset, + indexElemSize, + firstVertex, + firstInstance); + } + else if (_primitiveType == PrimitiveType.QuadStrip) + { + DrawQuadStripIndexedImpl( + indexCount, + instanceCount, + indexBaseOffset, + indexElemSize, + firstVertex, + firstInstance); + } + else + { + DrawIndexedImpl( + indexCount, + instanceCount, + indexBaseOffset, + firstVertex, + firstInstance); + } + } + + private void DrawQuadsIndexedImpl( + int indexCount, + int instanceCount, + IntPtr indexBaseOffset, + int indexElemSize, + int firstVertex, + int firstInstance) + { + // TODO: Instanced rendering. + int quadsCount = indexCount / 4; + + IntPtr[] indices = new IntPtr[quadsCount]; + + int[] counts = new int[quadsCount]; + + int[] baseVertices = new int[quadsCount]; + + for (int quadIndex = 0; quadIndex < quadsCount; quadIndex++) + { + indices[quadIndex] = indexBaseOffset + quadIndex * 4 * indexElemSize; + + counts[quadIndex] = 4; + + baseVertices[quadIndex] = firstVertex; + } + + GL.MultiDrawElementsBaseVertex( + PrimitiveType.TriangleFan, + counts, + _elementsType, + indices, + quadsCount, + baseVertices); + } + + private void DrawQuadStripIndexedImpl( + int indexCount, + int instanceCount, + IntPtr indexBaseOffset, + int indexElemSize, + int firstVertex, + int firstInstance) + { + // TODO: Instanced rendering. + int quadsCount = (indexCount - 2) / 2; + + IntPtr[] indices = new IntPtr[quadsCount]; + + int[] counts = new int[quadsCount]; + + int[] baseVertices = new int[quadsCount]; + + indices[0] = indexBaseOffset; + + counts[0] = 4; + + baseVertices[0] = firstVertex; + + for (int quadIndex = 1; quadIndex < quadsCount; quadIndex++) + { + indices[quadIndex] = indexBaseOffset + quadIndex * 2 * indexElemSize; + + counts[quadIndex] = 4; + + baseVertices[quadIndex] = firstVertex; + } + + GL.MultiDrawElementsBaseVertex( + PrimitiveType.TriangleFan, + counts, + _elementsType, + indices, + quadsCount, + baseVertices); + } + + private void DrawIndexedImpl( + int indexCount, + int instanceCount, + IntPtr indexBaseOffset, + int firstVertex, + int firstInstance) + { + if (firstInstance == 0 && firstVertex == 0 && instanceCount == 1) + { + GL.DrawElements(_primitiveType, indexCount, _elementsType, indexBaseOffset); + } + else if (firstInstance == 0 && instanceCount == 1) + { + GL.DrawElementsBaseVertex( + _primitiveType, + indexCount, + _elementsType, + indexBaseOffset, + firstVertex); + } + else if (firstInstance == 0 && firstVertex == 0) + { + GL.DrawElementsInstanced( + _primitiveType, + indexCount, + _elementsType, + indexBaseOffset, + instanceCount); + } + else if (firstInstance == 0) + { + GL.DrawElementsInstancedBaseVertex( + _primitiveType, + indexCount, + _elementsType, + indexBaseOffset, + instanceCount, + firstVertex); + } + else if (firstVertex == 0) + { + GL.DrawElementsInstancedBaseInstance( + _primitiveType, + indexCount, + _elementsType, + indexBaseOffset, + instanceCount, + firstInstance); + } + else + { + GL.DrawElementsInstancedBaseVertexBaseInstance( + _primitiveType, + indexCount, + _elementsType, + indexBaseOffset, + instanceCount, + firstVertex, + firstInstance); + } + } + + public void SetBlendColor(ColorF color) + { + GL.BlendColor(color.Red, color.Green, color.Blue, color.Alpha); + } + + public void SetBlendState(int index, BlendDescriptor blend) + { + if (!blend.Enable) + { + GL.Disable(IndexedEnableCap.Blend, index); + + return; + } + + GL.BlendEquationSeparate( + index, + blend.ColorOp.Convert(), + blend.AlphaOp.Convert()); + + GL.BlendFuncSeparate( + index, + (BlendingFactorSrc)blend.ColorSrcFactor.Convert(), + (BlendingFactorDest)blend.ColorDstFactor.Convert(), + (BlendingFactorSrc)blend.AlphaSrcFactor.Convert(), + (BlendingFactorDest)blend.AlphaDstFactor.Convert()); + + GL.Enable(IndexedEnableCap.Blend, index); + } + + public void SetDepthBias(PolygonModeMask enables, float factor, float units, float clamp) + { + if ((enables & PolygonModeMask.Point) != 0) + { + GL.Enable(EnableCap.PolygonOffsetPoint); + } + else + { + GL.Disable(EnableCap.PolygonOffsetPoint); + } + + if ((enables & PolygonModeMask.Line) != 0) + { + GL.Enable(EnableCap.PolygonOffsetLine); + } + else + { + GL.Disable(EnableCap.PolygonOffsetLine); + } + + if ((enables & PolygonModeMask.Fill) != 0) + { + GL.Enable(EnableCap.PolygonOffsetFill); + } + else + { + GL.Disable(EnableCap.PolygonOffsetFill); + } + + if (enables == 0) + { + return; + } + + GL.PolygonOffset(factor, units); + // TODO: Enable when GL_EXT_polygon_offset_clamp is supported. + // GL.PolygonOffsetClamp(factor, units, clamp); + } + + public void SetDepthMode(DepthMode mode) + { + ClipDepthMode depthMode = mode.Convert(); + + if (_clipDepthMode != depthMode) + { + _clipDepthMode = depthMode; + + GL.ClipControl(_clipOrigin, depthMode); + } + } + + public void SetDepthTest(DepthTestDescriptor depthTest) + { + GL.DepthFunc((DepthFunction)depthTest.Func.Convert()); + + _depthMask = depthTest.WriteEnable; + _depthTest = depthTest.TestEnable; + + UpdateDepthTest(); + } + + public void SetFaceCulling(bool enable, Face face) + { + if (!enable) + { + GL.Disable(EnableCap.CullFace); + + return; + } + + GL.CullFace(face.Convert()); + + GL.Enable(EnableCap.CullFace); + } + + public void SetFrontFace(FrontFace frontFace) + { + GL.FrontFace(frontFace.Convert()); + } + + public void SetImage(int index, ShaderStage stage, ITexture texture) + { + int unit = _program.GetImageUnit(stage, index); + + if (unit != -1 && texture != null) + { + TextureView view = (TextureView)texture; + + FormatInfo formatInfo = FormatTable.GetFormatInfo(view.Format); + + SizedInternalFormat format = (SizedInternalFormat)formatInfo.PixelInternalFormat; + + GL.BindImageTexture(unit, view.Handle, 0, true, 0, TextureAccess.ReadWrite, format); + } + } + + public void SetIndexBuffer(BufferRange buffer, IndexType type) + { + _elementsType = type.Convert(); + + _indexBaseOffset = (IntPtr)buffer.Offset; + + EnsureVertexArray(); + + _vertexArray.SetIndexBuffer((Buffer)buffer.Buffer); + } + + public void SetPrimitiveRestart(bool enable, int index) + { + if (!enable) + { + GL.Disable(EnableCap.PrimitiveRestart); + + return; + } + + GL.PrimitiveRestartIndex(index); + + GL.Enable(EnableCap.PrimitiveRestart); + } + + public void SetPrimitiveTopology(PrimitiveTopology topology) + { + _primitiveType = topology.Convert(); + } + + public void SetProgram(IProgram program) + { + _program = (Program)program; + + _program.Bind(); + } + + public void SetRenderTargetColorMasks(uint[] componentMasks) + { + _componentMasks = (uint[])componentMasks.Clone(); + + for (int index = 0; index < componentMasks.Length; index++) + { + RestoreComponentMask(index); + } + } + + public void SetRenderTargets(ITexture[] colors, ITexture depthStencil) + { + EnsureFramebuffer(); + + for (int index = 0; index < colors.Length; index++) + { + TextureView color = (TextureView)colors[index]; + + _framebuffer.AttachColor(index, color); + } + + TextureView depthStencilView = (TextureView)depthStencil; + + _framebuffer.AttachDepthStencil(depthStencilView); + + _framebuffer.SetDrawBuffers(colors.Length); + + _hasDepthBuffer = depthStencil != null && depthStencilView.Format != Format.S8Uint; + + UpdateDepthTest(); + } + + public void SetSampler(int index, ShaderStage stage, ISampler sampler) + { + int unit = _program.GetTextureUnit(stage, index); + + if (unit != -1 && sampler != null) + { + ((Sampler)sampler).Bind(unit); + } + } + + public void SetStencilTest(StencilTestDescriptor stencilTest) + { + if (!stencilTest.TestEnable) + { + GL.Disable(EnableCap.StencilTest); + + return; + } + + GL.StencilOpSeparate( + StencilFace.Front, + stencilTest.FrontSFail.Convert(), + stencilTest.FrontDpFail.Convert(), + stencilTest.FrontDpPass.Convert()); + + GL.StencilFuncSeparate( + StencilFace.Front, + (StencilFunction)stencilTest.FrontFunc.Convert(), + stencilTest.FrontFuncRef, + stencilTest.FrontFuncMask); + + GL.StencilMaskSeparate(StencilFace.Front, stencilTest.FrontMask); + + GL.StencilOpSeparate( + StencilFace.Back, + stencilTest.BackSFail.Convert(), + stencilTest.BackDpFail.Convert(), + stencilTest.BackDpPass.Convert()); + + GL.StencilFuncSeparate( + StencilFace.Back, + (StencilFunction)stencilTest.BackFunc.Convert(), + stencilTest.BackFuncRef, + stencilTest.BackFuncMask); + + GL.StencilMaskSeparate(StencilFace.Back, stencilTest.BackMask); + + GL.Enable(EnableCap.StencilTest); + + _stencilFrontMask = stencilTest.FrontMask; + } + + public void SetStorageBuffer(int index, ShaderStage stage, BufferRange buffer) + { + SetBuffer(index, stage, buffer, isStorage: true); + } + + public void SetTexture(int index, ShaderStage stage, ITexture texture) + { + int unit = _program.GetTextureUnit(stage, index); + + if (unit != -1 && texture != null) + { + if (unit == 0) + { + _unit0Texture = ((TextureView)texture); + } + else + { + ((TextureView)texture).Bind(unit); + } + } + } + + public void SetUniformBuffer(int index, ShaderStage stage, BufferRange buffer) + { + SetBuffer(index, stage, buffer, isStorage: false); + } + + public void SetVertexAttribs(VertexAttribDescriptor[] vertexAttribs) + { + EnsureVertexArray(); + + _vertexArray.SetVertexAttributes(vertexAttribs); + } + + public void SetVertexBuffers(VertexBufferDescriptor[] vertexBuffers) + { + EnsureVertexArray(); + + _vertexArray.SetVertexBuffers(vertexBuffers); + } + + public void SetViewports(int first, Viewport[] viewports) + { + bool flipY = false; + + float[] viewportArray = new float[viewports.Length * 4]; + + double[] depthRangeArray = new double[viewports.Length * 2]; + + for (int index = 0; index < viewports.Length; index++) + { + int viewportElemIndex = index * 4; + + Viewport viewport = viewports[index]; + + viewportArray[viewportElemIndex + 0] = viewport.Region.X; + viewportArray[viewportElemIndex + 1] = viewport.Region.Y; + + // OpenGL does not support per-viewport flipping, so + // instead we decide that based on the viewport 0 value. + // It will apply to all viewports. + if (index == 0) + { + flipY = viewport.Region.Height < 0; + } + + if (viewport.SwizzleY == ViewportSwizzle.NegativeY) + { + flipY = !flipY; + } + + viewportArray[viewportElemIndex + 2] = MathF.Abs(viewport.Region.Width); + viewportArray[viewportElemIndex + 3] = MathF.Abs(viewport.Region.Height); + + depthRangeArray[index * 2 + 0] = viewport.DepthNear; + depthRangeArray[index * 2 + 1] = viewport.DepthFar; + } + + GL.ViewportArray(first, viewports.Length, viewportArray); + + GL.DepthRangeArray(first, viewports.Length, depthRangeArray); + + SetOrigin(flipY ? ClipOrigin.UpperLeft : ClipOrigin.LowerLeft); + } + + public void TextureBarrier() + { + GL.MemoryBarrier(MemoryBarrierFlags.TextureFetchBarrierBit); + } + + public void TextureBarrierTiled() + { + GL.MemoryBarrier(MemoryBarrierFlags.TextureFetchBarrierBit); + } + + private void SetBuffer(int index, ShaderStage stage, BufferRange buffer, bool isStorage) + { + int bindingPoint = isStorage + ? _program.GetStorageBufferBindingPoint(stage, index) + : _program.GetUniformBufferBindingPoint(stage, index); + + if (bindingPoint == -1) + { + return; + } + + BufferRangeTarget target = isStorage + ? BufferRangeTarget.ShaderStorageBuffer + : BufferRangeTarget.UniformBuffer; + + if (buffer.Buffer == null) + { + GL.BindBufferRange(target, bindingPoint, 0, IntPtr.Zero, 0); + + return; + } + + int bufferHandle = ((Buffer)buffer.Buffer).Handle; + + IntPtr bufferOffset = (IntPtr)buffer.Offset; + + GL.BindBufferRange(target, bindingPoint, bufferHandle, bufferOffset, buffer.Size); + } + + private void SetOrigin(ClipOrigin origin) + { + if (_clipOrigin != origin) + { + _clipOrigin = origin; + + GL.ClipControl(origin, _clipDepthMode); + } + } + + private void EnsureVertexArray() + { + if (_vertexArray == null) + { + _vertexArray = new VertexArray(); + + _vertexArray.Bind(); + } + } + + private void EnsureFramebuffer() + { + if (_framebuffer == null) + { + _framebuffer = new Framebuffer(); + + _framebuffer.Bind(); + + GL.Enable(EnableCap.FramebufferSrgb); + } + } + + private void UpdateDepthTest() + { + // Enabling depth operations is only valid when we have + // a depth buffer, otherwise it's not allowed. + if (_hasDepthBuffer) + { + if (_depthTest) + { + GL.Enable(EnableCap.DepthTest); + } + else + { + GL.Disable(EnableCap.DepthTest); + } + + GL.DepthMask(_depthMask); + } + else + { + GL.Disable(EnableCap.DepthTest); + + GL.DepthMask(false); + } + } + + private void PrepareForDispatch() + { + if (_unit0Texture != null) + { + _unit0Texture.Bind(0); + } + } + + private void PrepareForDraw() + { + _vertexArray.Validate(); + + if (_unit0Texture != null) + { + _unit0Texture.Bind(0); + } + } + + private void RestoreComponentMask(int index) + { + if (_componentMasks != null) + { + GL.ColorMask( + index, + (_componentMasks[index] & 1u) != 0, + (_componentMasks[index] & 2u) != 0, + (_componentMasks[index] & 4u) != 0, + (_componentMasks[index] & 8u) != 0); + } + } + + public void Dispose() + { + _framebuffer?.Dispose(); + _vertexArray?.Dispose(); + } + } +} diff --git a/Ryujinx.Graphics.OpenGL/Program.cs b/Ryujinx.Graphics.OpenGL/Program.cs new file mode 100644 index 0000000000..a8ee7ae895 --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/Program.cs @@ -0,0 +1,224 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Shader; + +namespace Ryujinx.Graphics.OpenGL +{ + class Program : IProgram + { + private const int ShaderStages = 6; + + private const int UbStageShift = 5; + private const int SbStageShift = 4; + private const int TexStageShift = 5; + private const int ImgStageShift = 3; + + private const int UbsPerStage = 1 << UbStageShift; + private const int SbsPerStage = 1 << SbStageShift; + private const int TexsPerStage = 1 << TexStageShift; + private const int ImgsPerStage = 1 << ImgStageShift; + + public int Handle { get; private set; } + + public bool IsLinked { get; private set; } + + private int[] _ubBindingPoints; + private int[] _sbBindingPoints; + private int[] _textureUnits; + private int[] _imageUnits; + + public Program(IShader[] shaders) + { + _ubBindingPoints = new int[UbsPerStage * ShaderStages]; + _sbBindingPoints = new int[SbsPerStage * ShaderStages]; + _textureUnits = new int[TexsPerStage * ShaderStages]; + _imageUnits = new int[ImgsPerStage * ShaderStages]; + + for (int index = 0; index < _ubBindingPoints.Length; index++) + { + _ubBindingPoints[index] = -1; + } + + for (int index = 0; index < _sbBindingPoints.Length; index++) + { + _sbBindingPoints[index] = -1; + } + + for (int index = 0; index < _textureUnits.Length; index++) + { + _textureUnits[index] = -1; + } + + for (int index = 0; index < _imageUnits.Length; index++) + { + _imageUnits[index] = -1; + } + + Handle = GL.CreateProgram(); + + for (int index = 0; index < shaders.Length; index++) + { + int shaderHandle = ((Shader)shaders[index]).Handle; + + GL.AttachShader(Handle, shaderHandle); + } + + GL.LinkProgram(Handle); + + for (int index = 0; index < shaders.Length; index++) + { + int shaderHandle = ((Shader)shaders[index]).Handle; + + GL.DetachShader(Handle, shaderHandle); + } + + CheckProgramLink(); + + Bind(); + + int extraBlockindex = GL.GetUniformBlockIndex(Handle, "Extra"); + + if (extraBlockindex >= 0) + { + GL.UniformBlockBinding(Handle, extraBlockindex, 0); + } + + int ubBindingPoint = 1; + int sbBindingPoint = 0; + int textureUnit = 0; + int imageUnit = 0; + + for (int index = 0; index < shaders.Length; index++) + { + Shader shader = (Shader)shaders[index]; + + foreach (BufferDescriptor descriptor in shader.Info.CBuffers) + { + int location = GL.GetUniformBlockIndex(Handle, descriptor.Name); + + if (location < 0) + { + continue; + } + + GL.UniformBlockBinding(Handle, location, ubBindingPoint); + + int bpIndex = (int)shader.Stage << UbStageShift | descriptor.Slot; + + _ubBindingPoints[bpIndex] = ubBindingPoint; + + ubBindingPoint++; + } + + foreach (BufferDescriptor descriptor in shader.Info.SBuffers) + { + int location = GL.GetProgramResourceIndex(Handle, ProgramInterface.ShaderStorageBlock, descriptor.Name); + + if (location < 0) + { + continue; + } + + GL.ShaderStorageBlockBinding(Handle, location, sbBindingPoint); + + int bpIndex = (int)shader.Stage << SbStageShift | descriptor.Slot; + + _sbBindingPoints[bpIndex] = sbBindingPoint; + + sbBindingPoint++; + } + + int samplerIndex = 0; + + foreach (TextureDescriptor descriptor in shader.Info.Textures) + { + int location = GL.GetUniformLocation(Handle, descriptor.Name); + + if (location < 0) + { + continue; + } + + GL.Uniform1(location, textureUnit); + + int uIndex = (int)shader.Stage << TexStageShift | samplerIndex++; + + _textureUnits[uIndex] = textureUnit; + + textureUnit++; + } + + int imageIndex = 0; + + foreach (TextureDescriptor descriptor in shader.Info.Images) + { + int location = GL.GetUniformLocation(Handle, descriptor.Name); + + if (location < 0) + { + continue; + } + + GL.Uniform1(location, imageUnit); + + int uIndex = (int)shader.Stage << ImgStageShift | imageIndex++; + + _imageUnits[uIndex] = imageUnit; + + imageUnit++; + } + } + } + + public void Bind() + { + GL.UseProgram(Handle); + } + + public int GetUniformBufferBindingPoint(ShaderStage stage, int index) + { + return _ubBindingPoints[(int)stage << UbStageShift | index]; + } + + public int GetStorageBufferBindingPoint(ShaderStage stage, int index) + { + return _sbBindingPoints[(int)stage << SbStageShift | index]; + } + + public int GetTextureUnit(ShaderStage stage, int index) + { + return _textureUnits[(int)stage << TexStageShift | index]; + } + + public int GetImageUnit(ShaderStage stage, int index) + { + return _imageUnits[(int)stage << ImgStageShift | index]; + } + + private void CheckProgramLink() + { + GL.GetProgram(Handle, GetProgramParameterName.LinkStatus, out int status); + + if (status == 0) + { + // Use GL.GetProgramInfoLog(Handle), it may be too long to print on the log. + Logger.PrintDebug(LogClass.Gpu, "Shader linking failed."); + } + else + { + IsLinked = true; + } + } + + public void Dispose() + { + if (Handle != 0) + { + GL.DeleteProgram(Handle); + + Handle = 0; + } + } + } +} diff --git a/Ryujinx.Graphics.OpenGL/Renderer.cs b/Ryujinx.Graphics.OpenGL/Renderer.cs new file mode 100644 index 0000000000..29a0ea2954 --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/Renderer.cs @@ -0,0 +1,105 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Shader; + +namespace Ryujinx.Graphics.OpenGL +{ + public sealed class Renderer : IRenderer + { + private Pipeline _pipeline; + + public IPipeline Pipeline => _pipeline; + + private readonly Counters _counters; + + private readonly Window _window; + + public IWindow Window => _window; + + internal TextureCopy TextureCopy { get; } + + public Renderer() + { + _pipeline = new Pipeline(); + + _counters = new Counters(); + + _window = new Window(); + + TextureCopy = new TextureCopy(); + } + + public IShader CompileShader(ShaderProgram shader) + { + return new Shader(shader); + } + + public IBuffer CreateBuffer(int size) + { + return new Buffer(size); + } + + public IProgram CreateProgram(IShader[] shaders) + { + return new Program(shaders); + } + + public ISampler CreateSampler(SamplerCreateInfo info) + { + return new Sampler(info); + } + + public ITexture CreateTexture(TextureCreateInfo info) + { + return new TextureStorage(this, info).CreateDefaultView(); + } + + public void FlushPipelines() + { + GL.Finish(); + } + + public Capabilities GetCapabilities() + { + return new Capabilities( + HwCapabilities.SupportsAstcCompression, + HwCapabilities.SupportsNonConstantTextureOffset, + HwCapabilities.MaximumComputeSharedMemorySize, + HwCapabilities.StorageBufferOffsetAlignment); + } + + public ulong GetCounter(CounterType type) + { + return _counters.GetCounter(type); + } + + public void Initialize() + { + PrintGpuInformation(); + + _counters.Initialize(); + } + + private void PrintGpuInformation() + { + string gpuVendor = GL.GetString(StringName.Vendor); + string gpuRenderer = GL.GetString(StringName.Renderer); + string gpuVersion = GL.GetString(StringName.Version); + + Logger.PrintInfo(LogClass.Gpu, $"{gpuVendor} {gpuRenderer} ({gpuVersion})"); + } + + public void ResetCounter(CounterType type) + { + _counters.ResetCounter(type); + } + + public void Dispose() + { + TextureCopy.Dispose(); + _pipeline.Dispose(); + _window.Dispose(); + } + } +} diff --git a/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj b/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj new file mode 100644 index 0000000000..f2a9377737 --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj @@ -0,0 +1,19 @@ + + + + true + netcoreapp3.0 + win-x64;osx-x64;linux-x64 + + + + + + + + + + + + + diff --git a/Ryujinx.Graphics.OpenGL/Sampler.cs b/Ryujinx.Graphics.OpenGL/Sampler.cs new file mode 100644 index 0000000000..674fc7978b --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/Sampler.cs @@ -0,0 +1,59 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.OpenGL +{ + class Sampler : ISampler + { + public int Handle { get; private set; } + + public Sampler(SamplerCreateInfo info) + { + Handle = GL.GenSampler(); + + GL.SamplerParameter(Handle, SamplerParameterName.TextureMinFilter, (int)info.MinFilter.Convert()); + GL.SamplerParameter(Handle, SamplerParameterName.TextureMagFilter, (int)info.MagFilter.Convert()); + + GL.SamplerParameter(Handle, SamplerParameterName.TextureWrapS, (int)info.AddressU.Convert()); + GL.SamplerParameter(Handle, SamplerParameterName.TextureWrapT, (int)info.AddressV.Convert()); + GL.SamplerParameter(Handle, SamplerParameterName.TextureWrapR, (int)info.AddressP.Convert()); + + GL.SamplerParameter(Handle, SamplerParameterName.TextureCompareMode, (int)info.CompareMode.Convert()); + GL.SamplerParameter(Handle, SamplerParameterName.TextureCompareFunc, (int)info.CompareOp.Convert()); + + unsafe + { + float* borderColor = stackalloc float[4] + { + info.BorderColor.Red, + info.BorderColor.Green, + info.BorderColor.Blue, + info.BorderColor.Alpha + }; + + GL.SamplerParameter(Handle, SamplerParameterName.TextureBorderColor, borderColor); + } + + GL.SamplerParameter(Handle, SamplerParameterName.TextureMinLod, info.MinLod); + GL.SamplerParameter(Handle, SamplerParameterName.TextureMaxLod, info.MaxLod); + GL.SamplerParameter(Handle, SamplerParameterName.TextureLodBias, info.MipLodBias); + + GL.SamplerParameter(Handle, SamplerParameterName.TextureMaxAnisotropyExt, info.MaxAnisotropy); + } + + public void Bind(int unit) + { + GL.BindSampler(unit, Handle); + } + + public void Dispose() + { + if (Handle != 0) + { + GL.DeleteSampler(Handle); + + Handle = 0; + } + } + } +} diff --git a/Ryujinx.Graphics.OpenGL/Shader.cs b/Ryujinx.Graphics.OpenGL/Shader.cs new file mode 100644 index 0000000000..f25845cfd4 --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/Shader.cs @@ -0,0 +1,49 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Shader; + +namespace Ryujinx.Graphics.OpenGL +{ + class Shader : IShader + { + public int Handle { get; private set; } + + private ShaderProgram _program; + + public ShaderProgramInfo Info => _program.Info; + + public ShaderStage Stage => _program.Stage; + + public Shader(ShaderProgram program) + { + _program = program; + + ShaderType type = ShaderType.VertexShader; + + switch (program.Stage) + { + case ShaderStage.Compute: type = ShaderType.ComputeShader; break; + case ShaderStage.Vertex: type = ShaderType.VertexShader; break; + case ShaderStage.TessellationControl: type = ShaderType.TessControlShader; break; + case ShaderStage.TessellationEvaluation: type = ShaderType.TessEvaluationShader; break; + case ShaderStage.Geometry: type = ShaderType.GeometryShader; break; + case ShaderStage.Fragment: type = ShaderType.FragmentShader; break; + } + + Handle = GL.CreateShader(type); + + GL.ShaderSource(Handle, program.Code); + GL.CompileShader(Handle); + } + + public void Dispose() + { + if (Handle != 0) + { + GL.DeleteShader(Handle); + + Handle = 0; + } + } + } +} diff --git a/Ryujinx.Graphics.OpenGL/TextureCopy.cs b/Ryujinx.Graphics.OpenGL/TextureCopy.cs new file mode 100644 index 0000000000..5566642696 --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/TextureCopy.cs @@ -0,0 +1,141 @@ +using Ryujinx.Graphics.GAL; +using OpenTK.Graphics.OpenGL; +using System; + +namespace Ryujinx.Graphics.OpenGL +{ + class TextureCopy : IDisposable + { + private int _srcFramebuffer; + private int _dstFramebuffer; + + public void Copy( + TextureView src, + TextureView dst, + Extents2D srcRegion, + Extents2D dstRegion, + bool linearFilter) + { + GL.Disable(EnableCap.FramebufferSrgb); + + int oldReadFramebufferHandle = GL.GetInteger(GetPName.ReadFramebufferBinding); + int oldDrawFramebufferHandle = GL.GetInteger(GetPName.DrawFramebufferBinding); + + GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, GetSrcFramebufferLazy()); + GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, GetDstFramebufferLazy()); + + Attach(FramebufferTarget.ReadFramebuffer, src.Format, src.Handle); + Attach(FramebufferTarget.DrawFramebuffer, dst.Format, dst.Handle); + + ClearBufferMask mask = GetMask(src.Format); + + BlitFramebufferFilter filter = linearFilter + ? BlitFramebufferFilter.Linear + : BlitFramebufferFilter.Nearest; + + GL.ReadBuffer(ReadBufferMode.ColorAttachment0); + GL.DrawBuffer(DrawBufferMode.ColorAttachment0); + + GL.BlitFramebuffer( + srcRegion.X1, + srcRegion.Y1, + srcRegion.X2, + srcRegion.Y2, + dstRegion.X1, + dstRegion.Y1, + dstRegion.X2, + dstRegion.Y2, + mask, + filter); + + GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, oldReadFramebufferHandle); + GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, oldDrawFramebufferHandle); + + GL.Enable(EnableCap.FramebufferSrgb); + } + + private static void Attach(FramebufferTarget target, Format format, int handle) + { + if (format == Format.D24UnormS8Uint || format == Format.D32FloatS8Uint) + { + GL.FramebufferTexture(target, FramebufferAttachment.DepthStencilAttachment, handle, 0); + } + else if (IsDepthOnly(format)) + { + GL.FramebufferTexture(target, FramebufferAttachment.DepthAttachment, handle, 0); + } + else if (format == Format.S8Uint) + { + GL.FramebufferTexture(target, FramebufferAttachment.StencilAttachment, handle, 0); + } + else + { + GL.FramebufferTexture(target, FramebufferAttachment.ColorAttachment0, handle, 0); + } + } + + private static ClearBufferMask GetMask(Format format) + { + if (format == Format.D24UnormS8Uint || format == Format.D32FloatS8Uint) + { + return ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit; + } + else if (IsDepthOnly(format)) + { + return ClearBufferMask.DepthBufferBit; + } + else if (format == Format.S8Uint) + { + return ClearBufferMask.StencilBufferBit; + } + else + { + return ClearBufferMask.ColorBufferBit; + } + } + + private static bool IsDepthOnly(Format format) + { + return format == Format.D16Unorm || + format == Format.D24X8Unorm || + format == Format.D32Float; + } + + private int GetSrcFramebufferLazy() + { + if (_srcFramebuffer == 0) + { + _srcFramebuffer = GL.GenFramebuffer(); + } + + return _srcFramebuffer; + } + + private int GetDstFramebufferLazy() + { + if (_dstFramebuffer == 0) + { + _dstFramebuffer = GL.GenFramebuffer(); + } + + return _dstFramebuffer; + } + + public void Dispose() + { + if (_srcFramebuffer != 0) + { + GL.DeleteFramebuffer(_srcFramebuffer); + + _srcFramebuffer = 0; + } + + if (_dstFramebuffer != 0) + { + GL.DeleteFramebuffer(_dstFramebuffer); + + _dstFramebuffer = 0; + } + } + } +} diff --git a/Ryujinx.Graphics.OpenGL/TextureCopyUnscaled.cs b/Ryujinx.Graphics.OpenGL/TextureCopyUnscaled.cs new file mode 100644 index 0000000000..2597214a22 --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/TextureCopyUnscaled.cs @@ -0,0 +1,85 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Common; +using Ryujinx.Graphics.GAL; +using System; + +namespace Ryujinx.Graphics.OpenGL +{ + static class TextureCopyUnscaled + { + public static void Copy(TextureView src, TextureView dst, int dstLayer, int dstLevel) + { + int srcWidth = src.Width; + int srcHeight = src.Height; + int srcDepth = src.DepthOrLayers; + int srcLevels = src.Levels; + + srcWidth = Math.Max(1, srcWidth >> dstLevel); + srcHeight = Math.Max(1, srcHeight >> dstLevel); + + if (src.Target == Target.Texture3D) + { + srcDepth = Math.Max(1, srcDepth >> dstLevel); + } + + int dstWidth = dst.Width; + int dstHeight = dst.Height; + int dstDepth = dst.DepthOrLayers; + int dstLevels = dst.Levels; + + // When copying from a compressed to a non-compressed format, + // the non-compressed texture will have the size of the texture + // in blocks (not in texels), so we must adjust that size to + // match the size in texels of the compressed texture. + if (!src.IsCompressed && dst.IsCompressed) + { + dstWidth = BitUtils.DivRoundUp(dstWidth, dst.BlockWidth); + dstHeight = BitUtils.DivRoundUp(dstHeight, dst.BlockHeight); + } + else if (src.IsCompressed && !dst.IsCompressed) + { + dstWidth *= dst.BlockWidth; + dstHeight *= dst.BlockHeight; + } + + int width = Math.Min(srcWidth, dstWidth); + int height = Math.Min(srcHeight, dstHeight); + int depth = Math.Min(srcDepth, dstDepth); + int levels = Math.Min(srcLevels, dstLevels); + + for (int level = 0; level < levels; level++) + { + // Stop copy if we are already out of the levels range. + if (level >= src.Levels || dstLevel + level >= dst.Levels) + { + break; + } + + GL.CopyImageSubData( + src.Handle, + src.Target.ConvertToImageTarget(), + level, + 0, + 0, + 0, + dst.Handle, + dst.Target.ConvertToImageTarget(), + dstLevel + level, + 0, + 0, + dstLayer, + width, + height, + depth); + + width = Math.Max(1, width >> 1); + height = Math.Max(1, height >> 1); + + if (src.Target == Target.Texture3D) + { + depth = Math.Max(1, depth >> 1); + } + } + } + } +} diff --git a/Ryujinx.Graphics.OpenGL/TextureStorage.cs b/Ryujinx.Graphics.OpenGL/TextureStorage.cs new file mode 100644 index 0000000000..241b4116ff --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/TextureStorage.cs @@ -0,0 +1,180 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.OpenGL +{ + class TextureStorage + { + public int Handle { get; private set; } + + private readonly Renderer _renderer; + + private readonly TextureCreateInfo _info; + + public Target Target => _info.Target; + + private int _viewsCount; + + public TextureStorage(Renderer renderer, TextureCreateInfo info) + { + _renderer = renderer; + _info = info; + + Handle = GL.GenTexture(); + + CreateImmutableStorage(); + } + + private void CreateImmutableStorage() + { + TextureTarget target = _info.Target.Convert(); + + GL.ActiveTexture(TextureUnit.Texture0); + + GL.BindTexture(target, Handle); + + FormatInfo format = FormatTable.GetFormatInfo(_info.Format); + + SizedInternalFormat internalFormat; + + if (format.IsCompressed) + { + internalFormat = (SizedInternalFormat)format.PixelFormat; + } + else + { + internalFormat = (SizedInternalFormat)format.PixelInternalFormat; + } + + switch (_info.Target) + { + case Target.Texture1D: + GL.TexStorage1D( + TextureTarget1d.Texture1D, + _info.Levels, + internalFormat, + _info.Width); + break; + + case Target.Texture1DArray: + GL.TexStorage2D( + TextureTarget2d.Texture1DArray, + _info.Levels, + internalFormat, + _info.Width, + _info.Height); + break; + + case Target.Texture2D: + GL.TexStorage2D( + TextureTarget2d.Texture2D, + _info.Levels, + internalFormat, + _info.Width, + _info.Height); + break; + + case Target.Texture2DArray: + GL.TexStorage3D( + TextureTarget3d.Texture2DArray, + _info.Levels, + internalFormat, + _info.Width, + _info.Height, + _info.Depth); + break; + + case Target.Texture2DMultisample: + GL.TexStorage2DMultisample( + TextureTargetMultisample2d.Texture2DMultisample, + _info.Samples, + internalFormat, + _info.Width, + _info.Height, + true); + break; + + case Target.Texture2DMultisampleArray: + GL.TexStorage3DMultisample( + TextureTargetMultisample3d.Texture2DMultisampleArray, + _info.Samples, + internalFormat, + _info.Width, + _info.Height, + _info.Depth, + true); + break; + + case Target.Texture3D: + GL.TexStorage3D( + TextureTarget3d.Texture3D, + _info.Levels, + internalFormat, + _info.Width, + _info.Height, + _info.Depth); + break; + + case Target.Cubemap: + GL.TexStorage2D( + TextureTarget2d.TextureCubeMap, + _info.Levels, + internalFormat, + _info.Width, + _info.Height); + break; + + case Target.CubemapArray: + GL.TexStorage3D( + (TextureTarget3d)All.TextureCubeMapArray, + _info.Levels, + internalFormat, + _info.Width, + _info.Height, + _info.Depth); + break; + + default: + Logger.PrintDebug(LogClass.Gpu, $"Invalid or unsupported texture target: {target}."); + break; + } + } + + public ITexture CreateDefaultView() + { + return CreateView(_info, 0, 0); + } + + public ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel) + { + IncrementViewsCount(); + + return new TextureView(_renderer, this, info, firstLayer, firstLevel); + } + + private void IncrementViewsCount() + { + _viewsCount++; + } + + public void DecrementViewsCount() + { + // If we don't have any views, then the storage is now useless, delete it. + if (--_viewsCount == 0) + { + Dispose(); + } + } + + public void Dispose() + { + if (Handle != 0) + { + GL.DeleteTexture(Handle); + + Handle = 0; + } + } + } +} diff --git a/Ryujinx.Graphics.OpenGL/TextureView.cs b/Ryujinx.Graphics.OpenGL/TextureView.cs new file mode 100644 index 0000000000..91f1865d32 --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/TextureView.cs @@ -0,0 +1,420 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.GAL; +using System; + +namespace Ryujinx.Graphics.OpenGL +{ + class TextureView : ITexture + { + public int Handle { get; private set; } + + private readonly Renderer _renderer; + + private readonly TextureStorage _parent; + + private TextureView _emulatedViewParent; + + private readonly TextureCreateInfo _info; + + private int _firstLayer; + private int _firstLevel; + + public int Width => _info.Width; + public int Height => _info.Height; + public int DepthOrLayers => _info.GetDepthOrLayers(); + public int Levels => _info.Levels; + + public Target Target => _info.Target; + public Format Format => _info.Format; + + public int BlockWidth => _info.BlockWidth; + public int BlockHeight => _info.BlockHeight; + + public bool IsCompressed => _info.IsCompressed; + + public TextureView( + Renderer renderer, + TextureStorage parent, + TextureCreateInfo info, + int firstLayer, + int firstLevel) + { + _renderer = renderer; + _parent = parent; + _info = info; + + _firstLayer = firstLayer; + _firstLevel = firstLevel; + + Handle = GL.GenTexture(); + + CreateView(); + } + + private void CreateView() + { + TextureTarget target = Target.Convert(); + + FormatInfo format = FormatTable.GetFormatInfo(_info.Format); + + PixelInternalFormat pixelInternalFormat; + + if (format.IsCompressed) + { + pixelInternalFormat = (PixelInternalFormat)format.PixelFormat; + } + else + { + pixelInternalFormat = format.PixelInternalFormat; + } + + GL.TextureView( + Handle, + target, + _parent.Handle, + pixelInternalFormat, + _firstLevel, + _info.Levels, + _firstLayer, + _info.GetLayers()); + + GL.ActiveTexture(TextureUnit.Texture0); + + GL.BindTexture(target, Handle); + + int[] swizzleRgba = new int[] + { + (int)_info.SwizzleR.Convert(), + (int)_info.SwizzleG.Convert(), + (int)_info.SwizzleB.Convert(), + (int)_info.SwizzleA.Convert() + }; + + GL.TexParameter(target, TextureParameterName.TextureSwizzleRgba, swizzleRgba); + + int maxLevel = _info.Levels - 1; + + if (maxLevel < 0) + { + maxLevel = 0; + } + + GL.TexParameter(target, TextureParameterName.TextureMaxLevel, maxLevel); + + // TODO: This requires ARB_stencil_texturing, we should uncomment and test this. + // GL.TexParameter(target, TextureParameterName.DepthStencilTextureMode, (int)_info.DepthStencilMode.Convert()); + } + + public ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel) + { + if (_info.IsCompressed == info.IsCompressed) + { + firstLayer += _firstLayer; + firstLevel += _firstLevel; + + return _parent.CreateView(info, firstLayer, firstLevel); + } + else + { + // TODO: Most graphics APIs doesn't support creating a texture view from a compressed format + // with a non-compressed format (or vice-versa), however NVN seems to support it. + // So we emulate that here with a texture copy (see the first CopyTo overload). + // However right now it only does a single copy right after the view is created, + // so it doesn't work for all cases. + TextureView emulatedView = (TextureView)_renderer.CreateTexture(info); + + emulatedView._emulatedViewParent = this; + + emulatedView._firstLayer = firstLayer; + emulatedView._firstLevel = firstLevel; + + return emulatedView; + } + } + + public void CopyTo(ITexture destination, int firstLayer, int firstLevel) + { + TextureView destinationView = (TextureView)destination; + + TextureCopyUnscaled.Copy(this, destinationView, firstLayer, firstLevel); + + if (destinationView._emulatedViewParent != null) + { + TextureCopyUnscaled.Copy( + this, + destinationView._emulatedViewParent, + destinationView._firstLayer, + destinationView._firstLevel); + } + } + + public void CopyTo(ITexture destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter) + { + _renderer.TextureCopy.Copy(this, (TextureView)destination, srcRegion, dstRegion, linearFilter); + } + + public byte[] GetData() + { + int size = 0; + + for (int level = 0; level < _info.Levels; level++) + { + size += _info.GetMipSize(level); + } + + byte[] data = new byte[size]; + + unsafe + { + fixed (byte* ptr = data) + { + WriteTo((IntPtr)ptr); + } + } + + return data; + } + + private void WriteTo(IntPtr ptr) + { + TextureTarget target = Target.Convert(); + + Bind(target, 0); + + FormatInfo format = FormatTable.GetFormatInfo(_info.Format); + + int faces = 1; + + if (target == TextureTarget.TextureCubeMap) + { + target = TextureTarget.TextureCubeMapPositiveX; + + faces = 6; + } + + for (int level = 0; level < _info.Levels; level++) + { + for (int face = 0; face < faces; face++) + { + int faceOffset = face * _info.GetMipSize2D(level); + + if (format.IsCompressed) + { + GL.GetCompressedTexImage(target + face, level, ptr + faceOffset); + } + else + { + GL.GetTexImage( + target + face, + level, + format.PixelFormat, + format.PixelType, + ptr + faceOffset); + } + } + + ptr += _info.GetMipSize(level); + } + } + + public void SetData(Span data) + { + unsafe + { + fixed (byte* ptr = data) + { + SetData((IntPtr)ptr, data.Length); + } + } + } + + private void SetData(IntPtr data, int size) + { + TextureTarget target = Target.Convert(); + + Bind(target, 0); + + FormatInfo format = FormatTable.GetFormatInfo(_info.Format); + + int width = _info.Width; + int height = _info.Height; + int depth = _info.Depth; + + int offset = 0; + + for (int level = 0; level < _info.Levels; level++) + { + int mipSize = _info.GetMipSize(level); + + int endOffset = offset + mipSize; + + if ((uint)endOffset > (uint)size) + { + return; + } + + switch (_info.Target) + { + case Target.Texture1D: + if (format.IsCompressed) + { + GL.CompressedTexSubImage1D( + target, + level, + 0, + width, + format.PixelFormat, + mipSize, + data); + } + else + { + GL.TexSubImage1D( + target, + level, + 0, + width, + format.PixelFormat, + format.PixelType, + data); + } + break; + + case Target.Texture1DArray: + case Target.Texture2D: + if (format.IsCompressed) + { + GL.CompressedTexSubImage2D( + target, + level, + 0, + 0, + width, + height, + format.PixelFormat, + mipSize, + data); + } + else + { + GL.TexSubImage2D( + target, + level, + 0, + 0, + width, + height, + format.PixelFormat, + format.PixelType, + data); + } + break; + + case Target.Texture2DArray: + case Target.Texture3D: + case Target.CubemapArray: + if (format.IsCompressed) + { + GL.CompressedTexSubImage3D( + target, + level, + 0, + 0, + 0, + width, + height, + depth, + format.PixelFormat, + mipSize, + data); + } + else + { + GL.TexSubImage3D( + target, + level, + 0, + 0, + 0, + width, + height, + depth, + format.PixelFormat, + format.PixelType, + data); + } + break; + + case Target.Cubemap: + int faceOffset = 0; + + for (int face = 0; face < 6; face++, faceOffset += mipSize / 6) + { + if (format.IsCompressed) + { + GL.CompressedTexSubImage2D( + TextureTarget.TextureCubeMapPositiveX + face, + level, + 0, + 0, + width, + height, + format.PixelFormat, + mipSize / 6, + data + faceOffset); + } + else + { + GL.TexSubImage2D( + TextureTarget.TextureCubeMapPositiveX + face, + level, + 0, + 0, + width, + height, + format.PixelFormat, + format.PixelType, + data + faceOffset); + } + } + break; + } + + data += mipSize; + offset += mipSize; + + width = Math.Max(1, width >> 1); + height = Math.Max(1, height >> 1); + + if (Target == Target.Texture3D) + { + depth = Math.Max(1, depth >> 1); + } + } + } + + public void Bind(int unit) + { + Bind(Target.Convert(), unit); + } + + private void Bind(TextureTarget target, int unit) + { + GL.ActiveTexture(TextureUnit.Texture0 + unit); + + GL.BindTexture(target, Handle); + } + + public void Dispose() + { + if (Handle != 0) + { + GL.DeleteTexture(Handle); + + _parent.DecrementViewsCount(); + + Handle = 0; + } + } + } +} diff --git a/Ryujinx.Graphics.OpenGL/VertexArray.cs b/Ryujinx.Graphics.OpenGL/VertexArray.cs new file mode 100644 index 0000000000..721a90f0c7 --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/VertexArray.cs @@ -0,0 +1,139 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.GAL; +using System; + +namespace Ryujinx.Graphics.OpenGL +{ + class VertexArray : IDisposable + { + public int Handle { get; private set; } + + private bool _needsAttribsUpdate; + + private VertexBufferDescriptor[] _vertexBuffers; + private VertexAttribDescriptor[] _vertexAttribs; + + public VertexArray() + { + Handle = GL.GenVertexArray(); + } + + public void Bind() + { + GL.BindVertexArray(Handle); + } + + public void SetVertexBuffers(VertexBufferDescriptor[] vertexBuffers) + { + int bindingIndex = 0; + + foreach (VertexBufferDescriptor vb in vertexBuffers) + { + if (vb.Buffer.Buffer != null) + { + int bufferHandle = ((Buffer)vb.Buffer.Buffer).Handle; + + GL.BindVertexBuffer(bindingIndex, bufferHandle, (IntPtr)vb.Buffer.Offset, vb.Stride); + + GL.VertexBindingDivisor(bindingIndex, vb.Divisor); + } + else + { + GL.BindVertexBuffer(bindingIndex, 0, IntPtr.Zero, 0); + } + + bindingIndex++; + } + + _vertexBuffers = vertexBuffers; + + _needsAttribsUpdate = true; + } + + public void SetVertexAttributes(VertexAttribDescriptor[] vertexAttribs) + { + int attribIndex = 0; + + foreach (VertexAttribDescriptor attrib in vertexAttribs) + { + FormatInfo fmtInfo = FormatTable.GetFormatInfo(attrib.Format); + + GL.EnableVertexAttribArray(attribIndex); + + int offset = attrib.Offset; + int size = fmtInfo.Components; + + bool isFloat = fmtInfo.PixelType == PixelType.Float || + fmtInfo.PixelType == PixelType.HalfFloat; + + if (isFloat || fmtInfo.Normalized || fmtInfo.Scaled) + { + VertexAttribType type = (VertexAttribType)fmtInfo.PixelType; + + GL.VertexAttribFormat(attribIndex, size, type, fmtInfo.Normalized, offset); + } + else + { + VertexAttribIntegerType type = (VertexAttribIntegerType)fmtInfo.PixelType; + + GL.VertexAttribIFormat(attribIndex, size, type, offset); + } + + GL.VertexAttribBinding(attribIndex, attrib.BufferIndex); + + attribIndex++; + } + + for (; attribIndex < 16; attribIndex++) + { + GL.DisableVertexAttribArray(attribIndex); + } + + _vertexAttribs = vertexAttribs; + } + + public void SetIndexBuffer(Buffer indexBuffer) + { + GL.BindBuffer(BufferTarget.ElementArrayBuffer, indexBuffer?.Handle ?? 0); + } + + public void Validate() + { + for (int attribIndex = 0; attribIndex < _vertexAttribs.Length; attribIndex++) + { + VertexAttribDescriptor attrib = _vertexAttribs[attribIndex]; + + if ((uint)attrib.BufferIndex >= _vertexBuffers.Length) + { + GL.DisableVertexAttribArray(attribIndex); + + continue; + } + + if (_vertexBuffers[attrib.BufferIndex].Buffer.Buffer == null) + { + GL.DisableVertexAttribArray(attribIndex); + + continue; + } + + if (_needsAttribsUpdate) + { + GL.EnableVertexAttribArray(attribIndex); + } + } + + _needsAttribsUpdate = false; + } + + public void Dispose() + { + if (Handle != 0) + { + GL.DeleteVertexArray(Handle); + + Handle = 0; + } + } + } +} diff --git a/Ryujinx.Graphics.OpenGL/VertexBuffer.cs b/Ryujinx.Graphics.OpenGL/VertexBuffer.cs new file mode 100644 index 0000000000..19a58053c1 --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/VertexBuffer.cs @@ -0,0 +1,19 @@ +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.OpenGL +{ + struct VertexBuffer + { + public BufferRange Range { get; } + + public int Divisor { get; } + public int Stride { get; } + + public VertexBuffer(BufferRange range, int divisor, int stride) + { + Range = range; + Divisor = divisor; + Stride = stride; + } + } +} diff --git a/Ryujinx.Graphics.OpenGL/Window.cs b/Ryujinx.Graphics.OpenGL/Window.cs new file mode 100644 index 0000000000..26fc6a64b8 --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/Window.cs @@ -0,0 +1,132 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.GAL; +using System; + +namespace Ryujinx.Graphics.OpenGL +{ + class Window : IWindow, IDisposable + { + private const int NativeWidth = 1280; + private const int NativeHeight = 720; + + private int _width; + private int _height; + + private int _copyFramebufferHandle; + + public Window() + { + _width = NativeWidth; + _height = NativeHeight; + } + + public void Present(ITexture texture, ImageCrop crop) + { + TextureView view = (TextureView)texture; + + GL.Disable(EnableCap.FramebufferSrgb); + + int oldReadFramebufferHandle = GL.GetInteger(GetPName.ReadFramebufferBinding); + int oldDrawFramebufferHandle = GL.GetInteger(GetPName.DrawFramebufferBinding); + + GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, 0); + GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, GetCopyFramebufferHandleLazy()); + + GL.FramebufferTexture( + FramebufferTarget.ReadFramebuffer, + FramebufferAttachment.ColorAttachment0, + view.Handle, + 0); + + GL.ReadBuffer(ReadBufferMode.ColorAttachment0); + + GL.Clear(ClearBufferMask.ColorBufferBit); + + int srcX0, srcX1, srcY0, srcY1; + + if (crop.Left == 0 && crop.Right == 0) + { + srcX0 = 0; + srcX1 = view.Width; + } + else + { + srcX0 = crop.Left; + srcX1 = crop.Right; + } + + if (crop.Top == 0 && crop.Bottom == 0) + { + srcY0 = 0; + srcY1 = view.Height; + } + else + { + srcY0 = crop.Top; + srcY1 = crop.Bottom; + } + + float ratioX = MathF.Min(1f, (_height * (float)NativeWidth) / ((float)NativeHeight * _width)); + float ratioY = MathF.Min(1f, (_width * (float)NativeHeight) / ((float)NativeWidth * _height)); + + int dstWidth = (int)(_width * ratioX); + int dstHeight = (int)(_height * ratioY); + + int dstPaddingX = (_width - dstWidth) / 2; + int dstPaddingY = (_height - dstHeight) / 2; + + int dstX0 = crop.FlipX ? _width - dstPaddingX : dstPaddingX; + int dstX1 = crop.FlipX ? dstPaddingX : _width - dstPaddingX; + + int dstY0 = crop.FlipY ? dstPaddingY : _height - dstPaddingY; + int dstY1 = crop.FlipY ? _height - dstPaddingY : dstPaddingY; + + GL.BlitFramebuffer( + srcX0, + srcY0, + srcX1, + srcY1, + dstX0, + dstY0, + dstX1, + dstY1, + ClearBufferMask.ColorBufferBit, + BlitFramebufferFilter.Linear); + + GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, oldReadFramebufferHandle); + GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, oldDrawFramebufferHandle); + + GL.Enable(EnableCap.FramebufferSrgb); + } + + public void SetSize(int width, int height) + { + _width = width; + _height = height; + } + + private int GetCopyFramebufferHandleLazy() + { + int handle = _copyFramebufferHandle; + + if (handle == 0) + { + handle = GL.GenFramebuffer(); + + _copyFramebufferHandle = handle; + } + + return handle; + } + + public void Dispose() + { + if (_copyFramebufferHandle != 0) + { + GL.DeleteFramebuffer(_copyFramebufferHandle); + + _copyFramebufferHandle = 0; + } + } + } +} diff --git a/Ryujinx.Graphics/Shader/CBufferDescriptor.cs b/Ryujinx.Graphics.Shader/BufferDescriptor.cs similarity index 67% rename from Ryujinx.Graphics/Shader/CBufferDescriptor.cs rename to Ryujinx.Graphics.Shader/BufferDescriptor.cs index f99665e162..4cc999989e 100644 --- a/Ryujinx.Graphics/Shader/CBufferDescriptor.cs +++ b/Ryujinx.Graphics.Shader/BufferDescriptor.cs @@ -1,12 +1,12 @@ namespace Ryujinx.Graphics.Shader { - public struct CBufferDescriptor + public struct BufferDescriptor { public string Name { get; } public int Slot { get; } - public CBufferDescriptor(string name, int slot) + public BufferDescriptor(string name, int slot) { Name = name; Slot = slot; diff --git a/Ryujinx.Graphics.Shader/CodeGen/Constants.cs b/Ryujinx.Graphics.Shader/CodeGen/Constants.cs new file mode 100644 index 0000000000..59e9f14584 --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Constants.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Shader.CodeGen +{ + static class Constants + { + public const int MaxShaderStorageBuffers = 16; + + public const int ConstantBufferSize = 0x10000; // In bytes + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/CodeGenContext.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/CodeGenContext.cs similarity index 78% rename from Ryujinx.Graphics/Shader/CodeGen/Glsl/CodeGenContext.cs rename to Ryujinx.Graphics.Shader/CodeGen/Glsl/CodeGenContext.cs index dcbdc3097d..6bef8e6c20 100644 --- a/Ryujinx.Graphics/Shader/CodeGen/Glsl/CodeGenContext.cs +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/CodeGenContext.cs @@ -1,3 +1,4 @@ +using Ryujinx.Graphics.Shader.Translation; using System.Collections.Generic; using System.Text; @@ -5,12 +6,14 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl { class CodeGenContext { - private const string Tab = " "; + public const string Tab = " "; public ShaderConfig Config { get; } - public List CBufferDescriptors { get; } + public List CBufferDescriptors { get; } + public List SBufferDescriptors { get; } public List TextureDescriptors { get; } + public List ImageDescriptors { get; } public OperandManager OperandManager { get; } @@ -24,8 +27,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl { Config = config; - CBufferDescriptors = new List(); + CBufferDescriptors = new List(); + SBufferDescriptors = new List(); TextureDescriptors = new List(); + ImageDescriptors = new List(); OperandManager = new OperandManager(); diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs new file mode 100644 index 0000000000..200569c48e --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs @@ -0,0 +1,469 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; +using Ryujinx.Graphics.Shader.Translation; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl +{ + static class Declarations + { + // At least 16 attributes are guaranteed by the spec. + public const int MaxAttributes = 16; + + public static void Declare(CodeGenContext context, StructuredProgramInfo info) + { + context.AppendLine("#version 420 core"); + context.AppendLine("#extension GL_ARB_gpu_shader_int64 : enable"); + context.AppendLine("#extension GL_ARB_shader_ballot : enable"); + context.AppendLine("#extension GL_ARB_shader_group_vote : enable"); + context.AppendLine("#extension GL_ARB_shader_storage_buffer_object : enable"); + + if (context.Config.Stage == ShaderStage.Compute) + { + context.AppendLine("#extension GL_ARB_compute_shader : enable"); + } + + context.AppendLine("#pragma optionNV(fastmath off)"); + + context.AppendLine(); + + context.AppendLine($"const int {DefaultNames.UndefinedName} = 0;"); + context.AppendLine(); + + if (context.Config.Stage == ShaderStage.Geometry) + { + string inPrimitive = ((InputTopology)context.Config.QueryInfo(QueryInfoName.PrimitiveTopology)).ToGlslString(); + + context.AppendLine($"layout ({inPrimitive}) in;"); + + string outPrimitive = context.Config.OutputTopology.ToGlslString(); + + int maxOutputVertices = context.Config.MaxOutputVertices; + + context.AppendLine($"layout ({outPrimitive}, max_vertices = {maxOutputVertices}) out;"); + context.AppendLine(); + } + + context.AppendLine("layout (std140) uniform Extra"); + + context.EnterScope(); + + context.AppendLine("vec2 flip;"); + context.AppendLine("int instance;"); + + context.LeaveScope(";"); + + context.AppendLine(); + + context.AppendLine($"uint {DefaultNames.LocalMemoryName}[0x100];"); + context.AppendLine(); + + if (context.Config.Stage == ShaderStage.Compute) + { + string size = NumberFormatter.FormatInt(BitUtils.DivRoundUp(context.Config.QueryInfo(QueryInfoName.ComputeSharedMemorySize), 4)); + + context.AppendLine($"shared uint {DefaultNames.SharedMemoryName}[{size}];"); + context.AppendLine(); + } + + if (info.CBuffers.Count != 0) + { + DeclareUniforms(context, info); + + context.AppendLine(); + } + + if (info.SBuffers.Count != 0) + { + DeclareStorages(context, info); + + context.AppendLine(); + } + + if (info.Samplers.Count != 0) + { + DeclareSamplers(context, info); + + context.AppendLine(); + } + + if (info.Images.Count != 0) + { + DeclareImages(context, info); + + context.AppendLine(); + } + + if (context.Config.Stage != ShaderStage.Compute) + { + if (info.IAttributes.Count != 0) + { + DeclareInputAttributes(context, info); + + context.AppendLine(); + } + + if (info.OAttributes.Count != 0 || context.Config.Stage != ShaderStage.Fragment) + { + DeclareOutputAttributes(context, info); + + context.AppendLine(); + } + } + else + { + string localSizeX = NumberFormatter.FormatInt(context.Config.QueryInfo(QueryInfoName.ComputeLocalSizeX)); + string localSizeY = NumberFormatter.FormatInt(context.Config.QueryInfo(QueryInfoName.ComputeLocalSizeY)); + string localSizeZ = NumberFormatter.FormatInt(context.Config.QueryInfo(QueryInfoName.ComputeLocalSizeZ)); + + context.AppendLine( + "layout (" + + $"local_size_x = {localSizeX}, " + + $"local_size_y = {localSizeY}, " + + $"local_size_z = {localSizeZ}) in;"); + context.AppendLine(); + } + + if ((info.HelperFunctionsMask & HelperFunctionsMask.MultiplyHighS32) != 0) + { + AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighS32.glsl"); + } + + if ((info.HelperFunctionsMask & HelperFunctionsMask.MultiplyHighU32) != 0) + { + AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighU32.glsl"); + } + + if ((info.HelperFunctionsMask & HelperFunctionsMask.Shuffle) != 0) + { + AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/Shuffle.glsl"); + } + + if ((info.HelperFunctionsMask & HelperFunctionsMask.ShuffleDown) != 0) + { + AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleDown.glsl"); + } + + if ((info.HelperFunctionsMask & HelperFunctionsMask.ShuffleUp) != 0) + { + AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleUp.glsl"); + } + + if ((info.HelperFunctionsMask & HelperFunctionsMask.ShuffleXor) != 0) + { + AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleXor.glsl"); + } + + if ((info.HelperFunctionsMask & HelperFunctionsMask.SwizzleAdd) != 0) + { + AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/SwizzleAdd.glsl"); + } + } + + public static void DeclareLocals(CodeGenContext context, StructuredProgramInfo info) + { + foreach (AstOperand decl in info.Locals) + { + string name = context.OperandManager.DeclareLocal(decl); + + context.AppendLine(GetVarTypeName(decl.VarType) + " " + name + ";"); + } + } + + private static string GetVarTypeName(VariableType type) + { + switch (type) + { + case VariableType.Bool: return "bool"; + case VariableType.F32: return "precise float"; + case VariableType.S32: return "int"; + case VariableType.U32: return "uint"; + } + + throw new ArgumentException($"Invalid variable type \"{type}\"."); + } + + private static void DeclareUniforms(CodeGenContext context, StructuredProgramInfo info) + { + foreach (int cbufSlot in info.CBuffers.OrderBy(x => x)) + { + string ubName = OperandManager.GetShaderStagePrefix(context.Config.Stage); + + ubName += "_" + DefaultNames.UniformNamePrefix + cbufSlot; + + context.CBufferDescriptors.Add(new BufferDescriptor(ubName, cbufSlot)); + + context.AppendLine("layout (std140) uniform " + ubName); + + context.EnterScope(); + + string ubSize = "[" + NumberFormatter.FormatInt(Constants.ConstantBufferSize / 16) + "]"; + + context.AppendLine("vec4 " + OperandManager.GetUbName(context.Config.Stage, cbufSlot) + ubSize + ";"); + + context.LeaveScope(";"); + } + } + + private static void DeclareStorages(CodeGenContext context, StructuredProgramInfo info) + { + string sbName = OperandManager.GetShaderStagePrefix(context.Config.Stage); + + sbName += "_" + DefaultNames.StorageNamePrefix; + + string blockName = $"{sbName}_{DefaultNames.BlockSuffix}"; + + int maxSlot = 0; + + foreach (int sbufSlot in info.SBuffers) + { + context.SBufferDescriptors.Add(new BufferDescriptor($"{blockName}[{sbufSlot}]", sbufSlot)); + + if (maxSlot < sbufSlot) + { + maxSlot = sbufSlot; + } + } + + context.AppendLine("layout (std430) buffer " + blockName); + + context.EnterScope(); + + context.AppendLine("uint " + DefaultNames.DataName + "[];"); + + string arraySize = NumberFormatter.FormatInt(maxSlot + 1); + + context.LeaveScope($" {sbName}[{arraySize}];"); + } + + private static void DeclareSamplers(CodeGenContext context, StructuredProgramInfo info) + { + Dictionary samplers = new Dictionary(); + + foreach (AstTextureOperation texOp in info.Samplers.OrderBy(x => x.Handle)) + { + string indexExpr = NumberFormatter.FormatInt(texOp.ArraySize); + + string samplerName = OperandManager.GetSamplerName(context.Config.Stage, texOp, indexExpr); + + if (!samplers.TryAdd(samplerName, texOp)) + { + continue; + } + + string samplerTypeName = GetSamplerTypeName(texOp.Type); + + context.AppendLine("uniform " + samplerTypeName + " " + samplerName + ";"); + } + + foreach (KeyValuePair kv in samplers) + { + string samplerName = kv.Key; + + AstTextureOperation texOp = kv.Value; + + TextureDescriptor desc; + + if ((texOp.Flags & TextureFlags.Bindless) != 0) + { + AstOperand operand = texOp.GetSource(0) as AstOperand; + + desc = new TextureDescriptor(samplerName, texOp.Type, operand.CbufSlot, operand.CbufOffset); + + context.TextureDescriptors.Add(desc); + } + else if ((texOp.Type & SamplerType.Indexed) != 0) + { + for (int index = 0; index < texOp.ArraySize; index++) + { + string indexExpr = NumberFormatter.FormatInt(index); + + string indexedSamplerName = OperandManager.GetSamplerName(context.Config.Stage, texOp, indexExpr); + + desc = new TextureDescriptor(indexedSamplerName, texOp.Type, texOp.Handle + index * 2); + + context.TextureDescriptors.Add(desc); + } + } + else + { + desc = new TextureDescriptor(samplerName, texOp.Type, texOp.Handle); + + context.TextureDescriptors.Add(desc); + } + } + } + + private static void DeclareImages(CodeGenContext context, StructuredProgramInfo info) + { + Dictionary images = new Dictionary(); + + foreach (AstTextureOperation texOp in info.Images.OrderBy(x => x.Handle)) + { + string indexExpr = NumberFormatter.FormatInt(texOp.ArraySize); + + string imageName = OperandManager.GetImageName(context.Config.Stage, texOp, indexExpr); + + if (!images.TryAdd(imageName, texOp)) + { + continue; + } + + string imageTypeName = GetImageTypeName(texOp.Type); + + context.AppendLine("writeonly uniform " + imageTypeName + " " + imageName + ";"); + } + + foreach (KeyValuePair kv in images) + { + string imageName = kv.Key; + + AstTextureOperation texOp = kv.Value; + + if ((texOp.Type & SamplerType.Indexed) != 0) + { + for (int index = 0; index < texOp.ArraySize; index++) + { + string indexExpr = NumberFormatter.FormatInt(index); + + string indexedSamplerName = OperandManager.GetSamplerName(context.Config.Stage, texOp, indexExpr); + + var desc = new TextureDescriptor(indexedSamplerName, texOp.Type, texOp.Handle + index * 2); + + context.TextureDescriptors.Add(desc); + } + } + else + { + var desc = new TextureDescriptor(imageName, texOp.Type, texOp.Handle); + + context.ImageDescriptors.Add(desc); + } + } + } + + private static void DeclareInputAttributes(CodeGenContext context, StructuredProgramInfo info) + { + string suffix = context.Config.Stage == ShaderStage.Geometry ? "[]" : string.Empty; + + foreach (int attr in info.IAttributes.OrderBy(x => x)) + { + string iq = info.InterpolationQualifiers[attr].ToGlslQualifier(); + + if (iq != string.Empty) + { + iq += " "; + } + + context.AppendLine($"layout (location = {attr}) {iq}in vec4 {DefaultNames.IAttributePrefix}{attr}{suffix};"); + } + } + + private static void DeclareOutputAttributes(CodeGenContext context, StructuredProgramInfo info) + { + if (context.Config.Stage == ShaderStage.Fragment) + { + DeclareUsedOutputAttributes(context, info); + } + else + { + DeclareAllOutputAttributes(context, info); + } + } + + private static void DeclareUsedOutputAttributes(CodeGenContext context, StructuredProgramInfo info) + { + foreach (int attr in info.OAttributes.OrderBy(x => x)) + { + context.AppendLine($"layout (location = {attr}) out vec4 {DefaultNames.OAttributePrefix}{attr};"); + } + } + + private static void DeclareAllOutputAttributes(CodeGenContext context, StructuredProgramInfo info) + { + for (int attr = 0; attr < MaxAttributes; attr++) + { + string iq = $"{DefineNames.OutQualifierPrefixName}{attr} "; + + context.AppendLine($"layout (location = {attr}) {iq}out vec4 {DefaultNames.OAttributePrefix}{attr};"); + } + + foreach (int attr in info.OAttributes.OrderBy(x => x).Where(x => x >= MaxAttributes)) + { + context.AppendLine($"layout (location = {attr}) out vec4 {DefaultNames.OAttributePrefix}{attr};"); + } + } + + private static void AppendHelperFunction(CodeGenContext context, string filename) + { + string code = EmbeddedResources.ReadAllText(filename); + + context.AppendLine(code.Replace("\t", CodeGenContext.Tab)); + context.AppendLine(); + } + + private static string GetSamplerTypeName(SamplerType type) + { + string typeName; + + switch (type & SamplerType.Mask) + { + case SamplerType.Texture1D: typeName = "sampler1D"; break; + case SamplerType.TextureBuffer: typeName = "samplerBuffer"; break; + case SamplerType.Texture2D: typeName = "sampler2D"; break; + case SamplerType.Texture3D: typeName = "sampler3D"; break; + case SamplerType.TextureCube: typeName = "samplerCube"; break; + + default: throw new ArgumentException($"Invalid sampler type \"{type}\"."); + } + + if ((type & SamplerType.Multisample) != 0) + { + typeName += "MS"; + } + + if ((type & SamplerType.Array) != 0) + { + typeName += "Array"; + } + + if ((type & SamplerType.Shadow) != 0) + { + typeName += "Shadow"; + } + + return typeName; + } + + private static string GetImageTypeName(SamplerType type) + { + string typeName; + + switch (type & SamplerType.Mask) + { + case SamplerType.Texture1D: typeName = "image1D"; break; + case SamplerType.TextureBuffer: typeName = "imageBuffer"; break; + case SamplerType.Texture2D: typeName = "image2D"; break; + case SamplerType.Texture3D: typeName = "image3D"; break; + case SamplerType.TextureCube: typeName = "imageCube"; break; + + default: throw new ArgumentException($"Invalid sampler type \"{type}\"."); + } + + if ((type & SamplerType.Multisample) != 0) + { + typeName += "MS"; + } + + if ((type & SamplerType.Array) != 0) + { + typeName += "Array"; + } + + return typeName; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/DefaultNames.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/DefaultNames.cs similarity index 59% rename from Ryujinx.Graphics/Shader/CodeGen/Glsl/DefaultNames.cs rename to Ryujinx.Graphics.Shader/CodeGen/Glsl/DefaultNames.cs index 1d3939fb79..4da38b2de5 100644 --- a/Ryujinx.Graphics/Shader/CodeGen/Glsl/DefaultNames.cs +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/DefaultNames.cs @@ -5,13 +5,23 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl public const string LocalNamePrefix = "temp"; public const string SamplerNamePrefix = "tex"; + public const string ImageNamePrefix = "img"; public const string IAttributePrefix = "in_attr"; public const string OAttributePrefix = "out_attr"; + public const string StorageNamePrefix = "s"; + + public const string DataName = "data"; + + public const string BlockSuffix = "block"; + public const string UniformNamePrefix = "c"; public const string UniformNameSuffix = "data"; + public const string LocalMemoryName = "local_mem"; + public const string SharedMemoryName = "shared_mem"; + public const string UndefinedName = "undef"; } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/GlslGenerator.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslGenerator.cs similarity index 73% rename from Ryujinx.Graphics/Shader/CodeGen/Glsl/GlslGenerator.cs rename to Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslGenerator.cs index 4edbda8b9e..1465338e1e 100644 --- a/Ryujinx.Graphics/Shader/CodeGen/Glsl/GlslGenerator.cs +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslGenerator.cs @@ -1,7 +1,7 @@ -using Ryujinx.Graphics.Gal; using Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions; using Ryujinx.Graphics.Shader.IntermediateRepresentation; using Ryujinx.Graphics.Shader.StructuredIr; +using Ryujinx.Graphics.Shader.Translation; using System; using static Ryujinx.Graphics.Shader.CodeGen.Glsl.TypeConversion; @@ -20,7 +20,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl return new GlslProgram( context.CBufferDescriptors.ToArray(), + context.SBufferDescriptors.ToArray(), context.TextureDescriptors.ToArray(), + context.ImageDescriptors.ToArray(), context.GetCode()); } @@ -32,6 +34,32 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl Declarations.DeclareLocals(context, info); + // Some games will leave some elements of gl_Position uninitialized, + // in those cases, the elements will contain undefined values according + // to the spec, but on NVIDIA they seems to be always initialized to (0, 0, 0, 1), + // so we do explicit initialization to avoid UB on non-NVIDIA gpus. + if (context.Config.Stage == ShaderStage.Vertex) + { + context.AppendLine("gl_Position = vec4(0.0, 0.0, 0.0, 1.0);"); + } + + // Ensure that unused attributes are set, otherwise the downstream + // compiler may eliminate them. + // (Not needed for fragment shader as it is the last stage). + if (context.Config.Stage != ShaderStage.Compute && + context.Config.Stage != ShaderStage.Fragment) + { + for (int attr = 0; attr < Declarations.MaxAttributes; attr++) + { + if (info.OAttributes.Contains(attr)) + { + continue; + } + + context.AppendLine($"{DefaultNames.OAttributePrefix}{attr} = vec4(0);"); + } + } + PrintBlock(context, info.MainBlock); context.LeaveScope(); @@ -81,11 +109,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl { if (node is AstOperation operation) { - if (operation.Inst == Instruction.Return) - { - PrepareForReturn(context); - } - context.AppendLine(InstGen.GetExpression(context, operation) + ";"); } else if (node is AstAssignment assignment) @@ -97,7 +120,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl if (assignment.Destination is AstOperand operand && operand.Type == OperandType.Attribute) { - dest = OperandManager.GetOutAttributeName(operand, context.Config.Type); + dest = OperandManager.GetOutAttributeName(operand, context.Config.Stage); } else { @@ -108,6 +131,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl context.AppendLine(dest + " = " + src + ";"); } + else if (node is AstComment comment) + { + context.AppendLine("// " + comment.Comment); + } else { throw new InvalidOperationException($"Found unexpected node type \"{node?.GetType().Name ?? "null"}\"."); @@ -121,13 +148,5 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl return ReinterpretCast(context, cond, srcType, VariableType.Bool); } - - private static void PrepareForReturn(CodeGenContext context) - { - if (context.Config.Type == GalShaderType.Vertex) - { - context.AppendLine("gl_Position.xy *= flip;"); - } - } } } \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslProgram.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslProgram.cs new file mode 100644 index 0000000000..31b7f31260 --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslProgram.cs @@ -0,0 +1,26 @@ +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl +{ + class GlslProgram + { + public BufferDescriptor[] CBufferDescriptors { get; } + public BufferDescriptor[] SBufferDescriptors { get; } + public TextureDescriptor[] TextureDescriptors { get; } + public TextureDescriptor[] ImageDescriptors { get; } + + public string Code { get; } + + public GlslProgram( + BufferDescriptor[] cBufferDescriptors, + BufferDescriptor[] sBufferDescriptors, + TextureDescriptor[] textureDescriptors, + TextureDescriptor[] imageDescriptors, + string code) + { + CBufferDescriptors = cBufferDescriptors; + SBufferDescriptors = sBufferDescriptors; + TextureDescriptors = textureDescriptors; + ImageDescriptors = imageDescriptors; + Code = code; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/HelperFunctionNames.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/HelperFunctionNames.cs new file mode 100644 index 0000000000..21c435475f --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/HelperFunctionNames.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl +{ + static class HelperFunctionNames + { + public static string MultiplyHighS32 = "Helper_MultiplyHighS32"; + public static string MultiplyHighU32 = "Helper_MultiplyHighU32"; + + public static string Shuffle = "Helper_Shuffle"; + public static string ShuffleDown = "Helper_ShuffleDown"; + public static string ShuffleUp = "Helper_ShuffleUp"; + public static string ShuffleXor = "Helper_ShuffleXor"; + public static string SwizzleAdd = "Helper_SwizzleAdd"; + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighS32.glsl b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighS32.glsl new file mode 100644 index 0000000000..caad6f5696 --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighS32.glsl @@ -0,0 +1,7 @@ +int Helper_MultiplyHighS32(int x, int y) +{ + int msb; + int lsb; + imulExtended(x, y, msb, lsb); + return msb; +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighU32.glsl b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighU32.glsl new file mode 100644 index 0000000000..617a925f6b --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighU32.glsl @@ -0,0 +1,7 @@ +uint Helper_MultiplyHighU32(uint x, uint y) +{ + uint msb; + uint lsb; + umulExtended(x, y, msb, lsb); + return msb; +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/Shuffle.glsl b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/Shuffle.glsl new file mode 100644 index 0000000000..380bc581f5 --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/Shuffle.glsl @@ -0,0 +1,9 @@ +float Helper_Shuffle(float x, uint index, uint mask) +{ + uint clamp = mask & 0x1fu; + uint segMask = (mask >> 8) & 0x1fu; + uint minThreadId = gl_SubGroupInvocationARB & segMask; + uint maxThreadId = minThreadId | (clamp & ~segMask); + uint srcThreadId = (index & ~segMask) | minThreadId; + return (srcThreadId <= maxThreadId) ? readInvocationARB(x, srcThreadId) : x; +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleDown.glsl b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleDown.glsl new file mode 100644 index 0000000000..46750f20de --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleDown.glsl @@ -0,0 +1,9 @@ +float Helper_ShuffleDown(float x, uint index, uint mask) +{ + uint clamp = mask & 0x1fu; + uint segMask = (mask >> 8) & 0x1fu; + uint minThreadId = gl_SubGroupInvocationARB & segMask; + uint maxThreadId = minThreadId | (clamp & ~segMask); + uint srcThreadId = gl_SubGroupInvocationARB + index; + return (srcThreadId <= maxThreadId) ? readInvocationARB(x, srcThreadId) : x; +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleUp.glsl b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleUp.glsl new file mode 100644 index 0000000000..2bc8346972 --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleUp.glsl @@ -0,0 +1,8 @@ +float Helper_ShuffleUp(float x, uint index, uint mask) +{ + uint clamp = mask & 0x1fu; + uint segMask = (mask >> 8) & 0x1fu; + uint minThreadId = gl_SubGroupInvocationARB & segMask; + uint srcThreadId = gl_SubGroupInvocationARB - index; + return (srcThreadId >= minThreadId) ? readInvocationARB(x, srcThreadId) : x; +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleXor.glsl b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleXor.glsl new file mode 100644 index 0000000000..1049e181fa --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleXor.glsl @@ -0,0 +1,9 @@ +float Helper_ShuffleXor(float x, uint index, uint mask) +{ + uint clamp = mask & 0x1fu; + uint segMask = (mask >> 8) & 0x1fu; + uint minThreadId = gl_SubGroupInvocationARB & segMask; + uint maxThreadId = minThreadId | (clamp & ~segMask); + uint srcThreadId = gl_SubGroupInvocationARB ^ index; + return (srcThreadId <= maxThreadId) ? readInvocationARB(x, srcThreadId) : x; +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/SwizzleAdd.glsl b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/SwizzleAdd.glsl new file mode 100644 index 0000000000..7df3e57fd0 --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/SwizzleAdd.glsl @@ -0,0 +1,7 @@ +float Helper_SwizzleAdd(float x, float y, int mask) +{ + vec4 xLut = vec4(1.0, -1.0, 1.0, 0.0); + vec4 yLut = vec4(1.0, 1.0, -1.0, 1.0); + int lutIdx = mask >> int(gl_SubGroupInvocationARB & 3u) * 2; + return x * xLut[lutIdx] + y * yLut[lutIdx]; +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGen.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs similarity index 58% rename from Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGen.cs rename to Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs index b0b2ec1a99..73a71f9ee4 100644 --- a/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGen.cs +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs @@ -3,6 +3,7 @@ using Ryujinx.Graphics.Shader.StructuredIr; using System; using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenHelper; +using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenMemory; using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo; namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions @@ -17,7 +18,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions } else if (node is AstOperand operand) { - return context.OperandManager.GetExpression(operand, context.Config.Type); + return context.OperandManager.GetExpression(operand, context.Config.Stage); } throw new ArgumentException($"Invalid node type \"{node?.GetType().Name ?? "null"}\"."); @@ -31,6 +32,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions if ((info.Type & InstType.Call) != 0) { + bool atomic = (info.Type & InstType.Atomic) != 0; + int arity = (int)(info.Type & InstType.ArityMask); string args = string.Empty; @@ -44,10 +47,35 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions VariableType dstType = GetSrcVarType(inst, argIndex); - args += GetSoureExpr(context, operation.GetSource(argIndex), dstType); + if (argIndex == 0 && atomic) + { + Instruction memRegion = inst & Instruction.MrMask; + + switch (memRegion) + { + case Instruction.MrShared: args += LoadShared (context, operation); break; + case Instruction.MrStorage: args += LoadStorage(context, operation); break; + + default: throw new InvalidOperationException($"Invalid memory region \"{memRegion}\"."); + } + + // We use the first 2 operands above. + argIndex++; + } + else + { + args += GetSoureExpr(context, operation.GetSource(argIndex), dstType); + } } - return info.OpName + "(" + args + ")"; + if (inst == Instruction.Ballot) + { + return $"unpackUint2x32({info.OpName}({args})).x"; + } + else + { + return info.OpName + "(" + args + ")"; + } } else if ((info.Type & InstType.Op) != 0) { @@ -87,12 +115,39 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions { switch (inst) { + case Instruction.ImageStore: + return InstGenMemory.ImageStore(context, operation); + + case Instruction.LoadAttribute: + return InstGenMemory.LoadAttribute(context, operation); + case Instruction.LoadConstant: return InstGenMemory.LoadConstant(context, operation); + case Instruction.LoadLocal: + return InstGenMemory.LoadLocal(context, operation); + + case Instruction.LoadShared: + return InstGenMemory.LoadShared(context, operation); + + case Instruction.LoadStorage: + return InstGenMemory.LoadStorage(context, operation); + + case Instruction.Lod: + return InstGenMemory.Lod(context, operation); + case Instruction.PackHalf2x16: return InstGenPacking.PackHalf2x16(context, operation); + case Instruction.StoreLocal: + return InstGenMemory.StoreLocal(context, operation); + + case Instruction.StoreShared: + return InstGenMemory.StoreShared(context, operation); + + case Instruction.StoreStorage: + return InstGenMemory.StoreStorage(context, operation); + case Instruction.TextureSample: return InstGenMemory.TextureSample(context, operation); diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs similarity index 70% rename from Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs rename to Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs index 9855cd9137..8dec34997a 100644 --- a/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs @@ -13,8 +13,21 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions { _infoTbl = new InstInfo[(int)Instruction.Count]; + Add(Instruction.AtomicAdd, InstType.AtomicBinary, "atomicAdd"); + Add(Instruction.AtomicAnd, InstType.AtomicBinary, "atomicAnd"); + Add(Instruction.AtomicCompareAndSwap, InstType.AtomicTernary, "atomicCompSwap"); + Add(Instruction.AtomicMaxS32, InstType.AtomicBinary, "atomicMax"); + Add(Instruction.AtomicMaxU32, InstType.AtomicBinary, "atomicMax"); + Add(Instruction.AtomicMinS32, InstType.AtomicBinary, "atomicMin"); + Add(Instruction.AtomicMinU32, InstType.AtomicBinary, "atomicMin"); + Add(Instruction.AtomicOr, InstType.AtomicBinary, "atomicOr"); + Add(Instruction.AtomicSwap, InstType.AtomicBinary, "atomicExchange"); + Add(Instruction.AtomicXor, InstType.AtomicBinary, "atomicXor"); Add(Instruction.Absolute, InstType.CallUnary, "abs"); Add(Instruction.Add, InstType.OpBinaryCom, "+", 2); + Add(Instruction.Ballot, InstType.CallUnary, "ballotARB"); + Add(Instruction.Barrier, InstType.CallNullary, "barrier"); + Add(Instruction.BitCount, InstType.CallUnary, "bitCount"); Add(Instruction.BitfieldExtractS32, InstType.CallTernary, "bitfieldExtract"); Add(Instruction.BitfieldExtractU32, InstType.CallTernary, "bitfieldExtract"); Add(Instruction.BitfieldInsert, InstType.CallQuaternary, "bitfieldInsert"); @@ -38,18 +51,31 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions Add(Instruction.CompareNotEqual, InstType.OpBinaryCom, "!=", 5); Add(Instruction.ConditionalSelect, InstType.OpTernary, "?:", 12); Add(Instruction.ConvertFPToS32, InstType.CallUnary, "int"); + Add(Instruction.ConvertFPToU32, InstType.CallUnary, "uint"); Add(Instruction.ConvertS32ToFP, InstType.CallUnary, "float"); Add(Instruction.ConvertU32ToFP, InstType.CallUnary, "float"); Add(Instruction.Cosine, InstType.CallUnary, "cos"); + Add(Instruction.Ddx, InstType.CallUnary, "dFdx"); + Add(Instruction.Ddy, InstType.CallUnary, "dFdy"); Add(Instruction.Discard, InstType.OpNullary, "discard"); Add(Instruction.Divide, InstType.OpBinary, "/", 1); Add(Instruction.EmitVertex, InstType.CallNullary, "EmitVertex"); Add(Instruction.EndPrimitive, InstType.CallNullary, "EndPrimitive"); Add(Instruction.ExponentB2, InstType.CallUnary, "exp2"); + Add(Instruction.FindFirstSetS32, InstType.CallUnary, "findMSB"); + Add(Instruction.FindFirstSetU32, InstType.CallUnary, "findMSB"); Add(Instruction.Floor, InstType.CallUnary, "floor"); Add(Instruction.FusedMultiplyAdd, InstType.CallTernary, "fma"); + Add(Instruction.GroupMemoryBarrier, InstType.CallNullary, "groupMemoryBarrier"); + Add(Instruction.ImageLoad, InstType.Special); + Add(Instruction.ImageStore, InstType.Special); Add(Instruction.IsNan, InstType.CallUnary, "isnan"); + Add(Instruction.LoadAttribute, InstType.Special); Add(Instruction.LoadConstant, InstType.Special); + Add(Instruction.LoadLocal, InstType.Special); + Add(Instruction.LoadShared, InstType.Special); + Add(Instruction.LoadStorage, InstType.Special); + Add(Instruction.Lod, InstType.Special); Add(Instruction.LogarithmB2, InstType.CallUnary, "log2"); Add(Instruction.LogicalAnd, InstType.OpBinaryCom, "&&", 9); Add(Instruction.LogicalExclusiveOr, InstType.OpBinaryCom, "^^", 10); @@ -61,21 +87,36 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions Add(Instruction.ShiftLeft, InstType.OpBinary, "<<", 3); Add(Instruction.ShiftRightS32, InstType.OpBinary, ">>", 3); Add(Instruction.ShiftRightU32, InstType.OpBinary, ">>", 3); + Add(Instruction.Shuffle, InstType.CallTernary, HelperFunctionNames.Shuffle); + Add(Instruction.ShuffleDown, InstType.CallTernary, HelperFunctionNames.ShuffleDown); + Add(Instruction.ShuffleUp, InstType.CallTernary, HelperFunctionNames.ShuffleUp); + Add(Instruction.ShuffleXor, InstType.CallTernary, HelperFunctionNames.ShuffleXor); Add(Instruction.Maximum, InstType.CallBinary, "max"); Add(Instruction.MaximumU32, InstType.CallBinary, "max"); + Add(Instruction.MemoryBarrier, InstType.CallNullary, "memoryBarrier"); Add(Instruction.Minimum, InstType.CallBinary, "min"); Add(Instruction.MinimumU32, InstType.CallBinary, "min"); Add(Instruction.Multiply, InstType.OpBinaryCom, "*", 1); + Add(Instruction.MultiplyHighS32, InstType.CallBinary, HelperFunctionNames.MultiplyHighS32); + Add(Instruction.MultiplyHighU32, InstType.CallBinary, HelperFunctionNames.MultiplyHighU32); Add(Instruction.Negate, InstType.OpUnary, "-", 0); Add(Instruction.ReciprocalSquareRoot, InstType.CallUnary, "inversesqrt"); Add(Instruction.Return, InstType.OpNullary, "return"); + Add(Instruction.Round, InstType.CallUnary, "roundEven"); Add(Instruction.Sine, InstType.CallUnary, "sin"); Add(Instruction.SquareRoot, InstType.CallUnary, "sqrt"); + Add(Instruction.StoreLocal, InstType.Special); + Add(Instruction.StoreShared, InstType.Special); + Add(Instruction.StoreStorage, InstType.Special); Add(Instruction.Subtract, InstType.OpBinary, "-", 2); + Add(Instruction.SwizzleAdd, InstType.CallTernary, HelperFunctionNames.SwizzleAdd); Add(Instruction.TextureSample, InstType.Special); Add(Instruction.TextureSize, InstType.Special); Add(Instruction.Truncate, InstType.CallUnary, "trunc"); Add(Instruction.UnpackHalf2x16, InstType.Special); + Add(Instruction.VoteAll, InstType.CallUnary, "allInvocationsARB"); + Add(Instruction.VoteAllEqual, InstType.CallUnary, "allInvocationsEqualARB"); + Add(Instruction.VoteAny, InstType.CallUnary, "anyInvocationARB"); } private static void Add(Instruction inst, InstType flags, string opName = null, int precedence = 0) diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs new file mode 100644 index 0000000000..5687ce7e16 --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs @@ -0,0 +1,514 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; +using System; + +using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenHelper; +using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions +{ + static class InstGenMemory + { + public static string ImageStore(CodeGenContext context, AstOperation operation) + { + AstTextureOperation texOp = (AstTextureOperation)operation; + + bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; + + bool isArray = (texOp.Type & SamplerType.Array) != 0; + bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; + + string texCall = "imageStore"; + + int srcIndex = isBindless ? 1 : 0; + + string Src(VariableType type) + { + return GetSoureExpr(context, texOp.GetSource(srcIndex++), type); + } + + string indexExpr = null; + + if (isIndexed) + { + indexExpr = Src(VariableType.S32); + } + + string imageName = OperandManager.GetImageName(context.Config.Stage, texOp, indexExpr); + + texCall += "(" + imageName; + + int coordsCount = texOp.Type.GetDimensions(); + + int pCount = coordsCount; + + int arrayIndexElem = -1; + + if (isArray) + { + arrayIndexElem = pCount++; + } + + void Append(string str) + { + texCall += ", " + str; + } + + if (pCount > 1) + { + string[] elems = new string[pCount]; + + for (int index = 0; index < pCount; index++) + { + elems[index] = Src(VariableType.S32); + } + + Append("ivec" + pCount + "(" + string.Join(", ", elems) + ")"); + } + else + { + Append(Src(VariableType.S32)); + } + + string[] cElems = new string[4]; + + for (int index = 0; index < 4; index++) + { + if (srcIndex < texOp.SourcesCount) + { + cElems[index] = Src(VariableType.F32); + } + else + { + cElems[index] = NumberFormatter.FormatFloat(0); + } + } + + Append("vec4(" + string.Join(", ", cElems) + ")"); + + texCall += ")"; + + return texCall; + } + + public static string LoadAttribute(CodeGenContext context, AstOperation operation) + { + IAstNode src1 = operation.GetSource(0); + IAstNode src2 = operation.GetSource(1); + + if (!(src1 is AstOperand attr) || attr.Type != OperandType.Attribute) + { + throw new InvalidOperationException("First source of LoadAttribute must be a attribute."); + } + + string indexExpr = GetSoureExpr(context, src2, GetSrcVarType(operation.Inst, 1)); + + return OperandManager.GetAttributeName(attr, context.Config.Stage, isOutAttr: false, indexExpr); + } + + public static string LoadConstant(CodeGenContext context, AstOperation operation) + { + IAstNode src1 = operation.GetSource(0); + IAstNode src2 = operation.GetSource(1); + + string offsetExpr = GetSoureExpr(context, src2, GetSrcVarType(operation.Inst, 1)); + + offsetExpr = Enclose(offsetExpr, src2, Instruction.ShiftRightS32, isLhs: true); + + return OperandManager.GetConstantBufferName(src1, offsetExpr, context.Config.Stage); + } + + public static string LoadLocal(CodeGenContext context, AstOperation operation) + { + return LoadLocalOrShared(context, operation, DefaultNames.LocalMemoryName); + } + + public static string LoadShared(CodeGenContext context, AstOperation operation) + { + return LoadLocalOrShared(context, operation, DefaultNames.SharedMemoryName); + } + + private static string LoadLocalOrShared(CodeGenContext context, AstOperation operation, string arrayName) + { + IAstNode src1 = operation.GetSource(0); + + string offsetExpr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 0)); + + return $"{arrayName}[{offsetExpr}]"; + } + + public static string LoadStorage(CodeGenContext context, AstOperation operation) + { + IAstNode src1 = operation.GetSource(0); + IAstNode src2 = operation.GetSource(1); + + string indexExpr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 0)); + string offsetExpr = GetSoureExpr(context, src2, GetSrcVarType(operation.Inst, 1)); + + return GetStorageBufferAccessor(indexExpr, offsetExpr, context.Config.Stage); + } + + public static string Lod(CodeGenContext context, AstOperation operation) + { + AstTextureOperation texOp = (AstTextureOperation)operation; + + int coordsCount = texOp.Type.GetDimensions(); + + bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; + + bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; + + string indexExpr = null; + + if (isIndexed) + { + indexExpr = GetSoureExpr(context, texOp.GetSource(0), VariableType.S32); + } + + string samplerName = OperandManager.GetSamplerName(context.Config.Stage, texOp, indexExpr); + + int coordsIndex = isBindless || isIndexed ? 1 : 0; + + string coordsExpr; + + if (coordsCount > 1) + { + string[] elems = new string[coordsCount]; + + for (int index = 0; index < coordsCount; index++) + { + elems[index] = GetSoureExpr(context, texOp.GetSource(coordsIndex + index), VariableType.F32); + } + + coordsExpr = "vec" + coordsCount + "(" + string.Join(", ", elems) + ")"; + } + else + { + coordsExpr = GetSoureExpr(context, texOp.GetSource(coordsIndex), VariableType.F32); + } + + return $"textureQueryLod({samplerName}, {coordsExpr}){GetMask(texOp.Index)}"; + } + + public static string StoreLocal(CodeGenContext context, AstOperation operation) + { + return StoreLocalOrShared(context, operation, DefaultNames.LocalMemoryName); + } + + public static string StoreShared(CodeGenContext context, AstOperation operation) + { + return StoreLocalOrShared(context, operation, DefaultNames.SharedMemoryName); + } + + private static string StoreLocalOrShared(CodeGenContext context, AstOperation operation, string arrayName) + { + IAstNode src1 = operation.GetSource(0); + IAstNode src2 = operation.GetSource(1); + + string offsetExpr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 0)); + + VariableType srcType = OperandManager.GetNodeDestType(src2); + + string src = TypeConversion.ReinterpretCast(context, src2, srcType, VariableType.U32); + + return $"{arrayName}[{offsetExpr}] = {src}"; + } + + public static string StoreStorage(CodeGenContext context, AstOperation operation) + { + IAstNode src1 = operation.GetSource(0); + IAstNode src2 = operation.GetSource(1); + IAstNode src3 = operation.GetSource(2); + + string indexExpr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 0)); + string offsetExpr = GetSoureExpr(context, src2, GetSrcVarType(operation.Inst, 1)); + + VariableType srcType = OperandManager.GetNodeDestType(src3); + + string src = TypeConversion.ReinterpretCast(context, src3, srcType, VariableType.U32); + + string sb = GetStorageBufferAccessor(indexExpr, offsetExpr, context.Config.Stage); + + return $"{sb} = {src}"; + } + + public static string TextureSample(CodeGenContext context, AstOperation operation) + { + AstTextureOperation texOp = (AstTextureOperation)operation; + + bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; + bool isGather = (texOp.Flags & TextureFlags.Gather) != 0; + bool hasDerivatives = (texOp.Flags & TextureFlags.Derivatives) != 0; + bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0; + bool hasLodBias = (texOp.Flags & TextureFlags.LodBias) != 0; + bool hasLodLevel = (texOp.Flags & TextureFlags.LodLevel) != 0; + bool hasOffset = (texOp.Flags & TextureFlags.Offset) != 0; + bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0; + + bool isArray = (texOp.Type & SamplerType.Array) != 0; + bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; + bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0; + bool isShadow = (texOp.Type & SamplerType.Shadow) != 0; + + // This combination is valid, but not available on GLSL. + // For now, ignore the LOD level and do a normal sample. + // TODO: How to implement it properly? + if (hasLodLevel && isArray && isShadow) + { + hasLodLevel = false; + } + + string texCall = intCoords ? "texelFetch" : "texture"; + + if (isGather) + { + texCall += "Gather"; + } + else if (hasDerivatives) + { + texCall += "Grad"; + } + else if (hasLodLevel && !intCoords) + { + texCall += "Lod"; + } + + if (hasOffset) + { + texCall += "Offset"; + } + else if (hasOffsets) + { + texCall += "Offsets"; + } + + int srcIndex = isBindless ? 1 : 0; + + string Src(VariableType type) + { + return GetSoureExpr(context, texOp.GetSource(srcIndex++), type); + } + + string indexExpr = null; + + if (isIndexed) + { + indexExpr = Src(VariableType.S32); + } + + string samplerName = OperandManager.GetSamplerName(context.Config.Stage, texOp, indexExpr); + + texCall += "(" + samplerName; + + int coordsCount = texOp.Type.GetDimensions(); + + int pCount = coordsCount; + + int arrayIndexElem = -1; + + if (isArray) + { + arrayIndexElem = pCount++; + } + + // The sampler 1D shadow overload expects a + // dummy value on the middle of the vector, who knows why... + bool hasDummy1DShadowElem = texOp.Type == (SamplerType.Texture1D | SamplerType.Shadow); + + if (hasDummy1DShadowElem) + { + pCount++; + } + + if (isShadow && !isGather) + { + pCount++; + } + + // On textureGather*, the comparison value is + // always specified as an extra argument. + bool hasExtraCompareArg = isShadow && isGather; + + if (pCount == 5) + { + pCount = 4; + + hasExtraCompareArg = true; + } + + void Append(string str) + { + texCall += ", " + str; + } + + VariableType coordType = intCoords ? VariableType.S32 : VariableType.F32; + + string AssemblePVector(int count) + { + if (count > 1) + { + string[] elems = new string[count]; + + for (int index = 0; index < count; index++) + { + if (arrayIndexElem == index) + { + elems[index] = Src(VariableType.S32); + + if (!intCoords) + { + elems[index] = "float(" + elems[index] + ")"; + } + } + else if (index == 1 && hasDummy1DShadowElem) + { + elems[index] = NumberFormatter.FormatFloat(0); + } + else + { + elems[index] = Src(coordType); + } + } + + string prefix = intCoords ? "i" : string.Empty; + + return prefix + "vec" + count + "(" + string.Join(", ", elems) + ")"; + } + else + { + return Src(coordType); + } + } + + Append(AssemblePVector(pCount)); + + string AssembleDerivativesVector(int count) + { + if (count > 1) + { + string[] elems = new string[count]; + + for (int index = 0; index < count; index++) + { + elems[index] = Src(VariableType.F32); + } + + return "vec" + count + "(" + string.Join(", ", elems) + ")"; + } + else + { + return Src(VariableType.F32); + } + } + + if (hasExtraCompareArg) + { + Append(Src(VariableType.F32)); + } + + if (hasDerivatives) + { + Append(AssembleDerivativesVector(coordsCount)); // dPdx + Append(AssembleDerivativesVector(coordsCount)); // dPdy + } + + if (isMultisample) + { + Append(Src(VariableType.S32)); + } + else if (hasLodLevel) + { + Append(Src(coordType)); + } + + string AssembleOffsetVector(int count) + { + if (count > 1) + { + string[] elems = new string[count]; + + for (int index = 0; index < count; index++) + { + elems[index] = Src(VariableType.S32); + } + + return "ivec" + count + "(" + string.Join(", ", elems) + ")"; + } + else + { + return Src(VariableType.S32); + } + } + + if (hasOffset) + { + Append(AssembleOffsetVector(coordsCount)); + } + else if (hasOffsets) + { + texCall += $", ivec{coordsCount}[4]("; + + texCall += AssembleOffsetVector(coordsCount) + ", "; + texCall += AssembleOffsetVector(coordsCount) + ", "; + texCall += AssembleOffsetVector(coordsCount) + ", "; + texCall += AssembleOffsetVector(coordsCount) + ")"; + } + + if (hasLodBias) + { + Append(Src(VariableType.F32)); + } + + // textureGather* optional extra component index, + // not needed for shadow samplers. + if (isGather && !isShadow) + { + Append(Src(VariableType.S32)); + } + + texCall += ")" + (isGather || !isShadow ? GetMask(texOp.Index) : ""); + + return texCall; + } + + public static string TextureSize(CodeGenContext context, AstOperation operation) + { + AstTextureOperation texOp = (AstTextureOperation)operation; + + bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; + + bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; + + string indexExpr = null; + + if (isIndexed) + { + indexExpr = GetSoureExpr(context, texOp.GetSource(0), VariableType.S32); + } + + string samplerName = OperandManager.GetSamplerName(context.Config.Stage, texOp, indexExpr); + + int lodSrcIndex = isBindless || isIndexed ? 1 : 0; + + IAstNode lod = operation.GetSource(lodSrcIndex); + + string lodExpr = GetSoureExpr(context, lod, GetSrcVarType(operation.Inst, lodSrcIndex)); + + return $"textureSize({samplerName}, {lodExpr}){GetMask(texOp.Index)}"; + } + + private static string GetStorageBufferAccessor(string slotExpr, string offsetExpr, ShaderStage stage) + { + string sbName = OperandManager.GetShaderStagePrefix(stage); + + sbName += "_" + DefaultNames.StorageNamePrefix; + + return $"{sbName}[{slotExpr}].{DefaultNames.DataName}[{offsetExpr}]"; + } + + private static string GetMask(int index) + { + return '.' + "rgba".Substring(index, 1); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGenPacking.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenPacking.cs similarity index 75% rename from Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGenPacking.cs rename to Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenPacking.cs index 4a40032c57..e5167f93f8 100644 --- a/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGenPacking.cs +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenPacking.cs @@ -24,22 +24,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions string srcExpr = GetSoureExpr(context, src, GetSrcVarType(operation.Inst, 0)); - return $"unpackHalf2x16({srcExpr}){GetMask(operation.ComponentMask)}"; + return $"unpackHalf2x16({srcExpr}){GetMask(operation.Index)}"; } - private static string GetMask(int compMask) + private static string GetMask(int index) { - string mask = "."; - - for (int index = 0; index < 2; index++) - { - if ((compMask & (1 << index)) != 0) - { - mask += "xy".Substring(index, 1); - } - } - - return mask; + return '.' + "xy".Substring(index, 1); } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstInfo.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstInfo.cs similarity index 100% rename from Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstInfo.cs rename to Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstInfo.cs diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstType.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstType.cs similarity index 63% rename from Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstType.cs rename to Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstType.cs index 121cd07906..84e36cdd62 100644 --- a/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstType.cs +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstType.cs @@ -8,8 +8,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions OpNullary = Op | 0, OpUnary = Op | 1, OpBinary = Op | 2, + OpBinaryCom = Op | 2 | Commutative, OpTernary = Op | 3, - OpBinaryCom = OpBinary | Commutative, CallNullary = Call | 0, CallUnary = Call | 1, @@ -17,10 +17,16 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions CallTernary = Call | 3, CallQuaternary = Call | 4, + // The atomic instructions have one extra operand, + // for the storage slot and offset pair. + AtomicBinary = Call | Atomic | 3, + AtomicTernary = Call | Atomic | 4, + Commutative = 1 << 8, Op = 1 << 9, Call = 1 << 10, - Special = 1 << 11, + Atomic = 1 << 11, + Special = 1 << 12, ArityMask = 0xff } diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/NumberFormatter.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/NumberFormatter.cs similarity index 100% rename from Ryujinx.Graphics/Shader/CodeGen/Glsl/NumberFormatter.cs rename to Ryujinx.Graphics.Shader/CodeGen/Glsl/NumberFormatter.cs diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/OperandManager.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs similarity index 60% rename from Ryujinx.Graphics/Shader/CodeGen/Glsl/OperandManager.cs rename to Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs index 19f7185e97..4c9d5b5507 100644 --- a/Ryujinx.Graphics/Shader/CodeGen/Glsl/OperandManager.cs +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs @@ -1,6 +1,6 @@ -using Ryujinx.Graphics.Gal; using Ryujinx.Graphics.Shader.IntermediateRepresentation; using Ryujinx.Graphics.Shader.StructuredIr; +using Ryujinx.Graphics.Shader.Translation; using System; using System.Collections.Generic; @@ -10,7 +10,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl { class OperandManager { - private static string[] _stagePrefixes = new string[] { "vp", "tcp", "tep", "gp", "fp" }; + private static string[] _stagePrefixes = new string[] { "cp", "vp", "tcp", "tep", "gp", "fp" }; private struct BuiltInAttribute { @@ -46,10 +46,24 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl { AttributeConsts.PointCoordY, new BuiltInAttribute("gl_PointCoord.y", VariableType.F32) }, { AttributeConsts.TessCoordX, new BuiltInAttribute("gl_TessCoord.x", VariableType.F32) }, { AttributeConsts.TessCoordY, new BuiltInAttribute("gl_TessCoord.y", VariableType.F32) }, - { AttributeConsts.InstanceId, new BuiltInAttribute("instance", VariableType.S32) }, + { AttributeConsts.InstanceId, new BuiltInAttribute("gl_InstanceID", VariableType.S32) }, { AttributeConsts.VertexId, new BuiltInAttribute("gl_VertexID", VariableType.S32) }, { AttributeConsts.FrontFacing, new BuiltInAttribute("gl_FrontFacing", VariableType.Bool) }, - { AttributeConsts.FragmentOutputDepth, new BuiltInAttribute("gl_FragDepth", VariableType.F32) } + + // Special. + { AttributeConsts.FragmentOutputDepth, new BuiltInAttribute("gl_FragDepth", VariableType.F32) }, + { AttributeConsts.ThreadIdX, new BuiltInAttribute("gl_LocalInvocationID.x", VariableType.U32) }, + { AttributeConsts.ThreadIdY, new BuiltInAttribute("gl_LocalInvocationID.y", VariableType.U32) }, + { AttributeConsts.ThreadIdZ, new BuiltInAttribute("gl_LocalInvocationID.z", VariableType.U32) }, + { AttributeConsts.CtaIdX, new BuiltInAttribute("gl_WorkGroupID.x", VariableType.U32) }, + { AttributeConsts.CtaIdY, new BuiltInAttribute("gl_WorkGroupID.y", VariableType.U32) }, + { AttributeConsts.CtaIdZ, new BuiltInAttribute("gl_WorkGroupID.z", VariableType.U32) }, + { AttributeConsts.LaneId, new BuiltInAttribute("gl_SubGroupInvocationARB", VariableType.U32) }, + { AttributeConsts.EqMask, new BuiltInAttribute("unpackUint2x32(gl_SubGroupEqMaskARB).x", VariableType.U32) }, + { AttributeConsts.GeMask, new BuiltInAttribute("unpackUint2x32(gl_SubGroupGeMaskARB).x", VariableType.U32) }, + { AttributeConsts.GtMask, new BuiltInAttribute("unpackUint2x32(gl_SubGroupGtMaskARB).x", VariableType.U32) }, + { AttributeConsts.LeMask, new BuiltInAttribute("unpackUint2x32(gl_SubGroupLeMaskARB).x", VariableType.U32) }, + { AttributeConsts.LtMask, new BuiltInAttribute("unpackUint2x32(gl_SubGroupLtMaskARB).x", VariableType.U32) }, }; private Dictionary _locals; @@ -68,18 +82,18 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl return name; } - public string GetExpression(AstOperand operand, GalShaderType shaderType) + public string GetExpression(AstOperand operand, ShaderStage stage) { switch (operand.Type) { case OperandType.Attribute: - return GetAttributeName(operand, shaderType); + return GetAttributeName(operand, stage); case OperandType.Constant: return NumberFormatter.FormatInt(operand.Value); case OperandType.ConstantBuffer: - return GetConstantBufferName(operand, shaderType); + return GetConstantBufferName(operand.CbufSlot, operand.CbufOffset, stage); case OperandType.LocalVariable: return _locals[operand]; @@ -91,36 +105,36 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl throw new ArgumentException($"Invalid operand type \"{operand.Type}\"."); } - public static string GetConstantBufferName(AstOperand cbuf, GalShaderType shaderType) + public static string GetConstantBufferName(int slot, int offset, ShaderStage stage) { - string ubName = GetUbName(shaderType, cbuf.CbufSlot); + string ubName = GetUbName(stage, slot); - ubName += "[" + (cbuf.CbufOffset >> 2) + "]"; + ubName += "[" + (offset >> 2) + "]"; - return ubName + "." + GetSwizzleMask(cbuf.CbufOffset & 3); + return ubName + "." + GetSwizzleMask(offset & 3); } - public static string GetConstantBufferName(IAstNode slot, string offsetExpr, GalShaderType shaderType) + public static string GetConstantBufferName(IAstNode slot, string offsetExpr, ShaderStage stage) { // Non-constant slots are not supported. // It is expected that upstream stages are never going to generate non-constant // slot access. AstOperand operand = (AstOperand)slot; - string ubName = GetUbName(shaderType, operand.Value); + string ubName = GetUbName(stage, operand.Value); - string index0 = "[" + offsetExpr + " >> 4]"; - string index1 = "[" + offsetExpr + " >> 2 & 3]"; + string index0 = "[" + offsetExpr + " >> 2]"; + string index1 = "[" + offsetExpr + " & 3]"; return ubName + index0 + index1; } - public static string GetOutAttributeName(AstOperand attr, GalShaderType shaderType) + public static string GetOutAttributeName(AstOperand attr, ShaderStage stage) { - return GetAttributeName(attr, shaderType, isOutAttr: true); + return GetAttributeName(attr, stage, isOutAttr: true); } - private static string GetAttributeName(AstOperand attr, GalShaderType shaderType, bool isOutAttr = false) + public static string GetAttributeName(AstOperand attr, ShaderStage stage, bool isOutAttr = false, string indexExpr = "0") { int value = attr.Value; @@ -137,9 +151,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl string name = $"{prefix}{(value >> 4)}"; - if (shaderType == GalShaderType.Geometry && !isOutAttr) + if (stage == ShaderStage.Geometry && !isOutAttr) { - name += "[0]"; + name += $"[{indexExpr}]"; } name += "." + swzMask; @@ -158,7 +172,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl else if (_builtInAttributes.TryGetValue(value & ~3, out BuiltInAttribute builtInAttr)) { // TODO: There must be a better way to handle this... - if (shaderType == GalShaderType.Fragment) + if (stage == ShaderStage.Fragment) { switch (value & ~3) { @@ -171,9 +185,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl string name = builtInAttr.Name; - if (shaderType == GalShaderType.Geometry && !isOutAttr) + if (stage == ShaderStage.Geometry && !isOutAttr) { - name = "gl_in[0]." + name; + name = $"gl_in[{indexExpr}].{name}"; } return name; @@ -185,16 +199,16 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl return isOutAttr ? "// bad_attr0x" + value.ToString("X") : "0.0"; } - public static string GetUbName(GalShaderType shaderType, int slot) + public static string GetUbName(ShaderStage stage, int slot) { - string ubName = GetShaderStagePrefix(shaderType); + string ubName = GetShaderStagePrefix(stage); ubName += "_" + DefaultNames.UniformNamePrefix + slot; return ubName + "_" + DefaultNames.UniformNameSuffix; } - public static string GetSamplerName(GalShaderType shaderType, AstTextureOperation texOp) + public static string GetSamplerName(ShaderStage stage, AstTextureOperation texOp, string indexExpr) { string suffix; @@ -206,15 +220,39 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl } else { - suffix = (texOp.Handle - 8).ToString(); + suffix = texOp.Handle.ToString(); + + if ((texOp.Type & SamplerType.Indexed) != 0) + { + suffix += $"a[{indexExpr}]"; + } } - return GetShaderStagePrefix(shaderType) + "_" + DefaultNames.SamplerNamePrefix + suffix; + return GetShaderStagePrefix(stage) + "_" + DefaultNames.SamplerNamePrefix + suffix; } - public static string GetShaderStagePrefix(GalShaderType shaderType) + public static string GetImageName(ShaderStage stage, AstTextureOperation texOp, string indexExpr) { - return _stagePrefixes[(int)shaderType]; + string suffix = texOp.Handle.ToString(); + + if ((texOp.Type & SamplerType.Indexed) != 0) + { + suffix += $"a[{indexExpr}]"; + } + + return GetShaderStagePrefix(stage) + "_" + DefaultNames.ImageNamePrefix + suffix; + } + + public static string GetShaderStagePrefix(ShaderStage stage) + { + int index = (int)stage; + + if ((uint)index >= _stagePrefixes.Length) + { + return "invalid"; + } + + return _stagePrefixes[index]; } private static string GetSwizzleMask(int value) @@ -226,24 +264,37 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl { if (node is AstOperation operation) { + // Load attribute basically just returns the attribute value. + // Some built-in attributes may have different types, so we need + // to return the type based on the attribute that is being read. + if (operation.Inst == Instruction.LoadAttribute) + { + return GetOperandVarType((AstOperand)operation.GetSource(0)); + } + return GetDestVarType(operation.Inst); } else if (node is AstOperand operand) { - if (operand.Type == OperandType.Attribute) - { - if (_builtInAttributes.TryGetValue(operand.Value & ~3, out BuiltInAttribute builtInAttr)) - { - return builtInAttr.Type; - } - } - - return OperandInfo.GetVarType(operand); + return GetOperandVarType(operand); } else { throw new ArgumentException($"Invalid node type \"{node?.GetType().Name ?? "null"}\"."); } } + + private static VariableType GetOperandVarType(AstOperand operand) + { + if (operand.Type == OperandType.Attribute) + { + if (_builtInAttributes.TryGetValue(operand.Value & ~3, out BuiltInAttribute builtInAttr)) + { + return builtInAttr.Type; + } + } + + return OperandInfo.GetVarType(operand); + } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/TypeConversion.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/TypeConversion.cs similarity index 100% rename from Ryujinx.Graphics/Shader/CodeGen/Glsl/TypeConversion.cs rename to Ryujinx.Graphics.Shader/CodeGen/Glsl/TypeConversion.cs diff --git a/Ryujinx.Graphics.Shader/Decoders/AtomicOp.cs b/Ryujinx.Graphics.Shader/Decoders/AtomicOp.cs new file mode 100644 index 0000000000..065a57c447 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/AtomicOp.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum AtomicOp + { + Add = 0, + Minimum = 1, + Maximum = 2, + Increment = 3, + Decrement = 4, + BitwiseAnd = 5, + BitwiseOr = 6, + BitwiseExclusiveOr = 7, + Swap = 8 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/BarrierLevel.cs b/Ryujinx.Graphics.Shader/Decoders/BarrierLevel.cs new file mode 100644 index 0000000000..95c71e0097 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/BarrierLevel.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum BarrierLevel + { + Cta = 0, + Gl = 1, + Sys = 2, + Vc = 3 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/BarrierMode.cs b/Ryujinx.Graphics.Shader/Decoders/BarrierMode.cs new file mode 100644 index 0000000000..a058cbbd71 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/BarrierMode.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum BarrierMode + { + ReductionPopCount = 2, + Scan = 3, + ReductionAnd = 0xa, + ReductionOr = 0x12, + Sync = 0x80, + Arrive = 0x81 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/BitfieldExtensions.cs b/Ryujinx.Graphics.Shader/Decoders/BitfieldExtensions.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/BitfieldExtensions.cs rename to Ryujinx.Graphics.Shader/Decoders/BitfieldExtensions.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/Block.cs b/Ryujinx.Graphics.Shader/Decoders/Block.cs similarity index 82% rename from Ryujinx.Graphics/Shader/Decoders/Block.cs rename to Ryujinx.Graphics.Shader/Decoders/Block.cs index b5e610d713..e147023736 100644 --- a/Ryujinx.Graphics/Shader/Decoders/Block.cs +++ b/Ryujinx.Graphics.Shader/Decoders/Block.cs @@ -11,15 +11,17 @@ namespace Ryujinx.Graphics.Shader.Decoders public Block Next { get; set; } public Block Branch { get; set; } - public List OpCodes { get; } - public List SsyOpCodes { get; } + public OpCodeBranchIndir BrIndir { get; set; } + + public List OpCodes { get; } + public List PushOpCodes { get; } public Block(ulong address) { Address = address; - OpCodes = new List(); - SsyOpCodes = new List(); + OpCodes = new List(); + PushOpCodes = new List(); } public void Split(Block rightBlock) @@ -45,7 +47,7 @@ namespace Ryujinx.Graphics.Shader.Decoders rightBlock.OpCodes.AddRange(OpCodes.GetRange(splitIndex, splitCount)); - rightBlock.UpdateSsyOpCodes(); + rightBlock.UpdatePushOps(); EndAddress = rightBlock.Address; @@ -54,7 +56,7 @@ namespace Ryujinx.Graphics.Shader.Decoders OpCodes.RemoveRange(splitIndex, splitCount); - UpdateSsyOpCodes(); + UpdatePushOps(); } private static int BinarySearch(List opCodes, ulong address) @@ -99,18 +101,18 @@ namespace Ryujinx.Graphics.Shader.Decoders return null; } - public void UpdateSsyOpCodes() + public void UpdatePushOps() { - SsyOpCodes.Clear(); + PushOpCodes.Clear(); for (int index = 0; index < OpCodes.Count; index++) { - if (!(OpCodes[index] is OpCodeSsy op)) + if (!(OpCodes[index] is OpCodePush op)) { continue; } - SsyOpCodes.Add(op); + PushOpCodes.Add(op); } } } diff --git a/Ryujinx.Graphics/Shader/Decoders/Condition.cs b/Ryujinx.Graphics.Shader/Decoders/Condition.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/Condition.cs rename to Ryujinx.Graphics.Shader/Decoders/Condition.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/ConditionalOperation.cs b/Ryujinx.Graphics.Shader/Decoders/ConditionalOperation.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/ConditionalOperation.cs rename to Ryujinx.Graphics.Shader/Decoders/ConditionalOperation.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/Decoder.cs b/Ryujinx.Graphics.Shader/Decoders/Decoder.cs similarity index 61% rename from Ryujinx.Graphics/Shader/Decoders/Decoder.cs rename to Ryujinx.Graphics.Shader/Decoders/Decoder.cs index 754e038855..db63712bff 100644 --- a/Ryujinx.Graphics/Shader/Decoders/Decoder.cs +++ b/Ryujinx.Graphics.Shader/Decoders/Decoder.cs @@ -1,6 +1,6 @@ -using Ryujinx.Graphics.Gal; using Ryujinx.Graphics.Shader.Instructions; using System; +using System.Buffers.Binary; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; @@ -12,8 +12,6 @@ namespace Ryujinx.Graphics.Shader.Decoders { static class Decoder { - private const long HeaderSize = 0x50; - private delegate object OpActivator(InstEmitter emitter, ulong address, long opCode); private static ConcurrentDictionary _opActivators; @@ -23,7 +21,7 @@ namespace Ryujinx.Graphics.Shader.Decoders _opActivators = new ConcurrentDictionary(); } - public static Block[] Decode(IGalMemory memory, ulong address) + public static Block[] Decode(Span code, ulong headerSize) { List blocks = new List(); @@ -31,6 +29,8 @@ namespace Ryujinx.Graphics.Shader.Decoders Dictionary visited = new Dictionary(); + ulong maxAddress = (ulong)code.Length - headerSize; + Block GetBlock(ulong blkAddress) { if (!visited.TryGetValue(blkAddress, out Block block)) @@ -45,9 +45,7 @@ namespace Ryujinx.Graphics.Shader.Decoders return block; } - ulong startAddress = address + HeaderSize; - - GetBlock(startAddress); + GetBlock(0); while (workQueue.TryDequeue(out Block currBlock)) { @@ -69,7 +67,7 @@ namespace Ryujinx.Graphics.Shader.Decoders } // If we have a block after the current one, set the limit address. - ulong limitAddress = ulong.MaxValue; + ulong limitAddress = maxAddress; if (nBlkIndex != blocks.Count) { @@ -87,13 +85,20 @@ namespace Ryujinx.Graphics.Shader.Decoders } } - FillBlock(memory, currBlock, limitAddress, startAddress); + FillBlock(code, currBlock, limitAddress, headerSize); if (currBlock.OpCodes.Count != 0) { - foreach (OpCodeSsy ssyOp in currBlock.SsyOpCodes) + // We should have blocks for all possible branch targets, + // including those from SSY/PBK instructions. + foreach (OpCodePush pushOp in currBlock.PushOpCodes) { - GetBlock(ssyOp.GetAbsoluteAddress()); + if (pushOp.GetAbsoluteAddress() >= maxAddress) + { + return null; + } + + GetBlock(pushOp.GetAbsoluteAddress()); } // Set child blocks. "Branch" is the block the branch instruction @@ -102,9 +107,30 @@ namespace Ryujinx.Graphics.Shader.Decoders // or end of program, Next is null. OpCode lastOp = currBlock.GetLastOp(); - if (lastOp is OpCodeBranch op) + if (lastOp is OpCodeBranch opBr) { - currBlock.Branch = GetBlock(op.GetAbsoluteAddress()); + if (opBr.GetAbsoluteAddress() >= maxAddress) + { + return null; + } + + currBlock.Branch = GetBlock(opBr.GetAbsoluteAddress()); + } + else if (lastOp is OpCodeBranchIndir opBrIndir) + { + // An indirect branch could go anywhere, we don't know the target. + // Those instructions are usually used on a switch to jump table + // compiler optimization, and in those cases the possible targets + // seems to be always right after the BRX itself. We can assume + // that the possible targets are all the blocks in-between the + // instruction right after the BRX, and the common target that + // all the "cases" should eventually jump to, acting as the + // switch break. + Block firstTarget = GetBlock(currBlock.EndAddress); + + firstTarget.BrIndir = opBrIndir; + + opBrIndir.PossibleTargets.Add(firstTarget); } if (!IsUnconditionalBranch(lastOp)) @@ -124,13 +150,28 @@ namespace Ryujinx.Graphics.Shader.Decoders { blocks.Add(currBlock); } + + // Do we have a block after the current one? + if (!IsExit(currBlock.GetLastOp()) && currBlock.BrIndir != null && currBlock.EndAddress < maxAddress) + { + bool targetVisited = visited.ContainsKey(currBlock.EndAddress); + + Block possibleTarget = GetBlock(currBlock.EndAddress); + + currBlock.BrIndir.PossibleTargets.Add(possibleTarget); + + if (!targetVisited) + { + possibleTarget.BrIndir = currBlock.BrIndir; + } + } } - foreach (Block ssyBlock in blocks.Where(x => x.SsyOpCodes.Count != 0)) + foreach (Block block in blocks.Where(x => x.PushOpCodes.Count != 0)) { - for (int ssyIndex = 0; ssyIndex < ssyBlock.SsyOpCodes.Count; ssyIndex++) + for (int pushOpIndex = 0; pushOpIndex < block.PushOpCodes.Count; pushOpIndex++) { - PropagateSsy(visited, ssyBlock, ssyIndex); + PropagatePushOp(visited, block, pushOpIndex); } } @@ -173,7 +214,7 @@ namespace Ryujinx.Graphics.Shader.Decoders } private static void FillBlock( - IGalMemory memory, + Span code, Block block, ulong limitAddress, ulong startAddress) @@ -182,21 +223,21 @@ namespace Ryujinx.Graphics.Shader.Decoders do { - if (address >= limitAddress) + if (address + 7 >= limitAddress) { break; } // Ignore scheduling instructions, which are written every 32 bytes. - if (((address - startAddress) & 0x1f) == 0) + if ((address & 0x1f) == 0) { address += 8; continue; } - uint word0 = (uint)memory.ReadInt32((long)(address + 0)); - uint word1 = (uint)memory.ReadInt32((long)(address + 4)); + uint word0 = BinaryPrimitives.ReadUInt32LittleEndian(code.Slice((int)(startAddress + address))); + uint word1 = BinaryPrimitives.ReadUInt32LittleEndian(code.Slice((int)(startAddress + address + 4))); ulong opAddress = address; @@ -209,6 +250,9 @@ namespace Ryujinx.Graphics.Shader.Decoders if (emitter == null) { // TODO: Warning, illegal encoding. + + block.OpCodes.Add(new OpCode(null, opAddress, opCode)); + continue; } @@ -220,7 +264,7 @@ namespace Ryujinx.Graphics.Shader.Decoders block.EndAddress = address; - block.UpdateSsyOpCodes(); + block.UpdatePushOps(); } private static bool IsUnconditionalBranch(OpCode opCode) @@ -240,11 +284,17 @@ namespace Ryujinx.Graphics.Shader.Decoders private static bool IsBranch(OpCode opCode) { - return (opCode is OpCodeBranch && opCode.Emitter != InstEmit.Ssy) || - opCode is OpCodeSync || + return (opCode is OpCodeBranch opBranch && !opBranch.PushTarget) || + opCode is OpCodeBranchIndir || + opCode is OpCodeBranchPop || opCode is OpCodeExit; } + private static bool IsExit(OpCode opCode) + { + return opCode is OpCodeExit; + } + private static OpCode MakeOpCode(Type type, InstEmitter emitter, ulong address, long opCode) { if (type == null) @@ -281,8 +331,8 @@ namespace Ryujinx.Graphics.Shader.Decoders private enum RestoreType { None, - PopSsy, - PushSync + PopPushOp, + PushBranchOp } private RestoreType _restoreType; @@ -298,45 +348,45 @@ namespace Ryujinx.Graphics.Shader.Decoders _restoreValue = 0; } - public PathBlockState(int oldSsyStackSize) + public PathBlockState(int oldStackSize) { Block = null; - _restoreType = RestoreType.PopSsy; - _restoreValue = (ulong)oldSsyStackSize; + _restoreType = RestoreType.PopPushOp; + _restoreValue = (ulong)oldStackSize; } public PathBlockState(ulong syncAddress) { Block = null; - _restoreType = RestoreType.PushSync; + _restoreType = RestoreType.PushBranchOp; _restoreValue = syncAddress; } - public void RestoreStackState(Stack ssyStack) + public void RestoreStackState(Stack branchStack) { - if (_restoreType == RestoreType.PushSync) + if (_restoreType == RestoreType.PushBranchOp) { - ssyStack.Push(_restoreValue); + branchStack.Push(_restoreValue); } - else if (_restoreType == RestoreType.PopSsy) + else if (_restoreType == RestoreType.PopPushOp) { - while (ssyStack.Count > (uint)_restoreValue) + while (branchStack.Count > (uint)_restoreValue) { - ssyStack.Pop(); + branchStack.Pop(); } } } } - private static void PropagateSsy(Dictionary blocks, Block ssyBlock, int ssyIndex) + private static void PropagatePushOp(Dictionary blocks, Block currBlock, int pushOpIndex) { - OpCodeSsy ssyOp = ssyBlock.SsyOpCodes[ssyIndex]; + OpCodePush pushOp = currBlock.PushOpCodes[pushOpIndex]; Stack workQueue = new Stack(); HashSet visited = new HashSet(); - Stack ssyStack = new Stack(); + Stack branchStack = new Stack(); void Push(PathBlockState pbs) { @@ -346,32 +396,32 @@ namespace Ryujinx.Graphics.Shader.Decoders } } - Push(new PathBlockState(ssyBlock)); + Push(new PathBlockState(currBlock)); while (workQueue.TryPop(out PathBlockState pbs)) { if (pbs.ReturningFromVisit) { - pbs.RestoreStackState(ssyStack); + pbs.RestoreStackState(branchStack); continue; } Block current = pbs.Block; - int ssyOpCodesCount = current.SsyOpCodes.Count; + int pushOpsCount = current.PushOpCodes.Count; - if (ssyOpCodesCount != 0) + if (pushOpsCount != 0) { - Push(new PathBlockState(ssyStack.Count)); + Push(new PathBlockState(branchStack.Count)); - for (int index = ssyIndex; index < ssyOpCodesCount; index++) + for (int index = pushOpIndex; index < pushOpsCount; index++) { - ssyStack.Push(current.SsyOpCodes[index].GetAbsoluteAddress()); + branchStack.Push(current.PushOpCodes[index].GetAbsoluteAddress()); } } - ssyIndex = 0; + pushOpIndex = 0; if (current.Next != null) { @@ -382,22 +432,29 @@ namespace Ryujinx.Graphics.Shader.Decoders { Push(new PathBlockState(current.Branch)); } - else if (current.GetLastOp() is OpCodeSync op) + else if (current.GetLastOp() is OpCodeBranchIndir brIndir) { - ulong syncAddress = ssyStack.Pop(); - - if (ssyStack.Count == 0) + foreach (Block possibleTarget in brIndir.PossibleTargets) { - ssyStack.Push(syncAddress); + Push(new PathBlockState(possibleTarget)); + } + } + else if (current.GetLastOp() is OpCodeBranchPop op) + { + ulong targetAddress = branchStack.Pop(); - op.Targets.Add(ssyOp, op.Targets.Count); + if (branchStack.Count == 0) + { + branchStack.Push(targetAddress); - ssyOp.Syncs.TryAdd(op, Local()); + op.Targets.Add(pushOp, op.Targets.Count); + + pushOp.PopOps.TryAdd(op, Local()); } else { - Push(new PathBlockState(syncAddress)); - Push(new PathBlockState(blocks[syncAddress])); + Push(new PathBlockState(targetAddress)); + Push(new PathBlockState(blocks[targetAddress])); } } } diff --git a/Ryujinx.Graphics/Shader/Decoders/DecoderHelper.cs b/Ryujinx.Graphics.Shader/Decoders/DecoderHelper.cs similarity index 91% rename from Ryujinx.Graphics/Shader/Decoders/DecoderHelper.cs rename to Ryujinx.Graphics.Shader/Decoders/DecoderHelper.cs index fd0a45e82b..77cd1bf728 100644 --- a/Ryujinx.Graphics/Shader/Decoders/DecoderHelper.cs +++ b/Ryujinx.Graphics.Shader/Decoders/DecoderHelper.cs @@ -8,11 +8,11 @@ namespace Ryujinx.Graphics.Shader.Decoders { int imm = opCode.Extract(20, 19); - bool negate = opCode.Extract(56); + bool sign = opCode.Extract(56); - if (negate) + if (sign) { - imm = -imm; + imm = (imm << 13) >> 13; } return imm; diff --git a/Ryujinx.Graphics/Shader/Decoders/FPHalfSwizzle.cs b/Ryujinx.Graphics.Shader/Decoders/FPHalfSwizzle.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/FPHalfSwizzle.cs rename to Ryujinx.Graphics.Shader/Decoders/FPHalfSwizzle.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/FmulScale.cs b/Ryujinx.Graphics.Shader/Decoders/FPMultiplyScale.cs similarity index 89% rename from Ryujinx.Graphics/Shader/Decoders/FmulScale.cs rename to Ryujinx.Graphics.Shader/Decoders/FPMultiplyScale.cs index c35c6e489e..398c0e66f7 100644 --- a/Ryujinx.Graphics/Shader/Decoders/FmulScale.cs +++ b/Ryujinx.Graphics.Shader/Decoders/FPMultiplyScale.cs @@ -1,6 +1,6 @@ namespace Ryujinx.Graphics.Shader.Decoders { - enum FmulScale + enum FPMultiplyScale { None = 0, Divide2 = 1, diff --git a/Ryujinx.Graphics/Shader/Decoders/FPType.cs b/Ryujinx.Graphics.Shader/Decoders/FPType.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/FPType.cs rename to Ryujinx.Graphics.Shader/Decoders/FPType.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/IOpCode.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCode.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/IOpCode.cs rename to Ryujinx.Graphics.Shader/Decoders/IOpCode.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/IOpCodeAlu.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeAlu.cs similarity index 56% rename from Ryujinx.Graphics/Shader/Decoders/IOpCodeAlu.cs rename to Ryujinx.Graphics.Shader/Decoders/IOpCodeAlu.cs index d840d49d1b..6d1382a8a3 100644 --- a/Ryujinx.Graphics/Shader/Decoders/IOpCodeAlu.cs +++ b/Ryujinx.Graphics.Shader/Decoders/IOpCodeAlu.cs @@ -1,10 +1,7 @@ namespace Ryujinx.Graphics.Shader.Decoders { - interface IOpCodeAlu : IOpCodeRd, IOpCodeRa + interface IOpCodeAlu : IOpCodeRd, IOpCodeRa, IOpCodePredicate39 { - Register Predicate39 { get; } - - bool InvertP { get; } bool Extended { get; } bool SetCondCode { get; } bool Saturate { get; } diff --git a/Ryujinx.Graphics/Shader/Decoders/IOpCodeCbuf.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeCbuf.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/IOpCodeCbuf.cs rename to Ryujinx.Graphics.Shader/Decoders/IOpCodeCbuf.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/IOpCodeFArith.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeFArith.cs similarity index 84% rename from Ryujinx.Graphics/Shader/Decoders/IOpCodeFArith.cs rename to Ryujinx.Graphics.Shader/Decoders/IOpCodeFArith.cs index d68ccf593b..3d06eae0d0 100644 --- a/Ryujinx.Graphics/Shader/Decoders/IOpCodeFArith.cs +++ b/Ryujinx.Graphics.Shader/Decoders/IOpCodeFArith.cs @@ -4,7 +4,7 @@ namespace Ryujinx.Graphics.Shader.Decoders { RoundingMode RoundingMode { get; } - FmulScale Scale { get; } + FPMultiplyScale Scale { get; } bool FlushToZero { get; } bool AbsoluteA { get; } diff --git a/Ryujinx.Graphics/Shader/Decoders/IOpCodeHfma.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeHfma.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/IOpCodeHfma.cs rename to Ryujinx.Graphics.Shader/Decoders/IOpCodeHfma.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/IOpCodeImm.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeImm.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/IOpCodeImm.cs rename to Ryujinx.Graphics.Shader/Decoders/IOpCodeImm.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/IOpCodeImmF.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeImmF.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/IOpCodeImmF.cs rename to Ryujinx.Graphics.Shader/Decoders/IOpCodeImmF.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/IOpCodeLop.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeLop.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/IOpCodeLop.cs rename to Ryujinx.Graphics.Shader/Decoders/IOpCodeLop.cs diff --git a/Ryujinx.Graphics.Shader/Decoders/IOpCodePredicate39.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodePredicate39.cs new file mode 100644 index 0000000000..74e7aff134 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IOpCodePredicate39.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodePredicate39 + { + Register Predicate39 { get; } + + bool InvertP { get; } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/IOpCodeRa.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeRa.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/IOpCodeRa.cs rename to Ryujinx.Graphics.Shader/Decoders/IOpCodeRa.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/IOpCodeRc.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeRc.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/IOpCodeRc.cs rename to Ryujinx.Graphics.Shader/Decoders/IOpCodeRc.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/IOpCodeRd.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeRd.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/IOpCodeRd.cs rename to Ryujinx.Graphics.Shader/Decoders/IOpCodeRd.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/IOpCodeReg.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeReg.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/IOpCodeReg.cs rename to Ryujinx.Graphics.Shader/Decoders/IOpCodeReg.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/IOpCodeRegCbuf.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeRegCbuf.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/IOpCodeRegCbuf.cs rename to Ryujinx.Graphics.Shader/Decoders/IOpCodeRegCbuf.cs diff --git a/Ryujinx.Graphics.Shader/Decoders/IOpCodeTexture.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeTexture.cs new file mode 100644 index 0000000000..55d1225aeb --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IOpCodeTexture.cs @@ -0,0 +1,23 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeTexture : IOpCode + { + Register Rd { get; } + Register Ra { get; } + Register Rb { get; } + + bool IsArray { get; } + + TextureDimensions Dimensions { get; } + + int ComponentMask { get; } + + int Immediate { get; } + + TextureLodMode LodMode { get; } + + bool HasOffset { get; } + bool HasDepthCompare { get; } + bool IsMultisample { get; } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/IOpCodeTld4.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeTld4.cs new file mode 100644 index 0000000000..219d00cbf7 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IOpCodeTld4.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeTld4 : IOpCodeTexture + { + TextureGatherOffset Offset { get; } + + int GatherCompIndex { get; } + + bool Bindless { get; } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/ImageComponents.cs b/Ryujinx.Graphics.Shader/Decoders/ImageComponents.cs new file mode 100644 index 0000000000..348a4768c9 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/ImageComponents.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum ImageComponents + { + Red = 1 << 0, + Green = 1 << 1, + Blue = 1 << 2, + Alpha = 1 << 3 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/ImageDimensions.cs b/Ryujinx.Graphics.Shader/Decoders/ImageDimensions.cs new file mode 100644 index 0000000000..ecf41a82fc --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/ImageDimensions.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum ImageDimensions + { + Image1D, + ImageBuffer, + Image1DArray, + Image2D, + Image2DArray, + Image3D + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/IntegerCondition.cs b/Ryujinx.Graphics.Shader/Decoders/IntegerCondition.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/IntegerCondition.cs rename to Ryujinx.Graphics.Shader/Decoders/IntegerCondition.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/IntegerHalfPart.cs b/Ryujinx.Graphics.Shader/Decoders/IntegerHalfPart.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/IntegerHalfPart.cs rename to Ryujinx.Graphics.Shader/Decoders/IntegerHalfPart.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/IntegerShift.cs b/Ryujinx.Graphics.Shader/Decoders/IntegerShift.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/IntegerShift.cs rename to Ryujinx.Graphics.Shader/Decoders/IntegerShift.cs diff --git a/Ryujinx.Graphics.Shader/Decoders/IntegerSize.cs b/Ryujinx.Graphics.Shader/Decoders/IntegerSize.cs new file mode 100644 index 0000000000..d39c2a9091 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IntegerSize.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum IntegerSize + { + U8 = 0, + S8 = 1, + U16 = 2, + S16 = 3, + B32 = 4, + B64 = 5, + B128 = 6, + UB128 = 7 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/IntegerType.cs b/Ryujinx.Graphics.Shader/Decoders/IntegerType.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/IntegerType.cs rename to Ryujinx.Graphics.Shader/Decoders/IntegerType.cs diff --git a/Ryujinx.Graphics.Shader/Decoders/InterpolationMode.cs b/Ryujinx.Graphics.Shader/Decoders/InterpolationMode.cs new file mode 100644 index 0000000000..98ee3b9703 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/InterpolationMode.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum InterpolationMode + { + Pass, + Default, + Constant, + Sc + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/LogicalOperation.cs b/Ryujinx.Graphics.Shader/Decoders/LogicalOperation.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/LogicalOperation.cs rename to Ryujinx.Graphics.Shader/Decoders/LogicalOperation.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/MufuOperation.cs b/Ryujinx.Graphics.Shader/Decoders/MufuOperation.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/MufuOperation.cs rename to Ryujinx.Graphics.Shader/Decoders/MufuOperation.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCode.cs b/Ryujinx.Graphics.Shader/Decoders/OpCode.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/OpCode.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCode.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeAlu.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeAlu.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeAlu.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeAlu.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeAluCbuf.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeAluCbuf.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeAluCbuf.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeAluCbuf.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeAluImm.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeAluImm.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeAluImm.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeAluImm.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeAluImm2x10.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeAluImm2x10.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeAluImm2x10.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeAluImm2x10.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeAluImm32.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeAluImm32.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeAluImm32.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeAluImm32.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeAluReg.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeAluReg.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeAluReg.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeAluReg.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeAluRegCbuf.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeAluRegCbuf.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeAluRegCbuf.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeAluRegCbuf.cs diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeAtom.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeAtom.cs new file mode 100644 index 0000000000..b572703ec8 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeAtom.cs @@ -0,0 +1,39 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeAtom : OpCode, IOpCodeRd, IOpCodeRa, IOpCodeReg + { + public Register Rd { get; } + public Register Ra { get; } + public Register Rb { get; } + + public ReductionType Type { get; } + + public int Offset { get; } + + public bool Extended { get; } + + public AtomicOp AtomicOp { get; } + + public OpCodeAtom(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rd = new Register(opCode.Extract(0, 8), RegisterType.Gpr); + Ra = new Register(opCode.Extract(8, 8), RegisterType.Gpr); + Rb = new Register(opCode.Extract(20, 8), RegisterType.Gpr); + + Type = (ReductionType)opCode.Extract(28, 2); + + if (Type == ReductionType.FP32FtzRn) + { + Type = ReductionType.S64; + } + + Offset = opCode.Extract(30, 22); + + Extended = opCode.Extract(48); + + AtomicOp = (AtomicOp)opCode.Extract(52, 4); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeAttribute.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeAttribute.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeAttribute.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeAttribute.cs diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeBarrier.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeBarrier.cs new file mode 100644 index 0000000000..81e28aa143 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeBarrier.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeBarrier : OpCode + { + public BarrierMode Mode { get; } + + public OpCodeBarrier(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Mode = (BarrierMode)((opCode >> 32) & 0x9b); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeBranch.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeBranch.cs similarity index 71% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeBranch.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeBranch.cs index 25941b3967..c4fa921265 100644 --- a/Ryujinx.Graphics/Shader/Decoders/OpCodeBranch.cs +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeBranch.cs @@ -4,11 +4,19 @@ namespace Ryujinx.Graphics.Shader.Decoders { class OpCodeBranch : OpCode { + public Condition Condition { get; } + public int Offset { get; } + public bool PushTarget { get; protected set; } + public OpCodeBranch(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) { + Condition = (Condition)(opCode & 0x1f); + Offset = ((int)(opCode >> 20) << 8) >> 8; + + PushTarget = false; } public ulong GetAbsoluteAddress() diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeBranchIndir.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeBranchIndir.cs new file mode 100644 index 0000000000..3e694e61c8 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeBranchIndir.cs @@ -0,0 +1,23 @@ +using Ryujinx.Graphics.Shader.Instructions; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeBranchIndir : OpCode + { + public HashSet PossibleTargets { get; } + + public Register Ra { get; } + + public int Offset { get; } + + public OpCodeBranchIndir(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + PossibleTargets = new HashSet(); + + Ra = new Register(opCode.Extract(8, 8), RegisterType.Gpr); + + Offset = ((int)(opCode >> 20) << 8) >> 8; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeBranchPop.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeBranchPop.cs new file mode 100644 index 0000000000..7ea66fe455 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeBranchPop.cs @@ -0,0 +1,15 @@ +using Ryujinx.Graphics.Shader.Instructions; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeBranchPop : OpCode + { + public Dictionary Targets { get; } + + public OpCodeBranchPop(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Targets = new Dictionary(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeExit.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeExit.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeExit.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeExit.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeFArith.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeFArith.cs similarity index 84% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeFArith.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeFArith.cs index c88f7f0ee3..cfbf65c3dd 100644 --- a/Ryujinx.Graphics/Shader/Decoders/OpCodeFArith.cs +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeFArith.cs @@ -6,7 +6,7 @@ namespace Ryujinx.Graphics.Shader.Decoders { public RoundingMode RoundingMode { get; } - public FmulScale Scale { get; } + public FPMultiplyScale Scale { get; } public bool FlushToZero { get; } public bool AbsoluteA { get; } @@ -15,7 +15,7 @@ namespace Ryujinx.Graphics.Shader.Decoders { RoundingMode = (RoundingMode)opCode.Extract(39, 2); - Scale = (FmulScale)opCode.Extract(41, 3); + Scale = (FPMultiplyScale)opCode.Extract(41, 3); FlushToZero = opCode.Extract(44); AbsoluteA = opCode.Extract(46); diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeFArithCbuf.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeFArithCbuf.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeFArithCbuf.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeFArithCbuf.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeFArithImm.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeFArithImm.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeFArithImm.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeFArithImm.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeFArithImm32.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeFArithImm32.cs similarity index 92% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeFArithImm32.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeFArithImm32.cs index ec9da6f302..aecc5143ca 100644 --- a/Ryujinx.Graphics/Shader/Decoders/OpCodeFArithImm32.cs +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeFArithImm32.cs @@ -7,7 +7,7 @@ namespace Ryujinx.Graphics.Shader.Decoders { public RoundingMode RoundingMode => RoundingMode.ToNearest; - public FmulScale Scale => FmulScale.None; + public FPMultiplyScale Scale => FPMultiplyScale.None; public bool FlushToZero { get; } public bool AbsoluteA { get; } diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeFArithReg.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeFArithReg.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeFArithReg.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeFArithReg.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeFArithRegCbuf.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeFArithRegCbuf.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeFArithRegCbuf.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeFArithRegCbuf.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeFsetImm.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeFsetImm.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeFsetImm.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeFsetImm.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeHfma.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeHfma.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeHfma.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeHfma.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaCbuf.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeHfmaCbuf.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaCbuf.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeHfmaCbuf.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaImm2x10.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeHfmaImm2x10.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaImm2x10.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeHfmaImm2x10.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaImm32.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeHfmaImm32.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaImm32.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeHfmaImm32.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaReg.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeHfmaReg.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaReg.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeHfmaReg.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaRegCbuf.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeHfmaRegCbuf.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeHfmaRegCbuf.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeHfmaRegCbuf.cs diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeHsetImm2x10.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeHsetImm2x10.cs new file mode 100644 index 0000000000..03e1e44cec --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeHsetImm2x10.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeHsetImm2x10 : OpCodeSet, IOpCodeImm + { + public int Immediate { get; } + + public OpCodeHsetImm2x10(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Immediate = DecoderHelper.Decode2xF10Immediate(opCode); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeImage.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeImage.cs new file mode 100644 index 0000000000..42fe677bdb --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeImage.cs @@ -0,0 +1,48 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeImage : OpCode + { + public Register Ra { get; } + public Register Rb { get; } + public Register Rc { get; } + + public ImageComponents Components { get; } + public IntegerSize Size { get; } + + public bool ByteAddress { get; } + + public ImageDimensions Dimensions { get; } + + public int Immediate { get; } + + public bool UseComponents { get; } + public bool IsBindless { get; } + + public OpCodeImage(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Ra = new Register(opCode.Extract(8, 8), RegisterType.Gpr); + Rb = new Register(opCode.Extract(0, 8), RegisterType.Gpr); + Rc = new Register(opCode.Extract(39, 8), RegisterType.Gpr); + + UseComponents = !opCode.Extract(52); + + if (UseComponents) + { + Components = (ImageComponents)opCode.Extract(20, 4); + } + else + { + Size = (IntegerSize)opCode.Extract(20, 4); + } + + ByteAddress = !opCode.Extract(23); + + Dimensions = (ImageDimensions)opCode.Extract(33, 3); + + Immediate = opCode.Extract(36, 13); + IsBindless = !opCode.Extract(51); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeIpa.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeIpa.cs similarity index 70% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeIpa.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeIpa.cs index e21095a325..b475b6a16a 100644 --- a/Ryujinx.Graphics/Shader/Decoders/OpCodeIpa.cs +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeIpa.cs @@ -6,9 +6,15 @@ namespace Ryujinx.Graphics.Shader.Decoders { public int AttributeOffset { get; } + public InterpolationMode Mode { get; } + public OpCodeIpa(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) { AttributeOffset = opCode.Extract(28, 10); + + Saturate = opCode.Extract(51); + + Mode = (InterpolationMode)opCode.Extract(54, 2); } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeLdc.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeLdc.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeLdc.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeLdc.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeLop.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeLop.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeLop.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeLop.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeLopCbuf.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeLopCbuf.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeLopCbuf.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeLopCbuf.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeLopImm.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeLopImm.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeLopImm.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeLopImm.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeLopImm32.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeLopImm32.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeLopImm32.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeLopImm32.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeLopReg.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeLopReg.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeLopReg.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeLopReg.cs diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeMemory.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeMemory.cs new file mode 100644 index 0000000000..bece456223 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeMemory.cs @@ -0,0 +1,28 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeMemory : OpCode, IOpCodeRd, IOpCodeRa + { + public Register Rd { get; } + public Register Ra { get; } + + public int Offset { get; } + + public bool Extended { get; } + + public IntegerSize Size { get; } + + public OpCodeMemory(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rd = new Register(opCode.Extract(0, 8), RegisterType.Gpr); + Ra = new Register(opCode.Extract(8, 8), RegisterType.Gpr); + + Offset = opCode.Extract(20, 24); + + Extended = opCode.Extract(45); + + Size = (IntegerSize)opCode.Extract(48, 3); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeMemoryBarrier.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeMemoryBarrier.cs new file mode 100644 index 0000000000..c31fe87b97 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeMemoryBarrier.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeMemoryBarrier : OpCode + { + public BarrierLevel Level { get; } + + public OpCodeMemoryBarrier(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Level = (BarrierLevel)opCode.Extract(8, 2); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodePsetp.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodePset.cs similarity index 62% rename from Ryujinx.Graphics/Shader/Decoders/OpCodePsetp.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodePset.cs index 729e3207e8..df508442d3 100644 --- a/Ryujinx.Graphics/Shader/Decoders/OpCodePsetp.cs +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodePset.cs @@ -2,18 +2,24 @@ using Ryujinx.Graphics.Shader.Instructions; namespace Ryujinx.Graphics.Shader.Decoders { - class OpCodePsetp : OpCodeSet + class OpCodePset : OpCodeSet { public Register Predicate12 { get; } public Register Predicate29 { get; } + public bool InvertA { get; } + public bool InvertB { get; } + public LogicalOperation LogicalOpAB { get; } - public OpCodePsetp(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + public OpCodePset(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) { Predicate12 = new Register(opCode.Extract(12, 3), RegisterType.Predicate); Predicate29 = new Register(opCode.Extract(29, 3), RegisterType.Predicate); + InvertA = opCode.Extract(15); + InvertB = opCode.Extract(32); + LogicalOpAB = (LogicalOperation)opCode.Extract(24, 2); } } diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeSsy.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodePush.cs similarity index 53% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeSsy.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodePush.cs index 499c070689..a7657bcf8e 100644 --- a/Ryujinx.Graphics/Shader/Decoders/OpCodeSsy.cs +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodePush.cs @@ -4,17 +4,19 @@ using System.Collections.Generic; namespace Ryujinx.Graphics.Shader.Decoders { - class OpCodeSsy : OpCodeBranch + class OpCodePush : OpCodeBranch { - public Dictionary Syncs { get; } + public Dictionary PopOps { get; } - public OpCodeSsy(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + public OpCodePush(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) { - Syncs = new Dictionary(); + PopOps = new Dictionary(); Predicate = new Register(RegisterConsts.PredicateTrueIndex, RegisterType.Predicate); InvertPredicate = false; + + PushTarget = true; } } } \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeRed.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeRed.cs new file mode 100644 index 0000000000..8fde82a260 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeRed.cs @@ -0,0 +1,32 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeRed : OpCode, IOpCodeRd, IOpCodeRa + { + public Register Rd { get; } + public Register Ra { get; } + + public AtomicOp AtomicOp { get; } + + public ReductionType Type { get; } + + public int Offset { get; } + + public bool Extended { get; } + + public OpCodeRed(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rd = new Register(opCode.Extract(0, 8), RegisterType.Gpr); + Ra = new Register(opCode.Extract(8, 8), RegisterType.Gpr); + + Type = (ReductionType)opCode.Extract(20, 3); + + AtomicOp = (AtomicOp)opCode.Extract(23, 3); + + Offset = opCode.Extract(28, 20); + + Extended = opCode.Extract(48); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeSet.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeSet.cs similarity index 77% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeSet.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeSet.cs index cd6773a13c..b4ee10fb3d 100644 --- a/Ryujinx.Graphics/Shader/Decoders/OpCodeSet.cs +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeSet.cs @@ -15,8 +15,8 @@ namespace Ryujinx.Graphics.Shader.Decoders public OpCodeSet(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) { - Predicate0 = new Register(opCode.Extract(0, 3), RegisterType.Predicate); - Predicate3 = new Register(opCode.Extract(3, 3), RegisterType.Predicate); + Predicate0 = new Register(opCode.Extract(0, 3), RegisterType.Predicate); + Predicate3 = new Register(opCode.Extract(3, 3), RegisterType.Predicate); LogicalOp = (LogicalOperation)opCode.Extract(45, 2); diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeSetCbuf.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeSetCbuf.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeSetCbuf.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeSetCbuf.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeSetImm.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeSetImm.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeSetImm.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeSetImm.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeSetReg.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeSetReg.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeSetReg.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeSetReg.cs diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeShuffle.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeShuffle.cs new file mode 100644 index 0000000000..43693cf490 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeShuffle.cs @@ -0,0 +1,40 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeShuffle : OpCode, IOpCodeRd, IOpCodeRa + { + public Register Rd { get; } + public Register Ra { get; } + public Register Rb { get; } + public Register Rc { get; } + + public int ImmediateB { get; } + public int ImmediateC { get; } + + public bool IsBImmediate { get; } + public bool IsCImmediate { get; } + + public ShuffleType ShuffleType { get; } + + public Register Predicate48 { get; } + + public OpCodeShuffle(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rd = new Register(opCode.Extract(0, 8), RegisterType.Gpr); + Ra = new Register(opCode.Extract(8, 8), RegisterType.Gpr); + Rb = new Register(opCode.Extract(20, 8), RegisterType.Gpr); + Rc = new Register(opCode.Extract(39, 8), RegisterType.Gpr); + + ImmediateB = opCode.Extract(20, 5); + ImmediateC = opCode.Extract(34, 13); + + IsBImmediate = opCode.Extract(28); + IsCImmediate = opCode.Extract(29); + + ShuffleType = (ShuffleType)opCode.Extract(30, 2); + + Predicate48 = new Register(opCode.Extract(48, 3), RegisterType.Predicate); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeTable.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeTable.cs new file mode 100644 index 0000000000..87f1de0c48 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeTable.cs @@ -0,0 +1,264 @@ +using Ryujinx.Graphics.Shader.Instructions; +using System; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + static class OpCodeTable + { + private const int EncodingBits = 14; + + private class TableEntry + { + public InstEmitter Emitter { get; } + + public Type OpCodeType { get; } + + public int XBits { get; } + + public TableEntry(InstEmitter emitter, Type opCodeType, int xBits) + { + Emitter = emitter; + OpCodeType = opCodeType; + XBits = xBits; + } + } + + private static TableEntry[] _opCodes; + + static OpCodeTable() + { + _opCodes = new TableEntry[1 << EncodingBits]; + +#region Instructions + Set("1110111111011x", InstEmit.Ald, typeof(OpCodeAttribute)); + Set("1110111111110x", InstEmit.Ast, typeof(OpCodeAttribute)); + Set("11101100xxxxxx", InstEmit.Atoms, typeof(OpCodeAtom)); + Set("1111000010101x", InstEmit.Bar, typeof(OpCodeBarrier)); + Set("0100110000000x", InstEmit.Bfe, typeof(OpCodeAluCbuf)); + Set("0011100x00000x", InstEmit.Bfe, typeof(OpCodeAluImm)); + Set("0101110000000x", InstEmit.Bfe, typeof(OpCodeAluReg)); + Set("0100101111110x", InstEmit.Bfi, typeof(OpCodeAluCbuf)); + Set("0011011x11110x", InstEmit.Bfi, typeof(OpCodeAluImm)); + Set("0101001111110x", InstEmit.Bfi, typeof(OpCodeAluRegCbuf)); + Set("0101101111110x", InstEmit.Bfi, typeof(OpCodeAluReg)); + Set("111000100100xx", InstEmit.Bra, typeof(OpCodeBranch)); + Set("111000110100xx", InstEmit.Brk, typeof(OpCodeBranchPop)); + Set("111000100101xx", InstEmit.Brx, typeof(OpCodeBranchIndir)); + Set("0101000010100x", InstEmit.Csetp, typeof(OpCodePset)); + Set("111000110000xx", InstEmit.Exit, typeof(OpCodeExit)); + Set("0100110010101x", InstEmit.F2F, typeof(OpCodeFArithCbuf)); + Set("0011100x10101x", InstEmit.F2F, typeof(OpCodeFArithImm)); + Set("0101110010101x", InstEmit.F2F, typeof(OpCodeFArithReg)); + Set("0100110010110x", InstEmit.F2I, typeof(OpCodeFArithCbuf)); + Set("0011100x10110x", InstEmit.F2I, typeof(OpCodeFArithImm)); + Set("0101110010110x", InstEmit.F2I, typeof(OpCodeFArithReg)); + Set("0100110001011x", InstEmit.Fadd, typeof(OpCodeFArithCbuf)); + Set("0011100x01011x", InstEmit.Fadd, typeof(OpCodeFArithImm)); + Set("000010xxxxxxxx", InstEmit.Fadd, typeof(OpCodeFArithImm32)); + Set("0101110001011x", InstEmit.Fadd, typeof(OpCodeFArithReg)); + Set("010010011xxxxx", InstEmit.Ffma, typeof(OpCodeFArithCbuf)); + Set("0011001x1xxxxx", InstEmit.Ffma, typeof(OpCodeFArithImm)); + Set("000011xxxxxxxx", InstEmit.Ffma32i, typeof(OpCodeFArithImm32)); + Set("010100011xxxxx", InstEmit.Ffma, typeof(OpCodeFArithRegCbuf)); + Set("010110011xxxxx", InstEmit.Ffma, typeof(OpCodeFArithReg)); + Set("0100110000110x", InstEmit.Flo, typeof(OpCodeAluCbuf)); + Set("0011100x00110x", InstEmit.Flo, typeof(OpCodeAluImm)); + Set("0101110000110x", InstEmit.Flo, typeof(OpCodeAluReg)); + Set("0100110001100x", InstEmit.Fmnmx, typeof(OpCodeFArithCbuf)); + Set("0011100x01100x", InstEmit.Fmnmx, typeof(OpCodeFArithImm)); + Set("0101110001100x", InstEmit.Fmnmx, typeof(OpCodeFArithReg)); + Set("0100110001101x", InstEmit.Fmul, typeof(OpCodeFArithCbuf)); + Set("0011100x01101x", InstEmit.Fmul, typeof(OpCodeFArithImm)); + Set("00011110xxxxxx", InstEmit.Fmul, typeof(OpCodeFArithImm32)); + Set("0101110001101x", InstEmit.Fmul, typeof(OpCodeFArithReg)); + Set("0100100xxxxxxx", InstEmit.Fset, typeof(OpCodeSetCbuf)); + Set("0011000xxxxxxx", InstEmit.Fset, typeof(OpCodeFsetImm)); + Set("01011000xxxxxx", InstEmit.Fset, typeof(OpCodeSetReg)); + Set("010010111011xx", InstEmit.Fsetp, typeof(OpCodeSetCbuf)); + Set("0011011x1011xx", InstEmit.Fsetp, typeof(OpCodeFsetImm)); + Set("010110111011xx", InstEmit.Fsetp, typeof(OpCodeSetReg)); + Set("0101000011111x", InstEmit.Fswzadd, typeof(OpCodeAluReg)); + Set("0111101x1xxxxx", InstEmit.Hadd2, typeof(OpCodeAluCbuf)); + Set("0111101x0xxxxx", InstEmit.Hadd2, typeof(OpCodeAluImm2x10)); + Set("0010110xxxxxxx", InstEmit.Hadd2, typeof(OpCodeAluImm32)); + Set("0101110100010x", InstEmit.Hadd2, typeof(OpCodeAluReg)); + Set("01110xxx1xxxxx", InstEmit.Hfma2, typeof(OpCodeHfmaCbuf)); + Set("01110xxx0xxxxx", InstEmit.Hfma2, typeof(OpCodeHfmaImm2x10)); + Set("0010100xxxxxxx", InstEmit.Hfma2, typeof(OpCodeHfmaImm32)); + Set("0101110100000x", InstEmit.Hfma2, typeof(OpCodeHfmaReg)); + Set("01100xxx1xxxxx", InstEmit.Hfma2, typeof(OpCodeHfmaRegCbuf)); + Set("0111100x1xxxxx", InstEmit.Hmul2, typeof(OpCodeAluCbuf)); + Set("0111100x0xxxxx", InstEmit.Hmul2, typeof(OpCodeAluImm2x10)); + Set("0010101xxxxxxx", InstEmit.Hmul2, typeof(OpCodeAluImm32)); + Set("0101110100001x", InstEmit.Hmul2, typeof(OpCodeAluReg)); + Set("0111110x1xxxxx", InstEmit.Hset2, typeof(OpCodeSetCbuf)); + Set("0111110x0xxxxx", InstEmit.Hset2, typeof(OpCodeHsetImm2x10)); + Set("0101110100011x", InstEmit.Hset2, typeof(OpCodeSetReg)); + Set("0111111x1xxxxx", InstEmit.Hsetp2, typeof(OpCodeSetCbuf)); + Set("0111111x0xxxxx", InstEmit.Hsetp2, typeof(OpCodeHsetImm2x10)); + Set("0101110100100x", InstEmit.Hsetp2, typeof(OpCodeSetReg)); + Set("0100110010111x", InstEmit.I2F, typeof(OpCodeAluCbuf)); + Set("0011100x10111x", InstEmit.I2F, typeof(OpCodeAluImm)); + Set("0101110010111x", InstEmit.I2F, typeof(OpCodeAluReg)); + Set("0100110011100x", InstEmit.I2I, typeof(OpCodeAluCbuf)); + Set("0011100x11100x", InstEmit.I2I, typeof(OpCodeAluImm)); + Set("0101110011100x", InstEmit.I2I, typeof(OpCodeAluReg)); + Set("0100110000010x", InstEmit.Iadd, typeof(OpCodeAluCbuf)); + Set("0011100x00010x", InstEmit.Iadd, typeof(OpCodeAluImm)); + Set("0001110x0xxxxx", InstEmit.Iadd, typeof(OpCodeAluImm32)); + Set("0101110000010x", InstEmit.Iadd, typeof(OpCodeAluReg)); + Set("010011001100xx", InstEmit.Iadd3, typeof(OpCodeAluCbuf)); + Set("0011100x1100xx", InstEmit.Iadd3, typeof(OpCodeAluImm)); + Set("010111001100xx", InstEmit.Iadd3, typeof(OpCodeAluReg)); + Set("010010100xxxxx", InstEmit.Imad, typeof(OpCodeAluCbuf)); + Set("0011010x0xxxxx", InstEmit.Imad, typeof(OpCodeAluImm)); + Set("010110100xxxxx", InstEmit.Imad, typeof(OpCodeAluReg)); + Set("010100100xxxxx", InstEmit.Imad, typeof(OpCodeAluRegCbuf)); + Set("0100110000100x", InstEmit.Imnmx, typeof(OpCodeAluCbuf)); + Set("0011100x00100x", InstEmit.Imnmx, typeof(OpCodeAluImm)); + Set("0101110000100x", InstEmit.Imnmx, typeof(OpCodeAluReg)); + Set("11100000xxxxxx", InstEmit.Ipa, typeof(OpCodeIpa)); + Set("1110111111010x", InstEmit.Isberd, typeof(OpCodeAlu)); + Set("0100110000011x", InstEmit.Iscadd, typeof(OpCodeAluCbuf)); + Set("0011100x00011x", InstEmit.Iscadd, typeof(OpCodeAluImm)); + Set("000101xxxxxxxx", InstEmit.Iscadd, typeof(OpCodeAluImm32)); + Set("0101110000011x", InstEmit.Iscadd, typeof(OpCodeAluReg)); + Set("010010110101xx", InstEmit.Iset, typeof(OpCodeSetCbuf)); + Set("001101100101xx", InstEmit.Iset, typeof(OpCodeSetImm)); + Set("010110110101xx", InstEmit.Iset, typeof(OpCodeSetReg)); + Set("010010110110xx", InstEmit.Isetp, typeof(OpCodeSetCbuf)); + Set("0011011x0110xx", InstEmit.Isetp, typeof(OpCodeSetImm)); + Set("010110110110xx", InstEmit.Isetp, typeof(OpCodeSetReg)); + Set("111000110011xx", InstEmit.Kil, typeof(OpCodeExit)); + Set("1110111101000x", InstEmit.Ld, typeof(OpCodeMemory)); + Set("1110111110010x", InstEmit.Ldc, typeof(OpCodeLdc)); + Set("1110111011010x", InstEmit.Ldg, typeof(OpCodeMemory)); + Set("1110111101001x", InstEmit.Lds, typeof(OpCodeMemory)); + Set("0100110001000x", InstEmit.Lop, typeof(OpCodeLopCbuf)); + Set("0011100001000x", InstEmit.Lop, typeof(OpCodeLopImm)); + Set("000001xxxxxxxx", InstEmit.Lop, typeof(OpCodeLopImm32)); + Set("0101110001000x", InstEmit.Lop, typeof(OpCodeLopReg)); + Set("0010000xxxxxxx", InstEmit.Lop3, typeof(OpCodeLopCbuf)); + Set("001111xxxxxxxx", InstEmit.Lop3, typeof(OpCodeLopImm)); + Set("0101101111100x", InstEmit.Lop3, typeof(OpCodeLopReg)); + Set("1110111110011x", InstEmit.Membar, typeof(OpCodeMemoryBarrier)); + Set("0100110010011x", InstEmit.Mov, typeof(OpCodeAluCbuf)); + Set("0011100x10011x", InstEmit.Mov, typeof(OpCodeAluImm)); + Set("000000010000xx", InstEmit.Mov, typeof(OpCodeAluImm32)); + Set("0101110010011x", InstEmit.Mov, typeof(OpCodeAluReg)); + Set("0101000010000x", InstEmit.Mufu, typeof(OpCodeFArith)); + Set("1111101111100x", InstEmit.Out, typeof(OpCode)); + Set("111000101010xx", InstEmit.Pbk, typeof(OpCodePush)); + Set("0100110000001x", InstEmit.Popc, typeof(OpCodeAluCbuf)); + Set("0011100x00001x", InstEmit.Popc, typeof(OpCodeAluImm)); + Set("0101110000001x", InstEmit.Popc, typeof(OpCodeAluReg)); + Set("0101000010001x", InstEmit.Pset, typeof(OpCodePset)); + Set("0101000010010x", InstEmit.Psetp, typeof(OpCodePset)); + Set("0100110011110x", InstEmit.R2p, typeof(OpCodeAluCbuf)); + Set("0011100x11110x", InstEmit.R2p, typeof(OpCodeAluImm)); + Set("0101110011110x", InstEmit.R2p, typeof(OpCodeAluReg)); + Set("1110101111111x", InstEmit.Red, typeof(OpCodeRed)); + Set("0100110010010x", InstEmit.Rro, typeof(OpCodeFArithCbuf)); + Set("0011100x10010x", InstEmit.Rro, typeof(OpCodeFArithImm)); + Set("0101110010010x", InstEmit.Rro, typeof(OpCodeFArithReg)); + Set("1111000011001x", InstEmit.S2r, typeof(OpCodeAlu)); + Set("0100110010100x", InstEmit.Sel, typeof(OpCodeAluCbuf)); + Set("0011100x10100x", InstEmit.Sel, typeof(OpCodeAluImm)); + Set("0101110010100x", InstEmit.Sel, typeof(OpCodeAluReg)); + Set("1110111100010x", InstEmit.Shfl, typeof(OpCodeShuffle)); + Set("0100110001001x", InstEmit.Shl, typeof(OpCodeAluCbuf)); + Set("0011100x01001x", InstEmit.Shl, typeof(OpCodeAluImm)); + Set("0101110001001x", InstEmit.Shl, typeof(OpCodeAluReg)); + Set("0100110000101x", InstEmit.Shr, typeof(OpCodeAluCbuf)); + Set("0011100x00101x", InstEmit.Shr, typeof(OpCodeAluImm)); + Set("0101110000101x", InstEmit.Shr, typeof(OpCodeAluReg)); + Set("111000101001xx", InstEmit.Ssy, typeof(OpCodePush)); + Set("1110111101010x", InstEmit.St, typeof(OpCodeMemory)); + Set("1110111011011x", InstEmit.Stg, typeof(OpCodeMemory)); + Set("1110111101011x", InstEmit.Sts, typeof(OpCodeMemory)); + Set("11101011001xxx", InstEmit.Sust, typeof(OpCodeImage)); + Set("1111000011111x", InstEmit.Sync, typeof(OpCodeBranchPop)); + Set("110000xxxx111x", InstEmit.Tex, typeof(OpCodeTex)); + Set("1101111010111x", InstEmit.TexB, typeof(OpCodeTexB)); + Set("1101x00xxxxxxx", InstEmit.Texs, typeof(OpCodeTexs)); + Set("1101x01xxxxxxx", InstEmit.Texs, typeof(OpCodeTlds)); + Set("11011111x0xxxx", InstEmit.Texs, typeof(OpCodeTld4s)); + Set("11011100xx111x", InstEmit.Tld, typeof(OpCodeTld)); + Set("11011101xx111x", InstEmit.TldB, typeof(OpCodeTld)); + Set("110010xxxx111x", InstEmit.Tld4, typeof(OpCodeTld4)); + Set("1101111011111x", InstEmit.Tld4, typeof(OpCodeTld4B)); + Set("110111100x1110", InstEmit.Txd, typeof(OpCodeTxd)); + Set("1101111101001x", InstEmit.Txq, typeof(OpCodeTex)); + Set("1101111101010x", InstEmit.TxqB, typeof(OpCodeTex)); + Set("01011111xxxxxx", InstEmit.Vmad, typeof(OpCodeVideo)); + Set("0101000011011x", InstEmit.Vote, typeof(OpCodeVote)); + Set("0100111xxxxxxx", InstEmit.Xmad, typeof(OpCodeAluCbuf)); + Set("0011011x00xxxx", InstEmit.Xmad, typeof(OpCodeAluImm)); + Set("010100010xxxxx", InstEmit.Xmad, typeof(OpCodeAluRegCbuf)); + Set("0101101100xxxx", InstEmit.Xmad, typeof(OpCodeAluReg)); +#endregion + } + + private static void Set(string encoding, InstEmitter emitter, Type opCodeType) + { + if (encoding.Length != EncodingBits) + { + throw new ArgumentException(nameof(encoding)); + } + + int bit = encoding.Length - 1; + int value = 0; + int xMask = 0; + int xBits = 0; + + int[] xPos = new int[encoding.Length]; + + for (int index = 0; index < encoding.Length; index++, bit--) + { + char chr = encoding[index]; + + if (chr == '1') + { + value |= 1 << bit; + } + else if (chr == 'x') + { + xMask |= 1 << bit; + + xPos[xBits++] = bit; + } + } + + xMask = ~xMask; + + TableEntry entry = new TableEntry(emitter, opCodeType, xBits); + + for (int index = 0; index < (1 << xBits); index++) + { + value &= xMask; + + for (int x = 0; x < xBits; x++) + { + value |= ((index >> x) & 1) << xPos[x]; + } + + if (_opCodes[value] == null || _opCodes[value].XBits > xBits) + { + _opCodes[value] = entry; + } + } + } + + public static (InstEmitter emitter, Type opCodeType) GetEmitter(long opCode) + { + TableEntry entry = _opCodes[(ulong)opCode >> (64 - EncodingBits)]; + + if (entry != null) + { + return (entry.Emitter, entry.OpCodeType); + } + + return (null, null); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeTex.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeTex.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeTex.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeTex.cs diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeTexB.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeTexB.cs new file mode 100644 index 0000000000..b18bf3befa --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeTexB.cs @@ -0,0 +1,20 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeTexB : OpCodeTex + { + public OpCodeTexB(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + switch (opCode.Extract(37, 3)) + { + case 0: LodMode = TextureLodMode.None; break; + case 1: LodMode = TextureLodMode.LodZero; break; + case 2: LodMode = TextureLodMode.LodBias; break; + case 3: LodMode = TextureLodMode.LodLevel; break; + case 6: LodMode = TextureLodMode.LodBiasA; break; + case 7: LodMode = TextureLodMode.LodLevelA; break; + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeTexs.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeTexs.cs similarity index 78% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeTexs.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeTexs.cs index 0822c4c074..fb90ccf60a 100644 --- a/Ryujinx.Graphics/Shader/Decoders/OpCodeTexs.cs +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeTexs.cs @@ -4,7 +4,7 @@ namespace Ryujinx.Graphics.Shader.Decoders { class OpCodeTexs : OpCodeTextureScalar { - public TextureScalarType Type => (TextureScalarType)RawType; + public TextureTarget Target => (TextureTarget)RawType; public OpCodeTexs(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) { } } diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeTexture.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeTexture.cs similarity index 96% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeTexture.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeTexture.cs index 7a7e8f46e7..76e95118f7 100644 --- a/Ryujinx.Graphics/Shader/Decoders/OpCodeTexture.cs +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeTexture.cs @@ -2,7 +2,7 @@ using Ryujinx.Graphics.Shader.Instructions; namespace Ryujinx.Graphics.Shader.Decoders { - class OpCodeTexture : OpCode + class OpCodeTexture : OpCode, IOpCodeTexture { public Register Rd { get; } public Register Ra { get; } diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeTextureScalar.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeTextureScalar.cs similarity index 94% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeTextureScalar.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeTextureScalar.cs index 470b81f5c0..543f8d1367 100644 --- a/Ryujinx.Graphics/Shader/Decoders/OpCodeTextureScalar.cs +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeTextureScalar.cs @@ -35,11 +35,11 @@ namespace Ryujinx.Graphics.Shader.Decoders public int Immediate { get; } - public int ComponentMask { get; } + public int ComponentMask { get; protected set; } protected int RawType; - public bool IsFp16 { get; } + public bool IsFp16 { get; protected set; } public OpCodeTextureScalar(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) { diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeTld.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeTld.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeTld.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeTld.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeTld4.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeTld4.cs similarity index 84% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeTld4.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeTld4.cs index 485edf936b..0ffafbe1a4 100644 --- a/Ryujinx.Graphics/Shader/Decoders/OpCodeTld4.cs +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeTld4.cs @@ -2,12 +2,14 @@ using Ryujinx.Graphics.Shader.Instructions; namespace Ryujinx.Graphics.Shader.Decoders { - class OpCodeTld4 : OpCodeTexture + class OpCodeTld4 : OpCodeTexture, IOpCodeTld4 { public TextureGatherOffset Offset { get; } public int GatherCompIndex { get; } + public bool Bindless => false; + public OpCodeTld4(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) { HasDepthCompare = opCode.Extract(50); diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeTld4B.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeTld4B.cs new file mode 100644 index 0000000000..dc274d1415 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeTld4B.cs @@ -0,0 +1,22 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeTld4B : OpCodeTexture, IOpCodeTld4 + { + public TextureGatherOffset Offset { get; } + + public int GatherCompIndex { get; } + + public bool Bindless => true; + + public OpCodeTld4B(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + HasDepthCompare = opCode.Extract(50); + + Offset = (TextureGatherOffset)opCode.Extract(36, 2); + + GatherCompIndex = opCode.Extract(38, 2); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeTld4s.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeTld4s.cs similarity index 86% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeTld4s.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeTld4s.cs index 0d7b84606a..fd3240a0ee 100644 --- a/Ryujinx.Graphics/Shader/Decoders/OpCodeTld4s.cs +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeTld4s.cs @@ -15,6 +15,10 @@ namespace Ryujinx.Graphics.Shader.Decoders HasOffset = opCode.Extract(51); GatherCompIndex = opCode.Extract(52, 2); + + IsFp16 = opCode.Extract(55); + + ComponentMask = Rd1.IsRZ ? 3 : 0xf; } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeTlds.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeTlds.cs similarity index 78% rename from Ryujinx.Graphics/Shader/Decoders/OpCodeTlds.cs rename to Ryujinx.Graphics.Shader/Decoders/OpCodeTlds.cs index e117721e9e..1e4e943ffa 100644 --- a/Ryujinx.Graphics/Shader/Decoders/OpCodeTlds.cs +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeTlds.cs @@ -4,7 +4,7 @@ namespace Ryujinx.Graphics.Shader.Decoders { class OpCodeTlds : OpCodeTextureScalar { - public TexelLoadScalarType Type => (TexelLoadScalarType)RawType; + public TexelLoadTarget Target => (TexelLoadTarget)RawType; public OpCodeTlds(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) { } } diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeTxd.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeTxd.cs new file mode 100644 index 0000000000..25df1f81f9 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeTxd.cs @@ -0,0 +1,18 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeTxd : OpCodeTexture + { + public bool IsBindless { get; } + + public OpCodeTxd(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + HasOffset = opCode.Extract(35); + + IsBindless = opCode.Extract(54); + + LodMode = TextureLodMode.None; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeVideo.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeVideo.cs new file mode 100644 index 0000000000..15dcfa981f --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeVideo.cs @@ -0,0 +1,24 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeVideo : OpCode, IOpCodeRd, IOpCodeRa, IOpCodeRc + { + public Register Rd { get; } + public Register Ra { get; } + public Register Rc { get; } + + public bool SetCondCode { get; protected set; } + public bool Saturate { get; protected set; } + + public OpCodeVideo(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rd = new Register(opCode.Extract(0, 8), RegisterType.Gpr); + Ra = new Register(opCode.Extract(8, 8), RegisterType.Gpr); + Rc = new Register(opCode.Extract(39, 8), RegisterType.Gpr); + + SetCondCode = opCode.Extract(47); + Saturate = opCode.Extract(55); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeVote.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeVote.cs new file mode 100644 index 0000000000..374767bd2d --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeVote.cs @@ -0,0 +1,26 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeVote : OpCode, IOpCodeRd, IOpCodePredicate39 + { + public Register Rd { get; } + public Register Predicate39 { get; } + public Register Predicate45 { get; } + + public VoteOp VoteOp { get; } + + public bool InvertP { get; } + + public OpCodeVote(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rd = new Register(opCode.Extract(0, 8), RegisterType.Gpr); + Predicate39 = new Register(opCode.Extract(39, 3), RegisterType.Predicate); + Predicate45 = new Register(opCode.Extract(45, 3), RegisterType.Predicate); + + InvertP = opCode.Extract(42); + + VoteOp = (VoteOp)opCode.Extract(48, 2); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/ReductionType.cs b/Ryujinx.Graphics.Shader/Decoders/ReductionType.cs new file mode 100644 index 0000000000..aaa2186e73 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/ReductionType.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum ReductionType + { + U32 = 0, + S32 = 1, + U64 = 2, + FP32FtzRn = 3, + U128 = 4, + S64 = 5 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/Register.cs b/Ryujinx.Graphics.Shader/Decoders/Register.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/Register.cs rename to Ryujinx.Graphics.Shader/Decoders/Register.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/RegisterConsts.cs b/Ryujinx.Graphics.Shader/Decoders/RegisterConsts.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/RegisterConsts.cs rename to Ryujinx.Graphics.Shader/Decoders/RegisterConsts.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/RegisterType.cs b/Ryujinx.Graphics.Shader/Decoders/RegisterType.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/RegisterType.cs rename to Ryujinx.Graphics.Shader/Decoders/RegisterType.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/RoundingMode.cs b/Ryujinx.Graphics.Shader/Decoders/RoundingMode.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/RoundingMode.cs rename to Ryujinx.Graphics.Shader/Decoders/RoundingMode.cs diff --git a/Ryujinx.Graphics.Shader/Decoders/ShuffleType.cs b/Ryujinx.Graphics.Shader/Decoders/ShuffleType.cs new file mode 100644 index 0000000000..2892c8dd1c --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/ShuffleType.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum ShuffleType + { + Indexed = 0, + Up = 1, + Down = 2, + Butterfly = 3 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/SystemRegister.cs b/Ryujinx.Graphics.Shader/Decoders/SystemRegister.cs new file mode 100644 index 0000000000..45ef378267 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/SystemRegister.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum SystemRegister + { + LaneId = 0, + YDirection = 0x12, + ThreadId = 0x20, + ThreadIdX = 0x21, + ThreadIdY = 0x22, + ThreadIdZ = 0x23, + CtaIdX = 0x25, + CtaIdY = 0x26, + CtaIdZ = 0x27, + EqMask = 0x38, + LtMask = 0x39, + LeMask = 0x3a, + GtMask = 0x3b, + GeMask = 0x3c + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/TexelLoadTarget.cs b/Ryujinx.Graphics.Shader/Decoders/TexelLoadTarget.cs new file mode 100644 index 0000000000..e5a0c004b5 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/TexelLoadTarget.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum TexelLoadTarget + { + Texture1DLodZero = 0x0, + Texture1DLodLevel = 0x1, + Texture2DLodZero = 0x2, + Texture2DLodZeroOffset = 0x4, + Texture2DLodLevel = 0x5, + Texture2DLodZeroMultisample = 0x6, + Texture3DLodZero = 0x7, + Texture2DArrayLodZero = 0x8, + Texture2DLodLevelOffset = 0xc + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/TextureDimensions.cs b/Ryujinx.Graphics.Shader/Decoders/TextureDimensions.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/TextureDimensions.cs rename to Ryujinx.Graphics.Shader/Decoders/TextureDimensions.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/TextureGatherOffset.cs b/Ryujinx.Graphics.Shader/Decoders/TextureGatherOffset.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/TextureGatherOffset.cs rename to Ryujinx.Graphics.Shader/Decoders/TextureGatherOffset.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/TextureLodMode.cs b/Ryujinx.Graphics.Shader/Decoders/TextureLodMode.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/TextureLodMode.cs rename to Ryujinx.Graphics.Shader/Decoders/TextureLodMode.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/TextureProperty.cs b/Ryujinx.Graphics.Shader/Decoders/TextureProperty.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/TextureProperty.cs rename to Ryujinx.Graphics.Shader/Decoders/TextureProperty.cs diff --git a/Ryujinx.Graphics/Shader/Decoders/TextureScalarType.cs b/Ryujinx.Graphics.Shader/Decoders/TextureTarget.cs similarity index 96% rename from Ryujinx.Graphics/Shader/Decoders/TextureScalarType.cs rename to Ryujinx.Graphics.Shader/Decoders/TextureTarget.cs index 0055174b4e..181a0a0d62 100644 --- a/Ryujinx.Graphics/Shader/Decoders/TextureScalarType.cs +++ b/Ryujinx.Graphics.Shader/Decoders/TextureTarget.cs @@ -1,6 +1,6 @@ namespace Ryujinx.Graphics.Shader.Decoders { - enum TextureScalarType + enum TextureTarget { Texture1DLodZero = 0x0, Texture2D = 0x1, diff --git a/Ryujinx.Graphics.Shader/Decoders/VoteOp.cs b/Ryujinx.Graphics.Shader/Decoders/VoteOp.cs new file mode 100644 index 0000000000..2fe937c8c4 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/VoteOp.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum VoteOp + { + All = 0, + Any = 1, + AllEqual = 2 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/XmadCMode.cs b/Ryujinx.Graphics.Shader/Decoders/XmadCMode.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Decoders/XmadCMode.cs rename to Ryujinx.Graphics.Shader/Decoders/XmadCMode.cs diff --git a/Ryujinx.Graphics.Shader/DefineNames.cs b/Ryujinx.Graphics.Shader/DefineNames.cs new file mode 100644 index 0000000000..b043049919 --- /dev/null +++ b/Ryujinx.Graphics.Shader/DefineNames.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Graphics.Shader +{ + public static class DefineNames + { + public const string OutQualifierPrefixName = "S_OUT_QUALIFIER"; + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/InputTopology.cs b/Ryujinx.Graphics.Shader/InputTopology.cs new file mode 100644 index 0000000000..3b0dda45f1 --- /dev/null +++ b/Ryujinx.Graphics.Shader/InputTopology.cs @@ -0,0 +1,28 @@ +namespace Ryujinx.Graphics.Shader +{ + public enum InputTopology + { + Points, + Lines, + LinesAdjacency, + Triangles, + TrianglesAdjacency + } + + static class InputTopologyExtensions + { + public static string ToGlslString(this InputTopology topology) + { + switch (topology) + { + case InputTopology.Points: return "points"; + case InputTopology.Lines: return "lines"; + case InputTopology.LinesAdjacency: return "lines_adjacency"; + case InputTopology.Triangles: return "triangles"; + case InputTopology.TrianglesAdjacency: return "triangles_adjacency"; + } + + return "points"; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Instructions/InstEmitAlu.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitAlu.cs similarity index 80% rename from Ryujinx.Graphics/Shader/Instructions/InstEmitAlu.cs rename to Ryujinx.Graphics.Shader/Instructions/InstEmitAlu.cs index 8e2b39bfe5..92354cece3 100644 --- a/Ryujinx.Graphics/Shader/Instructions/InstEmitAlu.cs +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitAlu.cs @@ -39,6 +39,64 @@ namespace Ryujinx.Graphics.Shader.Instructions // TODO: CC, X, corner cases } + public static void Bfi(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + Operand srcA = GetSrcA(context); + Operand srcB = GetSrcB(context); + Operand srcC = GetSrcC(context); + + Operand position = context.BitwiseAnd(srcB, Const(0xff)); + + Operand size = context.BitfieldExtractU32(srcB, Const(8), Const(8)); + + Operand res = context.BitfieldInsert(srcC, srcA, position, size); + + context.Copy(GetDest(context), res); + } + + public static void Csetp(EmitterContext context) + { + OpCodePset op = (OpCodePset)context.CurrOp; + + // TODO: Implement that properly + + Operand p0Res = Const(IrConsts.True); + + Operand p1Res = context.BitwiseNot(p0Res); + + Operand pred = GetPredicate39(context); + + p0Res = GetPredLogicalOp(context, op.LogicalOp, p0Res, pred); + p1Res = GetPredLogicalOp(context, op.LogicalOp, p1Res, pred); + + context.Copy(Register(op.Predicate3), p0Res); + context.Copy(Register(op.Predicate0), p1Res); + } + + public static void Flo(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + bool invert = op.RawOpCode.Extract(40); + bool countZeros = op.RawOpCode.Extract(41); + bool isSigned = op.RawOpCode.Extract(48); + + Operand srcB = context.BitwiseNot(GetSrcB(context), invert); + + Operand res = isSigned + ? context.FindFirstSetS32(srcB) + : context.FindFirstSetU32(srcB); + + if (countZeros) + { + res = context.BitwiseExclusiveOr(res, Const(31)); + } + + context.Copy(GetDest(context), res); + } + public static void Iadd(EmitterContext context) { OpCodeAlu op = (OpCodeAlu)context.CurrOp; @@ -50,6 +108,11 @@ namespace Ryujinx.Graphics.Shader.Instructions negateB = op.RawOpCode.Extract(48); negateA = op.RawOpCode.Extract(49); } + else + { + // TODO: Other IADD32I variant without the negate. + negateA = op.RawOpCode.Extract(56); + } Operand srcA = context.INegate(GetSrcA(context), negateA); Operand srcB = context.INegate(GetSrcB(context), negateB); @@ -62,8 +125,8 @@ namespace Ryujinx.Graphics.Shader.Instructions { // Add carry, or subtract borrow. res = context.IAdd(res, isSubtraction - ? context.BitwiseNot(GetCF(context)) - : context.BitwiseAnd(GetCF(context), Const(1))); + ? context.BitwiseNot(GetCF()) + : context.BitwiseAnd(GetCF(), Const(1))); } SetIaddFlags(context, res, srcA, srcB, op.SetCondCode, op.Extended, isSubtraction); @@ -137,6 +200,52 @@ namespace Ryujinx.Graphics.Shader.Instructions // TODO: CC, X, corner cases } + public static void Imad(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + bool signedA = context.CurrOp.RawOpCode.Extract(48); + bool signedB = context.CurrOp.RawOpCode.Extract(53); + bool high = context.CurrOp.RawOpCode.Extract(54); + + Operand srcA = GetSrcA(context); + Operand srcB = GetSrcB(context); + Operand srcC = GetSrcC(context); + + Operand res; + + if (high) + { + if (signedA && signedB) + { + res = context.MultiplyHighS32(srcA, srcB); + } + else + { + res = context.MultiplyHighU32(srcA, srcB); + + if (signedA) + { + res = context.IAdd(res, context.IMultiply(srcB, context.ShiftRightS32(srcA, Const(31)))); + } + else if (signedB) + { + res = context.IAdd(res, context.IMultiply(srcA, context.ShiftRightS32(srcB, Const(31)))); + } + } + } + else + { + res = context.IMultiply(srcA, srcB); + } + + res = context.IAdd(res, srcC); + + // TODO: CC, X, SAT, and more? + + context.Copy(GetDest(context), res); + } + public static void Imnmx(EmitterContext context) { OpCodeAlu op = (OpCodeAlu)context.CurrOp; @@ -218,14 +327,20 @@ namespace Ryujinx.Graphics.Shader.Instructions if (boolFloat) { - context.Copy(dest, context.ConditionalSelect(res, ConstF(1), Const(0))); + res = context.ConditionalSelect(res, ConstF(1), Const(0)); + + context.Copy(dest, res); + + SetFPZnFlags(context, res, op.SetCondCode); } else { context.Copy(dest, res); + + SetZnFlags(context, res, op.SetCondCode, op.Extended); } - // TODO: CC, X + // TODO: X } public static void Isetp(EmitterContext context) @@ -305,15 +420,51 @@ namespace Ryujinx.Graphics.Shader.Instructions SetZnFlags(context, dest, op.SetCondCode, op.Extended); } + public static void Popc(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + bool invert = op.RawOpCode.Extract(40); + + Operand srcB = context.BitwiseNot(GetSrcB(context), invert); + + Operand res = context.BitCount(srcB); + + context.Copy(GetDest(context), res); + } + + public static void Pset(EmitterContext context) + { + OpCodePset op = (OpCodePset)context.CurrOp; + + bool boolFloat = op.RawOpCode.Extract(44); + + Operand srcA = context.BitwiseNot(Register(op.Predicate12), op.InvertA); + Operand srcB = context.BitwiseNot(Register(op.Predicate29), op.InvertB); + Operand srcC = context.BitwiseNot(Register(op.Predicate39), op.InvertP); + + Operand res = GetPredLogicalOp(context, op.LogicalOpAB, srcA, srcB); + + res = GetPredLogicalOp(context, op.LogicalOp, res, srcC); + + Operand dest = GetDest(context); + + if (boolFloat) + { + context.Copy(dest, context.ConditionalSelect(res, ConstF(1), Const(0))); + } + else + { + context.Copy(dest, res); + } + } + public static void Psetp(EmitterContext context) { - OpCodePsetp op = (OpCodePsetp)context.CurrOp; + OpCodePset op = (OpCodePset)context.CurrOp; - bool invertA = op.RawOpCode.Extract(15); - bool invertB = op.RawOpCode.Extract(32); - - Operand srcA = context.BitwiseNot(Register(op.Predicate12), invertA); - Operand srcB = context.BitwiseNot(Register(op.Predicate29), invertB); + Operand srcA = context.BitwiseNot(Register(op.Predicate12), op.InvertA); + Operand srcB = context.BitwiseNot(Register(op.Predicate29), op.InvertB); Operand p0Res = GetPredLogicalOp(context, op.LogicalOpAB, srcA, srcB); @@ -542,7 +693,7 @@ namespace Ryujinx.Graphics.Shader.Instructions if (extended) { // Add with carry. - res = context.IAdd(res, context.BitwiseAnd(GetCF(context), Const(1))); + res = context.IAdd(res, context.BitwiseAnd(GetCF(), Const(1))); } else { @@ -655,7 +806,7 @@ namespace Ryujinx.Graphics.Shader.Instructions if (!extended || isSubtraction) { // C = d < a - context.Copy(GetCF(context), context.ICompareLessUnsigned(res, srcA)); + context.Copy(GetCF(), context.ICompareLessUnsigned(res, srcA)); } else { @@ -663,9 +814,9 @@ namespace Ryujinx.Graphics.Shader.Instructions Operand tempC0 = context.ICompareEqual (res, srcA); Operand tempC1 = context.ICompareLessUnsigned(res, srcA); - tempC0 = context.BitwiseAnd(tempC0, GetCF(context)); + tempC0 = context.BitwiseAnd(tempC0, GetCF()); - context.Copy(GetCF(context), context.BitwiseOr(tempC0, tempC1)); + context.Copy(GetCF(), context.BitwiseOr(tempC0, tempC1)); } // V = (d ^ a) & ~(a ^ b) < 0 @@ -676,7 +827,7 @@ namespace Ryujinx.Graphics.Shader.Instructions Operand tempV = context.BitwiseAnd(tempV0, tempV1); - context.Copy(GetVF(context), context.ICompareLess(tempV, Const(0))); + context.Copy(GetVF(), context.ICompareLess(tempV, Const(0))); SetZnFlags(context, res, setCC: true, extended: extended); } diff --git a/Ryujinx.Graphics/Shader/Instructions/InstEmitAluHelper.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitAluHelper.cs similarity index 77% rename from Ryujinx.Graphics/Shader/Instructions/InstEmitAluHelper.cs rename to Ryujinx.Graphics.Shader/Instructions/InstEmitAluHelper.cs index 5c4f539885..572068dad3 100644 --- a/Ryujinx.Graphics/Shader/Instructions/InstEmitAluHelper.cs +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitAluHelper.cs @@ -10,7 +10,7 @@ namespace Ryujinx.Graphics.Shader.Instructions { static class InstEmitAluHelper { - public static int GetIntMin(IntegerType type) + public static long GetIntMin(IntegerType type) { switch (type) { @@ -18,14 +18,14 @@ namespace Ryujinx.Graphics.Shader.Instructions case IntegerType.S8: return sbyte.MinValue; case IntegerType.U16: return ushort.MinValue; case IntegerType.S16: return short.MinValue; - case IntegerType.U32: return (int)uint.MinValue; + case IntegerType.U32: return uint.MinValue; case IntegerType.S32: return int.MinValue; } throw new ArgumentException($"The type \"{type}\" is not a supported int type."); } - public static int GetIntMax(IntegerType type) + public static long GetIntMax(IntegerType type) { switch (type) { @@ -33,7 +33,7 @@ namespace Ryujinx.Graphics.Shader.Instructions case IntegerType.S8: return sbyte.MaxValue; case IntegerType.U16: return ushort.MaxValue; case IntegerType.S16: return short.MaxValue; - case IntegerType.U32: return unchecked((int)uint.MaxValue); + case IntegerType.U32: return uint.MaxValue; case IntegerType.S32: return int.MaxValue; } @@ -71,18 +71,27 @@ namespace Ryujinx.Graphics.Shader.Instructions // previous result when extended is specified, to ensure // we have ZF set only if all words are zero, and not just // the last one. - Operand oldZF = GetZF(context); + Operand oldZF = GetZF(); Operand res = context.BitwiseAnd(context.ICompareEqual(dest, Const(0)), oldZF); - context.Copy(GetZF(context), res); + context.Copy(GetZF(), res); } else { - context.Copy(GetZF(context), context.ICompareEqual(dest, Const(0))); + context.Copy(GetZF(), context.ICompareEqual(dest, Const(0))); } - context.Copy(GetNF(context), context.ICompareLess(dest, Const(0))); + context.Copy(GetNF(), context.ICompareLess(dest, Const(0))); + } + + public static void SetFPZnFlags(EmitterContext context, Operand dest, bool setCC) + { + if (setCC) + { + context.Copy(GetZF(), context.FPCompareEqual(dest, ConstF(0))); + context.Copy(GetNF(), context.FPCompareLess (dest, ConstF(0))); + } } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Instructions/InstEmitConversion.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitConversion.cs similarity index 81% rename from Ryujinx.Graphics/Shader/Instructions/InstEmitConversion.cs rename to Ryujinx.Graphics.Shader/Instructions/InstEmitConversion.cs index c4de17501a..afec77616b 100644 --- a/Ryujinx.Graphics/Shader/Instructions/InstEmitConversion.cs +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitConversion.cs @@ -14,21 +14,23 @@ namespace Ryujinx.Graphics.Shader.Instructions { OpCodeFArith op = (OpCodeFArith)context.CurrOp; - FPType srcType = (FPType)op.RawOpCode.Extract(8, 2); - FPType dstType = (FPType)op.RawOpCode.Extract(10, 2); + FPType dstType = (FPType)op.RawOpCode.Extract(8, 2); + FPType srcType = (FPType)op.RawOpCode.Extract(10, 2); - bool pass = op.RawOpCode.Extract(40); + bool round = op.RawOpCode.Extract(42); bool negateB = op.RawOpCode.Extract(45); bool absoluteB = op.RawOpCode.Extract(49); - pass &= op.RoundingMode == RoundingMode.TowardsNegativeInfinity; - Operand srcB = context.FPAbsNeg(GetSrcB(context, srcType), absoluteB, negateB); - if (!pass) + if (round) { switch (op.RoundingMode) { + case RoundingMode.ToNearest: + srcB = context.FPRound(srcB); + break; + case RoundingMode.TowardsNegativeInfinity: srcB = context.FPFloor(srcB); break; @@ -56,6 +58,13 @@ namespace Ryujinx.Graphics.Shader.Instructions IntegerType intType = (IntegerType)op.RawOpCode.Extract(8, 2); + if (intType == IntegerType.U64) + { + context.Config.PrintLog("Unimplemented 64-bits F2I."); + + return; + } + bool isSmallInt = intType <= IntegerType.U16; FPType floatType = (FPType)op.RawOpCode.Extract(10, 2); @@ -73,6 +82,10 @@ namespace Ryujinx.Graphics.Shader.Instructions switch (op.RoundingMode) { + case RoundingMode.ToNearest: + srcB = context.FPRound(srcB); + break; + case RoundingMode.TowardsNegativeInfinity: srcB = context.FPFloor(srcB); break; @@ -86,13 +99,21 @@ namespace Ryujinx.Graphics.Shader.Instructions break; } - srcB = context.FPConvertToS32(srcB); - - // TODO: S/U64, conversion overflow handling. - if (intType != IntegerType.S32) + if (!isSignedInt) { - int min = GetIntMin(intType); - int max = GetIntMax(intType); + // Negative float to uint cast is undefined, so we clamp + // the value before conversion. + srcB = context.FPMaximum(srcB, ConstF(0)); + } + + srcB = isSignedInt + ? context.FPConvertToS32(srcB) + : context.FPConvertToU32(srcB); + + if (isSmallInt) + { + int min = (int)GetIntMin(intType); + int max = (int)GetIntMax(intType); srcB = isSignedInt ? context.IClampS32(srcB, Const(min), Const(max)) @@ -114,6 +135,8 @@ namespace Ryujinx.Graphics.Shader.Instructions IntegerType srcType = (IntegerType)op.RawOpCode.Extract(10, 2); + // TODO: Handle S/U64. + bool isSmallInt = srcType <= IntegerType.U16; bool isSignedInt = op.RawOpCode.Extract(13); @@ -149,7 +172,9 @@ namespace Ryujinx.Graphics.Shader.Instructions if (srcType == IntegerType.U64 || dstType == IntegerType.U64) { - // TODO: Warning. This instruction doesn't support 64-bits integers + context.Config.PrintLog("Invalid I2I encoding."); + + return; } bool srcIsSmallInt = srcType <= IntegerType.U16; @@ -179,8 +204,8 @@ namespace Ryujinx.Graphics.Shader.Instructions dstType |= IntegerType.S8; } - int min = GetIntMin(dstType); - int max = GetIntMax(dstType); + int min = (int)GetIntMin(dstType); + int max = (int)GetIntMax(dstType); srcB = dstIsSignedInt ? context.IClampS32(srcB, Const(min), Const(max)) diff --git a/Ryujinx.Graphics/Shader/Instructions/InstEmitFArith.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitFArith.cs similarity index 67% rename from Ryujinx.Graphics/Shader/Instructions/InstEmitFArith.cs rename to Ryujinx.Graphics.Shader/Instructions/InstEmitFArith.cs index b22639de9c..23f40d4692 100644 --- a/Ryujinx.Graphics/Shader/Instructions/InstEmitFArith.cs +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitFArith.cs @@ -59,6 +59,26 @@ namespace Ryujinx.Graphics.Shader.Instructions SetFPZnFlags(context, dest, op.SetCondCode); } + public static void Ffma32i(EmitterContext context) + { + IOpCodeFArith op = (IOpCodeFArith)context.CurrOp; + + bool saturate = op.RawOpCode.Extract(55); + bool negateA = op.RawOpCode.Extract(56); + bool negateC = op.RawOpCode.Extract(57); + + Operand srcA = context.FPNegate(GetSrcA(context), negateA); + Operand srcC = context.FPNegate(GetDest(context), negateC); + + Operand srcB = GetSrcB(context); + + Operand dest = GetDest(context); + + context.Copy(dest, context.FPSaturate(context.FPFusedMultiplyAdd(srcA, srcB, srcC), saturate)); + + SetFPZnFlags(context, dest, op.SetCondCode); + } + public static void Fmnmx(EmitterContext context) { IOpCodeFArith op = (IOpCodeFArith)context.CurrOp; @@ -87,7 +107,9 @@ namespace Ryujinx.Graphics.Shader.Instructions { IOpCodeFArith op = (IOpCodeFArith)context.CurrOp; - bool negateB = !(op is OpCodeFArithImm32) && op.RawOpCode.Extract(48); + bool isImm32 = op is OpCodeFArithImm32; + + bool negateB = !isImm32 && op.RawOpCode.Extract(48); Operand srcA = GetSrcA(context); @@ -95,21 +117,23 @@ namespace Ryujinx.Graphics.Shader.Instructions switch (op.Scale) { - case FmulScale.None: break; + case FPMultiplyScale.None: break; - case FmulScale.Divide2: srcA = context.FPDivide (srcA, ConstF(2)); break; - case FmulScale.Divide4: srcA = context.FPDivide (srcA, ConstF(4)); break; - case FmulScale.Divide8: srcA = context.FPDivide (srcA, ConstF(8)); break; - case FmulScale.Multiply2: srcA = context.FPMultiply(srcA, ConstF(2)); break; - case FmulScale.Multiply4: srcA = context.FPMultiply(srcA, ConstF(4)); break; - case FmulScale.Multiply8: srcA = context.FPMultiply(srcA, ConstF(8)); break; + case FPMultiplyScale.Divide2: srcA = context.FPDivide (srcA, ConstF(2)); break; + case FPMultiplyScale.Divide4: srcA = context.FPDivide (srcA, ConstF(4)); break; + case FPMultiplyScale.Divide8: srcA = context.FPDivide (srcA, ConstF(8)); break; + case FPMultiplyScale.Multiply2: srcA = context.FPMultiply(srcA, ConstF(2)); break; + case FPMultiplyScale.Multiply4: srcA = context.FPMultiply(srcA, ConstF(4)); break; + case FPMultiplyScale.Multiply8: srcA = context.FPMultiply(srcA, ConstF(8)); break; default: break; //TODO: Warning. } Operand dest = GetDest(context); - context.Copy(dest, context.FPSaturate(context.FPMultiply(srcA, srcB), op.Saturate)); + bool saturate = isImm32 ? op.RawOpCode.Extract(55) : op.Saturate; + + context.Copy(dest, context.FPSaturate(context.FPMultiply(srcA, srcB), saturate)); SetFPZnFlags(context, dest, op.SetCondCode); } @@ -139,14 +163,20 @@ namespace Ryujinx.Graphics.Shader.Instructions if (boolFloat) { - context.Copy(dest, context.ConditionalSelect(res, ConstF(1), Const(0))); + res = context.ConditionalSelect(res, ConstF(1), Const(0)); + + context.Copy(dest, res); + + SetFPZnFlags(context, res, op.SetCondCode); } else { context.Copy(dest, res); + + SetZnFlags(context, res, op.SetCondCode, op.Extended); } - // TODO: CC, X + // TODO: X } public static void Fsetp(EmitterContext context) @@ -155,10 +185,10 @@ namespace Ryujinx.Graphics.Shader.Instructions Condition cmpOp = (Condition)op.RawOpCode.Extract(48, 4); + bool negateB = op.RawOpCode.Extract(6); bool absoluteA = op.RawOpCode.Extract(7); bool negateA = op.RawOpCode.Extract(43); bool absoluteB = op.RawOpCode.Extract(44); - bool negateB = op.RawOpCode.Extract(6); Operand srcA = context.FPAbsNeg(GetSrcA(context), absoluteA, negateA); Operand srcB = context.FPAbsNeg(GetSrcB(context), absoluteB, negateB); @@ -176,6 +206,22 @@ namespace Ryujinx.Graphics.Shader.Instructions context.Copy(Register(op.Predicate0), p1Res); } + public static void Fswzadd(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + int mask = op.RawOpCode.Extract(28, 8); + + Operand srcA = GetSrcA(context); + Operand srcB = GetSrcB(context); + + Operand dest = GetDest(context); + + context.Copy(dest, context.FPSwizzleAdd(srcA, srcB, mask)); + + SetFPZnFlags(context, dest, op.SetCondCode); + } + public static void Hadd2(EmitterContext context) { Hadd2Hmul2Impl(context, isAdd: true); @@ -210,9 +256,9 @@ namespace Ryujinx.Graphics.Shader.Instructions { OpCode op = context.CurrOp; - bool saturate = op.RawOpCode.Extract(op is OpCodeAluImm32 ? 52 : 32); + bool saturate = op.RawOpCode.Extract(op is IOpCodeReg ? 32 : 52); - Operand[] srcA = GetHalfSrcA(context); + Operand[] srcA = GetHalfSrcA(context, isAdd); Operand[] srcB = GetHalfSrcB(context); Operand[] res = new Operand[2]; @@ -234,6 +280,86 @@ namespace Ryujinx.Graphics.Shader.Instructions context.Copy(GetDest(context), GetHalfPacked(context, res)); } + public static void Hset2(EmitterContext context) + { + OpCodeSet op = (OpCodeSet)context.CurrOp; + + bool isRegVariant = op is IOpCodeReg; + + bool boolFloat = isRegVariant + ? op.RawOpCode.Extract(49) + : op.RawOpCode.Extract(53); + + Condition cmpOp = isRegVariant + ? (Condition)op.RawOpCode.Extract(35, 4) + : (Condition)op.RawOpCode.Extract(49, 4); + + Operand[] srcA = GetHalfSrcA(context); + Operand[] srcB = GetHalfSrcB(context); + + Operand[] res = new Operand[2]; + + res[0] = GetFPComparison(context, cmpOp, srcA[0], srcB[0]); + res[1] = GetFPComparison(context, cmpOp, srcA[1], srcB[1]); + + Operand pred = GetPredicate39(context); + + res[0] = GetPredLogicalOp(context, op.LogicalOp, res[0], pred); + res[1] = GetPredLogicalOp(context, op.LogicalOp, res[1], pred); + + if (boolFloat) + { + res[0] = context.ConditionalSelect(res[0], ConstF(1), Const(0)); + res[1] = context.ConditionalSelect(res[1], ConstF(1), Const(0)); + + context.Copy(GetDest(context), context.PackHalf2x16(res[0], res[1])); + } + else + { + Operand low = context.BitwiseAnd(res[0], Const(0xffff)); + Operand high = context.ShiftLeft (res[1], Const(16)); + + Operand packed = context.BitwiseOr(low, high); + + context.Copy(GetDest(context), packed); + } + } + + public static void Hsetp2(EmitterContext context) + { + OpCodeSet op = (OpCodeSet)context.CurrOp; + + bool isRegVariant = op is IOpCodeReg; + + bool hAnd = isRegVariant + ? op.RawOpCode.Extract(49) + : op.RawOpCode.Extract(53); + + Condition cmpOp = isRegVariant + ? (Condition)op.RawOpCode.Extract(35, 4) + : (Condition)op.RawOpCode.Extract(49, 4); + + Operand[] srcA = GetHalfSrcA(context); + Operand[] srcB = GetHalfSrcB(context); + + Operand p0Res = GetFPComparison(context, cmpOp, srcA[0], srcB[0]); + Operand p1Res = GetFPComparison(context, cmpOp, srcA[1], srcB[1]); + + if (hAnd) + { + p0Res = context.BitwiseAnd(p0Res, p1Res); + p1Res = context.BitwiseNot(p0Res); + } + + Operand pred = GetPredicate39(context); + + p0Res = GetPredLogicalOp(context, op.LogicalOp, p0Res, pred); + p1Res = GetPredLogicalOp(context, op.LogicalOp, p1Res, pred); + + context.Copy(Register(op.Predicate3), p0Res); + context.Copy(Register(op.Predicate0), p1Res); + } + public static void Mufu(EmitterContext context) { IOpCodeFArith op = (IOpCodeFArith)context.CurrOp; @@ -333,27 +459,18 @@ namespace Ryujinx.Graphics.Shader.Instructions return res; } - private static void SetFPZnFlags(EmitterContext context, Operand dest, bool setCC) - { - if (setCC) - { - context.Copy(GetZF(context), context.FPCompareEqual(dest, ConstF(0))); - context.Copy(GetNF(context), context.FPCompareLess (dest, ConstF(0))); - } - } - private static Operand[] GetHfmaSrcA(EmitterContext context) { IOpCodeHfma op = (IOpCodeHfma)context.CurrOp; - return GetHalfSources(context, GetSrcA(context), op.SwizzleA); + return GetHalfUnpacked(context, GetSrcA(context), op.SwizzleA); } private static Operand[] GetHfmaSrcB(EmitterContext context) { IOpCodeHfma op = (IOpCodeHfma)context.CurrOp; - Operand[] operands = GetHalfSources(context, GetSrcB(context), op.SwizzleB); + Operand[] operands = GetHalfUnpacked(context, GetSrcB(context), op.SwizzleB); return FPAbsNeg(context, operands, false, op.NegateB); } @@ -362,7 +479,7 @@ namespace Ryujinx.Graphics.Shader.Instructions { IOpCodeHfma op = (IOpCodeHfma)context.CurrOp; - Operand[] operands = GetHalfSources(context, GetSrcC(context), op.SwizzleC); + Operand[] operands = GetHalfUnpacked(context, GetSrcC(context), op.SwizzleC); return FPAbsNeg(context, operands, false, op.NegateC); } diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitFlow.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitFlow.cs new file mode 100644 index 0000000000..0b96d7518e --- /dev/null +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitFlow.cs @@ -0,0 +1,181 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using System.Collections.Generic; +using System.Linq; + +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void Bra(EmitterContext context) + { + EmitBranch(context, context.CurrBlock.Branch.Address); + } + + public static void Brk(EmitterContext context) + { + EmitBrkOrSync(context); + } + + public static void Brx(EmitterContext context) + { + OpCodeBranchIndir op = (OpCodeBranchIndir)context.CurrOp; + + int offset = (int)op.Address + 8 + op.Offset; + + Operand address = context.IAdd(Register(op.Ra), Const(offset)); + + // Sorting the target addresses in descending order improves the code, + // since it will always check the most distant targets first, then the + // near ones. This can be easily transformed into if/else statements. + IOrderedEnumerable sortedTargets = op.PossibleTargets.OrderByDescending(x => x.Address); + + Block lastTarget = sortedTargets.LastOrDefault(); + + foreach (Block possibleTarget in sortedTargets) + { + Operand label = context.GetLabel(possibleTarget.Address); + + if (possibleTarget != lastTarget) + { + context.BranchIfTrue(label, context.ICompareEqual(address, Const((int)possibleTarget.Address))); + } + else + { + context.Branch(label); + } + } + } + + public static void Exit(EmitterContext context) + { + OpCodeExit op = (OpCodeExit)context.CurrOp; + + // TODO: Figure out how this is supposed to work in the + // presence of other condition codes. + if (op.Condition == Condition.Always) + { + context.Return(); + } + } + + public static void Kil(EmitterContext context) + { + context.Discard(); + } + + public static void Pbk(EmitterContext context) + { + EmitPbkOrSsy(context); + } + + public static void Ssy(EmitterContext context) + { + EmitPbkOrSsy(context); + } + + public static void Sync(EmitterContext context) + { + EmitBrkOrSync(context); + } + + private static void EmitPbkOrSsy(EmitterContext context) + { + OpCodePush op = (OpCodePush)context.CurrOp; + + foreach (KeyValuePair kv in op.PopOps) + { + OpCodeBranchPop opSync = kv.Key; + + Operand local = kv.Value; + + int pushOpIndex = opSync.Targets[op]; + + context.Copy(local, Const(pushOpIndex)); + } + } + + private static void EmitBrkOrSync(EmitterContext context) + { + OpCodeBranchPop op = (OpCodeBranchPop)context.CurrOp; + + if (op.Targets.Count == 1) + { + // If we have only one target, then the SSY/PBK is basically + // a branch, we can produce better codegen for this case. + OpCodePush pushOp = op.Targets.Keys.First(); + + EmitBranch(context, pushOp.GetAbsoluteAddress()); + } + else + { + foreach (KeyValuePair kv in op.Targets) + { + OpCodePush pushOp = kv.Key; + + Operand label = context.GetLabel(pushOp.GetAbsoluteAddress()); + + Operand local = pushOp.PopOps[op]; + + int pushOpIndex = kv.Value; + + context.BranchIfTrue(label, context.ICompareEqual(local, Const(pushOpIndex))); + } + } + } + + private static void EmitBranch(EmitterContext context, ulong address) + { + OpCode op = context.CurrOp; + + // If we're branching to the next instruction, then the branch + // is useless and we can ignore it. + if (address == op.Address + 8) + { + return; + } + + Operand label = context.GetLabel(address); + + Operand pred = Register(op.Predicate); + + if (op is OpCodeBranch opBranch && opBranch.Condition != Condition.Always) + { + pred = context.BitwiseAnd(pred, GetCondition(context, opBranch.Condition)); + } + + if (op.Predicate.IsPT) + { + context.Branch(label); + } + else if (op.InvertPredicate) + { + context.BranchIfFalse(label, pred); + } + else + { + context.BranchIfTrue(label, pred); + } + } + + private static Operand GetCondition(EmitterContext context, Condition cond) + { + // TODO: More condition codes, figure out how they work. + switch (cond) + { + case Condition.Equal: + case Condition.EqualUnordered: + return GetZF(); + case Condition.NotEqual: + case Condition.NotEqualUnordered: + return context.BitwiseNot(GetZF()); + } + + return Const(IrConsts.True); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Instructions/InstEmitHelper.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitHelper.cs similarity index 88% rename from Ryujinx.Graphics/Shader/Instructions/InstEmitHelper.cs rename to Ryujinx.Graphics.Shader/Instructions/InstEmitHelper.cs index c87e1789d3..5123a6e2af 100644 --- a/Ryujinx.Graphics/Shader/Instructions/InstEmitHelper.cs +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitHelper.cs @@ -9,22 +9,22 @@ namespace Ryujinx.Graphics.Shader.Instructions { static class InstEmitHelper { - public static Operand GetZF(EmitterContext context) + public static Operand GetZF() { return Register(0, RegisterType.Flag); } - public static Operand GetNF(EmitterContext context) + public static Operand GetNF() { return Register(1, RegisterType.Flag); } - public static Operand GetCF(EmitterContext context) + public static Operand GetCF() { return Register(2, RegisterType.Flag); } - public static Operand GetVF(EmitterContext context) + public static Operand GetVF() { return Register(3, RegisterType.Flag); } @@ -49,14 +49,16 @@ namespace Ryujinx.Graphics.Shader.Instructions { int h = context.CurrOp.RawOpCode.Extract(41, 1); - return GetHalfSources(context, GetSrcB(context), FPHalfSwizzle.FP16)[h]; + return GetHalfUnpacked(context, GetSrcB(context), FPHalfSwizzle.FP16)[h]; } else if (floatType == FPType.FP64) { - // TODO. + // TODO: Double floating-point type support. } - throw new ArgumentException($"Invalid floating point type \"{floatType}\"."); + context.Config.PrintLog($"Invalid floating point type: {floatType}."); + + return ConstF(0); } public static Operand GetSrcB(EmitterContext context) @@ -96,13 +98,17 @@ namespace Ryujinx.Graphics.Shader.Instructions throw new InvalidOperationException($"Unexpected opcode type \"{context.CurrOp.GetType().Name}\"."); } - public static Operand[] GetHalfSrcA(EmitterContext context) + public static Operand[] GetHalfSrcA(EmitterContext context, bool isAdd = false) { OpCode op = context.CurrOp; bool absoluteA = false, negateA = false; - if (op is IOpCodeCbuf || op is IOpCodeImm) + if (op is OpCodeAluImm32 && isAdd) + { + negateA = op.RawOpCode.Extract(56); + } + else if (isAdd || op is IOpCodeCbuf || op is IOpCodeImm) { negateA = op.RawOpCode.Extract(43); absoluteA = op.RawOpCode.Extract(44); @@ -111,14 +117,10 @@ namespace Ryujinx.Graphics.Shader.Instructions { absoluteA = op.RawOpCode.Extract(44); } - else if (op is OpCodeAluImm32 && op.Emitter == InstEmit.Hadd2) - { - negateA = op.RawOpCode.Extract(56); - } FPHalfSwizzle swizzle = (FPHalfSwizzle)op.RawOpCode.Extract(47, 2); - Operand[] operands = GetHalfSources(context, GetSrcA(context), swizzle); + Operand[] operands = GetHalfUnpacked(context, GetSrcA(context), swizzle); return FPAbsNeg(context, operands, absoluteA, negateA); } @@ -145,7 +147,7 @@ namespace Ryujinx.Graphics.Shader.Instructions absoluteB = op.RawOpCode.Extract(54); } - Operand[] operands = GetHalfSources(context, GetSrcB(context), swizzle); + Operand[] operands = GetHalfUnpacked(context, GetSrcB(context), swizzle); return FPAbsNeg(context, operands, absoluteB, negateB); } @@ -160,7 +162,7 @@ namespace Ryujinx.Graphics.Shader.Instructions return operands; } - public static Operand[] GetHalfSources(EmitterContext context, Operand src, FPHalfSwizzle swizzle) + public static Operand[] GetHalfUnpacked(EmitterContext context, Operand src, FPHalfSwizzle swizzle) { switch (swizzle) { @@ -240,7 +242,7 @@ namespace Ryujinx.Graphics.Shader.Instructions public static Operand GetPredicate39(EmitterContext context) { - IOpCodeAlu op = (IOpCodeAlu)context.CurrOp; + IOpCodePredicate39 op = (IOpCodePredicate39)context.CurrOp; Operand local = Register(op.Predicate39); diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitMemory.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitMemory.cs new file mode 100644 index 0000000000..25bf259255 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitMemory.cs @@ -0,0 +1,609 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; + +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + private enum MemoryRegion + { + Local, + Shared + } + + public static void Ald(EmitterContext context) + { + OpCodeAttribute op = (OpCodeAttribute)context.CurrOp; + + Operand primVertex = context.Copy(GetSrcC(context)); + + for (int index = 0; index < op.Count; index++) + { + Register rd = new Register(op.Rd.Index + index, RegisterType.Gpr); + + if (rd.IsRZ) + { + break; + } + + Operand src = Attribute(op.AttributeOffset + index * 4); + + context.Copy(Register(rd), context.LoadAttribute(src, primVertex)); + } + } + + public static void Ast(EmitterContext context) + { + OpCodeAttribute op = (OpCodeAttribute)context.CurrOp; + + for (int index = 0; index < op.Count; index++) + { + if (op.Rd.Index + index > RegisterConsts.RegisterZeroIndex) + { + break; + } + + Register rd = new Register(op.Rd.Index + index, RegisterType.Gpr); + + Operand dest = Attribute(op.AttributeOffset + index * 4); + + context.Copy(dest, Register(rd)); + } + } + + public static void Atoms(EmitterContext context) + { + OpCodeAtom op = (OpCodeAtom)context.CurrOp; + + Operand offset = context.ShiftRightU32(GetSrcA(context), Const(2)); + + offset = context.IAdd(offset, Const(op.Offset)); + + Operand value = GetSrcB(context); + + Operand res = EmitAtomicOp( + context, + Instruction.MrShared, + op.AtomicOp, + op.Type, + offset, + Const(0), + value); + + context.Copy(GetDest(context), res); + } + + public static void Bar(EmitterContext context) + { + OpCodeBarrier op = (OpCodeBarrier)context.CurrOp; + + // TODO: Support other modes. + if (op.Mode == BarrierMode.Sync) + { + context.Barrier(); + } + else + { + context.Config.PrintLog($"Invalid barrier mode: {op.Mode}."); + } + } + + public static void Ipa(EmitterContext context) + { + OpCodeIpa op = (OpCodeIpa)context.CurrOp; + + InterpolationQualifier iq = InterpolationQualifier.None; + + switch (op.Mode) + { + case InterpolationMode.Pass: iq = InterpolationQualifier.NoPerspective; break; + } + + Operand srcA = Attribute(op.AttributeOffset, iq); + + Operand res = context.FPSaturate(srcA, op.Saturate); + + context.Copy(GetDest(context), res); + } + + public static void Isberd(EmitterContext context) + { + // This instruction performs a load from ISBE memory, + // however it seems to be only used to get some vertex + // input data, so we instead propagate the offset so that + // it can be used on the attribute load. + context.Copy(GetDest(context), GetSrcA(context)); + } + + public static void Ld(EmitterContext context) + { + EmitLoad(context, MemoryRegion.Local); + } + + public static void Ldc(EmitterContext context) + { + OpCodeLdc op = (OpCodeLdc)context.CurrOp; + + if (op.Size > IntegerSize.B64) + { + context.Config.PrintLog($"Invalid LDC size: {op.Size}."); + } + + bool isSmallInt = op.Size < IntegerSize.B32; + + int count = op.Size == IntegerSize.B64 ? 2 : 1; + + Operand wordOffset = context.ShiftRightU32(GetSrcA(context), Const(2)); + + wordOffset = context.IAdd(wordOffset, Const(op.Offset)); + + Operand bitOffset = GetBitOffset(context, GetSrcA(context)); + + for (int index = 0; index < count; index++) + { + Register rd = new Register(op.Rd.Index + index, RegisterType.Gpr); + + if (rd.IsRZ) + { + break; + } + + Operand offset = context.IAdd(wordOffset, Const(index)); + + Operand value = context.LoadConstant(Const(op.Slot), offset); + + if (isSmallInt) + { + value = ExtractSmallInt(context, op.Size, bitOffset, value); + } + + context.Copy(Register(rd), value); + } + } + + public static void Ldg(EmitterContext context) + { + EmitLoadGlobal(context); + } + + public static void Lds(EmitterContext context) + { + EmitLoad(context, MemoryRegion.Shared); + } + + public static void Membar(EmitterContext context) + { + OpCodeMemoryBarrier op = (OpCodeMemoryBarrier)context.CurrOp; + + if (op.Level == BarrierLevel.Cta) + { + context.GroupMemoryBarrier(); + } + else + { + context.MemoryBarrier(); + } + } + + public static void Out(EmitterContext context) + { + OpCode op = context.CurrOp; + + bool emit = op.RawOpCode.Extract(39); + bool cut = op.RawOpCode.Extract(40); + + if (!(emit || cut)) + { + context.Config.PrintLog("Invalid OUT encoding."); + } + + if (emit) + { + context.EmitVertex(); + } + + if (cut) + { + context.EndPrimitive(); + } + } + + public static void Red(EmitterContext context) + { + OpCodeRed op = (OpCodeRed)context.CurrOp; + + (Operand addrLow, Operand addrHigh) = Get40BitsAddress(context, op.Ra, op.Extended, op.Offset); + + EmitAtomicOp( + context, + Instruction.MrGlobal, + op.AtomicOp, + op.Type, + addrLow, + addrHigh, + GetDest(context)); + } + + public static void St(EmitterContext context) + { + EmitStore(context, MemoryRegion.Local); + } + + public static void Stg(EmitterContext context) + { + EmitStoreGlobal(context); + } + + public static void Sts(EmitterContext context) + { + EmitStore(context, MemoryRegion.Shared); + } + + private static Operand EmitAtomicOp( + EmitterContext context, + Instruction mr, + AtomicOp op, + ReductionType type, + Operand addrLow, + Operand addrHigh, + Operand value) + { + Operand res = Const(0); + + switch (op) + { + case AtomicOp.Add: + if (type == ReductionType.S32 || type == ReductionType.U32) + { + res = context.AtomicAdd(mr, addrLow, addrHigh, value); + } + else + { + context.Config.PrintLog($"Invalid reduction type: {type}."); + } + break; + case AtomicOp.BitwiseAnd: + if (type == ReductionType.S32 || type == ReductionType.U32) + { + res = context.AtomicAnd(mr, addrLow, addrHigh, value); + } + else + { + context.Config.PrintLog($"Invalid reduction type: {type}."); + } + break; + case AtomicOp.BitwiseExclusiveOr: + if (type == ReductionType.S32 || type == ReductionType.U32) + { + res = context.AtomicXor(mr, addrLow, addrHigh, value); + } + else + { + context.Config.PrintLog($"Invalid reduction type: {type}."); + } + break; + case AtomicOp.BitwiseOr: + if (type == ReductionType.S32 || type == ReductionType.U32) + { + res = context.AtomicOr(mr, addrLow, addrHigh, value); + } + else + { + context.Config.PrintLog($"Invalid reduction type: {type}."); + } + break; + case AtomicOp.Maximum: + if (type == ReductionType.S32) + { + res = context.AtomicMaxS32(mr, addrLow, addrHigh, value); + } + else if (type == ReductionType.U32) + { + res = context.AtomicMaxU32(mr, addrLow, addrHigh, value); + } + else + { + context.Config.PrintLog($"Invalid reduction type: {type}."); + } + break; + case AtomicOp.Minimum: + if (type == ReductionType.S32) + { + res = context.AtomicMinS32(mr, addrLow, addrHigh, value); + } + else if (type == ReductionType.U32) + { + res = context.AtomicMinU32(mr, addrLow, addrHigh, value); + } + else + { + context.Config.PrintLog($"Invalid reduction type: {type}."); + } + break; + } + + return res; + } + + private static void EmitLoad(EmitterContext context, MemoryRegion region) + { + OpCodeMemory op = (OpCodeMemory)context.CurrOp; + + if (op.Size > IntegerSize.B128) + { + context.Config.PrintLog($"Invalid load size: {op.Size}."); + } + + bool isSmallInt = op.Size < IntegerSize.B32; + + int count = 1; + + switch (op.Size) + { + case IntegerSize.B64: count = 2; break; + case IntegerSize.B128: count = 4; break; + } + + Operand baseOffset = context.IAdd(GetSrcA(context), Const(op.Offset)); + + // Word offset = byte offset / 4 (one word = 4 bytes). + Operand wordOffset = context.ShiftRightU32(baseOffset, Const(2)); + + Operand bitOffset = GetBitOffset(context, baseOffset); + + for (int index = 0; index < count; index++) + { + Register rd = new Register(op.Rd.Index + index, RegisterType.Gpr); + + if (rd.IsRZ) + { + break; + } + + Operand offset = context.IAdd(wordOffset, Const(index)); + + Operand value = null; + + switch (region) + { + case MemoryRegion.Local: value = context.LoadLocal (offset); break; + case MemoryRegion.Shared: value = context.LoadShared(offset); break; + } + + if (isSmallInt) + { + value = ExtractSmallInt(context, op.Size, bitOffset, value); + } + + context.Copy(Register(rd), value); + } + } + + private static void EmitLoadGlobal(EmitterContext context) + { + OpCodeMemory op = (OpCodeMemory)context.CurrOp; + + bool isSmallInt = op.Size < IntegerSize.B32; + + int count = GetVectorCount(op.Size); + + (Operand addrLow, Operand addrHigh) = Get40BitsAddress(context, op.Ra, op.Extended, op.Offset); + + Operand bitOffset = GetBitOffset(context, addrLow); + + for (int index = 0; index < count; index++) + { + Register rd = new Register(op.Rd.Index + index, RegisterType.Gpr); + + if (rd.IsRZ) + { + break; + } + + Operand value = context.LoadGlobal(context.IAdd(addrLow, Const(index * 4)), addrHigh); + + if (isSmallInt) + { + value = ExtractSmallInt(context, op.Size, bitOffset, value); + } + + context.Copy(Register(rd), value); + } + } + + private static void EmitStore(EmitterContext context, MemoryRegion region) + { + OpCodeMemory op = (OpCodeMemory)context.CurrOp; + + if (op.Size > IntegerSize.B128) + { + context.Config.PrintLog($"Invalid store size: {op.Size}."); + } + + bool isSmallInt = op.Size < IntegerSize.B32; + + int count = 1; + + switch (op.Size) + { + case IntegerSize.B64: count = 2; break; + case IntegerSize.B128: count = 4; break; + } + + Operand baseOffset = context.IAdd(GetSrcA(context), Const(op.Offset)); + + Operand wordOffset = context.ShiftRightU32(baseOffset, Const(2)); + + Operand bitOffset = GetBitOffset(context, baseOffset); + + for (int index = 0; index < count; index++) + { + Register rd = new Register(op.Rd.Index + index, RegisterType.Gpr); + + Operand value = Register(rd); + + Operand offset = context.IAdd(wordOffset, Const(index)); + + if (isSmallInt) + { + Operand word = null; + + switch (region) + { + case MemoryRegion.Local: word = context.LoadLocal (offset); break; + case MemoryRegion.Shared: word = context.LoadShared(offset); break; + } + + value = InsertSmallInt(context, op.Size, bitOffset, word, value); + } + + switch (region) + { + case MemoryRegion.Local: context.StoreLocal (offset, value); break; + case MemoryRegion.Shared: context.StoreShared(offset, value); break; + } + + if (rd.IsRZ) + { + break; + } + } + } + + private static void EmitStoreGlobal(EmitterContext context) + { + OpCodeMemory op = (OpCodeMemory)context.CurrOp; + + bool isSmallInt = op.Size < IntegerSize.B32; + + int count = GetVectorCount(op.Size); + + (Operand addrLow, Operand addrHigh) = Get40BitsAddress(context, op.Ra, op.Extended, op.Offset); + + Operand bitOffset = GetBitOffset(context, addrLow); + + for (int index = 0; index < count; index++) + { + Register rd = new Register(op.Rd.Index + index, RegisterType.Gpr); + + Operand value = Register(rd); + + if (isSmallInt) + { + Operand word = context.LoadGlobal(addrLow, addrHigh); + + value = InsertSmallInt(context, op.Size, bitOffset, word, value); + } + + context.StoreGlobal(context.IAdd(addrLow, Const(index * 4)), addrHigh, value); + + if (rd.IsRZ) + { + break; + } + } + } + + private static int GetVectorCount(IntegerSize size) + { + switch (size) + { + case IntegerSize.B64: + return 2; + case IntegerSize.B128: + case IntegerSize.UB128: + return 4; + } + + return 1; + } + + private static (Operand, Operand) Get40BitsAddress( + EmitterContext context, + Register ra, + bool extended, + int offset) + { + Operand addrLow = GetSrcA(context); + Operand addrHigh; + + if (extended && !ra.IsRZ) + { + addrHigh = Register(ra.Index + 1, RegisterType.Gpr); + } + else + { + addrHigh = Const(0); + } + + Operand offs = Const(offset); + + addrLow = context.IAdd(addrLow, offs); + + if (extended) + { + Operand carry = context.ICompareLessUnsigned(addrLow, offs); + + addrHigh = context.IAdd(addrHigh, context.ConditionalSelect(carry, Const(1), Const(0))); + } + + return (addrLow, addrHigh); + } + + private static Operand GetBitOffset(EmitterContext context, Operand baseOffset) + { + // Note: bit offset = (baseOffset & 0b11) * 8. + // Addresses should be always aligned to the integer type, + // so we don't need to take unaligned addresses into account. + return context.ShiftLeft(context.BitwiseAnd(baseOffset, Const(3)), Const(3)); + } + + private static Operand ExtractSmallInt( + EmitterContext context, + IntegerSize size, + Operand bitOffset, + Operand value) + { + value = context.ShiftRightU32(value, bitOffset); + + switch (size) + { + case IntegerSize.U8: value = ZeroExtendTo32(context, value, 8); break; + case IntegerSize.U16: value = ZeroExtendTo32(context, value, 16); break; + case IntegerSize.S8: value = SignExtendTo32(context, value, 8); break; + case IntegerSize.S16: value = SignExtendTo32(context, value, 16); break; + } + + return value; + } + + private static Operand InsertSmallInt( + EmitterContext context, + IntegerSize size, + Operand bitOffset, + Operand word, + Operand value) + { + switch (size) + { + case IntegerSize.U8: + case IntegerSize.S8: + value = context.BitwiseAnd(value, Const(0xff)); + value = context.BitfieldInsert(word, value, bitOffset, Const(8)); + break; + + case IntegerSize.U16: + case IntegerSize.S16: + value = context.BitwiseAnd(value, Const(0xffff)); + value = context.BitfieldInsert(word, value, bitOffset, Const(16)); + break; + } + + return value; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitMove.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitMove.cs new file mode 100644 index 0000000000..ffc4c430f5 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitMove.cs @@ -0,0 +1,144 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; + +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void Mov(EmitterContext context) + { + context.Copy(GetDest(context), GetSrcB(context)); + } + + public static void R2p(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + bool isCC = op.RawOpCode.Extract(40); + int shift = op.RawOpCode.Extract(41, 2) * 8; + + Operand value = GetSrcA(context); + Operand mask = GetSrcB(context); + + Operand Test(Operand value, int bit) + { + return context.ICompareNotEqual(context.BitwiseAnd(value, Const(1 << bit)), Const(0)); + } + + if (isCC) + { + // TODO: Support Register to condition code flags copy. + context.Config.PrintLog("R2P.CC not implemented."); + } + else + { + for (int bit = 0; bit < 7; bit++) + { + Operand pred = Register(bit, RegisterType.Predicate); + + Operand res = context.ConditionalSelect(Test(mask, bit), Test(value, bit + shift), pred); + + context.Copy(pred, res); + } + } + } + + public static void S2r(EmitterContext context) + { + // TODO: Better impl. + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + SystemRegister sysReg = (SystemRegister)op.RawOpCode.Extract(20, 8); + + Operand src; + + switch (sysReg) + { + case SystemRegister.LaneId: src = Attribute(AttributeConsts.LaneId); break; + + // TODO: Use value from Y direction GPU register. + case SystemRegister.YDirection: src = ConstF(1); break; + + case SystemRegister.ThreadId: + { + Operand tidX = Attribute(AttributeConsts.ThreadIdX); + Operand tidY = Attribute(AttributeConsts.ThreadIdY); + Operand tidZ = Attribute(AttributeConsts.ThreadIdZ); + + tidY = context.ShiftLeft(tidY, Const(16)); + tidZ = context.ShiftLeft(tidZ, Const(26)); + + src = context.BitwiseOr(tidX, context.BitwiseOr(tidY, tidZ)); + + break; + } + + case SystemRegister.ThreadIdX: src = Attribute(AttributeConsts.ThreadIdX); break; + case SystemRegister.ThreadIdY: src = Attribute(AttributeConsts.ThreadIdY); break; + case SystemRegister.ThreadIdZ: src = Attribute(AttributeConsts.ThreadIdZ); break; + case SystemRegister.CtaIdX: src = Attribute(AttributeConsts.CtaIdX); break; + case SystemRegister.CtaIdY: src = Attribute(AttributeConsts.CtaIdY); break; + case SystemRegister.CtaIdZ: src = Attribute(AttributeConsts.CtaIdZ); break; + case SystemRegister.EqMask: src = Attribute(AttributeConsts.EqMask); break; + case SystemRegister.LtMask: src = Attribute(AttributeConsts.LtMask); break; + case SystemRegister.LeMask: src = Attribute(AttributeConsts.LeMask); break; + case SystemRegister.GtMask: src = Attribute(AttributeConsts.GtMask); break; + case SystemRegister.GeMask: src = Attribute(AttributeConsts.GeMask); break; + + default: src = Const(0); break; + } + + context.Copy(GetDest(context), src); + } + + public static void Sel(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + Operand pred = GetPredicate39(context); + + Operand srcA = GetSrcA(context); + Operand srcB = GetSrcB(context); + + Operand res = context.ConditionalSelect(pred, srcA, srcB); + + context.Copy(GetDest(context), res); + } + + public static void Shfl(EmitterContext context) + { + OpCodeShuffle op = (OpCodeShuffle)context.CurrOp; + + Operand pred = Register(op.Predicate48); + + Operand srcA = GetSrcA(context); + + Operand srcB = op.IsBImmediate ? Const(op.ImmediateB) : Register(op.Rb); + Operand srcC = op.IsCImmediate ? Const(op.ImmediateC) : Register(op.Rc); + + Operand res = null; + + switch (op.ShuffleType) + { + case ShuffleType.Indexed: + res = context.Shuffle(srcA, srcB, srcC); + break; + case ShuffleType.Up: + res = context.ShuffleUp(srcA, srcB, srcC); + break; + case ShuffleType.Down: + res = context.ShuffleDown(srcA, srcB, srcC); + break; + case ShuffleType.Butterfly: + res = context.ShuffleXor(srcA, srcB, srcC); + break; + } + + context.Copy(GetDest(context), res); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Instructions/InstEmitTexture.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs similarity index 54% rename from Ryujinx.Graphics/Shader/Instructions/InstEmitTexture.cs rename to Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs index a9b29f40ef..7b9794eaf1 100644 --- a/Ryujinx.Graphics/Shader/Instructions/InstEmitTexture.cs +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs @@ -10,24 +10,121 @@ namespace Ryujinx.Graphics.Shader.Instructions { static partial class InstEmit { - public static void Tex(EmitterContext context) + public static void Sust(EmitterContext context) { - Tex(context, TextureFlags.None); + OpCodeImage op = (OpCodeImage)context.CurrOp; + + SamplerType type = ConvertSamplerType(op.Dimensions); + + if (type == SamplerType.None) + { + context.Config.PrintLog("Invalid image store sampler type."); + + return; + } + + int raIndex = op.Ra.Index; + int rbIndex = op.Rb.Index; + + Operand Ra() + { + if (raIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(raIndex++, RegisterType.Gpr)); + } + + Operand Rb() + { + if (rbIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(rbIndex++, RegisterType.Gpr)); + } + + bool isArray = op.Dimensions == ImageDimensions.Image1DArray || + op.Dimensions == ImageDimensions.Image2DArray; + + Operand arrayIndex = isArray ? Ra() : null; + + List sourcesList = new List(); + + if (op.IsBindless) + { + sourcesList.Add(context.Copy(Register(op.Rc))); + } + + int coordsCount = type.GetDimensions(); + + for (int index = 0; index < coordsCount; index++) + { + sourcesList.Add(Ra()); + } + + if (isArray) + { + sourcesList.Add(arrayIndex); + + type |= SamplerType.Array; + } + + if (op.UseComponents) + { + int componentMask = (int)op.Components; + + for (int compMask = componentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++) + { + if ((compMask & 1) != 0) + { + sourcesList.Add(Rb()); + } + } + } + else + { + context.Config.PrintLog("Unsized image store not supported."); + } + + Operand[] sources = sourcesList.ToArray(); + + int handle = !op.IsBindless ? op.Immediate : 0; + + TextureFlags flags = op.IsBindless ? TextureFlags.Bindless : TextureFlags.None; + + TextureOperation operation = new TextureOperation( + Instruction.ImageStore, + type, + flags, + handle, + 0, + null, + sources); + + context.Add(operation); } - public static void Tex_B(EmitterContext context) + public static void Tex(EmitterContext context) { - Tex(context, TextureFlags.Bindless); + EmitTextureSample(context, TextureFlags.None); + } + + public static void TexB(EmitterContext context) + { + EmitTextureSample(context, TextureFlags.Bindless); } public static void Tld(EmitterContext context) { - Tex(context, TextureFlags.IntCoords); + EmitTextureSample(context, TextureFlags.IntCoords); } - public static void Tld_B(EmitterContext context) + public static void TldB(EmitterContext context) { - Tex(context, TextureFlags.IntCoords | TextureFlags.Bindless); + EmitTextureSample(context, TextureFlags.IntCoords | TextureFlags.Bindless); } public static void Texs(EmitterContext context) @@ -74,15 +171,23 @@ namespace Ryujinx.Graphics.Shader.Instructions } } - TextureType type; + SamplerType type; TextureFlags flags; if (op is OpCodeTexs texsOp) { - type = GetTextureType (texsOp.Type); - flags = GetTextureFlags(texsOp.Type); + type = ConvertSamplerType(texsOp.Target); - if ((type & TextureType.Array) != 0) + if (type == SamplerType.None) + { + context.Config.PrintLog("Invalid texture sampler type."); + + return; + } + + flags = ConvertTextureFlags(texsOp.Target); + + if ((type & SamplerType.Array) != 0) { Operand arrayIndex = Ra(); @@ -91,7 +196,7 @@ namespace Ryujinx.Graphics.Shader.Instructions sourcesList.Add(arrayIndex); - if ((type & TextureType.Shadow) != 0) + if ((type & SamplerType.Shadow) != 0) { sourcesList.Add(Rb()); } @@ -103,42 +208,42 @@ namespace Ryujinx.Graphics.Shader.Instructions } else { - switch (texsOp.Type) + switch (texsOp.Target) { - case TextureScalarType.Texture1DLodZero: + case TextureTarget.Texture1DLodZero: sourcesList.Add(Ra()); break; - case TextureScalarType.Texture2D: + case TextureTarget.Texture2D: sourcesList.Add(Ra()); sourcesList.Add(Rb()); break; - case TextureScalarType.Texture2DLodZero: + case TextureTarget.Texture2DLodZero: sourcesList.Add(Ra()); sourcesList.Add(Rb()); sourcesList.Add(ConstF(0)); break; - case TextureScalarType.Texture2DLodLevel: - case TextureScalarType.Texture2DDepthCompare: - case TextureScalarType.Texture3D: - case TextureScalarType.TextureCube: + case TextureTarget.Texture2DLodLevel: + case TextureTarget.Texture2DDepthCompare: + case TextureTarget.Texture3D: + case TextureTarget.TextureCube: sourcesList.Add(Ra()); sourcesList.Add(Ra()); sourcesList.Add(Rb()); break; - case TextureScalarType.Texture2DLodZeroDepthCompare: - case TextureScalarType.Texture3DLodZero: + case TextureTarget.Texture2DLodZeroDepthCompare: + case TextureTarget.Texture3DLodZero: sourcesList.Add(Ra()); sourcesList.Add(Ra()); sourcesList.Add(Rb()); sourcesList.Add(ConstF(0)); break; - case TextureScalarType.Texture2DLodLevelDepthCompare: - case TextureScalarType.TextureCubeLodLevel: + case TextureTarget.Texture2DLodLevelDepthCompare: + case TextureTarget.TextureCubeLodLevel: sourcesList.Add(Ra()); sourcesList.Add(Ra()); sourcesList.Add(Rb()); @@ -149,49 +254,57 @@ namespace Ryujinx.Graphics.Shader.Instructions } else if (op is OpCodeTlds tldsOp) { - type = GetTextureType (tldsOp.Type); - flags = GetTextureFlags(tldsOp.Type) | TextureFlags.IntCoords; + type = ConvertSamplerType (tldsOp.Target); - switch (tldsOp.Type) + if (type == SamplerType.None) { - case TexelLoadScalarType.Texture1DLodZero: + context.Config.PrintLog("Invalid texel fetch sampler type."); + + return; + } + + flags = ConvertTextureFlags(tldsOp.Target) | TextureFlags.IntCoords; + + switch (tldsOp.Target) + { + case TexelLoadTarget.Texture1DLodZero: sourcesList.Add(Ra()); sourcesList.Add(Const(0)); break; - case TexelLoadScalarType.Texture1DLodLevel: + case TexelLoadTarget.Texture1DLodLevel: sourcesList.Add(Ra()); sourcesList.Add(Rb()); break; - case TexelLoadScalarType.Texture2DLodZero: + case TexelLoadTarget.Texture2DLodZero: sourcesList.Add(Ra()); sourcesList.Add(Rb()); sourcesList.Add(Const(0)); break; - case TexelLoadScalarType.Texture2DLodZeroOffset: + case TexelLoadTarget.Texture2DLodZeroOffset: sourcesList.Add(Ra()); sourcesList.Add(Ra()); sourcesList.Add(Const(0)); break; - case TexelLoadScalarType.Texture2DLodZeroMultisample: - case TexelLoadScalarType.Texture2DLodLevel: - case TexelLoadScalarType.Texture2DLodLevelOffset: + case TexelLoadTarget.Texture2DLodZeroMultisample: + case TexelLoadTarget.Texture2DLodLevel: + case TexelLoadTarget.Texture2DLodLevelOffset: sourcesList.Add(Ra()); sourcesList.Add(Ra()); sourcesList.Add(Rb()); break; - case TexelLoadScalarType.Texture3DLodZero: + case TexelLoadTarget.Texture3DLodZero: sourcesList.Add(Ra()); sourcesList.Add(Ra()); sourcesList.Add(Rb()); sourcesList.Add(Const(0)); break; - case TexelLoadScalarType.Texture2DArrayLodZero: + case TexelLoadTarget.Texture2DArrayLodZero: sourcesList.Add(Rb()); sourcesList.Add(Rb()); sourcesList.Add(Ra()); @@ -201,7 +314,7 @@ namespace Ryujinx.Graphics.Shader.Instructions if ((flags & TextureFlags.Offset) != 0) { - AddTextureOffset(type.GetCoordsCount(), 4, 4); + AddTextureOffset(type.GetDimensions(), 4, 4); } } else if (op is OpCodeTld4s tld4sOp) @@ -217,19 +330,19 @@ namespace Ryujinx.Graphics.Shader.Instructions sourcesList.Add(Ra()); } - type = TextureType.Texture2D; + type = SamplerType.Texture2D; flags = TextureFlags.Gather; if (tld4sOp.HasDepthCompare) { sourcesList.Add(Rb()); - type |= TextureType.Shadow; + type |= SamplerType.Shadow; } if (tld4sOp.HasOffset) { - AddTextureOffset(type.GetCoordsCount(), 8, 6); + AddTextureOffset(type.GetDimensions(), 8, 6); flags |= TextureFlags.Offset; } @@ -304,7 +417,7 @@ namespace Ryujinx.Graphics.Shader.Instructions public static void Tld4(EmitterContext context) { - OpCodeTld4 op = (OpCodeTld4)context.CurrOp; + IOpCodeTld4 op = (IOpCodeTld4)context.CurrOp; if (op.Rd.IsRZ) { @@ -338,11 +451,18 @@ namespace Ryujinx.Graphics.Shader.Instructions List sourcesList = new List(); - TextureType type = GetTextureType(op.Dimensions); + SamplerType type = ConvertSamplerType(op.Dimensions); TextureFlags flags = TextureFlags.Gather; - int coordsCount = type.GetCoordsCount(); + if (op.Bindless) + { + sourcesList.Add(Rb()); + + flags |= TextureFlags.Bindless; + } + + int coordsCount = type.GetDimensions(); for (int index = 0; index < coordsCount; index++) { @@ -353,7 +473,7 @@ namespace Ryujinx.Graphics.Shader.Instructions { sourcesList.Add(arrayIndex); - type |= TextureType.Array; + type |= SamplerType.Array; } Operand[] packedOffs = new Operand[2]; @@ -365,7 +485,7 @@ namespace Ryujinx.Graphics.Shader.Instructions { sourcesList.Add(Rb()); - type |= TextureType.Shadow; + type |= SamplerType.Shadow; } if (op.Offset != TextureGatherOffset.None) @@ -422,17 +542,128 @@ namespace Ryujinx.Graphics.Shader.Instructions } } + public static void Txd(EmitterContext context) + { + OpCodeTxd op = (OpCodeTxd)context.CurrOp; + + if (op.Rd.IsRZ) + { + return; + } + + int raIndex = op.Ra.Index; + int rbIndex = op.Rb.Index; + + Operand Ra() + { + if (raIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(raIndex++, RegisterType.Gpr)); + } + + Operand Rb() + { + if (rbIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(rbIndex++, RegisterType.Gpr)); + } + + TextureFlags flags = TextureFlags.Derivatives; + + List sourcesList = new List(); + + if (op.IsBindless) + { + sourcesList.Add(Ra()); + } + + SamplerType type = ConvertSamplerType(op.Dimensions); + + int coordsCount = type.GetDimensions(); + + for (int index = 0; index < coordsCount; index++) + { + sourcesList.Add(Ra()); + } + + Operand packedParams = Ra(); + + if (op.IsArray) + { + sourcesList.Add(context.BitwiseAnd(packedParams, Const(0xffff))); + + type |= SamplerType.Array; + } + + // Derivatives (X and Y). + for (int dIndex = 0; dIndex < 2 * coordsCount; dIndex++) + { + sourcesList.Add(Rb()); + } + + if (op.HasOffset) + { + for (int index = 0; index < coordsCount; index++) + { + sourcesList.Add(context.BitfieldExtractS32(packedParams, Const(16 + index * 4), Const(4))); + } + + flags |= TextureFlags.Offset; + } + + Operand[] sources = sourcesList.ToArray(); + + int rdIndex = op.Rd.Index; + + Operand GetDest() + { + if (rdIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return Register(rdIndex++, RegisterType.Gpr); + } + + int handle = !op.IsBindless ? op.Immediate : 0; + + for (int compMask = op.ComponentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++) + { + if ((compMask & 1) != 0) + { + Operand dest = GetDest(); + + TextureOperation operation = new TextureOperation( + Instruction.TextureSample, + type, + flags, + handle, + compIndex, + dest, + sources); + + context.Add(operation); + } + } + } + public static void Txq(EmitterContext context) { - Txq(context, bindless: false); + EmitTextureQuery(context, bindless: false); } - public static void Txq_B(EmitterContext context) + public static void TxqB(EmitterContext context) { - Txq(context, bindless: true); + EmitTextureQuery(context, bindless: true); } - private static void Txq(EmitterContext context, bool bindless) + private static void EmitTextureQuery(EmitterContext context, bool bindless) { OpCodeTex op = (OpCodeTex)context.CurrOp; @@ -446,7 +677,7 @@ namespace Ryujinx.Graphics.Shader.Instructions // TODO: Validate and use property. Instruction inst = Instruction.TextureSize; - TextureType type = TextureType.Texture2D; + SamplerType type = SamplerType.Texture2D; TextureFlags flags = bindless ? TextureFlags.Bindless : TextureFlags.None; @@ -507,12 +738,11 @@ namespace Ryujinx.Graphics.Shader.Instructions } } - private static void Tex(EmitterContext context, TextureFlags flags) + private static void EmitTextureSample(EmitterContext context, TextureFlags flags) { OpCodeTexture op = (OpCodeTexture)context.CurrOp; - bool isBindless = (flags & TextureFlags.Bindless) != 0; - bool intCoords = (flags & TextureFlags.IntCoords) != 0; + bool isBindless = (flags & TextureFlags.Bindless) != 0; if (op.Rd.IsRZ) { @@ -551,9 +781,9 @@ namespace Ryujinx.Graphics.Shader.Instructions sourcesList.Add(Rb()); } - TextureType type = GetTextureType(op.Dimensions); + SamplerType type = ConvertSamplerType(op.Dimensions); - int coordsCount = type.GetCoordsCount(); + int coordsCount = type.GetDimensions(); for (int index = 0; index < coordsCount; index++) { @@ -564,7 +794,7 @@ namespace Ryujinx.Graphics.Shader.Instructions { sourcesList.Add(arrayIndex); - type |= TextureType.Array; + type |= SamplerType.Array; } bool hasLod = op.LodMode > TextureLodMode.LodZero; @@ -577,7 +807,7 @@ namespace Ryujinx.Graphics.Shader.Instructions { sourcesList.Add(Rb()); - type |= TextureType.Shadow; + type |= SamplerType.Shadow; } if ((op.LodMode == TextureLodMode.LodZero || @@ -611,7 +841,7 @@ namespace Ryujinx.Graphics.Shader.Instructions { sourcesList.Add(Rb()); - type |= TextureType.Multisample; + type |= SamplerType.Multisample; } Operand[] sources = sourcesList.ToArray(); @@ -650,127 +880,153 @@ namespace Ryujinx.Graphics.Shader.Instructions } } - private static TextureType GetTextureType(TextureDimensions dimensions) + private static SamplerType ConvertSamplerType(ImageDimensions target) + { + switch (target) + { + case ImageDimensions.Image1D: + return SamplerType.Texture1D; + + case ImageDimensions.ImageBuffer: + return SamplerType.TextureBuffer; + + case ImageDimensions.Image1DArray: + return SamplerType.Texture1D | SamplerType.Array; + + case ImageDimensions.Image2D: + return SamplerType.Texture2D; + + case ImageDimensions.Image2DArray: + return SamplerType.Texture2D | SamplerType.Array; + + case ImageDimensions.Image3D: + return SamplerType.Texture3D; + } + + return SamplerType.None; + } + + private static SamplerType ConvertSamplerType(TextureDimensions dimensions) { switch (dimensions) { - case TextureDimensions.Texture1D: return TextureType.Texture1D; - case TextureDimensions.Texture2D: return TextureType.Texture2D; - case TextureDimensions.Texture3D: return TextureType.Texture3D; - case TextureDimensions.TextureCube: return TextureType.TextureCube; + case TextureDimensions.Texture1D: return SamplerType.Texture1D; + case TextureDimensions.Texture2D: return SamplerType.Texture2D; + case TextureDimensions.Texture3D: return SamplerType.Texture3D; + case TextureDimensions.TextureCube: return SamplerType.TextureCube; } throw new ArgumentException($"Invalid texture dimensions \"{dimensions}\"."); } - private static TextureType GetTextureType(TextureScalarType type) + private static SamplerType ConvertSamplerType(TextureTarget type) { switch (type) { - case TextureScalarType.Texture1DLodZero: - return TextureType.Texture1D; + case TextureTarget.Texture1DLodZero: + return SamplerType.Texture1D; - case TextureScalarType.Texture2D: - case TextureScalarType.Texture2DLodZero: - case TextureScalarType.Texture2DLodLevel: - return TextureType.Texture2D; + case TextureTarget.Texture2D: + case TextureTarget.Texture2DLodZero: + case TextureTarget.Texture2DLodLevel: + return SamplerType.Texture2D; - case TextureScalarType.Texture2DDepthCompare: - case TextureScalarType.Texture2DLodLevelDepthCompare: - case TextureScalarType.Texture2DLodZeroDepthCompare: - return TextureType.Texture2D | TextureType.Shadow; + case TextureTarget.Texture2DDepthCompare: + case TextureTarget.Texture2DLodLevelDepthCompare: + case TextureTarget.Texture2DLodZeroDepthCompare: + return SamplerType.Texture2D | SamplerType.Shadow; - case TextureScalarType.Texture2DArray: - case TextureScalarType.Texture2DArrayLodZero: - return TextureType.Texture2D | TextureType.Array; + case TextureTarget.Texture2DArray: + case TextureTarget.Texture2DArrayLodZero: + return SamplerType.Texture2D | SamplerType.Array; - case TextureScalarType.Texture2DArrayLodZeroDepthCompare: - return TextureType.Texture2D | TextureType.Array | TextureType.Shadow; + case TextureTarget.Texture2DArrayLodZeroDepthCompare: + return SamplerType.Texture2D | SamplerType.Array | SamplerType.Shadow; - case TextureScalarType.Texture3D: - case TextureScalarType.Texture3DLodZero: - return TextureType.Texture3D; + case TextureTarget.Texture3D: + case TextureTarget.Texture3DLodZero: + return SamplerType.Texture3D; - case TextureScalarType.TextureCube: - case TextureScalarType.TextureCubeLodLevel: - return TextureType.TextureCube; + case TextureTarget.TextureCube: + case TextureTarget.TextureCubeLodLevel: + return SamplerType.TextureCube; } - throw new ArgumentException($"Invalid texture type \"{type}\"."); + return SamplerType.None; } - private static TextureType GetTextureType(TexelLoadScalarType type) + private static SamplerType ConvertSamplerType(TexelLoadTarget type) { switch (type) { - case TexelLoadScalarType.Texture1DLodZero: - case TexelLoadScalarType.Texture1DLodLevel: - return TextureType.Texture1D; + case TexelLoadTarget.Texture1DLodZero: + case TexelLoadTarget.Texture1DLodLevel: + return SamplerType.Texture1D; - case TexelLoadScalarType.Texture2DLodZero: - case TexelLoadScalarType.Texture2DLodZeroOffset: - case TexelLoadScalarType.Texture2DLodLevel: - case TexelLoadScalarType.Texture2DLodLevelOffset: - return TextureType.Texture2D; + case TexelLoadTarget.Texture2DLodZero: + case TexelLoadTarget.Texture2DLodZeroOffset: + case TexelLoadTarget.Texture2DLodLevel: + case TexelLoadTarget.Texture2DLodLevelOffset: + return SamplerType.Texture2D; - case TexelLoadScalarType.Texture2DLodZeroMultisample: - return TextureType.Texture2D | TextureType.Multisample; + case TexelLoadTarget.Texture2DLodZeroMultisample: + return SamplerType.Texture2D | SamplerType.Multisample; - case TexelLoadScalarType.Texture3DLodZero: - return TextureType.Texture3D; + case TexelLoadTarget.Texture3DLodZero: + return SamplerType.Texture3D; - case TexelLoadScalarType.Texture2DArrayLodZero: - return TextureType.Texture2D | TextureType.Array; + case TexelLoadTarget.Texture2DArrayLodZero: + return SamplerType.Texture2D | SamplerType.Array; } - throw new ArgumentException($"Invalid texture type \"{type}\"."); + return SamplerType.None; } - private static TextureFlags GetTextureFlags(TextureScalarType type) + private static TextureFlags ConvertTextureFlags(Decoders.TextureTarget type) { switch (type) { - case TextureScalarType.Texture1DLodZero: - case TextureScalarType.Texture2DLodZero: - case TextureScalarType.Texture2DLodLevel: - case TextureScalarType.Texture2DLodLevelDepthCompare: - case TextureScalarType.Texture2DLodZeroDepthCompare: - case TextureScalarType.Texture2DArrayLodZero: - case TextureScalarType.Texture2DArrayLodZeroDepthCompare: - case TextureScalarType.Texture3DLodZero: - case TextureScalarType.TextureCubeLodLevel: + case TextureTarget.Texture1DLodZero: + case TextureTarget.Texture2DLodZero: + case TextureTarget.Texture2DLodLevel: + case TextureTarget.Texture2DLodLevelDepthCompare: + case TextureTarget.Texture2DLodZeroDepthCompare: + case TextureTarget.Texture2DArrayLodZero: + case TextureTarget.Texture2DArrayLodZeroDepthCompare: + case TextureTarget.Texture3DLodZero: + case TextureTarget.TextureCubeLodLevel: return TextureFlags.LodLevel; - case TextureScalarType.Texture2D: - case TextureScalarType.Texture2DDepthCompare: - case TextureScalarType.Texture2DArray: - case TextureScalarType.Texture3D: - case TextureScalarType.TextureCube: + case TextureTarget.Texture2D: + case TextureTarget.Texture2DDepthCompare: + case TextureTarget.Texture2DArray: + case TextureTarget.Texture3D: + case TextureTarget.TextureCube: return TextureFlags.None; } - throw new ArgumentException($"Invalid texture type \"{type}\"."); + return TextureFlags.None; } - private static TextureFlags GetTextureFlags(TexelLoadScalarType type) + private static TextureFlags ConvertTextureFlags(TexelLoadTarget type) { switch (type) { - case TexelLoadScalarType.Texture1DLodZero: - case TexelLoadScalarType.Texture1DLodLevel: - case TexelLoadScalarType.Texture2DLodZero: - case TexelLoadScalarType.Texture2DLodLevel: - case TexelLoadScalarType.Texture2DLodZeroMultisample: - case TexelLoadScalarType.Texture3DLodZero: - case TexelLoadScalarType.Texture2DArrayLodZero: + case TexelLoadTarget.Texture1DLodZero: + case TexelLoadTarget.Texture1DLodLevel: + case TexelLoadTarget.Texture2DLodZero: + case TexelLoadTarget.Texture2DLodLevel: + case TexelLoadTarget.Texture2DLodZeroMultisample: + case TexelLoadTarget.Texture3DLodZero: + case TexelLoadTarget.Texture2DArrayLodZero: return TextureFlags.LodLevel; - case TexelLoadScalarType.Texture2DLodZeroOffset: - case TexelLoadScalarType.Texture2DLodLevelOffset: + case TexelLoadTarget.Texture2DLodZeroOffset: + case TexelLoadTarget.Texture2DLodLevelOffset: return TextureFlags.LodLevel | TextureFlags.Offset; } - throw new ArgumentException($"Invalid texture type \"{type}\"."); + return TextureFlags.None; } } } \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitVideo.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitVideo.cs new file mode 100644 index 0000000000..aac12c781c --- /dev/null +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitVideo.cs @@ -0,0 +1,17 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.Translation; + +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void Vmad(EmitterContext context) + { + OpCodeVideo op = (OpCodeVideo)context.CurrOp; + + context.Copy(GetDest(context), GetSrcC(context)); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitVote.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitVote.cs new file mode 100644 index 0000000000..8f81ecb4df --- /dev/null +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitVote.cs @@ -0,0 +1,48 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; + +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void Vote(EmitterContext context) + { + OpCodeVote op = (OpCodeVote)context.CurrOp; + + Operand pred = GetPredicate39(context); + + Operand res = null; + + switch (op.VoteOp) + { + case VoteOp.All: + res = context.VoteAll(pred); + break; + case VoteOp.Any: + res = context.VoteAny(pred); + break; + case VoteOp.AllEqual: + res = context.VoteAllEqual(pred); + break; + } + + if (res != null) + { + context.Copy(Register(op.Predicate45), res); + } + else + { + context.Config.PrintLog($"Invalid vote operation: {op.VoteOp}."); + } + + if (!op.Rd.IsRZ) + { + context.Copy(Register(op.Rd), context.Ballot(pred)); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Instructions/InstEmitter.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitter.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Instructions/InstEmitter.cs rename to Ryujinx.Graphics.Shader/Instructions/InstEmitter.cs diff --git a/Ryujinx.Graphics/Shader/Instructions/Lop3Expression.cs b/Ryujinx.Graphics.Shader/Instructions/Lop3Expression.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Instructions/Lop3Expression.cs rename to Ryujinx.Graphics.Shader/Instructions/Lop3Expression.cs diff --git a/Ryujinx.Graphics/Shader/IntermediateRepresentation/BasicBlock.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/BasicBlock.cs similarity index 100% rename from Ryujinx.Graphics/Shader/IntermediateRepresentation/BasicBlock.cs rename to Ryujinx.Graphics.Shader/IntermediateRepresentation/BasicBlock.cs diff --git a/Ryujinx.Graphics.Shader/IntermediateRepresentation/CommentNode.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/CommentNode.cs new file mode 100644 index 0000000000..d4d87b0676 --- /dev/null +++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/CommentNode.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + class CommentNode : Operation + { + public string Comment { get; } + + public CommentNode(string comment) : base(Instruction.Comment, null) + { + Comment = comment; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/IntermediateRepresentation/INode.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/INode.cs similarity index 100% rename from Ryujinx.Graphics/Shader/IntermediateRepresentation/INode.cs rename to Ryujinx.Graphics.Shader/IntermediateRepresentation/INode.cs diff --git a/Ryujinx.Graphics/Shader/IntermediateRepresentation/Instruction.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs similarity index 50% rename from Ryujinx.Graphics/Shader/IntermediateRepresentation/Instruction.cs rename to Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs index ac0ebc2b08..7108112c97 100644 --- a/Ryujinx.Graphics/Shader/IntermediateRepresentation/Instruction.cs +++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs @@ -7,6 +7,19 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation { Absolute = 1, Add, + AtomicAdd, + AtomicAnd, + AtomicCompareAndSwap, + AtomicMinS32, + AtomicMinU32, + AtomicMaxS32, + AtomicMaxU32, + AtomicOr, + AtomicSwap, + AtomicXor, + Ballot, + Barrier, + BitCount, BitfieldExtractS32, BitfieldExtractU32, BitfieldInsert, @@ -21,6 +34,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation Ceiling, Clamp, ClampU32, + Comment, CompareEqual, CompareGreater, CompareGreaterOrEqual, @@ -33,21 +47,33 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation CompareNotEqual, ConditionalSelect, ConvertFPToS32, + ConvertFPToU32, ConvertS32ToFP, ConvertU32ToFP, Copy, Cosine, + Ddx, + Ddy, Discard, Divide, EmitVertex, EndPrimitive, ExponentB2, + FindFirstSetS32, + FindFirstSetU32, Floor, FusedMultiplyAdd, + GroupMemoryBarrier, + ImageLoad, + ImageStore, IsNan, + LoadAttribute, LoadConstant, LoadGlobal, LoadLocal, + LoadShared, + LoadStorage, + Lod, LogarithmB2, LogicalAnd, LogicalExclusiveOr, @@ -58,30 +84,76 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation MarkLabel, Maximum, MaximumU32, + MemoryBarrier, Minimum, MinimumU32, Multiply, + MultiplyHighS32, + MultiplyHighU32, Negate, PackDouble2x32, PackHalf2x16, ReciprocalSquareRoot, Return, + Round, ShiftLeft, ShiftRightS32, ShiftRightU32, + Shuffle, + ShuffleDown, + ShuffleUp, + ShuffleXor, Sine, SquareRoot, StoreGlobal, StoreLocal, + StoreShared, + StoreStorage, Subtract, + SwizzleAdd, TextureSample, TextureSize, Truncate, UnpackDouble2x32, UnpackHalf2x16, + VoteAll, + VoteAllEqual, + VoteAny, Count, - FP = 1 << 16, + + FP = 1 << 16, + + MrShift = 17, + + MrGlobal = 0 << MrShift, + MrShared = 1 << MrShift, + MrStorage = 2 << MrShift, + MrMask = 3 << MrShift, + Mask = 0xffff } + + static class InstructionExtensions + { + public static bool IsAtomic(this Instruction inst) + { + switch (inst & Instruction.Mask) + { + case Instruction.AtomicAdd: + case Instruction.AtomicAnd: + case Instruction.AtomicCompareAndSwap: + case Instruction.AtomicMaxS32: + case Instruction.AtomicMaxU32: + case Instruction.AtomicMinS32: + case Instruction.AtomicMinU32: + case Instruction.AtomicOr: + case Instruction.AtomicSwap: + case Instruction.AtomicXor: + return true; + } + + return false; + } + } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/IntermediateRepresentation/IrConsts.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/IrConsts.cs similarity index 100% rename from Ryujinx.Graphics/Shader/IntermediateRepresentation/IrConsts.cs rename to Ryujinx.Graphics.Shader/IntermediateRepresentation/IrConsts.cs diff --git a/Ryujinx.Graphics/Shader/IntermediateRepresentation/Operand.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operand.cs similarity index 86% rename from Ryujinx.Graphics/Shader/IntermediateRepresentation/Operand.cs rename to Ryujinx.Graphics.Shader/IntermediateRepresentation/Operand.cs index 1df88a3d9b..567277a75b 100644 --- a/Ryujinx.Graphics/Shader/IntermediateRepresentation/Operand.cs +++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operand.cs @@ -14,6 +14,8 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation public int Value { get; } + public InterpolationQualifier Interpolation { get; } + public INode AsgOp { get; set; } public HashSet UseOps { get; } @@ -28,10 +30,11 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation Type = type; } - public Operand(OperandType type, int value) : this() + public Operand(OperandType type, int value, InterpolationQualifier iq = InterpolationQualifier.None) : this() { - Type = type; - Value = value; + Type = type; + Value = value; + Interpolation = iq; } public Operand(Register reg) : this() diff --git a/Ryujinx.Graphics/Shader/IntermediateRepresentation/OperandHelper.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandHelper.cs similarity index 88% rename from Ryujinx.Graphics/Shader/IntermediateRepresentation/OperandHelper.cs rename to Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandHelper.cs index 6765f8a44f..45c9ba1e55 100644 --- a/Ryujinx.Graphics/Shader/IntermediateRepresentation/OperandHelper.cs +++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandHelper.cs @@ -5,9 +5,9 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation { static class OperandHelper { - public static Operand Attribute(int value) + public static Operand Attribute(int value, InterpolationQualifier iq = InterpolationQualifier.None) { - return new Operand(OperandType.Attribute, value); + return new Operand(OperandType.Attribute, value, iq); } public static Operand Cbuf(int slot, int offset) diff --git a/Ryujinx.Graphics/Shader/IntermediateRepresentation/OperandType.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandType.cs similarity index 84% rename from Ryujinx.Graphics/Shader/IntermediateRepresentation/OperandType.cs rename to Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandType.cs index e0e2a6675a..8f8df9e4f0 100644 --- a/Ryujinx.Graphics/Shader/IntermediateRepresentation/OperandType.cs +++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandType.cs @@ -5,9 +5,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation Attribute, Constant, ConstantBuffer, - GlobalMemory, Label, - LocalMemory, LocalVariable, Register, Undefined diff --git a/Ryujinx.Graphics/Shader/IntermediateRepresentation/Operation.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs similarity index 84% rename from Ryujinx.Graphics/Shader/IntermediateRepresentation/Operation.cs rename to Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs index c60f393e77..6b7fb82fe6 100644 --- a/Ryujinx.Graphics/Shader/IntermediateRepresentation/Operation.cs +++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs @@ -16,7 +16,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation public int SourcesCount => _sources.Length; - public int ComponentIndex { get; } + public int Index { get; } public Operation(Instruction inst, Operand dest, params Operand[] sources) { @@ -39,11 +39,11 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation public Operation( Instruction inst, - int compIndex, + int index, Operand dest, params Operand[] sources) : this(inst, dest, sources) { - ComponentIndex = compIndex; + Index = index; } private Operand AssignDest(Operand dest) @@ -70,7 +70,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation oldSrc.UseOps.Remove(this); } - if (source.Type == OperandType.LocalVariable) + if (source != null && source.Type == OperandType.LocalVariable) { source.UseOps.Add(this); } @@ -80,11 +80,16 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation public void TurnIntoCopy(Operand source) { - Inst = Instruction.Copy; + TurnInto(Instruction.Copy, source); + } + + public void TurnInto(Instruction newInst, Operand source) + { + Inst = newInst; foreach (Operand oldSrc in _sources) { - if (oldSrc.Type == OperandType.LocalVariable) + if (oldSrc != null && oldSrc.Type == OperandType.LocalVariable) { oldSrc.UseOps.Remove(this); } diff --git a/Ryujinx.Graphics/Shader/IntermediateRepresentation/PhiNode.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/PhiNode.cs similarity index 100% rename from Ryujinx.Graphics/Shader/IntermediateRepresentation/PhiNode.cs rename to Ryujinx.Graphics.Shader/IntermediateRepresentation/PhiNode.cs diff --git a/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureFlags.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureFlags.cs new file mode 100644 index 0000000000..5334afacca --- /dev/null +++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureFlags.cs @@ -0,0 +1,18 @@ +using System; + +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + [Flags] + enum TextureFlags + { + None = 0, + Bindless = 1 << 0, + Gather = 1 << 1, + Derivatives = 1 << 2, + IntCoords = 1 << 3, + LodBias = 1 << 4, + LodLevel = 1 << 5, + Offset = 1 << 6, + Offsets = 1 << 7 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/IntermediateRepresentation/TextureOperation.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs similarity index 57% rename from Ryujinx.Graphics/Shader/IntermediateRepresentation/TextureOperation.cs rename to Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs index f5f2cc5c60..718d2c2ec1 100644 --- a/Ryujinx.Graphics/Shader/IntermediateRepresentation/TextureOperation.cs +++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs @@ -2,14 +2,14 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation { class TextureOperation : Operation { - public TextureType Type { get; } - public TextureFlags Flags { get; } + public SamplerType Type { get; private set; } + public TextureFlags Flags { get; private set; } - public int Handle { get; } + public int Handle { get; private set; } public TextureOperation( Instruction inst, - TextureType type, + SamplerType type, TextureFlags flags, int handle, int compIndex, @@ -20,5 +20,14 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation Flags = flags; Handle = handle; } + + public void TurnIntoIndexed(int handle) + { + Type |= SamplerType.Indexed; + + Flags &= ~TextureFlags.Bindless; + + Handle = handle; + } } } \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/InterpolationQualifier.cs b/Ryujinx.Graphics.Shader/InterpolationQualifier.cs new file mode 100644 index 0000000000..e710427dde --- /dev/null +++ b/Ryujinx.Graphics.Shader/InterpolationQualifier.cs @@ -0,0 +1,45 @@ +using System; + +namespace Ryujinx.Graphics.Shader +{ + [Flags] + public enum InterpolationQualifier + { + None = 0, + + Flat = 1, + NoPerspective = 2, + Smooth = 3, + + Centroid = 1 << 16, + Sample = 1 << 17, + + FlagsMask = Centroid | Sample + } + + public static class InterpolationQualifierExtensions + { + public static string ToGlslQualifier(this InterpolationQualifier iq) + { + string output = string.Empty; + + switch (iq & ~InterpolationQualifier.FlagsMask) + { + case InterpolationQualifier.Flat: output = "flat"; break; + case InterpolationQualifier.NoPerspective: output = "noperspective"; break; + case InterpolationQualifier.Smooth: output = "smooth"; break; + } + + if ((iq & InterpolationQualifier.Centroid) != 0) + { + output = "centroid " + output; + } + else if ((iq & InterpolationQualifier.Sample) != 0) + { + output = "sample " + output; + } + + return output; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/OutputTopology.cs b/Ryujinx.Graphics.Shader/OutputTopology.cs new file mode 100644 index 0000000000..6f977becb4 --- /dev/null +++ b/Ryujinx.Graphics.Shader/OutputTopology.cs @@ -0,0 +1,24 @@ +namespace Ryujinx.Graphics.Shader +{ + enum OutputTopology + { + PointList = 1, + LineStrip = 6, + TriangleStrip = 7 + } + + static class OutputTopologyExtensions + { + public static string ToGlslString(this OutputTopology topology) + { + switch (topology) + { + case OutputTopology.LineStrip: return "line_strip"; + case OutputTopology.PointList: return "points"; + case OutputTopology.TriangleStrip: return "triangle_strip"; + } + + return "points"; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/QueryInfoName.cs b/Ryujinx.Graphics.Shader/QueryInfoName.cs new file mode 100644 index 0000000000..c4f2cb6cc2 --- /dev/null +++ b/Ryujinx.Graphics.Shader/QueryInfoName.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Graphics.Shader +{ + public enum QueryInfoName + { + ComputeLocalSizeX, + ComputeLocalSizeY, + ComputeLocalSizeZ, + ComputeSharedMemorySize, + IsTextureBuffer, + IsTextureRectangle, + PrimitiveTopology, + StorageBufferOffsetAlignment, + SupportsNonConstantTextureOffset + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj b/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj new file mode 100644 index 0000000000..55864c6dd8 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + netcoreapp3.0 + win-x64;osx-x64;linux-x64 + + + diff --git a/Ryujinx.Graphics.Shader/SamplerType.cs b/Ryujinx.Graphics.Shader/SamplerType.cs new file mode 100644 index 0000000000..9546efe494 --- /dev/null +++ b/Ryujinx.Graphics.Shader/SamplerType.cs @@ -0,0 +1,39 @@ +using System; + +namespace Ryujinx.Graphics.Shader +{ + [Flags] + public enum SamplerType + { + None = 0, + Texture1D, + TextureBuffer, + Texture2D, + Texture3D, + TextureCube, + + Mask = 0xff, + + Array = 1 << 8, + Indexed = 1 << 9, + Multisample = 1 << 10, + Shadow = 1 << 11 + } + + static class SamplerTypeExtensions + { + public static int GetDimensions(this SamplerType type) + { + switch (type & SamplerType.Mask) + { + case SamplerType.Texture1D: return 1; + case SamplerType.TextureBuffer: return 1; + case SamplerType.Texture2D: return 2; + case SamplerType.Texture3D: return 3; + case SamplerType.TextureCube: return 3; + } + + throw new ArgumentException($"Invalid sampler type \"{type}\"."); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/ShaderProgram.cs b/Ryujinx.Graphics.Shader/ShaderProgram.cs new file mode 100644 index 0000000000..4d0c6e5bd6 --- /dev/null +++ b/Ryujinx.Graphics.Shader/ShaderProgram.cs @@ -0,0 +1,33 @@ +using System; + +namespace Ryujinx.Graphics.Shader +{ + public class ShaderProgram + { + public ShaderProgramInfo Info { get; } + + public ShaderStage Stage { get; } + + public string Code { get; private set; } + + public int Size { get; } + + internal ShaderProgram(ShaderProgramInfo info, ShaderStage stage, string code, int size) + { + Info = info; + Stage = stage; + Code = code; + Size = size; + } + + public void Prepend(string line) + { + Code = line + Environment.NewLine + Code; + } + + public void Replace(string name, string value) + { + Code = Code.Replace(name, value); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs b/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs new file mode 100644 index 0000000000..1ff602a240 --- /dev/null +++ b/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.ObjectModel; + +namespace Ryujinx.Graphics.Shader +{ + public class ShaderProgramInfo + { + public ReadOnlyCollection CBuffers { get; } + public ReadOnlyCollection SBuffers { get; } + public ReadOnlyCollection Textures { get; } + public ReadOnlyCollection Images { get; } + + public ReadOnlyCollection InterpolationQualifiers { get; } + + public bool UsesInstanceId { get; } + + internal ShaderProgramInfo( + BufferDescriptor[] cBuffers, + BufferDescriptor[] sBuffers, + TextureDescriptor[] textures, + TextureDescriptor[] images, + InterpolationQualifier[] interpolationQualifiers, + bool usesInstanceId) + { + CBuffers = Array.AsReadOnly(cBuffers); + SBuffers = Array.AsReadOnly(sBuffers); + Textures = Array.AsReadOnly(textures); + Images = Array.AsReadOnly(images); + + InterpolationQualifiers = Array.AsReadOnly(interpolationQualifiers); + + UsesInstanceId = usesInstanceId; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/ShaderStage.cs b/Ryujinx.Graphics.Shader/ShaderStage.cs new file mode 100644 index 0000000000..30b65348e6 --- /dev/null +++ b/Ryujinx.Graphics.Shader/ShaderStage.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Shader +{ + public enum ShaderStage + { + Compute, + Vertex, + TessellationControl, + TessellationEvaluation, + Geometry, + Fragment + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/StructuredIr/AstAssignment.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstAssignment.cs similarity index 100% rename from Ryujinx.Graphics/Shader/StructuredIr/AstAssignment.cs rename to Ryujinx.Graphics.Shader/StructuredIr/AstAssignment.cs diff --git a/Ryujinx.Graphics/Shader/StructuredIr/AstBlock.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstBlock.cs similarity index 100% rename from Ryujinx.Graphics/Shader/StructuredIr/AstBlock.cs rename to Ryujinx.Graphics.Shader/StructuredIr/AstBlock.cs diff --git a/Ryujinx.Graphics/Shader/StructuredIr/AstBlockType.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstBlockType.cs similarity index 100% rename from Ryujinx.Graphics/Shader/StructuredIr/AstBlockType.cs rename to Ryujinx.Graphics.Shader/StructuredIr/AstBlockType.cs diff --git a/Ryujinx.Graphics/Shader/StructuredIr/AstBlockVisitor.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstBlockVisitor.cs similarity index 100% rename from Ryujinx.Graphics/Shader/StructuredIr/AstBlockVisitor.cs rename to Ryujinx.Graphics.Shader/StructuredIr/AstBlockVisitor.cs diff --git a/Ryujinx.Graphics.Shader/StructuredIr/AstComment.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstComment.cs new file mode 100644 index 0000000000..dabe623fd8 --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/AstComment.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class AstComment : AstNode + { + public string Comment { get; } + + public AstComment(string comment) + { + Comment = comment; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/StructuredIr/AstHelper.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstHelper.cs similarity index 100% rename from Ryujinx.Graphics/Shader/StructuredIr/AstHelper.cs rename to Ryujinx.Graphics.Shader/StructuredIr/AstHelper.cs diff --git a/Ryujinx.Graphics/Shader/StructuredIr/AstNode.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstNode.cs similarity index 100% rename from Ryujinx.Graphics/Shader/StructuredIr/AstNode.cs rename to Ryujinx.Graphics.Shader/StructuredIr/AstNode.cs diff --git a/Ryujinx.Graphics/Shader/StructuredIr/AstOperand.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstOperand.cs similarity index 88% rename from Ryujinx.Graphics/Shader/StructuredIr/AstOperand.cs rename to Ryujinx.Graphics.Shader/StructuredIr/AstOperand.cs index 97ff3ca97c..25b09636fe 100644 --- a/Ryujinx.Graphics/Shader/StructuredIr/AstOperand.cs +++ b/Ryujinx.Graphics.Shader/StructuredIr/AstOperand.cs @@ -12,6 +12,8 @@ namespace Ryujinx.Graphics.Shader.StructuredIr public VariableType VarType { get; set; } + public InterpolationQualifier Interpolation { get; } + public int Value { get; } public int CbufSlot { get; } @@ -27,7 +29,8 @@ namespace Ryujinx.Graphics.Shader.StructuredIr public AstOperand(Operand operand) : this() { - Type = operand.Type; + Type = operand.Type; + Interpolation = operand.Interpolation; if (Type == OperandType.ConstantBuffer) { diff --git a/Ryujinx.Graphics/Shader/StructuredIr/AstOperation.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstOperation.cs similarity index 81% rename from Ryujinx.Graphics/Shader/StructuredIr/AstOperation.cs rename to Ryujinx.Graphics.Shader/StructuredIr/AstOperation.cs index 1607ffecde..76eee71e82 100644 --- a/Ryujinx.Graphics/Shader/StructuredIr/AstOperation.cs +++ b/Ryujinx.Graphics.Shader/StructuredIr/AstOperation.cs @@ -8,7 +8,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr { public Instruction Inst { get; } - public int ComponentMask { get; } + public int Index { get; } private IAstNode[] _sources; @@ -24,12 +24,12 @@ namespace Ryujinx.Graphics.Shader.StructuredIr AddUse(source, this); } - ComponentMask = 1; + Index = 0; } - public AstOperation(Instruction inst, int compMask, params IAstNode[] sources) : this(inst, sources) + public AstOperation(Instruction inst, int index, params IAstNode[] sources) : this(inst, sources) { - ComponentMask = compMask; + Index = index; } public IAstNode GetSource(int index) diff --git a/Ryujinx.Graphics/Shader/StructuredIr/AstOptimizer.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstOptimizer.cs similarity index 75% rename from Ryujinx.Graphics/Shader/StructuredIr/AstOptimizer.cs rename to Ryujinx.Graphics.Shader/StructuredIr/AstOptimizer.cs index 0f5392b7d6..a37e1a3e85 100644 --- a/Ryujinx.Graphics/Shader/StructuredIr/AstOptimizer.cs +++ b/Ryujinx.Graphics.Shader/StructuredIr/AstOptimizer.cs @@ -1,4 +1,5 @@ using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; using System.Collections.Generic; using System.Linq; @@ -8,28 +9,33 @@ namespace Ryujinx.Graphics.Shader.StructuredIr { static class AstOptimizer { - public static void Optimize(StructuredProgramInfo info) + public static void Optimize(StructuredProgramContext context) { - AstBlock mainBlock = info.MainBlock; + AstBlock mainBlock = context.Info.MainBlock; - AstBlockVisitor visitor = new AstBlockVisitor(mainBlock); - - foreach (IAstNode node in visitor.Visit()) + // When debug mode is enabled, we disable expression propagation + // (this makes comparison with the disassembly easier). + if ((context.Config.Flags & TranslationFlags.DebugMode) == 0) { - if (node is AstAssignment assignment && assignment.Destination is AstOperand propVar) + AstBlockVisitor visitor = new AstBlockVisitor(mainBlock); + + foreach (IAstNode node in visitor.Visit()) { - bool isWorthPropagating = propVar.Uses.Count == 1 || IsWorthPropagating(assignment.Source); - - if (propVar.Defs.Count == 1 && isWorthPropagating) + if (node is AstAssignment assignment && assignment.Destination is AstOperand propVar) { - PropagateExpression(propVar, assignment.Source); - } + bool isWorthPropagating = propVar.Uses.Count == 1 || IsWorthPropagating(assignment.Source); - if (propVar.Type == OperandType.LocalVariable && propVar.Uses.Count == 0) - { - visitor.Block.Remove(assignment); + if (propVar.Defs.Count == 1 && isWorthPropagating) + { + PropagateExpression(propVar, assignment.Source); + } - info.Locals.Remove(propVar); + if (propVar.Type == OperandType.LocalVariable && propVar.Uses.Count == 0) + { + visitor.Block.Remove(assignment); + + context.Info.Locals.Remove(propVar); + } } } } diff --git a/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs new file mode 100644 index 0000000000..5473978e2b --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs @@ -0,0 +1,28 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class AstTextureOperation : AstOperation + { + public SamplerType Type { get; } + public TextureFlags Flags { get; } + + public int Handle { get; } + public int ArraySize { get; } + + public AstTextureOperation( + Instruction inst, + SamplerType type, + TextureFlags flags, + int handle, + int arraySize, + int index, + params IAstNode[] sources) : base(inst, index, sources) + { + Type = type; + Flags = flags; + Handle = handle; + ArraySize = arraySize; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/StructuredIr/GotoElimination.cs b/Ryujinx.Graphics.Shader/StructuredIr/GotoElimination.cs similarity index 100% rename from Ryujinx.Graphics/Shader/StructuredIr/GotoElimination.cs rename to Ryujinx.Graphics.Shader/StructuredIr/GotoElimination.cs diff --git a/Ryujinx.Graphics/Shader/StructuredIr/GotoStatement.cs b/Ryujinx.Graphics.Shader/StructuredIr/GotoStatement.cs similarity index 100% rename from Ryujinx.Graphics/Shader/StructuredIr/GotoStatement.cs rename to Ryujinx.Graphics.Shader/StructuredIr/GotoStatement.cs diff --git a/Ryujinx.Graphics.Shader/StructuredIr/HelperFunctionsMask.cs b/Ryujinx.Graphics.Shader/StructuredIr/HelperFunctionsMask.cs new file mode 100644 index 0000000000..53367fce14 --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/HelperFunctionsMask.cs @@ -0,0 +1,16 @@ +using System; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + [Flags] + enum HelperFunctionsMask + { + MultiplyHighS32 = 1 << 0, + MultiplyHighU32 = 1 << 1, + Shuffle = 1 << 2, + ShuffleDown = 1 << 3, + ShuffleUp = 1 << 4, + ShuffleXor = 1 << 5, + SwizzleAdd = 1 << 6 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/StructuredIr/IAstNode.cs b/Ryujinx.Graphics.Shader/StructuredIr/IAstNode.cs similarity index 100% rename from Ryujinx.Graphics/Shader/StructuredIr/IAstNode.cs rename to Ryujinx.Graphics.Shader/StructuredIr/IAstNode.cs diff --git a/Ryujinx.Graphics/Shader/StructuredIr/InstructionInfo.cs b/Ryujinx.Graphics.Shader/StructuredIr/InstructionInfo.cs similarity index 63% rename from Ryujinx.Graphics/Shader/StructuredIr/InstructionInfo.cs rename to Ryujinx.Graphics.Shader/StructuredIr/InstructionInfo.cs index 46a61553b8..0482c35eea 100644 --- a/Ryujinx.Graphics/Shader/StructuredIr/InstructionInfo.cs +++ b/Ryujinx.Graphics.Shader/StructuredIr/InstructionInfo.cs @@ -24,8 +24,21 @@ namespace Ryujinx.Graphics.Shader.StructuredIr { _infoTbl = new InstInfo[(int)Instruction.Count]; + // Inst Destination type Source 1 type Source 2 type Source 3 type Source 4 type + Add(Instruction.AtomicAdd, VariableType.U32, VariableType.S32, VariableType.S32, VariableType.U32); + Add(Instruction.AtomicAnd, VariableType.U32, VariableType.S32, VariableType.S32, VariableType.U32); + Add(Instruction.AtomicCompareAndSwap, VariableType.U32, VariableType.S32, VariableType.S32, VariableType.U32, VariableType.U32); + Add(Instruction.AtomicMaxS32, VariableType.S32, VariableType.S32, VariableType.S32, VariableType.S32); + Add(Instruction.AtomicMaxU32, VariableType.U32, VariableType.S32, VariableType.S32, VariableType.U32); + Add(Instruction.AtomicMinS32, VariableType.S32, VariableType.S32, VariableType.S32, VariableType.S32); + Add(Instruction.AtomicMinU32, VariableType.U32, VariableType.S32, VariableType.S32, VariableType.U32); + Add(Instruction.AtomicOr, VariableType.U32, VariableType.S32, VariableType.S32, VariableType.U32); + Add(Instruction.AtomicSwap, VariableType.U32, VariableType.S32, VariableType.S32, VariableType.U32); + Add(Instruction.AtomicXor, VariableType.U32, VariableType.S32, VariableType.S32, VariableType.U32); Add(Instruction.Absolute, VariableType.Scalar, VariableType.Scalar); Add(Instruction.Add, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.Ballot, VariableType.U32, VariableType.Bool); + Add(Instruction.BitCount, VariableType.Int, VariableType.Int); Add(Instruction.BitfieldExtractS32, VariableType.S32, VariableType.S32, VariableType.S32, VariableType.S32); Add(Instruction.BitfieldExtractU32, VariableType.U32, VariableType.U32, VariableType.S32, VariableType.S32); Add(Instruction.BitfieldInsert, VariableType.Int, VariableType.Int, VariableType.Int, VariableType.S32, VariableType.S32); @@ -51,15 +64,28 @@ namespace Ryujinx.Graphics.Shader.StructuredIr Add(Instruction.CompareNotEqual, VariableType.Bool, VariableType.Scalar, VariableType.Scalar); Add(Instruction.ConditionalSelect, VariableType.Scalar, VariableType.Bool, VariableType.Scalar, VariableType.Scalar); Add(Instruction.ConvertFPToS32, VariableType.S32, VariableType.F32); + Add(Instruction.ConvertFPToU32, VariableType.U32, VariableType.F32); Add(Instruction.ConvertS32ToFP, VariableType.F32, VariableType.S32); Add(Instruction.ConvertU32ToFP, VariableType.F32, VariableType.U32); Add(Instruction.Cosine, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.Ddx, VariableType.F32, VariableType.F32); + Add(Instruction.Ddy, VariableType.F32, VariableType.F32); Add(Instruction.Divide, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar); Add(Instruction.ExponentB2, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.FindFirstSetS32, VariableType.S32, VariableType.S32); + Add(Instruction.FindFirstSetU32, VariableType.S32, VariableType.U32); Add(Instruction.Floor, VariableType.F32, VariableType.F32); Add(Instruction.FusedMultiplyAdd, VariableType.F32, VariableType.F32, VariableType.F32, VariableType.F32); + Add(Instruction.ImageLoad, VariableType.F32); + Add(Instruction.ImageStore, VariableType.None); Add(Instruction.IsNan, VariableType.Bool, VariableType.F32); + Add(Instruction.LoadAttribute, VariableType.F32, VariableType.S32, VariableType.S32); Add(Instruction.LoadConstant, VariableType.F32, VariableType.S32, VariableType.S32); + Add(Instruction.LoadGlobal, VariableType.U32, VariableType.S32, VariableType.S32); + Add(Instruction.LoadLocal, VariableType.U32, VariableType.S32); + Add(Instruction.LoadShared, VariableType.U32, VariableType.S32); + Add(Instruction.LoadStorage, VariableType.U32, VariableType.S32, VariableType.S32); + Add(Instruction.Lod, VariableType.F32); Add(Instruction.LogarithmB2, VariableType.Scalar, VariableType.Scalar); Add(Instruction.LogicalAnd, VariableType.Bool, VariableType.Bool, VariableType.Bool); Add(Instruction.LogicalExclusiveOr, VariableType.Bool, VariableType.Bool, VariableType.Bool); @@ -68,21 +94,36 @@ namespace Ryujinx.Graphics.Shader.StructuredIr Add(Instruction.ShiftLeft, VariableType.Int, VariableType.Int, VariableType.Int); Add(Instruction.ShiftRightS32, VariableType.S32, VariableType.S32, VariableType.Int); Add(Instruction.ShiftRightU32, VariableType.U32, VariableType.U32, VariableType.Int); + Add(Instruction.Shuffle, VariableType.F32, VariableType.F32, VariableType.U32, VariableType.U32); + Add(Instruction.ShuffleDown, VariableType.F32, VariableType.F32, VariableType.U32, VariableType.U32); + Add(Instruction.ShuffleUp, VariableType.F32, VariableType.F32, VariableType.U32, VariableType.U32); + Add(Instruction.ShuffleXor, VariableType.F32, VariableType.F32, VariableType.U32, VariableType.U32); Add(Instruction.Maximum, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar); Add(Instruction.MaximumU32, VariableType.U32, VariableType.U32, VariableType.U32); Add(Instruction.Minimum, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar); Add(Instruction.MinimumU32, VariableType.U32, VariableType.U32, VariableType.U32); Add(Instruction.Multiply, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.MultiplyHighS32, VariableType.S32, VariableType.S32, VariableType.S32); + Add(Instruction.MultiplyHighU32, VariableType.U32, VariableType.U32, VariableType.U32); Add(Instruction.Negate, VariableType.Scalar, VariableType.Scalar); Add(Instruction.PackHalf2x16, VariableType.U32, VariableType.F32, VariableType.F32); Add(Instruction.ReciprocalSquareRoot, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.Round, VariableType.F32, VariableType.F32); Add(Instruction.Sine, VariableType.Scalar, VariableType.Scalar); Add(Instruction.SquareRoot, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.StoreGlobal, VariableType.None, VariableType.S32, VariableType.S32, VariableType.U32); + Add(Instruction.StoreLocal, VariableType.None, VariableType.S32, VariableType.U32); + Add(Instruction.StoreShared, VariableType.None, VariableType.S32, VariableType.U32); + Add(Instruction.StoreStorage, VariableType.None, VariableType.S32, VariableType.S32, VariableType.U32); Add(Instruction.Subtract, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.SwizzleAdd, VariableType.F32, VariableType.F32, VariableType.F32, VariableType.S32); Add(Instruction.TextureSample, VariableType.F32); Add(Instruction.TextureSize, VariableType.S32, VariableType.S32, VariableType.S32); Add(Instruction.Truncate, VariableType.F32, VariableType.F32); Add(Instruction.UnpackHalf2x16, VariableType.F32, VariableType.U32); + Add(Instruction.VoteAll, VariableType.Bool, VariableType.Bool); + Add(Instruction.VoteAllEqual, VariableType.Bool, VariableType.Bool); + Add(Instruction.VoteAny, VariableType.Bool, VariableType.Bool); } private static void Add(Instruction inst, VariableType destType, params VariableType[] srcTypes) @@ -97,7 +138,12 @@ namespace Ryujinx.Graphics.Shader.StructuredIr public static VariableType GetSrcVarType(Instruction inst, int index) { - if (inst == Instruction.TextureSample) + // TODO: Return correct type depending on source index, + // that can improve the decompiler output. + if (inst == Instruction.ImageLoad || + inst == Instruction.ImageStore || + inst == Instruction.Lod || + inst == Instruction.TextureSample) { return VariableType.F32; } diff --git a/Ryujinx.Graphics/Shader/StructuredIr/OperandInfo.cs b/Ryujinx.Graphics.Shader/StructuredIr/OperandInfo.cs similarity index 93% rename from Ryujinx.Graphics/Shader/StructuredIr/OperandInfo.cs rename to Ryujinx.Graphics.Shader/StructuredIr/OperandInfo.cs index a3a8d13839..95c5731a9f 100644 --- a/Ryujinx.Graphics/Shader/StructuredIr/OperandInfo.cs +++ b/Ryujinx.Graphics.Shader/StructuredIr/OperandInfo.cs @@ -24,7 +24,6 @@ namespace Ryujinx.Graphics.Shader.StructuredIr case OperandType.Attribute: return VariableType.F32; case OperandType.Constant: return VariableType.S32; case OperandType.ConstantBuffer: return VariableType.F32; - case OperandType.GlobalMemory: return VariableType.F32; case OperandType.Undefined: return VariableType.S32; } diff --git a/Ryujinx.Graphics/Shader/StructuredIr/PhiFunctions.cs b/Ryujinx.Graphics.Shader/StructuredIr/PhiFunctions.cs similarity index 100% rename from Ryujinx.Graphics/Shader/StructuredIr/PhiFunctions.cs rename to Ryujinx.Graphics.Shader/StructuredIr/PhiFunctions.cs diff --git a/Ryujinx.Graphics/Shader/StructuredIr/StructuredProgram.cs b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs similarity index 61% rename from Ryujinx.Graphics/Shader/StructuredIr/StructuredProgram.cs rename to Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs index 26faaf36f5..504dc38676 100644 --- a/Ryujinx.Graphics/Shader/StructuredIr/StructuredProgram.cs +++ b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs @@ -1,4 +1,5 @@ using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; using System; using System.Collections.Generic; @@ -6,11 +7,11 @@ namespace Ryujinx.Graphics.Shader.StructuredIr { static class StructuredProgram { - public static StructuredProgramInfo MakeStructuredProgram(BasicBlock[] blocks) + public static StructuredProgramInfo MakeStructuredProgram(BasicBlock[] blocks, ShaderConfig config) { PhiFunctions.Remove(blocks); - StructuredProgramContext context = new StructuredProgramContext(blocks.Length); + StructuredProgramContext context = new StructuredProgramContext(blocks.Length, config); for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) { @@ -35,7 +36,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr GotoElimination.Eliminate(context.GetGotos()); - AstOptimizer.Optimize(context.Info); + AstOptimizer.Optimize(context); return context.Info; } @@ -51,20 +52,36 @@ namespace Ryujinx.Graphics.Shader.StructuredIr sources[index] = context.GetOperandUse(operation.GetSource(index)); } + AstTextureOperation GetAstTextureOperation(TextureOperation texOp) + { + return new AstTextureOperation( + inst, + texOp.Type, + texOp.Flags, + texOp.Handle, + 4, // TODO: Non-hardcoded array size. + texOp.Index, + sources); + } + if (operation.Dest != null) { AstOperand dest = context.GetOperandDef(operation.Dest); if (inst == Instruction.LoadConstant) { - Operand ldcSource = operation.GetSource(0); + Operand slot = operation.GetSource(0); - if (ldcSource.Type != OperandType.Constant) + if (slot.Type != OperandType.Constant) { - throw new InvalidOperationException("Found LDC with non-constant constant buffer slot."); + throw new InvalidOperationException("Found load with non-constant constant buffer slot."); } - context.Info.CBuffers.Add(ldcSource.Value); + context.Info.CBuffers.Add(slot.Value); + } + else if (UsesStorage(inst)) + { + AddSBufferUse(context.Info.SBuffers, operation); } AstAssignment assignment; @@ -97,27 +114,26 @@ namespace Ryujinx.Graphics.Shader.StructuredIr dest.VarType = InstructionInfo.GetDestVarType(inst); } - int componentMask = 1 << operation.ComponentIndex; - IAstNode source; if (operation is TextureOperation texOp) { - AstTextureOperation astTexOp = new AstTextureOperation( - inst, - texOp.Type, - texOp.Flags, - texOp.Handle, - componentMask, - sources); + AstTextureOperation astTexOp = GetAstTextureOperation(texOp); - context.Info.Samplers.Add(astTexOp); + if (texOp.Inst == Instruction.ImageLoad) + { + context.Info.Images.Add(astTexOp); + } + else + { + context.Info.Samplers.Add(astTexOp); + } source = astTexOp; } else if (!isCopy) { - source = new AstOperation(inst, componentMask, sources); + source = new AstOperation(inst, operation.Index, sources); } else { @@ -128,9 +144,74 @@ namespace Ryujinx.Graphics.Shader.StructuredIr context.AddNode(assignment); } + else if (operation.Inst == Instruction.Comment) + { + context.AddNode(new AstComment(((CommentNode)operation).Comment)); + } + else if (operation is TextureOperation texOp) + { + AstTextureOperation astTexOp = GetAstTextureOperation(texOp); + + context.Info.Images.Add(astTexOp); + + context.AddNode(astTexOp); + } else { - context.AddNode(new AstOperation(inst, sources)); + if (UsesStorage(inst)) + { + AddSBufferUse(context.Info.SBuffers, operation); + } + + context.AddNode(new AstOperation(inst, operation.Index, sources)); + } + + // Those instructions needs to be emulated by using helper functions, + // because they are NVIDIA specific. Those flags helps the backend to + // decide which helper functions are needed on the final generated code. + switch (operation.Inst) + { + case Instruction.MultiplyHighS32: + context.Info.HelperFunctionsMask |= HelperFunctionsMask.MultiplyHighS32; + break; + case Instruction.MultiplyHighU32: + context.Info.HelperFunctionsMask |= HelperFunctionsMask.MultiplyHighU32; + break; + case Instruction.Shuffle: + context.Info.HelperFunctionsMask |= HelperFunctionsMask.Shuffle; + break; + case Instruction.ShuffleDown: + context.Info.HelperFunctionsMask |= HelperFunctionsMask.ShuffleDown; + break; + case Instruction.ShuffleUp: + context.Info.HelperFunctionsMask |= HelperFunctionsMask.ShuffleUp; + break; + case Instruction.ShuffleXor: + context.Info.HelperFunctionsMask |= HelperFunctionsMask.ShuffleXor; + break; + case Instruction.SwizzleAdd: + context.Info.HelperFunctionsMask |= HelperFunctionsMask.SwizzleAdd; + break; + } + } + + private static void AddSBufferUse(HashSet sBuffers, Operation operation) + { + Operand slot = operation.GetSource(0); + + if (slot.Type == OperandType.Constant) + { + sBuffers.Add(slot.Value); + } + else + { + // If the value is not constant, then we don't know + // how many storage buffers are used, so we assume + // all of them are used. + for (int index = 0; index < GlobalMemory.StorageMaxCount; index++) + { + sBuffers.Add(index); + } } } @@ -250,5 +331,15 @@ namespace Ryujinx.Graphics.Shader.StructuredIr throw new ArgumentException($"Unexpected instruction \"{inst}\"."); } + + private static bool UsesStorage(Instruction inst) + { + if (inst == Instruction.LoadStorage || inst == Instruction.StoreStorage) + { + return true; + } + + return inst.IsAtomic() && (inst & Instruction.MrMask) == Instruction.MrStorage; + } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/StructuredIr/StructuredProgramContext.cs b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs similarity index 73% rename from Ryujinx.Graphics/Shader/StructuredIr/StructuredProgramContext.cs rename to Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs index 5d6ff89073..f2af84f3b5 100644 --- a/Ryujinx.Graphics/Shader/StructuredIr/StructuredProgramContext.cs +++ b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs @@ -1,4 +1,5 @@ using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; using System.Collections.Generic; using System.Linq; @@ -10,7 +11,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr { private HashSet _loopTails; - private Stack<(AstBlock Block, int EndIndex)> _blockStack; + private Stack<(AstBlock Block, int CurrEndIndex, int LoopEndIndex)> _blockStack; private Dictionary _localsMap; @@ -21,14 +22,17 @@ namespace Ryujinx.Graphics.Shader.StructuredIr private AstBlock _currBlock; private int _currEndIndex; + private int _loopEndIndex; public StructuredProgramInfo Info { get; } - public StructuredProgramContext(int blocksCount) + public ShaderConfig Config { get; } + + public StructuredProgramContext(int blocksCount, ShaderConfig config) { _loopTails = new HashSet(); - _blockStack = new Stack<(AstBlock, int)>(); + _blockStack = new Stack<(AstBlock, int, int)>(); _localsMap = new Dictionary(); @@ -39,15 +43,18 @@ namespace Ryujinx.Graphics.Shader.StructuredIr _currBlock = new AstBlock(AstBlockType.Main); _currEndIndex = blocksCount; + _loopEndIndex = blocksCount; Info = new StructuredProgramInfo(_currBlock); + + Config = config; } public void EnterBlock(BasicBlock block) { while (_currEndIndex == block.Index) { - (_currBlock, _currEndIndex) = _blockStack.Pop(); + (_currBlock, _currEndIndex, _loopEndIndex) = _blockStack.Pop(); } if (_gotoTempAsgs.TryGetValue(block.Index, out AstAssignment gotoTempAsg)) @@ -71,13 +78,19 @@ namespace Ryujinx.Graphics.Shader.StructuredIr foreach (BasicBlock predecessor in block.Predecessors.OrderByDescending(x => x.Index)) { + // If not a loop, break. if (predecessor.Index < block.Index) { break; } + // Check if we can create a do-while loop here (only possible if the loop end + // falls inside the current scope), if not add a goto instead. if (predecessor.Index < _currEndIndex && !done) { + // Create do-while loop block. We must avoid inserting a goto at the end + // of the loop later, when the tail block is processed. So we add the predecessor + // to a list of loop tails to prevent it from being processed later. Operation branchOp = (Operation)predecessor.GetLastOp(); NewBlock(AstBlockType.DoWhile, branchOp, predecessor.Index + 1); @@ -88,6 +101,9 @@ namespace Ryujinx.Graphics.Shader.StructuredIr } else { + // Failed to create loop. Since this block is the loop head, we reset the + // goto condition variable here. The variable is always reset on the jump + // target, and this block is the jump target for some loop. AddGotoTempReset(block, GetGotoTempAsg(block.Index)); break; @@ -102,9 +118,19 @@ namespace Ryujinx.Graphics.Shader.StructuredIr return; } + // We can only enclose the "if" when the branch lands before + // the end of the current block. If the current enclosing block + // is not a loop, then we can also do so if the branch lands + // right at the end of the current block. When it is a loop, + // this is not valid as the loop condition would be evaluated, + // and it could erroneously jump back to the start of the loop. + bool inRange = + block.Branch.Index < _currEndIndex || + (block.Branch.Index == _currEndIndex && block.Branch.Index < _loopEndIndex); + bool isLoop = block.Branch.Index <= block.Index; - if (block.Branch.Index <= _currEndIndex && !isLoop) + if (inRange && !isLoop) { NewBlock(AstBlockType.If, branchOp, block.Branch.Index); } @@ -112,6 +138,8 @@ namespace Ryujinx.Graphics.Shader.StructuredIr { AstAssignment gotoTempAsg = GetGotoTempAsg(block.Branch.Index); + // We use DoWhile type here, as the condition should be true for + // unconditional branches, or it should jump if the condition is true otherwise. IAstNode cond = GetBranchCond(AstBlockType.DoWhile, branchOp); AddNode(Assign(gotoTempAsg.Destination, cond)); @@ -144,6 +172,12 @@ namespace Ryujinx.Graphics.Shader.StructuredIr private void AddGotoTempReset(BasicBlock block, AstAssignment gotoTempAsg) { + // If it was already added, we don't need to add it again. + if (gotoTempAsg.Parent != null) + { + return; + } + AddNode(gotoTempAsg); // For block 0, we don't need to add the extra "reset" at the beginning, @@ -166,10 +200,15 @@ namespace Ryujinx.Graphics.Shader.StructuredIr AddNode(childBlock); - _blockStack.Push((_currBlock, _currEndIndex)); + _blockStack.Push((_currBlock, _currEndIndex, _loopEndIndex)); _currBlock = childBlock; _currEndIndex = endIndex; + + if (type == AstBlockType.DoWhile) + { + _loopEndIndex = endIndex; + } } private IAstNode GetBranchCond(AstBlockType type, Operation branchOp) @@ -178,6 +217,9 @@ namespace Ryujinx.Graphics.Shader.StructuredIr if (branchOp.Inst == Instruction.Branch) { + // If the branch is not conditional, the condition is a constant. + // For if it's false (always jump over, if block never executed). + // For loops it's always true (always loop). cond = Const(type == AstBlockType.If ? IrConsts.False : IrConsts.True); } else @@ -231,6 +273,12 @@ namespace Ryujinx.Graphics.Shader.StructuredIr if (TryGetUserAttributeIndex(operand, out int attrIndex)) { Info.IAttributes.Add(attrIndex); + + Info.InterpolationQualifiers[attrIndex] = operand.Interpolation; + } + else if (operand.Type == OperandType.Attribute && operand.Value == AttributeConsts.InstanceId) + { + Info.UsesInstanceId = true; } else if (operand.Type == OperandType.ConstantBuffer) { diff --git a/Ryujinx.Graphics/Shader/StructuredIr/StructuredProgramInfo.cs b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs similarity index 62% rename from Ryujinx.Graphics/Shader/StructuredIr/StructuredProgramInfo.cs rename to Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs index d368ef0058..0ef4bde340 100644 --- a/Ryujinx.Graphics/Shader/StructuredIr/StructuredProgramInfo.cs +++ b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs @@ -9,11 +9,19 @@ namespace Ryujinx.Graphics.Shader.StructuredIr public HashSet Locals { get; } public HashSet CBuffers { get; } + public HashSet SBuffers { get; } public HashSet IAttributes { get; } public HashSet OAttributes { get; } + public InterpolationQualifier[] InterpolationQualifiers { get; } + + public bool UsesInstanceId { get; set; } + + public HelperFunctionsMask HelperFunctionsMask { get; set; } + public HashSet Samplers { get; } + public HashSet Images { get; } public StructuredProgramInfo(AstBlock mainBlock) { @@ -22,11 +30,15 @@ namespace Ryujinx.Graphics.Shader.StructuredIr Locals = new HashSet(); CBuffers = new HashSet(); + SBuffers = new HashSet(); IAttributes = new HashSet(); OAttributes = new HashSet(); + InterpolationQualifiers = new InterpolationQualifier[32]; + Samplers = new HashSet(); + Images = new HashSet(); } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/StructuredIr/VariableType.cs b/Ryujinx.Graphics.Shader/StructuredIr/VariableType.cs similarity index 100% rename from Ryujinx.Graphics/Shader/StructuredIr/VariableType.cs rename to Ryujinx.Graphics.Shader/StructuredIr/VariableType.cs diff --git a/Ryujinx.Graphics/Shader/TextureDescriptor.cs b/Ryujinx.Graphics.Shader/TextureDescriptor.cs similarity index 65% rename from Ryujinx.Graphics/Shader/TextureDescriptor.cs rename to Ryujinx.Graphics.Shader/TextureDescriptor.cs index 96f0f5b16d..fae9b58c33 100644 --- a/Ryujinx.Graphics/Shader/TextureDescriptor.cs +++ b/Ryujinx.Graphics.Shader/TextureDescriptor.cs @@ -4,6 +4,8 @@ namespace Ryujinx.Graphics.Shader { public string Name { get; } + public SamplerType Type { get; } + public int HandleIndex { get; } public bool IsBindless { get; } @@ -11,10 +13,11 @@ namespace Ryujinx.Graphics.Shader public int CbufSlot { get; } public int CbufOffset { get; } - public TextureDescriptor(string name, int hIndex) + public TextureDescriptor(string name, SamplerType type, int handleIndex) { Name = name; - HandleIndex = hIndex; + Type = type; + HandleIndex = handleIndex; IsBindless = false; @@ -22,9 +25,10 @@ namespace Ryujinx.Graphics.Shader CbufOffset = 0; } - public TextureDescriptor(string name, int cbufSlot, int cbufOffset) + public TextureDescriptor(string name, SamplerType type, int cbufSlot, int cbufOffset) { Name = name; + Type = type; HandleIndex = 0; IsBindless = true; diff --git a/Ryujinx.Graphics/Shader/Translation/AttributeConsts.cs b/Ryujinx.Graphics.Shader/Translation/AttributeConsts.cs similarity index 72% rename from Ryujinx.Graphics/Shader/Translation/AttributeConsts.cs rename to Ryujinx.Graphics.Shader/Translation/AttributeConsts.cs index f21a6252a5..8ff37429ae 100644 --- a/Ryujinx.Graphics/Shader/Translation/AttributeConsts.cs +++ b/Ryujinx.Graphics.Shader/Translation/AttributeConsts.cs @@ -1,4 +1,4 @@ -namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +namespace Ryujinx.Graphics.Shader.Translation { static class AttributeConsts { @@ -34,5 +34,21 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation public const int FragmentOutputDepth = 0x1000000; public const int FragmentOutputColorBase = 0x1000010; public const int FragmentOutputColorEnd = FragmentOutputColorBase + 8 * 16; + + public const int ThreadIdX = 0x2000000; + public const int ThreadIdY = 0x2000004; + public const int ThreadIdZ = 0x2000008; + + public const int CtaIdX = 0x2000010; + public const int CtaIdY = 0x2000014; + public const int CtaIdZ = 0x2000018; + + public const int LaneId = 0x2000020; + + public const int EqMask = 0x2000024; + public const int GeMask = 0x2000028; + public const int GtMask = 0x200002c; + public const int LeMask = 0x2000030; + public const int LtMask = 0x2000034; } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Translation/ControlFlowGraph.cs b/Ryujinx.Graphics.Shader/Translation/ControlFlowGraph.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Translation/ControlFlowGraph.cs rename to Ryujinx.Graphics.Shader/Translation/ControlFlowGraph.cs diff --git a/Ryujinx.Graphics/Shader/Translation/Dominance.cs b/Ryujinx.Graphics.Shader/Translation/Dominance.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Translation/Dominance.cs rename to Ryujinx.Graphics.Shader/Translation/Dominance.cs diff --git a/Ryujinx.Graphics/Shader/Translation/EmitterContext.cs b/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs similarity index 82% rename from Ryujinx.Graphics/Shader/Translation/EmitterContext.cs rename to Ryujinx.Graphics.Shader/Translation/EmitterContext.cs index 6c2bf6e478..fbe197651e 100644 --- a/Ryujinx.Graphics/Shader/Translation/EmitterContext.cs +++ b/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs @@ -1,4 +1,3 @@ -using Ryujinx.Graphics.Gal; using Ryujinx.Graphics.Shader.Decoders; using Ryujinx.Graphics.Shader.IntermediateRepresentation; using System.Collections.Generic; @@ -12,18 +11,17 @@ namespace Ryujinx.Graphics.Shader.Translation public Block CurrBlock { get; set; } public OpCode CurrOp { get; set; } - private GalShaderType _shaderType; + private ShaderConfig _config; - private ShaderHeader _header; + public ShaderConfig Config => _config; private List _operations; private Dictionary _labels; - public EmitterContext(GalShaderType shaderType, ShaderHeader header) + public EmitterContext(ShaderConfig config) { - _shaderType = shaderType; - _header = header; + _config = config; _operations = new List(); @@ -63,13 +61,13 @@ namespace Ryujinx.Graphics.Shader.Translation public void PrepareForReturn() { - if (_shaderType == GalShaderType.Fragment) + if (_config.Stage == ShaderStage.Fragment) { - if (_header.OmapDepth) + if (_config.OmapDepth) { Operand dest = Attribute(AttributeConsts.FragmentOutputDepth); - Operand src = Register(_header.DepthRegister, RegisterType.Gpr); + Operand src = Register(_config.GetDepthRegister(), RegisterType.Gpr); this.Copy(dest, src); } @@ -78,7 +76,7 @@ namespace Ryujinx.Graphics.Shader.Translation for (int attachment = 0; attachment < 8; attachment++) { - OutputMapTarget target = _header.OmapTargets[attachment]; + OutputMapTarget target = _config.OmapTargets[attachment]; for (int component = 0; component < 4; component++) { diff --git a/Ryujinx.Graphics/Shader/Translation/EmitterContextInsts.cs b/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs similarity index 67% rename from Ryujinx.Graphics/Shader/Translation/EmitterContextInsts.cs rename to Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs index 604aa67d34..14675a55d6 100644 --- a/Ryujinx.Graphics/Shader/Translation/EmitterContextInsts.cs +++ b/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs @@ -6,6 +6,71 @@ namespace Ryujinx.Graphics.Shader.Translation { static class EmitterContextInsts { + public static Operand AtomicAdd(this EmitterContext context, Instruction mr, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.AtomicAdd | mr, Local(), a, b, c); + } + + public static Operand AtomicAnd(this EmitterContext context, Instruction mr, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.AtomicAnd | mr, Local(), a, b, c); + } + + public static Operand AtomicCompareAndSwap(this EmitterContext context, Instruction mr, Operand a, Operand b, Operand c, Operand d) + { + return context.Add(Instruction.AtomicCompareAndSwap | mr, Local(), a, b, c, d); + } + + public static Operand AtomicMaxS32(this EmitterContext context, Instruction mr, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.AtomicMaxS32 | mr, Local(), a, b, c); + } + + public static Operand AtomicMaxU32(this EmitterContext context, Instruction mr, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.AtomicMaxU32 | mr, Local(), a, b, c); + } + + public static Operand AtomicMinS32(this EmitterContext context, Instruction mr, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.AtomicMinS32 | mr, Local(), a, b, c); + } + + public static Operand AtomicMinU32(this EmitterContext context, Instruction mr, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.AtomicMinU32 | mr, Local(), a, b, c); + } + + public static Operand AtomicOr(this EmitterContext context, Instruction mr, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.AtomicOr | mr, Local(), a, b, c); + } + + public static Operand AtomicSwap(this EmitterContext context, Instruction mr, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.AtomicSwap | mr, Local(), a, b, c); + } + + public static Operand AtomicXor(this EmitterContext context, Instruction mr, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.AtomicXor | mr, Local(), a, b, c); + } + + public static Operand Ballot(this EmitterContext context, Operand a) + { + return context.Add(Instruction.Ballot, Local(), a); + } + + public static Operand Barrier(this EmitterContext context) + { + return context.Add(Instruction.Barrier); + } + + public static Operand BitCount(this EmitterContext context, Operand a) + { + return context.Add(Instruction.BitCount, Local(), a); + } + public static Operand BitfieldExtractS32(this EmitterContext context, Operand a, Operand b, Operand c) { return context.Add(Instruction.BitfieldExtractS32, Local(), a, b, c); @@ -106,6 +171,16 @@ namespace Ryujinx.Graphics.Shader.Translation return context.Add(Instruction.EndPrimitive); } + public static Operand FindFirstSetS32(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FindFirstSetS32, Local(), a); + } + + public static Operand FindFirstSetU32(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FindFirstSetU32, Local(), a); + } + public static Operand FPAbsNeg(this EmitterContext context, Operand a, bool abs, bool neg) { return context.FPNegate(context.FPAbsolute(a, abs), neg); @@ -151,6 +226,11 @@ namespace Ryujinx.Graphics.Shader.Translation return context.Add(Instruction.ConvertFPToS32, Local(), a); } + public static Operand FPConvertToU32(this EmitterContext context, Operand a) + { + return context.Add(Instruction.ConvertFPToU32, Local(), a); + } + public static Operand FPCosine(this EmitterContext context, Operand a) { return context.Add(Instruction.FP | Instruction.Cosine, Local(), a); @@ -171,6 +251,11 @@ namespace Ryujinx.Graphics.Shader.Translation return context.Add(Instruction.FP | Instruction.Floor, Local(), a); } + public static Operand FPFusedMultiplyAdd(this EmitterContext context, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.FusedMultiplyAdd, Local(), a, b, c); + } + public static Operand FPLogarithmB2(this EmitterContext context, Operand a) { return context.Add(Instruction.FP | Instruction.LogarithmB2, Local(), a); @@ -191,11 +276,6 @@ namespace Ryujinx.Graphics.Shader.Translation return context.Add(Instruction.FP | Instruction.Multiply, Local(), a, b); } - public static Operand FPFusedMultiplyAdd(this EmitterContext context, Operand a, Operand b, Operand c) - { - return context.Add(Instruction.FusedMultiplyAdd, Local(), a, b, c); - } - public static Operand FPNegate(this EmitterContext context, Operand a, bool neg) { if (neg) @@ -221,6 +301,11 @@ namespace Ryujinx.Graphics.Shader.Translation return context.Add(Instruction.FP | Instruction.ReciprocalSquareRoot, Local(), a); } + public static Operand FPRound(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FP | Instruction.Round, Local(), a); + } + public static Operand FPSaturate(this EmitterContext context, Operand a, bool sat) { if (sat) @@ -251,6 +336,16 @@ namespace Ryujinx.Graphics.Shader.Translation return context.Add(Instruction.Truncate, Local(), a); } + public static Operand FPSwizzleAdd(this EmitterContext context, Operand a, Operand b, int mask) + { + return context.Add(Instruction.SwizzleAdd, Local(), a, b, Const(mask)); + } + + public static Operand GroupMemoryBarrier(this EmitterContext context) + { + return context.Add(Instruction.GroupMemoryBarrier); + } + public static Operand IAbsNeg(this EmitterContext context, Operand a, bool abs, bool neg) { return context.INegate(context.IAbsolute(a, abs), neg); @@ -366,11 +461,46 @@ namespace Ryujinx.Graphics.Shader.Translation return context.Add(Instruction.IsNan, Local(), a); } + public static Operand LoadAttribute(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.LoadAttribute, Local(), a, b); + } + public static Operand LoadConstant(this EmitterContext context, Operand a, Operand b) { return context.Add(Instruction.LoadConstant, Local(), a, b); } + public static Operand LoadGlobal(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.LoadGlobal, Local(), a, b); + } + + public static Operand LoadLocal(this EmitterContext context, Operand a) + { + return context.Add(Instruction.LoadLocal, Local(), a); + } + + public static Operand LoadShared(this EmitterContext context, Operand a) + { + return context.Add(Instruction.LoadShared, Local(), a); + } + + public static Operand MemoryBarrier(this EmitterContext context) + { + return context.Add(Instruction.MemoryBarrier); + } + + public static Operand MultiplyHighS32(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.MultiplyHighS32, Local(), a, b); + } + + public static Operand MultiplyHighU32(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.MultiplyHighU32, Local(), a, b); + } + public static Operand PackHalf2x16(this EmitterContext context, Operand a, Operand b) { return context.Add(Instruction.PackHalf2x16, Local(), a, b); @@ -398,6 +528,41 @@ namespace Ryujinx.Graphics.Shader.Translation return context.Add(Instruction.ShiftRightU32, Local(), a, b); } + public static Operand Shuffle(this EmitterContext context, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.Shuffle, Local(), a, b, c); + } + + public static Operand ShuffleDown(this EmitterContext context, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.ShuffleDown, Local(), a, b, c); + } + + public static Operand ShuffleUp(this EmitterContext context, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.ShuffleUp, Local(), a, b, c); + } + + public static Operand ShuffleXor(this EmitterContext context, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.ShuffleXor, Local(), a, b, c); + } + + public static Operand StoreGlobal(this EmitterContext context, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.StoreGlobal, null, a, b, c); + } + + public static Operand StoreLocal(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.StoreLocal, null, a, b); + } + + public static Operand StoreShared(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.StoreShared, null, a, b); + } + public static Operand UnpackHalf2x16High(this EmitterContext context, Operand a) { return UnpackHalf2x16(context, a, 1); @@ -416,5 +581,20 @@ namespace Ryujinx.Graphics.Shader.Translation return dest; } + + public static Operand VoteAll(this EmitterContext context, Operand a) + { + return context.Add(Instruction.VoteAll, Local(), a); + } + + public static Operand VoteAllEqual(this EmitterContext context, Operand a) + { + return context.Add(Instruction.VoteAllEqual, Local(), a); + } + + public static Operand VoteAny(this EmitterContext context, Operand a) + { + return context.Add(Instruction.VoteAny, Local(), a); + } } } \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/GlobalMemory.cs b/Ryujinx.Graphics.Shader/Translation/GlobalMemory.cs new file mode 100644 index 0000000000..a442357dd1 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/GlobalMemory.cs @@ -0,0 +1,46 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; + +namespace Ryujinx.Graphics.Shader.Translation +{ + static class GlobalMemory + { + private const int StorageDescsBaseOffset = 0x44; // In words. + + public const int StorageDescSize = 4; // In words. + public const int StorageMaxCount = 16; + + public const int StorageDescsSize = StorageDescSize * StorageMaxCount; + + public static bool UsesGlobalMemory(Instruction inst) + { + return (inst.IsAtomic() && IsGlobalMr(inst)) || + inst == Instruction.LoadGlobal || + inst == Instruction.StoreGlobal; + } + + private static bool IsGlobalMr(Instruction inst) + { + return (inst & Instruction.MrMask) == Instruction.MrGlobal; + } + + public static int GetStorageCbOffset(ShaderStage stage, int slot) + { + return GetStorageBaseCbOffset(stage) + slot * StorageDescSize; + } + + public static int GetStorageBaseCbOffset(ShaderStage stage) + { + switch (stage) + { + case ShaderStage.Compute: return StorageDescsBaseOffset + 2 * StorageDescsSize; + case ShaderStage.Vertex: return StorageDescsBaseOffset; + case ShaderStage.TessellationControl: return StorageDescsBaseOffset + 1 * StorageDescsSize; + case ShaderStage.TessellationEvaluation: return StorageDescsBaseOffset + 2 * StorageDescsSize; + case ShaderStage.Geometry: return StorageDescsBaseOffset + 3 * StorageDescsSize; + case ShaderStage.Fragment: return StorageDescsBaseOffset + 4 * StorageDescsSize; + } + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/Lowering.cs b/Ryujinx.Graphics.Shader/Translation/Lowering.cs new file mode 100644 index 0000000000..1ee21e0a04 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/Lowering.cs @@ -0,0 +1,423 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; +using System.Diagnostics; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; +using static Ryujinx.Graphics.Shader.Translation.GlobalMemory; + +namespace Ryujinx.Graphics.Shader.Translation +{ + static class Lowering + { + public static void RunPass(BasicBlock[] blocks, ShaderConfig config) + { + for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) + { + BasicBlock block = blocks[blkIndex]; + + for (LinkedListNode node = block.Operations.First; node != null; node = node.Next) + { + if (!(node.Value is Operation operation)) + { + continue; + } + + if (UsesGlobalMemory(operation.Inst)) + { + node = RewriteGlobalAccess(node, config); + } + + if (operation.Inst == Instruction.TextureSample) + { + node = RewriteTextureSample(node, config); + } + } + } + } + + private static LinkedListNode RewriteGlobalAccess(LinkedListNode node, ShaderConfig config) + { + Operation operation = (Operation)node.Value; + + Operation storageOp; + + Operand PrependOperation(Instruction inst, params Operand[] sources) + { + Operand local = Local(); + + node.List.AddBefore(node, new Operation(inst, local, sources)); + + return local; + } + + Operand addrLow = operation.GetSource(0); + Operand addrHigh = operation.GetSource(1); + + Operand sbBaseAddrLow = Const(0); + Operand sbSlot = Const(0); + + for (int slot = 0; slot < StorageMaxCount; slot++) + { + int cbOffset = GetStorageCbOffset(config.Stage, slot); + + Operand baseAddrLow = Cbuf(0, cbOffset); + Operand baseAddrHigh = Cbuf(0, cbOffset + 1); + Operand size = Cbuf(0, cbOffset + 2); + + Operand offset = PrependOperation(Instruction.Subtract, addrLow, baseAddrLow); + Operand borrow = PrependOperation(Instruction.CompareLessU32, addrLow, baseAddrLow); + + Operand inRangeLow = PrependOperation(Instruction.CompareLessU32, offset, size); + + Operand addrHighBorrowed = PrependOperation(Instruction.Add, addrHigh, borrow); + + Operand inRangeHigh = PrependOperation(Instruction.CompareEqual, addrHighBorrowed, baseAddrHigh); + + Operand inRange = PrependOperation(Instruction.BitwiseAnd, inRangeLow, inRangeHigh); + + sbBaseAddrLow = PrependOperation(Instruction.ConditionalSelect, inRange, baseAddrLow, sbBaseAddrLow); + sbSlot = PrependOperation(Instruction.ConditionalSelect, inRange, Const(slot), sbSlot); + } + + Operand alignMask = Const(-config.QueryInfo(QueryInfoName.StorageBufferOffsetAlignment)); + + Operand baseAddrTrunc = PrependOperation(Instruction.BitwiseAnd, sbBaseAddrLow, Const(-64)); + Operand byteOffset = PrependOperation(Instruction.Subtract, addrLow, baseAddrTrunc); + Operand wordOffset = PrependOperation(Instruction.ShiftRightU32, byteOffset, Const(2)); + + Operand[] sources = new Operand[operation.SourcesCount]; + + sources[0] = sbSlot; + sources[1] = wordOffset; + + for (int index = 2; index < operation.SourcesCount; index++) + { + sources[index] = operation.GetSource(index); + } + + if (operation.Inst.IsAtomic()) + { + Instruction inst = (operation.Inst & ~Instruction.MrMask) | Instruction.MrStorage; + + storageOp = new Operation(inst, operation.Dest, sources); + } + else if (operation.Inst == Instruction.LoadGlobal) + { + storageOp = new Operation(Instruction.LoadStorage, operation.Dest, sources); + } + else + { + storageOp = new Operation(Instruction.StoreStorage, null, sources); + } + + for (int index = 0; index < operation.SourcesCount; index++) + { + operation.SetSource(index, null); + } + + LinkedListNode oldNode = node; + + node = node.List.AddBefore(node, storageOp); + + node.List.Remove(oldNode); + + return node; + } + + private static LinkedListNode RewriteTextureSample(LinkedListNode node, ShaderConfig config) + { + TextureOperation texOp = (TextureOperation)node.Value; + + bool hasOffset = (texOp.Flags & TextureFlags.Offset) != 0; + bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0; + + bool hasInvalidOffset = (hasOffset || hasOffsets) && !config.QueryInfoBool(QueryInfoName.SupportsNonConstantTextureOffset); + + bool isRect = config.QueryInfoBool(QueryInfoName.IsTextureRectangle, texOp.Handle); + + if (!(hasInvalidOffset || isRect)) + { + return node; + } + + bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; + bool isGather = (texOp.Flags & TextureFlags.Gather) != 0; + bool hasDerivatives = (texOp.Flags & TextureFlags.Derivatives) != 0; + bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0; + bool hasLodBias = (texOp.Flags & TextureFlags.LodBias) != 0; + bool hasLodLevel = (texOp.Flags & TextureFlags.LodLevel) != 0; + + bool isArray = (texOp.Type & SamplerType.Array) != 0; + bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; + bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0; + bool isShadow = (texOp.Type & SamplerType.Shadow) != 0; + + int coordsCount = texOp.Type.GetDimensions(); + + int offsetsCount; + + if (hasOffsets) + { + offsetsCount = coordsCount * 4; + } + else if (hasOffset) + { + offsetsCount = coordsCount; + } + else + { + offsetsCount = 0; + } + + Operand[] offsets = new Operand[offsetsCount]; + Operand[] sources = new Operand[texOp.SourcesCount - offsetsCount]; + + int copyCount = 0; + + if (isBindless || isIndexed) + { + copyCount++; + } + + Operand[] lodSources = new Operand[copyCount + coordsCount]; + + for (int index = 0; index < lodSources.Length; index++) + { + lodSources[index] = texOp.GetSource(index); + } + + copyCount += coordsCount; + + if (isArray) + { + copyCount++; + } + + if (isShadow) + { + copyCount++; + } + + if (hasDerivatives) + { + copyCount += coordsCount * 2; + } + + if (isMultisample) + { + copyCount++; + } + else if (hasLodLevel) + { + copyCount++; + } + + int srcIndex = 0; + int dstIndex = 0; + + for (int index = 0; index < copyCount; index++) + { + sources[dstIndex++] = texOp.GetSource(srcIndex++); + } + + bool areAllOffsetsConstant = true; + + for (int index = 0; index < offsetsCount; index++) + { + Operand offset = texOp.GetSource(srcIndex++); + + areAllOffsetsConstant &= offset.Type == OperandType.Constant; + + offsets[index] = offset; + } + + hasInvalidOffset &= !areAllOffsetsConstant; + + if (!(hasInvalidOffset || isRect)) + { + return node; + } + + if (hasLodBias) + { + sources[dstIndex++] = texOp.GetSource(srcIndex++); + } + + if (isGather && !isShadow) + { + sources[dstIndex++] = texOp.GetSource(srcIndex++); + } + + int coordsIndex = isBindless || isIndexed ? 1 : 0; + + int componentIndex = texOp.Index; + + Operand Int(Operand value) + { + Operand res = Local(); + + node.List.AddBefore(node, new Operation(Instruction.ConvertFPToS32, res, value)); + + return res; + } + + Operand Float(Operand value) + { + Operand res = Local(); + + node.List.AddBefore(node, new Operation(Instruction.ConvertS32ToFP, res, value)); + + return res; + } + + // Emulate texture rectangle by normalizing the coordinates on the shader. + // When sampler*Rect is used, the coords are expected to the in the [0, W or H] range, + // and otherwise, it is expected to be in the [0, 1] range. + // We normalize by dividing the coords by the texture size. + if (isRect && !intCoords) + { + for (int index = 0; index < coordsCount; index++) + { + Operand coordSize = Local(); + + Operand[] texSizeSources; + + if (isBindless || isIndexed) + { + texSizeSources = new Operand[] { sources[0], Const(0) }; + } + else + { + texSizeSources = new Operand[] { Const(0) }; + } + + node.List.AddBefore(node, new TextureOperation( + Instruction.TextureSize, + texOp.Type, + texOp.Flags, + texOp.Handle, + index, + coordSize, + texSizeSources)); + + Operand source = sources[coordsIndex + index]; + + Operand coordNormalized = Local(); + + node.List.AddBefore(node, new Operation(Instruction.FP | Instruction.Divide, coordNormalized, source, Float(coordSize))); + + sources[coordsIndex + index] = coordNormalized; + } + } + + // Technically, non-constant texture offsets are not allowed (according to the spec), + // however some GPUs does support that. + // For GPUs where it is not supported, we can replace the instruction with the following: + // For texture*Offset, we replace it by texture*, and add the offset to the P coords. + // The offset can be calculated as offset / textureSize(lod), where lod = textureQueryLod(coords). + // For texelFetchOffset, we replace it by texelFetch and add the offset to the P coords directly. + // For textureGatherOffset, we take advantage of the fact that the operation is already broken down + // to read the 4 pixels separately, and just replace it with 4 textureGather with a different offset + // for each pixel. + if (hasInvalidOffset) + { + if (intCoords) + { + for (int index = 0; index < coordsCount; index++) + { + Operand source = sources[coordsIndex + index]; + + Operand coordPlusOffset = Local(); + + node.List.AddBefore(node, new Operation(Instruction.Add, coordPlusOffset, source, offsets[index])); + + sources[coordsIndex + index] = coordPlusOffset; + } + } + else + { + Operand lod = Local(); + + node.List.AddBefore(node, new TextureOperation( + Instruction.Lod, + texOp.Type, + texOp.Flags, + texOp.Handle, + 1, + lod, + lodSources)); + + for (int index = 0; index < coordsCount; index++) + { + Operand coordSize = Local(); + + Operand[] texSizeSources; + + if (isBindless || isIndexed) + { + texSizeSources = new Operand[] { sources[0], Int(lod) }; + } + else + { + texSizeSources = new Operand[] { Int(lod) }; + } + + node.List.AddBefore(node, new TextureOperation( + Instruction.TextureSize, + texOp.Type, + texOp.Flags, + texOp.Handle, + index, + coordSize, + texSizeSources)); + + Operand offset = Local(); + + Operand intOffset = offsets[index + (hasOffsets ? texOp.Index * coordsCount : 0)]; + + node.List.AddBefore(node, new Operation(Instruction.FP | Instruction.Divide, offset, Float(intOffset), Float(coordSize))); + + Operand source = sources[coordsIndex + index]; + + Operand coordPlusOffset = Local(); + + node.List.AddBefore(node, new Operation(Instruction.FP | Instruction.Add, coordPlusOffset, source, offset)); + + sources[coordsIndex + index] = coordPlusOffset; + } + } + + if (isGather && !isShadow) + { + Operand gatherComponent = sources[dstIndex - 1]; + + Debug.Assert(gatherComponent.Type == OperandType.Constant); + + componentIndex = gatherComponent.Value; + } + } + + TextureOperation newTexOp = new TextureOperation( + Instruction.TextureSample, + texOp.Type, + texOp.Flags & ~(TextureFlags.Offset | TextureFlags.Offsets), + texOp.Handle, + componentIndex, + texOp.Dest, + sources); + + for (int index = 0; index < texOp.SourcesCount; index++) + { + texOp.SetSource(index, null); + } + + LinkedListNode oldNode = node; + + node = node.List.AddBefore(node, newTexOp); + + node.List.Remove(oldNode); + + return node; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToIndexed.cs b/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToIndexed.cs new file mode 100644 index 0000000000..83f8fe9a7b --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToIndexed.cs @@ -0,0 +1,74 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation.Optimizations +{ + static class BindlessToIndexed + { + public static void RunPass(BasicBlock block) + { + // We can turn a bindless texture access into a indexed access, + // as long the following conditions are true: + // - The handle is loaded using a LDC instruction. + // - The handle is loaded from the constant buffer with the handles (CB2 for NVN). + // - The load has a constant offset. + // The base offset of the array of handles on the constant buffer is the constant offset. + for (LinkedListNode node = block.Operations.First; node != null; node = node.Next) + { + if (!(node.Value is TextureOperation texOp)) + { + continue; + } + + if ((texOp.Flags & TextureFlags.Bindless) == 0) + { + continue; + } + + if (!(texOp.GetSource(0).AsgOp is Operation handleAsgOp)) + { + continue; + } + + if (handleAsgOp.Inst != Instruction.LoadConstant) + { + continue; + } + + Operand ldcSrc0 = handleAsgOp.GetSource(0); + Operand ldcSrc1 = handleAsgOp.GetSource(1); + + if (ldcSrc0.Type != OperandType.Constant || ldcSrc0.Value != 2) + { + continue; + } + + if (!(ldcSrc1.AsgOp is Operation addOp)) + { + continue; + } + + Operand addSrc1 = addOp.GetSource(1); + + if (addSrc1.Type != OperandType.Constant) + { + continue; + } + + texOp.TurnIntoIndexed(addSrc1.Value); + + Operand index = Local(); + + Operand source = addOp.GetSource(0); + + Operation shrBy1 = new Operation(Instruction.ShiftRightU32, index, source, Const(1)); + + block.Operations.AddBefore(node, shrBy1); + + texOp.SetSource(0, index); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Translation/Optimizations/BranchElimination.cs b/Ryujinx.Graphics.Shader/Translation/Optimizations/BranchElimination.cs similarity index 97% rename from Ryujinx.Graphics/Shader/Translation/Optimizations/BranchElimination.cs rename to Ryujinx.Graphics.Shader/Translation/Optimizations/BranchElimination.cs index 070f07a473..c87d147484 100644 --- a/Ryujinx.Graphics/Shader/Translation/Optimizations/BranchElimination.cs +++ b/Ryujinx.Graphics.Shader/Translation/Optimizations/BranchElimination.cs @@ -5,7 +5,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations { static class BranchElimination { - public static bool Eliminate(BasicBlock block) + public static bool RunPass(BasicBlock block) { if (block.HasBranch && IsRedundantBranch((Operation)block.GetLastOp(), Next(block))) { diff --git a/Ryujinx.Graphics/Shader/Translation/Optimizations/ConstantFolding.cs b/Ryujinx.Graphics.Shader/Translation/Optimizations/ConstantFolding.cs similarity index 95% rename from Ryujinx.Graphics/Shader/Translation/Optimizations/ConstantFolding.cs rename to Ryujinx.Graphics.Shader/Translation/Optimizations/ConstantFolding.cs index a2e05ef120..b69589294d 100644 --- a/Ryujinx.Graphics/Shader/Translation/Optimizations/ConstantFolding.cs +++ b/Ryujinx.Graphics.Shader/Translation/Optimizations/ConstantFolding.cs @@ -8,7 +8,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations { static class ConstantFolding { - public static void Fold(Operation operation) + public static void RunPass(Operation operation) { if (!AreAllSourcesConstant(operation)) { @@ -21,6 +21,10 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations EvaluateBinary(operation, (x, y) => x + y); break; + case Instruction.BitCount: + EvaluateUnary(operation, (x) => BitCount(x)); + break; + case Instruction.BitwiseAnd: EvaluateBinary(operation, (x, y) => x & y); break; @@ -208,6 +212,21 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations return true; } + private static int BitCount(int value) + { + int count = 0; + + for (int bit = 0; bit < 32; bit++) + { + if (value.Extract(bit)) + { + count++; + } + } + + return count; + } + private static void BitfieldExtractS32(Operation operation) { int value = GetBitfieldExtractValue(operation); @@ -237,7 +256,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations { int value = operation.GetSource(0).Value; - value = (value >> operation.ComponentIndex * 16) & 0xffff; + value = (value >> operation.Index * 16) & 0xffff; operation.TurnIntoCopy(ConstF(HalfConversion.HalfToSingle(value))); } diff --git a/Ryujinx.Graphics.Shader/Translation/Optimizations/GlobalToStorage.cs b/Ryujinx.Graphics.Shader/Translation/Optimizations/GlobalToStorage.cs new file mode 100644 index 0000000000..8efd2c5209 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/Optimizations/GlobalToStorage.cs @@ -0,0 +1,147 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; +using static Ryujinx.Graphics.Shader.Translation.GlobalMemory; + +namespace Ryujinx.Graphics.Shader.Translation.Optimizations +{ + static class GlobalToStorage + { + public static void RunPass(BasicBlock block, ShaderConfig config) + { + int sbStart = GetStorageBaseCbOffset(config.Stage); + + int sbEnd = sbStart + StorageDescsSize; + + for (LinkedListNode node = block.Operations.First; node != null; node = node.Next) + { + if (!(node.Value is Operation operation)) + { + continue; + } + + if (UsesGlobalMemory(operation.Inst)) + { + Operand source = operation.GetSource(0); + + if (source.AsgOp is Operation asgOperation) + { + int storageIndex = SearchForStorageBase(asgOperation, sbStart, sbEnd); + + if (storageIndex >= 0) + { + node = ReplaceGlobalWithStorage(node, config, storageIndex); + } + } + } + } + } + + private static LinkedListNode ReplaceGlobalWithStorage(LinkedListNode node, ShaderConfig config, int storageIndex) + { + Operation operation = (Operation)node.Value; + + Operation storageOp; + + Operand GetStorageOffset() + { + Operand addrLow = operation.GetSource(0); + + Operand baseAddrLow = Cbuf(0, GetStorageCbOffset(config.Stage, storageIndex)); + + Operand baseAddrTrunc = Local(); + + Operand alignMask = Const(-config.QueryInfo(QueryInfoName.StorageBufferOffsetAlignment)); + + Operation andOp = new Operation(Instruction.BitwiseAnd, baseAddrTrunc, baseAddrLow, alignMask); + + node.List.AddBefore(node, andOp); + + Operand byteOffset = Local(); + Operand wordOffset = Local(); + + Operation subOp = new Operation(Instruction.Subtract, byteOffset, addrLow, baseAddrTrunc); + Operation shrOp = new Operation(Instruction.ShiftRightU32, wordOffset, byteOffset, Const(2)); + + node.List.AddBefore(node, subOp); + node.List.AddBefore(node, shrOp); + + return wordOffset; + } + + Operand[] sources = new Operand[operation.SourcesCount]; + + sources[0] = Const(storageIndex); + sources[1] = GetStorageOffset(); + + for (int index = 2; index < operation.SourcesCount; index++) + { + sources[index] = operation.GetSource(index); + } + + if (operation.Inst.IsAtomic()) + { + Instruction inst = (operation.Inst & ~Instruction.MrMask) | Instruction.MrStorage; + + storageOp = new Operation(inst, operation.Dest, sources); + } + else if (operation.Inst == Instruction.LoadGlobal) + { + storageOp = new Operation(Instruction.LoadStorage, operation.Dest, sources); + } + else + { + storageOp = new Operation(Instruction.StoreStorage, null, sources); + } + + for (int index = 0; index < operation.SourcesCount; index++) + { + operation.SetSource(index, null); + } + + LinkedListNode oldNode = node; + + node = node.List.AddBefore(node, storageOp); + + node.List.Remove(oldNode); + + return node; + } + + private static int SearchForStorageBase(Operation operation, int sbStart, int sbEnd) + { + Queue assignments = new Queue(); + + assignments.Enqueue(operation); + + while (assignments.TryDequeue(out operation)) + { + for (int index = 0; index < operation.SourcesCount; index++) + { + Operand source = operation.GetSource(index); + + if (source.Type == OperandType.ConstantBuffer) + { + int slot = source.GetCbufSlot(); + int offset = source.GetCbufOffset(); + + if (slot == 0 && offset >= sbStart && offset < sbEnd) + { + int storageIndex = (offset - sbStart) / StorageDescSize; + + return storageIndex; + } + } + + if (source.AsgOp is Operation asgOperation) + { + assignments.Enqueue(asgOperation); + } + } + } + + return -1; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Translation/Optimizations/HalfConversion.cs b/Ryujinx.Graphics.Shader/Translation/Optimizations/HalfConversion.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Translation/Optimizations/HalfConversion.cs rename to Ryujinx.Graphics.Shader/Translation/Optimizations/HalfConversion.cs diff --git a/Ryujinx.Graphics/Shader/Translation/Optimizations/Optimizer.cs b/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs similarity index 53% rename from Ryujinx.Graphics/Shader/Translation/Optimizations/Optimizer.cs rename to Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs index 8cce0e74ee..c5db4678b7 100644 --- a/Ryujinx.Graphics/Shader/Translation/Optimizations/Optimizer.cs +++ b/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs @@ -1,13 +1,19 @@ using Ryujinx.Graphics.Shader.IntermediateRepresentation; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; namespace Ryujinx.Graphics.Shader.Translation.Optimizations { static class Optimizer { - public static void Optimize(BasicBlock[] blocks) + public static void RunPass(BasicBlock[] blocks, ShaderConfig config) { + for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) + { + GlobalToStorage.RunPass(blocks[blkIndex], config); + } + bool modified; do @@ -40,9 +46,9 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations continue; } - ConstantFolding.Fold(operation); + ConstantFolding.RunPass(operation); - Simplification.Simplify(operation); + Simplification.RunPass(operation); if (DestIsLocalVar(operation)) { @@ -54,7 +60,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations modified = true; } - else if (operation.Inst == Instruction.PackHalf2x16 && PropagatePack(operation)) + else if ((operation.Inst == Instruction.PackHalf2x16 && PropagatePack(operation)) || + (operation.Inst == Instruction.ShuffleXor && MatchDdxOrDdy(operation))) { if (operation.Dest.UseOps.Count == 0) { @@ -68,7 +75,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations node = nextNode; } - if (BranchElimination.Eliminate(block)) + if (BranchElimination.RunPass(block)) { RemoveNode(block, block.Operations.Last); @@ -77,6 +84,11 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations } } while (modified); + + for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) + { + BindlessToIndexed.RunPass(blocks[blkIndex]); + } } private static void PropagateCopy(Operation copyOp) @@ -121,7 +133,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations if (operation.GetSource(0) == dest) { - operation.TurnIntoCopy(operation.ComponentIndex == 1 ? src1 : src0); + operation.TurnIntoCopy(operation.Index == 1 ? src1 : src0); modified = true; } @@ -130,6 +142,84 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations return modified; } + public static bool MatchDdxOrDdy(Operation operation) + { + // It's assumed that "operation.Inst" is ShuffleXor, + // that should be checked before calling this method. + Debug.Assert(operation.Inst == Instruction.ShuffleXor); + + bool modified = false; + + Operand src2 = operation.GetSource(1); + Operand src3 = operation.GetSource(2); + + if (src2.Type != OperandType.Constant || (src2.Value != 1 && src2.Value != 2)) + { + return false; + } + + if (src3.Type != OperandType.Constant || src3.Value != 0x1c03) + { + return false; + } + + bool isDdy = src2.Value == 2; + bool isDdx = !isDdy; + + // We can replace any use by a FSWZADD with DDX/DDY, when + // the following conditions are true: + // - The mask should be 0b10100101 for DDY, or 0b10011001 for DDX. + // - The first source operand must be the shuffle output. + // - The second source operand must be the shuffle first source operand. + INode[] uses = operation.Dest.UseOps.ToArray(); + + foreach (INode use in uses) + { + if (!(use is Operation test)) + { + continue; + } + + if (!(use is Operation useOp) || useOp.Inst != Instruction.SwizzleAdd) + { + continue; + } + + Operand fswzaddSrc1 = useOp.GetSource(0); + Operand fswzaddSrc2 = useOp.GetSource(1); + Operand fswzaddSrc3 = useOp.GetSource(2); + + if (fswzaddSrc1 != operation.Dest) + { + continue; + } + + if (fswzaddSrc2 != operation.GetSource(0)) + { + continue; + } + + if (fswzaddSrc3.Type != OperandType.Constant) + { + continue; + } + + int mask = fswzaddSrc3.Value; + + if ((isDdx && mask != 0b10011001) || + (isDdy && mask != 0b10100101)) + { + continue; + } + + useOp.TurnInto(isDdx ? Instruction.Ddx : Instruction.Ddy, fswzaddSrc2); + + modified = true; + } + + return modified; + } + private static void RemoveNode(BasicBlock block, LinkedListNode llNode) { // Remove a node from the nodes list, and also remove itself @@ -161,7 +251,30 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations private static bool IsUnused(INode node) { - return DestIsLocalVar(node) && node.Dest.UseOps.Count == 0; + return !HasSideEffects(node) && DestIsLocalVar(node) && node.Dest.UseOps.Count == 0; + } + + private static bool HasSideEffects(INode node) + { + if (node is Operation operation) + { + switch (operation.Inst & Instruction.Mask) + { + case Instruction.AtomicAdd: + case Instruction.AtomicAnd: + case Instruction.AtomicCompareAndSwap: + case Instruction.AtomicMaxS32: + case Instruction.AtomicMaxU32: + case Instruction.AtomicMinS32: + case Instruction.AtomicMinU32: + case Instruction.AtomicOr: + case Instruction.AtomicSwap: + case Instruction.AtomicXor: + return true; + } + } + + return false; } private static bool DestIsLocalVar(INode node) diff --git a/Ryujinx.Graphics/Shader/Translation/Optimizations/Simplification.cs b/Ryujinx.Graphics.Shader/Translation/Optimizations/Simplification.cs similarity index 98% rename from Ryujinx.Graphics/Shader/Translation/Optimizations/Simplification.cs rename to Ryujinx.Graphics.Shader/Translation/Optimizations/Simplification.cs index d6366dfe07..8d05f99afa 100644 --- a/Ryujinx.Graphics/Shader/Translation/Optimizations/Simplification.cs +++ b/Ryujinx.Graphics.Shader/Translation/Optimizations/Simplification.cs @@ -8,7 +8,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations { private const int AllOnes = ~0; - public static void Simplify(Operation operation) + public static void RunPass(Operation operation) { switch (operation.Inst) { diff --git a/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs b/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs new file mode 100644 index 0000000000..8a0f25fe45 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs @@ -0,0 +1,106 @@ +using System; + +namespace Ryujinx.Graphics.Shader.Translation +{ + struct ShaderConfig + { + public ShaderStage Stage { get; } + + public OutputTopology OutputTopology { get; } + + public int MaxOutputVertices { get; } + + public OutputMapTarget[] OmapTargets { get; } + public bool OmapSampleMask { get; } + public bool OmapDepth { get; } + + public TranslationFlags Flags { get; } + + private TranslatorCallbacks _callbacks; + + public ShaderConfig(TranslationFlags flags, TranslatorCallbacks callbacks) + { + Stage = ShaderStage.Compute; + OutputTopology = OutputTopology.PointList; + MaxOutputVertices = 0; + OmapTargets = null; + OmapSampleMask = false; + OmapDepth = false; + Flags = flags; + _callbacks = callbacks; + } + + public ShaderConfig(ShaderHeader header, TranslationFlags flags, TranslatorCallbacks callbacks) + { + Stage = header.Stage; + OutputTopology = header.OutputTopology; + MaxOutputVertices = header.MaxOutputVertexCount; + OmapTargets = header.OmapTargets; + OmapSampleMask = header.OmapSampleMask; + OmapDepth = header.OmapDepth; + Flags = flags; + _callbacks = callbacks; + } + + public int GetDepthRegister() + { + int count = 0; + + for (int index = 0; index < OmapTargets.Length; index++) + { + for (int component = 0; component < 4; component++) + { + if (OmapTargets[index].ComponentEnabled(component)) + { + count++; + } + } + } + + // The depth register is always two registers after the last color output. + return count + 1; + } + + public bool QueryInfoBool(QueryInfoName info, int index = 0) + { + return Convert.ToBoolean(QueryInfo(info, index)); + } + + public int QueryInfo(QueryInfoName info, int index = 0) + { + if (_callbacks.QueryInfo != null) + { + return _callbacks.QueryInfo(info, index); + } + else + { + switch (info) + { + case QueryInfoName.ComputeLocalSizeX: + case QueryInfoName.ComputeLocalSizeY: + case QueryInfoName.ComputeLocalSizeZ: + return 1; + case QueryInfoName.ComputeSharedMemorySize: + return 0xc000; + case QueryInfoName.IsTextureBuffer: + return Convert.ToInt32(false); + case QueryInfoName.IsTextureRectangle: + return Convert.ToInt32(false); + case QueryInfoName.PrimitiveTopology: + return (int)InputTopology.Points; + case QueryInfoName.StorageBufferOffsetAlignment: + return 16; + case QueryInfoName.SupportsNonConstantTextureOffset: + return Convert.ToInt32(true); + } + } + + return 0; + } + + public void PrintLog(string message) + { + _callbacks.PrintLog?.Invoke(message); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/ShaderHeader.cs b/Ryujinx.Graphics.Shader/Translation/ShaderHeader.cs similarity index 68% rename from Ryujinx.Graphics/Shader/ShaderHeader.cs rename to Ryujinx.Graphics.Shader/Translation/ShaderHeader.cs index 379f3f35dd..0c56132d95 100644 --- a/Ryujinx.Graphics/Shader/ShaderHeader.cs +++ b/Ryujinx.Graphics.Shader/Translation/ShaderHeader.cs @@ -1,8 +1,8 @@ -using Ryujinx.Graphics.Gal; using Ryujinx.Graphics.Shader.Decoders; using System; +using System.Runtime.InteropServices; -namespace Ryujinx.Graphics.Shader +namespace Ryujinx.Graphics.Shader.Translation { struct OutputMapTarget { @@ -38,10 +38,9 @@ namespace Ryujinx.Graphics.Shader class ShaderHeader { public int SphType { get; } - public int Version { get; } - public int ShaderType { get; } + public ShaderStage Stage { get; } public bool MrtEnable { get; } @@ -52,10 +51,9 @@ namespace Ryujinx.Graphics.Shader public int SassVersion { get; } public bool DoesLoadOrStore { get; } + public bool DoesFp64 { get; } - public bool DoesFp64 { get; } - - public int StreamOutMask{ get; } + public int StreamOutMask { get; } public int ShaderLocalMemoryLowSize { get; } @@ -67,7 +65,7 @@ namespace Ryujinx.Graphics.Shader public int ShaderLocalMemoryCrsSize { get; } - public int OutputTopology { get; } + public OutputTopology OutputTopology { get; } public int MaxOutputVertexCount { get; } @@ -78,19 +76,26 @@ namespace Ryujinx.Graphics.Shader public bool OmapSampleMask { get; } public bool OmapDepth { get; } - public ShaderHeader(IGalMemory memory, ulong address) + public ShaderHeader(Span code) { - int commonWord0 = memory.ReadInt32((long)address + 0); - int commonWord1 = memory.ReadInt32((long)address + 4); - int commonWord2 = memory.ReadInt32((long)address + 8); - int commonWord3 = memory.ReadInt32((long)address + 12); - int commonWord4 = memory.ReadInt32((long)address + 16); + Span header = MemoryMarshal.Cast(code); + + int commonWord0 = header[0]; + int commonWord1 = header[1]; + int commonWord2 = header[2]; + int commonWord3 = header[3]; + int commonWord4 = header[4]; SphType = commonWord0.Extract(0, 5); - Version = commonWord0.Extract(5, 5); - ShaderType = commonWord0.Extract(10, 4); + Stage = (ShaderStage)commonWord0.Extract(10, 4); + + // Invalid. + if (Stage == ShaderStage.Compute) + { + Stage = ShaderStage.Vertex; + } MrtEnable = commonWord0.Extract(14); @@ -101,8 +106,7 @@ namespace Ryujinx.Graphics.Shader SassVersion = commonWord0.Extract(17, 4); DoesLoadOrStore = commonWord0.Extract(26); - - DoesFp64 = commonWord0.Extract(27); + DoesFp64 = commonWord0.Extract(27); StreamOutMask = commonWord0.Extract(28, 4); @@ -116,15 +120,15 @@ namespace Ryujinx.Graphics.Shader ShaderLocalMemoryCrsSize = commonWord3.Extract(0, 24); - OutputTopology = commonWord3.Extract(24, 4); + OutputTopology = (OutputTopology)commonWord3.Extract(24, 4); MaxOutputVertexCount = commonWord4.Extract(0, 12); StoreReqStart = commonWord4.Extract(12, 8); StoreReqEnd = commonWord4.Extract(24, 8); - int type2OmapTarget = memory.ReadInt32((long)address + 72); - int type2Omap = memory.ReadInt32((long)address + 76); + int type2OmapTarget = header[18]; + int type2Omap = header[19]; OmapTargets = new OutputMapTarget[8]; @@ -140,27 +144,5 @@ namespace Ryujinx.Graphics.Shader OmapSampleMask = type2Omap.Extract(0); OmapDepth = type2Omap.Extract(1); } - - public int DepthRegister - { - get - { - int count = 0; - - for (int index = 0; index < OmapTargets.Length; index++) - { - for (int component = 0; component < 4; component++) - { - if (OmapTargets[index].ComponentEnabled(component)) - { - count++; - } - } - } - - // Depth register is always two registers after the last color output. - return count + 1; - } - } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Translation/Ssa.cs b/Ryujinx.Graphics.Shader/Translation/Ssa.cs similarity index 100% rename from Ryujinx.Graphics/Shader/Translation/Ssa.cs rename to Ryujinx.Graphics.Shader/Translation/Ssa.cs diff --git a/Ryujinx.Graphics.Shader/Translation/TranslationFlags.cs b/Ryujinx.Graphics.Shader/Translation/TranslationFlags.cs new file mode 100644 index 0000000000..b871574627 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/TranslationFlags.cs @@ -0,0 +1,13 @@ +using System; + +namespace Ryujinx.Graphics.Shader.Translation +{ + [Flags] + public enum TranslationFlags + { + None = 0, + + Compute = 1 << 0, + DebugMode = 1 << 1 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Translation/Translator.cs b/Ryujinx.Graphics.Shader/Translation/Translator.cs similarity index 55% rename from Ryujinx.Graphics/Shader/Translation/Translator.cs rename to Ryujinx.Graphics.Shader/Translation/Translator.cs index fcebe913e6..760d616f63 100644 --- a/Ryujinx.Graphics/Shader/Translation/Translator.cs +++ b/Ryujinx.Graphics.Shader/Translation/Translator.cs @@ -1,10 +1,9 @@ -using Ryujinx.Graphics.Gal; using Ryujinx.Graphics.Shader.CodeGen.Glsl; using Ryujinx.Graphics.Shader.Decoders; -using Ryujinx.Graphics.Shader.Instructions; using Ryujinx.Graphics.Shader.IntermediateRepresentation; using Ryujinx.Graphics.Shader.StructuredIr; using Ryujinx.Graphics.Shader.Translation.Optimizations; +using System; using System.Collections.Generic; using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; @@ -13,60 +12,125 @@ namespace Ryujinx.Graphics.Shader.Translation { public static class Translator { - public static ShaderProgram Translate(IGalMemory memory, ulong address, ShaderConfig config) - { - return Translate(memory, address, 0, config); - } + private const int HeaderSize = 0x50; - public static ShaderProgram Translate( - IGalMemory memory, - ulong address, - ulong addressB, - ShaderConfig config) + public static Span ExtractCode(Span code, bool compute, out int headerSize) { - Operation[] shaderOps = DecodeShader(memory, address, config.Type); + headerSize = compute ? 0 : HeaderSize; - if (addressB != 0) + Block[] cfg = Decoder.Decode(code, (ulong)headerSize); + + if (cfg == null) { - // Dual vertex shader. - Operation[] shaderOpsB = DecodeShader(memory, addressB, config.Type); - - shaderOps = Combine(shaderOps, shaderOpsB); + return code; } - BasicBlock[] irBlocks = ControlFlowGraph.MakeCfg(shaderOps); + ulong endAddress = 0; - Dominance.FindDominators(irBlocks[0], irBlocks.Length); + foreach (Block block in cfg) + { + if (endAddress < block.EndAddress) + { + endAddress = block.EndAddress; + } + } - Dominance.FindDominanceFrontiers(irBlocks); + return code.Slice(0, headerSize + (int)endAddress); + } - Ssa.Rename(irBlocks); + public static ShaderProgram Translate(Span code, TranslatorCallbacks callbacks, TranslationFlags flags) + { + Operation[] ops = DecodeShader(code, callbacks, flags, out ShaderConfig config, out int size); - Optimizer.Optimize(irBlocks); + return Translate(ops, config, size); + } - StructuredProgramInfo sInfo = StructuredProgram.MakeStructuredProgram(irBlocks); + public static ShaderProgram Translate(Span vpACode, Span vpBCode, TranslatorCallbacks callbacks, TranslationFlags flags) + { + Operation[] vpAOps = DecodeShader(vpACode, callbacks, flags, out _, out _); + Operation[] vpBOps = DecodeShader(vpBCode, callbacks, flags, out ShaderConfig config, out int sizeB); + + return Translate(Combine(vpAOps, vpBOps), config, sizeB); + } + + private static ShaderProgram Translate(Operation[] ops, ShaderConfig config, int size) + { + BasicBlock[] blocks = ControlFlowGraph.MakeCfg(ops); + + if (blocks.Length > 0) + { + Dominance.FindDominators(blocks[0], blocks.Length); + + Dominance.FindDominanceFrontiers(blocks); + + Ssa.Rename(blocks); + + Optimizer.RunPass(blocks, config); + + Lowering.RunPass(blocks, config); + } + + StructuredProgramInfo sInfo = StructuredProgram.MakeStructuredProgram(blocks, config); GlslProgram program = GlslGenerator.Generate(sInfo, config); ShaderProgramInfo spInfo = new ShaderProgramInfo( program.CBufferDescriptors, - program.TextureDescriptors); + program.SBufferDescriptors, + program.TextureDescriptors, + program.ImageDescriptors, + sInfo.InterpolationQualifiers, + sInfo.UsesInstanceId); - return new ShaderProgram(spInfo, program.Code); + string glslCode = program.Code; + + return new ShaderProgram(spInfo, config.Stage, glslCode, size); } - private static Operation[] DecodeShader(IGalMemory memory, ulong address, GalShaderType shaderType) + private static Operation[] DecodeShader( + Span code, + TranslatorCallbacks callbacks, + TranslationFlags flags, + out ShaderConfig config, + out int size) { - ShaderHeader header = new ShaderHeader(memory, address); + Block[] cfg; - Block[] cfg = Decoder.Decode(memory, address); + if ((flags & TranslationFlags.Compute) != 0) + { + config = new ShaderConfig(flags, callbacks); - EmitterContext context = new EmitterContext(shaderType, header); + cfg = Decoder.Decode(code, 0); + } + else + { + config = new ShaderConfig(new ShaderHeader(code), flags, callbacks); + + cfg = Decoder.Decode(code, HeaderSize); + } + + if (cfg == null) + { + config.PrintLog("Invalid branch detected, failed to build CFG."); + + size = 0; + + return Array.Empty(); + } + + EmitterContext context = new EmitterContext(config); + + ulong maxEndAddress = 0; for (int blkIndex = 0; blkIndex < cfg.Length; blkIndex++) { Block block = cfg[blkIndex]; + if (maxEndAddress < block.EndAddress) + { + maxEndAddress = block.EndAddress; + } + context.CurrBlock = block; context.MarkLabel(context.GetLabel(block.Address)); @@ -75,6 +139,26 @@ namespace Ryujinx.Graphics.Shader.Translation { OpCode op = block.OpCodes[opIndex]; + if ((flags & TranslationFlags.DebugMode) != 0) + { + string instName; + + if (op.Emitter != null) + { + instName = op.Emitter.Method.Name; + } + else + { + instName = "???"; + + config.PrintLog($"Invalid instruction at 0x{op.Address:X6} (0x{op.RawOpCode:X16})."); + } + + string dbgComment = $"0x{op.Address:X6}: 0x{op.RawOpCode:X16} {instName}"; + + context.Add(new CommentNode(dbgComment)); + } + if (op.NeverExecute) { continue; @@ -82,15 +166,15 @@ namespace Ryujinx.Graphics.Shader.Translation Operand predSkipLbl = null; - bool skipPredicateCheck = op.Emitter == InstEmit.Bra; + bool skipPredicateCheck = op is OpCodeBranch opBranch && !opBranch.PushTarget; - if (op is OpCodeSync opSync) + if (op is OpCodeBranchPop opBranchPop) { - // If the instruction is a SYNC instruction with only one + // If the instruction is a SYNC or BRK instruction with only one // possible target address, then the instruction is basically // just a simple branch, we can generate code similar to branch // instructions, with the condition check on the branch itself. - skipPredicateCheck |= opSync.Targets.Count < 2; + skipPredicateCheck = opBranchPop.Targets.Count < 2; } if (!(op.Predicate.IsPT || skipPredicateCheck)) @@ -122,7 +206,7 @@ namespace Ryujinx.Graphics.Shader.Translation context.CurrOp = op; - op.Emitter(context); + op.Emitter?.Invoke(context); if (predSkipLbl != null) { @@ -131,6 +215,8 @@ namespace Ryujinx.Graphics.Shader.Translation } } + size = (int)maxEndAddress + (((flags & TranslationFlags.Compute) != 0) ? 0 : HeaderSize); + return context.GetOperations(); } diff --git a/Ryujinx.Graphics.Shader/Translation/TranslatorCallbacks.cs b/Ryujinx.Graphics.Shader/Translation/TranslatorCallbacks.cs new file mode 100644 index 0000000000..e0e9852fa3 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/TranslatorCallbacks.cs @@ -0,0 +1,17 @@ +using System; + +namespace Ryujinx.Graphics.Shader.Translation +{ + public struct TranslatorCallbacks + { + internal Func QueryInfo { get; } + + internal Action PrintLog { get; } + + public TranslatorCallbacks(Func queryInfoCallback, Action printLogCallback) + { + QueryInfo = queryInfoCallback; + PrintLog = printLogCallback; + } + } +} diff --git a/Ryujinx.Graphics/Graphics3d/Texture/AstcDecoder.cs b/Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs similarity index 62% rename from Ryujinx.Graphics/Graphics3d/Texture/AstcDecoder.cs rename to Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs index 3e68be7d89..74623b38f5 100644 --- a/Ryujinx.Graphics/Graphics3d/Texture/AstcDecoder.cs +++ b/Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs @@ -1,25 +1,186 @@ -using System; -using System.Collections; -using System.Collections.Generic; +using Ryujinx.Common.Utilities; +using System; using System.Diagnostics; -using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; -namespace Ryujinx.Graphics.Texture +namespace Ryujinx.Graphics.Texture.Astc { - public class AstcDecoderException : Exception - { - public AstcDecoderException(string exMsg) : base(exMsg) { } - } - // https://github.com/GammaUNC/FasTC/blob/master/ASTCEncoder/src/Decompressor.cpp - public static class AstcDecoder + public class AstcDecoder { + private ReadOnlyMemory InputBuffer { get; } + private Memory OutputBuffer { get; } + + private int BlockSizeX { get; } + private int BlockSizeY { get; } + + private AstcLevel[] Levels { get; } + + private bool Success { get; set; } + + public int TotalBlockCount { get; } + + public AstcDecoder( + ReadOnlyMemory inputBuffer, + Memory outputBuffer, + int blockWidth, + int blockHeight, + int width, + int height, + int depth, + int levels) + { + if ((uint)blockWidth > 12) + { + throw new ArgumentOutOfRangeException(nameof(blockWidth)); + } + + if ((uint)blockHeight > 12) + { + throw new ArgumentOutOfRangeException(nameof(blockHeight)); + } + + InputBuffer = inputBuffer; + OutputBuffer = outputBuffer; + + BlockSizeX = blockWidth; + BlockSizeY = blockHeight; + + Levels = new AstcLevel[levels]; + + Success = true; + + TotalBlockCount = 0; + + int currentInputBlock = 0; + int currentOutputOffset = 0; + + for (int i = 0; i < levels; i++) + { + ref AstcLevel level = ref Levels[i]; + + level.ImageSizeX = Math.Max(1, width >> i); + level.ImageSizeY = Math.Max(1, height >> i); + level.ImageSizeZ = Math.Max(1, depth >> i); + + level.BlockCountX = (level.ImageSizeX + blockWidth - 1) / blockWidth; + level.BlockCountY = (level.ImageSizeY + blockHeight - 1) / blockHeight; + + level.StartBlock = currentInputBlock; + level.OutputByteOffset = currentOutputOffset; + + currentInputBlock += level.TotalBlockCount; + currentOutputOffset += level.PixelCount * 4; + } + + TotalBlockCount = currentInputBlock; + } + + private struct AstcLevel + { + public int ImageSizeX { get; set; } + public int ImageSizeY { get; set; } + public int ImageSizeZ { get; set; } + + public int BlockCountX { get; set; } + public int BlockCountY { get; set; } + + public int StartBlock { get; set; } + public int OutputByteOffset { get; set; } + + public int TotalBlockCount => BlockCountX * BlockCountY * ImageSizeZ; + public int PixelCount => ImageSizeX * ImageSizeY * ImageSizeZ; + } + + public static int QueryDecompressedSize(int sizeX, int sizeY, int sizeZ, int levelCount) + { + int size = 0; + + for (int i = 0; i < levelCount; i++) + { + int levelSizeX = Math.Max(1, sizeX >> i); + int levelSizeY = Math.Max(1, sizeY >> i); + int levelSizeZ = Math.Max(1, sizeZ >> i); + + size += levelSizeX * levelSizeY * levelSizeZ; + } + + return size * 4; + } + + public void ProcessBlock(int index) + { + Buffer16 inputBlock = MemoryMarshal.Cast(InputBuffer.Span)[index]; + + Span decompressedData = stackalloc int[144]; + + try + { + DecompressBlock(inputBlock, decompressedData, BlockSizeX, BlockSizeY); + } + catch (Exception) + { + Success = false; + } + + Span decompressedBytes = MemoryMarshal.Cast(decompressedData); + + AstcLevel levelInfo = GetLevelInfo(index); + + WriteDecompressedBlock(decompressedBytes, OutputBuffer.Span.Slice(levelInfo.OutputByteOffset), + index - levelInfo.StartBlock, levelInfo); + } + + private AstcLevel GetLevelInfo(int blockIndex) + { + foreach (AstcLevel levelInfo in Levels) + { + if (blockIndex < levelInfo.StartBlock + levelInfo.TotalBlockCount) + { + return levelInfo; + } + } + + throw new AstcDecoderException("Invalid block index."); + } + + private void WriteDecompressedBlock(ReadOnlySpan block, Span outputBuffer, int blockIndex, AstcLevel level) + { + int stride = level.ImageSizeX * 4; + + int blockCordX = blockIndex % level.BlockCountX; + int blockCordY = blockIndex / level.BlockCountX; + + int pixelCordX = blockCordX * BlockSizeX; + int pixelCordY = blockCordY * BlockSizeY; + + int outputPixelsX = Math.Min(pixelCordX + BlockSizeX, level.ImageSizeX) - pixelCordX; + int outputPixelsY = Math.Min(pixelCordY + BlockSizeY, level.ImageSizeY * level.ImageSizeZ) - pixelCordY; + + int outputStart = pixelCordX * 4 + pixelCordY * stride; + int outputOffset = outputStart; + + int inputOffset = 0; + + for (int i = 0; i < outputPixelsY; i++) + { + ReadOnlySpan blockRow = block.Slice(inputOffset, outputPixelsX * 4); + Span outputRow = outputBuffer.Slice(outputOffset); + blockRow.CopyTo(outputRow); + + inputOffset += BlockSizeX * 4; + outputOffset += stride; + } + } + struct TexelWeightParams { - public int Width; - public int Height; + public int Width; + public int Height; + public int MaxWeight; public bool DualPlane; - public int MaxWeight; public bool Error; public bool VoidExtentLdr; public bool VoidExtentHdr; @@ -52,73 +213,98 @@ namespace Ryujinx.Graphics.Texture } } - public static byte[] DecodeToRgba8888( - byte[] inputBuffer, - int blockX, - int blockY, - int blockZ, - int x, - int y, - int z) + public static bool TryDecodeToRgba8( + ReadOnlyMemory data, + int blockWidth, + int blockHeight, + int width, + int height, + int depth, + int levels, + out Span decoded) { - using (MemoryStream inputStream = new MemoryStream(inputBuffer)) + byte[] output = new byte[QueryDecompressedSize(width, height, depth, levels)]; + + AstcDecoder decoder = new AstcDecoder(data, output, blockWidth, blockHeight, width, height, depth, levels); + + for (int i = 0; i < decoder.TotalBlockCount; i++) { - BinaryReader binReader = new BinaryReader(inputStream); - - if (blockX > 12 || blockY > 12) - { - throw new AstcDecoderException("Block size unsupported!"); - } - - if (blockZ != 1 || z != 1) - { - // TODO: Support 3D textures? - throw new AstcDecoderException("3D compressed textures unsupported!"); - } - - using (MemoryStream outputStream = new MemoryStream()) - { - int blockIndex = 0; - - for (int j = 0; j < y; j += blockY) - { - for (int i = 0; i < x; i += blockX) - { - int[] decompressedData = new int[144]; - - DecompressBlock(binReader.ReadBytes(0x10), decompressedData, blockX, blockY); - - int decompressedWidth = Math.Min(blockX, x - i); - int decompressedHeight = Math.Min(blockY, y - j); - int baseOffsets = (j * x + i) * 4; - - for (int jj = 0; jj < decompressedHeight; jj++) - { - outputStream.Seek(baseOffsets + jj * x * 4, SeekOrigin.Begin); - - byte[] outputBuffer = new byte[decompressedData.Length * sizeof(int)]; - Buffer.BlockCopy(decompressedData, 0, outputBuffer, 0, outputBuffer.Length); - - outputStream.Write(outputBuffer, jj * blockX * 4, decompressedWidth * 4); - } - - blockIndex++; - } - } - - return outputStream.ToArray(); - } + decoder.ProcessBlock(i); } + + decoded = output; + + return decoder.Success; + } + + public static bool TryDecodeToRgba8( + ReadOnlyMemory data, + Memory outputBuffer, + int blockWidth, + int blockHeight, + int width, + int height, + int depth, + int levels) + { + AstcDecoder decoder = new AstcDecoder(data, outputBuffer, blockWidth, blockHeight, width, height, depth, levels); + + for (int i = 0; i < decoder.TotalBlockCount; i++) + { + decoder.ProcessBlock(i); + } + + return decoder.Success; + } + + public static bool TryDecodeToRgba8P( + ReadOnlyMemory data, + Memory outputBuffer, + int blockWidth, + int blockHeight, + int width, + int height, + int depth, + int levels) + { + AstcDecoder decoder = new AstcDecoder(data, outputBuffer, blockWidth, blockHeight, width, height, depth, levels); + + // Lazy parallelism + Enumerable.Range(0, decoder.TotalBlockCount).AsParallel().ForAll(x => decoder.ProcessBlock(x)); + + return decoder.Success; + } + + public static bool TryDecodeToRgba8P( + ReadOnlyMemory data, + int blockWidth, + int blockHeight, + int width, + int height, + int depth, + int levels, + out Span decoded) + { + byte[] output = new byte[QueryDecompressedSize(width, height, depth, levels)]; + + AstcDecoder decoder = new AstcDecoder(data, output, blockWidth, blockHeight, width, height, depth, levels); + + Enumerable.Range(0, decoder.TotalBlockCount).AsParallel().ForAll(x => decoder.ProcessBlock(x)); + + decoded = output; + + return decoder.Success; } public static bool DecompressBlock( - byte[] inputBuffer, - int[] outputBuffer, - int blockWidth, - int blockHeight) + Buffer16 inputBlock, + Span outputBuffer, + int blockWidth, + int blockHeight) { - BitArrayStream bitStream = new BitArrayStream(new BitArray(inputBuffer)); - TexelWeightParams texelParams = DecodeBlockInfo(bitStream); + BitStream128 bitStream = new BitStream128(inputBlock); + + DecodeBlockInfo(ref bitStream, out TexelWeightParams texelParams); if (texelParams.Error) { @@ -127,24 +313,24 @@ namespace Ryujinx.Graphics.Texture if (texelParams.VoidExtentLdr) { - FillVoidExtentLdr(bitStream, outputBuffer, blockWidth, blockHeight); + FillVoidExtentLdr(ref bitStream, outputBuffer, blockWidth, blockHeight); return true; } if (texelParams.VoidExtentHdr) { - throw new AstcDecoderException("HDR void extent blocks are unsupported!"); + throw new AstcDecoderException("HDR void extent blocks are not supported."); } if (texelParams.Width > blockWidth) { - throw new AstcDecoderException("Texel weight grid width should be smaller than block width"); + throw new AstcDecoderException("Texel weight grid width should be smaller than block width."); } if (texelParams.Height > blockHeight) { - throw new AstcDecoderException("Texel weight grid height should be smaller than block height"); + throw new AstcDecoderException("Texel weight grid height should be smaller than block height."); } // Read num partitions @@ -153,18 +339,19 @@ namespace Ryujinx.Graphics.Texture if (numberPartitions == 4 && texelParams.DualPlane) { - throw new AstcDecoderException("Dual plane mode is incompatible with four partition blocks"); + throw new AstcDecoderException("Dual plane mode is incompatible with four partition blocks."); } // Based on the number of partitions, read the color endpoint mode for // each partition. // Determine partitions, partition index, and color endpoint modes - int planeIndices = -1; - int partitionIndex; - uint[] colorEndpointMode = { 0, 0, 0, 0 }; + int planeIndices; + int partitionIndex; - BitArrayStream colorEndpointStream = new BitArrayStream(new BitArray(16 * 8)); + Span colorEndpointMode = stackalloc uint[4]; + + BitStream128 colorEndpointStream = new BitStream128(); // Read extra config data... uint baseColorEndpointMode = 0; @@ -172,11 +359,11 @@ namespace Ryujinx.Graphics.Texture if (numberPartitions == 1) { colorEndpointMode[0] = (uint)bitStream.ReadBits(4); - partitionIndex = 0; + partitionIndex = 0; } else { - partitionIndex = bitStream.ReadBits(10); + partitionIndex = bitStream.ReadBits(10); baseColorEndpointMode = (uint)bitStream.ReadBits(6); } @@ -184,7 +371,7 @@ namespace Ryujinx.Graphics.Texture // Remaining bits are color endpoint data... int numberWeightBits = texelParams.GetPackedBitSize(); - int remainingBits = 128 - numberWeightBits - bitStream.Position; + int remainingBits = bitStream.BitsLeft - numberWeightBits; // Consider extra bits prior to texel data... uint extraColorEndpointModeBits = 0; @@ -193,9 +380,9 @@ namespace Ryujinx.Graphics.Texture { switch (numberPartitions) { - case 2: extraColorEndpointModeBits += 2; break; - case 3: extraColorEndpointModeBits += 5; break; - case 4: extraColorEndpointModeBits += 8; break; + case 2: extraColorEndpointModeBits += 2; break; + case 3: extraColorEndpointModeBits += 5; break; + case 4: extraColorEndpointModeBits += 8; break; default: Debug.Assert(false); break; } } @@ -230,10 +417,10 @@ namespace Ryujinx.Graphics.Texture if (baseMode != 0) { uint extraColorEndpointMode = (uint)bitStream.ReadBits((int)extraColorEndpointModeBits); - uint tempColorEndpointMode = (extraColorEndpointMode << 6) | baseColorEndpointMode; - tempColorEndpointMode >>= 2; + uint tempColorEndpointMode = (extraColorEndpointMode << 6) | baseColorEndpointMode; + tempColorEndpointMode >>= 2; - bool[] c = new bool[4]; + Span c = stackalloc bool[4]; for (int i = 0; i < numberPartitions; i++) { @@ -241,7 +428,7 @@ namespace Ryujinx.Graphics.Texture tempColorEndpointMode >>= 1; } - byte[] m = new byte[4]; + Span m = stackalloc byte[4]; for (int i = 0; i < numberPartitions; i++) { @@ -262,7 +449,7 @@ namespace Ryujinx.Graphics.Texture { uint tempColorEndpointMode = baseColorEndpointMode >> 2; - for (uint i = 0; i < numberPartitions; i++) + for (int i = 0; i < numberPartitions; i++) { colorEndpointMode[i] = tempColorEndpointMode; } @@ -273,27 +460,24 @@ namespace Ryujinx.Graphics.Texture { Debug.Assert(colorEndpointMode[i] < 16); } - Debug.Assert(bitStream.Position + texelParams.GetPackedBitSize() == 128); + Debug.Assert(bitStream.BitsLeft == texelParams.GetPackedBitSize()); // Decode both color data and texel weight data - int[] colorValues = new int[32]; // Four values * two endpoints * four maximum partitions - DecodeColorValues(colorValues, colorEndpointStream.ToByteArray(), colorEndpointMode, numberPartitions, colorDataBits); + Span colorValues = stackalloc int[32]; // Four values * two endpoints * four maximum partitions + DecodeColorValues(colorValues, ref colorEndpointStream, colorEndpointMode, numberPartitions, colorDataBits); - AstcPixel[][] endPoints = new AstcPixel[4][]; - endPoints[0] = new AstcPixel[2]; - endPoints[1] = new AstcPixel[2]; - endPoints[2] = new AstcPixel[2]; - endPoints[3] = new AstcPixel[2]; + EndPointSet endPoints; + unsafe { _ = &endPoints; } // Skip struct initialization int colorValuesPosition = 0; for (int i = 0; i < numberPartitions; i++) { - ComputeEndpoints(endPoints[i], colorValues, colorEndpointMode[i], ref colorValuesPosition); + ComputeEndpoints(endPoints.Get(i), colorValues, colorEndpointMode[i], ref colorValuesPosition); } // Read the texel weight data. - byte[] texelWeightData = (byte[])inputBuffer.Clone(); + Buffer16 texelWeightData = inputBlock; // Reverse everything for (int i = 0; i < 8; i++) @@ -301,28 +485,32 @@ namespace Ryujinx.Graphics.Texture byte a = ReverseByte(texelWeightData[i]); byte b = ReverseByte(texelWeightData[15 - i]); - texelWeightData[i] = b; + texelWeightData[i] = b; texelWeightData[15 - i] = a; } // Make sure that higher non-texel bits are set to zero - int clearByteStart = (texelParams.GetPackedBitSize() >> 3) + 1; + int clearByteStart = (texelParams.GetPackedBitSize() >> 3) + 1; texelWeightData[clearByteStart - 1] &= (byte)((1 << (texelParams.GetPackedBitSize() % 8)) - 1); int cLen = 16 - clearByteStart; for (int i = clearByteStart; i < clearByteStart + cLen; i++) texelWeightData[i] = 0; - List texelWeightValues = new List(); - BitArrayStream weightBitStream = new BitArrayStream(new BitArray(texelWeightData)); + IntegerSequence texelWeightValues; + unsafe { _ = &texelWeightValues; } // Skip struct initialization + texelWeightValues.Reset(); + + BitStream128 weightBitStream = new BitStream128(texelWeightData); + + IntegerEncoded.DecodeIntegerSequence(ref texelWeightValues, ref weightBitStream, texelParams.MaxWeight, texelParams.GetNumWeightValues()); - IntegerEncoded.DecodeIntegerSequence(texelWeightValues, weightBitStream, texelParams.MaxWeight, texelParams.GetNumWeightValues()); - // Blocks can be at most 12x12, so we can have as many as 144 weights - int[][] weights = new int[2][]; - weights[0] = new int[144]; - weights[1] = new int[144]; + Weights weights; + unsafe { _ = &weights; } // Skip struct initialization - UnquantizeTexelWeights(weights, texelWeightValues, texelParams, blockWidth, blockHeight); + UnquantizeTexelWeights(ref weights, ref texelWeightValues, ref texelParams, blockWidth, blockHeight); + + ushort[] table = Bits.Replicate8_16Table; // Now that we have endpoints and weights, we can interpolate and generate // the proper decoding... @@ -333,13 +521,13 @@ namespace Ryujinx.Graphics.Texture int partition = Select2dPartition(partitionIndex, i, j, numberPartitions, ((blockHeight * blockWidth) < 32)); Debug.Assert(partition < numberPartitions); - AstcPixel pixel = new AstcPixel(0, 0, 0, 0); + AstcPixel pixel = new AstcPixel(); for (int component = 0; component < 4; component++) { - int component0 = endPoints[partition][0].GetComponent(component); - component0 = BitArrayStream.Replicate(component0, 8, 16); - int component1 = endPoints[partition][1].GetComponent(component); - component1 = BitArrayStream.Replicate(component1, 8, 16); + int component0 = endPoints.Get(partition)[0].GetComponent(component); + component0 = table[component0]; + int component1 = endPoints.Get(partition)[1].GetComponent(component); + component1 = table[component1]; int plane = 0; @@ -348,7 +536,7 @@ namespace Ryujinx.Graphics.Texture plane = 1; } - int weight = weights[plane][j * blockWidth + i]; + int weight = weights.Get(plane)[j * blockWidth + i]; int finalComponent = (component0 * (64 - weight) + component1 * weight + 32) / 64; if (finalComponent == 65535) @@ -369,6 +557,38 @@ namespace Ryujinx.Graphics.Texture return true; } + // Blocks can be at most 12x12, so we can have as many as 144 weights + [StructLayout(LayoutKind.Sequential, Size = 144 * sizeof(int) * Count)] + private struct Weights + { + private int _start; + + public const int Count = 2; + + public Span this[int index] + { + get + { + if ((uint)index >= Count) + { + throw new ArgumentOutOfRangeException(); + } + + ref int start = ref Unsafe.Add(ref _start, index * 144); + + return MemoryMarshal.CreateSpan(ref start, 144); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span Get(int index) + { + ref int start = ref Unsafe.Add(ref _start, index * 144); + + return MemoryMarshal.CreateSpan(ref start, 144); + } + } + private static int Select2dPartition(int seed, int x, int y, int partitionCount, bool isSmallBlock) { return SelectPartition(seed, x, y, 0, partitionCount, isSmallBlock); @@ -390,19 +610,19 @@ namespace Ryujinx.Graphics.Texture seed += (partitionCount - 1) * 1024; - int rightNum = Hash52((uint)seed); - byte seed01 = (byte)(rightNum & 0xF); - byte seed02 = (byte)((rightNum >> 4) & 0xF); - byte seed03 = (byte)((rightNum >> 8) & 0xF); - byte seed04 = (byte)((rightNum >> 12) & 0xF); - byte seed05 = (byte)((rightNum >> 16) & 0xF); - byte seed06 = (byte)((rightNum >> 20) & 0xF); - byte seed07 = (byte)((rightNum >> 24) & 0xF); - byte seed08 = (byte)((rightNum >> 28) & 0xF); - byte seed09 = (byte)((rightNum >> 18) & 0xF); - byte seed10 = (byte)((rightNum >> 22) & 0xF); - byte seed11 = (byte)((rightNum >> 26) & 0xF); - byte seed12 = (byte)(((rightNum >> 30) | (rightNum << 2)) & 0xF); + int rightNum = Hash52((uint)seed); + byte seed01 = (byte)(rightNum & 0xF); + byte seed02 = (byte)((rightNum >> 4) & 0xF); + byte seed03 = (byte)((rightNum >> 8) & 0xF); + byte seed04 = (byte)((rightNum >> 12) & 0xF); + byte seed05 = (byte)((rightNum >> 16) & 0xF); + byte seed06 = (byte)((rightNum >> 20) & 0xF); + byte seed07 = (byte)((rightNum >> 24) & 0xF); + byte seed08 = (byte)((rightNum >> 28) & 0xF); + byte seed09 = (byte)((rightNum >> 18) & 0xF); + byte seed10 = (byte)((rightNum >> 22) & 0xF); + byte seed11 = (byte)((rightNum >> 26) & 0xF); + byte seed12 = (byte)(((rightNum >> 30) | (rightNum << 2)) & 0xF); seed01 *= seed01; seed02 *= seed02; seed03 *= seed03; seed04 *= seed04; @@ -449,50 +669,56 @@ namespace Ryujinx.Graphics.Texture static int Hash52(uint val) { val ^= val >> 15; val -= val << 17; val += val << 7; val += val << 4; - val ^= val >> 5; val += val << 16; val ^= val >> 7; val ^= val >> 3; - val ^= val << 6; val ^= val >> 17; + val ^= val >> 5; val += val << 16; val ^= val >> 7; val ^= val >> 3; + val ^= val << 6; val ^= val >> 17; return (int)val; } static void UnquantizeTexelWeights( - int[][] outputBuffer, - List weights, - TexelWeightParams texelParams, - int blockWidth, - int blockHeight) + ref Weights outputBuffer, + ref IntegerSequence weights, + ref TexelWeightParams texelParams, + int blockWidth, + int blockHeight) { - int weightIndices = 0; - int[][] unquantized = new int[2][]; - unquantized[0] = new int[144]; - unquantized[1] = new int[144]; + int weightIndices = 0; + Weights unquantized; + unsafe { _ = &unquantized; } // Skip struct initialization - for (int i = 0; i < weights.Count; i++) + Span weightsList = weights.List; + Span unquantized0 = unquantized[0]; + Span unquantized1 = unquantized[1]; + + for (int i = 0; i < weightsList.Length; i++) { - unquantized[0][weightIndices] = UnquantizeTexelWeight(weights[i]); + unquantized0[weightIndices] = UnquantizeTexelWeight(weightsList[i]); if (texelParams.DualPlane) { i++; - unquantized[1][weightIndices] = UnquantizeTexelWeight(weights[i]); + unquantized1[weightIndices] = UnquantizeTexelWeight(weightsList[i]); - if (i == weights.Count) + if (i == weightsList.Length) { break; } } - if (++weightIndices >= (texelParams.Width * texelParams.Height)) break; + if (++weightIndices >= texelParams.Width * texelParams.Height) break; } // Do infill if necessary (Section C.2.18) ... - int ds = (1024 + (blockWidth / 2)) / (blockWidth - 1); - int dt = (1024 + (blockHeight / 2)) / (blockHeight - 1); + int ds = (1024 + blockWidth / 2) / (blockWidth - 1); + int dt = (1024 + blockHeight / 2) / (blockHeight - 1); int planeScale = texelParams.DualPlane ? 2 : 1; for (int plane = 0; plane < planeScale; plane++) { + Span unquantizedSpan = unquantized.Get(plane); + Span outputSpan = outputBuffer.Get(plane); + for (int t = 0; t < blockHeight; t++) { for (int s = 0; s < blockWidth; s++) @@ -510,38 +736,34 @@ namespace Ryujinx.Graphics.Texture int ft = gt & 0x0F; int w11 = (fs * ft + 8) >> 4; - int w10 = ft - w11; - int w01 = fs - w11; - int w00 = 16 - fs - ft + w11; int v0 = js + jt * texelParams.Width; - int p00 = 0; - int p01 = 0; - int p10 = 0; - int p11 = 0; + int weight = 8; - if (v0 < (texelParams.Width * texelParams.Height)) + int wxh = texelParams.Width * texelParams.Height; + + if (v0 < wxh) { - p00 = unquantized[plane][v0]; + weight += unquantizedSpan[v0] * (16 - fs - ft + w11); + + if (v0 + 1 < wxh) + { + weight += unquantizedSpan[v0 + 1] * (fs - w11); + } } - if (v0 + 1 < (texelParams.Width * texelParams.Height)) + if (v0 + texelParams.Width < wxh) { - p01 = unquantized[plane][v0 + 1]; - } - - if (v0 + texelParams.Width < (texelParams.Width * texelParams.Height)) - { - p10 = unquantized[plane][v0 + texelParams.Width]; - } - - if (v0 + texelParams.Width + 1 < (texelParams.Width * texelParams.Height)) - { - p11 = unquantized[plane][v0 + texelParams.Width + 1]; + weight += unquantizedSpan[v0 + texelParams.Width] * (ft - w11); + + if (v0 + texelParams.Width + 1 < wxh) + { + weight += unquantizedSpan[v0 + texelParams.Width + 1] * w11; + } } - outputBuffer[plane][t * blockWidth + s] = (p00 * w00 + p01 * w01 + p10 * w10 + p11 * w11 + 8) >> 4; + outputSpan[t * blockWidth + s] = weight >> 4; } } } @@ -549,10 +771,10 @@ namespace Ryujinx.Graphics.Texture static int UnquantizeTexelWeight(IntegerEncoded intEncoded) { - int bitValue = intEncoded.BitValue; + int bitValue = intEncoded.BitValue; int bitLength = intEncoded.NumberBits; - int a = BitArrayStream.Replicate(bitValue & 1, 1, 7); + int a = Bits.Replicate1_7(bitValue & 1); int b = 0, c = 0, d = 0; int result = 0; @@ -560,7 +782,7 @@ namespace Ryujinx.Graphics.Texture switch (intEncoded.GetEncoding()) { case IntegerEncoded.EIntegerEncoding.JustBits: - result = BitArrayStream.Replicate(bitValue, bitLength, 6); + result = Bits.Replicate(bitValue, bitLength, 6); break; case IntegerEncoded.EIntegerEncoding.Trit: @@ -572,8 +794,13 @@ namespace Ryujinx.Graphics.Texture { case 0: { - int[] results = { 0, 32, 63 }; - result = results[d]; + result = d switch + { + 0 => 0, + 1 => 32, + 2 => 63, + _ => 0 + }; break; } @@ -603,11 +830,11 @@ namespace Ryujinx.Graphics.Texture } default: - throw new AstcDecoderException("Invalid trit encoding for texel weight"); + throw new AstcDecoderException("Invalid trit encoding for texel weight."); } break; - } + } case IntegerEncoded.EIntegerEncoding.Quint: { @@ -618,8 +845,15 @@ namespace Ryujinx.Graphics.Texture { case 0: { - int[] results = { 0, 16, 32, 47, 63 }; - result = results[d]; + result = d switch + { + 0 => 0, + 1 => 16, + 2 => 32, + 3 => 47, + 4 => 63, + _ => 0 + }; break; } @@ -639,21 +873,21 @@ namespace Ryujinx.Graphics.Texture break; } - + default: - throw new AstcDecoderException("Invalid quint encoding for texel weight"); + throw new AstcDecoderException("Invalid quint encoding for texel weight."); } break; - } + } } if (intEncoded.GetEncoding() != IntegerEncoded.EIntegerEncoding.JustBits && bitLength > 0) { // Decode the value... - result = d * c + b; + result = d * c + b; result ^= a; - result = (a & 0x20) | (result >> 2); + result = (a & 0x20) | (result >> 2); } Debug.Assert(result < 64); @@ -673,54 +907,48 @@ namespace Ryujinx.Graphics.Texture return (byte)((((b) * 0x80200802L) & 0x0884422110L) * 0x0101010101L >> 32); } - static uint[] ReadUintColorValues(int number, int[] colorValues, ref int colorValuesPosition) + static Span ReadUintColorValues(int number, Span colorValues, ref int colorValuesPosition) { - uint[] ret = new uint[number]; + Span ret = colorValues.Slice(colorValuesPosition, number); - for (int i = 0; i < number; i++) - { - ret[i] = (uint)colorValues[colorValuesPosition++]; - } + colorValuesPosition += number; - return ret; + return MemoryMarshal.Cast(ret); } - static int[] ReadIntColorValues(int number, int[] colorValues, ref int colorValuesPosition) + static Span ReadIntColorValues(int number, Span colorValues, ref int colorValuesPosition) { - int[] ret = new int[number]; + Span ret = colorValues.Slice(colorValuesPosition, number); - for (int i = 0; i < number; i++) - { - ret[i] = colorValues[colorValuesPosition++]; - } + colorValuesPosition += number; return ret; } static void ComputeEndpoints( - AstcPixel[] endPoints, - int[] colorValues, - uint colorEndpointMode, - ref int colorValuesPosition) + Span endPoints, + Span colorValues, + uint colorEndpointMode, + ref int colorValuesPosition) { switch (colorEndpointMode) { case 0: { - uint[] val = ReadUintColorValues(2, colorValues, ref colorValuesPosition); + Span val = ReadUintColorValues(2, colorValues, ref colorValuesPosition); endPoints[0] = new AstcPixel(0xFF, (short)val[0], (short)val[0], (short)val[0]); endPoints[1] = new AstcPixel(0xFF, (short)val[1], (short)val[1], (short)val[1]); break; } - + case 1: { - uint[] val = ReadUintColorValues(2, colorValues, ref colorValuesPosition); - int l0 = (int)((val[0] >> 2) | (val[1] & 0xC0)); - int l1 = (int)Math.Max(l0 + (val[1] & 0x3F), 0xFFU); + Span val = ReadUintColorValues(2, colorValues, ref colorValuesPosition); + int l0 = (int)((val[0] >> 2) | (val[1] & 0xC0)); + int l1 = (int)Math.Max(l0 + (val[1] & 0x3F), 0xFFU); endPoints[0] = new AstcPixel(0xFF, (short)l0, (short)l0, (short)l0); endPoints[1] = new AstcPixel(0xFF, (short)l1, (short)l1, (short)l1); @@ -730,7 +958,7 @@ namespace Ryujinx.Graphics.Texture case 4: { - uint[] val = ReadUintColorValues(4, colorValues, ref colorValuesPosition); + Span val = ReadUintColorValues(4, colorValues, ref colorValuesPosition); endPoints[0] = new AstcPixel((short)val[2], (short)val[0], (short)val[0], (short)val[0]); endPoints[1] = new AstcPixel((short)val[3], (short)val[1], (short)val[1], (short)val[1]); @@ -740,10 +968,10 @@ namespace Ryujinx.Graphics.Texture case 5: { - int[] val = ReadIntColorValues(4, colorValues, ref colorValuesPosition); + Span val = ReadIntColorValues(4, colorValues, ref colorValuesPosition); - BitArrayStream.BitTransferSigned(ref val[1], ref val[0]); - BitArrayStream.BitTransferSigned(ref val[3], ref val[2]); + Bits.BitTransferSigned(ref val[1], ref val[0]); + Bits.BitTransferSigned(ref val[3], ref val[2]); endPoints[0] = new AstcPixel((short)val[2], (short)val[0], (short)val[0], (short)val[0]); endPoints[1] = new AstcPixel((short)(val[2] + val[3]), (short)(val[0] + val[1]), (short)(val[0] + val[1]), (short)(val[0] + val[1])); @@ -756,7 +984,7 @@ namespace Ryujinx.Graphics.Texture case 6: { - uint[] val = ReadUintColorValues(4, colorValues, ref colorValuesPosition); + Span val = ReadUintColorValues(4, colorValues, ref colorValuesPosition); endPoints[0] = new AstcPixel(0xFF, (short)(val[0] * val[3] >> 8), (short)(val[1] * val[3] >> 8), (short)(val[2] * val[3] >> 8)); endPoints[1] = new AstcPixel(0xFF, (short)val[0], (short)val[1], (short)val[2]); @@ -766,7 +994,7 @@ namespace Ryujinx.Graphics.Texture case 8: { - uint[] val = ReadUintColorValues(6, colorValues, ref colorValuesPosition); + Span val = ReadUintColorValues(6, colorValues, ref colorValuesPosition); if (val[1] + val[3] + val[5] >= val[0] + val[2] + val[4]) { @@ -784,11 +1012,11 @@ namespace Ryujinx.Graphics.Texture case 9: { - int[] val = ReadIntColorValues(6, colorValues, ref colorValuesPosition); + Span val = ReadIntColorValues(6, colorValues, ref colorValuesPosition); - BitArrayStream.BitTransferSigned(ref val[1], ref val[0]); - BitArrayStream.BitTransferSigned(ref val[3], ref val[2]); - BitArrayStream.BitTransferSigned(ref val[5], ref val[4]); + Bits.BitTransferSigned(ref val[1], ref val[0]); + Bits.BitTransferSigned(ref val[3], ref val[2]); + Bits.BitTransferSigned(ref val[5], ref val[4]); if (val[1] + val[3] + val[5] >= 0) { @@ -809,7 +1037,7 @@ namespace Ryujinx.Graphics.Texture case 10: { - uint[] val = ReadUintColorValues(6, colorValues, ref colorValuesPosition); + Span val = ReadUintColorValues(6, colorValues, ref colorValuesPosition); endPoints[0] = new AstcPixel((short)val[4], (short)(val[0] * val[3] >> 8), (short)(val[1] * val[3] >> 8), (short)(val[2] * val[3] >> 8)); endPoints[1] = new AstcPixel((short)val[5], (short)val[0], (short)val[1], (short)val[2]); @@ -819,7 +1047,7 @@ namespace Ryujinx.Graphics.Texture case 12: { - uint[] val = ReadUintColorValues(8, colorValues, ref colorValuesPosition); + Span val = ReadUintColorValues(8, colorValues, ref colorValuesPosition); if (val[1] + val[3] + val[5] >= val[0] + val[2] + val[4]) { @@ -837,12 +1065,12 @@ namespace Ryujinx.Graphics.Texture case 13: { - int[] val = ReadIntColorValues(8, colorValues, ref colorValuesPosition); + Span val = ReadIntColorValues(8, colorValues, ref colorValuesPosition); - BitArrayStream.BitTransferSigned(ref val[1], ref val[0]); - BitArrayStream.BitTransferSigned(ref val[3], ref val[2]); - BitArrayStream.BitTransferSigned(ref val[5], ref val[4]); - BitArrayStream.BitTransferSigned(ref val[7], ref val[6]); + Bits.BitTransferSigned(ref val[1], ref val[0]); + Bits.BitTransferSigned(ref val[3], ref val[2]); + Bits.BitTransferSigned(ref val[5], ref val[4]); + Bits.BitTransferSigned(ref val[7], ref val[6]); if (val[1] + val[3] + val[5] >= 0) { @@ -867,11 +1095,11 @@ namespace Ryujinx.Graphics.Texture } static void DecodeColorValues( - int[] outputValues, - byte[] inputData, - uint[] modes, - int numberPartitions, - int numberBitsForColorData) + Span outputValues, + ref BitStream128 colorBitStream, + Span modes, + int numberPartitions, + int numberBitsForColorData) { // First figure out how many color values we have int numberValues = 0; @@ -888,7 +1116,7 @@ namespace Ryujinx.Graphics.Texture while (--range > 0) { IntegerEncoded intEncoded = IntegerEncoded.CreateEncoding(range); - int bitLength = intEncoded.GetBitLength(numberValues); + int bitLength = intEncoded.GetBitLength(numberValues); if (bitLength <= numberBitsForColorData) { @@ -909,31 +1137,32 @@ namespace Ryujinx.Graphics.Texture } // We now have enough to decode our integer sequence. - List integerEncodedSequence = new List(); - BitArrayStream colorBitStream = new BitArrayStream(new BitArray(inputData)); + IntegerSequence integerEncodedSequence; + unsafe { _ = &integerEncodedSequence; } // Skip struct initialization + integerEncodedSequence.Reset(); - IntegerEncoded.DecodeIntegerSequence(integerEncodedSequence, colorBitStream, range, numberValues); + IntegerEncoded.DecodeIntegerSequence(ref integerEncodedSequence, ref colorBitStream, range, numberValues); // Once we have the decoded values, we need to dequantize them to the 0-255 range // This procedure is outlined in ASTC spec C.2.13 int outputIndices = 0; - foreach (IntegerEncoded intEncoded in integerEncodedSequence) + foreach (ref IntegerEncoded intEncoded in integerEncodedSequence.List) { int bitLength = intEncoded.NumberBits; - int bitValue = intEncoded.BitValue; + int bitValue = intEncoded.BitValue; Debug.Assert(bitLength >= 1); int a = 0, b = 0, c = 0, d = 0; // A is just the lsb replicated 9 times. - a = BitArrayStream.Replicate(bitValue & 1, 1, 9); + a = Bits.Replicate(bitValue & 1, 1, 9); switch (intEncoded.GetEncoding()) { case IntegerEncoded.EIntegerEncoding.JustBits: { - outputValues[outputIndices++] = BitArrayStream.Replicate(bitValue, bitLength, 8); + outputValues[outputIndices++] = Bits.Replicate(bitValue, bitLength, 8); break; } @@ -950,7 +1179,7 @@ namespace Ryujinx.Graphics.Texture break; } - + case 2: { c = 93; @@ -970,7 +1199,7 @@ namespace Ryujinx.Graphics.Texture break; } - + case 4: { @@ -1003,12 +1232,12 @@ namespace Ryujinx.Graphics.Texture } default: - throw new AstcDecoderException("Unsupported trit encoding for color values!"); + throw new AstcDecoderException("Unsupported trit encoding for color values."); } break; } - + case IntegerEncoded.EIntegerEncoding.Quint: { d = intEncoded.QuintValue; @@ -1021,7 +1250,7 @@ namespace Ryujinx.Graphics.Texture break; } - + case 2: { c = 54; @@ -1031,7 +1260,7 @@ namespace Ryujinx.Graphics.Texture break; } - + case 3: { c = 26; @@ -1051,7 +1280,7 @@ namespace Ryujinx.Graphics.Texture break; } - + case 5: { c = 6; @@ -1063,17 +1292,17 @@ namespace Ryujinx.Graphics.Texture } default: - throw new AstcDecoderException("Unsupported quint encoding for color values!"); + throw new AstcDecoderException("Unsupported quint encoding for color values."); } break; - } + } } if (intEncoded.GetEncoding() != IntegerEncoded.EIntegerEncoding.JustBits) { int T = d * c + b; - T ^= a; - T = (a & 0x80) | (T >> 2); + T ^= a; + T = (a & 0x80) | (T >> 2); outputValues[outputIndices++] = T; } @@ -1086,7 +1315,7 @@ namespace Ryujinx.Graphics.Texture } } - static void FillVoidExtentLdr(BitArrayStream bitStream, int[] outputBuffer, int blockWidth, int blockHeight) + static void FillVoidExtentLdr(ref BitStream128 bitStream, Span outputBuffer, int blockWidth, int blockHeight) { // Don't actually care about the void extent, just read the bits... for (int i = 0; i < 4; ++i) @@ -1111,9 +1340,9 @@ namespace Ryujinx.Graphics.Texture } } - static TexelWeightParams DecodeBlockInfo(BitArrayStream bitStream) + static void DecodeBlockInfo(ref BitStream128 bitStream, out TexelWeightParams texelParams) { - TexelWeightParams texelParams = new TexelWeightParams(); + texelParams = new TexelWeightParams(); // Read the entire block mode all at once ushort modeBits = (ushort)bitStream.ReadBits(11); @@ -1136,14 +1365,15 @@ namespace Ryujinx.Graphics.Texture texelParams.Error = true; } - return texelParams; + return; } // First check if the last four bits are zero if ((modeBits & 0xF) == 0) { texelParams.Error = true; - return texelParams; + + return; } // If the last two bits are zero, then if bits @@ -1152,14 +1382,14 @@ namespace Ryujinx.Graphics.Texture { texelParams.Error = true; - return texelParams; + return; } // Otherwise, there is no error... Figure out the layout // of the block mode. Layout is determined by a number // between 0 and 9 corresponding to table C.2.8 of the // ASTC spec. - int layout = 0; + int layout; if ((modeBits & 0x1) != 0 || (modeBits & 0x2) != 0) { @@ -1259,7 +1489,7 @@ namespace Ryujinx.Graphics.Texture int a = (modeBits >> 5) & 0x3; int b = (modeBits >> 7) & 0x3; - texelParams.Width = b + 4; + texelParams.Width = b + 4; texelParams.Height = a + 2; break; @@ -1270,7 +1500,7 @@ namespace Ryujinx.Graphics.Texture int a = (modeBits >> 5) & 0x3; int b = (modeBits >> 7) & 0x3; - texelParams.Width = b + 8; + texelParams.Width = b + 8; texelParams.Height = a + 2; break; @@ -1281,7 +1511,7 @@ namespace Ryujinx.Graphics.Texture int a = (modeBits >> 5) & 0x3; int b = (modeBits >> 7) & 0x3; - texelParams.Width = a + 2; + texelParams.Width = a + 2; texelParams.Height = b + 8; break; @@ -1292,7 +1522,7 @@ namespace Ryujinx.Graphics.Texture int a = (modeBits >> 5) & 0x3; int b = (modeBits >> 7) & 0x1; - texelParams.Width = a + 2; + texelParams.Width = a + 2; texelParams.Height = b + 6; break; @@ -1303,7 +1533,7 @@ namespace Ryujinx.Graphics.Texture int a = (modeBits >> 5) & 0x3; int b = (modeBits >> 7) & 0x1; - texelParams.Width = b + 2; + texelParams.Width = b + 2; texelParams.Height = a + 2; break; @@ -1313,7 +1543,7 @@ namespace Ryujinx.Graphics.Texture { int a = (modeBits >> 5) & 0x3; - texelParams.Width = 12; + texelParams.Width = 12; texelParams.Height = a + 2; break; @@ -1323,7 +1553,7 @@ namespace Ryujinx.Graphics.Texture { int a = (modeBits >> 5) & 0x3; - texelParams.Width = a + 2; + texelParams.Width = a + 2; texelParams.Height = 12; break; @@ -1331,7 +1561,7 @@ namespace Ryujinx.Graphics.Texture case 7: { - texelParams.Width = 6; + texelParams.Width = 6; texelParams.Height = 10; break; @@ -1339,7 +1569,7 @@ namespace Ryujinx.Graphics.Texture case 8: { - texelParams.Width = 10; + texelParams.Width = 10; texelParams.Height = 6; break; } @@ -1349,7 +1579,7 @@ namespace Ryujinx.Graphics.Texture int a = (modeBits >> 5) & 0x3; int b = (modeBits >> 9) & 0x3; - texelParams.Width = a + 6; + texelParams.Width = a + 6; texelParams.Height = b + 6; break; @@ -1368,18 +1598,16 @@ namespace Ryujinx.Graphics.Texture if (h) { - int[] maxWeights = { 9, 11, 15, 19, 23, 31 }; + ReadOnlySpan maxWeights = new byte[] { 9, 11, 15, 19, 23, 31 }; texelParams.MaxWeight = maxWeights[r - 2]; } else { - int[] maxWeights = { 1, 2, 3, 4, 5, 7 }; + ReadOnlySpan maxWeights = new byte[] { 1, 2, 3, 4, 5, 7 }; texelParams.MaxWeight = maxWeights[r - 2]; } texelParams.DualPlane = d; - - return texelParams; } } } diff --git a/Ryujinx.Graphics.Texture/Astc/AstcDecoderException.cs b/Ryujinx.Graphics.Texture/Astc/AstcDecoderException.cs new file mode 100644 index 0000000000..fdc4826716 --- /dev/null +++ b/Ryujinx.Graphics.Texture/Astc/AstcDecoderException.cs @@ -0,0 +1,9 @@ +using System; + +namespace Ryujinx.Graphics.Texture.Astc +{ + public class AstcDecoderException : Exception + { + public AstcDecoderException(string exMsg) : base(exMsg) { } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Texture/Astc/AstcPixel.cs b/Ryujinx.Graphics.Texture/Astc/AstcPixel.cs new file mode 100644 index 0000000000..13197714e4 --- /dev/null +++ b/Ryujinx.Graphics.Texture/Astc/AstcPixel.cs @@ -0,0 +1,68 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Texture.Astc +{ + [StructLayout(LayoutKind.Sequential)] + struct AstcPixel + { + internal const int StructSize = 12; + + public short A; + public short R; + public short G; + public short B; + + private uint _bitDepthInt; + + private Span BitDepth => MemoryMarshal.CreateSpan(ref Unsafe.As(ref _bitDepthInt), 4); + private Span Components => MemoryMarshal.CreateSpan(ref A, 4); + + public AstcPixel(short a, short r, short g, short b) + { + A = a; + R = r; + G = g; + B = b; + + _bitDepthInt = 0x08080808; + } + + public void ClampByte() + { + R = Math.Min(Math.Max(R, (short)0), (short)255); + G = Math.Min(Math.Max(G, (short)0), (short)255); + B = Math.Min(Math.Max(B, (short)0), (short)255); + A = Math.Min(Math.Max(A, (short)0), (short)255); + } + + public short GetComponent(int index) + { + return Components[index]; + } + + public void SetComponent(int index, int value) + { + Components[index] = (short)value; + } + + public int Pack() + { + return A << 24 | + B << 16 | + G << 8 | + R << 0; + } + + // Adds more precision to the blue channel as described + // in C.2.14 + public static AstcPixel BlueContract(int a, int r, int g, int b) + { + return new AstcPixel((short)(a), + (short)((r + b) >> 1), + (short)((g + b) >> 1), + (short)(b)); + } + } +} diff --git a/Ryujinx.Graphics.Texture/Astc/BitStream128.cs b/Ryujinx.Graphics.Texture/Astc/BitStream128.cs new file mode 100644 index 0000000000..3bf9769f53 --- /dev/null +++ b/Ryujinx.Graphics.Texture/Astc/BitStream128.cs @@ -0,0 +1,72 @@ +using Ryujinx.Common.Utilities; +using System; +using System.Diagnostics; + +namespace Ryujinx.Graphics.Texture.Astc +{ + public struct BitStream128 + { + private Buffer16 _data; + public int BitsLeft { get; set; } + + public BitStream128(Buffer16 data) + { + _data = data; + BitsLeft = 128; + } + + public int ReadBits(int bitCount) + { + Debug.Assert(bitCount < 32); + + if (bitCount == 0) + { + return 0; + } + + int mask = (1 << bitCount) - 1; + int value = _data.As() & mask; + + Span span = _data.AsSpan(); + + ulong carry = span[1] << (64 - bitCount); + span[0] = (span[0] >> bitCount) | carry; + span[1] >>= bitCount; + + BitsLeft -= bitCount; + + return value; + } + + public void WriteBits(int value, int bitCount) + { + Debug.Assert(bitCount < 32); + + if (bitCount == 0) return; + + ulong maskedValue = (uint)(value & ((1 << bitCount) - 1)); + + Span span = _data.AsSpan(); + + if (BitsLeft < 64) + { + ulong lowMask = maskedValue << BitsLeft; + span[0] |= lowMask; + } + + if (BitsLeft + bitCount > 64) + { + if (BitsLeft > 64) + { + span[1] |= maskedValue << (BitsLeft - 64); + } + else + { + span[1] |= maskedValue >> (64 - BitsLeft); + } + } + + BitsLeft += bitCount; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Texture/Astc/Bits.cs b/Ryujinx.Graphics.Texture/Astc/Bits.cs new file mode 100644 index 0000000000..b140a20a02 --- /dev/null +++ b/Ryujinx.Graphics.Texture/Astc/Bits.cs @@ -0,0 +1,66 @@ +namespace Ryujinx.Graphics.Texture.Astc +{ + internal static class Bits + { + public static readonly ushort[] Replicate8_16Table; + public static readonly byte[] Replicate1_7Table; + + static Bits() + { + Replicate8_16Table = new ushort[0x200]; + Replicate1_7Table = new byte[0x200]; + + for (int i = 0; i < 0x200; i++) + { + Replicate8_16Table[i] = (ushort)Replicate(i, 8, 16); + Replicate1_7Table[i] = (byte)Replicate(i, 1, 7); + } + } + + public static int Replicate8_16(int value) + { + return Replicate8_16Table[value]; + } + + public static int Replicate1_7(int value) + { + return Replicate1_7Table[value]; + } + + public static int Replicate(int value, int numberBits, int toBit) + { + if (numberBits == 0) return 0; + if (toBit == 0) return 0; + + int tempValue = value & ((1 << numberBits) - 1); + int retValue = tempValue; + int resLength = numberBits; + + while (resLength < toBit) + { + int comp = 0; + if (numberBits > toBit - resLength) + { + int newShift = toBit - resLength; + comp = numberBits - newShift; + numberBits = newShift; + } + retValue <<= numberBits; + retValue |= tempValue >> comp; + resLength += numberBits; + } + + return retValue; + } + + // Transfers a bit as described in C.2.14 + public static void BitTransferSigned(ref int a, ref int b) + { + b >>= 1; + b |= a & 0x80; + a >>= 1; + a &= 0x3F; + if ((a & 0x20) != 0) a -= 0x40; + } + } +} diff --git a/Ryujinx.Graphics.Texture/Astc/EndPointSet.cs b/Ryujinx.Graphics.Texture/Astc/EndPointSet.cs new file mode 100644 index 0000000000..45e61ca2ca --- /dev/null +++ b/Ryujinx.Graphics.Texture/Astc/EndPointSet.cs @@ -0,0 +1,23 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Texture.Astc +{ + [StructLayout(LayoutKind.Sequential, Size = AstcPixel.StructSize * 8)] + internal struct EndPointSet + { + private AstcPixel _start; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span Get(int index) + { + Debug.Assert(index < 4); + + ref AstcPixel start = ref Unsafe.Add(ref _start, index * 2); + + return MemoryMarshal.CreateSpan(ref start, 2); + } + } +} diff --git a/Ryujinx.Graphics.Texture/Astc/IntegerEncoded.cs b/Ryujinx.Graphics.Texture/Astc/IntegerEncoded.cs new file mode 100644 index 0000000000..065de46be9 --- /dev/null +++ b/Ryujinx.Graphics.Texture/Astc/IntegerEncoded.cs @@ -0,0 +1,345 @@ +using System; +using System.Numerics; + +namespace Ryujinx.Graphics.Texture.Astc +{ + internal struct IntegerEncoded + { + internal const int StructSize = 8; + private static readonly IntegerEncoded[] Encodings; + + public enum EIntegerEncoding : byte + { + JustBits, + Quint, + Trit + } + + EIntegerEncoding _encoding; + public byte NumberBits { get; private set; } + public byte TritValue { get; private set; } + public byte QuintValue { get; private set; } + public int BitValue { get; private set; } + + static IntegerEncoded() + { + Encodings = new IntegerEncoded[0x100]; + + for (int i = 0; i < Encodings.Length; i++) + { + Encodings[i] = CreateEncodingCalc(i); + } + } + + public IntegerEncoded(EIntegerEncoding encoding, int numBits) + { + _encoding = encoding; + NumberBits = (byte)numBits; + BitValue = 0; + TritValue = 0; + QuintValue = 0; + } + + public bool MatchesEncoding(IntegerEncoded other) + { + return _encoding == other._encoding && NumberBits == other.NumberBits; + } + + public EIntegerEncoding GetEncoding() + { + return _encoding; + } + + public int GetBitLength(int numberVals) + { + int totalBits = NumberBits * numberVals; + if (_encoding == EIntegerEncoding.Trit) + { + totalBits += (numberVals * 8 + 4) / 5; + } + else if (_encoding == EIntegerEncoding.Quint) + { + totalBits += (numberVals * 7 + 2) / 3; + } + return totalBits; + } + + public static IntegerEncoded CreateEncoding(int maxVal) + { + return Encodings[maxVal]; + } + + private static IntegerEncoded CreateEncodingCalc(int maxVal) + { + while (maxVal > 0) + { + int check = maxVal + 1; + + // Is maxVal a power of two? + if ((check & (check - 1)) == 0) + { + return new IntegerEncoded(EIntegerEncoding.JustBits, BitOperations.PopCount((uint)maxVal)); + } + + // Is maxVal of the type 3*2^n - 1? + if ((check % 3 == 0) && ((check / 3) & ((check / 3) - 1)) == 0) + { + return new IntegerEncoded(EIntegerEncoding.Trit, BitOperations.PopCount((uint)(check / 3 - 1))); + } + + // Is maxVal of the type 5*2^n - 1? + if ((check % 5 == 0) && ((check / 5) & ((check / 5) - 1)) == 0) + { + return new IntegerEncoded(EIntegerEncoding.Quint, BitOperations.PopCount((uint)(check / 5 - 1))); + } + + // Apparently it can't be represented with a bounded integer sequence... + // just iterate. + maxVal--; + } + + return new IntegerEncoded(EIntegerEncoding.JustBits, 0); + } + + public static void DecodeTritBlock( + ref BitStream128 bitStream, + ref IntegerSequence listIntegerEncoded, + int numberBitsPerValue) + { + // Implement the algorithm in section C.2.12 + Span m = stackalloc int[5]; + + m[0] = bitStream.ReadBits(numberBitsPerValue); + int encoded = bitStream.ReadBits(2); + m[1] = bitStream.ReadBits(numberBitsPerValue); + encoded |= bitStream.ReadBits(2) << 2; + m[2] = bitStream.ReadBits(numberBitsPerValue); + encoded |= bitStream.ReadBits(1) << 4; + m[3] = bitStream.ReadBits(numberBitsPerValue); + encoded |= bitStream.ReadBits(2) << 5; + m[4] = bitStream.ReadBits(numberBitsPerValue); + encoded |= bitStream.ReadBits(1) << 7; + + ReadOnlySpan encodings = GetTritEncoding(encoded); + + IntegerEncoded intEncoded = new IntegerEncoded(EIntegerEncoding.Trit, numberBitsPerValue); + + for (int i = 0; i < 5; i++) + { + intEncoded.BitValue = m[i]; + intEncoded.TritValue = encodings[i]; + + listIntegerEncoded.Add(ref intEncoded); + } + } + + public static void DecodeQuintBlock( + ref BitStream128 bitStream, + ref IntegerSequence listIntegerEncoded, + int numberBitsPerValue) + { + ReadOnlySpan interleavedBits = new byte[] { 3, 2, 2 }; + + // Implement the algorithm in section C.2.12 + Span m = stackalloc int[3]; + ulong encoded = 0; + int encodedBitsRead = 0; + + for (int i = 0; i < m.Length; i++) + { + m[i] = bitStream.ReadBits(numberBitsPerValue); + + uint encodedBits = (uint)bitStream.ReadBits(interleavedBits[i]); + + encoded |= encodedBits << encodedBitsRead; + encodedBitsRead += interleavedBits[i]; + } + + ReadOnlySpan encodings = GetQuintEncoding((int)encoded); + + for (int i = 0; i < 3; i++) + { + IntegerEncoded intEncoded = new IntegerEncoded(EIntegerEncoding.Quint, numberBitsPerValue) + { + BitValue = m[i], + QuintValue = encodings[i] + }; + + listIntegerEncoded.Add(ref intEncoded); + } + } + + public static void DecodeIntegerSequence( + ref IntegerSequence decodeIntegerSequence, + ref BitStream128 bitStream, + int maxRange, + int numberValues) + { + // Determine encoding parameters + IntegerEncoded intEncoded = CreateEncoding(maxRange); + + // Start decoding + int numberValuesDecoded = 0; + while (numberValuesDecoded < numberValues) + { + switch (intEncoded.GetEncoding()) + { + case EIntegerEncoding.Quint: + { + DecodeQuintBlock(ref bitStream, ref decodeIntegerSequence, intEncoded.NumberBits); + numberValuesDecoded += 3; + + break; + } + + case EIntegerEncoding.Trit: + { + DecodeTritBlock(ref bitStream, ref decodeIntegerSequence, intEncoded.NumberBits); + numberValuesDecoded += 5; + + break; + } + + case EIntegerEncoding.JustBits: + { + intEncoded.BitValue = bitStream.ReadBits(intEncoded.NumberBits); + decodeIntegerSequence.Add(ref intEncoded); + numberValuesDecoded++; + + break; + } + } + } + } + + private static ReadOnlySpan GetTritEncoding(int index) + { + return TritEncodings.Slice(index * 5, 5); + } + + private static ReadOnlySpan GetQuintEncoding(int index) + { + return QuintEncodings.Slice(index * 3, 3); + } + + private static ReadOnlySpan TritEncodings => new byte[] + { + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 2, 0, 0, 0, 0, + 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, + 2, 1, 0, 0, 0, 1, 0, 2, 0, 0, 0, 2, 0, 0, 0, + 1, 2, 0, 0, 0, 2, 2, 0, 0, 0, 2, 0, 2, 0, 0, + 0, 2, 2, 0, 0, 1, 2, 2, 0, 0, 2, 2, 2, 0, 0, + 2, 0, 2, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, + 2, 0, 1, 0, 0, 0, 1, 2, 0, 0, 0, 1, 1, 0, 0, + 1, 1, 1, 0, 0, 2, 1, 1, 0, 0, 1, 1, 2, 0, 0, + 0, 2, 1, 0, 0, 1, 2, 1, 0, 0, 2, 2, 1, 0, 0, + 2, 1, 2, 0, 0, 0, 0, 0, 2, 2, 1, 0, 0, 2, 2, + 2, 0, 0, 2, 2, 0, 0, 2, 2, 2, 0, 0, 0, 1, 0, + 1, 0, 0, 1, 0, 2, 0, 0, 1, 0, 0, 0, 2, 1, 0, + 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 2, 1, 0, 1, 0, + 1, 0, 2, 1, 0, 0, 2, 0, 1, 0, 1, 2, 0, 1, 0, + 2, 2, 0, 1, 0, 2, 0, 2, 1, 0, 0, 2, 2, 1, 0, + 1, 2, 2, 1, 0, 2, 2, 2, 1, 0, 2, 0, 2, 1, 0, + 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 2, 0, 1, 1, 0, + 0, 1, 2, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, + 2, 1, 1, 1, 0, 1, 1, 2, 1, 0, 0, 2, 1, 1, 0, + 1, 2, 1, 1, 0, 2, 2, 1, 1, 0, 2, 1, 2, 1, 0, + 0, 1, 0, 2, 2, 1, 1, 0, 2, 2, 2, 1, 0, 2, 2, + 1, 0, 2, 2, 2, 0, 0, 0, 2, 0, 1, 0, 0, 2, 0, + 2, 0, 0, 2, 0, 0, 0, 2, 2, 0, 0, 1, 0, 2, 0, + 1, 1, 0, 2, 0, 2, 1, 0, 2, 0, 1, 0, 2, 2, 0, + 0, 2, 0, 2, 0, 1, 2, 0, 2, 0, 2, 2, 0, 2, 0, + 2, 0, 2, 2, 0, 0, 2, 2, 2, 0, 1, 2, 2, 2, 0, + 2, 2, 2, 2, 0, 2, 0, 2, 2, 0, 0, 0, 1, 2, 0, + 1, 0, 1, 2, 0, 2, 0, 1, 2, 0, 0, 1, 2, 2, 0, + 0, 1, 1, 2, 0, 1, 1, 1, 2, 0, 2, 1, 1, 2, 0, + 1, 1, 2, 2, 0, 0, 2, 1, 2, 0, 1, 2, 1, 2, 0, + 2, 2, 1, 2, 0, 2, 1, 2, 2, 0, 0, 2, 0, 2, 2, + 1, 2, 0, 2, 2, 2, 2, 0, 2, 2, 2, 0, 2, 2, 2, + 0, 0, 0, 0, 2, 1, 0, 0, 0, 2, 2, 0, 0, 0, 2, + 0, 0, 2, 0, 2, 0, 1, 0, 0, 2, 1, 1, 0, 0, 2, + 2, 1, 0, 0, 2, 1, 0, 2, 0, 2, 0, 2, 0, 0, 2, + 1, 2, 0, 0, 2, 2, 2, 0, 0, 2, 2, 0, 2, 0, 2, + 0, 2, 2, 0, 2, 1, 2, 2, 0, 2, 2, 2, 2, 0, 2, + 2, 0, 2, 0, 2, 0, 0, 1, 0, 2, 1, 0, 1, 0, 2, + 2, 0, 1, 0, 2, 0, 1, 2, 0, 2, 0, 1, 1, 0, 2, + 1, 1, 1, 0, 2, 2, 1, 1, 0, 2, 1, 1, 2, 0, 2, + 0, 2, 1, 0, 2, 1, 2, 1, 0, 2, 2, 2, 1, 0, 2, + 2, 1, 2, 0, 2, 0, 2, 2, 2, 2, 1, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, + 1, 0, 0, 0, 1, 2, 0, 0, 0, 1, 0, 0, 2, 0, 1, + 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 2, 1, 0, 0, 1, + 1, 0, 2, 0, 1, 0, 2, 0, 0, 1, 1, 2, 0, 0, 1, + 2, 2, 0, 0, 1, 2, 0, 2, 0, 1, 0, 2, 2, 0, 1, + 1, 2, 2, 0, 1, 2, 2, 2, 0, 1, 2, 0, 2, 0, 1, + 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 2, 0, 1, 0, 1, + 0, 1, 2, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, + 2, 1, 1, 0, 1, 1, 1, 2, 0, 1, 0, 2, 1, 0, 1, + 1, 2, 1, 0, 1, 2, 2, 1, 0, 1, 2, 1, 2, 0, 1, + 0, 0, 1, 2, 2, 1, 0, 1, 2, 2, 2, 0, 1, 2, 2, + 0, 1, 2, 2, 2, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, + 2, 0, 0, 1, 1, 0, 0, 2, 1, 1, 0, 1, 0, 1, 1, + 1, 1, 0, 1, 1, 2, 1, 0, 1, 1, 1, 0, 2, 1, 1, + 0, 2, 0, 1, 1, 1, 2, 0, 1, 1, 2, 2, 0, 1, 1, + 2, 0, 2, 1, 1, 0, 2, 2, 1, 1, 1, 2, 2, 1, 1, + 2, 2, 2, 1, 1, 2, 0, 2, 1, 1, 0, 0, 1, 1, 1, + 1, 0, 1, 1, 1, 2, 0, 1, 1, 1, 0, 1, 2, 1, 1, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, + 1, 1, 2, 1, 1, 0, 2, 1, 1, 1, 1, 2, 1, 1, 1, + 2, 2, 1, 1, 1, 2, 1, 2, 1, 1, 0, 1, 1, 2, 2, + 1, 1, 1, 2, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2, 2, + 0, 0, 0, 2, 1, 1, 0, 0, 2, 1, 2, 0, 0, 2, 1, + 0, 0, 2, 2, 1, 0, 1, 0, 2, 1, 1, 1, 0, 2, 1, + 2, 1, 0, 2, 1, 1, 0, 2, 2, 1, 0, 2, 0, 2, 1, + 1, 2, 0, 2, 1, 2, 2, 0, 2, 1, 2, 0, 2, 2, 1, + 0, 2, 2, 2, 1, 1, 2, 2, 2, 1, 2, 2, 2, 2, 1, + 2, 0, 2, 2, 1, 0, 0, 1, 2, 1, 1, 0, 1, 2, 1, + 2, 0, 1, 2, 1, 0, 1, 2, 2, 1, 0, 1, 1, 2, 1, + 1, 1, 1, 2, 1, 2, 1, 1, 2, 1, 1, 1, 2, 2, 1, + 0, 2, 1, 2, 1, 1, 2, 1, 2, 1, 2, 2, 1, 2, 1, + 2, 1, 2, 2, 1, 0, 2, 1, 2, 2, 1, 2, 1, 2, 2, + 2, 2, 1, 2, 2, 2, 1, 2, 2, 2, 0, 0, 0, 1, 2, + 1, 0, 0, 1, 2, 2, 0, 0, 1, 2, 0, 0, 2, 1, 2, + 0, 1, 0, 1, 2, 1, 1, 0, 1, 2, 2, 1, 0, 1, 2, + 1, 0, 2, 1, 2, 0, 2, 0, 1, 2, 1, 2, 0, 1, 2, + 2, 2, 0, 1, 2, 2, 0, 2, 1, 2, 0, 2, 2, 1, 2, + 1, 2, 2, 1, 2, 2, 2, 2, 1, 2, 2, 0, 2, 1, 2, + 0, 0, 1, 1, 2, 1, 0, 1, 1, 2, 2, 0, 1, 1, 2, + 0, 1, 2, 1, 2, 0, 1, 1, 1, 2, 1, 1, 1, 1, 2, + 2, 1, 1, 1, 2, 1, 1, 2, 1, 2, 0, 2, 1, 1, 2, + 1, 2, 1, 1, 2, 2, 2, 1, 1, 2, 2, 1, 2, 1, 2, + 0, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 1, 2, 2, 2 + }; + + private static ReadOnlySpan QuintEncodings => new byte[] + { + 0, 0, 0, 1, 0, 0, 2, 0, 0, 3, 0, 0, 4, 0, 0, + 0, 4, 0, 4, 4, 0, 4, 4, 4, 0, 1, 0, 1, 1, 0, + 2, 1, 0, 3, 1, 0, 4, 1, 0, 1, 4, 0, 4, 4, 1, + 4, 4, 4, 0, 2, 0, 1, 2, 0, 2, 2, 0, 3, 2, 0, + 4, 2, 0, 2, 4, 0, 4, 4, 2, 4, 4, 4, 0, 3, 0, + 1, 3, 0, 2, 3, 0, 3, 3, 0, 4, 3, 0, 3, 4, 0, + 4, 4, 3, 4, 4, 4, 0, 0, 1, 1, 0, 1, 2, 0, 1, + 3, 0, 1, 4, 0, 1, 0, 4, 1, 4, 0, 4, 0, 4, 4, + 0, 1, 1, 1, 1, 1, 2, 1, 1, 3, 1, 1, 4, 1, 1, + 1, 4, 1, 4, 1, 4, 1, 4, 4, 0, 2, 1, 1, 2, 1, + 2, 2, 1, 3, 2, 1, 4, 2, 1, 2, 4, 1, 4, 2, 4, + 2, 4, 4, 0, 3, 1, 1, 3, 1, 2, 3, 1, 3, 3, 1, + 4, 3, 1, 3, 4, 1, 4, 3, 4, 3, 4, 4, 0, 0, 2, + 1, 0, 2, 2, 0, 2, 3, 0, 2, 4, 0, 2, 0, 4, 2, + 2, 0, 4, 3, 0, 4, 0, 1, 2, 1, 1, 2, 2, 1, 2, + 3, 1, 2, 4, 1, 2, 1, 4, 2, 2, 1, 4, 3, 1, 4, + 0, 2, 2, 1, 2, 2, 2, 2, 2, 3, 2, 2, 4, 2, 2, + 2, 4, 2, 2, 2, 4, 3, 2, 4, 0, 3, 2, 1, 3, 2, + 2, 3, 2, 3, 3, 2, 4, 3, 2, 3, 4, 2, 2, 3, 4, + 3, 3, 4, 0, 0, 3, 1, 0, 3, 2, 0, 3, 3, 0, 3, + 4, 0, 3, 0, 4, 3, 0, 0, 4, 1, 0, 4, 0, 1, 3, + 1, 1, 3, 2, 1, 3, 3, 1, 3, 4, 1, 3, 1, 4, 3, + 0, 1, 4, 1, 1, 4, 0, 2, 3, 1, 2, 3, 2, 2, 3, + 3, 2, 3, 4, 2, 3, 2, 4, 3, 0, 2, 4, 1, 2, 4, + 0, 3, 3, 1, 3, 3, 2, 3, 3, 3, 3, 3, 4, 3, 3, + 3, 4, 3, 0, 3, 4, 1, 3, 4 + }; + } +} diff --git a/Ryujinx.Graphics.Texture/Astc/IntegerSequence.cs b/Ryujinx.Graphics.Texture/Astc/IntegerSequence.cs new file mode 100644 index 0000000000..367b68095e --- /dev/null +++ b/Ryujinx.Graphics.Texture/Astc/IntegerSequence.cs @@ -0,0 +1,31 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Texture.Astc +{ + [StructLayout(LayoutKind.Sequential, Size = IntegerEncoded.StructSize * Capacity + sizeof(int))] + internal struct IntegerSequence + { + private const int Capacity = 100; + + private int _length; + private IntegerEncoded _start; + + public Span List => MemoryMarshal.CreateSpan(ref _start, _length); + + public void Reset() => _length = 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(ref IntegerEncoded item) + { + Debug.Assert(_length < Capacity); + + int oldLength = _length; + _length++; + + List[oldLength] = item; + } + } +} diff --git a/Ryujinx.Graphics.Texture/BlockLinearConstants.cs b/Ryujinx.Graphics.Texture/BlockLinearConstants.cs new file mode 100644 index 0000000000..d95691cf65 --- /dev/null +++ b/Ryujinx.Graphics.Texture/BlockLinearConstants.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Texture +{ + static class BlockLinearConstants + { + public const int GobStride = 64; + public const int GobHeight = 8; + + public const int GobSize = GobStride * GobHeight; + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Texture/BlockLinearLayout.cs b/Ryujinx.Graphics.Texture/BlockLinearLayout.cs new file mode 100644 index 0000000000..b95db70290 --- /dev/null +++ b/Ryujinx.Graphics.Texture/BlockLinearLayout.cs @@ -0,0 +1,101 @@ +using Ryujinx.Common; +using System.Runtime.CompilerServices; + +using static Ryujinx.Graphics.Texture.BlockLinearConstants; + +namespace Ryujinx.Graphics.Texture +{ + class BlockLinearLayout + { + private struct RobAndSliceSizes + { + public int RobSize; + public int SliceSize; + + public RobAndSliceSizes(int robSize, int sliceSize) + { + RobSize = robSize; + SliceSize = sliceSize; + } + } + + private int _texBpp; + + private int _bhMask; + private int _bdMask; + + private int _bhShift; + private int _bdShift; + private int _bppShift; + + private int _xShift; + + private int _robSize; + private int _sliceSize; + + public BlockLinearLayout( + int width, + int height, + int depth, + int gobBlocksInY, + int gobBlocksInZ, + int bpp) + { + _texBpp = bpp; + + _bppShift = BitUtils.CountTrailingZeros32(bpp); + + _bhMask = gobBlocksInY - 1; + _bdMask = gobBlocksInZ - 1; + + _bhShift = BitUtils.CountTrailingZeros32(gobBlocksInY); + _bdShift = BitUtils.CountTrailingZeros32(gobBlocksInZ); + + _xShift = BitUtils.CountTrailingZeros32(GobSize * gobBlocksInY * gobBlocksInZ); + + RobAndSliceSizes rsSizes = GetRobAndSliceSizes(width, height, gobBlocksInY, gobBlocksInZ); + + _robSize = rsSizes.RobSize; + _sliceSize = rsSizes.SliceSize; + } + + private RobAndSliceSizes GetRobAndSliceSizes(int width, int height, int gobBlocksInY, int gobBlocksInZ) + { + int widthInGobs = BitUtils.DivRoundUp(width * _texBpp, GobStride); + + int robSize = GobSize * gobBlocksInY * gobBlocksInZ * widthInGobs; + + int sliceSize = BitUtils.DivRoundUp(height, gobBlocksInY * GobHeight) * robSize; + + return new RobAndSliceSizes(robSize, sliceSize); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetOffset(int x, int y, int z) + { + return GetOffsetWithLineOffset(x << _bppShift, y, z); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetOffsetWithLineOffset(int x, int y, int z) + { + int yh = y / GobHeight; + + int offset = (z >> _bdShift) * _sliceSize + (yh >> _bhShift) * _robSize; + + offset += (x / GobStride) << _xShift; + + offset += (yh & _bhMask) * GobSize; + + offset += ((z & _bdMask) * GobSize) << _bhShift; + + offset += ((x & 0x3f) >> 5) << 8; + offset += ((y & 0x07) >> 1) << 6; + offset += ((x & 0x1f) >> 4) << 5; + offset += ((y & 0x01) >> 0) << 4; + offset += ((x & 0x0f) >> 0) << 0; + + return offset; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Texture/LayoutConverter.cs b/Ryujinx.Graphics.Texture/LayoutConverter.cs new file mode 100644 index 0000000000..2c3d641bb2 --- /dev/null +++ b/Ryujinx.Graphics.Texture/LayoutConverter.cs @@ -0,0 +1,290 @@ +using Ryujinx.Common; +using System; + +using static Ryujinx.Graphics.Texture.BlockLinearConstants; + +namespace Ryujinx.Graphics.Texture +{ + public static class LayoutConverter + { + private const int HostStrideAlignment = 4; + + public static Span ConvertBlockLinearToLinear( + int width, + int height, + int depth, + int levels, + int layers, + int blockWidth, + int blockHeight, + int bytesPerPixel, + int gobBlocksInY, + int gobBlocksInZ, + int gobBlocksInTileX, + SizeInfo sizeInfo, + ReadOnlySpan data) + { + int outSize = GetTextureSize( + width, + height, + depth, + levels, + layers, + blockWidth, + blockHeight, + bytesPerPixel); + + Span output = new byte[outSize]; + + int outOffs = 0; + + int wAlignment = gobBlocksInTileX * (GobStride / bytesPerPixel); + + int mipGobBlocksInY = gobBlocksInY; + int mipGobBlocksInZ = gobBlocksInZ; + + for (int level = 0; level < levels; level++) + { + int w = Math.Max(1, width >> level); + int h = Math.Max(1, height >> level); + int d = Math.Max(1, depth >> level); + + w = BitUtils.DivRoundUp(w, blockWidth); + h = BitUtils.DivRoundUp(h, blockHeight); + + while (h <= (mipGobBlocksInY >> 1) * GobHeight && mipGobBlocksInY != 1) + { + mipGobBlocksInY >>= 1; + } + + while (d <= (mipGobBlocksInZ >> 1) && mipGobBlocksInZ != 1) + { + mipGobBlocksInZ >>= 1; + } + + int strideTrunc = BitUtils.AlignDown(w * bytesPerPixel, 16); + + int xStart = strideTrunc / bytesPerPixel; + + int stride = BitUtils.AlignUp(w * bytesPerPixel, HostStrideAlignment); + int wAligned = BitUtils.AlignUp(w, wAlignment); + + BlockLinearLayout layoutConverter = new BlockLinearLayout( + wAligned, + h, + d, + mipGobBlocksInY, + mipGobBlocksInZ, + bytesPerPixel); + + for (int layer = 0; layer < layers; layer++) + { + int inBaseOffset = layer * sizeInfo.LayerSize + sizeInfo.GetMipOffset(level); + + for (int z = 0; z < d; z++) + for (int y = 0; y < h; y++) + { + for (int x = 0; x < strideTrunc; x += 16) + { + int offset = inBaseOffset + layoutConverter.GetOffsetWithLineOffset(x, y, z); + + Span dest = output.Slice(outOffs + x, 16); + + data.Slice(offset, 16).CopyTo(dest); + } + + for (int x = xStart; x < w; x++) + { + int offset = inBaseOffset + layoutConverter.GetOffset(x, y, z); + + Span dest = output.Slice(outOffs + x * bytesPerPixel, bytesPerPixel); + + data.Slice(offset, bytesPerPixel).CopyTo(dest); + } + + outOffs += stride; + } + } + } + + return output; + } + + public static Span ConvertLinearStridedToLinear( + int width, + int height, + int blockWidth, + int blockHeight, + int stride, + int bytesPerPixel, + ReadOnlySpan data) + { + int w = BitUtils.DivRoundUp(width, blockWidth); + int h = BitUtils.DivRoundUp(height, blockHeight); + + int outStride = BitUtils.AlignUp(w * bytesPerPixel, HostStrideAlignment); + + Span output = new byte[h * outStride]; + + int outOffs = 0; + + for (int y = 0; y < h; y++) + { + for (int x = 0; x < w; x++) + { + int offset = y * stride + x * bytesPerPixel; + + Span dest = output.Slice(outOffs + x * bytesPerPixel, bytesPerPixel); + + data.Slice(offset, bytesPerPixel).CopyTo(dest); + } + + outOffs += outStride; + } + + return output; + } + + public static Span ConvertLinearToBlockLinear( + int width, + int height, + int depth, + int levels, + int layers, + int blockWidth, + int blockHeight, + int bytesPerPixel, + int gobBlocksInY, + int gobBlocksInZ, + int gobBlocksInTileX, + SizeInfo sizeInfo, + ReadOnlySpan data) + { + Span output = new byte[sizeInfo.TotalSize]; + + int inOffs = 0; + + int wAlignment = gobBlocksInTileX * (GobStride / bytesPerPixel); + + int mipGobBlocksInY = gobBlocksInY; + int mipGobBlocksInZ = gobBlocksInZ; + + for (int level = 0; level < levels; level++) + { + int w = Math.Max(1, width >> level); + int h = Math.Max(1, height >> level); + int d = Math.Max(1, depth >> level); + + w = BitUtils.DivRoundUp(w, blockWidth); + h = BitUtils.DivRoundUp(h, blockHeight); + + while (h <= (mipGobBlocksInY >> 1) * GobHeight && mipGobBlocksInY != 1) + { + mipGobBlocksInY >>= 1; + } + + while (d <= (mipGobBlocksInZ >> 1) && mipGobBlocksInZ != 1) + { + mipGobBlocksInZ >>= 1; + } + + int stride = BitUtils.AlignUp(w * bytesPerPixel, HostStrideAlignment); + int wAligned = BitUtils.AlignUp(w, wAlignment); + + BlockLinearLayout layoutConverter = new BlockLinearLayout( + wAligned, + h, + d, + mipGobBlocksInY, + mipGobBlocksInZ, + bytesPerPixel); + + for (int layer = 0; layer < layers; layer++) + { + int outBaseOffset = layer * sizeInfo.LayerSize + sizeInfo.GetMipOffset(level); + + for (int z = 0; z < d; z++) + for (int y = 0; y < h; y++) + { + for (int x = 0; x < w; x++) + { + int offset = outBaseOffset + layoutConverter.GetOffset(x, y, z); + + Span dest = output.Slice(offset, bytesPerPixel); + + data.Slice(inOffs + x * bytesPerPixel, bytesPerPixel).CopyTo(dest); + } + + inOffs += stride; + } + } + } + + return output; + } + + public static Span ConvertLinearToLinearStrided( + int width, + int height, + int blockWidth, + int blockHeight, + int stride, + int bytesPerPixel, + ReadOnlySpan data) + { + int w = BitUtils.DivRoundUp(width, blockWidth); + int h = BitUtils.DivRoundUp(height, blockHeight); + + int inStride = BitUtils.AlignUp(w * bytesPerPixel, HostStrideAlignment); + + Span output = new byte[h * stride]; + + int inOffs = 0; + + for (int y = 0; y < h; y++) + { + for (int x = 0; x < w; x++) + { + int offset = y * stride + x * bytesPerPixel; + + Span dest = output.Slice(offset, bytesPerPixel); + + data.Slice(inOffs + x * bytesPerPixel, bytesPerPixel).CopyTo(dest); + } + + inOffs += inStride; + } + + return output; + } + + private static int GetTextureSize( + int width, + int height, + int depth, + int levels, + int layers, + int blockWidth, + int blockHeight, + int bytesPerPixel) + { + int layerSize = 0; + + for (int level = 0; level < levels; level++) + { + int w = Math.Max(1, width >> level); + int h = Math.Max(1, height >> level); + int d = Math.Max(1, depth >> level); + + w = BitUtils.DivRoundUp(w, blockWidth); + h = BitUtils.DivRoundUp(h, blockHeight); + + int stride = BitUtils.AlignUp(w * bytesPerPixel, HostStrideAlignment); + + layerSize += stride * h * d; + } + + return layerSize * layers; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Texture/OffsetCalculator.cs b/Ryujinx.Graphics.Texture/OffsetCalculator.cs new file mode 100644 index 0000000000..bb5d606ca4 --- /dev/null +++ b/Ryujinx.Graphics.Texture/OffsetCalculator.cs @@ -0,0 +1,55 @@ +using Ryujinx.Common; + +using static Ryujinx.Graphics.Texture.BlockLinearConstants; + +namespace Ryujinx.Graphics.Texture +{ + public class OffsetCalculator + { + private int _stride; + private bool _isLinear; + private int _bytesPerPixel; + + private BlockLinearLayout _layoutConverter; + + public OffsetCalculator( + int width, + int height, + int stride, + bool isLinear, + int gobBlocksInY, + int bytesPerPixel) + { + _stride = stride; + _isLinear = isLinear; + _bytesPerPixel = bytesPerPixel; + + int wAlignment = GobStride / bytesPerPixel; + + int wAligned = BitUtils.AlignUp(width, wAlignment); + + if (!isLinear) + { + _layoutConverter = new BlockLinearLayout( + wAligned, + height, + 1, + gobBlocksInY, + 1, + bytesPerPixel); + } + } + + public int GetOffset(int x, int y) + { + if (_isLinear) + { + return x * _bytesPerPixel + y * _stride; + } + else + { + return _layoutConverter.GetOffset(x, y, 0); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Texture/Ryujinx.Graphics.Texture.csproj b/Ryujinx.Graphics.Texture/Ryujinx.Graphics.Texture.csproj new file mode 100644 index 0000000000..2009fbeef9 --- /dev/null +++ b/Ryujinx.Graphics.Texture/Ryujinx.Graphics.Texture.csproj @@ -0,0 +1,13 @@ + + + + + + + + netcoreapp3.0 + win-x64;osx-x64;linux-x64 + true + + + diff --git a/Ryujinx.Graphics.Texture/Size.cs b/Ryujinx.Graphics.Texture/Size.cs new file mode 100644 index 0000000000..4e070c9088 --- /dev/null +++ b/Ryujinx.Graphics.Texture/Size.cs @@ -0,0 +1,19 @@ +using Ryujinx.Common; +using System; + +namespace Ryujinx.Graphics.Texture +{ + public struct Size + { + public int Width { get; } + public int Height { get; } + public int Depth { get; } + + public Size(int width, int height, int depth) + { + Width = width; + Height = height; + Depth = depth; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Texture/SizeCalculator.cs b/Ryujinx.Graphics.Texture/SizeCalculator.cs new file mode 100644 index 0000000000..11385d28d3 --- /dev/null +++ b/Ryujinx.Graphics.Texture/SizeCalculator.cs @@ -0,0 +1,218 @@ +using Ryujinx.Common; +using System; + +using static Ryujinx.Graphics.Texture.BlockLinearConstants; + +namespace Ryujinx.Graphics.Texture +{ + public static class SizeCalculator + { + private const int StrideAlignment = 32; + + public static SizeInfo GetBlockLinearTextureSize( + int width, + int height, + int depth, + int levels, + int layers, + int blockWidth, + int blockHeight, + int bytesPerPixel, + int gobBlocksInY, + int gobBlocksInZ, + int gobBlocksInTileX) + { + bool is3D = depth > 1; + + int layerSize = 0; + + int[] allOffsets = new int[levels * layers * depth]; + int[] mipOffsets = new int[levels]; + + int mipGobBlocksInY = gobBlocksInY; + int mipGobBlocksInZ = gobBlocksInZ; + + for (int level = 0; level < levels; level++) + { + int w = Math.Max(1, width >> level); + int h = Math.Max(1, height >> level); + int d = Math.Max(1, depth >> level); + + w = BitUtils.DivRoundUp(w, blockWidth); + h = BitUtils.DivRoundUp(h, blockHeight); + + while (h <= (mipGobBlocksInY >> 1) * GobHeight && mipGobBlocksInY != 1) + { + mipGobBlocksInY >>= 1; + } + + while (d <= (mipGobBlocksInZ >> 1) && mipGobBlocksInZ != 1) + { + mipGobBlocksInZ >>= 1; + } + + int widthInGobs = BitUtils.AlignUp(BitUtils.DivRoundUp(w * bytesPerPixel, GobStride), gobBlocksInTileX); + + int totalBlocksOfGobsInZ = BitUtils.DivRoundUp(d, mipGobBlocksInZ); + int totalBlocksOfGobsInY = BitUtils.DivRoundUp(BitUtils.DivRoundUp(h, GobHeight), mipGobBlocksInY); + + int robSize = widthInGobs * mipGobBlocksInY * mipGobBlocksInZ * GobSize; + + if (is3D) + { + int gobSize = mipGobBlocksInY * GobSize; + + int sliceSize = totalBlocksOfGobsInY * widthInGobs * gobSize; + + int baseOffset = layerSize; + + int mask = gobBlocksInZ - 1; + + for (int z = 0; z < d; z++) + { + int zLow = z & mask; + int zHigh = z & ~mask; + + allOffsets[z * levels + level] = baseOffset + zLow * gobSize + zHigh * sliceSize; + } + } + + mipOffsets[level] = layerSize; + + layerSize += totalBlocksOfGobsInZ * totalBlocksOfGobsInY * robSize; + } + + layerSize = AlignLayerSize( + layerSize, + height, + depth, + blockHeight, + gobBlocksInY, + gobBlocksInZ); + + if (!is3D) + { + for (int layer = 0; layer < layers; layer++) + { + int baseIndex = layer * levels; + int baseOffset = layer * layerSize; + + for (int level = 0; level < levels; level++) + { + allOffsets[baseIndex + level] = baseOffset + mipOffsets[level]; + } + } + } + + int totalSize = layerSize * layers; + + return new SizeInfo(mipOffsets, allOffsets, levels, layerSize, totalSize); + } + + public static SizeInfo GetLinearTextureSize(int stride, int height, int blockHeight) + { + // Non-2D or mipmapped linear textures are not supported by the Switch GPU, + // so we only need to handle a single case (2D textures without mipmaps). + int totalSize = stride * BitUtils.DivRoundUp(height, blockHeight); + + return new SizeInfo(new int[] { 0 }, new int[] { 0 }, 1, totalSize, totalSize); + } + + private static int AlignLayerSize( + int size, + int height, + int depth, + int blockHeight, + int gobBlocksInY, + int gobBlocksInZ) + { + height = BitUtils.DivRoundUp(height, blockHeight); + + while (height <= (gobBlocksInY >> 1) * GobHeight && gobBlocksInY != 1) + { + gobBlocksInY >>= 1; + } + + while (depth <= (gobBlocksInZ >> 1) && gobBlocksInZ != 1) + { + gobBlocksInZ >>= 1; + } + + int blockOfGobsSize = gobBlocksInY * gobBlocksInZ * GobSize; + + int sizeInBlockOfGobs = size / blockOfGobsSize; + + if (size != sizeInBlockOfGobs * blockOfGobsSize) + { + size = (sizeInBlockOfGobs + 1) * blockOfGobsSize; + } + + return size; + } + + public static Size GetBlockLinearAlignedSize( + int width, + int height, + int depth, + int blockWidth, + int blockHeight, + int bytesPerPixel, + int gobBlocksInY, + int gobBlocksInZ, + int gobBlocksInTileX) + { + width = BitUtils.DivRoundUp(width, blockWidth); + height = BitUtils.DivRoundUp(height, blockHeight); + + int gobWidth = gobBlocksInTileX * (GobStride / bytesPerPixel); + + int blockOfGobsHeight = gobBlocksInY * GobHeight; + int blockOfGobsDepth = gobBlocksInZ; + + width = BitUtils.AlignUp(width, gobWidth); + height = BitUtils.AlignUp(height, blockOfGobsHeight); + depth = BitUtils.AlignUp(depth, blockOfGobsDepth); + + return new Size(width, height, depth); + } + + public static Size GetLinearAlignedSize( + int width, + int height, + int blockWidth, + int blockHeight, + int bytesPerPixel) + { + width = BitUtils.DivRoundUp(width, blockWidth); + height = BitUtils.DivRoundUp(height, blockHeight); + + int widthAlignment = StrideAlignment / bytesPerPixel; + + width = BitUtils.AlignUp(width, widthAlignment); + + return new Size(width, height, 1); + } + + public static (int, int) GetMipGobBlockSizes( + int height, + int depth, + int blockHeight, + int gobBlocksInY, + int gobBlocksInZ) + { + height = BitUtils.DivRoundUp(height, blockHeight); + + while (height <= (gobBlocksInY >> 1) * GobHeight && gobBlocksInY != 1) + { + gobBlocksInY >>= 1; + } + + while (depth <= (gobBlocksInZ >> 1) && gobBlocksInZ != 1) + { + gobBlocksInZ >>= 1; + } + + return (gobBlocksInY, gobBlocksInZ); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Texture/SizeInfo.cs b/Ryujinx.Graphics.Texture/SizeInfo.cs new file mode 100644 index 0000000000..37d824cc55 --- /dev/null +++ b/Ryujinx.Graphics.Texture/SizeInfo.cs @@ -0,0 +1,58 @@ +using Ryujinx.Common; +using System; + +namespace Ryujinx.Graphics.Texture +{ + public struct SizeInfo + { + private int[] _mipOffsets; + private int[] _allOffsets; + + private int _levels; + + public int LayerSize { get; } + public int TotalSize { get; } + + public SizeInfo( + int[] mipOffsets, + int[] allOffsets, + int levels, + int layerSize, + int totalSize) + { + _mipOffsets = mipOffsets; + _allOffsets = allOffsets; + _levels = levels; + LayerSize = layerSize; + TotalSize = totalSize; + } + + public int GetMipOffset(int level) + { + if ((uint)level > _mipOffsets.Length) + { + throw new ArgumentOutOfRangeException(nameof(level)); + } + + return _mipOffsets[level]; + } + + public bool FindView(int offset, int size, out int firstLayer, out int firstLevel) + { + int index = Array.BinarySearch(_allOffsets, offset); + + if (index < 0) + { + firstLayer = 0; + firstLevel = 0; + + return false; + } + + firstLayer = index / _levels; + firstLevel = index - (firstLayer * _levels); + + return true; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/DepthCompareFunc.cs b/Ryujinx.Graphics/DepthCompareFunc.cs deleted file mode 100644 index 24c8854a4b..0000000000 --- a/Ryujinx.Graphics/DepthCompareFunc.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Ryujinx.Graphics -{ - public enum DepthCompareFunc - { - Never = 0, - Less = 1, - Equal = 2, - LEqual = 3, - Greater = 4, - NotEqual = 5, - GEqual = 6, - Always = 7 - } -} diff --git a/Ryujinx.Graphics/Gal/EmbeddedResource.cs b/Ryujinx.Graphics/Gal/EmbeddedResource.cs deleted file mode 100644 index ba6624991e..0000000000 --- a/Ryujinx.Graphics/Gal/EmbeddedResource.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.IO; -using System.Reflection; - -namespace Ryujinx.Graphics.Gal -{ - static class EmbeddedResource - { - public static string GetString(string name) - { - Assembly asm = typeof(EmbeddedResource).Assembly; - - using (Stream resStream = asm.GetManifestResourceStream(name)) - { - StreamReader reader = new StreamReader(resStream); - - return reader.ReadToEnd(); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalBlendEquation.cs b/Ryujinx.Graphics/Gal/GalBlendEquation.cs deleted file mode 100644 index 7757faae90..0000000000 --- a/Ryujinx.Graphics/Gal/GalBlendEquation.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public enum GalBlendEquation - { - FuncAdd = 1, - FuncSubtract = 2, - FuncReverseSubtract = 3, - Min = 4, - Max = 5, - - FuncAddGl = 0x8006, - FuncSubtractGl = 0x8007, - FuncReverseSubtractGl = 0x8008, - MinGl = 0x800a, - MaxGl = 0x800b - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalBlendFactor.cs b/Ryujinx.Graphics/Gal/GalBlendFactor.cs deleted file mode 100644 index f70b050118..0000000000 --- a/Ryujinx.Graphics/Gal/GalBlendFactor.cs +++ /dev/null @@ -1,45 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public enum GalBlendFactor - { - Zero = 0x1, - One = 0x2, - SrcColor = 0x3, - OneMinusSrcColor = 0x4, - SrcAlpha = 0x5, - OneMinusSrcAlpha = 0x6, - DstAlpha = 0x7, - OneMinusDstAlpha = 0x8, - DstColor = 0x9, - OneMinusDstColor = 0xa, - SrcAlphaSaturate = 0xb, - Src1Color = 0x10, - OneMinusSrc1Color = 0x11, - Src1Alpha = 0x12, - OneMinusSrc1Alpha = 0x13, - ConstantColor = 0x61, - OneMinusConstantColor = 0x62, - ConstantAlpha = 0x63, - OneMinusConstantAlpha = 0x64, - - ZeroGl = 0x4000, - OneGl = 0x4001, - SrcColorGl = 0x4300, - OneMinusSrcColorGl = 0x4301, - SrcAlphaGl = 0x4302, - OneMinusSrcAlphaGl = 0x4303, - DstAlphaGl = 0x4304, - OneMinusDstAlphaGl = 0x4305, - DstColorGl = 0x4306, - OneMinusDstColorGl = 0x4307, - SrcAlphaSaturateGl = 0x4308, - ConstantColorGl = 0xc001, - OneMinusConstantColorGl = 0xc002, - ConstantAlphaGl = 0xc003, - OneMinusConstantAlphaGl = 0xc004, - Src1ColorGl = 0xc900, - OneMinusSrc1ColorGl = 0xc901, - Src1AlphaGl = 0xc902, - OneMinusSrc1AlphaGl = 0xc903 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalClearBufferFlags.cs b/Ryujinx.Graphics/Gal/GalClearBufferFlags.cs deleted file mode 100644 index 8565051cac..0000000000 --- a/Ryujinx.Graphics/Gal/GalClearBufferFlags.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace Ryujinx.Graphics.Gal -{ - [Flags] - public enum GalClearBufferFlags - { - Depth = 1 << 0, - Stencil = 1 << 1, - ColorRed = 1 << 2, - ColorGreen = 1 << 3, - ColorBlue = 1 << 4, - ColorAlpha = 1 << 5 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalColorF.cs b/Ryujinx.Graphics/Gal/GalColorF.cs deleted file mode 100644 index e915870c2c..0000000000 --- a/Ryujinx.Graphics/Gal/GalColorF.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public struct GalColorF - { - public float Red { get; private set; } - public float Green { get; private set; } - public float Blue { get; private set; } - public float Alpha { get; private set; } - - public GalColorF( - float red, - float green, - float blue, - float alpha) - { - Red = red; - Green = green; - Blue = blue; - Alpha = alpha; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalComparisonOp.cs b/Ryujinx.Graphics/Gal/GalComparisonOp.cs deleted file mode 100644 index f26a775337..0000000000 --- a/Ryujinx.Graphics/Gal/GalComparisonOp.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public enum GalComparisonOp - { - Never = 0x1, - Less = 0x2, - Equal = 0x3, - Lequal = 0x4, - Greater = 0x5, - NotEqual = 0x6, - Gequal = 0x7, - Always = 0x8 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalFrontFace.cs b/Ryujinx.Graphics/Gal/GalFrontFace.cs deleted file mode 100644 index 6cc4a80242..0000000000 --- a/Ryujinx.Graphics/Gal/GalFrontFace.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public enum GalFrontFace - { - Cw = 0x900, - Ccw = 0x901 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalImage.cs b/Ryujinx.Graphics/Gal/GalImage.cs deleted file mode 100644 index 1345704d29..0000000000 --- a/Ryujinx.Graphics/Gal/GalImage.cs +++ /dev/null @@ -1,87 +0,0 @@ -using Ryujinx.Graphics.Texture; - -namespace Ryujinx.Graphics.Gal -{ - public struct GalImage - { - public int Width; - public int Height; - - // FIXME: separate layer and depth - public int Depth; - public int LayerCount; - public int TileWidth; - public int GobBlockHeight; - public int GobBlockDepth; - public int Pitch; - public int MaxMipmapLevel; - - public GalImageFormat Format; - public GalMemoryLayout Layout; - public GalTextureSource XSource; - public GalTextureSource YSource; - public GalTextureSource ZSource; - public GalTextureSource WSource; - public GalTextureTarget TextureTarget; - - public GalImage( - int width, - int height, - int depth, - int layerCount, - int tileWidth, - int gobBlockHeight, - int gobBlockDepth, - GalMemoryLayout layout, - GalImageFormat format, - GalTextureTarget textureTarget, - int maxMipmapLevel = 1, - GalTextureSource xSource = GalTextureSource.Red, - GalTextureSource ySource = GalTextureSource.Green, - GalTextureSource zSource = GalTextureSource.Blue, - GalTextureSource wSource = GalTextureSource.Alpha) - { - Width = width; - Height = height; - LayerCount = layerCount; - Depth = depth; - TileWidth = tileWidth; - GobBlockHeight = gobBlockHeight; - GobBlockDepth = gobBlockDepth; - Layout = layout; - Format = format; - MaxMipmapLevel = maxMipmapLevel; - XSource = xSource; - YSource = ySource; - ZSource = zSource; - WSource = wSource; - TextureTarget = textureTarget; - - Pitch = ImageUtils.GetPitch(format, width); - } - - public bool SizeMatches(GalImage image, bool ignoreLayer = false) - { - if (ImageUtils.GetBytesPerPixel(Format) != - ImageUtils.GetBytesPerPixel(image.Format)) - { - return false; - } - - if (ImageUtils.GetAlignedWidth(this) != - ImageUtils.GetAlignedWidth(image)) - { - return false; - } - - bool result = Height == image.Height && Depth == image.Depth; - - if (!ignoreLayer) - { - result = result && LayerCount == image.LayerCount; - } - - return result; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalImageFormat.cs b/Ryujinx.Graphics/Gal/GalImageFormat.cs deleted file mode 100644 index 19ce6cfc49..0000000000 --- a/Ryujinx.Graphics/Gal/GalImageFormat.cs +++ /dev/null @@ -1,69 +0,0 @@ -// ReSharper disable InconsistentNaming -using System; - -namespace Ryujinx.Graphics.Gal -{ - [Flags] - public enum GalImageFormat - { - Astc2DStart, - Astc2D4x4, - Astc2D5x4, - Astc2D5x5, - Astc2D6x5, - Astc2D6x6, - Astc2D8x5, - Astc2D8x6, - Astc2D8x8, - Astc2D10x5, - Astc2D10x6, - Astc2D10x8, - Astc2D10x10, - Astc2D12x10, - Astc2D12x12, - Astc2DEnd, - - Rgba4, - Rgb565, - Bgr565, - Bgr5A1, - Rgb5A1, - R8, - Rg8, - Rgbx8, - Rgba8, - Bgra8, - Rgb10A2, - R16, - Rg16, - Rgba16, - R32, - Rg32, - Rgba32, - R11G11B10, - D16, - D24, - D32, - D24S8, - D32S8, - BC1, - BC2, - BC3, - BC4, - BC5, - BptcSfloat, - BptcUfloat, - BptcUnorm, - - Snorm = 1 << 26, - Unorm = 1 << 27, - Sint = 1 << 28, - Uint = 1 << 39, - Float = 1 << 30, - Srgb = 1 << 31, - - TypeMask = Snorm | Unorm | Sint | Uint | Float | Srgb, - - FormatMask = ~TypeMask - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalIndexFormat.cs b/Ryujinx.Graphics/Gal/GalIndexFormat.cs deleted file mode 100644 index 71a50cdba8..0000000000 --- a/Ryujinx.Graphics/Gal/GalIndexFormat.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public enum GalIndexFormat - { - Byte = 0, - Int16 = 1, - Int32 = 2 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalMemoryLayout.cs b/Ryujinx.Graphics/Gal/GalMemoryLayout.cs deleted file mode 100644 index 73fabf8c2a..0000000000 --- a/Ryujinx.Graphics/Gal/GalMemoryLayout.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public enum GalMemoryLayout - { - BlockLinear = 0, - Pitch = 1 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalPipelineState.cs b/Ryujinx.Graphics/Gal/GalPipelineState.cs deleted file mode 100644 index c044a55fc3..0000000000 --- a/Ryujinx.Graphics/Gal/GalPipelineState.cs +++ /dev/null @@ -1,130 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public struct ColorMaskState - { - private static readonly ColorMaskState DefaultBackingField = new ColorMaskState() - { - Red = true, - Green = true, - Blue = true, - Alpha = true - }; - - public static ColorMaskState Default => DefaultBackingField; - - public bool Red; - public bool Green; - public bool Blue; - public bool Alpha; - } - - public struct BlendState - { - private static readonly BlendState DefaultBackingField = new BlendState() - { - Enabled = false, - SeparateAlpha = false, - EquationRgb = GalBlendEquation.FuncAdd, - FuncSrcRgb = GalBlendFactor.One, - FuncDstRgb = GalBlendFactor.Zero, - EquationAlpha = GalBlendEquation.FuncAdd, - FuncSrcAlpha = GalBlendFactor.One, - FuncDstAlpha = GalBlendFactor.Zero - }; - - public static BlendState Default => DefaultBackingField; - - public bool Enabled; - public bool SeparateAlpha; - public GalBlendEquation EquationRgb; - public GalBlendFactor FuncSrcRgb; - public GalBlendFactor FuncDstRgb; - public GalBlendEquation EquationAlpha; - public GalBlendFactor FuncSrcAlpha; - public GalBlendFactor FuncDstAlpha; - } - - public class GalPipelineState - { - public const int Stages = 5; - public const int ConstBuffersPerStage = 18; - public const int RenderTargetsCount = 8; - - public long[][] ConstBufferKeys; - - public GalVertexBinding[] VertexBindings; - - public bool FramebufferSrgb; - - public float FlipX; - public float FlipY; - - public int Instance; - - public GalFrontFace FrontFace; - - public bool CullFaceEnabled; - public GalCullFace CullFace; - - public bool DepthTestEnabled; - public bool DepthWriteEnabled; - public GalComparisonOp DepthFunc; - public float DepthRangeNear; - public float DepthRangeFar; - - public bool StencilTestEnabled; - public bool StencilTwoSideEnabled; - - public GalComparisonOp StencilBackFuncFunc; - public int StencilBackFuncRef; - public uint StencilBackFuncMask; - public GalStencilOp StencilBackOpFail; - public GalStencilOp StencilBackOpZFail; - public GalStencilOp StencilBackOpZPass; - public uint StencilBackMask; - - public GalComparisonOp StencilFrontFuncFunc; - public int StencilFrontFuncRef; - public uint StencilFrontFuncMask; - public GalStencilOp StencilFrontOpFail; - public GalStencilOp StencilFrontOpZFail; - public GalStencilOp StencilFrontOpZPass; - public uint StencilFrontMask; - - public int ScissorTestCount; - public bool[] ScissorTestEnabled; - public int[] ScissorTestX; - public int[] ScissorTestY; - public int[] ScissorTestWidth; - public int[] ScissorTestHeight; - - public bool BlendIndependent; - public BlendState[] Blends; - - public bool ColorMaskCommon; - public ColorMaskState[] ColorMasks; - - public bool PrimitiveRestartEnabled; - public uint PrimitiveRestartIndex; - - public GalPipelineState() - { - ConstBufferKeys = new long[Stages][]; - - for (int stage = 0; stage < Stages; stage++) - { - ConstBufferKeys[stage] = new long[ConstBuffersPerStage]; - } - - Blends = new BlendState[RenderTargetsCount]; - - ScissorTestEnabled = new bool[RenderTargetsCount]; - ScissorTestY = new int[RenderTargetsCount]; - ScissorTestX = new int[RenderTargetsCount]; - ScissorTestWidth = new int[RenderTargetsCount]; - ScissorTestHeight = new int[RenderTargetsCount]; - - ColorMasks = new ColorMaskState[RenderTargetsCount]; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalPrimitiveType.cs b/Ryujinx.Graphics/Gal/GalPrimitiveType.cs deleted file mode 100644 index ce084149d1..0000000000 --- a/Ryujinx.Graphics/Gal/GalPrimitiveType.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public enum GalPrimitiveType - { - Points = 0x0, - Lines = 0x1, - LineLoop = 0x2, - LineStrip = 0x3, - Triangles = 0x4, - TriangleStrip = 0x5, - TriangleFan = 0x6, - Quads = 0x7, - QuadStrip = 0x8, - Polygon = 0x9, - LinesAdjacency = 0xa, - LineStripAdjacency = 0xb, - TrianglesAdjacency = 0xc, - TriangleStripAdjacency = 0xd, - Patches = 0xe - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalShaderType.cs b/Ryujinx.Graphics/Gal/GalShaderType.cs deleted file mode 100644 index eb5aaf889a..0000000000 --- a/Ryujinx.Graphics/Gal/GalShaderType.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public enum GalShaderType - { - Vertex = 0, - TessControl = 1, - TessEvaluation = 2, - Geometry = 3, - Fragment = 4 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalStencilOp.cs b/Ryujinx.Graphics/Gal/GalStencilOp.cs deleted file mode 100644 index fc83ca5ea6..0000000000 --- a/Ryujinx.Graphics/Gal/GalStencilOp.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public enum GalStencilOp - { - Keep = 0x1, - Zero = 0x2, - Replace = 0x3, - Incr = 0x4, - Decr = 0x5, - Invert = 0x6, - IncrWrap = 0x7, - DecrWrap = 0x8 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalSurfaceFormat.cs b/Ryujinx.Graphics/Gal/GalSurfaceFormat.cs deleted file mode 100644 index 9cebcc272b..0000000000 --- a/Ryujinx.Graphics/Gal/GalSurfaceFormat.cs +++ /dev/null @@ -1,68 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public enum GalSurfaceFormat - { - Bitmap = 0x1c, - Unknown1D = 0x1d, - Rgba32Float = 0xc0, - Rgba32Sint = 0xc1, - Rgba32Uint = 0xc2, - Rgbx32Float = 0xc3, - Rgbx32Sint = 0xc4, - Rgbx32Uint = 0xc5, - Rgba16Unorm = 0xc6, - Rgba16Snorm = 0xc7, - Rgba16Sint = 0xc8, - Rgba16Uint = 0xc9, - Rgba16Float = 0xca, - Rg32Float = 0xcb, - Rg32Sint = 0xcc, - Rg32Uint = 0xcd, - Rgbx16Float = 0xce, - Bgra8Unorm = 0xcf, - Bgra8Srgb = 0xd0, - Rgb10A2Unorm = 0xd1, - Rgb10A2Uint = 0xd2, - Rgba8Unorm = 0xd5, - Rgba8Srgb = 0xd6, - Rgba8Snorm = 0xd7, - Rgba8Sint = 0xd8, - Rgba8Uint = 0xd9, - Rg16Unorm = 0xda, - Rg16Snorm = 0xdb, - Rg16Sint = 0xdc, - Rg16Uint = 0xdd, - Rg16Float = 0xde, - Bgr10A2Unorm = 0xdf, - R11G11B10Float = 0xe0, - R32Sint = 0xe3, - R32Uint = 0xe4, - R32Float = 0xe5, - Bgrx8Unorm = 0xe6, - Bgrx8Srgb = 0xe7, - B5G6R5Unorm = 0xe8, - Bgr5A1Unorm = 0xe9, - Rg8Unorm = 0xea, - Rg8Snorm = 0xeb, - Rg8Sint = 0xec, - Rg8Uint = 0xed, - R16Unorm = 0xee, - R16Snorm = 0xef, - R16Sint = 0xf0, - R16Uint = 0xf1, - R16Float = 0xf2, - R8Unorm = 0xf3, - R8Snorm = 0xf4, - R8Sint = 0xf5, - R8Uint = 0xf6, - A8Unorm = 0xf7, - Bgr5x1Unorm = 0xf8, - Rgbx8Unorm = 0xf9, - Rgbx8Srgb = 0xfa, - Bgr5x1UnormUnknownFB = 0xfb, - Bgr5x1UnormUnknownFC = 0xfc, - Bgrx8UnormUnknownFD = 0xfd, - Bgrx8UnormUnknownFE = 0xfe, - Y32UintUnknownFF = 0xff - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalTextureFilter.cs b/Ryujinx.Graphics/Gal/GalTextureFilter.cs deleted file mode 100644 index 8e9669f00f..0000000000 --- a/Ryujinx.Graphics/Gal/GalTextureFilter.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public enum GalTextureFilter - { - Nearest = 1, - Linear = 2 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalTextureFormat.cs b/Ryujinx.Graphics/Gal/GalTextureFormat.cs deleted file mode 100644 index ed27180a27..0000000000 --- a/Ryujinx.Graphics/Gal/GalTextureFormat.cs +++ /dev/null @@ -1,46 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public enum GalTextureFormat - { - Rgba32 = 0x1, - Rgba16 = 0x3, - Rg32 = 0x4, - Rgba8 = 0x8, - Rgb10A2 = 0x9, - Rg16 = 0xc, - R32 = 0xf, - BptcSfloat = 0x10, - BptcUfloat = 0x11, - Rgba4 = 0x12, - Rgb5A1 = 0x14, - Rgb565 = 0x15, - BptcUnorm = 0x17, - Rg8 = 0x18, - R16 = 0x1b, - R8 = 0x1d, - R11G11B10F = 0x21, - BC1 = 0x24, - BC2 = 0x25, - BC3 = 0x26, - BC4 = 0x27, - BC5 = 0x28, - D24S8 = 0x29, - D32F = 0x2f, - D32Fx24S8 = 0x30, - D16 = 0x3a, - Astc2D4x4 = 0x40, - Astc2D5x5 = 0x41, - Astc2D6x6 = 0x42, - Astc2D8x8 = 0x44, - Astc2D10x10 = 0x45, - Astc2D12x12 = 0x46, - Astc2D5x4 = 0x50, - Astc2D6x5 = 0x51, - Astc2D8x6 = 0x52, - Astc2D10x8 = 0x53, - Astc2D12x10 = 0x54, - Astc2D8x5 = 0x55, - Astc2D10x5 = 0x56, - Astc2D10x6 = 0x57 - } -} diff --git a/Ryujinx.Graphics/Gal/GalTextureMipFilter.cs b/Ryujinx.Graphics/Gal/GalTextureMipFilter.cs deleted file mode 100644 index 2123ec7d24..0000000000 --- a/Ryujinx.Graphics/Gal/GalTextureMipFilter.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public enum GalTextureMipFilter - { - None = 1, - Nearest = 2, - Linear = 3 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalTextureSampler.cs b/Ryujinx.Graphics/Gal/GalTextureSampler.cs deleted file mode 100644 index 2e57a130c6..0000000000 --- a/Ryujinx.Graphics/Gal/GalTextureSampler.cs +++ /dev/null @@ -1,41 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public struct GalTextureSampler - { - public GalTextureWrap AddressU { get; private set; } - public GalTextureWrap AddressV { get; private set; } - public GalTextureWrap AddressP { get; private set; } - - public GalTextureFilter MinFilter { get; private set; } - public GalTextureFilter MagFilter { get; private set; } - public GalTextureMipFilter MipFilter { get; private set; } - - public GalColorF BorderColor { get; private set; } - - public bool DepthCompare { get; private set; } - public DepthCompareFunc DepthCompareFunc { get; private set; } - - public GalTextureSampler( - GalTextureWrap addressU, - GalTextureWrap addressV, - GalTextureWrap addressP, - GalTextureFilter minFilter, - GalTextureFilter magFilter, - GalTextureMipFilter mipFilter, - GalColorF borderColor, - bool depthCompare, - DepthCompareFunc depthCompareFunc) - { - AddressU = addressU; - AddressV = addressV; - AddressP = addressP; - MinFilter = minFilter; - MagFilter = magFilter; - MipFilter = mipFilter; - BorderColor = borderColor; - - DepthCompare = depthCompare; - DepthCompareFunc = depthCompareFunc; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalTextureSource.cs b/Ryujinx.Graphics/Gal/GalTextureSource.cs deleted file mode 100644 index 72dbec6066..0000000000 --- a/Ryujinx.Graphics/Gal/GalTextureSource.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public enum GalTextureSource - { - Zero = 0, - Red = 2, - Green = 3, - Blue = 4, - Alpha = 5, - OneInt = 6, - OneFloat = 7 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalTextureTarget.cs b/Ryujinx.Graphics/Gal/GalTextureTarget.cs deleted file mode 100644 index bcc0c49a51..0000000000 --- a/Ryujinx.Graphics/Gal/GalTextureTarget.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public enum GalTextureTarget - { - OneD = 0, - TwoD = 1, - ThreeD = 2, - CubeMap = 3, - OneDArray = 4, - TwoDArray = 5, - OneDBuffer = 6, - TwoDNoMipMap = 7, - CubeArray = 8, - } -} diff --git a/Ryujinx.Graphics/Gal/GalTextureType.cs b/Ryujinx.Graphics/Gal/GalTextureType.cs deleted file mode 100644 index b02b8b37b4..0000000000 --- a/Ryujinx.Graphics/Gal/GalTextureType.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public enum GalTextureType - { - Snorm = 1, - Unorm = 2, - Sint = 3, - Uint = 4, - SnormForceFp16 = 5, - UnormForceFp16 = 6, - Float = 7 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalTextureWrap.cs b/Ryujinx.Graphics/Gal/GalTextureWrap.cs deleted file mode 100644 index 66e5315409..0000000000 --- a/Ryujinx.Graphics/Gal/GalTextureWrap.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public enum GalTextureWrap - { - Repeat = 0, - MirroredRepeat = 1, - ClampToEdge = 2, - ClampToBorder = 3, - Clamp = 4, - MirrorClampToEdge = 5, - MirrorClampToBorder = 6, - MirrorClamp = 7 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalVertexAttrib.cs b/Ryujinx.Graphics/Gal/GalVertexAttrib.cs deleted file mode 100644 index feca5aea33..0000000000 --- a/Ryujinx.Graphics/Gal/GalVertexAttrib.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public struct GalVertexAttrib - { - public int Index { get; private set; } - public bool IsConst { get; private set; } - public int Offset { get; private set; } - public byte[] Data { get; private set; } - - public GalVertexAttribSize Size { get; private set; } - public GalVertexAttribType Type { get; private set; } - - public bool IsBgra { get; private set; } - - public GalVertexAttrib( - int index, - bool isConst, - int offset, - byte[] data, - GalVertexAttribSize size, - GalVertexAttribType type, - bool isBgra) - { - Index = index; - IsConst = isConst; - Data = data; - Offset = offset; - Size = size; - Type = type; - IsBgra = isBgra; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalVertexAttribSize.cs b/Ryujinx.Graphics/Gal/GalVertexAttribSize.cs deleted file mode 100644 index d3ce60ace3..0000000000 --- a/Ryujinx.Graphics/Gal/GalVertexAttribSize.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public enum GalVertexAttribSize - { - _32_32_32_32 = 0x1, - _32_32_32 = 0x2, - _16_16_16_16 = 0x3, - _32_32 = 0x4, - _16_16_16 = 0x5, - _8_8_8_8 = 0xa, - _16_16 = 0xf, - _32 = 0x12, - _8_8_8 = 0x13, - _8_8 = 0x18, - _16 = 0x1b, - _8 = 0x1d, - _10_10_10_2 = 0x30, - _11_11_10 = 0x31 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalVertexAttribType.cs b/Ryujinx.Graphics/Gal/GalVertexAttribType.cs deleted file mode 100644 index 358836fdaf..0000000000 --- a/Ryujinx.Graphics/Gal/GalVertexAttribType.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public enum GalVertexAttribType - { - Snorm = 1, - Unorm = 2, - Sint = 3, - Uint = 4, - Uscaled = 5, - Sscaled = 6, - Float = 7 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalVertexBinding.cs b/Ryujinx.Graphics/Gal/GalVertexBinding.cs deleted file mode 100644 index 2337c2e321..0000000000 --- a/Ryujinx.Graphics/Gal/GalVertexBinding.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public struct GalVertexBinding - { - // VboKey shouldn't be here, but ARB_vertex_attrib_binding is core since 4.3 - - public bool Enabled; - public int Stride; - public long VboKey; - public bool Instanced; - public int Divisor; - public GalVertexAttrib[] Attribs; - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalZetaFormat.cs b/Ryujinx.Graphics/Gal/GalZetaFormat.cs deleted file mode 100644 index 2429249e54..0000000000 --- a/Ryujinx.Graphics/Gal/GalZetaFormat.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public enum GalZetaFormat - { - D32Float = 0x0a, - D16Unorm = 0x13, - S8D24Unorm = 0x14, - D24X8Unorm = 0x15, - D24S8Unorm = 0x16, - D24C8Unorm = 0x18, - D32S8X24Float = 0x19, - D24X8S8C8X16Unorm = 0x1d, - D32X8C8X16Float = 0x1e, - D32S8C8X16Float = 0x1f - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/IGalConstBuffer.cs b/Ryujinx.Graphics/Gal/IGalConstBuffer.cs deleted file mode 100644 index 8c4e6d0328..0000000000 --- a/Ryujinx.Graphics/Gal/IGalConstBuffer.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace Ryujinx.Graphics.Gal -{ - public interface IGalConstBuffer - { - void LockCache(); - void UnlockCache(); - - void Create(long key, long size); - - bool IsCached(long key, long size); - - void SetData(long key, long size, IntPtr hostAddress); - void SetData(long key, byte[] data); - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/IGalMemory.cs b/Ryujinx.Graphics/Gal/IGalMemory.cs deleted file mode 100644 index 78eb7154b0..0000000000 --- a/Ryujinx.Graphics/Gal/IGalMemory.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public interface IGalMemory - { - int ReadInt32(long position); - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/IGalPipeline.cs b/Ryujinx.Graphics/Gal/IGalPipeline.cs deleted file mode 100644 index 1ecb260288..0000000000 --- a/Ryujinx.Graphics/Gal/IGalPipeline.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public interface IGalPipeline - { - void Bind(GalPipelineState state); - void Unbind(GalPipelineState state); - - void ResetDepthMask(); - void ResetColorMask(int index); - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/IGalRasterizer.cs b/Ryujinx.Graphics/Gal/IGalRasterizer.cs deleted file mode 100644 index 33bdeaad8c..0000000000 --- a/Ryujinx.Graphics/Gal/IGalRasterizer.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; - -namespace Ryujinx.Graphics.Gal -{ - public interface IGalRasterizer - { - void LockCaches(); - void UnlockCaches(); - - void ClearBuffers( - GalClearBufferFlags flags, - int attachment, - float red, - float green, - float blue, - float alpha, - float depth, - int stencil); - - bool IsVboCached(long key, long dataSize); - - bool IsIboCached(long key, long dataSize); - - void CreateVbo(long key, int dataSize, IntPtr hostAddress); - void CreateVbo(long key, byte[] data); - - void CreateIbo(long key, int dataSize, IntPtr hostAddress); - void CreateIbo(long key, int dataSize, byte[] buffer); - - void SetIndexArray(int size, GalIndexFormat format); - - void DrawArrays(int first, int count, GalPrimitiveType primType); - - void DrawElements(long iboKey, int first, int vertexBase, GalPrimitiveType primType); - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/IGalRenderTarget.cs b/Ryujinx.Graphics/Gal/IGalRenderTarget.cs deleted file mode 100644 index c281fe06c3..0000000000 --- a/Ryujinx.Graphics/Gal/IGalRenderTarget.cs +++ /dev/null @@ -1,45 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public interface IGalRenderTarget - { - void Bind(); - - void BindColor(long key, int attachment); - - void UnbindColor(int attachment); - - void BindZeta(long key); - - void UnbindZeta(); - - void Present(long key); - - void SetMap(int[] map); - - void SetTransform(bool flipX, bool flipY, int top, int left, int right, int bottom); - - void SetWindowSize(int width, int height); - - void SetViewport(int attachment, int x, int y, int width, int height); - - void Render(); - - void Copy( - GalImage srcImage, - GalImage dstImage, - long srcKey, - long dstKey, - int srcLayer, - int dstLayer, - int srcX0, - int srcY0, - int srcX1, - int srcY1, - int dstX0, - int dstY0, - int dstX1, - int dstY1); - - void Reinterpret(long key, GalImage newImage); - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/IGalRenderer.cs b/Ryujinx.Graphics/Gal/IGalRenderer.cs deleted file mode 100644 index 1acc4d03b1..0000000000 --- a/Ryujinx.Graphics/Gal/IGalRenderer.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; - -namespace Ryujinx.Graphics.Gal -{ - public interface IGalRenderer - { - void QueueAction(Action actionMthd); - - void RunActions(); - - IGalConstBuffer Buffer { get; } - - IGalRenderTarget RenderTarget { get; } - - IGalRasterizer Rasterizer { get; } - - IGalShader Shader { get; } - - IGalPipeline Pipeline { get; } - - IGalTexture Texture { get; } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/IGalShader.cs b/Ryujinx.Graphics/Gal/IGalShader.cs deleted file mode 100644 index 6a9abe75bc..0000000000 --- a/Ryujinx.Graphics/Gal/IGalShader.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Ryujinx.Graphics.Shader; -using System.Collections.Generic; - -namespace Ryujinx.Graphics.Gal -{ - public interface IGalShader - { - void Create(IGalMemory memory, long key, GalShaderType type); - - void Create(IGalMemory memory, long vpAPos, long key, GalShaderType type); - - IEnumerable GetConstBufferUsage(long key); - IEnumerable GetTextureUsage(long key); - - void Bind(long key); - - void Unbind(GalShaderType type); - - void BindProgram(); - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/IGalTexture.cs b/Ryujinx.Graphics/Gal/IGalTexture.cs deleted file mode 100644 index 23ce054ae9..0000000000 --- a/Ryujinx.Graphics/Gal/IGalTexture.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public interface IGalTexture - { - void LockCache(); - void UnlockCache(); - - void Create(long key, int size, GalImage image); - - void Create(long key, byte[] data, GalImage image); - - bool TryGetImage(long key, out GalImage image); - - void Bind(long key, int index, GalImage image); - - void SetSampler(GalImage image, GalTextureSampler sampler); - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/DeleteValueCallback.cs b/Ryujinx.Graphics/Gal/OpenGL/DeleteValueCallback.cs deleted file mode 100644 index 63b626f18a..0000000000 --- a/Ryujinx.Graphics/Gal/OpenGL/DeleteValueCallback.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace Ryujinx.Graphics.Gal.OpenGL -{ - delegate void DeleteValue(T value); -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/ImageHandler.cs b/Ryujinx.Graphics/Gal/OpenGL/ImageHandler.cs deleted file mode 100644 index d7f6f00443..0000000000 --- a/Ryujinx.Graphics/Gal/OpenGL/ImageHandler.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Ryujinx.Graphics.Texture; - -namespace Ryujinx.Graphics.Gal.OpenGL -{ - class ImageHandler - { - public GalImage Image { get; private set; } - - public int Width => Image.Width; - public int Height => Image.Height; - public int Depth => Image.Depth; - - public GalImageFormat Format => Image.Format; - - public int Handle { get; private set; } - - public bool HasColor => ImageUtils.HasColor(Image.Format); - public bool HasDepth => ImageUtils.HasDepth(Image.Format); - public bool HasStencil => ImageUtils.HasStencil(Image.Format); - - public ImageHandler(int handle, GalImage image) - { - Handle = handle; - Image = image; - } - } -} diff --git a/Ryujinx.Graphics/Gal/OpenGL/OglCachedResource.cs b/Ryujinx.Graphics/Gal/OpenGL/OglCachedResource.cs deleted file mode 100644 index 91f0a7e169..0000000000 --- a/Ryujinx.Graphics/Gal/OpenGL/OglCachedResource.cs +++ /dev/null @@ -1,191 +0,0 @@ -using Ryujinx.Common; -using System; -using System.Collections.Generic; - -namespace Ryujinx.Graphics.Gal.OpenGL -{ - class OglCachedResource - { - public delegate void DeleteValue(T value); - - private const int MinTimeDelta = 5 * 60000; - private const int MaxRemovalsPerRun = 10; - - private struct CacheBucket - { - public T Value { get; private set; } - - public LinkedListNode Node { get; private set; } - - public long DataSize { get; private set; } - - public long Timestamp { get; private set; } - - public CacheBucket(T value, long dataSize, LinkedListNode node) - { - Value = value; - DataSize = dataSize; - Node = node; - - Timestamp = PerformanceCounter.ElapsedMilliseconds; - } - } - - private Dictionary _cache; - - private LinkedList _sortedCache; - - private DeleteValue _deleteValueCallback; - - private Queue _deletePending; - - private bool _locked; - - private long _maxSize; - private long _totalSize; - - public OglCachedResource(DeleteValue deleteValueCallback, long maxSize) - { - _maxSize = maxSize; - - if (deleteValueCallback == null) - { - throw new ArgumentNullException(nameof(deleteValueCallback)); - } - - _deleteValueCallback = deleteValueCallback; - - _cache = new Dictionary(); - - _sortedCache = new LinkedList(); - - _deletePending = new Queue(); - } - - public void Lock() - { - _locked = true; - } - - public void Unlock() - { - _locked = false; - - while (_deletePending.TryDequeue(out T value)) - { - _deleteValueCallback(value); - } - - ClearCacheIfNeeded(); - } - - public void AddOrUpdate(long key, T value, long size) - { - if (!_locked) - { - ClearCacheIfNeeded(); - } - - LinkedListNode node = _sortedCache.AddLast(key); - - CacheBucket newBucket = new CacheBucket(value, size, node); - - if (_cache.TryGetValue(key, out CacheBucket bucket)) - { - if (_locked) - { - _deletePending.Enqueue(bucket.Value); - } - else - { - _deleteValueCallback(bucket.Value); - } - - _sortedCache.Remove(bucket.Node); - - _totalSize -= bucket.DataSize; - - _cache[key] = newBucket; - } - else - { - _cache.Add(key, newBucket); - } - - _totalSize += size; - } - - public bool TryGetValue(long key, out T value) - { - if (_cache.TryGetValue(key, out CacheBucket bucket)) - { - value = bucket.Value; - - _sortedCache.Remove(bucket.Node); - - LinkedListNode node = _sortedCache.AddLast(key); - - _cache[key] = new CacheBucket(value, bucket.DataSize, node); - - return true; - } - - value = default(T); - - return false; - } - - public bool TryGetSize(long key, out long size) - { - if (_cache.TryGetValue(key, out CacheBucket bucket)) - { - size = bucket.DataSize; - - return true; - } - - size = 0; - - return false; - } - - private void ClearCacheIfNeeded() - { - long timestamp = PerformanceCounter.ElapsedMilliseconds; - - int count = 0; - - while (count++ < MaxRemovalsPerRun) - { - LinkedListNode node = _sortedCache.First; - - if (node == null) - { - break; - } - - CacheBucket bucket = _cache[node.Value]; - - long timeDelta = timestamp - bucket.Timestamp; - - if (timeDelta <= MinTimeDelta && !UnderMemoryPressure()) - { - break; - } - - _sortedCache.Remove(node); - - _cache.Remove(node.Value); - - _deleteValueCallback(bucket.Value); - - _totalSize -= bucket.DataSize; - } - } - - private bool UnderMemoryPressure() - { - return _totalSize >= _maxSize; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OglConstBuffer.cs b/Ryujinx.Graphics/Gal/OpenGL/OglConstBuffer.cs deleted file mode 100644 index e076be336d..0000000000 --- a/Ryujinx.Graphics/Gal/OpenGL/OglConstBuffer.cs +++ /dev/null @@ -1,74 +0,0 @@ -using OpenTK.Graphics.OpenGL; -using System; - -namespace Ryujinx.Graphics.Gal.OpenGL -{ - class OglConstBuffer : IGalConstBuffer - { - private const long MaxConstBufferCacheSize = 64 * 1024 * 1024; - - private OglCachedResource _cache; - - public OglConstBuffer() - { - _cache = new OglCachedResource(DeleteBuffer, MaxConstBufferCacheSize); - } - - public void LockCache() - { - _cache.Lock(); - } - - public void UnlockCache() - { - _cache.Unlock(); - } - - public void Create(long key, long size) - { - OglStreamBuffer buffer = new OglStreamBuffer(BufferTarget.UniformBuffer, size); - - _cache.AddOrUpdate(key, buffer, size); - } - - public bool IsCached(long key, long size) - { - return _cache.TryGetSize(key, out long cachedSize) && cachedSize == size; - } - - public void SetData(long key, long size, IntPtr hostAddress) - { - if (_cache.TryGetValue(key, out OglStreamBuffer buffer)) - { - buffer.SetData(size, hostAddress); - } - } - - public void SetData(long key, byte[] data) - { - if (_cache.TryGetValue(key, out OglStreamBuffer buffer)) - { - buffer.SetData(data); - } - } - - public bool TryGetUbo(long key, out int uboHandle) - { - if (_cache.TryGetValue(key, out OglStreamBuffer buffer)) - { - uboHandle = buffer.Handle; - - return true; - } - - uboHandle = 0; - - return false; - } - - private static void DeleteBuffer(OglStreamBuffer buffer) - { - buffer.Dispose(); - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OglEnumConverter.cs b/Ryujinx.Graphics/Gal/OpenGL/OglEnumConverter.cs deleted file mode 100644 index c36d8bf930..0000000000 --- a/Ryujinx.Graphics/Gal/OpenGL/OglEnumConverter.cs +++ /dev/null @@ -1,427 +0,0 @@ -using OpenTK.Graphics.OpenGL; -using System; - -namespace Ryujinx.Graphics.Gal.OpenGL -{ - static class OglEnumConverter - { - public static FrontFaceDirection GetFrontFace(GalFrontFace frontFace) - { - switch (frontFace) - { - case GalFrontFace.Cw: return FrontFaceDirection.Cw; - case GalFrontFace.Ccw: return FrontFaceDirection.Ccw; - } - - throw new ArgumentException(nameof(frontFace) + " \"" + frontFace + "\" is not valid!"); - } - - public static CullFaceMode GetCullFace(GalCullFace cullFace) - { - switch (cullFace) - { - case GalCullFace.Front: return CullFaceMode.Front; - case GalCullFace.Back: return CullFaceMode.Back; - case GalCullFace.FrontAndBack: return CullFaceMode.FrontAndBack; - } - - throw new ArgumentException(nameof(cullFace) + " \"" + cullFace + "\" is not valid!"); - } - - public static StencilOp GetStencilOp(GalStencilOp op) - { - switch (op) - { - case GalStencilOp.Keep: return StencilOp.Keep; - case GalStencilOp.Zero: return StencilOp.Zero; - case GalStencilOp.Replace: return StencilOp.Replace; - case GalStencilOp.Incr: return StencilOp.Incr; - case GalStencilOp.Decr: return StencilOp.Decr; - case GalStencilOp.Invert: return StencilOp.Invert; - case GalStencilOp.IncrWrap: return StencilOp.IncrWrap; - case GalStencilOp.DecrWrap: return StencilOp.DecrWrap; - } - - throw new ArgumentException(nameof(op) + " \"" + op + "\" is not valid!"); - } - - public static DepthFunction GetDepthFunc(GalComparisonOp func) - { - return (DepthFunction)GetFunc(func); - } - - public static StencilFunction GetStencilFunc(GalComparisonOp func) - { - return (StencilFunction)GetFunc(func); - } - - private static All GetFunc(GalComparisonOp func) - { - if ((int)func >= (int)All.Never && - (int)func <= (int)All.Always) - { - return (All)func; - } - - switch (func) - { - case GalComparisonOp.Never: return All.Never; - case GalComparisonOp.Less: return All.Less; - case GalComparisonOp.Equal: return All.Equal; - case GalComparisonOp.Lequal: return All.Lequal; - case GalComparisonOp.Greater: return All.Greater; - case GalComparisonOp.NotEqual: return All.Notequal; - case GalComparisonOp.Gequal: return All.Gequal; - case GalComparisonOp.Always: return All.Always; - } - - throw new ArgumentException(nameof(func) + " \"" + func + "\" is not valid!"); - } - - public static DrawElementsType GetDrawElementsType(GalIndexFormat format) - { - switch (format) - { - case GalIndexFormat.Byte: return DrawElementsType.UnsignedByte; - case GalIndexFormat.Int16: return DrawElementsType.UnsignedShort; - case GalIndexFormat.Int32: return DrawElementsType.UnsignedInt; - } - - throw new ArgumentException(nameof(format) + " \"" + format + "\" is not valid!"); - } - - public static PrimitiveType GetPrimitiveType(GalPrimitiveType type) - { - switch (type) - { - case GalPrimitiveType.Points: return PrimitiveType.Points; - case GalPrimitiveType.Lines: return PrimitiveType.Lines; - case GalPrimitiveType.LineLoop: return PrimitiveType.LineLoop; - case GalPrimitiveType.LineStrip: return PrimitiveType.LineStrip; - case GalPrimitiveType.Triangles: return PrimitiveType.Triangles; - case GalPrimitiveType.TriangleStrip: return PrimitiveType.TriangleStrip; - case GalPrimitiveType.TriangleFan: return PrimitiveType.TriangleFan; - case GalPrimitiveType.Polygon: return PrimitiveType.Polygon; - case GalPrimitiveType.LinesAdjacency: return PrimitiveType.LinesAdjacency; - case GalPrimitiveType.LineStripAdjacency: return PrimitiveType.LineStripAdjacency; - case GalPrimitiveType.TrianglesAdjacency: return PrimitiveType.TrianglesAdjacency; - case GalPrimitiveType.TriangleStripAdjacency: return PrimitiveType.TriangleStripAdjacency; - case GalPrimitiveType.Patches: return PrimitiveType.Patches; - } - - throw new ArgumentException(nameof(type) + " \"" + type + "\" is not valid!"); - } - - public static ShaderType GetShaderType(GalShaderType type) - { - switch (type) - { - case GalShaderType.Vertex: return ShaderType.VertexShader; - case GalShaderType.TessControl: return ShaderType.TessControlShader; - case GalShaderType.TessEvaluation: return ShaderType.TessEvaluationShader; - case GalShaderType.Geometry: return ShaderType.GeometryShader; - case GalShaderType.Fragment: return ShaderType.FragmentShader; - } - - throw new ArgumentException(nameof(type) + " \"" + type + "\" is not valid!"); - } - - public static (PixelInternalFormat, PixelFormat, PixelType) GetImageFormat(GalImageFormat format) - { - switch (format) - { - case GalImageFormat.Rgba32 | GalImageFormat.Float: return (PixelInternalFormat.Rgba32f, PixelFormat.Rgba, PixelType.Float); - case GalImageFormat.Rgba32 | GalImageFormat.Sint: return (PixelInternalFormat.Rgba32i, PixelFormat.RgbaInteger, PixelType.Int); - case GalImageFormat.Rgba32 | GalImageFormat.Uint: return (PixelInternalFormat.Rgba32ui, PixelFormat.RgbaInteger, PixelType.UnsignedInt); - case GalImageFormat.Rgba16 | GalImageFormat.Float: return (PixelInternalFormat.Rgba16f, PixelFormat.Rgba, PixelType.HalfFloat); - case GalImageFormat.Rgba16 | GalImageFormat.Sint: return (PixelInternalFormat.Rgba16i, PixelFormat.RgbaInteger, PixelType.Short); - case GalImageFormat.Rgba16 | GalImageFormat.Uint: return (PixelInternalFormat.Rgba16ui, PixelFormat.RgbaInteger, PixelType.UnsignedShort); - case GalImageFormat.Rgba16 | GalImageFormat.Unorm: return (PixelInternalFormat.Rgba16, PixelFormat.Rgba, PixelType.UnsignedShort); - case GalImageFormat.Rg32 | GalImageFormat.Float: return (PixelInternalFormat.Rg32f, PixelFormat.Rg, PixelType.Float); - case GalImageFormat.Rg32 | GalImageFormat.Sint: return (PixelInternalFormat.Rg32i, PixelFormat.RgInteger, PixelType.Int); - case GalImageFormat.Rg32 | GalImageFormat.Uint: return (PixelInternalFormat.Rg32ui, PixelFormat.RgInteger, PixelType.UnsignedInt); - case GalImageFormat.Rgbx8 | GalImageFormat.Unorm: return (PixelInternalFormat.Rgb8, PixelFormat.Rgba, PixelType.UnsignedByte); - case GalImageFormat.Rgba8 | GalImageFormat.Snorm: return (PixelInternalFormat.Rgba8Snorm, PixelFormat.Rgba, PixelType.Byte); - case GalImageFormat.Rgba8 | GalImageFormat.Unorm: return (PixelInternalFormat.Rgba8, PixelFormat.Rgba, PixelType.UnsignedByte); - case GalImageFormat.Rgba8 | GalImageFormat.Sint: return (PixelInternalFormat.Rgba8i, PixelFormat.RgbaInteger, PixelType.Byte); - case GalImageFormat.Rgba8 | GalImageFormat.Uint: return (PixelInternalFormat.Rgba8ui, PixelFormat.RgbaInteger, PixelType.UnsignedByte); - case GalImageFormat.Rgba8 | GalImageFormat.Srgb: return (PixelInternalFormat.Srgb8Alpha8, PixelFormat.Rgba, PixelType.UnsignedByte); - case GalImageFormat.Bgra8 | GalImageFormat.Unorm: return (PixelInternalFormat.Rgba8, PixelFormat.Bgra, PixelType.UnsignedByte); - case GalImageFormat.Bgra8 | GalImageFormat.Srgb: return (PixelInternalFormat.Srgb8Alpha8, PixelFormat.Bgra, PixelType.UnsignedByte); - case GalImageFormat.Rgba4 | GalImageFormat.Unorm: return (PixelInternalFormat.Rgba4, PixelFormat.Rgba, PixelType.UnsignedShort4444Reversed); - case GalImageFormat.Rgb10A2 | GalImageFormat.Uint: return (PixelInternalFormat.Rgb10A2ui, PixelFormat.RgbaInteger, PixelType.UnsignedInt2101010Reversed); - case GalImageFormat.Rgb10A2 | GalImageFormat.Unorm: return (PixelInternalFormat.Rgb10A2, PixelFormat.Rgba, PixelType.UnsignedInt2101010Reversed); - case GalImageFormat.R32 | GalImageFormat.Float: return (PixelInternalFormat.R32f, PixelFormat.Red, PixelType.Float); - case GalImageFormat.R32 | GalImageFormat.Sint: return (PixelInternalFormat.R32i, PixelFormat.Red, PixelType.Int); - case GalImageFormat.R32 | GalImageFormat.Uint: return (PixelInternalFormat.R32ui, PixelFormat.Red, PixelType.UnsignedInt); - case GalImageFormat.Bgr5A1 | GalImageFormat.Unorm: return (PixelInternalFormat.Rgb5A1, PixelFormat.Rgba, PixelType.UnsignedShort5551); - case GalImageFormat.Rgb5A1 | GalImageFormat.Unorm: return (PixelInternalFormat.Rgb5A1, PixelFormat.Rgba, PixelType.UnsignedShort1555Reversed); - case GalImageFormat.Rgb565 | GalImageFormat.Unorm: return (PixelInternalFormat.Rgba, PixelFormat.Rgb, PixelType.UnsignedShort565Reversed); - case GalImageFormat.Bgr565 | GalImageFormat.Unorm: return (PixelInternalFormat.Rgba, PixelFormat.Rgb, PixelType.UnsignedShort565); - case GalImageFormat.Rg16 | GalImageFormat.Float: return (PixelInternalFormat.Rg16f, PixelFormat.Rg, PixelType.HalfFloat); - case GalImageFormat.Rg16 | GalImageFormat.Sint: return (PixelInternalFormat.Rg16i, PixelFormat.RgInteger, PixelType.Short); - case GalImageFormat.Rg16 | GalImageFormat.Snorm: return (PixelInternalFormat.Rg16Snorm, PixelFormat.Rg, PixelType.Short); - case GalImageFormat.Rg16 | GalImageFormat.Uint: return (PixelInternalFormat.Rg16ui, PixelFormat.RgInteger, PixelType.UnsignedShort); - case GalImageFormat.Rg16 | GalImageFormat.Unorm: return (PixelInternalFormat.Rg16, PixelFormat.Rg, PixelType.UnsignedShort); - case GalImageFormat.Rg8 | GalImageFormat.Sint: return (PixelInternalFormat.Rg8i, PixelFormat.RgInteger, PixelType.Byte); - case GalImageFormat.Rg8 | GalImageFormat.Snorm: return (PixelInternalFormat.Rg8Snorm, PixelFormat.Rg, PixelType.Byte); - case GalImageFormat.Rg8 | GalImageFormat.Uint: return (PixelInternalFormat.Rg8ui, PixelFormat.RgInteger, PixelType.UnsignedByte); - case GalImageFormat.Rg8 | GalImageFormat.Unorm: return (PixelInternalFormat.Rg8, PixelFormat.Rg, PixelType.UnsignedByte); - case GalImageFormat.R16 | GalImageFormat.Float: return (PixelInternalFormat.R16f, PixelFormat.Red, PixelType.HalfFloat); - case GalImageFormat.R16 | GalImageFormat.Sint: return (PixelInternalFormat.R16i, PixelFormat.RedInteger, PixelType.Short); - case GalImageFormat.R16 | GalImageFormat.Snorm: return (PixelInternalFormat.R16Snorm, PixelFormat.Red, PixelType.Short); - case GalImageFormat.R16 | GalImageFormat.Uint: return (PixelInternalFormat.R16ui, PixelFormat.RedInteger, PixelType.UnsignedShort); - case GalImageFormat.R16 | GalImageFormat.Unorm: return (PixelInternalFormat.R16, PixelFormat.Red, PixelType.UnsignedShort); - case GalImageFormat.R8 | GalImageFormat.Sint: return (PixelInternalFormat.R8i, PixelFormat.RedInteger, PixelType.Byte); - case GalImageFormat.R8 | GalImageFormat.Snorm: return (PixelInternalFormat.R8Snorm, PixelFormat.Red, PixelType.Byte); - case GalImageFormat.R8 | GalImageFormat.Uint: return (PixelInternalFormat.R8ui, PixelFormat.RedInteger, PixelType.UnsignedByte); - case GalImageFormat.R8 | GalImageFormat.Unorm: return (PixelInternalFormat.R8, PixelFormat.Red, PixelType.UnsignedByte); - case GalImageFormat.R11G11B10 | GalImageFormat.Float: return (PixelInternalFormat.R11fG11fB10f, PixelFormat.Rgb, PixelType.UnsignedInt10F11F11FRev); - - case GalImageFormat.D16 | GalImageFormat.Unorm: return (PixelInternalFormat.DepthComponent16, PixelFormat.DepthComponent, PixelType.UnsignedShort); - case GalImageFormat.D24 | GalImageFormat.Unorm: return (PixelInternalFormat.DepthComponent24, PixelFormat.DepthComponent, PixelType.UnsignedInt); - case GalImageFormat.D24S8 | GalImageFormat.Uint: return (PixelInternalFormat.Depth24Stencil8, PixelFormat.DepthStencil, PixelType.UnsignedInt248); - case GalImageFormat.D24S8 | GalImageFormat.Unorm: return (PixelInternalFormat.Depth24Stencil8, PixelFormat.DepthStencil, PixelType.UnsignedInt248); - case GalImageFormat.D32 | GalImageFormat.Float: return (PixelInternalFormat.DepthComponent32f, PixelFormat.DepthComponent, PixelType.Float); - case GalImageFormat.D32S8 | GalImageFormat.Float: return (PixelInternalFormat.Depth32fStencil8, PixelFormat.DepthStencil, PixelType.Float32UnsignedInt248Rev); - } - - throw new NotImplementedException($"{format & GalImageFormat.FormatMask} {format & GalImageFormat.TypeMask}"); - } - - public static All GetDepthCompareFunc(DepthCompareFunc depthCompareFunc) - { - switch (depthCompareFunc) - { - case DepthCompareFunc.LEqual: - return All.Lequal; - case DepthCompareFunc.GEqual: - return All.Gequal; - case DepthCompareFunc.Less: - return All.Less; - case DepthCompareFunc.Greater: - return All.Greater; - case DepthCompareFunc.Equal: - return All.Equal; - case DepthCompareFunc.NotEqual: - return All.Notequal; - case DepthCompareFunc.Always: - return All.Always; - case DepthCompareFunc.Never: - return All.Never; - default: - throw new ArgumentException(nameof(depthCompareFunc) + " \"" + depthCompareFunc + "\" is not valid!"); - } - } - - public static InternalFormat GetCompressedImageFormat(GalImageFormat format) - { - switch (format) - { - case GalImageFormat.BptcSfloat | GalImageFormat.Float: return InternalFormat.CompressedRgbBptcSignedFloat; - case GalImageFormat.BptcUfloat | GalImageFormat.Float: return InternalFormat.CompressedRgbBptcUnsignedFloat; - case GalImageFormat.BptcUnorm | GalImageFormat.Unorm: return InternalFormat.CompressedRgbaBptcUnorm; - case GalImageFormat.BptcUnorm | GalImageFormat.Srgb: return InternalFormat.CompressedSrgbAlphaBptcUnorm; - case GalImageFormat.BC1 | GalImageFormat.Unorm: return InternalFormat.CompressedRgbaS3tcDxt1Ext; - case GalImageFormat.BC1 | GalImageFormat.Srgb: return InternalFormat.CompressedSrgbAlphaS3tcDxt1Ext; - case GalImageFormat.BC2 | GalImageFormat.Unorm: return InternalFormat.CompressedRgbaS3tcDxt3Ext; - case GalImageFormat.BC2 | GalImageFormat.Srgb: return InternalFormat.CompressedSrgbAlphaS3tcDxt3Ext; - case GalImageFormat.BC3 | GalImageFormat.Unorm: return InternalFormat.CompressedRgbaS3tcDxt5Ext; - case GalImageFormat.BC3 | GalImageFormat.Srgb: return InternalFormat.CompressedSrgbAlphaS3tcDxt5Ext; - case GalImageFormat.BC4 | GalImageFormat.Snorm: return InternalFormat.CompressedSignedRedRgtc1; - case GalImageFormat.BC4 | GalImageFormat.Unorm: return InternalFormat.CompressedRedRgtc1; - case GalImageFormat.BC5 | GalImageFormat.Snorm: return InternalFormat.CompressedSignedRgRgtc2; - case GalImageFormat.BC5 | GalImageFormat.Unorm: return InternalFormat.CompressedRgRgtc2; - } - - throw new NotImplementedException($"{format & GalImageFormat.FormatMask} {format & GalImageFormat.TypeMask}"); - } - - public static All GetTextureSwizzle(GalTextureSource source) - { - switch (source) - { - case GalTextureSource.Zero: return All.Zero; - case GalTextureSource.Red: return All.Red; - case GalTextureSource.Green: return All.Green; - case GalTextureSource.Blue: return All.Blue; - case GalTextureSource.Alpha: return All.Alpha; - case GalTextureSource.OneInt: return All.One; - case GalTextureSource.OneFloat: return All.One; - } - - throw new ArgumentException(nameof(source) + " \"" + source + "\" is not valid!"); - } - - public static TextureWrapMode GetTextureWrapMode(GalTextureWrap wrap) - { - switch (wrap) - { - case GalTextureWrap.Repeat: return TextureWrapMode.Repeat; - case GalTextureWrap.MirroredRepeat: return TextureWrapMode.MirroredRepeat; - case GalTextureWrap.ClampToEdge: return TextureWrapMode.ClampToEdge; - case GalTextureWrap.ClampToBorder: return TextureWrapMode.ClampToBorder; - case GalTextureWrap.Clamp: return TextureWrapMode.Clamp; - } - - if (OglExtension.TextureMirrorClamp) - { - switch (wrap) - { - case GalTextureWrap.MirrorClampToEdge: return (TextureWrapMode)ExtTextureMirrorClamp.MirrorClampToEdgeExt; - case GalTextureWrap.MirrorClampToBorder: return (TextureWrapMode)ExtTextureMirrorClamp.MirrorClampToBorderExt; - case GalTextureWrap.MirrorClamp: return (TextureWrapMode)ExtTextureMirrorClamp.MirrorClampExt; - } - } - else - { - // Fallback to non-mirrored clamps - switch (wrap) - { - case GalTextureWrap.MirrorClampToEdge: return TextureWrapMode.ClampToEdge; - case GalTextureWrap.MirrorClampToBorder: return TextureWrapMode.ClampToBorder; - case GalTextureWrap.MirrorClamp: return TextureWrapMode.Clamp; - } - } - - throw new ArgumentException(nameof(wrap) + " \"" + wrap + "\" is not valid!"); - } - - public static TextureMinFilter GetTextureMinFilter( - GalTextureFilter minFilter, - GalTextureMipFilter mipFilter) - { - // TODO: Mip (needs mipmap support first). - switch (minFilter) - { - case GalTextureFilter.Nearest: return TextureMinFilter.Nearest; - case GalTextureFilter.Linear: return TextureMinFilter.Linear; - } - - throw new ArgumentException(nameof(minFilter) + " \"" + minFilter + "\" is not valid!"); - } - - public static TextureMagFilter GetTextureMagFilter(GalTextureFilter filter) - { - switch (filter) - { - case GalTextureFilter.Nearest: return TextureMagFilter.Nearest; - case GalTextureFilter.Linear: return TextureMagFilter.Linear; - } - - throw new ArgumentException(nameof(filter) + " \"" + filter + "\" is not valid!"); - } - - public static BlendEquationMode GetBlendEquation(GalBlendEquation blendEquation) - { - switch (blendEquation) - { - case GalBlendEquation.FuncAdd: - case GalBlendEquation.FuncAddGl: - return BlendEquationMode.FuncAdd; - - case GalBlendEquation.FuncSubtract: - case GalBlendEquation.FuncSubtractGl: - return BlendEquationMode.FuncSubtract; - - case GalBlendEquation.FuncReverseSubtract: - case GalBlendEquation.FuncReverseSubtractGl: - return BlendEquationMode.FuncReverseSubtract; - - case GalBlendEquation.Min: - case GalBlendEquation.MinGl: - return BlendEquationMode.Min; - - case GalBlendEquation.Max: - case GalBlendEquation.MaxGl: - return BlendEquationMode.Max; - } - - throw new ArgumentException(nameof(blendEquation) + " \"" + blendEquation + "\" is not valid!"); - } - - public static BlendingFactor GetBlendFactor(GalBlendFactor blendFactor) - { - switch (blendFactor) - { - case GalBlendFactor.Zero: - case GalBlendFactor.ZeroGl: - return BlendingFactor.Zero; - - case GalBlendFactor.One: - case GalBlendFactor.OneGl: - return BlendingFactor.One; - - case GalBlendFactor.SrcColor: - case GalBlendFactor.SrcColorGl: - return BlendingFactor.SrcColor; - - case GalBlendFactor.OneMinusSrcColor: - case GalBlendFactor.OneMinusSrcColorGl: - return BlendingFactor.OneMinusSrcColor; - - case GalBlendFactor.DstColor: - case GalBlendFactor.DstColorGl: - return BlendingFactor.DstColor; - - case GalBlendFactor.OneMinusDstColor: - case GalBlendFactor.OneMinusDstColorGl: - return BlendingFactor.OneMinusDstColor; - - case GalBlendFactor.SrcAlpha: - case GalBlendFactor.SrcAlphaGl: - return BlendingFactor.SrcAlpha; - - case GalBlendFactor.OneMinusSrcAlpha: - case GalBlendFactor.OneMinusSrcAlphaGl: - return BlendingFactor.OneMinusSrcAlpha; - - case GalBlendFactor.DstAlpha: - case GalBlendFactor.DstAlphaGl: - return BlendingFactor.DstAlpha; - - case GalBlendFactor.OneMinusDstAlpha: - case GalBlendFactor.OneMinusDstAlphaGl: - return BlendingFactor.OneMinusDstAlpha; - - case GalBlendFactor.OneMinusConstantColor: - case GalBlendFactor.OneMinusConstantColorGl: - return BlendingFactor.OneMinusConstantColor; - - case GalBlendFactor.ConstantAlpha: - case GalBlendFactor.ConstantAlphaGl: - return BlendingFactor.ConstantAlpha; - - case GalBlendFactor.OneMinusConstantAlpha: - case GalBlendFactor.OneMinusConstantAlphaGl: - return BlendingFactor.OneMinusConstantAlpha; - - case GalBlendFactor.SrcAlphaSaturate: - case GalBlendFactor.SrcAlphaSaturateGl: - return BlendingFactor.SrcAlphaSaturate; - - case GalBlendFactor.Src1Color: - case GalBlendFactor.Src1ColorGl: - return BlendingFactor.Src1Color; - - case GalBlendFactor.OneMinusSrc1Color: - case GalBlendFactor.OneMinusSrc1ColorGl: - return (BlendingFactor)BlendingFactorSrc.OneMinusSrc1Color; - - case GalBlendFactor.Src1Alpha: - case GalBlendFactor.Src1AlphaGl: - return BlendingFactor.Src1Alpha; - - case GalBlendFactor.OneMinusSrc1Alpha: - case GalBlendFactor.OneMinusSrc1AlphaGl: - return (BlendingFactor)BlendingFactorSrc.OneMinusSrc1Alpha; - - case GalBlendFactor.ConstantColor: - case GalBlendFactor.ConstantColorGl: - return BlendingFactor.ConstantColor; - } - - throw new ArgumentException(nameof(blendFactor) + " \"" + blendFactor + "\" is not valid!"); - } - } -} diff --git a/Ryujinx.Graphics/Gal/OpenGL/OglExtension.cs b/Ryujinx.Graphics/Gal/OpenGL/OglExtension.cs deleted file mode 100644 index 8a1a0510f7..0000000000 --- a/Ryujinx.Graphics/Gal/OpenGL/OglExtension.cs +++ /dev/null @@ -1,70 +0,0 @@ -using OpenTK.Graphics.OpenGL; -using Ryujinx.Common.Logging; -using System; - -namespace Ryujinx.Graphics.Gal.OpenGL -{ - static class OglExtension - { - // Private lazy backing variables - private static Lazy _enhancedLayouts = new Lazy(() => HasExtension("GL_ARB_enhanced_layouts")); - private static Lazy _textureMirrorClamp = new Lazy(() => HasExtension("GL_EXT_texture_mirror_clamp")); - private static Lazy _viewportArray = new Lazy(() => HasExtension("GL_ARB_viewport_array")); - - private static Lazy _nvidiaDriver = new Lazy(() => IsNvidiaDriver()); - - // Public accessors - public static bool EnhancedLayouts => _enhancedLayouts.Value; - public static bool TextureMirrorClamp => _textureMirrorClamp.Value; - public static bool ViewportArray => _viewportArray.Value; - - public static bool NvidiaDriver => _nvidiaDriver.Value; - - private static bool HasExtension(string name) - { - int numExtensions = GL.GetInteger(GetPName.NumExtensions); - - for (int extension = 0; extension < numExtensions; extension++) - { - if (GL.GetString(StringNameIndexed.Extensions, extension) == name) - { - return true; - } - } - - Logger.PrintInfo(LogClass.Gpu, $"OpenGL extension {name} unavailable. You may experience some performance degradation"); - - return false; - } - - private static bool IsNvidiaDriver() - { - return GL.GetString(StringName.Vendor).Equals("NVIDIA Corporation"); - } - - public static class Required - { - // Public accessors - public static bool EnhancedLayouts => _enhancedLayoutsRequired.Value; - public static bool TextureMirrorClamp => _textureMirrorClampRequired.Value; - public static bool ViewportArray => _viewportArrayRequired.Value; - - // Private lazy backing variables - private static Lazy _enhancedLayoutsRequired = new Lazy(() => HasExtensionRequired(OglExtension.EnhancedLayouts, "GL_ARB_enhanced_layouts")); - private static Lazy _textureMirrorClampRequired = new Lazy(() => HasExtensionRequired(OglExtension.TextureMirrorClamp, "GL_EXT_texture_mirror_clamp")); - private static Lazy _viewportArrayRequired = new Lazy(() => HasExtensionRequired(OglExtension.ViewportArray, "GL_ARB_viewport_array")); - - private static bool HasExtensionRequired(bool value, string name) - { - if (value) - { - return true; - } - - Logger.PrintWarning(LogClass.Gpu, $"Required OpenGL extension {name} unavailable. You may experience some rendering issues"); - - return false; - } - } - } -} diff --git a/Ryujinx.Graphics/Gal/OpenGL/OglLimit.cs b/Ryujinx.Graphics/Gal/OpenGL/OglLimit.cs deleted file mode 100644 index 2a227a374a..0000000000 --- a/Ryujinx.Graphics/Gal/OpenGL/OglLimit.cs +++ /dev/null @@ -1,12 +0,0 @@ -using OpenTK.Graphics.OpenGL; -using System; - -namespace Ryujinx.Graphics.Gal.OpenGL -{ - static class OglLimit - { - private static Lazy _sMaxUboSize = new Lazy(() => GL.GetInteger(GetPName.MaxUniformBlockSize)); - - public static int MaxUboSize => _sMaxUboSize.Value; - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OglPipeline.cs b/Ryujinx.Graphics/Gal/OpenGL/OglPipeline.cs deleted file mode 100644 index 8d886be4e0..0000000000 --- a/Ryujinx.Graphics/Gal/OpenGL/OglPipeline.cs +++ /dev/null @@ -1,833 +0,0 @@ -using OpenTK.Graphics.OpenGL; -using Ryujinx.Graphics.Shader; -using System; -using System.Collections.Generic; - -namespace Ryujinx.Graphics.Gal.OpenGL -{ - class OglPipeline : IGalPipeline - { - private static Dictionary _attribElements = - new Dictionary() - { - { GalVertexAttribSize._32_32_32_32, 4 }, - { GalVertexAttribSize._32_32_32, 3 }, - { GalVertexAttribSize._16_16_16_16, 4 }, - { GalVertexAttribSize._32_32, 2 }, - { GalVertexAttribSize._16_16_16, 3 }, - { GalVertexAttribSize._8_8_8_8, 4 }, - { GalVertexAttribSize._16_16, 2 }, - { GalVertexAttribSize._32, 1 }, - { GalVertexAttribSize._8_8_8, 3 }, - { GalVertexAttribSize._8_8, 2 }, - { GalVertexAttribSize._16, 1 }, - { GalVertexAttribSize._8, 1 }, - { GalVertexAttribSize._10_10_10_2, 4 }, - { GalVertexAttribSize._11_11_10, 3 } - }; - - private static Dictionary _floatAttribTypes = - new Dictionary() - { - { GalVertexAttribSize._32_32_32_32, VertexAttribPointerType.Float }, - { GalVertexAttribSize._32_32_32, VertexAttribPointerType.Float }, - { GalVertexAttribSize._16_16_16_16, VertexAttribPointerType.HalfFloat }, - { GalVertexAttribSize._32_32, VertexAttribPointerType.Float }, - { GalVertexAttribSize._16_16_16, VertexAttribPointerType.HalfFloat }, - { GalVertexAttribSize._16_16, VertexAttribPointerType.HalfFloat }, - { GalVertexAttribSize._32, VertexAttribPointerType.Float }, - { GalVertexAttribSize._16, VertexAttribPointerType.HalfFloat } - }; - - private static Dictionary _signedAttribTypes = - new Dictionary() - { - { GalVertexAttribSize._32_32_32_32, VertexAttribPointerType.Int }, - { GalVertexAttribSize._32_32_32, VertexAttribPointerType.Int }, - { GalVertexAttribSize._16_16_16_16, VertexAttribPointerType.Short }, - { GalVertexAttribSize._32_32, VertexAttribPointerType.Int }, - { GalVertexAttribSize._16_16_16, VertexAttribPointerType.Short }, - { GalVertexAttribSize._8_8_8_8, VertexAttribPointerType.Byte }, - { GalVertexAttribSize._16_16, VertexAttribPointerType.Short }, - { GalVertexAttribSize._32, VertexAttribPointerType.Int }, - { GalVertexAttribSize._8_8_8, VertexAttribPointerType.Byte }, - { GalVertexAttribSize._8_8, VertexAttribPointerType.Byte }, - { GalVertexAttribSize._16, VertexAttribPointerType.Short }, - { GalVertexAttribSize._8, VertexAttribPointerType.Byte }, - { GalVertexAttribSize._10_10_10_2, VertexAttribPointerType.Int2101010Rev } - }; - - private static Dictionary _unsignedAttribTypes = - new Dictionary() - { - { GalVertexAttribSize._32_32_32_32, VertexAttribPointerType.UnsignedInt }, - { GalVertexAttribSize._32_32_32, VertexAttribPointerType.UnsignedInt }, - { GalVertexAttribSize._16_16_16_16, VertexAttribPointerType.UnsignedShort }, - { GalVertexAttribSize._32_32, VertexAttribPointerType.UnsignedInt }, - { GalVertexAttribSize._16_16_16, VertexAttribPointerType.UnsignedShort }, - { GalVertexAttribSize._8_8_8_8, VertexAttribPointerType.UnsignedByte }, - { GalVertexAttribSize._16_16, VertexAttribPointerType.UnsignedShort }, - { GalVertexAttribSize._32, VertexAttribPointerType.UnsignedInt }, - { GalVertexAttribSize._8_8_8, VertexAttribPointerType.UnsignedByte }, - { GalVertexAttribSize._8_8, VertexAttribPointerType.UnsignedByte }, - { GalVertexAttribSize._16, VertexAttribPointerType.UnsignedShort }, - { GalVertexAttribSize._8, VertexAttribPointerType.UnsignedByte }, - { GalVertexAttribSize._10_10_10_2, VertexAttribPointerType.UnsignedInt2101010Rev }, - { GalVertexAttribSize._11_11_10, VertexAttribPointerType.UnsignedInt10F11F11FRev } - }; - - private GalPipelineState _old; - - private OglConstBuffer _buffer; - private OglRenderTarget _renderTarget; - private OglRasterizer _rasterizer; - private OglShader _shader; - - private int _vaoHandle; - - public OglPipeline( - OglConstBuffer buffer, - OglRenderTarget renderTarget, - OglRasterizer rasterizer, - OglShader shader) - { - _buffer = buffer; - _renderTarget = renderTarget; - _rasterizer = rasterizer; - _shader = shader; - - // These values match OpenGL's defaults - _old = new GalPipelineState - { - FrontFace = GalFrontFace.Ccw, - - CullFaceEnabled = false, - CullFace = GalCullFace.Back, - - DepthTestEnabled = false, - DepthWriteEnabled = true, - DepthFunc = GalComparisonOp.Less, - DepthRangeNear = 0, - DepthRangeFar = 1, - - StencilTestEnabled = false, - - StencilBackFuncFunc = GalComparisonOp.Always, - StencilBackFuncRef = 0, - StencilBackFuncMask = UInt32.MaxValue, - StencilBackOpFail = GalStencilOp.Keep, - StencilBackOpZFail = GalStencilOp.Keep, - StencilBackOpZPass = GalStencilOp.Keep, - StencilBackMask = UInt32.MaxValue, - - StencilFrontFuncFunc = GalComparisonOp.Always, - StencilFrontFuncRef = 0, - StencilFrontFuncMask = UInt32.MaxValue, - StencilFrontOpFail = GalStencilOp.Keep, - StencilFrontOpZFail = GalStencilOp.Keep, - StencilFrontOpZPass = GalStencilOp.Keep, - StencilFrontMask = UInt32.MaxValue, - - BlendIndependent = false, - - PrimitiveRestartEnabled = false, - PrimitiveRestartIndex = 0 - }; - - for (int index = 0; index < GalPipelineState.RenderTargetsCount; index++) - { - _old.Blends[index] = BlendState.Default; - - _old.ColorMasks[index] = ColorMaskState.Default; - } - } - - public void Bind(GalPipelineState New) - { - BindConstBuffers(New); - - BindVertexLayout(New); - - if (New.FramebufferSrgb != _old.FramebufferSrgb) - { - Enable(EnableCap.FramebufferSrgb, New.FramebufferSrgb); - - _renderTarget.FramebufferSrgb = New.FramebufferSrgb; - } - - if (New.FlipX != _old.FlipX || New.FlipY != _old.FlipY || New.Instance != _old.Instance) - { - _shader.SetExtraData(New.FlipX, New.FlipY, New.Instance); - } - - if (New.FrontFace != _old.FrontFace) - { - GL.FrontFace(OglEnumConverter.GetFrontFace(New.FrontFace)); - } - - if (New.CullFaceEnabled != _old.CullFaceEnabled) - { - Enable(EnableCap.CullFace, New.CullFaceEnabled); - } - - if (New.CullFaceEnabled) - { - if (New.CullFace != _old.CullFace) - { - GL.CullFace(OglEnumConverter.GetCullFace(New.CullFace)); - } - } - - if (New.DepthTestEnabled != _old.DepthTestEnabled) - { - Enable(EnableCap.DepthTest, New.DepthTestEnabled); - } - - if (New.DepthWriteEnabled != _old.DepthWriteEnabled) - { - GL.DepthMask(New.DepthWriteEnabled); - } - - if (New.DepthTestEnabled) - { - if (New.DepthFunc != _old.DepthFunc) - { - GL.DepthFunc(OglEnumConverter.GetDepthFunc(New.DepthFunc)); - } - } - - if (New.DepthRangeNear != _old.DepthRangeNear || - New.DepthRangeFar != _old.DepthRangeFar) - { - GL.DepthRange(New.DepthRangeNear, New.DepthRangeFar); - } - - if (New.StencilTestEnabled != _old.StencilTestEnabled) - { - Enable(EnableCap.StencilTest, New.StencilTestEnabled); - } - - if (New.StencilTwoSideEnabled != _old.StencilTwoSideEnabled) - { - Enable((EnableCap)All.StencilTestTwoSideExt, New.StencilTwoSideEnabled); - } - - if (New.StencilTestEnabled) - { - if (New.StencilBackFuncFunc != _old.StencilBackFuncFunc || - New.StencilBackFuncRef != _old.StencilBackFuncRef || - New.StencilBackFuncMask != _old.StencilBackFuncMask) - { - GL.StencilFuncSeparate( - StencilFace.Back, - OglEnumConverter.GetStencilFunc(New.StencilBackFuncFunc), - New.StencilBackFuncRef, - New.StencilBackFuncMask); - } - - if (New.StencilBackOpFail != _old.StencilBackOpFail || - New.StencilBackOpZFail != _old.StencilBackOpZFail || - New.StencilBackOpZPass != _old.StencilBackOpZPass) - { - GL.StencilOpSeparate( - StencilFace.Back, - OglEnumConverter.GetStencilOp(New.StencilBackOpFail), - OglEnumConverter.GetStencilOp(New.StencilBackOpZFail), - OglEnumConverter.GetStencilOp(New.StencilBackOpZPass)); - } - - if (New.StencilBackMask != _old.StencilBackMask) - { - GL.StencilMaskSeparate(StencilFace.Back, New.StencilBackMask); - } - - if (New.StencilFrontFuncFunc != _old.StencilFrontFuncFunc || - New.StencilFrontFuncRef != _old.StencilFrontFuncRef || - New.StencilFrontFuncMask != _old.StencilFrontFuncMask) - { - GL.StencilFuncSeparate( - StencilFace.Front, - OglEnumConverter.GetStencilFunc(New.StencilFrontFuncFunc), - New.StencilFrontFuncRef, - New.StencilFrontFuncMask); - } - - if (New.StencilFrontOpFail != _old.StencilFrontOpFail || - New.StencilFrontOpZFail != _old.StencilFrontOpZFail || - New.StencilFrontOpZPass != _old.StencilFrontOpZPass) - { - GL.StencilOpSeparate( - StencilFace.Front, - OglEnumConverter.GetStencilOp(New.StencilFrontOpFail), - OglEnumConverter.GetStencilOp(New.StencilFrontOpZFail), - OglEnumConverter.GetStencilOp(New.StencilFrontOpZPass)); - } - - if (New.StencilFrontMask != _old.StencilFrontMask) - { - GL.StencilMaskSeparate(StencilFace.Front, New.StencilFrontMask); - } - } - - - // Scissor Test - // All scissor test are disabled before drawing final framebuffer to screen so we don't need to handle disabling - // Skip if there are no scissor tests to enable - if (New.ScissorTestCount != 0) - { - int scissorsApplied = 0; - bool applyToAll = false; - - for (int index = 0; index < GalPipelineState.RenderTargetsCount; index++) - { - if (New.ScissorTestEnabled[index]) - { - // If viewport arrays are unavailable apply first scissor test to all or - // there is only 1 scissor test and it's the first, the scissor test applies to all viewports - if (!OglExtension.Required.ViewportArray || (index == 0 && New.ScissorTestCount == 1)) - { - GL.Enable(EnableCap.ScissorTest); - applyToAll = true; - } - else - { - GL.Enable(IndexedEnableCap.ScissorTest, index); - } - - if (New.ScissorTestEnabled[index] != _old.ScissorTestEnabled[index] || - New.ScissorTestX[index] != _old.ScissorTestX[index] || - New.ScissorTestY[index] != _old.ScissorTestY[index] || - New.ScissorTestWidth[index] != _old.ScissorTestWidth[index] || - New.ScissorTestHeight[index] != _old.ScissorTestHeight[index]) - { - if (applyToAll) - { - GL.Scissor(New.ScissorTestX[index], New.ScissorTestY[index], - New.ScissorTestWidth[index], New.ScissorTestHeight[index]); - } - else - { - GL.ScissorIndexed(index, New.ScissorTestX[index], New.ScissorTestY[index], - New.ScissorTestWidth[index], New.ScissorTestHeight[index]); - } - } - - // If all scissor tests have been applied, or viewport arrays are unavailable we can skip remaining iterations - if (!OglExtension.Required.ViewportArray || ++scissorsApplied == New.ScissorTestCount) - { - break; - } - } - } - } - - - if (New.BlendIndependent) - { - for (int index = 0; index < GalPipelineState.RenderTargetsCount; index++) - { - SetBlendState(index, New.Blends[index], _old.Blends[index]); - } - } - else - { - if (New.BlendIndependent != _old.BlendIndependent) - { - SetAllBlendState(New.Blends[0]); - } - else - { - SetBlendState(New.Blends[0], _old.Blends[0]); - } - } - - if (New.ColorMaskCommon) - { - if (New.ColorMaskCommon != _old.ColorMaskCommon || !New.ColorMasks[0].Equals(_old.ColorMasks[0])) - { - GL.ColorMask( - New.ColorMasks[0].Red, - New.ColorMasks[0].Green, - New.ColorMasks[0].Blue, - New.ColorMasks[0].Alpha); - } - } - else - { - for (int index = 0; index < GalPipelineState.RenderTargetsCount; index++) - { - if (!New.ColorMasks[index].Equals(_old.ColorMasks[index])) - { - GL.ColorMask( - index, - New.ColorMasks[index].Red, - New.ColorMasks[index].Green, - New.ColorMasks[index].Blue, - New.ColorMasks[index].Alpha); - } - } - } - - if (New.PrimitiveRestartEnabled != _old.PrimitiveRestartEnabled) - { - Enable(EnableCap.PrimitiveRestart, New.PrimitiveRestartEnabled); - } - - if (New.PrimitiveRestartEnabled) - { - if (New.PrimitiveRestartIndex != _old.PrimitiveRestartIndex) - { - GL.PrimitiveRestartIndex(New.PrimitiveRestartIndex); - } - } - - _old = New; - } - - public void Unbind(GalPipelineState state) - { - if (state.ScissorTestCount > 0) - { - GL.Disable(EnableCap.ScissorTest); - } - } - - private void SetAllBlendState(BlendState New) - { - Enable(EnableCap.Blend, New.Enabled); - - if (New.Enabled) - { - if (New.SeparateAlpha) - { - GL.BlendEquationSeparate( - OglEnumConverter.GetBlendEquation(New.EquationRgb), - OglEnumConverter.GetBlendEquation(New.EquationAlpha)); - - GL.BlendFuncSeparate( - (BlendingFactorSrc) OglEnumConverter.GetBlendFactor(New.FuncSrcRgb), - (BlendingFactorDest)OglEnumConverter.GetBlendFactor(New.FuncDstRgb), - (BlendingFactorSrc) OglEnumConverter.GetBlendFactor(New.FuncSrcAlpha), - (BlendingFactorDest)OglEnumConverter.GetBlendFactor(New.FuncDstAlpha)); - } - else - { - GL.BlendEquation(OglEnumConverter.GetBlendEquation(New.EquationRgb)); - - GL.BlendFunc( - OglEnumConverter.GetBlendFactor(New.FuncSrcRgb), - OglEnumConverter.GetBlendFactor(New.FuncDstRgb)); - } - } - } - - private void SetBlendState(BlendState New, BlendState old) - { - if (New.Enabled != old.Enabled) - { - Enable(EnableCap.Blend, New.Enabled); - } - - if (New.Enabled) - { - if (New.SeparateAlpha) - { - if (New.EquationRgb != old.EquationRgb || - New.EquationAlpha != old.EquationAlpha) - { - GL.BlendEquationSeparate( - OglEnumConverter.GetBlendEquation(New.EquationRgb), - OglEnumConverter.GetBlendEquation(New.EquationAlpha)); - } - - if (New.FuncSrcRgb != old.FuncSrcRgb || - New.FuncDstRgb != old.FuncDstRgb || - New.FuncSrcAlpha != old.FuncSrcAlpha || - New.FuncDstAlpha != old.FuncDstAlpha) - { - GL.BlendFuncSeparate( - (BlendingFactorSrc) OglEnumConverter.GetBlendFactor(New.FuncSrcRgb), - (BlendingFactorDest)OglEnumConverter.GetBlendFactor(New.FuncDstRgb), - (BlendingFactorSrc) OglEnumConverter.GetBlendFactor(New.FuncSrcAlpha), - (BlendingFactorDest)OglEnumConverter.GetBlendFactor(New.FuncDstAlpha)); - } - } - else - { - if (New.EquationRgb != old.EquationRgb) - { - GL.BlendEquation(OglEnumConverter.GetBlendEquation(New.EquationRgb)); - } - - if (New.FuncSrcRgb != old.FuncSrcRgb || - New.FuncDstRgb != old.FuncDstRgb) - { - GL.BlendFunc( - OglEnumConverter.GetBlendFactor(New.FuncSrcRgb), - OglEnumConverter.GetBlendFactor(New.FuncDstRgb)); - } - } - } - } - - private void SetBlendState(int index, BlendState New, BlendState old) - { - if (New.Enabled != old.Enabled) - { - Enable(IndexedEnableCap.Blend, index, New.Enabled); - } - - if (New.Enabled) - { - if (New.SeparateAlpha) - { - if (New.EquationRgb != old.EquationRgb || - New.EquationAlpha != old.EquationAlpha) - { - GL.BlendEquationSeparate( - index, - OglEnumConverter.GetBlendEquation(New.EquationRgb), - OglEnumConverter.GetBlendEquation(New.EquationAlpha)); - } - - if (New.FuncSrcRgb != old.FuncSrcRgb || - New.FuncDstRgb != old.FuncDstRgb || - New.FuncSrcAlpha != old.FuncSrcAlpha || - New.FuncDstAlpha != old.FuncDstAlpha) - { - GL.BlendFuncSeparate( - index, - (BlendingFactorSrc) OglEnumConverter.GetBlendFactor(New.FuncSrcRgb), - (BlendingFactorDest)OglEnumConverter.GetBlendFactor(New.FuncDstRgb), - (BlendingFactorSrc) OglEnumConverter.GetBlendFactor(New.FuncSrcAlpha), - (BlendingFactorDest)OglEnumConverter.GetBlendFactor(New.FuncDstAlpha)); - } - } - else - { - if (New.EquationRgb != old.EquationRgb) - { - GL.BlendEquation(index, OglEnumConverter.GetBlendEquation(New.EquationRgb)); - } - - if (New.FuncSrcRgb != old.FuncSrcRgb || - New.FuncDstRgb != old.FuncDstRgb) - { - GL.BlendFunc( - index, - (BlendingFactorSrc) OglEnumConverter.GetBlendFactor(New.FuncSrcRgb), - (BlendingFactorDest)OglEnumConverter.GetBlendFactor(New.FuncDstRgb)); - } - } - } - } - - private void BindConstBuffers(GalPipelineState New) - { - int freeBinding = OglShader.ReservedCbufCount; - - void BindIfNotNull(OglShaderStage stage) - { - if (stage != null) - { - foreach (CBufferDescriptor desc in stage.ConstBufferUsage) - { - long key = New.ConstBufferKeys[(int)stage.Type][desc.Slot]; - - if (key != 0 && _buffer.TryGetUbo(key, out int uboHandle)) - { - GL.BindBufferBase(BufferRangeTarget.UniformBuffer, freeBinding, uboHandle); - } - - freeBinding++; - } - } - } - - BindIfNotNull(_shader.Current.Vertex); - BindIfNotNull(_shader.Current.TessControl); - BindIfNotNull(_shader.Current.TessEvaluation); - BindIfNotNull(_shader.Current.Geometry); - BindIfNotNull(_shader.Current.Fragment); - } - - private void BindVertexLayout(GalPipelineState New) - { - foreach (GalVertexBinding binding in New.VertexBindings) - { - if (!binding.Enabled || !_rasterizer.TryGetVbo(binding.VboKey, out int vboHandle)) - { - continue; - } - - if (_vaoHandle == 0) - { - _vaoHandle = GL.GenVertexArray(); - - // Vertex arrays shouldn't be used anywhere else in OpenGL's backend - // if you want to use it, move this line out of the if - GL.BindVertexArray(_vaoHandle); - } - - foreach (GalVertexAttrib attrib in binding.Attribs) - { - // Skip uninitialized attributes. - if (attrib.Size == 0) - { - continue; - } - - GL.BindBuffer(BufferTarget.ArrayBuffer, vboHandle); - - bool unsigned = - attrib.Type == GalVertexAttribType.Unorm || - attrib.Type == GalVertexAttribType.Uint || - attrib.Type == GalVertexAttribType.Uscaled; - - bool normalize = - attrib.Type == GalVertexAttribType.Snorm || - attrib.Type == GalVertexAttribType.Unorm; - - VertexAttribPointerType type = 0; - - if (attrib.Type == GalVertexAttribType.Float) - { - type = GetType(_floatAttribTypes, attrib); - } - else - { - if (unsigned) - { - type = GetType(_unsignedAttribTypes, attrib); - } - else - { - type = GetType(_signedAttribTypes, attrib); - } - } - - if (!_attribElements.TryGetValue(attrib.Size, out int size)) - { - throw new InvalidOperationException("Invalid attribute size \"" + attrib.Size + "\"!"); - } - - int offset = attrib.Offset; - - if (binding.Stride != 0) - { - GL.EnableVertexAttribArray(attrib.Index); - - if (attrib.Type == GalVertexAttribType.Sint || - attrib.Type == GalVertexAttribType.Uint) - { - IntPtr pointer = new IntPtr(offset); - - VertexAttribIntegerType iType = (VertexAttribIntegerType)type; - - GL.VertexAttribIPointer(attrib.Index, size, iType, binding.Stride, pointer); - } - else - { - GL.VertexAttribPointer(attrib.Index, size, type, normalize, binding.Stride, offset); - } - } - else - { - GL.DisableVertexAttribArray(attrib.Index); - - SetConstAttrib(attrib); - } - - if (binding.Instanced && binding.Divisor != 0) - { - GL.VertexAttribDivisor(attrib.Index, 1); - } - else - { - GL.VertexAttribDivisor(attrib.Index, 0); - } - } - } - } - - private static VertexAttribPointerType GetType(Dictionary dict, GalVertexAttrib attrib) - { - if (!dict.TryGetValue(attrib.Size, out VertexAttribPointerType type)) - { - ThrowUnsupportedAttrib(attrib); - } - - return type; - } - - private static unsafe void SetConstAttrib(GalVertexAttrib attrib) - { - if (attrib.Size == GalVertexAttribSize._10_10_10_2 || - attrib.Size == GalVertexAttribSize._11_11_10) - { - ThrowUnsupportedAttrib(attrib); - } - - fixed (byte* ptr = attrib.Data) - { - if (attrib.Type == GalVertexAttribType.Unorm) - { - switch (attrib.Size) - { - case GalVertexAttribSize._8: - case GalVertexAttribSize._8_8: - case GalVertexAttribSize._8_8_8: - case GalVertexAttribSize._8_8_8_8: - GL.VertexAttrib4N((uint)attrib.Index, ptr); - break; - - case GalVertexAttribSize._16: - case GalVertexAttribSize._16_16: - case GalVertexAttribSize._16_16_16: - case GalVertexAttribSize._16_16_16_16: - GL.VertexAttrib4N((uint)attrib.Index, (ushort*)ptr); - break; - - case GalVertexAttribSize._32: - case GalVertexAttribSize._32_32: - case GalVertexAttribSize._32_32_32: - case GalVertexAttribSize._32_32_32_32: - GL.VertexAttrib4N((uint)attrib.Index, (uint*)ptr); - break; - } - } - else if (attrib.Type == GalVertexAttribType.Snorm) - { - switch (attrib.Size) - { - case GalVertexAttribSize._8: - case GalVertexAttribSize._8_8: - case GalVertexAttribSize._8_8_8: - case GalVertexAttribSize._8_8_8_8: - GL.VertexAttrib4N((uint)attrib.Index, (sbyte*)ptr); - break; - - case GalVertexAttribSize._16: - case GalVertexAttribSize._16_16: - case GalVertexAttribSize._16_16_16: - case GalVertexAttribSize._16_16_16_16: - GL.VertexAttrib4N((uint)attrib.Index, (short*)ptr); - break; - - case GalVertexAttribSize._32: - case GalVertexAttribSize._32_32: - case GalVertexAttribSize._32_32_32: - case GalVertexAttribSize._32_32_32_32: - GL.VertexAttrib4N((uint)attrib.Index, (int*)ptr); - break; - } - } - else if (attrib.Type == GalVertexAttribType.Uint) - { - switch (attrib.Size) - { - case GalVertexAttribSize._8: - case GalVertexAttribSize._8_8: - case GalVertexAttribSize._8_8_8: - case GalVertexAttribSize._8_8_8_8: - GL.VertexAttribI4((uint)attrib.Index, ptr); - break; - - case GalVertexAttribSize._16: - case GalVertexAttribSize._16_16: - case GalVertexAttribSize._16_16_16: - case GalVertexAttribSize._16_16_16_16: - GL.VertexAttribI4((uint)attrib.Index, (ushort*)ptr); - break; - - case GalVertexAttribSize._32: - case GalVertexAttribSize._32_32: - case GalVertexAttribSize._32_32_32: - case GalVertexAttribSize._32_32_32_32: - GL.VertexAttribI4((uint)attrib.Index, (uint*)ptr); - break; - } - } - else if (attrib.Type == GalVertexAttribType.Sint) - { - switch (attrib.Size) - { - case GalVertexAttribSize._8: - case GalVertexAttribSize._8_8: - case GalVertexAttribSize._8_8_8: - case GalVertexAttribSize._8_8_8_8: - GL.VertexAttribI4((uint)attrib.Index, (sbyte*)ptr); - break; - - case GalVertexAttribSize._16: - case GalVertexAttribSize._16_16: - case GalVertexAttribSize._16_16_16: - case GalVertexAttribSize._16_16_16_16: - GL.VertexAttribI4((uint)attrib.Index, (short*)ptr); - break; - - case GalVertexAttribSize._32: - case GalVertexAttribSize._32_32: - case GalVertexAttribSize._32_32_32: - case GalVertexAttribSize._32_32_32_32: - GL.VertexAttribI4((uint)attrib.Index, (int*)ptr); - break; - } - } - else if (attrib.Type == GalVertexAttribType.Float) - { - switch (attrib.Size) - { - case GalVertexAttribSize._32: - case GalVertexAttribSize._32_32: - case GalVertexAttribSize._32_32_32: - case GalVertexAttribSize._32_32_32_32: - GL.VertexAttrib4(attrib.Index, (float*)ptr); - break; - - default: ThrowUnsupportedAttrib(attrib); break; - } - } - } - } - - private static void ThrowUnsupportedAttrib(GalVertexAttrib attrib) - { - throw new NotImplementedException("Unsupported size \"" + attrib.Size + "\" on type \"" + attrib.Type + "\"!"); - } - - private void Enable(EnableCap cap, bool enabled) - { - if (enabled) - { - GL.Enable(cap); - } - else - { - GL.Disable(cap); - } - } - - private void Enable(IndexedEnableCap cap, int index, bool enabled) - { - if (enabled) - { - GL.Enable(cap, index); - } - else - { - GL.Disable(cap, index); - } - } - - public void ResetDepthMask() - { - _old.DepthWriteEnabled = true; - } - - public void ResetColorMask(int index) - { - _old.ColorMasks[index] = ColorMaskState.Default; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OglRasterizer.cs b/Ryujinx.Graphics/Gal/OpenGL/OglRasterizer.cs deleted file mode 100644 index c19911c57a..0000000000 --- a/Ryujinx.Graphics/Gal/OpenGL/OglRasterizer.cs +++ /dev/null @@ -1,207 +0,0 @@ -using OpenTK.Graphics.OpenGL; -using System; - -namespace Ryujinx.Graphics.Gal.OpenGL -{ - class OglRasterizer : IGalRasterizer - { - private const long MaxVertexBufferCacheSize = 128 * 1024 * 1024; - private const long MaxIndexBufferCacheSize = 64 * 1024 * 1024; - - private int[] _vertexBuffers; - - private OglCachedResource _vboCache; - private OglCachedResource _iboCache; - - private struct IbInfo - { - public int Count; - public int ElemSizeLog2; - - public DrawElementsType Type; - } - - private IbInfo _indexBuffer; - - public OglRasterizer() - { - _vertexBuffers = new int[32]; - - _vboCache = new OglCachedResource(GL.DeleteBuffer, MaxVertexBufferCacheSize); - _iboCache = new OglCachedResource(GL.DeleteBuffer, MaxIndexBufferCacheSize); - - _indexBuffer = new IbInfo(); - } - - public void LockCaches() - { - _vboCache.Lock(); - _iboCache.Lock(); - } - - public void UnlockCaches() - { - _vboCache.Unlock(); - _iboCache.Unlock(); - } - - public void ClearBuffers( - GalClearBufferFlags flags, - int attachment, - float red, - float green, - float blue, - float alpha, - float depth, - int stencil) - { - GL.ColorMask( - attachment, - flags.HasFlag(GalClearBufferFlags.ColorRed), - flags.HasFlag(GalClearBufferFlags.ColorGreen), - flags.HasFlag(GalClearBufferFlags.ColorBlue), - flags.HasFlag(GalClearBufferFlags.ColorAlpha)); - - GL.ClearBuffer(ClearBuffer.Color, attachment, new float[] { red, green, blue, alpha }); - - GL.ColorMask(attachment, true, true, true, true); - GL.DepthMask(true); - - if (flags.HasFlag(GalClearBufferFlags.Depth)) - { - GL.ClearBuffer(ClearBuffer.Depth, 0, ref depth); - } - - if (flags.HasFlag(GalClearBufferFlags.Stencil)) - { - GL.ClearBuffer(ClearBuffer.Stencil, 0, ref stencil); - } - } - - public bool IsVboCached(long key, long dataSize) - { - return _vboCache.TryGetSize(key, out long size) && size == dataSize; - } - - public bool IsIboCached(long key, long dataSize) - { - return _iboCache.TryGetSize(key, out long size) && size == dataSize; - } - - public void CreateVbo(long key, int dataSize, IntPtr hostAddress) - { - int handle = GL.GenBuffer(); - - _vboCache.AddOrUpdate(key, handle, dataSize); - - IntPtr length = new IntPtr(dataSize); - - GL.BindBuffer(BufferTarget.ArrayBuffer, handle); - GL.BufferData(BufferTarget.ArrayBuffer, length, hostAddress, BufferUsageHint.StreamDraw); - } - - public void CreateVbo(long key, byte[] data) - { - int handle = GL.GenBuffer(); - - _vboCache.AddOrUpdate(key, handle, data.Length); - - IntPtr length = new IntPtr(data.Length); - - GL.BindBuffer(BufferTarget.ArrayBuffer, handle); - GL.BufferData(BufferTarget.ArrayBuffer, length, data, BufferUsageHint.StreamDraw); - } - - public void CreateIbo(long key, int dataSize, IntPtr hostAddress) - { - int handle = GL.GenBuffer(); - - _iboCache.AddOrUpdate(key, handle, (uint)dataSize); - - IntPtr length = new IntPtr(dataSize); - - GL.BindBuffer(BufferTarget.ElementArrayBuffer, handle); - GL.BufferData(BufferTarget.ElementArrayBuffer, length, hostAddress, BufferUsageHint.StreamDraw); - } - - public void CreateIbo(long key, int dataSize, byte[] buffer) - { - int handle = GL.GenBuffer(); - - _iboCache.AddOrUpdate(key, handle, dataSize); - - IntPtr length = new IntPtr(buffer.Length); - - GL.BindBuffer(BufferTarget.ElementArrayBuffer, handle); - GL.BufferData(BufferTarget.ElementArrayBuffer, length, buffer, BufferUsageHint.StreamDraw); - } - - public void SetIndexArray(int size, GalIndexFormat format) - { - _indexBuffer.Type = OglEnumConverter.GetDrawElementsType(format); - - _indexBuffer.Count = size >> (int)format; - - _indexBuffer.ElemSizeLog2 = (int)format; - } - - public void DrawArrays(int first, int count, GalPrimitiveType primType) - { - if (count == 0) - { - return; - } - - if (primType == GalPrimitiveType.Quads) - { - for (int offset = 0; offset < count; offset += 4) - { - GL.DrawArrays(PrimitiveType.TriangleFan, first + offset, 4); - } - } - else if (primType == GalPrimitiveType.QuadStrip) - { - GL.DrawArrays(PrimitiveType.TriangleFan, first, 4); - - for (int offset = 2; offset < count; offset += 2) - { - GL.DrawArrays(PrimitiveType.TriangleFan, first + offset, 4); - } - } - else - { - GL.DrawArrays(OglEnumConverter.GetPrimitiveType(primType), first, count); - } - } - - public void DrawElements(long iboKey, int first, int vertexBase, GalPrimitiveType primType) - { - if (!_iboCache.TryGetValue(iboKey, out int iboHandle)) - { - return; - } - - PrimitiveType mode = OglEnumConverter.GetPrimitiveType(primType); - - GL.BindBuffer(BufferTarget.ElementArrayBuffer, iboHandle); - - first <<= _indexBuffer.ElemSizeLog2; - - if (vertexBase != 0) - { - IntPtr indices = new IntPtr(first); - - GL.DrawElementsBaseVertex(mode, _indexBuffer.Count, _indexBuffer.Type, indices, vertexBase); - } - else - { - GL.DrawElements(mode, _indexBuffer.Count, _indexBuffer.Type, first); - } - } - - public bool TryGetVbo(long vboKey, out int vboHandle) - { - return _vboCache.TryGetValue(vboKey, out vboHandle); - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OglRenderTarget.cs b/Ryujinx.Graphics/Gal/OpenGL/OglRenderTarget.cs deleted file mode 100644 index 11fc98cb44..0000000000 --- a/Ryujinx.Graphics/Gal/OpenGL/OglRenderTarget.cs +++ /dev/null @@ -1,549 +0,0 @@ -using OpenTK.Graphics.OpenGL; -using Ryujinx.Graphics.Texture; -using System; - -namespace Ryujinx.Graphics.Gal.OpenGL -{ - class OglRenderTarget : IGalRenderTarget - { - private const int NativeWidth = 1280; - private const int NativeHeight = 720; - - private const int RenderTargetsCount = GalPipelineState.RenderTargetsCount; - - private struct Rect - { - public int X { get; private set; } - public int Y { get; private set; } - public int Width { get; private set; } - public int Height { get; private set; } - - public Rect(int x, int y, int width, int height) - { - X = x; - Y = y; - Width = width; - Height = height; - } - } - - private class FrameBufferAttachments - { - public int MapCount { get; set; } - - public DrawBuffersEnum[] Map { get; private set; } - - public long[] Colors { get; private set; } - - public long Zeta { get; set; } - - public FrameBufferAttachments() - { - Colors = new long[RenderTargetsCount]; - - Map = new DrawBuffersEnum[RenderTargetsCount]; - } - - public void Update(FrameBufferAttachments source) - { - for (int index = 0; index < RenderTargetsCount; index++) - { - Map[index] = source.Map[index]; - - Colors[index] = source.Colors[index]; - } - - MapCount = source.MapCount; - Zeta = source.Zeta; - } - } - - private int[] _colorHandles; - private int _zetaHandle; - - private OglTexture _texture; - - private ImageHandler _readTex; - - private Rect _window; - - private float[] _viewports; - - private bool _flipX; - private bool _flipY; - - private int _cropTop; - private int _cropLeft; - private int _cropRight; - private int _cropBottom; - - // This framebuffer is used to attach guest rendertargets, - // think of it as a dummy OpenGL VAO - private int _dummyFrameBuffer; - - // These framebuffers are used to blit images - private int _srcFb; - private int _dstFb; - - private FrameBufferAttachments _attachments; - private FrameBufferAttachments _oldAttachments; - - private int _copyPbo; - - public bool FramebufferSrgb { get; set; } - - public OglRenderTarget(OglTexture texture) - { - _attachments = new FrameBufferAttachments(); - - _oldAttachments = new FrameBufferAttachments(); - - _colorHandles = new int[RenderTargetsCount]; - - _viewports = new float[RenderTargetsCount * 4]; - - _texture = texture; - - texture.TextureDeleted += TextureDeletionHandler; - } - - private void TextureDeletionHandler(object sender, int handle) - { - // Texture was deleted, the handle is no longer valid, so - // reset all uses of this handle on a render target. - for (int attachment = 0; attachment < RenderTargetsCount; attachment++) - { - if (_colorHandles[attachment] == handle) - { - _colorHandles[attachment] = 0; - } - } - - if (_zetaHandle == handle) - { - _zetaHandle = 0; - } - } - - public void Bind() - { - if (_dummyFrameBuffer == 0) - { - _dummyFrameBuffer = GL.GenFramebuffer(); - } - - GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, _dummyFrameBuffer); - - ImageHandler cachedImage; - - for (int attachment = 0; attachment < RenderTargetsCount; attachment++) - { - long key = _attachments.Colors[attachment]; - - int handle = 0; - - if (key != 0 && _texture.TryGetImageHandler(key, out cachedImage)) - { - handle = cachedImage.Handle; - } - - if (handle == _colorHandles[attachment]) - { - continue; - } - - GL.FramebufferTexture( - FramebufferTarget.DrawFramebuffer, - FramebufferAttachment.ColorAttachment0 + attachment, - handle, - 0); - - _colorHandles[attachment] = handle; - } - - if (_attachments.Zeta != 0 && _texture.TryGetImageHandler(_attachments.Zeta, out cachedImage)) - { - if (cachedImage.Handle != _zetaHandle) - { - if (cachedImage.HasDepth && cachedImage.HasStencil) - { - GL.FramebufferTexture( - FramebufferTarget.DrawFramebuffer, - FramebufferAttachment.DepthStencilAttachment, - cachedImage.Handle, - 0); - } - else if (cachedImage.HasDepth) - { - GL.FramebufferTexture( - FramebufferTarget.DrawFramebuffer, - FramebufferAttachment.DepthAttachment, - cachedImage.Handle, - 0); - - GL.FramebufferTexture( - FramebufferTarget.DrawFramebuffer, - FramebufferAttachment.StencilAttachment, - 0, - 0); - } - else - { - throw new InvalidOperationException("Invalid image format \"" + cachedImage.Format + "\" used as Zeta!"); - } - - _zetaHandle = cachedImage.Handle; - } - } - else if (_zetaHandle != 0) - { - GL.FramebufferTexture( - FramebufferTarget.DrawFramebuffer, - FramebufferAttachment.DepthStencilAttachment, - 0, - 0); - - _zetaHandle = 0; - } - - if (OglExtension.ViewportArray) - { - GL.ViewportArray(0, RenderTargetsCount, _viewports); - } - else - { - GL.Viewport( - (int)_viewports[0], - (int)_viewports[1], - (int)_viewports[2], - (int)_viewports[3]); - } - - if (_attachments.MapCount > 1) - { - GL.DrawBuffers(_attachments.MapCount, _attachments.Map); - } - else if (_attachments.MapCount == 1) - { - GL.DrawBuffer((DrawBufferMode)_attachments.Map[0]); - } - else - { - GL.DrawBuffer(DrawBufferMode.None); - } - - _oldAttachments.Update(_attachments); - } - - public void BindColor(long key, int attachment) - { - _attachments.Colors[attachment] = key; - } - - public void UnbindColor(int attachment) - { - _attachments.Colors[attachment] = 0; - } - - public void BindZeta(long key) - { - _attachments.Zeta = key; - } - - public void UnbindZeta() - { - _attachments.Zeta = 0; - } - - public void Present(long key) - { - _texture.TryGetImageHandler(key, out _readTex); - } - - public void SetMap(int[] map) - { - if (map != null) - { - _attachments.MapCount = map.Length; - - for (int attachment = 0; attachment < _attachments.MapCount; attachment++) - { - _attachments.Map[attachment] = DrawBuffersEnum.ColorAttachment0 + map[attachment]; - } - } - else - { - _attachments.MapCount = 0; - } - } - - public void SetTransform(bool flipX, bool flipY, int top, int left, int right, int bottom) - { - _flipX = flipX; - _flipY = flipY; - - _cropTop = top; - _cropLeft = left; - _cropRight = right; - _cropBottom = bottom; - } - - public void SetWindowSize(int width, int height) - { - _window = new Rect(0, 0, width, height); - } - - public void SetViewport(int attachment, int x, int y, int width, int height) - { - int offset = attachment * 4; - - _viewports[offset + 0] = x; - _viewports[offset + 1] = y; - _viewports[offset + 2] = width; - _viewports[offset + 3] = height; - } - - public void Render() - { - if (_readTex == null) - { - return; - } - - int srcX0, srcX1, srcY0, srcY1; - - if (_cropLeft == 0 && _cropRight == 0) - { - srcX0 = 0; - srcX1 = _readTex.Width; - } - else - { - srcX0 = _cropLeft; - srcX1 = _cropRight; - } - - if (_cropTop == 0 && _cropBottom == 0) - { - srcY0 = 0; - srcY1 = _readTex.Height; - } - else - { - srcY0 = _cropTop; - srcY1 = _cropBottom; - } - - float ratioX = MathF.Min(1f, (_window.Height * (float)NativeWidth) / ((float)NativeHeight * _window.Width)); - float ratioY = MathF.Min(1f, (_window.Width * (float)NativeHeight) / ((float)NativeWidth * _window.Height)); - - int dstWidth = (int)(_window.Width * ratioX); - int dstHeight = (int)(_window.Height * ratioY); - - int dstPaddingX = (_window.Width - dstWidth) / 2; - int dstPaddingY = (_window.Height - dstHeight) / 2; - - int dstX0 = _flipX ? _window.Width - dstPaddingX : dstPaddingX; - int dstX1 = _flipX ? dstPaddingX : _window.Width - dstPaddingX; - - int dstY0 = _flipY ? dstPaddingY : _window.Height - dstPaddingY; - int dstY1 = _flipY ? _window.Height - dstPaddingY : dstPaddingY; - - GL.Viewport(0, 0, _window.Width, _window.Height); - - if (_srcFb == 0) - { - _srcFb = GL.GenFramebuffer(); - } - - GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, _srcFb); - GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, 0); - - GL.FramebufferTexture(FramebufferTarget.ReadFramebuffer, FramebufferAttachment.ColorAttachment0, _readTex.Handle, 0); - - GL.ReadBuffer(ReadBufferMode.ColorAttachment0); - - GL.Clear(ClearBufferMask.ColorBufferBit); - - GL.Disable(EnableCap.FramebufferSrgb); - - GL.BlitFramebuffer( - srcX0, - srcY0, - srcX1, - srcY1, - dstX0, - dstY0, - dstX1, - dstY1, - ClearBufferMask.ColorBufferBit, - BlitFramebufferFilter.Linear); - - if (FramebufferSrgb) - { - GL.Enable(EnableCap.FramebufferSrgb); - } - } - - public void Copy( - GalImage srcImage, - GalImage dstImage, - long srcKey, - long dstKey, - int srcLayer, - int dstLayer, - int srcX0, - int srcY0, - int srcX1, - int srcY1, - int dstX0, - int dstY0, - int dstX1, - int dstY1) - { - if (_texture.TryGetImageHandler(srcKey, out ImageHandler srcTex) && - _texture.TryGetImageHandler(dstKey, out ImageHandler dstTex)) - { - if (srcTex.HasColor != dstTex.HasColor || - srcTex.HasDepth != dstTex.HasDepth || - srcTex.HasStencil != dstTex.HasStencil) - { - throw new NotImplementedException(); - } - - if (_srcFb == 0) - { - _srcFb = GL.GenFramebuffer(); - } - - if (_dstFb == 0) - { - _dstFb = GL.GenFramebuffer(); - } - - GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, _srcFb); - GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, _dstFb); - - FramebufferAttachment attachment = GetAttachment(srcTex); - - if (ImageUtils.IsArray(srcImage.TextureTarget) && srcLayer > 0) - { - GL.FramebufferTextureLayer(FramebufferTarget.ReadFramebuffer, attachment, srcTex.Handle, 0, srcLayer); - } - else - { - GL.FramebufferTexture(FramebufferTarget.ReadFramebuffer, attachment, srcTex.Handle, 0); - } - - if (ImageUtils.IsArray(dstImage.TextureTarget) && dstLayer > 0) - { - GL.FramebufferTextureLayer(FramebufferTarget.DrawFramebuffer, attachment, dstTex.Handle, 0, dstLayer); - } - else - { - GL.FramebufferTexture(FramebufferTarget.DrawFramebuffer, attachment, dstTex.Handle, 0); - } - - - BlitFramebufferFilter filter = BlitFramebufferFilter.Nearest; - - if (srcTex.HasColor) - { - GL.DrawBuffer(DrawBufferMode.ColorAttachment0); - - filter = BlitFramebufferFilter.Linear; - } - - ClearBufferMask mask = GetClearMask(srcTex); - - GL.BlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); - } - } - - public void Reinterpret(long key, GalImage newImage) - { - if (!_texture.TryGetImage(key, out GalImage oldImage)) - { - return; - } - - if (newImage.Format == oldImage.Format && - newImage.Width == oldImage.Width && - newImage.Height == oldImage.Height && - newImage.Depth == oldImage.Depth && - newImage.LayerCount == oldImage.LayerCount && - newImage.TextureTarget == oldImage.TextureTarget) - { - return; - } - - if (_copyPbo == 0) - { - _copyPbo = GL.GenBuffer(); - } - - GL.BindBuffer(BufferTarget.PixelPackBuffer, _copyPbo); - - // The buffer should be large enough to hold the largest texture. - int bufferSize = Math.Max(ImageUtils.GetSize(oldImage), - ImageUtils.GetSize(newImage)); - - GL.BufferData(BufferTarget.PixelPackBuffer, bufferSize, IntPtr.Zero, BufferUsageHint.StreamCopy); - - if (!_texture.TryGetImageHandler(key, out ImageHandler cachedImage)) - { - throw new InvalidOperationException(); - } - - (_, PixelFormat format, PixelType type) = OglEnumConverter.GetImageFormat(cachedImage.Format); - - TextureTarget target = ImageUtils.GetTextureTarget(newImage.TextureTarget); - - GL.BindTexture(target, cachedImage.Handle); - - GL.GetTexImage(target, 0, format, type, IntPtr.Zero); - - GL.BindBuffer(BufferTarget.PixelPackBuffer, 0); - GL.BindBuffer(BufferTarget.PixelUnpackBuffer, _copyPbo); - - GL.PixelStore(PixelStoreParameter.UnpackRowLength, oldImage.Width); - - _texture.Create(key, ImageUtils.GetSize(newImage), newImage); - - GL.PixelStore(PixelStoreParameter.UnpackRowLength, 0); - - GL.BindBuffer(BufferTarget.PixelUnpackBuffer, 0); - } - - private static FramebufferAttachment GetAttachment(ImageHandler cachedImage) - { - if (cachedImage.HasColor) - { - return FramebufferAttachment.ColorAttachment0; - } - else if (cachedImage.HasDepth && cachedImage.HasStencil) - { - return FramebufferAttachment.DepthStencilAttachment; - } - else if (cachedImage.HasDepth) - { - return FramebufferAttachment.DepthAttachment; - } - else if (cachedImage.HasStencil) - { - return FramebufferAttachment.StencilAttachment; - } - else - { - throw new InvalidOperationException(); - } - } - - private static ClearBufferMask GetClearMask(ImageHandler cachedImage) - { - return (cachedImage.HasColor ? ClearBufferMask.ColorBufferBit : 0) | - (cachedImage.HasDepth ? ClearBufferMask.DepthBufferBit : 0) | - (cachedImage.HasStencil ? ClearBufferMask.StencilBufferBit : 0); - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OglRenderer.cs b/Ryujinx.Graphics/Gal/OpenGL/OglRenderer.cs deleted file mode 100644 index 1ff8c7ad5d..0000000000 --- a/Ryujinx.Graphics/Gal/OpenGL/OglRenderer.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Collections.Concurrent; - -namespace Ryujinx.Graphics.Gal.OpenGL -{ - public class OglRenderer : IGalRenderer - { - public IGalConstBuffer Buffer { get; private set; } - - public IGalRenderTarget RenderTarget { get; private set; } - - public IGalRasterizer Rasterizer { get; private set; } - - public IGalShader Shader { get; private set; } - - public IGalPipeline Pipeline { get; private set; } - - public IGalTexture Texture { get; private set; } - - private ConcurrentQueue _actionsQueue; - - public OglRenderer() - { - Buffer = new OglConstBuffer(); - - Texture = new OglTexture(); - - RenderTarget = new OglRenderTarget(Texture as OglTexture); - - Rasterizer = new OglRasterizer(); - - Shader = new OglShader(Buffer as OglConstBuffer); - - Pipeline = new OglPipeline( - Buffer as OglConstBuffer, - RenderTarget as OglRenderTarget, - Rasterizer as OglRasterizer, - Shader as OglShader); - - _actionsQueue = new ConcurrentQueue(); - } - - public void QueueAction(Action actionMthd) - { - _actionsQueue.Enqueue(actionMthd); - } - - public void RunActions() - { - int count = _actionsQueue.Count; - - while (count-- > 0 && _actionsQueue.TryDequeue(out Action renderAction)) - { - renderAction(); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OglShader.cs b/Ryujinx.Graphics/Gal/OpenGL/OglShader.cs deleted file mode 100644 index 8da0104e11..0000000000 --- a/Ryujinx.Graphics/Gal/OpenGL/OglShader.cs +++ /dev/null @@ -1,291 +0,0 @@ -using OpenTK.Graphics.OpenGL; -using Ryujinx.Graphics.Shader; -using Ryujinx.Graphics.Shader.Translation; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; - -namespace Ryujinx.Graphics.Gal.OpenGL -{ - class OglShader : IGalShader - { - public const int ReservedCbufCount = 1; - - private const int ExtraDataSize = 4; - - public OglShaderProgram Current; - - private ConcurrentDictionary _stages; - - private Dictionary _programs; - - public int CurrentProgramHandle { get; private set; } - - private OglConstBuffer _buffer; - - private int _extraUboHandle; - - public OglShader(OglConstBuffer buffer) - { - _buffer = buffer; - - _stages = new ConcurrentDictionary(); - - _programs = new Dictionary(); - } - - public void Create(IGalMemory memory, long key, GalShaderType type) - { - _stages.GetOrAdd(key, (stage) => ShaderStageFactory(memory, key, 0, false, type)); - } - - public void Create(IGalMemory memory, long vpAPos, long key, GalShaderType type) - { - _stages.GetOrAdd(key, (stage) => ShaderStageFactory(memory, vpAPos, key, true, type)); - } - - private OglShaderStage ShaderStageFactory( - IGalMemory memory, - long position, - long positionB, - bool isDualVp, - GalShaderType type) - { - ShaderConfig config = new ShaderConfig(type, OglLimit.MaxUboSize); - - ShaderProgram program; - - if (isDualVp) - { - ShaderDumper.Dump(memory, position, type, "a"); - ShaderDumper.Dump(memory, positionB, type, "b"); - - program = Translator.Translate(memory, (ulong)position, (ulong)positionB, config); - } - else - { - ShaderDumper.Dump(memory, position, type); - - program = Translator.Translate(memory, (ulong)position, config); - } - - string code = program.Code; - - if (ShaderDumper.IsDumpEnabled()) - { - int shaderDumpIndex = ShaderDumper.DumpIndex; - - code = "//Shader " + shaderDumpIndex + Environment.NewLine + code; - } - - return new OglShaderStage(type, code, program.Info.CBuffers, program.Info.Textures); - } - - public IEnumerable GetConstBufferUsage(long key) - { - if (_stages.TryGetValue(key, out OglShaderStage stage)) - { - return stage.ConstBufferUsage; - } - - return Enumerable.Empty(); - } - - public IEnumerable GetTextureUsage(long key) - { - if (_stages.TryGetValue(key, out OglShaderStage stage)) - { - return stage.TextureUsage; - } - - return Enumerable.Empty(); - } - - public unsafe void SetExtraData(float flipX, float flipY, int instance) - { - BindProgram(); - - EnsureExtraBlock(); - - GL.BindBuffer(BufferTarget.UniformBuffer, _extraUboHandle); - - float* data = stackalloc float[ExtraDataSize]; - data[0] = flipX; - data[1] = flipY; - data[2] = BitConverter.Int32BitsToSingle(instance); - - // Invalidate buffer - GL.BufferData(BufferTarget.UniformBuffer, ExtraDataSize * sizeof(float), IntPtr.Zero, BufferUsageHint.StreamDraw); - - GL.BufferSubData(BufferTarget.UniformBuffer, IntPtr.Zero, ExtraDataSize * sizeof(float), (IntPtr)data); - } - - public void Bind(long key) - { - if (_stages.TryGetValue(key, out OglShaderStage stage)) - { - Bind(stage); - } - } - - private void Bind(OglShaderStage stage) - { - switch (stage.Type) - { - case GalShaderType.Vertex: Current.Vertex = stage; break; - case GalShaderType.TessControl: Current.TessControl = stage; break; - case GalShaderType.TessEvaluation: Current.TessEvaluation = stage; break; - case GalShaderType.Geometry: Current.Geometry = stage; break; - case GalShaderType.Fragment: Current.Fragment = stage; break; - } - } - - public void Unbind(GalShaderType type) - { - switch (type) - { - case GalShaderType.Vertex: Current.Vertex = null; break; - case GalShaderType.TessControl: Current.TessControl = null; break; - case GalShaderType.TessEvaluation: Current.TessEvaluation = null; break; - case GalShaderType.Geometry: Current.Geometry = null; break; - case GalShaderType.Fragment: Current.Fragment = null; break; - } - } - - public void BindProgram() - { - if (Current.Vertex == null || - Current.Fragment == null) - { - return; - } - - if (!_programs.TryGetValue(Current, out int handle)) - { - handle = GL.CreateProgram(); - - AttachIfNotNull(handle, Current.Vertex); - AttachIfNotNull(handle, Current.TessControl); - AttachIfNotNull(handle, Current.TessEvaluation); - AttachIfNotNull(handle, Current.Geometry); - AttachIfNotNull(handle, Current.Fragment); - - GL.LinkProgram(handle); - - CheckProgramLink(handle); - - BindUniformBlocks(handle); - BindTextureLocations(handle); - - _programs.Add(Current, handle); - } - - GL.UseProgram(handle); - - CurrentProgramHandle = handle; - } - - private void EnsureExtraBlock() - { - if (_extraUboHandle == 0) - { - _extraUboHandle = GL.GenBuffer(); - - GL.BindBuffer(BufferTarget.UniformBuffer, _extraUboHandle); - - GL.BufferData(BufferTarget.UniformBuffer, ExtraDataSize * sizeof(float), IntPtr.Zero, BufferUsageHint.StreamDraw); - - GL.BindBufferBase(BufferRangeTarget.UniformBuffer, 0, _extraUboHandle); - } - } - - private void AttachIfNotNull(int programHandle, OglShaderStage stage) - { - if (stage != null) - { - stage.Compile(); - - GL.AttachShader(programHandle, stage.Handle); - } - } - - private void BindUniformBlocks(int programHandle) - { - int extraBlockindex = GL.GetUniformBlockIndex(programHandle, "Extra"); - - GL.UniformBlockBinding(programHandle, extraBlockindex, 0); - - int freeBinding = ReservedCbufCount; - - void BindUniformBlocksIfNotNull(OglShaderStage stage) - { - if (stage != null) - { - foreach (CBufferDescriptor desc in stage.ConstBufferUsage) - { - int blockIndex = GL.GetUniformBlockIndex(programHandle, desc.Name); - - if (blockIndex < 0) - { - // This may be fine, the compiler may optimize away unused uniform buffers, - // and in this case the above call would return -1 as the buffer has been - // optimized away. - continue; - } - - GL.UniformBlockBinding(programHandle, blockIndex, freeBinding); - - freeBinding++; - } - } - } - - BindUniformBlocksIfNotNull(Current.Vertex); - BindUniformBlocksIfNotNull(Current.TessControl); - BindUniformBlocksIfNotNull(Current.TessEvaluation); - BindUniformBlocksIfNotNull(Current.Geometry); - BindUniformBlocksIfNotNull(Current.Fragment); - } - - private void BindTextureLocations(int programHandle) - { - int index = 0; - - void BindTexturesIfNotNull(OglShaderStage stage) - { - if (stage != null) - { - foreach (TextureDescriptor desc in stage.TextureUsage) - { - int location = GL.GetUniformLocation(programHandle, desc.Name); - - GL.Uniform1(location, index); - - index++; - } - } - } - - GL.UseProgram(programHandle); - - BindTexturesIfNotNull(Current.Vertex); - BindTexturesIfNotNull(Current.TessControl); - BindTexturesIfNotNull(Current.TessEvaluation); - BindTexturesIfNotNull(Current.Geometry); - BindTexturesIfNotNull(Current.Fragment); - } - - private static void CheckProgramLink(int handle) - { - int status = 0; - - GL.GetProgram(handle, GetProgramParameterName.LinkStatus, out status); - - if (status == 0) - { - throw new ShaderException(GL.GetProgramInfoLog(handle)); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OglShaderProgram.cs b/Ryujinx.Graphics/Gal/OpenGL/OglShaderProgram.cs deleted file mode 100644 index 86126bca4d..0000000000 --- a/Ryujinx.Graphics/Gal/OpenGL/OglShaderProgram.cs +++ /dev/null @@ -1,87 +0,0 @@ -using OpenTK.Graphics.OpenGL; -using Ryujinx.Graphics.Shader; -using System; -using System.Collections.Generic; - -namespace Ryujinx.Graphics.Gal.OpenGL -{ - struct OglShaderProgram - { - public OglShaderStage Vertex; - public OglShaderStage TessControl; - public OglShaderStage TessEvaluation; - public OglShaderStage Geometry; - public OglShaderStage Fragment; - } - - class OglShaderStage : IDisposable - { - public int Handle { get; private set; } - - public bool IsCompiled { get; private set; } - - public GalShaderType Type { get; private set; } - - public string Code { get; private set; } - - public IEnumerable ConstBufferUsage { get; private set; } - public IEnumerable TextureUsage { get; private set; } - - public OglShaderStage( - GalShaderType type, - string code, - IEnumerable constBufferUsage, - IEnumerable textureUsage) - { - Type = type; - Code = code; - ConstBufferUsage = constBufferUsage; - TextureUsage = textureUsage; - } - - public void Compile() - { - if (Handle == 0) - { - Handle = GL.CreateShader(OglEnumConverter.GetShaderType(Type)); - - CompileAndCheck(Handle, Code); - } - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing && Handle != 0) - { - GL.DeleteShader(Handle); - - Handle = 0; - } - } - - public static void CompileAndCheck(int handle, string code) - { - GL.ShaderSource(handle, code); - GL.CompileShader(handle); - - CheckCompilation(handle); - } - - private static void CheckCompilation(int handle) - { - int status = 0; - - GL.GetShader(handle, ShaderParameter.CompileStatus, out status); - - if (status == 0) - { - throw new ShaderException(GL.GetShaderInfoLog(handle)); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OglStreamBuffer.cs b/Ryujinx.Graphics/Gal/OpenGL/OglStreamBuffer.cs deleted file mode 100644 index 58b3ace5bb..0000000000 --- a/Ryujinx.Graphics/Gal/OpenGL/OglStreamBuffer.cs +++ /dev/null @@ -1,55 +0,0 @@ -using OpenTK.Graphics.OpenGL; -using System; - -namespace Ryujinx.Graphics.Gal.OpenGL -{ - class OglStreamBuffer : IDisposable - { - public int Handle { get; protected set; } - - public long Size { get; protected set; } - - protected BufferTarget Target { get; private set; } - - public OglStreamBuffer(BufferTarget target, long size) - { - Target = target; - Size = size; - - Handle = GL.GenBuffer(); - - GL.BindBuffer(target, Handle); - - GL.BufferData(target, (IntPtr)size, IntPtr.Zero, BufferUsageHint.StreamDraw); - } - - public void SetData(long size, IntPtr hostAddress) - { - GL.BindBuffer(Target, Handle); - - GL.BufferSubData(Target, IntPtr.Zero, (IntPtr)size, hostAddress); - } - - public void SetData(byte[] data) - { - GL.BindBuffer(Target, Handle); - - GL.BufferSubData(Target, IntPtr.Zero, (IntPtr)data.Length, data); - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing && Handle != 0) - { - GL.DeleteBuffer(Handle); - - Handle = 0; - } - } - } -} diff --git a/Ryujinx.Graphics/Gal/OpenGL/OglTexture.cs b/Ryujinx.Graphics/Gal/OpenGL/OglTexture.cs deleted file mode 100644 index b5ac669279..0000000000 --- a/Ryujinx.Graphics/Gal/OpenGL/OglTexture.cs +++ /dev/null @@ -1,408 +0,0 @@ -using OpenTK.Graphics.OpenGL; -using Ryujinx.Graphics.Texture; -using System; - -namespace Ryujinx.Graphics.Gal.OpenGL -{ - class OglTexture : IGalTexture - { - private const long MaxTextureCacheSize = 768 * 1024 * 1024; - - private OglCachedResource _textureCache; - - public EventHandler TextureDeleted { get; set; } - - public OglTexture() - { - _textureCache = new OglCachedResource(DeleteTexture, MaxTextureCacheSize); - } - - public void LockCache() - { - _textureCache.Lock(); - } - - public void UnlockCache() - { - _textureCache.Unlock(); - } - - private void DeleteTexture(ImageHandler cachedImage) - { - TextureDeleted?.Invoke(this, cachedImage.Handle); - - GL.DeleteTexture(cachedImage.Handle); - } - - public void Create(long key, int size, GalImage image) - { - int handle = GL.GenTexture(); - - TextureTarget target = ImageUtils.GetTextureTarget(image.TextureTarget); - - GL.BindTexture(target, handle); - - const int level = 0; //TODO: Support mipmap textures. - const int border = 0; - - _textureCache.AddOrUpdate(key, new ImageHandler(handle, image), (uint)size); - - if (ImageUtils.IsCompressed(image.Format)) - { - throw new InvalidOperationException("Surfaces with compressed formats are not supported!"); - } - - (PixelInternalFormat internalFmt, - PixelFormat format, - PixelType type) = OglEnumConverter.GetImageFormat(image.Format); - - switch (target) - { - case TextureTarget.Texture1D: - GL.TexImage1D( - target, - level, - internalFmt, - image.Width, - border, - format, - type, - IntPtr.Zero); - break; - - case TextureTarget.Texture2D: - GL.TexImage2D( - target, - level, - internalFmt, - image.Width, - image.Height, - border, - format, - type, - IntPtr.Zero); - break; - case TextureTarget.Texture3D: - GL.TexImage3D( - target, - level, - internalFmt, - image.Width, - image.Height, - image.Depth, - border, - format, - type, - IntPtr.Zero); - break; - // Cube map arrays are just 2D texture arrays with 6 entries - // per cube map so we can handle them in the same way - case TextureTarget.TextureCubeMapArray: - case TextureTarget.Texture2DArray: - GL.TexImage3D( - target, - level, - internalFmt, - image.Width, - image.Height, - image.LayerCount, - border, - format, - type, - IntPtr.Zero); - break; - default: - throw new NotImplementedException($"Unsupported texture target type: {target}"); - } - } - - public void Create(long key, byte[] data, GalImage image) - { - int handle = GL.GenTexture(); - - TextureTarget target = ImageUtils.GetTextureTarget(image.TextureTarget); - - GL.BindTexture(target, handle); - - const int level = 0; //TODO: Support mipmap textures. - const int border = 0; - - _textureCache.AddOrUpdate(key, new ImageHandler(handle, image), (uint)data.Length); - - if (ImageUtils.IsCompressed(image.Format) && !IsAstc(image.Format)) - { - InternalFormat internalFmt = OglEnumConverter.GetCompressedImageFormat(image.Format); - - switch (target) - { - case TextureTarget.Texture1D: - GL.CompressedTexImage1D( - target, - level, - internalFmt, - image.Width, - border, - data.Length, - data); - break; - case TextureTarget.Texture2D: - GL.CompressedTexImage2D( - target, - level, - internalFmt, - image.Width, - image.Height, - border, - data.Length, - data); - break; - case TextureTarget.Texture3D: - GL.CompressedTexImage3D( - target, - level, - internalFmt, - image.Width, - image.Height, - image.Depth, - border, - data.Length, - data); - break; - // Cube map arrays are just 2D texture arrays with 6 entries - // per cube map so we can handle them in the same way - case TextureTarget.TextureCubeMapArray: - case TextureTarget.Texture2DArray: - GL.CompressedTexImage3D( - target, - level, - internalFmt, - image.Width, - image.Height, - image.LayerCount, - border, - data.Length, - data); - break; - case TextureTarget.TextureCubeMap: - Span array = new Span(data); - - int faceSize = ImageUtils.GetSize(image) / 6; - - for (int Face = 0; Face < 6; Face++) - { - GL.CompressedTexImage2D( - TextureTarget.TextureCubeMapPositiveX + Face, - level, - internalFmt, - image.Width, - image.Height, - border, - faceSize, - array.Slice(Face * faceSize, faceSize).ToArray()); - } - break; - default: - throw new NotImplementedException($"Unsupported texture target type: {target}"); - } - } - else - { - // TODO: Use KHR_texture_compression_astc_hdr when available - if (IsAstc(image.Format)) - { - int textureBlockWidth = ImageUtils.GetBlockWidth(image.Format); - int textureBlockHeight = ImageUtils.GetBlockHeight(image.Format); - int textureBlockDepth = ImageUtils.GetBlockDepth(image.Format); - - data = AstcDecoder.DecodeToRgba8888( - data, - textureBlockWidth, - textureBlockHeight, - textureBlockDepth, - image.Width, - image.Height, - image.Depth); - - image.Format = GalImageFormat.Rgba8 | (image.Format & GalImageFormat.TypeMask); - } - - (PixelInternalFormat internalFmt, - PixelFormat format, - PixelType type) = OglEnumConverter.GetImageFormat(image.Format); - - - switch (target) - { - case TextureTarget.Texture1D: - GL.TexImage1D( - target, - level, - internalFmt, - image.Width, - border, - format, - type, - data); - break; - case TextureTarget.Texture2D: - GL.TexImage2D( - target, - level, - internalFmt, - image.Width, - image.Height, - border, - format, - type, - data); - break; - case TextureTarget.Texture3D: - GL.TexImage3D( - target, - level, - internalFmt, - image.Width, - image.Height, - image.Depth, - border, - format, - type, - data); - break; - // Cube map arrays are just 2D texture arrays with 6 entries - // per cube map so we can handle them in the same way - case TextureTarget.TextureCubeMapArray: - case TextureTarget.Texture2DArray: - GL.TexImage3D( - target, - level, - internalFmt, - image.Width, - image.Height, - image.LayerCount, - border, - format, - type, - data); - break; - case TextureTarget.TextureCubeMap: - Span array = new Span(data); - - int faceSize = ImageUtils.GetSize(image) / 6; - - for (int face = 0; face < 6; face++) - { - GL.TexImage2D( - TextureTarget.TextureCubeMapPositiveX + face, - level, - internalFmt, - image.Width, - image.Height, - border, - format, - type, - array.Slice(face * faceSize, faceSize).ToArray()); - } - break; - default: - throw new NotImplementedException($"Unsupported texture target type: {target}"); - } - } - } - - private static bool IsAstc(GalImageFormat format) - { - format &= GalImageFormat.FormatMask; - - return format > GalImageFormat.Astc2DStart && format < GalImageFormat.Astc2DEnd; - } - - public bool TryGetImage(long key, out GalImage image) - { - if (_textureCache.TryGetValue(key, out ImageHandler cachedImage)) - { - image = cachedImage.Image; - - return true; - } - - image = default(GalImage); - - return false; - } - - public bool TryGetImageHandler(long key, out ImageHandler cachedImage) - { - if (_textureCache.TryGetValue(key, out cachedImage)) - { - return true; - } - - cachedImage = null; - - return false; - } - - public void Bind(long key, int index, GalImage image) - { - if (_textureCache.TryGetValue(key, out ImageHandler cachedImage)) - { - GL.ActiveTexture(TextureUnit.Texture0 + index); - - TextureTarget target = ImageUtils.GetTextureTarget(image.TextureTarget); - - GL.BindTexture(target, cachedImage.Handle); - - int[] swizzleRgba = new int[] - { - (int)OglEnumConverter.GetTextureSwizzle(image.XSource), - (int)OglEnumConverter.GetTextureSwizzle(image.YSource), - (int)OglEnumConverter.GetTextureSwizzle(image.ZSource), - (int)OglEnumConverter.GetTextureSwizzle(image.WSource) - }; - - GL.TexParameter(target, TextureParameterName.TextureSwizzleRgba, swizzleRgba); - } - } - - public void SetSampler(GalImage image, GalTextureSampler sampler) - { - int wrapS = (int)OglEnumConverter.GetTextureWrapMode(sampler.AddressU); - int wrapT = (int)OglEnumConverter.GetTextureWrapMode(sampler.AddressV); - int wrapR = (int)OglEnumConverter.GetTextureWrapMode(sampler.AddressP); - - int minFilter = (int)OglEnumConverter.GetTextureMinFilter(sampler.MinFilter, sampler.MipFilter); - int magFilter = (int)OglEnumConverter.GetTextureMagFilter(sampler.MagFilter); - - TextureTarget target = ImageUtils.GetTextureTarget(image.TextureTarget); - - GL.TexParameter(target, TextureParameterName.TextureWrapS, wrapS); - GL.TexParameter(target, TextureParameterName.TextureWrapT, wrapT); - GL.TexParameter(target, TextureParameterName.TextureWrapR, wrapR); - - GL.TexParameter(target, TextureParameterName.TextureMinFilter, minFilter); - GL.TexParameter(target, TextureParameterName.TextureMagFilter, magFilter); - - float[] color = new float[] - { - sampler.BorderColor.Red, - sampler.BorderColor.Green, - sampler.BorderColor.Blue, - sampler.BorderColor.Alpha - }; - - GL.TexParameter(target, TextureParameterName.TextureBorderColor, color); - - if (sampler.DepthCompare) - { - GL.TexParameter(target, TextureParameterName.TextureCompareMode, (int)All.CompareRToTexture); - GL.TexParameter(target, TextureParameterName.TextureCompareFunc, (int)OglEnumConverter.GetDepthCompareFunc(sampler.DepthCompareFunc)); - } - else - { - GL.TexParameter(target, TextureParameterName.TextureCompareMode, (int)All.None); - GL.TexParameter(target, TextureParameterName.TextureCompareFunc, (int)All.Never); - } - } - } -} diff --git a/Ryujinx.Graphics/Gal/ShaderDumper.cs b/Ryujinx.Graphics/Gal/ShaderDumper.cs deleted file mode 100644 index e25127deba..0000000000 --- a/Ryujinx.Graphics/Gal/ShaderDumper.cs +++ /dev/null @@ -1,132 +0,0 @@ -using System; -using System.IO; - -namespace Ryujinx.Graphics.Gal -{ - static class ShaderDumper - { - private static string _runtimeDir; - - public static int DumpIndex { get; private set; } = 1; - - public static void Dump(IGalMemory memory, long position, GalShaderType type, string extSuffix = "") - { - if (!IsDumpEnabled()) - { - return; - } - - string fileName = "Shader" + DumpIndex.ToString("d4") + "." + ShaderExtension(type) + extSuffix + ".bin"; - - string fullPath = Path.Combine(FullDir(), fileName); - string codePath = Path.Combine(CodeDir(), fileName); - - DumpIndex++; - - using (FileStream fullFile = File.Create(fullPath)) - using (FileStream codeFile = File.Create(codePath)) - { - BinaryWriter fullWriter = new BinaryWriter(fullFile); - BinaryWriter codeWriter = new BinaryWriter(codeFile); - - for (long i = 0; i < 0x50; i += 4) - { - fullWriter.Write(memory.ReadInt32(position + i)); - } - - long offset = 0; - - ulong instruction = 0; - - // Dump until a NOP instruction is found - while ((instruction >> 48 & 0xfff8) != 0x50b0) - { - uint word0 = (uint)memory.ReadInt32(position + 0x50 + offset + 0); - uint word1 = (uint)memory.ReadInt32(position + 0x50 + offset + 4); - - instruction = word0 | (ulong)word1 << 32; - - // Zero instructions (other kind of NOP) stop immediately, - // this is to avoid two rows of zeroes - if (instruction == 0) - { - break; - } - - fullWriter.Write(instruction); - codeWriter.Write(instruction); - - offset += 8; - } - - // Align to meet nvdisasm requirements - while (offset % 0x20 != 0) - { - fullWriter.Write(0); - codeWriter.Write(0); - - offset += 4; - } - } - } - - public static bool IsDumpEnabled() - { - return !string.IsNullOrWhiteSpace(GraphicsConfig.ShadersDumpPath); - } - - private static string FullDir() - { - return CreateAndReturn(Path.Combine(DumpDir(), "Full")); - } - - private static string CodeDir() - { - return CreateAndReturn(Path.Combine(DumpDir(), "Code")); - } - - private static string DumpDir() - { - if (string.IsNullOrEmpty(_runtimeDir)) - { - int index = 1; - - do - { - _runtimeDir = Path.Combine(GraphicsConfig.ShadersDumpPath, "Dumps" + index.ToString("d2")); - - index++; - } - while (Directory.Exists(_runtimeDir)); - - Directory.CreateDirectory(_runtimeDir); - } - - return _runtimeDir; - } - - private static string CreateAndReturn(string dir) - { - if (!Directory.Exists(dir)) - { - Directory.CreateDirectory(dir); - } - - return dir; - } - - private static string ShaderExtension(GalShaderType type) - { - switch (type) - { - case GalShaderType.Vertex: return "vert"; - case GalShaderType.TessControl: return "tesc"; - case GalShaderType.TessEvaluation: return "tese"; - case GalShaderType.Geometry: return "geom"; - case GalShaderType.Fragment: return "frag"; - - default: throw new ArgumentException(nameof(type)); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/ShaderException.cs b/Ryujinx.Graphics/Gal/ShaderException.cs deleted file mode 100644 index b0aff42bf5..0000000000 --- a/Ryujinx.Graphics/Gal/ShaderException.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; - -namespace Ryujinx.Graphics.Gal -{ - class ShaderException : Exception - { - public ShaderException() : base() { } - - public ShaderException(string message) : base(message) { } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/GpuMethodCall.cs b/Ryujinx.Graphics/GpuMethodCall.cs deleted file mode 100644 index 4a310b0751..0000000000 --- a/Ryujinx.Graphics/GpuMethodCall.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Ryujinx.Graphics -{ - struct GpuMethodCall - { - public int Method { get; private set; } - public int Argument { get; private set; } - public int SubChannel { get; private set; } - public int MethodCount { get; private set; } - - public bool IsLastCall => MethodCount <= 1; - - public GpuMethodCall( - 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/GpuResourceManager.cs b/Ryujinx.Graphics/GpuResourceManager.cs deleted file mode 100644 index 16e9f57901..0000000000 --- a/Ryujinx.Graphics/GpuResourceManager.cs +++ /dev/null @@ -1,169 +0,0 @@ -using Ryujinx.Graphics.Gal; -using Ryujinx.Graphics.Memory; -using Ryujinx.Graphics.Texture; -using System.Collections.Generic; - -namespace Ryujinx.Graphics -{ - public class GpuResourceManager - { - private enum ImageType - { - None, - Texture, - TextureArrayLayer, - ColorBuffer, - ZetaBuffer - } - - private NvGpu _gpu; - - private HashSet[] _uploadedKeys; - - private Dictionary _imageTypes; - private Dictionary _mirroredTextures; - - public GpuResourceManager(NvGpu gpu) - { - _gpu = gpu; - - _uploadedKeys = new HashSet[(int)NvGpuBufferType.Count]; - - for (int index = 0; index < _uploadedKeys.Length; index++) - { - _uploadedKeys[index] = new HashSet(); - } - - _imageTypes = new Dictionary(); - _mirroredTextures = new Dictionary(); - } - - public void SendColorBuffer(NvGpuVmm vmm, long position, int attachment, GalImage newImage) - { - long size = (uint)ImageUtils.GetSize(newImage); - - _imageTypes[position] = ImageType.ColorBuffer; - - if (!TryReuse(vmm, position, newImage)) - { - _gpu.Renderer.Texture.Create(position, (int)size, newImage); - } - - _gpu.Renderer.RenderTarget.BindColor(position, attachment); - } - - public void SendZetaBuffer(NvGpuVmm vmm, long position, GalImage newImage) - { - long size = (uint)ImageUtils.GetSize(newImage); - - _imageTypes[position] = ImageType.ZetaBuffer; - - if (!TryReuse(vmm, position, newImage)) - { - _gpu.Renderer.Texture.Create(position, (int)size, newImage); - } - - _gpu.Renderer.RenderTarget.BindZeta(position); - } - - public void SendTexture(NvGpuVmm vmm, long position, GalImage newImage) - { - PrepareSendTexture(vmm, position, newImage); - - _imageTypes[position] = ImageType.Texture; - } - - public bool TryGetTextureLayer(long position, out int layerIndex) - { - if (_mirroredTextures.TryGetValue(position, out layerIndex)) - { - ImageType type = _imageTypes[position]; - - // FIXME(thog): I'm actually unsure if we should deny all other image type, gpu testing needs to be done here. - if (type != ImageType.Texture && type != ImageType.TextureArrayLayer) - { - layerIndex = -1; - return false; - } - - return true; - } - - layerIndex = -1; - return false; - } - - public void SetTextureArrayLayer(long position, int layerIndex) - { - _imageTypes[position] = ImageType.TextureArrayLayer; - _mirroredTextures[position] = layerIndex; - } - - private void PrepareSendTexture(NvGpuVmm vmm, long position, GalImage newImage) - { - long size = ImageUtils.GetSize(newImage); - - bool skipCheck = false; - - if (_imageTypes.TryGetValue(position, out ImageType oldType)) - { - if (oldType == ImageType.ColorBuffer || oldType == ImageType.ZetaBuffer) - { - // Avoid data destruction - MemoryRegionModified(vmm, position, size, NvGpuBufferType.Texture); - - skipCheck = true; - } - } - - if (skipCheck || !MemoryRegionModified(vmm, position, size, NvGpuBufferType.Texture)) - { - if (TryReuse(vmm, position, newImage)) - { - return; - } - } - - byte[] data = ImageUtils.ReadTexture(vmm, newImage, position); - - _gpu.Renderer.Texture.Create(position, data, newImage); - } - - private bool TryReuse(NvGpuVmm vmm, long position, GalImage newImage) - { - if (_gpu.Renderer.Texture.TryGetImage(position, out GalImage cachedImage) && cachedImage.TextureTarget == newImage.TextureTarget && cachedImage.SizeMatches(newImage)) - { - _gpu.Renderer.RenderTarget.Reinterpret(position, newImage); - - return true; - } - - return false; - } - - public bool MemoryRegionModified(NvGpuVmm vmm, long position, long size, NvGpuBufferType type) - { - HashSet uploaded = _uploadedKeys[(int)type]; - - if (!uploaded.Add(position)) - { - return false; - } - - return vmm.IsRegionModified(position, size, type); - } - - public void ClearPbCache() - { - for (int index = 0; index < _uploadedKeys.Length; index++) - { - _uploadedKeys[index].Clear(); - } - } - - public void ClearPbCache(NvGpuBufferType type) - { - _uploadedKeys[(int)type].Clear(); - } - } -} diff --git a/Ryujinx.Graphics/Graphics3d/INvGpuEngine.cs b/Ryujinx.Graphics/Graphics3d/INvGpuEngine.cs deleted file mode 100644 index aa0db6826f..0000000000 --- a/Ryujinx.Graphics/Graphics3d/INvGpuEngine.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Ryujinx.Graphics.Memory; - -namespace Ryujinx.Graphics.Graphics3d -{ - interface INvGpuEngine - { - int[] Registers { get; } - - void CallMethod(NvGpuVmm vmm, GpuMethodCall methCall); - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Graphics3d/NvGpuEngine.cs b/Ryujinx.Graphics/Graphics3d/NvGpuEngine.cs deleted file mode 100644 index 20c36fda33..0000000000 --- a/Ryujinx.Graphics/Graphics3d/NvGpuEngine.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Ryujinx.Graphics.Graphics3d -{ - enum NvGpuEngine - { - _2d = 0x902d, - _3d = 0xb197, - Compute = 0xb1c0, - P2mf = 0xa140, - M2mf = 0xb0b5 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Graphics3d/NvGpuEngine2d.cs b/Ryujinx.Graphics/Graphics3d/NvGpuEngine2d.cs deleted file mode 100644 index b6dae2a38e..0000000000 --- a/Ryujinx.Graphics/Graphics3d/NvGpuEngine2d.cs +++ /dev/null @@ -1,263 +0,0 @@ -using Ryujinx.Graphics.Gal; -using Ryujinx.Graphics.Memory; -using Ryujinx.Graphics.Texture; -using Ryujinx.Profiler; - -namespace Ryujinx.Graphics.Graphics3d -{ - class NvGpuEngine2d : INvGpuEngine - { - private enum CopyOperation - { - SrcCopyAnd, - RopAnd, - Blend, - SrcCopy, - Rop, - SrcCopyPremult, - BlendPremult - } - - public int[] Registers { get; private set; } - - private NvGpu _gpu; - - public NvGpuEngine2d(NvGpu gpu) - { - _gpu = gpu; - - Registers = new int[0x238]; - } - - public void CallMethod(NvGpuVmm vmm, GpuMethodCall methCall) - { - WriteRegister(methCall); - - if ((NvGpuEngine2dReg)methCall.Method == NvGpuEngine2dReg.BlitSrcYInt) - { - TextureCopy(vmm); - } - } - - private void TextureCopy(NvGpuVmm vmm) - { - Profile.Begin(Profiles.GPU.Engine2d.TextureCopy); - - CopyOperation operation = (CopyOperation)ReadRegister(NvGpuEngine2dReg.CopyOperation); - - int dstFormat = ReadRegister(NvGpuEngine2dReg.DstFormat); - bool dstLinear = ReadRegister(NvGpuEngine2dReg.DstLinear) != 0; - int dstWidth = ReadRegister(NvGpuEngine2dReg.DstWidth); - int dstHeight = ReadRegister(NvGpuEngine2dReg.DstHeight); - int dstDepth = ReadRegister(NvGpuEngine2dReg.DstDepth); - int dstLayer = ReadRegister(NvGpuEngine2dReg.DstLayer); - int dstPitch = ReadRegister(NvGpuEngine2dReg.DstPitch); - int dstBlkDim = ReadRegister(NvGpuEngine2dReg.DstBlockDimensions); - - int srcFormat = ReadRegister(NvGpuEngine2dReg.SrcFormat); - bool srcLinear = ReadRegister(NvGpuEngine2dReg.SrcLinear) != 0; - int srcWidth = ReadRegister(NvGpuEngine2dReg.SrcWidth); - int srcHeight = ReadRegister(NvGpuEngine2dReg.SrcHeight); - int srcDepth = ReadRegister(NvGpuEngine2dReg.SrcDepth); - int srcLayer = ReadRegister(NvGpuEngine2dReg.SrcLayer); - int srcPitch = ReadRegister(NvGpuEngine2dReg.SrcPitch); - int srcBlkDim = ReadRegister(NvGpuEngine2dReg.SrcBlockDimensions); - - int dstBlitX = ReadRegister(NvGpuEngine2dReg.BlitDstX); - int dstBlitY = ReadRegister(NvGpuEngine2dReg.BlitDstY); - int dstBlitW = ReadRegister(NvGpuEngine2dReg.BlitDstW); - int dstBlitH = ReadRegister(NvGpuEngine2dReg.BlitDstH); - - long blitDuDx = ReadRegisterFixed1_31_32(NvGpuEngine2dReg.BlitDuDxFract); - long blitDvDy = ReadRegisterFixed1_31_32(NvGpuEngine2dReg.BlitDvDyFract); - - long srcBlitX = ReadRegisterFixed1_31_32(NvGpuEngine2dReg.BlitSrcXFract); - long srcBlitY = ReadRegisterFixed1_31_32(NvGpuEngine2dReg.BlitSrcYFract); - - GalImageFormat srcImgFormat = ImageUtils.ConvertSurface((GalSurfaceFormat)srcFormat); - GalImageFormat dstImgFormat = ImageUtils.ConvertSurface((GalSurfaceFormat)dstFormat); - - GalMemoryLayout srcLayout = GetLayout(srcLinear); - GalMemoryLayout dstLayout = GetLayout(dstLinear); - - int srcBlockHeight = 1 << ((srcBlkDim >> 4) & 0xf); - int dstBlockHeight = 1 << ((dstBlkDim >> 4) & 0xf); - - long srcAddress = MakeInt64From2xInt32(NvGpuEngine2dReg.SrcAddress); - long dstAddress = MakeInt64From2xInt32(NvGpuEngine2dReg.DstAddress); - - long srcKey = vmm.GetPhysicalAddress(srcAddress); - long dstKey = vmm.GetPhysicalAddress(dstAddress); - - bool isSrcLayered = false; - bool isDstLayered = false; - - GalTextureTarget srcTarget = GalTextureTarget.TwoD; - - if (srcDepth != 0) - { - srcTarget = GalTextureTarget.TwoDArray; - srcDepth++; - isSrcLayered = true; - } - else - { - srcDepth = 1; - } - - GalTextureTarget dstTarget = GalTextureTarget.TwoD; - - if (dstDepth != 0) - { - dstTarget = GalTextureTarget.TwoDArray; - dstDepth++; - isDstLayered = true; - } - else - { - dstDepth = 1; - } - - GalImage srcTexture = new GalImage( - srcWidth, - srcHeight, - 1, srcDepth, 1, - srcBlockHeight, 1, - srcLayout, - srcImgFormat, - srcTarget); - - GalImage dstTexture = new GalImage( - dstWidth, - dstHeight, - 1, dstDepth, 1, - dstBlockHeight, 1, - dstLayout, - dstImgFormat, - dstTarget); - - srcTexture.Pitch = srcPitch; - dstTexture.Pitch = dstPitch; - - long GetLayerOffset(GalImage image, int layer) - { - int targetMipLevel = image.MaxMipmapLevel <= 1 ? 1 : image.MaxMipmapLevel - 1; - return ImageUtils.GetLayerOffset(image, targetMipLevel) * layer; - } - - int srcLayerIndex = -1; - - if (isSrcLayered && _gpu.ResourceManager.TryGetTextureLayer(srcKey, out srcLayerIndex) && srcLayerIndex != 0) - { - srcKey = srcKey - GetLayerOffset(srcTexture, srcLayerIndex); - } - - int dstLayerIndex = -1; - - if (isDstLayered && _gpu.ResourceManager.TryGetTextureLayer(dstKey, out dstLayerIndex) && dstLayerIndex != 0) - { - dstKey = dstKey - GetLayerOffset(dstTexture, dstLayerIndex); - } - - _gpu.ResourceManager.SendTexture(vmm, srcKey, srcTexture); - _gpu.ResourceManager.SendTexture(vmm, dstKey, dstTexture); - - if (isSrcLayered && srcLayerIndex == -1) - { - for (int layer = 0; layer < srcTexture.LayerCount; layer++) - { - _gpu.ResourceManager.SetTextureArrayLayer(srcKey + GetLayerOffset(srcTexture, layer), layer); - } - - srcLayerIndex = 0; - } - - if (isDstLayered && dstLayerIndex == -1) - { - for (int layer = 0; layer < dstTexture.LayerCount; layer++) - { - _gpu.ResourceManager.SetTextureArrayLayer(dstKey + GetLayerOffset(dstTexture, layer), layer); - } - - dstLayerIndex = 0; - } - - int srcBlitX1 = (int)(srcBlitX >> 32); - int srcBlitY1 = (int)(srcBlitY >> 32); - - int srcBlitX2 = (int)(srcBlitX + dstBlitW * blitDuDx >> 32); - int srcBlitY2 = (int)(srcBlitY + dstBlitH * blitDvDy >> 32); - - _gpu.Renderer.RenderTarget.Copy( - srcTexture, - dstTexture, - srcKey, - dstKey, - srcLayerIndex, - dstLayerIndex, - srcBlitX1, - srcBlitY1, - srcBlitX2, - srcBlitY2, - dstBlitX, - dstBlitY, - dstBlitX + dstBlitW, - dstBlitY + dstBlitH); - - // Do a guest side copy as well. This is necessary when - // the texture is modified by the guest, however it doesn't - // work when resources that the gpu can write to are copied, - // like framebuffers. - - // FIXME: SUPPORT MULTILAYER CORRECTLY HERE (this will cause weird stuffs on the first layer) - ImageUtils.CopyTexture( - vmm, - srcTexture, - dstTexture, - srcAddress, - dstAddress, - srcBlitX1, - srcBlitY1, - dstBlitX, - dstBlitY, - dstBlitW, - dstBlitH); - - vmm.IsRegionModified(dstKey, ImageUtils.GetSize(dstTexture), NvGpuBufferType.Texture); - - Profile.End(Profiles.GPU.Engine2d.TextureCopy); - } - - private static GalMemoryLayout GetLayout(bool linear) - { - return linear - ? GalMemoryLayout.Pitch - : GalMemoryLayout.BlockLinear; - } - - private long MakeInt64From2xInt32(NvGpuEngine2dReg reg) - { - return - (long)Registers[(int)reg + 0] << 32 | - (uint)Registers[(int)reg + 1]; - } - - private void WriteRegister(GpuMethodCall methCall) - { - Registers[methCall.Method] = methCall.Argument; - } - - private long ReadRegisterFixed1_31_32(NvGpuEngine2dReg reg) - { - long low = (uint)ReadRegister(reg + 0); - long high = (uint)ReadRegister(reg + 1); - - return low | (high << 32); - } - - private int ReadRegister(NvGpuEngine2dReg reg) - { - return Registers[(int)reg]; - } - } -} diff --git a/Ryujinx.Graphics/Graphics3d/NvGpuEngine2dReg.cs b/Ryujinx.Graphics/Graphics3d/NvGpuEngine2dReg.cs deleted file mode 100644 index 7747b5a3ab..0000000000 --- a/Ryujinx.Graphics/Graphics3d/NvGpuEngine2dReg.cs +++ /dev/null @@ -1,41 +0,0 @@ -namespace Ryujinx.Graphics.Graphics3d -{ - enum NvGpuEngine2dReg - { - DstFormat = 0x80, - DstLinear = 0x81, - DstBlockDimensions = 0x82, - DstDepth = 0x83, - DstLayer = 0x84, - DstPitch = 0x85, - DstWidth = 0x86, - DstHeight = 0x87, - DstAddress = 0x88, - DstAddressLow = 0x89, - SrcFormat = 0x8c, - SrcLinear = 0x8d, - SrcBlockDimensions = 0x8e, - SrcDepth = 0x8f, - SrcLayer = 0x90, - SrcPitch = 0x91, - SrcWidth = 0x92, - SrcHeight = 0x93, - SrcAddress = 0x94, - SrcAddressLow = 0x95, - ClipEnable = 0xa4, - CopyOperation = 0xab, - BlitControl = 0x223, - BlitDstX = 0x22c, - BlitDstY = 0x22d, - BlitDstW = 0x22e, - BlitDstH = 0x22f, - BlitDuDxFract = 0x230, - BlitDuDxInt = 0x231, - BlitDvDyFract = 0x232, - BlitDvDyInt = 0x233, - BlitSrcXFract = 0x234, - BlitSrcXInt = 0x235, - BlitSrcYFract = 0x236, - BlitSrcYInt = 0x237 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Graphics3d/NvGpuEngine3d.cs b/Ryujinx.Graphics/Graphics3d/NvGpuEngine3d.cs deleted file mode 100644 index e475a96414..0000000000 --- a/Ryujinx.Graphics/Graphics3d/NvGpuEngine3d.cs +++ /dev/null @@ -1,1237 +0,0 @@ -using Ryujinx.Common; -using Ryujinx.Graphics.Gal; -using Ryujinx.Graphics.Memory; -using Ryujinx.Graphics.Shader; -using Ryujinx.Graphics.Texture; -using System; -using System.Collections.Generic; -using Ryujinx.Profiler; - -namespace Ryujinx.Graphics.Graphics3d -{ - class NvGpuEngine3d : INvGpuEngine - { - public int[] Registers { get; private set; } - - private NvGpu _gpu; - - private Dictionary _methods; - - private struct ConstBuffer - { - public bool Enabled; - public long Position; - public int Size; - } - - private ConstBuffer[][] _constBuffers; - - // Viewport dimensions kept for scissor test limits - private int _viewportX0 = 0; - private int _viewportY0 = 0; - private int _viewportX1 = 0; - private int _viewportY1 = 0; - private int _viewportWidth = 0; - private int _viewportHeight = 0; - - private int _currentInstance = 0; - - public NvGpuEngine3d(NvGpu gpu) - { - _gpu = gpu; - - Registers = new int[0xe00]; - - _methods = new Dictionary(); - - void AddMethod(int meth, int count, int stride, NvGpuMethod method) - { - while (count-- > 0) - { - _methods.Add(meth, method); - - meth += stride; - } - } - - AddMethod(0x585, 1, 1, VertexEndGl); - AddMethod(0x674, 1, 1, ClearBuffers); - AddMethod(0x6c3, 1, 1, QueryControl); - AddMethod(0x8e4, 16, 1, CbData); - AddMethod(0x904, 5, 8, CbBind); - - _constBuffers = new ConstBuffer[6][]; - - for (int index = 0; index < _constBuffers.Length; index++) - { - _constBuffers[index] = new ConstBuffer[18]; - } - - // Ensure that all components are enabled by default. - // FIXME: Is this correct? - WriteRegister(NvGpuEngine3dReg.ColorMaskN, 0x1111); - - WriteRegister(NvGpuEngine3dReg.FrameBufferSrgb, 1); - - WriteRegister(NvGpuEngine3dReg.FrontFace, (int)GalFrontFace.Cw); - - for (int index = 0; index < GalPipelineState.RenderTargetsCount; index++) - { - WriteRegister(NvGpuEngine3dReg.IBlendNEquationRgb + index * 8, (int)GalBlendEquation.FuncAdd); - WriteRegister(NvGpuEngine3dReg.IBlendNFuncSrcRgb + index * 8, (int)GalBlendFactor.One); - WriteRegister(NvGpuEngine3dReg.IBlendNFuncDstRgb + index * 8, (int)GalBlendFactor.Zero); - WriteRegister(NvGpuEngine3dReg.IBlendNEquationAlpha + index * 8, (int)GalBlendEquation.FuncAdd); - WriteRegister(NvGpuEngine3dReg.IBlendNFuncSrcAlpha + index * 8, (int)GalBlendFactor.One); - WriteRegister(NvGpuEngine3dReg.IBlendNFuncDstAlpha + index * 8, (int)GalBlendFactor.Zero); - } - } - - public void CallMethod(NvGpuVmm vmm, GpuMethodCall methCall) - { - if (_methods.TryGetValue(methCall.Method, out NvGpuMethod method)) - { - ProfileConfig profile = Profiles.GPU.Engine3d.CallMethod; - - profile.SessionItem = method.Method.Name; - - Profile.Begin(profile); - - method(vmm, methCall); - - Profile.End(profile); - } - else - { - WriteRegister(methCall); - } - } - - private void VertexEndGl(NvGpuVmm vmm, GpuMethodCall methCall) - { - Profile.Begin(Profiles.GPU.Engine3d.VertexEnd); - - LockCaches(); - - Profile.Begin(Profiles.GPU.Engine3d.ConfigureState); - - GalPipelineState state = new GalPipelineState(); - - // Framebuffer must be run configured because viewport dimensions may be used in other methods - SetFrameBuffer(state); - - Profile.End(Profiles.GPU.Engine3d.ConfigureState); - - for (int fbIndex = 0; fbIndex < 8; fbIndex++) - { - SetFrameBuffer(vmm, fbIndex); - } - - SetFrontFace(state); - SetCullFace(state); - SetDepth(state); - SetStencil(state); - SetScissor(state); - SetBlending(state); - SetColorMask(state); - SetPrimitiveRestart(state); - - SetZeta(vmm); - - SetRenderTargets(); - - long[] keys = UploadShaders(vmm); - - _gpu.Renderer.Shader.BindProgram(); - - UploadTextures(vmm, state, keys); - UploadConstBuffers(vmm, state, keys); - UploadVertexArrays(vmm, state); - - DispatchRender(vmm, state); - - UnlockCaches(); - - Profile.End(Profiles.GPU.Engine3d.VertexEnd); - } - - private void LockCaches() - { - _gpu.Renderer.Buffer.LockCache(); - _gpu.Renderer.Rasterizer.LockCaches(); - _gpu.Renderer.Texture.LockCache(); - } - - private void UnlockCaches() - { - _gpu.Renderer.Buffer.UnlockCache(); - _gpu.Renderer.Rasterizer.UnlockCaches(); - _gpu.Renderer.Texture.UnlockCache(); - } - - private void ClearBuffers(NvGpuVmm vmm, GpuMethodCall methCall) - { - Profile.Begin(Profiles.GPU.Engine3d.ClearBuffers); - - int attachment = (methCall.Argument >> 6) & 0xf; - - GalClearBufferFlags flags = (GalClearBufferFlags)(methCall.Argument & 0x3f); - - float red = ReadRegisterFloat(NvGpuEngine3dReg.ClearNColor + 0); - float green = ReadRegisterFloat(NvGpuEngine3dReg.ClearNColor + 1); - float blue = ReadRegisterFloat(NvGpuEngine3dReg.ClearNColor + 2); - float alpha = ReadRegisterFloat(NvGpuEngine3dReg.ClearNColor + 3); - - float depth = ReadRegisterFloat(NvGpuEngine3dReg.ClearDepth); - - int stencil = ReadRegister(NvGpuEngine3dReg.ClearStencil); - - SetFrameBuffer(vmm, attachment); - - SetZeta(vmm); - - SetRenderTargets(); - - _gpu.Renderer.RenderTarget.Bind(); - - _gpu.Renderer.Rasterizer.ClearBuffers(flags, attachment, red, green, blue, alpha, depth, stencil); - - _gpu.Renderer.Pipeline.ResetDepthMask(); - _gpu.Renderer.Pipeline.ResetColorMask(attachment); - - Profile.End(Profiles.GPU.Engine3d.ClearBuffers); - } - - private void SetFrameBuffer(NvGpuVmm vmm, int fbIndex) - { - ProfileConfig profile = Profiles.GPU.Engine3d.SetFrameBuffer; - - profile.SessionItem = fbIndex.ToString(); - - Profile.Begin(profile); - - long va = MakeInt64From2xInt32(NvGpuEngine3dReg.FrameBufferNAddress + fbIndex * 0x10); - - int surfFormat = ReadRegister(NvGpuEngine3dReg.FrameBufferNFormat + fbIndex * 0x10); - - if (va == 0 || surfFormat == 0) - { - _gpu.Renderer.RenderTarget.UnbindColor(fbIndex); - - Profile.End(profile); - - return; - } - - long key = vmm.GetPhysicalAddress(va); - - int width = ReadRegister(NvGpuEngine3dReg.FrameBufferNWidth + fbIndex * 0x10); - int height = ReadRegister(NvGpuEngine3dReg.FrameBufferNHeight + fbIndex * 0x10); - - int arrayMode = ReadRegister(NvGpuEngine3dReg.FrameBufferNArrayMode + fbIndex * 0x10); - int layerCount = arrayMode & 0xFFFF; - int layerStride = ReadRegister(NvGpuEngine3dReg.FrameBufferNLayerStride + fbIndex * 0x10); - int baseLayer = ReadRegister(NvGpuEngine3dReg.FrameBufferNBaseLayer + fbIndex * 0x10); - int blockDim = ReadRegister(NvGpuEngine3dReg.FrameBufferNBlockDim + fbIndex * 0x10); - - int gobBlockHeight = 1 << ((blockDim >> 4) & 7); - - GalMemoryLayout layout = (GalMemoryLayout)((blockDim >> 12) & 1); - - float tx = ReadRegisterFloat(NvGpuEngine3dReg.ViewportNTranslateX + fbIndex * 8); - float ty = ReadRegisterFloat(NvGpuEngine3dReg.ViewportNTranslateY + fbIndex * 8); - - float sx = ReadRegisterFloat(NvGpuEngine3dReg.ViewportNScaleX + fbIndex * 8); - float sy = ReadRegisterFloat(NvGpuEngine3dReg.ViewportNScaleY + fbIndex * 8); - - _viewportX0 = (int)MathF.Max(0, tx - MathF.Abs(sx)); - _viewportY0 = (int)MathF.Max(0, ty - MathF.Abs(sy)); - - _viewportX1 = (int)(tx + MathF.Abs(sx)); - _viewportY1 = (int)(ty + MathF.Abs(sy)); - - GalImageFormat format = ImageUtils.ConvertSurface((GalSurfaceFormat)surfFormat); - - GalImage image = new GalImage(width, height, 1, 1, 1, gobBlockHeight, 1, layout, format, GalTextureTarget.TwoD); - - _gpu.ResourceManager.SendColorBuffer(vmm, key, fbIndex, image); - - _gpu.Renderer.RenderTarget.SetViewport(fbIndex, _viewportX0, _viewportY0, _viewportX1 - _viewportX0, _viewportY1 - _viewportY0); - - Profile.End(profile); - } - - private void SetFrameBuffer(GalPipelineState state) - { - state.FramebufferSrgb = ReadRegisterBool(NvGpuEngine3dReg.FrameBufferSrgb); - - state.FlipX = GetFlipSign(NvGpuEngine3dReg.ViewportNScaleX); - state.FlipY = GetFlipSign(NvGpuEngine3dReg.ViewportNScaleY); - - int screenYControl = ReadRegister(NvGpuEngine3dReg.ScreenYControl); - - bool negateY = (screenYControl & 1) != 0; - - if (negateY) - { - state.FlipY = -state.FlipY; - } - } - - private void SetZeta(NvGpuVmm vmm) - { - Profile.Begin(Profiles.GPU.Engine3d.SetZeta); - - long va = MakeInt64From2xInt32(NvGpuEngine3dReg.ZetaAddress); - - int zetaFormat = ReadRegister(NvGpuEngine3dReg.ZetaFormat); - - int blockDim = ReadRegister(NvGpuEngine3dReg.ZetaBlockDimensions); - - int gobBlockHeight = 1 << ((blockDim >> 4) & 7); - - GalMemoryLayout layout = (GalMemoryLayout)((blockDim >> 12) & 1); //? - - bool zetaEnable = ReadRegisterBool(NvGpuEngine3dReg.ZetaEnable); - - if (va == 0 || zetaFormat == 0 || !zetaEnable) - { - _gpu.Renderer.RenderTarget.UnbindZeta(); - - Profile.End(Profiles.GPU.Engine3d.SetZeta); - - return; - } - - long key = vmm.GetPhysicalAddress(va); - - int width = ReadRegister(NvGpuEngine3dReg.ZetaHoriz); - int height = ReadRegister(NvGpuEngine3dReg.ZetaVert); - - GalImageFormat format = ImageUtils.ConvertZeta((GalZetaFormat)zetaFormat); - - // TODO: Support non 2D? - GalImage image = new GalImage(width, height, 1, 1, 1, gobBlockHeight, 1, layout, format, GalTextureTarget.TwoD); - - _gpu.ResourceManager.SendZetaBuffer(vmm, key, image); - - Profile.End(Profiles.GPU.Engine3d.SetZeta); - } - - private long[] UploadShaders(NvGpuVmm vmm) - { - Profile.Begin(Profiles.GPU.Engine3d.UploadShaders); - - long[] keys = new long[5]; - - long basePosition = MakeInt64From2xInt32(NvGpuEngine3dReg.ShaderAddress); - - int index = 1; - - int vpAControl = ReadRegister(NvGpuEngine3dReg.ShaderNControl); - - bool vpAEnable = (vpAControl & 1) != 0; - - if (vpAEnable) - { - // Note: The maxwell supports 2 vertex programs, usually - // only VP B is used, but in some cases VP A is also used. - // In this case, it seems to function as an extra vertex - // shader stage. - // The graphics abstraction layer has a special overload for this - // case, which should merge the two shaders into one vertex shader. - int vpAOffset = ReadRegister(NvGpuEngine3dReg.ShaderNOffset); - int vpBOffset = ReadRegister(NvGpuEngine3dReg.ShaderNOffset + 0x10); - - long vpAPos = basePosition + (uint)vpAOffset; - long vpBPos = basePosition + (uint)vpBOffset; - - keys[(int)GalShaderType.Vertex] = vpBPos; - - _gpu.Renderer.Shader.Create(vmm, vpAPos, vpBPos, GalShaderType.Vertex); - _gpu.Renderer.Shader.Bind(vpBPos); - - index = 2; - } - - for (; index < 6; index++) - { - GalShaderType type = GetTypeFromProgram(index); - - int control = ReadRegister(NvGpuEngine3dReg.ShaderNControl + index * 0x10); - int offset = ReadRegister(NvGpuEngine3dReg.ShaderNOffset + index * 0x10); - - // Note: Vertex Program (B) is always enabled. - bool enable = (control & 1) != 0 || index == 1; - - if (!enable) - { - _gpu.Renderer.Shader.Unbind(type); - - continue; - } - - long key = basePosition + (uint)offset; - - keys[(int)type] = key; - - _gpu.Renderer.Shader.Create(vmm, key, type); - _gpu.Renderer.Shader.Bind(key); - } - - Profile.End(Profiles.GPU.Engine3d.UploadShaders); - - return keys; - } - - private static GalShaderType GetTypeFromProgram(int program) - { - switch (program) - { - case 0: - case 1: return GalShaderType.Vertex; - case 2: return GalShaderType.TessControl; - case 3: return GalShaderType.TessEvaluation; - case 4: return GalShaderType.Geometry; - case 5: return GalShaderType.Fragment; - } - - throw new ArgumentOutOfRangeException(nameof(program)); - } - - private void SetFrontFace(GalPipelineState state) - { - float signX = GetFlipSign(NvGpuEngine3dReg.ViewportNScaleX); - float signY = GetFlipSign(NvGpuEngine3dReg.ViewportNScaleY); - - GalFrontFace frontFace = (GalFrontFace)ReadRegister(NvGpuEngine3dReg.FrontFace); - - // Flipping breaks facing. Flipping front facing too fixes it - if (signX != signY) - { - switch (frontFace) - { - case GalFrontFace.Cw: frontFace = GalFrontFace.Ccw; break; - case GalFrontFace.Ccw: frontFace = GalFrontFace.Cw; break; - } - } - - state.FrontFace = frontFace; - } - - private void SetCullFace(GalPipelineState state) - { - state.CullFaceEnabled = ReadRegisterBool(NvGpuEngine3dReg.CullFaceEnable); - - if (state.CullFaceEnabled) - { - state.CullFace = (GalCullFace)ReadRegister(NvGpuEngine3dReg.CullFace); - } - } - - private void SetDepth(GalPipelineState state) - { - state.DepthTestEnabled = ReadRegisterBool(NvGpuEngine3dReg.DepthTestEnable); - - state.DepthWriteEnabled = ReadRegisterBool(NvGpuEngine3dReg.DepthWriteEnable); - - if (state.DepthTestEnabled) - { - state.DepthFunc = (GalComparisonOp)ReadRegister(NvGpuEngine3dReg.DepthTestFunction); - } - - state.DepthRangeNear = ReadRegisterFloat(NvGpuEngine3dReg.DepthRangeNNear); - state.DepthRangeFar = ReadRegisterFloat(NvGpuEngine3dReg.DepthRangeNFar); - } - - private void SetStencil(GalPipelineState state) - { - state.StencilTestEnabled = ReadRegisterBool(NvGpuEngine3dReg.StencilEnable); - - if (state.StencilTestEnabled) - { - state.StencilBackFuncFunc = (GalComparisonOp)ReadRegister(NvGpuEngine3dReg.StencilBackFuncFunc); - state.StencilBackFuncRef = ReadRegister(NvGpuEngine3dReg.StencilBackFuncRef); - state.StencilBackFuncMask = (uint)ReadRegister(NvGpuEngine3dReg.StencilBackFuncMask); - state.StencilBackOpFail = (GalStencilOp)ReadRegister(NvGpuEngine3dReg.StencilBackOpFail); - state.StencilBackOpZFail = (GalStencilOp)ReadRegister(NvGpuEngine3dReg.StencilBackOpZFail); - state.StencilBackOpZPass = (GalStencilOp)ReadRegister(NvGpuEngine3dReg.StencilBackOpZPass); - state.StencilBackMask = (uint)ReadRegister(NvGpuEngine3dReg.StencilBackMask); - - state.StencilFrontFuncFunc = (GalComparisonOp)ReadRegister(NvGpuEngine3dReg.StencilFrontFuncFunc); - state.StencilFrontFuncRef = ReadRegister(NvGpuEngine3dReg.StencilFrontFuncRef); - state.StencilFrontFuncMask = (uint)ReadRegister(NvGpuEngine3dReg.StencilFrontFuncMask); - state.StencilFrontOpFail = (GalStencilOp)ReadRegister(NvGpuEngine3dReg.StencilFrontOpFail); - state.StencilFrontOpZFail = (GalStencilOp)ReadRegister(NvGpuEngine3dReg.StencilFrontOpZFail); - state.StencilFrontOpZPass = (GalStencilOp)ReadRegister(NvGpuEngine3dReg.StencilFrontOpZPass); - state.StencilFrontMask = (uint)ReadRegister(NvGpuEngine3dReg.StencilFrontMask); - } - } - - private void SetScissor(GalPipelineState state) - { - int count = 0; - - for (int index = 0; index < GalPipelineState.RenderTargetsCount; index++) - { - state.ScissorTestEnabled[index] = ReadRegisterBool(NvGpuEngine3dReg.ScissorEnable + index * 4); - - if (state.ScissorTestEnabled[index]) - { - uint scissorHorizontal = (uint)ReadRegister(NvGpuEngine3dReg.ScissorHorizontal + index * 4); - uint scissorVertical = (uint)ReadRegister(NvGpuEngine3dReg.ScissorVertical + index * 4); - - int left = (int)(scissorHorizontal & 0xFFFF); // Left, lower 16 bits - int right = (int)(scissorHorizontal >> 16); // Right, upper 16 bits - - int bottom = (int)(scissorVertical & 0xFFFF); // Bottom, lower 16 bits - int top = (int)(scissorVertical >> 16); // Top, upper 16 bits - - int width = Math.Abs(right - left); - int height = Math.Abs(top - bottom); - - // If the scissor test covers the whole possible viewport, i.e. uninitialized, disable scissor test - if ((width > NvGpu.MaxViewportSize && height > NvGpu.MaxViewportSize) || width <= 0 || height <= 0) - { - state.ScissorTestEnabled[index] = false; - continue; - } - - // Keep track of how many scissor tests are active. - // If only 1, and it's the first user should apply to all viewports - count++; - - // Flip X - if (state.FlipX == -1) - { - left = _viewportX1 - (left - _viewportX0); - right = _viewportX1 - (right - _viewportX0); - } - - // Ensure X is in the right order - if (left > right) - { - int temp = left; - left = right; - right = temp; - } - - // Flip Y - if (state.FlipY == -1) - { - bottom = _viewportY1 - (bottom - _viewportY0); - top = _viewportY1 - (top - _viewportY0); - } - - // Ensure Y is in the right order - if (bottom > top) - { - int temp = top; - top = bottom; - bottom = temp; - } - - // Handle out of active viewport dimensions - left = Math.Clamp(left, _viewportX0, _viewportX1); - right = Math.Clamp(right, _viewportX0, _viewportX1); - top = Math.Clamp(top, _viewportY0, _viewportY1); - bottom = Math.Clamp(bottom, _viewportY0, _viewportY1); - - // Save values to state - state.ScissorTestX[index] = left; - state.ScissorTestY[index] = bottom; - - state.ScissorTestWidth[index] = right - left; - state.ScissorTestHeight[index] = top - bottom; - } - } - - state.ScissorTestCount = count; - } - - private void SetBlending(GalPipelineState state) - { - bool blendIndependent = ReadRegisterBool(NvGpuEngine3dReg.BlendIndependent); - - state.BlendIndependent = blendIndependent; - - for (int index = 0; index < GalPipelineState.RenderTargetsCount; index++) - { - if (blendIndependent) - { - state.Blends[index].Enabled = ReadRegisterBool(NvGpuEngine3dReg.IBlendNEnable + index); - - if (state.Blends[index].Enabled) - { - state.Blends[index].SeparateAlpha = ReadRegisterBool(NvGpuEngine3dReg.IBlendNSeparateAlpha + index * 8); - - state.Blends[index].EquationRgb = ReadBlendEquation(NvGpuEngine3dReg.IBlendNEquationRgb + index * 8); - state.Blends[index].FuncSrcRgb = ReadBlendFactor (NvGpuEngine3dReg.IBlendNFuncSrcRgb + index * 8); - state.Blends[index].FuncDstRgb = ReadBlendFactor (NvGpuEngine3dReg.IBlendNFuncDstRgb + index * 8); - state.Blends[index].EquationAlpha = ReadBlendEquation(NvGpuEngine3dReg.IBlendNEquationAlpha + index * 8); - state.Blends[index].FuncSrcAlpha = ReadBlendFactor (NvGpuEngine3dReg.IBlendNFuncSrcAlpha + index * 8); - state.Blends[index].FuncDstAlpha = ReadBlendFactor (NvGpuEngine3dReg.IBlendNFuncDstAlpha + index * 8); - } - } - else - { - // It seems that even when independent blend is disabled, the first IBlend enable - // register is still set to indicate whenever blend is enabled or not (?). - state.Blends[index].Enabled = ReadRegisterBool(NvGpuEngine3dReg.IBlendNEnable); - - if (state.Blends[index].Enabled) - { - state.Blends[index].SeparateAlpha = ReadRegisterBool(NvGpuEngine3dReg.BlendSeparateAlpha); - - state.Blends[index].EquationRgb = ReadBlendEquation(NvGpuEngine3dReg.BlendEquationRgb); - state.Blends[index].FuncSrcRgb = ReadBlendFactor (NvGpuEngine3dReg.BlendFuncSrcRgb); - state.Blends[index].FuncDstRgb = ReadBlendFactor (NvGpuEngine3dReg.BlendFuncDstRgb); - state.Blends[index].EquationAlpha = ReadBlendEquation(NvGpuEngine3dReg.BlendEquationAlpha); - state.Blends[index].FuncSrcAlpha = ReadBlendFactor (NvGpuEngine3dReg.BlendFuncSrcAlpha); - state.Blends[index].FuncDstAlpha = ReadBlendFactor (NvGpuEngine3dReg.BlendFuncDstAlpha); - } - } - } - } - - private GalBlendEquation ReadBlendEquation(NvGpuEngine3dReg register) - { - return (GalBlendEquation)ReadRegister(register); - } - - private GalBlendFactor ReadBlendFactor(NvGpuEngine3dReg register) - { - return (GalBlendFactor)ReadRegister(register); - } - - private void SetColorMask(GalPipelineState state) - { - bool colorMaskCommon = ReadRegisterBool(NvGpuEngine3dReg.ColorMaskCommon); - - state.ColorMaskCommon = colorMaskCommon; - - for (int index = 0; index < GalPipelineState.RenderTargetsCount; index++) - { - int colorMask = ReadRegister(NvGpuEngine3dReg.ColorMaskN + (colorMaskCommon ? 0 : index)); - - state.ColorMasks[index].Red = ((colorMask >> 0) & 0xf) != 0; - state.ColorMasks[index].Green = ((colorMask >> 4) & 0xf) != 0; - state.ColorMasks[index].Blue = ((colorMask >> 8) & 0xf) != 0; - state.ColorMasks[index].Alpha = ((colorMask >> 12) & 0xf) != 0; - } - } - - private void SetPrimitiveRestart(GalPipelineState state) - { - state.PrimitiveRestartEnabled = ReadRegisterBool(NvGpuEngine3dReg.PrimRestartEnable); - - if (state.PrimitiveRestartEnabled) - { - state.PrimitiveRestartIndex = (uint)ReadRegister(NvGpuEngine3dReg.PrimRestartIndex); - } - } - - private void SetRenderTargets() - { - // Commercial games do not seem to - // bool SeparateFragData = ReadRegisterBool(NvGpuEngine3dReg.RTSeparateFragData); - - uint control = (uint)(ReadRegister(NvGpuEngine3dReg.RtControl)); - - uint count = control & 0xf; - - if (count > 0) - { - int[] map = new int[count]; - - for (int index = 0; index < count; index++) - { - int shift = 4 + index * 3; - - map[index] = (int)((control >> shift) & 7); - } - - _gpu.Renderer.RenderTarget.SetMap(map); - } - else - { - _gpu.Renderer.RenderTarget.SetMap(null); - } - } - - private void UploadTextures(NvGpuVmm vmm, GalPipelineState state, long[] keys) - { - Profile.Begin(Profiles.GPU.Engine3d.UploadTextures); - - long baseShPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.ShaderAddress); - - int textureCbIndex = ReadRegister(NvGpuEngine3dReg.TextureCbIndex); - - List<(long, GalImage, GalTextureSampler)> unboundTextures = new List<(long, GalImage, GalTextureSampler)>(); - - for (int index = 0; index < keys.Length; index++) - { - foreach (TextureDescriptor desc in _gpu.Renderer.Shader.GetTextureUsage(keys[index])) - { - int textureHandle; - - if (desc.IsBindless) - { - long position = _constBuffers[index][desc.CbufSlot].Position; - - textureHandle = vmm.ReadInt32(position + desc.CbufOffset * 4); - } - else - { - long position = _constBuffers[index][textureCbIndex].Position; - - textureHandle = vmm.ReadInt32(position + desc.HandleIndex * 4); - } - - unboundTextures.Add(UploadTexture(vmm, textureHandle)); - } - } - - for (int index = 0; index < unboundTextures.Count; index++) - { - (long key, GalImage image, GalTextureSampler sampler) = unboundTextures[index]; - - if (key == 0) - { - continue; - } - - _gpu.Renderer.Texture.Bind(key, index, image); - _gpu.Renderer.Texture.SetSampler(image, sampler); - } - - Profile.End(Profiles.GPU.Engine3d.UploadTextures); - } - - private (long, GalImage, GalTextureSampler) UploadTexture(NvGpuVmm vmm, int textureHandle) - { - if (textureHandle == 0) - { - // FIXME: Some games like puyo puyo will use handles with the value 0. - // This is a bug, most likely caused by sync issues. - return (0, default(GalImage), default(GalTextureSampler)); - } - - Profile.Begin(Profiles.GPU.Engine3d.UploadTexture); - - bool linkedTsc = ReadRegisterBool(NvGpuEngine3dReg.LinkedTsc); - - int ticIndex = (textureHandle >> 0) & 0xfffff; - - int tscIndex = linkedTsc ? ticIndex : (textureHandle >> 20) & 0xfff; - - long ticPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.TexHeaderPoolOffset); - long tscPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.TexSamplerPoolOffset); - - ticPosition += ticIndex * 0x20; - tscPosition += tscIndex * 0x20; - - GalImage image = TextureFactory.MakeTexture(vmm, ticPosition); - - GalTextureSampler sampler = TextureFactory.MakeSampler(_gpu, vmm, tscPosition); - - long key = vmm.ReadInt64(ticPosition + 4) & 0xffffffffffff; - - if (image.Layout == GalMemoryLayout.BlockLinear) - { - key &= ~0x1ffL; - } - else if (image.Layout == GalMemoryLayout.Pitch) - { - key &= ~0x1fL; - } - - key = vmm.GetPhysicalAddress(key); - - if (key == -1) - { - Profile.End(Profiles.GPU.Engine3d.UploadTexture); - - // FIXME: Shouldn't ignore invalid addresses. - return (0, default(GalImage), default(GalTextureSampler)); - } - - _gpu.ResourceManager.SendTexture(vmm, key, image); - - Profile.End(Profiles.GPU.Engine3d.UploadTexture); - - return (key, image, sampler); - } - - private void UploadConstBuffers(NvGpuVmm vmm, GalPipelineState state, long[] keys) - { - Profile.Begin(Profiles.GPU.Engine3d.UploadConstBuffers); - - for (int stage = 0; stage < keys.Length; stage++) - { - foreach (CBufferDescriptor desc in _gpu.Renderer.Shader.GetConstBufferUsage(keys[stage])) - { - ConstBuffer cb = _constBuffers[stage][desc.Slot]; - - if (!cb.Enabled) - { - continue; - } - - long key = vmm.GetPhysicalAddress(cb.Position); - - if (_gpu.ResourceManager.MemoryRegionModified(vmm, key, cb.Size, NvGpuBufferType.ConstBuffer)) - { - if (vmm.TryGetHostAddress(cb.Position, cb.Size, out IntPtr cbPtr)) - { - _gpu.Renderer.Buffer.SetData(key, cb.Size, cbPtr); - } - else - { - _gpu.Renderer.Buffer.SetData(key, vmm.ReadBytes(cb.Position, cb.Size)); - } - } - - state.ConstBufferKeys[stage][desc.Slot] = key; - } - } - - Profile.End(Profiles.GPU.Engine3d.UploadConstBuffers); - } - - private void UploadVertexArrays(NvGpuVmm vmm, GalPipelineState state) - { - Profile.Begin(Profiles.GPU.Engine3d.UploadVertexArrays); - - long ibPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.IndexArrayAddress); - - long iboKey = vmm.GetPhysicalAddress(ibPosition); - - int indexEntryFmt = ReadRegister(NvGpuEngine3dReg.IndexArrayFormat); - int indexCount = ReadRegister(NvGpuEngine3dReg.IndexBatchCount); - int primCtrl = ReadRegister(NvGpuEngine3dReg.VertexBeginGl); - - GalPrimitiveType primType = (GalPrimitiveType)(primCtrl & 0xffff); - - GalIndexFormat indexFormat = (GalIndexFormat)indexEntryFmt; - - int indexEntrySize = 1 << indexEntryFmt; - - if (indexEntrySize > 4) - { - throw new InvalidOperationException("Invalid index entry size \"" + indexEntrySize + "\"!"); - } - - if (indexCount != 0) - { - int ibSize = indexCount * indexEntrySize; - - bool iboCached = _gpu.Renderer.Rasterizer.IsIboCached(iboKey, (uint)ibSize); - - bool usesLegacyQuads = - primType == GalPrimitiveType.Quads || - primType == GalPrimitiveType.QuadStrip; - - if (!iboCached || _gpu.ResourceManager.MemoryRegionModified(vmm, iboKey, (uint)ibSize, NvGpuBufferType.Index)) - { - if (!usesLegacyQuads) - { - if (vmm.TryGetHostAddress(ibPosition, ibSize, out IntPtr ibPtr)) - { - _gpu.Renderer.Rasterizer.CreateIbo(iboKey, ibSize, ibPtr); - } - else - { - _gpu.Renderer.Rasterizer.CreateIbo(iboKey, ibSize, vmm.ReadBytes(ibPosition, ibSize)); - } - } - else - { - byte[] buffer = vmm.ReadBytes(ibPosition, ibSize); - - if (primType == GalPrimitiveType.Quads) - { - buffer = QuadHelper.ConvertQuadsToTris(buffer, indexEntrySize, indexCount); - } - else /* if (PrimType == GalPrimitiveType.QuadStrip) */ - { - buffer = QuadHelper.ConvertQuadStripToTris(buffer, indexEntrySize, indexCount); - } - - _gpu.Renderer.Rasterizer.CreateIbo(iboKey, ibSize, buffer); - } - } - - if (!usesLegacyQuads) - { - _gpu.Renderer.Rasterizer.SetIndexArray(ibSize, indexFormat); - } - else - { - if (primType == GalPrimitiveType.Quads) - { - _gpu.Renderer.Rasterizer.SetIndexArray(QuadHelper.ConvertSizeQuadsToTris(ibSize), indexFormat); - } - else /* if (PrimType == GalPrimitiveType.QuadStrip) */ - { - _gpu.Renderer.Rasterizer.SetIndexArray(QuadHelper.ConvertSizeQuadStripToTris(ibSize), indexFormat); - } - } - } - - List[] attribs = new List[32]; - - for (int attr = 0; attr < 16; attr++) - { - int packed = ReadRegister(NvGpuEngine3dReg.VertexAttribNFormat + attr); - - int arrayIndex = packed & 0x1f; - - if (attribs[arrayIndex] == null) - { - attribs[arrayIndex] = new List(); - } - - long vbPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.VertexArrayNAddress + arrayIndex * 4); - - if (vbPosition == 0) - { - continue; - } - - bool isConst = ((packed >> 6) & 1) != 0; - - int offset = (packed >> 7) & 0x3fff; - - GalVertexAttribSize size = (GalVertexAttribSize)((packed >> 21) & 0x3f); - GalVertexAttribType type = (GalVertexAttribType)((packed >> 27) & 0x7); - - bool isRgba = ((packed >> 31) & 1) != 0; - - // Check vertex array is enabled to avoid out of bounds exception when reading bytes - bool enable = (ReadRegister(NvGpuEngine3dReg.VertexArrayNControl + arrayIndex * 4) & 0x1000) != 0; - - // Note: 16 is the maximum size of an attribute, - // having a component size of 32-bits with 4 elements (a vec4). - if (enable) - { - byte[] data = vmm.ReadBytes(vbPosition + offset, 16); - - attribs[arrayIndex].Add(new GalVertexAttrib(attr, isConst, offset, data, size, type, isRgba)); - } - } - - state.VertexBindings = new GalVertexBinding[32]; - - for (int index = 0; index < 32; index++) - { - if (attribs[index] == null) - { - continue; - } - - int control = ReadRegister(NvGpuEngine3dReg.VertexArrayNControl + index * 4); - - bool enable = (control & 0x1000) != 0; - - if (!enable) - { - continue; - } - - long vbPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.VertexArrayNAddress + index * 4); - long vbEndPos = MakeInt64From2xInt32(NvGpuEngine3dReg.VertexArrayNEndAddr + index * 2); - - int vertexDivisor = ReadRegister(NvGpuEngine3dReg.VertexArrayNDivisor + index * 4); - - bool instanced = ReadRegisterBool(NvGpuEngine3dReg.VertexArrayNInstance + index); - - int stride = control & 0xfff; - - if (instanced && vertexDivisor != 0) - { - vbPosition += stride * (_currentInstance / vertexDivisor); - } - - if (vbPosition > vbEndPos) - { - // Instance is invalid, ignore the draw call - continue; - } - - long vboKey = vmm.GetPhysicalAddress(vbPosition); - - long vbSize = (vbEndPos - vbPosition) + 1; - int modifiedVbSize = (int)vbSize; - - - // If quads convert size to triangle length - if (stride == 0) - { - if (primType == GalPrimitiveType.Quads) - { - modifiedVbSize = QuadHelper.ConvertSizeQuadsToTris(modifiedVbSize); - } - else if (primType == GalPrimitiveType.QuadStrip) - { - modifiedVbSize = QuadHelper.ConvertSizeQuadStripToTris(modifiedVbSize); - } - } - - bool vboCached = _gpu.Renderer.Rasterizer.IsVboCached(vboKey, modifiedVbSize); - - if (!vboCached || _gpu.ResourceManager.MemoryRegionModified(vmm, vboKey, vbSize, NvGpuBufferType.Vertex)) - { - if ((primType == GalPrimitiveType.Quads | primType == GalPrimitiveType.QuadStrip) && stride != 0) - { - // Convert quad buffer to triangles - byte[] data = vmm.ReadBytes(vbPosition, vbSize); - - if (primType == GalPrimitiveType.Quads) - { - data = QuadHelper.ConvertQuadsToTris(data, stride, (int)(vbSize / stride)); - } - else - { - data = QuadHelper.ConvertQuadStripToTris(data, stride, (int)(vbSize / stride)); - } - _gpu.Renderer.Rasterizer.CreateVbo(vboKey, data); - } - else if (vmm.TryGetHostAddress(vbPosition, vbSize, out IntPtr vbPtr)) - { - _gpu.Renderer.Rasterizer.CreateVbo(vboKey, (int)vbSize, vbPtr); - } - else - { - _gpu.Renderer.Rasterizer.CreateVbo(vboKey, vmm.ReadBytes(vbPosition, vbSize)); - } - } - - state.VertexBindings[index].Enabled = true; - state.VertexBindings[index].Stride = stride; - state.VertexBindings[index].VboKey = vboKey; - state.VertexBindings[index].Instanced = instanced; - state.VertexBindings[index].Divisor = vertexDivisor; - state.VertexBindings[index].Attribs = attribs[index].ToArray(); - } - - Profile.End(Profiles.GPU.Engine3d.UploadVertexArrays); - } - - private void DispatchRender(NvGpuVmm vmm, GalPipelineState state) - { - int indexCount = ReadRegister(NvGpuEngine3dReg.IndexBatchCount); - int primCtrl = ReadRegister(NvGpuEngine3dReg.VertexBeginGl); - - GalPrimitiveType primType = (GalPrimitiveType)(primCtrl & 0xffff); - - bool instanceNext = ((primCtrl >> 26) & 1) != 0; - bool instanceCont = ((primCtrl >> 27) & 1) != 0; - - if (instanceNext && instanceCont) - { - throw new InvalidOperationException("GPU tried to increase and reset instance count at the same time"); - } - - if (instanceNext) - { - _currentInstance++; - } - else if (!instanceCont) - { - _currentInstance = 0; - } - - state.Instance = _currentInstance; - - _gpu.Renderer.Pipeline.Bind(state); - - _gpu.Renderer.RenderTarget.Bind(); - - if (indexCount != 0) - { - int indexEntryFmt = ReadRegister(NvGpuEngine3dReg.IndexArrayFormat); - int indexFirst = ReadRegister(NvGpuEngine3dReg.IndexBatchFirst); - int vertexBase = ReadRegister(NvGpuEngine3dReg.VertexArrayElemBase); - - long indexPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.IndexArrayAddress); - - long iboKey = vmm.GetPhysicalAddress(indexPosition); - - // Quad primitive types were deprecated on OpenGL 3.x, - // they are converted to a triangles index buffer on IB creation, - // so we should use the triangles type here too. - if (primType == GalPrimitiveType.Quads || primType == GalPrimitiveType.QuadStrip) - { - // Note: We assume that index first points to the first - // vertex of a quad, if it points to the middle of a - // quad (First % 4 != 0 for Quads) then it will not work properly. - if (primType == GalPrimitiveType.Quads) - { - indexFirst = QuadHelper.ConvertSizeQuadsToTris(indexFirst); - } - else // QuadStrip - { - indexFirst = QuadHelper.ConvertSizeQuadStripToTris(indexFirst); - } - - primType = GalPrimitiveType.Triangles; - } - - _gpu.Renderer.Rasterizer.DrawElements(iboKey, indexFirst, vertexBase, primType); - } - else - { - int vertexFirst = ReadRegister(NvGpuEngine3dReg.VertexArrayFirst); - int vertexCount = ReadRegister(NvGpuEngine3dReg.VertexArrayCount); - - // Quad primitive types were deprecated on OpenGL 3.x, - // they are converted to a triangles index buffer on IB creation, - // so we should use the triangles type here too. - if (primType == GalPrimitiveType.Quads || primType == GalPrimitiveType.QuadStrip) - { - // Note: We assume that index first points to the first - // vertex of a quad, if it points to the middle of a - // quad (First % 4 != 0 for Quads) then it will not work properly. - if (primType == GalPrimitiveType.Quads) - { - vertexFirst = QuadHelper.ConvertSizeQuadsToTris(vertexFirst); - } - else // QuadStrip - { - vertexFirst = QuadHelper.ConvertSizeQuadStripToTris(vertexFirst); - } - - primType = GalPrimitiveType.Triangles; - vertexCount = QuadHelper.ConvertSizeQuadsToTris(vertexCount); - } - - _gpu.Renderer.Rasterizer.DrawArrays(vertexFirst, vertexCount, primType); - } - - // Reset pipeline for host OpenGL calls - _gpu.Renderer.Pipeline.Unbind(state); - - // Is the GPU really clearing those registers after draw? - WriteRegister(NvGpuEngine3dReg.IndexBatchFirst, 0); - WriteRegister(NvGpuEngine3dReg.IndexBatchCount, 0); - } - - private enum QueryMode - { - WriteSeq, - Sync, - WriteCounterAndTimestamp - } - - private void QueryControl(NvGpuVmm vmm, GpuMethodCall methCall) - { - WriteRegister(methCall); - - long position = MakeInt64From2xInt32(NvGpuEngine3dReg.QueryAddress); - - int seq = Registers[(int)NvGpuEngine3dReg.QuerySequence]; - int ctrl = Registers[(int)NvGpuEngine3dReg.QueryControl]; - - QueryMode mode = (QueryMode)(ctrl & 3); - - switch (mode) - { - case QueryMode.WriteSeq: vmm.WriteInt32(position, seq); break; - - case QueryMode.WriteCounterAndTimestamp: - { - // TODO: Implement counters. - long counter = 1; - - long timestamp = PerformanceCounter.ElapsedMilliseconds; - - vmm.WriteInt64(position + 0, counter); - vmm.WriteInt64(position + 8, timestamp); - - break; - } - } - } - - private void CbData(NvGpuVmm vmm, GpuMethodCall methCall) - { - long position = MakeInt64From2xInt32(NvGpuEngine3dReg.ConstBufferAddress); - - int offset = ReadRegister(NvGpuEngine3dReg.ConstBufferOffset); - - vmm.WriteInt32(position + offset, methCall.Argument); - - WriteRegister(NvGpuEngine3dReg.ConstBufferOffset, offset + 4); - - _gpu.ResourceManager.ClearPbCache(NvGpuBufferType.ConstBuffer); - } - - private void CbBind(NvGpuVmm vmm, GpuMethodCall methCall) - { - int stage = (methCall.Method - 0x904) >> 3; - - int index = methCall.Argument; - - bool enabled = (index & 1) != 0; - - index = (index >> 4) & 0x1f; - - long position = MakeInt64From2xInt32(NvGpuEngine3dReg.ConstBufferAddress); - - long cbKey = vmm.GetPhysicalAddress(position); - - int size = ReadRegister(NvGpuEngine3dReg.ConstBufferSize); - - if (!_gpu.Renderer.Buffer.IsCached(cbKey, size)) - { - _gpu.Renderer.Buffer.Create(cbKey, size); - } - - ConstBuffer cb = _constBuffers[stage][index]; - - if (cb.Position != position || cb.Enabled != enabled || cb.Size != size) - { - _constBuffers[stage][index].Position = position; - _constBuffers[stage][index].Enabled = enabled; - _constBuffers[stage][index].Size = size; - } - } - - private float GetFlipSign(NvGpuEngine3dReg reg) - { - return MathF.Sign(ReadRegisterFloat(reg)); - } - - private long MakeInt64From2xInt32(NvGpuEngine3dReg reg) - { - return - (long)Registers[(int)reg + 0] << 32 | - (uint)Registers[(int)reg + 1]; - } - - private void WriteRegister(GpuMethodCall methCall) - { - Registers[methCall.Method] = methCall.Argument; - } - - private int ReadRegister(NvGpuEngine3dReg reg) - { - return Registers[(int)reg]; - } - - private float ReadRegisterFloat(NvGpuEngine3dReg reg) - { - return BitConverter.Int32BitsToSingle(ReadRegister(reg)); - } - - private bool ReadRegisterBool(NvGpuEngine3dReg reg) - { - return (ReadRegister(reg) & 1) != 0; - } - - private void WriteRegister(NvGpuEngine3dReg reg, int value) - { - Registers[(int)reg] = value; - } - } -} diff --git a/Ryujinx.Graphics/Graphics3d/NvGpuEngine3dReg.cs b/Ryujinx.Graphics/Graphics3d/NvGpuEngine3dReg.cs deleted file mode 100644 index c6596a309a..0000000000 --- a/Ryujinx.Graphics/Graphics3d/NvGpuEngine3dReg.cs +++ /dev/null @@ -1,116 +0,0 @@ -namespace Ryujinx.Graphics.Graphics3d -{ - enum NvGpuEngine3dReg - { - FrameBufferNAddress = 0x200, - FrameBufferNWidth = 0x202, - FrameBufferNHeight = 0x203, - FrameBufferNFormat = 0x204, - FrameBufferNBlockDim = 0x205, - FrameBufferNArrayMode = 0x206, - FrameBufferNLayerStride = 0x207, - FrameBufferNBaseLayer = 0x208, - ViewportNScaleX = 0x280, - ViewportNScaleY = 0x281, - ViewportNScaleZ = 0x282, - ViewportNTranslateX = 0x283, - ViewportNTranslateY = 0x284, - ViewportNTranslateZ = 0x285, - ViewportNHoriz = 0x300, - ViewportNVert = 0x301, - DepthRangeNNear = 0x302, - DepthRangeNFar = 0x303, - VertexArrayFirst = 0x35d, - VertexArrayCount = 0x35e, - ClearNColor = 0x360, - ClearDepth = 0x364, - ClearStencil = 0x368, - ScissorEnable = 0x380, - ScissorHorizontal = 0x381, - ScissorVertical = 0x382, - StencilBackFuncRef = 0x3d5, - StencilBackMask = 0x3d6, - StencilBackFuncMask = 0x3d7, - ColorMaskCommon = 0x3e4, - RtSeparateFragData = 0x3eb, - ZetaAddress = 0x3f8, - ZetaFormat = 0x3fa, - ZetaBlockDimensions = 0x3fb, - ZetaLayerStride = 0x3fc, - VertexAttribNFormat = 0x458, - RtControl = 0x487, - ZetaHoriz = 0x48a, - ZetaVert = 0x48b, - ZetaArrayMode = 0x48c, - LinkedTsc = 0x48d, - DepthTestEnable = 0x4b3, - BlendIndependent = 0x4b9, - DepthWriteEnable = 0x4ba, - DepthTestFunction = 0x4c3, - BlendSeparateAlpha = 0x4cf, - BlendEquationRgb = 0x4d0, - BlendFuncSrcRgb = 0x4d1, - BlendFuncDstRgb = 0x4d2, - BlendEquationAlpha = 0x4d3, - BlendFuncSrcAlpha = 0x4d4, - BlendFuncDstAlpha = 0x4d6, - BlendEnable = 0x4d7, - IBlendNEnable = 0x4d8, - StencilEnable = 0x4e0, - StencilFrontOpFail = 0x4e1, - StencilFrontOpZFail = 0x4e2, - StencilFrontOpZPass = 0x4e3, - StencilFrontFuncFunc = 0x4e4, - StencilFrontFuncRef = 0x4e5, - StencilFrontFuncMask = 0x4e6, - StencilFrontMask = 0x4e7, - ScreenYControl = 0x4eb, - VertexArrayElemBase = 0x50d, - VertexArrayInstBase = 0x50e, - ZetaEnable = 0x54e, - TexHeaderPoolOffset = 0x55d, - TexSamplerPoolOffset = 0x557, - StencilTwoSideEnable = 0x565, - StencilBackOpFail = 0x566, - StencilBackOpZFail = 0x567, - StencilBackOpZPass = 0x568, - StencilBackFuncFunc = 0x569, - FrameBufferSrgb = 0x56e, - ShaderAddress = 0x582, - VertexBeginGl = 0x586, - PrimRestartEnable = 0x591, - PrimRestartIndex = 0x592, - IndexArrayAddress = 0x5f2, - IndexArrayEndAddr = 0x5f4, - IndexArrayFormat = 0x5f6, - IndexBatchFirst = 0x5f7, - IndexBatchCount = 0x5f8, - VertexArrayNInstance = 0x620, - CullFaceEnable = 0x646, - FrontFace = 0x647, - CullFace = 0x648, - ColorMaskN = 0x680, - QueryAddress = 0x6c0, - QuerySequence = 0x6c2, - QueryControl = 0x6c3, - VertexArrayNControl = 0x700, - VertexArrayNAddress = 0x701, - VertexArrayNDivisor = 0x703, - IBlendNSeparateAlpha = 0x780, - IBlendNEquationRgb = 0x781, - IBlendNFuncSrcRgb = 0x782, - IBlendNFuncDstRgb = 0x783, - IBlendNEquationAlpha = 0x784, - IBlendNFuncSrcAlpha = 0x785, - IBlendNFuncDstAlpha = 0x786, - VertexArrayNEndAddr = 0x7c0, - ShaderNControl = 0x800, - ShaderNOffset = 0x801, - ShaderNMaxGprs = 0x803, - ShaderNType = 0x804, - ConstBufferSize = 0x8e0, - ConstBufferAddress = 0x8e1, - ConstBufferOffset = 0x8e3, - TextureCbIndex = 0x982 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Graphics3d/NvGpuEngineM2mf.cs b/Ryujinx.Graphics/Graphics3d/NvGpuEngineM2mf.cs deleted file mode 100644 index fca8ae2281..0000000000 --- a/Ryujinx.Graphics/Graphics3d/NvGpuEngineM2mf.cs +++ /dev/null @@ -1,214 +0,0 @@ -using Ryujinx.Graphics.Memory; -using Ryujinx.Graphics.Texture; -using System.Collections.Generic; -using Ryujinx.Profiler; - -namespace Ryujinx.Graphics.Graphics3d -{ - class NvGpuEngineM2mf : INvGpuEngine - { - public int[] Registers { get; private set; } - - private NvGpu _gpu; - - private Dictionary _methods; - - public NvGpuEngineM2mf(NvGpu gpu) - { - _gpu = gpu; - - Registers = new int[0x1d6]; - - _methods = new Dictionary(); - - void AddMethod(int meth, int count, int stride, NvGpuMethod method) - { - while (count-- > 0) - { - _methods.Add(meth, method); - - meth += stride; - } - } - - AddMethod(0xc0, 1, 1, Execute); - } - - public void CallMethod(NvGpuVmm vmm, GpuMethodCall methCall) - { - if (_methods.TryGetValue(methCall.Method, out NvGpuMethod method)) - { - ProfileConfig profile = Profiles.GPU.EngineM2mf.CallMethod; - - profile.SessionItem = method.Method.Name; - - Profile.Begin(profile); - method(vmm, methCall); - Profile.End(profile); - } - else - { - WriteRegister(methCall); - } - } - - private void Execute(NvGpuVmm vmm, GpuMethodCall methCall) - { - Profile.Begin(Profiles.GPU.EngineM2mf.Execute); - - // TODO: Some registers and copy modes are still not implemented. - int control = methCall.Argument; - - bool srcLinear = ((control >> 7) & 1) != 0; - bool dstLinear = ((control >> 8) & 1) != 0; - bool copy2D = ((control >> 9) & 1) != 0; - - long srcAddress = MakeInt64From2xInt32(NvGpuEngineM2mfReg.SrcAddress); - long dstAddress = MakeInt64From2xInt32(NvGpuEngineM2mfReg.DstAddress); - - int srcPitch = ReadRegister(NvGpuEngineM2mfReg.SrcPitch); - int dstPitch = ReadRegister(NvGpuEngineM2mfReg.DstPitch); - - int xCount = ReadRegister(NvGpuEngineM2mfReg.XCount); - int yCount = ReadRegister(NvGpuEngineM2mfReg.YCount); - - int swizzle = ReadRegister(NvGpuEngineM2mfReg.Swizzle); - - int dstBlkDim = ReadRegister(NvGpuEngineM2mfReg.DstBlkDim); - int dstSizeX = ReadRegister(NvGpuEngineM2mfReg.DstSizeX); - int dstSizeY = ReadRegister(NvGpuEngineM2mfReg.DstSizeY); - int dstSizeZ = ReadRegister(NvGpuEngineM2mfReg.DstSizeZ); - int dstPosXY = ReadRegister(NvGpuEngineM2mfReg.DstPosXY); - int dstPosZ = ReadRegister(NvGpuEngineM2mfReg.DstPosZ); - - int srcBlkDim = ReadRegister(NvGpuEngineM2mfReg.SrcBlkDim); - int srcSizeX = ReadRegister(NvGpuEngineM2mfReg.SrcSizeX); - int srcSizeY = ReadRegister(NvGpuEngineM2mfReg.SrcSizeY); - int srcSizeZ = ReadRegister(NvGpuEngineM2mfReg.SrcSizeZ); - int srcPosXY = ReadRegister(NvGpuEngineM2mfReg.SrcPosXY); - int srcPosZ = ReadRegister(NvGpuEngineM2mfReg.SrcPosZ); - - int srcCpp = ((swizzle >> 20) & 7) + 1; - int dstCpp = ((swizzle >> 24) & 7) + 1; - - int dstPosX = (dstPosXY >> 0) & 0xffff; - int dstPosY = (dstPosXY >> 16) & 0xffff; - - int srcPosX = (srcPosXY >> 0) & 0xffff; - int srcPosY = (srcPosXY >> 16) & 0xffff; - - int srcBlockHeight = 1 << ((srcBlkDim >> 4) & 0xf); - int dstBlockHeight = 1 << ((dstBlkDim >> 4) & 0xf); - - long srcPa = vmm.GetPhysicalAddress(srcAddress); - long dstPa = vmm.GetPhysicalAddress(dstAddress); - - if (copy2D) - { - if (srcLinear) - { - srcPosX = srcPosY = srcPosZ = 0; - } - - if (dstLinear) - { - dstPosX = dstPosY = dstPosZ = 0; - } - - if (srcLinear && dstLinear) - { - for (int y = 0; y < yCount; y++) - { - int srcOffset = (srcPosY + y) * srcPitch + srcPosX * srcCpp; - int dstOffset = (dstPosY + y) * dstPitch + dstPosX * dstCpp; - - long src = srcPa + (uint)srcOffset; - long dst = dstPa + (uint)dstOffset; - - vmm.Memory.CopyBytes(src, dst, xCount * srcCpp); - } - } - else - { - ISwizzle srcSwizzle; - - if (srcLinear) - { - srcSwizzle = new LinearSwizzle(srcPitch, srcCpp, srcSizeX, srcSizeY); - } - else - { - srcSwizzle = new BlockLinearSwizzle( - srcSizeX, - srcSizeY, 1, - srcBlockHeight, 1, - srcCpp); - } - - ISwizzle dstSwizzle; - - if (dstLinear) - { - dstSwizzle = new LinearSwizzle(dstPitch, dstCpp, srcSizeX, srcSizeY); - } - else - { - dstSwizzle = new BlockLinearSwizzle( - dstSizeX, - dstSizeY, 1, - dstBlockHeight, 1, - dstCpp); - } - - // Calculate the bits per pixel - int bpp = srcPitch / xCount; - - // Copying all the bits at the same time corrupts the texture, unknown why but probably because the texture isn't linear - // To avoid this we will simply loop more times to cover all the bits, - // this allows up to recalculate the memory locations for each iteration around the loop - xCount *= bpp / srcCpp; - - for (int y = 0; y < yCount; y++) - for (int x = 0; x < xCount; x++) - { - int srcOffset = srcSwizzle.GetSwizzleOffset(srcPosX + x, srcPosY + y, 0); - int dstOffset = dstSwizzle.GetSwizzleOffset(dstPosX + x, dstPosY + y, 0); - - long src = srcPa + (uint)srcOffset; - long dst = dstPa + (uint)dstOffset; - - vmm.Memory.CopyBytes(src, dst, srcCpp); - } - } - } - else - { - vmm.Memory.CopyBytes(srcPa, dstPa, xCount); - } - - Profile.End(Profiles.GPU.EngineM2mf.Execute); - } - - private long MakeInt64From2xInt32(NvGpuEngineM2mfReg reg) - { - return - (long)Registers[(int)reg + 0] << 32 | - (uint)Registers[(int)reg + 1]; - } - - private void WriteRegister(GpuMethodCall methCall) - { - Registers[methCall.Method] = methCall.Argument; - } - - private int ReadRegister(NvGpuEngineM2mfReg reg) - { - return Registers[(int)reg]; - } - - private void WriteRegister(NvGpuEngineM2mfReg reg, int value) - { - Registers[(int)reg] = value; - } - } -} diff --git a/Ryujinx.Graphics/Graphics3d/NvGpuEngineM2mfReg.cs b/Ryujinx.Graphics/Graphics3d/NvGpuEngineM2mfReg.cs deleted file mode 100644 index 4bef8d9ed2..0000000000 --- a/Ryujinx.Graphics/Graphics3d/NvGpuEngineM2mfReg.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Ryujinx.Graphics.Graphics3d -{ - enum NvGpuEngineM2mfReg - { - SrcAddress = 0x100, - DstAddress = 0x102, - SrcPitch = 0x104, - DstPitch = 0x105, - XCount = 0x106, - YCount = 0x107, - Swizzle = 0x1c2, - DstBlkDim = 0x1c3, - DstSizeX = 0x1c4, - DstSizeY = 0x1c5, - DstSizeZ = 0x1c6, - DstPosZ = 0x1c7, - DstPosXY = 0x1c8, - SrcBlkDim = 0x1ca, - SrcSizeX = 0x1cb, - SrcSizeY = 0x1cc, - SrcSizeZ = 0x1cd, - SrcPosZ = 0x1ce, - SrcPosXY = 0x1cf - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Graphics3d/NvGpuEngineP2mf.cs b/Ryujinx.Graphics/Graphics3d/NvGpuEngineP2mf.cs deleted file mode 100644 index 2b2c7b5f12..0000000000 --- a/Ryujinx.Graphics/Graphics3d/NvGpuEngineP2mf.cs +++ /dev/null @@ -1,179 +0,0 @@ -using Ryujinx.Graphics.Memory; -using Ryujinx.Graphics.Texture; -using System.Collections.Generic; -using Ryujinx.Profiler; - -namespace Ryujinx.Graphics.Graphics3d -{ - class NvGpuEngineP2mf : INvGpuEngine - { - public int[] Registers { get; private set; } - - private NvGpu _gpu; - - private Dictionary _methods; - - private int _copyStartX; - private int _copyStartY; - - private int _copyWidth; - private int _copyHeight; - private int _copyGobBlockHeight; - - private long _copyAddress; - - private int _copyOffset; - private int _copySize; - - private bool _copyLinear; - - private byte[] _buffer; - - public NvGpuEngineP2mf(NvGpu gpu) - { - _gpu = gpu; - - Registers = new int[0x80]; - - _methods = new Dictionary(); - - void AddMethod(int meth, int count, int stride, NvGpuMethod method) - { - while (count-- > 0) - { - _methods.Add(meth, method); - - meth += stride; - } - } - - AddMethod(0x6c, 1, 1, Execute); - AddMethod(0x6d, 1, 1, PushData); - } - - public void CallMethod(NvGpuVmm vmm, GpuMethodCall methCall) - { - if (_methods.TryGetValue(methCall.Method, out NvGpuMethod method)) - { - ProfileConfig profile = Profiles.GPU.EngineP2mf.PushData; - - profile.SessionItem = method.Method.Name; - - Profile.Begin(profile); - method(vmm, methCall); - Profile.End(profile); - } - else - { - WriteRegister(methCall); - } - } - - private void Execute(NvGpuVmm vmm, GpuMethodCall methCall) - { - Profile.Begin(Profiles.GPU.EngineP2mf.Execute); - - // TODO: Some registers and copy modes are still not implemented. - int control = methCall.Argument; - - long dstAddress = MakeInt64From2xInt32(NvGpuEngineP2mfReg.DstAddress); - - int dstPitch = ReadRegister(NvGpuEngineP2mfReg.DstPitch); - int dstBlkDim = ReadRegister(NvGpuEngineP2mfReg.DstBlockDim); - - int dstX = ReadRegister(NvGpuEngineP2mfReg.DstX); - int dstY = ReadRegister(NvGpuEngineP2mfReg.DstY); - - int dstWidth = ReadRegister(NvGpuEngineP2mfReg.DstWidth); - int dstHeight = ReadRegister(NvGpuEngineP2mfReg.DstHeight); - - int lineLengthIn = ReadRegister(NvGpuEngineP2mfReg.LineLengthIn); - int lineCount = ReadRegister(NvGpuEngineP2mfReg.LineCount); - - _copyLinear = (control & 1) != 0; - - _copyGobBlockHeight = 1 << ((dstBlkDim >> 4) & 0xf); - - _copyStartX = dstX; - _copyStartY = dstY; - - _copyWidth = dstWidth; - _copyHeight = dstHeight; - - _copyAddress = dstAddress; - - _copyOffset = 0; - _copySize = lineLengthIn * lineCount; - - _buffer = new byte[_copySize]; - - Profile.End(Profiles.GPU.EngineP2mf.Execute); - } - - private void PushData(NvGpuVmm vmm, GpuMethodCall methCall) - { - if (_buffer == null) - { - return; - } - - Profile.Begin(Profiles.GPU.EngineP2mf.PushData); - - for (int shift = 0; shift < 32 && _copyOffset < _copySize; shift += 8, _copyOffset++) - { - _buffer[_copyOffset] = (byte)(methCall.Argument >> shift); - } - - if (methCall.IsLastCall) - { - if (_copyLinear) - { - vmm.WriteBytes(_copyAddress, _buffer); - } - else - { - BlockLinearSwizzle swizzle = new BlockLinearSwizzle( - _copyWidth, - _copyHeight, 1, - _copyGobBlockHeight, 1, 1); - - int srcOffset = 0; - - for (int y = _copyStartY; y < _copyHeight && srcOffset < _copySize; y++) - for (int x = _copyStartX; x < _copyWidth && srcOffset < _copySize; x++) - { - int dstOffset = swizzle.GetSwizzleOffset(x, y, 0); - - vmm.WriteByte(_copyAddress + dstOffset, _buffer[srcOffset++]); - } - } - - _buffer = null; - } - - Profile.End(Profiles.GPU.EngineP2mf.PushData); - } - - private long MakeInt64From2xInt32(NvGpuEngineP2mfReg reg) - { - return - (long)Registers[(int)reg + 0] << 32 | - (uint)Registers[(int)reg + 1]; - } - - private void WriteRegister(GpuMethodCall methCall) - { - Registers[methCall.Method] = methCall.Argument; - } - - private int ReadRegister(NvGpuEngineP2mfReg reg) - { - return Registers[(int)reg]; - } - - private void WriteRegister(NvGpuEngineP2mfReg reg, int value) - { - Registers[(int)reg] = value; - } - } -} diff --git a/Ryujinx.Graphics/Graphics3d/NvGpuEngineP2mfReg.cs b/Ryujinx.Graphics/Graphics3d/NvGpuEngineP2mfReg.cs deleted file mode 100644 index ab3a304dd1..0000000000 --- a/Ryujinx.Graphics/Graphics3d/NvGpuEngineP2mfReg.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Ryujinx.Graphics.Graphics3d -{ - enum NvGpuEngineP2mfReg - { - LineLengthIn = 0x60, - LineCount = 0x61, - DstAddress = 0x62, - DstPitch = 0x64, - DstBlockDim = 0x65, - DstWidth = 0x66, - DstHeight = 0x67, - DstDepth = 0x68, - DstZ = 0x69, - DstX = 0x6a, - DstY = 0x6b - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Graphics3d/NvGpuFifo.cs b/Ryujinx.Graphics/Graphics3d/NvGpuFifo.cs deleted file mode 100644 index 23bfd0b3c2..0000000000 --- a/Ryujinx.Graphics/Graphics3d/NvGpuFifo.cs +++ /dev/null @@ -1,176 +0,0 @@ -using Ryujinx.Graphics.Memory; - -namespace Ryujinx.Graphics.Graphics3d -{ - 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 NvGpu _gpu; - - private NvGpuEngine[] _subChannels; - - private struct CachedMacro - { - public int Position { get; private set; } - - private bool _executionPending; - private int _argument; - - private MacroInterpreter _interpreter; - - public CachedMacro(NvGpuFifo pFifo, INvGpuEngine engine, int position) - { - Position = position; - - _executionPending = false; - _argument = 0; - - _interpreter = new MacroInterpreter(pFifo, engine); - } - - public void StartExecution(int argument) - { - _argument = argument; - - _executionPending = true; - } - - public void Execute(NvGpuVmm vmm, int[] mme) - { - if (_executionPending) - { - _executionPending = false; - - _interpreter?.Execute(vmm, mme, Position, _argument); - } - } - - public void PushArgument(int argument) - { - _interpreter?.Fifo.Enqueue(argument); - } - } - - private int _currMacroPosition; - private int _currMacroBindIndex; - - private CachedMacro[] _macros; - - private int[] _mme; - - public NvGpuFifo(NvGpu gpu) - { - _gpu = gpu; - - _subChannels = new NvGpuEngine[8]; - - _macros = new CachedMacro[MacrosCount]; - - _mme = new int[MmeWords]; - } - - public void CallMethod(NvGpuVmm vmm, GpuMethodCall methCall) - { - if ((NvGpuFifoMeth)methCall.Method == NvGpuFifoMeth.BindChannel) - { - NvGpuEngine engine = (NvGpuEngine)methCall.Argument; - - _subChannels[methCall.SubChannel] = engine; - } - else - { - switch (_subChannels[methCall.SubChannel]) - { - case NvGpuEngine._2d: Call2dMethod (vmm, methCall); break; - case NvGpuEngine._3d: Call3dMethod (vmm, methCall); break; - case NvGpuEngine.P2mf: CallP2mfMethod(vmm, methCall); break; - case NvGpuEngine.M2mf: CallM2mfMethod(vmm, methCall); break; - } - } - } - - private void Call2dMethod(NvGpuVmm vmm, GpuMethodCall methCall) - { - _gpu.Engine2d.CallMethod(vmm, methCall); - } - - private void Call3dMethod(NvGpuVmm vmm, GpuMethodCall methCall) - { - if (methCall.Method < 0x80) - { - switch ((NvGpuFifoMeth)methCall.Method) - { - case NvGpuFifoMeth.SetMacroUploadAddress: - { - _currMacroPosition = methCall.Argument; - - break; - } - - case NvGpuFifoMeth.SendMacroCodeData: - { - _mme[_currMacroPosition++] = methCall.Argument; - - break; - } - - case NvGpuFifoMeth.SetMacroBindingIndex: - { - _currMacroBindIndex = methCall.Argument; - - break; - } - - case NvGpuFifoMeth.BindMacro: - { - int position = methCall.Argument; - - _macros[_currMacroBindIndex++] = new CachedMacro(this, _gpu.Engine3d, position); - - break; - } - - default: CallP2mfMethod(vmm, methCall); break; - } - } - else if (methCall.Method < 0xe00) - { - _gpu.Engine3d.CallMethod(vmm, methCall); - } - else - { - int macroIndex = (methCall.Method >> 1) & MacroIndexMask; - - if ((methCall.Method & 1) != 0) - { - _macros[macroIndex].PushArgument(methCall.Argument); - } - else - { - _macros[macroIndex].StartExecution(methCall.Argument); - } - - if (methCall.IsLastCall) - { - _macros[macroIndex].Execute(vmm, _mme); - } - } - } - - private void CallP2mfMethod(NvGpuVmm vmm, GpuMethodCall methCall) - { - _gpu.EngineP2mf.CallMethod(vmm, methCall); - } - - private void CallM2mfMethod(NvGpuVmm vmm, GpuMethodCall methCall) - { - _gpu.EngineM2mf.CallMethod(vmm, methCall); - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Graphics3d/NvGpuMethod.cs b/Ryujinx.Graphics/Graphics3d/NvGpuMethod.cs deleted file mode 100644 index 23185c81fc..0000000000 --- a/Ryujinx.Graphics/Graphics3d/NvGpuMethod.cs +++ /dev/null @@ -1,6 +0,0 @@ -using Ryujinx.Graphics.Memory; - -namespace Ryujinx.Graphics.Graphics3d -{ - delegate void NvGpuMethod(NvGpuVmm vmm, GpuMethodCall methCall); -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Graphics3d/Texture/AstcPixel.cs b/Ryujinx.Graphics/Graphics3d/Texture/AstcPixel.cs deleted file mode 100644 index 2f73c62b0f..0000000000 --- a/Ryujinx.Graphics/Graphics3d/Texture/AstcPixel.cs +++ /dev/null @@ -1,138 +0,0 @@ -using System; -using System.Diagnostics; - -namespace Ryujinx.Graphics.Texture -{ - class AstcPixel - { - public short R { get; set; } - public short G { get; set; } - public short B { get; set; } - public short A { get; set; } - - byte[] _bitDepth = new byte[4]; - - public AstcPixel(short a, short r, short g, short b) - { - A = a; - R = r; - G = g; - B = b; - - for (int i = 0; i < 4; i++) - _bitDepth[i] = 8; - } - - public void ClampByte() - { - R = Math.Min(Math.Max(R, (short)0), (short)255); - G = Math.Min(Math.Max(G, (short)0), (short)255); - B = Math.Min(Math.Max(B, (short)0), (short)255); - A = Math.Min(Math.Max(A, (short)0), (short)255); - } - - public short GetComponent(int index) - { - switch(index) - { - case 0: return A; - case 1: return R; - case 2: return G; - case 3: return B; - } - - return 0; - } - - public void SetComponent(int index, int value) - { - switch (index) - { - case 0: - A = (short)value; - break; - case 1: - R = (short)value; - break; - case 2: - G = (short)value; - break; - case 3: - B = (short)value; - break; - } - } - - public void ChangeBitDepth(byte[] depth) - { - for (int i = 0; i< 4; i++) - { - int value = ChangeBitDepth(GetComponent(i), _bitDepth[i], depth[i]); - - SetComponent(i, value); - _bitDepth[i] = depth[i]; - } - } - - short ChangeBitDepth(short value, byte oldDepth, byte newDepth) - { - Debug.Assert(newDepth <= 8); - Debug.Assert(oldDepth <= 8); - - if (oldDepth == newDepth) - { - // Do nothing - return value; - } - else if (oldDepth == 0 && newDepth != 0) - { - return (short)((1 << newDepth) - 1); - } - else if (newDepth > oldDepth) - { - return (short)BitArrayStream.Replicate(value, oldDepth, newDepth); - } - else - { - // oldDepth > newDepth - if (newDepth == 0) - { - return 0xFF; - } - else - { - byte bitsWasted = (byte)(oldDepth - newDepth); - short tempValue = value; - - tempValue = (short)((tempValue + (1 << (bitsWasted - 1))) >> bitsWasted); - tempValue = Math.Min(Math.Max((short)0, tempValue), (short)((1 << newDepth) - 1)); - - return (byte)(tempValue); - } - } - } - - public int Pack() - { - AstcPixel newPixel = new AstcPixel(A, R, G, B); - byte[] eightBitDepth = { 8, 8, 8, 8 }; - - newPixel.ChangeBitDepth(eightBitDepth); - - return (byte)newPixel.A << 24 | - (byte)newPixel.B << 16 | - (byte)newPixel.G << 8 | - (byte)newPixel.R << 0; - } - - // Adds more precision to the blue channel as described - // in C.2.14 - public static AstcPixel BlueContract(int a, int r, int g, int b) - { - return new AstcPixel((short)(a), - (short)((r + b) >> 1), - (short)((g + b) >> 1), - (short)(b)); - } - } -} diff --git a/Ryujinx.Graphics/Graphics3d/Texture/BitArrayStream.cs b/Ryujinx.Graphics/Graphics3d/Texture/BitArrayStream.cs deleted file mode 100644 index 24069d722b..0000000000 --- a/Ryujinx.Graphics/Graphics3d/Texture/BitArrayStream.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System; -using System.Collections; - -namespace Ryujinx.Graphics.Texture -{ - public class BitArrayStream - { - public BitArray BitsArray; - - public int Position { get; private set; } - - public BitArrayStream(BitArray bitArray) - { - BitsArray = bitArray; - Position = 0; - } - - public short ReadBits(int length) - { - int retValue = 0; - for (int i = Position; i < Position + length; i++) - { - if (BitsArray[i]) - { - retValue |= 1 << (i - Position); - } - } - - Position += length; - return (short)retValue; - } - - public int ReadBits(int start, int end) - { - int retValue = 0; - for (int i = start; i <= end; i++) - { - if (BitsArray[i]) - { - retValue |= 1 << (i - start); - } - } - - return retValue; - } - - public int ReadBit(int index) - { - return Convert.ToInt32(BitsArray[index]); - } - - public void WriteBits(int value, int length) - { - for (int i = Position; i < Position + length; i++) - { - BitsArray[i] = ((value >> (i - Position)) & 1) != 0; - } - - Position += length; - } - - public byte[] ToByteArray() - { - byte[] retArray = new byte[(BitsArray.Length + 7) / 8]; - BitsArray.CopyTo(retArray, 0); - return retArray; - } - - public static int Replicate(int value, int numberBits, int toBit) - { - if (numberBits == 0) return 0; - if (toBit == 0) return 0; - - int tempValue = value & ((1 << numberBits) - 1); - int retValue = tempValue; - int resLength = numberBits; - - while (resLength < toBit) - { - int comp = 0; - if (numberBits > toBit - resLength) - { - int newShift = toBit - resLength; - comp = numberBits - newShift; - numberBits = newShift; - } - retValue <<= numberBits; - retValue |= tempValue >> comp; - resLength += numberBits; - } - return retValue; - } - - public static int PopCnt(int number) - { - int counter; - for (counter = 0; number != 0; counter++) - { - number &= number - 1; - } - return counter; - } - - public static void Swap(ref T lhs, ref T rhs) - { - T temp = lhs; - lhs = rhs; - rhs = temp; - } - - // Transfers a bit as described in C.2.14 - public static void BitTransferSigned(ref int a, ref int b) - { - b >>= 1; - b |= a & 0x80; - a >>= 1; - a &= 0x3F; - if ((a & 0x20) != 0) a -= 0x40; - } - } -} diff --git a/Ryujinx.Graphics/Graphics3d/Texture/BlockLinearSwizzle.cs b/Ryujinx.Graphics/Graphics3d/Texture/BlockLinearSwizzle.cs deleted file mode 100644 index 682f7d671d..0000000000 --- a/Ryujinx.Graphics/Graphics3d/Texture/BlockLinearSwizzle.cs +++ /dev/null @@ -1,186 +0,0 @@ -using Ryujinx.Common; -using System; - -namespace Ryujinx.Graphics.Texture -{ - class BlockLinearSwizzle : ISwizzle - { - private const int GobWidth = 64; - private const int GobHeight = 8; - - private const int GobSize = GobWidth * GobHeight; - - private int _texWidth; - private int _texHeight; - private int _texDepth; - private int _texGobBlockHeight; - private int _texGobBlockDepth; - private int _texBpp; - - private int _bhMask; - private int _bdMask; - - private int _bhShift; - private int _bdShift; - private int _bppShift; - - private int _xShift; - - private int _robSize; - private int _sliceSize; - - private int _baseOffset; - - public BlockLinearSwizzle( - int width, - int height, - int depth, - int gobBlockHeight, - int gobBlockDepth, - int bpp) - { - _texWidth = width; - _texHeight = height; - _texDepth = depth; - _texGobBlockHeight = gobBlockHeight; - _texGobBlockDepth = gobBlockDepth; - _texBpp = bpp; - - _bppShift = BitUtils.CountTrailingZeros32(bpp); - - SetMipLevel(0); - } - - public void SetMipLevel(int level) - { - _baseOffset = GetMipOffset(level); - - int width = Math.Max(1, _texWidth >> level); - int height = Math.Max(1, _texHeight >> level); - int depth = Math.Max(1, _texDepth >> level); - - GobBlockSizes gbSizes = AdjustGobBlockSizes(height, depth); - - _bhMask = gbSizes.Height - 1; - _bdMask = gbSizes.Depth - 1; - - _bhShift = BitUtils.CountTrailingZeros32(gbSizes.Height); - _bdShift = BitUtils.CountTrailingZeros32(gbSizes.Depth); - - _xShift = BitUtils.CountTrailingZeros32(GobSize * gbSizes.Height * gbSizes.Depth); - - RobAndSliceSizes gsSizes = GetRobAndSliceSizes(width, height, gbSizes); - - _robSize = gsSizes.RobSize; - _sliceSize = gsSizes.SliceSize; - } - - public int GetImageSize(int mipsCount) - { - int size = GetMipOffset(mipsCount); - - size = (size + 0x1fff) & ~0x1fff; - - return size; - } - - public int GetMipOffset(int level) - { - int totalSize = 0; - - for (int index = 0; index < level; index++) - { - int width = Math.Max(1, _texWidth >> index); - int height = Math.Max(1, _texHeight >> index); - int depth = Math.Max(1, _texDepth >> index); - - GobBlockSizes gbSizes = AdjustGobBlockSizes(height, depth); - - RobAndSliceSizes rsSizes = GetRobAndSliceSizes(width, height, gbSizes); - - totalSize += BitUtils.DivRoundUp(depth, gbSizes.Depth) * rsSizes.SliceSize; - } - - return totalSize; - } - - private struct GobBlockSizes - { - public int Height; - public int Depth; - - public GobBlockSizes(int gobBlockHeight, int gobBlockDepth) - { - Height = gobBlockHeight; - Depth = gobBlockDepth; - } - } - - private GobBlockSizes AdjustGobBlockSizes(int height, int depth) - { - int gobBlockHeight = _texGobBlockHeight; - int gobBlockDepth = _texGobBlockDepth; - - int pow2Height = BitUtils.Pow2RoundUp(height); - int pow2Depth = BitUtils.Pow2RoundUp(depth); - - while (gobBlockHeight * GobHeight > pow2Height && gobBlockHeight > 1) - { - gobBlockHeight >>= 1; - } - - while (gobBlockDepth > pow2Depth && gobBlockDepth > 1) - { - gobBlockDepth >>= 1; - } - - return new GobBlockSizes(gobBlockHeight, gobBlockDepth); - } - - private struct RobAndSliceSizes - { - public int RobSize; - public int SliceSize; - - public RobAndSliceSizes(int robSize, int sliceSize) - { - RobSize = robSize; - SliceSize = sliceSize; - } - } - - private RobAndSliceSizes GetRobAndSliceSizes(int width, int height, GobBlockSizes gbSizes) - { - int widthInGobs = BitUtils.DivRoundUp(width * _texBpp, GobWidth); - - int robSize = GobSize * gbSizes.Height * gbSizes.Depth * widthInGobs; - - int sliceSize = BitUtils.DivRoundUp(height, gbSizes.Height * GobHeight) * robSize; - - return new RobAndSliceSizes(robSize, sliceSize); - } - - public int GetSwizzleOffset(int x, int y, int z) - { - x <<= _bppShift; - - int yh = y / GobHeight; - - int position = (z >> _bdShift) * _sliceSize + (yh >> _bhShift) * _robSize; - - position += (x / GobWidth) << _xShift; - - position += (yh & _bhMask) * GobSize; - - position += ((z & _bdMask) * GobSize) << _bhShift; - - position += ((x & 0x3f) >> 5) << 8; - position += ((y & 0x07) >> 1) << 6; - position += ((x & 0x1f) >> 4) << 5; - position += ((y & 0x01) >> 0) << 4; - position += ((x & 0x0f) >> 0) << 0; - - return _baseOffset + position; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Graphics3d/Texture/ISwizzle.cs b/Ryujinx.Graphics/Graphics3d/Texture/ISwizzle.cs deleted file mode 100644 index fae3eada87..0000000000 --- a/Ryujinx.Graphics/Graphics3d/Texture/ISwizzle.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Ryujinx.Graphics.Texture -{ - interface ISwizzle - { - int GetSwizzleOffset(int x, int y, int z); - - void SetMipLevel(int level); - - int GetMipOffset(int level); - - int GetImageSize(int mipsCount); - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Graphics3d/Texture/ImageUtils.cs b/Ryujinx.Graphics/Graphics3d/Texture/ImageUtils.cs deleted file mode 100644 index 10c36fe1da..0000000000 --- a/Ryujinx.Graphics/Graphics3d/Texture/ImageUtils.cs +++ /dev/null @@ -1,561 +0,0 @@ -using ARMeilleure.Memory; -using OpenTK.Graphics.OpenGL; -using Ryujinx.Graphics.Gal; -using Ryujinx.Graphics.Memory; -using System; -using System.Collections.Generic; - -namespace Ryujinx.Graphics.Texture -{ - public static class ImageUtils - { - [Flags] - private enum TargetBuffer - { - Color = 1 << 0, - Depth = 1 << 1, - Stencil = 1 << 2, - - DepthStencil = Depth | Stencil - } - - private struct ImageDescriptor - { - public int BytesPerPixel { get; private set; } - public int BlockWidth { get; private set; } - public int BlockHeight { get; private set; } - public int BlockDepth { get; private set; } - - public TargetBuffer Target { get; private set; } - - public ImageDescriptor(int bytesPerPixel, int blockWidth, int blockHeight, int blockDepth, TargetBuffer target) - { - BytesPerPixel = bytesPerPixel; - BlockWidth = blockWidth; - BlockHeight = blockHeight; - BlockDepth = blockDepth; - Target = target; - } - } - - private const GalImageFormat Snorm = GalImageFormat.Snorm; - private const GalImageFormat Unorm = GalImageFormat.Unorm; - private const GalImageFormat Sint = GalImageFormat.Sint; - private const GalImageFormat Uint = GalImageFormat.Uint; - private const GalImageFormat Float = GalImageFormat.Float; - private const GalImageFormat Srgb = GalImageFormat.Srgb; - - private static readonly Dictionary TextureTable = - new Dictionary() - { - { GalTextureFormat.Rgba32, GalImageFormat.Rgba32 | Sint | Uint | Float }, - { GalTextureFormat.Rgba16, GalImageFormat.Rgba16 | Snorm | Unorm | Sint | Uint | Float }, - { GalTextureFormat.Rg32, GalImageFormat.Rg32 | Sint | Uint | Float }, - { GalTextureFormat.Rgba8, GalImageFormat.Rgba8 | Snorm | Unorm | Sint | Uint | Srgb }, - { GalTextureFormat.Rgb10A2, GalImageFormat.Rgb10A2 | Snorm | Unorm | Sint | Uint }, - { GalTextureFormat.Rg8, GalImageFormat.Rg8 | Snorm | Unorm | Sint | Uint }, - { GalTextureFormat.R16, GalImageFormat.R16 | Snorm | Unorm | Sint | Uint | Float }, - { GalTextureFormat.R8, GalImageFormat.R8 | Snorm | Unorm | Sint | Uint }, - { GalTextureFormat.Rg16, GalImageFormat.Rg16 | Snorm | Unorm | Sint | Float }, - { GalTextureFormat.R32, GalImageFormat.R32 | Sint | Uint | Float }, - { GalTextureFormat.Rgba4, GalImageFormat.Rgba4 | Unorm }, - { GalTextureFormat.Rgb5A1, GalImageFormat.Rgb5A1 | Unorm }, - { GalTextureFormat.Rgb565, GalImageFormat.Rgb565 | Unorm }, - { GalTextureFormat.R11G11B10F, GalImageFormat.R11G11B10 | Float }, - { GalTextureFormat.D24S8, GalImageFormat.D24S8 | Unorm | Uint }, - { GalTextureFormat.D32F, GalImageFormat.D32 | Float }, - { GalTextureFormat.D32Fx24S8, GalImageFormat.D32S8 | Float }, - { GalTextureFormat.D16, GalImageFormat.D16 | Unorm }, - - // Compressed formats - { GalTextureFormat.BptcSfloat, GalImageFormat.BptcSfloat | Float }, - { GalTextureFormat.BptcUfloat, GalImageFormat.BptcUfloat | Float }, - { GalTextureFormat.BptcUnorm, GalImageFormat.BptcUnorm | Unorm | Srgb }, - { GalTextureFormat.BC1, GalImageFormat.BC1 | Unorm | Srgb }, - { GalTextureFormat.BC2, GalImageFormat.BC2 | Unorm | Srgb }, - { GalTextureFormat.BC3, GalImageFormat.BC3 | Unorm | Srgb }, - { GalTextureFormat.BC4, GalImageFormat.BC4 | Unorm | Snorm }, - { GalTextureFormat.BC5, GalImageFormat.BC5 | Unorm | Snorm }, - { GalTextureFormat.Astc2D4x4, GalImageFormat.Astc2D4x4 | Unorm | Srgb }, - { GalTextureFormat.Astc2D5x5, GalImageFormat.Astc2D5x5 | Unorm | Srgb }, - { GalTextureFormat.Astc2D6x6, GalImageFormat.Astc2D6x6 | Unorm | Srgb }, - { GalTextureFormat.Astc2D8x8, GalImageFormat.Astc2D8x8 | Unorm | Srgb }, - { GalTextureFormat.Astc2D10x10, GalImageFormat.Astc2D10x10 | Unorm | Srgb }, - { GalTextureFormat.Astc2D12x12, GalImageFormat.Astc2D12x12 | Unorm | Srgb }, - { GalTextureFormat.Astc2D5x4, GalImageFormat.Astc2D5x4 | Unorm | Srgb }, - { GalTextureFormat.Astc2D6x5, GalImageFormat.Astc2D6x5 | Unorm | Srgb }, - { GalTextureFormat.Astc2D8x6, GalImageFormat.Astc2D8x6 | Unorm | Srgb }, - { GalTextureFormat.Astc2D10x8, GalImageFormat.Astc2D10x8 | Unorm | Srgb }, - { GalTextureFormat.Astc2D12x10, GalImageFormat.Astc2D12x10 | Unorm | Srgb }, - { GalTextureFormat.Astc2D8x5, GalImageFormat.Astc2D8x5 | Unorm | Srgb }, - { GalTextureFormat.Astc2D10x5, GalImageFormat.Astc2D10x5 | Unorm | Srgb }, - { GalTextureFormat.Astc2D10x6, GalImageFormat.Astc2D10x6 | Unorm | Srgb } - }; - - private static readonly Dictionary ImageTable = - new Dictionary() - { - { GalImageFormat.Rgba32, new ImageDescriptor(16, 1, 1, 1, TargetBuffer.Color) }, - { GalImageFormat.Rgba16, new ImageDescriptor(8, 1, 1, 1, TargetBuffer.Color) }, - { GalImageFormat.Rg32, new ImageDescriptor(8, 1, 1, 1, TargetBuffer.Color) }, - { GalImageFormat.Rgbx8, new ImageDescriptor(4, 1, 1, 1, TargetBuffer.Color) }, - { GalImageFormat.Rgba8, new ImageDescriptor(4, 1, 1, 1, TargetBuffer.Color) }, - { GalImageFormat.Bgra8, new ImageDescriptor(4, 1, 1, 1, TargetBuffer.Color) }, - { GalImageFormat.Rgb10A2, new ImageDescriptor(4, 1, 1, 1, TargetBuffer.Color) }, - { GalImageFormat.R32, new ImageDescriptor(4, 1, 1, 1, TargetBuffer.Color) }, - { GalImageFormat.Rgba4, new ImageDescriptor(2, 1, 1, 1, TargetBuffer.Color) }, - { GalImageFormat.BptcSfloat, new ImageDescriptor(16, 4, 4, 1, TargetBuffer.Color) }, - { GalImageFormat.BptcUfloat, new ImageDescriptor(16, 4, 4, 1, TargetBuffer.Color) }, - { GalImageFormat.Bgr5A1, new ImageDescriptor(2, 1, 1, 1, TargetBuffer.Color) }, - { GalImageFormat.Rgb5A1, new ImageDescriptor(2, 1, 1, 1, TargetBuffer.Color) }, - { GalImageFormat.Rgb565, new ImageDescriptor(2, 1, 1, 1, TargetBuffer.Color) }, - { GalImageFormat.Bgr565, new ImageDescriptor(2, 1, 1, 1, TargetBuffer.Color) }, - { GalImageFormat.BptcUnorm, new ImageDescriptor(16, 4, 4, 1, TargetBuffer.Color) }, - { GalImageFormat.Rg16, new ImageDescriptor(4, 1, 1, 1, TargetBuffer.Color) }, - { GalImageFormat.Rg8, new ImageDescriptor(2, 1, 1, 1, TargetBuffer.Color) }, - { GalImageFormat.R16, new ImageDescriptor(2, 1, 1, 1, TargetBuffer.Color) }, - { GalImageFormat.R8, new ImageDescriptor(1, 1, 1, 1, TargetBuffer.Color) }, - { GalImageFormat.R11G11B10, new ImageDescriptor(4, 1, 1, 1, TargetBuffer.Color) }, - { GalImageFormat.BC1, new ImageDescriptor(8, 4, 4, 1, TargetBuffer.Color) }, - { GalImageFormat.BC2, new ImageDescriptor(16, 4, 4, 1, TargetBuffer.Color) }, - { GalImageFormat.BC3, new ImageDescriptor(16, 4, 4, 1, TargetBuffer.Color) }, - { GalImageFormat.BC4, new ImageDescriptor(8, 4, 4, 1, TargetBuffer.Color) }, - { GalImageFormat.BC5, new ImageDescriptor(16, 4, 4, 1, TargetBuffer.Color) }, - { GalImageFormat.Astc2D4x4, new ImageDescriptor(16, 4, 4, 1, TargetBuffer.Color) }, - { GalImageFormat.Astc2D5x5, new ImageDescriptor(16, 5, 5, 1, TargetBuffer.Color) }, - { GalImageFormat.Astc2D6x6, new ImageDescriptor(16, 6, 6, 1, TargetBuffer.Color) }, - { GalImageFormat.Astc2D8x8, new ImageDescriptor(16, 8, 8, 1, TargetBuffer.Color) }, - { GalImageFormat.Astc2D10x10, new ImageDescriptor(16, 10, 10, 1, TargetBuffer.Color) }, - { GalImageFormat.Astc2D12x12, new ImageDescriptor(16, 12, 12, 1, TargetBuffer.Color) }, - { GalImageFormat.Astc2D5x4, new ImageDescriptor(16, 5, 4, 1, TargetBuffer.Color) }, - { GalImageFormat.Astc2D6x5, new ImageDescriptor(16, 6, 5, 1, TargetBuffer.Color) }, - { GalImageFormat.Astc2D8x6, new ImageDescriptor(16, 8, 6, 1, TargetBuffer.Color) }, - { GalImageFormat.Astc2D10x8, new ImageDescriptor(16, 10, 8, 1, TargetBuffer.Color) }, - { GalImageFormat.Astc2D12x10, new ImageDescriptor(16, 12, 10, 1, TargetBuffer.Color) }, - { GalImageFormat.Astc2D8x5, new ImageDescriptor(16, 8, 5, 1, TargetBuffer.Color) }, - { GalImageFormat.Astc2D10x5, new ImageDescriptor(16, 10, 5, 1, TargetBuffer.Color) }, - { GalImageFormat.Astc2D10x6, new ImageDescriptor(16, 10, 6, 1, TargetBuffer.Color) }, - - { GalImageFormat.D16, new ImageDescriptor(2, 1, 1, 1, TargetBuffer.Depth) }, - { GalImageFormat.D24, new ImageDescriptor(4, 1, 1, 1, TargetBuffer.Depth) }, - { GalImageFormat.D24S8, new ImageDescriptor(4, 1, 1, 1, TargetBuffer.DepthStencil) }, - { GalImageFormat.D32, new ImageDescriptor(4, 1, 1, 1, TargetBuffer.Depth) }, - { GalImageFormat.D32S8, new ImageDescriptor(8, 1, 1, 1, TargetBuffer.DepthStencil) } - }; - - public static GalImageFormat ConvertTexture( - GalTextureFormat format, - GalTextureType rType, - GalTextureType gType, - GalTextureType bType, - GalTextureType aType, - bool convSrgb) - { - if (!TextureTable.TryGetValue(format, out GalImageFormat imageFormat)) - { - throw new NotImplementedException($"Format 0x{((int)format):x} not implemented!"); - } - - if (!HasDepth(imageFormat) && (rType != gType || rType != bType || rType != aType)) - { - throw new NotImplementedException("Per component types are not implemented!"); - } - - GalImageFormat formatType = convSrgb ? Srgb : GetFormatType(rType); - - GalImageFormat combinedFormat = (imageFormat & GalImageFormat.FormatMask) | formatType; - - if (!imageFormat.HasFlag(formatType)) - { - throw new NotImplementedException($"Format \"{combinedFormat}\" not implemented!"); - } - - return combinedFormat; - } - - public static GalImageFormat ConvertSurface(GalSurfaceFormat format) - { - switch (format) - { - case GalSurfaceFormat.Rgba32Float: return GalImageFormat.Rgba32 | Float; - case GalSurfaceFormat.Rgba32Uint: return GalImageFormat.Rgba32 | Uint; - case GalSurfaceFormat.Rgba16Float: return GalImageFormat.Rgba16 | Float; - case GalSurfaceFormat.Rgba16Uint: return GalImageFormat.Rgba16 | Uint; - case GalSurfaceFormat.Rgba16Unorm: return GalImageFormat.Rgba16 | Unorm; - case GalSurfaceFormat.Rg32Float: return GalImageFormat.Rg32 | Float; - case GalSurfaceFormat.Rg32Sint: return GalImageFormat.Rg32 | Sint; - case GalSurfaceFormat.Rg32Uint: return GalImageFormat.Rg32 | Uint; - case GalSurfaceFormat.Bgra8Unorm: return GalImageFormat.Bgra8 | Unorm; - case GalSurfaceFormat.Bgra8Srgb: return GalImageFormat.Bgra8 | Srgb; - case GalSurfaceFormat.Rgb10A2Unorm: return GalImageFormat.Rgb10A2 | Unorm; - case GalSurfaceFormat.Rgba8Unorm: return GalImageFormat.Rgba8 | Unorm; - case GalSurfaceFormat.Rgba8Srgb: return GalImageFormat.Rgba8 | Srgb; - case GalSurfaceFormat.Rgba8Snorm: return GalImageFormat.Rgba8 | Snorm; - case GalSurfaceFormat.Rg16Snorm: return GalImageFormat.Rg16 | Snorm; - case GalSurfaceFormat.Rg16Unorm: return GalImageFormat.Rg16 | Unorm; - case GalSurfaceFormat.Rg16Sint: return GalImageFormat.Rg16 | Sint; - case GalSurfaceFormat.Rg16Float: return GalImageFormat.Rg16 | Float; - case GalSurfaceFormat.R11G11B10Float: return GalImageFormat.R11G11B10 | Float; - case GalSurfaceFormat.R32Float: return GalImageFormat.R32 | Float; - case GalSurfaceFormat.R32Uint: return GalImageFormat.R32 | Uint; - case GalSurfaceFormat.Rg8Unorm: return GalImageFormat.Rg8 | Unorm; - case GalSurfaceFormat.Rg8Snorm: return GalImageFormat.Rg8 | Snorm; - case GalSurfaceFormat.R16Float: return GalImageFormat.R16 | Float; - case GalSurfaceFormat.R16Unorm: return GalImageFormat.R16 | Unorm; - case GalSurfaceFormat.R16Uint: return GalImageFormat.R16 | Uint; - case GalSurfaceFormat.R8Unorm: return GalImageFormat.R8 | Unorm; - case GalSurfaceFormat.R8Uint: return GalImageFormat.R8 | Uint; - case GalSurfaceFormat.B5G6R5Unorm: return GalImageFormat.Rgb565 | Unorm; - case GalSurfaceFormat.Bgr5A1Unorm: return GalImageFormat.Bgr5A1 | Unorm; - case GalSurfaceFormat.Rgbx8Unorm: return GalImageFormat.Rgbx8 | Unorm; - } - - throw new NotImplementedException(format.ToString()); - } - - public static GalImageFormat ConvertZeta(GalZetaFormat format) - { - switch (format) - { - case GalZetaFormat.D32Float: return GalImageFormat.D32 | Float; - case GalZetaFormat.S8D24Unorm: return GalImageFormat.D24S8 | Unorm; - case GalZetaFormat.D16Unorm: return GalImageFormat.D16 | Unorm; - case GalZetaFormat.D24X8Unorm: return GalImageFormat.D24 | Unorm; - case GalZetaFormat.D24S8Unorm: return GalImageFormat.D24S8 | Unorm; - case GalZetaFormat.D32S8X24Float: return GalImageFormat.D32S8 | Float; - } - - throw new NotImplementedException(format.ToString()); - } - - public static byte[] ReadTexture(IMemory memory, GalImage image, long position) - { - MemoryManager cpuMemory; - - if (memory is NvGpuVmm vmm) - { - cpuMemory = vmm.Memory; - } - else - { - cpuMemory = (MemoryManager)memory; - } - - ISwizzle swizzle = TextureHelper.GetSwizzle(image); - - ImageDescriptor desc = GetImageDescriptor(image.Format); - - (int width, int height, int depth) = GetImageSizeInBlocks(image); - - int bytesPerPixel = desc.BytesPerPixel; - - // Note: Each row of the texture needs to be aligned to 4 bytes. - int pitch = (width * bytesPerPixel + 3) & ~3; - - int dataLayerSize = height * pitch * depth; - byte[] data = new byte[dataLayerSize * image.LayerCount]; - - int targetMipLevel = image.MaxMipmapLevel <= 1 ? 1 : image.MaxMipmapLevel - 1; - int layerOffset = GetLayerOffset(image, targetMipLevel); - - for (int layer = 0; layer < image.LayerCount; layer++) - { - for (int z = 0; z < depth; z++) - { - for (int y = 0; y < height; y++) - { - int outOffs = (dataLayerSize * layer) + y * pitch + (z * width * height * bytesPerPixel); - - for (int x = 0; x < width; x++) - { - long offset = (uint)swizzle.GetSwizzleOffset(x, y, z); - - cpuMemory.ReadBytes(position + (layerOffset * layer) + offset, data, outOffs, bytesPerPixel); - - outOffs += bytesPerPixel; - } - } - } - } - - return data; - } - - public static void WriteTexture(NvGpuVmm vmm, GalImage image, long position, byte[] data) - { - ISwizzle swizzle = TextureHelper.GetSwizzle(image); - - ImageDescriptor desc = GetImageDescriptor(image.Format); - - (int width, int height, int depth) = GetImageSizeInBlocks(image); - - int bytesPerPixel = desc.BytesPerPixel; - - int inOffs = 0; - - for (int z = 0; z < depth; z++) - for (int y = 0; y < height; y++) - for (int x = 0; x < width; x++) - { - long offset = (uint)swizzle.GetSwizzleOffset(x, y, z); - - vmm.Memory.WriteBytes(position + offset, data, inOffs, bytesPerPixel); - - inOffs += bytesPerPixel; - } - } - - // TODO: Support non 2D - public static bool CopyTexture( - NvGpuVmm vmm, - GalImage srcImage, - GalImage dstImage, - long srcAddress, - long dstAddress, - int srcX, - int srcY, - int dstX, - int dstY, - int width, - int height) - { - ISwizzle srcSwizzle = TextureHelper.GetSwizzle(srcImage); - ISwizzle dstSwizzle = TextureHelper.GetSwizzle(dstImage); - - ImageDescriptor desc = GetImageDescriptor(srcImage.Format); - - if (GetImageDescriptor(dstImage.Format).BytesPerPixel != desc.BytesPerPixel) - { - return false; - } - - int bytesPerPixel = desc.BytesPerPixel; - - for (int y = 0; y < height; y++) - for (int x = 0; x < width; x++) - { - long srcOffset = (uint)srcSwizzle.GetSwizzleOffset(srcX + x, srcY + y, 0); - long dstOffset = (uint)dstSwizzle.GetSwizzleOffset(dstX + x, dstY + y, 0); - - byte[] texel = vmm.ReadBytes(srcAddress + srcOffset, bytesPerPixel); - - vmm.WriteBytes(dstAddress + dstOffset, texel); - } - - return true; - } - - public static int GetSize(GalImage image) - { - ImageDescriptor desc = GetImageDescriptor(image.Format); - - int componentCount = GetCoordsCountTextureTarget(image.TextureTarget); - - if (IsArray(image.TextureTarget)) - componentCount--; - - int width = DivRoundUp(image.Width, desc.BlockWidth); - int height = DivRoundUp(image.Height, desc.BlockHeight); - int depth = DivRoundUp(image.Depth, desc.BlockDepth); - - switch (componentCount) - { - case 1: - return desc.BytesPerPixel * width * image.LayerCount; - case 2: - return desc.BytesPerPixel * width * height * image.LayerCount; - case 3: - return desc.BytesPerPixel * width * height * depth * image.LayerCount; - default: - throw new InvalidOperationException($"Invalid component count: {componentCount}"); - } - } - - public static int GetGpuSize(GalImage image, bool forcePitch = false) - { - return TextureHelper.GetSwizzle(image).GetImageSize(image.MaxMipmapLevel) * image.LayerCount; - } - - public static int GetLayerOffset(GalImage image, int mipLevel) - { - if (mipLevel <= 0) - { - mipLevel = 1; - } - - return TextureHelper.GetSwizzle(image).GetMipOffset(mipLevel); - } - - public static int GetPitch(GalImageFormat format, int width) - { - ImageDescriptor desc = GetImageDescriptor(format); - - int pitch = desc.BytesPerPixel * DivRoundUp(width, desc.BlockWidth); - - pitch = (pitch + 0x1f) & ~0x1f; - - return pitch; - } - - public static int GetBlockWidth(GalImageFormat format) - { - return GetImageDescriptor(format).BlockWidth; - } - - public static int GetBlockHeight(GalImageFormat format) - { - return GetImageDescriptor(format).BlockHeight; - } - - public static int GetBlockDepth(GalImageFormat format) - { - return GetImageDescriptor(format).BlockDepth; - } - - public static int GetAlignedWidth(GalImage image) - { - ImageDescriptor desc = GetImageDescriptor(image.Format); - - int alignMask; - - if (image.Layout == GalMemoryLayout.BlockLinear) - { - alignMask = image.TileWidth * (64 / desc.BytesPerPixel) - 1; - } - else - { - alignMask = (32 / desc.BytesPerPixel) - 1; - } - - return (image.Width + alignMask) & ~alignMask; - } - - public static (int Width, int Height, int Depth) GetImageSizeInBlocks(GalImage image) - { - ImageDescriptor desc = GetImageDescriptor(image.Format); - - return (DivRoundUp(image.Width, desc.BlockWidth), - DivRoundUp(image.Height, desc.BlockHeight), - DivRoundUp(image.Depth, desc.BlockDepth)); - } - - public static int GetBytesPerPixel(GalImageFormat format) - { - return GetImageDescriptor(format).BytesPerPixel; - } - - private static int DivRoundUp(int lhs, int rhs) - { - return (lhs + (rhs - 1)) / rhs; - } - - public static bool HasColor(GalImageFormat format) - { - return (GetImageDescriptor(format).Target & TargetBuffer.Color) != 0; - } - - public static bool HasDepth(GalImageFormat format) - { - return (GetImageDescriptor(format).Target & TargetBuffer.Depth) != 0; - } - - public static bool HasStencil(GalImageFormat format) - { - return (GetImageDescriptor(format).Target & TargetBuffer.Stencil) != 0; - } - - public static bool IsCompressed(GalImageFormat format) - { - ImageDescriptor desc = GetImageDescriptor(format); - - return (desc.BlockWidth | desc.BlockHeight) != 1; - } - - private static ImageDescriptor GetImageDescriptor(GalImageFormat format) - { - GalImageFormat pixelFormat = format & GalImageFormat.FormatMask; - - if (ImageTable.TryGetValue(pixelFormat, out ImageDescriptor descriptor)) - { - return descriptor; - } - - throw new NotImplementedException($"Format \"{pixelFormat}\" not implemented!"); - } - - private static GalImageFormat GetFormatType(GalTextureType type) - { - switch (type) - { - case GalTextureType.Snorm: return Snorm; - case GalTextureType.Unorm: return Unorm; - case GalTextureType.Sint: return Sint; - case GalTextureType.Uint: return Uint; - case GalTextureType.Float: return Float; - - default: throw new NotImplementedException(((int)type).ToString()); - } - } - - public static TextureTarget GetTextureTarget(GalTextureTarget galTextureTarget) - { - switch (galTextureTarget) - { - case GalTextureTarget.OneD: - return TextureTarget.Texture1D; - case GalTextureTarget.TwoD: - case GalTextureTarget.TwoDNoMipMap: - return TextureTarget.Texture2D; - case GalTextureTarget.ThreeD: - return TextureTarget.Texture3D; - case GalTextureTarget.OneDArray: - return TextureTarget.Texture1DArray; - case GalTextureTarget.OneDBuffer: - return TextureTarget.TextureBuffer; - case GalTextureTarget.TwoDArray: - return TextureTarget.Texture2DArray; - case GalTextureTarget.CubeMap: - return TextureTarget.TextureCubeMap; - case GalTextureTarget.CubeArray: - return TextureTarget.TextureCubeMapArray; - default: - throw new NotSupportedException($"Texture target {galTextureTarget} currently not supported!"); - } - } - - public static bool IsArray(GalTextureTarget textureTarget) - { - switch (textureTarget) - { - case GalTextureTarget.OneDArray: - case GalTextureTarget.TwoDArray: - case GalTextureTarget.CubeArray: - return true; - default: - return false; - } - } - - public static int GetCoordsCountTextureTarget(GalTextureTarget textureTarget) - { - switch (textureTarget) - { - case GalTextureTarget.OneD: - return 1; - case GalTextureTarget.OneDArray: - case GalTextureTarget.OneDBuffer: - case GalTextureTarget.TwoD: - case GalTextureTarget.TwoDNoMipMap: - return 2; - case GalTextureTarget.ThreeD: - case GalTextureTarget.TwoDArray: - case GalTextureTarget.CubeMap: - return 3; - case GalTextureTarget.CubeArray: - return 4; - default: - throw new NotImplementedException($"TextureTarget.{textureTarget} not implemented yet."); - } - } - } -} diff --git a/Ryujinx.Graphics/Graphics3d/Texture/IntegerEncoded.cs b/Ryujinx.Graphics/Graphics3d/Texture/IntegerEncoded.cs deleted file mode 100644 index e6d67058e0..0000000000 --- a/Ryujinx.Graphics/Graphics3d/Texture/IntegerEncoded.cs +++ /dev/null @@ -1,269 +0,0 @@ -using System.Collections; -using System.Collections.Generic; - -namespace Ryujinx.Graphics.Texture -{ - public struct IntegerEncoded - { - public enum EIntegerEncoding - { - JustBits, - Quint, - Trit - } - - EIntegerEncoding _encoding; - public int NumberBits { get; private set; } - public int BitValue { get; private set; } - public int TritValue { get; private set; } - public int QuintValue { get; private set; } - - public IntegerEncoded(EIntegerEncoding encoding, int numBits) - { - _encoding = encoding; - NumberBits = numBits; - BitValue = 0; - TritValue = 0; - QuintValue = 0; - } - - public bool MatchesEncoding(IntegerEncoded other) - { - return _encoding == other._encoding && NumberBits == other.NumberBits; - } - - public EIntegerEncoding GetEncoding() - { - return _encoding; - } - - public int GetBitLength(int numberVals) - { - int totalBits = NumberBits * numberVals; - if (_encoding == EIntegerEncoding.Trit) - { - totalBits += (numberVals * 8 + 4) / 5; - } - else if (_encoding == EIntegerEncoding.Quint) - { - totalBits += (numberVals * 7 + 2) / 3; - } - return totalBits; - } - - public static IntegerEncoded CreateEncoding(int maxVal) - { - while (maxVal > 0) - { - int check = maxVal + 1; - - // Is maxVal a power of two? - if ((check & (check - 1)) == 0) - { - return new IntegerEncoded(EIntegerEncoding.JustBits, BitArrayStream.PopCnt(maxVal)); - } - - // Is maxVal of the type 3*2^n - 1? - if ((check % 3 == 0) && ((check / 3) & ((check / 3) - 1)) == 0) - { - return new IntegerEncoded(EIntegerEncoding.Trit, BitArrayStream.PopCnt(check / 3 - 1)); - } - - // Is maxVal of the type 5*2^n - 1? - if ((check % 5 == 0) && ((check / 5) & ((check / 5) - 1)) == 0) - { - return new IntegerEncoded(EIntegerEncoding.Quint, BitArrayStream.PopCnt(check / 5 - 1)); - } - - // Apparently it can't be represented with a bounded integer sequence... - // just iterate. - maxVal--; - } - - return new IntegerEncoded(EIntegerEncoding.JustBits, 0); - } - - public static void DecodeTritBlock( - BitArrayStream bitStream, - List listIntegerEncoded, - int numberBitsPerValue) - { - // Implement the algorithm in section C.2.12 - int[] m = new int[5]; - int[] t = new int[5]; - int T; - - // Read the trit encoded block according to - // table C.2.14 - m[0] = bitStream.ReadBits(numberBitsPerValue); - T = bitStream.ReadBits(2); - m[1] = bitStream.ReadBits(numberBitsPerValue); - T |= bitStream.ReadBits(2) << 2; - m[2] = bitStream.ReadBits(numberBitsPerValue); - T |= bitStream.ReadBits(1) << 4; - m[3] = bitStream.ReadBits(numberBitsPerValue); - T |= bitStream.ReadBits(2) << 5; - m[4] = bitStream.ReadBits(numberBitsPerValue); - T |= bitStream.ReadBits(1) << 7; - - int c = 0; - - BitArrayStream tb = new BitArrayStream(new BitArray(new int[] { T })); - if (tb.ReadBits(2, 4) == 7) - { - c = (tb.ReadBits(5, 7) << 2) | tb.ReadBits(0, 1); - t[4] = t[3] = 2; - } - else - { - c = tb.ReadBits(0, 4); - if (tb.ReadBits(5, 6) == 3) - { - t[4] = 2; - t[3] = tb.ReadBit(7); - } - else - { - t[4] = tb.ReadBit(7); - t[3] = tb.ReadBits(5, 6); - } - } - - BitArrayStream cb = new BitArrayStream(new BitArray(new int[] { c })); - if (cb.ReadBits(0, 1) == 3) - { - t[2] = 2; - t[1] = cb.ReadBit(4); - t[0] = (cb.ReadBit(3) << 1) | (cb.ReadBit(2) & ~cb.ReadBit(3)); - } - else if (cb.ReadBits(2, 3) == 3) - { - t[2] = 2; - t[1] = 2; - t[0] = cb.ReadBits(0, 1); - } - else - { - t[2] = cb.ReadBit(4); - t[1] = cb.ReadBits(2, 3); - t[0] = (cb.ReadBit(1) << 1) | (cb.ReadBit(0) & ~cb.ReadBit(1)); - } - - for (int i = 0; i < 5; i++) - { - IntegerEncoded intEncoded = new IntegerEncoded(EIntegerEncoding.Trit, numberBitsPerValue) - { - BitValue = m[i], - TritValue = t[i] - }; - listIntegerEncoded.Add(intEncoded); - } - } - - public static void DecodeQuintBlock( - BitArrayStream bitStream, - List listIntegerEncoded, - int numberBitsPerValue) - { - // Implement the algorithm in section C.2.12 - int[] m = new int[3]; - int[] qa = new int[3]; - int q; - - // Read the trit encoded block according to - // table C.2.15 - m[0] = bitStream.ReadBits(numberBitsPerValue); - q = bitStream.ReadBits(3); - m[1] = bitStream.ReadBits(numberBitsPerValue); - q |= bitStream.ReadBits(2) << 3; - m[2] = bitStream.ReadBits(numberBitsPerValue); - q |= bitStream.ReadBits(2) << 5; - - BitArrayStream qb = new BitArrayStream(new BitArray(new int[] { q })); - if (qb.ReadBits(1, 2) == 3 && qb.ReadBits(5, 6) == 0) - { - qa[0] = qa[1] = 4; - qa[2] = (qb.ReadBit(0) << 2) | ((qb.ReadBit(4) & ~qb.ReadBit(0)) << 1) | (qb.ReadBit(3) & ~qb.ReadBit(0)); - } - else - { - int c = 0; - if (qb.ReadBits(1, 2) == 3) - { - qa[2] = 4; - c = (qb.ReadBits(3, 4) << 3) | ((~qb.ReadBits(5, 6) & 3) << 1) | qb.ReadBit(0); - } - else - { - qa[2] = qb.ReadBits(5, 6); - c = qb.ReadBits(0, 4); - } - - BitArrayStream cb = new BitArrayStream(new BitArray(new int[] { c })); - if (cb.ReadBits(0, 2) == 5) - { - qa[1] = 4; - qa[0] = cb.ReadBits(3, 4); - } - else - { - qa[1] = cb.ReadBits(3, 4); - qa[0] = cb.ReadBits(0, 2); - } - } - - for (int i = 0; i < 3; i++) - { - IntegerEncoded intEncoded = new IntegerEncoded(EIntegerEncoding.Quint, numberBitsPerValue) - { - BitValue = m[i], - QuintValue = qa[i] - }; - listIntegerEncoded.Add(intEncoded); - } - } - - public static void DecodeIntegerSequence( - List decodeIntegerSequence, - BitArrayStream bitStream, - int maxRange, - int numberValues) - { - // Determine encoding parameters - IntegerEncoded intEncoded = CreateEncoding(maxRange); - - // Start decoding - int numberValuesDecoded = 0; - while (numberValuesDecoded < numberValues) - { - switch (intEncoded.GetEncoding()) - { - case EIntegerEncoding.Quint: - { - DecodeQuintBlock(bitStream, decodeIntegerSequence, intEncoded.NumberBits); - numberValuesDecoded += 3; - - break; - } - - case EIntegerEncoding.Trit: - { - DecodeTritBlock(bitStream, decodeIntegerSequence, intEncoded.NumberBits); - numberValuesDecoded += 5; - - break; - } - - case EIntegerEncoding.JustBits: - { - intEncoded.BitValue = bitStream.ReadBits(intEncoded.NumberBits); - decodeIntegerSequence.Add(intEncoded); - numberValuesDecoded++; - - break; - } - } - } - } - } -} diff --git a/Ryujinx.Graphics/Graphics3d/Texture/LinearSwizzle.cs b/Ryujinx.Graphics/Graphics3d/Texture/LinearSwizzle.cs deleted file mode 100644 index fb1bd0985f..0000000000 --- a/Ryujinx.Graphics/Graphics3d/Texture/LinearSwizzle.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; - -namespace Ryujinx.Graphics.Texture -{ - class LinearSwizzle : ISwizzle - { - private int _pitch; - private int _bpp; - - private int _sliceSize; - - public LinearSwizzle(int pitch, int bpp, int width, int height) - { - _pitch = pitch; - _bpp = bpp; - _sliceSize = width * height * bpp; - } - - public void SetMipLevel(int level) - { - throw new NotImplementedException(); - } - - public int GetMipOffset(int level) - { - if (level == 1) - return _sliceSize; - throw new NotImplementedException(); - } - - public int GetImageSize(int mipsCount) - { - int size = GetMipOffset(mipsCount); - - size = (size + 0x1fff) & ~0x1fff; - - return size; - } - - public int GetSwizzleOffset(int x, int y, int z) - { - return z * _sliceSize + x * _bpp + y * _pitch; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Graphics3d/Texture/TextureFactory.cs b/Ryujinx.Graphics/Graphics3d/Texture/TextureFactory.cs deleted file mode 100644 index 28c9050253..0000000000 --- a/Ryujinx.Graphics/Graphics3d/Texture/TextureFactory.cs +++ /dev/null @@ -1,166 +0,0 @@ -using Ryujinx.Graphics.Gal; -using Ryujinx.Graphics.Memory; -using System; - -namespace Ryujinx.Graphics.Texture -{ - static class TextureFactory - { - public static GalImage MakeTexture(NvGpuVmm vmm, long ticPosition) - { - int[] tic = ReadWords(vmm, ticPosition, 8); - - GalImageFormat format = GetImageFormat(tic); - - GalTextureTarget textureTarget = (GalTextureTarget)((tic[4] >> 23) & 0xF); - - GalTextureSource xSource = (GalTextureSource)((tic[0] >> 19) & 7); - GalTextureSource ySource = (GalTextureSource)((tic[0] >> 22) & 7); - GalTextureSource zSource = (GalTextureSource)((tic[0] >> 25) & 7); - GalTextureSource wSource = (GalTextureSource)((tic[0] >> 28) & 7); - - TextureSwizzle swizzle = (TextureSwizzle)((tic[2] >> 21) & 7); - - int maxMipmapLevel = (tic[3] >> 28) & 0xF + 1; - - GalMemoryLayout layout; - - if (swizzle == TextureSwizzle.BlockLinear || - swizzle == TextureSwizzle.BlockLinearColorKey) - { - layout = GalMemoryLayout.BlockLinear; - } - else - { - layout = GalMemoryLayout.Pitch; - } - - int gobBlockHeightLog2 = (tic[3] >> 3) & 7; - int gobBlockDepthLog2 = (tic[3] >> 6) & 7; - int tileWidthLog2 = (tic[3] >> 10) & 7; - - int gobBlockHeight = 1 << gobBlockHeightLog2; - int gobBlockDepth = 1 << gobBlockDepthLog2; - int tileWidth = 1 << tileWidthLog2; - - int width = ((tic[4] >> 0) & 0xffff) + 1; - int height = ((tic[5] >> 0) & 0xffff) + 1; - int depth = ((tic[5] >> 16) & 0x3fff) + 1; - - int layoutCount = 1; - - // TODO: check this - if (ImageUtils.IsArray(textureTarget)) - { - layoutCount = depth; - depth = 1; - } - - if (textureTarget == GalTextureTarget.OneD) - { - height = 1; - } - - if (textureTarget == GalTextureTarget.TwoD || textureTarget == GalTextureTarget.OneD) - { - depth = 1; - } - else if (textureTarget == GalTextureTarget.CubeMap) - { - // FIXME: This is a bit hacky but I guess it's fine for now - layoutCount = 6; - depth = 1; - } - else if (textureTarget == GalTextureTarget.CubeArray) - { - // FIXME: This is a really really hacky but I guess it's fine for now - layoutCount *= 6; - depth = 1; - } - - GalImage image = new GalImage( - width, - height, - depth, - layoutCount, - tileWidth, - gobBlockHeight, - gobBlockDepth, - layout, - format, - textureTarget, - maxMipmapLevel, - xSource, - ySource, - zSource, - wSource); - - if (layout == GalMemoryLayout.Pitch) - { - image.Pitch = (tic[3] & 0xffff) << 5; - } - - return image; - } - - public static GalTextureSampler MakeSampler(NvGpu gpu, NvGpuVmm vmm, long tscPosition) - { - int[] tsc = ReadWords(vmm, tscPosition, 8); - - GalTextureWrap addressU = (GalTextureWrap)((tsc[0] >> 0) & 7); - GalTextureWrap addressV = (GalTextureWrap)((tsc[0] >> 3) & 7); - GalTextureWrap addressP = (GalTextureWrap)((tsc[0] >> 6) & 7); - - bool depthCompare = ((tsc[0] >> 9) & 1) == 1; - - DepthCompareFunc depthCompareFunc = (DepthCompareFunc)((tsc[0] >> 10) & 7); - - GalTextureFilter magFilter = (GalTextureFilter) ((tsc[1] >> 0) & 3); - GalTextureFilter minFilter = (GalTextureFilter) ((tsc[1] >> 4) & 3); - GalTextureMipFilter mipFilter = (GalTextureMipFilter)((tsc[1] >> 6) & 3); - - GalColorF borderColor = new GalColorF( - BitConverter.Int32BitsToSingle(tsc[4]), - BitConverter.Int32BitsToSingle(tsc[5]), - BitConverter.Int32BitsToSingle(tsc[6]), - BitConverter.Int32BitsToSingle(tsc[7])); - - return new GalTextureSampler( - addressU, - addressV, - addressP, - minFilter, - magFilter, - mipFilter, - borderColor, - depthCompare, - depthCompareFunc); - } - - private static GalImageFormat GetImageFormat(int[] tic) - { - GalTextureType rType = (GalTextureType)((tic[0] >> 7) & 7); - GalTextureType gType = (GalTextureType)((tic[0] >> 10) & 7); - GalTextureType bType = (GalTextureType)((tic[0] >> 13) & 7); - GalTextureType aType = (GalTextureType)((tic[0] >> 16) & 7); - - GalTextureFormat format = (GalTextureFormat)(tic[0] & 0x7f); - - bool convSrgb = ((tic[4] >> 22) & 1) != 0; - - return ImageUtils.ConvertTexture(format, rType, gType, bType, aType, convSrgb); - } - - private static int[] ReadWords(NvGpuVmm vmm, long position, int count) - { - int[] words = new int[count]; - - for (int index = 0; index < count; index++, position += 4) - { - words[index] = vmm.ReadInt32(position); - } - - return words; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Graphics3d/Texture/TextureHelper.cs b/Ryujinx.Graphics/Graphics3d/Texture/TextureHelper.cs deleted file mode 100644 index e07eb037d1..0000000000 --- a/Ryujinx.Graphics/Graphics3d/Texture/TextureHelper.cs +++ /dev/null @@ -1,53 +0,0 @@ -using ARMeilleure.Memory; -using Ryujinx.Common; -using Ryujinx.Graphics.Gal; -using Ryujinx.Graphics.Memory; - -namespace Ryujinx.Graphics.Texture -{ - static class TextureHelper - { - public static ISwizzle GetSwizzle(GalImage image) - { - int blockWidth = ImageUtils.GetBlockWidth (image.Format); - int blockHeight = ImageUtils.GetBlockHeight (image.Format); - int blockDepth = ImageUtils.GetBlockDepth (image.Format); - int bytesPerPixel = ImageUtils.GetBytesPerPixel(image.Format); - - int width = BitUtils.DivRoundUp(image.Width, blockWidth); - int height = BitUtils.DivRoundUp(image.Height, blockHeight); - int depth = BitUtils.DivRoundUp(image.Depth, blockDepth); - - if (image.Layout == GalMemoryLayout.BlockLinear) - { - int alignMask = image.TileWidth * (64 / bytesPerPixel) - 1; - - width = (width + alignMask) & ~alignMask; - - return new BlockLinearSwizzle( - width, - height, - depth, - image.GobBlockHeight, - image.GobBlockDepth, - bytesPerPixel); - } - else - { - return new LinearSwizzle(image.Pitch, bytesPerPixel, width, height); - } - } - - public static (MemoryManager Memory, long Position) GetMemoryAndPosition( - IMemory memory, - long position) - { - if (memory is NvGpuVmm vmm) - { - return (vmm.Memory, vmm.GetPhysicalAddress(position)); - } - - return ((MemoryManager)memory, position); - } - } -} diff --git a/Ryujinx.Graphics/Graphics3d/Texture/TextureSwizzle.cs b/Ryujinx.Graphics/Graphics3d/Texture/TextureSwizzle.cs deleted file mode 100644 index 2cc426ab9d..0000000000 --- a/Ryujinx.Graphics/Graphics3d/Texture/TextureSwizzle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Ryujinx.Graphics.Texture -{ - public enum TextureSwizzle - { - _1DBuffer = 0, - PitchColorKey = 1, - Pitch = 2, - BlockLinear = 3, - BlockLinearColorKey = 4 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/GraphicsConfig.cs b/Ryujinx.Graphics/GraphicsConfig.cs deleted file mode 100644 index 3e3ef4ffa7..0000000000 --- a/Ryujinx.Graphics/GraphicsConfig.cs +++ /dev/null @@ -1,4 +0,0 @@ -public static class GraphicsConfig -{ - public static string ShadersDumpPath; -} diff --git a/Ryujinx.Graphics/Memory/NvGpuBufferType.cs b/Ryujinx.Graphics/Memory/NvGpuBufferType.cs deleted file mode 100644 index 6f0d257188..0000000000 --- a/Ryujinx.Graphics/Memory/NvGpuBufferType.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Ryujinx.Graphics.Memory -{ - public enum NvGpuBufferType - { - Index, - Vertex, - Texture, - ConstBuffer, - Count - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Memory/NvGpuVmm.cs b/Ryujinx.Graphics/Memory/NvGpuVmm.cs deleted file mode 100644 index c72b82e37e..0000000000 --- a/Ryujinx.Graphics/Memory/NvGpuVmm.cs +++ /dev/null @@ -1,399 +0,0 @@ -using ARMeilleure.Memory; -using Ryujinx.Graphics.Gal; -using System; - -namespace Ryujinx.Graphics.Memory -{ - public class NvGpuVmm : IMemory, IGalMemory - { - public const long AddrSize = 1L << 40; - - private const int PtLvl0Bits = 14; - private const int PtLvl1Bits = 14; - private const int PtPageBits = 12; - - private const int PtLvl0Size = 1 << PtLvl0Bits; - private const int PtLvl1Size = 1 << PtLvl1Bits; - public const int PageSize = 1 << PtPageBits; - - private const int PtLvl0Mask = PtLvl0Size - 1; - private const int PtLvl1Mask = PtLvl1Size - 1; - public const int PageMask = PageSize - 1; - - private const int PtLvl0Bit = PtPageBits + PtLvl1Bits; - private const int PtLvl1Bit = PtPageBits; - - public MemoryManager Memory { get; private set; } - - private NvGpuVmmCache _cache; - - private const long PteUnmapped = -1; - private const long PteReserved = -2; - - private long[][] _pageTable; - - public NvGpuVmm(MemoryManager memory) - { - Memory = memory; - - _cache = new NvGpuVmmCache(memory); - - _pageTable = new long[PtLvl0Size][]; - } - - public long Map(long pa, long va, long size) - { - lock (_pageTable) - { - for (long offset = 0; offset < size; offset += PageSize) - { - SetPte(va + offset, pa + offset); - } - } - - return va; - } - - public long Map(long pa, long size) - { - lock (_pageTable) - { - long va = GetFreePosition(size); - - if (va != -1) - { - for (long offset = 0; offset < size; offset += PageSize) - { - SetPte(va + offset, pa + offset); - } - } - - return va; - } - } - - public long MapLow(long pa, long size) - { - lock (_pageTable) - { - long va = GetFreePosition(size, 1, PageSize); - - if (va != -1 && (ulong)va <= uint.MaxValue && (ulong)(va + size) <= uint.MaxValue) - { - for (long offset = 0; offset < size; offset += PageSize) - { - SetPte(va + offset, pa + offset); - } - } - else - { - va = -1; - } - - return va; - } - } - - public long ReserveFixed(long va, long size) - { - lock (_pageTable) - { - for (long offset = 0; offset < size; offset += PageSize) - { - if (IsPageInUse(va + offset)) - { - return -1; - } - } - - for (long offset = 0; offset < size; offset += PageSize) - { - SetPte(va + offset, PteReserved); - } - } - - return va; - } - - public long Reserve(long size, long align) - { - lock (_pageTable) - { - long position = GetFreePosition(size, align); - - if (position != -1) - { - for (long offset = 0; offset < size; offset += PageSize) - { - SetPte(position + offset, PteReserved); - } - } - - return position; - } - } - - public void Free(long va, long size) - { - lock (_pageTable) - { - for (long offset = 0; offset < size; offset += PageSize) - { - SetPte(va + offset, PteUnmapped); - } - } - } - - private long GetFreePosition(long size, long align = 1, long start = 1L << 32) - { - // Note: Address 0 is not considered valid by the driver, - // when 0 is returned it's considered a mapping error. - long position = start; - long freeSize = 0; - - if (align < 1) - { - align = 1; - } - - align = (align + PageMask) & ~PageMask; - - while (position + freeSize < AddrSize) - { - if (!IsPageInUse(position + freeSize)) - { - freeSize += PageSize; - - if (freeSize >= size) - { - return position; - } - } - else - { - position += freeSize + PageSize; - freeSize = 0; - - long remainder = position % align; - - if (remainder != 0) - { - position = (position - remainder) + align; - } - } - } - - return -1; - } - - public long GetPhysicalAddress(long va) - { - long basePos = GetPte(va); - - if (basePos < 0) - { - return -1; - } - - return basePos + (va & PageMask); - } - - public bool IsRegionFree(long va, long size) - { - for (long offset = 0; offset < size; offset += PageSize) - { - if (IsPageInUse(va + offset)) - { - return false; - } - } - - return true; - } - - private bool IsPageInUse(long va) - { - if (va >> PtLvl0Bits + PtLvl1Bits + PtPageBits != 0) - { - return false; - } - - long l0 = (va >> PtLvl0Bit) & PtLvl0Mask; - long l1 = (va >> PtLvl1Bit) & PtLvl1Mask; - - if (_pageTable[l0] == null) - { - return false; - } - - return _pageTable[l0][l1] != PteUnmapped; - } - - private long GetPte(long position) - { - long l0 = (position >> PtLvl0Bit) & PtLvl0Mask; - long l1 = (position >> PtLvl1Bit) & PtLvl1Mask; - - if (_pageTable[l0] == null) - { - return -1; - } - - return _pageTable[l0][l1]; - } - - private void SetPte(long position, long tgtAddr) - { - long l0 = (position >> PtLvl0Bit) & PtLvl0Mask; - long l1 = (position >> PtLvl1Bit) & PtLvl1Mask; - - if (_pageTable[l0] == null) - { - _pageTable[l0] = new long[PtLvl1Size]; - - for (int index = 0; index < PtLvl1Size; index++) - { - _pageTable[l0][index] = PteUnmapped; - } - } - - _pageTable[l0][l1] = tgtAddr; - } - - public bool IsRegionModified(long pa, long size, NvGpuBufferType bufferType) - { - return _cache.IsRegionModified(pa, size, bufferType); - } - - public bool TryGetHostAddress(long position, long size, out IntPtr ptr) - { - return Memory.TryGetHostAddress(GetPhysicalAddress(position), size, out ptr); - } - - public byte ReadByte(long position) - { - position = GetPhysicalAddress(position); - - return Memory.ReadByte(position); - } - - public ushort ReadUInt16(long position) - { - position = GetPhysicalAddress(position); - - return Memory.ReadUInt16(position); - } - - public uint ReadUInt32(long position) - { - position = GetPhysicalAddress(position); - - return Memory.ReadUInt32(position); - } - - public ulong ReadUInt64(long position) - { - position = GetPhysicalAddress(position); - - return Memory.ReadUInt64(position); - } - - public sbyte ReadSByte(long position) - { - position = GetPhysicalAddress(position); - - return Memory.ReadSByte(position); - } - - public short ReadInt16(long position) - { - position = GetPhysicalAddress(position); - - return Memory.ReadInt16(position); - } - - public int ReadInt32(long position) - { - position = GetPhysicalAddress(position); - - return Memory.ReadInt32(position); - } - - public long ReadInt64(long position) - { - position = GetPhysicalAddress(position); - - return Memory.ReadInt64(position); - } - - public byte[] ReadBytes(long position, long size) - { - position = GetPhysicalAddress(position); - - return Memory.ReadBytes(position, size); - } - - public void WriteByte(long position, byte value) - { - position = GetPhysicalAddress(position); - - Memory.WriteByte(position, value); - } - - public void WriteUInt16(long position, ushort value) - { - position = GetPhysicalAddress(position); - - Memory.WriteUInt16(position, value); - } - - public void WriteUInt32(long position, uint value) - { - position = GetPhysicalAddress(position); - - Memory.WriteUInt32(position, value); - } - - public void WriteUInt64(long position, ulong value) - { - position = GetPhysicalAddress(position); - - Memory.WriteUInt64(position, value); - } - - public void WriteSByte(long position, sbyte value) - { - position = GetPhysicalAddress(position); - - Memory.WriteSByte(position, value); - } - - public void WriteInt16(long position, short value) - { - position = GetPhysicalAddress(position); - - Memory.WriteInt16(position, value); - } - - public void WriteInt32(long position, int value) - { - position = GetPhysicalAddress(position); - - Memory.WriteInt32(position, value); - } - - public void WriteInt64(long position, long value) - { - position = GetPhysicalAddress(position); - - Memory.WriteInt64(position, value); - } - - public void WriteBytes(long position, byte[] data) - { - position = GetPhysicalAddress(position); - - Memory.WriteBytes(position, data); - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Memory/NvGpuVmmCache.cs b/Ryujinx.Graphics/Memory/NvGpuVmmCache.cs deleted file mode 100644 index 2a5054434d..0000000000 --- a/Ryujinx.Graphics/Memory/NvGpuVmmCache.cs +++ /dev/null @@ -1,79 +0,0 @@ -using ARMeilleure.Memory; -using System.Collections.Concurrent; - -namespace Ryujinx.Graphics.Memory -{ - class NvGpuVmmCache - { - private const int PageBits = MemoryManager.PageBits; - - private const long PageSize = MemoryManager.PageSize; - private const long PageMask = MemoryManager.PageMask; - - private ConcurrentDictionary[] _cachedPages; - - private MemoryManager _memory; - - public NvGpuVmmCache(MemoryManager memory) - { - _memory = memory; - - _cachedPages = new ConcurrentDictionary[1 << 20]; - } - - public bool IsRegionModified(long position, long size, NvGpuBufferType bufferType) - { - long va = position; - - long pa = _memory.GetPhysicalAddress(va); - - long endAddr = (va + size + PageMask) & ~PageMask; - - long addrTruncated = va & ~PageMask; - - bool modified = _memory.IsRegionModified(addrTruncated, endAddr - addrTruncated); - - int newBuffMask = 1 << (int)bufferType; - - long cachedPagesCount = 0; - - while (va < endAddr) - { - long page = _memory.GetPhysicalAddress(va) >> PageBits; - - ConcurrentDictionary dictionary = _cachedPages[page]; - - if (dictionary == null) - { - dictionary = new ConcurrentDictionary(); - - _cachedPages[page] = dictionary; - } - else if (modified) - { - _cachedPages[page].Clear(); - } - - if (dictionary.TryGetValue(pa, out int currBuffMask)) - { - if ((currBuffMask & newBuffMask) != 0) - { - cachedPagesCount++; - } - else - { - dictionary[pa] |= newBuffMask; - } - } - else - { - dictionary[pa] = newBuffMask; - } - - va += PageSize; - } - - return cachedPagesCount != (endAddr - addrTruncated) >> PageBits; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/NvGpu.cs b/Ryujinx.Graphics/NvGpu.cs deleted file mode 100644 index baac0b2d2a..0000000000 --- a/Ryujinx.Graphics/NvGpu.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Ryujinx.Graphics.Gal; -using Ryujinx.Graphics.Graphics3d; -using Ryujinx.Graphics.Memory; -using Ryujinx.Graphics.VDec; -using Ryujinx.Graphics.Vic; - -namespace Ryujinx.Graphics -{ - public class NvGpu - { - public const int MaxViewportSize = 0x3FFF; - - public IGalRenderer Renderer { get; private set; } - - public GpuResourceManager ResourceManager { get; private set; } - - public DmaPusher Pusher { get; private set; } - - internal NvGpuFifo Fifo { get; private set; } - internal NvGpuEngine2d Engine2d { get; private set; } - internal NvGpuEngine3d Engine3d { get; private set; } - internal NvGpuEngineM2mf EngineM2mf { get; private set; } - internal NvGpuEngineP2mf EngineP2mf { get; private set; } - - private CdmaProcessor _cdmaProcessor; - internal VideoDecoder VideoDecoder { get; private set; } - internal VideoImageComposer VideoImageComposer { get; private set; } - - public NvGpu(IGalRenderer renderer) - { - Renderer = renderer; - - ResourceManager = new GpuResourceManager(this); - - Pusher = new DmaPusher(this); - - Fifo = new NvGpuFifo(this); - Engine2d = new NvGpuEngine2d(this); - Engine3d = new NvGpuEngine3d(this); - EngineM2mf = new NvGpuEngineM2mf(this); - EngineP2mf = new NvGpuEngineP2mf(this); - - _cdmaProcessor = new CdmaProcessor(this); - VideoDecoder = new VideoDecoder(this); - VideoImageComposer = new VideoImageComposer(this); - } - - public void PushCommandBuffer(NvGpuVmm vmm, int[] cmdBuffer) - { - lock (_cdmaProcessor) - { - _cdmaProcessor.PushCommands(vmm, cmdBuffer); - } - } - - public void UninitializeVideoDecoder() - { - lock (_cdmaProcessor) - { - FFmpegWrapper.Uninitialize(); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/QuadHelper.cs b/Ryujinx.Graphics/QuadHelper.cs deleted file mode 100644 index 643084ba49..0000000000 --- a/Ryujinx.Graphics/QuadHelper.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; - -namespace Ryujinx.Graphics -{ - static class QuadHelper - { - public static int ConvertSizeQuadsToTris(int size) - { - return size <= 0 ? 0 : (size / 4) * 6; - } - - public static int ConvertSizeQuadStripToTris(int size) - { - return size <= 1 ? 0 : ((size - 2) / 2) * 6; - } - - public static byte[] ConvertQuadsToTris(byte[] data, int entrySize, int count) - { - int primitivesCount = count / 4; - - int quadPrimSize = 4 * entrySize; - int trisPrimSize = 6 * entrySize; - - byte[] output = new byte[primitivesCount * 6 * entrySize]; - - for (int prim = 0; prim < primitivesCount; prim++) - { - void AssignIndex(int src, int dst, int copyCount = 1) - { - src = prim * quadPrimSize + src * entrySize; - dst = prim * trisPrimSize + dst * entrySize; - - Buffer.BlockCopy(data, src, output, dst, copyCount * entrySize); - } - - // 0 1 2 -> 0 1 2. - AssignIndex(0, 0, 3); - - // 2 3 -> 3 4. - AssignIndex(2, 3, 2); - - // 0 -> 5. - AssignIndex(0, 5); - } - - return output; - } - - public static byte[] ConvertQuadStripToTris(byte[] data, int entrySize, int count) - { - int primitivesCount = (count - 2) / 2; - - int quadPrimSize = 2 * entrySize; - int trisPrimSize = 6 * entrySize; - - byte[] output = new byte[primitivesCount * 6 * entrySize]; - - for (int prim = 0; prim < primitivesCount; prim++) - { - void AssignIndex(int src, int dst, int copyCount = 1) - { - src = prim * quadPrimSize + src * entrySize + 2 * entrySize; - dst = prim * trisPrimSize + dst * entrySize; - - Buffer.BlockCopy(data, src, output, dst, copyCount * entrySize); - } - - // -2 -1 0 -> 0 1 2. - AssignIndex(-2, 0, 3); - - // 0 1 -> 3 4. - AssignIndex(0, 3, 2); - - // -2 -> 5. - AssignIndex(-2, 5); - } - - return output; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Ryujinx.Graphics.csproj b/Ryujinx.Graphics/Ryujinx.Graphics.csproj deleted file mode 100644 index 4467fcdd90..0000000000 --- a/Ryujinx.Graphics/Ryujinx.Graphics.csproj +++ /dev/null @@ -1,40 +0,0 @@ - - - - netcoreapp3.0 - win-x64;osx-x64;linux-x64 - Debug;Release;Profile Debug;Profile Release - - - - true - - - - true - TRACE;USE_PROFILING - false - - - - true - - - - true - TRACE;USE_PROFILING - true - - - - - - - - - - - - - - diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/Declarations.cs b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Declarations.cs deleted file mode 100644 index 5412d87281..0000000000 --- a/Ryujinx.Graphics/Shader/CodeGen/Glsl/Declarations.cs +++ /dev/null @@ -1,206 +0,0 @@ -using Ryujinx.Graphics.Gal; -using Ryujinx.Graphics.Shader.IntermediateRepresentation; -using Ryujinx.Graphics.Shader.StructuredIr; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Ryujinx.Graphics.Shader.CodeGen.Glsl -{ - static class Declarations - { - public static void Declare(CodeGenContext context, StructuredProgramInfo info) - { - context.AppendLine("#version 420 core"); - - context.AppendLine(); - - context.AppendLine($"const int {DefaultNames.UndefinedName} = 0;"); - - context.AppendLine(); - - if (context.Config.Type == GalShaderType.Geometry) - { - context.AppendLine("layout (points) in;"); - context.AppendLine("layout (triangle_strip, max_vertices = 4) out;"); - - context.AppendLine(); - } - - context.AppendLine("layout (std140) uniform Extra"); - - context.EnterScope(); - - context.AppendLine("vec2 flip;"); - context.AppendLine("int instance;"); - - context.LeaveScope(";"); - - context.AppendLine(); - - if (info.CBuffers.Count != 0) - { - DeclareUniforms(context, info); - - context.AppendLine(); - } - - if (info.Samplers.Count != 0) - { - DeclareSamplers(context, info); - - context.AppendLine(); - } - - if (info.IAttributes.Count != 0) - { - DeclareInputAttributes(context, info); - - context.AppendLine(); - } - - if (info.OAttributes.Count != 0) - { - DeclareOutputAttributes(context, info); - - context.AppendLine(); - } - } - - public static void DeclareLocals(CodeGenContext context, StructuredProgramInfo info) - { - foreach (AstOperand decl in info.Locals) - { - string name = context.OperandManager.DeclareLocal(decl); - - context.AppendLine(GetVarTypeName(decl.VarType) + " " + name + ";"); - } - } - - private static string GetVarTypeName(VariableType type) - { - switch (type) - { - case VariableType.Bool: return "bool"; - case VariableType.F32: return "float"; - case VariableType.S32: return "int"; - case VariableType.U32: return "uint"; - } - - throw new ArgumentException($"Invalid variable type \"{type}\"."); - } - - private static void DeclareUniforms(CodeGenContext context, StructuredProgramInfo info) - { - foreach (int cbufSlot in info.CBuffers.OrderBy(x => x)) - { - string ubName = OperandManager.GetShaderStagePrefix(context.Config.Type); - - ubName += "_" + DefaultNames.UniformNamePrefix + cbufSlot; - - context.CBufferDescriptors.Add(new CBufferDescriptor(ubName, cbufSlot)); - - context.AppendLine("layout (std140) uniform " + ubName); - - context.EnterScope(); - - string ubSize = "[" + NumberFormatter.FormatInt(context.Config.MaxCBufferSize / 16) + "]"; - - context.AppendLine("vec4 " + OperandManager.GetUbName(context.Config.Type, cbufSlot) + ubSize + ";"); - - context.LeaveScope(";"); - } - } - - private static void DeclareSamplers(CodeGenContext context, StructuredProgramInfo info) - { - Dictionary samplers = new Dictionary(); - - foreach (AstTextureOperation texOp in info.Samplers.OrderBy(x => x.Handle)) - { - string samplerName = OperandManager.GetSamplerName(context.Config.Type, texOp); - - if (!samplers.TryAdd(samplerName, texOp)) - { - continue; - } - - string samplerTypeName = GetSamplerTypeName(texOp.Type); - - context.AppendLine("uniform " + samplerTypeName + " " + samplerName + ";"); - } - - foreach (KeyValuePair kv in samplers) - { - string samplerName = kv.Key; - - AstTextureOperation texOp = kv.Value; - - TextureDescriptor desc; - - if ((texOp.Flags & TextureFlags.Bindless) != 0) - { - AstOperand operand = texOp.GetSource(0) as AstOperand; - - desc = new TextureDescriptor(samplerName, operand.CbufSlot, operand.CbufOffset); - } - else - { - desc = new TextureDescriptor(samplerName, texOp.Handle); - } - - context.TextureDescriptors.Add(desc); - } - } - - private static void DeclareInputAttributes(CodeGenContext context, StructuredProgramInfo info) - { - string suffix = context.Config.Type == GalShaderType.Geometry ? "[]" : string.Empty; - - foreach (int attr in info.IAttributes.OrderBy(x => x)) - { - context.AppendLine($"layout (location = {attr}) in vec4 {DefaultNames.IAttributePrefix}{attr}{suffix};"); - } - } - - private static void DeclareOutputAttributes(CodeGenContext context, StructuredProgramInfo info) - { - foreach (int attr in info.OAttributes.OrderBy(x => x)) - { - context.AppendLine($"layout (location = {attr}) out vec4 {DefaultNames.OAttributePrefix}{attr};"); - } - } - - private static string GetSamplerTypeName(TextureType type) - { - string typeName; - - switch (type & TextureType.Mask) - { - case TextureType.Texture1D: typeName = "sampler1D"; break; - case TextureType.Texture2D: typeName = "sampler2D"; break; - case TextureType.Texture3D: typeName = "sampler3D"; break; - case TextureType.TextureCube: typeName = "samplerCube"; break; - - default: throw new ArgumentException($"Invalid sampler type \"{type}\"."); - } - - if ((type & TextureType.Multisample) != 0) - { - typeName += "MS"; - } - - if ((type & TextureType.Array) != 0) - { - typeName += "Array"; - } - - if ((type & TextureType.Shadow) != 0) - { - typeName += "Shadow"; - } - - return typeName; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/GlslProgram.cs b/Ryujinx.Graphics/Shader/CodeGen/Glsl/GlslProgram.cs deleted file mode 100644 index e616aa1f81..0000000000 --- a/Ryujinx.Graphics/Shader/CodeGen/Glsl/GlslProgram.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Ryujinx.Graphics.Shader.CodeGen.Glsl -{ - class GlslProgram - { - public CBufferDescriptor[] CBufferDescriptors { get; } - public TextureDescriptor[] TextureDescriptors { get; } - - public string Code { get; } - - public GlslProgram( - CBufferDescriptor[] cBufferDescs, - TextureDescriptor[] textureDescs, - string code) - { - CBufferDescriptors = cBufferDescs; - TextureDescriptors = textureDescs; - Code = code; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs b/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs deleted file mode 100644 index 8b5257fcbe..0000000000 --- a/Ryujinx.Graphics/Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs +++ /dev/null @@ -1,244 +0,0 @@ -using Ryujinx.Graphics.Shader.IntermediateRepresentation; -using Ryujinx.Graphics.Shader.StructuredIr; - -using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenHelper; -using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo; - -namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions -{ - static class InstGenMemory - { - public static string LoadConstant(CodeGenContext context, AstOperation operation) - { - IAstNode src1 = operation.GetSource(1); - - string offsetExpr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 1)); - - offsetExpr = Enclose(offsetExpr, src1, Instruction.ShiftRightS32, isLhs: true); - - return OperandManager.GetConstantBufferName(operation.GetSource(0), offsetExpr, context.Config.Type); - } - - public static string TextureSample(CodeGenContext context, AstOperation operation) - { - AstTextureOperation texOp = (AstTextureOperation)operation; - - bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; - bool isGather = (texOp.Flags & TextureFlags.Gather) != 0; - bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0; - bool hasLodBias = (texOp.Flags & TextureFlags.LodBias) != 0; - bool hasLodLevel = (texOp.Flags & TextureFlags.LodLevel) != 0; - bool hasOffset = (texOp.Flags & TextureFlags.Offset) != 0; - bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0; - bool isArray = (texOp.Type & TextureType.Array) != 0; - bool isMultisample = (texOp.Type & TextureType.Multisample) != 0; - bool isShadow = (texOp.Type & TextureType.Shadow) != 0; - - string texCall = intCoords ? "texelFetch" : "texture"; - - if (isGather) - { - texCall += "Gather"; - } - else if (hasLodLevel && !intCoords) - { - texCall += "Lod"; - } - - if (hasOffset) - { - texCall += "Offset"; - } - else if (hasOffsets) - { - texCall += "Offsets"; - } - - string samplerName = OperandManager.GetSamplerName(context.Config.Type, texOp); - - texCall += "(" + samplerName; - - int coordsCount = texOp.Type.GetCoordsCount(); - - int pCount = coordsCount; - - int arrayIndexElem = -1; - - if (isArray) - { - arrayIndexElem = pCount++; - } - - // The sampler 1D shadow overload expects a - // dummy value on the middle of the vector, who knows why... - bool hasDummy1DShadowElem = texOp.Type == (TextureType.Texture1D | TextureType.Shadow); - - if (hasDummy1DShadowElem) - { - pCount++; - } - - if (isShadow && !isGather) - { - pCount++; - } - - // On textureGather*, the comparison value is - // always specified as an extra argument. - bool hasExtraCompareArg = isShadow && isGather; - - if (pCount == 5) - { - pCount = 4; - - hasExtraCompareArg = true; - } - - int srcIndex = isBindless ? 1 : 0; - - string Src(VariableType type) - { - return GetSoureExpr(context, texOp.GetSource(srcIndex++), type); - } - - void Append(string str) - { - texCall += ", " + str; - } - - VariableType coordType = intCoords ? VariableType.S32 : VariableType.F32; - - string AssemblePVector(int count) - { - if (count > 1) - { - string[] elems = new string[count]; - - for (int index = 0; index < count; index++) - { - if (arrayIndexElem == index) - { - elems[index] = Src(VariableType.S32); - - if (!intCoords) - { - elems[index] = "float(" + elems[index] + ")"; - } - } - else if (index == 1 && hasDummy1DShadowElem) - { - elems[index] = NumberFormatter.FormatFloat(0); - } - else - { - elems[index] = Src(coordType); - } - } - - string prefix = intCoords ? "i" : string.Empty; - - return prefix + "vec" + count + "(" + string.Join(", ", elems) + ")"; - } - else - { - return Src(coordType); - } - } - - Append(AssemblePVector(pCount)); - - if (hasExtraCompareArg) - { - Append(Src(VariableType.F32)); - } - - if (isMultisample) - { - Append(Src(VariableType.S32)); - } - else if (hasLodLevel) - { - Append(Src(coordType)); - } - - string AssembleOffsetVector(int count) - { - if (count > 1) - { - string[] elems = new string[count]; - - for (int index = 0; index < count; index++) - { - elems[index] = Src(VariableType.S32); - } - - return "ivec" + count + "(" + string.Join(", ", elems) + ")"; - } - else - { - return Src(VariableType.S32); - } - } - - if (hasOffset) - { - Append(AssembleOffsetVector(coordsCount)); - } - else if (hasOffsets) - { - texCall += $", ivec{coordsCount}[4]("; - - texCall += AssembleOffsetVector(coordsCount) + ", "; - texCall += AssembleOffsetVector(coordsCount) + ", "; - texCall += AssembleOffsetVector(coordsCount) + ", "; - texCall += AssembleOffsetVector(coordsCount) + ")"; - } - - if (hasLodBias) - { - Append(Src(VariableType.F32)); - } - - // textureGather* optional extra component index, - // not needed for shadow samplers. - if (isGather && !isShadow) - { - Append(Src(VariableType.S32)); - } - - texCall += ")" + (isGather || !isShadow ? GetMask(texOp.ComponentMask) : ""); - - return texCall; - } - - public static string TextureSize(CodeGenContext context, AstOperation operation) - { - AstTextureOperation texOp = (AstTextureOperation)operation; - - bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; - - string samplerName = OperandManager.GetSamplerName(context.Config.Type, texOp); - - IAstNode src0 = operation.GetSource(isBindless ? 1 : 0); - - string src0Expr = GetSoureExpr(context, src0, GetSrcVarType(operation.Inst, 0)); - - return $"textureSize({samplerName}, {src0Expr}){GetMask(texOp.ComponentMask)}"; - } - - private static string GetMask(int compMask) - { - string mask = "."; - - for (int index = 0; index < 4; index++) - { - if ((compMask & (1 << index)) != 0) - { - mask += "rgba".Substring(index, 1); - } - } - - return mask; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/IntegerSize.cs b/Ryujinx.Graphics/Shader/Decoders/IntegerSize.cs deleted file mode 100644 index 70fdfc3dc9..0000000000 --- a/Ryujinx.Graphics/Shader/Decoders/IntegerSize.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Ryujinx.Graphics.Shader.Decoders -{ - enum IntegerSize - { - U8 = 0, - S8 = 1, - U16 = 2, - S16 = 3, - B32 = 4, - B64 = 5 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeSync.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeSync.cs deleted file mode 100644 index 081d08a0de..0000000000 --- a/Ryujinx.Graphics/Shader/Decoders/OpCodeSync.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Ryujinx.Graphics.Shader.Instructions; -using System.Collections.Generic; - -namespace Ryujinx.Graphics.Shader.Decoders -{ - class OpCodeSync : OpCode - { - public Dictionary Targets { get; } - - public OpCodeSync(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) - { - Targets = new Dictionary(); - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/OpCodeTable.cs b/Ryujinx.Graphics/Shader/Decoders/OpCodeTable.cs deleted file mode 100644 index d588ce8ee8..0000000000 --- a/Ryujinx.Graphics/Shader/Decoders/OpCodeTable.cs +++ /dev/null @@ -1,216 +0,0 @@ -using Ryujinx.Graphics.Shader.Instructions; -using System; - -namespace Ryujinx.Graphics.Shader.Decoders -{ - static class OpCodeTable - { - private const int EncodingBits = 14; - - private class TableEntry - { - public InstEmitter Emitter { get; } - - public Type OpCodeType { get; } - - public int XBits { get; } - - public TableEntry(InstEmitter emitter, Type opCodeType, int xBits) - { - Emitter = emitter; - OpCodeType = opCodeType; - XBits = xBits; - } - } - - private static TableEntry[] _opCodes; - - static OpCodeTable() - { - _opCodes = new TableEntry[1 << EncodingBits]; - -#region Instructions - Set("1110111111011x", InstEmit.Ald, typeof(OpCodeAttribute)); - Set("1110111111110x", InstEmit.Ast, typeof(OpCodeAttribute)); - Set("0100110000000x", InstEmit.Bfe, typeof(OpCodeAluCbuf)); - Set("0011100x00000x", InstEmit.Bfe, typeof(OpCodeAluImm)); - Set("0101110000000x", InstEmit.Bfe, typeof(OpCodeAluReg)); - Set("111000100100xx", InstEmit.Bra, typeof(OpCodeBranch)); - Set("111000110000xx", InstEmit.Exit, typeof(OpCodeExit)); - Set("0100110010101x", InstEmit.F2F, typeof(OpCodeFArithCbuf)); - Set("0011100x10101x", InstEmit.F2F, typeof(OpCodeFArithImm)); - Set("0101110010101x", InstEmit.F2F, typeof(OpCodeFArithReg)); - Set("0100110010110x", InstEmit.F2I, typeof(OpCodeFArithCbuf)); - Set("0011100x10110x", InstEmit.F2I, typeof(OpCodeFArithImm)); - Set("0101110010110x", InstEmit.F2I, typeof(OpCodeFArithReg)); - Set("0100110001011x", InstEmit.Fadd, typeof(OpCodeFArithCbuf)); - Set("0011100x01011x", InstEmit.Fadd, typeof(OpCodeFArithImm)); - Set("000010xxxxxxxx", InstEmit.Fadd, typeof(OpCodeFArithImm32)); - Set("0101110001011x", InstEmit.Fadd, typeof(OpCodeFArithReg)); - Set("010010011xxxxx", InstEmit.Ffma, typeof(OpCodeFArithCbuf)); - Set("0011001x1xxxxx", InstEmit.Ffma, typeof(OpCodeFArithImm)); - Set("010100011xxxxx", InstEmit.Ffma, typeof(OpCodeFArithRegCbuf)); - Set("010110011xxxxx", InstEmit.Ffma, typeof(OpCodeFArithReg)); - Set("0100110001100x", InstEmit.Fmnmx, typeof(OpCodeFArithCbuf)); - Set("0011100x01100x", InstEmit.Fmnmx, typeof(OpCodeFArithImm)); - Set("0101110001100x", InstEmit.Fmnmx, typeof(OpCodeFArithReg)); - Set("0100110001101x", InstEmit.Fmul, typeof(OpCodeFArithCbuf)); - Set("0011100x01101x", InstEmit.Fmul, typeof(OpCodeFArithImm)); - Set("00011110xxxxxx", InstEmit.Fmul, typeof(OpCodeFArithImm32)); - Set("0101110001101x", InstEmit.Fmul, typeof(OpCodeFArithReg)); - Set("0100100xxxxxxx", InstEmit.Fset, typeof(OpCodeSetCbuf)); - Set("0011000xxxxxxx", InstEmit.Fset, typeof(OpCodeFsetImm)); - Set("01011000xxxxxx", InstEmit.Fset, typeof(OpCodeSetReg)); - Set("010010111011xx", InstEmit.Fsetp, typeof(OpCodeSetCbuf)); - Set("0011011x1011xx", InstEmit.Fsetp, typeof(OpCodeFsetImm)); - Set("010110111011xx", InstEmit.Fsetp, typeof(OpCodeSetReg)); - Set("0111101x1xxxxx", InstEmit.Hadd2, typeof(OpCodeAluCbuf)); - Set("0111101x0xxxxx", InstEmit.Hadd2, typeof(OpCodeAluImm2x10)); - Set("0010110xxxxxxx", InstEmit.Hadd2, typeof(OpCodeAluImm32)); - Set("0101110100010x", InstEmit.Hadd2, typeof(OpCodeAluReg)); - Set("01110xxx1xxxxx", InstEmit.Hfma2, typeof(OpCodeHfmaCbuf)); - Set("01110xxx0xxxxx", InstEmit.Hfma2, typeof(OpCodeHfmaImm2x10)); - Set("0010100xxxxxxx", InstEmit.Hfma2, typeof(OpCodeHfmaImm32)); - Set("0101110100000x", InstEmit.Hfma2, typeof(OpCodeHfmaReg)); - Set("01100xxx1xxxxx", InstEmit.Hfma2, typeof(OpCodeHfmaRegCbuf)); - Set("0111100x1xxxxx", InstEmit.Hmul2, typeof(OpCodeAluCbuf)); - Set("0111100x0xxxxx", InstEmit.Hmul2, typeof(OpCodeAluImm2x10)); - Set("0010101xxxxxxx", InstEmit.Hmul2, typeof(OpCodeAluImm32)); - Set("0101110100001x", InstEmit.Hmul2, typeof(OpCodeAluReg)); - Set("0100110010111x", InstEmit.I2F, typeof(OpCodeAluCbuf)); - Set("0011100x10111x", InstEmit.I2F, typeof(OpCodeAluImm)); - Set("0101110010111x", InstEmit.I2F, typeof(OpCodeAluReg)); - Set("0100110011100x", InstEmit.I2I, typeof(OpCodeAluCbuf)); - Set("0011100x11100x", InstEmit.I2I, typeof(OpCodeAluImm)); - Set("0101110011100x", InstEmit.I2I, typeof(OpCodeAluReg)); - Set("0100110000010x", InstEmit.Iadd, typeof(OpCodeAluCbuf)); - Set("0011100000010x", InstEmit.Iadd, typeof(OpCodeAluImm)); - Set("0001110x0xxxxx", InstEmit.Iadd, typeof(OpCodeAluImm32)); - Set("0101110000010x", InstEmit.Iadd, typeof(OpCodeAluReg)); - Set("010011001100xx", InstEmit.Iadd3, typeof(OpCodeAluCbuf)); - Set("001110001100xx", InstEmit.Iadd3, typeof(OpCodeAluImm)); - Set("010111001100xx", InstEmit.Iadd3, typeof(OpCodeAluReg)); - Set("0100110000100x", InstEmit.Imnmx, typeof(OpCodeAluCbuf)); - Set("0011100x00100x", InstEmit.Imnmx, typeof(OpCodeAluImm)); - Set("0101110000100x", InstEmit.Imnmx, typeof(OpCodeAluReg)); - Set("11100000xxxxxx", InstEmit.Ipa, typeof(OpCodeIpa)); - Set("0100110000011x", InstEmit.Iscadd, typeof(OpCodeAluCbuf)); - Set("0011100x00011x", InstEmit.Iscadd, typeof(OpCodeAluImm)); - Set("000101xxxxxxxx", InstEmit.Iscadd, typeof(OpCodeAluImm32)); - Set("0101110000011x", InstEmit.Iscadd, typeof(OpCodeAluReg)); - Set("010010110101xx", InstEmit.Iset, typeof(OpCodeSetCbuf)); - Set("001101100101xx", InstEmit.Iset, typeof(OpCodeSetImm)); - Set("010110110101xx", InstEmit.Iset, typeof(OpCodeSetReg)); - Set("010010110110xx", InstEmit.Isetp, typeof(OpCodeSetCbuf)); - Set("0011011x0110xx", InstEmit.Isetp, typeof(OpCodeSetImm)); - Set("010110110110xx", InstEmit.Isetp, typeof(OpCodeSetReg)); - Set("111000110011xx", InstEmit.Kil, typeof(OpCodeExit)); - Set("1110111110010x", InstEmit.Ldc, typeof(OpCodeLdc)); - Set("0100110001000x", InstEmit.Lop, typeof(OpCodeLopCbuf)); - Set("0011100001000x", InstEmit.Lop, typeof(OpCodeLopImm)); - Set("000001xxxxxxxx", InstEmit.Lop, typeof(OpCodeLopImm32)); - Set("0101110001000x", InstEmit.Lop, typeof(OpCodeLopReg)); - Set("0010000xxxxxxx", InstEmit.Lop3, typeof(OpCodeLopCbuf)); - Set("001111xxxxxxxx", InstEmit.Lop3, typeof(OpCodeLopImm)); - Set("0101101111100x", InstEmit.Lop3, typeof(OpCodeLopReg)); - Set("0100110010011x", InstEmit.Mov, typeof(OpCodeAluCbuf)); - Set("0011100x10011x", InstEmit.Mov, typeof(OpCodeAluImm)); - Set("000000010000xx", InstEmit.Mov, typeof(OpCodeAluImm32)); - Set("0101110010011x", InstEmit.Mov, typeof(OpCodeAluReg)); - Set("0101000010000x", InstEmit.Mufu, typeof(OpCodeFArith)); - Set("1111101111100x", InstEmit.Out, typeof(OpCode)); - Set("0101000010010x", InstEmit.Psetp, typeof(OpCodePsetp)); - Set("0100110010010x", InstEmit.Rro, typeof(OpCodeFArithCbuf)); - Set("0011100x10010x", InstEmit.Rro, typeof(OpCodeFArithImm)); - Set("0101110010010x", InstEmit.Rro, typeof(OpCodeFArithReg)); - Set("0100110010100x", InstEmit.Sel, typeof(OpCodeAluCbuf)); - Set("0011100010100x", InstEmit.Sel, typeof(OpCodeAluImm)); - Set("0101110010100x", InstEmit.Sel, typeof(OpCodeAluReg)); - Set("0100110001001x", InstEmit.Shl, typeof(OpCodeAluCbuf)); - Set("0011100x01001x", InstEmit.Shl, typeof(OpCodeAluImm)); - Set("0101110001001x", InstEmit.Shl, typeof(OpCodeAluReg)); - Set("0100110000101x", InstEmit.Shr, typeof(OpCodeAluCbuf)); - Set("0011100x00101x", InstEmit.Shr, typeof(OpCodeAluImm)); - Set("0101110000101x", InstEmit.Shr, typeof(OpCodeAluReg)); - Set("111000101001xx", InstEmit.Ssy, typeof(OpCodeSsy)); - Set("1111000011111x", InstEmit.Sync, typeof(OpCodeSync)); - Set("110000xxxx111x", InstEmit.Tex, typeof(OpCodeTex)); - Set("1101111010111x", InstEmit.Tex_B, typeof(OpCodeTex)); - Set("1101x00xxxxxxx", InstEmit.Texs, typeof(OpCodeTexs)); - Set("1101x01xxxxxxx", InstEmit.Texs, typeof(OpCodeTlds)); - Set("1101x11100xxxx", InstEmit.Texs, typeof(OpCodeTld4s)); - Set("11011100xx111x", InstEmit.Tld, typeof(OpCodeTld)); - Set("11011101xx111x", InstEmit.Tld_B, typeof(OpCodeTld)); - Set("110010xxxx111x", InstEmit.Tld4, typeof(OpCodeTld4)); - Set("1101111101001x", InstEmit.Txq, typeof(OpCodeTex)); - Set("1101111101010x", InstEmit.Txq_B, typeof(OpCodeTex)); - Set("0100111xxxxxxx", InstEmit.Xmad, typeof(OpCodeAluCbuf)); - Set("0011011x00xxxx", InstEmit.Xmad, typeof(OpCodeAluImm)); - Set("010100010xxxxx", InstEmit.Xmad, typeof(OpCodeAluRegCbuf)); - Set("0101101100xxxx", InstEmit.Xmad, typeof(OpCodeAluReg)); -#endregion - } - - private static void Set(string encoding, InstEmitter emitter, Type opCodeType) - { - if (encoding.Length != EncodingBits) - { - throw new ArgumentException(nameof(encoding)); - } - - int bit = encoding.Length - 1; - int value = 0; - int xMask = 0; - int xBits = 0; - - int[] xPos = new int[encoding.Length]; - - for (int index = 0; index < encoding.Length; index++, bit--) - { - char chr = encoding[index]; - - if (chr == '1') - { - value |= 1 << bit; - } - else if (chr == 'x') - { - xMask |= 1 << bit; - - xPos[xBits++] = bit; - } - } - - xMask = ~xMask; - - TableEntry entry = new TableEntry(emitter, opCodeType, xBits); - - for (int index = 0; index < (1 << xBits); index++) - { - value &= xMask; - - for (int X = 0; X < xBits; X++) - { - value |= ((index >> X) & 1) << xPos[X]; - } - - if (_opCodes[value] == null || _opCodes[value].XBits > xBits) - { - _opCodes[value] = entry; - } - } - } - - public static (InstEmitter emitter, Type opCodeType) GetEmitter(long OpCode) - { - TableEntry entry = _opCodes[(ulong)OpCode >> (64 - EncodingBits)]; - - if (entry != null) - { - return (entry.Emitter, entry.OpCodeType); - } - - return (null, null); - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Decoders/TexelLoadScalarType.cs b/Ryujinx.Graphics/Shader/Decoders/TexelLoadScalarType.cs deleted file mode 100644 index cef5778a55..0000000000 --- a/Ryujinx.Graphics/Shader/Decoders/TexelLoadScalarType.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Ryujinx.Graphics.Shader.Decoders -{ - enum TexelLoadScalarType - { - Texture1DLodZero = 0x0, - Texture1DLodLevel = 0x1, - Texture2DLodZero = 0x2, - Texture2DLodZeroOffset = 0x4, - Texture2DLodLevel = 0x5, - Texture2DLodZeroMultisample = 0x6, - Texture3DLodZero = 0x7, - Texture2DArrayLodZero = 0x8, - Texture2DLodLevelOffset = 0xc - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Instructions/InstEmitFlow.cs b/Ryujinx.Graphics/Shader/Instructions/InstEmitFlow.cs deleted file mode 100644 index fb76e06a2b..0000000000 --- a/Ryujinx.Graphics/Shader/Instructions/InstEmitFlow.cs +++ /dev/null @@ -1,107 +0,0 @@ -using Ryujinx.Graphics.Shader.Decoders; -using Ryujinx.Graphics.Shader.IntermediateRepresentation; -using Ryujinx.Graphics.Shader.Translation; -using System.Collections.Generic; -using System.Linq; - -using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; - -namespace Ryujinx.Graphics.Shader.Instructions -{ - static partial class InstEmit - { - public static void Bra(EmitterContext context) - { - EmitBranch(context, context.CurrBlock.Branch.Address); - } - - public static void Exit(EmitterContext context) - { - OpCodeExit op = (OpCodeExit)context.CurrOp; - - // TODO: Figure out how this is supposed to work in the - // presence of other condition codes. - if (op.Condition == Condition.Always) - { - context.Return(); - } - } - - public static void Kil(EmitterContext context) - { - context.Discard(); - } - - public static void Ssy(EmitterContext context) - { - OpCodeSsy op = (OpCodeSsy)context.CurrOp; - - foreach (KeyValuePair kv in op.Syncs) - { - OpCodeSync opSync = kv.Key; - - Operand local = kv.Value; - - int ssyIndex = opSync.Targets[op]; - - context.Copy(local, Const(ssyIndex)); - } - } - - public static void Sync(EmitterContext context) - { - OpCodeSync op = (OpCodeSync)context.CurrOp; - - if (op.Targets.Count == 1) - { - // If we have only one target, then the SSY is basically - // a branch, we can produce better codegen for this case. - OpCodeSsy opSsy = op.Targets.Keys.First(); - - EmitBranch(context, opSsy.GetAbsoluteAddress()); - } - else - { - foreach (KeyValuePair kv in op.Targets) - { - OpCodeSsy opSsy = kv.Key; - - Operand label = context.GetLabel(opSsy.GetAbsoluteAddress()); - - Operand local = opSsy.Syncs[op]; - - int ssyIndex = kv.Value; - - context.BranchIfTrue(label, context.ICompareEqual(local, Const(ssyIndex))); - } - } - } - - private static void EmitBranch(EmitterContext context, ulong address) - { - // If we're branching to the next instruction, then the branch - // is useless and we can ignore it. - if (address == context.CurrOp.Address + 8) - { - return; - } - - Operand label = context.GetLabel(address); - - Operand pred = Register(context.CurrOp.Predicate); - - if (context.CurrOp.Predicate.IsPT) - { - context.Branch(label); - } - else if (context.CurrOp.InvertPredicate) - { - context.BranchIfFalse(label, pred); - } - else - { - context.BranchIfTrue(label, pred); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Instructions/InstEmitMemory.cs b/Ryujinx.Graphics/Shader/Instructions/InstEmitMemory.cs deleted file mode 100644 index a2a50fced9..0000000000 --- a/Ryujinx.Graphics/Shader/Instructions/InstEmitMemory.cs +++ /dev/null @@ -1,138 +0,0 @@ -using Ryujinx.Graphics.Shader.Decoders; -using Ryujinx.Graphics.Shader.IntermediateRepresentation; -using Ryujinx.Graphics.Shader.Translation; - -using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; -using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; - -namespace Ryujinx.Graphics.Shader.Instructions -{ - static partial class InstEmit - { - public static void Ald(EmitterContext context) - { - OpCodeAttribute op = (OpCodeAttribute)context.CurrOp; - - Operand[] elems = new Operand[op.Count]; - - for (int index = 0; index < op.Count; index++) - { - Operand src = Attribute(op.AttributeOffset + index * 4); - - context.Copy(elems[index] = Local(), src); - } - - for (int index = 0; index < op.Count; index++) - { - Register rd = new Register(op.Rd.Index + index, RegisterType.Gpr); - - if (rd.IsRZ) - { - break; - } - - context.Copy(Register(rd), elems[index]); - } - } - - public static void Ast(EmitterContext context) - { - OpCodeAttribute op = (OpCodeAttribute)context.CurrOp; - - for (int index = 0; index < op.Count; index++) - { - if (op.Rd.Index + index > RegisterConsts.RegisterZeroIndex) - { - break; - } - - Register rd = new Register(op.Rd.Index + index, RegisterType.Gpr); - - Operand dest = Attribute(op.AttributeOffset + index * 4); - - context.Copy(dest, Register(rd)); - } - } - - public static void Ipa(EmitterContext context) - { - OpCodeIpa op = (OpCodeIpa)context.CurrOp; - - Operand srcA = new Operand(OperandType.Attribute, op.AttributeOffset); - - Operand srcB = GetSrcB(context); - - context.Copy(GetDest(context), srcA); - } - - public static void Ldc(EmitterContext context) - { - OpCodeLdc op = (OpCodeLdc)context.CurrOp; - - if (op.Size > IntegerSize.B64) - { - // TODO: Warning. - } - - bool isSmallInt = op.Size < IntegerSize.B32; - - int count = op.Size == IntegerSize.B64 ? 2 : 1; - - Operand baseOffset = context.Copy(GetSrcA(context)); - - for (int index = 0; index < count; index++) - { - Register rd = new Register(op.Rd.Index + index, RegisterType.Gpr); - - if (rd.IsRZ) - { - break; - } - - Operand offset = context.IAdd(baseOffset, Const((op.Offset + index) * 4)); - - Operand value = context.LoadConstant(Const(op.Slot), offset); - - if (isSmallInt) - { - Operand shift = context.BitwiseAnd(baseOffset, Const(3)); - - value = context.ShiftRightU32(value, shift); - - switch (op.Size) - { - case IntegerSize.U8: value = ZeroExtendTo32(context, value, 8); break; - case IntegerSize.U16: value = ZeroExtendTo32(context, value, 16); break; - case IntegerSize.S8: value = SignExtendTo32(context, value, 8); break; - case IntegerSize.S16: value = SignExtendTo32(context, value, 16); break; - } - } - - context.Copy(Register(rd), value); - } - } - - public static void Out(EmitterContext context) - { - OpCode op = context.CurrOp; - - bool emit = op.RawOpCode.Extract(39); - bool cut = op.RawOpCode.Extract(40); - - if (!(emit || cut)) - { - // TODO: Warning. - } - - if (emit) - { - context.EmitVertex(); - } - - if (cut) - { - context.EndPrimitive(); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/Instructions/InstEmitMove.cs b/Ryujinx.Graphics/Shader/Instructions/InstEmitMove.cs deleted file mode 100644 index b74dbfd721..0000000000 --- a/Ryujinx.Graphics/Shader/Instructions/InstEmitMove.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Ryujinx.Graphics.Shader.Decoders; -using Ryujinx.Graphics.Shader.IntermediateRepresentation; -using Ryujinx.Graphics.Shader.Translation; - -using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; - -namespace Ryujinx.Graphics.Shader.Instructions -{ - static partial class InstEmit - { - public static void Mov(EmitterContext context) - { - OpCodeAlu op = (OpCodeAlu)context.CurrOp; - - context.Copy(GetDest(context), GetSrcB(context)); - } - - public static void Sel(EmitterContext context) - { - OpCodeAlu op = (OpCodeAlu)context.CurrOp; - - Operand pred = GetPredicate39(context); - - Operand srcA = GetSrcA(context); - Operand srcB = GetSrcB(context); - - Operand res = context.ConditionalSelect(pred, srcA, srcB); - - context.Copy(GetDest(context), res); - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/IntermediateRepresentation/TextureFlags.cs b/Ryujinx.Graphics/Shader/IntermediateRepresentation/TextureFlags.cs deleted file mode 100644 index 5f0a84276c..0000000000 --- a/Ryujinx.Graphics/Shader/IntermediateRepresentation/TextureFlags.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace Ryujinx.Graphics.Shader.IntermediateRepresentation -{ - [Flags] - enum TextureFlags - { - None = 0, - Bindless = 1 << 0, - Gather = 1 << 1, - IntCoords = 1 << 2, - LodBias = 1 << 3, - LodLevel = 1 << 4, - Offset = 1 << 5, - Offsets = 1 << 6 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/IntermediateRepresentation/TextureType.cs b/Ryujinx.Graphics/Shader/IntermediateRepresentation/TextureType.cs deleted file mode 100644 index bf20700776..0000000000 --- a/Ryujinx.Graphics/Shader/IntermediateRepresentation/TextureType.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; - -namespace Ryujinx.Graphics.Shader.IntermediateRepresentation -{ - [Flags] - enum TextureType - { - Texture1D, - Texture2D, - Texture3D, - TextureCube, - - Mask = 0xff, - - Array = 1 << 8, - Multisample = 1 << 9, - Shadow = 1 << 10 - } - - static class TextureTypeExtensions - { - public static int GetCoordsCount(this TextureType type) - { - switch (type & TextureType.Mask) - { - case TextureType.Texture1D: return 1; - case TextureType.Texture2D: return 2; - case TextureType.Texture3D: return 3; - case TextureType.TextureCube: return 3; - } - - throw new ArgumentException($"Invalid texture type \"{type}\"."); - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/ShaderConfig.cs b/Ryujinx.Graphics/Shader/ShaderConfig.cs deleted file mode 100644 index c2a94814e9..0000000000 --- a/Ryujinx.Graphics/Shader/ShaderConfig.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Ryujinx.Graphics.Gal; -using System; - -namespace Ryujinx.Graphics.Shader -{ - public struct ShaderConfig - { - public GalShaderType Type { get; } - - public int MaxCBufferSize; - - public ShaderConfig(GalShaderType type, int maxCBufferSize) - { - if (maxCBufferSize <= 0) - { - throw new ArgumentOutOfRangeException(nameof(maxCBufferSize)); - } - - Type = type; - MaxCBufferSize = maxCBufferSize; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/ShaderProgram.cs b/Ryujinx.Graphics/Shader/ShaderProgram.cs deleted file mode 100644 index 9257fd262d..0000000000 --- a/Ryujinx.Graphics/Shader/ShaderProgram.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Ryujinx.Graphics.Shader -{ - public class ShaderProgram - { - public ShaderProgramInfo Info { get; } - - public string Code { get; } - - internal ShaderProgram(ShaderProgramInfo info, string code) - { - Info = info; - Code = code; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/ShaderProgramInfo.cs b/Ryujinx.Graphics/Shader/ShaderProgramInfo.cs deleted file mode 100644 index c529a3536f..0000000000 --- a/Ryujinx.Graphics/Shader/ShaderProgramInfo.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.ObjectModel; - -namespace Ryujinx.Graphics.Shader -{ - public class ShaderProgramInfo - { - public ReadOnlyCollection CBuffers { get; } - public ReadOnlyCollection Textures { get; } - - internal ShaderProgramInfo(CBufferDescriptor[] cBuffers, TextureDescriptor[] textures) - { - CBuffers = Array.AsReadOnly(cBuffers); - Textures = Array.AsReadOnly(textures); - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Shader/StructuredIr/AstTextureOperation.cs b/Ryujinx.Graphics/Shader/StructuredIr/AstTextureOperation.cs deleted file mode 100644 index e40f7b70eb..0000000000 --- a/Ryujinx.Graphics/Shader/StructuredIr/AstTextureOperation.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Ryujinx.Graphics.Shader.IntermediateRepresentation; - -namespace Ryujinx.Graphics.Shader.StructuredIr -{ - class AstTextureOperation : AstOperation - { - public TextureType Type { get; } - public TextureFlags Flags { get; } - - public int Handle { get; } - - public AstTextureOperation( - Instruction inst, - TextureType type, - TextureFlags flags, - int handle, - int compMask, - params IAstNode[] sources) : base(inst, compMask, sources) - { - Type = type; - Flags = flags; - Handle = handle; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/VDec/VideoDecoder.cs b/Ryujinx.Graphics/VDec/VideoDecoder.cs deleted file mode 100644 index 9bf60c31b0..0000000000 --- a/Ryujinx.Graphics/VDec/VideoDecoder.cs +++ /dev/null @@ -1,281 +0,0 @@ -using ARMeilleure.Memory; -using Ryujinx.Graphics.Gal; -using Ryujinx.Graphics.Memory; -using Ryujinx.Graphics.Texture; -using Ryujinx.Graphics.Vic; -using System; - -namespace Ryujinx.Graphics.VDec -{ - unsafe class VideoDecoder - { - private NvGpu _gpu; - - private H264Decoder _h264Decoder; - private Vp9Decoder _vp9Decoder; - - private VideoCodec _currentVideoCodec; - - private long _decoderContextAddress; - private long _frameDataAddress; - private long _vpxCurrLumaAddress; - private long _vpxRef0LumaAddress; - private long _vpxRef1LumaAddress; - private long _vpxRef2LumaAddress; - private long _vpxCurrChromaAddress; - private long _vpxRef0ChromaAddress; - private long _vpxRef1ChromaAddress; - private long _vpxRef2ChromaAddress; - private long _vpxProbTablesAddress; - - public VideoDecoder(NvGpu gpu) - { - _gpu = gpu; - - _h264Decoder = new H264Decoder(); - _vp9Decoder = new Vp9Decoder(); - } - - public void Process(NvGpuVmm vmm, int methodOffset, int[] arguments) - { - VideoDecoderMeth method = (VideoDecoderMeth)methodOffset; - - switch (method) - { - case VideoDecoderMeth.SetVideoCodec: SetVideoCodec (vmm, arguments); break; - case VideoDecoderMeth.Execute: Execute (vmm, arguments); break; - case VideoDecoderMeth.SetDecoderCtxAddr: SetDecoderCtxAddr (vmm, arguments); break; - case VideoDecoderMeth.SetFrameDataAddr: SetFrameDataAddr (vmm, arguments); break; - case VideoDecoderMeth.SetVpxCurrLumaAddr: SetVpxCurrLumaAddr (vmm, arguments); break; - case VideoDecoderMeth.SetVpxRef0LumaAddr: SetVpxRef0LumaAddr (vmm, arguments); break; - case VideoDecoderMeth.SetVpxRef1LumaAddr: SetVpxRef1LumaAddr (vmm, arguments); break; - case VideoDecoderMeth.SetVpxRef2LumaAddr: SetVpxRef2LumaAddr (vmm, arguments); break; - case VideoDecoderMeth.SetVpxCurrChromaAddr: SetVpxCurrChromaAddr(vmm, arguments); break; - case VideoDecoderMeth.SetVpxRef0ChromaAddr: SetVpxRef0ChromaAddr(vmm, arguments); break; - case VideoDecoderMeth.SetVpxRef1ChromaAddr: SetVpxRef1ChromaAddr(vmm, arguments); break; - case VideoDecoderMeth.SetVpxRef2ChromaAddr: SetVpxRef2ChromaAddr(vmm, arguments); break; - case VideoDecoderMeth.SetVpxProbTablesAddr: SetVpxProbTablesAddr(vmm, arguments); break; - } - } - - private void SetVideoCodec(NvGpuVmm vmm, int[] arguments) - { - _currentVideoCodec = (VideoCodec)arguments[0]; - } - - private void Execute(NvGpuVmm vmm, int[] arguments) - { - if (_currentVideoCodec == VideoCodec.H264) - { - int frameDataSize = vmm.ReadInt32(_decoderContextAddress + 0x48); - - H264ParameterSets Params = MemoryHelper.Read(vmm.Memory, vmm.GetPhysicalAddress(_decoderContextAddress + 0x58)); - - H264Matrices matrices = new H264Matrices() - { - ScalingMatrix4 = vmm.ReadBytes(_decoderContextAddress + 0x1c0, 6 * 16), - ScalingMatrix8 = vmm.ReadBytes(_decoderContextAddress + 0x220, 2 * 64) - }; - - byte[] frameData = vmm.ReadBytes(_frameDataAddress, frameDataSize); - - _h264Decoder.Decode(Params, matrices, frameData); - } - else if (_currentVideoCodec == VideoCodec.Vp9) - { - int frameDataSize = vmm.ReadInt32(_decoderContextAddress + 0x30); - - Vp9FrameKeys keys = new Vp9FrameKeys() - { - CurrKey = vmm.GetPhysicalAddress(_vpxCurrLumaAddress), - Ref0Key = vmm.GetPhysicalAddress(_vpxRef0LumaAddress), - Ref1Key = vmm.GetPhysicalAddress(_vpxRef1LumaAddress), - Ref2Key = vmm.GetPhysicalAddress(_vpxRef2LumaAddress) - }; - - Vp9FrameHeader header = MemoryHelper.Read(vmm.Memory, vmm.GetPhysicalAddress(_decoderContextAddress + 0x48)); - - Vp9ProbabilityTables probs = new Vp9ProbabilityTables() - { - SegmentationTreeProbs = vmm.ReadBytes(_vpxProbTablesAddress + 0x387, 0x7), - SegmentationPredProbs = vmm.ReadBytes(_vpxProbTablesAddress + 0x38e, 0x3), - Tx8x8Probs = vmm.ReadBytes(_vpxProbTablesAddress + 0x470, 0x2), - Tx16x16Probs = vmm.ReadBytes(_vpxProbTablesAddress + 0x472, 0x4), - Tx32x32Probs = vmm.ReadBytes(_vpxProbTablesAddress + 0x476, 0x6), - CoefProbs = vmm.ReadBytes(_vpxProbTablesAddress + 0x5a0, 0x900), - SkipProbs = vmm.ReadBytes(_vpxProbTablesAddress + 0x537, 0x3), - InterModeProbs = vmm.ReadBytes(_vpxProbTablesAddress + 0x400, 0x1c), - InterpFilterProbs = vmm.ReadBytes(_vpxProbTablesAddress + 0x52a, 0x8), - IsInterProbs = vmm.ReadBytes(_vpxProbTablesAddress + 0x41c, 0x4), - CompModeProbs = vmm.ReadBytes(_vpxProbTablesAddress + 0x532, 0x5), - SingleRefProbs = vmm.ReadBytes(_vpxProbTablesAddress + 0x580, 0xa), - CompRefProbs = vmm.ReadBytes(_vpxProbTablesAddress + 0x58a, 0x5), - YModeProbs0 = vmm.ReadBytes(_vpxProbTablesAddress + 0x480, 0x20), - YModeProbs1 = vmm.ReadBytes(_vpxProbTablesAddress + 0x47c, 0x4), - PartitionProbs = vmm.ReadBytes(_vpxProbTablesAddress + 0x4e0, 0x40), - MvJointProbs = vmm.ReadBytes(_vpxProbTablesAddress + 0x53b, 0x3), - MvSignProbs = vmm.ReadBytes(_vpxProbTablesAddress + 0x53e, 0x3), - MvClassProbs = vmm.ReadBytes(_vpxProbTablesAddress + 0x54c, 0x14), - MvClass0BitProbs = vmm.ReadBytes(_vpxProbTablesAddress + 0x540, 0x3), - MvBitsProbs = vmm.ReadBytes(_vpxProbTablesAddress + 0x56c, 0x14), - MvClass0FrProbs = vmm.ReadBytes(_vpxProbTablesAddress + 0x560, 0xc), - MvFrProbs = vmm.ReadBytes(_vpxProbTablesAddress + 0x542, 0x6), - MvClass0HpProbs = vmm.ReadBytes(_vpxProbTablesAddress + 0x548, 0x2), - MvHpProbs = vmm.ReadBytes(_vpxProbTablesAddress + 0x54a, 0x2) - }; - - byte[] frameData = vmm.ReadBytes(_frameDataAddress, frameDataSize); - - _vp9Decoder.Decode(keys, header, probs, frameData); - } - else - { - ThrowUnimplementedCodec(); - } - } - - private void SetDecoderCtxAddr(NvGpuVmm vmm, int[] arguments) - { - _decoderContextAddress = GetAddress(arguments); - } - - private void SetFrameDataAddr(NvGpuVmm vmm, int[] arguments) - { - _frameDataAddress = GetAddress(arguments); - } - - private void SetVpxCurrLumaAddr(NvGpuVmm vmm, int[] arguments) - { - _vpxCurrLumaAddress = GetAddress(arguments); - } - - private void SetVpxRef0LumaAddr(NvGpuVmm vmm, int[] arguments) - { - _vpxRef0LumaAddress = GetAddress(arguments); - } - - private void SetVpxRef1LumaAddr(NvGpuVmm vmm, int[] arguments) - { - _vpxRef1LumaAddress = GetAddress(arguments); - } - - private void SetVpxRef2LumaAddr(NvGpuVmm vmm, int[] arguments) - { - _vpxRef2LumaAddress = GetAddress(arguments); - } - - private void SetVpxCurrChromaAddr(NvGpuVmm vmm, int[] arguments) - { - _vpxCurrChromaAddress = GetAddress(arguments); - } - - private void SetVpxRef0ChromaAddr(NvGpuVmm vmm, int[] arguments) - { - _vpxRef0ChromaAddress = GetAddress(arguments); - } - - private void SetVpxRef1ChromaAddr(NvGpuVmm vmm, int[] arguments) - { - _vpxRef1ChromaAddress = GetAddress(arguments); - } - - private void SetVpxRef2ChromaAddr(NvGpuVmm vmm, int[] arguments) - { - _vpxRef2ChromaAddress = GetAddress(arguments); - } - - private void SetVpxProbTablesAddr(NvGpuVmm vmm, int[] arguments) - { - _vpxProbTablesAddress = GetAddress(arguments); - } - - private static long GetAddress(int[] arguments) - { - return (long)(uint)arguments[0] << 8; - } - - internal void CopyPlanes(NvGpuVmm vmm, SurfaceOutputConfig outputConfig) - { - switch (outputConfig.PixelFormat) - { - case SurfacePixelFormat.Rgba8: CopyPlanesRgba8 (vmm, outputConfig); break; - case SurfacePixelFormat.Yuv420P: CopyPlanesYuv420P(vmm, outputConfig); break; - - default: ThrowUnimplementedPixelFormat(outputConfig.PixelFormat); break; - } - } - - private void CopyPlanesRgba8(NvGpuVmm vmm, SurfaceOutputConfig outputConfig) - { - FFmpegFrame frame = FFmpegWrapper.GetFrameRgba(); - - if ((frame.Width | frame.Height) == 0) - { - return; - } - - GalImage image = new GalImage( - outputConfig.SurfaceWidth, - outputConfig.SurfaceHeight, 1, 1, 1, - outputConfig.GobBlockHeight, 1, - GalMemoryLayout.BlockLinear, - GalImageFormat.Rgba8 | GalImageFormat.Unorm, - GalTextureTarget.TwoD); - - ImageUtils.WriteTexture(vmm, image, vmm.GetPhysicalAddress(outputConfig.SurfaceLumaAddress), frame.Data); - } - - private void CopyPlanesYuv420P(NvGpuVmm vmm, SurfaceOutputConfig outputConfig) - { - FFmpegFrame frame = FFmpegWrapper.GetFrame(); - - if ((frame.Width | frame.Height) == 0) - { - return; - } - - int halfSrcWidth = frame.Width / 2; - - int halfWidth = frame.Width / 2; - int halfHeight = frame.Height / 2; - - int alignedWidth = (outputConfig.SurfaceWidth + 0xff) & ~0xff; - - for (int y = 0; y < frame.Height; y++) - { - int src = y * frame.Width; - int dst = y * alignedWidth; - - int size = frame.Width; - - for (int offset = 0; offset < size; offset++) - { - vmm.WriteByte(outputConfig.SurfaceLumaAddress + dst + offset, *(frame.LumaPtr + src + offset)); - } - } - - // Copy chroma data from both channels with interleaving. - for (int y = 0; y < halfHeight; y++) - { - int src = y * halfSrcWidth; - int dst = y * alignedWidth; - - for (int x = 0; x < halfWidth; x++) - { - vmm.WriteByte(outputConfig.SurfaceChromaUAddress + dst + x * 2 + 0, *(frame.ChromaBPtr + src + x)); - vmm.WriteByte(outputConfig.SurfaceChromaUAddress + dst + x * 2 + 1, *(frame.ChromaRPtr + src + x)); - } - } - } - - private void ThrowUnimplementedCodec() - { - throw new NotImplementedException("Codec \"" + _currentVideoCodec + "\" is not supported!"); - } - - private void ThrowUnimplementedPixelFormat(SurfacePixelFormat pixelFormat) - { - throw new NotImplementedException("Pixel format \"" + pixelFormat + "\" is not supported!"); - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs b/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs index c74f6fca1b..f987c83c01 100644 --- a/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs +++ b/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs @@ -1115,6 +1115,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Process Translator = new Translator(CpuMemory); + // TODO: This should eventually be removed. + // The GPU shouldn't depend on the CPU memory manager at all. + _system.Device.Gpu.SetVmm(CpuMemory); + MemoryManager = new KMemoryManager(_system, CpuMemory); } diff --git a/Ryujinx.HLE/HOS/Services/Mm/IRequest.cs b/Ryujinx.HLE/HOS/Services/Mm/IRequest.cs index 5a3132e251..cdd4729550 100644 --- a/Ryujinx.HLE/HOS/Services/Mm/IRequest.cs +++ b/Ryujinx.HLE/HOS/Services/Mm/IRequest.cs @@ -24,8 +24,6 @@ namespace Ryujinx.HLE.HOS.Services.Mm // FinalizeOld(u32) public ResultCode FinalizeOld(ServiceCtx context) { - context.Device.Gpu.UninitializeVideoDecoder(); - Logger.PrintStub(LogClass.ServiceMm); return ResultCode.Success; @@ -69,8 +67,6 @@ namespace Ryujinx.HLE.HOS.Services.Mm // Finalize(u32) public ResultCode Finalize(ServiceCtx context) { - context.Device.Gpu.UninitializeVideoDecoder(); - Logger.PrintStub(LogClass.ServiceMm); return ResultCode.Success; diff --git a/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs b/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs index 2b9f09fa2f..a227d8924c 100644 --- a/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs @@ -102,7 +102,9 @@ namespace Ryujinx.HLE.HOS.Services.Nv byte[] outputData = new byte[outputDataSize]; - context.Memory.ReadBytes(inputDataPosition, outputData, 0, (int)inputDataSize); + byte[] temp = context.Memory.ReadBytes(inputDataPosition, inputDataSize); + + Buffer.BlockCopy(temp, 0, outputData, 0, temp.Length); arguments = new Span(outputData); } diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvDeviceFile.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvDeviceFile.cs index 73007c911a..b6e6b8f8fa 100644 --- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvDeviceFile.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvDeviceFile.cs @@ -9,11 +9,13 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices { abstract class NvDeviceFile { - public readonly KProcess Owner; + public readonly ServiceCtx Context; + public readonly KProcess Owner; public NvDeviceFile(ServiceCtx context) { - Owner = context.Process; + Context = context; + Owner = context.Process; } public virtual NvInternalResult QueryEvent(out int eventHandle, uint eventId) diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs index b48377a47e..008c6059ba 100644 --- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs @@ -1,5 +1,5 @@ using Ryujinx.Common.Logging; -using Ryujinx.Graphics.Memory; +using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types; using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap; @@ -79,7 +79,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu private NvInternalResult AllocSpace(ref AllocSpaceArguments arguments) { - AddressSpaceContext addressSpaceContext = GetAddressSpaceContext(Owner); + AddressSpaceContext addressSpaceContext = GetAddressSpaceContext(Context); ulong size = (ulong)arguments.Pages * (ulong)arguments.PageSize; @@ -91,11 +91,11 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu // the Offset field holds the alignment size instead. if ((arguments.Flags & AddressSpaceFlags.FixedOffset) != 0) { - arguments.Offset = addressSpaceContext.Vmm.ReserveFixed(arguments.Offset, (long)size); + arguments.Offset = (long)addressSpaceContext.Gmm.ReserveFixed((ulong)arguments.Offset, size); } else { - arguments.Offset = addressSpaceContext.Vmm.Reserve((long)size, arguments.Offset); + arguments.Offset = (long)addressSpaceContext.Gmm.Reserve((ulong)size, (ulong)arguments.Offset); } if (arguments.Offset < 0) @@ -117,7 +117,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu private NvInternalResult FreeSpace(ref FreeSpaceArguments arguments) { - AddressSpaceContext addressSpaceContext = GetAddressSpaceContext(Owner); + AddressSpaceContext addressSpaceContext = GetAddressSpaceContext(Context); NvInternalResult result = NvInternalResult.Success; @@ -127,7 +127,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu if (addressSpaceContext.RemoveReservation(arguments.Offset)) { - addressSpaceContext.Vmm.Free(arguments.Offset, (long)size); + addressSpaceContext.Gmm.Free((ulong)arguments.Offset, size); } else { @@ -143,7 +143,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu private NvInternalResult UnmapBuffer(ref UnmapBufferArguments arguments) { - AddressSpaceContext addressSpaceContext = GetAddressSpaceContext(Owner); + AddressSpaceContext addressSpaceContext = GetAddressSpaceContext(Context); lock (addressSpaceContext) { @@ -151,7 +151,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu { if (size != 0) { - addressSpaceContext.Vmm.Free(arguments.Offset, size); + addressSpaceContext.Gmm.Free((ulong)arguments.Offset, (ulong)size); } } else @@ -167,7 +167,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu { const string mapErrorMsg = "Failed to map fixed buffer with offset 0x{0:x16} and size 0x{1:x16}!"; - AddressSpaceContext addressSpaceContext = GetAddressSpaceContext(Owner); + AddressSpaceContext addressSpaceContext = GetAddressSpaceContext(Context); NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, arguments.NvMapHandle, true); @@ -190,7 +190,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu physicalAddress += arguments.BufferOffset; - if (addressSpaceContext.Vmm.Map(physicalAddress, virtualAddress, arguments.MappingSize) < 0) + if ((long)addressSpaceContext.Gmm.Map((ulong)physicalAddress, (ulong)virtualAddress, (ulong)arguments.MappingSize) < 0) { string message = string.Format(mapErrorMsg, virtualAddress, arguments.MappingSize); @@ -231,7 +231,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu { if (addressSpaceContext.ValidateFixedBuffer(arguments.Offset, size)) { - arguments.Offset = addressSpaceContext.Vmm.Map(physicalAddress, arguments.Offset, size); + arguments.Offset = (long)addressSpaceContext.Gmm.Map((ulong)physicalAddress, (ulong)arguments.Offset, (ulong)size); } else { @@ -244,7 +244,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu } else { - arguments.Offset = addressSpaceContext.Vmm.Map(physicalAddress, size); + arguments.Offset = (long)addressSpaceContext.Gmm.Map((ulong)physicalAddress, (ulong)size); } if (arguments.Offset < 0) @@ -282,7 +282,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu { for (int index = 0; index < arguments.Length; index++) { - NvGpuVmm vmm = GetAddressSpaceContext(Owner).Vmm; + MemoryManager gmm = GetAddressSpaceContext(Context).Gmm; NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, arguments[index].NvMapHandle, true); @@ -293,10 +293,10 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu return NvInternalResult.InvalidInput; } - long result = vmm.Map( - ((long)arguments[index].MapOffset << 16) + map.Address, - (long)arguments[index].GpuOffset << 16, - (long)arguments[index].Pages << 16); + long result = (long)gmm.Map( + ((ulong)arguments[index].MapOffset << 16) + (ulong)map.Address, + (ulong)arguments[index].GpuOffset << 16, + (ulong)arguments[index].Pages << 16); if (result < 0) { @@ -312,9 +312,9 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu public override void Close() { } - public static AddressSpaceContext GetAddressSpaceContext(KProcess process) + public static AddressSpaceContext GetAddressSpaceContext(ServiceCtx context) { - return _addressSpaceContextRegistry.GetOrAdd(process, (key) => new AddressSpaceContext(process)); + return _addressSpaceContextRegistry.GetOrAdd(context.Process, (key) => new AddressSpaceContext(context)); } } } diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceContext.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceContext.cs index 5c28300007..951994efcb 100644 --- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceContext.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceContext.cs @@ -1,16 +1,12 @@ -using ARMeilleure.Memory; -using Ryujinx.Graphics.Memory; +using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.HLE.HOS.Kernel.Process; using System; using System.Collections.Generic; -using System.Text; namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types { class AddressSpaceContext { - public NvGpuVmm Vmm { get; private set; } - private class Range { public ulong Start { get; private set; } @@ -42,9 +38,11 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types private SortedList _maps; private SortedList _reservations; - public AddressSpaceContext(KProcess process) + public MemoryManager Gmm { get; } + + public AddressSpaceContext(ServiceCtx context) { - Vmm = new NvGpuVmm(process.CpuMemory); + Gmm = context.Device.Gpu.MemoryManager; _maps = new SortedList(); _reservations = new SortedList(); @@ -61,7 +59,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types } // Check if address is page aligned. - if ((position & NvGpuVmm.PageMask) != 0) + if ((position & (long)MemoryManager.PageMask) != 0) { return false; } diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostChannelDeviceFile.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostChannelDeviceFile.cs index 80a7ce8004..212d69e09c 100644 --- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostChannelDeviceFile.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostChannelDeviceFile.cs @@ -1,7 +1,6 @@ -using ARMeilleure.Memory; -using Ryujinx.Common.Logging; -using Ryujinx.Graphics; -using Ryujinx.Graphics.Memory; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Gpu; +using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu; using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types; using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap; @@ -13,11 +12,13 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel { class NvHostChannelDeviceFile : NvDeviceFile { - private uint _timeout; - private uint _submitTimeout; - private uint _timeslice; - private NvGpu _gpu; - private MemoryManager _memory; + private uint _timeout; + private uint _submitTimeout; + private uint _timeslice; + + private GpuContext _gpu; + + private ARMeilleure.Memory.MemoryManager _memory; public NvHostChannelDeviceFile(ServiceCtx context) : base(context) { @@ -110,7 +111,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel int headerSize = Unsafe.SizeOf(); SubmitArguments submitHeader = MemoryMarshal.Cast(arguments)[0]; Span commandBufferEntries = MemoryMarshal.Cast(arguments.Slice(headerSize)).Slice(0, submitHeader.CmdBufsCount); - NvGpuVmm vmm = NvHostAsGpuDeviceFile.GetAddressSpaceContext(Owner).Vmm; + MemoryManager gmm = NvHostAsGpuDeviceFile.GetAddressSpaceContext(Context).Gmm; foreach (CommandBuffer commandBufferEntry in commandBufferEntries) { @@ -123,7 +124,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel commandBufferData[offset] = _memory.ReadInt32(map.Address + commandBufferEntry.Offset + offset * 4); } - _gpu.PushCommandBuffer(vmm, commandBufferData); + // TODO: Submit command to engines. } return NvInternalResult.Success; @@ -161,7 +162,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel int headerSize = Unsafe.SizeOf(); MapCommandBufferArguments commandBufferHeader = MemoryMarshal.Cast(arguments)[0]; Span commandBufferEntries = MemoryMarshal.Cast(arguments.Slice(headerSize)).Slice(0, commandBufferHeader.NumEntries); - NvGpuVmm vmm = NvHostAsGpuDeviceFile.GetAddressSpaceContext(Owner).Vmm; + MemoryManager gmm = NvHostAsGpuDeviceFile.GetAddressSpaceContext(Context).Gmm; foreach (ref CommandBufferHandle commandBufferEntry in commandBufferEntries) { @@ -178,7 +179,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel { if (map.DmaMapAddress == 0) { - map.DmaMapAddress = vmm.MapLow(map.Address, map.Size); + map.DmaMapAddress = (long)gmm.MapLow((ulong)map.Address, (uint)map.Size); } commandBufferEntry.MapAddress = (int)map.DmaMapAddress; @@ -193,7 +194,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel int headerSize = Unsafe.SizeOf(); MapCommandBufferArguments commandBufferHeader = MemoryMarshal.Cast(arguments)[0]; Span commandBufferEntries = MemoryMarshal.Cast(arguments.Slice(headerSize)).Slice(0, commandBufferHeader.NumEntries); - NvGpuVmm vmm = NvHostAsGpuDeviceFile.GetAddressSpaceContext(Owner).Vmm; + MemoryManager gmm = NvHostAsGpuDeviceFile.GetAddressSpaceContext(Context).Gmm; foreach (ref CommandBufferHandle commandBufferEntry in commandBufferEntries) { @@ -210,7 +211,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel { if (map.DmaMapAddress != 0) { - vmm.Free(map.DmaMapAddress, map.Size); + gmm.Free((ulong)map.DmaMapAddress, (uint)map.Size); map.DmaMapAddress = 0; } @@ -240,7 +241,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel { int headerSize = Unsafe.SizeOf(); SubmitGpfifoArguments gpfifoSubmissionHeader = MemoryMarshal.Cast(arguments)[0]; - Span gpfifoEntries = MemoryMarshal.Cast(arguments.Slice(headerSize)).Slice(0, gpfifoSubmissionHeader.NumEntries); + Span gpfifoEntries = MemoryMarshal.Cast(arguments.Slice(headerSize)).Slice(0, gpfifoSubmissionHeader.NumEntries); return SubmitGpfifo(ref gpfifoSubmissionHeader, gpfifoEntries); } @@ -327,13 +328,11 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel return NvInternalResult.Success; } - protected NvInternalResult SubmitGpfifo(ref SubmitGpfifoArguments header, Span entries) + protected NvInternalResult SubmitGpfifo(ref SubmitGpfifoArguments header, Span entries) { - NvGpuVmm vmm = NvHostAsGpuDeviceFile.GetAddressSpaceContext(Owner).Vmm; - - foreach (long entry in entries) + foreach (ulong entry in entries) { - _gpu.Pusher.Push(vmm, entry); + _gpu.DmaPusher.Push(entry); } header.Fence.Id = 0; diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostGpuDeviceFile.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostGpuDeviceFile.cs index 582ba50e98..7eaa5cc587 100644 --- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostGpuDeviceFile.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostGpuDeviceFile.cs @@ -27,7 +27,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel switch (command.Number) { case 0x1b: - result = CallIoctlMethod(SubmitGpfifoEx, arguments, inlineInBuffer); + result = CallIoctlMethod(SubmitGpfifoEx, arguments, inlineInBuffer); break; } } @@ -70,7 +70,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel return NvInternalResult.Success; } - private NvInternalResult SubmitGpfifoEx(ref SubmitGpfifoArguments arguments, Span inlineData) + private NvInternalResult SubmitGpfifoEx(ref SubmitGpfifoArguments arguments, Span inlineData) { return SubmitGpfifo(ref arguments, inlineData); } diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/NvMapDeviceFile.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/NvMapDeviceFile.cs index 2ca847a319..d03ede9445 100644 --- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/NvMapDeviceFile.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/NvMapDeviceFile.cs @@ -1,6 +1,6 @@ using Ryujinx.Common; using Ryujinx.Common.Logging; -using Ryujinx.Graphics.Memory; +using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.HLE.HOS.Kernel.Process; using System; using System.Collections.Concurrent; @@ -73,7 +73,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap return NvInternalResult.InvalidInput; } - int size = BitUtils.AlignUp(arguments.Size, NvGpuVmm.PageSize); + int size = BitUtils.AlignUp(arguments.Size, (int)MemoryManager.PageSize); arguments.Handle = CreateHandleFromMap(new NvMapHandle(size)); @@ -118,9 +118,9 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap return NvInternalResult.InvalidInput; } - if ((uint)arguments.Align < NvGpuVmm.PageSize) + if ((uint)arguments.Align < MemoryManager.PageSize) { - arguments.Align = NvGpuVmm.PageSize; + arguments.Align = (int)MemoryManager.PageSize; } NvInternalResult result = NvInternalResult.Success; @@ -132,7 +132,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap map.Align = arguments.Align; map.Kind = (byte)arguments.Kind; - int size = BitUtils.AlignUp(map.Size, NvGpuVmm.PageSize); + int size = BitUtils.AlignUp(map.Size, (int)MemoryManager.PageSize); long address = arguments.Address; diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NvFlinger.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NvFlinger.cs index e3e4a1a453..e70666ed9d 100644 --- a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NvFlinger.cs +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NvFlinger.cs @@ -1,8 +1,7 @@ using Ryujinx.Common.Logging; -using Ryujinx.Graphics.Gal; -using Ryujinx.Graphics.Memory; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu; using Ryujinx.HLE.HOS.Kernel.Threading; -using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu; using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap; using System; using System.Collections.Generic; @@ -23,7 +22,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger private KEvent _binderEvent; - private IGalRenderer _renderer; + private IRenderer _renderer; private const int BufferQueueCount = 0x40; private const int BufferQueueMask = BufferQueueCount - 1; @@ -34,7 +33,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger private bool _disposed; - public NvFlinger(IGalRenderer renderer, KEvent binderEvent) + public NvFlinger(IRenderer renderer, KEvent binderEvent) { _commands = new Dictionary<(string, int), ServiceProcessParcel> { @@ -256,20 +255,20 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger return ResultCode.Success; } - private GalImageFormat ConvertColorFormat(ColorFormat colorFormat) + private Format ConvertColorFormat(ColorFormat colorFormat) { switch (colorFormat) { case ColorFormat.A8B8G8R8: - return GalImageFormat.Rgba8 | GalImageFormat.Unorm; + return Format.R8G8B8A8Unorm; case ColorFormat.X8B8G8R8: - return GalImageFormat.Rgbx8 | GalImageFormat.Unorm; + return Format.R8G8B8A8Unorm; case ColorFormat.R5G6B5: - return GalImageFormat.Bgr565 | GalImageFormat.Unorm; + return Format.B5G6R5Unorm; case ColorFormat.A8R8G8B8: - return GalImageFormat.Bgra8 | GalImageFormat.Unorm; + return Format.B8G8R8A8Unorm; case ColorFormat.A4B4G4R4: - return GalImageFormat.Rgba4 | GalImageFormat.Unorm; + return Format.R4G4B4A4Unorm; default: throw new NotImplementedException($"Color Format \"{colorFormat}\" not implemented!"); } @@ -292,48 +291,50 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(context.Process, nvMapHandle); - long fbAddr = map.Address + bufferOffset; + ulong fbAddr = (ulong)(map.Address + bufferOffset); _bufferQueue[slot].State = BufferState.Acquired; - Rect crop = _bufferQueue[slot].Crop; + Format format = ConvertColorFormat(_bufferQueue[slot].Data.Buffer.Surfaces[0].ColorFormat); + + int bytesPerPixel = + format == Format.B5G6R5Unorm || + format == Format.R4G4B4A4Unorm ? 2 : 4; + + int gobBlocksInY = 1 << _bufferQueue[slot].Data.Buffer.Surfaces[0].BlockHeightLog2; + + // Note: Rotation is being ignored. + + Rect cropRect = _bufferQueue[slot].Crop; bool flipX = _bufferQueue[slot].Transform.HasFlag(HalTransform.FlipX); bool flipY = _bufferQueue[slot].Transform.HasFlag(HalTransform.FlipY); - GalImageFormat imageFormat = ConvertColorFormat(_bufferQueue[slot].Data.Buffer.Surfaces[0].ColorFormat); + ImageCrop crop = new ImageCrop( + cropRect.Left, + cropRect.Right, + cropRect.Top, + cropRect.Bottom, + flipX, + flipY); - int BlockHeight = 1 << _bufferQueue[slot].Data.Buffer.Surfaces[0].BlockHeightLog2; + context.Device.Gpu.Window.EnqueueFrameThreadSafe( + fbAddr, + fbWidth, + fbHeight, + 0, + false, + gobBlocksInY, + format, + bytesPerPixel, + crop, + ReleaseBuffer, + slot); + } - // Note: Rotation is being ignored. - - int top = crop.Top; - int left = crop.Left; - int right = crop.Right; - int bottom = crop.Bottom; - - NvGpuVmm vmm = NvHostAsGpuDeviceFile.GetAddressSpaceContext(context.Process).Vmm; - - _renderer.QueueAction(() => - { - if (!_renderer.Texture.TryGetImage(fbAddr, out GalImage image)) - { - image = new GalImage( - fbWidth, - fbHeight, 1, 1, 1, BlockHeight, 1, - GalMemoryLayout.BlockLinear, - imageFormat, - GalTextureTarget.TwoD); - } - - context.Device.Gpu.ResourceManager.ClearPbCache(); - context.Device.Gpu.ResourceManager.SendTexture(vmm, fbAddr, image); - - _renderer.RenderTarget.SetTransform(flipX, flipY, top, left, right, bottom); - _renderer.RenderTarget.Present(fbAddr); - - ReleaseBuffer(slot); - }); + private void ReleaseBuffer(object slot) + { + ReleaseBuffer((int)slot); } private void ReleaseBuffer(int slot) diff --git a/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZone.cs b/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZone.cs index ca0efbfaf9..496c678680 100644 --- a/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZone.cs +++ b/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZone.cs @@ -1707,7 +1707,8 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone Time = new CalendarTime() { Year = (short)calendarTime.Year, - Month = calendarTime.Month, + // NOTE: Nintendo's month range is 1-12, internal range is 0-11. + Month = (sbyte)(calendarTime.Month + 1), Day = calendarTime.Day, Hour = calendarTime.Hour, Minute = calendarTime.Minute, @@ -1724,7 +1725,8 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone CalendarTimeInternal calendarTimeInternal = new CalendarTimeInternal() { Year = calendarTime.Year, - Month = calendarTime.Month, + // NOTE: Nintendo's month range is 1-12, internal range is 0-11. + Month = (sbyte)(calendarTime.Month - 1), Day = calendarTime.Day, Hour = calendarTime.Hour, Minute = calendarTime.Minute, diff --git a/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/IHOSBinderDriver.cs b/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/IHOSBinderDriver.cs index 3364ce7045..e30d159cac 100644 --- a/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/IHOSBinderDriver.cs +++ b/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/IHOSBinderDriver.cs @@ -1,4 +1,4 @@ -using Ryujinx.Graphics.Gal; +using Ryujinx.Graphics.GAL; using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Threading; @@ -13,7 +13,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService private NvFlinger _flinger; - public IHOSBinderDriver(Horizon system, IGalRenderer renderer) + public IHOSBinderDriver(Horizon system, IRenderer renderer) { _binderEvent = new KEvent(system); diff --git a/Ryujinx.HLE/Ryujinx.HLE.csproj b/Ryujinx.HLE/Ryujinx.HLE.csproj index 6837146548..15c0c8c0c1 100644 --- a/Ryujinx.HLE/Ryujinx.HLE.csproj +++ b/Ryujinx.HLE/Ryujinx.HLE.csproj @@ -44,9 +44,10 @@ - + + diff --git a/Ryujinx.HLE/Switch.cs b/Ryujinx.HLE/Switch.cs index d1ca3d1f4b..90723c4caf 100644 --- a/Ryujinx.HLE/Switch.cs +++ b/Ryujinx.HLE/Switch.cs @@ -1,8 +1,8 @@ using LibHac.FsSystem; using Ryujinx.Audio; using Ryujinx.Configuration; -using Ryujinx.Graphics; -using Ryujinx.Graphics.Gal; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS; using Ryujinx.HLE.HOS.Services; @@ -19,7 +19,7 @@ namespace Ryujinx.HLE internal DeviceMemory Memory { get; private set; } - internal NvGpu Gpu { get; private set; } + internal GpuContext Gpu { get; private set; } public VirtualFileSystem FileSystem { get; private set; } @@ -35,7 +35,7 @@ namespace Ryujinx.HLE public event EventHandler Finish; - public Switch(IGalRenderer renderer, IAalOutput audioOut) + public Switch(IRenderer renderer, IAalOutput audioOut) { if (renderer == null) { @@ -51,7 +51,7 @@ namespace Ryujinx.HLE Memory = new DeviceMemory(); - Gpu = new NvGpu(renderer); + Gpu = new GpuContext(renderer); FileSystem = new VirtualFileSystem(); @@ -114,12 +114,17 @@ namespace Ryujinx.HLE public bool WaitFifo() { - return Gpu.Pusher.WaitForCommands(); + return Gpu.DmaPusher.WaitForCommands(); } public void ProcessFrame() { - Gpu.Pusher.DispatchCalls(); + Gpu.DmaPusher.DispatchCalls(); + } + + public void PresentFrame(Action swapBuffersCallback) + { + Gpu.Window.Present(swapBuffersCallback); } internal void Unload() @@ -129,6 +134,11 @@ namespace Ryujinx.HLE Memory.Dispose(); } + public void DisposeGpu() + { + Gpu.Dispose(); + } + public void Dispose() { Dispose(true); diff --git a/Ryujinx.ShaderTools/Memory.cs b/Ryujinx.ShaderTools/Memory.cs deleted file mode 100644 index c99224b5ed..0000000000 --- a/Ryujinx.ShaderTools/Memory.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Ryujinx.Graphics.Gal; -using System.IO; - -namespace Ryujinx.ShaderTools -{ - class Memory : IGalMemory - { - private Stream BaseStream; - - private BinaryReader Reader; - - public Memory(Stream BaseStream) - { - this.BaseStream = BaseStream; - - Reader = new BinaryReader(BaseStream); - } - - public int ReadInt32(long Position) - { - BaseStream.Seek(Position, SeekOrigin.Begin); - - return Reader.ReadInt32(); - } - } -} \ No newline at end of file diff --git a/Ryujinx.ShaderTools/Program.cs b/Ryujinx.ShaderTools/Program.cs index e763e2c1c1..25ac8d2af8 100644 --- a/Ryujinx.ShaderTools/Program.cs +++ b/Ryujinx.ShaderTools/Program.cs @@ -1,5 +1,4 @@ -using Ryujinx.Graphics.Gal; -using Ryujinx.Graphics.Shader; +using Ryujinx.Graphics.Shader; using Ryujinx.Graphics.Shader.Translation; using System; using System.IO; @@ -10,33 +9,24 @@ namespace Ryujinx.ShaderTools { static void Main(string[] args) { - if (args.Length == 2) + if (args.Length == 1 || args.Length == 2) { - GalShaderType type = GalShaderType.Vertex; + TranslationFlags flags = TranslationFlags.DebugMode; - switch (args[0].ToLower()) + if (args.Length == 2 && args[0] == "--compute") { - case "v": type = GalShaderType.Vertex; break; - case "tc": type = GalShaderType.TessControl; break; - case "te": type = GalShaderType.TessEvaluation; break; - case "g": type = GalShaderType.Geometry; break; - case "f": type = GalShaderType.Fragment; break; + flags |= TranslationFlags.Compute; } - using (FileStream fs = new FileStream(args[1], FileMode.Open, FileAccess.Read)) - { - Memory mem = new Memory(fs); + byte[] data = File.ReadAllBytes(args[^1]); - ShaderConfig config = new ShaderConfig(type, 65536); + string code = Translator.Translate(data, new TranslatorCallbacks(null, null), flags).Code; - string code = Translator.Translate(mem, 0, config).Code; - - Console.WriteLine(code); - } + Console.WriteLine(code); } else { - Console.WriteLine("Usage: Ryujinx.ShaderTools [v|tc|te|g|f] shader.bin"); + Console.WriteLine("Usage: Ryujinx.ShaderTools [--compute] shader.bin"); } } } diff --git a/Ryujinx.ShaderTools/Ryujinx.ShaderTools.csproj b/Ryujinx.ShaderTools/Ryujinx.ShaderTools.csproj index 2a71319dc6..2f9c777278 100644 --- a/Ryujinx.ShaderTools/Ryujinx.ShaderTools.csproj +++ b/Ryujinx.ShaderTools/Ryujinx.ShaderTools.csproj @@ -1,5 +1,9 @@ + + + + netcoreapp3.0 win-x64;osx-x64;linux-x64 @@ -17,8 +21,4 @@ false - - - - diff --git a/Ryujinx.sln b/Ryujinx.sln index e3cb4528fd..4ad74077cf 100644 --- a/Ryujinx.sln +++ b/Ryujinx.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26730.8 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29613.14 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx", "Ryujinx\Ryujinx.csproj", "{074045D4-3ED2-4711-9169-E385F2BFB5A0}" EndProject @@ -14,8 +14,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE", "Ryujinx.HLE\ {4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34} = {4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34} EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics", "Ryujinx.Graphics\Ryujinx.Graphics.csproj", "{EAAE36AF-7781-4578-A7E0-F0EFD2025569}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio", "Ryujinx.Audio\Ryujinx.Audio.csproj", "{5C1D818E-682A-46A5-9D54-30006E26C270}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.ShaderTools", "Ryujinx.ShaderTools\Ryujinx.ShaderTools.csproj", "{3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}" @@ -28,6 +26,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Profiler", "Ryujinx EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ARMeilleure", "ARMeilleure\ARMeilleure.csproj", "{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Gpu", "Ryujinx.Graphics.Gpu\Ryujinx.Graphics.Gpu.csproj", "{ADA7EA87-0D63-4D97-9433-922A2124401F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.GAL", "Ryujinx.Graphics.GAL\Ryujinx.Graphics.GAL.csproj", "{A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.OpenGL", "Ryujinx.Graphics.OpenGL\Ryujinx.Graphics.OpenGL.csproj", "{9558FB96-075D-4219-8FFF-401979DC0B69}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Texture", "Ryujinx.Graphics.Texture\Ryujinx.Graphics.Texture.csproj", "{E1B1AD28-289D-47B7-A106-326972240207}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Shader", "Ryujinx.Graphics.Shader\Ryujinx.Graphics.Shader.csproj", "{03B955CD-AD84-4B93-AAA7-BF17923BBAA5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Graphics.Nvdec", "Ryujinx.Graphics.Nvdec\Ryujinx.Graphics.Nvdec.csproj", "{85A0FA56-DC01-4A42-8808-70DAC76BD66D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -68,14 +78,6 @@ Global {CB92CFF9-1D62-4D4F-9E88-8130EF61E351}.Profile Release|Any CPU.Build.0 = Profile Release|Any CPU {CB92CFF9-1D62-4D4F-9E88-8130EF61E351}.Release|Any CPU.ActiveCfg = Release|Any CPU {CB92CFF9-1D62-4D4F-9E88-8130EF61E351}.Release|Any CPU.Build.0 = Release|Any CPU - {EAAE36AF-7781-4578-A7E0-F0EFD2025569}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EAAE36AF-7781-4578-A7E0-F0EFD2025569}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EAAE36AF-7781-4578-A7E0-F0EFD2025569}.Profile Debug|Any CPU.ActiveCfg = Profile Debug|Any CPU - {EAAE36AF-7781-4578-A7E0-F0EFD2025569}.Profile Debug|Any CPU.Build.0 = Profile Debug|Any CPU - {EAAE36AF-7781-4578-A7E0-F0EFD2025569}.Profile Release|Any CPU.ActiveCfg = Profile Release|Any CPU - {EAAE36AF-7781-4578-A7E0-F0EFD2025569}.Profile Release|Any CPU.Build.0 = Profile Release|Any CPU - {EAAE36AF-7781-4578-A7E0-F0EFD2025569}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EAAE36AF-7781-4578-A7E0-F0EFD2025569}.Release|Any CPU.Build.0 = Release|Any CPU {5C1D818E-682A-46A5-9D54-30006E26C270}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5C1D818E-682A-46A5-9D54-30006E26C270}.Debug|Any CPU.Build.0 = Debug|Any CPU {5C1D818E-682A-46A5-9D54-30006E26C270}.Profile Debug|Any CPU.ActiveCfg = Profile Debug|Any CPU @@ -124,6 +126,54 @@ Global {ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Profile Release|Any CPU.Build.0 = Release|Any CPU {ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Release|Any CPU.ActiveCfg = Release|Any CPU {ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Release|Any CPU.Build.0 = Release|Any CPU + {ADA7EA87-0D63-4D97-9433-922A2124401F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ADA7EA87-0D63-4D97-9433-922A2124401F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ADA7EA87-0D63-4D97-9433-922A2124401F}.Profile Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ADA7EA87-0D63-4D97-9433-922A2124401F}.Profile Debug|Any CPU.Build.0 = Debug|Any CPU + {ADA7EA87-0D63-4D97-9433-922A2124401F}.Profile Release|Any CPU.ActiveCfg = Debug|Any CPU + {ADA7EA87-0D63-4D97-9433-922A2124401F}.Profile Release|Any CPU.Build.0 = Debug|Any CPU + {ADA7EA87-0D63-4D97-9433-922A2124401F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ADA7EA87-0D63-4D97-9433-922A2124401F}.Release|Any CPU.Build.0 = Release|Any CPU + {A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}.Profile Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}.Profile Debug|Any CPU.Build.0 = Debug|Any CPU + {A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}.Profile Release|Any CPU.ActiveCfg = Debug|Any CPU + {A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}.Profile Release|Any CPU.Build.0 = Debug|Any CPU + {A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}.Release|Any CPU.Build.0 = Release|Any CPU + {9558FB96-075D-4219-8FFF-401979DC0B69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9558FB96-075D-4219-8FFF-401979DC0B69}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9558FB96-075D-4219-8FFF-401979DC0B69}.Profile Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9558FB96-075D-4219-8FFF-401979DC0B69}.Profile Debug|Any CPU.Build.0 = Debug|Any CPU + {9558FB96-075D-4219-8FFF-401979DC0B69}.Profile Release|Any CPU.ActiveCfg = Debug|Any CPU + {9558FB96-075D-4219-8FFF-401979DC0B69}.Profile Release|Any CPU.Build.0 = Debug|Any CPU + {9558FB96-075D-4219-8FFF-401979DC0B69}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9558FB96-075D-4219-8FFF-401979DC0B69}.Release|Any CPU.Build.0 = Release|Any CPU + {E1B1AD28-289D-47B7-A106-326972240207}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E1B1AD28-289D-47B7-A106-326972240207}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E1B1AD28-289D-47B7-A106-326972240207}.Profile Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E1B1AD28-289D-47B7-A106-326972240207}.Profile Debug|Any CPU.Build.0 = Debug|Any CPU + {E1B1AD28-289D-47B7-A106-326972240207}.Profile Release|Any CPU.ActiveCfg = Debug|Any CPU + {E1B1AD28-289D-47B7-A106-326972240207}.Profile Release|Any CPU.Build.0 = Debug|Any CPU + {E1B1AD28-289D-47B7-A106-326972240207}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E1B1AD28-289D-47B7-A106-326972240207}.Release|Any CPU.Build.0 = Release|Any CPU + {03B955CD-AD84-4B93-AAA7-BF17923BBAA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {03B955CD-AD84-4B93-AAA7-BF17923BBAA5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {03B955CD-AD84-4B93-AAA7-BF17923BBAA5}.Profile Debug|Any CPU.ActiveCfg = Debug|Any CPU + {03B955CD-AD84-4B93-AAA7-BF17923BBAA5}.Profile Debug|Any CPU.Build.0 = Debug|Any CPU + {03B955CD-AD84-4B93-AAA7-BF17923BBAA5}.Profile Release|Any CPU.ActiveCfg = Debug|Any CPU + {03B955CD-AD84-4B93-AAA7-BF17923BBAA5}.Profile Release|Any CPU.Build.0 = Debug|Any CPU + {03B955CD-AD84-4B93-AAA7-BF17923BBAA5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {03B955CD-AD84-4B93-AAA7-BF17923BBAA5}.Release|Any CPU.Build.0 = Release|Any CPU + {85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Profile Debug|Any CPU.ActiveCfg = Debug|Any CPU + {85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Profile Debug|Any CPU.Build.0 = Debug|Any CPU + {85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Profile Release|Any CPU.ActiveCfg = Release|Any CPU + {85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Profile Release|Any CPU.Build.0 = Release|Any CPU + {85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Ryujinx/Ryujinx.csproj b/Ryujinx/Ryujinx.csproj index e610e8277e..4977086345 100644 --- a/Ryujinx/Ryujinx.csproj +++ b/Ryujinx/Ryujinx.csproj @@ -79,10 +79,11 @@ - + + diff --git a/Ryujinx/Ui/GLScreen.cs b/Ryujinx/Ui/GLScreen.cs index e1994803a6..6c0c28586b 100644 --- a/Ryujinx/Ui/GLScreen.cs +++ b/Ryujinx/Ui/GLScreen.cs @@ -2,7 +2,7 @@ using OpenTK; using OpenTK.Graphics; using OpenTK.Input; using Ryujinx.Configuration; -using Ryujinx.Graphics.Gal; +using Ryujinx.Graphics.OpenGL; using Ryujinx.HLE; using Ryujinx.HLE.Input; using Ryujinx.Profiler.UI; @@ -23,7 +23,7 @@ namespace Ryujinx.Ui private Switch _device; - private IGalRenderer _renderer; + private Renderer _renderer; private HotkeyButtons _prevHotkeyButtons = 0; @@ -45,7 +45,7 @@ namespace Ryujinx.Ui private ProfileWindowManager _profileWindow; #endif - public GlScreen(Switch device, IGalRenderer renderer) + public GlScreen(Switch device, Renderer renderer) : base(1280, 720, new GraphicsMode(), "Ryujinx", 0, DisplayDevice.Default, 3, 3, @@ -59,7 +59,7 @@ namespace Ryujinx.Ui Location = new Point( (DisplayDevice.Default.Width / 2) - (Width / 2), (DisplayDevice.Default.Height / 2) - (Height / 2)); - + #if USE_PROFILING // Start profile window, it will handle itself from there _profileWindow = new ProfileWindowManager(); @@ -70,6 +70,8 @@ namespace Ryujinx.Ui { MakeCurrent(); + _renderer.Initialize(); + Stopwatch chrono = new Stopwatch(); chrono.Start(); @@ -85,13 +87,11 @@ namespace Ryujinx.Ui _device.ProcessFrame(); } - _renderer.RunActions(); - if (_resizeEvent) { _resizeEvent = false; - _renderer.RenderTarget.SetWindowSize(Width, Height); + _renderer.Window.SetSize(Width, Height); } ticks += chrono.ElapsedTicks; @@ -106,6 +106,9 @@ namespace Ryujinx.Ui ticks = Math.Min(ticks - ticksPerFrame, ticksPerFrame); } } + + _device.DisposeGpu(); + _renderer.Dispose(); } public void MainLoop() @@ -114,8 +117,6 @@ namespace Ryujinx.Ui Visible = true; - _renderer.RenderTarget.SetWindowSize(Width, Height); - Context.MakeCurrent(null); // OpenTK doesn't like sleeps in its thread, to avoid this a renderer thread is created @@ -188,7 +189,7 @@ namespace Ryujinx.Ui Keys = new int[0x8] }; } - + currentButton |= _primaryController.GetButtons(); // Keyboard has priority stick-wise @@ -296,7 +297,7 @@ namespace Ryujinx.Ui private new void RenderFrame() { - _renderer.RenderTarget.Render(); + _device.PresentFrame(SwapBuffers); _device.Statistics.RecordSystemFrameTime(); @@ -314,8 +315,6 @@ namespace Ryujinx.Ui _titleEvent = true; - SwapBuffers(); - _device.System.SignalVsync(); _device.VsyncEvent.Set(); diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs index 4b3c5ab89b..440295f63a 100644 --- a/Ryujinx/Ui/MainWindow.cs +++ b/Ryujinx/Ui/MainWindow.cs @@ -4,8 +4,7 @@ using JsonPrettyPrinterPlus; using Ryujinx.Audio; using Ryujinx.Common.Logging; using Ryujinx.Configuration; -using Ryujinx.Graphics.Gal; -using Ryujinx.Graphics.Gal.OpenGL; +using Ryujinx.Graphics.OpenGL; using Ryujinx.HLE.FileSystem; using Ryujinx.Profiler; using System; @@ -26,7 +25,7 @@ namespace Ryujinx.Ui { private static HLE.Switch _device; - private static IGalRenderer _renderer; + private static Renderer _renderer; private static IAalOutput _audioOut; @@ -75,12 +74,12 @@ namespace Ryujinx.Ui _gameTable.ButtonReleaseEvent += Row_Clicked; bool continueWithStartup = Migration.PromptIfMigrationNeededForStartup(this, out bool migrationNeeded); - if (!continueWithStartup) + if (!continueWithStartup) { End(); } - _renderer = new OglRenderer(); + _renderer = new Renderer(); _audioOut = InitializeAudioEngine(); @@ -232,7 +231,7 @@ namespace Ryujinx.Ui Logger.RestartTime(); // TODO: Move this somewhere else + reloadable? - GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath; + Ryujinx.Graphics.Gpu.GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath; if (Directory.Exists(path)) { @@ -409,14 +408,14 @@ namespace Ryujinx.Ui /// An supported by this machine private static IAalOutput InitializeAudioEngine() { - if (SoundIoAudioOut.IsSupported) - { - return new SoundIoAudioOut(); - } - else if (OpenALAudioOut.IsSupported) + if (OpenALAudioOut.IsSupported) { return new OpenALAudioOut(); } + else if (SoundIoAudioOut.IsSupported) + { + return new SoundIoAudioOut(); + } else { return new DummyAudioOut(); @@ -455,7 +454,7 @@ namespace Ryujinx.Ui IJsonFormatterResolver resolver = CompositeResolver.Create(new[] { StandardResolver.AllowPrivateSnakeCase }); ApplicationMetadata appMetadata; - + using (Stream stream = File.OpenRead(metadataPath)) { appMetadata = JsonSerializer.Deserialize(stream, resolver);