using System.Collections.Concurrent;
namespace Ghost.Shader.Concept;
///
/// High-performance material batch system for rendering.
/// Groups materials by shader variant and pass for efficient draw call submission.
/// Uses lock-free data structures where possible.
///
public sealed class MaterialBatchRenderer
{
private readonly IShaderCompiler _shaderCompiler;
private readonly IPipelineLibrary _pipelineLibrary;
private readonly ConcurrentDictionary _compiledVariants = new();
private readonly ConcurrentDictionary _cachedPipelines = new();
public MaterialBatchRenderer(IShaderCompiler shaderCompiler, IPipelineLibrary pipelineLibrary)
{
_shaderCompiler = shaderCompiler;
_pipelineLibrary = pipelineLibrary;
}
///
/// Batches draw calls by material and pass.
/// Returns sorted batches ready for submission.
///
public MaterialBatch[] BatchDrawCalls(ReadOnlySpan drawCalls)
{
var globalKeywords = GlobalKeywordState.Instance.GetKeywordSet();
var batchMap = new Dictionary>();
// Group by pipeline key
foreach (var drawCall in drawCalls)
{
var material = drawCall.Material;
var passIndex = drawCall.PassIndex;
var pipelineKey = material.GetPipelineKey(passIndex, globalKeywords);
if (!batchMap.TryGetValue(pipelineKey, out var batch))
{
batch = new List();
batchMap[pipelineKey] = batch;
}
batch.Add(drawCall);
}
// Convert to array and ensure PSOs are ready
var batches = new MaterialBatch[batchMap.Count];
int index = 0;
foreach (var kvp in batchMap)
{
var pipelineKey = kvp.Key;
var drawCommands = kvp.Value;
// Ensure shader variant is compiled
EnsureVariantCompiled(pipelineKey.VariantKey, drawCommands[0].Material);
// Get or create PSO
var pso = GetOrCreatePipeline(pipelineKey);
batches[index++] = new MaterialBatch
{
PipelineKey = pipelineKey,
Pipeline = pso,
DrawCommands = drawCommands.ToArray()
};
}
// Sort batches for optimal state changes (PSO switches are expensive)
Array.Sort(batches, (a, b) => a.PipelineKey.GetHashCode().CompareTo(b.PipelineKey.GetHashCode()));
return batches;
}
private unsafe void EnsureVariantCompiled(ShaderVariantKey variantKey, Material material)
{
if (_compiledVariants.ContainsKey(variantKey))
return;
var global = GlobalKeywordState.Instance.GetKeywordSet();
var local = material.GetLocalKeywords();
KeywordSet keywords = KeywordSet.Merge(&global, &local);
var compiledShader = _shaderCompiler.CompileVariant(variantKey, keywords);
_compiledVariants.TryAdd(variantKey, compiledShader);
}
private IntPtr GetOrCreatePipeline(GraphicsPipelineKey pipelineKey)
{
if (_cachedPipelines.TryGetValue(pipelineKey, out var cached))
return cached;
var pso = _pipelineLibrary.GetOrCreatePipeline(pipelineKey);
_cachedPipelines.TryAdd(pipelineKey, pso);
return pso;
}
///
/// Clears compiled shader and pipeline caches.
/// Call when shaders are reloaded or modified.
///
public void ClearCache()
{
_compiledVariants.Clear();
_cachedPipelines.Clear();
}
///
/// Pre-warms the cache by compiling common variants.
/// Can be called asynchronously during loading.
///
public async Task WarmupVariantsAsync(ShaderProgram shader, KeywordSet[] variantConfigurations)
{
var tasks = new List();
foreach (var keywords in variantConfigurations)
{
var variantKey = shader.CreateVariantKey(keywords);
if (!_compiledVariants.ContainsKey(variantKey))
{
tasks.Add(Task.Run(() =>
{
var compiled = _shaderCompiler.CompileVariant(variantKey, keywords);
_compiledVariants.TryAdd(variantKey, compiled);
}));
}
}
await Task.WhenAll(tasks);
}
}
///
/// Represents a single draw command with material and instance data.
///
public struct DrawCommand
{
public Material Material;
public int PassIndex;
public IntPtr VertexBuffer;
public IntPtr IndexBuffer;
public int IndexCount;
public int InstanceCount;
public IntPtr InstanceData;
}
///
/// A batch of draw commands sharing the same PSO.
///
public struct MaterialBatch
{
public GraphicsPipelineKey PipelineKey;
public IntPtr Pipeline;
public DrawCommand[] DrawCommands;
}