From 788ca6a411762035a6a7a88100c4b582b47ee82d Mon Sep 17 00:00:00 2001 From: gdkchan Date: Wed, 15 Jul 2020 00:01:10 -0300 Subject: [PATCH] Initial transform feedback support (#1370) * Initial transform feedback support * Some nits and fixes * Update ReportCounterType and Write method * Can't change shader or TFB bindings while TFB is active * Fix geometry shader input names with new naming --- Ryujinx.Graphics.GAL/IPipeline.cs | 5 ++ Ryujinx.Graphics.GAL/IRenderer.cs | 2 +- .../TransformFeedbackDescriptor.cs | 19 +++++ Ryujinx.Graphics.Gpu/Constants.cs | 5 ++ Ryujinx.Graphics.Gpu/Engine/MethodReport.cs | 10 +-- Ryujinx.Graphics.Gpu/Engine/Methods.cs | 44 +++++++++++- Ryujinx.Graphics.Gpu/Memory/BufferManager.cs | 50 +++++++++++++- Ryujinx.Graphics.Gpu/Memory/MemoryAccessor.cs | 4 +- Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs | 36 +++++++++- Ryujinx.Graphics.Gpu/State/GpuState.cs | 19 +++-- Ryujinx.Graphics.Gpu/State/GpuStateTable.cs | 54 ++++++++------- Ryujinx.Graphics.Gpu/State/MethodOffset.cs | 6 +- .../State/ReportCounterType.cs | 11 +-- Ryujinx.Graphics.Gpu/State/TfBufferState.cs | 18 +++++ Ryujinx.Graphics.Gpu/State/TfState.cs | 15 ++++ Ryujinx.Graphics.OpenGL/EnumConversion.cs | 25 +++++++ Ryujinx.Graphics.OpenGL/Pipeline.cs | 46 ++++++++++++- Ryujinx.Graphics.OpenGL/Program.cs | 54 ++++++++++++++- Ryujinx.Graphics.OpenGL/Renderer.cs | 4 +- .../CodeGen/Glsl/Declarations.cs | 25 +++++-- .../CodeGen/Glsl/GlslGenerator.cs | 5 +- .../CodeGen/Glsl/OperandManager.cs | 10 ++- .../CodeGen/Glsl/Varying.cs | 69 +++++++++++++++++++ 23 files changed, 468 insertions(+), 68 deletions(-) create mode 100644 Ryujinx.Graphics.GAL/TransformFeedbackDescriptor.cs create mode 100644 Ryujinx.Graphics.Gpu/State/TfBufferState.cs create mode 100644 Ryujinx.Graphics.Gpu/State/TfState.cs create mode 100644 Ryujinx.Graphics.Shader/CodeGen/Glsl/Varying.cs diff --git a/Ryujinx.Graphics.GAL/IPipeline.cs b/Ryujinx.Graphics.GAL/IPipeline.cs index 3eb778e08..7c67a1720 100644 --- a/Ryujinx.Graphics.GAL/IPipeline.cs +++ b/Ryujinx.Graphics.GAL/IPipeline.cs @@ -7,6 +7,8 @@ namespace Ryujinx.Graphics.GAL { void Barrier(); + void BeginTransformFeedback(PrimitiveTopology topology); + void ClearRenderTargetColor(int index, uint componentMask, ColorF color); void ClearRenderTargetDepthStencil( @@ -27,6 +29,8 @@ namespace Ryujinx.Graphics.GAL int firstVertex, int firstInstance); + void EndTransformFeedback(); + void SetBlendState(int index, BlendDescriptor blend); void SetDepthBias(PolygonModeMask enables, float factor, float units, float clamp); @@ -73,6 +77,7 @@ namespace Ryujinx.Graphics.GAL void SetTexture(int index, ShaderStage stage, ITexture texture); + void SetTransformFeedbackBuffer(int index, BufferRange buffer); void SetUniformBuffer(int index, ShaderStage stage, BufferRange buffer); void SetUserClipDistance(int index, bool enableClip); diff --git a/Ryujinx.Graphics.GAL/IRenderer.cs b/Ryujinx.Graphics.GAL/IRenderer.cs index 1052f1476..6fd3feba6 100644 --- a/Ryujinx.Graphics.GAL/IRenderer.cs +++ b/Ryujinx.Graphics.GAL/IRenderer.cs @@ -13,7 +13,7 @@ namespace Ryujinx.Graphics.GAL BufferHandle CreateBuffer(int size); - IProgram CreateProgram(IShader[] shaders); + IProgram CreateProgram(IShader[] shaders, TransformFeedbackDescriptor[] transformFeedbackDescriptors); ISampler CreateSampler(SamplerCreateInfo info); ITexture CreateTexture(TextureCreateInfo info, float scale); diff --git a/Ryujinx.Graphics.GAL/TransformFeedbackDescriptor.cs b/Ryujinx.Graphics.GAL/TransformFeedbackDescriptor.cs new file mode 100644 index 000000000..690b9108e --- /dev/null +++ b/Ryujinx.Graphics.GAL/TransformFeedbackDescriptor.cs @@ -0,0 +1,19 @@ +using System; + +namespace Ryujinx.Graphics.GAL +{ + public struct TransformFeedbackDescriptor + { + public int BufferIndex { get; } + public int Stride { get; } + + public byte[] VaryingLocations { get; } + + public TransformFeedbackDescriptor(int bufferIndex, int stride, byte[] varyingLocations) + { + BufferIndex = bufferIndex; + Stride = stride; + VaryingLocations = varyingLocations ?? throw new ArgumentNullException(nameof(varyingLocations)); + } + } +} diff --git a/Ryujinx.Graphics.Gpu/Constants.cs b/Ryujinx.Graphics.Gpu/Constants.cs index ac6b6139c..231152f5e 100644 --- a/Ryujinx.Graphics.Gpu/Constants.cs +++ b/Ryujinx.Graphics.Gpu/Constants.cs @@ -35,6 +35,11 @@ namespace Ryujinx.Graphics.Gpu /// public const int TotalGpStorageBuffers = 16; + /// + /// Maximum number of transform feedback buffers. + /// + public const int TotalTransformFeedbackBuffers = 4; + /// /// Maximum number of render target color buffers. /// diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodReport.cs b/Ryujinx.Graphics.Gpu/Engine/MethodReport.cs index 224a4da18..fcea43891 100644 --- a/Ryujinx.Graphics.Gpu/Engine/MethodReport.cs +++ b/Ryujinx.Graphics.Gpu/Engine/MethodReport.cs @@ -61,8 +61,6 @@ namespace Ryujinx.Graphics.Gpu.Engine /// Counter to be written to memory private void ReportCounter(GpuState state, ReportCounterType type) { - CounterData counterData = new CounterData(); - var rs = state.Get(MethodOffset.ReportState); ulong gpuVa = rs.Address.Pack(); @@ -80,16 +78,14 @@ namespace Ryujinx.Graphics.Gpu.Engine EventHandler resultHandler = (object evt, ulong result) => { + CounterData counterData = new CounterData(); + counterData.Counter = result; counterData.Timestamp = ticks; - Span counterDataSpan = MemoryMarshal.CreateSpan(ref counterData, 1); - - Span data = MemoryMarshal.Cast(counterDataSpan); - if (counter?.Invalid != true) { - _context.MemoryAccessor.Write(gpuVa, data); + _context.MemoryAccessor.Write(gpuVa, counterData); } }; diff --git a/Ryujinx.Graphics.Gpu/Engine/Methods.cs b/Ryujinx.Graphics.Gpu/Engine/Methods.cs index d5b11c2cd..093f90488 100644 --- a/Ryujinx.Graphics.Gpu/Engine/Methods.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Methods.cs @@ -40,6 +40,8 @@ namespace Ryujinx.Graphics.Gpu.Engine private bool _forceShaderUpdate; + private bool _prevTfEnable; + /// /// Creates a new instance of the GPU methods class. /// @@ -124,6 +126,14 @@ namespace Ryujinx.Graphics.Gpu.Engine /// Guest GPU state private void UpdateState(GpuState state) { + bool tfEnable = state.Get(MethodOffset.TfEnable); + + if (!tfEnable && _prevTfEnable) + { + _context.Renderer.Pipeline.EndTransformFeedback(); + _prevTfEnable = false; + } + // 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. @@ -134,6 +144,11 @@ namespace Ryujinx.Graphics.Gpu.Engine UpdateShaderState(state); } + if (state.QueryModified(MethodOffset.TfBufferState)) + { + UpdateTfBufferState(state); + } + if (state.QueryModified(MethodOffset.ClipDistanceEnable)) { UpdateUserClipState(state); @@ -258,6 +273,12 @@ namespace Ryujinx.Graphics.Gpu.Engine } CommitBindings(); + + if (tfEnable && !_prevTfEnable) + { + _context.Renderer.Pipeline.BeginTransformFeedback(PrimitiveType.Convert()); + _prevTfEnable = true; + } } /// @@ -318,7 +339,7 @@ namespace Ryujinx.Graphics.Gpu.Engine /// /// Current GPU state /// Use draw buffers information from render target control register - /// If this is not -1, it indicates that only the given indexed target will be used. + /// If this is not -1, it indicates that only the given indexed target will be used. private void UpdateRenderTargetState(GpuState state, bool useControl, int singleUse = -1) { var rtControl = state.Get(MethodOffset.RtControl); @@ -1003,6 +1024,27 @@ namespace Ryujinx.Graphics.Gpu.Engine _context.Renderer.Pipeline.SetProgram(gs.HostProgram); } + /// + /// Updates transform feedback buffer state based on the guest GPU state. + /// + /// Current GPU state + private void UpdateTfBufferState(GpuState state) + { + for (int index = 0; index < Constants.TotalTransformFeedbackBuffers; index++) + { + TfBufferState tfb = state.Get(MethodOffset.TfBufferState, index); + + if (!tfb.Enable) + { + BufferManager.SetTransformFeedbackBuffer(index, 0, 0); + + continue; + } + + BufferManager.SetTransformFeedbackBuffer(index, tfb.Address.Pack(), (uint)tfb.Size); + } + } + /// /// Updates user-defined clipping based on the guest GPU state. /// diff --git a/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs b/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs index 533b05768..9712f58fb 100644 --- a/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs +++ b/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs @@ -24,8 +24,8 @@ namespace Ryujinx.Graphics.Gpu.Memory private Buffer[] _bufferOverlaps; private IndexBuffer _indexBuffer; - private VertexBuffer[] _vertexBuffers; + private BufferBounds[] _transformFeedbackBuffers; private class BuffersPerStage { @@ -56,6 +56,7 @@ namespace Ryujinx.Graphics.Gpu.Memory private bool _indexBufferDirty; private bool _vertexBuffersDirty; private uint _vertexBuffersEnableMask; + private bool _transformFeedbackBuffersDirty; private bool _rebind; @@ -73,6 +74,8 @@ namespace Ryujinx.Graphics.Gpu.Memory _vertexBuffers = new VertexBuffer[Constants.TotalVertexBuffers]; + _transformFeedbackBuffers = new BufferBounds[Constants.TotalTransformFeedbackBuffers]; + _cpStorageBuffers = new BuffersPerStage(Constants.TotalCpStorageBuffers); _cpUniformBuffers = new BuffersPerStage(Constants.TotalCpUniformBuffers); @@ -144,6 +147,16 @@ namespace Ryujinx.Graphics.Gpu.Memory } } + public void SetTransformFeedbackBuffer(int index, ulong gpuVa, ulong size) + { + ulong address = TranslateAndCreateBuffer(gpuVa, size); + + _transformFeedbackBuffers[index].Address = address; + _transformFeedbackBuffers[index].Size = size; + + _transformFeedbackBuffersDirty = true; + } + /// /// Sets a storage buffer on the compute pipeline. /// Storage buffers can be read and written to on shaders. @@ -522,6 +535,41 @@ namespace Ryujinx.Graphics.Gpu.Memory } } + if (_transformFeedbackBuffersDirty) + { + _transformFeedbackBuffersDirty = false; + + for (int index = 0; index < Constants.TotalTransformFeedbackBuffers; index++) + { + BufferBounds tfb = _transformFeedbackBuffers[index]; + + if (tfb.Address == 0) + { + _context.Renderer.Pipeline.SetTransformFeedbackBuffer(index, new BufferRange(BufferHandle.Null, 0, 0)); + + continue; + } + + BufferRange buffer = GetBufferRange(tfb.Address, tfb.Size); + + _context.Renderer.Pipeline.SetTransformFeedbackBuffer(index, buffer); + } + } + else + { + for (int index = 0; index < Constants.TotalTransformFeedbackBuffers; index++) + { + BufferBounds tfb = _transformFeedbackBuffers[index]; + + if (tfb.Address == 0) + { + continue; + } + + SynchronizeBufferRange(tfb.Address, tfb.Size); + } + } + if (_gpStorageBuffersDirty || _rebind) { _gpStorageBuffersDirty = false; diff --git a/Ryujinx.Graphics.Gpu/Memory/MemoryAccessor.cs b/Ryujinx.Graphics.Gpu/Memory/MemoryAccessor.cs index 5cc8ec24f..2c53f6991 100644 --- a/Ryujinx.Graphics.Gpu/Memory/MemoryAccessor.cs +++ b/Ryujinx.Graphics.Gpu/Memory/MemoryAccessor.cs @@ -63,11 +63,11 @@ namespace Ryujinx.Graphics.Gpu.Memory /// /// GPU virtual address to write the value into /// The value to be written - public void Write(ulong gpuVa, int value) + public void Write(ulong gpuVa, T value) where T : unmanaged { ulong processVa = _context.MemoryManager.Translate(gpuVa); - _context.PhysicalMemory.Write(processVa, BitConverter.GetBytes(value)); + _context.PhysicalMemory.Write(processVa, MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref value, 1))); } /// diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs index 8a1abe32f..d16f10578 100644 --- a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs +++ b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs @@ -82,7 +82,7 @@ namespace Ryujinx.Graphics.Gpu.Shader shader.HostShader = _context.Renderer.CompileShader(shader.Program); - IProgram hostProgram = _context.Renderer.CreateProgram(new IShader[] { shader.HostShader }); + IProgram hostProgram = _context.Renderer.CreateProgram(new IShader[] { shader.HostShader }, null); ShaderBundle cpShader = new ShaderBundle(hostProgram, shader); @@ -150,6 +150,8 @@ namespace Ryujinx.Graphics.Gpu.Shader continue; } + var tfd = GetTransformFeedbackDescriptors(state); + IShader hostShader = _context.Renderer.CompileShader(program); shaders[stage].HostShader = hostShader; @@ -157,7 +159,7 @@ namespace Ryujinx.Graphics.Gpu.Shader hostShaders.Add(hostShader); } - IProgram hostProgram = _context.Renderer.CreateProgram(hostShaders.ToArray()); + IProgram hostProgram = _context.Renderer.CreateProgram(hostShaders.ToArray(), GetTransformFeedbackDescriptors(state)); ShaderBundle gpShaders = new ShaderBundle(hostProgram, shaders); @@ -173,6 +175,36 @@ namespace Ryujinx.Graphics.Gpu.Shader return gpShaders; } + /// + /// Gets transform feedback state from the current GPU state. + /// + /// Current GPU state + /// Four transform feedback descriptors for the enabled TFBs, or null if TFB is disabled + private TransformFeedbackDescriptor[] GetTransformFeedbackDescriptors(GpuState state) + { + bool tfEnable = state.Get(MethodOffset.TfEnable); + + if (!tfEnable) + { + return null; + } + + TransformFeedbackDescriptor[] descs = new TransformFeedbackDescriptor[Constants.TotalTransformFeedbackBuffers]; + + for (int i = 0; i < Constants.TotalTransformFeedbackBuffers; i++) + { + var tf = state.Get(MethodOffset.TfState, i); + + int length = (int)Math.Min((uint)tf.VaryingsCount, 0x80); + + var varyingLocations = state.GetSpan(MethodOffset.TfVaryingLocations + i * 0x80, length).ToArray(); + + descs[i] = new TransformFeedbackDescriptor(tf.BufferIndex, tf.Stride, varyingLocations); + } + + return descs; + } + /// /// Checks if compute shader code in memory is equal to the cached shader. /// diff --git a/Ryujinx.Graphics.Gpu/State/GpuState.cs b/Ryujinx.Graphics.Gpu/State/GpuState.cs index 264719b44..9e7d9492a 100644 --- a/Ryujinx.Graphics.Gpu/State/GpuState.cs +++ b/Ryujinx.Graphics.Gpu/State/GpuState.cs @@ -346,7 +346,7 @@ namespace Ryujinx.Graphics.Gpu.State /// Register offset /// Index for indexed data /// The data at the specified location - public T Get(MethodOffset offset, int index) where T : struct + public T Get(MethodOffset offset, int index) where T : unmanaged { Register register = _registers[(int)offset]; @@ -364,11 +364,22 @@ namespace Ryujinx.Graphics.Gpu.State /// Type of the data /// Register offset /// The data at the specified location - public T Get(MethodOffset offset) where T : struct + public T Get(MethodOffset offset) where T : unmanaged { return MemoryMarshal.Cast(_memory.AsSpan().Slice((int)offset))[0]; } + /// + /// Gets a span of the data at a given register offset. + /// + /// Register offset + /// Length of the data in bytes + /// The data at the specified location + public Span GetSpan(MethodOffset offset, int length) + { + return MemoryMarshal.Cast(_memory.AsSpan().Slice((int)offset)).Slice(0, length); + } + /// /// Sets indexed data to a given register offset. /// @@ -376,7 +387,7 @@ namespace Ryujinx.Graphics.Gpu.State /// Register offset /// Index for indexed data /// The data to set - public void Set(MethodOffset offset, int index, T data) where T : struct + public void Set(MethodOffset offset, int index, T data) where T : unmanaged { Register register = _registers[(int)offset]; @@ -394,7 +405,7 @@ namespace Ryujinx.Graphics.Gpu.State /// Type of the data /// Register offset /// The data to set - public void Set(MethodOffset offset, T data) where T : struct + public void Set(MethodOffset offset, T data) where T : unmanaged { ReadOnlySpan intSpan = MemoryMarshal.Cast(MemoryMarshal.CreateReadOnlySpan(ref data, 1)); intSpan.CopyTo(_memory.AsSpan().Slice((int)offset, intSpan.Length)); diff --git a/Ryujinx.Graphics.Gpu/State/GpuStateTable.cs b/Ryujinx.Graphics.Gpu/State/GpuStateTable.cs index 65df5f4e6..acc0fe858 100644 --- a/Ryujinx.Graphics.Gpu/State/GpuStateTable.cs +++ b/Ryujinx.Graphics.Gpu/State/GpuStateTable.cs @@ -53,32 +53,34 @@ namespace Ryujinx.Graphics.Gpu.State /// public static TableItem[] Table = new TableItem[] { - new TableItem(MethodOffset.RtColorState, typeof(RtColorState), Constants.TotalRenderTargets), - new TableItem(MethodOffset.ViewportTransform, typeof(ViewportTransform), Constants.TotalViewports), - new TableItem(MethodOffset.ViewportExtents, typeof(ViewportExtents), Constants.TotalViewports), - new TableItem(MethodOffset.VertexBufferDrawState, typeof(VertexBufferDrawState), 1), - new TableItem(MethodOffset.DepthBiasState, typeof(DepthBiasState), 1), - new TableItem(MethodOffset.ScissorState, typeof(ScissorState), Constants.TotalViewports), - new TableItem(MethodOffset.StencilBackMasks, typeof(StencilBackMasks), 1), - new TableItem(MethodOffset.RtDepthStencilState, typeof(RtDepthStencilState), 1), - new TableItem(MethodOffset.VertexAttribState, typeof(VertexAttribState), 16), - new TableItem(MethodOffset.RtDepthStencilSize, typeof(Size3D), 1), - new TableItem(MethodOffset.BlendEnable, typeof(Boolean32), Constants.TotalRenderTargets), - new TableItem(MethodOffset.StencilTestState, typeof(StencilTestState), 1), - new TableItem(MethodOffset.SamplerPoolState, typeof(PoolState), 1), - new TableItem(MethodOffset.TexturePoolState, typeof(PoolState), 1), - new TableItem(MethodOffset.StencilBackTestState, typeof(StencilBackTestState), 1), - new TableItem(MethodOffset.ShaderBaseAddress, typeof(GpuVa), 1), - new TableItem(MethodOffset.PrimitiveRestartState, typeof(PrimitiveRestartState), 1), - new TableItem(MethodOffset.IndexBufferState, typeof(IndexBufferState), 1), - new TableItem(MethodOffset.VertexBufferInstanced, typeof(Boolean32), 16), - new TableItem(MethodOffset.FaceState, typeof(FaceState), 1), - new TableItem(MethodOffset.RtColorMask, typeof(RtColorMask), Constants.TotalRenderTargets), - new TableItem(MethodOffset.VertexBufferState, typeof(VertexBufferState), 16), - new TableItem(MethodOffset.BlendConstant, typeof(ColorF), 1), - new TableItem(MethodOffset.BlendState, typeof(BlendState), Constants.TotalRenderTargets), - new TableItem(MethodOffset.VertexBufferEndAddress, typeof(GpuVa), 16), - new TableItem(MethodOffset.ShaderState, typeof(ShaderState), 6), + new TableItem(MethodOffset.TfBufferState, typeof(TfBufferState), Constants.TotalTransformFeedbackBuffers), + new TableItem(MethodOffset.TfState, typeof(TfState), Constants.TotalTransformFeedbackBuffers), + new TableItem(MethodOffset.RtColorState, typeof(RtColorState), Constants.TotalRenderTargets), + new TableItem(MethodOffset.ViewportTransform, typeof(ViewportTransform), Constants.TotalViewports), + new TableItem(MethodOffset.ViewportExtents, typeof(ViewportExtents), Constants.TotalViewports), + new TableItem(MethodOffset.VertexBufferDrawState, typeof(VertexBufferDrawState), 1), + new TableItem(MethodOffset.DepthBiasState, typeof(DepthBiasState), 1), + new TableItem(MethodOffset.ScissorState, typeof(ScissorState), Constants.TotalViewports), + new TableItem(MethodOffset.StencilBackMasks, typeof(StencilBackMasks), 1), + new TableItem(MethodOffset.RtDepthStencilState, typeof(RtDepthStencilState), 1), + new TableItem(MethodOffset.VertexAttribState, typeof(VertexAttribState), Constants.TotalVertexAttribs), + new TableItem(MethodOffset.RtDepthStencilSize, typeof(Size3D), 1), + new TableItem(MethodOffset.BlendEnable, typeof(Boolean32), Constants.TotalRenderTargets), + new TableItem(MethodOffset.StencilTestState, typeof(StencilTestState), 1), + new TableItem(MethodOffset.SamplerPoolState, typeof(PoolState), 1), + new TableItem(MethodOffset.TexturePoolState, typeof(PoolState), 1), + new TableItem(MethodOffset.StencilBackTestState, typeof(StencilBackTestState), 1), + new TableItem(MethodOffset.ShaderBaseAddress, typeof(GpuVa), 1), + new TableItem(MethodOffset.PrimitiveRestartState, typeof(PrimitiveRestartState), 1), + new TableItem(MethodOffset.IndexBufferState, typeof(IndexBufferState), 1), + new TableItem(MethodOffset.VertexBufferInstanced, typeof(Boolean32), Constants.TotalVertexBuffers), + new TableItem(MethodOffset.FaceState, typeof(FaceState), 1), + new TableItem(MethodOffset.RtColorMask, typeof(RtColorMask), Constants.TotalRenderTargets), + new TableItem(MethodOffset.VertexBufferState, typeof(VertexBufferState), Constants.TotalVertexBuffers), + new TableItem(MethodOffset.BlendConstant, typeof(ColorF), 1), + new TableItem(MethodOffset.BlendState, typeof(BlendState), Constants.TotalRenderTargets), + new TableItem(MethodOffset.VertexBufferEndAddress, typeof(GpuVa), Constants.TotalVertexBuffers), + new TableItem(MethodOffset.ShaderState, typeof(ShaderState), Constants.ShaderStages + 1), }; } } \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/State/MethodOffset.cs b/Ryujinx.Graphics.Gpu/State/MethodOffset.cs index d7d5d9039..b0eb6f328 100644 --- a/Ryujinx.Graphics.Gpu/State/MethodOffset.cs +++ b/Ryujinx.Graphics.Gpu/State/MethodOffset.cs @@ -28,10 +28,13 @@ namespace Ryujinx.Graphics.Gpu.State SyncpointAction = 0xb2, CopyBuffer = 0xc0, RasterizeEnable = 0xdf, + TfBufferState = 0xe0, CopyBufferParams = 0x100, + TfState = 0x1c0, CopyBufferSwizzle = 0x1c2, CopyBufferDstTexture = 0x1c3, CopyBufferSrcTexture = 0x1ca, + TfEnable = 0x1d1, RtColorState = 0x200, CopyTextureControl = 0x223, CopyRegion = 0x22c, @@ -116,6 +119,7 @@ namespace Ryujinx.Graphics.Gpu.State UniformBufferBindTessEvaluation = 0x914, UniformBufferBindGeometry = 0x91c, UniformBufferBindFragment = 0x924, - TextureBufferIndex = 0x982 + TextureBufferIndex = 0x982, + TfVaryingLocations = 0xa00 } } \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/State/ReportCounterType.cs b/Ryujinx.Graphics.Gpu/State/ReportCounterType.cs index cface55d0..6bde28441 100644 --- a/Ryujinx.Graphics.Gpu/State/ReportCounterType.cs +++ b/Ryujinx.Graphics.Gpu/State/ReportCounterType.cs @@ -11,18 +11,19 @@ namespace Ryujinx.Graphics.Gpu.State VertexShaderInvocations = 5, GeometryShaderInvocations = 7, GeometryShaderPrimitives = 9, + ZcullStats0 = 0xa, TransformFeedbackPrimitivesWritten = 0xb, + ZcullStats1 = 0xc, + ZcullStats2 = 0xe, ClipperInputPrimitives = 0xf, + ZcullStats3 = 0x10, ClipperOutputPrimitives = 0x11, PrimitivesGenerated = 0x12, FragmentShaderInvocations = 0x13, SamplesPassed = 0x15, + TransformFeedbackOffset = 0x1a, TessControlShaderInvocations = 0x1b, TessEvaluationShaderInvocations = 0x1d, - TessEvaluationShaderPrimitives = 0x1f, - ZcullStats0 = 0x2a, - ZcullStats1 = 0x2c, - ZcullStats2 = 0x2e, - ZcullStats3 = 0x30 + TessEvaluationShaderPrimitives = 0x1f } } \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/State/TfBufferState.cs b/Ryujinx.Graphics.Gpu/State/TfBufferState.cs new file mode 100644 index 000000000..24dc0952f --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/TfBufferState.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Transform feedback buffer state. + /// + struct TfBufferState + { +#pragma warning disable CS0649 + public Boolean32 Enable; + public GpuVa Address; + public int Size; + public int Offset; + public uint Padding0; + public uint Padding1; + public uint Padding2; +#pragma warning restore CS0649 + } +} diff --git a/Ryujinx.Graphics.Gpu/State/TfState.cs b/Ryujinx.Graphics.Gpu/State/TfState.cs new file mode 100644 index 000000000..fb8b950bc --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/TfState.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Transform feedback state. + /// + struct TfState + { +#pragma warning disable CS0649 + public int BufferIndex; + public int VaryingsCount; + public int Stride; + public uint Padding; +#pragma warning restore CS0649 + } +} diff --git a/Ryujinx.Graphics.OpenGL/EnumConversion.cs b/Ryujinx.Graphics.OpenGL/EnumConversion.cs index a4bd39cca..09860be3e 100644 --- a/Ryujinx.Graphics.OpenGL/EnumConversion.cs +++ b/Ryujinx.Graphics.OpenGL/EnumConversion.cs @@ -331,6 +331,31 @@ namespace Ryujinx.Graphics.OpenGL return PrimitiveType.Points; } + public static TransformFeedbackPrimitiveType ConvertToTfType(this PrimitiveTopology topology) + { + switch (topology) + { + case PrimitiveTopology.Points: + return TransformFeedbackPrimitiveType.Points; + case PrimitiveTopology.Lines: + case PrimitiveTopology.LineLoop: + case PrimitiveTopology.LineStrip: + case PrimitiveTopology.LinesAdjacency: + case PrimitiveTopology.LineStripAdjacency: + return TransformFeedbackPrimitiveType.Lines; + case PrimitiveTopology.Triangles: + case PrimitiveTopology.TriangleStrip: + case PrimitiveTopology.TriangleFan: + case PrimitiveTopology.TrianglesAdjacency: + case PrimitiveTopology.TriangleStripAdjacency: + return TransformFeedbackPrimitiveType.Triangles; + } + + Logger.PrintDebug(LogClass.Gpu, $"Invalid {nameof(PrimitiveTopology)} enum value: {topology}."); + + return TransformFeedbackPrimitiveType.Points; + } + public static OpenTK.Graphics.OpenGL.StencilOp Convert(this GAL.StencilOp op) { switch (op) diff --git a/Ryujinx.Graphics.OpenGL/Pipeline.cs b/Ryujinx.Graphics.OpenGL/Pipeline.cs index 9623c826f..7537b44fa 100644 --- a/Ryujinx.Graphics.OpenGL/Pipeline.cs +++ b/Ryujinx.Graphics.OpenGL/Pipeline.cs @@ -45,6 +45,8 @@ namespace Ryujinx.Graphics.OpenGL private bool _scissor0Enable = false; + private bool _tfEnabled; + ColorF _blendConstant = new ColorF(0, 0, 0, 0); internal Pipeline() @@ -76,6 +78,12 @@ namespace Ryujinx.Graphics.OpenGL GL.MemoryBarrier(MemoryBarrierFlags.AllBarrierBits); } + public void BeginTransformFeedback(PrimitiveTopology topology) + { + GL.BeginTransformFeedback(topology.ConvertToTfType()); + _tfEnabled = true; + } + public void ClearRenderTargetColor(int index, uint componentMask, ColorF color) { GL.ColorMask( @@ -512,6 +520,12 @@ namespace Ryujinx.Graphics.OpenGL } } + public void EndTransformFeedback() + { + GL.EndTransformFeedback(); + _tfEnabled = false; + } + public void SetBlendState(int index, BlendDescriptor blend) { if (!blend.Enable) @@ -713,7 +727,17 @@ namespace Ryujinx.Graphics.OpenGL public void SetProgram(IProgram program) { _program = (Program)program; - _program.Bind(); + + if (_tfEnabled) + { + GL.PauseTransformFeedback(); + _program.Bind(); + GL.ResumeTransformFeedback(); + } + else + { + _program.Bind(); + } SetRenderTargetScale(_fpRenderScale[0]); } @@ -904,6 +928,22 @@ namespace Ryujinx.Graphics.OpenGL } } + public void SetTransformFeedbackBuffer(int index, BufferRange buffer) + { + const BufferRangeTarget target = BufferRangeTarget.TransformFeedbackBuffer; + + if (_tfEnabled) + { + GL.PauseTransformFeedback(); + GL.BindBufferRange(target, index, buffer.Handle.ToInt32(), (IntPtr)buffer.Offset, buffer.Size); + GL.ResumeTransformFeedback(); + } + else + { + GL.BindBufferRange(target, index, buffer.Handle.ToInt32(), (IntPtr)buffer.Offset, buffer.Size); + } + } + public void SetUniformBuffer(int index, ShaderStage stage, BufferRange buffer) { SetBuffer(index, stage, buffer, isStorage: false); @@ -1132,7 +1172,7 @@ namespace Ryujinx.Graphics.OpenGL { // If the event has been flushed, then just use the values on the CPU. // The query object may already be repurposed for another draw (eg. begin + end). - return false; + return false; } if (compare == 0 && evt.Type == QueryTarget.SamplesPassed && evt.ClearCounter) @@ -1145,7 +1185,7 @@ namespace Ryujinx.Graphics.OpenGL // The GPU will flush the queries to CPU and evaluate the condition there instead. GL.Flush(); // The thread will be stalled manually flushing the counter, so flush GL commands now. - return false; + return false; } public bool TryHostConditionalRendering(ICounterEvent value, ICounterEvent compare, bool isEqual) diff --git a/Ryujinx.Graphics.OpenGL/Program.cs b/Ryujinx.Graphics.OpenGL/Program.cs index 8b4f6e242..a0f8eb015 100644 --- a/Ryujinx.Graphics.OpenGL/Program.cs +++ b/Ryujinx.Graphics.OpenGL/Program.cs @@ -2,6 +2,10 @@ using OpenTK.Graphics.OpenGL; using Ryujinx.Common.Logging; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Shader; +using Ryujinx.Graphics.Shader.CodeGen.Glsl; +using System; +using System.Collections.Generic; +using System.Linq; namespace Ryujinx.Graphics.OpenGL { @@ -31,7 +35,7 @@ namespace Ryujinx.Graphics.OpenGL private int[] _textureUnits; private int[] _imageUnits; - public Program(IShader[] shaders) + public Program(IShader[] shaders, TransformFeedbackDescriptor[] transformFeedbackDescriptors) { _ubBindingPoints = new int[UbsPerStage * ShaderStages]; _sbBindingPoints = new int[SbsPerStage * ShaderStages]; @@ -67,6 +71,54 @@ namespace Ryujinx.Graphics.OpenGL GL.AttachShader(Handle, shaderHandle); } + if (transformFeedbackDescriptors != null) + { + List varyings = new List(); + + int cbi = 0; + + foreach (var tfd in transformFeedbackDescriptors.OrderBy(x => x.BufferIndex)) + { + if (tfd.VaryingLocations.Length == 0) + { + continue; + } + + while (cbi < tfd.BufferIndex) + { + varyings.Add("gl_NextBuffer"); + + cbi++; + } + + int stride = Math.Min(128 * 4, (tfd.Stride + 3) & ~3); + + int j = 0; + + for (; j < tfd.VaryingLocations.Length && j * 4 < stride; j++) + { + byte location = tfd.VaryingLocations[j]; + + varyings.Add(Varying.GetName(location) ?? "gl_SkipComponents1"); + + j += Varying.GetSize(location) - 1; + } + + int feedbackBytes = j * 4; + + while (feedbackBytes < stride) + { + int bytes = Math.Min(16, stride - feedbackBytes); + + varyings.Add($"gl_SkipComponents{(bytes / 4)}"); + + feedbackBytes += bytes; + } + } + + GL.TransformFeedbackVaryings(Handle, varyings.Count, varyings.ToArray(), TransformFeedbackMode.InterleavedAttribs); + } + GL.LinkProgram(Handle); for (int index = 0; index < shaders.Length; index++) diff --git a/Ryujinx.Graphics.OpenGL/Renderer.cs b/Ryujinx.Graphics.OpenGL/Renderer.cs index cf90f81f5..49324637b 100644 --- a/Ryujinx.Graphics.OpenGL/Renderer.cs +++ b/Ryujinx.Graphics.OpenGL/Renderer.cs @@ -44,9 +44,9 @@ namespace Ryujinx.Graphics.OpenGL return Buffer.Create(size); } - public IProgram CreateProgram(IShader[] shaders) + public IProgram CreateProgram(IShader[] shaders, TransformFeedbackDescriptor[] transformFeedbackDescriptors) { - return new Program(shaders); + return new Program(shaders, transformFeedbackDescriptors); } public ISampler CreateSampler(SamplerCreateInfo info) diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs index f9d619286..efd30143b 100644 --- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs @@ -15,7 +15,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl public static void Declare(CodeGenContext context, StructuredProgramInfo info) { - context.AppendLine("#version 430 core"); + context.AppendLine("#version 440 core"); context.AppendLine("#extension GL_ARB_gpu_shader_int64 : enable"); context.AppendLine("#extension GL_ARB_shader_ballot : enable"); context.AppendLine("#extension GL_ARB_shader_group_vote : enable"); @@ -234,7 +234,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl string stage = OperandManager.GetShaderStagePrefix(context.Config.Stage); int scaleElements = context.TextureDescriptors.Count; - + if (context.Config.Stage == ShaderStage.Fragment) { scaleElements++; // Also includes render target scale, for gl_FragCoord. @@ -424,7 +424,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl }; } - context.AppendLine($"layout (location = {attr}) {iq}in vec4 {DefaultNames.IAttributePrefix}{attr}{suffix};"); + for (int c = 0; c < 4; c++) + { + char swzMask = "xyzw"[c]; + + context.AppendLine($"layout (location = {attr}, component = {c}) {iq}in float {DefaultNames.IAttributePrefix}{attr}_{swzMask}{suffix};"); + } } } @@ -452,12 +457,22 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl { for (int attr = 0; attr < MaxAttributes; attr++) { - context.AppendLine($"layout (location = {attr}) out vec4 {DefaultNames.OAttributePrefix}{attr};"); + for (int c = 0; c < 4; c++) + { + char swzMask = "xyzw"[c]; + + context.AppendLine($"layout (location = {attr}, component = {c}) out float {DefaultNames.OAttributePrefix}{attr}_{swzMask};"); + } } foreach (int attr in info.OAttributes.OrderBy(x => x).Where(x => x >= MaxAttributes)) { - context.AppendLine($"layout (location = {attr}) out vec4 {DefaultNames.OAttributePrefix}{attr};"); + for (int c = 0; c < 4; c++) + { + char swzMask = "xyzw"[c]; + + context.AppendLine($"layout (location = {attr}, component = {c}) out float {DefaultNames.OAttributePrefix}{attr}_{swzMask};"); + } } } diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslGenerator.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslGenerator.cs index 1465338e1..2105560af 100644 --- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslGenerator.cs +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslGenerator.cs @@ -56,7 +56,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl continue; } - context.AppendLine($"{DefaultNames.OAttributePrefix}{attr} = vec4(0);"); + context.AppendLine($"{DefaultNames.OAttributePrefix}{attr}_x = 0;"); + context.AppendLine($"{DefaultNames.OAttributePrefix}{attr}_y = 0;"); + context.AppendLine($"{DefaultNames.OAttributePrefix}{attr}_z = 0;"); + context.AppendLine($"{DefaultNames.OAttributePrefix}{attr}_w = 0;"); } } diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs index 971284f4c..8801fc111 100644 --- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs @@ -147,7 +147,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl { int value = attr.Value; - string swzMask = GetSwizzleMask((value >> 2) & 3); + char swzMask = GetSwizzleMask((value >> 2) & 3); if (value >= AttributeConsts.UserAttributeBase && value < AttributeConsts.UserAttributeEnd) @@ -158,15 +158,13 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl ? DefaultNames.OAttributePrefix : DefaultNames.IAttributePrefix; - string name = $"{prefix}{(value >> 4)}"; + string name = $"{prefix}{(value >> 4)}_{swzMask}"; if (stage == ShaderStage.Geometry && !isOutAttr) { name += $"[{indexExpr}]"; } - name += "." + swzMask; - return name; } else @@ -264,9 +262,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl return _stagePrefixes[index]; } - private static string GetSwizzleMask(int value) + private static char GetSwizzleMask(int value) { - return "xyzw".Substring(value, 1); + return "xyzw"[value]; } public static VariableType GetNodeDestType(IAstNode node) diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Varying.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Varying.cs new file mode 100644 index 000000000..b9b2afb47 --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Varying.cs @@ -0,0 +1,69 @@ +using Ryujinx.Graphics.Shader.Translation; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl +{ + public static class Varying + { + public static string GetName(int offset) + { + offset <<= 2; + + if (offset >= AttributeConsts.UserAttributeBase && + offset < AttributeConsts.UserAttributeEnd) + { + offset -= AttributeConsts.UserAttributeBase; + + string name = $"{ DefaultNames.OAttributePrefix}{(offset >> 4)}"; + + name += "_" + "xyzw"[(offset >> 2) & 3]; + + return name; + } + + switch (offset) + { + case AttributeConsts.PositionX: + case AttributeConsts.PositionY: + case AttributeConsts.PositionZ: + case AttributeConsts.PositionW: + return "gl_Position"; + case AttributeConsts.PointSize: + return "gl_PointSize"; + case AttributeConsts.ClipDistance0: + return "gl_ClipDistance[0]"; + case AttributeConsts.ClipDistance1: + return "gl_ClipDistance[1]"; + case AttributeConsts.ClipDistance2: + return "gl_ClipDistance[2]"; + case AttributeConsts.ClipDistance3: + return "gl_ClipDistance[3]"; + case AttributeConsts.ClipDistance4: + return "gl_ClipDistance[4]"; + case AttributeConsts.ClipDistance5: + return "gl_ClipDistance[5]"; + case AttributeConsts.ClipDistance6: + return "gl_ClipDistance[6]"; + case AttributeConsts.ClipDistance7: + return "gl_ClipDistance[7]"; + case AttributeConsts.VertexId: + return "gl_VertexID"; + } + + return null; + } + + public static int GetSize(int offset) + { + switch (offset << 2) + { + case AttributeConsts.PositionX: + case AttributeConsts.PositionY: + case AttributeConsts.PositionZ: + case AttributeConsts.PositionW: + return 4; + } + + return 1; + } + } +} \ No newline at end of file