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
This commit is contained in:
gdkchan 2020-07-15 00:01:10 -03:00 committed by GitHub
parent 16dafe6316
commit 788ca6a411
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 468 additions and 68 deletions

View file

@ -7,6 +7,8 @@ namespace Ryujinx.Graphics.GAL
{ {
void Barrier(); void Barrier();
void BeginTransformFeedback(PrimitiveTopology topology);
void ClearRenderTargetColor(int index, uint componentMask, ColorF color); void ClearRenderTargetColor(int index, uint componentMask, ColorF color);
void ClearRenderTargetDepthStencil( void ClearRenderTargetDepthStencil(
@ -27,6 +29,8 @@ namespace Ryujinx.Graphics.GAL
int firstVertex, int firstVertex,
int firstInstance); int firstInstance);
void EndTransformFeedback();
void SetBlendState(int index, BlendDescriptor blend); void SetBlendState(int index, BlendDescriptor blend);
void SetDepthBias(PolygonModeMask enables, float factor, float units, float clamp); 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 SetTexture(int index, ShaderStage stage, ITexture texture);
void SetTransformFeedbackBuffer(int index, BufferRange buffer);
void SetUniformBuffer(int index, ShaderStage stage, BufferRange buffer); void SetUniformBuffer(int index, ShaderStage stage, BufferRange buffer);
void SetUserClipDistance(int index, bool enableClip); void SetUserClipDistance(int index, bool enableClip);

View file

@ -13,7 +13,7 @@ namespace Ryujinx.Graphics.GAL
BufferHandle CreateBuffer(int size); BufferHandle CreateBuffer(int size);
IProgram CreateProgram(IShader[] shaders); IProgram CreateProgram(IShader[] shaders, TransformFeedbackDescriptor[] transformFeedbackDescriptors);
ISampler CreateSampler(SamplerCreateInfo info); ISampler CreateSampler(SamplerCreateInfo info);
ITexture CreateTexture(TextureCreateInfo info, float scale); ITexture CreateTexture(TextureCreateInfo info, float scale);

View file

@ -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));
}
}
}

View file

@ -35,6 +35,11 @@ namespace Ryujinx.Graphics.Gpu
/// </remarks> /// </remarks>
public const int TotalGpStorageBuffers = 16; public const int TotalGpStorageBuffers = 16;
/// <summary>
/// Maximum number of transform feedback buffers.
/// </summary>
public const int TotalTransformFeedbackBuffers = 4;
/// <summary> /// <summary>
/// Maximum number of render target color buffers. /// Maximum number of render target color buffers.
/// </summary> /// </summary>

View file

@ -61,8 +61,6 @@ namespace Ryujinx.Graphics.Gpu.Engine
/// <param name="type">Counter to be written to memory</param> /// <param name="type">Counter to be written to memory</param>
private void ReportCounter(GpuState state, ReportCounterType type) private void ReportCounter(GpuState state, ReportCounterType type)
{ {
CounterData counterData = new CounterData();
var rs = state.Get<SemaphoreState>(MethodOffset.ReportState); var rs = state.Get<SemaphoreState>(MethodOffset.ReportState);
ulong gpuVa = rs.Address.Pack(); ulong gpuVa = rs.Address.Pack();
@ -80,16 +78,14 @@ namespace Ryujinx.Graphics.Gpu.Engine
EventHandler<ulong> resultHandler = (object evt, ulong result) => EventHandler<ulong> resultHandler = (object evt, ulong result) =>
{ {
CounterData counterData = new CounterData();
counterData.Counter = result; counterData.Counter = result;
counterData.Timestamp = ticks; counterData.Timestamp = ticks;
Span<CounterData> counterDataSpan = MemoryMarshal.CreateSpan(ref counterData, 1);
Span<byte> data = MemoryMarshal.Cast<CounterData, byte>(counterDataSpan);
if (counter?.Invalid != true) if (counter?.Invalid != true)
{ {
_context.MemoryAccessor.Write(gpuVa, data); _context.MemoryAccessor.Write(gpuVa, counterData);
} }
}; };

View file

@ -40,6 +40,8 @@ namespace Ryujinx.Graphics.Gpu.Engine
private bool _forceShaderUpdate; private bool _forceShaderUpdate;
private bool _prevTfEnable;
/// <summary> /// <summary>
/// Creates a new instance of the GPU methods class. /// Creates a new instance of the GPU methods class.
/// </summary> /// </summary>
@ -124,6 +126,14 @@ namespace Ryujinx.Graphics.Gpu.Engine
/// <param name="state">Guest GPU state</param> /// <param name="state">Guest GPU state</param>
private void UpdateState(GpuState state) private void UpdateState(GpuState state)
{ {
bool tfEnable = state.Get<Boolean32>(MethodOffset.TfEnable);
if (!tfEnable && _prevTfEnable)
{
_context.Renderer.Pipeline.EndTransformFeedback();
_prevTfEnable = false;
}
// Shaders must be the first one to be updated if modified, because // Shaders must be the first one to be updated if modified, because
// some of the other state depends on information from the currently // some of the other state depends on information from the currently
// bound shaders. // bound shaders.
@ -134,6 +144,11 @@ namespace Ryujinx.Graphics.Gpu.Engine
UpdateShaderState(state); UpdateShaderState(state);
} }
if (state.QueryModified(MethodOffset.TfBufferState))
{
UpdateTfBufferState(state);
}
if (state.QueryModified(MethodOffset.ClipDistanceEnable)) if (state.QueryModified(MethodOffset.ClipDistanceEnable))
{ {
UpdateUserClipState(state); UpdateUserClipState(state);
@ -258,6 +273,12 @@ namespace Ryujinx.Graphics.Gpu.Engine
} }
CommitBindings(); CommitBindings();
if (tfEnable && !_prevTfEnable)
{
_context.Renderer.Pipeline.BeginTransformFeedback(PrimitiveType.Convert());
_prevTfEnable = true;
}
} }
/// <summary> /// <summary>
@ -1003,6 +1024,27 @@ namespace Ryujinx.Graphics.Gpu.Engine
_context.Renderer.Pipeline.SetProgram(gs.HostProgram); _context.Renderer.Pipeline.SetProgram(gs.HostProgram);
} }
/// <summary>
/// Updates transform feedback buffer state based on the guest GPU state.
/// </summary>
/// <param name="state">Current GPU state</param>
private void UpdateTfBufferState(GpuState state)
{
for (int index = 0; index < Constants.TotalTransformFeedbackBuffers; index++)
{
TfBufferState tfb = state.Get<TfBufferState>(MethodOffset.TfBufferState, index);
if (!tfb.Enable)
{
BufferManager.SetTransformFeedbackBuffer(index, 0, 0);
continue;
}
BufferManager.SetTransformFeedbackBuffer(index, tfb.Address.Pack(), (uint)tfb.Size);
}
}
/// <summary> /// <summary>
/// Updates user-defined clipping based on the guest GPU state. /// Updates user-defined clipping based on the guest GPU state.
/// </summary> /// </summary>

View file

@ -24,8 +24,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
private Buffer[] _bufferOverlaps; private Buffer[] _bufferOverlaps;
private IndexBuffer _indexBuffer; private IndexBuffer _indexBuffer;
private VertexBuffer[] _vertexBuffers; private VertexBuffer[] _vertexBuffers;
private BufferBounds[] _transformFeedbackBuffers;
private class BuffersPerStage private class BuffersPerStage
{ {
@ -56,6 +56,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
private bool _indexBufferDirty; private bool _indexBufferDirty;
private bool _vertexBuffersDirty; private bool _vertexBuffersDirty;
private uint _vertexBuffersEnableMask; private uint _vertexBuffersEnableMask;
private bool _transformFeedbackBuffersDirty;
private bool _rebind; private bool _rebind;
@ -73,6 +74,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
_vertexBuffers = new VertexBuffer[Constants.TotalVertexBuffers]; _vertexBuffers = new VertexBuffer[Constants.TotalVertexBuffers];
_transformFeedbackBuffers = new BufferBounds[Constants.TotalTransformFeedbackBuffers];
_cpStorageBuffers = new BuffersPerStage(Constants.TotalCpStorageBuffers); _cpStorageBuffers = new BuffersPerStage(Constants.TotalCpStorageBuffers);
_cpUniformBuffers = new BuffersPerStage(Constants.TotalCpUniformBuffers); _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;
}
/// <summary> /// <summary>
/// Sets a storage buffer on the compute pipeline. /// Sets a storage buffer on the compute pipeline.
/// Storage buffers can be read and written to on shaders. /// 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) if (_gpStorageBuffersDirty || _rebind)
{ {
_gpStorageBuffersDirty = false; _gpStorageBuffersDirty = false;

View file

@ -63,11 +63,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary> /// </summary>
/// <param name="gpuVa">GPU virtual address to write the value into</param> /// <param name="gpuVa">GPU virtual address to write the value into</param>
/// <param name="value">The value to be written</param> /// <param name="value">The value to be written</param>
public void Write(ulong gpuVa, int value) public void Write<T>(ulong gpuVa, T value) where T : unmanaged
{ {
ulong processVa = _context.MemoryManager.Translate(gpuVa); ulong processVa = _context.MemoryManager.Translate(gpuVa);
_context.PhysicalMemory.Write(processVa, BitConverter.GetBytes(value)); _context.PhysicalMemory.Write(processVa, MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref value, 1)));
} }
/// <summary> /// <summary>

View file

@ -82,7 +82,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
shader.HostShader = _context.Renderer.CompileShader(shader.Program); 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); ShaderBundle cpShader = new ShaderBundle(hostProgram, shader);
@ -150,6 +150,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
continue; continue;
} }
var tfd = GetTransformFeedbackDescriptors(state);
IShader hostShader = _context.Renderer.CompileShader(program); IShader hostShader = _context.Renderer.CompileShader(program);
shaders[stage].HostShader = hostShader; shaders[stage].HostShader = hostShader;
@ -157,7 +159,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
hostShaders.Add(hostShader); 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); ShaderBundle gpShaders = new ShaderBundle(hostProgram, shaders);
@ -173,6 +175,36 @@ namespace Ryujinx.Graphics.Gpu.Shader
return gpShaders; return gpShaders;
} }
/// <summary>
/// Gets transform feedback state from the current GPU state.
/// </summary>
/// <param name="state">Current GPU state</param>
/// <returns>Four transform feedback descriptors for the enabled TFBs, or null if TFB is disabled</returns>
private TransformFeedbackDescriptor[] GetTransformFeedbackDescriptors(GpuState state)
{
bool tfEnable = state.Get<Boolean32>(MethodOffset.TfEnable);
if (!tfEnable)
{
return null;
}
TransformFeedbackDescriptor[] descs = new TransformFeedbackDescriptor[Constants.TotalTransformFeedbackBuffers];
for (int i = 0; i < Constants.TotalTransformFeedbackBuffers; i++)
{
var tf = state.Get<TfState>(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;
}
/// <summary> /// <summary>
/// Checks if compute shader code in memory is equal to the cached shader. /// Checks if compute shader code in memory is equal to the cached shader.
/// </summary> /// </summary>

View file

@ -346,7 +346,7 @@ namespace Ryujinx.Graphics.Gpu.State
/// <param name="offset">Register offset</param> /// <param name="offset">Register offset</param>
/// <param name="index">Index for indexed data</param> /// <param name="index">Index for indexed data</param>
/// <returns>The data at the specified location</returns> /// <returns>The data at the specified location</returns>
public T Get<T>(MethodOffset offset, int index) where T : struct public T Get<T>(MethodOffset offset, int index) where T : unmanaged
{ {
Register register = _registers[(int)offset]; Register register = _registers[(int)offset];
@ -364,11 +364,22 @@ namespace Ryujinx.Graphics.Gpu.State
/// <typeparam name="T">Type of the data</typeparam> /// <typeparam name="T">Type of the data</typeparam>
/// <param name="offset">Register offset</param> /// <param name="offset">Register offset</param>
/// <returns>The data at the specified location</returns> /// <returns>The data at the specified location</returns>
public T Get<T>(MethodOffset offset) where T : struct public T Get<T>(MethodOffset offset) where T : unmanaged
{ {
return MemoryMarshal.Cast<int, T>(_memory.AsSpan().Slice((int)offset))[0]; return MemoryMarshal.Cast<int, T>(_memory.AsSpan().Slice((int)offset))[0];
} }
/// <summary>
/// Gets a span of the data at a given register offset.
/// </summary>
/// <param name="offset">Register offset</param>
/// <param name="length">Length of the data in bytes</param>
/// <returns>The data at the specified location</returns>
public Span<byte> GetSpan(MethodOffset offset, int length)
{
return MemoryMarshal.Cast<int, byte>(_memory.AsSpan().Slice((int)offset)).Slice(0, length);
}
/// <summary> /// <summary>
/// Sets indexed data to a given register offset. /// Sets indexed data to a given register offset.
/// </summary> /// </summary>
@ -376,7 +387,7 @@ namespace Ryujinx.Graphics.Gpu.State
/// <param name="offset">Register offset</param> /// <param name="offset">Register offset</param>
/// <param name="index">Index for indexed data</param> /// <param name="index">Index for indexed data</param>
/// <param name="data">The data to set</param> /// <param name="data">The data to set</param>
public void Set<T>(MethodOffset offset, int index, T data) where T : struct public void Set<T>(MethodOffset offset, int index, T data) where T : unmanaged
{ {
Register register = _registers[(int)offset]; Register register = _registers[(int)offset];
@ -394,7 +405,7 @@ namespace Ryujinx.Graphics.Gpu.State
/// <typeparam name="T">Type of the data</typeparam> /// <typeparam name="T">Type of the data</typeparam>
/// <param name="offset">Register offset</param> /// <param name="offset">Register offset</param>
/// <param name="data">The data to set</param> /// <param name="data">The data to set</param>
public void Set<T>(MethodOffset offset, T data) where T : struct public void Set<T>(MethodOffset offset, T data) where T : unmanaged
{ {
ReadOnlySpan<int> intSpan = MemoryMarshal.Cast<T, int>(MemoryMarshal.CreateReadOnlySpan(ref data, 1)); ReadOnlySpan<int> intSpan = MemoryMarshal.Cast<T, int>(MemoryMarshal.CreateReadOnlySpan(ref data, 1));
intSpan.CopyTo(_memory.AsSpan().Slice((int)offset, intSpan.Length)); intSpan.CopyTo(_memory.AsSpan().Slice((int)offset, intSpan.Length));

View file

@ -53,6 +53,8 @@ namespace Ryujinx.Graphics.Gpu.State
/// </summary> /// </summary>
public static TableItem[] Table = new TableItem[] public static TableItem[] Table = new TableItem[]
{ {
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.RtColorState, typeof(RtColorState), Constants.TotalRenderTargets),
new TableItem(MethodOffset.ViewportTransform, typeof(ViewportTransform), Constants.TotalViewports), new TableItem(MethodOffset.ViewportTransform, typeof(ViewportTransform), Constants.TotalViewports),
new TableItem(MethodOffset.ViewportExtents, typeof(ViewportExtents), Constants.TotalViewports), new TableItem(MethodOffset.ViewportExtents, typeof(ViewportExtents), Constants.TotalViewports),
@ -61,7 +63,7 @@ namespace Ryujinx.Graphics.Gpu.State
new TableItem(MethodOffset.ScissorState, typeof(ScissorState), Constants.TotalViewports), new TableItem(MethodOffset.ScissorState, typeof(ScissorState), Constants.TotalViewports),
new TableItem(MethodOffset.StencilBackMasks, typeof(StencilBackMasks), 1), new TableItem(MethodOffset.StencilBackMasks, typeof(StencilBackMasks), 1),
new TableItem(MethodOffset.RtDepthStencilState, typeof(RtDepthStencilState), 1), new TableItem(MethodOffset.RtDepthStencilState, typeof(RtDepthStencilState), 1),
new TableItem(MethodOffset.VertexAttribState, typeof(VertexAttribState), 16), new TableItem(MethodOffset.VertexAttribState, typeof(VertexAttribState), Constants.TotalVertexAttribs),
new TableItem(MethodOffset.RtDepthStencilSize, typeof(Size3D), 1), new TableItem(MethodOffset.RtDepthStencilSize, typeof(Size3D), 1),
new TableItem(MethodOffset.BlendEnable, typeof(Boolean32), Constants.TotalRenderTargets), new TableItem(MethodOffset.BlendEnable, typeof(Boolean32), Constants.TotalRenderTargets),
new TableItem(MethodOffset.StencilTestState, typeof(StencilTestState), 1), new TableItem(MethodOffset.StencilTestState, typeof(StencilTestState), 1),
@ -71,14 +73,14 @@ namespace Ryujinx.Graphics.Gpu.State
new TableItem(MethodOffset.ShaderBaseAddress, typeof(GpuVa), 1), new TableItem(MethodOffset.ShaderBaseAddress, typeof(GpuVa), 1),
new TableItem(MethodOffset.PrimitiveRestartState, typeof(PrimitiveRestartState), 1), new TableItem(MethodOffset.PrimitiveRestartState, typeof(PrimitiveRestartState), 1),
new TableItem(MethodOffset.IndexBufferState, typeof(IndexBufferState), 1), new TableItem(MethodOffset.IndexBufferState, typeof(IndexBufferState), 1),
new TableItem(MethodOffset.VertexBufferInstanced, typeof(Boolean32), 16), new TableItem(MethodOffset.VertexBufferInstanced, typeof(Boolean32), Constants.TotalVertexBuffers),
new TableItem(MethodOffset.FaceState, typeof(FaceState), 1), new TableItem(MethodOffset.FaceState, typeof(FaceState), 1),
new TableItem(MethodOffset.RtColorMask, typeof(RtColorMask), Constants.TotalRenderTargets), new TableItem(MethodOffset.RtColorMask, typeof(RtColorMask), Constants.TotalRenderTargets),
new TableItem(MethodOffset.VertexBufferState, typeof(VertexBufferState), 16), new TableItem(MethodOffset.VertexBufferState, typeof(VertexBufferState), Constants.TotalVertexBuffers),
new TableItem(MethodOffset.BlendConstant, typeof(ColorF), 1), new TableItem(MethodOffset.BlendConstant, typeof(ColorF), 1),
new TableItem(MethodOffset.BlendState, typeof(BlendState), Constants.TotalRenderTargets), new TableItem(MethodOffset.BlendState, typeof(BlendState), Constants.TotalRenderTargets),
new TableItem(MethodOffset.VertexBufferEndAddress, typeof(GpuVa), 16), new TableItem(MethodOffset.VertexBufferEndAddress, typeof(GpuVa), Constants.TotalVertexBuffers),
new TableItem(MethodOffset.ShaderState, typeof(ShaderState), 6), new TableItem(MethodOffset.ShaderState, typeof(ShaderState), Constants.ShaderStages + 1),
}; };
} }
} }

View file

@ -28,10 +28,13 @@ namespace Ryujinx.Graphics.Gpu.State
SyncpointAction = 0xb2, SyncpointAction = 0xb2,
CopyBuffer = 0xc0, CopyBuffer = 0xc0,
RasterizeEnable = 0xdf, RasterizeEnable = 0xdf,
TfBufferState = 0xe0,
CopyBufferParams = 0x100, CopyBufferParams = 0x100,
TfState = 0x1c0,
CopyBufferSwizzle = 0x1c2, CopyBufferSwizzle = 0x1c2,
CopyBufferDstTexture = 0x1c3, CopyBufferDstTexture = 0x1c3,
CopyBufferSrcTexture = 0x1ca, CopyBufferSrcTexture = 0x1ca,
TfEnable = 0x1d1,
RtColorState = 0x200, RtColorState = 0x200,
CopyTextureControl = 0x223, CopyTextureControl = 0x223,
CopyRegion = 0x22c, CopyRegion = 0x22c,
@ -116,6 +119,7 @@ namespace Ryujinx.Graphics.Gpu.State
UniformBufferBindTessEvaluation = 0x914, UniformBufferBindTessEvaluation = 0x914,
UniformBufferBindGeometry = 0x91c, UniformBufferBindGeometry = 0x91c,
UniformBufferBindFragment = 0x924, UniformBufferBindFragment = 0x924,
TextureBufferIndex = 0x982 TextureBufferIndex = 0x982,
TfVaryingLocations = 0xa00
} }
} }

View file

@ -11,18 +11,19 @@ namespace Ryujinx.Graphics.Gpu.State
VertexShaderInvocations = 5, VertexShaderInvocations = 5,
GeometryShaderInvocations = 7, GeometryShaderInvocations = 7,
GeometryShaderPrimitives = 9, GeometryShaderPrimitives = 9,
ZcullStats0 = 0xa,
TransformFeedbackPrimitivesWritten = 0xb, TransformFeedbackPrimitivesWritten = 0xb,
ZcullStats1 = 0xc,
ZcullStats2 = 0xe,
ClipperInputPrimitives = 0xf, ClipperInputPrimitives = 0xf,
ZcullStats3 = 0x10,
ClipperOutputPrimitives = 0x11, ClipperOutputPrimitives = 0x11,
PrimitivesGenerated = 0x12, PrimitivesGenerated = 0x12,
FragmentShaderInvocations = 0x13, FragmentShaderInvocations = 0x13,
SamplesPassed = 0x15, SamplesPassed = 0x15,
TransformFeedbackOffset = 0x1a,
TessControlShaderInvocations = 0x1b, TessControlShaderInvocations = 0x1b,
TessEvaluationShaderInvocations = 0x1d, TessEvaluationShaderInvocations = 0x1d,
TessEvaluationShaderPrimitives = 0x1f, TessEvaluationShaderPrimitives = 0x1f
ZcullStats0 = 0x2a,
ZcullStats1 = 0x2c,
ZcullStats2 = 0x2e,
ZcullStats3 = 0x30
} }
} }

View file

@ -0,0 +1,18 @@
namespace Ryujinx.Graphics.Gpu.State
{
/// <summary>
/// Transform feedback buffer state.
/// </summary>
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
}
}

View file

@ -0,0 +1,15 @@
namespace Ryujinx.Graphics.Gpu.State
{
/// <summary>
/// Transform feedback state.
/// </summary>
struct TfState
{
#pragma warning disable CS0649
public int BufferIndex;
public int VaryingsCount;
public int Stride;
public uint Padding;
#pragma warning restore CS0649
}
}

View file

@ -331,6 +331,31 @@ namespace Ryujinx.Graphics.OpenGL
return PrimitiveType.Points; 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) public static OpenTK.Graphics.OpenGL.StencilOp Convert(this GAL.StencilOp op)
{ {
switch (op) switch (op)

View file

@ -45,6 +45,8 @@ namespace Ryujinx.Graphics.OpenGL
private bool _scissor0Enable = false; private bool _scissor0Enable = false;
private bool _tfEnabled;
ColorF _blendConstant = new ColorF(0, 0, 0, 0); ColorF _blendConstant = new ColorF(0, 0, 0, 0);
internal Pipeline() internal Pipeline()
@ -76,6 +78,12 @@ namespace Ryujinx.Graphics.OpenGL
GL.MemoryBarrier(MemoryBarrierFlags.AllBarrierBits); GL.MemoryBarrier(MemoryBarrierFlags.AllBarrierBits);
} }
public void BeginTransformFeedback(PrimitiveTopology topology)
{
GL.BeginTransformFeedback(topology.ConvertToTfType());
_tfEnabled = true;
}
public void ClearRenderTargetColor(int index, uint componentMask, ColorF color) public void ClearRenderTargetColor(int index, uint componentMask, ColorF color)
{ {
GL.ColorMask( GL.ColorMask(
@ -512,6 +520,12 @@ namespace Ryujinx.Graphics.OpenGL
} }
} }
public void EndTransformFeedback()
{
GL.EndTransformFeedback();
_tfEnabled = false;
}
public void SetBlendState(int index, BlendDescriptor blend) public void SetBlendState(int index, BlendDescriptor blend)
{ {
if (!blend.Enable) if (!blend.Enable)
@ -713,7 +727,17 @@ namespace Ryujinx.Graphics.OpenGL
public void SetProgram(IProgram program) public void SetProgram(IProgram program)
{ {
_program = (Program)program; _program = (Program)program;
if (_tfEnabled)
{
GL.PauseTransformFeedback();
_program.Bind(); _program.Bind();
GL.ResumeTransformFeedback();
}
else
{
_program.Bind();
}
SetRenderTargetScale(_fpRenderScale[0]); 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) public void SetUniformBuffer(int index, ShaderStage stage, BufferRange buffer)
{ {
SetBuffer(index, stage, buffer, isStorage: false); SetBuffer(index, stage, buffer, isStorage: false);

View file

@ -2,6 +2,10 @@ using OpenTK.Graphics.OpenGL;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader; using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Shader.CodeGen.Glsl;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Ryujinx.Graphics.OpenGL namespace Ryujinx.Graphics.OpenGL
{ {
@ -31,7 +35,7 @@ namespace Ryujinx.Graphics.OpenGL
private int[] _textureUnits; private int[] _textureUnits;
private int[] _imageUnits; private int[] _imageUnits;
public Program(IShader[] shaders) public Program(IShader[] shaders, TransformFeedbackDescriptor[] transformFeedbackDescriptors)
{ {
_ubBindingPoints = new int[UbsPerStage * ShaderStages]; _ubBindingPoints = new int[UbsPerStage * ShaderStages];
_sbBindingPoints = new int[SbsPerStage * ShaderStages]; _sbBindingPoints = new int[SbsPerStage * ShaderStages];
@ -67,6 +71,54 @@ namespace Ryujinx.Graphics.OpenGL
GL.AttachShader(Handle, shaderHandle); GL.AttachShader(Handle, shaderHandle);
} }
if (transformFeedbackDescriptors != null)
{
List<string> varyings = new List<string>();
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); GL.LinkProgram(Handle);
for (int index = 0; index < shaders.Length; index++) for (int index = 0; index < shaders.Length; index++)

View file

@ -44,9 +44,9 @@ namespace Ryujinx.Graphics.OpenGL
return Buffer.Create(size); 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) public ISampler CreateSampler(SamplerCreateInfo info)

View file

@ -15,7 +15,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
public static void Declare(CodeGenContext context, StructuredProgramInfo info) 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_gpu_shader_int64 : enable");
context.AppendLine("#extension GL_ARB_shader_ballot : enable"); context.AppendLine("#extension GL_ARB_shader_ballot : enable");
context.AppendLine("#extension GL_ARB_shader_group_vote : enable"); context.AppendLine("#extension GL_ARB_shader_group_vote : enable");
@ -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++) 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)) 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};");
}
} }
} }

View file

@ -56,7 +56,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
continue; 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;");
} }
} }

View file

@ -147,7 +147,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
{ {
int value = attr.Value; int value = attr.Value;
string swzMask = GetSwizzleMask((value >> 2) & 3); char swzMask = GetSwizzleMask((value >> 2) & 3);
if (value >= AttributeConsts.UserAttributeBase && if (value >= AttributeConsts.UserAttributeBase &&
value < AttributeConsts.UserAttributeEnd) value < AttributeConsts.UserAttributeEnd)
@ -158,15 +158,13 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
? DefaultNames.OAttributePrefix ? DefaultNames.OAttributePrefix
: DefaultNames.IAttributePrefix; : DefaultNames.IAttributePrefix;
string name = $"{prefix}{(value >> 4)}"; string name = $"{prefix}{(value >> 4)}_{swzMask}";
if (stage == ShaderStage.Geometry && !isOutAttr) if (stage == ShaderStage.Geometry && !isOutAttr)
{ {
name += $"[{indexExpr}]"; name += $"[{indexExpr}]";
} }
name += "." + swzMask;
return name; return name;
} }
else else
@ -264,9 +262,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
return _stagePrefixes[index]; 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) public static VariableType GetNodeDestType(IAstNode node)

View file

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