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 }