ShaderCache.cs 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958
  1. using Ryujinx.Common;
  2. using Ryujinx.Common.Logging;
  3. using Ryujinx.Graphics.GAL;
  4. using Ryujinx.Graphics.Gpu.Shader.Cache;
  5. using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
  6. using Ryujinx.Graphics.Gpu.State;
  7. using Ryujinx.Graphics.Shader;
  8. using Ryujinx.Graphics.Shader.Translation;
  9. using System;
  10. using System.Collections.Generic;
  11. using System.Diagnostics;
  12. using System.Threading;
  13. using System.Threading.Tasks;
  14. namespace Ryujinx.Graphics.Gpu.Shader
  15. {
  16. /// <summary>
  17. /// Memory cache of shader code.
  18. /// </summary>
  19. class ShaderCache : IDisposable
  20. {
  21. private const TranslationFlags DefaultFlags = TranslationFlags.DebugMode;
  22. private readonly GpuContext _context;
  23. private readonly ShaderDumper _dumper;
  24. private readonly Dictionary<ulong, List<ShaderBundle>> _cpPrograms;
  25. private readonly Dictionary<ShaderAddresses, List<ShaderBundle>> _gpPrograms;
  26. private CacheManager _cacheManager;
  27. private Dictionary<Hash128, ShaderBundle> _gpProgramsDiskCache;
  28. private Dictionary<Hash128, ShaderBundle> _cpProgramsDiskCache;
  29. /// <summary>
  30. /// Version of the codegen (to be changed when codegen or guest format change).
  31. /// </summary>
  32. private const ulong ShaderCodeGenVersion = 2412;
  33. // Progress reporting helpers
  34. private volatile int _shaderCount;
  35. private volatile int _totalShaderCount;
  36. public event Action<ShaderCacheState, int, int> ShaderCacheStateChanged;
  37. /// <summary>
  38. /// Creates a new instance of the shader cache.
  39. /// </summary>
  40. /// <param name="context">GPU context that the shader cache belongs to</param>
  41. public ShaderCache(GpuContext context)
  42. {
  43. _context = context;
  44. _dumper = new ShaderDumper();
  45. _cpPrograms = new Dictionary<ulong, List<ShaderBundle>>();
  46. _gpPrograms = new Dictionary<ShaderAddresses, List<ShaderBundle>>();
  47. _gpProgramsDiskCache = new Dictionary<Hash128, ShaderBundle>();
  48. _cpProgramsDiskCache = new Dictionary<Hash128, ShaderBundle>();
  49. }
  50. /// <summary>
  51. /// Initialize the cache.
  52. /// </summary>
  53. internal void Initialize()
  54. {
  55. if (GraphicsConfig.EnableShaderCache && GraphicsConfig.TitleId != null)
  56. {
  57. _cacheManager = new CacheManager(CacheGraphicsApi.OpenGL, CacheHashType.XxHash128, "glsl", GraphicsConfig.TitleId, ShaderCodeGenVersion);
  58. bool isReadOnly = _cacheManager.IsReadOnly;
  59. HashSet<Hash128> invalidEntries = null;
  60. if (isReadOnly)
  61. {
  62. Logger.Warning?.Print(LogClass.Gpu, "Loading shader cache in read-only mode (cache in use by another program!)");
  63. }
  64. else
  65. {
  66. invalidEntries = new HashSet<Hash128>();
  67. }
  68. ReadOnlySpan<Hash128> guestProgramList = _cacheManager.GetGuestProgramList();
  69. using AutoResetEvent progressReportEvent = new AutoResetEvent(false);
  70. _shaderCount = 0;
  71. _totalShaderCount = guestProgramList.Length;
  72. ShaderCacheStateChanged?.Invoke(ShaderCacheState.Start, _shaderCount, _totalShaderCount);
  73. Thread progressReportThread = null;
  74. if (guestProgramList.Length > 0)
  75. {
  76. progressReportThread = new Thread(ReportProgress)
  77. {
  78. Name = "ShaderCache.ProgressReporter",
  79. Priority = ThreadPriority.Lowest,
  80. IsBackground = true
  81. };
  82. progressReportThread.Start(progressReportEvent);
  83. }
  84. // Make sure these are initialized before doing compilation.
  85. Capabilities caps = _context.Capabilities;
  86. int maxTaskCount = Math.Min(Environment.ProcessorCount, 8);
  87. int programIndex = 0;
  88. List<ShaderCompileTask> activeTasks = new List<ShaderCompileTask>();
  89. AutoResetEvent taskDoneEvent = new AutoResetEvent(false);
  90. // This thread dispatches tasks to do shader translation, and creates programs that OpenGL will link in the background.
  91. // The program link status is checked in a non-blocking manner so that multiple shaders can be compiled at once.
  92. while (programIndex < guestProgramList.Length || activeTasks.Count > 0)
  93. {
  94. if (activeTasks.Count < maxTaskCount && programIndex < guestProgramList.Length)
  95. {
  96. // Begin a new shader compilation.
  97. Hash128 key = guestProgramList[programIndex];
  98. byte[] hostProgramBinary = _cacheManager.GetHostProgramByHash(ref key);
  99. bool hasHostCache = hostProgramBinary != null;
  100. IProgram hostProgram = null;
  101. // If the program sources aren't in the cache, compile from saved guest program.
  102. byte[] guestProgram = _cacheManager.GetGuestProgramByHash(ref key);
  103. if (guestProgram == null)
  104. {
  105. Logger.Error?.Print(LogClass.Gpu, $"Ignoring orphan shader hash {key} in cache (is the cache incomplete?)");
  106. // Should not happen, but if someone messed with the cache it's better to catch it.
  107. invalidEntries?.Add(key);
  108. _shaderCount = ++programIndex;
  109. continue;
  110. }
  111. ReadOnlySpan<byte> guestProgramReadOnlySpan = guestProgram;
  112. ReadOnlySpan<GuestShaderCacheEntry> cachedShaderEntries = GuestShaderCacheEntry.Parse(ref guestProgramReadOnlySpan, out GuestShaderCacheHeader fileHeader);
  113. if (cachedShaderEntries[0].Header.Stage == ShaderStage.Compute)
  114. {
  115. Debug.Assert(cachedShaderEntries.Length == 1);
  116. GuestShaderCacheEntry entry = cachedShaderEntries[0];
  117. HostShaderCacheEntry[] hostShaderEntries = null;
  118. // Try loading host shader binary.
  119. if (hasHostCache)
  120. {
  121. hostShaderEntries = HostShaderCacheEntry.Parse(hostProgramBinary, out ReadOnlySpan<byte> hostProgramBinarySpan);
  122. hostProgramBinary = hostProgramBinarySpan.ToArray();
  123. hostProgram = _context.Renderer.LoadProgramBinary(hostProgramBinary);
  124. }
  125. ShaderCompileTask task = new ShaderCompileTask(taskDoneEvent);
  126. activeTasks.Add(task);
  127. task.OnCompiled(hostProgram, (bool isHostProgramValid, ShaderCompileTask task) =>
  128. {
  129. ShaderProgram program = null;
  130. ShaderProgramInfo shaderProgramInfo = null;
  131. if (isHostProgramValid)
  132. {
  133. // Reconstruct code holder.
  134. program = new ShaderProgram(entry.Header.Stage, "");
  135. shaderProgramInfo = hostShaderEntries[0].ToShaderProgramInfo();
  136. ShaderCodeHolder shader = new ShaderCodeHolder(program, shaderProgramInfo, entry.Code);
  137. _cpProgramsDiskCache.Add(key, new ShaderBundle(hostProgram, shader));
  138. return true;
  139. }
  140. else
  141. {
  142. // If the host program was rejected by the gpu driver or isn't in cache, try to build from program sources again.
  143. Task compileTask = Task.Run(() =>
  144. {
  145. IGpuAccessor gpuAccessor = new CachedGpuAccessor(_context, entry.Code, entry.Header.GpuAccessorHeader, entry.TextureDescriptors);
  146. program = Translator.CreateContext(0, gpuAccessor, DefaultFlags | TranslationFlags.Compute).Translate(out shaderProgramInfo);
  147. });
  148. task.OnTask(compileTask, (bool _, ShaderCompileTask task) =>
  149. {
  150. ShaderCodeHolder shader = new ShaderCodeHolder(program, shaderProgramInfo, entry.Code);
  151. Logger.Info?.Print(LogClass.Gpu, $"Host shader {key} got invalidated, rebuilding from guest...");
  152. // Compile shader and create program as the shader program binary got invalidated.
  153. shader.HostShader = _context.Renderer.CompileShader(ShaderStage.Compute, shader.Program.Code);
  154. hostProgram = _context.Renderer.CreateProgram(new IShader[] { shader.HostShader }, null);
  155. task.OnCompiled(hostProgram, (bool isNewProgramValid, ShaderCompileTask task) =>
  156. {
  157. // As the host program was invalidated, save the new entry in the cache.
  158. hostProgramBinary = HostShaderCacheEntry.Create(hostProgram.GetBinary(), new ShaderCodeHolder[] { shader });
  159. if (!isReadOnly)
  160. {
  161. if (hasHostCache)
  162. {
  163. _cacheManager.ReplaceHostProgram(ref key, hostProgramBinary);
  164. }
  165. else
  166. {
  167. Logger.Warning?.Print(LogClass.Gpu, $"Add missing host shader {key} in cache (is the cache incomplete?)");
  168. _cacheManager.AddHostProgram(ref key, hostProgramBinary);
  169. }
  170. }
  171. _cpProgramsDiskCache.Add(key, new ShaderBundle(hostProgram, shader));
  172. return true;
  173. });
  174. return false; // Not finished: still need to compile the host program.
  175. });
  176. return false; // Not finished: translating the program.
  177. }
  178. });
  179. }
  180. else
  181. {
  182. Debug.Assert(cachedShaderEntries.Length == Constants.ShaderStages);
  183. ShaderCodeHolder[] shaders = new ShaderCodeHolder[cachedShaderEntries.Length];
  184. List<ShaderProgram> shaderPrograms = new List<ShaderProgram>();
  185. TransformFeedbackDescriptor[] tfd = CacheHelper.ReadTransformFeedbackInformation(ref guestProgramReadOnlySpan, fileHeader);
  186. TranslationFlags flags = DefaultFlags;
  187. if (tfd != null)
  188. {
  189. flags |= TranslationFlags.Feedback;
  190. }
  191. TranslationCounts counts = new TranslationCounts();
  192. HostShaderCacheEntry[] hostShaderEntries = null;
  193. // Try loading host shader binary.
  194. if (hasHostCache)
  195. {
  196. hostShaderEntries = HostShaderCacheEntry.Parse(hostProgramBinary, out ReadOnlySpan<byte> hostProgramBinarySpan);
  197. hostProgramBinary = hostProgramBinarySpan.ToArray();
  198. hostProgram = _context.Renderer.LoadProgramBinary(hostProgramBinary);
  199. }
  200. ShaderCompileTask task = new ShaderCompileTask(taskDoneEvent);
  201. activeTasks.Add(task);
  202. GuestShaderCacheEntry[] entries = cachedShaderEntries.ToArray();
  203. task.OnCompiled(hostProgram, (bool isHostProgramValid, ShaderCompileTask task) =>
  204. {
  205. Task compileTask = Task.Run(() =>
  206. {
  207. // Reconstruct code holder.
  208. for (int i = 0; i < entries.Length; i++)
  209. {
  210. GuestShaderCacheEntry entry = entries[i];
  211. if (entry == null)
  212. {
  213. continue;
  214. }
  215. ShaderProgram program;
  216. if (entry.Header.SizeA != 0)
  217. {
  218. ShaderProgramInfo shaderProgramInfo;
  219. if (isHostProgramValid)
  220. {
  221. program = new ShaderProgram(entry.Header.Stage, "");
  222. shaderProgramInfo = hostShaderEntries[i].ToShaderProgramInfo();
  223. }
  224. else
  225. {
  226. IGpuAccessor gpuAccessor = new CachedGpuAccessor(_context, entry.Code, entry.Header.GpuAccessorHeader, entry.TextureDescriptors);
  227. TranslatorContext translatorContext = Translator.CreateContext(0, gpuAccessor, flags, counts);
  228. TranslatorContext translatorContext2 = Translator.CreateContext((ulong)entry.Header.Size, gpuAccessor, flags | TranslationFlags.VertexA, counts);
  229. program = translatorContext.Translate(out shaderProgramInfo, translatorContext2);
  230. }
  231. // NOTE: Vertex B comes first in the shader cache.
  232. byte[] code = entry.Code.AsSpan().Slice(0, entry.Header.Size).ToArray();
  233. byte[] code2 = entry.Code.AsSpan().Slice(entry.Header.Size, entry.Header.SizeA).ToArray();
  234. shaders[i] = new ShaderCodeHolder(program, shaderProgramInfo, code, code2);
  235. }
  236. else
  237. {
  238. ShaderProgramInfo shaderProgramInfo;
  239. if (isHostProgramValid)
  240. {
  241. program = new ShaderProgram(entry.Header.Stage, "");
  242. shaderProgramInfo = hostShaderEntries[i].ToShaderProgramInfo();
  243. }
  244. else
  245. {
  246. IGpuAccessor gpuAccessor = new CachedGpuAccessor(_context, entry.Code, entry.Header.GpuAccessorHeader, entry.TextureDescriptors);
  247. program = Translator.CreateContext(0, gpuAccessor, flags, counts).Translate(out shaderProgramInfo);
  248. }
  249. shaders[i] = new ShaderCodeHolder(program, shaderProgramInfo, entry.Code);
  250. }
  251. shaderPrograms.Add(program);
  252. }
  253. });
  254. task.OnTask(compileTask, (bool _, ShaderCompileTask task) =>
  255. {
  256. // If the host program was rejected by the gpu driver or isn't in cache, try to build from program sources again.
  257. if (!isHostProgramValid)
  258. {
  259. Logger.Info?.Print(LogClass.Gpu, $"Host shader {key} got invalidated, rebuilding from guest...");
  260. List<IShader> hostShaders = new List<IShader>();
  261. // Compile shaders and create program as the shader program binary got invalidated.
  262. for (int stage = 0; stage < Constants.ShaderStages; stage++)
  263. {
  264. ShaderProgram program = shaders[stage]?.Program;
  265. if (program == null)
  266. {
  267. continue;
  268. }
  269. IShader hostShader = _context.Renderer.CompileShader(program.Stage, program.Code);
  270. shaders[stage].HostShader = hostShader;
  271. hostShaders.Add(hostShader);
  272. }
  273. hostProgram = _context.Renderer.CreateProgram(hostShaders.ToArray(), tfd);
  274. task.OnCompiled(hostProgram, (bool isNewProgramValid, ShaderCompileTask task) =>
  275. {
  276. // As the host program was invalidated, save the new entry in the cache.
  277. hostProgramBinary = HostShaderCacheEntry.Create(hostProgram.GetBinary(), shaders);
  278. if (!isReadOnly)
  279. {
  280. if (hasHostCache)
  281. {
  282. _cacheManager.ReplaceHostProgram(ref key, hostProgramBinary);
  283. }
  284. else
  285. {
  286. Logger.Warning?.Print(LogClass.Gpu, $"Add missing host shader {key} in cache (is the cache incomplete?)");
  287. _cacheManager.AddHostProgram(ref key, hostProgramBinary);
  288. }
  289. }
  290. _gpProgramsDiskCache.Add(key, new ShaderBundle(hostProgram, shaders));
  291. return true;
  292. });
  293. return false; // Not finished: still need to compile the host program.
  294. }
  295. else
  296. {
  297. _gpProgramsDiskCache.Add(key, new ShaderBundle(hostProgram, shaders));
  298. return true;
  299. }
  300. });
  301. return false; // Not finished: translating the program.
  302. });
  303. }
  304. _shaderCount = ++programIndex;
  305. }
  306. // Process the queue.
  307. for (int i = 0; i < activeTasks.Count; i++)
  308. {
  309. ShaderCompileTask task = activeTasks[i];
  310. if (task.IsDone())
  311. {
  312. activeTasks.RemoveAt(i--);
  313. }
  314. }
  315. if (activeTasks.Count == maxTaskCount)
  316. {
  317. // Wait for a task to be done, or for 1ms.
  318. // Host shader compilation cannot signal when it is done,
  319. // so the 1ms timeout is required to poll status.
  320. taskDoneEvent.WaitOne(1);
  321. }
  322. }
  323. if (!isReadOnly)
  324. {
  325. // Remove entries that are broken in the cache
  326. _cacheManager.RemoveManifestEntries(invalidEntries);
  327. _cacheManager.FlushToArchive();
  328. _cacheManager.Synchronize();
  329. }
  330. progressReportEvent.Set();
  331. progressReportThread?.Join();
  332. ShaderCacheStateChanged?.Invoke(ShaderCacheState.Loaded, _shaderCount, _totalShaderCount);
  333. Logger.Info?.Print(LogClass.Gpu, $"Shader cache loaded {_shaderCount} entries.");
  334. }
  335. }
  336. /// <summary>
  337. /// Raises ShaderCacheStateChanged events periodically.
  338. /// </summary>
  339. private void ReportProgress(object state)
  340. {
  341. const int refreshRate = 50; // ms
  342. AutoResetEvent endEvent = (AutoResetEvent)state;
  343. int count = 0;
  344. do
  345. {
  346. int newCount = _shaderCount;
  347. if (count != newCount)
  348. {
  349. ShaderCacheStateChanged?.Invoke(ShaderCacheState.Loading, newCount, _totalShaderCount);
  350. count = newCount;
  351. }
  352. }
  353. while (!endEvent.WaitOne(refreshRate));
  354. }
  355. /// <summary>
  356. /// Gets a compute shader from the cache.
  357. /// </summary>
  358. /// <remarks>
  359. /// This automatically translates, compiles and adds the code to the cache if not present.
  360. /// </remarks>
  361. /// <param name="state">Current GPU state</param>
  362. /// <param name="gpuVa">GPU virtual address of the binary shader code</param>
  363. /// <param name="localSizeX">Local group size X of the computer shader</param>
  364. /// <param name="localSizeY">Local group size Y of the computer shader</param>
  365. /// <param name="localSizeZ">Local group size Z of the computer shader</param>
  366. /// <param name="localMemorySize">Local memory size of the compute shader</param>
  367. /// <param name="sharedMemorySize">Shared memory size of the compute shader</param>
  368. /// <returns>Compiled compute shader code</returns>
  369. public ShaderBundle GetComputeShader(
  370. GpuState state,
  371. ulong gpuVa,
  372. int localSizeX,
  373. int localSizeY,
  374. int localSizeZ,
  375. int localMemorySize,
  376. int sharedMemorySize)
  377. {
  378. bool isCached = _cpPrograms.TryGetValue(gpuVa, out List<ShaderBundle> list);
  379. if (isCached)
  380. {
  381. foreach (ShaderBundle cachedCpShader in list)
  382. {
  383. if (IsShaderEqual(cachedCpShader, gpuVa))
  384. {
  385. return cachedCpShader;
  386. }
  387. }
  388. }
  389. TranslatorContext[] shaderContexts = new TranslatorContext[1];
  390. shaderContexts[0] = DecodeComputeShader(
  391. state,
  392. gpuVa,
  393. localSizeX,
  394. localSizeY,
  395. localSizeZ,
  396. localMemorySize,
  397. sharedMemorySize);
  398. bool isShaderCacheEnabled = _cacheManager != null;
  399. bool isShaderCacheReadOnly = false;
  400. Hash128 programCodeHash = default;
  401. GuestShaderCacheEntry[] shaderCacheEntries = null;
  402. // Current shader cache doesn't support bindless textures
  403. if (shaderContexts[0].UsedFeatures.HasFlag(FeatureFlags.Bindless))
  404. {
  405. isShaderCacheEnabled = false;
  406. }
  407. if (isShaderCacheEnabled)
  408. {
  409. isShaderCacheReadOnly = _cacheManager.IsReadOnly;
  410. // Compute hash and prepare data for shader disk cache comparison.
  411. shaderCacheEntries = CacheHelper.CreateShaderCacheEntries(_context.MemoryManager, shaderContexts);
  412. programCodeHash = CacheHelper.ComputeGuestHashFromCache(shaderCacheEntries);
  413. }
  414. ShaderBundle cpShader;
  415. // Search for the program hash in loaded shaders.
  416. if (!isShaderCacheEnabled || !_cpProgramsDiskCache.TryGetValue(programCodeHash, out cpShader))
  417. {
  418. if (isShaderCacheEnabled)
  419. {
  420. Logger.Debug?.Print(LogClass.Gpu, $"Shader {programCodeHash} not in cache, compiling!");
  421. }
  422. // The shader isn't currently cached, translate it and compile it.
  423. ShaderCodeHolder shader = TranslateShader(shaderContexts[0]);
  424. shader.HostShader = _context.Renderer.CompileShader(ShaderStage.Compute, shader.Program.Code);
  425. IProgram hostProgram = _context.Renderer.CreateProgram(new IShader[] { shader.HostShader }, null);
  426. hostProgram.CheckProgramLink(true);
  427. byte[] hostProgramBinary = HostShaderCacheEntry.Create(hostProgram.GetBinary(), new ShaderCodeHolder[] { shader });
  428. cpShader = new ShaderBundle(hostProgram, shader);
  429. if (isShaderCacheEnabled)
  430. {
  431. _cpProgramsDiskCache.Add(programCodeHash, cpShader);
  432. if (!isShaderCacheReadOnly)
  433. {
  434. _cacheManager.SaveProgram(ref programCodeHash, CacheHelper.CreateGuestProgramDump(shaderCacheEntries), hostProgramBinary);
  435. }
  436. }
  437. }
  438. if (!isCached)
  439. {
  440. list = new List<ShaderBundle>();
  441. _cpPrograms.Add(gpuVa, list);
  442. }
  443. list.Add(cpShader);
  444. return cpShader;
  445. }
  446. /// <summary>
  447. /// Gets a graphics shader program from the shader cache.
  448. /// This includes all the specified shader stages.
  449. /// </summary>
  450. /// <remarks>
  451. /// This automatically translates, compiles and adds the code to the cache if not present.
  452. /// </remarks>
  453. /// <param name="state">Current GPU state</param>
  454. /// <param name="addresses">Addresses of the shaders for each stage</param>
  455. /// <returns>Compiled graphics shader code</returns>
  456. public ShaderBundle GetGraphicsShader(GpuState state, ShaderAddresses addresses)
  457. {
  458. bool isCached = _gpPrograms.TryGetValue(addresses, out List<ShaderBundle> list);
  459. if (isCached)
  460. {
  461. foreach (ShaderBundle cachedGpShaders in list)
  462. {
  463. if (IsShaderEqual(cachedGpShaders, addresses))
  464. {
  465. return cachedGpShaders;
  466. }
  467. }
  468. }
  469. TranslatorContext[] shaderContexts = new TranslatorContext[Constants.ShaderStages + 1];
  470. TransformFeedbackDescriptor[] tfd = GetTransformFeedbackDescriptors(state);
  471. TranslationFlags flags = DefaultFlags;
  472. if (tfd != null)
  473. {
  474. flags |= TranslationFlags.Feedback;
  475. }
  476. TranslationCounts counts = new TranslationCounts();
  477. if (addresses.VertexA != 0)
  478. {
  479. shaderContexts[0] = DecodeGraphicsShader(state, counts, flags | TranslationFlags.VertexA, ShaderStage.Vertex, addresses.VertexA);
  480. }
  481. shaderContexts[1] = DecodeGraphicsShader(state, counts, flags, ShaderStage.Vertex, addresses.Vertex);
  482. shaderContexts[2] = DecodeGraphicsShader(state, counts, flags, ShaderStage.TessellationControl, addresses.TessControl);
  483. shaderContexts[3] = DecodeGraphicsShader(state, counts, flags, ShaderStage.TessellationEvaluation, addresses.TessEvaluation);
  484. shaderContexts[4] = DecodeGraphicsShader(state, counts, flags, ShaderStage.Geometry, addresses.Geometry);
  485. shaderContexts[5] = DecodeGraphicsShader(state, counts, flags, ShaderStage.Fragment, addresses.Fragment);
  486. bool isShaderCacheEnabled = _cacheManager != null;
  487. bool isShaderCacheReadOnly = false;
  488. Hash128 programCodeHash = default;
  489. GuestShaderCacheEntry[] shaderCacheEntries = null;
  490. // Current shader cache doesn't support bindless textures
  491. for (int i = 0; i < shaderContexts.Length; i++)
  492. {
  493. if (shaderContexts[i] != null && shaderContexts[i].UsedFeatures.HasFlag(FeatureFlags.Bindless))
  494. {
  495. isShaderCacheEnabled = false;
  496. break;
  497. }
  498. }
  499. if (isShaderCacheEnabled)
  500. {
  501. isShaderCacheReadOnly = _cacheManager.IsReadOnly;
  502. // Compute hash and prepare data for shader disk cache comparison.
  503. shaderCacheEntries = CacheHelper.CreateShaderCacheEntries(_context.MemoryManager, shaderContexts);
  504. programCodeHash = CacheHelper.ComputeGuestHashFromCache(shaderCacheEntries, tfd);
  505. }
  506. ShaderBundle gpShaders;
  507. // Search for the program hash in loaded shaders.
  508. if (!isShaderCacheEnabled || !_gpProgramsDiskCache.TryGetValue(programCodeHash, out gpShaders))
  509. {
  510. if (isShaderCacheEnabled)
  511. {
  512. Logger.Debug?.Print(LogClass.Gpu, $"Shader {programCodeHash} not in cache, compiling!");
  513. }
  514. // The shader isn't currently cached, translate it and compile it.
  515. ShaderCodeHolder[] shaders = new ShaderCodeHolder[Constants.ShaderStages];
  516. shaders[0] = TranslateShader(shaderContexts[1], shaderContexts[0]);
  517. shaders[1] = TranslateShader(shaderContexts[2]);
  518. shaders[2] = TranslateShader(shaderContexts[3]);
  519. shaders[3] = TranslateShader(shaderContexts[4]);
  520. shaders[4] = TranslateShader(shaderContexts[5]);
  521. List<IShader> hostShaders = new List<IShader>();
  522. for (int stage = 0; stage < Constants.ShaderStages; stage++)
  523. {
  524. ShaderProgram program = shaders[stage]?.Program;
  525. if (program == null)
  526. {
  527. continue;
  528. }
  529. IShader hostShader = _context.Renderer.CompileShader(program.Stage, program.Code);
  530. shaders[stage].HostShader = hostShader;
  531. hostShaders.Add(hostShader);
  532. }
  533. IProgram hostProgram = _context.Renderer.CreateProgram(hostShaders.ToArray(), tfd);
  534. hostProgram.CheckProgramLink(true);
  535. byte[] hostProgramBinary = HostShaderCacheEntry.Create(hostProgram.GetBinary(), shaders);
  536. gpShaders = new ShaderBundle(hostProgram, shaders);
  537. if (isShaderCacheEnabled)
  538. {
  539. _gpProgramsDiskCache.Add(programCodeHash, gpShaders);
  540. if (!isShaderCacheReadOnly)
  541. {
  542. _cacheManager.SaveProgram(ref programCodeHash, CacheHelper.CreateGuestProgramDump(shaderCacheEntries, tfd), hostProgramBinary);
  543. }
  544. }
  545. }
  546. if (!isCached)
  547. {
  548. list = new List<ShaderBundle>();
  549. _gpPrograms.Add(addresses, list);
  550. }
  551. list.Add(gpShaders);
  552. return gpShaders;
  553. }
  554. /// <summary>
  555. /// Gets transform feedback state from the current GPU state.
  556. /// </summary>
  557. /// <param name="state">Current GPU state</param>
  558. /// <returns>Four transform feedback descriptors for the enabled TFBs, or null if TFB is disabled</returns>
  559. private TransformFeedbackDescriptor[] GetTransformFeedbackDescriptors(GpuState state)
  560. {
  561. bool tfEnable = state.Get<Boolean32>(MethodOffset.TfEnable);
  562. if (!tfEnable)
  563. {
  564. return null;
  565. }
  566. TransformFeedbackDescriptor[] descs = new TransformFeedbackDescriptor[Constants.TotalTransformFeedbackBuffers];
  567. for (int i = 0; i < Constants.TotalTransformFeedbackBuffers; i++)
  568. {
  569. var tf = state.Get<TfState>(MethodOffset.TfState, i);
  570. int length = (int)Math.Min((uint)tf.VaryingsCount, 0x80);
  571. var varyingLocations = state.GetSpan(MethodOffset.TfVaryingLocations + i * 0x80, length).ToArray();
  572. descs[i] = new TransformFeedbackDescriptor(tf.BufferIndex, tf.Stride, varyingLocations);
  573. }
  574. return descs;
  575. }
  576. /// <summary>
  577. /// Checks if compute shader code in memory is equal to the cached shader.
  578. /// </summary>
  579. /// <param name="cpShader">Cached compute shader</param>
  580. /// <param name="gpuVa">GPU virtual address of the shader code in memory</param>
  581. /// <returns>True if the code is different, false otherwise</returns>
  582. private bool IsShaderEqual(ShaderBundle cpShader, ulong gpuVa)
  583. {
  584. return IsShaderEqual(cpShader.Shaders[0], gpuVa);
  585. }
  586. /// <summary>
  587. /// Checks if graphics shader code from all stages in memory are equal to the cached shaders.
  588. /// </summary>
  589. /// <param name="gpShaders">Cached graphics shaders</param>
  590. /// <param name="addresses">GPU virtual addresses of all enabled shader stages</param>
  591. /// <returns>True if the code is different, false otherwise</returns>
  592. private bool IsShaderEqual(ShaderBundle gpShaders, ShaderAddresses addresses)
  593. {
  594. for (int stage = 0; stage < gpShaders.Shaders.Length; stage++)
  595. {
  596. ShaderCodeHolder shader = gpShaders.Shaders[stage];
  597. ulong gpuVa = 0;
  598. switch (stage)
  599. {
  600. case 0: gpuVa = addresses.Vertex; break;
  601. case 1: gpuVa = addresses.TessControl; break;
  602. case 2: gpuVa = addresses.TessEvaluation; break;
  603. case 3: gpuVa = addresses.Geometry; break;
  604. case 4: gpuVa = addresses.Fragment; break;
  605. }
  606. if (!IsShaderEqual(shader, gpuVa, addresses.VertexA))
  607. {
  608. return false;
  609. }
  610. }
  611. return true;
  612. }
  613. /// <summary>
  614. /// Checks if the code of the specified cached shader is different from the code in memory.
  615. /// </summary>
  616. /// <param name="shader">Cached shader to compare with</param>
  617. /// <param name="gpuVa">GPU virtual address of the binary shader code</param>
  618. /// <param name="gpuVaA">Optional GPU virtual address of the "Vertex A" binary shader code</param>
  619. /// <returns>True if the code is different, false otherwise</returns>
  620. private bool IsShaderEqual(ShaderCodeHolder shader, ulong gpuVa, ulong gpuVaA = 0)
  621. {
  622. if (shader == null)
  623. {
  624. return true;
  625. }
  626. ReadOnlySpan<byte> memoryCode = _context.MemoryManager.GetSpan(gpuVa, shader.Code.Length);
  627. bool equals = memoryCode.SequenceEqual(shader.Code);
  628. if (equals && shader.Code2 != null)
  629. {
  630. memoryCode = _context.MemoryManager.GetSpan(gpuVaA, shader.Code2.Length);
  631. equals = memoryCode.SequenceEqual(shader.Code2);
  632. }
  633. return equals;
  634. }
  635. /// <summary>
  636. /// Decode the binary Maxwell shader code to a translator context.
  637. /// </summary>
  638. /// <param name="state">Current GPU state</param>
  639. /// <param name="gpuVa">GPU virtual address of the binary shader code</param>
  640. /// <param name="localSizeX">Local group size X of the computer shader</param>
  641. /// <param name="localSizeY">Local group size Y of the computer shader</param>
  642. /// <param name="localSizeZ">Local group size Z of the computer shader</param>
  643. /// <param name="localMemorySize">Local memory size of the compute shader</param>
  644. /// <param name="sharedMemorySize">Shared memory size of the compute shader</param>
  645. /// <returns>The generated translator context</returns>
  646. private TranslatorContext DecodeComputeShader(
  647. GpuState state,
  648. ulong gpuVa,
  649. int localSizeX,
  650. int localSizeY,
  651. int localSizeZ,
  652. int localMemorySize,
  653. int sharedMemorySize)
  654. {
  655. if (gpuVa == 0)
  656. {
  657. return null;
  658. }
  659. GpuAccessor gpuAccessor = new GpuAccessor(_context, state, localSizeX, localSizeY, localSizeZ, localMemorySize, sharedMemorySize);
  660. return Translator.CreateContext(gpuVa, gpuAccessor, DefaultFlags | TranslationFlags.Compute);
  661. }
  662. /// <summary>
  663. /// Decode the binary Maxwell shader code to a translator context.
  664. /// </summary>
  665. /// <remarks>
  666. /// This will combine the "Vertex A" and "Vertex B" shader stages, if specified, into one shader.
  667. /// </remarks>
  668. /// <param name="state">Current GPU state</param>
  669. /// <param name="counts">Cumulative shader resource counts</param>
  670. /// <param name="flags">Flags that controls shader translation</param>
  671. /// <param name="stage">Shader stage</param>
  672. /// <param name="gpuVa">GPU virtual address of the shader code</param>
  673. /// <returns>The generated translator context</returns>
  674. private TranslatorContext DecodeGraphicsShader(
  675. GpuState state,
  676. TranslationCounts counts,
  677. TranslationFlags flags,
  678. ShaderStage stage,
  679. ulong gpuVa)
  680. {
  681. if (gpuVa == 0)
  682. {
  683. return null;
  684. }
  685. GpuAccessor gpuAccessor = new GpuAccessor(_context, state, (int)stage - 1);
  686. return Translator.CreateContext(gpuVa, gpuAccessor, flags, counts);
  687. }
  688. /// <summary>
  689. /// Translates a previously generated translator context to something that the host API accepts.
  690. /// </summary>
  691. /// <param name="translatorContext">Current translator context to translate</param>
  692. /// <param name="translatorContext2">Optional translator context of the shader that should be combined</param>
  693. /// <returns>Compiled graphics shader code</returns>
  694. private ShaderCodeHolder TranslateShader(TranslatorContext translatorContext, TranslatorContext translatorContext2 = null)
  695. {
  696. if (translatorContext == null)
  697. {
  698. return null;
  699. }
  700. if (translatorContext2 != null)
  701. {
  702. byte[] codeA = _context.MemoryManager.GetSpan(translatorContext2.Address, translatorContext2.Size).ToArray();
  703. byte[] codeB = _context.MemoryManager.GetSpan(translatorContext.Address, translatorContext.Size).ToArray();
  704. _dumper.Dump(codeA, compute: false, out string fullPathA, out string codePathA);
  705. _dumper.Dump(codeB, compute: false, out string fullPathB, out string codePathB);
  706. ShaderProgram program = translatorContext.Translate(out ShaderProgramInfo shaderProgramInfo, translatorContext2);
  707. if (fullPathA != null && fullPathB != null && codePathA != null && codePathB != null)
  708. {
  709. program.Prepend("// " + codePathB);
  710. program.Prepend("// " + fullPathB);
  711. program.Prepend("// " + codePathA);
  712. program.Prepend("// " + fullPathA);
  713. }
  714. return new ShaderCodeHolder(program, shaderProgramInfo, codeB, codeA);
  715. }
  716. else
  717. {
  718. byte[] code = _context.MemoryManager.GetSpan(translatorContext.Address, translatorContext.Size).ToArray();
  719. _dumper.Dump(code, translatorContext.Stage == ShaderStage.Compute, out string fullPath, out string codePath);
  720. ShaderProgram program = translatorContext.Translate(out ShaderProgramInfo shaderProgramInfo);
  721. if (fullPath != null && codePath != null)
  722. {
  723. program.Prepend("// " + codePath);
  724. program.Prepend("// " + fullPath);
  725. }
  726. return new ShaderCodeHolder(program, shaderProgramInfo, code);
  727. }
  728. }
  729. /// <summary>
  730. /// Disposes the shader cache, deleting all the cached shaders.
  731. /// It's an error to use the shader cache after disposal.
  732. /// </summary>
  733. public void Dispose()
  734. {
  735. foreach (List<ShaderBundle> list in _cpPrograms.Values)
  736. {
  737. foreach (ShaderBundle bundle in list)
  738. {
  739. bundle.Dispose();
  740. }
  741. }
  742. foreach (List<ShaderBundle> list in _gpPrograms.Values)
  743. {
  744. foreach (ShaderBundle bundle in list)
  745. {
  746. bundle.Dispose();
  747. }
  748. }
  749. _cacheManager?.Dispose();
  750. }
  751. }
  752. }