Introduces a new Ghost.Shader.Concept project implementing a modern, data-oriented material and shader system with: - Global/local keyword bitsets (fast O(1) ops, 64 bytes) - Multi-pass shader program and per-pass render state overrides - Thread-safe, 16-byte aligned material property blocks - Material pooling to reduce GC pressure - Batch renderer for efficient PSO grouping and async variant warmup - Full demo (Program.cs) and extensive documentation (ARCHITECTURE.md, README.md, PROJECT_SUMMARY.md) - Minor integration: new enums, doc updates, and keyword handling in existing code No breaking changes to the existing engine; all new code is isolated. This serves as a reference implementation for high-performance, extensible material/shader architectures.
259 lines
11 KiB
C#
259 lines
11 KiB
C#
using System.Diagnostics;
|
|
|
|
namespace Ghost.Shader.Concept;
|
|
|
|
/// <summary>
|
|
/// Mock implementations for demonstration
|
|
/// </summary>
|
|
internal class MockShaderCompiler : IShaderCompiler
|
|
{
|
|
public IntPtr CompileVariant(ShaderVariantKey key, in KeywordSet keywords)
|
|
{
|
|
// Simulate compilation delay
|
|
Thread.Sleep(10);
|
|
return new IntPtr(key.GetHashCode());
|
|
}
|
|
}
|
|
|
|
internal class MockPipelineLibrary : IPipelineLibrary
|
|
{
|
|
public IntPtr GetOrCreatePipeline(in GraphicsPipelineKey key)
|
|
{
|
|
// Simulate PSO creation
|
|
Thread.Sleep(5);
|
|
return new IntPtr(key.GetHashCode());
|
|
}
|
|
}
|
|
|
|
class Program
|
|
{
|
|
static void Main(string[] args)
|
|
{
|
|
Console.WriteLine("=== Ghost Shader Concept - High Performance Material System ===\n");
|
|
|
|
// Initialize system
|
|
var registry = ShaderKeywordRegistry.Instance;
|
|
var compiler = new MockShaderCompiler();
|
|
var pipelineLib = new MockPipelineLibrary();
|
|
var batchRenderer = new MaterialBatchRenderer(compiler, pipelineLib);
|
|
var materialPool = new MaterialPool();
|
|
|
|
Console.WriteLine("1. Creating Keywords...");
|
|
var globalHDR = registry.GetOrRegister("HDR", KeywordScope.Global);
|
|
var globalShadows = registry.GetOrRegister("SHADOWS_ENABLED", KeywordScope.Global);
|
|
var localAlphaTest = registry.GetOrRegister("ALPHA_TEST", KeywordScope.Local);
|
|
var localNormalMap = registry.GetOrRegister("NORMAL_MAP", KeywordScope.Local);
|
|
var localMetallic = registry.GetOrRegister("METALLIC_WORKFLOW", KeywordScope.Local);
|
|
|
|
// Set global keywords
|
|
GlobalKeywordState.Instance.EnableKeyword(globalHDR);
|
|
GlobalKeywordState.Instance.EnableKeyword(globalShadows);
|
|
Console.WriteLine($" - Global Keywords: HDR, SHADOWS_ENABLED");
|
|
Console.WriteLine($" - Local Keywords: ALPHA_TEST, NORMAL_MAP, METALLIC_WORKFLOW\n");
|
|
|
|
// Create shader program with multiple passes
|
|
Console.WriteLine("2. Creating Shader Program with Multi-Pass...");
|
|
var shaderProgram = new ShaderProgramBuilder()
|
|
.WithName("StandardPBR")
|
|
.AddPass("ForwardBase", RenderState.Default)
|
|
.AddPass("ShadowCaster", new RenderState
|
|
{
|
|
CullMode = CullMode.Back,
|
|
FillMode = FillMode.Solid,
|
|
DepthTestEnable = true,
|
|
DepthWriteEnable = true,
|
|
DepthCompareFunc = CompareFunction.LessEqual,
|
|
ColorWriteMask = ColorWriteMask.None,
|
|
Topology = PrimitiveTopology.TriangleList
|
|
})
|
|
.AddPass("DepthPrepass", new RenderState
|
|
{
|
|
CullMode = CullMode.Back,
|
|
DepthTestEnable = true,
|
|
DepthWriteEnable = true,
|
|
ColorWriteMask = ColorWriteMask.None,
|
|
Topology = PrimitiveTopology.TriangleList
|
|
})
|
|
.DeclareKeywords(localAlphaTest, localNormalMap, localMetallic)
|
|
.Build();
|
|
|
|
Console.WriteLine($" - Shader: {shaderProgram.Name} (ID: {shaderProgram.Id})");
|
|
Console.WriteLine($" - Passes: {shaderProgram.Passes.Length}");
|
|
foreach (var pass in shaderProgram.Passes)
|
|
{
|
|
Console.WriteLine($" * {pass.Name} (ID: {pass.PassId})");
|
|
}
|
|
Console.WriteLine();
|
|
|
|
// Create materials with different configurations
|
|
Console.WriteLine("3. Creating Material Instances...");
|
|
var materials = new List<Material>();
|
|
|
|
// Material 1: Basic opaque
|
|
var mat1 = new Material(shaderProgram);
|
|
mat1.SetVector4("_Color", 1.0f, 0.0f, 0.0f, 1.0f);
|
|
mat1.SetFloat("_Metallic", 0.5f);
|
|
mat1.SetFloat("_Roughness", 0.3f);
|
|
mat1.EnableKeyword(localMetallic);
|
|
materials.Add(mat1);
|
|
Console.WriteLine($" - Material 1: Red Metallic (Keywords: METALLIC_WORKFLOW)");
|
|
|
|
// Material 2: Alpha tested
|
|
var mat2 = new Material(shaderProgram);
|
|
mat2.SetVector4("_Color", 0.0f, 1.0f, 0.0f, 0.5f);
|
|
mat2.SetFloat("_Cutoff", 0.5f);
|
|
mat2.EnableKeyword(localAlphaTest);
|
|
materials.Add(mat2);
|
|
Console.WriteLine($" - Material 2: Green Alpha Test (Keywords: ALPHA_TEST)");
|
|
|
|
// Material 3: Full featured
|
|
var mat3 = new Material(shaderProgram);
|
|
mat3.SetVector4("_Color", 0.0f, 0.0f, 1.0f, 1.0f);
|
|
mat3.SetFloat("_Metallic", 1.0f);
|
|
mat3.SetFloat("_Roughness", 0.1f);
|
|
mat3.EnableKeyword(localMetallic);
|
|
mat3.EnableKeyword(localNormalMap);
|
|
materials.Add(mat3);
|
|
Console.WriteLine($" - Material 3: Blue Metallic + Normal Map (Keywords: METALLIC_WORKFLOW, NORMAL_MAP)");
|
|
|
|
// Material 4: Override blend state for transparent pass
|
|
var mat4 = new Material(shaderProgram);
|
|
mat4.SetVector4("_Color", 1.0f, 1.0f, 0.0f, 0.7f);
|
|
var transparentState = RenderState.Default;
|
|
transparentState.BlendEnable = true;
|
|
transparentState.SrcBlend = BlendFactor.SrcAlpha;
|
|
transparentState.DestBlend = BlendFactor.InvSrcAlpha;
|
|
mat4.SetPassRenderState(0, transparentState);
|
|
materials.Add(mat4);
|
|
Console.WriteLine($" - Material 4: Yellow Transparent (Per-pass blend override)\n");
|
|
|
|
// Demonstrate material property updates
|
|
Console.WriteLine("4. Testing Material Property Updates...");
|
|
var sw = Stopwatch.StartNew();
|
|
for (int i = 0; i < 10000; i++)
|
|
{
|
|
mat1.SetFloat("_Metallic", (float)Math.Sin(i * 0.01));
|
|
mat1.SetVector4("_Color", 1.0f, 0.5f, 0.5f, 1.0f);
|
|
}
|
|
sw.Stop();
|
|
Console.WriteLine($" - 10,000 property updates: {sw.ElapsedMilliseconds}ms ({10000.0 / sw.ElapsedMilliseconds:F2} updates/ms)\n");
|
|
|
|
// Demonstrate keyword toggling
|
|
Console.WriteLine("5. Testing Keyword Toggle Performance...");
|
|
sw.Restart();
|
|
for (int i = 0; i < 10000; i++)
|
|
{
|
|
if (i % 2 == 0)
|
|
mat3.EnableKeyword(localAlphaTest);
|
|
else
|
|
mat3.DisableKeyword(localAlphaTest);
|
|
}
|
|
sw.Stop();
|
|
Console.WriteLine($" - 10,000 keyword toggles: {sw.ElapsedMilliseconds}ms ({10000.0 / sw.ElapsedMilliseconds:F2} toggles/ms)\n");
|
|
|
|
// Create draw commands
|
|
Console.WriteLine("6. Creating Draw Commands...");
|
|
var drawCommands = new List<DrawCommand>();
|
|
var random = new Random(42);
|
|
|
|
for (int i = 0; i < 1000; i++)
|
|
{
|
|
drawCommands.Add(new DrawCommand
|
|
{
|
|
Material = materials[random.Next(materials.Count)],
|
|
PassIndex = random.Next(3), // Random pass
|
|
VertexBuffer = new IntPtr(i * 1000),
|
|
IndexBuffer = new IntPtr(i * 1000 + 500),
|
|
IndexCount = 36,
|
|
InstanceCount = 1,
|
|
InstanceData = IntPtr.Zero
|
|
});
|
|
}
|
|
Console.WriteLine($" - Created {drawCommands.Count} draw commands\n");
|
|
|
|
// Batch rendering
|
|
Console.WriteLine("7. Batching Draw Calls...");
|
|
sw.Restart();
|
|
var batches = batchRenderer.BatchDrawCalls(drawCommands.ToArray());
|
|
sw.Stop();
|
|
Console.WriteLine($" - Batched into {batches.Length} unique PSO states");
|
|
Console.WriteLine($" - Batching time: {sw.ElapsedMilliseconds}ms");
|
|
Console.WriteLine($" - Average batch size: {drawCommands.Count / (float)batches.Length:F2} draws/batch\n");
|
|
|
|
// Show batch details
|
|
Console.WriteLine("8. Batch Details:");
|
|
int batchNum = 1;
|
|
foreach (var batch in batches.Take(5))
|
|
{
|
|
Console.WriteLine($" Batch {batchNum++}:");
|
|
Console.WriteLine($" - PSO Hash: 0x{batch.PipelineKey.GetHashCode():X8}");
|
|
Console.WriteLine($" - Draw calls: {batch.DrawCommands.Length}");
|
|
Console.WriteLine($" - Shader variant: 0x{batch.PipelineKey.VariantKey.KeywordHash:X16}");
|
|
}
|
|
if (batches.Length > 5)
|
|
Console.WriteLine($" ... and {batches.Length - 5} more batches\n");
|
|
|
|
// Demonstrate variant warmup
|
|
Console.WriteLine("9. Shader Variant Warmup (Async)...");
|
|
var warmupConfigs = new KeywordSet[8];
|
|
for (int i = 0; i < warmupConfigs.Length; i++)
|
|
{
|
|
warmupConfigs[i] = new KeywordSet();
|
|
if ((i & 1) != 0) warmupConfigs[i].Enable(localAlphaTest);
|
|
if ((i & 2) != 0) warmupConfigs[i].Enable(localNormalMap);
|
|
if ((i & 4) != 0) warmupConfigs[i].Enable(localMetallic);
|
|
}
|
|
|
|
sw.Restart();
|
|
var warmupTask = batchRenderer.WarmupVariantsAsync(shaderProgram, warmupConfigs);
|
|
warmupTask.Wait();
|
|
sw.Stop();
|
|
Console.WriteLine($" - Pre-compiled {warmupConfigs.Length} variants in {sw.ElapsedMilliseconds}ms\n");
|
|
|
|
// Material cloning
|
|
Console.WriteLine("10. Material Cloning...");
|
|
var clonedMat = mat3.Clone();
|
|
Console.WriteLine($" - Cloned material 3");
|
|
Console.WriteLine($" - Original metallic: {(mat3.TryGetFloat("_Metallic", out var m1) ? m1 : 0)}");
|
|
Console.WriteLine($" - Clone metallic: {(clonedMat.TryGetFloat("_Metallic", out var m2) ? m2 : 0)}");
|
|
clonedMat.SetFloat("_Metallic", 0.0f);
|
|
Console.WriteLine($" - After clone modification:");
|
|
Console.WriteLine($" Original: {(mat3.TryGetFloat("_Metallic", out var m3) ? m3 : 0)}");
|
|
Console.WriteLine($" Clone: {(clonedMat.TryGetFloat("_Metallic", out var m4) ? m4 : 0)}\n");
|
|
|
|
// Material pooling
|
|
Console.WriteLine("11. Material Pooling...");
|
|
sw.Restart();
|
|
for (int i = 0; i < 1000; i++)
|
|
{
|
|
var pooledMat = materialPool.Rent(shaderProgram);
|
|
pooledMat.SetFloat("_Test", i);
|
|
materialPool.Return(pooledMat);
|
|
}
|
|
sw.Stop();
|
|
Console.WriteLine($" - 1000 rent/return cycles: {sw.ElapsedMilliseconds}ms\n");
|
|
|
|
// Cleanup
|
|
Console.WriteLine("12. Cleanup...");
|
|
foreach (var mat in materials)
|
|
{
|
|
mat.Dispose();
|
|
}
|
|
clonedMat.Dispose();
|
|
materialPool.Clear();
|
|
Console.WriteLine(" - All materials disposed\n");
|
|
|
|
Console.WriteLine("=== Demo Complete ===");
|
|
Console.WriteLine("\nKey Features Demonstrated:");
|
|
Console.WriteLine(" ✓ Multi-pass shader support");
|
|
Console.WriteLine(" ✓ Global and local keyword system");
|
|
Console.WriteLine(" ✓ Shader variant compilation");
|
|
Console.WriteLine(" ✓ Per-pass pipeline state overrides");
|
|
Console.WriteLine(" ✓ Fast material property updates");
|
|
Console.WriteLine(" ✓ Efficient draw call batching");
|
|
Console.WriteLine(" ✓ Async variant warmup");
|
|
Console.WriteLine(" ✓ Material cloning and pooling");
|
|
Console.WriteLine(" ✓ Cache-friendly data structures");
|
|
}
|
|
}
|