using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Gpu.Shader.HashTable;
using Ryujinx.Graphics.Shader;
using System;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Gpu.Shader
{
///
/// Holds already cached code for a guest shader.
///
struct CachedGraphicsGuestCode
{
public byte[] VertexACode;
public byte[] VertexBCode;
public byte[] TessControlCode;
public byte[] TessEvaluationCode;
public byte[] GeometryCode;
public byte[] FragmentCode;
///
/// Gets the guest code of a shader stage by its index.
///
/// Index of the shader stage
/// Guest code, or null if not present
public readonly byte[] GetByIndex(int stageIndex)
{
return stageIndex switch
{
1 => TessControlCode,
2 => TessEvaluationCode,
3 => GeometryCode,
4 => FragmentCode,
_ => VertexBCode,
};
}
}
///
/// Graphics shader cache hash table.
///
class ShaderCacheHashTable
{
///
/// Shader ID cache.
///
private struct IdCache
{
private PartitionedHashTable _cache;
private int _id;
///
/// Initializes the state.
///
public void Initialize()
{
_cache = new PartitionedHashTable();
_id = 0;
}
///
/// Adds guest code to the cache.
///
///
/// If the code was already cached, it will just return the existing ID.
///
/// Code to add
/// Unique ID for the guest code
public int Add(byte[] code)
{
int id = ++_id;
int cachedId = _cache.GetOrAdd(code, id);
if (cachedId != id)
{
--_id;
}
return cachedId;
}
///
/// Tries to find cached guest code.
///
/// Code accessor used to read guest code to find a match on the hash table
/// ID of the guest code, if found
/// Cached guest code, if found
/// True if found, false otherwise
public readonly bool TryFind(IDataAccessor dataAccessor, out int id, out byte[] data)
{
return _cache.TryFindItem(dataAccessor, out id, out data);
}
}
///
/// Guest code IDs of the guest shaders that when combined forms a single host program.
///
private struct IdTable : IEquatable
{
public int VertexAId;
public int VertexBId;
public int TessControlId;
public int TessEvaluationId;
public int GeometryId;
public int FragmentId;
public readonly override bool Equals(object obj)
{
return obj is IdTable other && Equals(other);
}
public readonly bool Equals(IdTable other)
{
return other.VertexAId == VertexAId &&
other.VertexBId == VertexBId &&
other.TessControlId == TessControlId &&
other.TessEvaluationId == TessEvaluationId &&
other.GeometryId == GeometryId &&
other.FragmentId == FragmentId;
}
public readonly override int GetHashCode()
{
return HashCode.Combine(VertexAId, VertexBId, TessControlId, TessEvaluationId, GeometryId, FragmentId);
}
}
private IdCache _vertexACache;
private IdCache _vertexBCache;
private IdCache _tessControlCache;
private IdCache _tessEvaluationCache;
private IdCache _geometryCache;
private IdCache _fragmentCache;
private readonly Dictionary _shaderPrograms;
///
/// Creates a new graphics shader cache hash table.
///
public ShaderCacheHashTable()
{
_vertexACache.Initialize();
_vertexBCache.Initialize();
_tessControlCache.Initialize();
_tessEvaluationCache.Initialize();
_geometryCache.Initialize();
_fragmentCache.Initialize();
_shaderPrograms = new Dictionary();
}
///
/// Adds a program to the cache.
///
/// Program to be added
public void Add(CachedShaderProgram program)
{
IdTable idTable = new();
foreach (var shader in program.Shaders)
{
if (shader == null)
{
continue;
}
if (shader.Info != null)
{
switch (shader.Info.Stage)
{
case ShaderStage.Vertex:
idTable.VertexBId = _vertexBCache.Add(shader.Code);
break;
case ShaderStage.TessellationControl:
idTable.TessControlId = _tessControlCache.Add(shader.Code);
break;
case ShaderStage.TessellationEvaluation:
idTable.TessEvaluationId = _tessEvaluationCache.Add(shader.Code);
break;
case ShaderStage.Geometry:
idTable.GeometryId = _geometryCache.Add(shader.Code);
break;
case ShaderStage.Fragment:
idTable.FragmentId = _fragmentCache.Add(shader.Code);
break;
}
}
else
{
idTable.VertexAId = _vertexACache.Add(shader.Code);
}
}
if (!_shaderPrograms.TryGetValue(idTable, out ShaderSpecializationList specList))
{
specList = new ShaderSpecializationList();
_shaderPrograms.Add(idTable, specList);
}
specList.Add(program);
}
///
/// Tries to find a cached program.
///
///
/// Even if false is returned, might still contain cached guest code.
/// This can be used to avoid additional allocations for guest code that was already cached.
///
/// GPU channel
/// Texture pool state
/// Graphics state
/// Guest addresses of the shaders to find
/// Cached host program for the given state, if found
/// Cached guest code, if any found
/// True if a cached host program was found, false otherwise
public bool TryFind(
GpuChannel channel,
ref GpuChannelPoolState poolState,
ref GpuChannelGraphicsState graphicsState,
ShaderAddresses addresses,
out CachedShaderProgram program,
out CachedGraphicsGuestCode guestCode)
{
var memoryManager = channel.MemoryManager;
IdTable idTable = new();
guestCode = new CachedGraphicsGuestCode();
program = null;
bool found = TryGetId(_vertexACache, memoryManager, addresses.VertexA, out idTable.VertexAId, out guestCode.VertexACode);
found &= TryGetId(_vertexBCache, memoryManager, addresses.VertexB, out idTable.VertexBId, out guestCode.VertexBCode);
found &= TryGetId(_tessControlCache, memoryManager, addresses.TessControl, out idTable.TessControlId, out guestCode.TessControlCode);
found &= TryGetId(_tessEvaluationCache, memoryManager, addresses.TessEvaluation, out idTable.TessEvaluationId, out guestCode.TessEvaluationCode);
found &= TryGetId(_geometryCache, memoryManager, addresses.Geometry, out idTable.GeometryId, out guestCode.GeometryCode);
found &= TryGetId(_fragmentCache, memoryManager, addresses.Fragment, out idTable.FragmentId, out guestCode.FragmentCode);
if (found && _shaderPrograms.TryGetValue(idTable, out ShaderSpecializationList specList))
{
return specList.TryFindForGraphics(channel, ref poolState, ref graphicsState, out program);
}
return false;
}
///
/// Tries to get the ID of a single cached shader stage.
///
/// ID cache of the stage
/// GPU memory manager
/// Base address of the shader
/// ID, if found
/// Cached guest code, if found
/// True if a cached shader is found, false otherwise
private static bool TryGetId(IdCache idCache, MemoryManager memoryManager, ulong baseAddress, out int id, out byte[] data)
{
if (baseAddress == 0)
{
id = 0;
data = null;
return true;
}
ShaderCodeAccessor codeAccessor = new(memoryManager, baseAddress);
return idCache.TryFind(codeAccessor, out id, out data);
}
///
/// Gets all programs that have been added to the table.
///
/// Programs added to the table
public IEnumerable GetPrograms()
{
foreach (var specList in _shaderPrograms.Values)
{
foreach (var program in specList)
{
yield return program;
}
}
}
}
}