Skip to content

Commit

Permalink
Initial transform feedback support (#1370)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
gdkchan authored Jul 15, 2020
1 parent 16dafe6 commit 788ca6a
Show file tree
Hide file tree
Showing 23 changed files with 468 additions and 68 deletions.
5 changes: 5 additions & 0 deletions Ryujinx.Graphics.GAL/IPipeline.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ public interface IPipeline
{
void Barrier();

void BeginTransformFeedback(PrimitiveTopology topology);

void ClearRenderTargetColor(int index, uint componentMask, ColorF color);

void ClearRenderTargetDepthStencil(
Expand All @@ -27,6 +29,8 @@ void DrawIndexed(
int firstVertex,
int firstInstance);

void EndTransformFeedback();

void SetBlendState(int index, BlendDescriptor blend);

void SetDepthBias(PolygonModeMask enables, float factor, float units, float clamp);
Expand Down Expand Up @@ -73,6 +77,7 @@ void DrawIndexed(

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);
Expand Down
2 changes: 1 addition & 1 deletion Ryujinx.Graphics.GAL/IRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public interface IRenderer : IDisposable

BufferHandle CreateBuffer(int size);

IProgram CreateProgram(IShader[] shaders);
IProgram CreateProgram(IShader[] shaders, TransformFeedbackDescriptor[] transformFeedbackDescriptors);

ISampler CreateSampler(SamplerCreateInfo info);
ITexture CreateTexture(TextureCreateInfo info, float scale);
Expand Down
19 changes: 19 additions & 0 deletions Ryujinx.Graphics.GAL/TransformFeedbackDescriptor.cs
Original file line number Diff line number Diff line change
@@ -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));
}
}
}
5 changes: 5 additions & 0 deletions Ryujinx.Graphics.Gpu/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ static class Constants
/// </remarks>
public const int TotalGpStorageBuffers = 16;

/// <summary>
/// Maximum number of transform feedback buffers.
/// </summary>
public const int TotalTransformFeedbackBuffers = 4;

/// <summary>
/// Maximum number of render target color buffers.
/// </summary>
Expand Down
10 changes: 3 additions & 7 deletions Ryujinx.Graphics.Gpu/Engine/MethodReport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,6 @@ private struct CounterData
/// <param name="type">Counter to be written to memory</param>
private void ReportCounter(GpuState state, ReportCounterType type)
{
CounterData counterData = new CounterData();

var rs = state.Get<SemaphoreState>(MethodOffset.ReportState);

ulong gpuVa = rs.Address.Pack();
Expand All @@ -80,16 +78,14 @@ private void ReportCounter(GpuState state, ReportCounterType type)

EventHandler<ulong> resultHandler = (object evt, ulong result) =>
{
CounterData counterData = new CounterData();

counterData.Counter = result;
counterData.Timestamp = ticks;

Span<CounterData> counterDataSpan = MemoryMarshal.CreateSpan(ref counterData, 1);

Span<byte> data = MemoryMarshal.Cast<CounterData, byte>(counterDataSpan);

if (counter?.Invalid != true)
{
_context.MemoryAccessor.Write(gpuVa, data);
_context.MemoryAccessor.Write(gpuVa, counterData);
}
};

Expand Down
44 changes: 43 additions & 1 deletion Ryujinx.Graphics.Gpu/Engine/Methods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ partial class Methods

private bool _forceShaderUpdate;

private bool _prevTfEnable;

/// <summary>
/// Creates a new instance of the GPU methods class.
/// </summary>
Expand Down Expand Up @@ -124,6 +126,14 @@ public void RegisterCallbacksForFifo(GpuState state)
/// <param name="state">Guest GPU state</param>
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
// some of the other state depends on information from the currently
// bound shaders.
Expand All @@ -134,6 +144,11 @@ private void UpdateState(GpuState state)
UpdateShaderState(state);
}

if (state.QueryModified(MethodOffset.TfBufferState))
{
UpdateTfBufferState(state);
}

if (state.QueryModified(MethodOffset.ClipDistanceEnable))
{
UpdateUserClipState(state);
Expand Down Expand Up @@ -258,6 +273,12 @@ private void UpdateState(GpuState state)
}

CommitBindings();

if (tfEnable && !_prevTfEnable)
{
_context.Renderer.Pipeline.BeginTransformFeedback(PrimitiveType.Convert());
_prevTfEnable = true;
}
}

/// <summary>
Expand Down Expand Up @@ -318,7 +339,7 @@ private void UpdateStorageBuffers()
/// </summary>
/// <param name="state">Current GPU state</param>
/// <param name="useControl">Use draw buffers information from render target control register</param>
/// <param name="singleUse">If this is not -1, it indicates that only the given indexed target will be used.</param>
/// <param name="singleUse">If this is not -1, it indicates that only the given indexed target will be used.</param>
private void UpdateRenderTargetState(GpuState state, bool useControl, int singleUse = -1)
{
var rtControl = state.Get<RtControl>(MethodOffset.RtControl);
Expand Down Expand Up @@ -1003,6 +1024,27 @@ private void UpdateShaderState(GpuState state)
_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>
/// Updates user-defined clipping based on the guest GPU state.
/// </summary>
Expand Down
50 changes: 49 additions & 1 deletion Ryujinx.Graphics.Gpu/Memory/BufferManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ class BufferManager
private Buffer[] _bufferOverlaps;

private IndexBuffer _indexBuffer;

private VertexBuffer[] _vertexBuffers;
private BufferBounds[] _transformFeedbackBuffers;

private class BuffersPerStage
{
Expand Down Expand Up @@ -56,6 +56,7 @@ public void Bind(int index, ulong address, ulong size)
private bool _indexBufferDirty;
private bool _vertexBuffersDirty;
private uint _vertexBuffersEnableMask;
private bool _transformFeedbackBuffersDirty;

private bool _rebind;

Expand All @@ -73,6 +74,8 @@ public BufferManager(GpuContext context)

_vertexBuffers = new VertexBuffer[Constants.TotalVertexBuffers];

_transformFeedbackBuffers = new BufferBounds[Constants.TotalTransformFeedbackBuffers];

_cpStorageBuffers = new BuffersPerStage(Constants.TotalCpStorageBuffers);
_cpUniformBuffers = new BuffersPerStage(Constants.TotalCpUniformBuffers);

Expand Down Expand Up @@ -144,6 +147,16 @@ public void SetVertexBuffer(int index, ulong gpuVa, ulong size, int stride, int
}
}

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>
/// Sets a storage buffer on the compute pipeline.
/// Storage buffers can be read and written to on shaders.
Expand Down Expand Up @@ -522,6 +535,41 @@ public void CommitGraphicsBindings()
}
}

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;
Expand Down
4 changes: 2 additions & 2 deletions Ryujinx.Graphics.Gpu/Memory/MemoryAccessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,11 @@ public T Read<T>(ulong gpuVa) where T : unmanaged
/// </summary>
/// <param name="gpuVa">GPU virtual address to write the value into</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);

_context.PhysicalMemory.Write(processVa, BitConverter.GetBytes(value));
_context.PhysicalMemory.Write(processVa, MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref value, 1)));
}

/// <summary>
Expand Down
36 changes: 34 additions & 2 deletions Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public ShaderBundle GetComputeShader(

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

Expand Down Expand Up @@ -150,14 +150,16 @@ public ShaderBundle GetGraphicsShader(GpuState state, ShaderAddresses addresses)
continue;
}

var tfd = GetTransformFeedbackDescriptors(state);

IShader hostShader = _context.Renderer.CompileShader(program);

shaders[stage].HostShader = 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);

Expand All @@ -173,6 +175,36 @@ public ShaderBundle GetGraphicsShader(GpuState state, ShaderAddresses addresses)
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>
/// Checks if compute shader code in memory is equal to the cached shader.
/// </summary>
Expand Down
19 changes: 15 additions & 4 deletions Ryujinx.Graphics.Gpu/State/GpuState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ public bool QueryModified(
/// <param name="offset">Register offset</param>
/// <param name="index">Index for indexed data</param>
/// <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];

Expand All @@ -364,19 +364,30 @@ public T Get<T>(MethodOffset offset, int index) where T : struct
/// <typeparam name="T">Type of the data</typeparam>
/// <param name="offset">Register offset</param>
/// <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];
}

/// <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>
/// Sets indexed data to a given register offset.
/// </summary>
/// <typeparam name="T">Type of the data</typeparam>
/// <param name="offset">Register offset</param>
/// <param name="index">Index for indexed data</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];

Expand All @@ -394,7 +405,7 @@ public void Set<T>(MethodOffset offset, int index, T data) where T : struct
/// <typeparam name="T">Type of the data</typeparam>
/// <param name="offset">Register offset</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));
intSpan.CopyTo(_memory.AsSpan().Slice((int)offset, intSpan.Length));
Expand Down
Loading

0 comments on commit 788ca6a

Please sign in to comment.