ShaderCache.cs 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853
  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. namespace Ryujinx.Graphics.Gpu.Shader
  16. {
  17. /// <summary>
  18. /// Memory cache of shader code.
  19. /// </summary>
  20. class ShaderCache : IDisposable
  21. {
  22. /// <summary>
  23. /// Default flags used on the shader translation process.
  24. /// </summary>
  25. public const TranslationFlags DefaultFlags = TranslationFlags.DebugMode;
  26. private readonly struct TranslatedShader
  27. {
  28. public readonly CachedShaderStage Shader;
  29. public readonly ShaderProgram Program;
  30. public TranslatedShader(CachedShaderStage shader, ShaderProgram program)
  31. {
  32. Shader = shader;
  33. Program = program;
  34. }
  35. }
  36. private readonly struct TranslatedShaderVertexPair
  37. {
  38. public readonly CachedShaderStage VertexA;
  39. public readonly CachedShaderStage VertexB;
  40. public readonly ShaderProgram Program;
  41. public TranslatedShaderVertexPair(CachedShaderStage vertexA, CachedShaderStage vertexB, ShaderProgram program)
  42. {
  43. VertexA = vertexA;
  44. VertexB = vertexB;
  45. Program = program;
  46. }
  47. }
  48. private readonly GpuContext _context;
  49. private readonly ShaderDumper _dumper;
  50. private readonly Dictionary<ulong, CachedShaderProgram> _cpPrograms;
  51. private readonly Dictionary<ShaderAddresses, CachedShaderProgram> _gpPrograms;
  52. private readonly struct ProgramToSave
  53. {
  54. public readonly CachedShaderProgram CachedProgram;
  55. public readonly IProgram HostProgram;
  56. public readonly byte[] BinaryCode;
  57. public ProgramToSave(CachedShaderProgram cachedProgram, IProgram hostProgram, byte[] binaryCode)
  58. {
  59. CachedProgram = cachedProgram;
  60. HostProgram = hostProgram;
  61. BinaryCode = binaryCode;
  62. }
  63. }
  64. private readonly Queue<ProgramToSave> _programsToSaveQueue;
  65. private readonly ComputeShaderCacheHashTable _computeShaderCache;
  66. private readonly ShaderCacheHashTable _graphicsShaderCache;
  67. private readonly DiskCacheHostStorage _diskCacheHostStorage;
  68. private readonly BackgroundDiskCacheWriter _cacheWriter;
  69. /// <summary>
  70. /// Event for signalling shader cache loading progress.
  71. /// </summary>
  72. public event Action<ShaderCacheState, int, int> ShaderCacheStateChanged;
  73. /// <summary>
  74. /// Creates a new instance of the shader cache.
  75. /// </summary>
  76. /// <param name="context">GPU context that the shader cache belongs to</param>
  77. public ShaderCache(GpuContext context)
  78. {
  79. _context = context;
  80. _dumper = new ShaderDumper();
  81. _cpPrograms = new Dictionary<ulong, CachedShaderProgram>();
  82. _gpPrograms = new Dictionary<ShaderAddresses, CachedShaderProgram>();
  83. _programsToSaveQueue = new Queue<ProgramToSave>();
  84. string diskCacheTitleId = GetDiskCachePath();
  85. _computeShaderCache = new ComputeShaderCacheHashTable();
  86. _graphicsShaderCache = new ShaderCacheHashTable();
  87. _diskCacheHostStorage = new DiskCacheHostStorage(diskCacheTitleId);
  88. if (_diskCacheHostStorage.CacheEnabled)
  89. {
  90. _cacheWriter = new BackgroundDiskCacheWriter(context, _diskCacheHostStorage);
  91. }
  92. }
  93. /// <summary>
  94. /// Gets the path where the disk cache for the current application is stored.
  95. /// </summary>
  96. private static string GetDiskCachePath()
  97. {
  98. return GraphicsConfig.EnableShaderCache && GraphicsConfig.TitleId != null
  99. ? Path.Combine(AppDataManager.GamesDirPath, GraphicsConfig.TitleId, "cache", "shader")
  100. : null;
  101. }
  102. /// <summary>
  103. /// Processes the queue of shaders that must save their binaries to the disk cache.
  104. /// </summary>
  105. public void ProcessShaderCacheQueue()
  106. {
  107. // Check to see if the binaries for previously compiled shaders are ready, and save them out.
  108. while (_programsToSaveQueue.TryPeek(out ProgramToSave programToSave))
  109. {
  110. ProgramLinkStatus result = programToSave.HostProgram.CheckProgramLink(false);
  111. if (result != ProgramLinkStatus.Incomplete)
  112. {
  113. if (result == ProgramLinkStatus.Success)
  114. {
  115. _cacheWriter.AddShader(programToSave.CachedProgram, programToSave.BinaryCode ?? programToSave.HostProgram.GetBinary());
  116. }
  117. _programsToSaveQueue.Dequeue();
  118. }
  119. else
  120. {
  121. break;
  122. }
  123. }
  124. }
  125. /// <summary>
  126. /// Initialize the cache.
  127. /// </summary>
  128. /// <param name="cancellationToken">Cancellation token to cancel the shader cache initialization process</param>
  129. internal void Initialize(CancellationToken cancellationToken)
  130. {
  131. if (_diskCacheHostStorage.CacheEnabled)
  132. {
  133. ParallelDiskCacheLoader loader = new(
  134. _context,
  135. _graphicsShaderCache,
  136. _computeShaderCache,
  137. _diskCacheHostStorage,
  138. ShaderCacheStateUpdate, cancellationToken);
  139. loader.LoadShaders();
  140. int errorCount = loader.ErrorCount;
  141. if (errorCount != 0)
  142. {
  143. Logger.Warning?.Print(LogClass.Gpu, $"Failed to load {errorCount} shaders from the disk cache.");
  144. }
  145. }
  146. }
  147. /// <summary>
  148. /// Shader cache state update handler.
  149. /// </summary>
  150. /// <param name="state">Current state of the shader cache load process</param>
  151. /// <param name="current">Number of the current shader being processed</param>
  152. /// <param name="total">Total number of shaders to process</param>
  153. private void ShaderCacheStateUpdate(ShaderCacheState state, int current, int total)
  154. {
  155. ShaderCacheStateChanged?.Invoke(state, current, total);
  156. }
  157. /// <summary>
  158. /// Gets a compute shader from the cache.
  159. /// </summary>
  160. /// <remarks>
  161. /// This automatically translates, compiles and adds the code to the cache if not present.
  162. /// </remarks>
  163. /// <param name="channel">GPU channel</param>
  164. /// <param name="poolState">Texture pool state</param>
  165. /// <param name="computeState">Compute engine state</param>
  166. /// <param name="gpuVa">GPU virtual address of the binary shader code</param>
  167. /// <returns>Compiled compute shader code</returns>
  168. public CachedShaderProgram GetComputeShader(
  169. GpuChannel channel,
  170. GpuChannelPoolState poolState,
  171. GpuChannelComputeState computeState,
  172. ulong gpuVa)
  173. {
  174. if (_cpPrograms.TryGetValue(gpuVa, out var cpShader) && IsShaderEqual(channel, poolState, computeState, cpShader, gpuVa))
  175. {
  176. return cpShader;
  177. }
  178. if (_computeShaderCache.TryFind(channel, poolState, computeState, gpuVa, out cpShader, out byte[] cachedGuestCode))
  179. {
  180. _cpPrograms[gpuVa] = cpShader;
  181. return cpShader;
  182. }
  183. ShaderSpecializationState specState = new(ref computeState);
  184. GpuAccessorState gpuAccessorState = new(poolState, computeState, default, specState);
  185. GpuAccessor gpuAccessor = new(_context, channel, gpuAccessorState);
  186. gpuAccessor.InitializeReservedCounts(tfEnabled: false, vertexAsCompute: false);
  187. TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, _context.Capabilities.Api, gpuVa);
  188. TranslatedShader translatedShader = TranslateShader(_dumper, channel, translatorContext, cachedGuestCode, asCompute: false);
  189. ShaderSource[] shaderSourcesArray = new ShaderSource[] { CreateShaderSource(translatedShader.Program) };
  190. ShaderInfo info = ShaderInfoBuilder.BuildForCompute(_context, translatedShader.Program.Info);
  191. IProgram hostProgram = _context.Renderer.CreateProgram(shaderSourcesArray, info);
  192. cpShader = new CachedShaderProgram(hostProgram, specState, translatedShader.Shader);
  193. _computeShaderCache.Add(cpShader);
  194. EnqueueProgramToSave(cpShader, hostProgram, shaderSourcesArray);
  195. _cpPrograms[gpuVa] = cpShader;
  196. return cpShader;
  197. }
  198. /// <summary>
  199. /// Updates the shader pipeline state based on the current GPU state.
  200. /// </summary>
  201. /// <param name="state">Current GPU 3D engine state</param>
  202. /// <param name="pipeline">Shader pipeline state to be updated</param>
  203. /// <param name="graphicsState">Current graphics state</param>
  204. /// <param name="channel">Current GPU channel</param>
  205. private static void UpdatePipelineInfo(
  206. ref ThreedClassState state,
  207. ref ProgramPipelineState pipeline,
  208. GpuChannelGraphicsState graphicsState,
  209. GpuChannel channel)
  210. {
  211. channel.TextureManager.UpdateRenderTargets();
  212. var rtControl = state.RtControl;
  213. var msaaMode = state.RtMsaaMode;
  214. pipeline.SamplesCount = msaaMode.SamplesInX() * msaaMode.SamplesInY();
  215. int count = rtControl.UnpackCount();
  216. for (int index = 0; index < Constants.TotalRenderTargets; index++)
  217. {
  218. int rtIndex = rtControl.UnpackPermutationIndex(index);
  219. var colorState = state.RtColorState[rtIndex];
  220. if (index >= count || colorState.Format == 0 || colorState.WidthOrStride == 0)
  221. {
  222. pipeline.AttachmentEnable[index] = false;
  223. pipeline.AttachmentFormats[index] = Format.R8G8B8A8Unorm;
  224. }
  225. else
  226. {
  227. pipeline.AttachmentEnable[index] = true;
  228. pipeline.AttachmentFormats[index] = colorState.Format.Convert().Format;
  229. }
  230. }
  231. pipeline.DepthStencilEnable = state.RtDepthStencilEnable;
  232. pipeline.DepthStencilFormat = pipeline.DepthStencilEnable ? state.RtDepthStencilState.Format.Convert().Format : Format.D24UnormS8Uint;
  233. pipeline.VertexBufferCount = Constants.TotalVertexBuffers;
  234. pipeline.Topology = graphicsState.Topology;
  235. }
  236. /// <summary>
  237. /// Gets a graphics shader program from the shader cache.
  238. /// This includes all the specified shader stages.
  239. /// </summary>
  240. /// <remarks>
  241. /// This automatically translates, compiles and adds the code to the cache if not present.
  242. /// </remarks>
  243. /// <param name="state">GPU state</param>
  244. /// <param name="pipeline">Pipeline state</param>
  245. /// <param name="channel">GPU channel</param>
  246. /// <param name="poolState">Texture pool state</param>
  247. /// <param name="graphicsState">3D engine state</param>
  248. /// <param name="addresses">Addresses of the shaders for each stage</param>
  249. /// <returns>Compiled graphics shader code</returns>
  250. public CachedShaderProgram GetGraphicsShader(
  251. ref ThreedClassState state,
  252. ref ProgramPipelineState pipeline,
  253. GpuChannel channel,
  254. ref GpuChannelPoolState poolState,
  255. ref GpuChannelGraphicsState graphicsState,
  256. ShaderAddresses addresses)
  257. {
  258. if (_gpPrograms.TryGetValue(addresses, out var gpShaders) && IsShaderEqual(channel, ref poolState, ref graphicsState, gpShaders, addresses))
  259. {
  260. return gpShaders;
  261. }
  262. if (_graphicsShaderCache.TryFind(channel, ref poolState, ref graphicsState, addresses, out gpShaders, out var cachedGuestCode))
  263. {
  264. _gpPrograms[addresses] = gpShaders;
  265. return gpShaders;
  266. }
  267. TransformFeedbackDescriptor[] transformFeedbackDescriptors = GetTransformFeedbackDescriptors(ref state);
  268. UpdatePipelineInfo(ref state, ref pipeline, graphicsState, channel);
  269. ShaderSpecializationState specState = new(ref graphicsState, ref pipeline, transformFeedbackDescriptors);
  270. GpuAccessorState gpuAccessorState = new(poolState, default, graphicsState, specState, transformFeedbackDescriptors);
  271. ReadOnlySpan<ulong> addressesSpan = addresses.AsSpan();
  272. GpuAccessor[] gpuAccessors = new GpuAccessor[Constants.ShaderStages];
  273. TranslatorContext[] translatorContexts = new TranslatorContext[Constants.ShaderStages + 1];
  274. TranslatorContext nextStage = null;
  275. TargetApi api = _context.Capabilities.Api;
  276. for (int stageIndex = Constants.ShaderStages - 1; stageIndex >= 0; stageIndex--)
  277. {
  278. ulong gpuVa = addressesSpan[stageIndex + 1];
  279. if (gpuVa != 0)
  280. {
  281. GpuAccessor gpuAccessor = new(_context, channel, gpuAccessorState, stageIndex);
  282. TranslatorContext currentStage = DecodeGraphicsShader(gpuAccessor, api, DefaultFlags, gpuVa);
  283. if (nextStage != null)
  284. {
  285. currentStage.SetNextStage(nextStage);
  286. }
  287. if (stageIndex == 0 && addresses.VertexA != 0)
  288. {
  289. translatorContexts[0] = DecodeGraphicsShader(gpuAccessor, api, DefaultFlags | TranslationFlags.VertexA, addresses.VertexA);
  290. }
  291. gpuAccessors[stageIndex] = gpuAccessor;
  292. translatorContexts[stageIndex + 1] = currentStage;
  293. nextStage = currentStage;
  294. }
  295. }
  296. bool hasGeometryShader = translatorContexts[4] != null;
  297. bool vertexHasStore = translatorContexts[1] != null && translatorContexts[1].HasStore;
  298. bool geometryHasStore = hasGeometryShader && translatorContexts[4].HasStore;
  299. bool vertexToCompute = ShouldConvertVertexToCompute(_context, vertexHasStore, geometryHasStore, hasGeometryShader);
  300. bool geometryToCompute = ShouldConvertGeometryToCompute(_context, geometryHasStore);
  301. CachedShaderStage[] shaders = new CachedShaderStage[Constants.ShaderStages + 1];
  302. List<ShaderSource> shaderSources = new();
  303. TranslatorContext previousStage = null;
  304. ShaderInfoBuilder infoBuilder = new(_context, transformFeedbackDescriptors != null, vertexToCompute);
  305. if (geometryToCompute && translatorContexts[4] != null)
  306. {
  307. translatorContexts[4].SetVertexOutputMapForGeometryAsCompute(translatorContexts[1]);
  308. }
  309. ShaderAsCompute vertexAsCompute = null;
  310. ShaderAsCompute geometryAsCompute = null;
  311. for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++)
  312. {
  313. TranslatorContext currentStage = translatorContexts[stageIndex + 1];
  314. if (currentStage != null)
  315. {
  316. gpuAccessors[stageIndex].InitializeReservedCounts(transformFeedbackDescriptors != null, vertexToCompute);
  317. ShaderProgram program;
  318. bool asCompute = (stageIndex == 0 && vertexToCompute) || (stageIndex == 3 && geometryToCompute);
  319. if (stageIndex == 0 && translatorContexts[0] != null)
  320. {
  321. TranslatedShaderVertexPair translatedShader = TranslateShader(
  322. _dumper,
  323. channel,
  324. currentStage,
  325. translatorContexts[0],
  326. cachedGuestCode.VertexACode,
  327. cachedGuestCode.VertexBCode,
  328. asCompute);
  329. shaders[0] = translatedShader.VertexA;
  330. shaders[1] = translatedShader.VertexB;
  331. program = translatedShader.Program;
  332. }
  333. else
  334. {
  335. byte[] code = cachedGuestCode.GetByIndex(stageIndex);
  336. TranslatedShader translatedShader = TranslateShader(_dumper, channel, currentStage, code, asCompute);
  337. shaders[stageIndex + 1] = translatedShader.Shader;
  338. program = translatedShader.Program;
  339. }
  340. if (asCompute)
  341. {
  342. bool tfEnabled = transformFeedbackDescriptors != null;
  343. if (stageIndex == 0)
  344. {
  345. vertexAsCompute = CreateHostVertexAsComputeProgram(program, currentStage, tfEnabled);
  346. TranslatorContext lastInVertexPipeline = geometryToCompute ? translatorContexts[4] ?? currentStage : currentStage;
  347. program = lastInVertexPipeline.GenerateVertexPassthroughForCompute();
  348. }
  349. else
  350. {
  351. geometryAsCompute = CreateHostVertexAsComputeProgram(program, currentStage, tfEnabled);
  352. program = null;
  353. }
  354. }
  355. if (program != null)
  356. {
  357. shaderSources.Add(CreateShaderSource(program));
  358. infoBuilder.AddStageInfo(program.Info);
  359. }
  360. previousStage = currentStage;
  361. }
  362. else if (
  363. previousStage != null &&
  364. previousStage.LayerOutputWritten &&
  365. stageIndex == 3 &&
  366. !_context.Capabilities.SupportsLayerVertexTessellation)
  367. {
  368. shaderSources.Add(CreateShaderSource(previousStage.GenerateGeometryPassthrough()));
  369. }
  370. }
  371. ShaderSource[] shaderSourcesArray = shaderSources.ToArray();
  372. ShaderInfo info = infoBuilder.Build(pipeline);
  373. IProgram hostProgram = _context.Renderer.CreateProgram(shaderSourcesArray, info);
  374. gpShaders = new(hostProgram, vertexAsCompute, geometryAsCompute, specState, shaders);
  375. _graphicsShaderCache.Add(gpShaders);
  376. // We don't currently support caching shaders that have been converted to compute.
  377. if (vertexAsCompute == null)
  378. {
  379. EnqueueProgramToSave(gpShaders, hostProgram, shaderSourcesArray);
  380. }
  381. _gpPrograms[addresses] = gpShaders;
  382. return gpShaders;
  383. }
  384. /// <summary>
  385. /// Checks if a vertex shader should be converted to a compute shader due to it making use of
  386. /// features that are not supported on the host.
  387. /// </summary>
  388. /// <param name="context">GPU context of the shader</param>
  389. /// <param name="vertexHasStore">Whether the vertex shader has image or storage buffer store operations</param>
  390. /// <param name="geometryHasStore">Whether the geometry shader has image or storage buffer store operations, if one exists</param>
  391. /// <param name="hasGeometryShader">Whether a geometry shader exists</param>
  392. /// <returns>True if the vertex shader should be converted to compute, false otherwise</returns>
  393. public static bool ShouldConvertVertexToCompute(GpuContext context, bool vertexHasStore, bool geometryHasStore, bool hasGeometryShader)
  394. {
  395. // If the host does not support store operations on vertex,
  396. // we need to emulate it on a compute shader.
  397. if (!context.Capabilities.SupportsVertexStoreAndAtomics && vertexHasStore)
  398. {
  399. return true;
  400. }
  401. // If any stage after the vertex stage is converted to compute,
  402. // we need to convert vertex to compute too.
  403. return hasGeometryShader && ShouldConvertGeometryToCompute(context, geometryHasStore);
  404. }
  405. /// <summary>
  406. /// Checks if a geometry shader should be converted to a compute shader due to it making use of
  407. /// features that are not supported on the host.
  408. /// </summary>
  409. /// <param name="context">GPU context of the shader</param>
  410. /// <param name="geometryHasStore">Whether the geometry shader has image or storage buffer store operations, if one exists</param>
  411. /// <returns>True if the geometry shader should be converted to compute, false otherwise</returns>
  412. public static bool ShouldConvertGeometryToCompute(GpuContext context, bool geometryHasStore)
  413. {
  414. return (!context.Capabilities.SupportsVertexStoreAndAtomics && geometryHasStore) ||
  415. !context.Capabilities.SupportsGeometryShader;
  416. }
  417. /// <summary>
  418. /// Checks if it might be necessary for any vertex, tessellation or geometry shader to be converted to compute,
  419. /// based on the supported host features.
  420. /// </summary>
  421. /// <param name="capabilities">Host capabilities</param>
  422. /// <returns>True if the possibility of a shader being converted to compute exists, false otherwise</returns>
  423. public static bool MayConvertVtgToCompute(ref Capabilities capabilities)
  424. {
  425. return !capabilities.SupportsVertexStoreAndAtomics || !capabilities.SupportsGeometryShader;
  426. }
  427. /// <summary>
  428. /// Creates a compute shader from a vertex, tessellation or geometry shader that has been converted to compute.
  429. /// </summary>
  430. /// <param name="program">Shader program</param>
  431. /// <param name="context">Translation context of the shader</param>
  432. /// <param name="tfEnabled">Whether transform feedback is enabled</param>
  433. /// <returns>Compute shader</returns>
  434. private ShaderAsCompute CreateHostVertexAsComputeProgram(ShaderProgram program, TranslatorContext context, bool tfEnabled)
  435. {
  436. ShaderSource source = new(program.Code, program.BinaryCode, ShaderStage.Compute, program.Language);
  437. ShaderInfo info = ShaderInfoBuilder.BuildForVertexAsCompute(_context, program.Info, tfEnabled);
  438. return new(_context.Renderer.CreateProgram(new[] { source }, info), program.Info, context.GetResourceReservations());
  439. }
  440. /// <summary>
  441. /// Creates a shader source for use with the backend from a translated shader program.
  442. /// </summary>
  443. /// <param name="program">Translated shader program</param>
  444. /// <returns>Shader source</returns>
  445. public static ShaderSource CreateShaderSource(ShaderProgram program)
  446. {
  447. return new ShaderSource(program.Code, program.BinaryCode, program.Info.Stage, program.Language);
  448. }
  449. /// <summary>
  450. /// Puts a program on the queue of programs to be saved on the disk cache.
  451. /// </summary>
  452. /// <remarks>
  453. /// This will not do anything if disk shader cache is disabled.
  454. /// </remarks>
  455. /// <param name="program">Cached shader program</param>
  456. /// <param name="hostProgram">Host program</param>
  457. /// <param name="sources">Source for each shader stage</param>
  458. private void EnqueueProgramToSave(CachedShaderProgram program, IProgram hostProgram, ShaderSource[] sources)
  459. {
  460. if (_diskCacheHostStorage.CacheEnabled)
  461. {
  462. byte[] binaryCode = _context.Capabilities.Api == TargetApi.Vulkan ? ShaderBinarySerializer.Pack(sources) : null;
  463. ProgramToSave programToSave = new(program, hostProgram, binaryCode);
  464. _programsToSaveQueue.Enqueue(programToSave);
  465. }
  466. }
  467. /// <summary>
  468. /// Gets transform feedback state from the current GPU state.
  469. /// </summary>
  470. /// <param name="state">Current GPU state</param>
  471. /// <returns>Four transform feedback descriptors for the enabled TFBs, or null if TFB is disabled</returns>
  472. private static TransformFeedbackDescriptor[] GetTransformFeedbackDescriptors(ref ThreedClassState state)
  473. {
  474. bool tfEnable = state.TfEnable;
  475. if (!tfEnable)
  476. {
  477. return null;
  478. }
  479. TransformFeedbackDescriptor[] descs = new TransformFeedbackDescriptor[Constants.TotalTransformFeedbackBuffers];
  480. for (int i = 0; i < Constants.TotalTransformFeedbackBuffers; i++)
  481. {
  482. var tf = state.TfState[i];
  483. descs[i] = new TransformFeedbackDescriptor(
  484. tf.BufferIndex,
  485. tf.Stride,
  486. tf.VaryingsCount,
  487. ref state.TfVaryingLocations[i]);
  488. }
  489. return descs;
  490. }
  491. /// <summary>
  492. /// Checks if compute shader code in memory is equal to the cached shader.
  493. /// </summary>
  494. /// <param name="channel">GPU channel using the shader</param>
  495. /// <param name="poolState">GPU channel state to verify shader compatibility</param>
  496. /// <param name="computeState">GPU channel compute state to verify shader compatibility</param>
  497. /// <param name="cpShader">Cached compute shader</param>
  498. /// <param name="gpuVa">GPU virtual address of the shader code in memory</param>
  499. /// <returns>True if the code is different, false otherwise</returns>
  500. private static bool IsShaderEqual(
  501. GpuChannel channel,
  502. GpuChannelPoolState poolState,
  503. GpuChannelComputeState computeState,
  504. CachedShaderProgram cpShader,
  505. ulong gpuVa)
  506. {
  507. if (IsShaderEqual(channel.MemoryManager, cpShader.Shaders[0], gpuVa))
  508. {
  509. return cpShader.SpecializationState.MatchesCompute(channel, ref poolState, computeState, true);
  510. }
  511. return false;
  512. }
  513. /// <summary>
  514. /// Checks if graphics shader code from all stages in memory are equal to the cached shaders.
  515. /// </summary>
  516. /// <param name="channel">GPU channel using the shader</param>
  517. /// <param name="poolState">GPU channel state to verify shader compatibility</param>
  518. /// <param name="graphicsState">GPU channel graphics state to verify shader compatibility</param>
  519. /// <param name="gpShaders">Cached graphics shaders</param>
  520. /// <param name="addresses">GPU virtual addresses of all enabled shader stages</param>
  521. /// <returns>True if the code is different, false otherwise</returns>
  522. private static bool IsShaderEqual(
  523. GpuChannel channel,
  524. ref GpuChannelPoolState poolState,
  525. ref GpuChannelGraphicsState graphicsState,
  526. CachedShaderProgram gpShaders,
  527. ShaderAddresses addresses)
  528. {
  529. ReadOnlySpan<ulong> addressesSpan = addresses.AsSpan();
  530. for (int stageIndex = 0; stageIndex < gpShaders.Shaders.Length; stageIndex++)
  531. {
  532. CachedShaderStage shader = gpShaders.Shaders[stageIndex];
  533. ulong gpuVa = addressesSpan[stageIndex];
  534. if (!IsShaderEqual(channel.MemoryManager, shader, gpuVa))
  535. {
  536. return false;
  537. }
  538. }
  539. bool vertexAsCompute = gpShaders.VertexAsCompute != null;
  540. bool usesDrawParameters = gpShaders.Shaders[1]?.Info.UsesDrawParameters ?? false;
  541. return gpShaders.SpecializationState.MatchesGraphics(
  542. channel,
  543. ref poolState,
  544. ref graphicsState,
  545. vertexAsCompute,
  546. usesDrawParameters,
  547. checkTextures: true);
  548. }
  549. /// <summary>
  550. /// Checks if the code of the specified cached shader is different from the code in memory.
  551. /// </summary>
  552. /// <param name="memoryManager">Memory manager used to access the GPU memory where the shader is located</param>
  553. /// <param name="shader">Cached shader to compare with</param>
  554. /// <param name="gpuVa">GPU virtual address of the binary shader code</param>
  555. /// <returns>True if the code is different, false otherwise</returns>
  556. private static bool IsShaderEqual(MemoryManager memoryManager, CachedShaderStage shader, ulong gpuVa)
  557. {
  558. if (shader == null)
  559. {
  560. return true;
  561. }
  562. ReadOnlySpan<byte> memoryCode = memoryManager.GetSpanMapped(gpuVa, shader.Code.Length);
  563. return memoryCode.SequenceEqual(shader.Code);
  564. }
  565. /// <summary>
  566. /// Decode the binary Maxwell shader code to a translator context.
  567. /// </summary>
  568. /// <param name="gpuAccessor">GPU state accessor</param>
  569. /// <param name="api">Graphics API that will be used with the shader</param>
  570. /// <param name="gpuVa">GPU virtual address of the binary shader code</param>
  571. /// <returns>The generated translator context</returns>
  572. public static TranslatorContext DecodeComputeShader(IGpuAccessor gpuAccessor, TargetApi api, ulong gpuVa)
  573. {
  574. var options = CreateTranslationOptions(api, DefaultFlags | TranslationFlags.Compute);
  575. return Translator.CreateContext(gpuVa, gpuAccessor, options);
  576. }
  577. /// <summary>
  578. /// Decode the binary Maxwell shader code to a translator context.
  579. /// </summary>
  580. /// <remarks>
  581. /// This will combine the "Vertex A" and "Vertex B" shader stages, if specified, into one shader.
  582. /// </remarks>
  583. /// <param name="gpuAccessor">GPU state accessor</param>
  584. /// <param name="api">Graphics API that will be used with the shader</param>
  585. /// <param name="flags">Flags that controls shader translation</param>
  586. /// <param name="gpuVa">GPU virtual address of the shader code</param>
  587. /// <returns>The generated translator context</returns>
  588. public static TranslatorContext DecodeGraphicsShader(IGpuAccessor gpuAccessor, TargetApi api, TranslationFlags flags, ulong gpuVa)
  589. {
  590. var options = CreateTranslationOptions(api, flags);
  591. return Translator.CreateContext(gpuVa, gpuAccessor, options);
  592. }
  593. /// <summary>
  594. /// Translates a previously generated translator context to something that the host API accepts.
  595. /// </summary>
  596. /// <param name="dumper">Optional shader code dumper</param>
  597. /// <param name="channel">GPU channel using the shader</param>
  598. /// <param name="currentStage">Translator context of the stage to be translated</param>
  599. /// <param name="vertexA">Optional translator context of the shader that should be combined</param>
  600. /// <param name="codeA">Optional Maxwell binary code of the Vertex A shader, if present</param>
  601. /// <param name="codeB">Optional Maxwell binary code of the Vertex B or current stage shader, if present on cache</param>
  602. /// <param name="asCompute">Indicates that the vertex shader should be converted to a compute shader</param>
  603. /// <returns>Compiled graphics shader code</returns>
  604. private static TranslatedShaderVertexPair TranslateShader(
  605. ShaderDumper dumper,
  606. GpuChannel channel,
  607. TranslatorContext currentStage,
  608. TranslatorContext vertexA,
  609. byte[] codeA,
  610. byte[] codeB,
  611. bool asCompute)
  612. {
  613. ulong cb1DataAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(0, 1);
  614. var memoryManager = channel.MemoryManager;
  615. codeA ??= memoryManager.GetSpan(vertexA.Address, vertexA.Size).ToArray();
  616. codeB ??= memoryManager.GetSpan(currentStage.Address, currentStage.Size).ToArray();
  617. byte[] cb1DataA = ReadArray(memoryManager, cb1DataAddress, vertexA.Cb1DataSize);
  618. byte[] cb1DataB = ReadArray(memoryManager, cb1DataAddress, currentStage.Cb1DataSize);
  619. ShaderDumpPaths pathsA = default;
  620. ShaderDumpPaths pathsB = default;
  621. if (dumper != null)
  622. {
  623. pathsA = dumper.Dump(codeA, compute: false);
  624. pathsB = dumper.Dump(codeB, compute: false);
  625. }
  626. ShaderProgram program = currentStage.Translate(vertexA, asCompute);
  627. pathsB.Prepend(program);
  628. pathsA.Prepend(program);
  629. CachedShaderStage vertexAStage = new(null, codeA, cb1DataA);
  630. CachedShaderStage vertexBStage = new(program.Info, codeB, cb1DataB);
  631. return new TranslatedShaderVertexPair(vertexAStage, vertexBStage, program);
  632. }
  633. /// <summary>
  634. /// Translates a previously generated translator context to something that the host API accepts.
  635. /// </summary>
  636. /// <param name="dumper">Optional shader code dumper</param>
  637. /// <param name="channel">GPU channel using the shader</param>
  638. /// <param name="context">Translator context of the stage to be translated</param>
  639. /// <param name="code">Optional Maxwell binary code of the current stage shader, if present on cache</param>
  640. /// <param name="asCompute">Indicates that the vertex shader should be converted to a compute shader</param>
  641. /// <returns>Compiled graphics shader code</returns>
  642. private static TranslatedShader TranslateShader(ShaderDumper dumper, GpuChannel channel, TranslatorContext context, byte[] code, bool asCompute)
  643. {
  644. var memoryManager = channel.MemoryManager;
  645. ulong cb1DataAddress = context.Stage == ShaderStage.Compute
  646. ? channel.BufferManager.GetComputeUniformBufferAddress(1)
  647. : channel.BufferManager.GetGraphicsUniformBufferAddress(StageToStageIndex(context.Stage), 1);
  648. byte[] cb1Data = ReadArray(memoryManager, cb1DataAddress, context.Cb1DataSize);
  649. code ??= memoryManager.GetSpan(context.Address, context.Size).ToArray();
  650. ShaderDumpPaths paths = dumper?.Dump(code, context.Stage == ShaderStage.Compute) ?? default;
  651. ShaderProgram program = context.Translate(asCompute);
  652. paths.Prepend(program);
  653. return new TranslatedShader(new CachedShaderStage(program.Info, code, cb1Data), program);
  654. }
  655. /// <summary>
  656. /// Reads data from physical memory, returns an empty array if the memory is unmapped or size is 0.
  657. /// </summary>
  658. /// <param name="memoryManager">Memory manager with the physical memory to read from</param>
  659. /// <param name="address">Physical address of the region to read</param>
  660. /// <param name="size">Size in bytes of the data</param>
  661. /// <returns>An array with the data at the specified memory location</returns>
  662. private static byte[] ReadArray(MemoryManager memoryManager, ulong address, int size)
  663. {
  664. if (address == MemoryManager.PteUnmapped || size == 0)
  665. {
  666. return Array.Empty<byte>();
  667. }
  668. return memoryManager.Physical.GetSpan(address, size).ToArray();
  669. }
  670. /// <summary>
  671. /// Gets the index of a stage from a <see cref="ShaderStage"/>.
  672. /// </summary>
  673. /// <param name="stage">Stage to get the index from</param>
  674. /// <returns>Stage index</returns>
  675. private static int StageToStageIndex(ShaderStage stage)
  676. {
  677. return stage switch
  678. {
  679. ShaderStage.TessellationControl => 1,
  680. ShaderStage.TessellationEvaluation => 2,
  681. ShaderStage.Geometry => 3,
  682. ShaderStage.Fragment => 4,
  683. _ => 0,
  684. };
  685. }
  686. /// <summary>
  687. /// Creates shader translation options with the requested graphics API and flags.
  688. /// The shader language is choosen based on the current configuration and graphics API.
  689. /// </summary>
  690. /// <param name="api">Target graphics API</param>
  691. /// <param name="flags">Translation flags</param>
  692. /// <returns>Translation options</returns>
  693. private static TranslationOptions CreateTranslationOptions(TargetApi api, TranslationFlags flags)
  694. {
  695. TargetLanguage lang = GraphicsConfig.EnableSpirvCompilationOnVulkan && api == TargetApi.Vulkan
  696. ? TargetLanguage.Spirv
  697. : TargetLanguage.Glsl;
  698. return new TranslationOptions(lang, api, flags);
  699. }
  700. /// <summary>
  701. /// Disposes the shader cache, deleting all the cached shaders.
  702. /// It's an error to use the shader cache after disposal.
  703. /// </summary>
  704. public void Dispose()
  705. {
  706. foreach (CachedShaderProgram program in _graphicsShaderCache.GetPrograms())
  707. {
  708. program.Dispose();
  709. }
  710. foreach (CachedShaderProgram program in _computeShaderCache.GetPrograms())
  711. {
  712. program.Dispose();
  713. }
  714. _cacheWriter?.Dispose();
  715. }
  716. }
  717. }