Archived
1
0
Fork 0
forked from Mirror/Ryujinx

Move shader resource descriptor creation out of the backend (#2290)

* Move shader resource descriptor creation out of the backend

* Remove now unused code, and other nits

* Shader cache version bump

* Nits

* Set format for bindless image load/store

* Fix buffer write flag
This commit is contained in:
gdkchan 2021-05-19 18:15:26 -03:00 committed by GitHub
parent b5c72b44de
commit 49745cfa37
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 565 additions and 516 deletions

View file

@ -36,7 +36,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <summary> /// <summary>
/// Version of the codegen (to be changed when codegen or guest format change). /// Version of the codegen (to be changed when codegen or guest format change).
/// </summary> /// </summary>
private const ulong ShaderCodeGenVersion = 2261; private const ulong ShaderCodeGenVersion = 2290;
// Progress reporting helpers // Progress reporting helpers
private volatile int _shaderCount; private volatile int _shaderCount;

View file

@ -1,7 +1,5 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr; using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation; using Ryujinx.Graphics.Shader.Translation;
using System.Collections.Generic;
using System.Text; using System.Text;
namespace Ryujinx.Graphics.Shader.CodeGen.Glsl namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
@ -10,22 +8,15 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
{ {
public const string Tab = " "; public const string Tab = " ";
private readonly StructuredProgramInfo _info;
public StructuredFunction CurrentFunction { get; set; } public StructuredFunction CurrentFunction { get; set; }
public ShaderConfig Config { get; } public ShaderConfig Config { get; }
public bool CbIndexable => _info.UsesCbIndexing;
public List<BufferDescriptor> CBufferDescriptors { get; }
public List<BufferDescriptor> SBufferDescriptors { get; }
public List<TextureDescriptor> TextureDescriptors { get; }
public List<TextureDescriptor> ImageDescriptors { get; }
public OperandManager OperandManager { get; } public OperandManager OperandManager { get; }
private StringBuilder _sb; private readonly StructuredProgramInfo _info;
private readonly StringBuilder _sb;
private int _level; private int _level;
@ -36,11 +27,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
_info = info; _info = info;
Config = config; Config = config;
CBufferDescriptors = new List<BufferDescriptor>();
SBufferDescriptors = new List<BufferDescriptor>();
TextureDescriptors = new List<TextureDescriptor>();
ImageDescriptors = new List<TextureDescriptor>();
OperandManager = new OperandManager(); OperandManager = new OperandManager();
_sb = new StringBuilder(); _sb = new StringBuilder();
@ -84,23 +70,32 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
AppendLine("}" + suffix); AppendLine("}" + suffix);
} }
private int FindDescriptorIndex(List<TextureDescriptor> list, AstTextureOperation texOp) private static int FindDescriptorIndex(TextureDescriptor[] array, AstTextureOperation texOp)
{ {
return list.FindIndex(descriptor => for (int i = 0; i < array.Length; i++)
descriptor.Type == texOp.Type && {
descriptor.CbufSlot == texOp.CbufSlot && var descriptor = array[i];
descriptor.HandleIndex == texOp.Handle &&
descriptor.Format == texOp.Format); if (descriptor.Type == texOp.Type &&
descriptor.CbufSlot == texOp.CbufSlot &&
descriptor.HandleIndex == texOp.Handle &&
descriptor.Format == texOp.Format)
{
return i;
}
}
return -1;
} }
public int FindTextureDescriptorIndex(AstTextureOperation texOp) public int FindTextureDescriptorIndex(AstTextureOperation texOp)
{ {
return FindDescriptorIndex(TextureDescriptors, texOp); return FindDescriptorIndex(Config.GetTextureDescriptors(), texOp);
} }
public int FindImageDescriptorIndex(AstTextureOperation texOp) public int FindImageDescriptorIndex(AstTextureOperation texOp)
{ {
return FindDescriptorIndex(ImageDescriptors, texOp); return FindDescriptorIndex(Config.GetImageDescriptors(), texOp);
} }
public StructuredFunction GetFunction(int id) public StructuredFunction GetFunction(int id)

View file

@ -70,30 +70,34 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
context.AppendLine(); context.AppendLine();
} }
if (info.CBuffers.Count != 0) var cBufferDescriptors = context.Config.GetConstantBufferDescriptors();
if (cBufferDescriptors.Length != 0)
{ {
DeclareUniforms(context, info); DeclareUniforms(context, cBufferDescriptors);
context.AppendLine(); context.AppendLine();
} }
if (info.SBuffers.Count != 0) var sBufferDescriptors = context.Config.GetStorageBufferDescriptors();
if (sBufferDescriptors.Length != 0)
{ {
DeclareStorages(context, info); DeclareStorages(context, sBufferDescriptors);
context.AppendLine(); context.AppendLine();
} }
if (info.Samplers.Count != 0) var textureDescriptors = context.Config.GetTextureDescriptors();
if (textureDescriptors.Length != 0)
{ {
DeclareSamplers(context, info); DeclareSamplers(context, textureDescriptors);
context.AppendLine(); context.AppendLine();
} }
if (info.Images.Count != 0) var imageDescriptors = context.Config.GetImageDescriptors();
if (imageDescriptors.Length != 0)
{ {
DeclareImages(context, info); DeclareImages(context, imageDescriptors);
context.AppendLine(); context.AppendLine();
} }
@ -246,58 +250,40 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
throw new ArgumentException($"Invalid variable type \"{type}\"."); throw new ArgumentException($"Invalid variable type \"{type}\".");
} }
private static void DeclareUniforms(CodeGenContext context, StructuredProgramInfo info) private static void DeclareUniforms(CodeGenContext context, BufferDescriptor[] descriptors)
{ {
string ubSize = "[" + NumberFormatter.FormatInt(Constants.ConstantBufferSize / 16) + "]"; string ubSize = "[" + NumberFormatter.FormatInt(Constants.ConstantBufferSize / 16) + "]";
if (info.UsesCbIndexing) if (context.Config.UsedFeatures.HasFlag(FeatureFlags.CbIndexing))
{ {
int count = info.CBuffers.Max() + 1;
int[] bindings = new int[count];
for (int i = 0; i < count; i++)
{
bindings[i] = context.Config.Counts.IncrementUniformBuffersCount();
}
foreach (int cbufSlot in info.CBuffers.OrderBy(x => x))
{
context.CBufferDescriptors.Add(new BufferDescriptor(bindings[cbufSlot], cbufSlot));
}
string ubName = OperandManager.GetShaderStagePrefix(context.Config.Stage); string ubName = OperandManager.GetShaderStagePrefix(context.Config.Stage);
ubName += "_" + DefaultNames.UniformNamePrefix; ubName += "_" + DefaultNames.UniformNamePrefix;
string blockName = $"{ubName}_{DefaultNames.BlockSuffix}"; string blockName = $"{ubName}_{DefaultNames.BlockSuffix}";
context.AppendLine($"layout (binding = {bindings[0]}, std140) uniform {blockName}"); context.AppendLine($"layout (binding = {descriptors[0].Binding}, std140) uniform {blockName}");
context.EnterScope(); context.EnterScope();
context.AppendLine("vec4 " + DefaultNames.DataName + ubSize + ";"); context.AppendLine("vec4 " + DefaultNames.DataName + ubSize + ";");
context.LeaveScope($" {ubName}[{NumberFormatter.FormatInt(count)}];"); context.LeaveScope($" {ubName}[{NumberFormatter.FormatInt(descriptors.Length)}];");
} }
else else
{ {
foreach (int cbufSlot in info.CBuffers.OrderBy(x => x)) foreach (var descriptor in descriptors)
{ {
int binding = context.Config.Counts.IncrementUniformBuffersCount();
context.CBufferDescriptors.Add(new BufferDescriptor(binding, cbufSlot));
string ubName = OperandManager.GetShaderStagePrefix(context.Config.Stage); string ubName = OperandManager.GetShaderStagePrefix(context.Config.Stage);
ubName += "_" + DefaultNames.UniformNamePrefix + cbufSlot; ubName += "_" + DefaultNames.UniformNamePrefix + descriptor.Slot;
context.AppendLine($"layout (binding = {binding}, std140) uniform {ubName}"); context.AppendLine($"layout (binding = {descriptor.Binding}, std140) uniform {ubName}");
context.EnterScope(); context.EnterScope();
context.AppendLine("vec4 " + OperandManager.GetUbName(context.Config.Stage, cbufSlot, false) + ubSize + ";"); context.AppendLine("vec4 " + OperandManager.GetUbName(context.Config.Stage, descriptor.Slot, false) + ubSize + ";");
context.LeaveScope(";"); context.LeaveScope(";");
} }
} }
} }
private static void DeclareStorages(CodeGenContext context, StructuredProgramInfo info) private static void DeclareStorages(CodeGenContext context, BufferDescriptor[] descriptors)
{ {
string sbName = OperandManager.GetShaderStagePrefix(context.Config.Stage); string sbName = OperandManager.GetShaderStagePrefix(context.Config.Stage);
@ -305,130 +291,81 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
string blockName = $"{sbName}_{DefaultNames.BlockSuffix}"; string blockName = $"{sbName}_{DefaultNames.BlockSuffix}";
int count = info.SBuffers.Max() + 1; context.AppendLine($"layout (binding = {descriptors[0].Binding}, std430) buffer {blockName}");
int[] bindings = new int[count];
for (int i = 0; i < count; i++)
{
bindings[i] = context.Config.Counts.IncrementStorageBuffersCount();
}
foreach (int sbufSlot in info.SBuffers)
{
context.SBufferDescriptors.Add(new BufferDescriptor(bindings[sbufSlot], sbufSlot));
}
context.AppendLine($"layout (binding = {bindings[0]}, std430) buffer {blockName}");
context.EnterScope(); context.EnterScope();
context.AppendLine("uint " + DefaultNames.DataName + "[];"); context.AppendLine("uint " + DefaultNames.DataName + "[];");
context.LeaveScope($" {sbName}[{NumberFormatter.FormatInt(count)}];"); context.LeaveScope($" {sbName}[{NumberFormatter.FormatInt(descriptors.Length)}];");
} }
private static void DeclareSamplers(CodeGenContext context, StructuredProgramInfo info) private static void DeclareSamplers(CodeGenContext context, TextureDescriptor[] descriptors)
{ {
HashSet<string> samplers = new HashSet<string>(); int arraySize = 0;
foreach (var descriptor in descriptors)
// Texture instructions other than TextureSample (like TextureSize)
// may have incomplete sampler type information. In those cases,
// we prefer instead the more accurate information from the
// TextureSample instruction, if both are available.
foreach (AstTextureOperation texOp in info.Samplers.OrderBy(x => x.Handle * 2 + (x.Inst == Instruction.TextureSample ? 0 : 1)))
{ {
string indexExpr = NumberFormatter.FormatInt(texOp.ArraySize); if (descriptor.Type.HasFlag(SamplerType.Indexed))
string samplerName = OperandManager.GetSamplerName(context.Config.Stage, texOp, indexExpr);
if ((texOp.Flags & TextureFlags.Bindless) != 0 || !samplers.Add(samplerName))
{ {
continue; if (arraySize == 0)
}
int firstBinding = -1;
if ((texOp.Type & SamplerType.Indexed) != 0)
{
for (int index = 0; index < texOp.ArraySize; index++)
{ {
int binding = context.Config.Counts.IncrementTexturesCount(); arraySize = ShaderConfig.SamplerArraySize;
}
if (firstBinding < 0) else if (--arraySize != 0)
{ {
firstBinding = binding; continue;
}
var desc = new TextureDescriptor(binding, texOp.Type, texOp.Format, texOp.CbufSlot, texOp.Handle + index * 2);
context.TextureDescriptors.Add(desc);
} }
} }
else
{
firstBinding = context.Config.Counts.IncrementTexturesCount();
var desc = new TextureDescriptor(firstBinding, texOp.Type, texOp.Format, texOp.CbufSlot, texOp.Handle); string indexExpr = NumberFormatter.FormatInt(arraySize);
context.TextureDescriptors.Add(desc); string samplerName = OperandManager.GetSamplerName(
} context.Config.Stage,
descriptor.CbufSlot,
descriptor.HandleIndex,
descriptor.Type.HasFlag(SamplerType.Indexed),
indexExpr);
string samplerTypeName = texOp.Type.ToGlslSamplerType(); string samplerTypeName = descriptor.Type.ToGlslSamplerType();
context.AppendLine($"layout (binding = {firstBinding}) uniform {samplerTypeName} {samplerName};"); context.AppendLine($"layout (binding = {descriptor.Binding}) uniform {samplerTypeName} {samplerName};");
} }
} }
private static void DeclareImages(CodeGenContext context, StructuredProgramInfo info) private static void DeclareImages(CodeGenContext context, TextureDescriptor[] descriptors)
{ {
HashSet<string> images = new HashSet<string>(); int arraySize = 0;
foreach (var descriptor in descriptors)
foreach (AstTextureOperation texOp in info.Images.OrderBy(x => x.Handle))
{ {
string indexExpr = NumberFormatter.FormatInt(texOp.ArraySize); if (descriptor.Type.HasFlag(SamplerType.Indexed))
string imageName = OperandManager.GetImageName(context.Config.Stage, texOp, indexExpr);
if ((texOp.Flags & TextureFlags.Bindless) != 0 || !images.Add(imageName))
{ {
continue; if (arraySize == 0)
}
int firstBinding = -1;
if ((texOp.Type & SamplerType.Indexed) != 0)
{
for (int index = 0; index < texOp.ArraySize; index++)
{ {
int binding = context.Config.Counts.IncrementImagesCount(); arraySize = ShaderConfig.SamplerArraySize;
}
if (firstBinding < 0) else if (--arraySize != 0)
{ {
firstBinding = binding; continue;
}
var desc = new TextureDescriptor(binding, texOp.Type, texOp.Format, texOp.CbufSlot, texOp.Handle + index * 2);
context.ImageDescriptors.Add(desc);
} }
} }
else
{
firstBinding = context.Config.Counts.IncrementImagesCount();
var desc = new TextureDescriptor(firstBinding, texOp.Type, texOp.Format, texOp.CbufSlot, texOp.Handle); string indexExpr = NumberFormatter.FormatInt(arraySize);
context.ImageDescriptors.Add(desc); string imageName = OperandManager.GetImageName(
} context.Config.Stage,
descriptor.CbufSlot,
descriptor.HandleIndex,
descriptor.Format,
descriptor.Type.HasFlag(SamplerType.Indexed),
indexExpr);
string layout = texOp.Format.ToGlslFormat(); string layout = descriptor.Format.ToGlslFormat();
if (!string.IsNullOrEmpty(layout)) if (!string.IsNullOrEmpty(layout))
{ {
layout = ", " + layout; layout = ", " + layout;
} }
string imageTypeName = texOp.Type.ToGlslImageType(texOp.Format.GetComponentType()); string imageTypeName = descriptor.Type.ToGlslImageType(descriptor.Format.GetComponentType());
context.AppendLine($"layout (binding = {firstBinding}{layout}) uniform {imageTypeName} {imageName};"); context.AppendLine($"layout (binding = {descriptor.Binding}{layout}) uniform {imageTypeName} {imageName};");
} }
} }
@ -528,7 +465,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
{ {
string stage = OperandManager.GetShaderStagePrefix(context.Config.Stage); string stage = OperandManager.GetShaderStagePrefix(context.Config.Stage);
int scaleElements = context.TextureDescriptors.Count + context.ImageDescriptors.Count; int scaleElements = context.Config.GetTextureDescriptors().Length + context.Config.GetImageDescriptors().Length;
if (context.Config.Stage == ShaderStage.Fragment) if (context.Config.Stage == ShaderStage.Fragment)
{ {

View file

@ -12,7 +12,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
{ {
private const string MainFunctionName = "main"; private const string MainFunctionName = "main";
public static GlslProgram Generate(StructuredProgramInfo info, ShaderConfig config) public static string Generate(StructuredProgramInfo info, ShaderConfig config)
{ {
CodeGenContext context = new CodeGenContext(info, config); CodeGenContext context = new CodeGenContext(info, config);
@ -37,12 +37,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
PrintFunction(context, info, info.Functions[0], MainFunctionName); PrintFunction(context, info, info.Functions[0], MainFunctionName);
return new GlslProgram( return context.GetCode();
context.CBufferDescriptors.ToArray(),
context.SBufferDescriptors.ToArray(),
context.TextureDescriptors.ToArray(),
context.ImageDescriptors.ToArray(),
context.GetCode());
} }
private static void PrintFunction(CodeGenContext context, StructuredProgramInfo info, StructuredFunction function, string funcName = null) private static void PrintFunction(CodeGenContext context, StructuredProgramInfo info, StructuredFunction function, string funcName = null)

View file

@ -1,26 +0,0 @@
namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
{
class GlslProgram
{
public BufferDescriptor[] CBufferDescriptors { get; }
public BufferDescriptor[] SBufferDescriptors { get; }
public TextureDescriptor[] TextureDescriptors { get; }
public TextureDescriptor[] ImageDescriptors { get; }
public string Code { get; }
public GlslProgram(
BufferDescriptor[] cBufferDescriptors,
BufferDescriptor[] sBufferDescriptors,
TextureDescriptor[] textureDescriptors,
TextureDescriptor[] imageDescriptors,
string code)
{
CBufferDescriptors = cBufferDescriptors;
SBufferDescriptors = sBufferDescriptors;
TextureDescriptors = textureDescriptors;
ImageDescriptors = imageDescriptors;
Code = code;
}
}
}

View file

@ -20,7 +20,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
} }
else if (node is AstOperand operand) else if (node is AstOperand operand)
{ {
return context.OperandManager.GetExpression(operand, context.Config, context.CbIndexable); return context.OperandManager.GetExpression(operand, context.Config);
} }
throw new ArgumentException($"Invalid node type \"{node?.GetType().Name ?? "null"}\"."); throw new ArgumentException($"Invalid node type \"{node?.GetType().Name ?? "null"}\".");
@ -62,7 +62,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
switch (memRegion) switch (memRegion)
{ {
case Instruction.MrShared: args += LoadShared(context, operation); break; case Instruction.MrShared: args += LoadShared(context, operation); break;
case Instruction.MrStorage: args += LoadStorage(context, operation, forAtomic: true); break; case Instruction.MrStorage: args += LoadStorage(context, operation); break;
default: throw new InvalidOperationException($"Invalid memory region \"{memRegion}\"."); default: throw new InvalidOperationException($"Invalid memory region \"{memRegion}\".");
} }

View file

@ -56,7 +56,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
string ApplyScaling(string vector) string ApplyScaling(string vector)
{ {
int index = context.FindImageDescriptorIndex(texOp); int index = context.FindImageDescriptorIndex(texOp);
TextureUsageFlags flags = TextureUsageFlags.NeedsScaleValue;
if ((context.Config.Stage == ShaderStage.Fragment || context.Config.Stage == ShaderStage.Compute) && if ((context.Config.Stage == ShaderStage.Fragment || context.Config.Stage == ShaderStage.Compute) &&
texOp.Inst == Instruction.ImageLoad && texOp.Inst == Instruction.ImageLoad &&
@ -64,7 +63,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
!isIndexed) !isIndexed)
{ {
// Image scales start after texture ones. // Image scales start after texture ones.
int scaleIndex = context.TextureDescriptors.Count + index; int scaleIndex = context.Config.GetTextureDescriptors().Length + index;
if (pCount == 3 && isArray) if (pCount == 3 && isArray)
{ {
@ -75,19 +74,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
{ {
vector = "Helper_TexelFetchScale(" + vector + ", " + scaleIndex + ")"; vector = "Helper_TexelFetchScale(" + vector + ", " + scaleIndex + ")";
} }
else
{
flags |= TextureUsageFlags.ResScaleUnsupported;
}
}
else
{
flags |= TextureUsageFlags.ResScaleUnsupported;
}
if (!isBindless)
{
context.ImageDescriptors[index] = context.ImageDescriptors[index].SetFlag(flags);
} }
return vector; return vector;
@ -112,7 +98,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
if (texOp.Inst == Instruction.ImageStore) if (texOp.Inst == Instruction.ImageStore)
{ {
int texIndex = context.FindImageDescriptorIndex(texOp); int texIndex = context.FindImageDescriptorIndex(texOp);
context.ImageDescriptors[texIndex] = context.ImageDescriptors[texIndex].SetFlag(TextureUsageFlags.ImageStore);
VariableType type = texOp.Format.GetComponentType(); VariableType type = texOp.Format.GetComponentType();
@ -176,12 +161,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
if (src1 is AstOperand oper && oper.Type == OperandType.Constant) if (src1 is AstOperand oper && oper.Type == OperandType.Constant)
{ {
return OperandManager.GetConstantBufferName(oper.Value, offsetExpr, context.Config.Stage, context.CbIndexable); bool cbIndexable = context.Config.UsedFeatures.HasFlag(Translation.FeatureFlags.CbIndexing);
return OperandManager.GetConstantBufferName(oper.Value, offsetExpr, context.Config.Stage, cbIndexable);
} }
else else
{ {
string slotExpr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 0)); string slotExpr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 0));
return OperandManager.GetConstantBufferName(slotExpr, offsetExpr, context.Config.Stage); return OperandManager.GetConstantBufferName(slotExpr, offsetExpr, context.Config.Stage);
} }
} }
@ -205,7 +190,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
return $"{arrayName}[{offsetExpr}]"; return $"{arrayName}[{offsetExpr}]";
} }
public static string LoadStorage(CodeGenContext context, AstOperation operation, bool forAtomic = false) public static string LoadStorage(CodeGenContext context, AstOperation operation)
{ {
IAstNode src1 = operation.GetSource(0); IAstNode src1 = operation.GetSource(0);
IAstNode src2 = operation.GetSource(1); IAstNode src2 = operation.GetSource(1);
@ -213,11 +198,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
string indexExpr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 0)); string indexExpr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 0));
string offsetExpr = GetSoureExpr(context, src2, GetSrcVarType(operation.Inst, 1)); string offsetExpr = GetSoureExpr(context, src2, GetSrcVarType(operation.Inst, 1));
if (forAtomic)
{
SetStorageWriteFlag(context, src1, context.Config.Stage);
}
return GetStorageBufferAccessor(indexExpr, offsetExpr, context.Config.Stage); return GetStorageBufferAccessor(indexExpr, offsetExpr, context.Config.Stage);
} }
@ -306,7 +286,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
string src = TypeConversion.ReinterpretCast(context, src3, srcType, VariableType.U32); string src = TypeConversion.ReinterpretCast(context, src3, srcType, VariableType.U32);
SetStorageWriteFlag(context, src1, context.Config.Stage);
string sb = GetStorageBufferAccessor(indexExpr, offsetExpr, context.Config.Stage); string sb = GetStorageBufferAccessor(indexExpr, offsetExpr, context.Config.Stage);
return $"{sb} = {src}"; return $"{sb} = {src}";
@ -471,7 +450,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
if (intCoords) if (intCoords)
{ {
int index = context.FindTextureDescriptorIndex(texOp); int index = context.FindTextureDescriptorIndex(texOp);
TextureUsageFlags flags = TextureUsageFlags.NeedsScaleValue;
if ((context.Config.Stage == ShaderStage.Fragment || context.Config.Stage == ShaderStage.Compute) && if ((context.Config.Stage == ShaderStage.Fragment || context.Config.Stage == ShaderStage.Compute) &&
!isBindless && !isBindless &&
@ -486,22 +464,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
{ {
vector = "Helper_TexelFetchScale(" + vector + ", " + index + ")"; vector = "Helper_TexelFetchScale(" + vector + ", " + index + ")";
} }
else
{
flags |= TextureUsageFlags.ResScaleUnsupported;
}
}
else
{
// Resolution scaling cannot be applied to this texture right now.
// Flag so that we know to blacklist scaling on related textures when binding them.
flags |= TextureUsageFlags.ResScaleUnsupported;
}
if (!isBindless)
{
context.TextureDescriptors[index] = context.TextureDescriptors[index].SetFlag(flags);
} }
} }
@ -638,32 +600,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
} }
} }
private static void SetStorageWriteFlag(CodeGenContext context, IAstNode indexExpr, ShaderStage stage)
{
// Attempt to find a BufferDescriptor with the given index.
// If it cannot be resolved or is not constant, assume that the slot expression could potentially index any of them,
// and set the flag on all storage buffers.
int index = -1;
if (indexExpr is AstOperand operand && operand.Type == OperandType.Constant)
{
index = context.SBufferDescriptors.FindIndex(buffer => buffer.Slot == operand.Value);
}
if (index != -1)
{
context.SBufferDescriptors[index] = context.SBufferDescriptors[index].SetFlag(BufferUsageFlags.Write);
}
else
{
for (int i = 0; i < context.SBufferDescriptors.Count; i++)
{
context.SBufferDescriptors[i] = context.SBufferDescriptors[i].SetFlag(BufferUsageFlags.Write);
}
}
}
private static string GetStorageBufferAccessor(string slotExpr, string offsetExpr, ShaderStage stage) private static string GetStorageBufferAccessor(string slotExpr, string offsetExpr, ShaderStage stage)
{ {
string sbName = OperandManager.GetShaderStagePrefix(stage); string sbName = OperandManager.GetShaderStagePrefix(stage);

View file

@ -94,30 +94,22 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
return name; return name;
} }
public string GetExpression(AstOperand operand, ShaderConfig config, bool cbIndexable) public string GetExpression(AstOperand operand, ShaderConfig config)
{ {
switch (operand.Type) return operand.Type switch
{ {
case OperandType.Argument: OperandType.Argument => GetArgumentName(operand.Value),
return GetArgumentName(operand.Value); OperandType.Attribute => GetAttributeName(operand, config),
OperandType.Constant => NumberFormatter.FormatInt(operand.Value),
case OperandType.Attribute: OperandType.ConstantBuffer => GetConstantBufferName(
return GetAttributeName(operand, config); operand.CbufSlot,
operand.CbufOffset,
case OperandType.Constant: config.Stage,
return NumberFormatter.FormatInt(operand.Value); config.UsedFeatures.HasFlag(FeatureFlags.CbIndexing)),
OperandType.LocalVariable => _locals[operand],
case OperandType.ConstantBuffer: OperandType.Undefined => DefaultNames.UndefinedName,
return GetConstantBufferName(operand.CbufSlot, operand.CbufOffset, config.Stage, cbIndexable); _ => throw new ArgumentException($"Invalid operand type \"{operand.Type}\".")
};
case OperandType.LocalVariable:
return _locals[operand];
case OperandType.Undefined:
return DefaultNames.UndefinedName;
}
throw new ArgumentException($"Invalid operand type \"{operand.Type}\".");
} }
public static string GetConstantBufferName(int slot, int offset, ShaderStage stage, bool cbIndexable) public static string GetConstantBufferName(int slot, int offset, ShaderStage stage, bool cbIndexable)
@ -242,9 +234,14 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
public static string GetSamplerName(ShaderStage stage, AstTextureOperation texOp, string indexExpr) public static string GetSamplerName(ShaderStage stage, AstTextureOperation texOp, string indexExpr)
{ {
string suffix = texOp.CbufSlot < 0 ? $"_tcb_{texOp.Handle:X}" : $"_cb{texOp.CbufSlot}_{texOp.Handle:X}"; return GetSamplerName(stage, texOp.CbufSlot, texOp.Handle, texOp.Type.HasFlag(SamplerType.Indexed), indexExpr);
}
if ((texOp.Type & SamplerType.Indexed) != 0) public static string GetSamplerName(ShaderStage stage, int cbufSlot, int handle, bool indexed, string indexExpr)
{
string suffix = cbufSlot < 0 ? $"_tcb_{handle:X}" : $"_cb{cbufSlot}_{handle:X}";
if (indexed)
{ {
suffix += $"a[{indexExpr}]"; suffix += $"a[{indexExpr}]";
} }
@ -254,9 +251,22 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
public static string GetImageName(ShaderStage stage, AstTextureOperation texOp, string indexExpr) public static string GetImageName(ShaderStage stage, AstTextureOperation texOp, string indexExpr)
{ {
string suffix = texOp.CbufSlot < 0 ? $"_tcb_{texOp.Handle:X}_{texOp.Format.ToGlslFormat()}" : $"_cb{texOp.CbufSlot}_{texOp.Handle:X}_{texOp.Format.ToGlslFormat()}"; return GetImageName(stage, texOp.CbufSlot, texOp.Handle, texOp.Format, texOp.Type.HasFlag(SamplerType.Indexed), indexExpr);
}
if ((texOp.Type & SamplerType.Indexed) != 0) public static string GetImageName(
ShaderStage stage,
int cbufSlot,
int handle,
TextureFormat format,
bool indexed,
string indexExpr)
{
string suffix = cbufSlot < 0
? $"_tcb_{handle:X}_{format.ToGlslFormat()}"
: $"_cb{cbufSlot}_{handle:X}_{format.ToGlslFormat()}";
if (indexed)
{ {
suffix += $"a[{indexExpr}]"; suffix += $"a[{indexExpr}]";
} }

View file

@ -82,7 +82,9 @@ namespace Ryujinx.Graphics.Shader.Instructions
switch (context.CurrOp) switch (context.CurrOp)
{ {
case IOpCodeCbuf op: case IOpCodeCbuf op:
return context.PackDouble2x32(Cbuf(op.Slot, op.Offset), Cbuf(op.Slot, op.Offset + 1)); return context.PackDouble2x32(
context.Config.CreateCbuf(op.Slot, op.Offset),
context.Config.CreateCbuf(op.Slot, op.Offset + 1));
case IOpCodeImmF op: case IOpCodeImmF op:
return context.FP32ConvertToFP64(ConstF(op.Immediate)); return context.FP32ConvertToFP64(ConstF(op.Immediate));
@ -99,7 +101,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
switch (context.CurrOp) switch (context.CurrOp)
{ {
case IOpCodeCbuf op: case IOpCodeCbuf op:
return Cbuf(op.Slot, op.Offset); return context.Config.CreateCbuf(op.Slot, op.Offset);
case IOpCodeImm op: case IOpCodeImm op:
return Const(op.Immediate); return Const(op.Immediate);
@ -125,7 +127,9 @@ namespace Ryujinx.Graphics.Shader.Instructions
switch (context.CurrOp) switch (context.CurrOp)
{ {
case IOpCodeRegCbuf op: case IOpCodeRegCbuf op:
return context.PackDouble2x32(Cbuf(op.Slot, op.Offset), Cbuf(op.Slot, op.Offset + 1)); return context.PackDouble2x32(
context.Config.CreateCbuf(op.Slot, op.Offset),
context.Config.CreateCbuf(op.Slot, op.Offset + 1));
case IOpCodeRc op: case IOpCodeRc op:
return context.PackDouble2x32(Register(op.Rc.Index, op.Rc.Type), Register(op.Rc.Index | 1, op.Rc.Type)); return context.PackDouble2x32(Register(op.Rc.Index, op.Rc.Type), Register(op.Rc.Index | 1, op.Rc.Type));
@ -136,7 +140,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
switch (context.CurrOp) switch (context.CurrOp)
{ {
case IOpCodeRegCbuf op: case IOpCodeRegCbuf op:
return Cbuf(op.Slot, op.Offset); return context.Config.CreateCbuf(op.Slot, op.Offset);
case IOpCodeRc op: case IOpCodeRc op:
return Register(op.Rc); return Register(op.Rc);

View file

@ -95,7 +95,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
Operand rd = Register(rdIndex++, RegisterType.Gpr); Operand rd = Register(rdIndex++, RegisterType.Gpr);
TextureOperation operation = new TextureOperation( TextureOperation operation = context.CreateTextureOperation(
Instruction.ImageLoad, Instruction.ImageLoad,
type, type,
flags, flags,
@ -132,17 +132,15 @@ namespace Ryujinx.Graphics.Shader.Instructions
Operand rd = Register(rdIndex++, RegisterType.Gpr); Operand rd = Register(rdIndex++, RegisterType.Gpr);
TextureOperation operation = new TextureOperation( TextureOperation operation = context.CreateTextureOperation(
Instruction.ImageLoad, Instruction.ImageLoad,
type, type,
GetTextureFormat(op.Size),
flags, flags,
handle, handle,
compIndex, compIndex,
rd, rd,
sources) sources);
{
Format = GetTextureFormat(op.Size)
};
context.Add(operation); context.Add(operation);
@ -266,17 +264,15 @@ namespace Ryujinx.Graphics.Shader.Instructions
TextureFlags flags = op.IsBindless ? TextureFlags.Bindless : TextureFlags.None; TextureFlags flags = op.IsBindless ? TextureFlags.Bindless : TextureFlags.None;
TextureOperation operation = new TextureOperation( TextureOperation operation = context.CreateTextureOperation(
Instruction.ImageStore, Instruction.ImageStore,
type, type,
format,
flags, flags,
handle, handle,
0, 0,
null, null,
sources) sources);
{
Format = format
};
context.Add(operation); context.Add(operation);
} }
@ -615,7 +611,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
{ {
Operand dest = GetDest(); Operand dest = GetDest();
TextureOperation operation = new TextureOperation( TextureOperation operation = context.CreateTextureOperation(
Instruction.TextureSample, Instruction.TextureSample,
type, type,
flags, flags,
@ -764,7 +760,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
{ {
Operand dest = GetDest(); Operand dest = GetDest();
TextureOperation operation = new TextureOperation( TextureOperation operation = context.CreateTextureOperation(
Instruction.TextureSample, Instruction.TextureSample,
type, type,
flags, flags,
@ -888,7 +884,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
{ {
Operand tempDest = Local(); Operand tempDest = Local();
TextureOperation operation = new TextureOperation( TextureOperation operation = context.CreateTextureOperation(
Instruction.Lod, Instruction.Lod,
type, type,
flags, flags,
@ -1027,7 +1023,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
{ {
Operand dest = GetDest(); Operand dest = GetDest();
TextureOperation operation = new TextureOperation( TextureOperation operation = context.CreateTextureOperation(
Instruction.TextureSample, Instruction.TextureSample,
type, type,
flags, flags,
@ -1112,7 +1108,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
{ {
Operand dest = GetDest(); Operand dest = GetDest();
TextureOperation operation = new TextureOperation( TextureOperation operation = context.CreateTextureOperation(
inst, inst,
type, type,
flags, flags,
@ -1277,7 +1273,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
{ {
Operand dest = GetDest(); Operand dest = GetDest();
TextureOperation operation = new TextureOperation( TextureOperation operation = context.CreateTextureOperation(
Instruction.TextureSample, Instruction.TextureSample,
type, type,
flags, flags,

View file

@ -2,30 +2,30 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
{ {
class TextureOperation : Operation class TextureOperation : Operation
{ {
private const int DefaultCbufSlot = -1; public const int DefaultCbufSlot = -1;
public SamplerType Type { get; private set; } public SamplerType Type { get; private set; }
public TextureFormat Format { get; set; }
public TextureFlags Flags { get; private set; } public TextureFlags Flags { get; private set; }
public int CbufSlot { get; private set; } public int CbufSlot { get; private set; }
public int Handle { get; private set; } public int Handle { get; private set; }
public TextureFormat Format { get; set; }
public TextureOperation( public TextureOperation(
Instruction inst, Instruction inst,
SamplerType type, SamplerType type,
TextureFlags flags, TextureFormat format,
int handle, TextureFlags flags,
int compIndex, int handle,
Operand dest, int compIndex,
Operand dest,
params Operand[] sources) : base(inst, compIndex, dest, sources) params Operand[] sources) : base(inst, compIndex, dest, sources)
{ {
Type = type; Type = type;
Flags = flags; Format = format;
Flags = flags;
CbufSlot = DefaultCbufSlot; CbufSlot = DefaultCbufSlot;
Handle = handle; Handle = handle;
} }
public void TurnIntoIndexed(int handle) public void TurnIntoIndexed(int handle)
@ -45,7 +45,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
} }
CbufSlot = cbufSlot; CbufSlot = cbufSlot;
Handle = handle; Handle = handle;
} }
} }
} }

View file

@ -4,31 +4,28 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
{ {
class AstTextureOperation : AstOperation class AstTextureOperation : AstOperation
{ {
public SamplerType Type { get; } public SamplerType Type { get; }
public TextureFormat Format { get; } public TextureFormat Format { get; }
public TextureFlags Flags { get; } public TextureFlags Flags { get; }
public int CbufSlot { get; } public int CbufSlot { get; }
public int Handle { get; } public int Handle { get; }
public int ArraySize { get; }
public AstTextureOperation( public AstTextureOperation(
Instruction inst, Instruction inst,
SamplerType type, SamplerType type,
TextureFormat format, TextureFormat format,
TextureFlags flags, TextureFlags flags,
int cbufSlot, int cbufSlot,
int handle, int handle,
int arraySize, int index,
int index,
params IAstNode[] sources) : base(inst, index, sources, sources.Length) params IAstNode[] sources) : base(inst, index, sources, sources.Length)
{ {
Type = type; Type = type;
Format = format; Format = format;
Flags = flags; Flags = flags;
CbufSlot = cbufSlot; CbufSlot = cbufSlot;
Handle = handle; Handle = handle;
ArraySize = arraySize;
} }
} }
} }

View file

@ -2,7 +2,6 @@ using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.Translation; using Ryujinx.Graphics.Shader.Translation;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Numerics;
namespace Ryujinx.Graphics.Shader.StructuredIr namespace Ryujinx.Graphics.Shader.StructuredIr
{ {
@ -100,7 +99,6 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
texOp.Flags, texOp.Flags,
texOp.CbufSlot, texOp.CbufSlot,
texOp.Handle, texOp.Handle,
4, // TODO: Non-hardcoded array size.
texOp.Index, texOp.Index,
sources); sources);
} }
@ -109,34 +107,6 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
{ {
AstOperand dest = context.GetOperandDef(operation.Dest); AstOperand dest = context.GetOperandDef(operation.Dest);
if (inst == Instruction.LoadConstant)
{
Operand slot = operation.GetSource(0);
if (slot.Type == OperandType.Constant)
{
context.Info.CBuffers.Add(slot.Value);
}
else
{
// If the value is not constant, then we don't know
// how many constant buffers are used, so we assume
// all of them are used.
int cbCount = 32 - BitOperations.LeadingZeroCount(context.Config.GpuAccessor.QueryConstantBufferUse());
for (int index = 0; index < cbCount; index++)
{
context.Info.CBuffers.Add(index);
}
context.Info.UsesCbIndexing = true;
}
}
else if (UsesStorage(inst))
{
AddSBufferUse(context.Info.SBuffers, operation);
}
// If all the sources are bool, it's better to use short-circuiting // If all the sources are bool, it's better to use short-circuiting
// logical operations, rather than forcing a cast to int and doing // logical operations, rather than forcing a cast to int and doing
// a bitwise operation with the value, as it is likely to be used as // a bitwise operation with the value, as it is likely to be used as
@ -169,23 +139,12 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
if (operation is TextureOperation texOp) if (operation is TextureOperation texOp)
{ {
if (texOp.Inst == Instruction.ImageLoad || texOp.Inst == Instruction.ImageStore) if (texOp.Inst == Instruction.ImageLoad)
{ {
dest.VarType = texOp.Format.GetComponentType(); dest.VarType = texOp.Format.GetComponentType();
} }
AstTextureOperation astTexOp = GetAstTextureOperation(texOp); source = GetAstTextureOperation(texOp);
if (texOp.Inst == Instruction.ImageLoad)
{
context.Info.Images.Add(astTexOp);
}
else
{
context.Info.Samplers.Add(astTexOp);
}
source = astTexOp;
} }
else if (!isCopy) else if (!isCopy)
{ {
@ -206,17 +165,10 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
{ {
AstTextureOperation astTexOp = GetAstTextureOperation(texOp); AstTextureOperation astTexOp = GetAstTextureOperation(texOp);
context.Info.Images.Add(astTexOp);
context.AddNode(astTexOp); context.AddNode(astTexOp);
} }
else else
{ {
if (UsesStorage(inst))
{
AddSBufferUse(context.Info.SBuffers, operation);
}
context.AddNode(new AstOperation(inst, operation.Index, sources, operation.SourcesCount)); context.AddNode(new AstOperation(inst, operation.Index, sources, operation.SourcesCount));
} }
@ -257,26 +209,6 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
} }
} }
private static void AddSBufferUse(HashSet<int> sBuffers, Operation operation)
{
Operand slot = operation.GetSource(0);
if (slot.Type == OperandType.Constant)
{
sBuffers.Add(slot.Value);
}
else
{
// If the value is not constant, then we don't know
// how many storage buffers are used, so we assume
// all of them are used.
for (int index = 0; index < GlobalMemory.StorageMaxCount; index++)
{
sBuffers.Add(index);
}
}
}
private static VariableType GetVarTypeFromUses(Operand dest) private static VariableType GetVarTypeFromUses(Operand dest)
{ {
HashSet<Operand> visited = new HashSet<Operand>(); HashSet<Operand> visited = new HashSet<Operand>();
@ -301,7 +233,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
{ {
foreach (INode useNode in operand.UseOps) foreach (INode useNode in operand.UseOps)
{ {
if (!(useNode is Operation operation)) if (useNode is not Operation operation)
{ {
continue; continue;
} }
@ -340,7 +272,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
{ {
foreach (IAstNode node in sources) foreach (IAstNode node in sources)
{ {
if (!(node is AstOperand operand)) if (node is not AstOperand operand)
{ {
return false; return false;
} }
@ -356,52 +288,37 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
private static bool IsBranchInst(Instruction inst) private static bool IsBranchInst(Instruction inst)
{ {
switch (inst) return inst switch
{ {
case Instruction.Branch: Instruction.Branch or
case Instruction.BranchIfFalse: Instruction.BranchIfFalse or
case Instruction.BranchIfTrue: Instruction.BranchIfTrue => true,
return true; _ => false,
} };
return false;
} }
private static bool IsBitwiseInst(Instruction inst) private static bool IsBitwiseInst(Instruction inst)
{ {
switch (inst) return inst switch
{ {
case Instruction.BitwiseAnd: Instruction.BitwiseAnd or
case Instruction.BitwiseExclusiveOr: Instruction.BitwiseExclusiveOr or
case Instruction.BitwiseNot: Instruction.BitwiseNot or
case Instruction.BitwiseOr: Instruction.BitwiseOr => true,
return true; _ => false
} };
return false;
} }
private static Instruction GetLogicalFromBitwiseInst(Instruction inst) private static Instruction GetLogicalFromBitwiseInst(Instruction inst)
{ {
switch (inst) return inst switch
{ {
case Instruction.BitwiseAnd: return Instruction.LogicalAnd; Instruction.BitwiseAnd => Instruction.LogicalAnd,
case Instruction.BitwiseExclusiveOr: return Instruction.LogicalExclusiveOr; Instruction.BitwiseExclusiveOr => Instruction.LogicalExclusiveOr,
case Instruction.BitwiseNot: return Instruction.LogicalNot; Instruction.BitwiseNot => Instruction.LogicalNot,
case Instruction.BitwiseOr: return Instruction.LogicalOr; Instruction.BitwiseOr => Instruction.LogicalOr,
} _ => throw new ArgumentException($"Unexpected instruction \"{inst}\".")
};
throw new ArgumentException($"Unexpected instruction \"{inst}\".");
}
private static bool UsesStorage(Instruction inst)
{
if (inst == Instruction.LoadStorage || inst == Instruction.StoreStorage)
{
return true;
}
return inst.IsAtomic() && (inst & Instruction.MrMask) == Instruction.MrStorage;
} }
} }
} }

View file

@ -291,10 +291,6 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
{ {
Info.IAttributes.Add(attrIndex); Info.IAttributes.Add(attrIndex);
} }
else if (operand.Type == OperandType.ConstantBuffer)
{
Info.CBuffers.Add(operand.GetCbufSlot());
}
return GetOperand(operand); return GetOperand(operand);
} }

View file

@ -6,31 +6,17 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
{ {
public List<StructuredFunction> Functions { get; } public List<StructuredFunction> Functions { get; }
public HashSet<int> CBuffers { get; }
public HashSet<int> SBuffers { get; }
public HashSet<int> IAttributes { get; } public HashSet<int> IAttributes { get; }
public HashSet<int> OAttributes { get; } public HashSet<int> OAttributes { get; }
public bool UsesCbIndexing { get; set; }
public HelperFunctionsMask HelperFunctionsMask { get; set; } public HelperFunctionsMask HelperFunctionsMask { get; set; }
public HashSet<AstTextureOperation> Samplers { get; }
public HashSet<AstTextureOperation> Images { get; }
public StructuredProgramInfo() public StructuredProgramInfo()
{ {
Functions = new List<StructuredFunction>(); Functions = new List<StructuredFunction>();
CBuffers = new HashSet<int>();
SBuffers = new HashSet<int>();
IAttributes = new HashSet<int>(); IAttributes = new HashSet<int>();
OAttributes = new HashSet<int>(); OAttributes = new HashSet<int>();
Samplers = new HashSet<AstTextureOperation>();
Images = new HashSet<AstTextureOperation>();
} }
} }
} }

View file

@ -53,6 +53,36 @@ namespace Ryujinx.Graphics.Shader.Translation
_operations.Add(operation); _operations.Add(operation);
} }
public TextureOperation CreateTextureOperation(
Instruction inst,
SamplerType type,
TextureFlags flags,
int handle,
int compIndex,
Operand dest,
params Operand[] sources)
{
return CreateTextureOperation(inst, type, TextureFormat.Unknown, flags, handle, compIndex, dest, sources);
}
public TextureOperation CreateTextureOperation(
Instruction inst,
SamplerType type,
TextureFormat format,
TextureFlags flags,
int handle,
int compIndex,
Operand dest,
params Operand[] sources)
{
if (!flags.HasFlag(TextureFlags.Bindless))
{
Config.SetUsedTexture(inst, type, format, flags, TextureOperation.DefaultCbufSlot, handle);
}
return new TextureOperation(inst, type, format, flags, handle, compIndex, dest, sources);
}
public void FlagAttributeRead(int attribute) public void FlagAttributeRead(int attribute)
{ {
if (Config.Stage == ShaderStage.Vertex && attribute == AttributeConsts.InstanceId) if (Config.Stage == ShaderStage.Vertex && attribute == AttributeConsts.InstanceId)

View file

@ -518,6 +518,15 @@ namespace Ryujinx.Graphics.Shader.Translation
public static Operand LoadConstant(this EmitterContext context, Operand a, Operand b) public static Operand LoadConstant(this EmitterContext context, Operand a, Operand b)
{ {
if (a.Type == OperandType.Constant)
{
context.Config.SetUsedConstantBuffer(a.Value);
}
else
{
context.Config.SetUsedFeature(FeatureFlags.CbIndexing);
}
return context.Add(Instruction.LoadConstant, Local(), a, b); return context.Add(Instruction.LoadConstant, Local(), a, b);
} }

View file

@ -16,7 +16,7 @@ namespace Ryujinx.Graphics.Shader.Translation
FragCoordXY = 1 << 1, FragCoordXY = 1 << 1,
Bindless = 1 << 2, Bindless = 1 << 2,
InstanceId = 1 << 3,
InstanceId = 1 << 3 CbIndexing = 1 << 4
} }
} }

View file

@ -33,7 +33,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
if (bindlessHandle.Type == OperandType.ConstantBuffer) if (bindlessHandle.Type == OperandType.ConstantBuffer)
{ {
texOp.SetHandle(bindlessHandle.GetCbufOffset(), bindlessHandle.GetCbufSlot()); SetHandle(config, texOp, bindlessHandle.GetCbufOffset(), bindlessHandle.GetCbufSlot());
continue; continue;
} }
@ -56,7 +56,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
continue; continue;
} }
texOp.SetHandle(src0.GetCbufOffset() | (src1.GetCbufOffset() << 16), src0.GetCbufSlot()); SetHandle(config, texOp, src0.GetCbufOffset() | (src1.GetCbufOffset() << 16), src0.GetCbufSlot());
} }
else if (texOp.Inst == Instruction.ImageLoad || texOp.Inst == Instruction.ImageStore) else if (texOp.Inst == Instruction.ImageLoad || texOp.Inst == Instruction.ImageStore)
{ {
@ -64,11 +64,19 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
if (src0.Type == OperandType.ConstantBuffer) if (src0.Type == OperandType.ConstantBuffer)
{ {
texOp.SetHandle(src0.GetCbufOffset(), src0.GetCbufSlot()); int cbufOffset = src0.GetCbufOffset();
texOp.Format = config.GetTextureFormat(texOp.Handle, texOp.CbufSlot); int cbufSlot = src0.GetCbufSlot();
texOp.Format = config.GetTextureFormat(cbufOffset, cbufSlot);
SetHandle(config, texOp, cbufOffset, cbufSlot);
} }
} }
} }
} }
private static void SetHandle(ShaderConfig config, TextureOperation texOp, int cbufOffset, int cbufSlot)
{
texOp.SetHandle(cbufOffset, cbufSlot);
config.SetUsedTexture(texOp.Inst, texOp.Type, texOp.Format, texOp.Flags, cbufSlot, cbufOffset);
}
} }
} }

View file

@ -7,7 +7,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
{ {
static class BindlessToIndexed static class BindlessToIndexed
{ {
public static void RunPass(BasicBlock block) public static void RunPass(BasicBlock block, ShaderConfig config)
{ {
// We can turn a bindless texture access into a indexed access, // We can turn a bindless texture access into a indexed access,
// as long the following conditions are true: // as long the following conditions are true:
@ -62,7 +62,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
continue; continue;
} }
texOp.TurnIntoIndexed(addSrc1.Value / 4); TurnIntoIndexed(config, texOp, addSrc1.Value / 4);
Operand index = Local(); Operand index = Local();
@ -75,5 +75,11 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
texOp.SetSource(0, index); texOp.SetSource(0, index);
} }
} }
private static void TurnIntoIndexed(ShaderConfig config, TextureOperation texOp, int handle)
{
texOp.TurnIntoIndexed(handle);
config.SetUsedTexture(texOp.Inst, texOp.Type, texOp.Format, texOp.Flags, texOp.CbufSlot, handle);
}
} }
} }

View file

@ -58,11 +58,16 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
{ {
Operation operation = (Operation)node.Value; Operation operation = (Operation)node.Value;
bool isAtomic = operation.Inst.IsAtomic();
bool isWrite = isAtomic || operation.Inst == Instruction.StoreGlobal;
config.SetUsedStorageBuffer(storageIndex, isWrite);
Operand GetStorageOffset() Operand GetStorageOffset()
{ {
Operand addrLow = operation.GetSource(0); Operand addrLow = operation.GetSource(0);
Operand baseAddrLow = Cbuf(0, GetStorageCbOffset(config.Stage, storageIndex)); Operand baseAddrLow = config.CreateCbuf(0, GetStorageCbOffset(config.Stage, storageIndex));
Operand baseAddrTrunc = Local(); Operand baseAddrTrunc = Local();
@ -96,7 +101,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
Operation storageOp; Operation storageOp;
if (operation.Inst.IsAtomic()) if (isAtomic)
{ {
Instruction inst = (operation.Inst & ~Instruction.MrMask) | Instruction.MrStorage; Instruction inst = (operation.Inst & ~Instruction.MrMask) | Instruction.MrStorage;
@ -133,7 +138,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
{ {
Operand addrLow = operation.GetSource(0); Operand addrLow = operation.GetSource(0);
Operand baseAddrLow = Cbuf(0, UbeBaseOffset + storageIndex * StorageDescSize); Operand baseAddrLow = config.CreateCbuf(0, UbeBaseOffset + storageIndex * StorageDescSize);
Operand baseAddrTrunc = Local(); Operand baseAddrTrunc = Local();
@ -157,9 +162,13 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
Operand[] sources = new Operand[operation.SourcesCount]; Operand[] sources = new Operand[operation.SourcesCount];
sources[0] = Const(UbeFirstCbuf + storageIndex); int cbSlot = UbeFirstCbuf + storageIndex;
sources[0] = Const(cbSlot);
sources[1] = GetCbufOffset(); sources[1] = GetCbufOffset();
config.SetUsedConstantBuffer(cbSlot);
for (int index = 2; index < operation.SourcesCount; index++) for (int index = 2; index < operation.SourcesCount; index++)
{ {
sources[index] = operation.GetSource(index); sources[index] = operation.GetSource(index);

View file

@ -16,7 +16,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++)
{ {
GlobalToStorage.RunPass(blocks[blkIndex], config); GlobalToStorage.RunPass(blocks[blkIndex], config);
BindlessToIndexed.RunPass(blocks[blkIndex]); BindlessToIndexed.RunPass(blocks[blkIndex], config);
BindlessElimination.RunPass(blocks[blkIndex], config); BindlessElimination.RunPass(blocks[blkIndex], config);
} }

View file

@ -48,6 +48,9 @@ namespace Ryujinx.Graphics.Shader.Translation
{ {
Operation operation = (Operation)node.Value; Operation operation = (Operation)node.Value;
bool isAtomic = operation.Inst.IsAtomic();
bool isWrite = isAtomic || operation.Inst == Instruction.StoreGlobal;
Operation storageOp; Operation storageOp;
Operand PrependOperation(Instruction inst, params Operand[] sources) Operand PrependOperation(Instruction inst, params Operand[] sources)
@ -67,11 +70,13 @@ namespace Ryujinx.Graphics.Shader.Translation
for (int slot = 0; slot < StorageMaxCount; slot++) for (int slot = 0; slot < StorageMaxCount; slot++)
{ {
config.SetUsedStorageBuffer(slot, isWrite);
int cbOffset = GetStorageCbOffset(config.Stage, slot); int cbOffset = GetStorageCbOffset(config.Stage, slot);
Operand baseAddrLow = Cbuf(0, cbOffset); Operand baseAddrLow = config.CreateCbuf(0, cbOffset);
Operand baseAddrHigh = Cbuf(0, cbOffset + 1); Operand baseAddrHigh = config.CreateCbuf(0, cbOffset + 1);
Operand size = Cbuf(0, cbOffset + 2); Operand size = config.CreateCbuf(0, cbOffset + 2);
Operand offset = PrependOperation(Instruction.Subtract, addrLow, baseAddrLow); Operand offset = PrependOperation(Instruction.Subtract, addrLow, baseAddrLow);
Operand borrow = PrependOperation(Instruction.CompareLessU32, addrLow, baseAddrLow); Operand borrow = PrependOperation(Instruction.CompareLessU32, addrLow, baseAddrLow);
@ -104,7 +109,7 @@ namespace Ryujinx.Graphics.Shader.Translation
sources[index] = operation.GetSource(index); sources[index] = operation.GetSource(index);
} }
if (operation.Inst.IsAtomic()) if (isAtomic)
{ {
Instruction inst = (operation.Inst & ~Instruction.MrMask) | Instruction.MrStorage; Instruction inst = (operation.Inst & ~Instruction.MrMask) | Instruction.MrStorage;
@ -303,6 +308,7 @@ namespace Ryujinx.Graphics.Shader.Translation
node.List.AddBefore(node, new TextureOperation( node.List.AddBefore(node, new TextureOperation(
Instruction.TextureSize, Instruction.TextureSize,
texOp.Type, texOp.Type,
texOp.Format,
texOp.Flags, texOp.Flags,
texOp.Handle, texOp.Handle,
index, index,
@ -350,6 +356,7 @@ namespace Ryujinx.Graphics.Shader.Translation
node.List.AddBefore(node, new TextureOperation( node.List.AddBefore(node, new TextureOperation(
Instruction.Lod, Instruction.Lod,
texOp.Type, texOp.Type,
texOp.Format,
texOp.Flags, texOp.Flags,
texOp.Handle, texOp.Handle,
1, 1,
@ -374,6 +381,7 @@ namespace Ryujinx.Graphics.Shader.Translation
node.List.AddBefore(node, new TextureOperation( node.List.AddBefore(node, new TextureOperation(
Instruction.TextureSize, Instruction.TextureSize,
texOp.Type, texOp.Type,
texOp.Format,
texOp.Flags, texOp.Flags,
texOp.Handle, texOp.Handle,
index, index,
@ -409,6 +417,7 @@ namespace Ryujinx.Graphics.Shader.Translation
TextureOperation newTexOp = new TextureOperation( TextureOperation newTexOp = new TextureOperation(
Instruction.TextureSample, Instruction.TextureSample,
texOp.Type, texOp.Type,
texOp.Format,
texOp.Flags & ~(TextureFlags.Offset | TextureFlags.Offsets), texOp.Flags & ~(TextureFlags.Offset | TextureFlags.Offsets),
texOp.Handle, texOp.Handle,
componentIndex, componentIndex,

View file

@ -1,9 +1,16 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Numerics;
namespace Ryujinx.Graphics.Shader.Translation namespace Ryujinx.Graphics.Shader.Translation
{ {
class ShaderConfig class ShaderConfig
{ {
// TODO: Non-hardcoded array size.
public const int SamplerArraySize = 4;
public ShaderStage Stage { get; } public ShaderStage Stage { get; }
public bool GpPassthrough { get; } public bool GpPassthrough { get; }
@ -24,8 +31,6 @@ namespace Ryujinx.Graphics.Shader.Translation
public TranslationFlags Flags { get; } public TranslationFlags Flags { get; }
public TranslationCounts Counts { get; }
public int Size { get; private set; } public int Size { get; private set; }
public byte ClipDistancesWritten { get; private set; } public byte ClipDistancesWritten { get; private set; }
@ -34,42 +39,80 @@ namespace Ryujinx.Graphics.Shader.Translation
public HashSet<int> TextureHandlesForCache { get; } public HashSet<int> TextureHandlesForCache { get; }
private readonly TranslationCounts _counts;
private int _usedConstantBuffers;
private int _usedStorageBuffers;
private int _usedStorageBuffersWrite;
private struct TextureInfo : IEquatable<TextureInfo>
{
public int CbufSlot { get; }
public int Handle { get; }
public bool Indexed { get; }
public TextureFormat Format { get; }
public TextureInfo(int cbufSlot, int handle, bool indexed, TextureFormat format)
{
CbufSlot = cbufSlot;
Handle = handle;
Indexed = indexed;
Format = format;
}
public override bool Equals(object obj)
{
return obj is TextureInfo other && Equals(other);
}
public bool Equals(TextureInfo other)
{
return CbufSlot == other.CbufSlot && Handle == other.Handle && Indexed == other.Indexed && Format == other.Format;
}
public override int GetHashCode()
{
return HashCode.Combine(CbufSlot, Handle, Indexed, Format);
}
}
private struct TextureMeta
{
public bool AccurateType;
public SamplerType Type;
public TextureUsageFlags UsageFlags;
}
private readonly Dictionary<TextureInfo, TextureMeta> _usedTextures;
private readonly Dictionary<TextureInfo, TextureMeta> _usedImages;
private BufferDescriptor[] _cachedConstantBufferDescriptors;
private BufferDescriptor[] _cachedStorageBufferDescriptors;
private TextureDescriptor[] _cachedTextureDescriptors;
private TextureDescriptor[] _cachedImageDescriptors;
public ShaderConfig(IGpuAccessor gpuAccessor, TranslationFlags flags, TranslationCounts counts) public ShaderConfig(IGpuAccessor gpuAccessor, TranslationFlags flags, TranslationCounts counts)
{ {
Stage = ShaderStage.Compute; Stage = ShaderStage.Compute;
GpPassthrough = false;
OutputTopology = OutputTopology.PointList;
MaxOutputVertices = 0;
LocalMemorySize = 0;
ImapTypes = null;
OmapTargets = null;
OmapSampleMask = false;
OmapDepth = false;
GpuAccessor = gpuAccessor; GpuAccessor = gpuAccessor;
Flags = flags; Flags = flags;
Size = 0; _counts = counts;
UsedFeatures = FeatureFlags.None;
Counts = counts;
TextureHandlesForCache = new HashSet<int>(); TextureHandlesForCache = new HashSet<int>();
_usedTextures = new Dictionary<TextureInfo, TextureMeta>();
_usedImages = new Dictionary<TextureInfo, TextureMeta>();
} }
public ShaderConfig(ShaderHeader header, IGpuAccessor gpuAccessor, TranslationFlags flags, TranslationCounts counts) public ShaderConfig(ShaderHeader header, IGpuAccessor gpuAccessor, TranslationFlags flags, TranslationCounts counts) : this(gpuAccessor, flags, counts)
{ {
Stage = header.Stage; Stage = header.Stage;
GpPassthrough = header.Stage == ShaderStage.Geometry && header.GpPassthrough; GpPassthrough = header.Stage == ShaderStage.Geometry && header.GpPassthrough;
OutputTopology = header.OutputTopology; OutputTopology = header.OutputTopology;
MaxOutputVertices = header.MaxOutputVertexCount; MaxOutputVertices = header.MaxOutputVertexCount;
LocalMemorySize = header.ShaderLocalMemoryLowSize + header.ShaderLocalMemoryHighSize; LocalMemorySize = header.ShaderLocalMemoryLowSize + header.ShaderLocalMemoryHighSize;
ImapTypes = header.ImapTypes; ImapTypes = header.ImapTypes;
OmapTargets = header.OmapTargets; OmapTargets = header.OmapTargets;
OmapSampleMask = header.OmapSampleMask; OmapSampleMask = header.OmapSampleMask;
OmapDepth = header.OmapDepth; OmapDepth = header.OmapDepth;
GpuAccessor = gpuAccessor;
Flags = flags;
Size = 0;
UsedFeatures = FeatureFlags.None;
Counts = counts;
TextureHandlesForCache = new HashSet<int>();
} }
public int GetDepthRegister() public int GetDepthRegister()
@ -126,5 +169,199 @@ namespace Ryujinx.Graphics.Shader.Translation
{ {
UsedFeatures |= flags; UsedFeatures |= flags;
} }
public Operand CreateCbuf(int slot, int offset)
{
SetUsedConstantBuffer(slot);
return OperandHelper.Cbuf(slot, offset);
}
public void SetUsedConstantBuffer(int slot)
{
_usedConstantBuffers |= 1 << slot;
}
public void SetUsedStorageBuffer(int slot, bool write)
{
int mask = 1 << slot;
_usedStorageBuffers |= mask;
if (write)
{
_usedStorageBuffersWrite |= mask;
}
}
public void SetUsedTexture(
Instruction inst,
SamplerType type,
TextureFormat format,
TextureFlags flags,
int cbufSlot,
int handle)
{
inst &= Instruction.Mask;
bool isImage = inst == Instruction.ImageLoad || inst == Instruction.ImageStore;
bool isWrite = inst == Instruction.ImageStore;
bool accurateType = inst != Instruction.TextureSize && inst != Instruction.Lod;
if (isImage)
{
SetUsedTextureOrImage(_usedImages, cbufSlot, handle, type, format, true, isWrite, false);
}
else
{
SetUsedTextureOrImage(_usedTextures, cbufSlot, handle, type, TextureFormat.Unknown, flags.HasFlag(TextureFlags.IntCoords), false, accurateType);
}
}
private static void SetUsedTextureOrImage(
Dictionary<TextureInfo, TextureMeta> dict,
int cbufSlot,
int handle,
SamplerType type,
TextureFormat format,
bool intCoords,
bool write,
bool accurateType)
{
var dimensions = type.GetDimensions();
var isArray = type.HasFlag(SamplerType.Array);
var isIndexed = type.HasFlag(SamplerType.Indexed);
var usageFlags = TextureUsageFlags.None;
if (intCoords)
{
usageFlags |= TextureUsageFlags.NeedsScaleValue;
var canScale = (dimensions == 2 && !isArray) || (dimensions == 3 && isArray);
if (!canScale)
{
// Resolution scaling cannot be applied to this texture right now.
// Flag so that we know to blacklist scaling on related textures when binding them.
usageFlags |= TextureUsageFlags.ResScaleUnsupported;
}
}
if (write)
{
usageFlags |= TextureUsageFlags.ImageStore;
}
int arraySize = isIndexed ? SamplerArraySize : 1;
for (int layer = 0; layer < arraySize; layer++)
{
var info = new TextureInfo(cbufSlot, handle + layer * 2, isIndexed, format);
var meta = new TextureMeta()
{
AccurateType = accurateType,
Type = type,
UsageFlags = usageFlags
};
if (dict.TryGetValue(info, out var existingMeta))
{
meta.UsageFlags |= existingMeta.UsageFlags;
// If the texture we have has inaccurate type information, then
// we prefer the most accurate one.
if (existingMeta.AccurateType)
{
meta.AccurateType = true;
meta.Type = existingMeta.Type;
}
dict[info] = meta;
}
else
{
dict.Add(info, meta);
}
}
}
public BufferDescriptor[] GetConstantBufferDescriptors()
{
if (_cachedConstantBufferDescriptors != null)
{
return _cachedConstantBufferDescriptors;
}
int usedMask = _usedConstantBuffers;
if (UsedFeatures.HasFlag(FeatureFlags.CbIndexing))
{
usedMask = FillMask(usedMask);
}
return _cachedConstantBufferDescriptors = GetBufferDescriptors(usedMask, 0, _counts.IncrementUniformBuffersCount);
}
public BufferDescriptor[] GetStorageBufferDescriptors()
{
return _cachedStorageBufferDescriptors ??= GetBufferDescriptors(FillMask(_usedStorageBuffers), _usedStorageBuffersWrite, _counts.IncrementStorageBuffersCount);
}
private static int FillMask(int mask)
{
// When the storage or uniform buffers are used as array, we must allocate a binding
// even for the "gaps" that are not used on the shader.
// For this reason, fill up the gaps so that all slots up to the highest one are
// marked as "used".
return mask != 0 ? (int)(uint.MaxValue >> BitOperations.LeadingZeroCount((uint)mask)) : 0;
}
private static BufferDescriptor[] GetBufferDescriptors(int usedMask, int writtenMask, Func<int> getBindingCallback)
{
var descriptors = new BufferDescriptor[BitOperations.PopCount((uint)usedMask)];
for (int i = 0; i < descriptors.Length; i++)
{
int slot = BitOperations.TrailingZeroCount(usedMask);
descriptors[i] = new BufferDescriptor(getBindingCallback(), slot);
if ((writtenMask & (1 << slot)) != 0)
{
descriptors[i].SetFlag(BufferUsageFlags.Write);
}
usedMask &= ~(1 << slot);
}
return descriptors;
}
public TextureDescriptor[] GetTextureDescriptors()
{
return _cachedTextureDescriptors ??= GetTextureOrImageDescriptors(_usedTextures, _counts.IncrementTexturesCount);
}
public TextureDescriptor[] GetImageDescriptors()
{
return _cachedImageDescriptors ??= GetTextureOrImageDescriptors(_usedImages, _counts.IncrementImagesCount);
}
private static TextureDescriptor[] GetTextureOrImageDescriptors(Dictionary<TextureInfo, TextureMeta> dict, Func<int> getBindingCallback)
{
var descriptors = new TextureDescriptor[dict.Count];
int i = 0;
foreach (var kv in dict.OrderBy(x => x.Key.Indexed).OrderBy(x => x.Key.Handle))
{
var info = kv.Key;
var meta = kv.Value;
int binding = getBindingCallback();
descriptors[i] = new TextureDescriptor(binding, meta.Type, info.Format, info.CbufSlot, info.Handle);
descriptors[i].SetFlag(meta.UsageFlags);
i++;
}
return descriptors;
}
} }
} }

View file

@ -87,18 +87,16 @@ namespace Ryujinx.Graphics.Shader.Translation
StructuredProgramInfo sInfo = StructuredProgram.MakeStructuredProgram(funcs, config); StructuredProgramInfo sInfo = StructuredProgram.MakeStructuredProgram(funcs, config);
GlslProgram program = GlslGenerator.Generate(sInfo, config); string glslCode = GlslGenerator.Generate(sInfo, config);
shaderProgramInfo = new ShaderProgramInfo( shaderProgramInfo = new ShaderProgramInfo(
program.CBufferDescriptors, config.GetConstantBufferDescriptors(),
program.SBufferDescriptors, config.GetStorageBufferDescriptors(),
program.TextureDescriptors, config.GetTextureDescriptors(),
program.ImageDescriptors, config.GetImageDescriptors(),
config.UsedFeatures.HasFlag(FeatureFlags.InstanceId), config.UsedFeatures.HasFlag(FeatureFlags.InstanceId),
config.ClipDistancesWritten); config.ClipDistancesWritten);
string glslCode = program.Code;
return new ShaderProgram(config.Stage, glslCode); return new ShaderProgram(config.Stage, glslCode);
} }
@ -112,7 +110,7 @@ namespace Ryujinx.Graphics.Shader.Translation
Block[][] cfg; Block[][] cfg;
ulong maxEndAddress = 0; ulong maxEndAddress = 0;
bool hasBindless = false; bool hasBindless;
if ((flags & TranslationFlags.Compute) != 0) if ((flags & TranslationFlags.Compute) != 0)
{ {