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 }