/ src / Ryujinx.Graphics.Gpu / Shader / DiskCache / BinarySerializer.cs
BinarySerializer.cs
  1  using System;
  2  using System.IO;
  3  using System.IO.Compression;
  4  using System.Runtime.CompilerServices;
  5  using System.Runtime.InteropServices;
  6  
  7  namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
  8  {
  9      /// <summary>
 10      /// Binary data serializer.
 11      /// </summary>
 12      struct BinarySerializer
 13      {
 14          private readonly Stream _stream;
 15          private Stream _activeStream;
 16  
 17          /// <summary>
 18          /// Creates a new binary serializer.
 19          /// </summary>
 20          /// <param name="stream">Stream to read from or write into</param>
 21          public BinarySerializer(Stream stream)
 22          {
 23              _stream = stream;
 24              _activeStream = stream;
 25          }
 26  
 27          /// <summary>
 28          /// Reads data from the stream.
 29          /// </summary>
 30          /// <typeparam name="T">Type of the data</typeparam>
 31          /// <param name="data">Data read</param>
 32          public readonly void Read<T>(ref T data) where T : unmanaged
 33          {
 34              Span<byte> buffer = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref data, 1));
 35              for (int offset = 0; offset < buffer.Length;)
 36              {
 37                  offset += _activeStream.Read(buffer[offset..]);
 38              }
 39          }
 40  
 41          /// <summary>
 42          /// Tries to read data from the stream.
 43          /// </summary>
 44          /// <typeparam name="T">Type of the data</typeparam>
 45          /// <param name="data">Data read</param>
 46          /// <returns>True if the read was successful, false otherwise</returns>
 47          public readonly bool TryRead<T>(ref T data) where T : unmanaged
 48          {
 49              // Length is unknown on compressed streams.
 50              if (_activeStream == _stream)
 51              {
 52                  int size = Unsafe.SizeOf<T>();
 53                  if (_activeStream.Length - _activeStream.Position < size)
 54                  {
 55                      return false;
 56                  }
 57              }
 58  
 59              Read(ref data);
 60              return true;
 61          }
 62  
 63          /// <summary>
 64          /// Reads data prefixed with a magic and size from the stream.
 65          /// </summary>
 66          /// <typeparam name="T">Type of the data</typeparam>
 67          /// <param name="data">Data read</param>
 68          /// <param name="magic">Expected magic value, for validation</param>
 69          public readonly void ReadWithMagicAndSize<T>(ref T data, uint magic) where T : unmanaged
 70          {
 71              uint actualMagic = 0;
 72              int size = 0;
 73              Read(ref actualMagic);
 74              Read(ref size);
 75  
 76              if (actualMagic != magic)
 77              {
 78                  throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedInvalidMagic);
 79              }
 80  
 81              // Structs are expected to expand but not shrink between versions.
 82              if (size > Unsafe.SizeOf<T>())
 83              {
 84                  throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedInvalidLength);
 85              }
 86  
 87              Span<byte> buffer = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref data, 1))[..size];
 88              for (int offset = 0; offset < buffer.Length;)
 89              {
 90                  offset += _activeStream.Read(buffer[offset..]);
 91              }
 92          }
 93  
 94          /// <summary>
 95          /// Writes data into the stream.
 96          /// </summary>
 97          /// <typeparam name="T">Type of the data</typeparam>
 98          /// <param name="data">Data to be written</param>
 99          public readonly void Write<T>(ref T data) where T : unmanaged
100          {
101              Span<byte> buffer = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref data, 1));
102              _activeStream.Write(buffer);
103          }
104  
105          /// <summary>
106          /// Writes data prefixed with a magic and size into the stream.
107          /// </summary>
108          /// <typeparam name="T">Type of the data</typeparam>
109          /// <param name="data">Data to write</param>
110          /// <param name="magic">Magic value to write</param>
111          public readonly void WriteWithMagicAndSize<T>(ref T data, uint magic) where T : unmanaged
112          {
113              int size = Unsafe.SizeOf<T>();
114              Write(ref magic);
115              Write(ref size);
116              Span<byte> buffer = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref data, 1));
117              _activeStream.Write(buffer);
118          }
119  
120          /// <summary>
121          /// Indicates that all data that will be read from the stream has been compressed.
122          /// </summary>
123          public void BeginCompression()
124          {
125              CompressionAlgorithm algorithm = CompressionAlgorithm.None;
126              Read(ref algorithm);
127  
128              switch (algorithm)
129              {
130                  case CompressionAlgorithm.None:
131                      break;
132                  case CompressionAlgorithm.Deflate:
133                      _activeStream = new DeflateStream(_stream, CompressionMode.Decompress, true);
134                      break;
135                  case CompressionAlgorithm.Brotli:
136                      _activeStream = new BrotliStream(_stream, CompressionMode.Decompress, true);
137                      break;
138                  default:
139                      throw new ArgumentException($"Invalid compression algorithm \"{algorithm}\"");
140              }
141          }
142  
143          /// <summary>
144          /// Indicates that all data that will be written into the stream should be compressed.
145          /// </summary>
146          /// <param name="algorithm">Compression algorithm that should be used</param>
147          public void BeginCompression(CompressionAlgorithm algorithm)
148          {
149              Write(ref algorithm);
150  
151              switch (algorithm)
152              {
153                  case CompressionAlgorithm.None:
154                      break;
155                  case CompressionAlgorithm.Deflate:
156                      _activeStream = new DeflateStream(_stream, CompressionLevel.Fastest, true);
157                      break;
158                  case CompressionAlgorithm.Brotli:
159                      _activeStream = new BrotliStream(_stream, CompressionLevel.Fastest, true);
160                      break;
161                  default:
162                      throw new ArgumentException($"Invalid compression algorithm \"{algorithm}\"");
163              }
164          }
165  
166          /// <summary>
167          /// Indicates the end of a compressed chunck.
168          /// </summary>
169          /// <remarks>
170          /// Any data written after this will not be compressed unless <see cref="BeginCompression(CompressionAlgorithm)"/> is called again.
171          /// Any data read after this will be assumed to be uncompressed unless <see cref="BeginCompression"/> is called again.
172          /// </remarks>
173          public void EndCompression()
174          {
175              if (_activeStream != _stream)
176              {
177                  _activeStream.Dispose();
178                  _activeStream = _stream;
179              }
180          }
181  
182          /// <summary>
183          /// Reads compressed data from the stream.
184          /// </summary>
185          /// <remarks>
186          /// <paramref name="data"/> must have the exact length of the uncompressed data,
187          /// otherwise decompression will fail.
188          /// </remarks>
189          /// <param name="stream">Stream to read from</param>
190          /// <param name="data">Buffer to write the uncompressed data into</param>
191          public static void ReadCompressed(Stream stream, Span<byte> data)
192          {
193              CompressionAlgorithm algorithm = (CompressionAlgorithm)stream.ReadByte();
194  
195              switch (algorithm)
196              {
197                  case CompressionAlgorithm.None:
198                      stream.ReadExactly(data);
199                      break;
200                  case CompressionAlgorithm.Deflate:
201                      stream = new DeflateStream(stream, CompressionMode.Decompress, true);
202                      for (int offset = 0; offset < data.Length;)
203                      {
204                          offset += stream.Read(data[offset..]);
205                      }
206                      stream.Dispose();
207                      break;
208                  case CompressionAlgorithm.Brotli:
209                      stream = new BrotliStream(stream, CompressionMode.Decompress, true);
210                      for (int offset = 0; offset < data.Length;)
211                      {
212                          offset += stream.Read(data[offset..]);
213                      }
214                      stream.Dispose();
215                      break;
216              }
217          }
218  
219          /// <summary>
220          /// Compresses and writes the compressed data into the stream.
221          /// </summary>
222          /// <param name="stream">Stream to write into</param>
223          /// <param name="data">Data to compress</param>
224          /// <param name="algorithm">Compression algorithm to be used</param>
225          public static void WriteCompressed(Stream stream, ReadOnlySpan<byte> data, CompressionAlgorithm algorithm)
226          {
227              stream.WriteByte((byte)algorithm);
228  
229              switch (algorithm)
230              {
231                  case CompressionAlgorithm.None:
232                      stream.Write(data);
233                      break;
234                  case CompressionAlgorithm.Deflate:
235                      stream = new DeflateStream(stream, CompressionLevel.Fastest, true);
236                      stream.Write(data);
237                      stream.Dispose();
238                      break;
239                  case CompressionAlgorithm.Brotli:
240                      stream = new BrotliStream(stream, CompressionLevel.Fastest, true);
241                      stream.Write(data);
242                      stream.Dispose();
243                      break;
244              }
245          }
246      }
247  }