ShaderCollection.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  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. namespace Ryujinx.Graphics.Vulkan
  10. {
  11. class ShaderCollection : IProgram
  12. {
  13. private readonly PipelineShaderStageCreateInfo[] _infos;
  14. private readonly Shader[] _shaders;
  15. private readonly PipelineLayoutCacheEntry _plce;
  16. public PipelineLayout PipelineLayout => _plce.PipelineLayout;
  17. public bool HasMinimalLayout { get; }
  18. public bool UsePushDescriptors { get; }
  19. public bool IsCompute { get; }
  20. public uint Stages { get; }
  21. public ResourceBindingSegment[][] ClearSegments { get; }
  22. public ResourceBindingSegment[][] BindingSegments { get; }
  23. public ProgramLinkStatus LinkStatus { get; private set; }
  24. public readonly SpecDescription[] SpecDescriptions;
  25. public bool IsLinked
  26. {
  27. get
  28. {
  29. if (LinkStatus == ProgramLinkStatus.Incomplete)
  30. {
  31. CheckProgramLink(true);
  32. }
  33. return LinkStatus == ProgramLinkStatus.Success;
  34. }
  35. }
  36. private HashTableSlim<PipelineUid, Auto<DisposablePipeline>> _graphicsPipelineCache;
  37. private HashTableSlim<SpecData, Auto<DisposablePipeline>> _computePipelineCache;
  38. private readonly VulkanRenderer _gd;
  39. private Device _device;
  40. private bool _initialized;
  41. private ProgramPipelineState _state;
  42. private DisposableRenderPass _dummyRenderPass;
  43. private readonly Task _compileTask;
  44. private bool _firstBackgroundUse;
  45. public ShaderCollection(
  46. VulkanRenderer gd,
  47. Device device,
  48. ShaderSource[] shaders,
  49. ResourceLayout resourceLayout,
  50. SpecDescription[] specDescription = null,
  51. bool isMinimal = false)
  52. {
  53. _gd = gd;
  54. _device = device;
  55. if (specDescription != null && specDescription.Length != shaders.Length)
  56. {
  57. throw new ArgumentException($"{nameof(specDescription)} array length must match {nameof(shaders)} array if provided");
  58. }
  59. gd.Shaders.Add(this);
  60. var internalShaders = new Shader[shaders.Length];
  61. _infos = new PipelineShaderStageCreateInfo[shaders.Length];
  62. SpecDescriptions = specDescription;
  63. LinkStatus = ProgramLinkStatus.Incomplete;
  64. uint stages = 0;
  65. for (int i = 0; i < shaders.Length; i++)
  66. {
  67. var shader = new Shader(gd.Api, device, shaders[i]);
  68. stages |= 1u << shader.StageFlags switch
  69. {
  70. ShaderStageFlags.FragmentBit => 1,
  71. ShaderStageFlags.GeometryBit => 2,
  72. ShaderStageFlags.TessellationControlBit => 3,
  73. ShaderStageFlags.TessellationEvaluationBit => 4,
  74. _ => 0,
  75. };
  76. if (shader.StageFlags == ShaderStageFlags.ComputeBit)
  77. {
  78. IsCompute = true;
  79. }
  80. internalShaders[i] = shader;
  81. }
  82. _shaders = internalShaders;
  83. bool usePushDescriptors = !isMinimal && VulkanConfiguration.UsePushDescriptors && _gd.Capabilities.SupportsPushDescriptors;
  84. _plce = gd.PipelineLayoutCache.GetOrCreate(gd, device, resourceLayout.Sets, usePushDescriptors);
  85. HasMinimalLayout = isMinimal;
  86. UsePushDescriptors = usePushDescriptors;
  87. Stages = stages;
  88. ClearSegments = BuildClearSegments(resourceLayout.Sets);
  89. BindingSegments = BuildBindingSegments(resourceLayout.SetUsages);
  90. _compileTask = Task.CompletedTask;
  91. _firstBackgroundUse = false;
  92. }
  93. public ShaderCollection(
  94. VulkanRenderer gd,
  95. Device device,
  96. ShaderSource[] sources,
  97. ResourceLayout resourceLayout,
  98. ProgramPipelineState state,
  99. bool fromCache) : this(gd, device, sources, resourceLayout)
  100. {
  101. _state = state;
  102. _compileTask = BackgroundCompilation();
  103. _firstBackgroundUse = !fromCache;
  104. }
  105. private static ResourceBindingSegment[][] BuildClearSegments(ReadOnlyCollection<ResourceDescriptorCollection> sets)
  106. {
  107. ResourceBindingSegment[][] segments = new ResourceBindingSegment[sets.Count][];
  108. for (int setIndex = 0; setIndex < sets.Count; setIndex++)
  109. {
  110. List<ResourceBindingSegment> currentSegments = new();
  111. ResourceDescriptor currentDescriptor = default;
  112. int currentCount = 0;
  113. for (int index = 0; index < sets[setIndex].Descriptors.Count; index++)
  114. {
  115. ResourceDescriptor descriptor = sets[setIndex].Descriptors[index];
  116. if (currentDescriptor.Binding + currentCount != descriptor.Binding ||
  117. currentDescriptor.Type != descriptor.Type ||
  118. currentDescriptor.Stages != descriptor.Stages)
  119. {
  120. if (currentCount != 0)
  121. {
  122. currentSegments.Add(new ResourceBindingSegment(
  123. currentDescriptor.Binding,
  124. currentCount,
  125. currentDescriptor.Type,
  126. currentDescriptor.Stages,
  127. ResourceAccess.ReadWrite));
  128. }
  129. currentDescriptor = descriptor;
  130. currentCount = descriptor.Count;
  131. }
  132. else
  133. {
  134. currentCount += descriptor.Count;
  135. }
  136. }
  137. if (currentCount != 0)
  138. {
  139. currentSegments.Add(new ResourceBindingSegment(
  140. currentDescriptor.Binding,
  141. currentCount,
  142. currentDescriptor.Type,
  143. currentDescriptor.Stages,
  144. ResourceAccess.ReadWrite));
  145. }
  146. segments[setIndex] = currentSegments.ToArray();
  147. }
  148. return segments;
  149. }
  150. private static ResourceBindingSegment[][] BuildBindingSegments(ReadOnlyCollection<ResourceUsageCollection> setUsages)
  151. {
  152. ResourceBindingSegment[][] segments = new ResourceBindingSegment[setUsages.Count][];
  153. for (int setIndex = 0; setIndex < setUsages.Count; setIndex++)
  154. {
  155. List<ResourceBindingSegment> currentSegments = new();
  156. ResourceUsage currentUsage = default;
  157. int currentCount = 0;
  158. for (int index = 0; index < setUsages[setIndex].Usages.Count; index++)
  159. {
  160. ResourceUsage usage = setUsages[setIndex].Usages[index];
  161. // If the resource is not accessed, we don't need to update it.
  162. if (usage.Access == ResourceAccess.None)
  163. {
  164. continue;
  165. }
  166. if (currentUsage.Binding + currentCount != usage.Binding ||
  167. currentUsage.Type != usage.Type ||
  168. currentUsage.Stages != usage.Stages ||
  169. currentUsage.Access != usage.Access)
  170. {
  171. if (currentCount != 0)
  172. {
  173. currentSegments.Add(new ResourceBindingSegment(
  174. currentUsage.Binding,
  175. currentCount,
  176. currentUsage.Type,
  177. currentUsage.Stages,
  178. currentUsage.Access));
  179. }
  180. currentUsage = usage;
  181. currentCount = 1;
  182. }
  183. else
  184. {
  185. currentCount++;
  186. }
  187. }
  188. if (currentCount != 0)
  189. {
  190. currentSegments.Add(new ResourceBindingSegment(
  191. currentUsage.Binding,
  192. currentCount,
  193. currentUsage.Type,
  194. currentUsage.Stages,
  195. currentUsage.Access));
  196. }
  197. segments[setIndex] = currentSegments.ToArray();
  198. }
  199. return segments;
  200. }
  201. private async Task BackgroundCompilation()
  202. {
  203. await Task.WhenAll(_shaders.Select(shader => shader.CompileTask));
  204. if (Array.Exists(_shaders, shader => shader.CompileStatus == ProgramLinkStatus.Failure))
  205. {
  206. LinkStatus = ProgramLinkStatus.Failure;
  207. return;
  208. }
  209. try
  210. {
  211. if (IsCompute)
  212. {
  213. CreateBackgroundComputePipeline();
  214. }
  215. else
  216. {
  217. CreateBackgroundGraphicsPipeline();
  218. }
  219. }
  220. catch (VulkanException e)
  221. {
  222. Logger.Error?.PrintMsg(LogClass.Gpu, $"Background Compilation failed: {e.Message}");
  223. LinkStatus = ProgramLinkStatus.Failure;
  224. }
  225. }
  226. private void EnsureShadersReady()
  227. {
  228. if (!_initialized)
  229. {
  230. CheckProgramLink(true);
  231. ProgramLinkStatus resultStatus = ProgramLinkStatus.Success;
  232. for (int i = 0; i < _shaders.Length; i++)
  233. {
  234. var shader = _shaders[i];
  235. if (shader.CompileStatus != ProgramLinkStatus.Success)
  236. {
  237. resultStatus = ProgramLinkStatus.Failure;
  238. }
  239. _infos[i] = shader.GetInfo();
  240. }
  241. // If the link status was already set as failure by background compilation, prefer that decision.
  242. if (LinkStatus != ProgramLinkStatus.Failure)
  243. {
  244. LinkStatus = resultStatus;
  245. }
  246. _initialized = true;
  247. }
  248. }
  249. public PipelineShaderStageCreateInfo[] GetInfos()
  250. {
  251. EnsureShadersReady();
  252. return _infos;
  253. }
  254. protected DisposableRenderPass CreateDummyRenderPass()
  255. {
  256. if (_dummyRenderPass.Value.Handle != 0)
  257. {
  258. return _dummyRenderPass;
  259. }
  260. return _dummyRenderPass = _state.ToRenderPass(_gd, _device);
  261. }
  262. public void CreateBackgroundComputePipeline()
  263. {
  264. PipelineState pipeline = new();
  265. pipeline.Initialize();
  266. pipeline.Stages[0] = _shaders[0].GetInfo();
  267. pipeline.StagesCount = 1;
  268. pipeline.PipelineLayout = PipelineLayout;
  269. pipeline.CreateComputePipeline(_gd, _device, this, (_gd.Pipeline as PipelineBase).PipelineCache);
  270. pipeline.Dispose();
  271. }
  272. public void CreateBackgroundGraphicsPipeline()
  273. {
  274. // To compile shaders in the background in Vulkan, we need to create valid pipelines using the shader modules.
  275. // The GPU provides pipeline state via the GAL that can be converted into our internal Vulkan pipeline state.
  276. // This should match the pipeline state at the time of the first draw. If it doesn't, then it'll likely be
  277. // close enough that the GPU driver will reuse the compiled shader for the different state.
  278. // First, we need to create a render pass object compatible with the one that will be used at runtime.
  279. // The active attachment formats have been provided by the abstraction layer.
  280. var renderPass = CreateDummyRenderPass();
  281. PipelineState pipeline = _state.ToVulkanPipelineState(_gd);
  282. // Copy the shader stage info to the pipeline.
  283. var stages = pipeline.Stages.AsSpan();
  284. for (int i = 0; i < _shaders.Length; i++)
  285. {
  286. stages[i] = _shaders[i].GetInfo();
  287. }
  288. pipeline.StagesCount = (uint)_shaders.Length;
  289. pipeline.PipelineLayout = PipelineLayout;
  290. pipeline.CreateGraphicsPipeline(_gd, _device, this, (_gd.Pipeline as PipelineBase).PipelineCache, renderPass.Value);
  291. pipeline.Dispose();
  292. }
  293. public ProgramLinkStatus CheckProgramLink(bool blocking)
  294. {
  295. if (LinkStatus == ProgramLinkStatus.Incomplete)
  296. {
  297. ProgramLinkStatus resultStatus = ProgramLinkStatus.Success;
  298. foreach (Shader shader in _shaders)
  299. {
  300. if (shader.CompileStatus == ProgramLinkStatus.Incomplete)
  301. {
  302. if (blocking)
  303. {
  304. // Wait for this shader to finish compiling.
  305. shader.WaitForCompile();
  306. if (shader.CompileStatus != ProgramLinkStatus.Success)
  307. {
  308. resultStatus = ProgramLinkStatus.Failure;
  309. }
  310. }
  311. else
  312. {
  313. return ProgramLinkStatus.Incomplete;
  314. }
  315. }
  316. }
  317. if (!_compileTask.IsCompleted)
  318. {
  319. if (blocking)
  320. {
  321. _compileTask.Wait();
  322. if (LinkStatus == ProgramLinkStatus.Failure)
  323. {
  324. return ProgramLinkStatus.Failure;
  325. }
  326. }
  327. else
  328. {
  329. return ProgramLinkStatus.Incomplete;
  330. }
  331. }
  332. return resultStatus;
  333. }
  334. return LinkStatus;
  335. }
  336. public byte[] GetBinary()
  337. {
  338. return null;
  339. }
  340. public void AddComputePipeline(ref SpecData key, Auto<DisposablePipeline> pipeline)
  341. {
  342. (_computePipelineCache ??= new()).Add(ref key, pipeline);
  343. }
  344. public void AddGraphicsPipeline(ref PipelineUid key, Auto<DisposablePipeline> pipeline)
  345. {
  346. (_graphicsPipelineCache ??= new()).Add(ref key, pipeline);
  347. }
  348. public bool TryGetComputePipeline(ref SpecData key, out Auto<DisposablePipeline> pipeline)
  349. {
  350. if (_computePipelineCache == null)
  351. {
  352. pipeline = default;
  353. return false;
  354. }
  355. if (_computePipelineCache.TryGetValue(ref key, out pipeline))
  356. {
  357. return true;
  358. }
  359. return false;
  360. }
  361. public bool TryGetGraphicsPipeline(ref PipelineUid key, out Auto<DisposablePipeline> pipeline)
  362. {
  363. if (_graphicsPipelineCache == null)
  364. {
  365. pipeline = default;
  366. return false;
  367. }
  368. if (!_graphicsPipelineCache.TryGetValue(ref key, out pipeline))
  369. {
  370. if (_firstBackgroundUse)
  371. {
  372. Logger.Warning?.Print(LogClass.Gpu, "Background pipeline compile missed on draw - incorrect pipeline state?");
  373. _firstBackgroundUse = false;
  374. }
  375. return false;
  376. }
  377. _firstBackgroundUse = false;
  378. return true;
  379. }
  380. public Auto<DescriptorSetCollection> GetNewDescriptorSetCollection(
  381. VulkanRenderer gd,
  382. int commandBufferIndex,
  383. int setIndex,
  384. out bool isNew)
  385. {
  386. return _plce.GetNewDescriptorSetCollection(gd, commandBufferIndex, setIndex, out isNew);
  387. }
  388. protected virtual void Dispose(bool disposing)
  389. {
  390. if (disposing)
  391. {
  392. if (!_gd.Shaders.Remove(this))
  393. {
  394. return;
  395. }
  396. for (int i = 0; i < _shaders.Length; i++)
  397. {
  398. _shaders[i].Dispose();
  399. }
  400. if (_graphicsPipelineCache != null)
  401. {
  402. foreach (Auto<DisposablePipeline> pipeline in _graphicsPipelineCache.Values)
  403. {
  404. pipeline.Dispose();
  405. }
  406. }
  407. if (_computePipelineCache != null)
  408. {
  409. foreach (Auto<DisposablePipeline> pipeline in _computePipelineCache.Values)
  410. {
  411. pipeline.Dispose();
  412. }
  413. }
  414. if (_dummyRenderPass.Value.Handle != 0)
  415. {
  416. _dummyRenderPass.Dispose();
  417. }
  418. }
  419. }
  420. public void Dispose()
  421. {
  422. Dispose(true);
  423. }
  424. }
  425. }