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

@ -35,6 +35,11 @@ namespace Ryujinx.Graphics.Gpu
/// </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>

View file

@ -61,8 +61,6 @@ namespace Ryujinx.Graphics.Gpu.Engine
/// <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();
@ -80,16 +78,14 @@ namespace Ryujinx.Graphics.Gpu.Engine
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);
}
};

View file

@ -40,6 +40,8 @@ namespace Ryujinx.Graphics.Gpu.Engine
private bool _forceShaderUpdate;
private bool _prevTfEnable;
/// <summary>
/// Creates a new instance of the GPU methods class.
/// </summary>
@ -124,6 +126,14 @@ namespace Ryujinx.Graphics.Gpu.Engine
/// <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.
@ -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;
}
}
/// <summary>
@ -318,7 +339,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
/// </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);
@ -1003,6 +1024,27 @@ namespace Ryujinx.Graphics.Gpu.Engine
_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>

View file

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

View file

@ -63,11 +63,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </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>

View file

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

View file

@ -346,7 +346,7 @@ namespace Ryujinx.Graphics.Gpu.State
/// <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];
@ -364,11 +364,22 @@ namespace Ryujinx.Graphics.Gpu.State
/// <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>
@ -376,7 +387,7 @@ namespace Ryujinx.Graphics.Gpu.State
/// <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];
@ -394,7 +405,7 @@ namespace Ryujinx.Graphics.Gpu.State
/// <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));

View file

@ -53,32 +53,34 @@ namespace Ryujinx.Graphics.Gpu.State
/// </summary>
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),
};
}
}

View file

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

View file

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

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