ShaderCache.cs 46 KB

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