using Ryujinx.Common; using Ryujinx.Common.Logging; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Gpu.Engine.Threed; using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.Graphics.Gpu.Shader.Cache; using Ryujinx.Graphics.Gpu.Shader.Cache.Definition; using Ryujinx.Graphics.Shader; using Ryujinx.Graphics.Shader.Translation; using System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; namespace Ryujinx.Graphics.Gpu.Shader { /// /// Memory cache of shader code. /// class ShaderCache : IDisposable { private const TranslationFlags DefaultFlags = TranslationFlags.DebugMode; private readonly GpuContext _context; private readonly ShaderDumper _dumper; private readonly Dictionary> _cpPrograms; private readonly Dictionary> _gpPrograms; private CacheManager _cacheManager; private Dictionary _gpProgramsDiskCache; private Dictionary _cpProgramsDiskCache; private Queue<(IProgram, Action)> _programsToSaveQueue; /// /// Version of the codegen (to be changed when codegen or guest format change). /// private const ulong ShaderCodeGenVersion = 3251; // Progress reporting helpers private volatile int _shaderCount; private volatile int _totalShaderCount; public event Action ShaderCacheStateChanged; /// /// Creates a new instance of the shader cache. /// /// GPU context that the shader cache belongs to public ShaderCache(GpuContext context) { _context = context; _dumper = new ShaderDumper(); _cpPrograms = new Dictionary>(); _gpPrograms = new Dictionary>(); _gpProgramsDiskCache = new Dictionary(); _cpProgramsDiskCache = new Dictionary(); _programsToSaveQueue = new Queue<(IProgram, Action)>(); } /// /// Processes the queue of shaders that must save their binaries to the disk cache. /// public void ProcessShaderCacheQueue() { // Check to see if the binaries for previously compiled shaders are ready, and save them out. while (_programsToSaveQueue.Count > 0) { (IProgram program, Action dataAction) = _programsToSaveQueue.Peek(); if (program.CheckProgramLink(false) != ProgramLinkStatus.Incomplete) { dataAction(program.GetBinary()); _programsToSaveQueue.Dequeue(); } else { break; } } } /// /// Initialize the cache. /// internal void Initialize() { if (GraphicsConfig.EnableShaderCache && GraphicsConfig.TitleId != null) { _cacheManager = new CacheManager(CacheGraphicsApi.OpenGL, CacheHashType.XxHash128, "glsl", GraphicsConfig.TitleId, ShaderCodeGenVersion); bool isReadOnly = _cacheManager.IsReadOnly; HashSet invalidEntries = null; if (isReadOnly) { Logger.Warning?.Print(LogClass.Gpu, "Loading shader cache in read-only mode (cache in use by another program!)"); } else { invalidEntries = new HashSet(); } ReadOnlySpan guestProgramList = _cacheManager.GetGuestProgramList(); using AutoResetEvent progressReportEvent = new AutoResetEvent(false); _shaderCount = 0; _totalShaderCount = guestProgramList.Length; ShaderCacheStateChanged?.Invoke(ShaderCacheState.Start, _shaderCount, _totalShaderCount); Thread progressReportThread = null; if (guestProgramList.Length > 0) { progressReportThread = new Thread(ReportProgress) { Name = "ShaderCache.ProgressReporter", Priority = ThreadPriority.Lowest, IsBackground = true }; progressReportThread.Start(progressReportEvent); } // Make sure these are initialized before doing compilation. Capabilities caps = _context.Capabilities; int maxTaskCount = Math.Min(Environment.ProcessorCount, 8); int programIndex = 0; List activeTasks = new List(); using AutoResetEvent taskDoneEvent = new AutoResetEvent(false); // This thread dispatches tasks to do shader translation, and creates programs that OpenGL will link in the background. // The program link status is checked in a non-blocking manner so that multiple shaders can be compiled at once. while (programIndex < guestProgramList.Length || activeTasks.Count > 0) { if (activeTasks.Count < maxTaskCount && programIndex < guestProgramList.Length) { // Begin a new shader compilation. Hash128 key = guestProgramList[programIndex]; byte[] hostProgramBinary = _cacheManager.GetHostProgramByHash(ref key); bool hasHostCache = hostProgramBinary != null; IProgram hostProgram = null; // If the program sources aren't in the cache, compile from saved guest program. byte[] guestProgram = _cacheManager.GetGuestProgramByHash(ref key); if (guestProgram == null) { Logger.Error?.Print(LogClass.Gpu, $"Ignoring orphan shader hash {key} in cache (is the cache incomplete?)"); // Should not happen, but if someone messed with the cache it's better to catch it. invalidEntries?.Add(key); _shaderCount = ++programIndex; continue; } ReadOnlySpan guestProgramReadOnlySpan = guestProgram; ReadOnlySpan cachedShaderEntries = GuestShaderCacheEntry.Parse(ref guestProgramReadOnlySpan, out GuestShaderCacheHeader fileHeader); if (cachedShaderEntries[0].Header.Stage == ShaderStage.Compute) { Debug.Assert(cachedShaderEntries.Length == 1); GuestShaderCacheEntry entry = cachedShaderEntries[0]; HostShaderCacheEntry[] hostShaderEntries = null; // Try loading host shader binary. if (hasHostCache) { hostShaderEntries = HostShaderCacheEntry.Parse(hostProgramBinary, out ReadOnlySpan hostProgramBinarySpan); hostProgramBinary = hostProgramBinarySpan.ToArray(); hostProgram = _context.Renderer.LoadProgramBinary(hostProgramBinary, false, new ShaderInfo(-1)); } ShaderCompileTask task = new ShaderCompileTask(taskDoneEvent); activeTasks.Add(task); task.OnCompiled(hostProgram, (bool isHostProgramValid, ShaderCompileTask task) => { ShaderProgram program = null; ShaderProgramInfo shaderProgramInfo = null; if (isHostProgramValid) { // Reconstruct code holder. program = new ShaderProgram(entry.Header.Stage, ""); shaderProgramInfo = hostShaderEntries[0].ToShaderProgramInfo(); byte[] code = entry.Code.AsSpan(0, entry.Header.Size - entry.Header.Cb1DataSize).ToArray(); ShaderCodeHolder shader = new ShaderCodeHolder(program, shaderProgramInfo, code); _cpProgramsDiskCache.Add(key, new ShaderBundle(hostProgram, shader)); return true; } else { // If the host program was rejected by the gpu driver or isn't in cache, try to build from program sources again. Task compileTask = Task.Run(() => { var binaryCode = new Memory(entry.Code); var gpuAccessor = new CachedGpuAccessor( _context, binaryCode, binaryCode.Slice(binaryCode.Length - entry.Header.Cb1DataSize), entry.Header.GpuAccessorHeader, entry.TextureDescriptors, null); var options = new TranslationOptions(TargetLanguage.Glsl, TargetApi.OpenGL, DefaultFlags | TranslationFlags.Compute); program = Translator.CreateContext(0, gpuAccessor, options).Translate(out shaderProgramInfo); }); task.OnTask(compileTask, (bool _, ShaderCompileTask task) => { if (task.IsFaulted) { Logger.Warning?.Print(LogClass.Gpu, $"Host shader {key} is corrupted or incompatible, discarding..."); _cacheManager.RemoveProgram(ref key); return true; // Exit early, the decoding step failed. } byte[] code = entry.Code.AsSpan(0, entry.Header.Size - entry.Header.Cb1DataSize).ToArray(); ShaderCodeHolder shader = new ShaderCodeHolder(program, shaderProgramInfo, code); Logger.Info?.Print(LogClass.Gpu, $"Host shader {key} got invalidated, rebuilding from guest..."); // Compile shader and create program as the shader program binary got invalidated. shader.HostShader = _context.Renderer.CompileShader(ShaderStage.Compute, program.Code); hostProgram = _context.Renderer.CreateProgram(new IShader[] { shader.HostShader }, new ShaderInfo(-1)); task.OnCompiled(hostProgram, (bool isNewProgramValid, ShaderCompileTask task) => { // As the host program was invalidated, save the new entry in the cache. hostProgramBinary = HostShaderCacheEntry.Create(hostProgram.GetBinary(), new ShaderCodeHolder[] { shader }); if (!isReadOnly) { if (hasHostCache) { _cacheManager.ReplaceHostProgram(ref key, hostProgramBinary); } else { Logger.Warning?.Print(LogClass.Gpu, $"Add missing host shader {key} in cache (is the cache incomplete?)"); _cacheManager.AddHostProgram(ref key, hostProgramBinary); } } _cpProgramsDiskCache.Add(key, new ShaderBundle(hostProgram, shader)); return true; }); return false; // Not finished: still need to compile the host program. }); return false; // Not finished: translating the program. } }); } else { Debug.Assert(cachedShaderEntries.Length == Constants.ShaderStages); ShaderCodeHolder[] shaders = new ShaderCodeHolder[cachedShaderEntries.Length]; List shaderPrograms = new List(); TransformFeedbackDescriptor[] tfd = CacheHelper.ReadTransformFeedbackInformation(ref guestProgramReadOnlySpan, fileHeader); TranslationCounts counts = new TranslationCounts(); HostShaderCacheEntry[] hostShaderEntries = null; // Try loading host shader binary. if (hasHostCache) { hostShaderEntries = HostShaderCacheEntry.Parse(hostProgramBinary, out ReadOnlySpan hostProgramBinarySpan); hostProgramBinary = hostProgramBinarySpan.ToArray(); bool hasFragmentShader = false; int fragmentOutputMap = -1; int fragmentIndex = (int)ShaderStage.Fragment - 1; if (hostShaderEntries[fragmentIndex] != null && hostShaderEntries[fragmentIndex].Header.InUse) { hasFragmentShader = true; fragmentOutputMap = hostShaderEntries[fragmentIndex].Header.FragmentOutputMap; } hostProgram = _context.Renderer.LoadProgramBinary(hostProgramBinary, hasFragmentShader, new ShaderInfo(fragmentOutputMap)); } ShaderCompileTask task = new ShaderCompileTask(taskDoneEvent); activeTasks.Add(task); GuestShaderCacheEntry[] entries = cachedShaderEntries.ToArray(); task.OnCompiled(hostProgram, (bool isHostProgramValid, ShaderCompileTask task) => { Task compileTask = Task.Run(() => { TranslatorContext[] shaderContexts = null; if (!isHostProgramValid) { shaderContexts = new TranslatorContext[1 + entries.Length]; for (int i = 0; i < entries.Length; i++) { GuestShaderCacheEntry entry = entries[i]; if (entry == null) { continue; } var binaryCode = new Memory(entry.Code); var gpuAccessor = new CachedGpuAccessor( _context, binaryCode, binaryCode.Slice(binaryCode.Length - entry.Header.Cb1DataSize), entry.Header.GpuAccessorHeader, entry.TextureDescriptors, tfd); var options = new TranslationOptions(TargetLanguage.Glsl, TargetApi.OpenGL, DefaultFlags); shaderContexts[i + 1] = Translator.CreateContext(0, gpuAccessor, options, counts); if (entry.Header.SizeA != 0) { var options2 = new TranslationOptions(TargetLanguage.Glsl, TargetApi.OpenGL, DefaultFlags | TranslationFlags.VertexA); shaderContexts[0] = Translator.CreateContext((ulong)entry.Header.Size, gpuAccessor, options2, counts); } } } // Reconstruct code holder. for (int i = 0; i < entries.Length; i++) { GuestShaderCacheEntry entry = entries[i]; if (entry == null) { continue; } ShaderProgram program; ShaderProgramInfo shaderProgramInfo; if (isHostProgramValid) { program = new ShaderProgram(entry.Header.Stage, ""); shaderProgramInfo = hostShaderEntries[i].ToShaderProgramInfo(); } else { int stageIndex = i + 1; TranslatorContext currentStage = shaderContexts[stageIndex]; TranslatorContext nextStage = GetNextStageContext(shaderContexts, stageIndex); TranslatorContext vertexA = stageIndex == 1 ? shaderContexts[0] : null; program = currentStage.Translate(out shaderProgramInfo, nextStage, vertexA); } // NOTE: Vertex B comes first in the shader cache. byte[] code = entry.Code.AsSpan(0, entry.Header.Size - entry.Header.Cb1DataSize).ToArray(); byte[] code2 = entry.Header.SizeA != 0 ? entry.Code.AsSpan(entry.Header.Size, entry.Header.SizeA).ToArray() : null; shaders[i] = new ShaderCodeHolder(program, shaderProgramInfo, code, code2); shaderPrograms.Add(program); } }); task.OnTask(compileTask, (bool _, ShaderCompileTask task) => { if (task.IsFaulted) { Logger.Warning?.Print(LogClass.Gpu, $"Host shader {key} is corrupted or incompatible, discarding..."); _cacheManager.RemoveProgram(ref key); return true; // Exit early, the decoding step failed. } // If the host program was rejected by the gpu driver or isn't in cache, try to build from program sources again. if (!isHostProgramValid) { Logger.Info?.Print(LogClass.Gpu, $"Host shader {key} got invalidated, rebuilding from guest..."); List hostShaders = new List(); // Compile shaders and create program as the shader program binary got invalidated. for (int stage = 0; stage < Constants.ShaderStages; stage++) { ShaderProgram program = shaders[stage]?.Program; if (program == null) { continue; } IShader hostShader = _context.Renderer.CompileShader(program.Stage, program.Code); shaders[stage].HostShader = hostShader; hostShaders.Add(hostShader); } int fragmentIndex = (int)ShaderStage.Fragment - 1; int fragmentOutputMap = -1; if (shaders[fragmentIndex] != null) { fragmentOutputMap = shaders[fragmentIndex].Info.FragmentOutputMap; } hostProgram = _context.Renderer.CreateProgram(hostShaders.ToArray(), new ShaderInfo(fragmentOutputMap)); task.OnCompiled(hostProgram, (bool isNewProgramValid, ShaderCompileTask task) => { // As the host program was invalidated, save the new entry in the cache. hostProgramBinary = HostShaderCacheEntry.Create(hostProgram.GetBinary(), shaders); if (!isReadOnly) { if (hasHostCache) { _cacheManager.ReplaceHostProgram(ref key, hostProgramBinary); } else { Logger.Warning?.Print(LogClass.Gpu, $"Add missing host shader {key} in cache (is the cache incomplete?)"); _cacheManager.AddHostProgram(ref key, hostProgramBinary); } } _gpProgramsDiskCache.Add(key, new ShaderBundle(hostProgram, shaders)); return true; }); return false; // Not finished: still need to compile the host program. } else { _gpProgramsDiskCache.Add(key, new ShaderBundle(hostProgram, shaders)); return true; } }); return false; // Not finished: translating the program. }); } _shaderCount = ++programIndex; } // Process the queue. for (int i = 0; i < activeTasks.Count; i++) { ShaderCompileTask task = activeTasks[i]; if (task.IsDone()) { activeTasks.RemoveAt(i--); } } if (activeTasks.Count == maxTaskCount) { // Wait for a task to be done, or for 1ms. // Host shader compilation cannot signal when it is done, // so the 1ms timeout is required to poll status. taskDoneEvent.WaitOne(1); } } if (!isReadOnly) { // Remove entries that are broken in the cache _cacheManager.RemoveManifestEntries(invalidEntries); _cacheManager.FlushToArchive(); _cacheManager.Synchronize(); } progressReportEvent.Set(); progressReportThread?.Join(); ShaderCacheStateChanged?.Invoke(ShaderCacheState.Loaded, _shaderCount, _totalShaderCount); Logger.Info?.Print(LogClass.Gpu, $"Shader cache loaded {_shaderCount} entries."); } } /// /// Raises ShaderCacheStateChanged events periodically. /// private void ReportProgress(object state) { const int refreshRate = 50; // ms AutoResetEvent endEvent = (AutoResetEvent)state; int count = 0; do { int newCount = _shaderCount; if (count != newCount) { ShaderCacheStateChanged?.Invoke(ShaderCacheState.Loading, newCount, _totalShaderCount); count = newCount; } } while (!endEvent.WaitOne(refreshRate)); } /// /// Gets a compute shader from the cache. /// /// /// This automatically translates, compiles and adds the code to the cache if not present. /// /// GPU channel /// GPU accessor state /// GPU virtual address of the binary shader code /// Local group size X of the computer shader /// Local group size Y of the computer shader /// Local group size Z of the computer shader /// Local memory size of the compute shader /// Shared memory size of the compute shader /// Compiled compute shader code public ShaderBundle GetComputeShader( GpuChannel channel, GpuAccessorState gas, ulong gpuVa, int localSizeX, int localSizeY, int localSizeZ, int localMemorySize, int sharedMemorySize) { bool isCached = _cpPrograms.TryGetValue(gpuVa, out List list); if (isCached) { foreach (ShaderBundle cachedCpShader in list) { if (IsShaderEqual(channel.MemoryManager, cachedCpShader, gpuVa)) { return cachedCpShader; } } } TranslatorContext[] shaderContexts = new TranslatorContext[1]; shaderContexts[0] = DecodeComputeShader( channel, gas, gpuVa, localSizeX, localSizeY, localSizeZ, localMemorySize, sharedMemorySize); bool isShaderCacheEnabled = _cacheManager != null; bool isShaderCacheReadOnly = false; Hash128 programCodeHash = default; GuestShaderCacheEntry[] shaderCacheEntries = null; // Current shader cache doesn't support bindless textures if (shaderContexts[0].UsedFeatures.HasFlag(FeatureFlags.Bindless)) { isShaderCacheEnabled = false; } if (isShaderCacheEnabled) { isShaderCacheReadOnly = _cacheManager.IsReadOnly; // Compute hash and prepare data for shader disk cache comparison. shaderCacheEntries = CacheHelper.CreateShaderCacheEntries(channel, shaderContexts); programCodeHash = CacheHelper.ComputeGuestHashFromCache(shaderCacheEntries); } ShaderBundle cpShader; // Search for the program hash in loaded shaders. if (!isShaderCacheEnabled || !_cpProgramsDiskCache.TryGetValue(programCodeHash, out cpShader)) { if (isShaderCacheEnabled) { Logger.Debug?.Print(LogClass.Gpu, $"Shader {programCodeHash} not in cache, compiling!"); } // The shader isn't currently cached, translate it and compile it. ShaderCodeHolder shader = TranslateShader(_dumper, channel.MemoryManager, shaderContexts[0], null, null); shader.HostShader = _context.Renderer.CompileShader(ShaderStage.Compute, shader.Program.Code); IProgram hostProgram = _context.Renderer.CreateProgram(new IShader[] { shader.HostShader }, new ShaderInfo(-1)); cpShader = new ShaderBundle(hostProgram, shader); if (isShaderCacheEnabled) { _cpProgramsDiskCache.Add(programCodeHash, cpShader); if (!isShaderCacheReadOnly) { byte[] guestProgramDump = CacheHelper.CreateGuestProgramDump(shaderCacheEntries); _programsToSaveQueue.Enqueue((hostProgram, (byte[] hostProgramBinary) => { _cacheManager.SaveProgram(ref programCodeHash, guestProgramDump, HostShaderCacheEntry.Create(hostProgramBinary, new ShaderCodeHolder[] { shader })); })); } } } if (!isCached) { list = new List(); _cpPrograms.Add(gpuVa, list); } list.Add(cpShader); return cpShader; } /// /// Gets a graphics shader program from the shader cache. /// This includes all the specified shader stages. /// /// /// This automatically translates, compiles and adds the code to the cache if not present. /// /// GPU state /// GPU channel /// GPU accessor state /// Addresses of the shaders for each stage /// Compiled graphics shader code public ShaderBundle GetGraphicsShader(ref ThreedClassState state, GpuChannel channel, GpuAccessorState gas, ShaderAddresses addresses) { bool isCached = _gpPrograms.TryGetValue(addresses, out List list); if (isCached) { foreach (ShaderBundle cachedGpShaders in list) { if (IsShaderEqual(channel.MemoryManager, cachedGpShaders, addresses)) { return cachedGpShaders; } } } TranslatorContext[] shaderContexts = new TranslatorContext[Constants.ShaderStages + 1]; TransformFeedbackDescriptor[] tfd = GetTransformFeedbackDescriptors(ref state); gas.TransformFeedbackDescriptors = tfd; TranslationCounts counts = new TranslationCounts(); if (addresses.VertexA != 0) { shaderContexts[0] = DecodeGraphicsShader(channel, gas, counts, DefaultFlags | TranslationFlags.VertexA, ShaderStage.Vertex, addresses.VertexA); } shaderContexts[1] = DecodeGraphicsShader(channel, gas, counts, DefaultFlags, ShaderStage.Vertex, addresses.Vertex); shaderContexts[2] = DecodeGraphicsShader(channel, gas, counts, DefaultFlags, ShaderStage.TessellationControl, addresses.TessControl); shaderContexts[3] = DecodeGraphicsShader(channel, gas, counts, DefaultFlags, ShaderStage.TessellationEvaluation, addresses.TessEvaluation); shaderContexts[4] = DecodeGraphicsShader(channel, gas, counts, DefaultFlags, ShaderStage.Geometry, addresses.Geometry); shaderContexts[5] = DecodeGraphicsShader(channel, gas, counts, DefaultFlags, ShaderStage.Fragment, addresses.Fragment); bool isShaderCacheEnabled = _cacheManager != null; bool isShaderCacheReadOnly = false; Hash128 programCodeHash = default; GuestShaderCacheEntry[] shaderCacheEntries = null; // Current shader cache doesn't support bindless textures for (int i = 0; i < shaderContexts.Length; i++) { if (shaderContexts[i] != null && shaderContexts[i].UsedFeatures.HasFlag(FeatureFlags.Bindless)) { isShaderCacheEnabled = false; break; } } if (isShaderCacheEnabled) { isShaderCacheReadOnly = _cacheManager.IsReadOnly; // Compute hash and prepare data for shader disk cache comparison. shaderCacheEntries = CacheHelper.CreateShaderCacheEntries(channel, shaderContexts); programCodeHash = CacheHelper.ComputeGuestHashFromCache(shaderCacheEntries, tfd); } ShaderBundle gpShaders; // Search for the program hash in loaded shaders. if (!isShaderCacheEnabled || !_gpProgramsDiskCache.TryGetValue(programCodeHash, out gpShaders)) { if (isShaderCacheEnabled) { Logger.Debug?.Print(LogClass.Gpu, $"Shader {programCodeHash} not in cache, compiling!"); } // The shader isn't currently cached, translate it and compile it. ShaderCodeHolder[] shaders = new ShaderCodeHolder[Constants.ShaderStages]; for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++) { shaders[stageIndex] = TranslateShader(_dumper, channel.MemoryManager, shaderContexts, stageIndex + 1); } List hostShaders = new List(); for (int stage = 0; stage < Constants.ShaderStages; stage++) { ShaderProgram program = shaders[stage]?.Program; if (program == null) { continue; } IShader hostShader = _context.Renderer.CompileShader(program.Stage, program.Code); shaders[stage].HostShader = hostShader; hostShaders.Add(hostShader); } int fragmentIndex = (int)ShaderStage.Fragment - 1; int fragmentOutputMap = -1; if (shaders[fragmentIndex] != null) { fragmentOutputMap = shaders[fragmentIndex].Info.FragmentOutputMap; } IProgram hostProgram = _context.Renderer.CreateProgram(hostShaders.ToArray(), new ShaderInfo(fragmentOutputMap)); gpShaders = new ShaderBundle(hostProgram, shaders); if (isShaderCacheEnabled) { _gpProgramsDiskCache.Add(programCodeHash, gpShaders); if (!isShaderCacheReadOnly) { byte[] guestProgramDump = CacheHelper.CreateGuestProgramDump(shaderCacheEntries, tfd); _programsToSaveQueue.Enqueue((hostProgram, (byte[] hostProgramBinary) => { _cacheManager.SaveProgram(ref programCodeHash, guestProgramDump, HostShaderCacheEntry.Create(hostProgramBinary, shaders)); })); } } } if (!isCached) { list = new List(); _gpPrograms.Add(addresses, list); } list.Add(gpShaders); return gpShaders; } /// /// Gets transform feedback state from the current GPU state. /// /// Current GPU state /// Four transform feedback descriptors for the enabled TFBs, or null if TFB is disabled private static TransformFeedbackDescriptor[] GetTransformFeedbackDescriptors(ref ThreedClassState state) { bool tfEnable = state.TfEnable; if (!tfEnable) { return null; } TransformFeedbackDescriptor[] descs = new TransformFeedbackDescriptor[Constants.TotalTransformFeedbackBuffers]; for (int i = 0; i < Constants.TotalTransformFeedbackBuffers; i++) { var tf = state.TfState[i]; int length = (int)Math.Min((uint)tf.VaryingsCount, 0x80); var varyingLocations = MemoryMarshal.Cast(state.TfVaryingLocations[i].ToSpan()).Slice(0, length); descs[i] = new TransformFeedbackDescriptor(tf.BufferIndex, tf.Stride, varyingLocations.ToArray()); } return descs; } /// /// Checks if compute shader code in memory is equal to the cached shader. /// /// Memory manager used to access the GPU memory where the shader is located /// Cached compute shader /// GPU virtual address of the shader code in memory /// True if the code is different, false otherwise private static bool IsShaderEqual(MemoryManager memoryManager, ShaderBundle cpShader, ulong gpuVa) { return IsShaderEqual(memoryManager, cpShader.Shaders[0], gpuVa); } /// /// Checks if graphics shader code from all stages in memory are equal to the cached shaders. /// /// Memory manager used to access the GPU memory where the shader is located /// Cached graphics shaders /// GPU virtual addresses of all enabled shader stages /// True if the code is different, false otherwise private static bool IsShaderEqual(MemoryManager memoryManager, ShaderBundle gpShaders, ShaderAddresses addresses) { for (int stage = 0; stage < gpShaders.Shaders.Length; stage++) { ShaderCodeHolder 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 (!IsShaderEqual(memoryManager, shader, gpuVa, addresses.VertexA)) { return false; } } return true; } /// /// Checks if the code of the specified cached shader is different from the code in memory. /// /// Memory manager used to access the GPU memory where the shader is located /// Cached shader to compare with /// GPU virtual address of the binary shader code /// Optional GPU virtual address of the "Vertex A" binary shader code /// True if the code is different, false otherwise private static bool IsShaderEqual(MemoryManager memoryManager, ShaderCodeHolder shader, ulong gpuVa, ulong gpuVaA = 0) { if (shader == null) { return true; } ReadOnlySpan memoryCode = memoryManager.GetSpan(gpuVa, shader.Code.Length); bool equals = memoryCode.SequenceEqual(shader.Code); if (equals && shader.Code2 != null) { memoryCode = memoryManager.GetSpan(gpuVaA, shader.Code2.Length); equals = memoryCode.SequenceEqual(shader.Code2); } return equals; } /// /// Decode the binary Maxwell shader code to a translator context. /// /// GPU channel /// GPU accessor state /// GPU virtual address of the binary shader code /// Local group size X of the computer shader /// Local group size Y of the computer shader /// Local group size Z of the computer shader /// Local memory size of the compute shader /// Shared memory size of the compute shader /// The generated translator context private TranslatorContext DecodeComputeShader( GpuChannel channel, GpuAccessorState gas, ulong gpuVa, int localSizeX, int localSizeY, int localSizeZ, int localMemorySize, int sharedMemorySize) { if (gpuVa == 0) { return null; } GpuAccessor gpuAccessor = new GpuAccessor(_context, channel, gas, localSizeX, localSizeY, localSizeZ, localMemorySize, sharedMemorySize); var options = new TranslationOptions(TargetLanguage.Glsl, TargetApi.OpenGL, DefaultFlags | TranslationFlags.Compute); return Translator.CreateContext(gpuVa, gpuAccessor, options); } /// /// Decode the binary Maxwell shader code to a translator context. /// /// /// This will combine the "Vertex A" and "Vertex B" shader stages, if specified, into one shader. /// /// GPU channel /// GPU accessor state /// Cumulative shader resource counts /// Flags that controls shader translation /// Shader stage /// GPU virtual address of the shader code /// The generated translator context private TranslatorContext DecodeGraphicsShader( GpuChannel channel, GpuAccessorState gas, TranslationCounts counts, TranslationFlags flags, ShaderStage stage, ulong gpuVa) { if (gpuVa == 0) { return null; } GpuAccessor gpuAccessor = new GpuAccessor(_context, channel, gas, (int)stage - 1); var options = new TranslationOptions(TargetLanguage.Glsl, TargetApi.OpenGL, flags); return Translator.CreateContext(gpuVa, gpuAccessor, options, counts); } /// /// Translates a previously generated translator context to something that the host API accepts. /// /// Optional shader code dumper /// Memory manager used to access the GPU memory where the shader is located /// Translator context of all available shader stages /// Index on the stages array to translate /// Compiled graphics shader code private static ShaderCodeHolder TranslateShader( ShaderDumper dumper, MemoryManager memoryManager, TranslatorContext[] stages, int stageIndex) { TranslatorContext currentStage = stages[stageIndex]; TranslatorContext nextStage = GetNextStageContext(stages, stageIndex); TranslatorContext vertexA = stageIndex == 1 ? stages[0] : null; return TranslateShader(dumper, memoryManager, currentStage, nextStage, vertexA); } /// /// Gets the next shader stage context, from an array of contexts and index of the current stage. /// /// Translator context of all available shader stages /// Index on the stages array to translate /// The translator context of the next stage, or null if inexistent private static TranslatorContext GetNextStageContext(TranslatorContext[] stages, int stageIndex) { for (int nextStageIndex = stageIndex + 1; nextStageIndex < stages.Length; nextStageIndex++) { if (stages[nextStageIndex] != null) { return stages[nextStageIndex]; } } return null; } /// /// Translates a previously generated translator context to something that the host API accepts. /// /// Optional shader code dumper /// Memory manager used to access the GPU memory where the shader is located /// Translator context of the stage to be translated /// Translator context of the next active stage, if existent /// Optional translator context of the shader that should be combined /// Compiled graphics shader code private static ShaderCodeHolder TranslateShader( ShaderDumper dumper, MemoryManager memoryManager, TranslatorContext currentStage, TranslatorContext nextStage, TranslatorContext vertexA) { if (currentStage == null) { return null; } if (vertexA != null) { byte[] codeA = memoryManager.GetSpan(vertexA.Address, vertexA.Size).ToArray(); byte[] codeB = memoryManager.GetSpan(currentStage.Address, currentStage.Size).ToArray(); ShaderDumpPaths pathsA = default; ShaderDumpPaths pathsB = default; if (dumper != null) { pathsA = dumper.Dump(codeA, compute: false); pathsB = dumper.Dump(codeB, compute: false); } ShaderProgram program = currentStage.Translate(out ShaderProgramInfo shaderProgramInfo, nextStage, vertexA); pathsB.Prepend(program); pathsA.Prepend(program); return new ShaderCodeHolder(program, shaderProgramInfo, codeB, codeA); } else { byte[] code = memoryManager.GetSpan(currentStage.Address, currentStage.Size).ToArray(); ShaderDumpPaths paths = dumper?.Dump(code, currentStage.Stage == ShaderStage.Compute) ?? default; ShaderProgram program = currentStage.Translate(out ShaderProgramInfo shaderProgramInfo, nextStage); paths.Prepend(program); return new ShaderCodeHolder(program, shaderProgramInfo, code); } } /// /// Disposes the shader cache, deleting all the cached shaders. /// It's an error to use the shader cache after disposal. /// public void Dispose() { foreach (List list in _cpPrograms.Values) { foreach (ShaderBundle bundle in list) { bundle.Dispose(); } } foreach (List list in _gpPrograms.Values) { foreach (ShaderBundle bundle in list) { bundle.Dispose(); } } _cacheManager?.Dispose(); } } }