/ src / Ryujinx.Graphics.Gpu / Shader / ShaderCache.cs
ShaderCache.cs
  1  using Ryujinx.Common.Configuration;
  2  using Ryujinx.Common.Logging;
  3  using Ryujinx.Graphics.GAL;
  4  using Ryujinx.Graphics.Gpu.Engine.Threed;
  5  using Ryujinx.Graphics.Gpu.Engine.Types;
  6  using Ryujinx.Graphics.Gpu.Image;
  7  using Ryujinx.Graphics.Gpu.Memory;
  8  using Ryujinx.Graphics.Gpu.Shader.DiskCache;
  9  using Ryujinx.Graphics.Shader;
 10  using Ryujinx.Graphics.Shader.Translation;
 11  using System;
 12  using System.Collections.Generic;
 13  using System.IO;
 14  using System.Threading;
 15  
 16  namespace Ryujinx.Graphics.Gpu.Shader
 17  {
 18      /// <summary>
 19      /// Memory cache of shader code.
 20      /// </summary>
 21      class ShaderCache : IDisposable
 22      {
 23          /// <summary>
 24          /// Default flags used on the shader translation process.
 25          /// </summary>
 26          public const TranslationFlags DefaultFlags = TranslationFlags.DebugMode;
 27  
 28          private readonly struct TranslatedShader
 29          {
 30              public readonly CachedShaderStage Shader;
 31              public readonly ShaderProgram Program;
 32  
 33              public TranslatedShader(CachedShaderStage shader, ShaderProgram program)
 34              {
 35                  Shader = shader;
 36                  Program = program;
 37              }
 38          }
 39  
 40          private readonly struct TranslatedShaderVertexPair
 41          {
 42              public readonly CachedShaderStage VertexA;
 43              public readonly CachedShaderStage VertexB;
 44              public readonly ShaderProgram Program;
 45  
 46              public TranslatedShaderVertexPair(CachedShaderStage vertexA, CachedShaderStage vertexB, ShaderProgram program)
 47              {
 48                  VertexA = vertexA;
 49                  VertexB = vertexB;
 50                  Program = program;
 51              }
 52          }
 53  
 54          private readonly GpuContext _context;
 55  
 56          private readonly ShaderDumper _dumper;
 57  
 58          private readonly Dictionary<ulong, CachedShaderProgram> _cpPrograms;
 59          private readonly Dictionary<ShaderAddresses, CachedShaderProgram> _gpPrograms;
 60  
 61          private readonly struct ProgramToSave
 62          {
 63              public readonly CachedShaderProgram CachedProgram;
 64              public readonly IProgram HostProgram;
 65              public readonly byte[] BinaryCode;
 66  
 67              public ProgramToSave(CachedShaderProgram cachedProgram, IProgram hostProgram, byte[] binaryCode)
 68              {
 69                  CachedProgram = cachedProgram;
 70                  HostProgram = hostProgram;
 71                  BinaryCode = binaryCode;
 72              }
 73          }
 74  
 75          private readonly Queue<ProgramToSave> _programsToSaveQueue;
 76  
 77          private readonly ComputeShaderCacheHashTable _computeShaderCache;
 78          private readonly ShaderCacheHashTable _graphicsShaderCache;
 79          private readonly DiskCacheHostStorage _diskCacheHostStorage;
 80          private readonly BackgroundDiskCacheWriter _cacheWriter;
 81  
 82          /// <summary>
 83          /// Event for signalling shader cache loading progress.
 84          /// </summary>
 85          public event Action<ShaderCacheState, int, int> ShaderCacheStateChanged;
 86  
 87          /// <summary>
 88          /// Creates a new instance of the shader cache.
 89          /// </summary>
 90          /// <param name="context">GPU context that the shader cache belongs to</param>
 91          public ShaderCache(GpuContext context)
 92          {
 93              _context = context;
 94  
 95              _dumper = new ShaderDumper();
 96  
 97              _cpPrograms = new Dictionary<ulong, CachedShaderProgram>();
 98              _gpPrograms = new Dictionary<ShaderAddresses, CachedShaderProgram>();
 99  
100              _programsToSaveQueue = new Queue<ProgramToSave>();
101  
102              string diskCacheTitleId = GetDiskCachePath();
103  
104              _computeShaderCache = new ComputeShaderCacheHashTable();
105              _graphicsShaderCache = new ShaderCacheHashTable();
106              _diskCacheHostStorage = new DiskCacheHostStorage(diskCacheTitleId);
107  
108              if (_diskCacheHostStorage.CacheEnabled)
109              {
110                  _cacheWriter = new BackgroundDiskCacheWriter(context, _diskCacheHostStorage);
111              }
112          }
113  
114          /// <summary>
115          /// Gets the path where the disk cache for the current application is stored.
116          /// </summary>
117          private static string GetDiskCachePath()
118          {
119              return GraphicsConfig.EnableShaderCache && GraphicsConfig.TitleId != null
120                  ? Path.Combine(AppDataManager.GamesDirPath, GraphicsConfig.TitleId, "cache", "shader")
121                  : null;
122          }
123  
124          /// <summary>
125          /// Processes the queue of shaders that must save their binaries to the disk cache.
126          /// </summary>
127          public void ProcessShaderCacheQueue()
128          {
129              // Check to see if the binaries for previously compiled shaders are ready, and save them out.
130  
131              while (_programsToSaveQueue.TryPeek(out ProgramToSave programToSave))
132              {
133                  ProgramLinkStatus result = programToSave.HostProgram.CheckProgramLink(false);
134  
135                  if (result != ProgramLinkStatus.Incomplete)
136                  {
137                      if (result == ProgramLinkStatus.Success)
138                      {
139                          _cacheWriter.AddShader(programToSave.CachedProgram, programToSave.BinaryCode ?? programToSave.HostProgram.GetBinary());
140                      }
141  
142                      _programsToSaveQueue.Dequeue();
143                  }
144                  else
145                  {
146                      break;
147                  }
148              }
149          }
150  
151          /// <summary>
152          /// Initialize the cache.
153          /// </summary>
154          /// <param name="cancellationToken">Cancellation token to cancel the shader cache initialization process</param>
155          internal void Initialize(CancellationToken cancellationToken)
156          {
157              if (_diskCacheHostStorage.CacheEnabled)
158              {
159                  ParallelDiskCacheLoader loader = new(
160                      _context,
161                      _graphicsShaderCache,
162                      _computeShaderCache,
163                      _diskCacheHostStorage,
164                      ShaderCacheStateUpdate,
165                      cancellationToken);
166  
167                  loader.LoadShaders();
168  
169                  int errorCount = loader.ErrorCount;
170                  if (errorCount != 0)
171                  {
172                      Logger.Warning?.Print(LogClass.Gpu, $"Failed to load {errorCount} shaders from the disk cache.");
173                  }
174              }
175          }
176  
177          /// <summary>
178          /// Shader cache state update handler.
179          /// </summary>
180          /// <param name="state">Current state of the shader cache load process</param>
181          /// <param name="current">Number of the current shader being processed</param>
182          /// <param name="total">Total number of shaders to process</param>
183          private void ShaderCacheStateUpdate(ShaderCacheState state, int current, int total)
184          {
185              ShaderCacheStateChanged?.Invoke(state, current, total);
186          }
187  
188          /// <summary>
189          /// Gets a compute shader from the cache.
190          /// </summary>
191          /// <remarks>
192          /// This automatically translates, compiles and adds the code to the cache if not present.
193          /// </remarks>
194          /// <param name="channel">GPU channel</param>
195          /// <param name="samplerPoolMaximumId">Maximum ID that an entry in the sampler pool may have</param>
196          /// <param name="poolState">Texture pool state</param>
197          /// <param name="computeState">Compute engine state</param>
198          /// <param name="gpuVa">GPU virtual address of the binary shader code</param>
199          /// <returns>Compiled compute shader code</returns>
200          public CachedShaderProgram GetComputeShader(
201              GpuChannel channel,
202              int samplerPoolMaximumId,
203              GpuChannelPoolState poolState,
204              GpuChannelComputeState computeState,
205              ulong gpuVa)
206          {
207              if (_cpPrograms.TryGetValue(gpuVa, out var cpShader) && IsShaderEqual(channel, poolState, computeState, cpShader, gpuVa))
208              {
209                  return cpShader;
210              }
211  
212              if (_computeShaderCache.TryFind(channel, poolState, computeState, gpuVa, out cpShader, out byte[] cachedGuestCode))
213              {
214                  _cpPrograms[gpuVa] = cpShader;
215                  return cpShader;
216              }
217  
218              ShaderSpecializationState specState = new(ref computeState);
219              GpuAccessorState gpuAccessorState = new(samplerPoolMaximumId, poolState, computeState, default, specState);
220              GpuAccessor gpuAccessor = new(_context, channel, gpuAccessorState);
221              gpuAccessor.InitializeReservedCounts(tfEnabled: false, vertexAsCompute: false);
222  
223              TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, _context.Capabilities.Api, gpuVa);
224              TranslatedShader translatedShader = TranslateShader(_dumper, channel, translatorContext, cachedGuestCode, asCompute: false);
225  
226              ShaderSource[] shaderSourcesArray = new ShaderSource[] { CreateShaderSource(translatedShader.Program) };
227              ShaderInfo info = ShaderInfoBuilder.BuildForCompute(_context, translatedShader.Program.Info);
228              IProgram hostProgram = _context.Renderer.CreateProgram(shaderSourcesArray, info);
229  
230              cpShader = new CachedShaderProgram(hostProgram, specState, translatedShader.Shader);
231  
232              _computeShaderCache.Add(cpShader);
233              EnqueueProgramToSave(cpShader, hostProgram, shaderSourcesArray);
234              _cpPrograms[gpuVa] = cpShader;
235  
236              return cpShader;
237          }
238  
239          /// <summary>
240          /// Updates the shader pipeline state based on the current GPU state.
241          /// </summary>
242          /// <param name="state">Current GPU 3D engine state</param>
243          /// <param name="pipeline">Shader pipeline state to be updated</param>
244          /// <param name="graphicsState">Current graphics state</param>
245          /// <param name="channel">Current GPU channel</param>
246          private static void UpdatePipelineInfo(
247              ref ThreedClassState state,
248              ref ProgramPipelineState pipeline,
249              GpuChannelGraphicsState graphicsState,
250              GpuChannel channel)
251          {
252              channel.TextureManager.UpdateRenderTargets();
253  
254              var rtControl = state.RtControl;
255              var msaaMode = state.RtMsaaMode;
256  
257              pipeline.SamplesCount = msaaMode.SamplesInX() * msaaMode.SamplesInY();
258  
259              int count = rtControl.UnpackCount();
260  
261              for (int index = 0; index < Constants.TotalRenderTargets; index++)
262              {
263                  int rtIndex = rtControl.UnpackPermutationIndex(index);
264  
265                  var colorState = state.RtColorState[rtIndex];
266  
267                  if (index >= count || colorState.Format == 0 || colorState.WidthOrStride == 0)
268                  {
269                      pipeline.AttachmentEnable[index] = false;
270                      pipeline.AttachmentFormats[index] = Format.R8G8B8A8Unorm;
271                  }
272                  else
273                  {
274                      pipeline.AttachmentEnable[index] = true;
275                      pipeline.AttachmentFormats[index] = colorState.Format.Convert().Format;
276                  }
277              }
278  
279              pipeline.DepthStencilEnable = state.RtDepthStencilEnable;
280              pipeline.DepthStencilFormat = pipeline.DepthStencilEnable ? state.RtDepthStencilState.Format.Convert().Format : Format.D24UnormS8Uint;
281  
282              pipeline.VertexBufferCount = Constants.TotalVertexBuffers;
283              pipeline.Topology = graphicsState.Topology;
284          }
285  
286          /// <summary>
287          /// Gets a graphics shader program from the shader cache.
288          /// This includes all the specified shader stages.
289          /// </summary>
290          /// <remarks>
291          /// This automatically translates, compiles and adds the code to the cache if not present.
292          /// </remarks>
293          /// <param name="state">GPU state</param>
294          /// <param name="pipeline">Pipeline state</param>
295          /// <param name="channel">GPU channel</param>
296          /// <param name="samplerPoolMaximumId">Maximum ID that an entry in the sampler pool may have</param>
297          /// <param name="poolState">Texture pool state</param>
298          /// <param name="graphicsState">3D engine state</param>
299          /// <param name="addresses">Addresses of the shaders for each stage</param>
300          /// <returns>Compiled graphics shader code</returns>
301          public CachedShaderProgram GetGraphicsShader(
302              ref ThreedClassState state,
303              ref ProgramPipelineState pipeline,
304              GpuChannel channel,
305              int samplerPoolMaximumId,
306              ref GpuChannelPoolState poolState,
307              ref GpuChannelGraphicsState graphicsState,
308              ShaderAddresses addresses)
309          {
310              if (_gpPrograms.TryGetValue(addresses, out var gpShaders) && IsShaderEqual(channel, ref poolState, ref graphicsState, gpShaders, addresses))
311              {
312                  return gpShaders;
313              }
314  
315              if (_graphicsShaderCache.TryFind(channel, ref poolState, ref graphicsState, addresses, out gpShaders, out var cachedGuestCode))
316              {
317                  _gpPrograms[addresses] = gpShaders;
318                  return gpShaders;
319              }
320  
321              TransformFeedbackDescriptor[] transformFeedbackDescriptors = GetTransformFeedbackDescriptors(ref state);
322  
323              UpdatePipelineInfo(ref state, ref pipeline, graphicsState, channel);
324  
325              ShaderSpecializationState specState = new(ref graphicsState, ref pipeline, transformFeedbackDescriptors);
326              GpuAccessorState gpuAccessorState = new(samplerPoolMaximumId, poolState, default, graphicsState, specState, transformFeedbackDescriptors);
327  
328              ReadOnlySpan<ulong> addressesSpan = addresses.AsSpan();
329  
330              GpuAccessor[] gpuAccessors = new GpuAccessor[Constants.ShaderStages];
331              TranslatorContext[] translatorContexts = new TranslatorContext[Constants.ShaderStages + 1];
332              TranslatorContext nextStage = null;
333  
334              TargetApi api = _context.Capabilities.Api;
335  
336              for (int stageIndex = Constants.ShaderStages - 1; stageIndex >= 0; stageIndex--)
337              {
338                  ulong gpuVa = addressesSpan[stageIndex + 1];
339  
340                  if (gpuVa != 0)
341                  {
342                      GpuAccessor gpuAccessor = new(_context, channel, gpuAccessorState, stageIndex, addresses.Geometry != 0);
343                      TranslatorContext currentStage = DecodeGraphicsShader(gpuAccessor, api, DefaultFlags, gpuVa);
344  
345                      if (nextStage != null)
346                      {
347                          currentStage.SetNextStage(nextStage);
348                      }
349  
350                      if (stageIndex == 0 && addresses.VertexA != 0)
351                      {
352                          translatorContexts[0] = DecodeGraphicsShader(gpuAccessor, api, DefaultFlags | TranslationFlags.VertexA, addresses.VertexA);
353                      }
354  
355                      gpuAccessors[stageIndex] = gpuAccessor;
356                      translatorContexts[stageIndex + 1] = currentStage;
357                      nextStage = currentStage;
358                  }
359              }
360  
361              bool hasGeometryShader = translatorContexts[4] != null;
362              bool vertexHasStore = translatorContexts[1] != null && translatorContexts[1].HasStore;
363              bool geometryHasStore = hasGeometryShader && translatorContexts[4].HasStore;
364              bool vertexToCompute = ShouldConvertVertexToCompute(_context, vertexHasStore, geometryHasStore, hasGeometryShader);
365              bool geometryToCompute = ShouldConvertGeometryToCompute(_context, geometryHasStore);
366  
367              CachedShaderStage[] shaders = new CachedShaderStage[Constants.ShaderStages + 1];
368              List<ShaderSource> shaderSources = new();
369  
370              TranslatorContext previousStage = null;
371              ShaderInfoBuilder infoBuilder = new(_context, transformFeedbackDescriptors != null, vertexToCompute);
372  
373              if (geometryToCompute && translatorContexts[4] != null)
374              {
375                  translatorContexts[4].SetVertexOutputMapForGeometryAsCompute(translatorContexts[1]);
376              }
377  
378              ShaderAsCompute vertexAsCompute = null;
379              ShaderAsCompute geometryAsCompute = null;
380  
381              for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++)
382              {
383                  TranslatorContext currentStage = translatorContexts[stageIndex + 1];
384  
385                  if (currentStage != null)
386                  {
387                      gpuAccessors[stageIndex].InitializeReservedCounts(transformFeedbackDescriptors != null, vertexToCompute);
388  
389                      ShaderProgram program;
390  
391                      bool asCompute = (stageIndex == 0 && vertexToCompute) || (stageIndex == 3 && geometryToCompute);
392  
393                      if (stageIndex == 0 && translatorContexts[0] != null)
394                      {
395                          TranslatedShaderVertexPair translatedShader = TranslateShader(
396                              _dumper,
397                              channel,
398                              currentStage,
399                              translatorContexts[0],
400                              cachedGuestCode.VertexACode,
401                              cachedGuestCode.VertexBCode,
402                              asCompute);
403  
404                          shaders[0] = translatedShader.VertexA;
405                          shaders[1] = translatedShader.VertexB;
406                          program = translatedShader.Program;
407                      }
408                      else
409                      {
410                          byte[] code = cachedGuestCode.GetByIndex(stageIndex);
411  
412                          TranslatedShader translatedShader = TranslateShader(_dumper, channel, currentStage, code, asCompute);
413  
414                          shaders[stageIndex + 1] = translatedShader.Shader;
415                          program = translatedShader.Program;
416                      }
417  
418                      if (asCompute)
419                      {
420                          bool tfEnabled = transformFeedbackDescriptors != null;
421  
422                          if (stageIndex == 0)
423                          {
424                              vertexAsCompute = CreateHostVertexAsComputeProgram(program, currentStage, tfEnabled);
425  
426                              TranslatorContext lastInVertexPipeline = geometryToCompute ? translatorContexts[4] ?? currentStage : currentStage;
427  
428                              program = lastInVertexPipeline.GenerateVertexPassthroughForCompute();
429                          }
430                          else
431                          {
432                              geometryAsCompute = CreateHostVertexAsComputeProgram(program, currentStage, tfEnabled);
433                              program = null;
434                          }
435                      }
436  
437                      if (program != null)
438                      {
439                          shaderSources.Add(CreateShaderSource(program));
440                          infoBuilder.AddStageInfo(program.Info);
441                      }
442  
443                      previousStage = currentStage;
444                  }
445                  else if (
446                      previousStage != null &&
447                      previousStage.LayerOutputWritten &&
448                      stageIndex == 3 &&
449                      !_context.Capabilities.SupportsLayerVertexTessellation)
450                  {
451                      shaderSources.Add(CreateShaderSource(previousStage.GenerateGeometryPassthrough()));
452                  }
453              }
454  
455              ShaderSource[] shaderSourcesArray = shaderSources.ToArray();
456  
457              ShaderInfo info = infoBuilder.Build(pipeline);
458  
459              IProgram hostProgram = _context.Renderer.CreateProgram(shaderSourcesArray, info);
460  
461              gpShaders = new(hostProgram, vertexAsCompute, geometryAsCompute, specState, shaders);
462  
463              _graphicsShaderCache.Add(gpShaders);
464  
465              // We don't currently support caching shaders that have been converted to compute.
466              if (vertexAsCompute == null)
467              {
468                  EnqueueProgramToSave(gpShaders, hostProgram, shaderSourcesArray);
469              }
470  
471              _gpPrograms[addresses] = gpShaders;
472  
473              return gpShaders;
474          }
475  
476          /// <summary>
477          /// Checks if a vertex shader should be converted to a compute shader due to it making use of
478          /// features that are not supported on the host.
479          /// </summary>
480          /// <param name="context">GPU context of the shader</param>
481          /// <param name="vertexHasStore">Whether the vertex shader has image or storage buffer store operations</param>
482          /// <param name="geometryHasStore">Whether the geometry shader has image or storage buffer store operations, if one exists</param>
483          /// <param name="hasGeometryShader">Whether a geometry shader exists</param>
484          /// <returns>True if the vertex shader should be converted to compute, false otherwise</returns>
485          public static bool ShouldConvertVertexToCompute(GpuContext context, bool vertexHasStore, bool geometryHasStore, bool hasGeometryShader)
486          {
487              // If the host does not support store operations on vertex,
488              // we need to emulate it on a compute shader.
489              if (!context.Capabilities.SupportsVertexStoreAndAtomics && vertexHasStore)
490              {
491                  return true;
492              }
493  
494              // If any stage after the vertex stage is converted to compute,
495              // we need to convert vertex to compute too.
496              return hasGeometryShader && ShouldConvertGeometryToCompute(context, geometryHasStore);
497          }
498  
499          /// <summary>
500          /// Checks if a geometry shader should be converted to a compute shader due to it making use of
501          /// features that are not supported on the host.
502          /// </summary>
503          /// <param name="context">GPU context of the shader</param>
504          /// <param name="geometryHasStore">Whether the geometry shader has image or storage buffer store operations, if one exists</param>
505          /// <returns>True if the geometry shader should be converted to compute, false otherwise</returns>
506          public static bool ShouldConvertGeometryToCompute(GpuContext context, bool geometryHasStore)
507          {
508              return (!context.Capabilities.SupportsVertexStoreAndAtomics && geometryHasStore) ||
509                     !context.Capabilities.SupportsGeometryShader;
510          }
511  
512          /// <summary>
513          /// Checks if it might be necessary for any vertex, tessellation or geometry shader to be converted to compute,
514          /// based on the supported host features.
515          /// </summary>
516          /// <param name="capabilities">Host capabilities</param>
517          /// <returns>True if the possibility of a shader being converted to compute exists, false otherwise</returns>
518          public static bool MayConvertVtgToCompute(ref Capabilities capabilities)
519          {
520              return !capabilities.SupportsVertexStoreAndAtomics || !capabilities.SupportsGeometryShader;
521          }
522  
523          /// <summary>
524          /// Creates a compute shader from a vertex, tessellation or geometry shader that has been converted to compute.
525          /// </summary>
526          /// <param name="program">Shader program</param>
527          /// <param name="context">Translation context of the shader</param>
528          /// <param name="tfEnabled">Whether transform feedback is enabled</param>
529          /// <returns>Compute shader</returns>
530          private ShaderAsCompute CreateHostVertexAsComputeProgram(ShaderProgram program, TranslatorContext context, bool tfEnabled)
531          {
532              ShaderSource source = new(program.Code, program.BinaryCode, ShaderStage.Compute, program.Language);
533              ShaderInfo info = ShaderInfoBuilder.BuildForVertexAsCompute(_context, program.Info, tfEnabled);
534  
535              return new(_context.Renderer.CreateProgram(new[] { source }, info), program.Info, context.GetResourceReservations());
536          }
537  
538          /// <summary>
539          /// Creates a shader source for use with the backend from a translated shader program.
540          /// </summary>
541          /// <param name="program">Translated shader program</param>
542          /// <returns>Shader source</returns>
543          public static ShaderSource CreateShaderSource(ShaderProgram program)
544          {
545              return new ShaderSource(program.Code, program.BinaryCode, program.Info.Stage, program.Language);
546          }
547  
548          /// <summary>
549          /// Puts a program on the queue of programs to be saved on the disk cache.
550          /// </summary>
551          /// <remarks>
552          /// This will not do anything if disk shader cache is disabled.
553          /// </remarks>
554          /// <param name="program">Cached shader program</param>
555          /// <param name="hostProgram">Host program</param>
556          /// <param name="sources">Source for each shader stage</param>
557          private void EnqueueProgramToSave(CachedShaderProgram program, IProgram hostProgram, ShaderSource[] sources)
558          {
559              if (_diskCacheHostStorage.CacheEnabled)
560              {
561                  byte[] binaryCode = _context.Capabilities.Api == TargetApi.Vulkan ? ShaderBinarySerializer.Pack(sources) : null;
562                  ProgramToSave programToSave = new(program, hostProgram, binaryCode);
563  
564                  _programsToSaveQueue.Enqueue(programToSave);
565              }
566          }
567  
568          /// <summary>
569          /// Gets transform feedback state from the current GPU state.
570          /// </summary>
571          /// <param name="state">Current GPU state</param>
572          /// <returns>Four transform feedback descriptors for the enabled TFBs, or null if TFB is disabled</returns>
573          private static TransformFeedbackDescriptor[] GetTransformFeedbackDescriptors(ref ThreedClassState state)
574          {
575              bool tfEnable = state.TfEnable;
576              if (!tfEnable)
577              {
578                  return null;
579              }
580  
581              TransformFeedbackDescriptor[] descs = new TransformFeedbackDescriptor[Constants.TotalTransformFeedbackBuffers];
582  
583              for (int i = 0; i < Constants.TotalTransformFeedbackBuffers; i++)
584              {
585                  var tf = state.TfState[i];
586  
587                  descs[i] = new TransformFeedbackDescriptor(
588                      tf.BufferIndex,
589                      tf.Stride,
590                      tf.VaryingsCount,
591                      ref state.TfVaryingLocations[i]);
592              }
593  
594              return descs;
595          }
596  
597          /// <summary>
598          /// Checks if compute shader code in memory is equal to the cached shader.
599          /// </summary>
600          /// <param name="channel">GPU channel using the shader</param>
601          /// <param name="poolState">GPU channel state to verify shader compatibility</param>
602          /// <param name="computeState">GPU channel compute state to verify shader compatibility</param>
603          /// <param name="cpShader">Cached compute shader</param>
604          /// <param name="gpuVa">GPU virtual address of the shader code in memory</param>
605          /// <returns>True if the code is different, false otherwise</returns>
606          private static bool IsShaderEqual(
607              GpuChannel channel,
608              GpuChannelPoolState poolState,
609              GpuChannelComputeState computeState,
610              CachedShaderProgram cpShader,
611              ulong gpuVa)
612          {
613              if (IsShaderEqual(channel.MemoryManager, cpShader.Shaders[0], gpuVa))
614              {
615                  return cpShader.SpecializationState.MatchesCompute(channel, ref poolState, computeState, true);
616              }
617  
618              return false;
619          }
620  
621          /// <summary>
622          /// Checks if graphics shader code from all stages in memory are equal to the cached shaders.
623          /// </summary>
624          /// <param name="channel">GPU channel using the shader</param>
625          /// <param name="poolState">GPU channel state to verify shader compatibility</param>
626          /// <param name="graphicsState">GPU channel graphics state to verify shader compatibility</param>
627          /// <param name="gpShaders">Cached graphics shaders</param>
628          /// <param name="addresses">GPU virtual addresses of all enabled shader stages</param>
629          /// <returns>True if the code is different, false otherwise</returns>
630          private static bool IsShaderEqual(
631              GpuChannel channel,
632              ref GpuChannelPoolState poolState,
633              ref GpuChannelGraphicsState graphicsState,
634              CachedShaderProgram gpShaders,
635              ShaderAddresses addresses)
636          {
637              ReadOnlySpan<ulong> addressesSpan = addresses.AsSpan();
638  
639              for (int stageIndex = 0; stageIndex < gpShaders.Shaders.Length; stageIndex++)
640              {
641                  CachedShaderStage shader = gpShaders.Shaders[stageIndex];
642  
643                  ulong gpuVa = addressesSpan[stageIndex];
644  
645                  if (!IsShaderEqual(channel.MemoryManager, shader, gpuVa))
646                  {
647                      return false;
648                  }
649              }
650  
651              bool vertexAsCompute = gpShaders.VertexAsCompute != null;
652              bool usesDrawParameters = gpShaders.Shaders[1]?.Info.UsesDrawParameters ?? false;
653  
654              return gpShaders.SpecializationState.MatchesGraphics(
655                  channel,
656                  ref poolState,
657                  ref graphicsState,
658                  vertexAsCompute,
659                  usesDrawParameters,
660                  checkTextures: true);
661          }
662  
663          /// <summary>
664          /// Checks if the code of the specified cached shader is different from the code in memory.
665          /// </summary>
666          /// <param name="memoryManager">Memory manager used to access the GPU memory where the shader is located</param>
667          /// <param name="shader">Cached shader to compare with</param>
668          /// <param name="gpuVa">GPU virtual address of the binary shader code</param>
669          /// <returns>True if the code is different, false otherwise</returns>
670          private static bool IsShaderEqual(MemoryManager memoryManager, CachedShaderStage shader, ulong gpuVa)
671          {
672              if (shader == null)
673              {
674                  return true;
675              }
676  
677              ReadOnlySpan<byte> memoryCode = memoryManager.GetSpanMapped(gpuVa, shader.Code.Length);
678  
679              return memoryCode.SequenceEqual(shader.Code);
680          }
681  
682          /// <summary>
683          /// Decode the binary Maxwell shader code to a translator context.
684          /// </summary>
685          /// <param name="gpuAccessor">GPU state accessor</param>
686          /// <param name="api">Graphics API that will be used with the shader</param>
687          /// <param name="gpuVa">GPU virtual address of the binary shader code</param>
688          /// <returns>The generated translator context</returns>
689          public static TranslatorContext DecodeComputeShader(IGpuAccessor gpuAccessor, TargetApi api, ulong gpuVa)
690          {
691              var options = CreateTranslationOptions(api, DefaultFlags | TranslationFlags.Compute);
692              return Translator.CreateContext(gpuVa, gpuAccessor, options);
693          }
694  
695          /// <summary>
696          /// Decode the binary Maxwell shader code to a translator context.
697          /// </summary>
698          /// <remarks>
699          /// This will combine the "Vertex A" and "Vertex B" shader stages, if specified, into one shader.
700          /// </remarks>
701          /// <param name="gpuAccessor">GPU state accessor</param>
702          /// <param name="api">Graphics API that will be used with the shader</param>
703          /// <param name="flags">Flags that controls shader translation</param>
704          /// <param name="gpuVa">GPU virtual address of the shader code</param>
705          /// <returns>The generated translator context</returns>
706          public static TranslatorContext DecodeGraphicsShader(IGpuAccessor gpuAccessor, TargetApi api, TranslationFlags flags, ulong gpuVa)
707          {
708              var options = CreateTranslationOptions(api, flags);
709              return Translator.CreateContext(gpuVa, gpuAccessor, options);
710          }
711  
712          /// <summary>
713          /// Translates a previously generated translator context to something that the host API accepts.
714          /// </summary>
715          /// <param name="dumper">Optional shader code dumper</param>
716          /// <param name="channel">GPU channel using the shader</param>
717          /// <param name="currentStage">Translator context of the stage to be translated</param>
718          /// <param name="vertexA">Optional translator context of the shader that should be combined</param>
719          /// <param name="codeA">Optional Maxwell binary code of the Vertex A shader, if present</param>
720          /// <param name="codeB">Optional Maxwell binary code of the Vertex B or current stage shader, if present on cache</param>
721          /// <param name="asCompute">Indicates that the vertex shader should be converted to a compute shader</param>
722          /// <returns>Compiled graphics shader code</returns>
723          private static TranslatedShaderVertexPair TranslateShader(
724              ShaderDumper dumper,
725              GpuChannel channel,
726              TranslatorContext currentStage,
727              TranslatorContext vertexA,
728              byte[] codeA,
729              byte[] codeB,
730              bool asCompute)
731          {
732              ulong cb1DataAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(0, 1);
733  
734              var memoryManager = channel.MemoryManager;
735  
736              codeA ??= memoryManager.GetSpan(vertexA.Address, vertexA.Size).ToArray();
737              codeB ??= memoryManager.GetSpan(currentStage.Address, currentStage.Size).ToArray();
738              byte[] cb1DataA = ReadArray(memoryManager, cb1DataAddress, vertexA.Cb1DataSize);
739              byte[] cb1DataB = ReadArray(memoryManager, cb1DataAddress, currentStage.Cb1DataSize);
740  
741              ShaderDumpPaths pathsA = default;
742              ShaderDumpPaths pathsB = default;
743  
744              if (dumper != null)
745              {
746                  pathsA = dumper.Dump(codeA, compute: false);
747                  pathsB = dumper.Dump(codeB, compute: false);
748              }
749  
750              ShaderProgram program = currentStage.Translate(vertexA, asCompute);
751  
752              pathsB.Prepend(program);
753              pathsA.Prepend(program);
754  
755              CachedShaderStage vertexAStage = new(null, codeA, cb1DataA);
756              CachedShaderStage vertexBStage = new(program.Info, codeB, cb1DataB);
757  
758              return new TranslatedShaderVertexPair(vertexAStage, vertexBStage, program);
759          }
760  
761          /// <summary>
762          /// Translates a previously generated translator context to something that the host API accepts.
763          /// </summary>
764          /// <param name="dumper">Optional shader code dumper</param>
765          /// <param name="channel">GPU channel using the shader</param>
766          /// <param name="context">Translator context of the stage to be translated</param>
767          /// <param name="code">Optional Maxwell binary code of the current stage shader, if present on cache</param>
768          /// <param name="asCompute">Indicates that the vertex shader should be converted to a compute shader</param>
769          /// <returns>Compiled graphics shader code</returns>
770          private static TranslatedShader TranslateShader(ShaderDumper dumper, GpuChannel channel, TranslatorContext context, byte[] code, bool asCompute)
771          {
772              var memoryManager = channel.MemoryManager;
773  
774              ulong cb1DataAddress = context.Stage == ShaderStage.Compute
775                  ? channel.BufferManager.GetComputeUniformBufferAddress(1)
776                  : channel.BufferManager.GetGraphicsUniformBufferAddress(StageToStageIndex(context.Stage), 1);
777  
778              byte[] cb1Data = ReadArray(memoryManager, cb1DataAddress, context.Cb1DataSize);
779              code ??= memoryManager.GetSpan(context.Address, context.Size).ToArray();
780  
781              ShaderDumpPaths paths = dumper?.Dump(code, context.Stage == ShaderStage.Compute) ?? default;
782              ShaderProgram program = context.Translate(asCompute);
783  
784              paths.Prepend(program);
785  
786              return new TranslatedShader(new CachedShaderStage(program.Info, code, cb1Data), program);
787          }
788  
789          /// <summary>
790          /// Reads data from physical memory, returns an empty array if the memory is unmapped or size is 0.
791          /// </summary>
792          /// <param name="memoryManager">Memory manager with the physical memory to read from</param>
793          /// <param name="address">Physical address of the region to read</param>
794          /// <param name="size">Size in bytes of the data</param>
795          /// <returns>An array with the data at the specified memory location</returns>
796          private static byte[] ReadArray(MemoryManager memoryManager, ulong address, int size)
797          {
798              if (address == MemoryManager.PteUnmapped || size == 0)
799              {
800                  return Array.Empty<byte>();
801              }
802  
803              return memoryManager.Physical.GetSpan(address, size).ToArray();
804          }
805  
806          /// <summary>
807          /// Gets the index of a stage from a <see cref="ShaderStage"/>.
808          /// </summary>
809          /// <param name="stage">Stage to get the index from</param>
810          /// <returns>Stage index</returns>
811          private static int StageToStageIndex(ShaderStage stage)
812          {
813              return stage switch
814              {
815                  ShaderStage.TessellationControl => 1,
816                  ShaderStage.TessellationEvaluation => 2,
817                  ShaderStage.Geometry => 3,
818                  ShaderStage.Fragment => 4,
819                  _ => 0,
820              };
821          }
822  
823          /// <summary>
824          /// Creates shader translation options with the requested graphics API and flags.
825          /// The shader language is choosen based on the current configuration and graphics API.
826          /// </summary>
827          /// <param name="api">Target graphics API</param>
828          /// <param name="flags">Translation flags</param>
829          /// <returns>Translation options</returns>
830          private static TranslationOptions CreateTranslationOptions(TargetApi api, TranslationFlags flags)
831          {
832              TargetLanguage lang = GraphicsConfig.EnableSpirvCompilationOnVulkan && api == TargetApi.Vulkan
833                  ? TargetLanguage.Spirv
834                  : TargetLanguage.Glsl;
835  
836              return new TranslationOptions(lang, api, flags);
837          }
838  
839          /// <summary>
840          /// Disposes the shader cache, deleting all the cached shaders.
841          /// It's an error to use the shader cache after disposal.
842          /// </summary>
843          public void Dispose()
844          {
845              foreach (CachedShaderProgram program in _graphicsShaderCache.GetPrograms())
846              {
847                  program.Dispose();
848              }
849  
850              foreach (CachedShaderProgram program in _computeShaderCache.GetPrograms())
851              {
852                  program.Dispose();
853              }
854  
855              _cacheWriter?.Dispose();
856          }
857      }
858  }