diff options
| author | gdk <gab.dark.100@gmail.com> | 2019-10-13 03:02:07 -0300 |
|---|---|---|
| committer | Thog <thog@protonmail.com> | 2020-01-09 02:13:00 +0100 |
| commit | 1876b346fea647e8284a66bb6d62c38801035cff (patch) | |
| tree | 6eeff094298cda84d1613dc5ec0691e51d7b35f1 /Ryujinx.Graphics.Gpu/Engine | |
| parent | f617fb542a0e3d36012d77a4b5acbde7b08902f2 (diff) | |
Initial work
Diffstat (limited to 'Ryujinx.Graphics.Gpu/Engine')
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Engine/Compute.cs | 83 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Engine/ComputeParams.cs | 126 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Engine/ComputeShader.cs | 18 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Engine/GraphicsShader.cs | 17 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Engine/Inline2Memory.cs | 42 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Engine/MethodClear.cs | 55 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Engine/MethodCopyBuffer.cs | 79 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Engine/MethodCopyTexture.cs | 70 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Engine/MethodDraw.cs | 133 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Engine/MethodReport.cs | 100 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Engine/MethodResetCounter.cs | 26 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferBind.cs | 52 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferUpdate.cs | 18 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Engine/Methods.cs | 784 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Engine/ShaderAddresses.cs | 34 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Engine/ShaderCache.cs | 228 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Engine/ShaderDumper.cs | 126 |
17 files changed, 1991 insertions, 0 deletions
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 |
