| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459 |
- using Ryujinx.Common;
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Runtime.CompilerServices;
- namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
- {
- /// <summary>
- /// On-disk shader cache storage for guest code.
- /// </summary>
- class DiskCacheGuestStorage
- {
- private const uint TocMagic = (byte)'T' | ((byte)'O' << 8) | ((byte)'C' << 16) | ((byte)'G' << 24);
- private const ushort VersionMajor = 1;
- private const ushort VersionMinor = 1;
- private const uint VersionPacked = ((uint)VersionMajor << 16) | VersionMinor;
- private const string TocFileName = "guest.toc";
- private const string DataFileName = "guest.data";
- private readonly string _basePath;
- /// <summary>
- /// TOC (Table of contents) file header.
- /// </summary>
- private struct TocHeader
- {
- /// <summary>
- /// Magic value, for validation and identification purposes.
- /// </summary>
- public uint Magic;
- /// <summary>
- /// File format version.
- /// </summary>
- public uint Version;
- /// <summary>
- /// Header padding.
- /// </summary>
- public uint Padding;
- /// <summary>
- /// Number of modifications to the file, also the shaders count.
- /// </summary>
- public uint ModificationsCount;
- /// <summary>
- /// Reserved space, to be used in the future. Write as zero.
- /// </summary>
- public ulong Reserved;
- /// <summary>
- /// Reserved space, to be used in the future. Write as zero.
- /// </summary>
- public ulong Reserved2;
- }
- /// <summary>
- /// TOC (Table of contents) file entry.
- /// </summary>
- private struct TocEntry
- {
- /// <summary>
- /// Offset of the data on the data file.
- /// </summary>
- public uint Offset;
- /// <summary>
- /// Code size.
- /// </summary>
- public uint CodeSize;
- /// <summary>
- /// Constant buffer 1 data size.
- /// </summary>
- public uint Cb1DataSize;
- /// <summary>
- /// Hash of the code and constant buffer data.
- /// </summary>
- public uint Hash;
- }
- /// <summary>
- /// TOC (Table of contents) memory cache entry.
- /// </summary>
- private struct TocMemoryEntry
- {
- /// <summary>
- /// Offset of the data on the data file.
- /// </summary>
- public uint Offset;
- /// <summary>
- /// Code size.
- /// </summary>
- public uint CodeSize;
- /// <summary>
- /// Constant buffer 1 data size.
- /// </summary>
- public uint Cb1DataSize;
- /// <summary>
- /// Index of the shader on the cache.
- /// </summary>
- public readonly int Index;
- /// <summary>
- /// Creates a new TOC memory entry.
- /// </summary>
- /// <param name="offset">Offset of the data on the data file</param>
- /// <param name="codeSize">Code size</param>
- /// <param name="cb1DataSize">Constant buffer 1 data size</param>
- /// <param name="index">Index of the shader on the cache</param>
- public TocMemoryEntry(uint offset, uint codeSize, uint cb1DataSize, int index)
- {
- Offset = offset;
- CodeSize = codeSize;
- Cb1DataSize = cb1DataSize;
- Index = index;
- }
- }
- private Dictionary<uint, List<TocMemoryEntry>> _toc;
- private uint _tocModificationsCount;
- private (byte[], byte[])[] _cache;
- /// <summary>
- /// Creates a new disk cache guest storage.
- /// </summary>
- /// <param name="basePath">Base path of the disk shader cache</param>
- public DiskCacheGuestStorage(string basePath)
- {
- _basePath = basePath;
- }
- /// <summary>
- /// Checks if the TOC (table of contents) file for the guest cache exists.
- /// </summary>
- /// <returns>True if the file exists, false otherwise</returns>
- public bool TocFileExists()
- {
- return File.Exists(Path.Combine(_basePath, TocFileName));
- }
- /// <summary>
- /// Checks if the data file for the guest cache exists.
- /// </summary>
- /// <returns>True if the file exists, false otherwise</returns>
- public bool DataFileExists()
- {
- return File.Exists(Path.Combine(_basePath, DataFileName));
- }
- /// <summary>
- /// Opens the guest cache TOC (table of contents) file.
- /// </summary>
- /// <returns>File stream</returns>
- public Stream OpenTocFileStream()
- {
- return DiskCacheCommon.OpenFile(_basePath, TocFileName, writable: false);
- }
- /// <summary>
- /// Opens the guest cache data file.
- /// </summary>
- /// <returns>File stream</returns>
- public Stream OpenDataFileStream()
- {
- return DiskCacheCommon.OpenFile(_basePath, DataFileName, writable: false);
- }
- /// <summary>
- /// Clear all content from the guest cache files.
- /// </summary>
- public void ClearCache()
- {
- using var tocFileStream = DiskCacheCommon.OpenFile(_basePath, TocFileName, writable: true);
- using var dataFileStream = DiskCacheCommon.OpenFile(_basePath, DataFileName, writable: true);
- tocFileStream.SetLength(0);
- dataFileStream.SetLength(0);
- }
- /// <summary>
- /// Loads the guest cache from file or memory cache.
- /// </summary>
- /// <param name="tocFileStream">Guest TOC file stream</param>
- /// <param name="dataFileStream">Guest data file stream</param>
- /// <param name="index">Guest shader index</param>
- /// <returns>Guest code and constant buffer 1 data</returns>
- public GuestCodeAndCbData LoadShader(Stream tocFileStream, Stream dataFileStream, int index)
- {
- if (_cache == null || index >= _cache.Length)
- {
- _cache = new (byte[], byte[])[Math.Max(index + 1, GetShadersCountFromLength(tocFileStream.Length))];
- }
- (byte[] guestCode, byte[] cb1Data) = _cache[index];
- if (guestCode == null || cb1Data == null)
- {
- BinarySerializer tocReader = new BinarySerializer(tocFileStream);
- tocFileStream.Seek(Unsafe.SizeOf<TocHeader>() + index * Unsafe.SizeOf<TocEntry>(), SeekOrigin.Begin);
- TocEntry entry = new TocEntry();
- tocReader.Read(ref entry);
- guestCode = new byte[entry.CodeSize];
- cb1Data = new byte[entry.Cb1DataSize];
- if (entry.Offset >= (ulong)dataFileStream.Length)
- {
- throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedGeneric);
- }
- dataFileStream.Seek((long)entry.Offset, SeekOrigin.Begin);
- dataFileStream.Read(cb1Data);
- BinarySerializer.ReadCompressed(dataFileStream, guestCode);
- _cache[index] = (guestCode, cb1Data);
- }
- return new GuestCodeAndCbData(guestCode, cb1Data);
- }
- /// <summary>
- /// Clears guest code memory cache, forcing future loads to be from file.
- /// </summary>
- public void ClearMemoryCache()
- {
- _cache = null;
- }
- /// <summary>
- /// Calculates the guest shaders count from the TOC file length.
- /// </summary>
- /// <param name="length">TOC file length</param>
- /// <returns>Shaders count</returns>
- private static int GetShadersCountFromLength(long length)
- {
- return (int)((length - Unsafe.SizeOf<TocHeader>()) / Unsafe.SizeOf<TocEntry>());
- }
- /// <summary>
- /// Adds a guest shader to the cache.
- /// </summary>
- /// <remarks>
- /// If the shader is already on the cache, the existing index will be returned and nothing will be written.
- /// </remarks>
- /// <param name="data">Guest code</param>
- /// <param name="cb1Data">Constant buffer 1 data accessed by the code</param>
- /// <returns>Index of the shader on the cache</returns>
- public int AddShader(ReadOnlySpan<byte> data, ReadOnlySpan<byte> cb1Data)
- {
- using var tocFileStream = DiskCacheCommon.OpenFile(_basePath, TocFileName, writable: true);
- using var dataFileStream = DiskCacheCommon.OpenFile(_basePath, DataFileName, writable: true);
- TocHeader header = new TocHeader();
- LoadOrCreateToc(tocFileStream, ref header);
- uint hash = CalcHash(data, cb1Data);
- if (_toc.TryGetValue(hash, out var list))
- {
- foreach (var entry in list)
- {
- if (data.Length != entry.CodeSize || cb1Data.Length != entry.Cb1DataSize)
- {
- continue;
- }
- dataFileStream.Seek((long)entry.Offset, SeekOrigin.Begin);
- byte[] cachedCode = new byte[entry.CodeSize];
- byte[] cachedCb1Data = new byte[entry.Cb1DataSize];
- dataFileStream.Read(cachedCb1Data);
- BinarySerializer.ReadCompressed(dataFileStream, cachedCode);
- if (data.SequenceEqual(cachedCode) && cb1Data.SequenceEqual(cachedCb1Data))
- {
- return entry.Index;
- }
- }
- }
- return WriteNewEntry(tocFileStream, dataFileStream, ref header, data, cb1Data, hash);
- }
- /// <summary>
- /// Loads the guest cache TOC file, or create a new one if not present.
- /// </summary>
- /// <param name="tocFileStream">Guest TOC file stream</param>
- /// <param name="header">Set to the TOC file header</param>
- private void LoadOrCreateToc(Stream tocFileStream, ref TocHeader header)
- {
- BinarySerializer reader = new BinarySerializer(tocFileStream);
- if (!reader.TryRead(ref header) || header.Magic != TocMagic || header.Version != VersionPacked)
- {
- CreateToc(tocFileStream, ref header);
- }
- if (_toc == null || header.ModificationsCount != _tocModificationsCount)
- {
- if (!LoadTocEntries(tocFileStream, ref reader))
- {
- CreateToc(tocFileStream, ref header);
- }
- _tocModificationsCount = header.ModificationsCount;
- }
- }
- /// <summary>
- /// Creates a new guest cache TOC file.
- /// </summary>
- /// <param name="tocFileStream">Guest TOC file stream</param>
- /// <param name="header">Set to the TOC header</param>
- private void CreateToc(Stream tocFileStream, ref TocHeader header)
- {
- BinarySerializer writer = new BinarySerializer(tocFileStream);
- header.Magic = TocMagic;
- header.Version = VersionPacked;
- header.Padding = 0;
- header.ModificationsCount = 0;
- header.Reserved = 0;
- header.Reserved2 = 0;
- if (tocFileStream.Length > 0)
- {
- tocFileStream.Seek(0, SeekOrigin.Begin);
- tocFileStream.SetLength(0);
- }
- writer.Write(ref header);
- }
- /// <summary>
- /// Reads all the entries on the guest TOC file.
- /// </summary>
- /// <param name="tocFileStream">Guest TOC file stream</param>
- /// <param name="reader">TOC file reader</param>
- /// <returns>True if the operation was successful, false otherwise</returns>
- private bool LoadTocEntries(Stream tocFileStream, ref BinarySerializer reader)
- {
- _toc = new Dictionary<uint, List<TocMemoryEntry>>();
- TocEntry entry = new TocEntry();
- int index = 0;
- while (tocFileStream.Position < tocFileStream.Length)
- {
- if (!reader.TryRead(ref entry))
- {
- return false;
- }
- AddTocMemoryEntry(entry.Offset, entry.CodeSize, entry.Cb1DataSize, entry.Hash, index++);
- }
- return true;
- }
- /// <summary>
- /// Writes a new guest code entry into the file.
- /// </summary>
- /// <param name="tocFileStream">TOC file stream</param>
- /// <param name="dataFileStream">Data file stream</param>
- /// <param name="header">TOC header, to be updated with the new count</param>
- /// <param name="data">Guest code</param>
- /// <param name="cb1Data">Constant buffer 1 data accessed by the guest code</param>
- /// <param name="hash">Code and constant buffer data hash</param>
- /// <returns>Entry index</returns>
- private int WriteNewEntry(
- Stream tocFileStream,
- Stream dataFileStream,
- ref TocHeader header,
- ReadOnlySpan<byte> data,
- ReadOnlySpan<byte> cb1Data,
- uint hash)
- {
- BinarySerializer tocWriter = new BinarySerializer(tocFileStream);
- dataFileStream.Seek(0, SeekOrigin.End);
- uint dataOffset = checked((uint)dataFileStream.Position);
- uint codeSize = (uint)data.Length;
- uint cb1DataSize = (uint)cb1Data.Length;
- dataFileStream.Write(cb1Data);
- BinarySerializer.WriteCompressed(dataFileStream, data, DiskCacheCommon.GetCompressionAlgorithm());
- _tocModificationsCount = ++header.ModificationsCount;
- tocFileStream.Seek(0, SeekOrigin.Begin);
- tocWriter.Write(ref header);
- TocEntry entry = new TocEntry()
- {
- Offset = dataOffset,
- CodeSize = codeSize,
- Cb1DataSize = cb1DataSize,
- Hash = hash
- };
- tocFileStream.Seek(0, SeekOrigin.End);
- int index = (int)((tocFileStream.Position - Unsafe.SizeOf<TocHeader>()) / Unsafe.SizeOf<TocEntry>());
- tocWriter.Write(ref entry);
- AddTocMemoryEntry(dataOffset, codeSize, cb1DataSize, hash, index);
- return index;
- }
- /// <summary>
- /// Adds an entry to the memory TOC cache. This can be used to avoid reading the TOC file all the time.
- /// </summary>
- /// <param name="dataOffset">Offset of the code and constant buffer data in the data file</param>
- /// <param name="codeSize">Code size</param>
- /// <param name="cb1DataSize">Constant buffer 1 data size</param>
- /// <param name="hash">Code and constant buffer data hash</param>
- /// <param name="index">Index of the data on the cache</param>
- private void AddTocMemoryEntry(uint dataOffset, uint codeSize, uint cb1DataSize, uint hash, int index)
- {
- if (!_toc.TryGetValue(hash, out var list))
- {
- _toc.Add(hash, list = new List<TocMemoryEntry>());
- }
- list.Add(new TocMemoryEntry(dataOffset, codeSize, cb1DataSize, index));
- }
- /// <summary>
- /// Calculates the hash for a data pair.
- /// </summary>
- /// <param name="data">Data 1</param>
- /// <param name="data2">Data 2</param>
- /// <returns>Hash of both data</returns>
- private static uint CalcHash(ReadOnlySpan<byte> data, ReadOnlySpan<byte> data2)
- {
- return CalcHash(data2) * 23 ^ CalcHash(data);
- }
- /// <summary>
- /// Calculates the hash for data.
- /// </summary>
- /// <param name="data">Data to be hashed</param>
- /// <returns>Hash of the data</returns>
- private static uint CalcHash(ReadOnlySpan<byte> data)
- {
- return (uint)XXHash128.ComputeHash(data).Low;
- }
- }
- }
|