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

View file

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

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

View file

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

View file

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

View file

@ -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<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);
for (int index = 0; index < shaders.Length; index++)

View file

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

View file

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

View file

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

View file

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

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