R/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs
Mary 48f6570557
Salieri: shader cache (#1701)
Here come Salieri, my implementation of a disk shader cache!

"I'm sure you know why I named it that."
"It doesn't really mean anything."

This implementation collects shaders at runtime and cache them to be later compiled when starting a game.
2020-11-13 00:15:34 +01:00

160 lines
5.2 KiB
C#

using Ryujinx.Graphics.Shader.Decoders;
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using System.Collections.Generic;
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
using static Ryujinx.Graphics.Shader.Translation.Translator;
namespace Ryujinx.Graphics.Shader.Translation
{
public class TranslatorContext
{
private readonly Block[][] _cfg;
private readonly Block[][] _cfgA;
private ShaderConfig _config;
private ShaderConfig _configA;
public ulong Address { get; }
public ulong AddressA { get; }
public ShaderStage Stage => _config.Stage;
public int Size => _config.Size;
public int SizeA => _configA != null ? _configA.Size : 0;
public HashSet<int> TextureHandlesForCache => _config.TextureHandlesForCache;
public IGpuAccessor GpuAccessor => _config.GpuAccessor;
internal TranslatorContext(ulong address, Block[][] cfg, ShaderConfig config)
{
Address = address;
AddressA = 0;
_config = config;
_configA = null;
_cfg = cfg;
_cfgA = null;
}
internal TranslatorContext(ulong addressA, ulong addressB, Block[][] cfgA, Block[][] cfgB, ShaderConfig configA, ShaderConfig configB)
{
Address = addressB;
AddressA = addressA;
_config = configB;
_configA = configA;
_cfg = cfgB;
_cfgA = cfgA;
}
private static bool IsUserAttribute(Operand operand)
{
return operand != null &&
operand.Type == OperandType.Attribute &&
operand.Value >= AttributeConsts.UserAttributeBase &&
operand.Value < AttributeConsts.UserAttributeEnd;
}
private static FunctionCode[] Combine(FunctionCode[] a, FunctionCode[] b)
{
// Here we combine two shaders.
// For shader A:
// - All user attribute stores on shader A are turned into copies to a
// temporary variable. It's assumed that shader B will consume them.
// - All return instructions are turned into branch instructions, the
// branch target being the start of the shader B code.
// For shader B:
// - All user attribute loads on shader B are turned into copies from a
// temporary variable, as long that attribute is written by shader A.
FunctionCode[] output = new FunctionCode[a.Length + b.Length - 1];
List<Operation> ops = new List<Operation>(a.Length + b.Length);
Operand[] temps = new Operand[AttributeConsts.UserAttributesCount * 4];
Operand lblB = Label();
for (int index = 0; index < a[0].Code.Length; index++)
{
Operation operation = a[0].Code[index];
if (IsUserAttribute(operation.Dest))
{
int tIndex = (operation.Dest.Value - AttributeConsts.UserAttributeBase) / 4;
Operand temp = temps[tIndex];
if (temp == null)
{
temp = Local();
temps[tIndex] = temp;
}
operation.Dest = temp;
}
if (operation.Inst == Instruction.Return)
{
ops.Add(new Operation(Instruction.Branch, lblB));
}
else
{
ops.Add(operation);
}
}
ops.Add(new Operation(Instruction.MarkLabel, lblB));
for (int index = 0; index < b[0].Code.Length; index++)
{
Operation operation = b[0].Code[index];
for (int srcIndex = 0; srcIndex < operation.SourcesCount; srcIndex++)
{
Operand src = operation.GetSource(srcIndex);
if (IsUserAttribute(src))
{
Operand temp = temps[(src.Value - AttributeConsts.UserAttributeBase) / 4];
if (temp != null)
{
operation.SetSource(srcIndex, temp);
}
}
}
ops.Add(operation);
}
output[0] = new FunctionCode(ops.ToArray());
for (int i = 1; i < a.Length; i++)
{
output[i] = a[i];
}
for (int i = 1; i < b.Length; i++)
{
output[a.Length + i - 1] = b[i];
}
return output;
}
public ShaderProgram Translate(out ShaderProgramInfo shaderProgramInfo)
{
FunctionCode[] code = EmitShader(_cfg, _config);
if (_configA != null)
{
FunctionCode[] codeA = EmitShader(_cfgA, _configA);
_config.SetUsedFeature(_configA.UsedFeatures);
code = Combine(codeA, code);
}
return Translator.Translate(code, _config, out shaderProgramInfo, SizeA);
}
}
}