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

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

@ -0,0 +1,181 @@
using System.Collections.Concurrent;
using System.Threading;
namespace Ryujinx.Graphics.Gpu
{
public class DmaPusher
{
private ConcurrentQueue<ulong> _ibBuffer;
private ulong _dmaPut;
private ulong _dmaGet;
private struct DmaState
{
public int Method;
public int SubChannel;
public int MethodCount;
public bool NonIncrementing;
public bool IncrementOnce;
public int LengthPending;
}
private DmaState _state;
private bool _sliEnable;
private bool _sliActive;
private bool _ibEnable;
private bool _nonMain;
private ulong _dmaMGet;
private GpuContext _context;
private AutoResetEvent _event;
internal DmaPusher(GpuContext context)
{
_context = context;
_ibBuffer = new ConcurrentQueue<ulong>();
_ibEnable = true;
_event = new AutoResetEvent(false);
}
public void Push(ulong entry)
{
_ibBuffer.Enqueue(entry);
_event.Set();
}
public bool WaitForCommands()
{
return _event.WaitOne(8);
}
public void DispatchCalls()
{
while (Step());
}
private bool Step()
{
if (_dmaGet != _dmaPut)
{
int word = _context.MemoryAccessor.ReadInt32(_dmaGet);
_dmaGet += 4;
if (!_nonMain)
{
_dmaMGet = _dmaGet;
}
if (_state.LengthPending != 0)
{
_state.LengthPending = 0;
_state.MethodCount = word & 0xffffff;
}
else if (_state.MethodCount != 0)
{
if (!_sliEnable || _sliActive)
{
CallMethod(word);
}
if (!_state.NonIncrementing)
{
_state.Method++;
}
if (_state.IncrementOnce)
{
_state.NonIncrementing = true;
}
_state.MethodCount--;
}
else
{
int submissionMode = (word >> 29) & 7;
switch (submissionMode)
{
case 1:
// Incrementing.
SetNonImmediateState(word);
_state.NonIncrementing = false;
_state.IncrementOnce = false;
break;
case 3:
// Non-incrementing.
SetNonImmediateState(word);
_state.NonIncrementing = true;
_state.IncrementOnce = false;
break;
case 4:
// Immediate.
_state.Method = (word >> 0) & 0x1fff;
_state.SubChannel = (word >> 13) & 7;
_state.NonIncrementing = true;
_state.IncrementOnce = false;
CallMethod((word >> 16) & 0x1fff);
break;
case 5:
// Increment-once.
SetNonImmediateState(word);
_state.NonIncrementing = false;
_state.IncrementOnce = true;
break;
}
}
}
else if (_ibEnable && _ibBuffer.TryDequeue(out ulong entry))
{
ulong length = (entry >> 42) & 0x1fffff;
_dmaGet = entry & 0xfffffffffc;
_dmaPut = _dmaGet + length * 4;
_nonMain = (entry & (1UL << 41)) != 0;
}
else
{
return false;
}
return true;
}
private void SetNonImmediateState(int word)
{
_state.Method = (word >> 0) & 0x1fff;
_state.SubChannel = (word >> 13) & 7;
_state.MethodCount = (word >> 16) & 0x1fff;
}
private void CallMethod(int argument)
{
_context.Fifo.CallMethod(new MethodParams(
_state.Method,
argument,
_state.SubChannel,
_state.MethodCount));
}
}
}

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

@ -0,0 +1,126 @@
using System.IO;
namespace Ryujinx.Graphics.Gpu.Engine
{
class ShaderDumper
{
private const int ShaderHeaderSize = 0x50;
private GpuContext _context;
private string _runtimeDir;
private string _dumpPath;
private int _dumpIndex;
public int CurrentDumpIndex => _dumpIndex;
public ShaderDumper(GpuContext context)
{
_context = context;
_dumpIndex = 1;
}
public void Dump(ulong gpuVa, bool compute)
{
_dumpPath = GraphicsConfig.ShadersDumpPath;
if (string.IsNullOrWhiteSpace(_dumpPath))
{
return;
}
string fileName = "Shader" + _dumpIndex.ToString("d4") + ".bin";
string fullPath = Path.Combine(FullDir(), fileName);
string codePath = Path.Combine(CodeDir(), fileName);
_dumpIndex++;
ulong headerSize = compute ? 0UL : ShaderHeaderSize;
using (FileStream fullFile = File.Create(fullPath))
using (FileStream codeFile = File.Create(codePath))
{
BinaryWriter fullWriter = new BinaryWriter(fullFile);
BinaryWriter codeWriter = new BinaryWriter(codeFile);
for (ulong i = 0; i < headerSize; i += 4)
{
fullWriter.Write(_context.MemoryAccessor.ReadInt32(gpuVa + i));
}
ulong offset = 0;
ulong instruction = 0;
// Dump until a NOP instruction is found.
while ((instruction >> 48 & 0xfff8) != 0x50b0)
{
uint word0 = (uint)_context.MemoryAccessor.ReadInt32(gpuVa + headerSize + offset + 0);
uint word1 = (uint)_context.MemoryAccessor.ReadInt32(gpuVa + headerSize + offset + 4);
instruction = word0 | (ulong)word1 << 32;
// Zero instructions (other kind of NOP) stop immediately,
// this is to avoid two rows of zeroes.
if (instruction == 0)
{
break;
}
fullWriter.Write(instruction);
codeWriter.Write(instruction);
offset += 8;
}
// Align to meet nvdisasm requirements.
while (offset % 0x20 != 0)
{
fullWriter.Write(0);
codeWriter.Write(0);
offset += 4;
}
}
}
private string FullDir()
{
return CreateAndReturn(Path.Combine(DumpDir(), "Full"));
}
private string CodeDir()
{
return CreateAndReturn(Path.Combine(DumpDir(), "Code"));
}
private string DumpDir()
{
if (string.IsNullOrEmpty(_runtimeDir))
{
int index = 1;
do
{
_runtimeDir = Path.Combine(_dumpPath, "Dumps" + index.ToString("d2"));
index++;
}
while (Directory.Exists(_runtimeDir));
Directory.CreateDirectory(_runtimeDir);
}
return _runtimeDir;
}
private static string CreateAndReturn(string dir)
{
Directory.CreateDirectory(dir);
return dir;
}
}
}

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

@ -0,0 +1,415 @@
using Ryujinx.Common.Logging;
using System;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Gpu
{
class MacroInterpreter
{
private enum AssignmentOperation
{
IgnoreAndFetch = 0,
Move = 1,
MoveAndSetMaddr = 2,
FetchAndSend = 3,
MoveAndSend = 4,
FetchAndSetMaddr = 5,
MoveAndSetMaddrThenFetchAndSend = 6,
MoveAndSetMaddrThenSendHigh = 7
}
private enum AluOperation
{
AluReg = 0,
AddImmediate = 1,
BitfieldReplace = 2,
BitfieldExtractLslImm = 3,
BitfieldExtractLslReg = 4,
ReadImmediate = 5
}
private enum AluRegOperation
{
Add = 0,
AddWithCarry = 1,
Subtract = 2,
SubtractWithBorrow = 3,
BitwiseExclusiveOr = 8,
BitwiseOr = 9,
BitwiseAnd = 10,
BitwiseAndNot = 11,
BitwiseNotAnd = 12
}
private GpuContext _context;
private NvGpuFifo _pFifo;
public Queue<int> Fifo { get; private set; }
private int[] _gprs;
private int _methAddr;
private int _methIncr;
private bool _carry;
private int _opCode;
private int _pipeOp;
private int _pc;
public MacroInterpreter(GpuContext context, NvGpuFifo pFifo)
{
_context = context;
_pFifo = pFifo;
Fifo = new Queue<int>();
_gprs = new int[8];
}
public void Execute(int[] mme, int position, int param)
{
Reset();
_gprs[1] = param;
_pc = position;
FetchOpCode(mme);
while (Step(mme));
// Due to the delay slot, we still need to execute
// one more instruction before we actually exit.
Step(mme);
}
private void Reset()
{
for (int index = 0; index < _gprs.Length; index++)
{
_gprs[index] = 0;
}
_methAddr = 0;
_methIncr = 0;
_carry = false;
}
private bool Step(int[] mme)
{
int baseAddr = _pc - 1;
FetchOpCode(mme);
if ((_opCode & 7) < 7)
{
// Operation produces a value.
AssignmentOperation asgOp = (AssignmentOperation)((_opCode >> 4) & 7);
int result = GetAluResult();
switch (asgOp)
{
// Fetch parameter and ignore result.
case AssignmentOperation.IgnoreAndFetch:
{
SetDstGpr(FetchParam());
break;
}
// Move result.
case AssignmentOperation.Move:
{
SetDstGpr(result);
break;
}
// Move result and use as Method Address.
case AssignmentOperation.MoveAndSetMaddr:
{
SetDstGpr(result);
SetMethAddr(result);
break;
}
// Fetch parameter and send result.
case AssignmentOperation.FetchAndSend:
{
SetDstGpr(FetchParam());
Send(result);
break;
}
// Move and send result.
case AssignmentOperation.MoveAndSend:
{
SetDstGpr(result);
Send(result);
break;
}
// Fetch parameter and use result as Method Address.
case AssignmentOperation.FetchAndSetMaddr:
{
SetDstGpr(FetchParam());
SetMethAddr(result);
break;
}
// Move result and use as Method Address, then fetch and send parameter.
case AssignmentOperation.MoveAndSetMaddrThenFetchAndSend:
{
SetDstGpr(result);
SetMethAddr(result);
Send(FetchParam());
break;
}
// Move result and use as Method Address, then send bits 17:12 of result.
case AssignmentOperation.MoveAndSetMaddrThenSendHigh:
{
SetDstGpr(result);
SetMethAddr(result);
Send((result >> 12) & 0x3f);
break;
}
}
}
else
{
// Branch.
bool onNotZero = ((_opCode >> 4) & 1) != 0;
bool taken = onNotZero
? GetGprA() != 0
: GetGprA() == 0;
if (taken)
{
_pc = baseAddr + GetImm();
bool noDelays = (_opCode & 0x20) != 0;
if (noDelays)
{
FetchOpCode(mme);
}
return true;
}
}
bool exit = (_opCode & 0x80) != 0;
return !exit;
}
private void FetchOpCode(int[] mme)
{
_opCode = _pipeOp;
_pipeOp = mme[_pc++];
}
private int GetAluResult()
{
AluOperation op = (AluOperation)(_opCode & 7);
switch (op)
{
case AluOperation.AluReg:
{
AluRegOperation aluOp = (AluRegOperation)((_opCode >> 17) & 0x1f);
return GetAluResult(aluOp, GetGprA(), GetGprB());
}
case AluOperation.AddImmediate:
{
return GetGprA() + GetImm();
}
case AluOperation.BitfieldReplace:
case AluOperation.BitfieldExtractLslImm:
case AluOperation.BitfieldExtractLslReg:
{
int bfSrcBit = (_opCode >> 17) & 0x1f;
int bfSize = (_opCode >> 22) & 0x1f;
int bfDstBit = (_opCode >> 27) & 0x1f;
int bfMask = (1 << bfSize) - 1;
int dst = GetGprA();
int src = GetGprB();
switch (op)
{
case AluOperation.BitfieldReplace:
{
src = (int)((uint)src >> bfSrcBit) & bfMask;
dst &= ~(bfMask << bfDstBit);
dst |= src << bfDstBit;
return dst;
}
case AluOperation.BitfieldExtractLslImm:
{
src = (int)((uint)src >> dst) & bfMask;
return src << bfDstBit;
}
case AluOperation.BitfieldExtractLslReg:
{
src = (int)((uint)src >> bfSrcBit) & bfMask;
return src << dst;
}
}
break;
}
case AluOperation.ReadImmediate:
{
return Read(GetGprA() + GetImm());
}
}
throw new ArgumentException(nameof(_opCode));
}
private int GetAluResult(AluRegOperation aluOp, int a, int b)
{
switch (aluOp)
{
case AluRegOperation.Add:
{
ulong result = (ulong)a + (ulong)b;
_carry = result > 0xffffffff;
return (int)result;
}
case AluRegOperation.AddWithCarry:
{
ulong result = (ulong)a + (ulong)b + (_carry ? 1UL : 0UL);
_carry = result > 0xffffffff;
return (int)result;
}
case AluRegOperation.Subtract:
{
ulong result = (ulong)a - (ulong)b;
_carry = result < 0x100000000;
return (int)result;
}
case AluRegOperation.SubtractWithBorrow:
{
ulong result = (ulong)a - (ulong)b - (_carry ? 0UL : 1UL);
_carry = result < 0x100000000;
return (int)result;
}
case AluRegOperation.BitwiseExclusiveOr: return a ^ b;
case AluRegOperation.BitwiseOr: return a | b;
case AluRegOperation.BitwiseAnd: return a & b;
case AluRegOperation.BitwiseAndNot: return a & ~b;
case AluRegOperation.BitwiseNotAnd: return ~(a & b);
}
throw new ArgumentOutOfRangeException(nameof(aluOp));
}
private int GetImm()
{
// Note: The immediate is signed, the sign-extension is intended here.
return _opCode >> 14;
}
private void SetMethAddr(int value)
{
_methAddr = (value >> 0) & 0xfff;
_methIncr = (value >> 12) & 0x3f;
}
private void SetDstGpr(int value)
{
_gprs[(_opCode >> 8) & 7] = value;
}
private int GetGprA()
{
return GetGprValue((_opCode >> 11) & 7);
}
private int GetGprB()
{
return GetGprValue((_opCode >> 14) & 7);
}
private int GetGprValue(int index)
{
return index != 0 ? _gprs[index] : 0;
}
private int FetchParam()
{
int value;
if (!Fifo.TryDequeue(out value))
{
Logger.PrintWarning(LogClass.Gpu, "Macro attempted to fetch an inexistent argument.");
return 0;
}
return value;
}
private int Read(int reg)
{
return _context.State.Read(reg);
}
private void Send(int value)
{
MethodParams meth = new MethodParams(_methAddr, value);
_context.State.CallMethod(meth);
_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;
}
}

View file

@ -0,0 +1,530 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.GAL.InputAssembler;
using Ryujinx.Graphics.Gpu.State;
using Ryujinx.Graphics.Shader;
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Gpu.Memory
{
class BufferManager
{
private const ulong BufferAlignmentSize = 0x1000;
private const ulong BufferAlignmentMask = BufferAlignmentSize - 1;
private GpuContext _context;
private RangeList<Buffer> _buffers;
private IndexBuffer _indexBuffer;
private VertexBuffer[] _vertexBuffers;
private class BuffersPerStage
{
public uint EnableMask { get; set; }
public BufferBounds[] Buffers { get; }
public BuffersPerStage(int count)
{
Buffers = new BufferBounds[count];
}
public void Bind(int index, ulong address, ulong size)
{
Buffers[index].Address = address;
Buffers[index].Size = size;
}
}
private BuffersPerStage _cpStorageBuffers;
private BuffersPerStage _cpUniformBuffers;
private BuffersPerStage[] _gpStorageBuffers;
private BuffersPerStage[] _gpUniformBuffers;
private bool _gpStorageBuffersDirty;
private bool _gpUniformBuffersDirty;
private bool _indexBufferDirty;
private bool _vertexBuffersDirty;
private bool _rebind;
public BufferManager(GpuContext context)
{
_context = context;
_buffers = new RangeList<Buffer>();
_vertexBuffers = new VertexBuffer[Constants.TotalVertexBuffers];
_cpStorageBuffers = new BuffersPerStage(Constants.TotalCpStorageBuffers);
_cpUniformBuffers = new BuffersPerStage(Constants.TotalCpUniformBuffers);
_gpStorageBuffers = new BuffersPerStage[Constants.TotalShaderStages];
_gpUniformBuffers = new BuffersPerStage[Constants.TotalShaderStages];
for (int index = 0; index < Constants.TotalShaderStages; index++)
{
_gpStorageBuffers[index] = new BuffersPerStage(Constants.TotalGpStorageBuffers);
_gpUniformBuffers[index] = new BuffersPerStage(Constants.TotalGpUniformBuffers);
}
}
public void SetIndexBuffer(ulong gpuVa, ulong size, IndexType type)
{
ulong address = TranslateAndCreateBuffer(gpuVa, size);
_indexBuffer.Address = address;
_indexBuffer.Size = size;
_indexBuffer.Type = type;
_indexBufferDirty = true;
}
public void SetVertexBuffer(int index, ulong gpuVa, ulong size, int stride, int divisor)
{
ulong address = TranslateAndCreateBuffer(gpuVa, size);
_vertexBuffers[index].Address = address;
_vertexBuffers[index].Size = size;
_vertexBuffers[index].Stride = stride;
_vertexBuffers[index].Divisor = divisor;
_vertexBuffersDirty = true;
}
public void SetComputeStorageBuffer(int index, ulong gpuVa, ulong size)
{
// TODO: Improve
size += gpuVa & 0x3fUL;
gpuVa &= ~0x3fUL;
ulong address = TranslateAndCreateBuffer(gpuVa, size);
_cpStorageBuffers.Bind(index, address, size);
}
public void SetGraphicsStorageBuffer(int stage, int index, ulong gpuVa, ulong size)
{
// TODO: Improve
size += gpuVa & 0x3fUL;
gpuVa &= ~0x3fUL;
ulong address = TranslateAndCreateBuffer(gpuVa, size);
_gpStorageBuffers[stage].Bind(index, address, size);
_gpStorageBuffersDirty = true;
}
public void SetComputeUniformBuffer(int index, ulong gpuVa, ulong size)
{
ulong address = TranslateAndCreateBuffer(gpuVa, size);
_cpUniformBuffers.Bind(index, address, size);
}
public void SetGraphicsUniformBuffer(int stage, int index, ulong gpuVa, ulong size)
{
ulong address = TranslateAndCreateBuffer(gpuVa, size);
_gpUniformBuffers[stage].Bind(index, address, size);
_gpUniformBuffersDirty = true;
}
public void SetComputeStorageBufferEnableMask(uint mask)
{
_cpStorageBuffers.EnableMask = mask;
}
public void SetGraphicsStorageBufferEnableMask(int stage, uint mask)
{
_gpStorageBuffers[stage].EnableMask = mask;
_gpStorageBuffersDirty = true;
}
public void SetComputeUniformBufferEnableMask(uint mask)
{
_cpUniformBuffers.EnableMask = mask;
}
public void SetGraphicsUniformBufferEnableMask(int stage, uint mask)
{
_gpUniformBuffers[stage].EnableMask = mask;
_gpUniformBuffersDirty = true;
}
private ulong TranslateAndCreateBuffer(ulong gpuVa, ulong size)
{
if (gpuVa == 0)
{
return 0;
}
ulong address = _context.MemoryManager.Translate(gpuVa);
if (address == MemoryManager.BadAddress)
{
return 0;
}
ulong endAddress = address + size;
ulong alignedAddress = address & ~BufferAlignmentMask;
ulong alignedEndAddress = (endAddress + BufferAlignmentMask) & ~BufferAlignmentMask;
// The buffer must have the size of at least one page.
if (alignedEndAddress == alignedAddress)
{
alignedEndAddress += BufferAlignmentSize;
}
CreateBuffer(alignedAddress, alignedEndAddress - alignedAddress);
return address;
}
private void CreateBuffer(ulong address, ulong size)
{
Buffer[] overlaps = _buffers.FindOverlaps(address, size);
if (overlaps.Length != 0)
{
// The buffer already exists. We can just return the existing buffer
// if the buffer we need is fully contained inside the overlapping buffer.
// Otherwise, we must delete the overlapping buffers and create a bigger buffer
// that fits all the data we need. We also need to copy the contents from the
// old buffer(s) to the new buffer.
ulong endAddress = address + size;
if (overlaps[0].Address > address || overlaps[0].EndAddress < endAddress)
{
foreach (Buffer buffer in overlaps)
{
address = Math.Min(address, buffer.Address);
endAddress = Math.Max(endAddress, buffer.EndAddress);
buffer.SynchronizeMemory(buffer.Address, buffer.Size);
_buffers.Remove(buffer);
}
Buffer newBuffer = new Buffer(_context, address, endAddress - address);
_buffers.Add(newBuffer);
foreach (Buffer buffer in overlaps)
{
int dstOffset = (int)(buffer.Address - newBuffer.Address);
buffer.CopyTo(newBuffer, dstOffset);
buffer.Dispose();
}
_rebind = true;
}
}
else
{
// No overlap, just create a new buffer.
Buffer buffer = new Buffer(_context, address, size);
_buffers.Add(buffer);
}
}
public ulong GetComputeUniformBufferAddress(int index)
{
return _cpUniformBuffers.Buffers[index].Address;
}
public ulong GetGraphicsUniformBufferAddress(int stage, int index)
{
return _gpUniformBuffers[stage].Buffers[index].Address;
}
public void CommitComputeBindings()
{
uint enableMask = _cpStorageBuffers.EnableMask;
for (int index = 0; (enableMask >> index) != 0; index++)
{
if ((enableMask & (1u << index)) == 0)
{
continue;
}
BufferBounds bounds = _cpStorageBuffers.Buffers[index];
if (bounds.Address == 0)
{
continue;
}
BufferRange buffer = GetBufferRange(bounds.Address, bounds.Size);
_context.Renderer.ComputePipeline.SetStorageBuffer(index, buffer);
}
enableMask = _cpUniformBuffers.EnableMask;
for (int index = 0; (enableMask >> index) != 0; index++)
{
if ((enableMask & (1u << index)) == 0)
{
continue;
}
BufferBounds bounds = _cpUniformBuffers.Buffers[index];
if (bounds.Address == 0)
{
continue;
}
BufferRange buffer = GetBufferRange(bounds.Address, bounds.Size);
_context.Renderer.ComputePipeline.SetUniformBuffer(index, buffer);
if (index == 0)
{
// TODO: Improve
Span<byte> data = _context.PhysicalMemory.Read(bounds.Address + 0x310, 0x100);
Span<int> words = MemoryMarshal.Cast<byte, int>(data);
for (int offset = 0; offset < 0x40; offset += 4)
{
words[offset] &= 0x3f;
}
buffer = GetBufferRange(bounds.Address + 0x310, 0x100);
buffer.Buffer.SetData(buffer.Offset, data);
}
}
}
public void CommitBindings()
{
if (_indexBufferDirty || _rebind)
{
_indexBufferDirty = false;
if (_indexBuffer.Address != 0)
{
BufferRange buffer = GetBufferRange(_indexBuffer.Address, _indexBuffer.Size);
_context.Renderer.GraphicsPipeline.BindIndexBuffer(buffer, _indexBuffer.Type);
}
}
else if (_indexBuffer.Address != 0)
{
SynchronizeBufferRange(_indexBuffer.Address, _indexBuffer.Size);
}
if (_vertexBuffersDirty || _rebind)
{
_vertexBuffersDirty = false;
VertexBufferDescriptor[] vertexBuffers = new VertexBufferDescriptor[Constants.TotalVertexBuffers];
for (int index = 0; index < Constants.TotalVertexBuffers; index++)
{
VertexBuffer vb = _vertexBuffers[index];
if (vb.Address == 0)
{
continue;
}
BufferRange buffer = GetBufferRange(vb.Address, vb.Size);
vertexBuffers[index] = new VertexBufferDescriptor(buffer, vb.Stride, vb.Divisor);
}
_context.Renderer.GraphicsPipeline.BindVertexBuffers(vertexBuffers);
}
else
{
for (int index = 0; index < Constants.TotalVertexBuffers; index++)
{
VertexBuffer vb = _vertexBuffers[index];
if (vb.Address == 0)
{
continue;
}
SynchronizeBufferRange(vb.Address, vb.Size);
}
}
if (_gpStorageBuffersDirty || _rebind)
{
_gpStorageBuffersDirty = false;
BindBuffers(_gpStorageBuffers, isStorage: true);
}
else
{
UpdateBuffers(_gpStorageBuffers);
}
if (_gpUniformBuffersDirty || _rebind)
{
_gpUniformBuffersDirty = false;
BindBuffers(_gpUniformBuffers, isStorage: false);
}
else
{
UpdateBuffers(_gpUniformBuffers);
}
_rebind = false;
}
private void BindBuffers(BuffersPerStage[] bindings, bool isStorage)
{
BindOrUpdateBuffers(bindings, bind: true, isStorage);
}
private void UpdateBuffers(BuffersPerStage[] bindings)
{
BindOrUpdateBuffers(bindings, bind: false);
}
private void BindOrUpdateBuffers(BuffersPerStage[] bindings, bool bind, bool isStorage = false)
{
for (ShaderStage stage = ShaderStage.Vertex; stage <= ShaderStage.Fragment; stage++)
{
uint enableMask = bindings[(int)stage - 1].EnableMask;
if (enableMask == 0)
{
continue;
}
for (int index = 0; (enableMask >> index) != 0; index++)
{
if ((enableMask & (1u << index)) == 0)
{
continue;
}
BufferBounds bounds = bindings[(int)stage - 1].Buffers[index];
if (bounds.Address == 0)
{
continue;
}
if (bind)
{
BindBuffer(index, stage, bounds, isStorage);
}
else
{
SynchronizeBufferRange(bounds.Address, bounds.Size);
}
}
}
}
private void BindBuffer(int index, ShaderStage stage, BufferBounds bounds, bool isStorage)
{
BufferRange buffer = GetBufferRange(bounds.Address, bounds.Size);
BufferRange[] buffers = new BufferRange[] { buffer };
if (isStorage)
{
_context.Renderer.GraphicsPipeline.BindStorageBuffers(index, stage, buffers);
}
else
{
_context.Renderer.GraphicsPipeline.BindUniformBuffers(index, stage, buffers);
}
if (!isStorage && index == 0)
{
// TODO: Improve
Span<byte> data = _context.PhysicalMemory.Read(bounds.Address + 0x110, 0x100);
Span<int> words = MemoryMarshal.Cast<byte, int>(data);
for (int offset = 0; offset < 0x40; offset += 4)
{
words[offset] &= 0x3f;
}
buffer = GetBufferRange(bounds.Address + 0x110, 0x100);
buffer.Buffer.SetData(buffer.Offset, data);
}
}
public void CopyBuffer(GpuVa srcVa, GpuVa dstVa, ulong size)
{
ulong srcAddress = TranslateAndCreateBuffer(srcVa.Pack(), size);
ulong dstAddress = TranslateAndCreateBuffer(dstVa.Pack(), size);
BufferRange srcBuffer = GetBufferRange(srcAddress, size);
BufferRange dstBuffer = GetBufferRange(dstAddress, size);
srcBuffer.Buffer.CopyTo(
dstBuffer.Buffer,
srcBuffer.Offset,
dstBuffer.Offset,
(int)size);
}
private BufferRange GetBufferRange(ulong address, ulong size)
{
Buffer buffer;
if (size != 0)
{
buffer = _buffers.FindFirstOverlap(address, size);
buffer.SynchronizeMemory(address, size);
}
else
{
buffer = _buffers.FindFirstOverlap(address, 1);
}
return buffer.GetRange(address, size);
}
private void SynchronizeBufferRange(ulong address, ulong size)
{
if (size != 0)
{
Buffer buffer = _buffers.FindFirstOverlap(address, size);
buffer.SynchronizeMemory(address, size);
}
}
public void InvalidateRange(ulong address, ulong size)
{
Buffer[] overlappingBuffers = _buffers.FindOverlaps(address, size);
foreach (Buffer buffer in overlappingBuffers)
{
buffer.Invalidate();
}
}
}
}

View file

@ -0,0 +1,15 @@
using System;
namespace Ryujinx.Graphics.Gpu.Memory
{
public interface IPhysicalMemory
{
int GetPageSize();
Span<byte> Read(ulong address, ulong size);
void Write(ulong address, Span<byte> data);
(ulong, ulong)[] GetModifiedRanges(ulong address, ulong size);
}
}

View file

@ -0,0 +1,10 @@
namespace Ryujinx.Graphics.Gpu.Memory
{
interface IRange<T>
{
ulong Address { get; }
ulong Size { get; }
bool OverlapsWith(ulong address, ulong size);
}
}

View file

@ -0,0 +1,12 @@
using Ryujinx.Graphics.GAL;
namespace Ryujinx.Graphics.Gpu.Memory
{
struct IndexBuffer
{
public ulong Address;
public ulong Size;
public IndexType Type;
}
}

View file

@ -0,0 +1,54 @@
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Gpu.Memory
{
class MemoryAccessor
{
private GpuContext _context;
public MemoryAccessor(GpuContext context)
{
_context = context;
}
public Span<byte> Read(ulong gpuVa, ulong maxSize)
{
ulong processVa = _context.MemoryManager.Translate(gpuVa);
ulong size = Math.Min(_context.MemoryManager.GetSubSize(gpuVa), maxSize);
return _context.PhysicalMemory.Read(processVa, size);
}
public T Read<T>(ulong gpuVa) where T : struct
{
ulong processVa = _context.MemoryManager.Translate(gpuVa);
ulong size = (uint)Marshal.SizeOf<T>();
return MemoryMarshal.Cast<byte, T>(_context.PhysicalMemory.Read(processVa, size))[0];
}
public int ReadInt32(ulong gpuVa)
{
ulong processVa = _context.MemoryManager.Translate(gpuVa);
return BitConverter.ToInt32(_context.PhysicalMemory.Read(processVa, 4));
}
public void Write(ulong gpuVa, int value)
{
ulong processVa = _context.MemoryManager.Translate(gpuVa);
_context.PhysicalMemory.Write(processVa, BitConverter.GetBytes(value));
}
public void Write(ulong gpuVa, Span<byte> data)
{
ulong processVa = _context.MemoryManager.Translate(gpuVa);
_context.PhysicalMemory.Write(processVa, data);
}
}
}

View file

@ -0,0 +1,265 @@
namespace Ryujinx.Graphics.Gpu.Memory
{
public class MemoryManager
{
private const ulong AddressSpaceSize = 1UL << 40;
public const ulong BadAddress = ulong.MaxValue;
private const int PtLvl0Bits = 14;
private const int PtLvl1Bits = 14;
private const int PtPageBits = 12;
private const ulong PtLvl0Size = 1UL << PtLvl0Bits;
private const ulong PtLvl1Size = 1UL << PtLvl1Bits;
public const ulong PageSize = 1UL << PtPageBits;
private const ulong PtLvl0Mask = PtLvl0Size - 1;
private const ulong PtLvl1Mask = PtLvl1Size - 1;
public const ulong PageMask = PageSize - 1;
private const int PtLvl0Bit = PtPageBits + PtLvl1Bits;
private const int PtLvl1Bit = PtPageBits;
private const ulong PteUnmapped = 0xffffffff_ffffffff;
private const ulong PteReserved = 0xffffffff_fffffffe;
private ulong[][] _pageTable;
public MemoryManager()
{
_pageTable = new ulong[PtLvl0Size][];
}
public ulong Map(ulong pa, ulong va, ulong size)
{
lock (_pageTable)
{
for (ulong offset = 0; offset < size; offset += PageSize)
{
SetPte(va + offset, pa + offset);
}
}
return va;
}
public ulong Map(ulong pa, ulong size)
{
lock (_pageTable)
{
ulong va = GetFreePosition(size);
if (va != PteUnmapped)
{
for (ulong offset = 0; offset < size; offset += PageSize)
{
SetPte(va + offset, pa + offset);
}
}
return va;
}
}
public ulong MapLow(ulong pa, ulong size)
{
lock (_pageTable)
{
ulong va = GetFreePosition(size, 1, PageSize);
if (va != PteUnmapped && va <= uint.MaxValue && (va + size) <= uint.MaxValue)
{
for (ulong offset = 0; offset < size; offset += PageSize)
{
SetPte(va + offset, pa + offset);
}
}
else
{
va = PteUnmapped;
}
return va;
}
}
public ulong ReserveFixed(ulong va, ulong size)
{
lock (_pageTable)
{
for (ulong offset = 0; offset < size; offset += PageSize)
{
if (IsPageInUse(va + offset))
{
return PteUnmapped;
}
}
for (ulong offset = 0; offset < size; offset += PageSize)
{
SetPte(va + offset, PteReserved);
}
}
return va;
}
public ulong Reserve(ulong size, ulong alignment)
{
lock (_pageTable)
{
ulong address = GetFreePosition(size, alignment);
if (address != PteUnmapped)
{
for (ulong offset = 0; offset < size; offset += PageSize)
{
SetPte(address + offset, PteReserved);
}
}
return address;
}
}
public void Free(ulong va, ulong size)
{
lock (_pageTable)
{
for (ulong offset = 0; offset < size; offset += PageSize)
{
SetPte(va + offset, PteUnmapped);
}
}
}
private ulong GetFreePosition(ulong size, ulong alignment = 1, ulong start = 1UL << 32)
{
// Note: Address 0 is not considered valid by the driver,
// when 0 is returned it's considered a mapping error.
ulong address = start;
ulong freeSize = 0;
if (alignment == 0)
{
alignment = 1;
}
alignment = (alignment + PageMask) & ~PageMask;
while (address + freeSize < AddressSpaceSize)
{
if (!IsPageInUse(address + freeSize))
{
freeSize += PageSize;
if (freeSize >= size)
{
return address;
}
}
else
{
address += freeSize + PageSize;
freeSize = 0;
ulong remainder = address % alignment;
if (remainder != 0)
{
address = (address - remainder) + alignment;
}
}
}
return PteUnmapped;
}
internal ulong GetSubSize(ulong gpuVa)
{
ulong size = 0;
while (GetPte(gpuVa + size) != PteUnmapped)
{
size += PageSize;
}
return size;
}
internal ulong Translate(ulong gpuVa)
{
ulong baseAddress = GetPte(gpuVa);
if (baseAddress == PteUnmapped || baseAddress == PteReserved)
{
return PteUnmapped;
}
return baseAddress + (gpuVa & PageMask);
}
public bool IsRegionFree(ulong va, ulong size)
{
for (ulong offset = 0; offset < size; offset += PageSize)
{
if (IsPageInUse(va + offset))
{
return false;
}
}
return true;
}
private bool IsPageInUse(ulong va)
{
if (va >> PtLvl0Bits + PtLvl1Bits + PtPageBits != 0)
{
return false;
}
ulong l0 = (va >> PtLvl0Bit) & PtLvl0Mask;
ulong l1 = (va >> PtLvl1Bit) & PtLvl1Mask;
if (_pageTable[l0] == null)
{
return false;
}
return _pageTable[l0][l1] != PteUnmapped;
}
private ulong GetPte(ulong address)
{
ulong l0 = (address >> PtLvl0Bit) & PtLvl0Mask;
ulong l1 = (address >> PtLvl1Bit) & PtLvl1Mask;
if (_pageTable[l0] == null)
{
return PteUnmapped;
}
return _pageTable[l0][l1];
}
private void SetPte(ulong address, ulong tgtAddr)
{
ulong l0 = (address >> PtLvl0Bit) & PtLvl0Mask;
ulong l1 = (address >> PtLvl1Bit) & PtLvl1Mask;
if (_pageTable[l0] == null)
{
_pageTable[l0] = new ulong[PtLvl1Size];
for (ulong index = 0; index < PtLvl1Size; index++)
{
_pageTable[l0][index] = PteUnmapped;
}
}
_pageTable[l0][l1] = tgtAddr;
}
}
}

View file

@ -0,0 +1,208 @@
using System.Collections.Generic;
namespace Ryujinx.Graphics.Gpu.Memory
{
class RangeList<T> where T : IRange<T>
{
private List<T> _items;
public RangeList()
{
_items = new List<T>();
}
public void Add(T item)
{
lock (_items)
{
int index = BinarySearch(item.Address);
if (index < 0)
{
index = ~index;
}
_items.Insert(index, item);
}
}
public bool Remove(T item)
{
lock (_items)
{
int index = BinarySearch(item.Address);
if (index >= 0)
{
while (index > 0 && _items[index - 1].Address == item.Address)
{
index--;
}
while (index < _items.Count)
{
if (_items[index].Equals(item))
{
_items.RemoveAt(index);
return true;
}
if (_items[index].Address > item.Address)
{
break;
}
index++;
}
}
}
return false;
}
public T FindFirstOverlap(T item)
{
return FindFirstOverlap(item.Address, item.Size);
}
public T FindFirstOverlap(ulong address, ulong size)
{
lock (_items)
{
int index = BinarySearch(address, size);
if (index < 0)
{
return default(T);
}
return _items[index];
}
}
public T[] FindOverlaps(T item)
{
return FindOverlaps(item.Address, item.Size);
}
public T[] FindOverlaps(ulong address, ulong size)
{
List<T> overlapsList = new List<T>();
ulong endAddress = address + size;
lock (_items)
{
foreach (T item in _items)
{
if (item.Address >= endAddress)
{
break;
}
if (item.OverlapsWith(address, size))
{
overlapsList.Add(item);
}
}
}
return overlapsList.ToArray();
}
public T[] FindOverlaps(ulong address)
{
List<T> overlapsList = new List<T>();
lock (_items)
{
int index = BinarySearch(address);
if (index >= 0)
{
while (index > 0 && _items[index - 1].Address == address)
{
index--;
}
while (index < _items.Count)
{
T overlap = _items[index++];
if (overlap.Address != address)
{
break;
}
overlapsList.Add(overlap);
}
}
}
return overlapsList.ToArray();
}
private int BinarySearch(ulong address)
{
int left = 0;
int right = _items.Count - 1;
while (left <= right)
{
int range = right - left;
int middle = left + (range >> 1);
T item = _items[middle];
if (item.Address == address)
{
return middle;
}
if (address < item.Address)
{
right = middle - 1;
}
else
{
left = middle + 1;
}
}
return ~left;
}
private int BinarySearch(ulong address, ulong size)
{
int left = 0;
int right = _items.Count - 1;
while (left <= right)
{
int range = right - left;
int middle = left + (range >> 1);
T item = _items[middle];
if (item.OverlapsWith(address, size))
{
return middle;
}
if (address < item.Address)
{
right = middle - 1;
}
else
{
left = middle + 1;
}
}
return ~left;
}
}
}

View file

@ -0,0 +1,10 @@
namespace Ryujinx.Graphics.Gpu.Memory
{
struct VertexBuffer
{
public ulong Address;
public ulong Size;
public int Stride;
public int Divisor;
}
}

View file

@ -0,0 +1,24 @@
namespace Ryujinx.Graphics
{
struct MethodParams
{
public int Method { get; private set; }
public int Argument { get; private set; }
public int SubChannel { get; private set; }
public int MethodCount { get; private set; }
public bool IsLastCall => MethodCount <= 1;
public MethodParams(
int method,
int argument,
int subChannel = 0,
int methodCount = 0)
{
Method = method;
Argument = argument;
SubChannel = subChannel;
MethodCount = methodCount;
}
}
}

View file

@ -0,0 +1,150 @@
namespace Ryujinx.Graphics.Gpu
{
class NvGpuFifo
{
private const int MacrosCount = 0x80;
private const int MacroIndexMask = MacrosCount - 1;
// Note: The size of the macro memory is unknown, we just make
// a guess here and use 256kb as the size. Increase if needed.
private const int MmeWords = 256 * 256;
private GpuContext _context;
private struct CachedMacro
{
public int Position { get; private set; }
private bool _executionPending;
private int _argument;
private MacroInterpreter _interpreter;
public CachedMacro(GpuContext context, NvGpuFifo fifo, int position)
{
Position = position;
_executionPending = false;
_argument = 0;
_interpreter = new MacroInterpreter(context, fifo);
}
public void StartExecution(int argument)
{
_argument = argument;
_executionPending = true;
}
public void Execute(int[] mme)
{
if (_executionPending)
{
_executionPending = false;
_interpreter?.Execute(mme, Position, _argument);
}
}
public void PushArgument(int argument)
{
_interpreter?.Fifo.Enqueue(argument);
}
}
private int _currMacroPosition;
private int _currMacroBindIndex;
private CachedMacro[] _macros;
private int[] _mme;
private ClassId[] _subChannels;
public NvGpuFifo(GpuContext context)
{
_context = context;
_macros = new CachedMacro[MacrosCount];
_mme = new int[MmeWords];
_subChannels = new ClassId[8];
}
public void CallMethod(MethodParams meth)
{
if ((NvGpuFifoMeth)meth.Method == NvGpuFifoMeth.BindChannel)
{
_subChannels[meth.SubChannel] = (ClassId)meth.Argument;
}
else if (meth.Method < 0x60)
{
switch ((NvGpuFifoMeth)meth.Method)
{
case NvGpuFifoMeth.WaitForIdle:
{
_context.Renderer.FlushPipelines();
break;
}
case NvGpuFifoMeth.SetMacroUploadAddress:
{
_currMacroPosition = meth.Argument;
break;
}
case NvGpuFifoMeth.SendMacroCodeData:
{
_mme[_currMacroPosition++] = meth.Argument;
break;
}
case NvGpuFifoMeth.SetMacroBindingIndex:
{
_currMacroBindIndex = meth.Argument;
break;
}
case NvGpuFifoMeth.BindMacro:
{
int position = meth.Argument;
_macros[_currMacroBindIndex++] = new CachedMacro(_context, this, position);
break;
}
}
}
else if (meth.Method < 0xe00)
{
_context.State.CallMethod(meth);
}
else
{
int macroIndex = (meth.Method >> 1) & MacroIndexMask;
if ((meth.Method & 1) != 0)
{
_macros[macroIndex].PushArgument(meth.Argument);
}
else
{
_macros[macroIndex].StartExecution(meth.Argument);
}
if (meth.IsLastCall)
{
_macros[macroIndex].Execute(_mme);
_context.Methods.PerformDeferredDraws();
}
}
}
}
}

View file

@ -0,0 +1,12 @@
namespace Ryujinx.Graphics.Gpu
{
enum NvGpuFifoMeth
{
BindChannel = 0,
WaitForIdle = 0x44,
SetMacroUploadAddress = 0x45,
SendMacroCodeData = 0x46,
SetMacroBindingIndex = 0x47,
BindMacro = 0x48
}
}

View file

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

View file

@ -0,0 +1,15 @@
using Ryujinx.Graphics.GAL.Blend;
namespace Ryujinx.Graphics.Gpu.State
{
struct BlendState
{
public Bool SeparateAlpha;
public BlendOp ColorOp;
public BlendFactor ColorSrcFactor;
public BlendFactor ColorDstFactor;
public BlendOp AlphaOp;
public BlendFactor AlphaSrcFactor;
public BlendFactor AlphaDstFactor;
}
}

View file

@ -0,0 +1,17 @@
namespace Ryujinx.Graphics.Gpu.State
{
struct Bool
{
private uint _value;
public bool IsTrue()
{
return (_value & 1) != 0;
}
public bool IsFalse()
{
return (_value & 1) == 0;
}
}
}

View file

@ -0,0 +1,10 @@
namespace Ryujinx.Graphics.Gpu.State
{
struct ClearColors
{
public float Red;
public float Green;
public float Blue;
public float Alpha;
}
}

View file

@ -0,0 +1,11 @@
namespace Ryujinx.Graphics.Gpu.State
{
enum Condition
{
Never,
Always,
ResultNonZero,
Equal,
NotEqual
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.Graphics.Gpu.State
{
struct ConditionState
{
public GpuVa Address;
public Condition Condition;
}
}

View file

@ -0,0 +1,12 @@
namespace Ryujinx.Graphics.Gpu.State
{
struct CopyBufferParams
{
public GpuVa SrcAddress;
public GpuVa DstAddress;
public int SrcStride;
public int DstStride;
public int XCount;
public int YCount;
}
}

View file

@ -0,0 +1,22 @@
namespace Ryujinx.Graphics.Gpu.State
{
struct CopyBufferSwizzle
{
public uint Swizzle;
public int UnpackComponentSize()
{
return (int)((Swizzle >> 16) & 3) + 1;
}
public int UnpackSrcComponentsCount()
{
return (int)((Swizzle >> 20) & 7) + 1;
}
public int UnpackDstComponentsCount()
{
return (int)((Swizzle >> 24) & 7) + 1;
}
}
}

View file

@ -0,0 +1,13 @@
namespace Ryujinx.Graphics.Gpu.State
{
struct CopyBufferTexture
{
public MemoryLayout MemoryLayout;
public int Width;
public int Height;
public int Depth;
public int RegionZ;
public ushort RegionX;
public ushort RegionY;
}
}

View file

@ -0,0 +1,14 @@
namespace Ryujinx.Graphics.Gpu.State
{
struct CopyRegion
{
public int DstX;
public int DstY;
public int DstWidth;
public int DstHeight;
public long SrcWidthRF;
public long SrcHeightRF;
public long SrcXF;
public long SrcYF;
}
}

View file

@ -0,0 +1,15 @@
namespace Ryujinx.Graphics.Gpu.State
{
struct CopyTexture
{
public RtFormat Format;
public bool LinearLayout;
public MemoryLayout MemoryLayout;
public int Depth;
public int Layer;
public int Stride;
public int Width;
public int Height;
public GpuVa Address;
}
}

View file

@ -0,0 +1,12 @@
namespace Ryujinx.Graphics.Gpu.State
{
struct CopyTextureControl
{
public uint Packed;
public bool UnpackLinearFilter()
{
return (Packed & (1u << 4)) != 0;
}
}
}

View file

@ -0,0 +1,9 @@
namespace Ryujinx.Graphics.Gpu.State
{
struct DepthBiasState
{
public Bool PointEnable;
public Bool LineEnable;
public Bool FillEnable;
}
}

View file

@ -0,0 +1,11 @@
using Ryujinx.Graphics.GAL;
namespace Ryujinx.Graphics.Gpu.State
{
struct FaceState
{
public Bool CullEnable;
public FrontFace FrontFace;
public Face CullFace;
}
}

View file

@ -0,0 +1,425 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Image;
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Gpu.State
{
class GpuState
{
private const int RegistersCount = 0xe00;
public delegate void MethodCallback(int argument);
private int[] _backingMemory;
private struct Register
{
public MethodCallback Callback;
public StateWriteFlags WriteFlag;
}
private Register[] _registers;
public StateWriteFlags StateWriteFlags { get; set; }
public GpuState()
{
_backingMemory = new int[RegistersCount];
_registers = new Register[RegistersCount];
StateWriteFlags = StateWriteFlags.Any;
InitializeDefaultState();
InitializeStateWatchers();
}
public bool ExitEarly;
public void CallMethod(MethodParams meth)
{
if (ExitEarly)
{
return;
}
Register register = _registers[meth.Method];
if (_backingMemory[meth.Method] != meth.Argument)
{
StateWriteFlags |= register.WriteFlag;
}
_backingMemory[meth.Method] = meth.Argument;
MethodCallback callback = register.Callback;
if (callback != null)
{
callback(meth.Argument);
}
}
public int Read(int offset)
{
return _backingMemory[offset];
}
public void RegisterCopyBufferCallback(MethodCallback callback)
{
RegisterCallback(0xc0, callback);
}
public void RegisterCopyTextureCallback(MethodCallback callback)
{
RegisterCallback(0x237, callback);
}
public void RegisterDrawEndCallback(MethodCallback callback)
{
RegisterCallback(0x585, callback);
}
public void RegisterDrawBeginCallback(MethodCallback callback)
{
RegisterCallback(0x586, callback);
}
public void RegisterSetIndexCountCallback(MethodCallback callback)
{
RegisterCallback(0x5f8, callback);
}
public void RegisterClearCallback(MethodCallback callback)
{
RegisterCallback(0x674, callback);
}
public void RegisterReportCallback(MethodCallback callback)
{
RegisterCallback(0x6c3, callback);
}
public void RegisterUniformBufferUpdateCallback(MethodCallback callback)
{
for (int index = 0; index < 16; index++)
{
RegisterCallback(0x8e4 + index, callback);
}
}
public void RegisterUniformBufferBind0Callback(MethodCallback callback)
{
RegisterCallback(0x904, callback);
}
public void RegisterUniformBufferBind1Callback(MethodCallback callback)
{
RegisterCallback(0x90c, callback);
}
public void RegisterUniformBufferBind2Callback(MethodCallback callback)
{
RegisterCallback(0x914, callback);
}
public void RegisterUniformBufferBind3Callback(MethodCallback callback)
{
RegisterCallback(0x91c, callback);
}
public void RegisterUniformBufferBind4Callback(MethodCallback callback)
{
RegisterCallback(0x924, callback);
}
public CopyTexture GetCopyDstTexture()
{
return Get<CopyTexture>(MethodOffset.CopyDstTexture);
}
public CopyTexture GetCopySrcTexture()
{
return Get<CopyTexture>(MethodOffset.CopySrcTexture);
}
public RtColorState GetRtColorState(int index)
{
return Get<RtColorState>(MethodOffset.RtColorState + 16 * index);
}
public CopyTextureControl GetCopyTextureControl()
{
return Get<CopyTextureControl>(MethodOffset.CopyTextureControl);
}
public CopyRegion GetCopyRegion()
{
return Get<CopyRegion>(MethodOffset.CopyRegion);
}
public ViewportTransform GetViewportTransform(int index)
{
return Get<ViewportTransform>(MethodOffset.ViewportTransform + 8 * index);
}
public ViewportExtents GetViewportExtents(int index)
{
return Get<ViewportExtents>(MethodOffset.ViewportExtents + 4 * index);
}
public VertexBufferDrawState GetVertexBufferDrawState()
{
return Get<VertexBufferDrawState>(MethodOffset.VertexBufferDrawState);
}
public ClearColors GetClearColors()
{
return Get<ClearColors>(MethodOffset.ClearColors);
}
public float GetClearDepthValue()
{
return Get<float>(MethodOffset.ClearDepthValue);
}
public int GetClearStencilValue()
{
return _backingMemory[(int)MethodOffset.ClearStencilValue];
}
public StencilBackMasks GetStencilBackMasks()
{
return Get<StencilBackMasks>(MethodOffset.StencilBackMasks);
}
public RtDepthStencilState GetRtDepthStencilState()
{
return Get<RtDepthStencilState>(MethodOffset.RtDepthStencilState);
}
public VertexAttribState GetVertexAttribState(int index)
{
return Get<VertexAttribState>(MethodOffset.VertexAttribState + index);
}
public Size3D GetRtDepthStencilSize()
{
return Get<Size3D>(MethodOffset.RtDepthStencilSize);
}
public Bool GetDepthTestEnable()
{
return Get<Bool>(MethodOffset.DepthTestEnable);
}
public CompareOp GetDepthTestFunc()
{
return Get<CompareOp>(MethodOffset.DepthTestFunc);
}
public Bool GetDepthWriteEnable()
{
return Get<Bool>(MethodOffset.DepthWriteEnable);
}
public Bool GetBlendEnable(int index)
{
return Get<Bool>(MethodOffset.BlendEnable + index);
}
public StencilTestState GetStencilTestState()
{
return Get<StencilTestState>(MethodOffset.StencilTestState);
}
public int GetBaseVertex()
{
return _backingMemory[(int)MethodOffset.FirstVertex];
}
public int GetBaseInstance()
{
return _backingMemory[(int)MethodOffset.FirstInstance];
}
public PoolState GetSamplerPoolState()
{
return Get<PoolState>(MethodOffset.SamplerPoolState);
}
public PoolState GetTexturePoolState()
{
return Get<PoolState>(MethodOffset.TexturePoolState);
}
public StencilBackTestState GetStencilBackTestState()
{
return Get<StencilBackTestState>(MethodOffset.StencilBackTestState);
}
public TextureMsaaMode GetRtMsaaMode()
{
return Get<TextureMsaaMode>(MethodOffset.RtMsaaMode);
}
public GpuVa GetShaderBaseAddress()
{
return Get<GpuVa>(MethodOffset.ShaderBaseAddress);
}
public PrimitiveRestartState GetPrimitiveRestartState()
{
return Get<PrimitiveRestartState>(MethodOffset.PrimitiveRestartState);
}
public IndexBufferState GetIndexBufferState()
{
return Get<IndexBufferState>(MethodOffset.IndexBufferState);
}
public FaceState GetFaceState()
{
return Get<FaceState>(MethodOffset.FaceState);
}
public ReportState GetReportState()
{
return Get<ReportState>(MethodOffset.ReportState);
}
public VertexBufferState GetVertexBufferState(int index)
{
return Get<VertexBufferState>(MethodOffset.VertexBufferState + 4 * index);
}
public BlendState GetBlendState(int index)
{
return Get<BlendState>(MethodOffset.BlendState + 8 * index);
}
public GpuVa GetVertexBufferEndAddress(int index)
{
return Get<GpuVa>(MethodOffset.VertexBufferEndAddress + 2 * index);
}
public ShaderState GetShaderState(int index)
{
return Get<ShaderState>(MethodOffset.ShaderState + 16 * index);
}
public UniformBufferState GetUniformBufferState()
{
return Get<UniformBufferState>(MethodOffset.UniformBufferState);
}
public void SetUniformBufferOffset(int offset)
{
_backingMemory[(int)MethodOffset.UniformBufferState + 3] = offset;
}
public int GetTextureBufferIndex()
{
return _backingMemory[(int)MethodOffset.TextureBufferIndex];
}
private void InitializeDefaultState()
{
// Depth ranges.
for (int index = 0; index < 8; index++)
{
_backingMemory[(int)MethodOffset.ViewportExtents + index * 4 + 2] = 0;
_backingMemory[(int)MethodOffset.ViewportExtents + index * 4 + 3] = 0x3F800000;
}
// Default front stencil mask.
_backingMemory[0x4e7] = 0xff;
// Default color mask.
_backingMemory[(int)MethodOffset.RtColorMask] = 0x1111;
}
private void InitializeStateWatchers()
{
SetWriteStateFlag(MethodOffset.RtColorState, StateWriteFlags.RtColorState, 16 * 8);
SetWriteStateFlag(MethodOffset.ViewportTransform, StateWriteFlags.ViewportTransform, 8 * 8);
SetWriteStateFlag(MethodOffset.ViewportExtents, StateWriteFlags.ViewportTransform, 4 * 8);
SetWriteStateFlag<VertexBufferDrawState>(MethodOffset.VertexBufferDrawState, StateWriteFlags.VertexBufferState);
SetWriteStateFlag<DepthBiasState>(MethodOffset.DepthBiasState, StateWriteFlags.DepthBiasState);
SetWriteStateFlag(MethodOffset.DepthBiasFactor, StateWriteFlags.DepthBiasState, 1);
SetWriteStateFlag(MethodOffset.DepthBiasUnits, StateWriteFlags.DepthBiasState, 1);
SetWriteStateFlag(MethodOffset.DepthBiasClamp, StateWriteFlags.DepthBiasState, 1);
SetWriteStateFlag<RtDepthStencilState>(MethodOffset.RtDepthStencilState, StateWriteFlags.RtDepthStencilState);
SetWriteStateFlag<Size3D> (MethodOffset.RtDepthStencilSize, StateWriteFlags.RtDepthStencilState);
SetWriteStateFlag(MethodOffset.DepthTestEnable, StateWriteFlags.DepthTestState, 1);
SetWriteStateFlag(MethodOffset.DepthWriteEnable, StateWriteFlags.DepthTestState, 1);
SetWriteStateFlag(MethodOffset.DepthTestFunc, StateWriteFlags.DepthTestState, 1);
SetWriteStateFlag(MethodOffset.VertexAttribState, StateWriteFlags.VertexAttribState, 16);
SetWriteStateFlag<StencilBackMasks> (MethodOffset.StencilBackMasks, StateWriteFlags.StencilTestState);
SetWriteStateFlag<StencilTestState> (MethodOffset.StencilTestState, StateWriteFlags.StencilTestState);
SetWriteStateFlag<StencilBackTestState>(MethodOffset.StencilBackTestState, StateWriteFlags.StencilTestState);
SetWriteStateFlag<PoolState>(MethodOffset.SamplerPoolState, StateWriteFlags.SamplerPoolState);
SetWriteStateFlag<PoolState>(MethodOffset.TexturePoolState, StateWriteFlags.TexturePoolState);
SetWriteStateFlag<ShaderState>(MethodOffset.ShaderBaseAddress, StateWriteFlags.ShaderState);
SetWriteStateFlag<PrimitiveRestartState>(MethodOffset.PrimitiveRestartState, StateWriteFlags.PrimitiveRestartState);
SetWriteStateFlag<IndexBufferState>(MethodOffset.IndexBufferState, StateWriteFlags.IndexBufferState);
SetWriteStateFlag<FaceState>(MethodOffset.FaceState, StateWriteFlags.FaceState);
SetWriteStateFlag<RtColorMask>(MethodOffset.RtColorMask, StateWriteFlags.RtColorMask);
SetWriteStateFlag(MethodOffset.VertexBufferInstanced, StateWriteFlags.VertexBufferState, 16);
SetWriteStateFlag(MethodOffset.VertexBufferState, StateWriteFlags.VertexBufferState, 4 * 16);
SetWriteStateFlag(MethodOffset.VertexBufferEndAddress, StateWriteFlags.VertexBufferState, 2 * 16);
SetWriteStateFlag(MethodOffset.BlendEnable, StateWriteFlags.BlendState, 8);
SetWriteStateFlag(MethodOffset.BlendState, StateWriteFlags.BlendState, 8 * 8);
SetWriteStateFlag(MethodOffset.ShaderState, StateWriteFlags.ShaderState, 16 * 6);
SetWriteStateFlag(MethodOffset.TextureBufferIndex, StateWriteFlags.TexturePoolState, 1);
}
private void SetWriteStateFlag<T>(MethodOffset offset, StateWriteFlags flag)
{
SetWriteStateFlag(offset, flag, Marshal.SizeOf<T>());
}
private void SetWriteStateFlag(MethodOffset offset, StateWriteFlags flag, int size)
{
for (int index = 0; index < size; index++)
{
_registers[(int)offset + index].WriteFlag = flag;
}
}
public void RegisterCallback(MethodOffset offset, MethodCallback callback)
{
_registers[(int)offset].Callback = callback;
}
private void RegisterCallback(int offset, MethodCallback callback)
{
_registers[offset].Callback = callback;
}
public T Get<T>(MethodOffset offset) where T : struct
{
return MemoryMarshal.Cast<int, T>(_backingMemory.AsSpan().Slice((int)offset))[0];
}
}
}

View file

@ -0,0 +1,18 @@
namespace Ryujinx.Graphics.Gpu.State
{
struct GpuVa
{
public uint High;
public uint Low;
public ulong Pack()
{
return Low | ((ulong)High << 32);
}
public bool IsNullPtr()
{
return (Low | High) == 0;
}
}
}

View file

@ -0,0 +1,13 @@
using Ryujinx.Graphics.GAL;
namespace Ryujinx.Graphics.Gpu.State
{
struct IndexBufferState
{
public GpuVa Address;
public GpuVa EndAddress;
public IndexType Type;
public int First;
public int Count;
}
}

View file

@ -0,0 +1,17 @@
namespace Ryujinx.Graphics.Gpu.State
{
struct Inline2MemoryParams
{
public int LineLengthIn;
public int LineCount;
public GpuVa DstAddress;
public int DstStride;
public MemoryLayout DstMemoryLayout;
public int DstWidth;
public int DstHeight;
public int DstDepth;
public int DstZ;
public int DstX;
public int DstY;
}
}

View file

@ -0,0 +1,32 @@
namespace Ryujinx.Graphics.Gpu.State
{
struct MemoryLayout
{
public uint Packed;
public int UnpackGobBlocksInX()
{
return 1 << (int)(Packed & 0xf);
}
public int UnpackGobBlocksInY()
{
return 1 << (int)((Packed >> 4) & 0xf);
}
public int UnpackGobBlocksInZ()
{
return 1 << (int)((Packed >> 8) & 0xf);
}
public bool UnpackIsLinear()
{
return (Packed & 0x1000) != 0;
}
public bool UnpackIsTarget3D()
{
return (Packed & 0x10000) != 0;
}
}
}

View file

@ -0,0 +1,62 @@
namespace Ryujinx.Graphics.Gpu.State
{
enum MethodOffset
{
Inline2MemoryParams = 0x60,
Inline2MemoryExecute = 0x6c,
Inline2MemoryPushData = 0x6d,
CopyDstTexture = 0x80,
CopySrcTexture = 0x8c,
DispatchParamsAddress = 0xad,
Dispatch = 0xaf,
CopyBufferParams = 0x100,
CopyBufferSwizzle = 0x1c2,
CopyBufferDstTexture = 0x1c3,
CopyBufferSrcTexture = 0x1ca,
RtColorState = 0x200,
CopyTextureControl = 0x223,
CopyRegion = 0x22c,
ViewportTransform = 0x280,
ViewportExtents = 0x300,
VertexBufferDrawState = 0x35d,
ClearColors = 0x360,
ClearDepthValue = 0x364,
ClearStencilValue = 0x368,
DepthBiasState = 0x370,
StencilBackMasks = 0x3d5,
InvalidateTextures = 0x3dd,
RtDepthStencilState = 0x3f8,
VertexAttribState = 0x458,
RtDepthStencilSize = 0x48a,
DepthTestEnable = 0x4b3,
DepthWriteEnable = 0x4ba,
DepthTestFunc = 0x4c3,
BlendEnable = 0x4d8,
StencilTestState = 0x4e0,
FirstVertex = 0x50d,
FirstInstance = 0x50e,
ResetCounter = 0x54c,
RtDepthStencilEnable = 0x54e,
ConditionState = 0x554,
SamplerPoolState = 0x557,
DepthBiasFactor = 0x55b,
TexturePoolState = 0x55d,
StencilBackTestState = 0x565,
DepthBiasUnits = 0x56f,
RtMsaaMode = 0x574,
ShaderBaseAddress = 0x582,
PrimitiveRestartState = 0x591,
IndexBufferState = 0x5f2,
DepthBiasClamp = 0x61f,
VertexBufferInstanced = 0x620,
FaceState = 0x646,
RtColorMask = 0x680,
ReportState = 0x6c0,
VertexBufferState = 0x700,
BlendState = 0x780,
VertexBufferEndAddress = 0x7c0,
ShaderState = 0x800,
UniformBufferState = 0x8e0,
TextureBufferIndex = 0x982
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.Graphics.Gpu.State
{
struct PoolState
{
public GpuVa Address;
public int MaximumId;
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.Graphics.Gpu.State
{
struct PrimitiveRestartState
{
public bool Enable;
public int Index;
}
}

View file

@ -0,0 +1,50 @@
using Ryujinx.Graphics.GAL;
namespace Ryujinx.Graphics.Gpu.State
{
enum PrimitiveType
{
Points,
Lines,
LineLoop,
LineStrip,
Triangles,
TriangleStrip,
TriangleFan,
Quads,
QuadStrip,
Polygon,
LinesAdjacency,
LineStripAdjacency,
TrianglesAdjacency,
TriangleStripAdjacency,
Patches
}
static class PrimitiveTypeConverter
{
public static PrimitiveTopology Convert(this PrimitiveType topology)
{
switch (topology)
{
case PrimitiveType.Points: return PrimitiveTopology.Points;
case PrimitiveType.Lines: return PrimitiveTopology.Lines;
case PrimitiveType.LineLoop: return PrimitiveTopology.LineLoop;
case PrimitiveType.LineStrip: return PrimitiveTopology.LineStrip;
case PrimitiveType.Triangles: return PrimitiveTopology.Triangles;
case PrimitiveType.TriangleStrip: return PrimitiveTopology.TriangleStrip;
case PrimitiveType.TriangleFan: return PrimitiveTopology.TriangleFan;
case PrimitiveType.Quads: return PrimitiveTopology.Quads;
case PrimitiveType.QuadStrip: return PrimitiveTopology.QuadStrip;
case PrimitiveType.Polygon: return PrimitiveTopology.Polygon;
case PrimitiveType.LinesAdjacency: return PrimitiveTopology.LinesAdjacency;
case PrimitiveType.LineStripAdjacency: return PrimitiveTopology.LineStripAdjacency;
case PrimitiveType.TrianglesAdjacency: return PrimitiveTopology.TrianglesAdjacency;
case PrimitiveType.TriangleStripAdjacency: return PrimitiveTopology.TriangleStripAdjacency;
case PrimitiveType.Patches: return PrimitiveTopology.Patches;
}
return PrimitiveTopology.Triangles;
}
}
}

View file

@ -0,0 +1,25 @@
namespace Ryujinx.Graphics.Gpu.State
{
enum ReportCounterType
{
Zero = 0,
InputVertices = 1,
InputPrimitives = 3,
VertexShaderInvocations = 5,
GeometryShaderInvocations = 7,
GeometryShaderPrimitives = 9,
TransformFeedbackPrimitivesWritten = 0xb,
ClipperInputPrimitives = 0xf,
ClipperOutputPrimitives = 0x11,
PrimitivesGenerated = 0x12,
FragmentShaderInvocations = 0x13,
SamplesPassed = 0x15,
TessControlShaderInvocations = 0x1b,
TessEvaluationShaderInvocations = 0x1d,
TessEvaluationShaderPrimitives = 0x1f,
ZcullStats0 = 0x2a,
ZcullStats1 = 0x2c,
ZcullStats2 = 0x2e,
ZcullStats3 = 0x30
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.Graphics.Gpu.State
{
enum ReportMode
{
Semaphore = 0,
Counter = 2
}
}

View file

@ -0,0 +1,9 @@
namespace Ryujinx.Graphics.Gpu.State
{
struct ReportState
{
public GpuVa Address;
public int Payload;
public uint Control;
}
}

View file

@ -0,0 +1,21 @@
namespace Ryujinx.Graphics.Gpu.State
{
enum ResetCounterType
{
SamplesPassed = 1,
ZcullStats = 2,
TransformFeedbackPrimitivesWritten = 0x10,
InputVertices = 0x12,
InputPrimitives = 0x13,
VertexShaderInvocations = 0x15,
TessControlShaderInvocations = 0x16,
TessEvaluationShaderInvocations = 0x17,
TessEvaluationShaderPrimitives = 0x18,
GeometryShaderInvocations = 0x1a,
GeometryShaderPrimitives = 0x1b,
ClipperInputPrimitives = 0x1c,
ClipperOutputPrimitives = 0x1d,
FragmentShaderInvocations = 0x1e,
PrimitivesGenerated = 0x1f
}
}

View file

@ -0,0 +1,27 @@
namespace Ryujinx.Graphics.Gpu.State
{
struct RtColorMask
{
public uint Packed;
public bool UnpackRed()
{
return (Packed & 0x1) != 0;
}
public bool UnpackGreen()
{
return (Packed & 0x10) != 0;
}
public bool UnpackBlue()
{
return (Packed & 0x100) != 0;
}
public bool UnpackAlpha()
{
return (Packed & 0x1000) != 0;
}
}
}

View file

@ -0,0 +1,13 @@
namespace Ryujinx.Graphics.Gpu.State
{
struct RtColorState
{
public GpuVa Address;
public int WidthOrStride;
public int Height;
public RtFormat Format;
public MemoryLayout MemoryLayout;
public int Depth;
public int LayerSize;
}
}

View file

@ -0,0 +1,10 @@
namespace Ryujinx.Graphics.Gpu.State
{
struct RtDepthStencilState
{
public GpuVa Address;
public RtFormat Format;
public MemoryLayout MemoryLayout;
public int LayerSize;
}
}

View file

@ -0,0 +1,137 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Image;
namespace Ryujinx.Graphics.Gpu.State
{
enum RtFormat
{
D32Float = 0xa,
D16Unorm = 0x13,
D24UnormS8Uint = 0x14,
S8Uint = 0x17,
D32FloatS8Uint = 0x19,
R32G32B32A32Float = 0xc0,
R32G32B32A32Sint = 0xc1,
R32G32B32A32Uint = 0xc2,
R32G32B32X32Float = 0xc3,
R32G32B32X32Sint = 0xc4,
R32G32B32X32Uint = 0xc5,
R16G16B16X16Unorm = 0xc6,
R16G16B16X16Snorm = 0xc7,
R16G16B16X16Sint = 0xc8,
R16G16B16X16Uint = 0xc9,
R16G16B16A16Float = 0xca,
R32G32Float = 0xcb,
R32G32Sint = 0xcc,
R32G32Uint = 0xcd,
R16G16B16X16Float = 0xce,
B8G8R8A8Unorm = 0xcf,
B8G8R8A8Srgb = 0xd0,
R10G10B10A2Unorm = 0xd1,
R10G10B10A2Uint = 0xd2,
R8G8B8A8Unorm = 0xd5,
R8G8B8A8Srgb = 0xd6,
R8G8B8X8Snorm = 0xd7,
R8G8B8X8Sint = 0xd8,
R8G8B8X8Uint = 0xd9,
R16G16Unorm = 0xda,
R16G16Snorm = 0xdb,
R16G16Sint = 0xdc,
R16G16Uint = 0xdd,
R16G16Float = 0xde,
R11G11B10Float = 0xe0,
R32Sint = 0xe3,
R32Uint = 0xe4,
R32Float = 0xe5,
B8G8R8X8Unorm = 0xe6,
B8G8R8X8Srgb = 0xe7,
B5G6R5Unorm = 0xe8,
B5G5R5A1Unorm = 0xe9,
R8G8Unorm = 0xea,
R8G8Snorm = 0xeb,
R8G8Sint = 0xec,
R8G8Uint = 0xed,
R16Unorm = 0xee,
R16Snorm = 0xef,
R16Sint = 0xf0,
R16Uint = 0xf1,
R16Float = 0xf2,
R8Unorm = 0xf3,
R8Snorm = 0xf4,
R8Sint = 0xf5,
R8Uint = 0xf6,
B5G5R5X1Unorm = 0xf8,
R8G8B8X8Unorm = 0xf9,
R8G8B8X8Srgb = 0xfa
}
static class RtFormatConverter
{
public static FormatInfo Convert(this RtFormat format)
{
switch (format)
{
case RtFormat.D32Float: return new FormatInfo(Format.D32Float, 1, 1, 4);
case RtFormat.D16Unorm: return new FormatInfo(Format.D16Unorm, 1, 1, 2);
case RtFormat.D24UnormS8Uint: return new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4);
case RtFormat.S8Uint: return new FormatInfo(Format.S8Uint, 1, 1, 1);
case RtFormat.D32FloatS8Uint: return new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8);
case RtFormat.R32G32B32A32Float: return new FormatInfo(Format.R32G32B32A32Float, 1, 1, 16);
case RtFormat.R32G32B32A32Sint: return new FormatInfo(Format.R32G32B32A32Sint, 1, 1, 16);
case RtFormat.R32G32B32A32Uint: return new FormatInfo(Format.R32G32B32A32Uint, 1, 1, 16);
case RtFormat.R32G32B32X32Float: return new FormatInfo(Format.R32G32B32A32Float, 1, 1, 16);
case RtFormat.R32G32B32X32Sint: return new FormatInfo(Format.R32G32B32A32Sint, 1, 1, 16);
case RtFormat.R32G32B32X32Uint: return new FormatInfo(Format.R32G32B32A32Uint, 1, 1, 16);
case RtFormat.R16G16B16X16Unorm: return new FormatInfo(Format.R16G16B16A16Unorm, 1, 1, 8);
case RtFormat.R16G16B16X16Snorm: return new FormatInfo(Format.R16G16B16A16Snorm, 1, 1, 8);
case RtFormat.R16G16B16X16Sint: return new FormatInfo(Format.R16G16B16A16Sint, 1, 1, 8);
case RtFormat.R16G16B16X16Uint: return new FormatInfo(Format.R16G16B16A16Uint, 1, 1, 8);
case RtFormat.R16G16B16A16Float: return new FormatInfo(Format.R16G16B16A16Float, 1, 1, 8);
case RtFormat.R32G32Float: return new FormatInfo(Format.R32G32Float, 1, 1, 8);
case RtFormat.R32G32Sint: return new FormatInfo(Format.R32G32Sint, 1, 1, 8);
case RtFormat.R32G32Uint: return new FormatInfo(Format.R32G32Uint, 1, 1, 8);
case RtFormat.R16G16B16X16Float: return new FormatInfo(Format.R16G16B16A16Float, 1, 1, 8);
case RtFormat.B8G8R8A8Unorm: return new FormatInfo(Format.B8G8R8A8Unorm, 1, 1, 4);
case RtFormat.B8G8R8A8Srgb: return new FormatInfo(Format.B8G8R8A8Srgb, 1, 1, 4);
case RtFormat.R10G10B10A2Unorm: return new FormatInfo(Format.R10G10B10A2Unorm, 1, 1, 4);
case RtFormat.R10G10B10A2Uint: return new FormatInfo(Format.R10G10B10A2Uint, 1, 1, 4);
case RtFormat.R8G8B8A8Unorm: return new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4);
case RtFormat.R8G8B8A8Srgb: return new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4);
case RtFormat.R8G8B8X8Snorm: return new FormatInfo(Format.R8G8B8A8Snorm, 1, 1, 4);
case RtFormat.R8G8B8X8Sint: return new FormatInfo(Format.R8G8B8A8Sint, 1, 1, 4);
case RtFormat.R8G8B8X8Uint: return new FormatInfo(Format.R8G8B8A8Uint, 1, 1, 4);
case RtFormat.R16G16Unorm: return new FormatInfo(Format.R16G16Unorm, 1, 1, 4);
case RtFormat.R16G16Snorm: return new FormatInfo(Format.R16G16Snorm, 1, 1, 4);
case RtFormat.R16G16Sint: return new FormatInfo(Format.R16G16Sint, 1, 1, 4);
case RtFormat.R16G16Uint: return new FormatInfo(Format.R16G16Uint, 1, 1, 4);
case RtFormat.R16G16Float: return new FormatInfo(Format.R16G16Float, 1, 1, 4);
case RtFormat.R11G11B10Float: return new FormatInfo(Format.R11G11B10Float, 1, 1, 4);
case RtFormat.R32Sint: return new FormatInfo(Format.R32Sint, 1, 1, 4);
case RtFormat.R32Uint: return new FormatInfo(Format.R32Uint, 1, 1, 4);
case RtFormat.R32Float: return new FormatInfo(Format.R32Float, 1, 1, 4);
case RtFormat.B8G8R8X8Unorm: return new FormatInfo(Format.B8G8R8A8Unorm, 1, 1, 4);
case RtFormat.B8G8R8X8Srgb: return new FormatInfo(Format.B8G8R8A8Srgb, 1, 1, 4);
case RtFormat.B5G6R5Unorm: return new FormatInfo(Format.B5G6R5Unorm, 1, 1, 2);
case RtFormat.B5G5R5A1Unorm: return new FormatInfo(Format.B5G5R5A1Unorm, 1, 1, 2);
case RtFormat.R8G8Unorm: return new FormatInfo(Format.R8G8Unorm, 1, 1, 2);
case RtFormat.R8G8Snorm: return new FormatInfo(Format.R8G8Snorm, 1, 1, 2);
case RtFormat.R8G8Sint: return new FormatInfo(Format.R8G8Sint, 1, 1, 2);
case RtFormat.R8G8Uint: return new FormatInfo(Format.R8G8Uint, 1, 1, 2);
case RtFormat.R16Unorm: return new FormatInfo(Format.R16Unorm, 1, 1, 2);
case RtFormat.R16Snorm: return new FormatInfo(Format.R16Snorm, 1, 1, 2);
case RtFormat.R16Sint: return new FormatInfo(Format.R16Sint, 1, 1, 2);
case RtFormat.R16Uint: return new FormatInfo(Format.R16Uint, 1, 1, 2);
case RtFormat.R16Float: return new FormatInfo(Format.R16Float, 1, 1, 2);
case RtFormat.R8Unorm: return new FormatInfo(Format.R8Unorm, 1, 1, 1);
case RtFormat.R8Snorm: return new FormatInfo(Format.R8Snorm, 1, 1, 1);
case RtFormat.R8Sint: return new FormatInfo(Format.R8Sint, 1, 1, 1);
case RtFormat.R8Uint: return new FormatInfo(Format.R8Uint, 1, 1, 1);
case RtFormat.B5G5R5X1Unorm: return new FormatInfo(Format.B5G5R5X1Unorm, 1, 1, 2);
case RtFormat.R8G8B8X8Unorm: return new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4);
case RtFormat.R8G8B8X8Srgb: return new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4);
}
return FormatInfo.Default;
}
}
}

View file

@ -0,0 +1,19 @@
namespace Ryujinx.Graphics.Gpu.State
{
struct ShaderState
{
public uint Control;
public uint Offset;
public uint Unknown0x8;
public int MaxRegisters;
public ShaderType Type;
public uint Unknown0x14;
public uint Unknown0x18;
public uint Unknown0x1c;
public bool UnpackEnable()
{
return (Control & 1) != 0;
}
}
}

View file

@ -0,0 +1,11 @@
namespace Ryujinx.Graphics.Gpu.State
{
enum ShaderType
{
Vertex,
TessellationControl,
TessellationEvaluation,
Geometry,
Fragment
}
}

View file

@ -0,0 +1,9 @@
namespace Ryujinx.Graphics.Gpu.State
{
struct Size3D
{
public int Width;
public int Height;
public int Depth;
}
}

View file

@ -0,0 +1,34 @@
namespace Ryujinx.Graphics.Gpu.State
{
enum StateWriteFlags
{
InputAssemblerGroup =
VertexAttribState |
PrimitiveRestartState |
IndexBufferState |
VertexBufferState,
RenderTargetGroup =
RtColorState |
RtDepthStencilState,
RtColorState = 1 << 0,
ViewportTransform = 1 << 1,
DepthBiasState = 1 << 2,
RtDepthStencilState = 1 << 3,
DepthTestState = 1 << 4,
VertexAttribState = 1 << 5,
StencilTestState = 1 << 6,
SamplerPoolState = 1 << 7,
TexturePoolState = 1 << 8,
PrimitiveRestartState = 1 << 9,
IndexBufferState = 1 << 10,
FaceState = 1 << 11,
RtColorMask = 1 << 12,
VertexBufferState = 1 << 13,
BlendState = 1 << 14,
ShaderState = 1 << 15,
Any = -1
}
}

View file

@ -0,0 +1,9 @@
namespace Ryujinx.Graphics.Gpu.State
{
struct StencilBackMasks
{
public int FuncRef;
public int Mask;
public int FuncMask;
}
}

View file

@ -0,0 +1,14 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.GAL.DepthStencil;
namespace Ryujinx.Graphics.Gpu.State
{
struct StencilBackTestState
{
public Bool TwoSided;
public StencilOp BackSFail;
public StencilOp BackDpFail;
public StencilOp BackDpPass;
public CompareOp BackFunc;
}
}

View file

@ -0,0 +1,17 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.GAL.DepthStencil;
namespace Ryujinx.Graphics.Gpu.State
{
struct StencilTestState
{
public Bool Enable;
public StencilOp FrontSFail;
public StencilOp FrontDpFail;
public StencilOp FrontDpPass;
public CompareOp FrontFunc;
public int FrontFuncRef;
public int FrontFuncMask;
public int FrontMask;
}
}

View file

@ -0,0 +1,9 @@
namespace Ryujinx.Graphics.Gpu.State
{
struct UniformBufferState
{
public int Size;
public GpuVa Address;
public int Offset;
}
}

View file

@ -0,0 +1,22 @@
namespace Ryujinx.Graphics.Gpu.State
{
struct VertexAttribState
{
public uint Attribute;
public int UnpackBufferIndex()
{
return (int)(Attribute & 0x1f);
}
public int UnpackOffset()
{
return (int)((Attribute >> 7) & 0x3fff);
}
public uint UnpackFormat()
{
return Attribute & 0x3fe00000;
}
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.Graphics.Gpu.State
{
struct VertexBufferDrawState
{
public int First;
public int Count;
}
}

View file

@ -0,0 +1,19 @@
namespace Ryujinx.Graphics.Gpu.State
{
struct VertexBufferState
{
public uint Control;
public GpuVa Address;
public int Divisor;
public int UnpackStride()
{
return (int)(Control & 0xfff);
}
public bool UnpackEnable()
{
return (Control & (1 << 12)) != 0;
}
}
}

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