Files
2026-07-13 13:15:05 +08:00

180 lines
6.2 KiB
C#

#nullable enable
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using T3.Core.Logging;
using T3.Core.Settings;
using T3.Core.SystemUi;
namespace T3.Core.Resource.ShaderCompiling;
public abstract partial class ShaderCompiler
{
public static void DeleteShaderCache(bool all)
{
int failures = 0;
int total = 0;
long totalFileSize = 0;
var directory = all ? _shaderCacheRootPath : _shaderCacheDirectory;
if(!Directory.Exists(directory))
{
BlockingWindow.Instance.ShowMessageBox($"No shader cache found at \"{directory}\".", "No shader cache found");
return;
}
lock (_shaderCacheLock)
{
Directory.EnumerateFiles(directory, $"*{FileExtension}", SearchOption.AllDirectories)
.AsParallel()
.ForAll(file =>
{
Interlocked.Increment(ref total);
try
{
var fileInfo = new FileInfo(file);
var fileSize = fileInfo.Length;
fileInfo.Delete();
Interlocked.Add(ref totalFileSize, fileSize);
}
catch (Exception e)
{
Log.Error($"Failed to delete shader cache file '{file}': {e.Message}");
Interlocked.Increment(ref failures);
}
});
}
var deletionsInKb = totalFileSize / 1024d;
var deletionsString = deletionsInKb < 1024d
? $"{deletionsInKb:0.0} KB"
: $"{deletionsInKb / 1024d:0.0} MB";
var isError = failures > 0;
string message;
string title;
if (isError)
{
message = $"Failed to delete {failures} out of {total} shader cache files.";
title = "Shader Cache Deletion Failed";
}
else
{
message = string.Empty;
title = "Shader Cache Deleted Successfully";
}
var finalMessage = $"Deleted {deletionsString} of shader cache from \"{_shaderCacheRootPath}\".\n{message}\n" +
$"Restart the application to refresh the removed cache, as all shaders still reside in memory.";
BlockingWindow.Instance.ShowMessageBox(finalMessage, title);
}
private static void CacheSuccessfulCompilation(byte[]? oldBytecode, ulong hash, byte[] newBytecode)
{
CacheShaderInMemory(oldBytecode, hash, newBytecode);
SaveBytecodeToDisk(hash, newBytecode);
}
private static void SaveBytecodeToDisk(ulong hash, byte[] byteCode)
{
if (!_diskCachingEnabled)
{
return;
}
var path = GetPathForShaderCache(hash);
File.WriteAllBytes(path, byteCode);
}
private static bool TryLoadBytecodeFromDisk(ulong hash, [NotNullWhen(true)] out byte[]? bytecode)
{
if (!_diskCachingEnabled)
{
bytecode = null;
return false;
}
var path = GetPathForShaderCache(hash);
var file = new FileInfo(path);
if (!file.Exists)
{
bytecode = null;
return false;
}
bytecode = File.ReadAllBytes(path);
return true;
}
private static void CacheShaderInMemory(byte[]? oldBytecode, ulong hash, byte[] newBytecode)
{
if (oldBytecode != null)
{
if (_shaderBytecodeHashes.Remove(oldBytecode, out var oldHash))
{
_shaderBytecodeCache.Remove(oldHash);
}
}
_shaderBytecodeCache[hash] = newBytecode;
_shaderBytecodeHashes[newBytecode] = hash;
}
private static string GetPathForShaderCache(ulong hashCode)
{
// ReSharper disable once StringLiteralTypo
return Path.Combine(_shaderCacheDirectory, hashCode + FileExtension);
}
private static readonly Dictionary<byte[], ulong> _shaderBytecodeHashes = new();
private static readonly Dictionary<ulong, byte[]> _shaderBytecodeCache = new();
[StructLayout(LayoutKind.Explicit)]
private readonly struct ULongFromTwoInts(int a, int b)
{
[FieldOffset(0)]
public readonly ulong Value = 0;
[FieldOffset(0)]
public readonly int A = a;
[FieldOffset(4)]
public readonly int B = b;
}
private static readonly object _shaderCacheLock = new();
private static readonly string _shaderCacheRootPath = Path.Combine(FileLocations.TempFolder, "Cache");
private static string _shaderCacheDirectory = string.Empty;
public static string ShaderCacheSubdirectory
{
set
{
if(!string.IsNullOrEmpty(_shaderCacheDirectory) )
throw new InvalidOperationException("Shader cache subdirectory can only be set once.");
_shaderCacheDirectory = Path.Combine(_shaderCacheRootPath, value);
var potentialCacheFilePath = Path.Combine(_shaderCacheDirectory, ulong.MaxValue + FileExtension);
if (potentialCacheFilePath.Length > 259)
{
string message = $"File path for shader cache is too long: \"{_shaderCacheDirectory}\". Disk caching will be disabled.";
BlockingWindow.Instance.ShowMessageBox(message);
Log.Error(message);
_diskCachingEnabled = false;
return;
}
Directory.CreateDirectory(_shaderCacheDirectory);
}
}
private static bool _diskCachingEnabled = true;
private const string FileExtension = ".shadercache";
}