feat(shader): refactor and enhance shader pipeline

Refactored the shader compilation pipeline to introduce modularity, improve performance, and enhance maintainability. Key changes include:

- Added `ShaderCompilationConfig`, `CompilerOptimizeLevel`, and `ShaderStage` enums.
- Replaced `SM` property with `ShaderModel` in shader models.
- Introduced `ShaderLibrary` for in-memory and disk-based shader caching.
- Refactored `DSLShaderCompiler` and `AntlrShaderCompiler` for better hashing and error handling.
- Centralized shader compilation logic in `ShaderCompilerUtility`.
- Removed legacy shader compilation logic from `IShaderCompiler`.
- Updated `RenderGraph`, `ResourceManager`, and `Material` to integrate with the new caching system.
- Improved memory management with `NativeMemoryManager<T>`.

BREAKING CHANGE: Removed legacy shader compilation methods and replaced them with a new caching and compilation system.
This commit is contained in:
2026-04-11 23:10:39 +09:00
parent f9a6e9cbbe
commit c66fda5332
30 changed files with 630 additions and 500 deletions

View File

@@ -1,8 +1,12 @@
using Ghost.Core.Utilities;
using System.IO.Hashing;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ghost.Core.Graphics;
public enum ShaderModel
{
Invalid,
SM_6_6,
SM_6_7,
SM_6_8
@@ -20,6 +24,18 @@ public struct ShaderCode
public string entryPoint;
public readonly bool IsCreated => !string.IsNullOrEmpty(code) && !string.IsNullOrEmpty(entryPoint);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly ulong GetHashCode64()
{
return Hash.Combine64(XxHash64.HashToUInt64(MemoryMarshal.AsBytes(code.AsSpan())), XxHash64.HashToUInt64(MemoryMarshal.AsBytes(entryPoint.AsSpan())));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override readonly int GetHashCode()
{
return HashCode.Combine(code, entryPoint);
}
}
public struct KeywordsGroup
@@ -53,7 +69,6 @@ public class GraphicsShaderDescriptor
public class ComputeShaderDescriptor
{
public required ulong identifier;
public required string name = string.Empty;
public required uint propertyBufferSize;
public required ShaderModel shaderModel;

View File

@@ -1,5 +1,6 @@
using Misaki.HighPerformance.LowLevel;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ghost.Core;
@@ -260,6 +261,64 @@ public readonly ref struct RefResult<T, E>
public static implicit operator bool(RefResult<T, E> result) => result.IsSuccess;
}
[AsyncMethodBuilder(typeof(AsyncValueTaskMethodBuilder))]
public readonly struct ResultTask
{
private readonly ValueTask<Result> _task;
public ResultTask(ValueTask<Result> task)
{
_task = task;
}
public ValueTaskAwaiter<Result> GetAwaiter() => _task.GetAwaiter();
public ValueTask<Result> AsValueTask() => _task;
public Task<Result> AsTask() => _task.AsTask();
public static implicit operator ResultTask(ValueTask<Result> task) => new ResultTask(task);
public static implicit operator ValueTask<Result>(ResultTask resultTask) => resultTask._task;
}
[AsyncMethodBuilder(typeof(AsyncValueTaskMethodBuilder))]
public readonly struct ResultTask<T>
{
private readonly ValueTask<Result<T>> _task;
public ResultTask(ValueTask<Result<T>> task)
{
_task = task;
}
public ValueTaskAwaiter<Result<T>> GetAwaiter() => _task.GetAwaiter();
public ValueTask<Result<T>> AsValueTask() => _task;
public Task<Result<T>> AsTask() => _task.AsTask();
public static implicit operator ResultTask<T>(ValueTask<Result<T>> task) => new ResultTask<T>(task);
public static implicit operator ValueTask<Result<T>>(ResultTask<T> resultTask) => resultTask._task;
}
[AsyncMethodBuilder(typeof(AsyncValueTaskMethodBuilder))]
public readonly struct ResultTask<T, E>
where E : struct, Enum
{
private readonly ValueTask<Result<T, E>> _task;
public ResultTask(ValueTask<Result<T, E>> task)
{
_task = task;
}
public ValueTaskAwaiter<Result<T, E>> GetAwaiter() => _task.GetAwaiter();
public ValueTask<Result<T, E>> AsValueTask() => _task;
public Task<Result<T, E>> AsTask() => _task.AsTask();
public static implicit operator ResultTask<T, E>(ValueTask<Result<T, E>> task) => new ResultTask<T, E>(task);
public static implicit operator ValueTask<Result<T, E>>(ResultTask<T, E> resultTask) => resultTask._task;
}
public static class ResultExtensions
{
extension(Error error)

View File

@@ -0,0 +1,46 @@
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using System.Buffers;
namespace Ghost.Core.Utilities;
public unsafe class NativeMemoryManager<T> : MemoryManager<T>
where T : unmanaged
{
private readonly T* _pointer;
private readonly int _length;
public NativeMemoryManager(T* pointer, int length)
{
_pointer = pointer;
_length = length;
}
public static NativeMemoryManager<T> FromUnsafeCollection<C>(ref readonly C collection)
where C : unmanaged, IUnsafeCollection<T>
{
if (!collection.IsCreated)
{
throw new InvalidOperationException("The collection is not created.");
}
return new NativeMemoryManager<T>((T*)collection.GetUnsafePtr(), collection.Count);
}
public override Span<T> GetSpan()
{
return new Span<T>(_pointer, _length);
}
public override MemoryHandle Pin(int elementIndex = 0)
{
return new MemoryHandle(_pointer + elementIndex);
}
public override void Unpin()
{
}
protected override void Dispose(bool disposing)
{
}
}

View File

@@ -3,6 +3,7 @@ using Ghost.Graphics;
using Ghost.Graphics.Core;
using Ghost.Graphics.RenderGraphModule;
using Ghost.Graphics.RHI;
using Ghost.Graphics.Services;
using Misaki.HighPerformance.LowLevel.Utilities;
using Misaki.HighPerformance.Mathematics;
using System.Diagnostics;

View File

@@ -66,7 +66,7 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
_descriptorAllocator = new D3D12DescriptorAllocator(_device);
_resourceDatabase = new D3D12ResourceDatabase(_descriptorAllocator);
_pipelineLibrary = new D3D12PipelineLibrary(_device, _resourceDatabase);
_pipelineLibrary = new D3D12PipelineLibrary(_device);
_resourceAllocator = new D3D12ResourceAllocator(_device, _descriptorAllocator, _resourceDatabase, _pipelineLibrary);
_commandBufferPool = new List<ICommandBuffer>(4);

View File

@@ -1,15 +0,0 @@
using Ghost.Core;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel;
using TerraFX.Interop.DirectX;
namespace Ghost.Graphics.D3D12;
internal class D3D12WorkGraphPipeline : IWorkGraphPipeline
{
private UniquePtr<ID3D12StateObject> _stateObject;
private D3D12_PROGRAM_IDENTIFIER _programIdentifier;
private D3D12_WORK_GRAPH_MEMORY_REQUIREMENTS _memoryRequirements;
private Handle<GPUResource> _backingBuffer;
}

View File

@@ -1,152 +0,0 @@
using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.Core.Utilities;
using Misaki.HighPerformance.LowLevel.Collections;
namespace Ghost.Graphics.RHI;
public struct ShaderCompileResult : IDisposable
{
public UnsafeArray<byte> bytecode;
public ulong hashCode;
public readonly bool IsCreated => bytecode.IsCreated;
public void Dispose()
{
bytecode.Dispose();
}
}
public struct GraphicsCompiledResult
{
public ulong tsResultHash;
public ulong msResultHash;
public ulong psResultHash;
public readonly ulong HashCode => Hash.Combine64(tsResultHash, msResultHash, psResultHash);
}
public unsafe struct ComputeCompileResult
{
public fixed ulong resultHash[8];
public readonly int count;
public ulong HashCode
{
get
{
var a = Hash.Combine64(resultHash[0], resultHash[1], resultHash[2], resultHash[3]);
var b = Hash.Combine64(resultHash[4], resultHash[5], resultHash[6], resultHash[7]);
return Hash.Combine64(a, b);
}
}
}
public ref struct ShaderCompilationConfig
{
public ReadOnlySpan<string> defines;
public string shaderCode;
public string entryPoint;
public ShaderStage stage;
public ShaderModel model;
public CompilerOptimizeLevel optimizeLevel;
public CompilerOption options;
}
public enum CompilerOptimizeLevel
{
O0,
O1,
O2,
O3
}
[Flags]
public enum CompilerOption
{
None = 0,
KeepDebugInfo = 1 << 0,
KeepReflections = 1 << 1,
WarnAsError = 1 << 2,
SpirvCrossCompile = 1 << 3
}
public enum ShaderStage
{
TaskShader,
MeshShader,
PixelShader,
ComputeShader,
Library // For ray tracing shaders or work graph shaders that don't fit into the traditional shader stages
}
public enum ShaderInputType
{
ConstantBuffer,
Texture,
Sampler,
UAV,
StructuredBuffer,
ByteAddressBuffer,
RWStructuredBuffer,
RWByteAddressBuffer
}
public struct ResourceBindingInfo
{
public string Name
{
get; set;
}
public ShaderInputType Type
{
get; set;
}
public uint BindPoint
{
get; set;
}
public uint BindCount
{
get; set;
}
public uint Space
{
get; set;
}
public uint Size
{
get; set;
}
public IReadOnlyList<CBufferPropertyInfo>? Properties
{
get; set;
}
}
public readonly struct ShaderReflectionData
{
public List<ResourceBindingInfo> ResourcesBindings
{
get;
}
public ShaderReflectionData()
{
ResourcesBindings = new List<ResourceBindingInfo>();
}
}
public interface IShaderCompiler : IDisposable
{
Result<Key64<ShaderCompileResult>> Compile(ref readonly ShaderCompilationConfig config);
Result<GraphicsCompiledResult> CompilePass(ref readonly PassDescriptor descriptor, ref readonly ShaderCompilationConfig additionalConfig, ref readonly LocalKeywordSet keywords);
Result<ShaderCompileResult, Error> GetCompiledCache(Key64<ShaderCompileResult> key);
}

View File

@@ -1,6 +1,7 @@
using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.Graphics.RHI;
using Ghost.Graphics.Services;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.CompilerServices;
@@ -87,7 +88,7 @@ public struct Material : IResourceReleasable
return r.Error;
}
ref readonly var shader = ref r.Value;
ref var shader = ref r.Value;
if (_passPipelineOverride.Count < shader.PassCount)
{
if (!_passPipelineOverride.IsCreated)
@@ -212,7 +213,7 @@ public struct Material : IResourceReleasable
return r.Error;
}
ref readonly var shader = ref r.Value;
ref var shader = ref r.Value;
var localIndex = shader.GetLocalKeywordIndex(keywordId);
if (localIndex == -1)
{
@@ -233,7 +234,7 @@ public struct Material : IResourceReleasable
return false;
}
ref readonly var shader = ref r.Value;
ref var shader = ref r.Value;
var localIndex = shader.GetLocalKeywordIndex(keywordId);
if (localIndex == -1)
{

View File

@@ -1,5 +1,6 @@
using Ghost.Core;
using Ghost.Graphics.RHI;
using Ghost.Graphics.Services;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities;

View File

@@ -1,6 +1,5 @@
using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.Core.Utilities;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
@@ -87,10 +86,8 @@ public partial struct Shader : IResourceReleasable
public readonly int PassCount => _shaderPasses.Count;
public readonly uint PropertyBufferSize => _propertyBufferSize;
internal Shader(GraphicsShaderDescriptor descriptor, ReadOnlySpan<GraphicsCompiledResult> compiledResults)
internal Shader(GraphicsShaderDescriptor descriptor)
{
Debug.Assert(descriptor.passes.Length == compiledResults.Length);
_propertyBufferSize = descriptor.propertyBufferSize;
_shaderPasses = new UnsafeArray<ShaderPass>(descriptor.passes.Length, Allocator.Persistent);
_passIDToLocal = new UnsafeHashMap<int, int>(descriptor.passes.Length, Allocator.Persistent);
@@ -100,8 +97,7 @@ public partial struct Shader : IResourceReleasable
{
ref readonly var pass = ref descriptor.passes[i];
var passKey = RHIUtility.CreateShaderPassKey(pass.identifier, compiledResults[i].HashCode);
var keywords = default(LocalKeywordSet);
var keywords = new LocalKeywordSet();
if (pass.keywords.Length > 0)
{
@@ -133,7 +129,7 @@ public partial struct Shader : IResourceReleasable
_shaderPasses[i] = new ShaderPass
{
Key = passKey,
Key = pass.identifier,
DefaultState = pass.localPipeline,
KeywordIDs = keywords,
};
@@ -213,10 +209,8 @@ public unsafe partial struct ComputeShader : IResourceReleasable
public readonly uint PropertyBufferSize => _propertyBufferSize;
internal ComputeShader(ComputeShaderDescriptor descriptor, ReadOnlySpan<ShaderCompileResult> compiledResults)
internal ComputeShader(ComputeShaderDescriptor descriptor)
{
Debug.Assert(descriptor.shaderCodes.Length == compiledResults.Length);
_propertyBufferSize = descriptor.propertyBufferSize;
_entryPointCount = descriptor.shaderCodes.Length;
@@ -224,7 +218,7 @@ public unsafe partial struct ComputeShader : IResourceReleasable
for (var i = 0; i < descriptor.shaderCodes.Length; i++)
{
_entryHash[i] = Hash.Combine64(descriptor.identifier, compiledResults[i].hashCode);
_entryHash[i] = descriptor.shaderCodes[i].GetHashCode64();
}
var localKeywordIndex = 0;
@@ -271,4 +265,4 @@ public unsafe partial struct ComputeShader : IResourceReleasable
{
_keywordIDToLocal.Dispose();
}
}
}

View File

@@ -0,0 +1,19 @@
namespace Ghost.Graphics;
public interface IShaderCompilationBridge
{
bool TryGetBytecode(ulong manifestKey, out ReadOnlyMemory<byte> bytecode);
bool IsCompiling(ulong manifestKey);
}
// NOTE: For testing only.
internal sealed class NullShaderCompilationBridge : IShaderCompilationBridge
{
public bool TryGetBytecode(ulong manifestKey, out ReadOnlyMemory<byte> bytecode)
{
bytecode = default;
return false; // Always fall through to ShaderLibrary's disk cache
}
public bool IsCompiling(ulong manifestKey) => false;
}

View File

@@ -1,6 +1,7 @@
using Ghost.Core;
using Ghost.Graphics.RHI;
using System.Diagnostics;
using Ghost.Graphics.Services;
namespace Ghost.Graphics.RenderGraphModule;
@@ -9,8 +10,6 @@ namespace Ghost.Graphics.RenderGraphModule;
/// </summary>
public sealed class RenderGraph : IDisposable
{
private readonly ResourceManager _resourceManager;
private readonly IResourceAllocator _resourceAllocator;
private readonly IResourceDatabase _resourceDatabase;
private readonly RenderGraphObjectPool _objectPool;
@@ -38,11 +37,9 @@ public sealed class RenderGraph : IDisposable
public RenderGraphBlackboard Blackboard => _blackboard;
public RenderGraph(ResourceManager resourceManager, IResourceAllocator resourceAllocator, IResourceDatabase resourceDatabase, IPipelineLibrary pipelineLibrary, IShaderCompiler shaderCompiler)
public RenderGraph(RenderSystem renderSystem)
{
_resourceManager = resourceManager;
_resourceAllocator = resourceAllocator;
_resourceDatabase = resourceDatabase;
_resourceDatabase = renderSystem.GraphicsEngine.ResourceDatabase;
_objectPool = new RenderGraphObjectPool();
_resources = new RenderGraphResourceRegistry(_objectPool);
@@ -52,30 +49,25 @@ public sealed class RenderGraph : IDisposable
_nativePasses = new List<NativeRenderPass>(32);
_builder = new RenderGraphBuilder();
_aliasingManager = new ResourceAliasingManager(_resourceAllocator, _objectPool);
_aliasingManager = new ResourceAliasingManager(renderSystem.GraphicsEngine.ResourceAllocator, _objectPool);
_compilationCache = new RenderGraphCompilationCache();
_context = new RenderGraphContext(
_resourceManager,
_resourceDatabase,
pipelineLibrary,
shaderCompiler,
renderSystem.ResourceManager,
renderSystem.ShaderLibrary,
renderSystem.GraphicsEngine.ResourceDatabase,
renderSystem.GraphicsEngine.PipelineLibrary,
_resources
);
_nativePassBuilder = new RenderGraphNativePassBuilder(_objectPool, _resources);
_compiler = new RenderGraphCompiler(_resourceDatabase, _resourceAllocator, _resources, _aliasingManager, _nativePassBuilder, _compilationCache);
_executor = new RenderGraphExecutor(_resourceManager, _resourceDatabase, _resources, _context);
_compiler = new RenderGraphCompiler(renderSystem.GraphicsEngine.ResourceDatabase, renderSystem.GraphicsEngine.ResourceAllocator, _resources, _aliasingManager, _nativePassBuilder, _compilationCache);
_executor = new RenderGraphExecutor(renderSystem.ResourceManager, renderSystem.GraphicsEngine.ResourceDatabase, _resources, _context);
_blackboard = new RenderGraphBlackboard();
}
public RenderGraph(ResourceManager resourceManager, IGraphicsEngine graphicsEngine)
: this(resourceManager, graphicsEngine.ResourceAllocator, graphicsEngine.ResourceDatabase, graphicsEngine.PipelineLibrary, graphicsEngine.ShaderCompiler)
{
}
/// <summary>
/// Resets the render graph for a new frame.
/// </summary>

View File

@@ -1,6 +1,7 @@
using Ghost.Core;
using Ghost.Graphics.Core;
using Ghost.Graphics.RHI;
using Ghost.Graphics.Services;
using Misaki.HighPerformance.Mathematics;
namespace Ghost.Graphics.RenderGraphModule;
@@ -47,9 +48,9 @@ public interface IUnsafeRenderContext : IRasterRenderContext, IComputeRenderCont
internal sealed class RenderGraphContext : IUnsafeRenderContext
{
private readonly ResourceManager _resourceManager;
private readonly ShaderLibrary _shaderLibrary;
private readonly IResourceDatabase _resourceDatabase;
private readonly IPipelineLibrary _pipelineLibrary;
private readonly IShaderCompiler _shaderCompiler;
private readonly RenderGraphResourceRegistry _resources;
private ICommandBuffer _commandBuffer;
@@ -77,12 +78,12 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
get; set;
}
internal RenderGraphContext(ResourceManager resourceManager, IResourceDatabase resourceDatabase, IPipelineLibrary pipelineLibrary, IShaderCompiler shaderCompiler, RenderGraphResourceRegistry resources)
internal RenderGraphContext(ResourceManager resourceManager, ShaderLibrary shaderLibrary, IResourceDatabase resourceDatabase, IPipelineLibrary pipelineLibrary, RenderGraphResourceRegistry resources)
{
_resourceManager = resourceManager;
_shaderLibrary = shaderLibrary;
_resourceDatabase = resourceDatabase;
_pipelineLibrary = pipelineLibrary;
_shaderCompiler = shaderCompiler;
_resources = resources;
_commandBuffer = null!;
@@ -171,7 +172,7 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
if (!_pipelineLibrary.HasPipelineStateObject(pipelineKey))
{
var compiledCacheResult = _shaderCompiler.GetCompiledCache(shaderVariantKey);
var compiledCacheResult = _shaderLibrary.GetCache(shaderVariantKey);
if (compiledCacheResult.IsFailure)
{
throw new InvalidOperationException("Failed to load compiled shader cache for pipeline state object creation.");

View File

@@ -1,8 +1,7 @@
using Ghost.Core;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.Mathematics;
using Ghost.Graphics.Services;
using System.Diagnostics;
using TerraFX.Interop.Windows;
namespace Ghost.Graphics.RenderGraphModule;

View File

@@ -30,6 +30,16 @@ internal readonly struct RenderSystemDesc
{
get; init;
}
public required string ShaderCacheDirectory
{
get; init;
}
public IShaderCompilationBridge? ShaderCompilationBridge
{
get; init;
}
}
/// <summary>
@@ -79,6 +89,7 @@ public class RenderSystem : IDisposable
private readonly IGraphicsEngine _graphicsEngine;
private readonly ResourceManager _resourceManager;
private readonly SwapChainManager _swapChainManager;
private readonly ShaderLibrary _shaderLibrary;
private readonly FrameResource[] _frameResources;
private readonly Thread _renderThread;
@@ -95,10 +106,12 @@ public class RenderSystem : IDisposable
private bool _isRunning;
private bool _disposed;
internal SwapChainManager SwapChainManager => _swapChainManager;
internal ShaderLibrary ShaderLibrary => _shaderLibrary;
public IGraphicsEngine GraphicsEngine => _graphicsEngine;
public ResourceManager ResourceManager => _resourceManager;
public SwapChainManager SwapChainManager => _swapChainManager;
public bool IsRunning => _isRunning;
public ulong CPUFenceValue => _cpuFenceValue;
@@ -120,7 +133,7 @@ public class RenderSystem : IDisposable
}
_renderPipeline?.Dispose();
for (int i = 0; i < _frameResources.Length; i++)
for (var i = 0; i < _frameResources.Length; i++)
{
_frameResources[i].RenderPayload?.Dispose();
}
@@ -165,6 +178,7 @@ public class RenderSystem : IDisposable
_resourceManager = new ResourceManager(_graphicsEngine.Device, _graphicsEngine.ResourceAllocator, _graphicsEngine.ResourceDatabase);
_swapChainManager = new SwapChainManager(_graphicsEngine);
_shaderLibrary = new ShaderLibrary(desc.ShaderCompilationBridge, desc.ShaderCacheDirectory);
// Create frame resources for synchronization
_frameResources = new FrameResource[desc.FrameBufferCount];

View File

@@ -4,7 +4,7 @@ using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Diagnostics;
namespace Ghost.Graphics;
namespace Ghost.Graphics.Services;
public partial class ResourceManager
{

View File

@@ -6,7 +6,7 @@ using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Diagnostics;
namespace Ghost.Graphics;
namespace Ghost.Graphics.Services;
public sealed partial class ResourceManager : IDisposable
{
@@ -182,7 +182,7 @@ public sealed partial class ResourceManager : IDisposable
/// </summary>
/// <returns>An <see cref="Handle{Shader}"/> representing the newly created shader.</returns>
/// <param name="descriptor">The viewGroup containing the shader's properties and passes.</param>
public Handle<Shader> CreateGraphicsShader(GraphicsShaderDescriptor descriptor, ReadOnlySpan<GraphicsCompiledResult> compiledResults)
public Handle<Shader> CreateGraphicsShader(GraphicsShaderDescriptor descriptor)
{
Debug.Assert(!_disposed);
@@ -194,7 +194,7 @@ public sealed partial class ResourceManager : IDisposable
try
{
var shader = new Shader(descriptor, compiledResults);
var shader = new Shader(descriptor);
var id = _shaders.Add(shader, out var generation);
return new Handle<Shader>(id, generation);
@@ -205,7 +205,7 @@ public sealed partial class ResourceManager : IDisposable
}
}
public Handle<ComputeShader> CreateComputeShader(ComputeShaderDescriptor descriptor, ReadOnlySpan<ShaderCompileResult> compiledResults)
public Handle<ComputeShader> CreateComputeShader(ComputeShaderDescriptor descriptor)
{
Debug.Assert(!_disposed);
var spinner = new SpinWait();
@@ -216,7 +216,7 @@ public sealed partial class ResourceManager : IDisposable
try
{
var computeShader = new ComputeShader(descriptor, compiledResults);
var computeShader = new ComputeShader(descriptor);
var id = _computeShaders.Add(computeShader, out var generation);
return new Handle<ComputeShader>(id, generation);
}

View File

@@ -1,5 +1,6 @@
using Ghost.Core;
using Ghost.Graphics.RHI;
using Ghost.Graphics.Services;
namespace Ghost.Graphics.Services;
@@ -44,4 +45,4 @@ public class ResourceUploadBatch
{
return _device.CopyQueue.WaitAsync();
}
}
}

View File

@@ -1,11 +1,105 @@
using Ghost.Core;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.CompilerServices;
namespace Ghost.Graphics.Services;
public class ShaderLibrary : IDisposable
internal class ShaderLibrary : IDisposable
{
private struct CacheEntry: IDisposable
{
public UnsafeArray<UnsafeArray<byte>> byteCode;
public void Insert(int index, ReadOnlySpan<byte> data)
{
if (index >= byteCode.Length)
{
var newByteCode = new UnsafeArray<UnsafeArray<byte>>(index + 1, Allocator.Persistent);
for (int i = 0; i < byteCode.Length; i++)
{
newByteCode[i] = byteCode[i];
}
byteCode.Dispose();
byteCode = newByteCode;
}
var byteData = new UnsafeArray<byte>(data.Length, Allocator.Persistent);
byteData.CopyFrom(data);
byteCode[index] = byteData;
}
public readonly void Dispose()
{
for (int i = 0; i < byteCode.Length; i++)
{
byteCode[i].Dispose();
}
}
}
private UnsafeHashMap<ulong, CacheEntry> _inMemoryCache;
private readonly string _cacheDirectory;
private readonly IShaderCompilationBridge? _shaderCompilationBridge;
internal ShaderLibrary(IShaderCompilationBridge? shaderCompilationBridge, string cacheDirectory)
{
_inMemoryCache = new UnsafeHashMap<ulong, CacheEntry>(16, Allocator.Persistent);
_cacheDirectory = cacheDirectory;
_shaderCompilationBridge = shaderCompilationBridge;
}
private string GetShaderCacheFilePath(ulong hash)
{
var hashString = hash.ToString("X16"); // Convert to hexadecimal string
var folderName = hashString[..2]; // Use the first two characters as the folder name
var folderPath = Path.Combine(_cacheDirectory, folderName);
if (!Directory.Exists(folderPath))
{
Directory.CreateDirectory(folderPath);
}
return Path.Combine(folderPath, $"shader_cache_{hashString}.bin");
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void CacheCompiledResult(ulong id, int index, ReadOnlySpan<byte> byteCode)
{
var data = new UnsafeArray<byte>(byteCode.Length, Allocator.Persistent);
data.CopyFrom(byteCode);
ref var entry = ref _inMemoryCache.GetValueRefOrAddDefault(id, out var exists);
entry.Insert(index, byteCode);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Result<UnsafeArray<byte>, Error> GetCache(ulong id, int index, AllocationHandle allocationHandle)
{
if (_inMemoryCache.TryGetValue(id, out var entry))
{
if (index < entry.byteCode.Length)
{
var byteCode = entry.byteCode[index];
var result = new UnsafeArray<byte>(byteCode.Length, allocationHandle);
result.CopyFrom(byteCode);
return result;
}
}
return Error.NotFound;
}
public void Dispose()
{
throw new NotImplementedException();
foreach (var kvp in _inMemoryCache)
{
kvp.Value.Dispose();
}
GC.SuppressFinalize(this);
}
}

View File

@@ -50,7 +50,7 @@ internal sealed class SwapChainRecord
}
}
internal class SwapChainManager : IDisposable
public class SwapChainManager : IDisposable
{
public const int MAX_SWAP_CHAINS = 8;
private readonly IGraphicsEngine _graphicsEngine;

View File

@@ -2,6 +2,7 @@ using Ghost.Core;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Diagnostics;
using Ghost.Graphics.Services;
namespace Ghost.Graphics.Utilities;