/ src / Ryujinx.Graphics.Gpu / Shader / DiskCache / ParallelDiskCacheLoader.cs
ParallelDiskCacheLoader.cs
  1  using Ryujinx.Common.Logging;
  2  using Ryujinx.Graphics.GAL;
  3  using Ryujinx.Graphics.Shader;
  4  using Ryujinx.Graphics.Shader.Translation;
  5  using System;
  6  using System.Collections.Concurrent;
  7  using System.Collections.Generic;
  8  using System.IO;
  9  using System.Threading;
 10  using static Ryujinx.Graphics.Gpu.Shader.ShaderCache;
 11  
 12  namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
 13  {
 14      class ParallelDiskCacheLoader
 15      {
 16          private const int ThreadCount = 8;
 17  
 18          private readonly GpuContext _context;
 19          private readonly ShaderCacheHashTable _graphicsCache;
 20          private readonly ComputeShaderCacheHashTable _computeCache;
 21          private readonly DiskCacheHostStorage _hostStorage;
 22          private readonly CancellationToken _cancellationToken;
 23          private readonly Action<ShaderCacheState, int, int> _stateChangeCallback;
 24  
 25          /// <summary>
 26          /// Indicates if the cache should be loaded.
 27          /// </summary>
 28          public bool Active => !_cancellationToken.IsCancellationRequested;
 29  
 30          private bool _needsHostRegen;
 31  
 32          /// <summary>
 33          /// Number of shaders that failed to compile from the cache.
 34          /// </summary>
 35          public int ErrorCount { get; private set; }
 36  
 37          /// <summary>
 38          /// Program validation entry.
 39          /// </summary>
 40          private readonly struct ProgramEntry
 41          {
 42              /// <summary>
 43              /// Cached shader program.
 44              /// </summary>
 45              public readonly CachedShaderProgram CachedProgram;
 46  
 47              /// <summary>
 48              /// Optional binary code. If not null, it is used instead of the backend host binary.
 49              /// </summary>
 50              public readonly byte[] BinaryCode;
 51  
 52              /// <summary>
 53              /// Program index.
 54              /// </summary>
 55              public readonly int ProgramIndex;
 56  
 57              /// <summary>
 58              /// Indicates if the program is a compute shader.
 59              /// </summary>
 60              public readonly bool IsCompute;
 61  
 62              /// <summary>
 63              /// Indicates if the program is a host binary shader.
 64              /// </summary>
 65              public readonly bool IsBinary;
 66  
 67              /// <summary>
 68              /// Creates a new program validation entry.
 69              /// </summary>
 70              /// <param name="cachedProgram">Cached shader program</param>
 71              /// <param name="binaryCode">Optional binary code. If not null, it is used instead of the backend host binary</param>
 72              /// <param name="programIndex">Program index</param>
 73              /// <param name="isCompute">Indicates if the program is a compute shader</param>
 74              /// <param name="isBinary">Indicates if the program is a host binary shader</param>
 75              public ProgramEntry(
 76                  CachedShaderProgram cachedProgram,
 77                  byte[] binaryCode,
 78                  int programIndex,
 79                  bool isCompute,
 80                  bool isBinary)
 81              {
 82                  CachedProgram = cachedProgram;
 83                  BinaryCode = binaryCode;
 84                  ProgramIndex = programIndex;
 85                  IsCompute = isCompute;
 86                  IsBinary = isBinary;
 87              }
 88          }
 89  
 90          /// <summary>
 91          /// Translated shader compilation entry.
 92          /// </summary>
 93          private readonly struct ProgramCompilation
 94          {
 95              /// <summary>
 96              /// Translated shader stages.
 97              /// </summary>
 98              public readonly ShaderProgram[] TranslatedStages;
 99  
100              /// <summary>
101              /// Cached shaders.
102              /// </summary>
103              public readonly CachedShaderStage[] Shaders;
104  
105              /// <summary>
106              /// Specialization state.
107              /// </summary>
108              public readonly ShaderSpecializationState SpecializationState;
109  
110              /// <summary>
111              /// Program index.
112              /// </summary>
113              public readonly int ProgramIndex;
114  
115              /// <summary>
116              /// Indicates if the program is a compute shader.
117              /// </summary>
118              public readonly bool IsCompute;
119  
120              /// <summary>
121              /// Creates a new translated shader compilation entry.
122              /// </summary>
123              /// <param name="translatedStages">Translated shader stages</param>
124              /// <param name="shaders">Cached shaders</param>
125              /// <param name="specState">Specialization state</param>
126              /// <param name="programIndex">Program index</param>
127              /// <param name="isCompute">Indicates if the program is a compute shader</param>
128              public ProgramCompilation(
129                  ShaderProgram[] translatedStages,
130                  CachedShaderStage[] shaders,
131                  ShaderSpecializationState specState,
132                  int programIndex,
133                  bool isCompute)
134              {
135                  TranslatedStages = translatedStages;
136                  Shaders = shaders;
137                  SpecializationState = specState;
138                  ProgramIndex = programIndex;
139                  IsCompute = isCompute;
140              }
141          }
142  
143          /// <summary>
144          /// Program translation entry.
145          /// </summary>
146          private readonly struct AsyncProgramTranslation
147          {
148              /// <summary>
149              /// Guest code for each active stage.
150              /// </summary>
151              public readonly GuestCodeAndCbData?[] GuestShaders;
152  
153              /// <summary>
154              /// Specialization state.
155              /// </summary>
156              public readonly ShaderSpecializationState SpecializationState;
157  
158              /// <summary>
159              /// Program index.
160              /// </summary>
161              public readonly int ProgramIndex;
162  
163              /// <summary>
164              /// Indicates if the program is a compute shader.
165              /// </summary>
166              public readonly bool IsCompute;
167  
168              /// <summary>
169              /// Creates a new program translation entry.
170              /// </summary>
171              /// <param name="guestShaders">Guest code for each active stage</param>
172              /// <param name="specState">Specialization state</param>
173              /// <param name="programIndex">Program index</param>
174              /// <param name="isCompute">Indicates if the program is a compute shader</param>
175              public AsyncProgramTranslation(
176                  GuestCodeAndCbData?[] guestShaders,
177                  ShaderSpecializationState specState,
178                  int programIndex,
179                  bool isCompute)
180              {
181                  GuestShaders = guestShaders;
182                  SpecializationState = specState;
183                  ProgramIndex = programIndex;
184                  IsCompute = isCompute;
185              }
186          }
187  
188          private readonly Queue<ProgramEntry> _validationQueue;
189          private readonly ConcurrentQueue<ProgramCompilation> _compilationQueue;
190          private readonly BlockingCollection<AsyncProgramTranslation> _asyncTranslationQueue;
191          private readonly SortedList<int, (CachedShaderProgram, byte[])> _programList;
192  
193          private readonly int _backendParallelCompileThreads;
194          private int _compiledCount;
195          private int _totalCount;
196  
197          /// <summary>
198          /// Creates a new parallel disk cache loader.
199          /// </summary>
200          /// <param name="context">GPU context</param>
201          /// <param name="graphicsCache">Graphics shader cache</param>
202          /// <param name="computeCache">Compute shader cache</param>
203          /// <param name="hostStorage">Disk cache host storage</param>
204          /// <param name="stateChangeCallback">Function to be called when there is a state change, reporting state, compiled and total shaders count</param>
205          /// <param name="cancellationToken">Cancellation token</param>
206          public ParallelDiskCacheLoader(GpuContext context,
207              ShaderCacheHashTable graphicsCache,
208              ComputeShaderCacheHashTable computeCache,
209              DiskCacheHostStorage hostStorage,
210              Action<ShaderCacheState, int, int> stateChangeCallback,
211              CancellationToken cancellationToken)
212          {
213              _context = context;
214              _graphicsCache = graphicsCache;
215              _computeCache = computeCache;
216              _hostStorage = hostStorage;
217              _stateChangeCallback = stateChangeCallback;
218              _cancellationToken = cancellationToken;
219              _validationQueue = new Queue<ProgramEntry>();
220              _compilationQueue = new ConcurrentQueue<ProgramCompilation>();
221              _asyncTranslationQueue = new BlockingCollection<AsyncProgramTranslation>(ThreadCount);
222              _programList = new SortedList<int, (CachedShaderProgram, byte[])>();
223              _backendParallelCompileThreads = Math.Min(Environment.ProcessorCount, 8); // Must be kept in sync with the backend code.
224          }
225  
226          /// <summary>
227          /// Loads all shaders from the cache.
228          /// </summary>
229          public void LoadShaders()
230          {
231              Thread[] workThreads = new Thread[ThreadCount];
232  
233              for (int index = 0; index < ThreadCount; index++)
234              {
235                  workThreads[index] = new Thread(ProcessAsyncQueue)
236                  {
237                      Name = $"GPU.AsyncTranslationThread.{index}",
238                  };
239              }
240  
241              int programCount = _hostStorage.GetProgramCount();
242  
243              _compiledCount = 0;
244              _totalCount = programCount;
245  
246              _stateChangeCallback(ShaderCacheState.Start, 0, programCount);
247  
248              Logger.Info?.Print(LogClass.Gpu, $"Loading {programCount} shaders from the cache...");
249  
250              for (int index = 0; index < ThreadCount; index++)
251              {
252                  workThreads[index].Start(_cancellationToken);
253              }
254  
255              try
256              {
257                  _hostStorage.LoadShaders(_context, this);
258              }
259              catch (DiskCacheLoadException diskCacheLoadException)
260              {
261                  Logger.Warning?.Print(LogClass.Gpu, $"Error loading the shader cache. {diskCacheLoadException.Message}");
262  
263                  // If we can't even access the file, then we also can't rebuild.
264                  if (diskCacheLoadException.Result != DiskCacheLoadResult.NoAccess)
265                  {
266                      _needsHostRegen = true;
267                  }
268              }
269              catch (InvalidDataException invalidDataException)
270              {
271                  Logger.Warning?.Print(LogClass.Gpu, $"Error decompressing the shader cache file. {invalidDataException.Message}");
272                  _needsHostRegen = true;
273              }
274              catch (IOException ioException)
275              {
276                  Logger.Warning?.Print(LogClass.Gpu, $"Error reading the shader cache file. {ioException.Message}");
277                  _needsHostRegen = true;
278              }
279  
280              _asyncTranslationQueue.CompleteAdding();
281  
282              for (int index = 0; index < ThreadCount; index++)
283              {
284                  workThreads[index].Join();
285              }
286  
287              CheckCompilationBlocking();
288  
289              if (_needsHostRegen && Active)
290              {
291                  // Rebuild both shared and host cache files.
292                  // Rebuilding shared is required because the shader information returned by the translator
293                  // might have changed, and so we have to reconstruct the file with the new information.
294                  try
295                  {
296                      _hostStorage.ClearSharedCache();
297                      _hostStorage.ClearHostCache(_context);
298  
299                      if (_programList.Count != 0)
300                      {
301                          _stateChangeCallback(ShaderCacheState.Packaging, 0, _programList.Count);
302  
303                          Logger.Info?.Print(LogClass.Gpu, $"Rebuilding {_programList.Count} shaders...");
304  
305                          using var streams = _hostStorage.GetOutputStreams(_context);
306  
307                          int packagedShaders = 0;
308                          foreach (var kv in _programList)
309                          {
310                              if (!Active)
311                              {
312                                  break;
313                              }
314  
315                              (CachedShaderProgram program, byte[] binaryCode) = kv.Value;
316  
317                              _hostStorage.AddShader(_context, program, binaryCode, streams);
318  
319                              _stateChangeCallback(ShaderCacheState.Packaging, ++packagedShaders, _programList.Count);
320                          }
321  
322                          Logger.Info?.Print(LogClass.Gpu, $"Rebuilt {_programList.Count} shaders successfully.");
323                      }
324                      else
325                      {
326                          _hostStorage.ClearGuestCache();
327  
328                          Logger.Info?.Print(LogClass.Gpu, "Shader cache deleted due to corruption.");
329                      }
330                  }
331                  catch (DiskCacheLoadException diskCacheLoadException)
332                  {
333                      Logger.Warning?.Print(LogClass.Gpu, $"Error deleting the shader cache. {diskCacheLoadException.Message}");
334                  }
335                  catch (IOException ioException)
336                  {
337                      Logger.Warning?.Print(LogClass.Gpu, $"Error deleting the shader cache file. {ioException.Message}");
338                  }
339              }
340  
341              Logger.Info?.Print(LogClass.Gpu, "Shader cache loaded.");
342  
343              _stateChangeCallback(ShaderCacheState.Loaded, programCount, programCount);
344          }
345  
346          /// <summary>
347          /// Enqueues a host program for compilation.
348          /// </summary>
349          /// <param name="cachedProgram">Cached program</param>
350          /// <param name="binaryCode">Host binary code</param>
351          /// <param name="programIndex">Program index</param>
352          /// <param name="isCompute">Indicates if the program is a compute shader</param>
353          public void QueueHostProgram(CachedShaderProgram cachedProgram, byte[] binaryCode, int programIndex, bool isCompute)
354          {
355              EnqueueForValidation(new ProgramEntry(cachedProgram, binaryCode, programIndex, isCompute, isBinary: true));
356          }
357  
358          /// <summary>
359          /// Enqueues a guest program for compilation.
360          /// </summary>
361          /// <param name="guestShaders">Guest code for each active stage</param>
362          /// <param name="specState">Specialization state</param>
363          /// <param name="programIndex">Program index</param>
364          /// <param name="isCompute">Indicates if the program is a compute shader</param>
365          public void QueueGuestProgram(GuestCodeAndCbData?[] guestShaders, ShaderSpecializationState specState, int programIndex, bool isCompute)
366          {
367              try
368              {
369                  AsyncProgramTranslation asyncTranslation = new(guestShaders, specState, programIndex, isCompute);
370                  _asyncTranslationQueue.Add(asyncTranslation, _cancellationToken);
371              }
372              catch (OperationCanceledException)
373              {
374              }
375          }
376  
377          /// <summary>
378          /// Check the state of programs that have already been compiled,
379          /// and add to the cache if the compilation was successful.
380          /// </summary>
381          public void CheckCompilation()
382          {
383              ProcessCompilationQueue();
384  
385              // Process programs that already finished compiling.
386              // If not yet compiled, do nothing. This avoids blocking to wait for shader compilation.
387              while (_validationQueue.TryPeek(out ProgramEntry entry))
388              {
389                  ProgramLinkStatus result = entry.CachedProgram.HostProgram.CheckProgramLink(false);
390  
391                  if (result != ProgramLinkStatus.Incomplete)
392                  {
393                      ProcessCompiledProgram(ref entry, result);
394                      _validationQueue.Dequeue();
395                  }
396                  else
397                  {
398                      break;
399                  }
400              }
401          }
402  
403          /// <summary>
404          /// Waits until all programs finishes compiling, then adds the ones
405          /// with successful compilation to the cache.
406          /// </summary>
407          private void CheckCompilationBlocking()
408          {
409              ProcessCompilationQueue();
410  
411              while (_validationQueue.TryDequeue(out ProgramEntry entry) && Active)
412              {
413                  ProcessCompiledProgram(ref entry, entry.CachedProgram.HostProgram.CheckProgramLink(true), asyncCompile: false);
414              }
415          }
416  
417          /// <summary>
418          /// Process a compiled program result.
419          /// </summary>
420          /// <param name="entry">Compiled program entry</param>
421          /// <param name="result">Compilation result</param>
422          /// <param name="asyncCompile">For failed host compilations, indicates if a guest compilation should be done asynchronously</param>
423          private void ProcessCompiledProgram(ref ProgramEntry entry, ProgramLinkStatus result, bool asyncCompile = true)
424          {
425              if (result == ProgramLinkStatus.Success)
426              {
427                  // Compilation successful, add to memory cache.
428                  if (entry.IsCompute)
429                  {
430                      _computeCache.Add(entry.CachedProgram);
431                  }
432                  else
433                  {
434                      _graphicsCache.Add(entry.CachedProgram);
435                  }
436  
437                  if (!entry.IsBinary)
438                  {
439                      _needsHostRegen = true;
440                  }
441  
442                  // Fetch the binary code from the backend if it isn't already present.
443                  byte[] binaryCode = entry.BinaryCode ?? entry.CachedProgram.HostProgram.GetBinary();
444  
445                  _programList.Add(entry.ProgramIndex, (entry.CachedProgram, binaryCode));
446                  SignalCompiled();
447              }
448              else if (entry.IsBinary)
449              {
450                  // If this is a host binary and compilation failed,
451                  // we still have a chance to recompile from the guest binary.
452                  CachedShaderProgram program = entry.CachedProgram;
453  
454                  GuestCodeAndCbData?[] guestShaders = new GuestCodeAndCbData?[program.Shaders.Length];
455  
456                  for (int index = 0; index < program.Shaders.Length; index++)
457                  {
458                      CachedShaderStage shader = program.Shaders[index];
459  
460                      if (shader != null)
461                      {
462                          guestShaders[index] = new GuestCodeAndCbData(shader.Code, shader.Cb1Data);
463                      }
464                  }
465  
466                  if (asyncCompile)
467                  {
468                      QueueGuestProgram(guestShaders, program.SpecializationState, entry.ProgramIndex, entry.IsCompute);
469                  }
470                  else
471                  {
472                      RecompileFromGuestCode(guestShaders, program.SpecializationState, entry.ProgramIndex, entry.IsCompute);
473                      ProcessCompilationQueue();
474                  }
475              }
476              else
477              {
478                  // Failed to compile from both host and guest binary.
479                  ErrorCount++;
480                  SignalCompiled();
481              }
482          }
483  
484          /// <summary>
485          /// Processes the queue of translated guest programs that should be compiled on the host.
486          /// </summary>
487          private void ProcessCompilationQueue()
488          {
489              while (_compilationQueue.TryDequeue(out ProgramCompilation compilation) && Active)
490              {
491                  ShaderSource[] shaderSources = new ShaderSource[compilation.TranslatedStages.Length];
492  
493                  ShaderInfoBuilder shaderInfoBuilder = new(_context, compilation.SpecializationState.TransformFeedbackDescriptors != null);
494  
495                  for (int index = 0; index < compilation.TranslatedStages.Length; index++)
496                  {
497                      ShaderProgram shader = compilation.TranslatedStages[index];
498                      shaderSources[index] = CreateShaderSource(shader);
499                      shaderInfoBuilder.AddStageInfo(shader.Info);
500                  }
501  
502                  ShaderInfo shaderInfo = shaderInfoBuilder.Build(compilation.SpecializationState.PipelineState, fromCache: true);
503                  IProgram hostProgram = _context.Renderer.CreateProgram(shaderSources, shaderInfo);
504                  CachedShaderProgram program = new(hostProgram, compilation.SpecializationState, compilation.Shaders);
505  
506                  // Vulkan's binary code is the SPIR-V used for compilation, so it is ready immediately. Other APIs get this after compilation.
507                  byte[] binaryCode = _context.Capabilities.Api == TargetApi.Vulkan ? ShaderBinarySerializer.Pack(shaderSources) : null;
508  
509                  EnqueueForValidation(new ProgramEntry(program, binaryCode, compilation.ProgramIndex, compilation.IsCompute, isBinary: false));
510              }
511          }
512  
513          /// <summary>
514          /// Enqueues a program for validation, which will check if the program was compiled successfully.
515          /// </summary>
516          /// <param name="newEntry">Program entry to be validated</param>
517          private void EnqueueForValidation(ProgramEntry newEntry)
518          {
519              _validationQueue.Enqueue(newEntry);
520  
521              // Do not allow more than N shader compilation in-flight, where N is the maximum number of threads
522              // the driver will be using for parallel compilation.
523              // Submitting more seems to cause NVIDIA OpenGL driver to crash.
524              if (_validationQueue.Count >= _backendParallelCompileThreads && _validationQueue.TryDequeue(out ProgramEntry entry))
525              {
526                  ProcessCompiledProgram(ref entry, entry.CachedProgram.HostProgram.CheckProgramLink(true), asyncCompile: false);
527              }
528          }
529  
530          /// <summary>
531          /// Processses the queue of programs that should be translated from guest code.
532          /// </summary>
533          /// <param name="state">Cancellation token</param>
534          private void ProcessAsyncQueue(object state)
535          {
536              CancellationToken ct = (CancellationToken)state;
537  
538              try
539              {
540                  foreach (AsyncProgramTranslation asyncCompilation in _asyncTranslationQueue.GetConsumingEnumerable(ct))
541                  {
542                      RecompileFromGuestCode(
543                          asyncCompilation.GuestShaders,
544                          asyncCompilation.SpecializationState,
545                          asyncCompilation.ProgramIndex,
546                          asyncCompilation.IsCompute);
547                  }
548              }
549              catch (OperationCanceledException)
550              {
551              }
552          }
553  
554          /// <summary>
555          /// Recompiles a program from guest code.
556          /// </summary>
557          /// <param name="guestShaders">Guest code for each active stage</param>
558          /// <param name="specState">Specialization state</param>
559          /// <param name="programIndex">Program index</param>
560          /// <param name="isCompute">Indicates if the program is a compute shader</param>
561          private void RecompileFromGuestCode(GuestCodeAndCbData?[] guestShaders, ShaderSpecializationState specState, int programIndex, bool isCompute)
562          {
563              try
564              {
565                  if (isCompute)
566                  {
567                      RecompileComputeFromGuestCode(guestShaders, specState, programIndex);
568                  }
569                  else
570                  {
571                      RecompileGraphicsFromGuestCode(guestShaders, specState, programIndex);
572                  }
573              }
574              catch (Exception exception)
575              {
576                  Logger.Error?.Print(LogClass.Gpu, $"Error translating guest shader. {exception.Message}");
577  
578                  ErrorCount++;
579                  SignalCompiled();
580              }
581          }
582  
583          /// <summary>
584          /// Recompiles a graphics program from guest code.
585          /// </summary>
586          /// <param name="guestShaders">Guest code for each active stage</param>
587          /// <param name="specState">Specialization state</param>
588          /// <param name="programIndex">Program index</param>
589          private void RecompileGraphicsFromGuestCode(GuestCodeAndCbData?[] guestShaders, ShaderSpecializationState specState, int programIndex)
590          {
591              ShaderSpecializationState newSpecState = new(
592                  ref specState.GraphicsState,
593                  specState.PipelineState,
594                  specState.TransformFeedbackDescriptors);
595  
596              ResourceCounts counts = new();
597  
598              DiskCacheGpuAccessor[] gpuAccessors = new DiskCacheGpuAccessor[Constants.ShaderStages];
599              TranslatorContext[] translatorContexts = new TranslatorContext[Constants.ShaderStages + 1];
600              TranslatorContext nextStage = null;
601  
602              TargetApi api = _context.Capabilities.Api;
603  
604              bool hasCachedGs = guestShaders[4].HasValue;
605  
606              for (int stageIndex = Constants.ShaderStages - 1; stageIndex >= 0; stageIndex--)
607              {
608                  if (guestShaders[stageIndex + 1].HasValue)
609                  {
610                      GuestCodeAndCbData shader = guestShaders[stageIndex + 1].Value;
611  
612                      byte[] guestCode = shader.Code;
613                      byte[] cb1Data = shader.Cb1Data;
614  
615                      DiskCacheGpuAccessor gpuAccessor = new(_context, guestCode, cb1Data, specState, newSpecState, counts, stageIndex, hasCachedGs);
616                      TranslatorContext currentStage = DecodeGraphicsShader(gpuAccessor, api, DefaultFlags, 0);
617  
618                      if (nextStage != null)
619                      {
620                          currentStage.SetNextStage(nextStage);
621                      }
622  
623                      if (stageIndex == 0 && guestShaders[0].HasValue)
624                      {
625                          byte[] guestCodeA = guestShaders[0].Value.Code;
626                          byte[] cb1DataA = guestShaders[0].Value.Cb1Data;
627  
628                          DiskCacheGpuAccessor gpuAccessorA = new(_context, guestCodeA, cb1DataA, specState, newSpecState, counts, 0, hasCachedGs);
629                          translatorContexts[0] = DecodeGraphicsShader(gpuAccessorA, api, DefaultFlags | TranslationFlags.VertexA, 0);
630                      }
631  
632                      gpuAccessors[stageIndex] = gpuAccessor;
633                      translatorContexts[stageIndex + 1] = currentStage;
634                      nextStage = currentStage;
635                  }
636              }
637  
638              bool hasGeometryShader = translatorContexts[4] != null;
639              bool vertexHasStore = translatorContexts[1] != null && translatorContexts[1].HasStore;
640              bool geometryHasStore = hasGeometryShader && translatorContexts[4].HasStore;
641              bool vertexToCompute = ShouldConvertVertexToCompute(_context, vertexHasStore, geometryHasStore, hasGeometryShader);
642  
643              // We don't support caching shader stages that have been converted to compute currently,
644              // so just eliminate them if they exist in the cache.
645              if (vertexToCompute)
646              {
647                  return;
648              }
649  
650              CachedShaderStage[] shaders = new CachedShaderStage[guestShaders.Length];
651              List<ShaderProgram> translatedStages = new();
652  
653              TranslatorContext previousStage = null;
654  
655              for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++)
656              {
657                  TranslatorContext currentStage = translatorContexts[stageIndex + 1];
658  
659                  if (currentStage != null)
660                  {
661                      gpuAccessors[stageIndex].InitializeReservedCounts(specState.TransformFeedbackDescriptors != null, vertexToCompute);
662  
663                      ShaderProgram program;
664  
665                      byte[] guestCode = guestShaders[stageIndex + 1].Value.Code;
666                      byte[] cb1Data = guestShaders[stageIndex + 1].Value.Cb1Data;
667  
668                      if (stageIndex == 0 && guestShaders[0].HasValue)
669                      {
670                          program = currentStage.Translate(translatorContexts[0]);
671  
672                          byte[] guestCodeA = guestShaders[0].Value.Code;
673                          byte[] cb1DataA = guestShaders[0].Value.Cb1Data;
674  
675                          shaders[0] = new CachedShaderStage(null, guestCodeA, cb1DataA);
676                          shaders[1] = new CachedShaderStage(program.Info, guestCode, cb1Data);
677                      }
678                      else
679                      {
680                          program = currentStage.Translate();
681  
682                          shaders[stageIndex + 1] = new CachedShaderStage(program.Info, guestCode, cb1Data);
683                      }
684  
685                      if (program != null)
686                      {
687                          translatedStages.Add(program);
688                      }
689  
690                      previousStage = currentStage;
691                  }
692                  else if (
693                      previousStage != null &&
694                      previousStage.LayerOutputWritten &&
695                      stageIndex == 3 &&
696                      !_context.Capabilities.SupportsLayerVertexTessellation)
697                  {
698                      translatedStages.Add(previousStage.GenerateGeometryPassthrough());
699                  }
700              }
701  
702              _compilationQueue.Enqueue(new ProgramCompilation(translatedStages.ToArray(), shaders, newSpecState, programIndex, isCompute: false));
703          }
704  
705          /// <summary>
706          /// Recompiles a compute program from guest code.
707          /// </summary>
708          /// <param name="guestShaders">Guest code for each active stage</param>
709          /// <param name="specState">Specialization state</param>
710          /// <param name="programIndex">Program index</param>
711          private void RecompileComputeFromGuestCode(GuestCodeAndCbData?[] guestShaders, ShaderSpecializationState specState, int programIndex)
712          {
713              GuestCodeAndCbData shader = guestShaders[0].Value;
714              ResourceCounts counts = new();
715              ShaderSpecializationState newSpecState = new(ref specState.ComputeState);
716              DiskCacheGpuAccessor gpuAccessor = new(_context, shader.Code, shader.Cb1Data, specState, newSpecState, counts, 0, false);
717              gpuAccessor.InitializeReservedCounts(tfEnabled: false, vertexAsCompute: false);
718  
719              TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, _context.Capabilities.Api, 0);
720  
721              ShaderProgram program = translatorContext.Translate();
722  
723              CachedShaderStage[] shaders = new[] { new CachedShaderStage(program.Info, shader.Code, shader.Cb1Data) };
724  
725              _compilationQueue.Enqueue(new ProgramCompilation(new[] { program }, shaders, newSpecState, programIndex, isCompute: true));
726          }
727  
728          /// <summary>
729          /// Signals that compilation of a program has been finished successfully,
730          /// or that it failed and guest recompilation has also been attempted.
731          /// </summary>
732          private void SignalCompiled()
733          {
734              _stateChangeCallback(ShaderCacheState.Loading, ++_compiledCount, _totalCount);
735          }
736      }
737  }