0
0
Fork 0

Initial work

This commit is contained in:
gdk 2019-10-13 03:02:07 -03:00 committed by Thog
parent f617fb542a
commit 1876b346fe
518 changed files with 15170 additions and 12486 deletions

View file

@ -355,12 +355,9 @@ namespace ARMeilleure.Instructions
} }
while (bit < context.Memory.AddressSpaceBits); 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)); Operand pageOffset = context.BitwiseAnd(address, Const(address.Type, MemoryManager.PageMask));

View file

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

View file

@ -6,8 +6,6 @@ namespace ARMeilleure.Memory
{ {
public static class MemoryManagement public static class MemoryManagement
{ {
public static bool HasWriteWatchSupport => RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
public static IntPtr Allocate(ulong size) public static IntPtr Allocate(ulong size)
{ {
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
@ -88,27 +86,5 @@ namespace ARMeilleure.Memory
throw new PlatformNotSupportedException(); 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;
}
}
} }
} }

View file

@ -36,12 +36,6 @@ namespace ARMeilleure.Memory
WriteCombineModifierflag = 0x400 WriteCombineModifierflag = 0x400
} }
private enum WriteWatchFlags : uint
{
None = 0,
Reset = 1
}
[DllImport("kernel32.dll")] [DllImport("kernel32.dll")]
private static extern IntPtr VirtualAlloc( private static extern IntPtr VirtualAlloc(
IntPtr lpAddress, IntPtr lpAddress,
@ -62,15 +56,6 @@ namespace ARMeilleure.Memory
IntPtr dwSize, IntPtr dwSize,
AllocationType dwFreeType); 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) public static IntPtr Allocate(IntPtr size)
{ {
const AllocationType flags = const AllocationType flags =
@ -130,27 +115,5 @@ namespace ARMeilleure.Memory
{ {
return VirtualFree(address, IntPtr.Zero, AllocationType.Release); 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;
}
} }
} }

View file

@ -1,5 +1,6 @@
using ARMeilleure.State; using ARMeilleure.State;
using System; using System;
using System.Collections.Generic;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
@ -29,8 +30,6 @@ namespace ARMeilleure.Memory
internal int PtLevelSize { get; } internal int PtLevelSize { get; }
internal int PtLevelMask { get; } internal int PtLevelMask { get; }
public bool HasWriteWatchSupport => MemoryManagement.HasWriteWatchSupport;
public int AddressSpaceBits { get; } public int AddressSpaceBits { get; }
public long AddressSpaceSize { get; } public long AddressSpaceSize { get; }
@ -254,119 +253,57 @@ namespace ARMeilleure.Memory
return ptePtr; return ptePtr;
} }
public bool IsRegionModified(long position, long size) public unsafe (ulong, ulong)[] GetModifiedRanges(ulong address, ulong size)
{ {
if (!HasWriteWatchSupport) 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); if (IsValidPosition((long)address))
}
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; byte* ptr = ((byte**)_pageTable)[address >> PageBits];
}
else
{
return true;
}
}
while (pages-- > 0)
{
if (address != expectedAddr)
{
modified |= IsAnyPageModified();
baseAddr = address;
pendingPages = 0;
}
expectedAddr = address + PageSize;
pendingPages++;
if (pages == 0)
{
break;
}
position += PageSize;
address = Translate(position);
}
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))
{
byte* ptr = ((byte**)_pageTable)[position >> PageBits];
ulong ptrUlong = (ulong)ptr; ulong ptrUlong = (ulong)ptr;
if ((ptrUlong & PteFlagNotModified) == 0) if ((ptrUlong & PteFlagNotModified) == 0)
{ {
modified = true; // Modified.
currSize += PageSize;
SetPtEntryFlag(position, PteFlagNotModified); SetPtEntryFlag((long)address, PteFlagNotModified);
}
else
{
if (currSize != 0)
{
ranges.Add((currAddr, currSize));
}
currAddr = address + PageSize;
currSize = 0;
} }
} }
else else
{ {
modified = true; currSize += PageSize;
} }
position += PageSize; address += PageSize;
} }
return modified; if (currSize != 0)
}
public bool TryGetHostAddress(long position, long size, out IntPtr ptr)
{
if (IsContiguous(position, size))
{ {
ptr = (IntPtr)Translate(position); ranges.Add((currAddr, currSize));
return true;
} }
ptr = IntPtr.Zero; return ranges.ToArray();
return false;
} }
private bool IsContiguous(long position, long size) private bool IsContiguous(long position, long size)
@ -612,41 +549,6 @@ namespace ARMeilleure.Memory
return data; 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) public void WriteSByte(long position, sbyte value)
{ {
WriteByte(position, (byte)value); WriteByte(position, (byte)value);
@ -746,53 +648,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() public void Dispose()
{ {
Dispose(true); Dispose(true);

View file

@ -1,9 +1,11 @@
using System.Diagnostics; using System.Diagnostics;
namespace Ryujinx.Common namespace Ryujinx.Common
{ {
public static class PerformanceCounter public static class PerformanceCounter
{ {
private static double _ticksToNs;
/// <summary> /// <summary>
/// Represents the number of ticks in 1 day. /// Represents the number of ticks in 1 day.
/// </summary> /// </summary>
@ -53,6 +55,19 @@ namespace Ryujinx.Common
} }
} }
/// <summary>
/// Gets the number of nanoseconds elapsed since the system started.
/// </summary>
public static long ElapsedNanoseconds
{
get
{
long timestamp = Stopwatch.GetTimestamp();
return (long)(timestamp * _ticksToNs);
}
}
static PerformanceCounter() static PerformanceCounter()
{ {
TicksPerMillisecond = Stopwatch.Frequency / 1000; TicksPerMillisecond = Stopwatch.Frequency / 1000;
@ -60,6 +75,8 @@ namespace Ryujinx.Common
TicksPerMinute = TicksPerSecond * 60; TicksPerMinute = TicksPerSecond * 60;
TicksPerHour = TicksPerMinute * 60; TicksPerHour = TicksPerMinute * 60;
TicksPerDay = TicksPerHour * 24; TicksPerDay = TicksPerHour * 24;
_ticksToNs = 1000000000.0 / (double)Stopwatch.Frequency;
} }
} }
} }

View file

@ -0,0 +1,32 @@
namespace Ryujinx.Graphics.GAL.Blend
{
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;
}
}
}

View file

@ -0,0 +1,25 @@
namespace Ryujinx.Graphics.GAL.Blend
{
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
}
}

View file

@ -0,0 +1,11 @@
namespace Ryujinx.Graphics.GAL.Blend
{
public enum BlendOp
{
Add = 1,
Subtract,
ReverseSubtract,
Minimum,
Maximum
}
}

View file

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

View file

@ -0,0 +1,12 @@
namespace Ryujinx.Graphics.GAL
{
public struct Capabilities
{
public bool SupportsAstcCompression { get; }
public Capabilities(bool supportsAstcCompression)
{
SupportsAstcCompression = supportsAstcCompression;
}
}
}

View file

@ -0,0 +1,18 @@
namespace Ryujinx.Graphics.GAL.Color
{
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;
}
}
}

View file

@ -0,0 +1,18 @@
namespace Ryujinx.Graphics.GAL.Color
{
public struct ColorSI
{
public int Red { get; }
public int Green { get; }
public int Blue { get; }
public int Alpha { get; }
public ColorSI(int red, int green, int blue, int alpha)
{
Red = red;
Green = green;
Blue = blue;
Alpha = alpha;
}
}
}

View file

@ -0,0 +1,18 @@
namespace Ryujinx.Graphics.GAL.Color
{
public struct ColorUI
{
public uint Red { get; }
public uint Green { get; }
public uint Blue { get; }
public uint Alpha { get; }
public ColorUI(uint red, uint green, uint blue, uint alpha)
{
Red = red;
Green = green;
Blue = blue;
Alpha = alpha;
}
}
}

View file

@ -0,0 +1,14 @@
namespace Ryujinx.Graphics.GAL
{
public enum CompareOp
{
Never = 1,
Less,
Equal,
LessOrEqual,
Greater,
NotEqual,
GreaterOrEqual,
Always
}
}

View file

@ -0,0 +1,9 @@
namespace Ryujinx.Graphics.GAL
{
public enum CounterType
{
SamplesPassed,
PrimitivesGenerated,
TransformFeedbackPrimitivesWritten
}
}

View file

@ -0,0 +1,47 @@
namespace Ryujinx.Graphics.GAL.DepthStencil
{
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;
}
}
}

View file

@ -0,0 +1,20 @@
namespace Ryujinx.Graphics.GAL.DepthStencil
{
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;
}
}
}

View file

@ -0,0 +1,14 @@
namespace Ryujinx.Graphics.GAL.DepthStencil
{
public enum StencilOp
{
Keep = 1,
Zero,
Replace,
IncrementAndClamp,
DecrementAndClamp,
Invert,
IncrementAndWrap,
DecrementAndWrap
}
}

View file

@ -0,0 +1,56 @@
namespace Ryujinx.Graphics.GAL.DepthStencil
{
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;
}
}
}

View file

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

View file

@ -1,9 +1,9 @@
namespace Ryujinx.Graphics.Gal namespace Ryujinx.Graphics.GAL
{ {
public enum GalCullFace public enum Face
{ {
Front = 0x404, Front = 0x404,
Back = 0x405, Back = 0x405,
FrontAndBack = 0x408 FrontAndBack = 0x408
} }
} }

View file

@ -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,
Bc6HUfloat,
Bc6HSfloat,
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;
}
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.Graphics.GAL
{
public enum FrontFace
{
Clockwise = 0x900,
CounterClockwise = 0x901
}
}

View file

@ -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<byte> data);
void SetData(int offset, Span<byte> data);
}
}

View file

@ -0,0 +1,12 @@
namespace Ryujinx.Graphics.GAL
{
public interface IComputePipeline
{
void Dispatch(int groupsX, int groupsY, int groupsZ);
void SetProgram(IProgram program);
void SetStorageBuffer(int index, BufferRange buffer);
void SetUniformBuffer(int index, BufferRange buffer);
}
}

View file

@ -0,0 +1,69 @@
using Ryujinx.Graphics.GAL.Blend;
using Ryujinx.Graphics.GAL.Color;
using Ryujinx.Graphics.GAL.DepthStencil;
using Ryujinx.Graphics.GAL.InputAssembler;
using Ryujinx.Graphics.Shader;
namespace Ryujinx.Graphics.GAL
{
public interface IGraphicsPipeline
{
void BindBlendState(int index, BlendDescriptor blend);
void BindIndexBuffer(BufferRange buffer, IndexType type);
void BindProgram(IProgram program);
void BindSampler(int index, ShaderStage stage, ISampler sampler);
void BindTexture(int index, ShaderStage stage, ITexture texture);
void BindStorageBuffers(int index, ShaderStage stage, BufferRange[] buffers);
void BindUniformBuffers(int index, ShaderStage stage, BufferRange[] buffers);
void BindVertexAttribs(VertexAttribDescriptor[] vertexAttribs);
void BindVertexBuffers(VertexBufferDescriptor[] vertexBuffers);
void ClearRenderTargetColor(int index, uint componentMask, ColorF color);
void ClearRenderTargetColor(int index, uint componentMask, ColorSI color);
void ClearRenderTargetColor(int index, uint componentMask, ColorUI color);
void ClearRenderTargetDepthStencil(
float depthValue,
bool depthMask,
int stencilValue,
int stencilMask);
void Draw(int vertexCount, int instanceCount, int firstVertex, int firstInstance);
void DrawIndexed(
int indexCount,
int instanceCount,
int firstIndex,
int firstVertex,
int firstInstance);
void DrawIndirect (BufferRange buffer, ulong offset, int drawCount, int stride);
void DrawIndexedIndirect(BufferRange buffer, ulong offset, int drawCount, int stride);
void SetBlendColor(ColorF color);
void SetDepthBias(PolygonModeMask enables, float factor, float units, float clamp);
void SetDepthTest(DepthTestDescriptor depthTest);
void SetFaceCulling(bool enable, Face face);
void SetFrontFace(FrontFace frontFace);
void SetPrimitiveRestart(bool enable, int index);
void SetPrimitiveTopology(PrimitiveTopology topology);
void SetRenderTargetColorMasks(uint[] componentMask);
void SetRenderTargets(ITexture color3D, ITexture depthStencil);
void SetRenderTargets(ITexture[] colors, ITexture depthStencil);
void SetStencilTest(StencilTestDescriptor stencilTest);
void SetViewports(int first, Viewport[] viewports);
}
}

View file

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

View file

@ -0,0 +1,33 @@
using Ryujinx.Graphics.GAL.Sampler;
using Ryujinx.Graphics.GAL.Texture;
using Ryujinx.Graphics.Shader;
namespace Ryujinx.Graphics.GAL
{
public interface IRenderer
{
IComputePipeline ComputePipeline { get; }
IGraphicsPipeline GraphicsPipeline { 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 InitializeCounters();
void ResetCounter(CounterType type);
}
}

View file

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

View file

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

View file

@ -0,0 +1,21 @@
using Ryujinx.Graphics.GAL.Texture;
using System;
namespace Ryujinx.Graphics.GAL
{
public interface ITexture : IDisposable
{
int Handle { get; }
void CopyTo(ITexture destination);
void CopyTo(ITexture destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter);
ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel);
int GetStorageDebugId();
byte[] GetData(int face);
void SetData(Span<byte> data);
}
}

View file

@ -0,0 +1,13 @@
using System;
namespace Ryujinx.Graphics.GAL
{
public interface IWindow
{
void Present();
void QueueTexture(ITexture texture, ImageCrop crop, object context);
void RegisterTextureReleaseCallback(TextureReleaseCallback callback);
}
}

View file

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

View file

@ -0,0 +1,9 @@
namespace Ryujinx.Graphics.GAL
{
public enum IndexType
{
UByte,
UShort,
UInt
}
}

View file

@ -0,0 +1,17 @@
namespace Ryujinx.Graphics.GAL.InputAssembler
{
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;
}
}
}

View file

@ -0,0 +1,17 @@
namespace Ryujinx.Graphics.GAL.InputAssembler
{
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;
}
}
}

View file

@ -0,0 +1,12 @@
using System;
namespace Ryujinx.Graphics.GAL
{
[Flags]
public enum PolygonModeMask
{
Point = 1 << 0,
Line = 1 << 1,
Fill = 1 << 2
}
}

View file

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

View file

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

View file

@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\Ryujinx.Graphics.Shader\Ryujinx.Graphics.Shader.csproj" />
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
</ItemGroup>
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
</Project>

View file

@ -0,0 +1,14 @@
namespace Ryujinx.Graphics.GAL.Sampler
{
public enum AddressMode
{
Repeat,
MirroredRepeat,
ClampToEdge,
ClampToBorder,
Clamp,
MirrorClampToEdge,
MirrorClampToBorder,
MirrorClamp
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.Graphics.GAL.Sampler
{
public enum CompareMode
{
None,
CompareRToTexture
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.Graphics.GAL.Sampler
{
public enum MagFilter
{
Nearest = 1,
Linear
}
}

View file

@ -0,0 +1,12 @@
namespace Ryujinx.Graphics.GAL.Sampler
{
public enum MinFilter
{
Nearest = 1,
Linear,
NearestMipmapNearest,
LinearMipmapNearest,
NearestMipmapLinear,
LinearMipmapLinear
}
}

View file

@ -0,0 +1,52 @@
using Ryujinx.Graphics.GAL.Color;
namespace Ryujinx.Graphics.GAL.Sampler
{
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;
}
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.Graphics.GAL.Texture
{
public enum DepthStencilMode
{
Depth,
Stencil
}
}

View file

@ -0,0 +1,12 @@
namespace Ryujinx.Graphics.GAL.Texture
{
public enum SwizzleComponent
{
Zero,
One,
Red,
Green,
Blue,
Alpha
}
}

View file

@ -0,0 +1,17 @@
namespace Ryujinx.Graphics.GAL.Texture
{
public enum Target
{
Texture1D,
Texture2D,
Texture3D,
Texture1DArray,
Texture2DArray,
Texture2DMultisample,
Texture2DMultisampleArray,
Rectangle,
Cubemap,
CubemapArray,
TextureBuffer
}
}

View file

@ -0,0 +1,115 @@
using Ryujinx.Common;
using System;
namespace Ryujinx.Graphics.GAL.Texture
{
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 int GetMipSize(int level)
{
return GetMipStride(level) * GetLevelHeight(level) * GetLevelDepth(level);
}
public int GetMipStride(int level)
{
return BitUtils.AlignUp(GetLevelWidth(level) * BytesPerPixel, 4);
}
private int GetLevelWidth(int level)
{
return BitUtils.DivRoundUp(GetLevelSize(Width, level), BlockWidth);
}
private int GetLevelHeight(int level)
{
return BitUtils.DivRoundUp(GetLevelSize(Height, level), BlockHeight);
}
private int GetLevelDepth(int level)
{
return Target == Target.Texture3D ? GetLevelSize(Depth, level) : GetLayers();
}
public int GetDepthOrLayers()
{
return Target == Target.Texture3D ? Depth : GetLayers();
}
public 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);
}
}
}

View file

@ -0,0 +1,4 @@
namespace Ryujinx.Graphics.GAL
{
public delegate void TextureReleaseCallback(object context);
}

View file

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

View file

@ -0,0 +1,14 @@
namespace Ryujinx.Graphics.GAL
{
public enum ViewportSwizzle
{
PositiveX,
NegativeX,
PositiveY,
NegativeY,
PositiveZ,
NegativeZ,
PositiveW,
NegativeW
}
}

View file

@ -0,0 +1,11 @@
namespace Ryujinx.Graphics.Gpu
{
enum ClassId
{
Engine2D = 0x902d,
Engine3D = 0xb197,
EngineCompute = 0xb1c0,
EngineInline2Memory = 0xa140,
EngineDma = 0xb0b5
}
}

View file

@ -0,0 +1,14 @@
namespace Ryujinx.Graphics.Gpu
{
static class Constants
{
public const int TotalCpUniformBuffers = 8;
public const int TotalCpStorageBuffers = 16;
public const int TotalGpUniformBuffers = 18;
public const int TotalGpStorageBuffers = 16;
public const int TotalRenderTargets = 8;
public const int TotalShaderStages = 5;
public const int TotalVertexBuffers = 16;
public const int TotalViewports = 8;
}
}

View file

@ -0,0 +1,25 @@
using System;
namespace Ryujinx.Graphics.Gpu
{
static class Debugging
{
public static void PrintTexInfo(string prefix, Image.Texture tex)
{
if (tex == null)
{
Console.WriteLine(prefix + " null");
return;
}
string range = $"{tex.Address:X}..{(tex.Address + tex.Size):X}";
int debugId = tex.HostTexture.GetStorageDebugId();
string str = $"{prefix} p {debugId:X8} {tex.Info.Target} {tex.Info.FormatInfo.Format} {tex.Info.Width}x{tex.Info.Height}x{tex.Info.DepthOrLayers} mips {tex.Info.Levels} addr {range}";
Console.WriteLine(str);
}
}
}

View file

@ -1,15 +1,14 @@
using Ryujinx.Graphics.Memory;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Threading; using System.Threading;
namespace Ryujinx.Graphics namespace Ryujinx.Graphics.Gpu
{ {
public class DmaPusher public class DmaPusher
{ {
private ConcurrentQueue<(NvGpuVmm, long)> _ibBuffer; private ConcurrentQueue<ulong> _ibBuffer;
private long _dmaPut; private ulong _dmaPut;
private long _dmaGet; private ulong _dmaGet;
private struct DmaState private struct DmaState
{ {
@ -29,28 +28,26 @@ namespace Ryujinx.Graphics
private bool _ibEnable; private bool _ibEnable;
private bool _nonMain; private bool _nonMain;
private long _dmaMGet; private ulong _dmaMGet;
private NvGpuVmm _vmm; private GpuContext _context;
private NvGpu _gpu;
private AutoResetEvent _event; private AutoResetEvent _event;
public DmaPusher(NvGpu gpu) internal DmaPusher(GpuContext context)
{ {
_gpu = gpu; _context = context;
_ibBuffer = new ConcurrentQueue<(NvGpuVmm, long)>(); _ibBuffer = new ConcurrentQueue<ulong>();
_ibEnable = true; _ibEnable = true;
_event = new AutoResetEvent(false); _event = new AutoResetEvent(false);
} }
public void Push(NvGpuVmm vmm, long entry) public void Push(ulong entry)
{ {
_ibBuffer.Enqueue((vmm, entry)); _ibBuffer.Enqueue(entry);
_event.Set(); _event.Set();
} }
@ -69,7 +66,7 @@ namespace Ryujinx.Graphics
{ {
if (_dmaGet != _dmaPut) if (_dmaGet != _dmaPut)
{ {
int word = _vmm.ReadInt32(_dmaGet); int word = _context.MemoryAccessor.ReadInt32(_dmaGet);
_dmaGet += 4; _dmaGet += 4;
@ -148,20 +145,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; ulong length = (entry >> 42) & 0x1fffff;
long entry = tuple.Entry;
int length = (int)(entry >> 42) & 0x1fffff;
_dmaGet = entry & 0xfffffffffc; _dmaGet = entry & 0xfffffffffc;
_dmaPut = _dmaGet + length * 4; _dmaPut = _dmaGet + length * 4;
_nonMain = (entry & (1L << 41)) != 0; _nonMain = (entry & (1UL << 41)) != 0;
_gpu.ResourceManager.ClearPbCache();
} }
else else
{ {
@ -180,7 +171,7 @@ namespace Ryujinx.Graphics
private void CallMethod(int argument) private void CallMethod(int argument)
{ {
_gpu.Fifo.CallMethod(_vmm, new GpuMethodCall( _context.Fifo.CallMethod(new MethodParams(
_state.Method, _state.Method,
argument, argument,
_state.SubChannel, _state.SubChannel,

View file

@ -0,0 +1,83 @@
using Ryujinx.Graphics.Gpu.State;
using Ryujinx.Graphics.Shader;
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Gpu.Engine
{
partial class Methods
{
public void Dispatch(int argument)
{
uint dispatchParamsAddress = (uint)_context.State.Get<int>(MethodOffset.DispatchParamsAddress);
var dispatchParams = _context.MemoryAccessor.Read<ComputeParams>((ulong)dispatchParamsAddress << 8);
GpuVa shaderBaseAddress = _context.State.Get<GpuVa>(MethodOffset.ShaderBaseAddress);
ulong shaderGpuVa = shaderBaseAddress.Pack() + (uint)dispatchParams.ShaderOffset;
ComputeShader cs = _shaderCache.GetComputeShader(
shaderGpuVa,
dispatchParams.UnpackBlockSizeX(),
dispatchParams.UnpackBlockSizeY(),
dispatchParams.UnpackBlockSizeZ());
_context.Renderer.ComputePipeline.SetProgram(cs.Interface);
ShaderProgramInfo info = cs.Shader.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<byte> sbDescriptorData = _context.PhysicalMemory.Read(sbDescAddress, 0x10);
SbDescriptor sbDescriptor = MemoryMarshal.Cast<byte, SbDescriptor>(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);
_bufferManager.CommitComputeBindings();
_context.Renderer.ComputePipeline.Dispatch(
dispatchParams.UnpackGridSizeX(),
dispatchParams.UnpackGridSizeY(),
dispatchParams.UnpackGridSizeZ());
}
}
}

View file

@ -0,0 +1,126 @@
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Gpu.Engine
{
struct UniformBufferParams
{
public int AddressLow;
public int AddressHighAndSize;
public ulong PackAddress()
{
return (uint)AddressLow | ((ulong)(AddressHighAndSize & 0xff) << 32);
}
public ulong UnpackSize()
{
return (ulong)((AddressHighAndSize >> 15) & 0x1ffff);
}
}
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 int Unknown11;
public int GridSizeX;
public int GridSizeYZ;
public int Unknown14;
public int Unknown15;
public int Unknown16;
public int Unknown17;
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;
public Span<UniformBufferParams> 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;
public int UnpackGridSizeX()
{
return GridSizeX & 0x7fffffff;
}
public int UnpackGridSizeY()
{
return GridSizeYZ & 0xffff;
}
public int UnpackGridSizeZ()
{
return (GridSizeYZ >> 16) & 0xffff;
}
public int UnpackBlockSizeX()
{
return (BlockSizeX >> 16) & 0xffff;
}
public int UnpackBlockSizeY()
{
return BlockSizeYZ & 0xffff;
}
public int UnpackBlockSizeZ()
{
return (BlockSizeYZ >> 16) & 0xffff;
}
public uint UnpackUniformBuffersEnableMask()
{
return (uint)UniformBuffersConfig & 0xff;
}
}
}

View file

@ -0,0 +1,18 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader;
namespace Ryujinx.Graphics.Gpu.Engine
{
class ComputeShader
{
public IProgram Interface { get; set; }
public ShaderProgram Shader { get; }
public ComputeShader(IProgram program, ShaderProgram shader)
{
Interface = program;
Shader = shader;
}
}
}

View file

@ -0,0 +1,17 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader;
namespace Ryujinx.Graphics.Gpu.Engine
{
class GraphicsShader
{
public IProgram Interface { get; set; }
public ShaderProgram[] Shader { get; }
public GraphicsShader()
{
Shader = new ShaderProgram[5];
}
}
}

View file

@ -0,0 +1,42 @@
using Ryujinx.Graphics.Gpu.State;
using System;
namespace Ryujinx.Graphics.Gpu.Engine
{
partial class Methods
{
private Inline2MemoryParams _params;
private bool _isLinear;
private int _offset;
private int _size;
public void Execute(int argument)
{
_params = _context.State.Get<Inline2MemoryParams>(MethodOffset.Inline2MemoryParams);
_isLinear = (argument & 1) != 0;
_offset = 0;
_size = _params.LineLengthIn * _params.LineCount;
}
public void PushData(int argument)
{
if (_isLinear)
{
for (int shift = 0; shift < 32 && _offset < _size; shift += 8, _offset++)
{
ulong gpuVa = _params.DstAddress.Pack() + (ulong)_offset;
_context.MemoryAccessor.Write(gpuVa, new byte[] { (byte)(argument >> shift) });
}
}
else
{
throw new NotImplementedException();
}
}
}
}

View file

@ -0,0 +1,55 @@
using Ryujinx.Graphics.GAL.Color;
using Ryujinx.Graphics.Gpu.State;
namespace Ryujinx.Graphics.Gpu.Engine
{
partial class Methods
{
private void Clear(int argument)
{
UpdateState();
bool clearDepth = (argument & 1) != 0;
bool clearStencil = (argument & 2) != 0;
uint componentMask = (uint)((argument >> 2) & 0xf);
int index = (argument >> 6) & 0xf;
if (componentMask != 0)
{
ClearColors clearColor = _context.State.GetClearColors();
ColorF color = new ColorF(
clearColor.Red,
clearColor.Green,
clearColor.Blue,
clearColor.Alpha);
_context.Renderer.GraphicsPipeline.ClearRenderTargetColor(
index,
componentMask,
color);
}
if (clearDepth || clearStencil)
{
float depthValue = _context.State.GetClearDepthValue();
int stencilValue = _context.State.GetClearStencilValue();
int stencilMask = 0;
if (clearStencil)
{
stencilMask = _context.State.GetStencilTestState().FrontMask;
}
_context.Renderer.GraphicsPipeline.ClearRenderTargetDepthStencil(
depthValue,
clearDepth,
stencilValue,
stencilMask);
}
}
}
}

View file

@ -0,0 +1,79 @@
using Ryujinx.Graphics.Gpu.State;
using Ryujinx.Graphics.Texture;
using System;
namespace Ryujinx.Graphics.Gpu.Engine
{
partial class Methods
{
private void CopyBuffer(int argument)
{
var cbp = _context.State.Get<CopyBufferParams>(MethodOffset.CopyBufferParams);
var swizzle = _context.State.Get<CopyBufferSwizzle>(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 = _context.State.Get<CopyBufferTexture>(MethodOffset.CopyBufferDstTexture);
var src = _context.State.Get<CopyBufferTexture>(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<byte> 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);
Span<byte> data = _context.MemoryAccessor.Read(cbp.SrcAddress.Pack(), (uint)size);
_context.MemoryAccessor.Write(cbp.DstAddress.Pack(), data);
}
}
}
}

View file

@ -0,0 +1,70 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.State;
namespace Ryujinx.Graphics.Gpu.Engine
{
partial class Methods
{
private void CopyTexture(int argument)
{
CopyTexture dstCopyTexture = _context.State.GetCopyDstTexture();
CopyTexture srcCopyTexture = _context.State.GetCopySrcTexture();
Image.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;
}
Image.Texture dstTexture = _textureManager.FindOrCreateTexture(dstCopyTexture);
if (dstTexture == null)
{
return;
}
CopyTextureControl control = _context.State.GetCopyTextureControl();
CopyRegion region = _context.State.GetCopyRegion();
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);
dstTexture.Modified = true;
}
}
}

View file

@ -0,0 +1,133 @@
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 _instancedHasState;
private bool _instancedIndexed;
private int _instancedFirstIndex;
private int _instancedFirstVertex;
private int _instancedFirstInstance;
private int _instancedIndexCount;
private int _instancedDrawStateFirst;
private int _instancedDrawStateCount;
private int _instanceIndex;
public PrimitiveType PrimitiveType { get; private set; }
private void DrawEnd(int argument)
{
UpdateState();
bool instanced = _vsUsesInstanceId || _isAnyVbInstanced;
if (instanced)
{
if (!_instancedHasState)
{
_instancedHasState = true;
_instancedIndexed = _drawIndexed;
_instancedFirstIndex = _firstIndex;
_instancedFirstVertex = _context.State.GetBaseVertex();
_instancedFirstInstance = _context.State.GetBaseInstance();
_instancedIndexCount = _indexCount;
VertexBufferDrawState drawState = _context.State.GetVertexBufferDrawState();
_instancedDrawStateFirst = drawState.First;
_instancedDrawStateCount = drawState.Count;
}
return;
}
int firstInstance = _context.State.GetBaseInstance();
if (_drawIndexed)
{
_drawIndexed = false;
int firstVertex = _context.State.GetBaseVertex();
_context.Renderer.GraphicsPipeline.DrawIndexed(
_indexCount,
1,
_firstIndex,
firstVertex,
firstInstance);
}
else
{
VertexBufferDrawState drawState = _context.State.GetVertexBufferDrawState();
_context.Renderer.GraphicsPipeline.Draw(
drawState.Count,
1,
drawState.First,
firstInstance);
}
}
private void DrawBegin(int argument)
{
PrimitiveType type = (PrimitiveType)(argument & 0xffff);
_context.Renderer.GraphicsPipeline.SetPrimitiveTopology(type.Convert());
PrimitiveType = type;
if ((argument & (1 << 26)) != 0)
{
_instanceIndex++;
}
else if ((argument & (1 << 27)) == 0)
{
_instanceIndex = 0;
}
}
private void SetIndexCount(int argument)
{
_drawIndexed = true;
}
public void PerformDeferredDraws()
{
// Perform any pending instanced draw.
if (_instancedHasState)
{
_instancedHasState = false;
if (_instancedIndexed)
{
_context.Renderer.GraphicsPipeline.DrawIndexed(
_instancedIndexCount,
_instanceIndex + 1,
_instancedFirstIndex,
_instancedFirstVertex,
_instancedFirstInstance);
}
else
{
_context.Renderer.GraphicsPipeline.Draw(
_instancedDrawStateCount,
_instanceIndex + 1,
_instancedDrawStateFirst,
_instancedFirstInstance);
}
}
}
}
}

View file

@ -0,0 +1,100 @@
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 ulong _runningCounter;
private void Report(int argument)
{
ReportMode mode = (ReportMode)(argument & 3);
ReportCounterType type = (ReportCounterType)((argument >> 23) & 0x1f);
switch (mode)
{
case ReportMode.Semaphore: ReportSemaphore(); break;
case ReportMode.Counter: ReportCounter(type); break;
}
}
private void ReportSemaphore()
{
ReportState state = _context.State.GetReportState();
_context.MemoryAccessor.Write(state.Address.Pack(), state.Payload);
_context.AdvanceSequence();
}
private struct CounterData
{
public ulong Counter;
public ulong Timestamp;
}
private void ReportCounter(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<CounterData> counterDataSpan = MemoryMarshal.CreateSpan(ref counterData, 1);
Span<byte> data = MemoryMarshal.Cast<CounterData, byte>(counterDataSpan);
ReportState state = _context.State.GetReportState();
_context.MemoryAccessor.Write(state.Address.Pack(), data);
}
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 / 625;
ulong rounded = divided * 625;
ulong errorBias = ((nanoseconds - rounded) * 384) / 625;
return divided * 384 + errorBias;
}
}
}

View file

@ -0,0 +1,26 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.State;
namespace Ryujinx.Graphics.Gpu.Engine
{
partial class Methods
{
private void ResetCounter(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;
}
}
}
}

View file

@ -0,0 +1,52 @@
using Ryujinx.Graphics.Gpu.State;
namespace Ryujinx.Graphics.Gpu.Engine
{
partial class Methods
{
private void UniformBufferBind0(int argument)
{
UniformBufferBind(argument, ShaderType.Vertex);
}
private void UniformBufferBind1(int argument)
{
UniformBufferBind(argument, ShaderType.TessellationControl);
}
private void UniformBufferBind2(int argument)
{
UniformBufferBind(argument, ShaderType.TessellationEvaluation);
}
private void UniformBufferBind3(int argument)
{
UniformBufferBind(argument, ShaderType.Geometry);
}
private void UniformBufferBind4(int argument)
{
UniformBufferBind(argument, ShaderType.Fragment);
}
private void UniformBufferBind(int argument, ShaderType type)
{
bool enable = (argument & 1) != 0;
int index = (argument >> 4) & 0x1f;
if (enable)
{
UniformBufferState uniformBuffer = _context.State.GetUniformBufferState();
ulong address = uniformBuffer.Address.Pack();
_bufferManager.SetGraphicsUniformBuffer((int)type, index, address, (uint)uniformBuffer.Size);
}
else
{
_bufferManager.SetGraphicsUniformBuffer((int)type, index, 0, 0);
}
}
}
}

View file

@ -0,0 +1,18 @@
using Ryujinx.Graphics.Gpu.State;
namespace Ryujinx.Graphics.Gpu.Engine
{
partial class Methods
{
private void UniformBufferUpdate(int argument)
{
UniformBufferState uniformBuffer = _context.State.GetUniformBufferState();
_context.MemoryAccessor.Write(uniformBuffer.Address.Pack() + (uint)uniformBuffer.Offset, argument);
_context.State.SetUniformBufferOffset(uniformBuffer.Offset + 4);
_context.AdvanceSequence();
}
}
}

View file

@ -0,0 +1,784 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.GAL.Blend;
using Ryujinx.Graphics.GAL.DepthStencil;
using Ryujinx.Graphics.GAL.InputAssembler;
using Ryujinx.Graphics.GAL.Texture;
using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Gpu.State;
using Ryujinx.Graphics.Shader;
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Gpu.Engine
{
partial class Methods
{
private GpuContext _context;
private ShaderCache _shaderCache;
private BufferManager _bufferManager;
private TextureManager _textureManager;
public TextureManager TextureManager => _textureManager;
private bool _isAnyVbInstanced;
private bool _vsUsesInstanceId;
public Methods(GpuContext context)
{
_context = context;
_shaderCache = new ShaderCache(_context);
_bufferManager = new BufferManager(context);
_textureManager = new TextureManager(context, _bufferManager);
RegisterCallbacks();
}
private void RegisterCallbacks()
{
_context.State.RegisterCopyBufferCallback(CopyBuffer);
_context.State.RegisterCopyTextureCallback(CopyTexture);
_context.State.RegisterDrawEndCallback(DrawEnd);
_context.State.RegisterDrawBeginCallback(DrawBegin);
_context.State.RegisterSetIndexCountCallback(SetIndexCount);
_context.State.RegisterClearCallback(Clear);
_context.State.RegisterReportCallback(Report);
_context.State.RegisterUniformBufferUpdateCallback(UniformBufferUpdate);
_context.State.RegisterUniformBufferBind0Callback(UniformBufferBind0);
_context.State.RegisterUniformBufferBind1Callback(UniformBufferBind1);
_context.State.RegisterUniformBufferBind2Callback(UniformBufferBind2);
_context.State.RegisterUniformBufferBind3Callback(UniformBufferBind3);
_context.State.RegisterUniformBufferBind4Callback(UniformBufferBind4);
_context.State.RegisterCallback(MethodOffset.InvalidateTextures, InvalidateTextures);
_context.State.RegisterCallback(MethodOffset.ResetCounter, ResetCounter);
_context.State.RegisterCallback(MethodOffset.Inline2MemoryExecute, Execute);
_context.State.RegisterCallback(MethodOffset.Inline2MemoryPushData, PushData);
_context.State.RegisterCallback(MethodOffset.Dispatch, Dispatch);
}
public Image.Texture GetTexture(ulong address) => _textureManager.Find2(address);
private void UpdateState()
{
if ((_context.State.StateWriteFlags & StateWriteFlags.Any) == 0)
{
CommitBindings();
return;
}
// 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 ((_context.State.StateWriteFlags & StateWriteFlags.ShaderState) != 0)
{
UpdateShaderState();
}
if ((_context.State.StateWriteFlags & StateWriteFlags.RenderTargetGroup) != 0)
{
UpdateRenderTargetGroupState();
}
if ((_context.State.StateWriteFlags & StateWriteFlags.DepthTestState) != 0)
{
UpdateDepthTestState();
}
if ((_context.State.StateWriteFlags & StateWriteFlags.ViewportTransform) != 0)
{
UpdateViewportTransform();
}
if ((_context.State.StateWriteFlags & StateWriteFlags.DepthBiasState) != 0)
{
UpdateDepthBiasState();
}
if ((_context.State.StateWriteFlags & StateWriteFlags.StencilTestState) != 0)
{
UpdateStencilTestState();
}
if ((_context.State.StateWriteFlags & StateWriteFlags.SamplerPoolState) != 0)
{
UpdateSamplerPoolState();
}
if ((_context.State.StateWriteFlags & StateWriteFlags.TexturePoolState) != 0)
{
UpdateTexturePoolState();
}
if ((_context.State.StateWriteFlags & StateWriteFlags.InputAssemblerGroup) != 0)
{
UpdateInputAssemblerGroupState();
}
if ((_context.State.StateWriteFlags & StateWriteFlags.FaceState) != 0)
{
UpdateFaceState();
}
if ((_context.State.StateWriteFlags & StateWriteFlags.RtColorMask) != 0)
{
UpdateRtColorMask();
}
if ((_context.State.StateWriteFlags & StateWriteFlags.BlendState) != 0)
{
UpdateBlendState();
}
_context.State.StateWriteFlags &= ~StateWriteFlags.Any;
CommitBindings();
}
private void CommitBindings()
{
_bufferManager.CommitBindings();
_textureManager.CommitBindings();
}
public void InvalidateRange(ulong address, ulong size)
{
_bufferManager.InvalidateRange(address, size);
_textureManager.InvalidateRange(address, size);
}
public void InvalidateTextureRange(ulong address, ulong size)
{
_textureManager.InvalidateRange(address, size);
}
private void UpdateRenderTargetGroupState()
{
TextureMsaaMode msaaMode = _context.State.GetRtMsaaMode();
int samplesInX = msaaMode.SamplesInX();
int samplesInY = msaaMode.SamplesInY();
Image.Texture color3D = Get3DRenderTarget(samplesInX, samplesInY);
if (color3D == null)
{
for (int index = 0; index < Constants.TotalRenderTargets; index++)
{
RtColorState colorState = _context.State.GetRtColorState(index);
if (!IsRtEnabled(colorState))
{
_textureManager.SetRenderTargetColor(index, null);
continue;
}
Image.Texture color = _textureManager.FindOrCreateTexture(
colorState,
samplesInX,
samplesInY);
_textureManager.SetRenderTargetColor(index, color);
color.Modified = true;
}
}
else
{
_textureManager.SetRenderTargetColor3D(color3D);
color3D.Modified = true;
}
bool dsEnable = _context.State.Get<bool>(MethodOffset.RtDepthStencilEnable);
Image.Texture depthStencil = null;
if (dsEnable)
{
var dsState = _context.State.GetRtDepthStencilState();
var dsSize = _context.State.GetRtDepthStencilSize();
depthStencil = _textureManager.FindOrCreateTexture(
dsState,
dsSize,
samplesInX,
samplesInY);
}
_textureManager.SetRenderTargetDepthStencil(depthStencil);
}
private Image.Texture Get3DRenderTarget(int samplesInX, int samplesInY)
{
RtColorState colorState0 = _context.State.GetRtColorState(0);
if (!IsRtEnabled(colorState0) || !colorState0.MemoryLayout.UnpackIsTarget3D() || colorState0.Depth != 1)
{
return null;
}
int slices = 1;
int unused = 0;
for (int index = 1; index < Constants.TotalRenderTargets; index++)
{
RtColorState colorState = _context.State.GetRtColorState(index);
if (!IsRtEnabled(colorState))
{
unused++;
continue;
}
if (colorState.MemoryLayout.UnpackIsTarget3D() && colorState.Depth == 1)
{
slices++;
}
}
if (slices + unused == Constants.TotalRenderTargets)
{
colorState0.Depth = slices;
return _textureManager.FindOrCreateTexture(colorState0, samplesInX, samplesInY);
}
return null;
}
private static bool IsRtEnabled(RtColorState colorState)
{
// Colors are disabled by writing 0 to the format.
return colorState.Format != 0 && colorState.WidthOrStride != 0;
}
private void UpdateDepthTestState()
{
_context.Renderer.GraphicsPipeline.SetDepthTest(new DepthTestDescriptor(
_context.State.GetDepthTestEnable().IsTrue(),
_context.State.GetDepthWriteEnable().IsTrue(),
_context.State.GetDepthTestFunc()));
}
private void UpdateViewportTransform()
{
Viewport[] viewports = new Viewport[Constants.TotalViewports];
for (int index = 0; index < Constants.TotalViewports; index++)
{
var transform = _context.State.Get<ViewportTransform>(MethodOffset.ViewportTransform + index * 8);
var extents = _context.State.Get<ViewportExtents> (MethodOffset.ViewportExtents + index * 4);
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;
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.GraphicsPipeline.SetViewports(0, viewports);
}
private void UpdateDepthBiasState()
{
var polygonOffset = _context.State.Get<DepthBiasState>(MethodOffset.DepthBiasState);
float factor = _context.State.Get<float>(MethodOffset.DepthBiasFactor);
float units = _context.State.Get<float>(MethodOffset.DepthBiasUnits);
float clamp = _context.State.Get<float>(MethodOffset.DepthBiasClamp);
PolygonModeMask enables = 0;
enables = (polygonOffset.PointEnable.IsTrue() ? PolygonModeMask.Point : 0);
enables |= (polygonOffset.LineEnable.IsTrue() ? PolygonModeMask.Line : 0);
enables |= (polygonOffset.FillEnable.IsTrue() ? PolygonModeMask.Fill : 0);
_context.Renderer.GraphicsPipeline.SetDepthBias(enables, factor, units, clamp);
}
private void UpdateStencilTestState()
{
StencilBackMasks backMasks = _context.State.GetStencilBackMasks();
StencilTestState test = _context.State.GetStencilTestState();
StencilBackTestState backTest = _context.State.GetStencilBackTestState();
CompareOp backFunc;
StencilOp backSFail;
StencilOp backDpPass;
StencilOp backDpFail;
int backFuncRef;
int backFuncMask;
int backMask;
if (backTest.TwoSided.IsTrue())
{
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.GraphicsPipeline.SetStencilTest(new StencilTestDescriptor(
test.Enable.IsTrue(),
test.FrontFunc,
test.FrontSFail,
test.FrontDpPass,
test.FrontDpFail,
test.FrontFuncRef,
test.FrontFuncMask,
test.FrontMask,
backFunc,
backSFail,
backDpPass,
backDpFail,
backFuncRef,
backFuncMask,
backMask));
}
private void UpdateSamplerPoolState()
{
PoolState samplerPool = _context.State.GetSamplerPoolState();
_textureManager.SetSamplerPool(samplerPool.Address.Pack(), samplerPool.MaximumId);
}
private void UpdateTexturePoolState()
{
PoolState texturePool = _context.State.GetTexturePoolState();
_textureManager.SetTexturePool(texturePool.Address.Pack(), texturePool.MaximumId);
_textureManager.SetTextureBufferIndex(_context.State.GetTextureBufferIndex());
}
private void UpdateInputAssemblerGroupState()
{
// Must be updated before the vertex buffer.
if ((_context.State.StateWriteFlags & StateWriteFlags.VertexAttribState) != 0)
{
UpdateVertexAttribState();
}
if ((_context.State.StateWriteFlags & StateWriteFlags.PrimitiveRestartState) != 0)
{
UpdatePrimitiveRestartState();
}
if ((_context.State.StateWriteFlags & StateWriteFlags.IndexBufferState) != 0)
{
UpdateIndexBufferState();
}
if ((_context.State.StateWriteFlags & StateWriteFlags.VertexBufferState) != 0)
{
UpdateVertexBufferState();
}
}
private void UpdateVertexAttribState()
{
VertexAttribDescriptor[] vertexAttribs = new VertexAttribDescriptor[16];
for (int index = 0; index < 16; index++)
{
VertexAttribState vertexAttrib = _context.State.GetVertexAttribState(index);
if (!FormatTable.TryGetAttribFormat(vertexAttrib.UnpackFormat(), out Format format))
{
// TODO: warning.
format = Format.R32G32B32A32Float;
}
vertexAttribs[index] = new VertexAttribDescriptor(
vertexAttrib.UnpackBufferIndex(),
vertexAttrib.UnpackOffset(),
format);
}
_context.Renderer.GraphicsPipeline.BindVertexAttribs(vertexAttribs);
}
private void UpdatePrimitiveRestartState()
{
PrimitiveRestartState primitiveRestart = _context.State.Get<PrimitiveRestartState>(MethodOffset.PrimitiveRestartState);
_context.Renderer.GraphicsPipeline.SetPrimitiveRestart(
primitiveRestart.Enable,
primitiveRestart.Index);
}
private void UpdateIndexBufferState()
{
IndexBufferState indexBuffer = _context.State.GetIndexBufferState();
_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();
}
private uint GetIndexBufferMaxIndex(ulong gpuVa, ulong size, IndexType type)
{
ulong address = _context.MemoryManager.Translate(gpuVa);
Span<byte> data = _context.PhysicalMemory.Read(address, size);
uint maxIndex = 0;
switch (type)
{
case IndexType.UByte:
{
for (int index = 0; index < data.Length; index++)
{
if (maxIndex < data[index])
{
maxIndex = data[index];
}
}
break;
}
case IndexType.UShort:
{
Span<ushort> indices = MemoryMarshal.Cast<byte, ushort>(data);
for (int index = 0; index < indices.Length; index++)
{
if (maxIndex < indices[index])
{
maxIndex = indices[index];
}
}
break;
}
case IndexType.UInt:
{
Span<uint> indices = MemoryMarshal.Cast<byte, uint>(data);
for (int index = 0; index < indices.Length; index++)
{
if (maxIndex < indices[index])
{
maxIndex = indices[index];
}
}
break;
}
}
return maxIndex;
}
private void UpdateVertexBufferState()
{
_isAnyVbInstanced = false;
for (int index = 0; index < 16; index++)
{
VertexBufferState vertexBuffer = _context.State.GetVertexBufferState(index);
if (!vertexBuffer.UnpackEnable())
{
_bufferManager.SetVertexBuffer(index, 0, 0, 0, 0);
continue;
}
GpuVa endAddress = _context.State.GetVertexBufferEndAddress(index);
ulong address = vertexBuffer.Address.Pack();
int stride = vertexBuffer.UnpackStride();
bool instanced = _context.State.Get<bool>(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 = _context.State.GetBaseInstance();
VertexBufferDrawState drawState = _context.State.GetVertexBufferDrawState();
size = (ulong)((firstInstance + drawState.First + drawState.Count) * stride);
}
_bufferManager.SetVertexBuffer(index, address, size, stride, divisor);
}
}
private void UpdateFaceState()
{
FaceState face = _context.State.GetFaceState();
_context.Renderer.GraphicsPipeline.SetFaceCulling(face.CullEnable.IsTrue(), face.CullFace);
_context.Renderer.GraphicsPipeline.SetFrontFace(face.FrontFace);
}
private void UpdateRtColorMask()
{
uint[] componentMasks = new uint[Constants.TotalRenderTargets];
for (int index = 0; index < Constants.TotalRenderTargets; index++)
{
RtColorMask colorMask = _context.State.Get<RtColorMask>(MethodOffset.RtColorMask + index);
uint componentMask = 0;
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.GraphicsPipeline.SetRenderTargetColorMasks(componentMasks);
}
private void UpdateBlendState()
{
BlendState[] blends = new BlendState[8];
for (int index = 0; index < 8; index++)
{
bool blendEnable = _context.State.GetBlendEnable(index).IsTrue();
BlendState blend = _context.State.GetBlendState(index);
BlendDescriptor descriptor = new BlendDescriptor(
blendEnable,
blend.ColorOp,
blend.ColorSrcFactor,
blend.ColorDstFactor,
blend.AlphaOp,
blend.AlphaSrcFactor,
blend.AlphaDstFactor);
_context.Renderer.GraphicsPipeline.BindBlendState(index, descriptor);
}
}
private struct SbDescriptor
{
public uint AddressLow;
public uint AddressHigh;
public int Size;
public int Padding;
public ulong PackAddress()
{
return AddressLow | ((ulong)AddressHigh << 32);
}
}
private void UpdateShaderState()
{
ShaderAddresses addresses = new ShaderAddresses();
Span<ShaderAddresses> addressesSpan = MemoryMarshal.CreateSpan(ref addresses, 1);
Span<ulong> addressesArray = MemoryMarshal.Cast<ShaderAddresses, ulong>(addressesSpan);
ulong baseAddress = _context.State.GetShaderBaseAddress().Pack();
for (int index = 0; index < 6; index++)
{
ShaderState shader = _context.State.GetShaderState(index);
if (!shader.UnpackEnable() && index != 1)
{
continue;
}
addressesArray[index] = baseAddress + shader.Offset;
}
GraphicsShader gs = _shaderCache.GetGraphicsShader(addresses);
_vsUsesInstanceId = gs.Shader[0].Info.UsesInstanceId;
for (int stage = 0; stage < Constants.TotalShaderStages; stage++)
{
ShaderProgramInfo info = gs.Shader[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.Target);
textureBindings[index] = new TextureBindingInfo(target, descriptor.HandleIndex);
}
_textureManager.BindTextures(stage, textureBindings);
uint sbEnableMask = 0;
uint ubEnableMask = 0;
for (int index = 0; index < info.SBuffers.Count; index++)
{
BufferDescriptor sb = info.SBuffers[index];
sbEnableMask |= 1u << sb.Slot;
ulong sbDescAddress = _bufferManager.GetGraphicsUniformBufferAddress(stage, 0);
int sbDescOffset = 0x110 + stage * 0x100 + sb.Slot * 0x10;
sbDescAddress += (ulong)sbDescOffset;
Span<byte> sbDescriptorData = _context.PhysicalMemory.Read(sbDescAddress, 0x10);
SbDescriptor sbDescriptor = MemoryMarshal.Cast<byte, SbDescriptor>(sbDescriptorData)[0];
_bufferManager.SetGraphicsStorageBuffer(stage, sb.Slot, sbDescriptor.PackAddress(), (uint)sbDescriptor.Size);
}
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.GraphicsPipeline.BindProgram(gs.Interface);
}
private static Target GetTarget(Shader.TextureTarget target)
{
target &= ~Shader.TextureTarget.Shadow;
switch (target)
{
case Shader.TextureTarget.Texture1D:
return Target.Texture1D;
case Shader.TextureTarget.Texture1D | Shader.TextureTarget.Array:
return Target.Texture1DArray;
case Shader.TextureTarget.Texture2D:
return Target.Texture2D;
case Shader.TextureTarget.Texture2D | Shader.TextureTarget.Array:
return Target.Texture2DArray;
case Shader.TextureTarget.Texture2D | Shader.TextureTarget.Multisample:
return Target.Texture2DMultisample;
case Shader.TextureTarget.Texture2D | Shader.TextureTarget.Multisample | Shader.TextureTarget.Array:
return Target.Texture2DMultisampleArray;
case Shader.TextureTarget.Texture3D:
return Target.Texture3D;
case Shader.TextureTarget.TextureCube:
return Target.Cubemap;
case Shader.TextureTarget.TextureCube | Shader.TextureTarget.Array:
return Target.CubemapArray;
}
// TODO: Warning.
return Target.Texture2D;
}
private void InvalidateTextures(int argument)
{
_textureManager.Flush();
}
}
}

View file

@ -0,0 +1,34 @@
using System;
namespace Ryujinx.Graphics.Gpu.Engine
{
struct ShaderAddresses : IEquatable<ShaderAddresses>
{
public ulong VertexA;
public ulong Vertex;
public ulong TessControl;
public ulong TessEvaluation;
public ulong Geometry;
public ulong Fragment;
public override bool Equals(object other)
{
return other is ShaderAddresses addresses && Equals(addresses);
}
public bool Equals(ShaderAddresses other)
{
return VertexA == other.VertexA &&
Vertex == other.Vertex &&
TessControl == other.TessControl &&
TessEvaluation == other.TessEvaluation &&
Geometry == other.Geometry &&
Fragment == other.Fragment;
}
public override int GetHashCode()
{
return HashCode.Combine(VertexA, Vertex, TessControl, TessEvaluation, Geometry, Fragment);
}
}
}

View file

@ -0,0 +1,228 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.State;
using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Shader.Translation;
using System;
using System.Collections.Generic;
using System.Globalization;
namespace Ryujinx.Graphics.Gpu.Engine
{
class ShaderCache
{
private const int MaxProgramSize = 0x100000;
private GpuContext _context;
private ShaderDumper _dumper;
private Dictionary<ulong, ComputeShader> _cpPrograms;
private Dictionary<ShaderAddresses, GraphicsShader> _gpPrograms;
public ShaderCache(GpuContext context)
{
_context = context;
_dumper = new ShaderDumper(context);
_cpPrograms = new Dictionary<ulong, ComputeShader>();
_gpPrograms = new Dictionary<ShaderAddresses, GraphicsShader>();
}
public ComputeShader GetComputeShader(ulong gpuVa, int localSizeX, int localSizeY, int localSizeZ)
{
if (!_cpPrograms.TryGetValue(gpuVa, out ComputeShader cpShader))
{
ShaderProgram shader = TranslateComputeShader(gpuVa);
shader.Replace(DefineNames.LocalSizeX, localSizeX.ToString(CultureInfo.InvariantCulture));
shader.Replace(DefineNames.LocalSizeY, localSizeY.ToString(CultureInfo.InvariantCulture));
shader.Replace(DefineNames.LocalSizeZ, localSizeZ.ToString(CultureInfo.InvariantCulture));
IShader hostShader = _context.Renderer.CompileShader(shader);
IProgram program = _context.Renderer.CreateProgram(new IShader[] { hostShader });
cpShader = new ComputeShader(program, shader);
_cpPrograms.Add(gpuVa, cpShader);
}
return cpShader;
}
public GraphicsShader GetGraphicsShader(ShaderAddresses addresses)
{
if (!_gpPrograms.TryGetValue(addresses, out GraphicsShader gpShader))
{
gpShader = new GraphicsShader();
if (addresses.VertexA != 0)
{
gpShader.Shader[0] = TranslateGraphicsShader(addresses.Vertex, addresses.VertexA);
}
else
{
gpShader.Shader[0] = TranslateGraphicsShader(addresses.Vertex);
}
gpShader.Shader[1] = TranslateGraphicsShader(addresses.TessControl);
gpShader.Shader[2] = TranslateGraphicsShader(addresses.TessEvaluation);
gpShader.Shader[3] = TranslateGraphicsShader(addresses.Geometry);
gpShader.Shader[4] = TranslateGraphicsShader(addresses.Fragment);
BackpropQualifiers(gpShader);
List<IShader> shaders = new List<IShader>();
for (int stage = 0; stage < gpShader.Shader.Length; stage++)
{
if (gpShader.Shader[stage] == null)
{
continue;
}
IShader shader = _context.Renderer.CompileShader(gpShader.Shader[stage]);
shaders.Add(shader);
}
gpShader.Interface = _context.Renderer.CreateProgram(shaders.ToArray());
_gpPrograms.Add(addresses, gpShader);
}
return gpShader;
}
private ShaderProgram TranslateComputeShader(ulong gpuVa)
{
if (gpuVa == 0)
{
return null;
}
ShaderProgram program;
const TranslationFlags flags =
TranslationFlags.Compute |
TranslationFlags.Unspecialized;
TranslationConfig translationConfig = new TranslationConfig(0x10000, _dumper.CurrentDumpIndex, flags);
Span<byte> code = _context.MemoryAccessor.Read(gpuVa, MaxProgramSize);
program = Translator.Translate(code, translationConfig);
_dumper.Dump(gpuVa, compute : true);
return program;
}
private ShaderProgram TranslateGraphicsShader(ulong gpuVa, ulong gpuVaA = 0)
{
if (gpuVa == 0)
{
return null;
}
ShaderProgram program;
const TranslationFlags flags =
TranslationFlags.DebugMode |
TranslationFlags.Unspecialized;
TranslationConfig translationConfig = new TranslationConfig(0x10000, _dumper.CurrentDumpIndex, flags);
if (gpuVaA != 0)
{
Span<byte> codeA = _context.MemoryAccessor.Read(gpuVaA, MaxProgramSize);
Span<byte> codeB = _context.MemoryAccessor.Read(gpuVa, MaxProgramSize);
program = Translator.Translate(codeA, codeB, translationConfig);
_dumper.Dump(gpuVaA, compute: false);
_dumper.Dump(gpuVa, compute: false);
}
else
{
Span<byte> code = _context.MemoryAccessor.Read(gpuVa, MaxProgramSize);
program = Translator.Translate(code, translationConfig);
_dumper.Dump(gpuVa, compute: false);
}
if (program.Stage == ShaderStage.Geometry)
{
PrimitiveType primitiveType = _context.Methods.PrimitiveType;
string inPrimitive = "points";
switch (primitiveType)
{
case PrimitiveType.Points:
inPrimitive = "points";
break;
case PrimitiveType.Lines:
case PrimitiveType.LineLoop:
case PrimitiveType.LineStrip:
inPrimitive = "lines";
break;
case PrimitiveType.LinesAdjacency:
case PrimitiveType.LineStripAdjacency:
inPrimitive = "lines_adjacency";
break;
case PrimitiveType.Triangles:
case PrimitiveType.TriangleStrip:
case PrimitiveType.TriangleFan:
inPrimitive = "triangles";
break;
case PrimitiveType.TrianglesAdjacency:
case PrimitiveType.TriangleStripAdjacency:
inPrimitive = "triangles_adjacency";
break;
}
program.Replace(DefineNames.InputTopologyName, inPrimitive);
}
return program;
}
private void BackpropQualifiers(GraphicsShader program)
{
ShaderProgram fragmentShader = program.Shader[4];
bool isFirst = true;
for (int stage = 3; stage >= 0; stage--)
{
if (program.Shader[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 && iq != string.Empty)
{
program.Shader[stage].Replace($"{DefineNames.OutQualifierPrefixName}{attr}", iq);
}
else
{
program.Shader[stage].Replace($"{DefineNames.OutQualifierPrefixName}{attr} ", string.Empty);
}
}
isFirst = false;
}
}
}
}

View file

@ -1,27 +1,43 @@
using System;
using System.IO; using System.IO;
namespace Ryujinx.Graphics.Gal namespace Ryujinx.Graphics.Gpu.Engine
{ {
static class ShaderDumper class ShaderDumper
{ {
private static string _runtimeDir; private const int ShaderHeaderSize = 0x50;
public static int DumpIndex { get; private set; } = 1; private GpuContext _context;
public static void Dump(IGalMemory memory, long position, GalShaderType type, string extSuffix = "") private string _runtimeDir;
private string _dumpPath;
private int _dumpIndex;
public int CurrentDumpIndex => _dumpIndex;
public ShaderDumper(GpuContext context)
{ {
if (!IsDumpEnabled()) _context = context;
_dumpIndex = 1;
}
public void Dump(ulong gpuVa, bool compute)
{
_dumpPath = GraphicsConfig.ShadersDumpPath;
if (string.IsNullOrWhiteSpace(_dumpPath))
{ {
return; return;
} }
string fileName = "Shader" + DumpIndex.ToString("d4") + "." + ShaderExtension(type) + extSuffix + ".bin"; string fileName = "Shader" + _dumpIndex.ToString("d4") + ".bin";
string fullPath = Path.Combine(FullDir(), fileName); string fullPath = Path.Combine(FullDir(), fileName);
string codePath = Path.Combine(CodeDir(), fileName); string codePath = Path.Combine(CodeDir(), fileName);
DumpIndex++; _dumpIndex++;
ulong headerSize = compute ? 0UL : ShaderHeaderSize;
using (FileStream fullFile = File.Create(fullPath)) using (FileStream fullFile = File.Create(fullPath))
using (FileStream codeFile = File.Create(codePath)) using (FileStream codeFile = File.Create(codePath))
@ -29,25 +45,25 @@ namespace Ryujinx.Graphics.Gal
BinaryWriter fullWriter = new BinaryWriter(fullFile); BinaryWriter fullWriter = new BinaryWriter(fullFile);
BinaryWriter codeWriter = new BinaryWriter(codeFile); BinaryWriter codeWriter = new BinaryWriter(codeFile);
for (long i = 0; i < 0x50; i += 4) for (ulong i = 0; i < headerSize; i += 4)
{ {
fullWriter.Write(memory.ReadInt32(position + i)); fullWriter.Write(_context.MemoryAccessor.ReadInt32(gpuVa + i));
} }
long offset = 0; ulong offset = 0;
ulong instruction = 0; ulong instruction = 0;
// Dump until a NOP instruction is found // Dump until a NOP instruction is found.
while ((instruction >> 48 & 0xfff8) != 0x50b0) while ((instruction >> 48 & 0xfff8) != 0x50b0)
{ {
uint word0 = (uint)memory.ReadInt32(position + 0x50 + offset + 0); uint word0 = (uint)_context.MemoryAccessor.ReadInt32(gpuVa + headerSize + offset + 0);
uint word1 = (uint)memory.ReadInt32(position + 0x50 + offset + 4); uint word1 = (uint)_context.MemoryAccessor.ReadInt32(gpuVa + headerSize + offset + 4);
instruction = word0 | (ulong)word1 << 32; instruction = word0 | (ulong)word1 << 32;
// Zero instructions (other kind of NOP) stop immediately, // Zero instructions (other kind of NOP) stop immediately,
// this is to avoid two rows of zeroes // this is to avoid two rows of zeroes.
if (instruction == 0) if (instruction == 0)
{ {
break; break;
@ -59,7 +75,7 @@ namespace Ryujinx.Graphics.Gal
offset += 8; offset += 8;
} }
// Align to meet nvdisasm requirements // Align to meet nvdisasm requirements.
while (offset % 0x20 != 0) while (offset % 0x20 != 0)
{ {
fullWriter.Write(0); fullWriter.Write(0);
@ -70,22 +86,17 @@ namespace Ryujinx.Graphics.Gal
} }
} }
public static bool IsDumpEnabled() private string FullDir()
{
return !string.IsNullOrWhiteSpace(GraphicsConfig.ShadersDumpPath);
}
private static string FullDir()
{ {
return CreateAndReturn(Path.Combine(DumpDir(), "Full")); return CreateAndReturn(Path.Combine(DumpDir(), "Full"));
} }
private static string CodeDir() private string CodeDir()
{ {
return CreateAndReturn(Path.Combine(DumpDir(), "Code")); return CreateAndReturn(Path.Combine(DumpDir(), "Code"));
} }
private static string DumpDir() private string DumpDir()
{ {
if (string.IsNullOrEmpty(_runtimeDir)) if (string.IsNullOrEmpty(_runtimeDir))
{ {
@ -93,7 +104,7 @@ namespace Ryujinx.Graphics.Gal
do do
{ {
_runtimeDir = Path.Combine(GraphicsConfig.ShadersDumpPath, "Dumps" + index.ToString("d2")); _runtimeDir = Path.Combine(_dumpPath, "Dumps" + index.ToString("d2"));
index++; index++;
} }
@ -107,26 +118,9 @@ namespace Ryujinx.Graphics.Gal
private static string CreateAndReturn(string dir) private static string CreateAndReturn(string dir)
{ {
if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
{
Directory.CreateDirectory(dir);
}
return 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));
}
}
} }
} }

View file

@ -0,0 +1,100 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.GAL.Texture;
using Ryujinx.Graphics.Gpu.Engine;
using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Gpu.State;
using System;
namespace Ryujinx.Graphics.Gpu
{
public class GpuContext
{
public IRenderer Renderer { get; }
internal GpuState State { get; }
internal IPhysicalMemory PhysicalMemory { get; private set; }
public MemoryManager MemoryManager { get; }
internal MemoryAccessor MemoryAccessor { get; }
internal Methods Methods { get; }
internal NvGpuFifo Fifo { get; }
public DmaPusher DmaPusher { get; }
internal int SequenceNumber { get; private set; }
private Lazy<Capabilities> _caps;
internal Capabilities Capabilities => _caps.Value;
public GpuContext(IRenderer renderer)
{
Renderer = renderer;
State = new GpuState();
MemoryManager = new MemoryManager();
MemoryAccessor = new MemoryAccessor(this);
Methods = new Methods(this);
Fifo = new NvGpuFifo(this);
DmaPusher = new DmaPusher(this);
_caps = new Lazy<Capabilities>(GetCapabilities);
}
internal void AdvanceSequence()
{
SequenceNumber++;
}
public ITexture GetTexture(
ulong address,
int width,
int height,
int stride,
bool isLinear,
int gobBlocksInY,
Format format,
int bytesPerPixel)
{
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);
return Methods.GetTexture(address)?.HostTexture;
}
private Capabilities GetCapabilities()
{
return Renderer.GetCapabilities();
}
public void SetVmm(IPhysicalMemory mm)
{
PhysicalMemory = mm;
}
}
}

View file

@ -0,0 +1,12 @@
namespace Ryujinx.Graphics.Gpu
{
public static class GraphicsConfig
{
public static string ShadersDumpPath;
public static bool FastGpuTime = true;
public static bool DisableTUpdate;
public static bool DisableBUpdate;
}
}

View file

@ -0,0 +1,62 @@
using System.Collections;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Gpu.Image
{
class AutoDeleteCache : IEnumerable<Texture>
{
private const int MaxCapacity = 2048;
private LinkedList<Texture> _textures;
public AutoDeleteCache()
{
_textures = new LinkedList<Texture>();
}
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;
}
}
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<Texture> GetEnumerator()
{
return _textures.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return _textures.GetEnumerator();
}
}
}

View file

@ -0,0 +1,31 @@
using Ryujinx.Graphics.GAL;
namespace Ryujinx.Graphics.Gpu.Image
{
struct FormatInfo
{
private static FormatInfo _rgba8 = new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4);
public static FormatInfo Default => _rgba8;
public Format Format { get; }
public int BlockWidth { get; }
public int BlockHeight { get; }
public int BytesPerPixel { get; }
public bool IsCompressed => (BlockWidth | BlockHeight) != 1;
public FormatInfo(
Format format,
int blockWidth,
int blockHeight,
int bytesPerPixel)
{
Format = format;
BlockWidth = blockWidth;
BlockHeight = blockHeight;
BytesPerPixel = bytesPerPixel;
}
}
}

View file

@ -0,0 +1,201 @@
using Ryujinx.Graphics.GAL;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Gpu.Image
{
static class FormatTable
{
private static Dictionary<uint, FormatInfo> _textureFormats = new Dictionary<uint, FormatInfo>()
{
{ 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.Bc6HUfloat, 4, 4, 16) },
{ 0x7ff91, new FormatInfo(Format.Bc6HSfloat, 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<ulong, Format> _attribFormats = new Dictionary<ulong, Format>()
{
{ 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 }
};
public static bool TryGetTextureFormat(uint encoded, bool isSrgb, out FormatInfo format)
{
encoded |= (isSrgb ? 1u << 19 : 0u);
return _textureFormats.TryGetValue(encoded, out format);
}
public static bool TryGetAttribFormat(uint encoded, out Format format)
{
return _attribFormats.TryGetValue(encoded, out format);
}
}
}

View file

@ -0,0 +1,99 @@
using System;
namespace Ryujinx.Graphics.Gpu.Image
{
abstract class Pool<T> : IDisposable
{
protected const int DescriptorSize = 0x20;
protected GpuContext Context;
protected T[] Items;
public ulong Address { get; }
public ulong Size { get; }
public Pool(GpuContext context, ulong address, int maximumId)
{
Context = context;
int count = maximumId + 1;
ulong size = (ulong)(uint)count * DescriptorSize;;
Items = new T[count];
Address = address;
Size = size;
}
public abstract T Get(int id);
public void SynchronizeMemory()
{
(ulong, ulong)[] modifiedRanges = Context.PhysicalMemory.GetModifiedRanges(Address, Size);
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);
}
}
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;
}
InvalidateRangeImpl(address, size);
}
protected abstract void InvalidateRangeImpl(ulong address, ulong size);
protected abstract void Delete(T item);
public void Dispose()
{
if (Items != null)
{
for (int index = 0; index < Items.Length; index++)
{
Delete(Items[index]);
}
Items = null;
}
}
}
}

View file

@ -0,0 +1,9 @@
namespace Ryujinx.Graphics.Gpu.Image
{
enum ReductionFilter
{
Average,
Minimum,
Maximum
}
}

View file

@ -0,0 +1,52 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.GAL.Color;
using Ryujinx.Graphics.GAL.Sampler;
using System;
namespace Ryujinx.Graphics.Gpu.Image
{
class Sampler : IDisposable
{
public ISampler HostSampler { get; }
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));
}
public void Dispose()
{
HostSampler.Dispose();
}
}
}

View file

@ -0,0 +1,132 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.GAL.Sampler;
namespace Ryujinx.Graphics.Gpu.Image
{
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;
public AddressMode UnpackAddressU()
{
return (AddressMode)(Word0 & 7);
}
public AddressMode UnpackAddressV()
{
return (AddressMode)((Word0 >> 3) & 7);
}
public AddressMode UnpackAddressP()
{
return (AddressMode)((Word0 >> 6) & 7);
}
public CompareMode UnpackCompareMode()
{
return (CompareMode)((Word0 >> 9) & 1);
}
public CompareOp UnpackCompareOp()
{
return (CompareOp)(((Word0 >> 10) & 7) + 1);
}
public float UnpackMaxAnisotropy()
{
return _maxAnisotropyLut[(Word0 >> 20) & 7];
}
public MagFilter UnpackMagFilter()
{
return (MagFilter)(Word1 & 3);
}
public MinFilter UnpackMinFilter()
{
int minFilter = (int)(Word1 >> 4) & 3;
int mipFilter = (int)(Word1 >> 6) & 3;
return (MinFilter)(minFilter + (mipFilter - 1) * 2);
}
public ReductionFilter UnpackReductionFilter()
{
return (ReductionFilter)((Word1 >> 10) & 3);
}
public float UnpackMipLodBias()
{
int fixedValue = (int)(Word1 >> 12) & 0x1fff;
fixedValue = (fixedValue << 19) >> 19;
return fixedValue * Frac8ToF32;
}
public float UnpackLodSnap()
{
return _f5ToF32ConversionLut[(Word1 >> 26) & 0x1f];
}
public float UnpackMinLod()
{
return (Word2 & 0xfff) * Frac8ToF32;
}
public float UnpackMaxLod()
{
return ((Word2 >> 12) & 0xfff) * Frac8ToF32;
}
}
}

View file

@ -0,0 +1,61 @@
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Gpu.Image
{
class SamplerPool : Pool<Sampler>
{
public SamplerPool(GpuContext context, ulong address, int maximumId) : base(context, address, maximumId) { }
public override Sampler Get(int id)
{
if ((uint)id >= Items.Length)
{
return null;
}
SynchronizeMemory();
Sampler sampler = Items[id];
if (sampler == null)
{
ulong address = Address + (ulong)(uint)id * DescriptorSize;
Span<byte> data = Context.PhysicalMemory.Read(address, DescriptorSize);
SamplerDescriptor descriptor = MemoryMarshal.Cast<byte, SamplerDescriptor>(data)[0];
sampler = new Sampler(Context, descriptor);
Items[id] = sampler;
}
return sampler;
}
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;
}
}
}
protected override void Delete(Sampler item)
{
item?.Dispose();
}
}
}

View file

@ -0,0 +1,719 @@
using Ryujinx.Common;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.GAL.Texture;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Texture;
using Ryujinx.Graphics.Texture.Astc;
using System;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Gpu.Image
{
class Texture : IRange<Texture>
{
private GpuContext _context;
private TextureInfo _info;
private SizeInfo _sizeInfo;
public Format Format => _info.FormatInfo.Format;
public TextureInfo Info => _info;
private int _depth;
private int _layers;
private int _firstLayer;
private int _firstLevel;
private bool _hasData;
private ITexture _arrayViewTexture;
private Target _arrayViewTarget;
private Texture _viewStorage;
private List<Texture> _views;
public ITexture HostTexture { get; private set; }
public LinkedListNode<Texture> CacheNode { get; set; }
public bool Modified { get; set; }
public ulong Address => _info.Address;
public ulong EndAddress => _info.Address + Size;
public ulong Size => (ulong)_sizeInfo.TotalSize;
private int _referenceCount;
private int _sequenceNumber;
private Texture(
GpuContext context,
TextureInfo info,
SizeInfo sizeInfo,
int firstLayer,
int firstLevel)
{
InitializeTexture(context, info, sizeInfo);
_firstLayer = firstLayer;
_firstLevel = firstLevel;
_hasData = true;
}
public Texture(GpuContext context, TextureInfo info, SizeInfo sizeInfo)
{
InitializeTexture(context, info, sizeInfo);
TextureCreateInfo createInfo = TextureManager.GetCreateInfo(info, context.Capabilities);
HostTexture = _context.Renderer.CreateTexture(createInfo);
}
private void InitializeTexture(GpuContext context, TextureInfo info, SizeInfo sizeInfo)
{
_context = context;
_sizeInfo = sizeInfo;
SetInfo(info);
_viewStorage = this;
_views = new List<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;
}
private void AddView(Texture texture)
{
_views.Add(texture);
texture._viewStorage = this;
}
private void RemoveView(Texture texture)
{
_views.Remove(texture);
texture._viewStorage = null;
}
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);
}
}
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);
ReplaceStorage(newStorage);
}
}
public void SynchronizeMemory()
{
if (_sequenceNumber == _context.SequenceNumber && _hasData)
{
return;
}
_sequenceNumber = _context.SequenceNumber;
bool modified = _context.PhysicalMemory.GetModifiedRanges(Address, Size).Length != 0;
if (!modified && _hasData)
{
return;
}
ulong pageSize = (uint)_context.PhysicalMemory.GetPageSize();
ulong pageMask = pageSize - 1;
ulong rangeAddress = Address & ~pageMask;
ulong rangeSize = (EndAddress - Address + pageMask) & ~pageMask;
_context.Methods.InvalidateRange(rangeAddress, rangeSize);
Span<byte> 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())
{
int blockWidth = _info.FormatInfo.BlockWidth;
int blockHeight = _info.FormatInfo.BlockHeight;
data = AstcDecoder.DecodeToRgba8(
data,
blockWidth,
blockHeight,
1,
_info.Width,
_info.Height,
_depth);
}
HostTexture.SetData(data);
_hasData = true;
}
public void Flush()
{
byte[] data = HostTexture.GetData(0);
_context.PhysicalMemory.Write(Address, data);
}
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;
}
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;
}
if (_info.FormatInfo.Format == Format.R8G8B8A8Srgb &&
info.FormatInfo.Format == Format.R8G8B8A8Unorm && !strict)
{
return true;
}
if (_info.FormatInfo.Format == Format.R8G8B8A8Unorm &&
info.FormatInfo.Format == Format.R8G8B8A8Srgb && !strict)
{
return true;
}
return _info.FormatInfo.Format == info.FormatInfo.Format;
}
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;
}
}
public bool SizeMatches(TextureInfo info)
{
return SizeMatches(info, alignSizes: false);
}
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();
}
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();
}
}
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;
}
private bool TargetAndSamplesCompatible(TextureInfo info)
{
return _info.Target == info.Target &&
_info.SamplesInX == info.SamplesInX &&
_info.SamplesInY == info.SamplesInY;
}
public bool IsViewCompatible(TextureInfo info, ulong size, 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))
{
return false;
}
if (!ViewTargetCompatible(info))
{
return false;
}
return _info.SamplesInX == info.SamplesInX &&
_info.SamplesInY == info.SamplesInY;
}
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;
}
}
private bool ViewFormatCompatible(TextureInfo info)
{
return TextureCompatibility.FormatCompatible(_info.FormatInfo, info.FormatInfo);
}
private bool ViewSizeMatches(TextureInfo info, int level)
{
Size size = GetAlignedSize(_info, level);
Size otherSize = GetAlignedSize(info);
return size.Width == otherSize.Width &&
size.Height == otherSize.Height &&
size.Depth == otherSize.Depth;
}
private bool ViewTargetCompatible(TextureInfo info)
{
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;
}
return false;
}
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);
}
}
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;
}
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;
}
public void ReplaceView(Texture parent, TextureInfo info, ITexture hostTexture)
{
ReplaceStorage(hostTexture);
parent._viewStorage.AddView(this);
SetInfo(info);
}
private void SetInfo(TextureInfo info)
{
_info = info;
_depth = info.GetDepth();
_layers = info.GetLayers();
}
private void ReplaceStorage(ITexture hostTexture)
{
DisposeTextures();
HostTexture = hostTexture;
}
public bool OverlapsWith(ulong address, ulong size)
{
return Address < address + size && address < EndAddress;
}
public void Invalidate()
{
// _hasData = false;
}
public void IncrementReferenceCount()
{
_referenceCount++;
}
public void DecrementReferenceCount()
{
if (--_referenceCount == 0)
{
if (_viewStorage != this)
{
_viewStorage.RemoveView(this);
}
_context.Methods.TextureManager.RemoveTextureFromCache(this);
DisposeTextures();
}
}
private void DisposeTextures()
{
HostTexture.Dispose();
_arrayViewTexture?.Dispose();
_arrayViewTexture = null;
}
}
}

View file

@ -0,0 +1,17 @@
using Ryujinx.Graphics.GAL.Texture;
namespace Ryujinx.Graphics.Gpu.Image
{
struct TextureBindingInfo
{
public Target Target { get; }
public int Handle { get; }
public TextureBindingInfo(Target target, int handle)
{
Target = target;
Handle = handle;
}
}
}

View file

@ -0,0 +1,95 @@
using Ryujinx.Graphics.GAL;
namespace Ryujinx.Graphics.Gpu.Image
{
static class TextureCompatibility
{
private enum FormatClass
{
Unclassified,
BCn64,
BCn128,
Bc1Rgb,
Bc1Rgba,
Bc2,
Bc3,
Bc4,
Bc5,
Bc6,
Bc7
}
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;
}
}
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;
}
private static bool IsDsFormat(Format format)
{
switch (format)
{
case Format.D16Unorm:
case Format.D24X8Unorm:
case Format.D24UnormS8Uint:
case Format.D32Float:
case Format.D32FloatS8Uint:
return true;
}
return false;
}
}
}

View file

@ -0,0 +1,35 @@
using Ryujinx.Graphics.GAL.Texture;
namespace Ryujinx.Graphics.Gpu.Image
{
enum TextureComponent
{
Zero = 0,
Red = 2,
Green = 3,
Blue = 4,
Alpha = 5,
OneSI = 6,
OneF = 7
}
static class TextureComponentConverter
{
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;
}
}
}

View file

@ -0,0 +1,119 @@
namespace Ryujinx.Graphics.Gpu.Image
{
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;
public uint UnpackFormat()
{
return Word0 & 0x8007ffff;
}
public TextureComponent UnpackSwizzleR()
{
return(TextureComponent)((Word0 >> 19) & 7);
}
public TextureComponent UnpackSwizzleG()
{
return(TextureComponent)((Word0 >> 22) & 7);
}
public TextureComponent UnpackSwizzleB()
{
return(TextureComponent)((Word0 >> 25) & 7);
}
public TextureComponent UnpackSwizzleA()
{
return(TextureComponent)((Word0 >> 28) & 7);
}
public ulong UnpackAddress()
{
return Word1 | ((ulong)(Word2 & 0xffff) << 32);
}
public TextureDescriptorType UnpackTextureDescriptorType()
{
return (TextureDescriptorType)((Word2 >> 21) & 7);
}
public int UnpackStride()
{
return (int)(Word3 & 0xffff) << 5;
}
public int UnpackGobBlocksInX()
{
return 1 << (int)(Word3 & 7);
}
public int UnpackGobBlocksInY()
{
return 1 << (int)((Word3 >> 3) & 7);
}
public int UnpackGobBlocksInZ()
{
return 1 << (int)((Word3 >> 6) & 7);
}
public int UnpackGobBlocksInTileX()
{
return 1 << (int)((Word3 >> 10) & 7);
}
public int UnpackLevels()
{
return (int)(Word3 >> 28) + 1;
}
public int UnpackWidth()
{
return (int)(Word4 & 0xffff) + 1;
}
public bool UnpackSrgb()
{
return (Word4 & (1 << 22)) != 0;
}
public TextureTarget UnpackTextureTarget()
{
return (TextureTarget)((Word4 >> 23) & 0xf);
}
public int UnpackHeight()
{
return (int)(Word5 & 0xffff) + 1;
}
public int UnpackDepth()
{
return (int)((Word5 >> 16) & 0x3fff) + 1;
}
public int UnpackBaseLevel()
{
return (int)(Word7 & 0xf);
}
public int UnpackMaxLevelInclusive()
{
return (int)((Word7 >> 4) & 0xf);
}
public TextureMsaaMode UnpackTextureMsaaMode()
{
return (TextureMsaaMode)((Word7 >> 8) & 0xf);
}
}
}

View file

@ -0,0 +1,11 @@
namespace Ryujinx.Graphics.Gpu.Image
{
enum TextureDescriptorType
{
Buffer,
LinearColorKey,
Linear,
BlockLinear,
BlockLinearColorKey
}
}

View file

@ -0,0 +1,101 @@
using Ryujinx.Graphics.GAL.Texture;
namespace Ryujinx.Graphics.Gpu.Image
{
struct TextureInfo
{
public ulong Address { get; }
public int Width { get; }
public int Height { get; }
public int DepthOrLayers { get; }
public int Levels { get; }
public int SamplesInX { get; }
public int SamplesInY { get; }
public int Stride { get; }
public bool IsLinear { get; }
public int GobBlocksInY { get; }
public int GobBlocksInZ { get; }
public int GobBlocksInTileX { get; }
public int Samples => SamplesInX * SamplesInY;
public Target Target { get; }
public FormatInfo FormatInfo { get; }
public DepthStencilMode DepthStencilMode { get; }
public SwizzleComponent SwizzleR { get; }
public SwizzleComponent SwizzleG { get; }
public SwizzleComponent SwizzleB { get; }
public SwizzleComponent SwizzleA { get; }
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;
}
public int GetDepth()
{
return Target == Target.Texture3D ? DepthOrLayers : 1;
}
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;
}
}
}
}

View file

@ -0,0 +1,669 @@
using Ryujinx.Common;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.GAL.Texture;
using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Gpu.State;
using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Texture;
using System;
namespace Ryujinx.Graphics.Gpu.Image
{
class TextureManager
{
private GpuContext _context;
private BufferManager _bufferManager;
private SamplerPool _samplerPool;
private ulong _texturePoolAddress;
private int _texturePoolMaximumId;
private TexturePoolCache _texturePoolCache;
private Texture[] _rtColors;
private Texture _rtColor3D;
private Texture _rtDepthStencil;
private ITexture[] _rtHostColors;
private ITexture _rtHostDs;
private RangeList<Texture> _textures;
private AutoDeleteCache _cache;
private TextureBindingInfo[][] _bindings;
private struct TextureStatePerStage
{
public ITexture Texture;
public ISampler Sampler;
}
private TextureStatePerStage[][] _textureState;
private int _textureBufferIndex;
public TextureManager(GpuContext context, BufferManager bufferManager)
{
_context = context;
_bufferManager = bufferManager;
_texturePoolCache = new TexturePoolCache(context, this);
_rtColors = new Texture[Constants.TotalRenderTargets];
_rtHostColors = new ITexture[Constants.TotalRenderTargets];
_textures = new RangeList<Texture>();
_cache = new AutoDeleteCache();
_bindings = new TextureBindingInfo[Constants.TotalShaderStages][];
_textureState = new TextureStatePerStage[Constants.TotalShaderStages][];
}
public void BindTextures(int stage, TextureBindingInfo[] bindings)
{
_bindings[stage] = bindings;
_textureState[stage] = new TextureStatePerStage[bindings.Length];
}
public void SetTextureBufferIndex(int index)
{
_textureBufferIndex = index;
}
public void SetSamplerPool(ulong gpuVa, int maximumId)
{
ulong address = _context.MemoryManager.Translate(gpuVa);
if (_samplerPool != null)
{
if (_samplerPool.Address == address)
{
return;
}
_samplerPool.Dispose();
}
_samplerPool = new SamplerPool(_context, address, maximumId);
}
public void SetTexturePool(ulong gpuVa, int maximumId)
{
ulong address = _context.MemoryManager.Translate(gpuVa);
_texturePoolAddress = address;
_texturePoolMaximumId = maximumId;
}
public void SetRenderTargetColor(int index, Texture color)
{
_rtColors[index] = color;
_rtColor3D = null;
}
public void SetRenderTargetColor3D(Texture color)
{
_rtColor3D = color;
}
public void SetRenderTargetDepthStencil(Texture depthStencil)
{
_rtDepthStencil = depthStencil;
}
public void CommitBindings()
{
UpdateTextures();
UpdateRenderTargets();
}
private void UpdateTextures()
{
TexturePool texturePool = _texturePoolCache.FindOrCreate(
_texturePoolAddress,
_texturePoolMaximumId);
for (ShaderStage stage = ShaderStage.Vertex; stage <= ShaderStage.Fragment; stage++)
{
int stageIndex = (int)stage - 1;
if (_bindings[stageIndex] == null)
{
continue;
}
for (int index = 0; index < _bindings[stageIndex].Length; index++)
{
TextureBindingInfo binding = _bindings[stageIndex][index];
int packedId = ReadPackedId(stageIndex, binding.Handle);
int textureId = (packedId >> 0) & 0xfffff;
int samplerId = (packedId >> 20) & 0xfff;
Texture texture = texturePool.Get(textureId);
ITexture hostTexture = texture?.GetTargetTexture(binding.Target);
if (_textureState[stageIndex][index].Texture != hostTexture)
{
_textureState[stageIndex][index].Texture = hostTexture;
_context.Renderer.GraphicsPipeline.BindTexture(index, stage, hostTexture);
}
Sampler sampler = _samplerPool.Get(samplerId);
ISampler hostSampler = sampler?.HostSampler;
if (_textureState[stageIndex][index].Sampler != hostSampler)
{
_textureState[stageIndex][index].Sampler = hostSampler;
_context.Renderer.GraphicsPipeline.BindSampler(index, stage, hostSampler);
}
}
}
}
private void UpdateRenderTargets()
{
bool anyChanged = false;
if (_rtHostDs != _rtDepthStencil?.HostTexture)
{
_rtHostDs = _rtDepthStencil?.HostTexture;
anyChanged = true;
}
if (_rtColor3D == null)
{
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.GraphicsPipeline.SetRenderTargets(_rtHostColors, _rtHostDs);
}
}
else
{
if (_rtHostColors[0] != _rtColor3D.HostTexture)
{
_rtHostColors[0] = _rtColor3D.HostTexture;
anyChanged = true;
}
if (anyChanged)
{
_context.Renderer.GraphicsPipeline.SetRenderTargets(_rtColor3D.HostTexture, _rtHostDs);
}
}
}
private int ReadPackedId(int stage, int wordOffset)
{
ulong address = _bufferManager.GetGraphicsUniformBufferAddress(stage, _textureBufferIndex);
address += (uint)wordOffset * 4;
return BitConverter.ToInt32(_context.PhysicalMemory.Read(address, 4));
}
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();
TextureInfo info = new TextureInfo(
address,
copyTexture.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;
}
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;
}
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;
}
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.
Texture[] sameAddressOverlaps = _textures.FindOverlaps(info.Address);
foreach (Texture overlap in sameAddressOverlaps)
{
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;
Texture[] overlaps = _textures.FindOverlaps(info.Address, size);
Texture texture = null;
foreach (Texture overlap in overlaps)
{
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();
foreach (Texture overlap in overlaps)
{
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);
overlap.ReplaceView(texture, overlapInfo, newView);
}
}
}
// 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);
return texture;
}
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);
}
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);
}
public Texture Find2(ulong address)
{
Texture[] ts = _textures.FindOverlaps(address, 1);
if (ts.Length == 2)
{
return ts[1];
}
if (ts.Length == 0)
{
ts = _textures.FindOverlaps(address - 1, 2);
}
if (ts.Length == 0)
{
return null;
}
return ts[0];
}
public void InvalidateRange(ulong address, ulong size)
{
Texture[] overlaps = _textures.FindOverlaps(address, size);
foreach (Texture overlap in overlaps)
{
overlap.Invalidate();
}
_samplerPool?.InvalidateRange(address, size);
_texturePoolCache.InvalidateRange(address, size);
}
public void Flush()
{
foreach (Texture texture in _cache)
{
if (texture.Info.IsLinear && texture.Modified)
{
texture.Flush();
texture.Modified = false;
}
}
}
public void RemoveTextureFromCache(Texture texture)
{
_textures.Remove(texture);
}
}
}

View file

@ -0,0 +1,53 @@
namespace Ryujinx.Graphics.Gpu.Image
{
enum TextureMsaaMode
{
Ms1x1 = 0,
Ms2x2 = 2,
Ms4x2 = 4,
Ms2x1 = 5,
Ms4x4 = 6
}
static class TextureMsaaModeConverter
{
public static int SamplesCount(this TextureMsaaMode msaaMode)
{
switch (msaaMode)
{
case TextureMsaaMode.Ms2x1: return 2;
case TextureMsaaMode.Ms2x2: return 4;
case TextureMsaaMode.Ms4x2: return 8;
case TextureMsaaMode.Ms4x4: return 16;
}
return 1;
}
public static int SamplesInX(this TextureMsaaMode msaaMode)
{
switch (msaaMode)
{
case TextureMsaaMode.Ms2x1: return 2;
case TextureMsaaMode.Ms2x2: return 2;
case TextureMsaaMode.Ms4x2: return 4;
case TextureMsaaMode.Ms4x4: return 4;
}
return 1;
}
public static int SamplesInY(this TextureMsaaMode msaaMode)
{
switch (msaaMode)
{
case TextureMsaaMode.Ms2x1: return 1;
case TextureMsaaMode.Ms2x2: return 2;
case TextureMsaaMode.Ms4x2: return 2;
case TextureMsaaMode.Ms4x4: return 4;
}
return 1;
}
}
}

View file

@ -0,0 +1,219 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.GAL.Texture;
using Ryujinx.Graphics.Gpu.Memory;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Gpu.Image
{
class TexturePool : Pool<Texture>
{
private TextureManager _textureManager;
public LinkedListNode<TexturePool> CacheNode { get; set; }
private struct TextureContainer
{
public Texture Texture0 { get; set; }
public Texture Texture1 { get; set; }
}
public TexturePool(
GpuContext context,
TextureManager textureManager,
ulong address,
int maximumId) : base(context, address, maximumId)
{
_textureManager = textureManager;
}
public override Texture Get(int id)
{
if ((uint)id >= Items.Length)
{
return null;
}
SynchronizeMemory();
Texture texture = Items[id];
if (texture == null)
{
ulong address = Address + (ulong)(uint)id * DescriptorSize;
Span<byte> data = Context.PhysicalMemory.Read(address, DescriptorSize);
TextureDescriptor descriptor = MemoryMarshal.Cast<byte, TextureDescriptor>(data)[0];
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 = _textureManager.FindOrCreateTexture(info, TextureSearchFlags.Sampler);
texture.IncrementReferenceCount();
Items[id] = texture;
}
else
{
// Memory is automatically synchronized on texture creation.
texture.SynchronizeMemory();
}
return texture;
}
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<byte> data = Context.PhysicalMemory.Read(address, DescriptorSize);
TextureDescriptor descriptor = MemoryMarshal.Cast<byte, TextureDescriptor>(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;
}
}
}
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))
{
// TODO: Warning.
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);
}
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;
}
}
private static bool IsRG(SwizzleComponent component)
{
return component == SwizzleComponent.Red ||
component == SwizzleComponent.Green;
}
protected override void Delete(Texture item)
{
item?.DecrementReferenceCount();
}
}
}

View file

@ -0,0 +1,73 @@
using System.Collections.Generic;
namespace Ryujinx.Graphics.Gpu.Image
{
class TexturePoolCache
{
private const int MaxCapacity = 4;
private GpuContext _context;
private TextureManager _textureManager;
private LinkedList<TexturePool> _pools;
public TexturePoolCache(GpuContext context, TextureManager textureManager)
{
_context = context;
_textureManager = textureManager;
_pools = new LinkedList<TexturePool>();
}
public TexturePool FindOrCreate(ulong address, int maximumId)
{
TexturePool pool;
// First we try to find the pool.
for (LinkedListNode<TexturePool> 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, _textureManager, 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;
}
public void InvalidateRange(ulong address, ulong size)
{
for (LinkedListNode<TexturePool> node = _pools.First; node != null; node = node.Next)
{
TexturePool pool = node.Value;
pool.InvalidateRange(address, size);
}
}
}
}

View file

@ -0,0 +1,13 @@
using System;
namespace Ryujinx.Graphics.Gpu.Image
{
[Flags]
enum TextureSearchFlags
{
None = 0,
IgnoreMs = 1 << 0,
Strict = 1 << 1 | Sampler,
Sampler = 1 << 2
}
}

View file

@ -0,0 +1,49 @@
using Ryujinx.Graphics.GAL.Texture;
namespace Ryujinx.Graphics.Gpu.Image
{
enum TextureTarget
{
Texture1D,
Texture2D,
Texture3D,
Cubemap,
Texture1DArray,
Texture2DArray,
TextureBuffer,
Texture2DLinear,
CubemapArray
}
static class TextureTargetConverter
{
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.Texture2DLinear: 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;
}
}
}

View file

@ -1,9 +1,8 @@
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Graphics.Memory;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Ryujinx.Graphics.Graphics3d namespace Ryujinx.Graphics.Gpu
{ {
class MacroInterpreter class MacroInterpreter
{ {
@ -42,8 +41,9 @@ namespace Ryujinx.Graphics.Graphics3d
BitwiseNotAnd = 12 BitwiseNotAnd = 12
} }
private NvGpuFifo _pFifo; private GpuContext _context;
private INvGpuEngine _engine;
private NvGpuFifo _pFifo;
public Queue<int> Fifo { get; private set; } public Queue<int> Fifo { get; private set; }
@ -60,17 +60,17 @@ namespace Ryujinx.Graphics.Graphics3d
private int _pc; private int _pc;
public MacroInterpreter(NvGpuFifo pFifo, INvGpuEngine engine) public MacroInterpreter(GpuContext context, NvGpuFifo pFifo)
{ {
_pFifo = pFifo; _context = context;
_engine = engine; _pFifo = pFifo;
Fifo = new Queue<int>(); Fifo = new Queue<int>();
_gprs = new int[8]; _gprs = new int[8];
} }
public void Execute(NvGpuVmm vmm, int[] mme, int position, int param) public void Execute(int[] mme, int position, int param)
{ {
Reset(); Reset();
@ -80,11 +80,11 @@ namespace Ryujinx.Graphics.Graphics3d
FetchOpCode(mme); FetchOpCode(mme);
while (Step(vmm, mme)); while (Step(mme));
// Due to the delay slot, we still need to execute // Due to the delay slot, we still need to execute
// one more instruction before we actually exit. // one more instruction before we actually exit.
Step(vmm, mme); Step(mme);
} }
private void Reset() private void Reset()
@ -100,7 +100,7 @@ namespace Ryujinx.Graphics.Graphics3d
_carry = false; _carry = false;
} }
private bool Step(NvGpuVmm vmm, int[] mme) private bool Step(int[] mme)
{ {
int baseAddr = _pc - 1; int baseAddr = _pc - 1;
@ -146,7 +146,7 @@ namespace Ryujinx.Graphics.Graphics3d
{ {
SetDstGpr(FetchParam()); SetDstGpr(FetchParam());
Send(vmm, result); Send(result);
break; break;
} }
@ -156,7 +156,7 @@ namespace Ryujinx.Graphics.Graphics3d
{ {
SetDstGpr(result); SetDstGpr(result);
Send(vmm, result); Send(result);
break; break;
} }
@ -178,7 +178,7 @@ namespace Ryujinx.Graphics.Graphics3d
SetMethAddr(result); SetMethAddr(result);
Send(vmm, FetchParam()); Send(FetchParam());
break; break;
} }
@ -190,7 +190,7 @@ namespace Ryujinx.Graphics.Graphics3d
SetMethAddr(result); SetMethAddr(result);
Send(vmm, (result >> 12) & 0x3f); Send((result >> 12) & 0x3f);
break; break;
} }
@ -228,7 +228,6 @@ namespace Ryujinx.Graphics.Graphics3d
private void FetchOpCode(int[] mme) private void FetchOpCode(int[] mme)
{ {
_opCode = _pipeOp; _opCode = _pipeOp;
_pipeOp = mme[_pc++]; _pipeOp = mme[_pc++];
} }
@ -401,14 +400,14 @@ namespace Ryujinx.Graphics.Graphics3d
private int Read(int reg) private int Read(int reg)
{ {
return _engine.Registers[reg]; return _context.State.Read(reg);
} }
private void Send(NvGpuVmm vmm, int value) private void Send(int value)
{ {
GpuMethodCall methCall = new GpuMethodCall(_methAddr, value); MethodParams meth = new MethodParams(_methAddr, value);
_engine.CallMethod(vmm, methCall); _context.State.CallMethod(meth);
_methAddr += _methIncr; _methAddr += _methIncr;
} }

View file

@ -0,0 +1,99 @@
using Ryujinx.Graphics.GAL;
using System;
namespace Ryujinx.Graphics.Gpu.Memory
{
class Buffer : IRange<Buffer>, IDisposable
{
private GpuContext _context;
private IBuffer _buffer;
public ulong Address { get; }
public ulong Size { get; }
public ulong EndAddress => Address + Size;
private int[] _sequenceNumbers;
public Buffer(GpuContext context, ulong address, ulong size)
{
_context = context;
Address = address;
Size = size;
_buffer = context.Renderer.CreateBuffer((int)size);
_sequenceNumbers = new int[size / MemoryManager.PageSize];
Invalidate();
}
public BufferRange GetRange(ulong address, ulong size)
{
int offset = (int)(address - Address);
return new BufferRange(_buffer, offset, (int)size);
}
public bool OverlapsWith(ulong address, ulong size)
{
return Address < address + size && address < EndAddress;
}
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);
for (int index = 0; index < modifiedRanges.Length; index++)
{
(ulong mAddress, ulong mSize) = modifiedRanges[index];
int offset = (int)(mAddress - Address);
_buffer.SetData(offset, _context.PhysicalMemory.Read(mAddress, mSize));
}
}
public void CopyTo(Buffer destination, int dstOffset)
{
_buffer.CopyTo(destination._buffer, 0, dstOffset, (int)Size);
}
public void Invalidate()
{
_buffer.SetData(0, _context.PhysicalMemory.Read(Address, Size));
}
public void Dispose()
{
_buffer.Dispose();
}
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.Graphics.Gpu.Memory
{
struct BufferBounds
{
public ulong Address;
public ulong Size;
}
}

Some files were not shown because too many files have changed in this diff Show more