Stop using glTransformFeedbackVaryings and use explicit layout on the shader (#3012)

* Stop using glTransformFeedbackVarying and use explicit layout on the shader

* This is no longer needed

* Shader cache version bump

* Fix gl_PerVertex output for tessellation control shaders
This commit is contained in:
gdkchan 2022-01-21 12:35:21 -03:00 committed by GitHub
parent 0e59573f2b
commit 7e967d796c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 192 additions and 164 deletions

View file

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

View file

@ -7,14 +7,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources.Programs
public ThreadedProgram Threaded { get; set; }
private IShader[] _shaders;
private TransformFeedbackDescriptor[] _transformFeedbackDescriptors;
public SourceProgramRequest(ThreadedProgram program, IShader[] shaders, TransformFeedbackDescriptor[] transformFeedbackDescriptors)
public SourceProgramRequest(ThreadedProgram program, IShader[] shaders)
{
Threaded = program;
_shaders = shaders;
_transformFeedbackDescriptors = transformFeedbackDescriptors;
}
public IProgram Create(IRenderer renderer)
@ -26,7 +24,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources.Programs
return threaded?.Base;
}).ToArray();
return renderer.CreateProgram(shaders, _transformFeedbackDescriptors);
return renderer.CreateProgram(shaders);
}
}
}

View file

@ -268,10 +268,10 @@ namespace Ryujinx.Graphics.GAL.Multithreading
return handle;
}
public IProgram CreateProgram(IShader[] shaders, TransformFeedbackDescriptor[] transformFeedbackDescriptors)
public IProgram CreateProgram(IShader[] shaders)
{
var program = new ThreadedProgram(this);
SourceProgramRequest request = new SourceProgramRequest(program, shaders, transformFeedbackDescriptors);
SourceProgramRequest request = new SourceProgramRequest(program, shaders);
Programs.Add(request);
New<CreateProgramCommand>().Set(Ref((IProgramRequest)request));

View file

@ -13,6 +13,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
private readonly ReadOnlyMemory<byte> _cb1Data;
private readonly GuestGpuAccessorHeader _header;
private readonly Dictionary<int, GuestTextureDescriptor> _textureDescriptors;
private readonly TransformFeedbackDescriptor[] _tfd;
/// <summary>
/// Creates a new instance of the cached GPU state accessor for shader translation.
@ -27,7 +28,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
ReadOnlyMemory<byte> data,
ReadOnlyMemory<byte> cb1Data,
GuestGpuAccessorHeader header,
IReadOnlyDictionary<int, GuestTextureDescriptor> guestTextureDescriptors) : base(context)
IReadOnlyDictionary<int, GuestTextureDescriptor> guestTextureDescriptors,
TransformFeedbackDescriptor[] tfd) : base(context)
{
_data = data;
_cb1Data = cb1Data;
@ -38,6 +40,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
_textureDescriptors.Add(guestTextureDescriptor.Key, guestTextureDescriptor.Value);
}
_tfd = tfd;
}
/// <summary>
@ -177,6 +181,35 @@ namespace Ryujinx.Graphics.Gpu.Shader
return textureDescriptor;
}
/// <summary>
/// Queries transform feedback enable state.
/// </summary>
/// <returns>True if the shader uses transform feedback, false otherwise</returns>
public bool QueryTransformFeedbackEnabled()
{
return _tfd != null;
}
/// <summary>
/// Queries the varying locations that should be written to the transform feedback buffer.
/// </summary>
/// <param name="bufferIndex">Index of the transform feedback buffer</param>
/// <returns>Varying locations for the specified buffer</returns>
public ReadOnlySpan<byte> QueryTransformFeedbackVaryingLocations(int bufferIndex)
{
return _tfd[bufferIndex].VaryingLocations;
}
/// <summary>
/// Queries the stride (in bytes) of the per vertex data written into the transform feedback buffer.
/// </summary>
/// <param name="bufferIndex">Index of the transform feedback buffer</param>
/// <returns>Stride for the specified buffer</returns>
public int QueryTransformFeedbackStride(int bufferIndex)
{
return _tfd[bufferIndex].Stride;
}
/// <summary>
/// Queries if host state forces early depth testing.
/// </summary>

View file

@ -222,6 +222,35 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
}
/// <summary>
/// Queries transform feedback enable state.
/// </summary>
/// <returns>True if the shader uses transform feedback, false otherwise</returns>
public bool QueryTransformFeedbackEnabled()
{
return _state.TransformFeedbackDescriptors != null;
}
/// <summary>
/// Queries the varying locations that should be written to the transform feedback buffer.
/// </summary>
/// <param name="bufferIndex">Index of the transform feedback buffer</param>
/// <returns>Varying locations for the specified buffer</returns>
public ReadOnlySpan<byte> QueryTransformFeedbackVaryingLocations(int bufferIndex)
{
return _state.TransformFeedbackDescriptors[bufferIndex].VaryingLocations;
}
/// <summary>
/// Queries the stride (in bytes) of the per vertex data written into the transform feedback buffer.
/// </summary>
/// <param name="bufferIndex">Index of the transform feedback buffer</param>
/// <returns>Stride for the specified buffer</returns>
public int QueryTransformFeedbackStride(int bufferIndex)
{
return _state.TransformFeedbackDescriptors[bufferIndex].Stride;
}
/// <summary>
/// Queries if host state forces early depth testing.
/// </summary>

View file

@ -38,6 +38,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary>
public TessMode TessellationMode { get; }
/// <summary>
/// Transform feedback information, if the shader uses transform feedback. Otherwise, should be null.
/// </summary>
public TransformFeedbackDescriptor[] TransformFeedbackDescriptors { get; set; }
/// <summary>
/// Creates a new instance of the GPU accessor state.
/// </summary>
@ -61,6 +66,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
EarlyZForce = earlyZForce;
Topology = topology;
TessellationMode = tessellationMode;
TransformFeedbackDescriptors = null;
}
}
}

View file

@ -40,7 +40,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <summary>
/// Version of the codegen (to be changed when codegen or guest format change).
/// </summary>
private const ulong ShaderCodeGenVersion = 2972;
private const ulong ShaderCodeGenVersion = 3012;
// Progress reporting helpers
private volatile int _shaderCount;
@ -227,7 +227,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
binaryCode,
binaryCode.Slice(binaryCode.Length - entry.Header.Cb1DataSize),
entry.Header.GpuAccessorHeader,
entry.TextureDescriptors);
entry.TextureDescriptors,
null);
var options = new TranslationOptions(TargetLanguage.Glsl, TargetApi.OpenGL, DefaultFlags | TranslationFlags.Compute);
program = Translator.CreateContext(0, gpuAccessor, options).Translate(out shaderProgramInfo);
@ -251,7 +252,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
// Compile shader and create program as the shader program binary got invalidated.
shader.HostShader = _context.Renderer.CompileShader(ShaderStage.Compute, program.Code);
hostProgram = _context.Renderer.CreateProgram(new IShader[] { shader.HostShader }, null);
hostProgram = _context.Renderer.CreateProgram(new IShader[] { shader.HostShader });
task.OnCompiled(hostProgram, (bool isNewProgramValid, ShaderCompileTask task) =>
{
@ -293,13 +294,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
TransformFeedbackDescriptor[] tfd = CacheHelper.ReadTransformFeedbackInformation(ref guestProgramReadOnlySpan, fileHeader);
TranslationFlags flags = DefaultFlags;
if (tfd != null)
{
flags |= TranslationFlags.Feedback;
}
TranslationCounts counts = new TranslationCounts();
HostShaderCacheEntry[] hostShaderEntries = null;
@ -343,15 +337,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
binaryCode,
binaryCode.Slice(binaryCode.Length - entry.Header.Cb1DataSize),
entry.Header.GpuAccessorHeader,
entry.TextureDescriptors);
entry.TextureDescriptors,
tfd);
var options = new TranslationOptions(TargetLanguage.Glsl, TargetApi.OpenGL, flags);
var options = new TranslationOptions(TargetLanguage.Glsl, TargetApi.OpenGL, DefaultFlags);
shaderContexts[i + 1] = Translator.CreateContext(0, gpuAccessor, options, counts);
if (entry.Header.SizeA != 0)
{
var options2 = new TranslationOptions(TargetLanguage.Glsl, TargetApi.OpenGL, flags | TranslationFlags.VertexA);
var options2 = new TranslationOptions(TargetLanguage.Glsl, TargetApi.OpenGL, DefaultFlags | TranslationFlags.VertexA);
shaderContexts[0] = Translator.CreateContext((ulong)entry.Header.Size, gpuAccessor, options2, counts);
}
@ -431,7 +426,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
hostShaders.Add(hostShader);
}
hostProgram = _context.Renderer.CreateProgram(hostShaders.ToArray(), tfd);
hostProgram = _context.Renderer.CreateProgram(hostShaders.ToArray());
task.OnCompiled(hostProgram, (bool isNewProgramValid, ShaderCompileTask task) =>
{
@ -622,7 +617,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
shader.HostShader = _context.Renderer.CompileShader(ShaderStage.Compute, shader.Program.Code);
IProgram hostProgram = _context.Renderer.CreateProgram(new IShader[] { shader.HostShader }, null);
IProgram hostProgram = _context.Renderer.CreateProgram(new IShader[] { shader.HostShader });
cpShader = new ShaderBundle(hostProgram, shader);
@ -684,25 +679,20 @@ namespace Ryujinx.Graphics.Gpu.Shader
TransformFeedbackDescriptor[] tfd = GetTransformFeedbackDescriptors(ref state);
TranslationFlags flags = DefaultFlags;
if (tfd != null)
{
flags |= TranslationFlags.Feedback;
}
gas.TransformFeedbackDescriptors = tfd;
TranslationCounts counts = new TranslationCounts();
if (addresses.VertexA != 0)
{
shaderContexts[0] = DecodeGraphicsShader(channel, gas, counts, flags | TranslationFlags.VertexA, ShaderStage.Vertex, addresses.VertexA);
shaderContexts[0] = DecodeGraphicsShader(channel, gas, counts, DefaultFlags | TranslationFlags.VertexA, ShaderStage.Vertex, addresses.VertexA);
}
shaderContexts[1] = DecodeGraphicsShader(channel, gas, counts, flags, ShaderStage.Vertex, addresses.Vertex);
shaderContexts[2] = DecodeGraphicsShader(channel, gas, counts, flags, ShaderStage.TessellationControl, addresses.TessControl);
shaderContexts[3] = DecodeGraphicsShader(channel, gas, counts, flags, ShaderStage.TessellationEvaluation, addresses.TessEvaluation);
shaderContexts[4] = DecodeGraphicsShader(channel, gas, counts, flags, ShaderStage.Geometry, addresses.Geometry);
shaderContexts[5] = DecodeGraphicsShader(channel, gas, counts, flags, ShaderStage.Fragment, addresses.Fragment);
shaderContexts[1] = DecodeGraphicsShader(channel, gas, counts, DefaultFlags, ShaderStage.Vertex, addresses.Vertex);
shaderContexts[2] = DecodeGraphicsShader(channel, gas, counts, DefaultFlags, ShaderStage.TessellationControl, addresses.TessControl);
shaderContexts[3] = DecodeGraphicsShader(channel, gas, counts, DefaultFlags, ShaderStage.TessellationEvaluation, addresses.TessEvaluation);
shaderContexts[4] = DecodeGraphicsShader(channel, gas, counts, DefaultFlags, ShaderStage.Geometry, addresses.Geometry);
shaderContexts[5] = DecodeGraphicsShader(channel, gas, counts, DefaultFlags, ShaderStage.Fragment, addresses.Fragment);
bool isShaderCacheEnabled = _cacheManager != null;
bool isShaderCacheReadOnly = false;
@ -765,7 +755,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
hostShaders.Add(hostShader);
}
IProgram hostProgram = _context.Renderer.CreateProgram(hostShaders.ToArray(), tfd);
IProgram hostProgram = _context.Renderer.CreateProgram(hostShaders.ToArray());
gpShaders = new ShaderBundle(hostProgram, shaders);

View file

@ -1,8 +1,8 @@
using System;
namespace Ryujinx.Graphics.GAL
namespace Ryujinx.Graphics.Gpu.Shader
{
public struct TransformFeedbackDescriptor
struct TransformFeedbackDescriptor
{
public int BufferIndex { get; }
public int Stride { get; }

View file

@ -29,7 +29,7 @@ namespace Ryujinx.Graphics.OpenGL
private ProgramLinkStatus _status = ProgramLinkStatus.Incomplete;
private IShader[] _shaders;
public Program(IShader[] shaders, TransformFeedbackDescriptor[] transformFeedbackDescriptors)
public Program(IShader[] shaders)
{
Handle = GL.CreateProgram();
@ -42,54 +42,6 @@ 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);
_shaders = shaders;

View file

@ -66,9 +66,9 @@ namespace Ryujinx.Graphics.OpenGL
return Buffer.Create(size);
}
public IProgram CreateProgram(IShader[] shaders, TransformFeedbackDescriptor[] transformFeedbackDescriptors)
public IProgram CreateProgram(IShader[] shaders)
{
return new Program(shaders, transformFeedbackDescriptors);
return new Program(shaders);
}
public ISampler CreateSampler(SamplerCreateInfo info)

View file

@ -103,6 +103,18 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
return _info.Functions[id];
}
public TransformFeedbackOutput GetTransformFeedbackOutput(int location, int component)
{
int index = (AttributeConsts.UserAttributeBase / 4) + location * 4 + component;
return _info.TransformFeedbackOutputs[index];
}
public TransformFeedbackOutput GetTransformFeedbackOutput(int location)
{
int index = location / 4;
return _info.TransformFeedbackOutputs[index];
}
private void UpdateIndentation()
{
_indentation = GetIndentation(_level);

View file

@ -191,6 +191,20 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
context.AppendLine();
}
if (context.Config.Stage != ShaderStage.Compute &&
context.Config.Stage != ShaderStage.Fragment &&
context.Config.TransformFeedbackEnabled)
{
var tfOutput = context.GetTransformFeedbackOutput(AttributeConsts.PositionX);
if (tfOutput.Valid)
{
context.AppendLine($"layout (xfb_buffer = {tfOutput.Buffer}, xfb_offset = {tfOutput.Offset}, xfb_stride = {tfOutput.Stride}) out gl_PerVertex");
context.EnterScope();
context.AppendLine("vec4 gl_Position;");
context.LeaveScope(context.Config.Stage == ShaderStage.TessellationControl ? " gl_out[];" : ";");
}
}
}
else
{
@ -514,7 +528,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
string pass = (context.Config.PassthroughAttributes & (1 << attr)) != 0 ? "passthrough, " : string.Empty;
string name = $"{DefaultNames.IAttributePrefix}{attr}";
if ((context.Config.Options.Flags & TranslationFlags.Feedback) != 0)
if (context.Config.TransformFeedbackEnabled && context.Config.Stage != ShaderStage.Vertex)
{
for (int c = 0; c < 4; c++)
{
@ -559,13 +573,21 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
string suffix = OperandManager.IsArrayAttribute(context.Config.Stage, isOutAttr: true) ? "[]" : string.Empty;
string name = $"{DefaultNames.OAttributePrefix}{attr}{suffix}";
if ((context.Config.Options.Flags & TranslationFlags.Feedback) != 0)
if (context.Config.TransformFeedbackEnabled && context.Config.Stage != ShaderStage.Fragment)
{
for (int c = 0; c < 4; c++)
{
char swzMask = "xyzw"[c];
context.AppendLine($"layout (location = {attr}, component = {c}) out float {name}_{swzMask};");
string xfb = string.Empty;
var tfOutput = context.GetTransformFeedbackOutput(attr, c);
if (tfOutput.Valid)
{
xfb = $", xfb_buffer = {tfOutput.Buffer}, xfb_offset = {tfOutput.Offset}, xfb_stride = {tfOutput.Stride}";
}
context.AppendLine($"layout (location = {attr}, component = {c}{xfb}) out float {name}_{swzMask};");
}
}
else

View file

@ -194,7 +194,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
return name + $"[{(value >> 4)}]." + swzMask;
}
else if (config.Options.Flags.HasFlag(TranslationFlags.Feedback))
else if (config.TransformFeedbackEnabled && (config.Stage != ShaderStage.Vertex || isOutAttr))
{
string name = $"{prefix}{(value >> 4)}_{swzMask}";

View file

@ -1,69 +0,0 @@
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;
}
}
}

View file

@ -131,6 +131,21 @@ namespace Ryujinx.Graphics.Shader
return TextureFormat.R8G8B8A8Unorm;
}
bool QueryTransformFeedbackEnabled()
{
return false;
}
ReadOnlySpan<byte> QueryTransformFeedbackVaryingLocations(int bufferIndex)
{
return ReadOnlySpan<byte>.Empty;
}
int QueryTransformFeedbackStride(int bufferIndex)
{
return 0;
}
bool QueryEarlyZForce()
{
return false;

View file

@ -64,6 +64,24 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
context.LeaveFunction();
}
if (config.TransformFeedbackEnabled)
{
for (int tfbIndex = 0; tfbIndex < 4; tfbIndex++)
{
var locations = config.GpuAccessor.QueryTransformFeedbackVaryingLocations(tfbIndex);
var stride = config.GpuAccessor.QueryTransformFeedbackStride(tfbIndex);
for (int j = 0; j < locations.Length; j++)
{
byte location = locations[j];
if (location < 0x80)
{
context.Info.TransformFeedbackOutputs[location] = new TransformFeedbackOutput(tfbIndex, j * 4, stride);
}
}
}
}
return context.Info;
}

View file

@ -2,15 +2,35 @@ using System.Collections.Generic;
namespace Ryujinx.Graphics.Shader.StructuredIr
{
struct TransformFeedbackOutput
{
public readonly bool Valid;
public readonly int Buffer;
public readonly int Offset;
public readonly int Stride;
public TransformFeedbackOutput(int buffer, int offset, int stride)
{
Valid = true;
Buffer = buffer;
Offset = offset;
Stride = stride;
}
}
class StructuredProgramInfo
{
public List<StructuredFunction> Functions { get; }
public HelperFunctionsMask HelperFunctionsMask { get; set; }
public TransformFeedbackOutput[] TransformFeedbackOutputs { get; }
public StructuredProgramInfo()
{
Functions = new List<StructuredFunction>();
TransformFeedbackOutputs = new TransformFeedbackOutput[0x80];
}
}
}

View file

@ -33,6 +33,8 @@ namespace Ryujinx.Graphics.Shader.Translation
public TranslationOptions Options { get; }
public bool TransformFeedbackEnabled { get; }
public int Size { get; private set; }
public byte ClipDistancesWritten { get; private set; }
@ -128,6 +130,7 @@ namespace Ryujinx.Graphics.Shader.Translation
OmapTargets = header.OmapTargets;
OmapSampleMask = header.OmapSampleMask;
OmapDepth = header.OmapDepth;
TransformFeedbackEnabled = gpuAccessor.QueryTransformFeedbackEnabled();
}
public int GetDepthRegister()

View file

@ -9,7 +9,6 @@ namespace Ryujinx.Graphics.Shader.Translation
VertexA = 1 << 0,
Compute = 1 << 1,
Feedback = 1 << 2,
DebugMode = 1 << 3
DebugMode = 1 << 2
}
}