diff options
Diffstat (limited to 'Ryujinx.Graphics.Gpu')
102 files changed, 8251 insertions, 0 deletions
diff --git a/Ryujinx.Graphics.Gpu/ClassId.cs b/Ryujinx.Graphics.Gpu/ClassId.cs new file mode 100644 index 00000000..3cde0ac0 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/ClassId.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Graphics.Gpu +{ + enum ClassId + { + Engine2D = 0x902d, + Engine3D = 0xb197, + EngineCompute = 0xb1c0, + EngineInline2Memory = 0xa140, + EngineDma = 0xb0b5 + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Constants.cs b/Ryujinx.Graphics.Gpu/Constants.cs new file mode 100644 index 00000000..501933fb --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Constants.cs @@ -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; + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Debugging.cs b/Ryujinx.Graphics.Gpu/Debugging.cs new file mode 100644 index 00000000..cb99cee1 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Debugging.cs @@ -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); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/DmaPusher.cs b/Ryujinx.Graphics.Gpu/DmaPusher.cs new file mode 100644 index 00000000..ae9cc868 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/DmaPusher.cs @@ -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)); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/Compute.cs b/Ryujinx.Graphics.Gpu/Engine/Compute.cs new file mode 100644 index 00000000..c8627435 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/Compute.cs @@ -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()); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/ComputeParams.cs b/Ryujinx.Graphics.Gpu/Engine/ComputeParams.cs new file mode 100644 index 00000000..03582f05 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/ComputeParams.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/ComputeShader.cs b/Ryujinx.Graphics.Gpu/Engine/ComputeShader.cs new file mode 100644 index 00000000..cc7d4d99 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/ComputeShader.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/GraphicsShader.cs b/Ryujinx.Graphics.Gpu/Engine/GraphicsShader.cs new file mode 100644 index 00000000..a8ccc05a --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/GraphicsShader.cs @@ -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]; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/Inline2Memory.cs b/Ryujinx.Graphics.Gpu/Engine/Inline2Memory.cs new file mode 100644 index 00000000..d2816ac5 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/Inline2Memory.cs @@ -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(); + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodClear.cs b/Ryujinx.Graphics.Gpu/Engine/MethodClear.cs new file mode 100644 index 00000000..b4680fa5 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/MethodClear.cs @@ -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); + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodCopyBuffer.cs b/Ryujinx.Graphics.Gpu/Engine/MethodCopyBuffer.cs new file mode 100644 index 00000000..19ffb0e3 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/MethodCopyBuffer.cs @@ -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); + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodCopyTexture.cs b/Ryujinx.Graphics.Gpu/Engine/MethodCopyTexture.cs new file mode 100644 index 00000000..e2c40805 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/MethodCopyTexture.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodDraw.cs b/Ryujinx.Graphics.Gpu/Engine/MethodDraw.cs new file mode 100644 index 00000000..dd360113 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/MethodDraw.cs @@ -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); + } + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodReport.cs b/Ryujinx.Graphics.Gpu/Engine/MethodReport.cs new file mode 100644 index 00000000..fd0a31a1 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/MethodReport.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodResetCounter.cs b/Ryujinx.Graphics.Gpu/Engine/MethodResetCounter.cs new file mode 100644 index 00000000..12a87845 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/MethodResetCounter.cs @@ -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; + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferBind.cs b/Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferBind.cs new file mode 100644 index 00000000..a9ebce83 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferBind.cs @@ -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); + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferUpdate.cs b/Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferUpdate.cs new file mode 100644 index 00000000..f0b8e2d7 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferUpdate.cs @@ -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(); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/Methods.cs b/Ryujinx.Graphics.Gpu/Engine/Methods.cs new file mode 100644 index 00000000..db72a861 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/Methods.cs @@ -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(); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/ShaderAddresses.cs b/Ryujinx.Graphics.Gpu/Engine/ShaderAddresses.cs new file mode 100644 index 00000000..368b5a17 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/ShaderAddresses.cs @@ -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); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/ShaderCache.cs b/Ryujinx.Graphics.Gpu/Engine/ShaderCache.cs new file mode 100644 index 00000000..79a84a6d --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/ShaderCache.cs @@ -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; + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/ShaderDumper.cs b/Ryujinx.Graphics.Gpu/Engine/ShaderDumper.cs new file mode 100644 index 00000000..fdcf0612 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/ShaderDumper.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/GpuContext.cs b/Ryujinx.Graphics.Gpu/GpuContext.cs new file mode 100644 index 00000000..1e865e6a --- /dev/null +++ b/Ryujinx.Graphics.Gpu/GpuContext.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/GraphicsConfig.cs b/Ryujinx.Graphics.Gpu/GraphicsConfig.cs new file mode 100644 index 00000000..c5eaa0b3 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/GraphicsConfig.cs @@ -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; + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs b/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs new file mode 100644 index 00000000..33ed7881 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs @@ -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(); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/FormatInfo.cs b/Ryujinx.Graphics.Gpu/Image/FormatInfo.cs new file mode 100644 index 00000000..a728c66e --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/FormatInfo.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/FormatTable.cs b/Ryujinx.Graphics.Gpu/Image/FormatTable.cs new file mode 100644 index 00000000..9f5b26d4 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/FormatTable.cs @@ -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); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/Pool.cs b/Ryujinx.Graphics.Gpu/Image/Pool.cs new file mode 100644 index 00000000..196fc137 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/Pool.cs @@ -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; + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/ReductionFilter.cs b/Ryujinx.Graphics.Gpu/Image/ReductionFilter.cs new file mode 100644 index 00000000..f14a17f2 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/ReductionFilter.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Gpu.Image +{ + enum ReductionFilter + { + Average, + Minimum, + Maximum + } +} diff --git a/Ryujinx.Graphics.Gpu/Image/Sampler.cs b/Ryujinx.Graphics.Gpu/Image/Sampler.cs new file mode 100644 index 00000000..06fedd8a --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/Sampler.cs @@ -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(); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/SamplerDescriptor.cs b/Ryujinx.Graphics.Gpu/Image/SamplerDescriptor.cs new file mode 100644 index 00000000..0a43dd1b --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/SamplerDescriptor.cs @@ -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; + } + } +} diff --git a/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs b/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs new file mode 100644 index 00000000..970a0983 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs @@ -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(); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs new file mode 100644 index 00000000..32db8688 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/Texture.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs b/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs new file mode 100644 index 00000000..19090ab3 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs b/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs new file mode 100644 index 00000000..52472164 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/TextureComponent.cs b/Ryujinx.Graphics.Gpu/Image/TextureComponent.cs new file mode 100644 index 00000000..37b3f65e --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/TextureComponent.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs b/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs new file mode 100644 index 00000000..79e4f55e --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs @@ -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); + } + } +} diff --git a/Ryujinx.Graphics.Gpu/Image/TextureDescriptorType.cs b/Ryujinx.Graphics.Gpu/Image/TextureDescriptorType.cs new file mode 100644 index 00000000..6f6048a6 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/TextureDescriptorType.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Graphics.Gpu.Image +{ + enum TextureDescriptorType + { + Buffer, + LinearColorKey, + Linear, + BlockLinear, + BlockLinearColorKey + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs b/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs new file mode 100644 index 00000000..639abdd8 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs @@ -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; + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs new file mode 100644 index 00000000..56dff9ad --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs @@ -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); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/TextureMsaaMode.cs b/Ryujinx.Graphics.Gpu/Image/TextureMsaaMode.cs new file mode 100644 index 00000000..13421067 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/TextureMsaaMode.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/TexturePool.cs b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs new file mode 100644 index 00000000..558f4def --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs @@ -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(); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/TexturePoolCache.cs b/Ryujinx.Graphics.Gpu/Image/TexturePoolCache.cs new file mode 100644 index 00000000..8e8313ae --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/TexturePoolCache.cs @@ -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); + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/TextureSearchFlags.cs b/Ryujinx.Graphics.Gpu/Image/TextureSearchFlags.cs new file mode 100644 index 00000000..a5c951b5 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/TextureSearchFlags.cs @@ -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 + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/TextureTarget.cs b/Ryujinx.Graphics.Gpu/Image/TextureTarget.cs new file mode 100644 index 00000000..8f513903 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/TextureTarget.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/MacroInterpreter.cs b/Ryujinx.Graphics.Gpu/MacroInterpreter.cs new file mode 100644 index 00000000..93d5c2ca --- /dev/null +++ b/Ryujinx.Graphics.Gpu/MacroInterpreter.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Memory/Buffer.cs b/Ryujinx.Graphics.Gpu/Memory/Buffer.cs new file mode 100644 index 00000000..30bd1ac0 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Memory/Buffer.cs @@ -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(); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs b/Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs new file mode 100644 index 00000000..2a074bd3 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.Gpu.Memory +{ + struct BufferBounds + { + public ulong Address; + public ulong Size; + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs b/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs new file mode 100644 index 00000000..eb2e0ca9 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs @@ -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(); + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Memory/IPhysicalMemory.cs b/Ryujinx.Graphics.Gpu/Memory/IPhysicalMemory.cs new file mode 100644 index 00000000..5f21704d --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Memory/IPhysicalMemory.cs @@ -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); + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Memory/IRange.cs b/Ryujinx.Graphics.Gpu/Memory/IRange.cs new file mode 100644 index 00000000..ee3d5c0b --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Memory/IRange.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Gpu.Memory +{ + interface IRange<T> + { + ulong Address { get; } + ulong Size { get; } + + bool OverlapsWith(ulong address, ulong size); + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Memory/IndexBuffer.cs b/Ryujinx.Graphics.Gpu/Memory/IndexBuffer.cs new file mode 100644 index 00000000..ce2a2c74 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Memory/IndexBuffer.cs @@ -0,0 +1,12 @@ +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + struct IndexBuffer + { + public ulong Address; + public ulong Size; + + public IndexType Type; + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Memory/MemoryAccessor.cs b/Ryujinx.Graphics.Gpu/Memory/MemoryAccessor.cs new file mode 100644 index 00000000..500c36e5 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Memory/MemoryAccessor.cs @@ -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); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs b/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs new file mode 100644 index 00000000..d1a3e69c --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Memory/RangeList.cs b/Ryujinx.Graphics.Gpu/Memory/RangeList.cs new file mode 100644 index 00000000..6114f15d --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Memory/RangeList.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Memory/VertexBuffer.cs b/Ryujinx.Graphics.Gpu/Memory/VertexBuffer.cs new file mode 100644 index 00000000..1cb854d6 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Memory/VertexBuffer.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Gpu.Memory +{ + struct VertexBuffer + { + public ulong Address; + public ulong Size; + public int Stride; + public int Divisor; + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/MethodParams.cs b/Ryujinx.Graphics.Gpu/MethodParams.cs new file mode 100644 index 00000000..fea4eb30 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/MethodParams.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/NvGpuFifo.cs b/Ryujinx.Graphics.Gpu/NvGpuFifo.cs new file mode 100644 index 00000000..f4deea54 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/NvGpuFifo.cs @@ -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(); + } + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/NvGpuFifoMeth.cs b/Ryujinx.Graphics.Gpu/NvGpuFifoMeth.cs new file mode 100644 index 00000000..09aaa6c3 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/NvGpuFifoMeth.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Gpu +{ + enum NvGpuFifoMeth + { + BindChannel = 0, + WaitForIdle = 0x44, + SetMacroUploadAddress = 0x45, + SendMacroCodeData = 0x46, + SetMacroBindingIndex = 0x47, + BindMacro = 0x48 + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj b/Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj new file mode 100644 index 00000000..88761ddf --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj @@ -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> diff --git a/Ryujinx.Graphics.Gpu/State/BlendState.cs b/Ryujinx.Graphics.Gpu/State/BlendState.cs new file mode 100644 index 00000000..cf22dc1b --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/BlendState.cs @@ -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; + } +} diff --git a/Ryujinx.Graphics.Gpu/State/Bool.cs b/Ryujinx.Graphics.Gpu/State/Bool.cs new file mode 100644 index 00000000..8aadcfcc --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/Bool.cs @@ -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; + } + } +} diff --git a/Ryujinx.Graphics.Gpu/State/ClearColors.cs b/Ryujinx.Graphics.Gpu/State/ClearColors.cs new file mode 100644 index 00000000..584c4791 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/ClearColors.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + struct ClearColors + { + public float Red; + public float Green; + public float Blue; + public float Alpha; + } +} diff --git a/Ryujinx.Graphics.Gpu/State/Condition.cs b/Ryujinx.Graphics.Gpu/State/Condition.cs new file mode 100644 index 00000000..41cce519 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/Condition.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + enum Condition + { + Never, + Always, + ResultNonZero, + Equal, + NotEqual + } +} diff --git a/Ryujinx.Graphics.Gpu/State/ConditionState.cs b/Ryujinx.Graphics.Gpu/State/ConditionState.cs new file mode 100644 index 00000000..24302c60 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/ConditionState.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + struct ConditionState + { + public GpuVa Address; + public Condition Condition; + } +} diff --git a/Ryujinx.Graphics.Gpu/State/CopyBufferParams.cs b/Ryujinx.Graphics.Gpu/State/CopyBufferParams.cs new file mode 100644 index 00000000..2876dedd --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/CopyBufferParams.cs @@ -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; + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/State/CopyBufferSwizzle.cs b/Ryujinx.Graphics.Gpu/State/CopyBufferSwizzle.cs new file mode 100644 index 00000000..24071372 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/CopyBufferSwizzle.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/State/CopyBufferTexture.cs b/Ryujinx.Graphics.Gpu/State/CopyBufferTexture.cs new file mode 100644 index 00000000..cb63c607 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/CopyBufferTexture.cs @@ -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; + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/State/CopyRegion.cs b/Ryujinx.Graphics.Gpu/State/CopyRegion.cs new file mode 100644 index 00000000..cad9a5db --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/CopyRegion.cs @@ -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; + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/State/CopyTexture.cs b/Ryujinx.Graphics.Gpu/State/CopyTexture.cs new file mode 100644 index 00000000..363d84a2 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/CopyTexture.cs @@ -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; + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/State/CopyTextureControl.cs b/Ryujinx.Graphics.Gpu/State/CopyTextureControl.cs new file mode 100644 index 00000000..c49cf276 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/CopyTextureControl.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + struct CopyTextureControl + { + public uint Packed; + + public bool UnpackLinearFilter() + { + return (Packed & (1u << 4)) != 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/State/DepthBiasState.cs b/Ryujinx.Graphics.Gpu/State/DepthBiasState.cs new file mode 100644 index 00000000..c88d27dd --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/DepthBiasState.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + struct DepthBiasState + { + public Bool PointEnable; + public Bool LineEnable; + public Bool FillEnable; + } +} diff --git a/Ryujinx.Graphics.Gpu/State/FaceState.cs b/Ryujinx.Graphics.Gpu/State/FaceState.cs new file mode 100644 index 00000000..53763032 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/FaceState.cs @@ -0,0 +1,11 @@ +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.Gpu.State +{ + struct FaceState + { + public Bool CullEnable; + public FrontFace FrontFace; + public Face CullFace; + } +} diff --git a/Ryujinx.Graphics.Gpu/State/GpuState.cs b/Ryujinx.Graphics.Gpu/State/GpuState.cs new file mode 100644 index 00000000..4e4241ca --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/GpuState.cs @@ -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]; + } + } +} diff --git a/Ryujinx.Graphics.Gpu/State/GpuVa.cs b/Ryujinx.Graphics.Gpu/State/GpuVa.cs new file mode 100644 index 00000000..01ad70b7 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/GpuVa.cs @@ -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; + } + } +} diff --git a/Ryujinx.Graphics.Gpu/State/IndexBufferState.cs b/Ryujinx.Graphics.Gpu/State/IndexBufferState.cs new file mode 100644 index 00000000..8a07bb52 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/IndexBufferState.cs @@ -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; + } +} diff --git a/Ryujinx.Graphics.Gpu/State/Inline2MemoryParams.cs b/Ryujinx.Graphics.Gpu/State/Inline2MemoryParams.cs new file mode 100644 index 00000000..10d24900 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/Inline2MemoryParams.cs @@ -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; + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/State/MemoryLayout.cs b/Ryujinx.Graphics.Gpu/State/MemoryLayout.cs new file mode 100644 index 00000000..8e53a36a --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/MemoryLayout.cs @@ -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; + } + } +} diff --git a/Ryujinx.Graphics.Gpu/State/MethodOffset.cs b/Ryujinx.Graphics.Gpu/State/MethodOffset.cs new file mode 100644 index 00000000..91dffe9f --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/MethodOffset.cs @@ -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 + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/State/PoolState.cs b/Ryujinx.Graphics.Gpu/State/PoolState.cs new file mode 100644 index 00000000..3d51eb7e --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/PoolState.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + struct PoolState + { + public GpuVa Address; + public int MaximumId; + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/State/PrimitiveRestartState.cs b/Ryujinx.Graphics.Gpu/State/PrimitiveRestartState.cs new file mode 100644 index 00000000..21405be7 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/PrimitiveRestartState.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + struct PrimitiveRestartState + { + public bool Enable; + public int Index; + } +} diff --git a/Ryujinx.Graphics.Gpu/State/PrimitiveTopology.cs b/Ryujinx.Graphics.Gpu/State/PrimitiveTopology.cs new file mode 100644 index 00000000..02df9ac1 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/PrimitiveTopology.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/State/ReportCounterType.cs b/Ryujinx.Graphics.Gpu/State/ReportCounterType.cs new file mode 100644 index 00000000..38a0de71 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/ReportCounterType.cs @@ -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 + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/State/ReportMode.cs b/Ryujinx.Graphics.Gpu/State/ReportMode.cs new file mode 100644 index 00000000..84af3d75 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/ReportMode.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + enum ReportMode + { + Semaphore = 0, + Counter = 2 + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/State/ReportState.cs b/Ryujinx.Graphics.Gpu/State/ReportState.cs new file mode 100644 index 00000000..212281aa --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/ReportState.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + struct ReportState + { + public GpuVa Address; + public int Payload; + public uint Control; + } +} diff --git a/Ryujinx.Graphics.Gpu/State/ResetCounterType.cs b/Ryujinx.Graphics.Gpu/State/ResetCounterType.cs new file mode 100644 index 00000000..49b3b6da --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/ResetCounterType.cs @@ -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 + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/State/RtColorMask.cs b/Ryujinx.Graphics.Gpu/State/RtColorMask.cs new file mode 100644 index 00000000..5992673f --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/RtColorMask.cs @@ -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; + } + } +} diff --git a/Ryujinx.Graphics.Gpu/State/RtColorState.cs b/Ryujinx.Graphics.Gpu/State/RtColorState.cs new file mode 100644 index 00000000..bb6ae208 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/RtColorState.cs @@ -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; + } +} diff --git a/Ryujinx.Graphics.Gpu/State/RtDepthStencilState.cs b/Ryujinx.Graphics.Gpu/State/RtDepthStencilState.cs new file mode 100644 index 00000000..bfa812cb --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/RtDepthStencilState.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + struct RtDepthStencilState + { + public GpuVa Address; + public RtFormat Format; + public MemoryLayout MemoryLayout; + public int LayerSize; + } +} diff --git a/Ryujinx.Graphics.Gpu/State/RtFormat.cs b/Ryujinx.Graphics.Gpu/State/RtFormat.cs new file mode 100644 index 00000000..960da445 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/RtFormat.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/State/ShaderState.cs b/Ryujinx.Graphics.Gpu/State/ShaderState.cs new file mode 100644 index 00000000..536d7dcf --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/ShaderState.cs @@ -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; + } + } +} diff --git a/Ryujinx.Graphics.Gpu/State/ShaderType.cs b/Ryujinx.Graphics.Gpu/State/ShaderType.cs new file mode 100644 index 00000000..703159f4 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/ShaderType.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + enum ShaderType + { + Vertex, + TessellationControl, + TessellationEvaluation, + Geometry, + Fragment + } +} diff --git a/Ryujinx.Graphics.Gpu/State/Size3D.cs b/Ryujinx.Graphics.Gpu/State/Size3D.cs new file mode 100644 index 00000000..82771389 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/Size3D.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + struct Size3D + { + public int Width; + public int Height; + public int Depth; + } +} diff --git a/Ryujinx.Graphics.Gpu/State/StateWriteFlags.cs b/Ryujinx.Graphics.Gpu/State/StateWriteFlags.cs new file mode 100644 index 00000000..32d5127a --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/StateWriteFlags.cs @@ -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 + } +} diff --git a/Ryujinx.Graphics.Gpu/State/StencilBackMasks.cs b/Ryujinx.Graphics.Gpu/State/StencilBackMasks.cs new file mode 100644 index 00000000..d3779f87 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/StencilBackMasks.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + struct StencilBackMasks + { + public int FuncRef; + public int Mask; + public int FuncMask; + } +} diff --git a/Ryujinx.Graphics.Gpu/State/StencilBackTestState.cs b/Ryujinx.Graphics.Gpu/State/StencilBackTestState.cs new file mode 100644 index 00000000..18b31eaa --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/StencilBackTestState.cs @@ -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; + } +} diff --git a/Ryujinx.Graphics.Gpu/State/StencilTestState.cs b/Ryujinx.Graphics.Gpu/State/StencilTestState.cs new file mode 100644 index 00000000..b60f002f --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/StencilTestState.cs @@ -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; + } +} diff --git a/Ryujinx.Graphics.Gpu/State/UniformBufferState.cs b/Ryujinx.Graphics.Gpu/State/UniformBufferState.cs new file mode 100644 index 00000000..80e4b6bc --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/UniformBufferState.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + struct UniformBufferState + { + public int Size; + public GpuVa Address; + public int Offset; + } +} diff --git a/Ryujinx.Graphics.Gpu/State/VertexAttribState.cs b/Ryujinx.Graphics.Gpu/State/VertexAttribState.cs new file mode 100644 index 00000000..0e327a75 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/VertexAttribState.cs @@ -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; + } + } +} diff --git a/Ryujinx.Graphics.Gpu/State/VertexBufferDrawState.cs b/Ryujinx.Graphics.Gpu/State/VertexBufferDrawState.cs new file mode 100644 index 00000000..c2f3b7bb --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/VertexBufferDrawState.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + struct VertexBufferDrawState + { + public int First; + public int Count; + } +} diff --git a/Ryujinx.Graphics.Gpu/State/VertexBufferState.cs b/Ryujinx.Graphics.Gpu/State/VertexBufferState.cs new file mode 100644 index 00000000..a8bd2c92 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/VertexBufferState.cs @@ -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; + } + } +} diff --git a/Ryujinx.Graphics.Gpu/State/ViewportExtents.cs b/Ryujinx.Graphics.Gpu/State/ViewportExtents.cs new file mode 100644 index 00000000..4be2db90 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/ViewportExtents.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + struct ViewportExtents + { + public ushort X; + public ushort Width; + public ushort Y; + public ushort Height; + public float DepthNear; + public float DepthFar; + } +} diff --git a/Ryujinx.Graphics.Gpu/State/ViewportTransform.cs b/Ryujinx.Graphics.Gpu/State/ViewportTransform.cs new file mode 100644 index 00000000..335b039c --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/ViewportTransform.cs @@ -0,0 +1,36 @@ +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.Gpu.State +{ + struct ViewportTransform + { + public float ScaleX; + public float ScaleY; + public float ScaleZ; + public float TranslateX; + public float TranslateY; + public float TranslateZ; + public uint Swizzle; + public uint SubpixelPrecisionBias; + + public ViewportSwizzle UnpackSwizzleX() + { + return (ViewportSwizzle)(Swizzle & 7); + } + + public ViewportSwizzle UnpackSwizzleY() + { + return (ViewportSwizzle)((Swizzle >> 4) & 7); + } + + public ViewportSwizzle UnpackSwizzleZ() + { + return (ViewportSwizzle)((Swizzle >> 8) & 7); + } + + public ViewportSwizzle UnpackSwizzleW() + { + return (ViewportSwizzle)((Swizzle >> 12) & 7); + } + } +} |
