ShaderCollection.cs
1 using Ryujinx.Common.Logging; 2 using Ryujinx.Graphics.GAL; 3 using Silk.NET.Vulkan; 4 using System; 5 using System.Collections.Generic; 6 using System.Collections.ObjectModel; 7 using System.Linq; 8 using System.Threading.Tasks; 9 10 namespace Ryujinx.Graphics.Vulkan 11 { 12 class ShaderCollection : IProgram 13 { 14 private readonly PipelineShaderStageCreateInfo[] _infos; 15 private readonly Shader[] _shaders; 16 17 private readonly PipelineLayoutCacheEntry _plce; 18 19 public PipelineLayout PipelineLayout => _plce.PipelineLayout; 20 21 public bool HasMinimalLayout { get; } 22 public bool UsePushDescriptors { get; } 23 public bool IsCompute { get; } 24 public bool HasTessellationControlShader => (Stages & (1u << 3)) != 0; 25 26 public bool UpdateTexturesWithoutTemplate { get; } 27 28 public uint Stages { get; } 29 30 public PipelineStageFlags IncoherentBufferWriteStages { get; } 31 public PipelineStageFlags IncoherentTextureWriteStages { get; } 32 33 public ResourceBindingSegment[][] ClearSegments { get; } 34 public ResourceBindingSegment[][] BindingSegments { get; } 35 public DescriptorSetTemplate[] Templates { get; } 36 37 public ProgramLinkStatus LinkStatus { get; private set; } 38 39 public readonly SpecDescription[] SpecDescriptions; 40 41 public bool IsLinked 42 { 43 get 44 { 45 if (LinkStatus == ProgramLinkStatus.Incomplete) 46 { 47 CheckProgramLink(true); 48 } 49 50 return LinkStatus == ProgramLinkStatus.Success; 51 } 52 } 53 54 private HashTableSlim<PipelineUid, Auto<DisposablePipeline>> _graphicsPipelineCache; 55 private HashTableSlim<SpecData, Auto<DisposablePipeline>> _computePipelineCache; 56 57 private readonly VulkanRenderer _gd; 58 private Device _device; 59 private bool _initialized; 60 61 private ProgramPipelineState _state; 62 private DisposableRenderPass _dummyRenderPass; 63 private readonly Task _compileTask; 64 private bool _firstBackgroundUse; 65 66 public ShaderCollection( 67 VulkanRenderer gd, 68 Device device, 69 ShaderSource[] shaders, 70 ResourceLayout resourceLayout, 71 SpecDescription[] specDescription = null, 72 bool isMinimal = false) 73 { 74 _gd = gd; 75 _device = device; 76 77 if (specDescription != null && specDescription.Length != shaders.Length) 78 { 79 throw new ArgumentException($"{nameof(specDescription)} array length must match {nameof(shaders)} array if provided"); 80 } 81 82 gd.Shaders.Add(this); 83 84 var internalShaders = new Shader[shaders.Length]; 85 86 _infos = new PipelineShaderStageCreateInfo[shaders.Length]; 87 88 SpecDescriptions = specDescription; 89 90 LinkStatus = ProgramLinkStatus.Incomplete; 91 92 uint stages = 0; 93 94 for (int i = 0; i < shaders.Length; i++) 95 { 96 var shader = new Shader(gd.Api, device, shaders[i]); 97 98 stages |= 1u << shader.StageFlags switch 99 { 100 ShaderStageFlags.FragmentBit => 1, 101 ShaderStageFlags.GeometryBit => 2, 102 ShaderStageFlags.TessellationControlBit => 3, 103 ShaderStageFlags.TessellationEvaluationBit => 4, 104 _ => 0, 105 }; 106 107 if (shader.StageFlags == ShaderStageFlags.ComputeBit) 108 { 109 IsCompute = true; 110 } 111 112 internalShaders[i] = shader; 113 } 114 115 _shaders = internalShaders; 116 117 bool usePushDescriptors = !isMinimal && 118 VulkanConfiguration.UsePushDescriptors && 119 _gd.Capabilities.SupportsPushDescriptors && 120 !IsCompute && 121 !HasPushDescriptorsBug(gd) && 122 CanUsePushDescriptors(gd, resourceLayout, IsCompute); 123 124 ReadOnlyCollection<ResourceDescriptorCollection> sets = usePushDescriptors ? 125 BuildPushDescriptorSets(gd, resourceLayout.Sets) : resourceLayout.Sets; 126 127 _plce = gd.PipelineLayoutCache.GetOrCreate(gd, device, sets, usePushDescriptors); 128 129 HasMinimalLayout = isMinimal; 130 UsePushDescriptors = usePushDescriptors; 131 132 Stages = stages; 133 134 ClearSegments = BuildClearSegments(sets); 135 BindingSegments = BuildBindingSegments(resourceLayout.SetUsages, out bool usesBufferTextures); 136 Templates = BuildTemplates(usePushDescriptors); 137 (IncoherentBufferWriteStages, IncoherentTextureWriteStages) = BuildIncoherentStages(resourceLayout.SetUsages); 138 139 // Updating buffer texture bindings using template updates crashes the Adreno driver on Windows. 140 UpdateTexturesWithoutTemplate = gd.IsQualcommProprietary && usesBufferTextures; 141 142 _compileTask = Task.CompletedTask; 143 _firstBackgroundUse = false; 144 } 145 146 public ShaderCollection( 147 VulkanRenderer gd, 148 Device device, 149 ShaderSource[] sources, 150 ResourceLayout resourceLayout, 151 ProgramPipelineState state, 152 bool fromCache) : this(gd, device, sources, resourceLayout) 153 { 154 _state = state; 155 156 _compileTask = BackgroundCompilation(); 157 _firstBackgroundUse = !fromCache; 158 } 159 160 private static bool HasPushDescriptorsBug(VulkanRenderer gd) 161 { 162 // Those GPUs/drivers do not work properly with push descriptors, so we must force disable them. 163 return gd.IsNvidiaPreTuring || (gd.IsIntelArc && gd.IsIntelWindows); 164 } 165 166 private static bool CanUsePushDescriptors(VulkanRenderer gd, ResourceLayout layout, bool isCompute) 167 { 168 // If binding 3 is immediately used, use an alternate set of reserved bindings. 169 ReadOnlyCollection<ResourceUsage> uniformUsage = layout.SetUsages[0].Usages; 170 bool hasBinding3 = uniformUsage.Any(x => x.Binding == 3); 171 int[] reserved = isCompute ? Array.Empty<int>() : gd.GetPushDescriptorReservedBindings(hasBinding3); 172 173 // Can't use any of the reserved usages. 174 for (int i = 0; i < uniformUsage.Count; i++) 175 { 176 var binding = uniformUsage[i].Binding; 177 178 if (reserved.Contains(binding) || 179 binding >= Constants.MaxPushDescriptorBinding || 180 binding >= gd.Capabilities.MaxPushDescriptors + reserved.Count(id => id < binding)) 181 { 182 return false; 183 } 184 } 185 186 return true; 187 } 188 189 private static ReadOnlyCollection<ResourceDescriptorCollection> BuildPushDescriptorSets( 190 VulkanRenderer gd, 191 ReadOnlyCollection<ResourceDescriptorCollection> sets) 192 { 193 // The reserved bindings were selected when determining if push descriptors could be used. 194 int[] reserved = gd.GetPushDescriptorReservedBindings(false); 195 196 var result = new ResourceDescriptorCollection[sets.Count]; 197 198 for (int i = 0; i < sets.Count; i++) 199 { 200 if (i == 0) 201 { 202 // Push descriptors apply here. Remove reserved bindings. 203 ResourceDescriptorCollection original = sets[i]; 204 205 var pdUniforms = new ResourceDescriptor[original.Descriptors.Count]; 206 int j = 0; 207 208 foreach (ResourceDescriptor descriptor in original.Descriptors) 209 { 210 if (reserved.Contains(descriptor.Binding)) 211 { 212 // If the binding is reserved, set its descriptor count to 0. 213 pdUniforms[j++] = new ResourceDescriptor( 214 descriptor.Binding, 215 0, 216 descriptor.Type, 217 descriptor.Stages); 218 } 219 else 220 { 221 pdUniforms[j++] = descriptor; 222 } 223 } 224 225 result[i] = new ResourceDescriptorCollection(new(pdUniforms)); 226 } 227 else 228 { 229 result[i] = sets[i]; 230 } 231 } 232 233 return new(result); 234 } 235 236 private static ResourceBindingSegment[][] BuildClearSegments(ReadOnlyCollection<ResourceDescriptorCollection> sets) 237 { 238 ResourceBindingSegment[][] segments = new ResourceBindingSegment[sets.Count][]; 239 240 for (int setIndex = 0; setIndex < sets.Count; setIndex++) 241 { 242 List<ResourceBindingSegment> currentSegments = new(); 243 244 ResourceDescriptor currentDescriptor = default; 245 int currentCount = 0; 246 247 for (int index = 0; index < sets[setIndex].Descriptors.Count; index++) 248 { 249 ResourceDescriptor descriptor = sets[setIndex].Descriptors[index]; 250 251 if (currentDescriptor.Binding + currentCount != descriptor.Binding || 252 currentDescriptor.Type != descriptor.Type || 253 currentDescriptor.Stages != descriptor.Stages || 254 currentDescriptor.Count > 1 || 255 descriptor.Count > 1) 256 { 257 if (currentCount != 0) 258 { 259 currentSegments.Add(new ResourceBindingSegment( 260 currentDescriptor.Binding, 261 currentCount, 262 currentDescriptor.Type, 263 currentDescriptor.Stages, 264 currentDescriptor.Count > 1)); 265 } 266 267 currentDescriptor = descriptor; 268 currentCount = descriptor.Count; 269 } 270 else 271 { 272 currentCount += descriptor.Count; 273 } 274 } 275 276 if (currentCount != 0) 277 { 278 currentSegments.Add(new ResourceBindingSegment( 279 currentDescriptor.Binding, 280 currentCount, 281 currentDescriptor.Type, 282 currentDescriptor.Stages, 283 currentDescriptor.Count > 1)); 284 } 285 286 segments[setIndex] = currentSegments.ToArray(); 287 } 288 289 return segments; 290 } 291 292 private static ResourceBindingSegment[][] BuildBindingSegments(ReadOnlyCollection<ResourceUsageCollection> setUsages, out bool usesBufferTextures) 293 { 294 usesBufferTextures = false; 295 296 ResourceBindingSegment[][] segments = new ResourceBindingSegment[setUsages.Count][]; 297 298 for (int setIndex = 0; setIndex < setUsages.Count; setIndex++) 299 { 300 List<ResourceBindingSegment> currentSegments = new(); 301 302 ResourceUsage currentUsage = default; 303 int currentCount = 0; 304 305 for (int index = 0; index < setUsages[setIndex].Usages.Count; index++) 306 { 307 ResourceUsage usage = setUsages[setIndex].Usages[index]; 308 309 if (usage.Type == ResourceType.BufferTexture) 310 { 311 usesBufferTextures = true; 312 } 313 314 if (currentUsage.Binding + currentCount != usage.Binding || 315 currentUsage.Type != usage.Type || 316 currentUsage.Stages != usage.Stages || 317 currentUsage.ArrayLength > 1 || 318 usage.ArrayLength > 1) 319 { 320 if (currentCount != 0) 321 { 322 currentSegments.Add(new ResourceBindingSegment( 323 currentUsage.Binding, 324 currentCount, 325 currentUsage.Type, 326 currentUsage.Stages, 327 currentUsage.ArrayLength > 1)); 328 } 329 330 currentUsage = usage; 331 currentCount = usage.ArrayLength; 332 } 333 else 334 { 335 currentCount++; 336 } 337 } 338 339 if (currentCount != 0) 340 { 341 currentSegments.Add(new ResourceBindingSegment( 342 currentUsage.Binding, 343 currentCount, 344 currentUsage.Type, 345 currentUsage.Stages, 346 currentUsage.ArrayLength > 1)); 347 } 348 349 segments[setIndex] = currentSegments.ToArray(); 350 } 351 352 return segments; 353 } 354 355 private DescriptorSetTemplate[] BuildTemplates(bool usePushDescriptors) 356 { 357 var templates = new DescriptorSetTemplate[BindingSegments.Length]; 358 359 for (int setIndex = 0; setIndex < BindingSegments.Length; setIndex++) 360 { 361 if (usePushDescriptors && setIndex == 0) 362 { 363 // Push descriptors get updated using templates owned by the pipeline layout. 364 continue; 365 } 366 367 ResourceBindingSegment[] segments = BindingSegments[setIndex]; 368 369 if (segments != null && segments.Length > 0) 370 { 371 templates[setIndex] = new DescriptorSetTemplate( 372 _gd, 373 _device, 374 segments, 375 _plce, 376 IsCompute ? PipelineBindPoint.Compute : PipelineBindPoint.Graphics, 377 setIndex); 378 } 379 } 380 381 return templates; 382 } 383 384 private PipelineStageFlags GetPipelineStages(ResourceStages stages) 385 { 386 PipelineStageFlags result = 0; 387 388 if ((stages & ResourceStages.Compute) != 0) 389 { 390 result |= PipelineStageFlags.ComputeShaderBit; 391 } 392 393 if ((stages & ResourceStages.Vertex) != 0) 394 { 395 result |= PipelineStageFlags.VertexShaderBit; 396 } 397 398 if ((stages & ResourceStages.Fragment) != 0) 399 { 400 result |= PipelineStageFlags.FragmentShaderBit; 401 } 402 403 if ((stages & ResourceStages.Geometry) != 0) 404 { 405 result |= PipelineStageFlags.GeometryShaderBit; 406 } 407 408 if ((stages & ResourceStages.TessellationControl) != 0) 409 { 410 result |= PipelineStageFlags.TessellationControlShaderBit; 411 } 412 413 if ((stages & ResourceStages.TessellationEvaluation) != 0) 414 { 415 result |= PipelineStageFlags.TessellationEvaluationShaderBit; 416 } 417 418 return result; 419 } 420 421 private (PipelineStageFlags Buffer, PipelineStageFlags Texture) BuildIncoherentStages(ReadOnlyCollection<ResourceUsageCollection> setUsages) 422 { 423 PipelineStageFlags buffer = PipelineStageFlags.None; 424 PipelineStageFlags texture = PipelineStageFlags.None; 425 426 foreach (var set in setUsages) 427 { 428 foreach (var range in set.Usages) 429 { 430 if (range.Write) 431 { 432 PipelineStageFlags stages = GetPipelineStages(range.Stages); 433 434 switch (range.Type) 435 { 436 case ResourceType.Image: 437 texture |= stages; 438 break; 439 case ResourceType.StorageBuffer: 440 case ResourceType.BufferImage: 441 buffer |= stages; 442 break; 443 } 444 } 445 } 446 } 447 448 return (buffer, texture); 449 } 450 451 private async Task BackgroundCompilation() 452 { 453 await Task.WhenAll(_shaders.Select(shader => shader.CompileTask)); 454 455 if (Array.Exists(_shaders, shader => shader.CompileStatus == ProgramLinkStatus.Failure)) 456 { 457 LinkStatus = ProgramLinkStatus.Failure; 458 459 return; 460 } 461 462 try 463 { 464 if (IsCompute) 465 { 466 CreateBackgroundComputePipeline(); 467 } 468 else 469 { 470 CreateBackgroundGraphicsPipeline(); 471 } 472 } 473 catch (VulkanException e) 474 { 475 Logger.Error?.PrintMsg(LogClass.Gpu, $"Background Compilation failed: {e.Message}"); 476 477 LinkStatus = ProgramLinkStatus.Failure; 478 } 479 } 480 481 private void EnsureShadersReady() 482 { 483 if (!_initialized) 484 { 485 CheckProgramLink(true); 486 487 ProgramLinkStatus resultStatus = ProgramLinkStatus.Success; 488 489 for (int i = 0; i < _shaders.Length; i++) 490 { 491 var shader = _shaders[i]; 492 493 if (shader.CompileStatus != ProgramLinkStatus.Success) 494 { 495 resultStatus = ProgramLinkStatus.Failure; 496 } 497 498 _infos[i] = shader.GetInfo(); 499 } 500 501 // If the link status was already set as failure by background compilation, prefer that decision. 502 if (LinkStatus != ProgramLinkStatus.Failure) 503 { 504 LinkStatus = resultStatus; 505 } 506 507 _initialized = true; 508 } 509 } 510 511 public PipelineShaderStageCreateInfo[] GetInfos() 512 { 513 EnsureShadersReady(); 514 515 return _infos; 516 } 517 518 protected DisposableRenderPass CreateDummyRenderPass() 519 { 520 if (_dummyRenderPass.Value.Handle != 0) 521 { 522 return _dummyRenderPass; 523 } 524 525 return _dummyRenderPass = _state.ToRenderPass(_gd, _device); 526 } 527 528 public void CreateBackgroundComputePipeline() 529 { 530 PipelineState pipeline = new(); 531 pipeline.Initialize(); 532 533 pipeline.Stages[0] = _shaders[0].GetInfo(); 534 pipeline.StagesCount = 1; 535 pipeline.PipelineLayout = PipelineLayout; 536 537 pipeline.CreateComputePipeline(_gd, _device, this, (_gd.Pipeline as PipelineBase).PipelineCache); 538 pipeline.Dispose(); 539 } 540 541 public void CreateBackgroundGraphicsPipeline() 542 { 543 // To compile shaders in the background in Vulkan, we need to create valid pipelines using the shader modules. 544 // The GPU provides pipeline state via the GAL that can be converted into our internal Vulkan pipeline state. 545 // This should match the pipeline state at the time of the first draw. If it doesn't, then it'll likely be 546 // close enough that the GPU driver will reuse the compiled shader for the different state. 547 548 // First, we need to create a render pass object compatible with the one that will be used at runtime. 549 // The active attachment formats have been provided by the abstraction layer. 550 var renderPass = CreateDummyRenderPass(); 551 552 PipelineState pipeline = _state.ToVulkanPipelineState(_gd); 553 554 // Copy the shader stage info to the pipeline. 555 var stages = pipeline.Stages.AsSpan(); 556 557 for (int i = 0; i < _shaders.Length; i++) 558 { 559 stages[i] = _shaders[i].GetInfo(); 560 } 561 562 pipeline.HasTessellationControlShader = HasTessellationControlShader; 563 pipeline.StagesCount = (uint)_shaders.Length; 564 pipeline.PipelineLayout = PipelineLayout; 565 566 pipeline.CreateGraphicsPipeline(_gd, _device, this, (_gd.Pipeline as PipelineBase).PipelineCache, renderPass.Value, throwOnError: true); 567 pipeline.Dispose(); 568 } 569 570 public ProgramLinkStatus CheckProgramLink(bool blocking) 571 { 572 if (LinkStatus == ProgramLinkStatus.Incomplete) 573 { 574 ProgramLinkStatus resultStatus = ProgramLinkStatus.Success; 575 576 foreach (Shader shader in _shaders) 577 { 578 if (shader.CompileStatus == ProgramLinkStatus.Incomplete) 579 { 580 if (blocking) 581 { 582 // Wait for this shader to finish compiling. 583 shader.WaitForCompile(); 584 585 if (shader.CompileStatus != ProgramLinkStatus.Success) 586 { 587 resultStatus = ProgramLinkStatus.Failure; 588 } 589 } 590 else 591 { 592 return ProgramLinkStatus.Incomplete; 593 } 594 } 595 } 596 597 if (!_compileTask.IsCompleted) 598 { 599 if (blocking) 600 { 601 _compileTask.Wait(); 602 603 if (LinkStatus == ProgramLinkStatus.Failure) 604 { 605 return ProgramLinkStatus.Failure; 606 } 607 } 608 else 609 { 610 return ProgramLinkStatus.Incomplete; 611 } 612 } 613 614 return resultStatus; 615 } 616 617 return LinkStatus; 618 } 619 620 public byte[] GetBinary() 621 { 622 return null; 623 } 624 625 public DescriptorSetTemplate GetPushDescriptorTemplate(long updateMask) 626 { 627 return _plce.GetPushDescriptorTemplate(IsCompute ? PipelineBindPoint.Compute : PipelineBindPoint.Graphics, updateMask); 628 } 629 630 public void AddComputePipeline(ref SpecData key, Auto<DisposablePipeline> pipeline) 631 { 632 (_computePipelineCache ??= new()).Add(ref key, pipeline); 633 } 634 635 public void AddGraphicsPipeline(ref PipelineUid key, Auto<DisposablePipeline> pipeline) 636 { 637 (_graphicsPipelineCache ??= new()).Add(ref key, pipeline); 638 } 639 640 public bool TryGetComputePipeline(ref SpecData key, out Auto<DisposablePipeline> pipeline) 641 { 642 if (_computePipelineCache == null) 643 { 644 pipeline = default; 645 return false; 646 } 647 648 if (_computePipelineCache.TryGetValue(ref key, out pipeline)) 649 { 650 return true; 651 } 652 653 return false; 654 } 655 656 public bool TryGetGraphicsPipeline(ref PipelineUid key, out Auto<DisposablePipeline> pipeline) 657 { 658 if (_graphicsPipelineCache == null) 659 { 660 pipeline = default; 661 return false; 662 } 663 664 if (!_graphicsPipelineCache.TryGetValue(ref key, out pipeline)) 665 { 666 if (_firstBackgroundUse) 667 { 668 Logger.Warning?.Print(LogClass.Gpu, "Background pipeline compile missed on draw - incorrect pipeline state?"); 669 _firstBackgroundUse = false; 670 } 671 672 return false; 673 } 674 675 _firstBackgroundUse = false; 676 677 return true; 678 } 679 680 public void UpdateDescriptorCacheCommandBufferIndex(int commandBufferIndex) 681 { 682 _plce.UpdateCommandBufferIndex(commandBufferIndex); 683 } 684 685 public Auto<DescriptorSetCollection> GetNewDescriptorSetCollection(int setIndex, out bool isNew) 686 { 687 return _plce.GetNewDescriptorSetCollection(setIndex, out isNew); 688 } 689 690 public Auto<DescriptorSetCollection> GetNewManualDescriptorSetCollection(CommandBufferScoped cbs, int setIndex, out int cacheIndex) 691 { 692 return _plce.GetNewManualDescriptorSetCollection(cbs, setIndex, out cacheIndex); 693 } 694 695 public void UpdateManualDescriptorSetCollectionOwnership(CommandBufferScoped cbs, int setIndex, int cacheIndex) 696 { 697 _plce.UpdateManualDescriptorSetCollectionOwnership(cbs, setIndex, cacheIndex); 698 } 699 700 public void ReleaseManualDescriptorSetCollection(int setIndex, int cacheIndex) 701 { 702 _plce.ReleaseManualDescriptorSetCollection(setIndex, cacheIndex); 703 } 704 705 public bool HasSameLayout(ShaderCollection other) 706 { 707 return other != null && _plce == other._plce; 708 } 709 710 protected virtual void Dispose(bool disposing) 711 { 712 if (disposing) 713 { 714 if (!_gd.Shaders.Remove(this)) 715 { 716 return; 717 } 718 719 for (int i = 0; i < _shaders.Length; i++) 720 { 721 _shaders[i].Dispose(); 722 } 723 724 if (_graphicsPipelineCache != null) 725 { 726 foreach (Auto<DisposablePipeline> pipeline in _graphicsPipelineCache.Values) 727 { 728 pipeline?.Dispose(); 729 } 730 } 731 732 if (_computePipelineCache != null) 733 { 734 foreach (Auto<DisposablePipeline> pipeline in _computePipelineCache.Values) 735 { 736 pipeline.Dispose(); 737 } 738 } 739 740 for (int i = 0; i < Templates.Length; i++) 741 { 742 Templates[i]?.Dispose(); 743 } 744 745 if (_dummyRenderPass.Value.Handle != 0) 746 { 747 _dummyRenderPass.Dispose(); 748 } 749 } 750 } 751 752 public void Dispose() 753 { 754 Dispose(true); 755 } 756 } 757 }