ShaderCache.cs 47 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058
  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 = 2538;
  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. TranslatorContext[] shaderContexts = null;
  223. if (!isHostProgramValid)
  224. {
  225. shaderContexts = new TranslatorContext[1 + entries.Length];
  226. for (int i = 0; i < entries.Length; i++)
  227. {
  228. GuestShaderCacheEntry entry = entries[i];
  229. if (entry == null)
  230. {
  231. continue;
  232. }
  233. var binaryCode = new Memory<byte>(entry.Code);
  234. var gpuAccessor = new CachedGpuAccessor(
  235. _context,
  236. binaryCode,
  237. binaryCode.Slice(binaryCode.Length - entry.Header.Cb1DataSize),
  238. entry.Header.GpuAccessorHeader,
  239. entry.TextureDescriptors);
  240. var options = new TranslationOptions(TargetLanguage.Glsl, TargetApi.OpenGL, flags);
  241. shaderContexts[i + 1] = Translator.CreateContext(0, gpuAccessor, options, counts);
  242. if (entry.Header.SizeA != 0)
  243. {
  244. var options2 = new TranslationOptions(TargetLanguage.Glsl, TargetApi.OpenGL, flags | TranslationFlags.VertexA);
  245. shaderContexts[0] = Translator.CreateContext((ulong)entry.Header.Size, gpuAccessor, options2, counts);
  246. }
  247. }
  248. }
  249. // Reconstruct code holder.
  250. for (int i = 0; i < entries.Length; i++)
  251. {
  252. GuestShaderCacheEntry entry = entries[i];
  253. if (entry == null)
  254. {
  255. continue;
  256. }
  257. ShaderProgram program;
  258. ShaderProgramInfo shaderProgramInfo;
  259. if (isHostProgramValid)
  260. {
  261. program = new ShaderProgram(entry.Header.Stage, "");
  262. shaderProgramInfo = hostShaderEntries[i].ToShaderProgramInfo();
  263. }
  264. else
  265. {
  266. int stageIndex = i + 1;
  267. TranslatorContext currentStage = shaderContexts[stageIndex];
  268. TranslatorContext nextStage = GetNextStageContext(shaderContexts, stageIndex);
  269. TranslatorContext vertexA = stageIndex == 1 ? shaderContexts[0] : null;
  270. program = currentStage.Translate(out shaderProgramInfo, nextStage, vertexA);
  271. }
  272. // NOTE: Vertex B comes first in the shader cache.
  273. byte[] code = entry.Code.AsSpan().Slice(0, entry.Header.Size - entry.Header.Cb1DataSize).ToArray();
  274. byte[] code2 = entry.Header.SizeA != 0 ? entry.Code.AsSpan().Slice(entry.Header.Size, entry.Header.SizeA).ToArray() : null;
  275. shaders[i] = new ShaderCodeHolder(program, shaderProgramInfo, code, code2);
  276. shaderPrograms.Add(program);
  277. }
  278. });
  279. task.OnTask(compileTask, (bool _, ShaderCompileTask task) =>
  280. {
  281. if (task.IsFaulted)
  282. {
  283. Logger.Warning?.Print(LogClass.Gpu, $"Host shader {key} is corrupted or incompatible, discarding...");
  284. _cacheManager.RemoveProgram(ref key);
  285. return true; // Exit early, the decoding step failed.
  286. }
  287. // If the host program was rejected by the gpu driver or isn't in cache, try to build from program sources again.
  288. if (!isHostProgramValid)
  289. {
  290. Logger.Info?.Print(LogClass.Gpu, $"Host shader {key} got invalidated, rebuilding from guest...");
  291. List<IShader> hostShaders = new List<IShader>();
  292. // Compile shaders and create program as the shader program binary got invalidated.
  293. for (int stage = 0; stage < Constants.ShaderStages; stage++)
  294. {
  295. ShaderProgram program = shaders[stage]?.Program;
  296. if (program == null)
  297. {
  298. continue;
  299. }
  300. IShader hostShader = _context.Renderer.CompileShader(program.Stage, program.Code);
  301. shaders[stage].HostShader = hostShader;
  302. hostShaders.Add(hostShader);
  303. }
  304. hostProgram = _context.Renderer.CreateProgram(hostShaders.ToArray(), tfd);
  305. task.OnCompiled(hostProgram, (bool isNewProgramValid, ShaderCompileTask task) =>
  306. {
  307. // As the host program was invalidated, save the new entry in the cache.
  308. hostProgramBinary = HostShaderCacheEntry.Create(hostProgram.GetBinary(), shaders);
  309. if (!isReadOnly)
  310. {
  311. if (hasHostCache)
  312. {
  313. _cacheManager.ReplaceHostProgram(ref key, hostProgramBinary);
  314. }
  315. else
  316. {
  317. Logger.Warning?.Print(LogClass.Gpu, $"Add missing host shader {key} in cache (is the cache incomplete?)");
  318. _cacheManager.AddHostProgram(ref key, hostProgramBinary);
  319. }
  320. }
  321. _gpProgramsDiskCache.Add(key, new ShaderBundle(hostProgram, shaders));
  322. return true;
  323. });
  324. return false; // Not finished: still need to compile the host program.
  325. }
  326. else
  327. {
  328. _gpProgramsDiskCache.Add(key, new ShaderBundle(hostProgram, shaders));
  329. return true;
  330. }
  331. });
  332. return false; // Not finished: translating the program.
  333. });
  334. }
  335. _shaderCount = ++programIndex;
  336. }
  337. // Process the queue.
  338. for (int i = 0; i < activeTasks.Count; i++)
  339. {
  340. ShaderCompileTask task = activeTasks[i];
  341. if (task.IsDone())
  342. {
  343. activeTasks.RemoveAt(i--);
  344. }
  345. }
  346. if (activeTasks.Count == maxTaskCount)
  347. {
  348. // Wait for a task to be done, or for 1ms.
  349. // Host shader compilation cannot signal when it is done,
  350. // so the 1ms timeout is required to poll status.
  351. taskDoneEvent.WaitOne(1);
  352. }
  353. }
  354. if (!isReadOnly)
  355. {
  356. // Remove entries that are broken in the cache
  357. _cacheManager.RemoveManifestEntries(invalidEntries);
  358. _cacheManager.FlushToArchive();
  359. _cacheManager.Synchronize();
  360. }
  361. progressReportEvent.Set();
  362. progressReportThread?.Join();
  363. ShaderCacheStateChanged?.Invoke(ShaderCacheState.Loaded, _shaderCount, _totalShaderCount);
  364. Logger.Info?.Print(LogClass.Gpu, $"Shader cache loaded {_shaderCount} entries.");
  365. }
  366. }
  367. /// <summary>
  368. /// Raises ShaderCacheStateChanged events periodically.
  369. /// </summary>
  370. private void ReportProgress(object state)
  371. {
  372. const int refreshRate = 50; // ms
  373. AutoResetEvent endEvent = (AutoResetEvent)state;
  374. int count = 0;
  375. do
  376. {
  377. int newCount = _shaderCount;
  378. if (count != newCount)
  379. {
  380. ShaderCacheStateChanged?.Invoke(ShaderCacheState.Loading, newCount, _totalShaderCount);
  381. count = newCount;
  382. }
  383. }
  384. while (!endEvent.WaitOne(refreshRate));
  385. }
  386. /// <summary>
  387. /// Gets a compute shader from the cache.
  388. /// </summary>
  389. /// <remarks>
  390. /// This automatically translates, compiles and adds the code to the cache if not present.
  391. /// </remarks>
  392. /// <param name="channel">GPU channel</param>
  393. /// <param name="gas">GPU accessor state</param>
  394. /// <param name="gpuVa">GPU virtual address of the binary shader code</param>
  395. /// <param name="localSizeX">Local group size X of the computer shader</param>
  396. /// <param name="localSizeY">Local group size Y of the computer shader</param>
  397. /// <param name="localSizeZ">Local group size Z of the computer shader</param>
  398. /// <param name="localMemorySize">Local memory size of the compute shader</param>
  399. /// <param name="sharedMemorySize">Shared memory size of the compute shader</param>
  400. /// <returns>Compiled compute shader code</returns>
  401. public ShaderBundle GetComputeShader(
  402. GpuChannel channel,
  403. GpuAccessorState gas,
  404. ulong gpuVa,
  405. int localSizeX,
  406. int localSizeY,
  407. int localSizeZ,
  408. int localMemorySize,
  409. int sharedMemorySize)
  410. {
  411. bool isCached = _cpPrograms.TryGetValue(gpuVa, out List<ShaderBundle> list);
  412. if (isCached)
  413. {
  414. foreach (ShaderBundle cachedCpShader in list)
  415. {
  416. if (IsShaderEqual(channel.MemoryManager, cachedCpShader, gpuVa))
  417. {
  418. return cachedCpShader;
  419. }
  420. }
  421. }
  422. TranslatorContext[] shaderContexts = new TranslatorContext[1];
  423. shaderContexts[0] = DecodeComputeShader(
  424. channel,
  425. gas,
  426. gpuVa,
  427. localSizeX,
  428. localSizeY,
  429. localSizeZ,
  430. localMemorySize,
  431. sharedMemorySize);
  432. bool isShaderCacheEnabled = _cacheManager != null;
  433. bool isShaderCacheReadOnly = false;
  434. Hash128 programCodeHash = default;
  435. GuestShaderCacheEntry[] shaderCacheEntries = null;
  436. // Current shader cache doesn't support bindless textures
  437. if (shaderContexts[0].UsedFeatures.HasFlag(FeatureFlags.Bindless))
  438. {
  439. isShaderCacheEnabled = false;
  440. }
  441. if (isShaderCacheEnabled)
  442. {
  443. isShaderCacheReadOnly = _cacheManager.IsReadOnly;
  444. // Compute hash and prepare data for shader disk cache comparison.
  445. shaderCacheEntries = CacheHelper.CreateShaderCacheEntries(channel, shaderContexts);
  446. programCodeHash = CacheHelper.ComputeGuestHashFromCache(shaderCacheEntries);
  447. }
  448. ShaderBundle cpShader;
  449. // Search for the program hash in loaded shaders.
  450. if (!isShaderCacheEnabled || !_cpProgramsDiskCache.TryGetValue(programCodeHash, out cpShader))
  451. {
  452. if (isShaderCacheEnabled)
  453. {
  454. Logger.Debug?.Print(LogClass.Gpu, $"Shader {programCodeHash} not in cache, compiling!");
  455. }
  456. // The shader isn't currently cached, translate it and compile it.
  457. ShaderCodeHolder shader = TranslateShader(_dumper, channel.MemoryManager, shaderContexts[0], null, null);
  458. shader.HostShader = _context.Renderer.CompileShader(ShaderStage.Compute, shader.Program.Code);
  459. IProgram hostProgram = _context.Renderer.CreateProgram(new IShader[] { shader.HostShader }, null);
  460. hostProgram.CheckProgramLink(true);
  461. byte[] hostProgramBinary = HostShaderCacheEntry.Create(hostProgram.GetBinary(), new ShaderCodeHolder[] { shader });
  462. cpShader = new ShaderBundle(hostProgram, shader);
  463. if (isShaderCacheEnabled)
  464. {
  465. _cpProgramsDiskCache.Add(programCodeHash, cpShader);
  466. if (!isShaderCacheReadOnly)
  467. {
  468. _cacheManager.SaveProgram(ref programCodeHash, CacheHelper.CreateGuestProgramDump(shaderCacheEntries), hostProgramBinary);
  469. }
  470. }
  471. }
  472. if (!isCached)
  473. {
  474. list = new List<ShaderBundle>();
  475. _cpPrograms.Add(gpuVa, list);
  476. }
  477. list.Add(cpShader);
  478. return cpShader;
  479. }
  480. /// <summary>
  481. /// Gets a graphics shader program from the shader cache.
  482. /// This includes all the specified shader stages.
  483. /// </summary>
  484. /// <remarks>
  485. /// This automatically translates, compiles and adds the code to the cache if not present.
  486. /// </remarks>
  487. /// <param name="state">GPU state</param>
  488. /// <param name="channel">GPU channel</param>
  489. /// <param name="gas">GPU accessor state</param>
  490. /// <param name="addresses">Addresses of the shaders for each stage</param>
  491. /// <returns>Compiled graphics shader code</returns>
  492. public ShaderBundle GetGraphicsShader(ref ThreedClassState state, GpuChannel channel, GpuAccessorState gas, ShaderAddresses addresses)
  493. {
  494. bool isCached = _gpPrograms.TryGetValue(addresses, out List<ShaderBundle> list);
  495. if (isCached)
  496. {
  497. foreach (ShaderBundle cachedGpShaders in list)
  498. {
  499. if (IsShaderEqual(channel.MemoryManager, cachedGpShaders, addresses))
  500. {
  501. return cachedGpShaders;
  502. }
  503. }
  504. }
  505. TranslatorContext[] shaderContexts = new TranslatorContext[Constants.ShaderStages + 1];
  506. TransformFeedbackDescriptor[] tfd = GetTransformFeedbackDescriptors(ref state);
  507. TranslationFlags flags = DefaultFlags;
  508. if (tfd != null)
  509. {
  510. flags |= TranslationFlags.Feedback;
  511. }
  512. TranslationCounts counts = new TranslationCounts();
  513. if (addresses.VertexA != 0)
  514. {
  515. shaderContexts[0] = DecodeGraphicsShader(channel, gas, counts, flags | TranslationFlags.VertexA, ShaderStage.Vertex, addresses.VertexA);
  516. }
  517. shaderContexts[1] = DecodeGraphicsShader(channel, gas, counts, flags, ShaderStage.Vertex, addresses.Vertex);
  518. shaderContexts[2] = DecodeGraphicsShader(channel, gas, counts, flags, ShaderStage.TessellationControl, addresses.TessControl);
  519. shaderContexts[3] = DecodeGraphicsShader(channel, gas, counts, flags, ShaderStage.TessellationEvaluation, addresses.TessEvaluation);
  520. shaderContexts[4] = DecodeGraphicsShader(channel, gas, counts, flags, ShaderStage.Geometry, addresses.Geometry);
  521. shaderContexts[5] = DecodeGraphicsShader(channel, gas, counts, flags, ShaderStage.Fragment, addresses.Fragment);
  522. bool isShaderCacheEnabled = _cacheManager != null;
  523. bool isShaderCacheReadOnly = false;
  524. Hash128 programCodeHash = default;
  525. GuestShaderCacheEntry[] shaderCacheEntries = null;
  526. // Current shader cache doesn't support bindless textures
  527. for (int i = 0; i < shaderContexts.Length; i++)
  528. {
  529. if (shaderContexts[i] != null && shaderContexts[i].UsedFeatures.HasFlag(FeatureFlags.Bindless))
  530. {
  531. isShaderCacheEnabled = false;
  532. break;
  533. }
  534. }
  535. if (isShaderCacheEnabled)
  536. {
  537. isShaderCacheReadOnly = _cacheManager.IsReadOnly;
  538. // Compute hash and prepare data for shader disk cache comparison.
  539. shaderCacheEntries = CacheHelper.CreateShaderCacheEntries(channel, shaderContexts);
  540. programCodeHash = CacheHelper.ComputeGuestHashFromCache(shaderCacheEntries, tfd);
  541. }
  542. ShaderBundle gpShaders;
  543. // Search for the program hash in loaded shaders.
  544. if (!isShaderCacheEnabled || !_gpProgramsDiskCache.TryGetValue(programCodeHash, out gpShaders))
  545. {
  546. if (isShaderCacheEnabled)
  547. {
  548. Logger.Debug?.Print(LogClass.Gpu, $"Shader {programCodeHash} not in cache, compiling!");
  549. }
  550. // The shader isn't currently cached, translate it and compile it.
  551. ShaderCodeHolder[] shaders = new ShaderCodeHolder[Constants.ShaderStages];
  552. for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++)
  553. {
  554. shaders[stageIndex] = TranslateShader(_dumper, channel.MemoryManager, shaderContexts, stageIndex + 1);
  555. }
  556. List<IShader> hostShaders = new List<IShader>();
  557. for (int stage = 0; stage < Constants.ShaderStages; stage++)
  558. {
  559. ShaderProgram program = shaders[stage]?.Program;
  560. if (program == null)
  561. {
  562. continue;
  563. }
  564. IShader hostShader = _context.Renderer.CompileShader(program.Stage, program.Code);
  565. shaders[stage].HostShader = hostShader;
  566. hostShaders.Add(hostShader);
  567. }
  568. IProgram hostProgram = _context.Renderer.CreateProgram(hostShaders.ToArray(), tfd);
  569. hostProgram.CheckProgramLink(true);
  570. byte[] hostProgramBinary = HostShaderCacheEntry.Create(hostProgram.GetBinary(), shaders);
  571. gpShaders = new ShaderBundle(hostProgram, shaders);
  572. if (isShaderCacheEnabled)
  573. {
  574. _gpProgramsDiskCache.Add(programCodeHash, gpShaders);
  575. if (!isShaderCacheReadOnly)
  576. {
  577. _cacheManager.SaveProgram(ref programCodeHash, CacheHelper.CreateGuestProgramDump(shaderCacheEntries, tfd), hostProgramBinary);
  578. }
  579. }
  580. }
  581. if (!isCached)
  582. {
  583. list = new List<ShaderBundle>();
  584. _gpPrograms.Add(addresses, list);
  585. }
  586. list.Add(gpShaders);
  587. return gpShaders;
  588. }
  589. /// <summary>
  590. /// Gets transform feedback state from the current GPU state.
  591. /// </summary>
  592. /// <param name="state">Current GPU state</param>
  593. /// <returns>Four transform feedback descriptors for the enabled TFBs, or null if TFB is disabled</returns>
  594. private static TransformFeedbackDescriptor[] GetTransformFeedbackDescriptors(ref ThreedClassState state)
  595. {
  596. bool tfEnable = state.TfEnable;
  597. if (!tfEnable)
  598. {
  599. return null;
  600. }
  601. TransformFeedbackDescriptor[] descs = new TransformFeedbackDescriptor[Constants.TotalTransformFeedbackBuffers];
  602. for (int i = 0; i < Constants.TotalTransformFeedbackBuffers; i++)
  603. {
  604. var tf = state.TfState[i];
  605. int length = (int)Math.Min((uint)tf.VaryingsCount, 0x80);
  606. var varyingLocations = MemoryMarshal.Cast<uint, byte>(state.TfVaryingLocations[i].ToSpan()).Slice(0, length);
  607. descs[i] = new TransformFeedbackDescriptor(tf.BufferIndex, tf.Stride, varyingLocations.ToArray());
  608. }
  609. return descs;
  610. }
  611. /// <summary>
  612. /// Checks if compute shader code in memory is equal to the cached shader.
  613. /// </summary>
  614. /// <param name="memoryManager">Memory manager used to access the GPU memory where the shader is located</param>
  615. /// <param name="cpShader">Cached compute shader</param>
  616. /// <param name="gpuVa">GPU virtual address of the shader code in memory</param>
  617. /// <returns>True if the code is different, false otherwise</returns>
  618. private static bool IsShaderEqual(MemoryManager memoryManager, ShaderBundle cpShader, ulong gpuVa)
  619. {
  620. return IsShaderEqual(memoryManager, cpShader.Shaders[0], gpuVa);
  621. }
  622. /// <summary>
  623. /// Checks if graphics shader code from all stages in memory are equal to the cached shaders.
  624. /// </summary>
  625. /// <param name="memoryManager">Memory manager used to access the GPU memory where the shader is located</param>
  626. /// <param name="gpShaders">Cached graphics shaders</param>
  627. /// <param name="addresses">GPU virtual addresses of all enabled shader stages</param>
  628. /// <returns>True if the code is different, false otherwise</returns>
  629. private static bool IsShaderEqual(MemoryManager memoryManager, ShaderBundle gpShaders, ShaderAddresses addresses)
  630. {
  631. for (int stage = 0; stage < gpShaders.Shaders.Length; stage++)
  632. {
  633. ShaderCodeHolder shader = gpShaders.Shaders[stage];
  634. ulong gpuVa = 0;
  635. switch (stage)
  636. {
  637. case 0: gpuVa = addresses.Vertex; break;
  638. case 1: gpuVa = addresses.TessControl; break;
  639. case 2: gpuVa = addresses.TessEvaluation; break;
  640. case 3: gpuVa = addresses.Geometry; break;
  641. case 4: gpuVa = addresses.Fragment; break;
  642. }
  643. if (!IsShaderEqual(memoryManager, shader, gpuVa, addresses.VertexA))
  644. {
  645. return false;
  646. }
  647. }
  648. return true;
  649. }
  650. /// <summary>
  651. /// Checks if the code of the specified cached shader is different from the code in memory.
  652. /// </summary>
  653. /// <param name="memoryManager">Memory manager used to access the GPU memory where the shader is located</param>
  654. /// <param name="shader">Cached shader to compare with</param>
  655. /// <param name="gpuVa">GPU virtual address of the binary shader code</param>
  656. /// <param name="gpuVaA">Optional GPU virtual address of the "Vertex A" binary shader code</param>
  657. /// <returns>True if the code is different, false otherwise</returns>
  658. private static bool IsShaderEqual(MemoryManager memoryManager, ShaderCodeHolder shader, ulong gpuVa, ulong gpuVaA = 0)
  659. {
  660. if (shader == null)
  661. {
  662. return true;
  663. }
  664. ReadOnlySpan<byte> memoryCode = memoryManager.GetSpan(gpuVa, shader.Code.Length);
  665. bool equals = memoryCode.SequenceEqual(shader.Code);
  666. if (equals && shader.Code2 != null)
  667. {
  668. memoryCode = memoryManager.GetSpan(gpuVaA, shader.Code2.Length);
  669. equals = memoryCode.SequenceEqual(shader.Code2);
  670. }
  671. return equals;
  672. }
  673. /// <summary>
  674. /// Decode the binary Maxwell shader code to a translator context.
  675. /// </summary>
  676. /// <param name="channel">GPU channel</param>
  677. /// <param name="gas">GPU accessor state</param>
  678. /// <param name="gpuVa">GPU virtual address of the binary shader code</param>
  679. /// <param name="localSizeX">Local group size X of the computer shader</param>
  680. /// <param name="localSizeY">Local group size Y of the computer shader</param>
  681. /// <param name="localSizeZ">Local group size Z of the computer shader</param>
  682. /// <param name="localMemorySize">Local memory size of the compute shader</param>
  683. /// <param name="sharedMemorySize">Shared memory size of the compute shader</param>
  684. /// <returns>The generated translator context</returns>
  685. private TranslatorContext DecodeComputeShader(
  686. GpuChannel channel,
  687. GpuAccessorState gas,
  688. ulong gpuVa,
  689. int localSizeX,
  690. int localSizeY,
  691. int localSizeZ,
  692. int localMemorySize,
  693. int sharedMemorySize)
  694. {
  695. if (gpuVa == 0)
  696. {
  697. return null;
  698. }
  699. GpuAccessor gpuAccessor = new GpuAccessor(_context, channel, gas, localSizeX, localSizeY, localSizeZ, localMemorySize, sharedMemorySize);
  700. var options = new TranslationOptions(TargetLanguage.Glsl, TargetApi.OpenGL, DefaultFlags | TranslationFlags.Compute);
  701. return Translator.CreateContext(gpuVa, gpuAccessor, options);
  702. }
  703. /// <summary>
  704. /// Decode the binary Maxwell shader code to a translator context.
  705. /// </summary>
  706. /// <remarks>
  707. /// This will combine the "Vertex A" and "Vertex B" shader stages, if specified, into one shader.
  708. /// </remarks>
  709. /// <param name="channel">GPU channel</param>
  710. /// <param name="gas">GPU accessor state</param>
  711. /// <param name="counts">Cumulative shader resource counts</param>
  712. /// <param name="flags">Flags that controls shader translation</param>
  713. /// <param name="stage">Shader stage</param>
  714. /// <param name="gpuVa">GPU virtual address of the shader code</param>
  715. /// <returns>The generated translator context</returns>
  716. private TranslatorContext DecodeGraphicsShader(
  717. GpuChannel channel,
  718. GpuAccessorState gas,
  719. TranslationCounts counts,
  720. TranslationFlags flags,
  721. ShaderStage stage,
  722. ulong gpuVa)
  723. {
  724. if (gpuVa == 0)
  725. {
  726. return null;
  727. }
  728. GpuAccessor gpuAccessor = new GpuAccessor(_context, channel, gas, (int)stage - 1);
  729. var options = new TranslationOptions(TargetLanguage.Glsl, TargetApi.OpenGL, flags);
  730. return Translator.CreateContext(gpuVa, gpuAccessor, options, counts);
  731. }
  732. /// <summary>
  733. /// Translates a previously generated translator context to something that the host API accepts.
  734. /// </summary>
  735. /// <param name="dumper">Optional shader code dumper</param>
  736. /// <param name="memoryManager">Memory manager used to access the GPU memory where the shader is located</param>
  737. /// <param name="stages">Translator context of all available shader stages</param>
  738. /// <param name="stageIndex">Index on the stages array to translate</param>
  739. /// <returns>Compiled graphics shader code</returns>
  740. private static ShaderCodeHolder TranslateShader(
  741. ShaderDumper dumper,
  742. MemoryManager memoryManager,
  743. TranslatorContext[] stages,
  744. int stageIndex)
  745. {
  746. TranslatorContext currentStage = stages[stageIndex];
  747. TranslatorContext nextStage = GetNextStageContext(stages, stageIndex);
  748. TranslatorContext vertexA = stageIndex == 1 ? stages[0] : null;
  749. return TranslateShader(dumper, memoryManager, currentStage, nextStage, vertexA);
  750. }
  751. /// <summary>
  752. /// Gets the next shader stage context, from an array of contexts and index of the current stage.
  753. /// </summary>
  754. /// <param name="stages">Translator context of all available shader stages</param>
  755. /// <param name="stageIndex">Index on the stages array to translate</param>
  756. /// <returns>The translator context of the next stage, or null if inexistent</returns>
  757. private static TranslatorContext GetNextStageContext(TranslatorContext[] stages, int stageIndex)
  758. {
  759. for (int nextStageIndex = stageIndex + 1; nextStageIndex < stages.Length; nextStageIndex++)
  760. {
  761. if (stages[nextStageIndex] != null)
  762. {
  763. return stages[nextStageIndex];
  764. }
  765. }
  766. return null;
  767. }
  768. /// <summary>
  769. /// Translates a previously generated translator context to something that the host API accepts.
  770. /// </summary>
  771. /// <param name="dumper">Optional shader code dumper</param>
  772. /// <param name="memoryManager">Memory manager used to access the GPU memory where the shader is located</param>
  773. /// <param name="currentStage">Translator context of the stage to be translated</param>
  774. /// <param name="nextStage">Translator context of the next active stage, if existent</param>
  775. /// <param name="vertexA">Optional translator context of the shader that should be combined</param>
  776. /// <returns>Compiled graphics shader code</returns>
  777. private static ShaderCodeHolder TranslateShader(
  778. ShaderDumper dumper,
  779. MemoryManager memoryManager,
  780. TranslatorContext currentStage,
  781. TranslatorContext nextStage,
  782. TranslatorContext vertexA)
  783. {
  784. if (currentStage == null)
  785. {
  786. return null;
  787. }
  788. if (vertexA != null)
  789. {
  790. byte[] codeA = memoryManager.GetSpan(vertexA.Address, vertexA.Size).ToArray();
  791. byte[] codeB = memoryManager.GetSpan(currentStage.Address, currentStage.Size).ToArray();
  792. ShaderDumpPaths pathsA = default;
  793. ShaderDumpPaths pathsB = default;
  794. if (dumper != null)
  795. {
  796. pathsA = dumper.Dump(codeA, compute: false);
  797. pathsB = dumper.Dump(codeB, compute: false);
  798. }
  799. ShaderProgram program = currentStage.Translate(out ShaderProgramInfo shaderProgramInfo, nextStage, vertexA);
  800. pathsB.Prepend(program);
  801. pathsA.Prepend(program);
  802. return new ShaderCodeHolder(program, shaderProgramInfo, codeB, codeA);
  803. }
  804. else
  805. {
  806. byte[] code = memoryManager.GetSpan(currentStage.Address, currentStage.Size).ToArray();
  807. ShaderDumpPaths paths = dumper?.Dump(code, currentStage.Stage == ShaderStage.Compute) ?? default;
  808. ShaderProgram program = currentStage.Translate(out ShaderProgramInfo shaderProgramInfo, nextStage);
  809. paths.Prepend(program);
  810. return new ShaderCodeHolder(program, shaderProgramInfo, code);
  811. }
  812. }
  813. /// <summary>
  814. /// Disposes the shader cache, deleting all the cached shaders.
  815. /// It's an error to use the shader cache after disposal.
  816. /// </summary>
  817. public void Dispose()
  818. {
  819. foreach (List<ShaderBundle> list in _cpPrograms.Values)
  820. {
  821. foreach (ShaderBundle bundle in list)
  822. {
  823. bundle.Dispose();
  824. }
  825. }
  826. foreach (List<ShaderBundle> list in _gpPrograms.Values)
  827. {
  828. foreach (ShaderBundle bundle in list)
  829. {
  830. bundle.Dispose();
  831. }
  832. }
  833. _cacheManager?.Dispose();
  834. }
  835. }
  836. }