feat(render): add ECS-based test render pipeline
Introduce TestRenderPipeline and settings, replacing MeshRenderPass. The new pipeline manages per-frame instance, view, and global data buffers, and uploads them for each render request. Refactor GraphicsTestWindow to use ECS World, setting up camera and mesh entities. Remove MeshRenderPass and related demo code. Add TotalRecordCount to RenderList, new data structs for buffer uploads, and static masks to RenderingLayerMask. Update project references and InternalsVisibleTo for Ghost.Graphics.Test access.
This commit is contained in:
@@ -51,6 +51,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Runtime\Ghost.Engine\Ghost.Engine.csproj" />
|
||||
<ProjectReference Include="..\..\Test\Ghost.Test.Core\Ghost.Test.Core.csproj" />
|
||||
<ProjectReference Include="..\..\Runtime\Ghost.Graphics\Ghost.Graphics.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
|
||||
@@ -1,337 +0,0 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Core.Graphics;
|
||||
using Ghost.DSL.ShaderCompiler;
|
||||
using Ghost.Graphics.Core;
|
||||
using Ghost.Graphics.Core.Contracts;
|
||||
using Ghost.Graphics.RenderGraphModule;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Ghost.Graphics.Utilities;
|
||||
using Misaki.HighPerformance.Image;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
using Misaki.HighPerformance.Utilities;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ghost.Graphics.RenderPasses;
|
||||
|
||||
internal class MeshRenderPassData
|
||||
{
|
||||
public Handle<Mesh> mesh;
|
||||
public Handle<Material> material;
|
||||
public Identifier<RGTexture> renderTarget;
|
||||
}
|
||||
|
||||
internal class BlitPassData
|
||||
{
|
||||
public Identifier<RGTexture> source;
|
||||
public Identifier<RGTexture> destination;
|
||||
|
||||
public Handle<Material> blitMaterial;
|
||||
public Identifier<Sampler> sampler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simplified bindless mesh render pass using high-level bindless APIs with fully bindless vertex/index buffer access
|
||||
/// </summary>
|
||||
internal class MeshRenderPass : IRenderPass
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct ShaderProperties_MyShader_Standard
|
||||
{
|
||||
public float4 color;
|
||||
public uint texture1;
|
||||
public uint texture2;
|
||||
public uint texture3;
|
||||
public uint texture4;
|
||||
public uint tex_sampler;
|
||||
|
||||
private readonly uint _padding1;
|
||||
private readonly uint _padding2;
|
||||
private readonly uint _padding3;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct ShaderProperties_Hidden_Blit
|
||||
{
|
||||
public uint mainTex;
|
||||
public uint sampler_mainTex;
|
||||
private readonly uint _padding1;
|
||||
private readonly uint _padding2;
|
||||
}
|
||||
|
||||
private Handle<Mesh> _mesh;
|
||||
private Identifier<Shader> _shader;
|
||||
private Handle<Material> _material;
|
||||
private Handle<Texture>[]? _textures;
|
||||
private Identifier<Sampler> _sampler;
|
||||
|
||||
private Identifier<Shader> _blitShader;
|
||||
private Handle<Material> _blitMaterial;
|
||||
|
||||
// Texture file paths for this demo
|
||||
private readonly string[] _textureFiles = [
|
||||
"C:/Users/Misaki/Downloads/Im/Icon.png",
|
||||
"C:/Users/Misaki/Downloads/Im/Backdrop.jpg",
|
||||
"C:/Users/Misaki/Downloads/Im/101167591_p0.png",
|
||||
"C:/Users/Misaki/Downloads/Im/yande.re 1134666 blue_archive nakamasa_ichika sugarhigh.jpg"
|
||||
];
|
||||
|
||||
private static IEnumerable<ReadOnlyMemory<string>> GetAllVariantCombination(KeywordsGroup[] keywordsGroups)
|
||||
{
|
||||
if (keywordsGroups.Length == 0)
|
||||
{
|
||||
yield return ReadOnlyMemory<string>.Empty;
|
||||
yield break;
|
||||
}
|
||||
|
||||
var firstGroup = keywordsGroups[0];
|
||||
var remainingGroups = keywordsGroups[1..];
|
||||
|
||||
foreach (var combination in GetAllVariantCombination(remainingGroups))
|
||||
{
|
||||
yield return combination;
|
||||
}
|
||||
|
||||
foreach (var keyword in firstGroup.keywords)
|
||||
{
|
||||
foreach (var combination in GetAllVariantCombination(remainingGroups))
|
||||
{
|
||||
var array = new string[combination.Length + 1];
|
||||
array[0] = keyword;
|
||||
combination.Span.CopyTo(array.AsSpan(1));
|
||||
|
||||
yield return array;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CompileBlitShader(ref readonly RenderingContext ctx)
|
||||
{
|
||||
var shaderDescriptor = DSLShaderCompiler.CompileShader("F:/csharp/GhostEngine/src/Runtime/Ghost.Graphics/Shaders/Blit.gshdr", "C:/Users/Misaki/Downloads/Archive").GetValueOrThrow();
|
||||
_blitShader = ctx.ResourceManager.CreateGraphicsShader(shaderDescriptor);
|
||||
_blitMaterial = ctx.ResourceManager.CreateMaterial(_blitShader);
|
||||
|
||||
var config = new ShaderCompilationConfig
|
||||
{
|
||||
optimizeLevel = CompilerOptimizeLevel.O3,
|
||||
options = CompilerOption.KeepReflections,
|
||||
tier = CompilerTier.Tier2
|
||||
};
|
||||
|
||||
var pass = shaderDescriptor.passes[0];
|
||||
var emptyKeywords = new LocalKeywordSet();
|
||||
var variantKey = RHIUtility.CreateShaderVariantKey(
|
||||
RHIUtility.CreateShaderPassKey(pass.identifier),
|
||||
in emptyKeywords);
|
||||
|
||||
ctx.ShaderCompiler.CompilePass(in pass, in config, variantKey).GetValueOrThrow();
|
||||
}
|
||||
|
||||
public void Initialize(ref readonly RenderingContext ctx)
|
||||
{
|
||||
CompileBlitShader(in ctx);
|
||||
|
||||
var shaderDescriptor = DSLShaderCompiler.CompileShader("F:/csharp/GhostEngine/src/Runtime/Ghost.Graphics/test.gshdr", "C:/Users/Misaki/Downloads/Archive").GetValueOrThrow();
|
||||
|
||||
_shader = ctx.ResourceManager.CreateGraphicsShader(shaderDescriptor);
|
||||
_material = ctx.ResourceManager.CreateMaterial(_shader);
|
||||
|
||||
for (var i = 0; i < shaderDescriptor.passes.Length; i++)
|
||||
{
|
||||
ref var pass = ref shaderDescriptor.passes[i];
|
||||
var config = new ShaderCompilationConfig
|
||||
{
|
||||
optimizeLevel = CompilerOptimizeLevel.O3,
|
||||
options = CompilerOption.KeepReflections,
|
||||
tier = CompilerTier.Tier2
|
||||
};
|
||||
|
||||
// TODO: Ideally, in editor mode, we compile a single variant when it's needed during rendering. Before the compilation is done, we fallback to a special "compilation in progress" shader.
|
||||
// During the build process, we can precompile all the variants and store them in the cache for fast loading in runtime.
|
||||
// After the compilation, we should store the compiled result in the disk cache even in editor mode. This allows us to avoid recompiling the same variant, same code hash and same version) multiple times.
|
||||
if (pass.keywords.Length == 0)
|
||||
{
|
||||
var emptyKeywords = new LocalKeywordSet();
|
||||
var variantKey = RHIUtility.CreateShaderVariantKey(
|
||||
RHIUtility.CreateShaderPassKey(pass.identifier),
|
||||
in emptyKeywords);
|
||||
|
||||
ctx.ShaderCompiler.CompilePass(in pass, in config, variantKey).GetValueOrThrow();
|
||||
}
|
||||
else
|
||||
{
|
||||
var shaderResult = ctx.ResourceManager.GetShaderReference(_shader);
|
||||
if (shaderResult.IsFailure)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to get shader reference.");
|
||||
}
|
||||
|
||||
ref readonly var shaderRef = ref shaderResult.Value;
|
||||
foreach (var keyGroup in GetAllVariantCombination(pass.keywords))
|
||||
{
|
||||
config.defines = keyGroup.Span;
|
||||
var keywordsSet = new LocalKeywordSet();
|
||||
|
||||
foreach (var key in keyGroup.Span)
|
||||
{
|
||||
var localIndex = shaderRef.GetLocalKeywordIndex(Shader.GetKeywordID(key));
|
||||
if (localIndex == -1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
keywordsSet.SetKeyword(localIndex, true);
|
||||
}
|
||||
|
||||
var variantKey = RHIUtility.CreateShaderVariantKey(
|
||||
RHIUtility.CreateShaderPassKey(pass.identifier),
|
||||
in keywordsSet);
|
||||
|
||||
ctx.ShaderCompiler.CompilePass(in pass, in config, variantKey).GetValueOrThrow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MeshBuilder.CreateCube(0.75f, default, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent, out var vertices, out var indices);
|
||||
|
||||
_mesh = ctx.CreateMesh(vertices, indices, true);
|
||||
|
||||
// Cook meshlets for the mesh
|
||||
var meshRef = ctx.ResourceManager.GetMeshReference(_mesh);
|
||||
if (meshRef.IsSuccess)
|
||||
{
|
||||
meshRef.Value.CookMeshlets();
|
||||
}
|
||||
|
||||
ctx.UploadMeshlets(_mesh);
|
||||
|
||||
ctx.UpdateObjectData(_mesh, float4x4.identity);
|
||||
|
||||
_textures = new Handle<Texture>[_textureFiles.Length];
|
||||
for (var i = 0; i < _textureFiles.Length; i++)
|
||||
{
|
||||
using var stream = File.OpenRead(_textureFiles[i]);
|
||||
using var imageData = ImageResult.FromStream(stream, ColorComponents.RGBA);
|
||||
var desc = new TextureDesc
|
||||
{
|
||||
Width = imageData.Width,
|
||||
Height = imageData.Height,
|
||||
Dimension = TextureDimension.Texture2D,
|
||||
Format = TextureFormat.R8G8B8A8_UNorm,
|
||||
MipLevels = 1,
|
||||
Slice = 1,
|
||||
Usage = TextureUsage.ShaderResource,
|
||||
};
|
||||
|
||||
_textures[i] = ctx.CreateTexture<byte>(in desc, imageData.AsSpan(), $"Texture_{i}");
|
||||
}
|
||||
|
||||
var samplerDesc = new SamplerDesc
|
||||
{
|
||||
AddressU = TextureAddressMode.Repeat,
|
||||
AddressV = TextureAddressMode.Repeat,
|
||||
AddressW = TextureAddressMode.Repeat,
|
||||
FilterMode = TextureFilterMode.Bilinear,
|
||||
MaxAnisotropy = 16,
|
||||
};
|
||||
|
||||
_sampler = ctx.ResourceAllocator.CreateSampler(in samplerDesc);
|
||||
|
||||
var meshResult = ctx.ResourceManager.GetMaterialReference(_material);
|
||||
if (meshResult.IsFailure)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to get material reference.");
|
||||
}
|
||||
|
||||
ref var matRef = ref meshResult.Value;
|
||||
var matProps = new ShaderProperties_MyShader_Standard
|
||||
{
|
||||
color = new float4(1.0f, 1.0f, 1.0f, 1.0f),
|
||||
texture1 = ctx.ResourceDatabase.GetBindlessIndex(_textures[0].AsResource()),
|
||||
texture2 = ctx.ResourceDatabase.GetBindlessIndex(_textures[1].AsResource()),
|
||||
texture3 = ctx.ResourceDatabase.GetBindlessIndex(_textures[2].AsResource()),
|
||||
texture4 = ctx.ResourceDatabase.GetBindlessIndex(_textures[3].AsResource()),
|
||||
tex_sampler = (uint)_sampler.Value,
|
||||
};
|
||||
|
||||
matRef.SetPropertyCache(in matProps).ThrowIfFailed();
|
||||
matRef.UploadData(ctx.DirectCommandBuffer, ctx.ResourceDatabase);
|
||||
}
|
||||
|
||||
public void Build(RenderGraph graph, Identifier<RGTexture> backbuffer)
|
||||
{
|
||||
Identifier<RGTexture> renderTarget;
|
||||
using (var builder = graph.AddRasterRenderPass<MeshRenderPassData>("Mesh Render Pass", out var passData))
|
||||
{
|
||||
passData.mesh = _mesh;
|
||||
passData.material = _material;
|
||||
|
||||
passData.renderTarget = builder.CreateTexture(RGTextureDesc.Relative(1.0f, TextureFormat.R8G8B8A8_UNorm), "Render Target");
|
||||
builder.SetColorAttachment(passData.renderTarget, 0);
|
||||
|
||||
renderTarget = passData.renderTarget;
|
||||
|
||||
builder.SetRenderFunc<MeshRenderPassData>(static (data, ctx) =>
|
||||
{
|
||||
ctx.SetActiveMaterial(data.material);
|
||||
ctx.SetActiveMesh(data.mesh);
|
||||
|
||||
var threadGroupCountX = ((uint)ctx.ActiveMeshIndexCount + 2u) / 3u;
|
||||
ctx.DispatchMesh(new uint3(threadGroupCountX, 1u, 1u));
|
||||
});
|
||||
}
|
||||
|
||||
using (var builder = graph.AddUnsafeRenderPass<BlitPassData>("Blit Pass", out var passData))
|
||||
{
|
||||
passData.source = renderTarget;
|
||||
passData.destination = backbuffer;
|
||||
passData.blitMaterial = _blitMaterial;
|
||||
passData.sampler = _sampler;
|
||||
|
||||
builder.UseTexture(passData.source, AccessFlags.Read);
|
||||
builder.UseTexture(passData.destination, AccessFlags.WriteAll);
|
||||
|
||||
builder.SetRenderFunc<BlitPassData>(static (data, ctx) =>
|
||||
{
|
||||
var r = ctx.ResourceManager.GetMaterialReference(data.blitMaterial);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ref var matRef = ref r.Value;
|
||||
var blitProps = new ShaderProperties_Hidden_Blit
|
||||
{
|
||||
mainTex = ctx.ResourceDatabase.GetBindlessIndex(ctx.GetActualResource(data.source.AsResource())),
|
||||
sampler_mainTex = (uint)data.sampler.Value,
|
||||
};
|
||||
|
||||
matRef.SetPropertyCache(in blitProps).ThrowIfFailed();
|
||||
matRef.UploadData(ctx.CommandBuffer, ctx.ResourceDatabase);
|
||||
|
||||
ctx.CommandBuffer.SetRenderTargets([ctx.GetActualTexture(data.destination)], Handle<Texture>.Invalid);
|
||||
|
||||
ctx.SetActiveMaterial(data.blitMaterial);
|
||||
ctx.SetActiveMesh(Handle<Mesh>.Invalid); // Generate a full-screen triangle dynamically in mesh shader.
|
||||
ctx.DispatchMesh(new uint3(1, 1, 1));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void Cleanup(ResourceManager resourceManager, IResourceDatabase resourceDatabase)
|
||||
{
|
||||
resourceManager.ReleaseMaterial(_blitMaterial);
|
||||
|
||||
resourceManager.ReleaseMaterial(_material);
|
||||
resourceManager.ReleaseShader(_shader);
|
||||
resourceManager.ReleaseMesh(_mesh);
|
||||
resourceDatabase.ReleaseSampler(_sampler);
|
||||
|
||||
if (_textures != null)
|
||||
{
|
||||
foreach (var texture in _textures)
|
||||
{
|
||||
resourceDatabase.ReleaseResource(texture.AsResource());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
160
src/Test/Ghost.Graphics.Test/RenderPasses/TestRenderPipeline.cs
Normal file
160
src/Test/Ghost.Graphics.Test/RenderPasses/TestRenderPipeline.cs
Normal file
@@ -0,0 +1,160 @@
|
||||
using Ghost.Graphics.Core;
|
||||
using Ghost.Graphics.RenderGraphModule;
|
||||
using Ghost.Graphics.RenderPipeline;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.Graphics.Test.RenderPasses;
|
||||
|
||||
public sealed class TestRenderPipelineSettings : IRenderPipelineSettings
|
||||
{
|
||||
public IRenderPipeline CreatePipeline(RenderSystem renderSystem)
|
||||
{
|
||||
return new TestRenderPipeline(renderSystem);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe partial class TestRenderPipeline : IRenderPipeline
|
||||
{
|
||||
private readonly RenderGraph _renderGraph;
|
||||
private readonly RenderSystem _renderSystem;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
~TestRenderPipeline()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected void ThrowIfDisposed()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
}
|
||||
|
||||
internal TestRenderPipeline(RenderSystem renderSystem)
|
||||
{
|
||||
_renderSystem = renderSystem;
|
||||
_renderGraph = new RenderGraph(renderSystem.ResourceManager,
|
||||
renderSystem.GraphicsEngine.ResourceAllocator,
|
||||
renderSystem.GraphicsEngine.ResourceDatabase,
|
||||
renderSystem.GraphicsEngine.PipelineLibrary,
|
||||
renderSystem.GraphicsEngine.ShaderCompiler);
|
||||
}
|
||||
|
||||
public void Render(RenderContext ctx, ReadOnlySpan<RenderRequest> requests)
|
||||
{
|
||||
var resourceManager = _renderSystem.ResourceManager;
|
||||
var resourceDatabase = _renderSystem.GraphicsEngine.ResourceDatabase;
|
||||
|
||||
for (var i = 0; i < requests.Length; i++)
|
||||
{
|
||||
ref readonly var request = ref requests[i];
|
||||
|
||||
// 1. Allocate and populate Instance Data buffer
|
||||
var instanceCount = request.opaqueRenderList.TotalRecordCount;
|
||||
|
||||
if (instanceCount == 0)
|
||||
{
|
||||
continue; // Nothing to render
|
||||
}
|
||||
|
||||
var instanceDataSize = (uint)(instanceCount * sizeof(InstanceData));
|
||||
var instanceBufferDesc = ResourceDesc.Buffer(new BufferDesc
|
||||
{
|
||||
Size = instanceDataSize,
|
||||
Stride = (uint)sizeof(InstanceData),
|
||||
Usage = BufferUsage.Raw | BufferUsage.ShaderResource,
|
||||
MemoryType = ResourceMemoryType.Upload, // Upload directly for simplicity in testing
|
||||
});
|
||||
|
||||
var instanceBufferHandle = resourceManager.GetPooledResource(instanceBufferDesc);
|
||||
var instanceBufferResource = instanceBufferHandle.AsGraphicsBuffer();
|
||||
|
||||
var instanceDataArray = new InstanceData[instanceCount];
|
||||
var instanceIdx = 0;
|
||||
foreach (var record in request.opaqueRenderList)
|
||||
{
|
||||
instanceDataArray[instanceIdx++] = new InstanceData
|
||||
{
|
||||
localToWorld = record.localToWorld
|
||||
};
|
||||
}
|
||||
|
||||
ctx.CommandBuffer.UploadBuffer(instanceBufferResource, instanceDataArray.AsSpan());
|
||||
|
||||
// 2. Allocate and populate View Data buffer
|
||||
var viewDataSize = (uint)sizeof(PerViewData);
|
||||
var viewBufferDesc = ResourceDesc.Buffer(new BufferDesc
|
||||
{
|
||||
Size = viewDataSize,
|
||||
Stride = viewDataSize,
|
||||
Usage = BufferUsage.Raw | BufferUsage.ShaderResource,
|
||||
MemoryType = ResourceMemoryType.Upload,
|
||||
});
|
||||
|
||||
var viewBufferHandle = resourceManager.GetPooledResource(viewBufferDesc);
|
||||
var viewBufferResource = viewBufferHandle.AsGraphicsBuffer();
|
||||
|
||||
var viewData = new PerViewData
|
||||
{
|
||||
viewMatrix = request.view.viewMatrix,
|
||||
projectionMatrix = request.view.projectionMatrix,
|
||||
cameraPosition = request.view.position,
|
||||
nearClip = request.view.nearClipPlane,
|
||||
cameraDirection = request.view.viewMatrix.c2.xyz, // check if that's correct orientation
|
||||
farClip = request.view.farClipPlane,
|
||||
screenSize = new float4(request.view.sensorSize.x, request.view.sensorSize.y, 1.0f / request.view.sensorSize.x, 1.0f / request.view.sensorSize.y)
|
||||
};
|
||||
|
||||
ctx.CommandBuffer.UploadBuffer(viewBufferResource, new ReadOnlySpan<PerViewData>(in viewData));
|
||||
|
||||
// 3. Allocate and populate Global Frame Data buffer
|
||||
var frameDataSize = (uint)sizeof(GlobalFrameData);
|
||||
var frameBufferDesc = ResourceDesc.Buffer(new BufferDesc
|
||||
{
|
||||
Size = frameDataSize,
|
||||
Stride = frameDataSize,
|
||||
Usage = BufferUsage.Raw | BufferUsage.ShaderResource, // or CBV? Let's use Raw to keep it consistent
|
||||
MemoryType = ResourceMemoryType.Upload,
|
||||
});
|
||||
|
||||
var frameBufferHandle = resourceManager.GetPooledResource(frameBufferDesc);
|
||||
var frameBufferResource = frameBufferHandle.AsGraphicsBuffer();
|
||||
|
||||
var frameData = new GlobalFrameData
|
||||
{
|
||||
viewBufferIndex = resourceDatabase.GetBindlessIndex(viewBufferResource.AsResource()),
|
||||
instanceBufferIndex = resourceDatabase.GetBindlessIndex(instanceBufferResource.AsResource()),
|
||||
};
|
||||
|
||||
ctx.CommandBuffer.UploadBuffer(frameBufferResource, new ReadOnlySpan<GlobalFrameData>(in frameData));
|
||||
|
||||
if (request.renderFunc != null)
|
||||
{
|
||||
request.renderFunc(in ctx, in request);
|
||||
}
|
||||
|
||||
// We must enqueue a return for the pooled resources so they are freed next frame.
|
||||
resourceManager.ReturnPooledResource(instanceBufferHandle);
|
||||
resourceManager.ReturnPooledResource(viewBufferHandle);
|
||||
resourceManager.ReturnPooledResource(frameBufferHandle);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_renderGraph.Dispose();
|
||||
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,15 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Engine.Components;
|
||||
using Ghost.Engine.Systems;
|
||||
using Ghost.Engine.Utilities;
|
||||
using Ghost.Entities;
|
||||
using Ghost.Graphics.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Ghost.Graphics.Utilities;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
|
||||
namespace Ghost.Graphics.Test.Windows;
|
||||
@@ -10,8 +17,8 @@ namespace Ghost.Graphics.Test.Windows;
|
||||
public sealed partial class GraphicsTestWindow : Window
|
||||
{
|
||||
private RenderSystem? _renderSystem;
|
||||
private IRenderer? _renderer;
|
||||
private ISwapChain? _swapChain;
|
||||
private World? _world;
|
||||
|
||||
private bool _isFirstActivationHandled;
|
||||
|
||||
@@ -33,16 +40,12 @@ public sealed partial class GraphicsTestWindow : Window
|
||||
return;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
Misaki.HighPerformance.LowLevel.Buffer.AllocationManager.EnableDebugLayer();
|
||||
#endif
|
||||
|
||||
_renderSystem = new RenderSystem(new RenderSystemDesc()
|
||||
{
|
||||
FrameBufferCount = 2,
|
||||
GraphicsAPI = GraphicsAPI.Direct3D12
|
||||
});
|
||||
_renderer = _renderSystem.GraphicsEngine.CreateRenderer();
|
||||
|
||||
_swapChain = _renderSystem.GraphicsEngine.CreateSwapChain(new SwapChainDesc
|
||||
{
|
||||
Width = (uint)AppWindow.Size.Width,
|
||||
@@ -53,9 +56,96 @@ public sealed partial class GraphicsTestWindow : Window
|
||||
Target = SwapChainTarget.FromCompositionSurface(Panel)
|
||||
});
|
||||
|
||||
_renderer.RenderOutput = new SwapChainRenderOutput(_swapChain);
|
||||
|
||||
_renderSystem.RenderPipelineSettings = new RenderPasses.TestRenderPipelineSettings();
|
||||
_renderSystem.Start();
|
||||
|
||||
// ECS Setup
|
||||
_world = World.Create();
|
||||
_world.AddService(_renderSystem);
|
||||
|
||||
// Add Systems
|
||||
_world.SystemManager.GetSystem<DefaultSystemGroup>().AddSystem<RenderExtractionSystem>();
|
||||
|
||||
_world.SystemManager.InitializeAll(default);
|
||||
|
||||
// Create Camera Entity
|
||||
|
||||
using var scope = AllocationManager.CreateStackScope();
|
||||
var camSet = new ComponentSet(scope.AllocationHandle, ComponentTypeID<Camera>.Value, ComponentTypeID<LocalToWorld>.Value);
|
||||
var cameraEntity = _world.EntityManager.CreateEntity(camSet);
|
||||
|
||||
_world.EntityManager.SetComponent(cameraEntity, new Camera
|
||||
{
|
||||
colorTarget = _swapChain.GetCurrentBackBuffer(), // TODO: This should be updated every frame to the current back buffer.
|
||||
depthTarget = Handle<Texture>.Invalid,
|
||||
nearClipPlane = 0.1f,
|
||||
farClipPlane = 1000.0f,
|
||||
focalLength = 50.0f,
|
||||
sensorSize = new float2(36.0f, 24.0f),
|
||||
gateFit = GateFit.Vertical,
|
||||
renderingLayerMask = RenderingLayerMask.All,
|
||||
});
|
||||
|
||||
_world.EntityManager.SetComponent(cameraEntity, new LocalToWorld
|
||||
{
|
||||
matrix = float4x4.TRS(new float3(0.0f, 0.0f, -5.0f), quaternion.identity, new float3(1.0f, 1.0f, 1.0f))
|
||||
});
|
||||
|
||||
// var cameraEntity = _world.EntityManager.CreateEntity();
|
||||
// _world.EntityManager.AddComponent(cameraEntity, new Camera
|
||||
// {
|
||||
// colorTarget = _swapChain.GetCurrentBackBuffer(),
|
||||
// depthTarget = Handle<Texture>.Invalid,
|
||||
// nearClipPlane = 0.1f,
|
||||
// farClipPlane = 1000.0f,
|
||||
// focalLength = 50.0f,
|
||||
// sensorSize = new float2(36.0f, 24.0f),
|
||||
// gateFit = GateFit.Fill,
|
||||
// renderingLayerMask = new RenderingLayerMask(uint.MaxValue),
|
||||
// });
|
||||
//
|
||||
// _world.EntityManager.AddComponent(cameraEntity, new LocalToWorld
|
||||
// {
|
||||
// matrix = float4x4.TRS(new float3(0.0f, 0.0f, -5.0f), quaternion.identity, new float3(1.0f, 1.0f, 1.0f))
|
||||
// });
|
||||
|
||||
// Create Mesh Entity
|
||||
var meshEntity = _world.EntityManager.CreateEntity();
|
||||
|
||||
MeshBuilder.CreateCube(0.75f, default, Allocator.Persistent, out var vertices, out var indices);
|
||||
|
||||
var directCmd = _renderSystem.GraphicsEngine.CreateCommandBuffer(CommandBufferType.Graphics);
|
||||
var ctx = new RenderingContext(_renderSystem.GraphicsEngine, _renderSystem.ResourceManager, directCmd);
|
||||
|
||||
directCmd.Begin(_renderSystem.GraphicsEngine.CreateCommandAllocator(CommandBufferType.Graphics));
|
||||
|
||||
var meshHandle = ctx.CreateMesh(vertices, indices, true);
|
||||
|
||||
var meshRefResult = _renderSystem.ResourceManager.GetMeshReference(meshHandle);
|
||||
if (meshRefResult.IsSuccess)
|
||||
{
|
||||
meshRefResult.Value.CookMeshlets();
|
||||
}
|
||||
|
||||
ctx.UploadMeshlets(meshHandle);
|
||||
ctx.UpdateObjectData(meshHandle, float4x4.identity);
|
||||
|
||||
directCmd.End().ThrowIfFailed();
|
||||
_renderSystem.GraphicsEngine.Device.GraphicsQueue.Submit(directCmd);
|
||||
_renderSystem.GraphicsEngine.Device.GraphicsQueue.WaitIdle();
|
||||
|
||||
_world.EntityManager.AddComponent(meshEntity, new MeshInstance
|
||||
{
|
||||
mesh = meshHandle,
|
||||
renderingLayerMask = new RenderingLayerMask(uint.MaxValue),
|
||||
shadowCastingMode = Engine.ShadowCastingMode.On
|
||||
});
|
||||
|
||||
_world.EntityManager.AddComponent(meshEntity, new LocalToWorld
|
||||
{
|
||||
matrix = float4x4.identity
|
||||
});
|
||||
|
||||
CompositionTarget.Rendering += OnRendering;
|
||||
|
||||
e.Handled = true;
|
||||
@@ -67,16 +157,20 @@ public sealed partial class GraphicsTestWindow : Window
|
||||
CompositionTarget.Rendering -= OnRendering;
|
||||
_renderSystem?.Stop();
|
||||
|
||||
_renderer?.Dispose();
|
||||
if (_world != null)
|
||||
{
|
||||
World.Destroy(_world.ID);
|
||||
}
|
||||
|
||||
_swapChain?.Dispose();
|
||||
_renderSystem?.Dispose();
|
||||
|
||||
Misaki.HighPerformance.LowLevel.Buffer.AllocationManager.Dispose();
|
||||
AllocationManager.Dispose();
|
||||
}
|
||||
|
||||
private void SwapChainPanel_SizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
if (_renderSystem == null || _swapChain == null || _renderer == null)
|
||||
if (_renderSystem == null || _swapChain == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -90,8 +184,6 @@ public sealed partial class GraphicsTestWindow : Window
|
||||
}
|
||||
|
||||
_renderSystem.RequestSwapChainResize(_swapChain, new uint2(newWidth, newHeight));
|
||||
_renderer.RenderOutput!.Viewport = new ViewportDesc { Width = newWidth, Height = newHeight, MinDepth = 0.0f, MaxDepth = 1.0f };
|
||||
_renderer.RenderOutput!.Scissor = new RectDesc { Right = newWidth, Bottom = newHeight };
|
||||
}
|
||||
|
||||
private void SwapChainPanel_CompositionScaleChanged(SwapChainPanel sender, object args)
|
||||
@@ -101,13 +193,17 @@ public sealed partial class GraphicsTestWindow : Window
|
||||
|
||||
private void OnRendering(object? sender, object e)
|
||||
{
|
||||
if (_renderSystem == null)
|
||||
if (_renderSystem == null || _world == null || _swapChain == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_renderSystem.CPUFenceValue < _renderSystem.GPUFenceValue + _renderSystem.MaxFrameLatency)
|
||||
{
|
||||
// TODO: In a real system, the camera target would be updated correctly.
|
||||
// For now, let's just make sure it renders to the correct back buffer.
|
||||
|
||||
_world.SystemManager.UpdateAll(default); // This runs RenderExtractionSystem, extracting data and queueing RenderRequests
|
||||
_renderSystem.SignalCPUReady();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user