ShaderCollection.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  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.Linq;
  7. using System.Threading.Tasks;
  8. namespace Ryujinx.Graphics.Vulkan
  9. {
  10. class ShaderCollection : IProgram
  11. {
  12. private readonly PipelineShaderStageCreateInfo[] _infos;
  13. private readonly Shader[] _shaders;
  14. private readonly PipelineLayoutCacheEntry _plce;
  15. public PipelineLayout PipelineLayout => _plce.PipelineLayout;
  16. public bool HasMinimalLayout { get; }
  17. public bool UsePushDescriptors { get; }
  18. public uint Stages { get; }
  19. public int[][][] Bindings { get; }
  20. public ProgramLinkStatus LinkStatus { get; private set; }
  21. public bool IsLinked
  22. {
  23. get
  24. {
  25. if (LinkStatus == ProgramLinkStatus.Incomplete)
  26. {
  27. CheckProgramLink(true);
  28. }
  29. return LinkStatus == ProgramLinkStatus.Success;
  30. }
  31. }
  32. private HashTableSlim<PipelineUid, Auto<DisposablePipeline>> _graphicsPipelineCache;
  33. private Auto<DisposablePipeline> _computePipeline;
  34. private VulkanRenderer _gd;
  35. private Device _device;
  36. private bool _initialized;
  37. private bool _isCompute;
  38. private ProgramPipelineState _state;
  39. private DisposableRenderPass _dummyRenderPass;
  40. private Task _compileTask;
  41. private bool _firstBackgroundUse;
  42. public ShaderCollection(VulkanRenderer gd, Device device, ShaderSource[] shaders, bool isMinimal = false)
  43. {
  44. _gd = gd;
  45. _device = device;
  46. gd.Shaders.Add(this);
  47. var internalShaders = new Shader[shaders.Length];
  48. _infos = new PipelineShaderStageCreateInfo[shaders.Length];
  49. LinkStatus = ProgramLinkStatus.Incomplete;
  50. uint stages = 0;
  51. for (int i = 0; i < shaders.Length; i++)
  52. {
  53. var shader = new Shader(gd.Api, device, shaders[i]);
  54. stages |= 1u << shader.StageFlags switch
  55. {
  56. ShaderStageFlags.ShaderStageFragmentBit => 1,
  57. ShaderStageFlags.ShaderStageGeometryBit => 2,
  58. ShaderStageFlags.ShaderStageTessellationControlBit => 3,
  59. ShaderStageFlags.ShaderStageTessellationEvaluationBit => 4,
  60. _ => 0
  61. };
  62. if (shader.StageFlags == ShaderStageFlags.ShaderStageComputeBit)
  63. {
  64. _isCompute = true;
  65. }
  66. internalShaders[i] = shader;
  67. }
  68. _shaders = internalShaders;
  69. bool usePd = !isMinimal && VulkanConfiguration.UsePushDescriptors && _gd.Capabilities.SupportsPushDescriptors;
  70. _plce = isMinimal
  71. ? gd.PipelineLayoutCache.Create(gd, device, shaders)
  72. : gd.PipelineLayoutCache.GetOrCreate(gd, device, stages, usePd);
  73. HasMinimalLayout = isMinimal;
  74. UsePushDescriptors = usePd;
  75. Stages = stages;
  76. int[][] GrabAll(Func<ShaderBindings, IReadOnlyCollection<int>> selector)
  77. {
  78. bool hasAny = false;
  79. int[][] bindings = new int[internalShaders.Length][];
  80. for (int i = 0; i < internalShaders.Length; i++)
  81. {
  82. var collection = selector(internalShaders[i].Bindings);
  83. hasAny |= collection.Count != 0;
  84. bindings[i] = collection.ToArray();
  85. }
  86. return hasAny ? bindings : Array.Empty<int[]>();
  87. }
  88. Bindings = new[]
  89. {
  90. GrabAll(x => x.UniformBufferBindings),
  91. GrabAll(x => x.StorageBufferBindings),
  92. GrabAll(x => x.TextureBindings),
  93. GrabAll(x => x.ImageBindings)
  94. };
  95. _compileTask = Task.CompletedTask;
  96. _firstBackgroundUse = false;
  97. }
  98. public ShaderCollection(
  99. VulkanRenderer gd,
  100. Device device,
  101. ShaderSource[] sources,
  102. ProgramPipelineState state,
  103. bool fromCache) : this(gd, device, sources)
  104. {
  105. _state = state;
  106. _compileTask = BackgroundCompilation();
  107. _firstBackgroundUse = !fromCache;
  108. }
  109. private async Task BackgroundCompilation()
  110. {
  111. await Task.WhenAll(_shaders.Select(shader => shader.CompileTask));
  112. if (_shaders.Any(shader => shader.CompileStatus == ProgramLinkStatus.Failure))
  113. {
  114. LinkStatus = ProgramLinkStatus.Failure;
  115. return;
  116. }
  117. try
  118. {
  119. if (_isCompute)
  120. {
  121. CreateBackgroundComputePipeline();
  122. }
  123. else
  124. {
  125. CreateBackgroundGraphicsPipeline();
  126. }
  127. }
  128. catch (VulkanException e)
  129. {
  130. Logger.Error?.PrintMsg(LogClass.Gpu, $"Background Compilation failed: {e.Message}");
  131. LinkStatus = ProgramLinkStatus.Failure;
  132. }
  133. }
  134. private void EnsureShadersReady()
  135. {
  136. if (!_initialized)
  137. {
  138. CheckProgramLink(true);
  139. ProgramLinkStatus resultStatus = ProgramLinkStatus.Success;
  140. for (int i = 0; i < _shaders.Length; i++)
  141. {
  142. var shader = _shaders[i];
  143. if (shader.CompileStatus != ProgramLinkStatus.Success)
  144. {
  145. resultStatus = ProgramLinkStatus.Failure;
  146. }
  147. _infos[i] = shader.GetInfo();
  148. }
  149. // If the link status was already set as failure by background compilation, prefer that decision.
  150. if (LinkStatus != ProgramLinkStatus.Failure)
  151. {
  152. LinkStatus = resultStatus;
  153. }
  154. _initialized = true;
  155. }
  156. }
  157. public PipelineShaderStageCreateInfo[] GetInfos()
  158. {
  159. EnsureShadersReady();
  160. return _infos;
  161. }
  162. protected unsafe DisposableRenderPass CreateDummyRenderPass()
  163. {
  164. if (_dummyRenderPass.Value.Handle != 0)
  165. {
  166. return _dummyRenderPass;
  167. }
  168. return _dummyRenderPass = _state.ToRenderPass(_gd, _device);
  169. }
  170. public void CreateBackgroundComputePipeline()
  171. {
  172. PipelineState pipeline = new PipelineState();
  173. pipeline.Initialize();
  174. pipeline.Stages[0] = _shaders[0].GetInfo();
  175. pipeline.StagesCount = 1;
  176. pipeline.PipelineLayout = PipelineLayout;
  177. pipeline.CreateComputePipeline(_gd, _device, this, (_gd.Pipeline as PipelineBase).PipelineCache);
  178. pipeline.Dispose();
  179. }
  180. public void CreateBackgroundGraphicsPipeline()
  181. {
  182. // To compile shaders in the background in Vulkan, we need to create valid pipelines using the shader modules.
  183. // The GPU provides pipeline state via the GAL that can be converted into our internal Vulkan pipeline state.
  184. // This should match the pipeline state at the time of the first draw. If it doesn't, then it'll likely be
  185. // close enough that the GPU driver will reuse the compiled shader for the different state.
  186. // First, we need to create a render pass object compatible with the one that will be used at runtime.
  187. // The active attachment formats have been provided by the abstraction layer.
  188. var renderPass = CreateDummyRenderPass();
  189. PipelineState pipeline = _state.ToVulkanPipelineState(_gd);
  190. // Copy the shader stage info to the pipeline.
  191. var stages = pipeline.Stages.AsSpan();
  192. for (int i = 0; i < _shaders.Length; i++)
  193. {
  194. stages[i] = _shaders[i].GetInfo();
  195. }
  196. pipeline.StagesCount = (uint)_shaders.Length;
  197. pipeline.PipelineLayout = PipelineLayout;
  198. pipeline.CreateGraphicsPipeline(_gd, _device, this, (_gd.Pipeline as PipelineBase).PipelineCache, renderPass.Value);
  199. pipeline.Dispose();
  200. }
  201. public ProgramLinkStatus CheckProgramLink(bool blocking)
  202. {
  203. if (LinkStatus == ProgramLinkStatus.Incomplete)
  204. {
  205. ProgramLinkStatus resultStatus = ProgramLinkStatus.Success;
  206. foreach (Shader shader in _shaders)
  207. {
  208. if (shader.CompileStatus == ProgramLinkStatus.Incomplete)
  209. {
  210. if (blocking)
  211. {
  212. // Wait for this shader to finish compiling.
  213. shader.WaitForCompile();
  214. if (shader.CompileStatus != ProgramLinkStatus.Success)
  215. {
  216. resultStatus = ProgramLinkStatus.Failure;
  217. }
  218. }
  219. else
  220. {
  221. return ProgramLinkStatus.Incomplete;
  222. }
  223. }
  224. }
  225. if (!_compileTask.IsCompleted)
  226. {
  227. if (blocking)
  228. {
  229. _compileTask.Wait();
  230. if (LinkStatus == ProgramLinkStatus.Failure)
  231. {
  232. return ProgramLinkStatus.Failure;
  233. }
  234. }
  235. else
  236. {
  237. return ProgramLinkStatus.Incomplete;
  238. }
  239. }
  240. return resultStatus;
  241. }
  242. return LinkStatus;
  243. }
  244. public byte[] GetBinary()
  245. {
  246. return null;
  247. }
  248. public void AddComputePipeline(Auto<DisposablePipeline> pipeline)
  249. {
  250. _computePipeline = pipeline;
  251. }
  252. public void RemoveComputePipeline()
  253. {
  254. _computePipeline = null;
  255. }
  256. public void AddGraphicsPipeline(ref PipelineUid key, Auto<DisposablePipeline> pipeline)
  257. {
  258. (_graphicsPipelineCache ??= new()).Add(ref key, pipeline);
  259. }
  260. public bool TryGetComputePipeline(out Auto<DisposablePipeline> pipeline)
  261. {
  262. pipeline = _computePipeline;
  263. return pipeline != null;
  264. }
  265. public bool TryGetGraphicsPipeline(ref PipelineUid key, out Auto<DisposablePipeline> pipeline)
  266. {
  267. if (_graphicsPipelineCache == null)
  268. {
  269. pipeline = default;
  270. return false;
  271. }
  272. if (!_graphicsPipelineCache.TryGetValue(ref key, out pipeline))
  273. {
  274. if (_firstBackgroundUse)
  275. {
  276. Logger.Warning?.Print(LogClass.Gpu, "Background pipeline compile missed on draw - incorrect pipeline state?");
  277. _firstBackgroundUse = false;
  278. }
  279. return false;
  280. }
  281. _firstBackgroundUse = false;
  282. return true;
  283. }
  284. public Auto<DescriptorSetCollection> GetNewDescriptorSetCollection(
  285. VulkanRenderer gd,
  286. int commandBufferIndex,
  287. int setIndex,
  288. out bool isNew)
  289. {
  290. return _plce.GetNewDescriptorSetCollection(gd, commandBufferIndex, setIndex, out isNew);
  291. }
  292. protected virtual unsafe void Dispose(bool disposing)
  293. {
  294. if (disposing)
  295. {
  296. if (!_gd.Shaders.Remove(this))
  297. {
  298. return;
  299. }
  300. for (int i = 0; i < _shaders.Length; i++)
  301. {
  302. _shaders[i].Dispose();
  303. }
  304. if (_graphicsPipelineCache != null)
  305. {
  306. foreach (Auto<DisposablePipeline> pipeline in _graphicsPipelineCache.Values)
  307. {
  308. pipeline.Dispose();
  309. }
  310. }
  311. _computePipeline?.Dispose();
  312. if (_dummyRenderPass.Value.Handle != 0)
  313. {
  314. _dummyRenderPass.Dispose();
  315. }
  316. }
  317. }
  318. public void Dispose()
  319. {
  320. Dispose(true);
  321. }
  322. }
  323. }