Change shader cache to identify code by hash instead of address

This commit is contained in:
gdkchan 2020-02-06 13:55:29 -03:00
parent fb0939f9b6
commit d0febd50e8
13 changed files with 406 additions and 365 deletions

View file

@ -49,7 +49,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
TextureManager.SetComputeTextureBufferIndex(state.Get<int>(MethodOffset.TextureBufferIndex));
ShaderProgramInfo info = cs.Shader.Program.Info;
ShaderProgramInfo info = cs.Shader.Info;
uint sbEnableMask = 0;
uint ubEnableMask = 0;

View file

@ -755,11 +755,11 @@ namespace Ryujinx.Graphics.Gpu.Engine
GraphicsShader gs = ShaderCache.GetGraphicsShader(state, addresses);
_vsUsesInstanceId = gs.Shaders[0]?.Program.Info.UsesInstanceId ?? false;
_vsUsesInstanceId = gs.Shaders[0]?.Info.UsesInstanceId ?? false;
for (int stage = 0; stage < Constants.ShaderStages; stage++)
{
ShaderProgramInfo info = gs.Shaders[stage]?.Program.Info;
ShaderProgramInfo info = gs.Shaders[stage]?.Info;
_currentProgramInfo[stage] = info;

View file

@ -35,14 +35,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// This reads as much data as possible, up to the specified maximum size.
/// </summary>
/// <param name="gpuVa">GPU virtual address where the data is located</param>
/// <param name="maxSize">Maximum size of the data</param>
/// <param name="size">Size of the data</param>
/// <returns>The span of the data at the specified memory location</returns>
public ReadOnlySpan<byte> GetSpan(ulong gpuVa, ulong maxSize)
public ReadOnlySpan<byte> GetSpan(ulong gpuVa, ulong size)
{
ulong processVa = _context.MemoryManager.Translate(gpuVa);
ulong size = _context.MemoryManager.GetSubSize(gpuVa, maxSize);
return _context.PhysicalMemory.GetSpan(processVa, size);
}
@ -86,7 +84,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
/// <summary>
/// Reads a 8-bits unsigned integer from GPU mapped memory.
/// Writes a 8-bits unsigned integer from GPU mapped memory.
/// </summary>
/// <param name="gpuVa">GPU virtual address where the value is located</param>
/// <param name="value">The value to be written</param>

View file

@ -1,37 +0,0 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader;
namespace Ryujinx.Graphics.Gpu.Shader
{
/// <summary>
/// Cached shader code for a single shader stage.
/// </summary>
class CachedShader
{
/// <summary>
/// Shader program containing translated code.
/// </summary>
public ShaderProgram Program { get; }
/// <summary>
/// Host shader object.
/// </summary>
public IShader HostShader { get; set; }
/// <summary>
/// Maxwell binary shader code.
/// </summary>
public int[] Code { get; }
/// <summary>
/// Creates a new instace of the cached shader.
/// </summary>
/// <param name="program">Shader program</param>
/// <param name="code">Maxwell binary shader code</param>
public CachedShader(ShaderProgram program, int[] code)
{
Program = program;
Code = code;
}
}
}

View file

@ -1,4 +1,5 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader;
namespace Ryujinx.Graphics.Gpu.Shader
{
@ -15,14 +16,14 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <summary>
/// Cached shader.
/// </summary>
public CachedShader Shader { get; }
public ShaderProgram Shader { get; }
/// <summary>
/// Creates a new instance of the compute shader.
/// </summary>
/// <param name="hostProgram">Host shader program</param>
/// <param name="shader">Cached shader</param>
public ComputeShader(IProgram hostProgram, CachedShader shader)
public ComputeShader(IProgram hostProgram, ShaderProgram shader)
{
HostProgram = hostProgram;
Shader = shader;

View file

@ -0,0 +1,35 @@
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Gpu.Shader
{
struct Fnv1a
{
private const int Prime = 0x1000193;
private const int OffsetBasis = unchecked((int)0x811c9dc5);
public int Hash { get; private set; }
public void Initialize()
{
Hash = OffsetBasis;
}
public void Add(ReadOnlySpan<byte> data)
{
ReadOnlySpan<int> dataInt = MemoryMarshal.Cast<byte, int>(data);
int offset;
for (offset = 0; offset < dataInt.Length; offset++)
{
Hash = (Hash ^ data[offset]) * Prime;
}
for (offset *= 4; offset < data.Length; offset++)
{
Hash = (Hash ^ data[offset]) * Prime;
}
}
}
}

View file

@ -1,4 +1,5 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader;
namespace Ryujinx.Graphics.Gpu.Shader
{
@ -15,14 +16,14 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <summary>
/// Compiled shader for each shader stage.
/// </summary>
public CachedShader[] Shaders { get; }
public ShaderProgram[] Shaders { get; }
/// <summary>
/// Creates a new instance of cached graphics shader.
/// </summary>
public GraphicsShader()
{
Shaders = new CachedShader[Constants.ShaderStages];
Shaders = new ShaderProgram[Constants.ShaderStages];
}
}
}

View file

@ -6,7 +6,6 @@ using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Shader.Translation;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Gpu.Shader
{
@ -17,17 +16,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary>
class ShaderCache : IDisposable
{
private const int MaxProgramSize = 0x100000;
private const int ShaderHeaderSize = 0x50;
private const TranslationFlags DefaultFlags = TranslationFlags.DebugMode;
private GpuContext _context;
private readonly GpuContext _context;
private ShaderDumper _dumper;
private readonly ShaderMap<ComputeShader> _cpPrograms;
private readonly ShaderMap<GraphicsShader> _gpPrograms;
private Dictionary<ulong, List<ComputeShader>> _cpPrograms;
private Dictionary<ShaderAddresses, List<GraphicsShader>> _gpPrograms;
private readonly ShaderDumper _dumper;
/// <summary>
/// Creates a new instance of the shader cache.
@ -37,11 +35,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
_context = context;
_dumper = new ShaderDumper();
_cpPrograms = new ShaderMap<ComputeShader>();
_gpPrograms = new ShaderMap<GraphicsShader>();
_cpPrograms = new Dictionary<ulong, List<ComputeShader>>();
_gpPrograms = new Dictionary<ShaderAddresses, List<GraphicsShader>>();
_dumper = new ShaderDumper();
}
/// <summary>
@ -64,205 +61,25 @@ namespace Ryujinx.Graphics.Gpu.Shader
int localSizeZ,
int localMemorySize,
int sharedMemorySize)
{
bool isCached = _cpPrograms.TryGetValue(gpuVa, out List<ComputeShader> list);
if (isCached)
{
foreach (ComputeShader cachedCpShader in list)
{
if (!IsShaderDifferent(cachedCpShader, gpuVa))
{
return cachedCpShader;
}
}
}
CachedShader shader = TranslateComputeShader(
gpuVa,
localSizeX,
localSizeY,
localSizeZ,
localMemorySize,
sharedMemorySize);
shader.HostShader = _context.Renderer.CompileShader(shader.Program);
IProgram hostProgram = _context.Renderer.CreateProgram(new IShader[] { shader.HostShader });
ComputeShader cpShader = new ComputeShader(hostProgram, shader);
if (!isCached)
{
list = new List<ComputeShader>();
_cpPrograms.Add(gpuVa, list);
}
list.Add(cpShader);
return cpShader;
}
/// <summary>
/// Gets a graphics shader program from the shader cache.
/// This includes all the specified shader stages.
/// </summary>
/// <remarks>
/// This automatically translates, compiles and adds the code to the cache if not present.
/// </remarks>
/// <param name="state">Current GPU state</param>
/// <param name="addresses">Addresses of the shaders for each stage</param>
/// <returns>Compiled graphics shader code</returns>
public GraphicsShader GetGraphicsShader(GpuState state, ShaderAddresses addresses)
{
bool isCached = _gpPrograms.TryGetValue(addresses, out List<GraphicsShader> list);
if (isCached)
{
foreach (GraphicsShader cachedGpShaders in list)
{
if (!IsShaderDifferent(cachedGpShaders, addresses))
{
return cachedGpShaders;
}
}
}
GraphicsShader gpShaders = new GraphicsShader();
if (addresses.VertexA != 0)
{
gpShaders.Shaders[0] = TranslateGraphicsShader(state, ShaderStage.Vertex, addresses.Vertex, addresses.VertexA);
}
else
{
gpShaders.Shaders[0] = TranslateGraphicsShader(state, ShaderStage.Vertex, addresses.Vertex);
}
gpShaders.Shaders[1] = TranslateGraphicsShader(state, ShaderStage.TessellationControl, addresses.TessControl);
gpShaders.Shaders[2] = TranslateGraphicsShader(state, ShaderStage.TessellationEvaluation, addresses.TessEvaluation);
gpShaders.Shaders[3] = TranslateGraphicsShader(state, ShaderStage.Geometry, addresses.Geometry);
gpShaders.Shaders[4] = TranslateGraphicsShader(state, ShaderStage.Fragment, addresses.Fragment);
BackpropQualifiers(gpShaders);
List<IShader> hostShaders = new List<IShader>();
for (int stage = 0; stage < gpShaders.Shaders.Length; stage++)
{
ShaderProgram program = gpShaders.Shaders[stage]?.Program;
if (program == null)
{
continue;
}
IShader hostShader = _context.Renderer.CompileShader(program);
gpShaders.Shaders[stage].HostShader = hostShader;
hostShaders.Add(hostShader);
}
gpShaders.HostProgram = _context.Renderer.CreateProgram(hostShaders.ToArray());
if (!isCached)
{
list = new List<GraphicsShader>();
_gpPrograms.Add(addresses, list);
}
list.Add(gpShaders);
return gpShaders;
}
/// <summary>
/// Checks if compute shader code in memory is different from the cached shader.
/// </summary>
/// <param name="cpShader">Cached compute shader</param>
/// <param name="gpuVa">GPU virtual address of the shader code in memory</param>
/// <returns>True if the code is different, false otherwise</returns>
private bool IsShaderDifferent(ComputeShader cpShader, ulong gpuVa)
{
return IsShaderDifferent(cpShader.Shader, gpuVa);
}
/// <summary>
/// Checks if graphics shader code from all stages in memory is different from the cached shaders.
/// </summary>
/// <param name="gpShaders">Cached graphics shaders</param>
/// <param name="addresses">GPU virtual addresses of all enabled shader stages</param>
/// <returns>True if the code is different, false otherwise</returns>
private bool IsShaderDifferent(GraphicsShader gpShaders, ShaderAddresses addresses)
{
for (int stage = 0; stage < gpShaders.Shaders.Length; stage++)
{
CachedShader shader = gpShaders.Shaders[stage];
ulong gpuVa = 0;
switch (stage)
{
case 0: gpuVa = addresses.Vertex; break;
case 1: gpuVa = addresses.TessControl; break;
case 2: gpuVa = addresses.TessEvaluation; break;
case 3: gpuVa = addresses.Geometry; break;
case 4: gpuVa = addresses.Fragment; break;
}
if (IsShaderDifferent(shader, gpuVa))
{
return true;
}
}
return false;
}
/// <summary>
/// Checks if the code of the specified cached shader is different from the code in memory.
/// </summary>
/// <param name="shader">Cached shader to compare with</param>
/// <param name="gpuVa">GPU virtual address of the binary shader code</param>
/// <returns>True if the code is different, false otherwise</returns>
private bool IsShaderDifferent(CachedShader shader, ulong gpuVa)
{
if (shader == null)
{
return false;
}
ReadOnlySpan<byte> memoryCode = _context.MemoryAccessor.GetSpan(gpuVa, (ulong)shader.Code.Length * 4);
return !MemoryMarshal.Cast<byte, int>(memoryCode).SequenceEqual(shader.Code);
}
/// <summary>
/// Translates the binary Maxwell shader code to something that the host API accepts.
/// </summary>
/// <param name="gpuVa">GPU virtual address of the binary shader code</param>
/// <param name="localSizeX">Local group size X of the computer shader</param>
/// <param name="localSizeY">Local group size Y of the computer shader</param>
/// <param name="localSizeZ">Local group size Z of the computer shader</param>
/// <param name="localMemorySize">Local memory size of the compute shader</param>
/// <param name="sharedMemorySize">Shared memory size of the compute shader</param>
/// <returns>Compiled compute shader code</returns>
private CachedShader TranslateComputeShader(
ulong gpuVa,
int localSizeX,
int localSizeY,
int localSizeZ,
int localMemorySize,
int sharedMemorySize)
{
if (gpuVa == 0)
{
return null;
}
ShaderPack pack = new ShaderPack();
ReadOnlySpan<byte> code = GetShaderCodeHeaderless(gpuVa);
pack.Add(code);
ComputeShader cs = _cpPrograms.Get(pack, out int hash);
if (cs != null)
{
return cs;
}
int QueryInfo(QueryInfoName info, int index)
{
return info switch
@ -280,21 +97,108 @@ namespace Ryujinx.Graphics.Gpu.Shader
ShaderProgram program;
ReadOnlySpan<byte> code = _context.MemoryAccessor.GetSpan(gpuVa, MaxProgramSize);
_dumper.Dump(code, compute: true, out string fullPath, out string codePath);
program = Translator.Translate(code, callbacks, DefaultFlags | TranslationFlags.Compute);
int[] codeCached = MemoryMarshal.Cast<byte, int>(code.Slice(0, program.Size)).ToArray();
_dumper.Dump(code, compute: true, out string fullPath, out string codePath);
if (fullPath != null && codePath != null)
{
program.Prepend("// " + codePath);
program.Prepend("// " + fullPath);
}
return new CachedShader(program, codeCached);
IShader shader = _context.Renderer.CompileShader(program);
cs = new ComputeShader(_context.Renderer.CreateProgram(new IShader[] { shader }), program);
_cpPrograms.Add(hash, cs, pack);
return cs;
}
/// <summary>
/// Gets a graphics shader program from the shader cache.
/// This includes all the specified shader stages.
/// </summary>
/// <remarks>
/// This automatically translates, compiles and adds the code to the cache if not present.
/// </remarks>
/// <param name="state">Current GPU state</param>
/// <param name="addresses">Addresses of the shaders for each stage</param>
/// <returns>Compiled graphics shader code</returns>
public GraphicsShader GetGraphicsShader(GpuState state, ShaderAddresses addresses)
{
ShaderPack pack = new ShaderPack();
if (addresses.Vertex != 0)
{
pack.Add(GetShaderCode(addresses.Vertex));
}
if (addresses.TessControl != 0)
{
pack.Add(GetShaderCode(addresses.TessControl));
}
if (addresses.TessEvaluation != 0)
{
pack.Add(GetShaderCode(addresses.TessEvaluation));
}
if (addresses.Geometry != 0)
{
pack.Add(GetShaderCode(addresses.Geometry));
}
if (addresses.Fragment != 0)
{
pack.Add(GetShaderCode(addresses.Fragment));
}
GraphicsShader gs = _gpPrograms.Get(pack, out int hash);
if (gs != null)
{
return gs;
}
gs = new GraphicsShader();
if (addresses.VertexA != 0)
{
gs.Shaders[0] = TranslateGraphicsShader(state, ShaderStage.Vertex, addresses.Vertex, addresses.VertexA);
}
else
{
gs.Shaders[0] = TranslateGraphicsShader(state, ShaderStage.Vertex, addresses.Vertex);
}
gs.Shaders[1] = TranslateGraphicsShader(state, ShaderStage.TessellationControl, addresses.TessControl);
gs.Shaders[2] = TranslateGraphicsShader(state, ShaderStage.TessellationEvaluation, addresses.TessEvaluation);
gs.Shaders[3] = TranslateGraphicsShader(state, ShaderStage.Geometry, addresses.Geometry);
gs.Shaders[4] = TranslateGraphicsShader(state, ShaderStage.Fragment, addresses.Fragment);
BackpropQualifiers(gs);
List<IShader> shaders = new List<IShader>();
for (int stage = 0; stage < gs.Shaders.Length; stage++)
{
ShaderProgram program = gs.Shaders[stage];
if (program == null)
{
continue;
}
shaders.Add(_context.Renderer.CompileShader(program));
}
gs.HostProgram = _context.Renderer.CreateProgram(shaders.ToArray());
_gpPrograms.Add(hash, gs, pack);
return gs;
}
/// <summary>
@ -308,7 +212,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="gpuVa">GPU virtual address of the shader code</param>
/// <param name="gpuVaA">Optional GPU virtual address of the "Vertex A" shader code</param>
/// <returns>Compiled graphics shader code</returns>
private CachedShader TranslateGraphicsShader(GpuState state, ShaderStage stage, ulong gpuVa, ulong gpuVaA = 0)
private ShaderProgram TranslateGraphicsShader(GpuState state, ShaderStage stage, ulong gpuVa, ulong gpuVaA = 0)
{
if (gpuVa == 0)
{
@ -330,37 +234,17 @@ namespace Ryujinx.Graphics.Gpu.Shader
ShaderProgram program;
int[] codeCached = null;
if (gpuVaA != 0)
{
ReadOnlySpan<byte> codeA = _context.MemoryAccessor.GetSpan(gpuVaA, MaxProgramSize);
ReadOnlySpan<byte> codeB = _context.MemoryAccessor.GetSpan(gpuVa, MaxProgramSize);
program = Translator.Translate(codeA, codeB, callbacks, DefaultFlags);
// TODO: We should also take "codeA" into account.
codeCached = MemoryMarshal.Cast<byte, int>(codeB.Slice(0, program.Size)).ToArray();
_dumper.Dump(codeA, compute: false, out string fullPathA, out string codePathA);
_dumper.Dump(codeB, compute: false, out string fullPathB, out string codePathB);
if (fullPathA != null && fullPathB != null && codePathA != null && codePathB != null)
{
program.Prepend("// " + codePathB);
program.Prepend("// " + fullPathB);
program.Prepend("// " + codePathA);
program.Prepend("// " + fullPathA);
}
// TODO.
program = null;
}
else
{
ReadOnlySpan<byte> code = _context.MemoryAccessor.GetSpan(gpuVa, MaxProgramSize);
ReadOnlySpan<byte> code = GetShaderCode(gpuVa);
program = Translator.Translate(code, callbacks, DefaultFlags);
codeCached = MemoryMarshal.Cast<byte, int>(code.Slice(0, program.Size)).ToArray();
_dumper.Dump(code, compute: false, out string fullPath, out string codePath);
if (fullPath != null && codePath != null)
@ -370,9 +254,34 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
}
ulong address = _context.MemoryManager.Translate(gpuVa);
return program;
}
return new CachedShader(program, codeCached);
private ReadOnlySpan<byte> GetShaderCode(ulong gpuVa)
{
return GetShaderCodeImpl(gpuVa, ShaderHeaderSize);
}
private ReadOnlySpan<byte> GetShaderCodeHeaderless(ulong gpuVa)
{
return GetShaderCodeImpl(gpuVa, 0);
}
private ReadOnlySpan<byte> GetShaderCodeImpl(ulong gpuVa, ulong size)
{
while (true)
{
ulong op = _context.MemoryAccessor.Read<ulong>(gpuVa + size);
size += sizeof(ulong);
if (op == 0x50b0000000070f00 || op == 0)
{
break;
}
}
return _context.MemoryAccessor.GetSpan(gpuVa, size);
}
/// <summary>
@ -380,16 +289,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// to ealier shader stages output.
/// This is required by older versions of OpenGL (pre-4.3).
/// </summary>
/// <param name="program">Graphics shader cached code</param>
private void BackpropQualifiers(GraphicsShader program)
/// <param name="gs">Graphics shader cached code</param>
private void BackpropQualifiers(GraphicsShader gs)
{
ShaderProgram fragmentShader = program.Shaders[4]?.Program;
ShaderProgram fragmentShader = gs.Shaders[4];
bool isFirst = true;
for (int stage = 3; stage >= 0; stage--)
{
if (program.Shaders[stage] == null)
if (gs.Shaders[stage] == null)
{
continue;
}
@ -402,11 +311,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
if (isFirst && !string.IsNullOrEmpty(iq))
{
program.Shaders[stage].Program.Replace($"{DefineNames.OutQualifierPrefixName}{attr}", iq);
gs.Shaders[stage].Replace($"{DefineNames.OutQualifierPrefixName}{attr}", iq);
}
else
{
program.Shaders[stage].Program.Replace($"{DefineNames.OutQualifierPrefixName}{attr} ", string.Empty);
gs.Shaders[stage].Replace($"{DefineNames.OutQualifierPrefixName}{attr} ", string.Empty);
}
}
@ -473,8 +382,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
TextureTarget target = descriptor.UnpackTextureTarget();
bool is2DTexture = target == TextureTarget.Texture2D ||
target == TextureTarget.Texture2DRect;
bool is2DTexture = target == TextureTarget.Texture2D || target == TextureTarget.Texture2DRect;
return !descriptor.UnpackTextureCoordNormalized() && is2DTexture;
}
@ -521,26 +429,14 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary>
public void Dispose()
{
foreach (List<ComputeShader> list in _cpPrograms.Values)
foreach (ComputeShader shader in _cpPrograms)
{
foreach (ComputeShader shader in list)
{
shader.HostProgram.Dispose();
shader.Shader?.HostShader.Dispose();
}
shader.HostProgram.Dispose();
}
foreach (List<GraphicsShader> list in _gpPrograms.Values)
foreach (GraphicsShader shader in _gpPrograms)
{
foreach (GraphicsShader shader in list)
{
shader.HostProgram.Dispose();
foreach (CachedShader cachedShader in shader.Shaders)
{
cachedShader?.HostShader.Dispose();
}
}
shader.HostProgram.Dispose();
}
}
}

View file

@ -1,4 +1,3 @@
using Ryujinx.Graphics.Shader.Translation;
using System;
using System.IO;
@ -9,12 +8,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary>
class ShaderDumper
{
private const int ShaderHeaderSize = 0x50;
private string _runtimeDir;
private string _dumpPath;
private int _dumpIndex;
public int CurrentDumpIndex => _dumpIndex;
public ShaderDumper()
{
_dumpIndex = 1;
@ -29,6 +28,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="codePath">Output path for the shader code without header</param>
public void Dump(ReadOnlySpan<byte> code, bool compute, out string fullPath, out string codePath)
{
int headerSize = compute ? 0 : ShaderHeaderSize;
_dumpPath = GraphicsConfig.ShadersDumpPath;
if (string.IsNullOrWhiteSpace(_dumpPath))
@ -46,8 +47,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
_dumpIndex++;
code = Translator.ExtractCode(code, compute, out int headerSize);
using (MemoryStream stream = new MemoryStream(code.ToArray()))
{
BinaryReader codeReader = new BinaryReader(stream);

View file

@ -0,0 +1,111 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Gpu.Shader
{
class ShaderMap<T> : IEnumerable<T>
{
private struct ShaderEntry
{
public T Shader { get; }
private readonly byte[][] _code;
public ShaderEntry(T shader, byte[][] code)
{
Shader = shader;
_code = code;
}
public bool CodeEquals(ShaderPack pack)
{
if (pack.Count != _code.Length)
{
return false;
}
for (int index = 0; index < pack.Count; index++)
{
if (!pack[index].SequenceEqual(_code[index]))
{
return false;
}
}
return true;
}
}
private readonly Dictionary<int, List<ShaderEntry>> _shaders;
public ShaderMap()
{
_shaders = new Dictionary<int, List<ShaderEntry>>();
}
public T Get(ShaderPack pack, out int hash)
{
Fnv1a hasher = new Fnv1a();
hasher.Initialize();
for (int index = 0; index < pack.Count; index++)
{
hasher.Add(pack[index]);
}
hash = hasher.Hash;
if (_shaders.TryGetValue(hash, out List<ShaderEntry> list))
{
for (int index = 0; index < list.Count; index++)
{
ShaderEntry entry = list[index];
if (entry.CodeEquals(pack))
{
return entry.Shader;
}
}
}
return default;
}
public void Add(int hash, T shader, ShaderPack pack)
{
if (!_shaders.TryGetValue(hash, out List<ShaderEntry> list))
{
list = new List<ShaderEntry>();
_shaders.Add(hash, list);
}
byte[][] code = new byte[pack.Count][];
for (int index = 0; index < pack.Count; index++)
{
code[index] = pack[index].ToArray();
}
list.Add(new ShaderEntry(shader, code));
}
public IEnumerator<T> GetEnumerator()
{
foreach (var list in _shaders.Values)
{
foreach (ShaderEntry entry in list)
{
yield return entry.Shader;
}
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View file

@ -0,0 +1,78 @@
using System;
namespace Ryujinx.Graphics.Gpu.Shader
{
ref struct ShaderPack
{
private ReadOnlySpan<byte> _code0;
private ReadOnlySpan<byte> _code1;
private ReadOnlySpan<byte> _code2;
private ReadOnlySpan<byte> _code3;
private ReadOnlySpan<byte> _code4;
public int Count { get; private set; }
public ReadOnlySpan<byte> this[int index]
{
get
{
if ((uint)index > 4)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
if (index == 0)
{
return _code0;
}
else if (index == 1)
{
return _code1;
}
else if (index == 2)
{
return _code2;
}
else if (index == 3)
{
return _code3;
}
else /* if (index == 4) */
{
return _code4;
}
}
}
public void Add(ReadOnlySpan<byte> code)
{
if (Count >= 5)
{
throw new InvalidOperationException("Already full.");
}
int index = Count++;
if (index == 0)
{
_code0 = code;
}
else if (index == 1)
{
_code1 = code;
}
else if (index == 2)
{
_code2 = code;
}
else if (index == 3)
{
_code3 = code;
}
else /* if (index == 4) */
{
_code4 = code;
}
}
}
}

View file

@ -10,14 +10,11 @@ namespace Ryujinx.Graphics.Shader
public string Code { get; private set; }
public int Size { get; }
internal ShaderProgram(ShaderProgramInfo info, ShaderStage stage, string code, int size)
internal ShaderProgram(ShaderProgramInfo info, ShaderStage stage, string code)
{
Info = info;
Stage = stage;
Code = code;
Size = size;
}
public void Prepend(string line)

View file

@ -14,46 +14,22 @@ namespace Ryujinx.Graphics.Shader.Translation
{
private const int HeaderSize = 0x50;
public static ReadOnlySpan<byte> ExtractCode(ReadOnlySpan<byte> code, bool compute, out int headerSize)
{
headerSize = compute ? 0 : HeaderSize;
Block[] cfg = Decoder.Decode(code, (ulong)headerSize);
if (cfg == null)
{
return code;
}
ulong endAddress = 0;
foreach (Block block in cfg)
{
if (endAddress < block.EndAddress)
{
endAddress = block.EndAddress;
}
}
return code.Slice(0, headerSize + (int)endAddress);
}
public static ShaderProgram Translate(ReadOnlySpan<byte> code, TranslatorCallbacks callbacks, TranslationFlags flags)
{
Operation[] ops = DecodeShader(code, callbacks, flags, out ShaderConfig config, out int size);
Operation[] ops = DecodeShader(code, callbacks, flags, out ShaderConfig config);
return Translate(ops, config, size);
return Translate(ops, config);
}
public static ShaderProgram Translate(ReadOnlySpan<byte> vpACode, ReadOnlySpan<byte> vpBCode, TranslatorCallbacks callbacks, TranslationFlags flags)
{
Operation[] vpAOps = DecodeShader(vpACode, callbacks, flags, out _, out _);
Operation[] vpBOps = DecodeShader(vpBCode, callbacks, flags, out ShaderConfig config, out int sizeB);
Operation[] vpAOps = DecodeShader(vpACode, callbacks, flags, out _);
Operation[] vpBOps = DecodeShader(vpBCode, callbacks, flags, out ShaderConfig config);
return Translate(Combine(vpAOps, vpBOps), config, sizeB);
return Translate(Combine(vpAOps, vpBOps), config);
}
private static ShaderProgram Translate(Operation[] ops, ShaderConfig config, int size)
private static ShaderProgram Translate(Operation[] ops, ShaderConfig config)
{
BasicBlock[] blocks = ControlFlowGraph.MakeCfg(ops);
@ -82,17 +58,14 @@ namespace Ryujinx.Graphics.Shader.Translation
sInfo.InterpolationQualifiers,
sInfo.UsesInstanceId);
string glslCode = program.Code;
return new ShaderProgram(spInfo, config.Stage, glslCode, size);
return new ShaderProgram(spInfo, config.Stage, program.Code);
}
private static Operation[] DecodeShader(
ReadOnlySpan<byte> code,
TranslatorCallbacks callbacks,
TranslationFlags flags,
out ShaderConfig config,
out int size)
out ShaderConfig config)
{
Block[] cfg;
@ -113,24 +86,15 @@ namespace Ryujinx.Graphics.Shader.Translation
{
config.PrintLog("Invalid branch detected, failed to build CFG.");
size = 0;
return Array.Empty<Operation>();
}
EmitterContext context = new EmitterContext(config);
ulong maxEndAddress = 0;
for (int blkIndex = 0; blkIndex < cfg.Length; blkIndex++)
{
Block block = cfg[blkIndex];
if (maxEndAddress < block.EndAddress)
{
maxEndAddress = block.EndAddress;
}
context.CurrBlock = block;
context.MarkLabel(context.GetLabel(block.Address));
@ -215,8 +179,6 @@ namespace Ryujinx.Graphics.Shader.Translation
}
}
size = (int)maxEndAddress + (((flags & TranslationFlags.Compute) != 0) ? 0 : HeaderSize);
return context.GetOperations();
}