mirror of
https://github.com/GreemDev/Ryujinx
synced 2024-12-12 03:00:00 +01:00
280 lines
10 KiB
C#
280 lines
10 KiB
C#
|
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
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// Holds already cached code for a guest shader.
|
||
|
/// </summary>
|
||
|
struct CachedGraphicsGuestCode
|
||
|
{
|
||
|
public byte[] VertexACode;
|
||
|
public byte[] VertexBCode;
|
||
|
public byte[] TessControlCode;
|
||
|
public byte[] TessEvaluationCode;
|
||
|
public byte[] GeometryCode;
|
||
|
public byte[] FragmentCode;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the guest code of a shader stage by its index.
|
||
|
/// </summary>
|
||
|
/// <param name="stageIndex">Index of the shader stage</param>
|
||
|
/// <returns>Guest code, or null if not present</returns>
|
||
|
public byte[] GetByIndex(int stageIndex)
|
||
|
{
|
||
|
return stageIndex switch
|
||
|
{
|
||
|
1 => TessControlCode,
|
||
|
2 => TessEvaluationCode,
|
||
|
3 => GeometryCode,
|
||
|
4 => FragmentCode,
|
||
|
_ => VertexBCode
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Graphics shader cache hash table.
|
||
|
/// </summary>
|
||
|
class ShaderCacheHashTable
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// Shader ID cache.
|
||
|
/// </summary>
|
||
|
private struct IdCache
|
||
|
{
|
||
|
private PartitionedHashTable<int> _cache;
|
||
|
private int _id;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Initializes the state.
|
||
|
/// </summary>
|
||
|
public void Initialize()
|
||
|
{
|
||
|
_cache = new PartitionedHashTable<int>();
|
||
|
_id = 0;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Adds guest code to the cache.
|
||
|
/// </summary>
|
||
|
/// <remarks>
|
||
|
/// If the code was already cached, it will just return the existing ID.
|
||
|
/// </remarks>
|
||
|
/// <param name="code">Code to add</param>
|
||
|
/// <returns>Unique ID for the guest code</returns>
|
||
|
public int Add(byte[] code)
|
||
|
{
|
||
|
int id = ++_id;
|
||
|
int cachedId = _cache.GetOrAdd(code, id);
|
||
|
if (cachedId != id)
|
||
|
{
|
||
|
--_id;
|
||
|
}
|
||
|
|
||
|
return cachedId;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Tries to find cached guest code.
|
||
|
/// </summary>
|
||
|
/// <param name="dataAccessor">Code accessor used to read guest code to find a match on the hash table</param>
|
||
|
/// <param name="id">ID of the guest code, if found</param>
|
||
|
/// <param name="data">Cached guest code, if found</param>
|
||
|
/// <returns>True if found, false otherwise</returns>
|
||
|
public bool TryFind(IDataAccessor dataAccessor, out int id, out byte[] data)
|
||
|
{
|
||
|
return _cache.TryFindItem(dataAccessor, out id, out data);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Guest code IDs of the guest shaders that when combined forms a single host program.
|
||
|
/// </summary>
|
||
|
private struct IdTable : IEquatable<IdTable>
|
||
|
{
|
||
|
public int VertexAId;
|
||
|
public int VertexBId;
|
||
|
public int TessControlId;
|
||
|
public int TessEvaluationId;
|
||
|
public int GeometryId;
|
||
|
public int FragmentId;
|
||
|
|
||
|
public override bool Equals(object obj)
|
||
|
{
|
||
|
return obj is IdTable other && Equals(other);
|
||
|
}
|
||
|
|
||
|
public bool Equals(IdTable other)
|
||
|
{
|
||
|
return other.VertexAId == VertexAId &&
|
||
|
other.VertexBId == VertexBId &&
|
||
|
other.TessControlId == TessControlId &&
|
||
|
other.TessEvaluationId == TessEvaluationId &&
|
||
|
other.GeometryId == GeometryId &&
|
||
|
other.FragmentId == FragmentId;
|
||
|
}
|
||
|
|
||
|
public 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<IdTable, ShaderSpecializationList> _shaderPrograms;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Creates a new graphics shader cache hash table.
|
||
|
/// </summary>
|
||
|
public ShaderCacheHashTable()
|
||
|
{
|
||
|
_vertexACache.Initialize();
|
||
|
_vertexBCache.Initialize();
|
||
|
_tessControlCache.Initialize();
|
||
|
_tessEvaluationCache.Initialize();
|
||
|
_geometryCache.Initialize();
|
||
|
_fragmentCache.Initialize();
|
||
|
|
||
|
_shaderPrograms = new Dictionary<IdTable, ShaderSpecializationList>();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Adds a program to the cache.
|
||
|
/// </summary>
|
||
|
/// <param name="program">Program to be added</param>
|
||
|
public void Add(CachedShaderProgram program)
|
||
|
{
|
||
|
IdTable idTable = new IdTable();
|
||
|
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Tries to find a cached program.
|
||
|
/// </summary>
|
||
|
/// <remarks>
|
||
|
/// Even if false is returned, <paramref name="guestCode"/> might still contain cached guest code.
|
||
|
/// This can be used to avoid additional allocations for guest code that was already cached.
|
||
|
/// </remarks>
|
||
|
/// <param name="channel">GPU channel</param>
|
||
|
/// <param name="poolState">Texture pool state</param>
|
||
|
/// <param name="addresses">Guest addresses of the shaders to find</param>
|
||
|
/// <param name="program">Cached host program for the given state, if found</param>
|
||
|
/// <param name="guestCode">Cached guest code, if any found</param>
|
||
|
/// <returns>True if a cached host program was found, false otherwise</returns>
|
||
|
public bool TryFind(
|
||
|
GpuChannel channel,
|
||
|
GpuChannelPoolState poolState,
|
||
|
ShaderAddresses addresses,
|
||
|
out CachedShaderProgram program,
|
||
|
out CachedGraphicsGuestCode guestCode)
|
||
|
{
|
||
|
var memoryManager = channel.MemoryManager;
|
||
|
IdTable idTable = new IdTable();
|
||
|
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, poolState, out program);
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Tries to get the ID of a single cached shader stage.
|
||
|
/// </summary>
|
||
|
/// <param name="idCache">ID cache of the stage</param>
|
||
|
/// <param name="memoryManager">GPU memory manager</param>
|
||
|
/// <param name="baseAddress">Base address of the shader</param>
|
||
|
/// <param name="id">ID, if found</param>
|
||
|
/// <param name="data">Cached guest code, if found</param>
|
||
|
/// <returns>True if a cached shader is found, false otherwise</returns>
|
||
|
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 ShaderCodeAccessor(memoryManager, baseAddress);
|
||
|
return idCache.TryFind(codeAccessor, out id, out data);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets all programs that have been added to the table.
|
||
|
/// </summary>
|
||
|
/// <returns>Programs added to the table</returns>
|
||
|
public IEnumerable<CachedShaderProgram> GetPrograms()
|
||
|
{
|
||
|
foreach (var specList in _shaderPrograms.Values)
|
||
|
{
|
||
|
foreach (var program in specList)
|
||
|
{
|
||
|
yield return program;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|