/ src / Ryujinx.Graphics.Gpu / Shader / DiskCache / DiskCacheHostStorage.cs
DiskCacheHostStorage.cs
  1  using Ryujinx.Graphics.GAL;
  2  using Ryujinx.Graphics.Shader;
  3  using Ryujinx.Graphics.Shader.Translation;
  4  using System;
  5  using System.IO;
  6  using System.Numerics;
  7  using System.Runtime.CompilerServices;
  8  
  9  namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
 10  {
 11      /// <summary>
 12      /// On-disk shader cache storage for host code.
 13      /// </summary>
 14      class DiskCacheHostStorage
 15      {
 16          private const uint TocsMagic = (byte)'T' | ((byte)'O' << 8) | ((byte)'C' << 16) | ((byte)'S' << 24);
 17          private const uint TochMagic = (byte)'T' | ((byte)'O' << 8) | ((byte)'C' << 16) | ((byte)'H' << 24);
 18          private const uint ShdiMagic = (byte)'S' | ((byte)'H' << 8) | ((byte)'D' << 16) | ((byte)'I' << 24);
 19          private const uint BufdMagic = (byte)'B' | ((byte)'U' << 8) | ((byte)'F' << 16) | ((byte)'D' << 24);
 20          private const uint TexdMagic = (byte)'T' | ((byte)'E' << 8) | ((byte)'X' << 16) | ((byte)'D' << 24);
 21  
 22          private const ushort FileFormatVersionMajor = 1;
 23          private const ushort FileFormatVersionMinor = 2;
 24          private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
 25          private const uint CodeGenVersion = 7353;
 26  
 27          private const string SharedTocFileName = "shared.toc";
 28          private const string SharedDataFileName = "shared.data";
 29  
 30          private readonly string _basePath;
 31  
 32          public bool CacheEnabled => !string.IsNullOrEmpty(_basePath);
 33  
 34          /// <summary>
 35          /// TOC (Table of contents) file header.
 36          /// </summary>
 37          private struct TocHeader
 38          {
 39              /// <summary>
 40              /// Magic value, for validation and identification.
 41              /// </summary>
 42              public uint Magic;
 43  
 44              /// <summary>
 45              /// File format version.
 46              /// </summary>
 47              public uint FormatVersion;
 48  
 49              /// <summary>
 50              /// Generated shader code version.
 51              /// </summary>
 52              public uint CodeGenVersion;
 53  
 54              /// <summary>
 55              /// Header padding.
 56              /// </summary>
 57              public uint Padding;
 58  
 59              /// <summary>
 60              /// Timestamp of when the file was first created.
 61              /// </summary>
 62              public ulong Timestamp;
 63  
 64              /// <summary>
 65              /// Reserved space, to be used in the future. Write as zero.
 66              /// </summary>
 67              public ulong Reserved;
 68          }
 69  
 70          /// <summary>
 71          /// Offset and size pair.
 72          /// </summary>
 73          private struct OffsetAndSize
 74          {
 75              /// <summary>
 76              /// Offset.
 77              /// </summary>
 78              public ulong Offset;
 79  
 80              /// <summary>
 81              /// Size of uncompressed data.
 82              /// </summary>
 83              public uint UncompressedSize;
 84  
 85              /// <summary>
 86              /// Size of compressed data.
 87              /// </summary>
 88              public uint CompressedSize;
 89          }
 90  
 91          /// <summary>
 92          /// Per-stage data entry.
 93          /// </summary>
 94          private struct DataEntryPerStage
 95          {
 96              /// <summary>
 97              /// Index of the guest code on the guest code cache TOC file.
 98              /// </summary>
 99              public int GuestCodeIndex;
100          }
101  
102          /// <summary>
103          /// Per-program data entry.
104          /// </summary>
105          private struct DataEntry
106          {
107              /// <summary>
108              /// Bit mask where each bit set is a used shader stage. Should be zero for compute shaders.
109              /// </summary>
110              public uint StagesBitMask;
111          }
112  
113          /// <summary>
114          /// Per-stage shader information, returned by the translator.
115          /// </summary>
116          private struct DataShaderInfo
117          {
118              /// <summary>
119              /// Total constant buffers used.
120              /// </summary>
121              public ushort CBuffersCount;
122  
123              /// <summary>
124              /// Total storage buffers used.
125              /// </summary>
126              public ushort SBuffersCount;
127  
128              /// <summary>
129              /// Total textures used.
130              /// </summary>
131              public ushort TexturesCount;
132  
133              /// <summary>
134              /// Total images used.
135              /// </summary>
136              public ushort ImagesCount;
137  
138              /// <summary>
139              /// Shader stage.
140              /// </summary>
141              public ShaderStage Stage;
142  
143              /// <summary>
144              /// Number of vertices that each output primitive has on a geometry shader.
145              /// </summary>
146              public byte GeometryVerticesPerPrimitive;
147  
148              /// <summary>
149              /// Maximum number of vertices that a geometry shader may generate.
150              /// </summary>
151              public ushort GeometryMaxOutputVertices;
152  
153              /// <summary>
154              /// Number of invocations per primitive on tessellation or geometry shaders.
155              /// </summary>
156              public ushort ThreadsPerInputPrimitive;
157  
158              /// <summary>
159              /// Indicates if the fragment shader accesses the fragment coordinate built-in variable.
160              /// </summary>
161              public bool UsesFragCoord;
162  
163              /// <summary>
164              /// Indicates if the shader accesses the Instance ID built-in variable.
165              /// </summary>
166              public bool UsesInstanceId;
167  
168              /// <summary>
169              /// Indicates if the shader modifies the Layer built-in variable.
170              /// </summary>
171              public bool UsesRtLayer;
172  
173              /// <summary>
174              /// Bit mask with the clip distances written on the vertex stage.
175              /// </summary>
176              public byte ClipDistancesWritten;
177  
178              /// <summary>
179              /// Bit mask of the render target components written by the fragment stage.
180              /// </summary>
181              public int FragmentOutputMap;
182  
183              /// <summary>
184              /// Indicates if the vertex shader accesses draw parameters.
185              /// </summary>
186              public bool UsesDrawParameters;
187          }
188  
189          private readonly DiskCacheGuestStorage _guestStorage;
190  
191          /// <summary>
192          /// Creates a disk cache host storage.
193          /// </summary>
194          /// <param name="basePath">Base path of the shader cache</param>
195          public DiskCacheHostStorage(string basePath)
196          {
197              _basePath = basePath;
198              _guestStorage = new DiskCacheGuestStorage(basePath);
199  
200              if (CacheEnabled)
201              {
202                  Directory.CreateDirectory(basePath);
203              }
204          }
205  
206          /// <summary>
207          /// Gets the total of host programs on the cache.
208          /// </summary>
209          /// <returns>Host programs count</returns>
210          public int GetProgramCount()
211          {
212              string tocFilePath = Path.Combine(_basePath, SharedTocFileName);
213  
214              if (!File.Exists(tocFilePath))
215              {
216                  return 0;
217              }
218  
219              return Math.Max((int)((new FileInfo(tocFilePath).Length - Unsafe.SizeOf<TocHeader>()) / sizeof(ulong)), 0);
220          }
221  
222          /// <summary>
223          /// Guest the name of the host program cache file, with extension.
224          /// </summary>
225          /// <param name="context">GPU context</param>
226          /// <returns>Name of the file, without extension</returns>
227          private static string GetHostFileName(GpuContext context)
228          {
229              string apiName = context.Capabilities.Api.ToString().ToLowerInvariant();
230              string vendorName = RemoveInvalidCharacters(context.Capabilities.VendorName.ToLowerInvariant());
231              return $"{apiName}_{vendorName}";
232          }
233  
234          /// <summary>
235          /// Removes invalid path characters and spaces from a file name.
236          /// </summary>
237          /// <param name="fileName">File name</param>
238          /// <returns>Filtered file name</returns>
239          private static string RemoveInvalidCharacters(string fileName)
240          {
241              int indexOfSpace = fileName.IndexOf(' ');
242              if (indexOfSpace >= 0)
243              {
244                  fileName = fileName[..indexOfSpace];
245              }
246  
247              return string.Concat(fileName.Split(Path.GetInvalidFileNameChars(), StringSplitOptions.RemoveEmptyEntries));
248          }
249  
250          /// <summary>
251          /// Gets the name of the TOC host file.
252          /// </summary>
253          /// <param name="context">GPU context</param>
254          /// <returns>File name</returns>
255          private static string GetHostTocFileName(GpuContext context)
256          {
257              return GetHostFileName(context) + ".toc";
258          }
259  
260          /// <summary>
261          /// Gets the name of the data host file.
262          /// </summary>
263          /// <param name="context">GPU context</param>
264          /// <returns>File name</returns>
265          private static string GetHostDataFileName(GpuContext context)
266          {
267              return GetHostFileName(context) + ".data";
268          }
269  
270          /// <summary>
271          /// Checks if a disk cache exists for the current application.
272          /// </summary>
273          /// <returns>True if a disk cache exists, false otherwise</returns>
274          public bool CacheExists()
275          {
276              string tocFilePath = Path.Combine(_basePath, SharedTocFileName);
277              string dataFilePath = Path.Combine(_basePath, SharedDataFileName);
278  
279              if (!File.Exists(tocFilePath) || !File.Exists(dataFilePath) || !_guestStorage.TocFileExists() || !_guestStorage.DataFileExists())
280              {
281                  return false;
282              }
283  
284              return true;
285          }
286  
287          /// <summary>
288          /// Loads all shaders from the cache.
289          /// </summary>
290          /// <param name="context">GPU context</param>
291          /// <param name="loader">Parallel disk cache loader</param>
292          public void LoadShaders(GpuContext context, ParallelDiskCacheLoader loader)
293          {
294              if (!CacheExists())
295              {
296                  return;
297              }
298  
299              Stream hostTocFileStream = null;
300              Stream hostDataFileStream = null;
301  
302              try
303              {
304                  using var tocFileStream = DiskCacheCommon.OpenFile(_basePath, SharedTocFileName, writable: false);
305                  using var dataFileStream = DiskCacheCommon.OpenFile(_basePath, SharedDataFileName, writable: false);
306  
307                  using var guestTocFileStream = _guestStorage.OpenTocFileStream();
308                  using var guestDataFileStream = _guestStorage.OpenDataFileStream();
309  
310                  BinarySerializer tocReader = new(tocFileStream);
311                  BinarySerializer dataReader = new(dataFileStream);
312  
313                  TocHeader header = new();
314  
315                  if (!tocReader.TryRead(ref header) || header.Magic != TocsMagic)
316                  {
317                      throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedGeneric);
318                  }
319  
320                  if (header.FormatVersion != FileFormatVersionPacked)
321                  {
322                      throw new DiskCacheLoadException(DiskCacheLoadResult.IncompatibleVersion);
323                  }
324  
325                  bool loadHostCache = header.CodeGenVersion == CodeGenVersion;
326  
327                  int programIndex = 0;
328  
329                  DataEntry entry = new();
330  
331                  while (tocFileStream.Position < tocFileStream.Length && loader.Active)
332                  {
333                      ulong dataOffset = 0;
334                      tocReader.Read(ref dataOffset);
335  
336                      if ((ulong)dataOffset >= (ulong)dataFileStream.Length)
337                      {
338                          throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedGeneric);
339                      }
340  
341                      dataFileStream.Seek((long)dataOffset, SeekOrigin.Begin);
342  
343                      dataReader.BeginCompression();
344                      dataReader.Read(ref entry);
345                      uint stagesBitMask = entry.StagesBitMask;
346  
347                      if ((stagesBitMask & ~0x3fu) != 0)
348                      {
349                          throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedGeneric);
350                      }
351  
352                      bool isCompute = stagesBitMask == 0;
353                      if (isCompute)
354                      {
355                          stagesBitMask = 1;
356                      }
357  
358                      GuestCodeAndCbData?[] guestShaders = new GuestCodeAndCbData?[isCompute ? 1 : Constants.ShaderStages + 1];
359  
360                      DataEntryPerStage stageEntry = new();
361  
362                      while (stagesBitMask != 0)
363                      {
364                          int stageIndex = BitOperations.TrailingZeroCount(stagesBitMask);
365  
366                          dataReader.Read(ref stageEntry);
367  
368                          guestShaders[stageIndex] = _guestStorage.LoadShader(
369                              guestTocFileStream,
370                              guestDataFileStream,
371                              stageEntry.GuestCodeIndex);
372  
373                          stagesBitMask &= ~(1u << stageIndex);
374                      }
375  
376                      ShaderSpecializationState specState = ShaderSpecializationState.Read(ref dataReader);
377                      dataReader.EndCompression();
378  
379                      if (loadHostCache)
380                      {
381                          (byte[] hostCode, CachedShaderStage[] shaders) = ReadHostCode(
382                              context,
383                              ref hostTocFileStream,
384                              ref hostDataFileStream,
385                              guestShaders,
386                              programIndex,
387                              header.Timestamp);
388  
389                          if (hostCode != null)
390                          {
391                              ShaderInfo shaderInfo = ShaderInfoBuilder.BuildForCache(
392                                  context,
393                                  shaders,
394                                  specState.PipelineState,
395                                  specState.TransformFeedbackDescriptors != null);
396  
397                              IProgram hostProgram;
398  
399                              if (context.Capabilities.Api == TargetApi.Vulkan)
400                              {
401                                  ShaderSource[] shaderSources = ShaderBinarySerializer.Unpack(shaders, hostCode);
402  
403                                  hostProgram = context.Renderer.CreateProgram(shaderSources, shaderInfo);
404                              }
405                              else
406                              {
407                                  bool hasFragmentShader = shaders.Length > 5 && shaders[5] != null;
408  
409                                  hostProgram = context.Renderer.LoadProgramBinary(hostCode, hasFragmentShader, shaderInfo);
410                              }
411  
412                              CachedShaderProgram program = new(hostProgram, specState, shaders);
413  
414                              loader.QueueHostProgram(program, hostCode, programIndex, isCompute);
415                          }
416                          else
417                          {
418                              loadHostCache = false;
419                          }
420                      }
421  
422                      if (!loadHostCache)
423                      {
424                          loader.QueueGuestProgram(guestShaders, specState, programIndex, isCompute);
425                      }
426  
427                      loader.CheckCompilation();
428                      programIndex++;
429                  }
430              }
431              finally
432              {
433                  _guestStorage.ClearMemoryCache();
434  
435                  hostTocFileStream?.Dispose();
436                  hostDataFileStream?.Dispose();
437              }
438          }
439  
440          /// <summary>
441          /// Reads the host code for a given shader, if existent.
442          /// </summary>
443          /// <param name="context">GPU context</param>
444          /// <param name="tocFileStream">Host TOC file stream, intialized if needed</param>
445          /// <param name="dataFileStream">Host data file stream, initialized if needed</param>
446          /// <param name="guestShaders">Guest shader code for each active stage</param>
447          /// <param name="programIndex">Index of the program on the cache</param>
448          /// <param name="expectedTimestamp">Timestamp of the shared cache file. The host file must be newer than it</param>
449          /// <returns>Host binary code, or null if not found</returns>
450          private (byte[], CachedShaderStage[]) ReadHostCode(
451              GpuContext context,
452              ref Stream tocFileStream,
453              ref Stream dataFileStream,
454              GuestCodeAndCbData?[] guestShaders,
455              int programIndex,
456              ulong expectedTimestamp)
457          {
458              if (tocFileStream == null && dataFileStream == null)
459              {
460                  string tocFilePath = Path.Combine(_basePath, GetHostTocFileName(context));
461                  string dataFilePath = Path.Combine(_basePath, GetHostDataFileName(context));
462  
463                  if (!File.Exists(tocFilePath) || !File.Exists(dataFilePath))
464                  {
465                      return (null, null);
466                  }
467  
468                  tocFileStream = DiskCacheCommon.OpenFile(_basePath, GetHostTocFileName(context), writable: false);
469                  dataFileStream = DiskCacheCommon.OpenFile(_basePath, GetHostDataFileName(context), writable: false);
470  
471                  BinarySerializer tempTocReader = new(tocFileStream);
472  
473                  TocHeader header = new();
474  
475                  tempTocReader.Read(ref header);
476  
477                  if (header.Timestamp < expectedTimestamp)
478                  {
479                      return (null, null);
480                  }
481              }
482  
483              int offset = Unsafe.SizeOf<TocHeader>() + programIndex * Unsafe.SizeOf<OffsetAndSize>();
484              if (offset + Unsafe.SizeOf<OffsetAndSize>() > tocFileStream.Length)
485              {
486                  return (null, null);
487              }
488  
489              if ((ulong)offset >= (ulong)dataFileStream.Length)
490              {
491                  throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedGeneric);
492              }
493  
494              tocFileStream.Seek(offset, SeekOrigin.Begin);
495  
496              BinarySerializer tocReader = new(tocFileStream);
497  
498              OffsetAndSize offsetAndSize = new();
499              tocReader.Read(ref offsetAndSize);
500  
501              if (offsetAndSize.Offset >= (ulong)dataFileStream.Length)
502              {
503                  throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedGeneric);
504              }
505  
506              dataFileStream.Seek((long)offsetAndSize.Offset, SeekOrigin.Begin);
507  
508              byte[] hostCode = new byte[offsetAndSize.UncompressedSize];
509  
510              BinarySerializer.ReadCompressed(dataFileStream, hostCode);
511  
512              CachedShaderStage[] shaders = new CachedShaderStage[guestShaders.Length];
513              BinarySerializer dataReader = new(dataFileStream);
514  
515              dataFileStream.Seek((long)(offsetAndSize.Offset + offsetAndSize.CompressedSize), SeekOrigin.Begin);
516  
517              dataReader.BeginCompression();
518  
519              for (int index = 0; index < guestShaders.Length; index++)
520              {
521                  if (!guestShaders[index].HasValue)
522                  {
523                      continue;
524                  }
525  
526                  GuestCodeAndCbData guestShader = guestShaders[index].Value;
527                  ShaderProgramInfo info = index != 0 || guestShaders.Length == 1 ? ReadShaderProgramInfo(ref dataReader) : null;
528  
529                  shaders[index] = new CachedShaderStage(info, guestShader.Code, guestShader.Cb1Data);
530              }
531  
532              dataReader.EndCompression();
533  
534              return (hostCode, shaders);
535          }
536  
537          /// <summary>
538          /// Gets output streams for the disk cache, for faster batch writing.
539          /// </summary>
540          /// <param name="context">The GPU context, used to determine the host disk cache</param>
541          /// <returns>A collection of disk cache output streams</returns>
542          public DiskCacheOutputStreams GetOutputStreams(GpuContext context)
543          {
544              var tocFileStream = DiskCacheCommon.OpenFile(_basePath, SharedTocFileName, writable: true);
545              var dataFileStream = DiskCacheCommon.OpenFile(_basePath, SharedDataFileName, writable: true);
546  
547              var hostTocFileStream = DiskCacheCommon.OpenFile(_basePath, GetHostTocFileName(context), writable: true);
548              var hostDataFileStream = DiskCacheCommon.OpenFile(_basePath, GetHostDataFileName(context), writable: true);
549  
550              return new DiskCacheOutputStreams(tocFileStream, dataFileStream, hostTocFileStream, hostDataFileStream);
551          }
552  
553          /// <summary>
554          /// Adds a shader to the cache.
555          /// </summary>
556          /// <param name="context">GPU context</param>
557          /// <param name="program">Cached program</param>
558          /// <param name="hostCode">Optional host binary code</param>
559          /// <param name="streams">Output streams to use</param>
560          public void AddShader(GpuContext context, CachedShaderProgram program, ReadOnlySpan<byte> hostCode, DiskCacheOutputStreams streams = null)
561          {
562              uint stagesBitMask = 0;
563  
564              for (int index = 0; index < program.Shaders.Length; index++)
565              {
566                  var shader = program.Shaders[index];
567                  if (shader == null || (shader.Info != null && shader.Info.Stage == ShaderStage.Compute))
568                  {
569                      continue;
570                  }
571  
572                  stagesBitMask |= 1u << index;
573              }
574  
575              var tocFileStream = streams != null ? streams.TocFileStream : DiskCacheCommon.OpenFile(_basePath, SharedTocFileName, writable: true);
576              var dataFileStream = streams != null ? streams.DataFileStream : DiskCacheCommon.OpenFile(_basePath, SharedDataFileName, writable: true);
577  
578              ulong timestamp = (ulong)DateTime.UtcNow.Subtract(DateTime.UnixEpoch).TotalSeconds;
579  
580              if (tocFileStream.Length == 0)
581              {
582                  TocHeader header = new();
583                  CreateToc(tocFileStream, ref header, TocsMagic, CodeGenVersion, timestamp);
584              }
585  
586              tocFileStream.Seek(0, SeekOrigin.End);
587              dataFileStream.Seek(0, SeekOrigin.End);
588  
589              BinarySerializer tocWriter = new(tocFileStream);
590              BinarySerializer dataWriter = new(dataFileStream);
591  
592              ulong dataOffset = (ulong)dataFileStream.Position;
593              tocWriter.Write(ref dataOffset);
594  
595              DataEntry entry = new()
596              {
597                  StagesBitMask = stagesBitMask,
598              };
599  
600              dataWriter.BeginCompression(DiskCacheCommon.GetCompressionAlgorithm());
601              dataWriter.Write(ref entry);
602  
603              DataEntryPerStage stageEntry = new();
604  
605              for (int index = 0; index < program.Shaders.Length; index++)
606              {
607                  var shader = program.Shaders[index];
608                  if (shader == null)
609                  {
610                      continue;
611                  }
612  
613                  stageEntry.GuestCodeIndex = _guestStorage.AddShader(shader.Code, shader.Cb1Data);
614  
615                  dataWriter.Write(ref stageEntry);
616              }
617  
618              program.SpecializationState.Write(ref dataWriter);
619              dataWriter.EndCompression();
620  
621              if (streams == null)
622              {
623                  tocFileStream.Dispose();
624                  dataFileStream.Dispose();
625              }
626  
627              if (hostCode.IsEmpty)
628              {
629                  return;
630              }
631  
632              WriteHostCode(context, hostCode, program.Shaders, streams, timestamp);
633          }
634  
635          /// <summary>
636          /// Clears all content from the guest cache files.
637          /// </summary>
638          public void ClearGuestCache()
639          {
640              _guestStorage.ClearCache();
641          }
642  
643          /// <summary>
644          /// Clears all content from the shared cache files.
645          /// </summary>
646          /// <param name="context">GPU context</param>
647          public void ClearSharedCache()
648          {
649              using var tocFileStream = DiskCacheCommon.OpenFile(_basePath, SharedTocFileName, writable: true);
650              using var dataFileStream = DiskCacheCommon.OpenFile(_basePath, SharedDataFileName, writable: true);
651  
652              tocFileStream.SetLength(0);
653              dataFileStream.SetLength(0);
654          }
655  
656          /// <summary>
657          /// Deletes all content from the host cache files.
658          /// </summary>
659          /// <param name="context">GPU context</param>
660          public void ClearHostCache(GpuContext context)
661          {
662              using var tocFileStream = DiskCacheCommon.OpenFile(_basePath, GetHostTocFileName(context), writable: true);
663              using var dataFileStream = DiskCacheCommon.OpenFile(_basePath, GetHostDataFileName(context), writable: true);
664  
665              tocFileStream.SetLength(0);
666              dataFileStream.SetLength(0);
667          }
668  
669          /// <summary>
670          /// Writes the host binary code on the host cache.
671          /// </summary>
672          /// <param name="context">GPU context</param>
673          /// <param name="hostCode">Host binary code</param>
674          /// <param name="shaders">Shader stages to be added to the host cache</param>
675          /// <param name="streams">Output streams to use</param>
676          /// <param name="timestamp">File creation timestamp</param>
677          private void WriteHostCode(
678              GpuContext context,
679              ReadOnlySpan<byte> hostCode,
680              CachedShaderStage[] shaders,
681              DiskCacheOutputStreams streams,
682              ulong timestamp)
683          {
684              var tocFileStream = streams != null ? streams.HostTocFileStream : DiskCacheCommon.OpenFile(_basePath, GetHostTocFileName(context), writable: true);
685              var dataFileStream = streams != null ? streams.HostDataFileStream : DiskCacheCommon.OpenFile(_basePath, GetHostDataFileName(context), writable: true);
686  
687              if (tocFileStream.Length == 0)
688              {
689                  TocHeader header = new();
690                  CreateToc(tocFileStream, ref header, TochMagic, 0, timestamp);
691              }
692  
693              tocFileStream.Seek(0, SeekOrigin.End);
694              dataFileStream.Seek(0, SeekOrigin.End);
695  
696              BinarySerializer tocWriter = new(tocFileStream);
697              BinarySerializer dataWriter = new(dataFileStream);
698  
699              OffsetAndSize offsetAndSize = new()
700              {
701                  Offset = (ulong)dataFileStream.Position,
702                  UncompressedSize = (uint)hostCode.Length,
703              };
704  
705              long dataStartPosition = dataFileStream.Position;
706  
707              BinarySerializer.WriteCompressed(dataFileStream, hostCode, DiskCacheCommon.GetCompressionAlgorithm());
708  
709              offsetAndSize.CompressedSize = (uint)(dataFileStream.Position - dataStartPosition);
710  
711              tocWriter.Write(ref offsetAndSize);
712  
713              dataWriter.BeginCompression(DiskCacheCommon.GetCompressionAlgorithm());
714  
715              for (int index = 0; index < shaders.Length; index++)
716              {
717                  if (shaders[index] != null)
718                  {
719                      WriteShaderProgramInfo(ref dataWriter, shaders[index].Info);
720                  }
721              }
722  
723              dataWriter.EndCompression();
724  
725              if (streams == null)
726              {
727                  tocFileStream.Dispose();
728                  dataFileStream.Dispose();
729              }
730          }
731  
732          /// <summary>
733          /// Creates a TOC file for the host or shared cache.
734          /// </summary>
735          /// <param name="tocFileStream">TOC file stream</param>
736          /// <param name="header">Set to the TOC file header</param>
737          /// <param name="magic">Magic value to be written</param>
738          /// <param name="codegenVersion">Shader codegen version, only valid for the host file</param>
739          /// <param name="timestamp">File creation timestamp</param>
740          private static void CreateToc(Stream tocFileStream, ref TocHeader header, uint magic, uint codegenVersion, ulong timestamp)
741          {
742              BinarySerializer writer = new(tocFileStream);
743  
744              header.Magic = magic;
745              header.FormatVersion = FileFormatVersionPacked;
746              header.CodeGenVersion = codegenVersion;
747              header.Padding = 0;
748              header.Reserved = 0;
749              header.Timestamp = timestamp;
750  
751              if (tocFileStream.Length > 0)
752              {
753                  tocFileStream.Seek(0, SeekOrigin.Begin);
754                  tocFileStream.SetLength(0);
755              }
756  
757              writer.Write(ref header);
758          }
759  
760          /// <summary>
761          /// Reads the shader program info from the cache.
762          /// </summary>
763          /// <param name="dataReader">Cache data reader</param>
764          /// <returns>Shader program info</returns>
765          private static ShaderProgramInfo ReadShaderProgramInfo(ref BinarySerializer dataReader)
766          {
767              DataShaderInfo dataInfo = new();
768  
769              dataReader.ReadWithMagicAndSize(ref dataInfo, ShdiMagic);
770  
771              BufferDescriptor[] cBuffers = new BufferDescriptor[dataInfo.CBuffersCount];
772              BufferDescriptor[] sBuffers = new BufferDescriptor[dataInfo.SBuffersCount];
773              TextureDescriptor[] textures = new TextureDescriptor[dataInfo.TexturesCount];
774              TextureDescriptor[] images = new TextureDescriptor[dataInfo.ImagesCount];
775  
776              for (int index = 0; index < dataInfo.CBuffersCount; index++)
777              {
778                  dataReader.ReadWithMagicAndSize(ref cBuffers[index], BufdMagic);
779              }
780  
781              for (int index = 0; index < dataInfo.SBuffersCount; index++)
782              {
783                  dataReader.ReadWithMagicAndSize(ref sBuffers[index], BufdMagic);
784              }
785  
786              for (int index = 0; index < dataInfo.TexturesCount; index++)
787              {
788                  dataReader.ReadWithMagicAndSize(ref textures[index], TexdMagic);
789              }
790  
791              for (int index = 0; index < dataInfo.ImagesCount; index++)
792              {
793                  dataReader.ReadWithMagicAndSize(ref images[index], TexdMagic);
794              }
795  
796              return new ShaderProgramInfo(
797                  cBuffers,
798                  sBuffers,
799                  textures,
800                  images,
801                  dataInfo.Stage,
802                  dataInfo.GeometryVerticesPerPrimitive,
803                  dataInfo.GeometryMaxOutputVertices,
804                  dataInfo.ThreadsPerInputPrimitive,
805                  dataInfo.UsesFragCoord,
806                  dataInfo.UsesInstanceId,
807                  dataInfo.UsesDrawParameters,
808                  dataInfo.UsesRtLayer,
809                  dataInfo.ClipDistancesWritten,
810                  dataInfo.FragmentOutputMap);
811          }
812  
813          /// <summary>
814          /// Writes the shader program info into the cache.
815          /// </summary>
816          /// <param name="dataWriter">Cache data writer</param>
817          /// <param name="info">Program info</param>
818          private static void WriteShaderProgramInfo(ref BinarySerializer dataWriter, ShaderProgramInfo info)
819          {
820              if (info == null)
821              {
822                  return;
823              }
824  
825              DataShaderInfo dataInfo = new()
826              {
827                  CBuffersCount = (ushort)info.CBuffers.Count,
828                  SBuffersCount = (ushort)info.SBuffers.Count,
829                  TexturesCount = (ushort)info.Textures.Count,
830                  ImagesCount = (ushort)info.Images.Count,
831                  Stage = info.Stage,
832                  GeometryVerticesPerPrimitive = (byte)info.GeometryVerticesPerPrimitive,
833                  GeometryMaxOutputVertices = (ushort)info.GeometryMaxOutputVertices,
834                  ThreadsPerInputPrimitive = (ushort)info.ThreadsPerInputPrimitive,
835                  UsesFragCoord = info.UsesFragCoord,
836                  UsesInstanceId = info.UsesInstanceId,
837                  UsesDrawParameters = info.UsesDrawParameters,
838                  UsesRtLayer = info.UsesRtLayer,
839                  ClipDistancesWritten = info.ClipDistancesWritten,
840                  FragmentOutputMap = info.FragmentOutputMap,
841              };
842  
843              dataWriter.WriteWithMagicAndSize(ref dataInfo, ShdiMagic);
844  
845              for (int index = 0; index < info.CBuffers.Count; index++)
846              {
847                  var entry = info.CBuffers[index];
848                  dataWriter.WriteWithMagicAndSize(ref entry, BufdMagic);
849              }
850  
851              for (int index = 0; index < info.SBuffers.Count; index++)
852              {
853                  var entry = info.SBuffers[index];
854                  dataWriter.WriteWithMagicAndSize(ref entry, BufdMagic);
855              }
856  
857              for (int index = 0; index < info.Textures.Count; index++)
858              {
859                  var entry = info.Textures[index];
860                  dataWriter.WriteWithMagicAndSize(ref entry, TexdMagic);
861              }
862  
863              for (int index = 0; index < info.Images.Count; index++)
864              {
865                  var entry = info.Images[index];
866                  dataWriter.WriteWithMagicAndSize(ref entry, TexdMagic);
867              }
868          }
869      }
870  }