diff --git a/Ryujinx.Graphics.GAL/IPipeline.cs b/Ryujinx.Graphics.GAL/IPipeline.cs
index 3eb778e08e..7c67a1720f 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 1052f1476c..6fd3feba60 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 0000000000..690b9108ed
--- /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 ac6b6139c0..231152f5ea 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 224a4da181..fcea438918 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 d5b11c2cd8..093f90488d 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 533b05768f..9712f58fb4 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 5cc8ec24f9..2c53f6991f 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 8a1abe32f3..d16f105787 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 264719b443..9e7d9492a6 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 65df5f4e67..acc0fe858d 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 d7d5d90391..b0eb6f328e 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 cface55d03..6bde284415 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 0000000000..24dc0952fe
--- /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 0000000000..fb8b950bcf
--- /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 a4bd39ccaf..09860be3e1 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 9623c826f8..7537b44fab 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 8b4f6e242a..a0f8eb0159 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 cf90f81f50..49324637bc 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 f9d619286a..efd30143ba 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 1465338e1e..2105560afc 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 971284f4c5..8801fc1113 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 0000000000..b9b2afb476
--- /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