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 }