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 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 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 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 _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 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); } /// /// 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 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, 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 ShaderCodeAccessor(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; } } } } }