mirror of
https://github.com/Ryujinx/Ryujinx.git
synced 2024-11-14 12:56:41 +00:00
f1d1670b0b
* Implement HLE macro for DrawElementsIndirect * Shader cache version bump * Use GL_ARB_shader_draw_parameters extension on OpenGL * Fix DrawIndexedIndirectCount on Vulkan when extension is not supported * Implement DrawIndex * Alignment * Fix some validation errors * Rename BaseIds to DrawParameters * Fix incorrect index buffer and vertex buffer size in some cases * Add HLE macros for DrawArraysInstanced and DrawElementsInstanced * Perform a regular draw when indirect data is not modified * Use non-indirect draw methods if indirect buffer was not GPU modified * Only check if draw parameters match if the shader actually uses them * Expose Macro HLE setting on GUI * Reset FirstVertex and FirstInstance after draw * Update shader cache version again since some people already tested this * PR feedback Co-authored-by: riperiperi <rhy3756547@hotmail.com>
837 lines
31 KiB
C#
837 lines
31 KiB
C#
using Ryujinx.Graphics.GAL;
|
|
using Ryujinx.Graphics.Shader;
|
|
using Ryujinx.Graphics.Shader.Translation;
|
|
using System;
|
|
using System.IO;
|
|
using System.Numerics;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|
{
|
|
/// <summary>
|
|
/// On-disk shader cache storage for host code.
|
|
/// </summary>
|
|
class DiskCacheHostStorage
|
|
{
|
|
private const uint TocsMagic = (byte)'T' | ((byte)'O' << 8) | ((byte)'C' << 16) | ((byte)'S' << 24);
|
|
private const uint TochMagic = (byte)'T' | ((byte)'O' << 8) | ((byte)'C' << 16) | ((byte)'H' << 24);
|
|
private const uint ShdiMagic = (byte)'S' | ((byte)'H' << 8) | ((byte)'D' << 16) | ((byte)'I' << 24);
|
|
private const uint BufdMagic = (byte)'B' | ((byte)'U' << 8) | ((byte)'F' << 16) | ((byte)'D' << 24);
|
|
private const uint TexdMagic = (byte)'T' | ((byte)'E' << 8) | ((byte)'X' << 16) | ((byte)'D' << 24);
|
|
|
|
private const ushort FileFormatVersionMajor = 1;
|
|
private const ushort FileFormatVersionMinor = 2;
|
|
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
|
private const uint CodeGenVersion = 3747;
|
|
|
|
private const string SharedTocFileName = "shared.toc";
|
|
private const string SharedDataFileName = "shared.data";
|
|
|
|
private readonly string _basePath;
|
|
|
|
public bool CacheEnabled => !string.IsNullOrEmpty(_basePath);
|
|
|
|
/// <summary>
|
|
/// TOC (Table of contents) file header.
|
|
/// </summary>
|
|
private struct TocHeader
|
|
{
|
|
/// <summary>
|
|
/// Magic value, for validation and identification.
|
|
/// </summary>
|
|
public uint Magic;
|
|
|
|
/// <summary>
|
|
/// File format version.
|
|
/// </summary>
|
|
public uint FormatVersion;
|
|
|
|
/// <summary>
|
|
/// Generated shader code version.
|
|
/// </summary>
|
|
public uint CodeGenVersion;
|
|
|
|
/// <summary>
|
|
/// Header padding.
|
|
/// </summary>
|
|
public uint Padding;
|
|
|
|
/// <summary>
|
|
/// Timestamp of when the file was first created.
|
|
/// </summary>
|
|
public ulong Timestamp;
|
|
|
|
/// <summary>
|
|
/// Reserved space, to be used in the future. Write as zero.
|
|
/// </summary>
|
|
public ulong Reserved;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Offset and size pair.
|
|
/// </summary>
|
|
private struct OffsetAndSize
|
|
{
|
|
/// <summary>
|
|
/// Offset.
|
|
/// </summary>
|
|
public ulong Offset;
|
|
|
|
/// <summary>
|
|
/// Size of uncompressed data.
|
|
/// </summary>
|
|
public uint UncompressedSize;
|
|
|
|
/// <summary>
|
|
/// Size of compressed data.
|
|
/// </summary>
|
|
public uint CompressedSize;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Per-stage data entry.
|
|
/// </summary>
|
|
private struct DataEntryPerStage
|
|
{
|
|
/// <summary>
|
|
/// Index of the guest code on the guest code cache TOC file.
|
|
/// </summary>
|
|
public int GuestCodeIndex;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Per-program data entry.
|
|
/// </summary>
|
|
private struct DataEntry
|
|
{
|
|
/// <summary>
|
|
/// Bit mask where each bit set is a used shader stage. Should be zero for compute shaders.
|
|
/// </summary>
|
|
public uint StagesBitMask;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Per-stage shader information, returned by the translator.
|
|
/// </summary>
|
|
private struct DataShaderInfo
|
|
{
|
|
/// <summary>
|
|
/// Total constant buffers used.
|
|
/// </summary>
|
|
public ushort CBuffersCount;
|
|
|
|
/// <summary>
|
|
/// Total storage buffers used.
|
|
/// </summary>
|
|
public ushort SBuffersCount;
|
|
|
|
/// <summary>
|
|
/// Total textures used.
|
|
/// </summary>
|
|
public ushort TexturesCount;
|
|
|
|
/// <summary>
|
|
/// Total images used.
|
|
/// </summary>
|
|
public ushort ImagesCount;
|
|
|
|
/// <summary>
|
|
/// Shader stage.
|
|
/// </summary>
|
|
public ShaderStage Stage;
|
|
|
|
/// <summary>
|
|
/// Indicates if the shader accesses the Instance ID built-in variable.
|
|
/// </summary>
|
|
public bool UsesInstanceId;
|
|
|
|
/// <summary>
|
|
/// Indicates if the shader modifies the Layer built-in variable.
|
|
/// </summary>
|
|
public bool UsesRtLayer;
|
|
|
|
/// <summary>
|
|
/// Bit mask with the clip distances written on the vertex stage.
|
|
/// </summary>
|
|
public byte ClipDistancesWritten;
|
|
|
|
/// <summary>
|
|
/// Bit mask of the render target components written by the fragment stage.
|
|
/// </summary>
|
|
public int FragmentOutputMap;
|
|
|
|
/// <summary>
|
|
/// Indicates if the vertex shader accesses draw parameters.
|
|
/// </summary>
|
|
public bool UsesDrawParameters;
|
|
}
|
|
|
|
private readonly DiskCacheGuestStorage _guestStorage;
|
|
|
|
/// <summary>
|
|
/// Creates a disk cache host storage.
|
|
/// </summary>
|
|
/// <param name="basePath">Base path of the shader cache</param>
|
|
public DiskCacheHostStorage(string basePath)
|
|
{
|
|
_basePath = basePath;
|
|
_guestStorage = new DiskCacheGuestStorage(basePath);
|
|
|
|
if (CacheEnabled)
|
|
{
|
|
Directory.CreateDirectory(basePath);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the total of host programs on the cache.
|
|
/// </summary>
|
|
/// <returns>Host programs count</returns>
|
|
public int GetProgramCount()
|
|
{
|
|
string tocFilePath = Path.Combine(_basePath, SharedTocFileName);
|
|
|
|
if (!File.Exists(tocFilePath))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return Math.Max((int)((new FileInfo(tocFilePath).Length - Unsafe.SizeOf<TocHeader>()) / sizeof(ulong)), 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Guest the name of the host program cache file, with extension.
|
|
/// </summary>
|
|
/// <param name="context">GPU context</param>
|
|
/// <returns>Name of the file, without extension</returns>
|
|
private static string GetHostFileName(GpuContext context)
|
|
{
|
|
string apiName = context.Capabilities.Api.ToString().ToLowerInvariant();
|
|
string vendorName = RemoveInvalidCharacters(context.Capabilities.VendorName.ToLowerInvariant());
|
|
return $"{apiName}_{vendorName}";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes invalid path characters and spaces from a file name.
|
|
/// </summary>
|
|
/// <param name="fileName">File name</param>
|
|
/// <returns>Filtered file name</returns>
|
|
private static string RemoveInvalidCharacters(string fileName)
|
|
{
|
|
int indexOfSpace = fileName.IndexOf(' ');
|
|
if (indexOfSpace >= 0)
|
|
{
|
|
fileName = fileName.Substring(0, indexOfSpace);
|
|
}
|
|
|
|
return string.Concat(fileName.Split(Path.GetInvalidFileNameChars(), StringSplitOptions.RemoveEmptyEntries));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the name of the TOC host file.
|
|
/// </summary>
|
|
/// <param name="context">GPU context</param>
|
|
/// <returns>File name</returns>
|
|
private static string GetHostTocFileName(GpuContext context)
|
|
{
|
|
return GetHostFileName(context) + ".toc";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the name of the data host file.
|
|
/// </summary>
|
|
/// <param name="context">GPU context</param>
|
|
/// <returns>File name</returns>
|
|
private static string GetHostDataFileName(GpuContext context)
|
|
{
|
|
return GetHostFileName(context) + ".data";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if a disk cache exists for the current application.
|
|
/// </summary>
|
|
/// <returns>True if a disk cache exists, false otherwise</returns>
|
|
public bool CacheExists()
|
|
{
|
|
string tocFilePath = Path.Combine(_basePath, SharedTocFileName);
|
|
string dataFilePath = Path.Combine(_basePath, SharedDataFileName);
|
|
|
|
if (!File.Exists(tocFilePath) || !File.Exists(dataFilePath) || !_guestStorage.TocFileExists() || !_guestStorage.DataFileExists())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loads all shaders from the cache.
|
|
/// </summary>
|
|
/// <param name="context">GPU context</param>
|
|
/// <param name="loader">Parallel disk cache loader</param>
|
|
public void LoadShaders(GpuContext context, ParallelDiskCacheLoader loader)
|
|
{
|
|
if (!CacheExists())
|
|
{
|
|
return;
|
|
}
|
|
|
|
Stream hostTocFileStream = null;
|
|
Stream hostDataFileStream = null;
|
|
|
|
try
|
|
{
|
|
using var tocFileStream = DiskCacheCommon.OpenFile(_basePath, SharedTocFileName, writable: false);
|
|
using var dataFileStream = DiskCacheCommon.OpenFile(_basePath, SharedDataFileName, writable: false);
|
|
|
|
using var guestTocFileStream = _guestStorage.OpenTocFileStream();
|
|
using var guestDataFileStream = _guestStorage.OpenDataFileStream();
|
|
|
|
BinarySerializer tocReader = new BinarySerializer(tocFileStream);
|
|
BinarySerializer dataReader = new BinarySerializer(dataFileStream);
|
|
|
|
TocHeader header = new TocHeader();
|
|
|
|
if (!tocReader.TryRead(ref header) || header.Magic != TocsMagic)
|
|
{
|
|
throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedGeneric);
|
|
}
|
|
|
|
if (header.FormatVersion != FileFormatVersionPacked)
|
|
{
|
|
throw new DiskCacheLoadException(DiskCacheLoadResult.IncompatibleVersion);
|
|
}
|
|
|
|
bool loadHostCache = header.CodeGenVersion == CodeGenVersion;
|
|
|
|
int programIndex = 0;
|
|
|
|
DataEntry entry = new DataEntry();
|
|
|
|
while (tocFileStream.Position < tocFileStream.Length && loader.Active)
|
|
{
|
|
ulong dataOffset = 0;
|
|
tocReader.Read(ref dataOffset);
|
|
|
|
if ((ulong)dataOffset >= (ulong)dataFileStream.Length)
|
|
{
|
|
throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedGeneric);
|
|
}
|
|
|
|
dataFileStream.Seek((long)dataOffset, SeekOrigin.Begin);
|
|
|
|
dataReader.BeginCompression();
|
|
dataReader.Read(ref entry);
|
|
uint stagesBitMask = entry.StagesBitMask;
|
|
|
|
if ((stagesBitMask & ~0x3fu) != 0)
|
|
{
|
|
throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedGeneric);
|
|
}
|
|
|
|
bool isCompute = stagesBitMask == 0;
|
|
if (isCompute)
|
|
{
|
|
stagesBitMask = 1;
|
|
}
|
|
|
|
GuestCodeAndCbData?[] guestShaders = new GuestCodeAndCbData?[isCompute ? 1 : Constants.ShaderStages + 1];
|
|
|
|
DataEntryPerStage stageEntry = new DataEntryPerStage();
|
|
|
|
while (stagesBitMask != 0)
|
|
{
|
|
int stageIndex = BitOperations.TrailingZeroCount(stagesBitMask);
|
|
|
|
dataReader.Read(ref stageEntry);
|
|
|
|
guestShaders[stageIndex] = _guestStorage.LoadShader(
|
|
guestTocFileStream,
|
|
guestDataFileStream,
|
|
stageEntry.GuestCodeIndex);
|
|
|
|
stagesBitMask &= ~(1u << stageIndex);
|
|
}
|
|
|
|
ShaderSpecializationState specState = ShaderSpecializationState.Read(ref dataReader);
|
|
dataReader.EndCompression();
|
|
|
|
if (loadHostCache)
|
|
{
|
|
(byte[] hostCode, CachedShaderStage[] shaders) = ReadHostCode(
|
|
context,
|
|
ref hostTocFileStream,
|
|
ref hostDataFileStream,
|
|
guestShaders,
|
|
programIndex,
|
|
header.Timestamp);
|
|
|
|
if (hostCode != null)
|
|
{
|
|
bool hasFragmentShader = shaders.Length > 5 && shaders[5] != null;
|
|
int fragmentOutputMap = hasFragmentShader ? shaders[5].Info.FragmentOutputMap : -1;
|
|
|
|
ShaderInfo shaderInfo = specState.PipelineState.HasValue
|
|
? new ShaderInfo(fragmentOutputMap, specState.PipelineState.Value, fromCache: true)
|
|
: new ShaderInfo(fragmentOutputMap, fromCache: true);
|
|
|
|
IProgram hostProgram;
|
|
|
|
if (context.Capabilities.Api == TargetApi.Vulkan)
|
|
{
|
|
ShaderSource[] shaderSources = ShaderBinarySerializer.Unpack(shaders, hostCode, isCompute);
|
|
|
|
hostProgram = context.Renderer.CreateProgram(shaderSources, shaderInfo);
|
|
}
|
|
else
|
|
{
|
|
hostProgram = context.Renderer.LoadProgramBinary(hostCode, hasFragmentShader, shaderInfo);
|
|
}
|
|
|
|
CachedShaderProgram program = new CachedShaderProgram(hostProgram, specState, shaders);
|
|
|
|
loader.QueueHostProgram(program, hostCode, programIndex, isCompute);
|
|
}
|
|
else
|
|
{
|
|
loadHostCache = false;
|
|
}
|
|
}
|
|
|
|
if (!loadHostCache)
|
|
{
|
|
loader.QueueGuestProgram(guestShaders, specState, programIndex, isCompute);
|
|
}
|
|
|
|
loader.CheckCompilation();
|
|
programIndex++;
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
_guestStorage.ClearMemoryCache();
|
|
|
|
hostTocFileStream?.Dispose();
|
|
hostDataFileStream?.Dispose();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the host code for a given shader, if existent.
|
|
/// </summary>
|
|
/// <param name="context">GPU context</param>
|
|
/// <param name="tocFileStream">Host TOC file stream, intialized if needed</param>
|
|
/// <param name="dataFileStream">Host data file stream, initialized if needed</param>
|
|
/// <param name="guestShaders">Guest shader code for each active stage</param>
|
|
/// <param name="programIndex">Index of the program on the cache</param>
|
|
/// <param name="expectedTimestamp">Timestamp of the shared cache file. The host file must be newer than it</param>
|
|
/// <returns>Host binary code, or null if not found</returns>
|
|
private (byte[], CachedShaderStage[]) ReadHostCode(
|
|
GpuContext context,
|
|
ref Stream tocFileStream,
|
|
ref Stream dataFileStream,
|
|
GuestCodeAndCbData?[] guestShaders,
|
|
int programIndex,
|
|
ulong expectedTimestamp)
|
|
{
|
|
if (tocFileStream == null && dataFileStream == null)
|
|
{
|
|
string tocFilePath = Path.Combine(_basePath, GetHostTocFileName(context));
|
|
string dataFilePath = Path.Combine(_basePath, GetHostDataFileName(context));
|
|
|
|
if (!File.Exists(tocFilePath) || !File.Exists(dataFilePath))
|
|
{
|
|
return (null, null);
|
|
}
|
|
|
|
tocFileStream = DiskCacheCommon.OpenFile(_basePath, GetHostTocFileName(context), writable: false);
|
|
dataFileStream = DiskCacheCommon.OpenFile(_basePath, GetHostDataFileName(context), writable: false);
|
|
|
|
BinarySerializer tempTocReader = new BinarySerializer(tocFileStream);
|
|
|
|
TocHeader header = new TocHeader();
|
|
|
|
tempTocReader.Read(ref header);
|
|
|
|
if (header.Timestamp < expectedTimestamp)
|
|
{
|
|
return (null, null);
|
|
}
|
|
}
|
|
|
|
int offset = Unsafe.SizeOf<TocHeader>() + programIndex * Unsafe.SizeOf<OffsetAndSize>();
|
|
if (offset + Unsafe.SizeOf<OffsetAndSize>() > tocFileStream.Length)
|
|
{
|
|
return (null, null);
|
|
}
|
|
|
|
if ((ulong)offset >= (ulong)dataFileStream.Length)
|
|
{
|
|
throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedGeneric);
|
|
}
|
|
|
|
tocFileStream.Seek(offset, SeekOrigin.Begin);
|
|
|
|
BinarySerializer tocReader = new BinarySerializer(tocFileStream);
|
|
|
|
OffsetAndSize offsetAndSize = new OffsetAndSize();
|
|
tocReader.Read(ref offsetAndSize);
|
|
|
|
if (offsetAndSize.Offset >= (ulong)dataFileStream.Length)
|
|
{
|
|
throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedGeneric);
|
|
}
|
|
|
|
dataFileStream.Seek((long)offsetAndSize.Offset, SeekOrigin.Begin);
|
|
|
|
byte[] hostCode = new byte[offsetAndSize.UncompressedSize];
|
|
|
|
BinarySerializer.ReadCompressed(dataFileStream, hostCode);
|
|
|
|
CachedShaderStage[] shaders = new CachedShaderStage[guestShaders.Length];
|
|
BinarySerializer dataReader = new BinarySerializer(dataFileStream);
|
|
|
|
dataFileStream.Seek((long)(offsetAndSize.Offset + offsetAndSize.CompressedSize), SeekOrigin.Begin);
|
|
|
|
dataReader.BeginCompression();
|
|
|
|
for (int index = 0; index < guestShaders.Length; index++)
|
|
{
|
|
if (!guestShaders[index].HasValue)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
GuestCodeAndCbData guestShader = guestShaders[index].Value;
|
|
ShaderProgramInfo info = index != 0 || guestShaders.Length == 1 ? ReadShaderProgramInfo(ref dataReader) : null;
|
|
|
|
shaders[index] = new CachedShaderStage(info, guestShader.Code, guestShader.Cb1Data);
|
|
}
|
|
|
|
dataReader.EndCompression();
|
|
|
|
return (hostCode, shaders);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets output streams for the disk cache, for faster batch writing.
|
|
/// </summary>
|
|
/// <param name="context">The GPU context, used to determine the host disk cache</param>
|
|
/// <returns>A collection of disk cache output streams</returns>
|
|
public DiskCacheOutputStreams GetOutputStreams(GpuContext context)
|
|
{
|
|
var tocFileStream = DiskCacheCommon.OpenFile(_basePath, SharedTocFileName, writable: true);
|
|
var dataFileStream = DiskCacheCommon.OpenFile(_basePath, SharedDataFileName, writable: true);
|
|
|
|
var hostTocFileStream = DiskCacheCommon.OpenFile(_basePath, GetHostTocFileName(context), writable: true);
|
|
var hostDataFileStream = DiskCacheCommon.OpenFile(_basePath, GetHostDataFileName(context), writable: true);
|
|
|
|
return new DiskCacheOutputStreams(tocFileStream, dataFileStream, hostTocFileStream, hostDataFileStream);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a shader to the cache.
|
|
/// </summary>
|
|
/// <param name="context">GPU context</param>
|
|
/// <param name="program">Cached program</param>
|
|
/// <param name="hostCode">Optional host binary code</param>
|
|
/// <param name="streams">Output streams to use</param>
|
|
public void AddShader(GpuContext context, CachedShaderProgram program, ReadOnlySpan<byte> hostCode, DiskCacheOutputStreams streams = null)
|
|
{
|
|
uint stagesBitMask = 0;
|
|
|
|
for (int index = 0; index < program.Shaders.Length; index++)
|
|
{
|
|
var shader = program.Shaders[index];
|
|
if (shader == null || (shader.Info != null && shader.Info.Stage == ShaderStage.Compute))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
stagesBitMask |= 1u << index;
|
|
}
|
|
|
|
var tocFileStream = streams != null ? streams.TocFileStream : DiskCacheCommon.OpenFile(_basePath, SharedTocFileName, writable: true);
|
|
var dataFileStream = streams != null ? streams.DataFileStream : DiskCacheCommon.OpenFile(_basePath, SharedDataFileName, writable: true);
|
|
|
|
ulong timestamp = (ulong)DateTime.UtcNow.Subtract(DateTime.UnixEpoch).TotalSeconds;
|
|
|
|
if (tocFileStream.Length == 0)
|
|
{
|
|
TocHeader header = new TocHeader();
|
|
CreateToc(tocFileStream, ref header, TocsMagic, CodeGenVersion, timestamp);
|
|
}
|
|
|
|
tocFileStream.Seek(0, SeekOrigin.End);
|
|
dataFileStream.Seek(0, SeekOrigin.End);
|
|
|
|
BinarySerializer tocWriter = new BinarySerializer(tocFileStream);
|
|
BinarySerializer dataWriter = new BinarySerializer(dataFileStream);
|
|
|
|
ulong dataOffset = (ulong)dataFileStream.Position;
|
|
tocWriter.Write(ref dataOffset);
|
|
|
|
DataEntry entry = new DataEntry();
|
|
|
|
entry.StagesBitMask = stagesBitMask;
|
|
|
|
dataWriter.BeginCompression(DiskCacheCommon.GetCompressionAlgorithm());
|
|
dataWriter.Write(ref entry);
|
|
|
|
DataEntryPerStage stageEntry = new DataEntryPerStage();
|
|
|
|
for (int index = 0; index < program.Shaders.Length; index++)
|
|
{
|
|
var shader = program.Shaders[index];
|
|
if (shader == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
stageEntry.GuestCodeIndex = _guestStorage.AddShader(shader.Code, shader.Cb1Data);
|
|
|
|
dataWriter.Write(ref stageEntry);
|
|
}
|
|
|
|
program.SpecializationState.Write(ref dataWriter);
|
|
dataWriter.EndCompression();
|
|
|
|
if (streams == null)
|
|
{
|
|
tocFileStream.Dispose();
|
|
dataFileStream.Dispose();
|
|
}
|
|
|
|
if (hostCode.IsEmpty)
|
|
{
|
|
return;
|
|
}
|
|
|
|
WriteHostCode(context, hostCode, program.Shaders, streams, timestamp);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears all content from the guest cache files.
|
|
/// </summary>
|
|
public void ClearGuestCache()
|
|
{
|
|
_guestStorage.ClearCache();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears all content from the shared cache files.
|
|
/// </summary>
|
|
/// <param name="context">GPU context</param>
|
|
public void ClearSharedCache()
|
|
{
|
|
using var tocFileStream = DiskCacheCommon.OpenFile(_basePath, SharedTocFileName, writable: true);
|
|
using var dataFileStream = DiskCacheCommon.OpenFile(_basePath, SharedDataFileName, writable: true);
|
|
|
|
tocFileStream.SetLength(0);
|
|
dataFileStream.SetLength(0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deletes all content from the host cache files.
|
|
/// </summary>
|
|
/// <param name="context">GPU context</param>
|
|
public void ClearHostCache(GpuContext context)
|
|
{
|
|
using var tocFileStream = DiskCacheCommon.OpenFile(_basePath, GetHostTocFileName(context), writable: true);
|
|
using var dataFileStream = DiskCacheCommon.OpenFile(_basePath, GetHostDataFileName(context), writable: true);
|
|
|
|
tocFileStream.SetLength(0);
|
|
dataFileStream.SetLength(0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the host binary code on the host cache.
|
|
/// </summary>
|
|
/// <param name="context">GPU context</param>
|
|
/// <param name="hostCode">Host binary code</param>
|
|
/// <param name="shaders">Shader stages to be added to the host cache</param>
|
|
/// <param name="streams">Output streams to use</param>
|
|
/// <param name="timestamp">File creation timestamp</param>
|
|
private void WriteHostCode(
|
|
GpuContext context,
|
|
ReadOnlySpan<byte> hostCode,
|
|
CachedShaderStage[] shaders,
|
|
DiskCacheOutputStreams streams,
|
|
ulong timestamp)
|
|
{
|
|
var tocFileStream = streams != null ? streams.HostTocFileStream : DiskCacheCommon.OpenFile(_basePath, GetHostTocFileName(context), writable: true);
|
|
var dataFileStream = streams != null ? streams.HostDataFileStream : DiskCacheCommon.OpenFile(_basePath, GetHostDataFileName(context), writable: true);
|
|
|
|
if (tocFileStream.Length == 0)
|
|
{
|
|
TocHeader header = new TocHeader();
|
|
CreateToc(tocFileStream, ref header, TochMagic, 0, timestamp);
|
|
}
|
|
|
|
tocFileStream.Seek(0, SeekOrigin.End);
|
|
dataFileStream.Seek(0, SeekOrigin.End);
|
|
|
|
BinarySerializer tocWriter = new BinarySerializer(tocFileStream);
|
|
BinarySerializer dataWriter = new BinarySerializer(dataFileStream);
|
|
|
|
OffsetAndSize offsetAndSize = new OffsetAndSize();
|
|
offsetAndSize.Offset = (ulong)dataFileStream.Position;
|
|
offsetAndSize.UncompressedSize = (uint)hostCode.Length;
|
|
|
|
long dataStartPosition = dataFileStream.Position;
|
|
|
|
BinarySerializer.WriteCompressed(dataFileStream, hostCode, DiskCacheCommon.GetCompressionAlgorithm());
|
|
|
|
offsetAndSize.CompressedSize = (uint)(dataFileStream.Position - dataStartPosition);
|
|
|
|
tocWriter.Write(ref offsetAndSize);
|
|
|
|
dataWriter.BeginCompression(DiskCacheCommon.GetCompressionAlgorithm());
|
|
|
|
for (int index = 0; index < shaders.Length; index++)
|
|
{
|
|
if (shaders[index] != null)
|
|
{
|
|
WriteShaderProgramInfo(ref dataWriter, shaders[index].Info);
|
|
}
|
|
}
|
|
|
|
dataWriter.EndCompression();
|
|
|
|
if (streams == null)
|
|
{
|
|
tocFileStream.Dispose();
|
|
dataFileStream.Dispose();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a TOC file for the host or shared cache.
|
|
/// </summary>
|
|
/// <param name="tocFileStream">TOC file stream</param>
|
|
/// <param name="header">Set to the TOC file header</param>
|
|
/// <param name="magic">Magic value to be written</param>
|
|
/// <param name="codegenVersion">Shader codegen version, only valid for the host file</param>
|
|
/// <param name="timestamp">File creation timestamp</param>
|
|
private void CreateToc(Stream tocFileStream, ref TocHeader header, uint magic, uint codegenVersion, ulong timestamp)
|
|
{
|
|
BinarySerializer writer = new BinarySerializer(tocFileStream);
|
|
|
|
header.Magic = magic;
|
|
header.FormatVersion = FileFormatVersionPacked;
|
|
header.CodeGenVersion = codegenVersion;
|
|
header.Padding = 0;
|
|
header.Reserved = 0;
|
|
header.Timestamp = timestamp;
|
|
|
|
if (tocFileStream.Length > 0)
|
|
{
|
|
tocFileStream.Seek(0, SeekOrigin.Begin);
|
|
tocFileStream.SetLength(0);
|
|
}
|
|
|
|
writer.Write(ref header);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the shader program info from the cache.
|
|
/// </summary>
|
|
/// <param name="dataReader">Cache data reader</param>
|
|
/// <returns>Shader program info</returns>
|
|
private static ShaderProgramInfo ReadShaderProgramInfo(ref BinarySerializer dataReader)
|
|
{
|
|
DataShaderInfo dataInfo = new DataShaderInfo();
|
|
|
|
dataReader.ReadWithMagicAndSize(ref dataInfo, ShdiMagic);
|
|
|
|
BufferDescriptor[] cBuffers = new BufferDescriptor[dataInfo.CBuffersCount];
|
|
BufferDescriptor[] sBuffers = new BufferDescriptor[dataInfo.SBuffersCount];
|
|
TextureDescriptor[] textures = new TextureDescriptor[dataInfo.TexturesCount];
|
|
TextureDescriptor[] images = new TextureDescriptor[dataInfo.ImagesCount];
|
|
|
|
for (int index = 0; index < dataInfo.CBuffersCount; index++)
|
|
{
|
|
dataReader.ReadWithMagicAndSize(ref cBuffers[index], BufdMagic);
|
|
}
|
|
|
|
for (int index = 0; index < dataInfo.SBuffersCount; index++)
|
|
{
|
|
dataReader.ReadWithMagicAndSize(ref sBuffers[index], BufdMagic);
|
|
}
|
|
|
|
for (int index = 0; index < dataInfo.TexturesCount; index++)
|
|
{
|
|
dataReader.ReadWithMagicAndSize(ref textures[index], TexdMagic);
|
|
}
|
|
|
|
for (int index = 0; index < dataInfo.ImagesCount; index++)
|
|
{
|
|
dataReader.ReadWithMagicAndSize(ref images[index], TexdMagic);
|
|
}
|
|
|
|
return new ShaderProgramInfo(
|
|
cBuffers,
|
|
sBuffers,
|
|
textures,
|
|
images,
|
|
dataInfo.Stage,
|
|
dataInfo.UsesInstanceId,
|
|
dataInfo.UsesDrawParameters,
|
|
dataInfo.UsesRtLayer,
|
|
dataInfo.ClipDistancesWritten,
|
|
dataInfo.FragmentOutputMap);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the shader program info into the cache.
|
|
/// </summary>
|
|
/// <param name="dataWriter">Cache data writer</param>
|
|
/// <param name="info">Program info</param>
|
|
private static void WriteShaderProgramInfo(ref BinarySerializer dataWriter, ShaderProgramInfo info)
|
|
{
|
|
if (info == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
DataShaderInfo dataInfo = new DataShaderInfo();
|
|
|
|
dataInfo.CBuffersCount = (ushort)info.CBuffers.Count;
|
|
dataInfo.SBuffersCount = (ushort)info.SBuffers.Count;
|
|
dataInfo.TexturesCount = (ushort)info.Textures.Count;
|
|
dataInfo.ImagesCount = (ushort)info.Images.Count;
|
|
dataInfo.Stage = info.Stage;
|
|
dataInfo.UsesInstanceId = info.UsesInstanceId;
|
|
dataInfo.UsesDrawParameters = info.UsesDrawParameters;
|
|
dataInfo.UsesRtLayer = info.UsesRtLayer;
|
|
dataInfo.ClipDistancesWritten = info.ClipDistancesWritten;
|
|
dataInfo.FragmentOutputMap = info.FragmentOutputMap;
|
|
|
|
dataWriter.WriteWithMagicAndSize(ref dataInfo, ShdiMagic);
|
|
|
|
for (int index = 0; index < info.CBuffers.Count; index++)
|
|
{
|
|
var entry = info.CBuffers[index];
|
|
dataWriter.WriteWithMagicAndSize(ref entry, BufdMagic);
|
|
}
|
|
|
|
for (int index = 0; index < info.SBuffers.Count; index++)
|
|
{
|
|
var entry = info.SBuffers[index];
|
|
dataWriter.WriteWithMagicAndSize(ref entry, BufdMagic);
|
|
}
|
|
|
|
for (int index = 0; index < info.Textures.Count; index++)
|
|
{
|
|
var entry = info.Textures[index];
|
|
dataWriter.WriteWithMagicAndSize(ref entry, TexdMagic);
|
|
}
|
|
|
|
for (int index = 0; index < info.Images.Count; index++)
|
|
{
|
|
var entry = info.Images[index];
|
|
dataWriter.WriteWithMagicAndSize(ref entry, TexdMagic);
|
|
}
|
|
}
|
|
}
|
|
}
|