Refactor rendering projects

This commit is contained in:
2026-02-24 20:08:26 +09:00
parent 93c58fa7fb
commit 30090f84ab
88 changed files with 1350 additions and 1136 deletions

View File

@@ -1,67 +0,0 @@
using System.Runtime.CompilerServices;
namespace Ghost.Core;
public readonly struct TypeHandle
{
public readonly IntPtr Value
{
get;
}
private TypeHandle(IntPtr value)
{
Value = value;
}
/// <summary>
/// Gets the space handle for the specified space.
/// </summary>
/// <param name="type">The space to get the handle for.</param>
/// <returns>The space handle as a nint.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TypeHandle Get(Type type) => new TypeHandle(type.TypeHandle.Value);
/// <summary>
/// Gets the space handle for the specified space.
/// </summary>
/// <typeparam name="T">The space to get the handle for.</typeparam>
/// <returns>The space handle as a nint.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TypeHandle Get<T>() => Get(typeof(T));
/// <summary>
/// Converts a TypeHandle to a Type.
/// </summary>
/// <param name="handle">The TypeHandle to convert.</param>
/// <returns>The corresponding Type.</returns>
public Type? ToType()
{
return Type.GetTypeFromHandle(RuntimeTypeHandle.FromIntPtr(Value));
}
public override int GetHashCode()
{
return Value.GetHashCode();
}
public static implicit operator TypeHandle(IntPtr value)
{
return new TypeHandle(value);
}
public static implicit operator IntPtr(TypeHandle handle)
{
return handle.Value;
}
public static implicit operator TypeHandle(Type type)
{
return Get(type);
}
public static implicit operator Type?(TypeHandle handle)
{
return handle.ToType();
}
}

View File

@@ -10,80 +10,6 @@ namespace Ghost.Core.Utilities;
[SupportedOSPlatform("windows10.0.19041.0")]
internal static unsafe partial class Win32Utility
{
[EditorBrowsable(EditorBrowsableState.Never)]
public readonly ref struct IID_PPV
{
public readonly Guid* iid;
public readonly void** ppv;
public IID_PPV(Guid* iid, void** ppv)
{
this.iid = iid;
this.ppv = ppv;
}
public void Deconstruct(out Guid* iid, out void** ppv)
{
iid = this.iid;
ppv = this.ppv;
}
}
public static Guid* IID_NULL
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => (Guid*)Unsafe.AsPointer(ref Unsafe.AsRef(in IID.IID_NULL));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IID_PPV IID_PPV_ARGS<T>(ComPtr<T>* comPtr)
where T : unmanaged, IUnknown.Interface
{
return new IID_PPV(Windows.__uuidof<T>(), (void**)comPtr);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Attach<T>(ref this UniquePtr<T> uPtr, T* other)
where T : unmanaged, IUnknown.Interface
{
var ptr = uPtr.Get();
if (ptr != null)
{
var refCount = ptr->Release();
Debug.Assert((refCount != 0) || (ptr != other));
}
uPtr = new UniquePtr<T>(other);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Dispose<T>(ref this UniquePtr<T> uPtr)
where T : unmanaged, IUnknown.Interface
{
var ptr = uPtr.Detach();
if (ptr != null)
{
ptr->Release();
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Result ToResult(this HRESULT hr, [CallerArgumentExpression(nameof(hr))] string? op = null)
{
if (hr.SUCCEEDED)
{
return Result.Success();
}
return Result.Failure($"{op} failed with code {hr}");
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void** ReleaseAndGetVoidAddressOf<T>(ref this ComPtr<T> comPtr)
where T : unmanaged, IUnknown.Interface
{
return (void**)comPtr.ReleaseAndGetAddressOf();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ComPtr<T> Move<T>(ref this ComPtr<T> comPtr)

View File

@@ -1,5 +1,6 @@
using Ghost.Entities;
using Ghost.Graphics;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.Jobs;
namespace Ghost.Engine;

View File

@@ -208,10 +208,10 @@ public class ComponentManager : IDisposable
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Identifier<EntityQuery> CreateEntityQuery(EntityQueryMask mask, int maskHash)
internal Identifier<EntityQuery> CreateEntityQuery(ref readonly EntityQueryMask mask, int maskHash)
{
var queryID = new Identifier<EntityQuery>(_entityQueries.Count);
_entityQueries.Add(new EntityQuery(queryID, _world.ID, mask));
_entityQueries.Add(new EntityQuery(queryID, _world.ID, in mask));
_querieLookup.Add(maskHash, queryID);
ref var query = ref _entityQueries[queryID.Value];

View File

@@ -1,4 +1,3 @@
using Ghost.Core;
using Misaki.HighPerformance.Jobs;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.CompilerServices;
@@ -64,7 +63,7 @@ public unsafe partial struct EntityQuery
{
ref var arch = ref world.ComponentManager.GetArchetypeReference(archID);
for (int i = 0; i < arch.ChunkCount; i++)
for (var i = 0; i < arch.ChunkCount; i++)
{
var pChunk = (Chunk*)arch._chunks.GetUnsafePtr() + i;

View File

@@ -340,7 +340,7 @@ public unsafe partial struct EntityQuery : IDisposable
private readonly Identifier<EntityQuery> _id;
private readonly Identifier<World> _worldID;
internal EntityQuery(Identifier<EntityQuery> id, Identifier<World> worldID, EntityQueryMask mask)
internal EntityQuery(Identifier<EntityQuery> id, Identifier<World> worldID, ref readonly EntityQueryMask mask)
{
_id = id;
_worldID = worldID;
@@ -632,7 +632,7 @@ public ref partial struct QueryBuilder
}
// NOTE: We do not dispose the mask here, as it is now owned by the EntityQuery.
queryID = world.ComponentManager.CreateEntityQuery(mask, maskHash);
queryID = world.ComponentManager.CreateEntityQuery(in mask, maskHash);
Return:
Dispose();

View File

@@ -0,0 +1,8 @@
global using static TerraFX.Interop.DirectX.D3D12;
global using static TerraFX.Interop.DirectX.DirectX;
global using static TerraFX.Interop.DirectX.DXGI;
global using static TerraFX.Interop.Windows.Windows;
using System.Runtime.Versioning;
[assembly: SupportedOSPlatform("windows10.0.19041.0")]

View File

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

View File

@@ -1,4 +1,5 @@
using Ghost.Core.Utilities;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel;
using TerraFX.Interop.DirectX;

View File

@@ -1,4 +1,5 @@
using Ghost.Core.Utilities;
using Ghost.Graphics.D3D12.Utilities;
using Misaki.HighPerformance.LowLevel;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;

View File

@@ -1,4 +1,3 @@
using Ghost.Core.Utilities;
using Ghost.Graphics.D3D12.Utilities;
using Misaki.HighPerformance.LowLevel;
using Misaki.HighPerformance.LowLevel.Collections;
@@ -18,9 +17,11 @@ internal unsafe class D3D12DescriptorHeap : IDisposable
private UniquePtr<ID3D12DescriptorHeap> _heap;
private UniquePtr<ID3D12DescriptorHeap> _shaderVisibleHeap;
private D3D12_CPU_DESCRIPTOR_HANDLE _startCpuHandle;
private D3D12_CPU_DESCRIPTOR_HANDLE _startCpuHandleShaderVisible;
private D3D12_GPU_DESCRIPTOR_HANDLE _startGpuHandleShaderVisible;
private int _searchStart;
private UnsafeBitSet _allocatedDescriptors;

View File

@@ -3,22 +3,31 @@
#endif
using Ghost.Core;
using Ghost.Graphics.Contracts;
using Ghost.Graphics.RHI;
using Ghost.Graphics.Core;
using Ghost.Graphics.RHI;
using System.Collections.Immutable;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ghost.Graphics.D3D12;
public static class D3D12GraphicsEngineFactory
{
public static IGraphicsEngine Create(IRenderSystem renderSystem)
{
return new D3D12GraphicsEngine(renderSystem);
}
}
internal class D3D12GraphicsEngine : IGraphicsEngine
{
private GCHandle _thisHandle;
private readonly IRenderSystem _renderSystem;
#if ENABLE_DEBUG
private readonly D3D12DebugLayer _debugLayer;
#endif
private readonly D3D12RenderDevice _device;
private readonly DxcShaderCompiler _shaderCompiler;
private readonly D3D12DescriptorAllocator _descriptorAllocator;
@@ -71,7 +80,7 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
{
ThrowIfDisposed();
var renderer = new D3D12Renderer(this, _resourceDatabase);
var renderer = new D3D12Renderer(this);
ImmutableInterlocked.Update(ref _renderers, renderers => renderers.Add(renderer));
return renderer;
}
@@ -157,6 +166,8 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
_debugLayer.Dispose();
#endif
_thisHandle.Free();
_disposed = true;
GC.SuppressFinalize(this);
}

View File

@@ -1,8 +1,5 @@
using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.Core.Utilities;
using Ghost.Graphics.Contracts;
using Ghost.Graphics.Core;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel;

View File

@@ -1,4 +1,4 @@
using Ghost.Core.Utilities;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel;
using TerraFX.Interop.DirectX;
@@ -78,7 +78,7 @@ internal unsafe class D3D12RenderDevice : IRenderDevice
pAdapter->GetDesc1(&desc);
// Don't select the Basic Render Driver adapter.
if (desc.Flags.HasFlag(DXGI_ADAPTER_FLAG_SOFTWARE))
if ((desc.Flags & (uint)DXGI_ADAPTER_FLAG_SOFTWARE) != 0)
{
goto NEXT_ITERATION;
}
@@ -106,7 +106,7 @@ internal unsafe class D3D12RenderDevice : IRenderDevice
{
ObjectDisposedException.ThrowIf(_disposed, this);
FeatureSupport support = FeatureSupport.None;
var support = FeatureSupport.None;
D3D12_FEATURE_DATA_D3D12_OPTIONS options = default;
if (_device.Get()->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS, &options, (uint)sizeof(D3D12_FEATURE_DATA_D3D12_OPTIONS)).SUCCEEDED)

View File

@@ -0,0 +1,124 @@
using Ghost.Core;
using Ghost.Graphics.RHI;
namespace Ghost.Graphics.D3D12;
/// <summary>
/// D3D12 implementation of the renderer interface using RHI abstractions
/// </summary>
internal class D3D12Renderer : IRenderer
{
private readonly D3D12GraphicsEngine _graphicsEngine;
private readonly ICommandBuffer _commandBuffer;
private bool _disposed;
public IRenderOutput? RenderOutput
{
get; set;
}
public Func<RenderContext, Error>? RenderFunc
{
get; set;
}
public D3D12Renderer(D3D12GraphicsEngine graphicsEngine)
{
_graphicsEngine = graphicsEngine;
_commandBuffer = _graphicsEngine.CreateCommandBuffer(CommandBufferType.Graphics);
}
~D3D12Renderer()
{
Dispose();
}
public Result Render(ICommandAllocator commandAllocator)
{
if (RenderFunc is null)
{
return Result.Success(); // No render function set, skip rendering.
}
if (RenderOutput is null)
{
return Result.Failure("Render target strategy is not set.");
}
var target = RenderOutput.GetRenderTarget();
if (target.IsInvalid)
{
return Result.Failure("Render target is invalid.");
}
_commandBuffer.Begin(commandAllocator);
RenderOutput.BeginRender(_commandBuffer);
var ctx = new RenderContext();
var error = RenderFunc.Invoke(ctx);
if (error != Error.None)
{
_commandBuffer.End();
return Result.Failure(error);
}
RenderOutput.EndRender(_commandBuffer);
var r = _commandBuffer.End();
if (r.IsFailure)
{
return r;
}
_graphicsEngine.Device.GraphicsQueue.Submit(_commandBuffer);
RenderOutput.Present();
return Result.Success();
}
//// TODO: A proper render graph integration.
//private Error RenderScene(Handle<Texture> target, ViewportDesc viewport, RectDesc rect)
//{
// // NOTE: Testing only.
// var ctx = new RenderingContext(_graphicsEngine, _commandBuffer);
// if (_frameIndex == 0)
// {
// _pass.Initialize(ref ctx);
// }
// //_commandBuffer.BeginRenderPass(rtDesc, depthDesc, false);
// _commandBuffer.SetViewport(viewport);
// _commandBuffer.SetScissorRect(rect);
// _renderGraph.Reset();
// var backBuffer = _renderGraph.ImportTexture(target, "Back Buffer");
// _pass.Build(_renderGraph, backBuffer);
// // Create view state from viewport
// var viewState = new ViewState((uint)viewport.Width, (uint)viewport.Height);
// // Compile with view state
// _renderGraph.Compile(in viewState);
// _renderGraph.Execute(_commandBuffer);
// //_commandBuffer.EndRenderPass();
// _frameIndex++;
// return Error.None;
//}
public void Dispose()
{
if (_disposed)
{
return;
}
_commandBuffer.Dispose();
_disposed = true;
GC.SuppressFinalize(this);
}
}

View File

@@ -1,7 +1,6 @@
using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.Core.Utilities;
using Ghost.Graphics.Core;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel;
@@ -866,71 +865,6 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
return _resourceDatabase.CreateSampler(in desc, samplerDescriptor.Value);
}
public Handle<Mesh> CreateMesh(UnsafeList<Vertex> vertices, UnsafeList<uint> indices)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var vertexBufferDesc = new BufferDesc
{
Size = (uint)(vertices.Count * sizeof(Vertex)),
Stride = (uint)sizeof(Vertex),
Usage = BufferUsage.Vertex | BufferUsage.ShaderResource | BufferUsage.Raw,
MemoryType = ResourceMemoryType.Default,
};
var indexBufferDesc = new BufferDesc
{
Size = (uint)(indices.Count * sizeof(uint)),
Stride = sizeof(uint),
Usage = BufferUsage.Index | BufferUsage.ShaderResource | BufferUsage.Raw,
MemoryType = ResourceMemoryType.Default,
};
var objectBufferDesc = new BufferDesc
{
Size = (uint)sizeof(PerObjectData),
Stride = (uint)sizeof(PerObjectData),
Usage = BufferUsage.Raw | BufferUsage.ShaderResource,
MemoryType = ResourceMemoryType.Default,
};
var vertexBuffer = CreateBuffer(in vertexBufferDesc, "VertexBuffer");
var indexBuffer = CreateBuffer(in indexBufferDesc, "IndexBuffer");
var objectBuffer = CreateBuffer(in objectBufferDesc, "ObjectBuffer");
var data = new Mesh
{
Vertices = vertices,
Indices = indices,
VertexBuffer = vertexBuffer,
IndexBuffer = indexBuffer,
ObjectDataBuffer = objectBuffer,
};
return _resourceDatabase.AddMesh(in data);
}
public Handle<Material> CreateMaterial(Identifier<Shader> shader)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var material = new Material();
if (material.SetShader(shader, this, _resourceDatabase) != Error.None)
{
return Handle<Material>.Invalid;
}
return _resourceDatabase.AddMaterial(in material);
}
public Identifier<Shader> CreateGraphicsShader(ShaderDescriptor descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var shader = new Shader(descriptor);
return _resourceDatabase.AddShader(shader);
}
public void Dispose()
{
if (_disposed)

View File

@@ -1,5 +1,4 @@
using Ghost.Core;
using Ghost.Graphics.Core;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.Collections;
@@ -102,15 +101,11 @@ internal class D3D12ResourceDatabase : IResourceDatabase
private readonly D3D12DescriptorAllocator _descriptorAllocator;
private UnsafeSlotMap<ResourceRecord> _resources;
private UnsafeHashMap<SamplerDesc, Identifier<Sampler>> _samplers;
#if DEBUG || GHOST_EDITOR
private readonly Dictionary<Handle<GPUResource>, string> _resourceName;
#endif
private UnsafeHashMap<SamplerDesc, Identifier<Sampler>> _samplers;
private UnsafeSlotMap<Mesh> _meshes;
private UnsafeSlotMap<Material> _materials;
private readonly DynamicArray<Shader> _shaders; // TODO: Use SlotMap?
private UnsafeQueue<ReleaseEntry> _releaseQueue;
private bool _disposed;
@@ -121,13 +116,10 @@ internal class D3D12ResourceDatabase : IResourceDatabase
_descriptorAllocator = descriptorAllocator;
_resources = new UnsafeSlotMap<ResourceRecord>(64, Allocator.Persistent, AllocationOption.Clear);
_samplers = new UnsafeHashMap<SamplerDesc, Identifier<Sampler>>(32, Allocator.Persistent);
#if DEBUG || GHOST_EDITOR
_resourceName = new Dictionary<Handle<GPUResource>, string>(64);
#endif
_samplers = new UnsafeHashMap<SamplerDesc, Identifier<Sampler>>(32, Allocator.Persistent);
_meshes = new UnsafeSlotMap<Mesh>(64, Allocator.Persistent, AllocationOption.Clear);
_materials = new UnsafeSlotMap<Material>(16, Allocator.Persistent, AllocationOption.Clear);
_shaders = new DynamicArray<Shader>(16);
_releaseQueue = new UnsafeQueue<ReleaseEntry>(32, Allocator.Persistent);
}
@@ -137,12 +129,6 @@ internal class D3D12ResourceDatabase : IResourceDatabase
Dispose();
}
private void ReleaseResource<T>(T resource)
where T : IResourceReleasable
{
resource.ReleaseResource(this);
}
internal unsafe Handle<GPUResource> ImportExternalResource(ID3D12Resource* pResource, ResourceBarrierData initialBarrierData, ResourceViewGroup viewGroup, string? name = null)
{
ObjectDisposedException.ThrowIf(_disposed, this);
@@ -356,122 +342,6 @@ internal class D3D12ResourceDatabase : IResourceDatabase
_descriptorAllocator.Release(new Identifier<SamplerDescriptor>(id.Value));
}
public Handle<Mesh> AddMesh(ref readonly Mesh mesh)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var id = _meshes.Add(mesh, out var generation);
return new Handle<Mesh>(id, generation);
}
public bool HasMesh(Handle<Mesh> handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _meshes.Contains(handle.ID, handle.Generation);
}
public RefResult<Mesh, Error> GetMeshReference(Handle<Mesh> handle)
{
ref var mesh = ref _meshes.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
if (!exist)
{
return Error.NotFound;
}
return RefResult<Mesh, Error>.Success(ref mesh);
}
public void ReleaseMesh(Handle<Mesh> handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
ref var mesh = ref _meshes.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
if (!exist)
{
return;
}
ReleaseResource(mesh);
_meshes.Remove(handle.ID, handle.Generation);
}
public Handle<Material> AddMaterial(ref readonly Material material)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var id = _materials.Add(material, out var generation);
return new Handle<Material>(id, generation);
}
public bool HasMaterial(Handle<Material> handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _materials.Contains(handle.ID, handle.Generation);
}
public RefResult<Material, Error> GetMaterialReference(Handle<Material> handle)
{
ref var material = ref _materials.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
if (!exist)
{
return Error.NotFound;
}
return RefResult<Material, Error>.Success(ref material);
}
public void ReleaseMaterial(Handle<Material> handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
ref var material = ref _materials.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
if (!exist)
{
return;
}
ReleaseResource(material);
_materials.Remove(handle.ID, handle.Generation);
}
public Identifier<Shader> AddShader(Shader shader)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var id = _shaders.Count;
_shaders.Add(shader);
return new Identifier<Shader>(id);
}
public bool HasShader(Identifier<Shader> id)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return id.Value >= 0 && id.Value < _shaders.Count;
}
public RefResult<Shader, Error> GetShaderReference(Identifier<Shader> id)
{
if (!HasShader(id))
{
return Error.NotFound;
}
return RefResult<Shader, Error>.Success(ref _shaders[id.Value]);
}
public void ReleaseShader(Identifier<Shader> id)
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (!HasShader(id))
{
return;
}
ref var shader = ref _shaders[id.Value]!;
ReleaseResource(shader);
}
public void EndFrame()
{
ObjectDisposedException.ThrowIf(_disposed, this);
@@ -494,21 +364,6 @@ internal class D3D12ResourceDatabase : IResourceDatabase
{
ObjectDisposedException.ThrowIf(_disposed, this);
foreach (var mesh in _meshes)
{
ReleaseResource(mesh);
}
foreach (var material in _materials)
{
ReleaseResource(material);
}
foreach (var shader in _shaders)
{
ReleaseResource(shader);
}
foreach (ref var record in _resources)
{
record.Release(_descriptorAllocator);
@@ -524,8 +379,6 @@ internal class D3D12ResourceDatabase : IResourceDatabase
_resources.Dispose();
_samplers.Dispose();
_meshes.Dispose();
_materials.Dispose();
_releaseQueue.Dispose();
_disposed = true;

View File

@@ -1,7 +1,4 @@
using Ghost.Core;
using Ghost.Core.Utilities;
using Ghost.Graphics.Contracts;
using Ghost.Graphics.Core;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel;

View File

@@ -1,7 +1,6 @@
using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.Core.Utilities;
using Ghost.Graphics.Contracts;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel;
using Misaki.HighPerformance.LowLevel.Buffer;
@@ -116,7 +115,7 @@ internal sealed partial class DxcShaderCompiler
{
sb.AppendLine($"#include \"{includePath}\"");
}
if (!string.IsNullOrEmpty(injectedCode))
{
sb.AppendLine($"#line 1 \"hlsl_block\"");
@@ -229,41 +228,41 @@ internal sealed unsafe partial class DxcShaderCompiler : IShaderCompiler
switch (bindDesc.Type)
{
case D3D_SHADER_INPUT_TYPE.D3D_SIT_CBUFFER:
{
var cbuffer = pReflection->GetConstantBufferByName(bindDesc.Name);
D3D12_SHADER_BUFFER_DESC cbufferDesc;
ThrowIfFailed(cbuffer->GetDesc(&cbufferDesc));
var variables = new List<CBufferPropertyInfo>((int)cbufferDesc.Variables);
// Now we iterate all variables for *every* cbuffer, not just b3
for (uint j = 0; j < cbufferDesc.Variables; j++)
{
var cbuffer = pReflection->GetConstantBufferByName(bindDesc.Name);
D3D12_SHADER_BUFFER_DESC cbufferDesc;
ThrowIfFailed(cbuffer->GetDesc(&cbufferDesc));
var variable = cbuffer->GetVariableByIndex(j);
D3D12_SHADER_VARIABLE_DESC varDesc;
variable->GetDesc(&varDesc);
var variables = new List<CBufferPropertyInfo>((int)cbufferDesc.Variables);
// Now we iterate all variables for *every* cbuffer, not just b3
for (uint j = 0; j < cbufferDesc.Variables; j++)
var variableName = Marshal.PtrToStringUTF8((IntPtr)varDesc.Name);
if (variableName == null)
{
var variable = cbuffer->GetVariableByIndex(j);
D3D12_SHADER_VARIABLE_DESC varDesc;
variable->GetDesc(&varDesc);
var variableName = Marshal.PtrToStringUTF8((IntPtr)varDesc.Name);
if (variableName == null)
{
continue;
}
variables.Add(new CBufferPropertyInfo
{
Name = variableName,
StartOffset = varDesc.StartOffset,
Size = varDesc.Size
});
continue;
}
info.Size = cbufferDesc.Size;
info.Properties = variables;
break;
variables.Add(new CBufferPropertyInfo
{
Name = variableName,
StartOffset = varDesc.StartOffset,
Size = varDesc.Size
});
}
// NOTE: Currently we do not support resource bindings yet, everything access through bindless heaps.
info.Size = cbufferDesc.Size;
info.Properties = variables;
break;
}
// NOTE: Currently we do not support resource bindings yet, everything access through bindless heaps.
}
reflectionData.ResourcesBindings.Add(info);
@@ -318,8 +317,7 @@ internal sealed unsafe partial class DxcShaderCompiler : IShaderCompiler
Encoding = DXC_CP_UTF8
};
var (iid, ppv) = Win32Utility.IID_PPV_ARGS(&result);
ThrowIfFailed(_compiler.Get()->Compile(&buffer, argPtrs, (uint)argsArray.Count, includeHandler, iid, ppv));
ThrowIfFailed(_compiler.Get()->Compile(&buffer, argPtrs, (uint)argsArray.Count, includeHandler, __uuidof(result.Get()), (void**)&result));
// Check compilation result
HRESULT hrStatus;
@@ -351,9 +349,7 @@ internal sealed unsafe partial class DxcShaderCompiler : IShaderCompiler
if (config.options.HasFlag(CompilerOption.KeepReflections))
{
using ComPtr<IDxcBlob> reflection = default;
(iid, ppv) = Win32Utility.IID_PPV_ARGS(&reflection);
if (result.Get()->GetOutput(DXC_OUT_KIND.DXC_OUT_REFLECTION, iid, ppv, null).SUCCEEDED)
if (result.Get()->GetOutput(DXC_OUT_KIND.DXC_OUT_REFLECTION, __uuidof(reflection.Get()), (void**)&reflection, null).SUCCEEDED)
{
reflectionData = PerformDXCReflection(reflection).GetValueOrDefault();
}

View File

@@ -0,0 +1,37 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PublishAot>true</PublishAot>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<IsAotCompatible>True</IsAotCompatible>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<IsAotCompatible>True</IsAotCompatible>
</PropertyGroup>
<ItemGroup>
<Content Include="runtimes\win-x64\native\dxcompiler.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="runtimes\win-x64\native\dxil.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="TerraFX.Interop.D3D12MemoryAllocator" Version="3.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ghost.Core\Ghost.Core.csproj" />
<ProjectReference Include="..\Ghost.Graphics.RHI\Ghost.Graphics.RHI.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,9 +1,8 @@
using System.Runtime.InteropServices;
using TerraFX.Interop.Windows;
namespace Ghost.Graphics.Contracts;
namespace Ghost.Graphics.D3D12;
public unsafe readonly struct ISwapChainPanelNative : ISwapChainPanelNative.Interface, IDisposable
public readonly unsafe struct ISwapChainPanelNative : ISwapChainPanelNative.Interface, IDisposable
{
[ComImport]
[Guid("63aad0b8-7c24-40ff-85a8-640d944cc325")]

View File

@@ -0,0 +1,27 @@
using Ghost.Graphics.RHI;
using System.Runtime.CompilerServices;
using TerraFX.Interop.DirectX;
using static TerraFX.Interop.DirectX.D3D12_INPUT_CLASSIFICATION;
using static TerraFX.Interop.DirectX.DXGI_FORMAT;
namespace Ghost.Graphics.D3D12.Utilities;
internal static unsafe class D3D12PipelineResource
{
private static readonly D3D12_INPUT_ELEMENT_DESC[] s_inputElementDescs = [
new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Vertex.Semantic.Position.GetUnsafePtr(), SemanticIndex = 0u, Format = DXGI_FORMAT_R32G32B32A32_FLOAT, InputSlot = 0u, AlignedByteOffset = 0u, InputSlotClass = D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 },
new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Vertex.Semantic.Normal.GetUnsafePtr(), SemanticIndex = 0u, Format = DXGI_FORMAT_R32G32B32A32_FLOAT, InputSlot = 0u, AlignedByteOffset = 16u, InputSlotClass = D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 },
new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Vertex.Semantic.Tangent.GetUnsafePtr(), SemanticIndex = 0u, Format = DXGI_FORMAT_R32G32B32A32_FLOAT, InputSlot = 0u, AlignedByteOffset = 32u, InputSlotClass = D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 },
new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Vertex.Semantic.Uv.GetUnsafePtr(), SemanticIndex = 0u, Format = DXGI_FORMAT_R32G32B32A32_FLOAT, InputSlot = 0u, AlignedByteOffset = 48u, InputSlotClass = D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 },
new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Vertex.Semantic.Color.GetUnsafePtr(), SemanticIndex = 0u, Format = DXGI_FORMAT_R32G32B32A32_FLOAT, InputSlot = 0u, AlignedByteOffset = 64u, InputSlotClass = D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 },
];
public const DXGI_FORMAT SWAP_CHAIN_BACK_BUFFER_FORMAT = DXGI_FORMAT_B8G8R8A8_UNORM;
public static D3D12_INPUT_LAYOUT_DESC InputLayoutDescription => new()
{
pInputElementDescs = (D3D12_INPUT_ELEMENT_DESC*)Unsafe.AsPointer(ref s_inputElementDescs[0]),
NumElements = (uint)s_inputElementDescs.Length
};
}

View File

@@ -1,7 +1,12 @@
using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;
using static TerraFX.Aliases.D3D12_Alias;
using static TerraFX.Aliases.DXGI_Alias;
@@ -9,6 +14,82 @@ namespace Ghost.Graphics.D3D12.Utilities;
internal static unsafe class D3D12Utility
{
[EditorBrowsable(EditorBrowsableState.Never)]
public readonly ref struct IID_PPV
{
public readonly Guid* iid;
public readonly void** ppv;
public IID_PPV(Guid* iid, void** ppv)
{
this.iid = iid;
this.ppv = ppv;
}
public void Deconstruct(out Guid* iid, out void** ppv)
{
iid = this.iid;
ppv = this.ppv;
}
}
public static Guid* IID_NULL
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => (Guid*)Unsafe.AsPointer(ref Unsafe.AsRef(in IID.IID_NULL));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IID_PPV IID_PPV_ARGS<T>(ComPtr<T>* comPtr)
where T : unmanaged, IUnknown.Interface
{
return new IID_PPV(__uuidof<T>(), (void**)comPtr);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Attach<T>(ref this UniquePtr<T> uPtr, T* other)
where T : unmanaged, IUnknown.Interface
{
var ptr = uPtr.Get();
if (ptr != null)
{
var refCount = ptr->Release();
Debug.Assert((refCount != 0) || (ptr != other));
}
uPtr = new UniquePtr<T>(other);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Dispose<T>(ref this UniquePtr<T> uPtr)
where T : unmanaged, IUnknown.Interface
{
var ptr = uPtr.Detach();
if (ptr != null)
{
var refCount = ptr->Release();
Debug.Assert((refCount != 0));
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Result ToResult(this HRESULT hr, [CallerArgumentExpression(nameof(hr))] string? op = null)
{
if (hr.SUCCEEDED)
{
return Result.Success();
}
return Result.Failure($"{op} failed with code {hr}");
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void** ReleaseAndGetVoidAddressOf<T>(ref this ComPtr<T> comPtr)
where T : unmanaged, IUnknown.Interface
{
return (void**)comPtr.ReleaseAndGetAddressOf();
}
public static void SetName<T>(ref this T obj, ReadOnlySpan<char> name)
where T : unmanaged, ID3D12Object.Interface
{

View File

@@ -1,16 +1,178 @@
using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.Graphics.Core;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.Mathematics;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ghost.Graphics.RHI;
/// <summary>
/// Represents a color with 4 bytes components.
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = 4)]
public struct Color32 : IEquatable<Color32>
{
public byte r;
public byte g;
public byte b;
public byte a;
public Color32(byte r, byte g, byte b, byte a)
{
this.r = r;
this.g = g;
this.b = b;
this.a = a;
}
public Color32(Color color)
: this(color.R, color.G, color.B, color.A)
{
}
public Color32(Color128 color128)
: this((byte)(color128.r * 255.0f), (byte)(color128.g * 255.0f), (byte)(color128.b * 255.0f), (byte)(color128.a * 255.0f))
{
}
public Color32(float4 v)
: this((byte)(v.x * 255.0f), (byte)(v.y * 255.0f), (byte)(v.z * 255.0f), (byte)(v.w * 255.0f))
{
}
public readonly bool Equals(Color32 other)
{
return r == other.r && g == other.g && b == other.b && a == other.a;
}
public override readonly bool Equals(object? obj)
{
return obj is Color32 color && Equals(color);
}
public override readonly int GetHashCode()
{
return HashCode.Combine(r, g, b, a);
}
public static bool operator ==(Color32 left, Color32 right)
{
return left.Equals(right);
}
public static bool operator !=(Color32 left, Color32 right)
{
return !(left == right);
}
}
/// <summary>
/// Represents a color with 16 bytes components.
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = 16)]
public struct Color128 : IEquatable<Color128>
{
public float r;
public float g;
public float b;
public float a;
public Color128(float r, float g, float b, float a)
{
this.r = r;
this.g = g;
this.b = b;
this.a = a;
}
public Color128(Color color)
: this(color.R / 255.0f, color.G / 255.0f, color.B / 255.0f, color.A / 255.0f)
{
}
public Color128(Color32 color32)
: this(color32.r / 255.0f, color32.g / 255.0f, color32.b / 255.0f, color32.a / 255.0f)
{
}
public Color128(float4 v)
: this(v.x, v.y, v.z, v.w)
{
}
public readonly bool Equals(Color128 other)
{
return r.Equals(other.r) && g.Equals(other.g) && b.Equals(other.b) && a.Equals(other.a);
}
public override readonly bool Equals(object? obj)
{
return obj is Color128 color && Equals(color);
}
public readonly override int GetHashCode()
{
return HashCode.Combine(r, g, b, a);
}
public static bool operator ==(Color128 left, Color128 right)
{
return left.Equals(right);
}
public static bool operator !=(Color128 left, Color128 right)
{
return !(left == right);
}
}
[StructLayout(LayoutKind.Sequential)]
public struct Vertex
{
public static class Semantic
{
public const int COUNT = 5;
public static readonly FixedText32 Position = new("POSITION");
public static readonly FixedText32 Normal = new("NORMAL");
public static readonly FixedText32 Tangent = new("TANGENT");
public static readonly FixedText32 Uv = new("TEXCOORD");
public static readonly FixedText32 Color = new("COLOR");
}
public float4 position;
public float4 normal;
public float4 tangent;
public float4 uv;
public Color128 color;
}
public readonly struct ShaderVariant;
public readonly struct GraphicsPipeline;
public readonly struct ShaderPass
{
public Key64<ShaderPass> Key
{
get; init;
}
public PipelineState DefaultState
{
get; init;
}
public LocalKeywordSet KeywordIDs
{
get; init;
}
}
public readonly struct PassPipelineHash : IEquatable<PassPipelineHash>
{
public readonly UInt128 value;
@@ -431,31 +593,23 @@ public struct ResourceDesc
get; init;
}
public TextureDesc TextureDescription
[UnscopedRef]
public ref TextureDesc TextureDescription
{
readonly get
get
{
Debug.Assert(Type == ResourceType.Texture);
return _desc.textureDescription;
}
set
{
Debug.Assert(Type == ResourceType.Texture);
_desc.textureDescription = value;
return ref _desc.textureDescription;
}
}
public BufferDesc BufferDescription
[UnscopedRef]
public ref BufferDesc BufferDescription
{
readonly get
get
{
Debug.Assert(Type == ResourceType.Buffer);
return _desc.bufferDescription;
}
set
{
Debug.Assert(Type == ResourceType.Buffer);
_desc.bufferDescription = value;
return ref _desc.bufferDescription;
}
}

View File

@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<IsAotCompatible>True</IsAotCompatible>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<IsAotCompatible>True</IsAotCompatible>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Ghost.Core\Ghost.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,5 +1,4 @@
using Ghost.Core;
using Ghost.Graphics.Core;
namespace Ghost.Graphics.RHI;

View File

@@ -1,5 +1,4 @@
using Ghost.Core;
using Ghost.Graphics.Contracts;
namespace Ghost.Graphics.RHI;
@@ -31,15 +30,15 @@ public interface IGraphicsEngine : IDisposable
}
/// <summary>
/// Creates a new instance of an object that implements the IRenderer interface.
/// Creates a new instance of a renderer for drawing graphical content.
/// </summary>
/// <returns>An object that provides rendering functionality through the IRenderer interface.</returns>
/// <returns>An object that implements the IRenderer interface, which can be used to render graphics.</returns>
IRenderer CreateRenderer();
/// <summary>
/// Removes the specified renderer from the collection of active renderers.
/// </summary>
/// <param name="renderer">The renderer instance to remove. Cannot be null.</param>
/// <param name="renderer">The renderer instance to remove.</param>
void RemoveRenderer(IRenderer renderer);
/// <summary>

View File

@@ -1,5 +1,4 @@
using Ghost.Core;
using Ghost.Graphics.Contracts;
namespace Ghost.Graphics.RHI;

View File

@@ -1,8 +1,6 @@
using Ghost.Core;
using Ghost.Graphics.Core;
using Ghost.Graphics.RHI;
namespace Ghost.Graphics.Contracts;
namespace Ghost.Graphics.RHI;
public interface IRenderOutput
{

View File

@@ -0,0 +1,47 @@
using Misaki.HighPerformance.Mathematics;
namespace Ghost.Graphics.RHI;
public interface IFenceSynchronizer
{
uint CPUFenceValue
{
get;
}
uint GPUFenceValue
{
get;
}
uint FrameIndex
{
get;
}
uint MaxFrameLatency
{
get;
}
bool WaitForGPUReady(int timeOut = -1);
void SignalCPUReady();
void WaitIdle();
}
public interface IRenderSystem : IFenceSynchronizer, IDisposable
{
IGraphicsEngine GraphicsEngine
{
get;
}
bool IsRunning
{
get;
}
void Start();
void Stop();
void RequestSwapChainResize(ISwapChain swapChain, uint2 newSize);
}

View File

@@ -1,18 +1,33 @@
using Ghost.Core;
using Ghost.Graphics.Contracts;
namespace Ghost.Graphics.RHI;
public readonly struct RenderContext
{
public ICommandBuffer CommandBuffer { get; init; }
}
/// <summary>
/// High-level renderer interface that uses RHI abstractions
/// </summary>
public interface IRenderer : IDisposable
{
/// <summary>
/// Gets or sets the render output target for this renderer.
/// </summary>
IRenderOutput? RenderOutput
{
get; set;
}
/// <summary>
/// The function that performs the actual rendering operations. Skip rendering if this is null.
/// </summary>
Func<RenderContext, Error>? RenderFunc
{
get; set;
}
/// <summary>
/// Renders a frame
/// </summary>

View File

@@ -1,6 +1,5 @@
using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.Graphics.Core;
using Misaki.HighPerformance.LowLevel.Collections;
namespace Ghost.Graphics.RHI;
@@ -138,26 +137,4 @@ public interface IResourceAllocator : IDisposable
/// <param name="desc">A read-only reference to a <see cref="SamplerDesc"/> structure that defines the properties of the sampler to be created.</param>
/// <returns>An <see cref="Identifier{Sampler}"/> that uniquely identifies the created sampler object.</returns>
Identifier<Sampler> CreateSampler(ref readonly SamplerDesc desc);
/// <summary>
/// Creates a new mesh from the specified vertex and index data.
/// </summary>
/// <param name="vertices">A UnsafeList containing the vertices that define the geometry of the mesh. Must contain at least one vertex.</param>
/// <param name="indices">A UnsafeList containing the indices that specify how vertices are connected to form primitives. Must contain at least one index.</param>
/// <returns>An <see cref="Identifier{Mesh}"/> representing the newly created mesh.</returns>
Handle<Mesh> CreateMesh(UnsafeList<Vertex> vertices, UnsafeList<uint> indices);
/// <summary>
/// Creates a new material instance using the specified shader.
/// </summary>
/// <param name="shader">The identifier of the shader to associate with the new material. Cannot be null.</param>
/// <returns>An <see cref="Identifier{Material}"/> representing the newly created material.</returns>
Handle<Material> CreateMaterial(Identifier<Shader> shader);
/// <summary>
/// Creates a new shader and returns its unique identifier.
/// </summary>
/// <returns>An <see cref="Identifier{Shader}"/> representing the newly created shader.</returns>
/// <param name="descriptor">The viewGroup containing the shader's properties and passes.</param>
Identifier<Shader> CreateGraphicsShader(ShaderDescriptor descriptor);
}

View File

@@ -1,5 +1,4 @@
using Ghost.Core;
using Ghost.Graphics.Core;
namespace Ghost.Graphics.RHI;
@@ -129,85 +128,4 @@ public interface IResourceDatabase : IDisposable
/// </summary>
/// <param name="id">The identifier of the sampler to release. Must reference a valid, existing sampler.</param>
void ReleaseSampler(Identifier<Sampler> id);
/// <summary>
/// Adds a mesh to the resource database and returns its handle.
/// </summary>
/// <param name="mesh">The mesh data to be added to the database.</param>
/// <returns>The <see cref="Handle{Mesh}"/> representing the newly added mesh.</returns>"/>
Handle<Mesh> AddMesh(ref readonly Mesh mesh);
/// <summary>
/// Determines whether a mesh with the specified Handle exists.
/// </summary>
/// <param name="handle">The handle of the mesh to check for existence. Cannot be null.</param>
/// <returns>true if a mesh with the specified Handle exists; otherwise, false.</returns>
bool HasMesh(Handle<Mesh> handle);
/// <summary>
/// Returns a reference to the mesh associated with the specified handle.
/// </summary>
/// <param name="handle">The handle of the mesh to retrieve. Must refer to a valid mesh; otherwise, the behavior is undefined.</param>
/// <returns>A result containing a reference to the mesh corresponding to the specified handle, or an error status if the handle is invalid.</returns>
RefResult<Mesh, Error> GetMeshReference(Handle<Mesh> handle);
/// <summary>
/// Releases the mesh resource associated with the specified handle, freeing any resources held by it. Includes both CPU and GPU resources.
/// </summary>
/// <param name="handle">The handle of the mesh to release. Must refer to a mesh that was previously created and not already released.</param>
void ReleaseMesh(Handle<Mesh> handle);
/// <summary>
/// Adds a new material to the collection and returns its unique handle.
/// </summary>
/// <param name="material">The material to add. The material must be fully initialized before calling this method.</param>
/// <returns>The <see cref="Handle{Material}"/> representing the newly added material.</returns>
Handle<Material> AddMaterial(ref readonly Material material);
/// <summary>
/// Determines whether a material with the specified handle exists in the collection.
/// </summary>
/// <param name="handle">The handle of the material to check for existence.</param>
/// <returns>true if a material with the specified handle exists; otherwise, false.</returns>
bool HasMaterial(Handle<Material> handle);
/// <summary>
/// Gets a reference to the material associated with the specified handle.
/// </summary>
/// <param name="handle">The handle of the material to retrieve. Must refer to a valid material.</param>
/// <returns>A result containing a reference to the material corresponding to the specified handle, or an error status if the handle is invalid.</returns>
RefResult<Material, Error> GetMaterialReference(Handle<Material> handle);
/// <summary>
/// Releases the material associated with the specified handle, making it available for reuse or disposal.
/// </summary>
/// <param name="handle">The handle of the material to release. Must refer to a material that has been previously acquired.</param>
void ReleaseMaterial(Handle<Material> handle);
/// <summary>
/// Adds the specified shader to the collection and returns its unique identifier.
/// </summary>
/// <param name="shader">The shader to add. The shader is passed by read-only reference and will not be modified.</param>
/// <returns>The <see cref="Identifier{Shader}"/> representing the newly added shader.</returns>
Identifier<Shader> AddShader(Shader shader);
/// <summary>
/// Determines whether a shader with the specified identifier exists in the collection.
/// </summary>
/// <param name="id">The identifier of the shader to check for existence.</param>
/// <returns>true if a shader with the specified identifier exists; otherwise, false.</returns>
bool HasShader(Identifier<Shader> id);
/// <summary>
/// Returns a reference to the shader associated with the specified identifier.
/// </summary>
/// <param name="id">The identifier of the shader to retrieve. Must refer to a valid shader.</param>
/// <returns>A result containing a reference to the shader corresponding to the specified identifier, or an error status if the identifier is invalid.</returns>
RefResult<Shader, Error> GetShaderReference(Identifier<Shader> id);
/// <summary>
/// Releases the shader associated with the specified identifier, freeing any resources allocated to it.
/// </summary>
/// <param name="id">The identifier of the shader to release. Must refer to a valid, previously created shader.</param>
void ReleaseShader(Identifier<Shader> id);
}

View File

@@ -1,10 +1,9 @@
using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
namespace Ghost.Graphics.Contracts;
namespace Ghost.Graphics.RHI;
public struct ShaderCompileResult : IDisposable
{

View File

@@ -1,5 +1,4 @@
using Ghost.Core;
using Ghost.Graphics.Core;
namespace Ghost.Graphics.RHI;

View File

@@ -1,8 +1,7 @@
using System.Runtime.Intrinsics;
using TerraFX.Interop.Windows;
using ElementType = uint;
namespace Ghost.Graphics.Core;
namespace Ghost.Graphics.RHI;
public unsafe struct LocalKeywordSet
{
@@ -42,7 +41,7 @@ public unsafe struct LocalKeywordSet
public ulong GetHash64()
{
ulong hash = 14695981039346656037ul; // FNV Offset basis
var hash = 14695981039346656037ul; // FNV Offset basis
for (var i = 0; i < _DATA_ARRAY_LENGTH; i++)
{

View File

@@ -1,13 +1,12 @@
using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.Core.Utilities;
using Ghost.Graphics.Core;
using System.IO.Hashing;
using System.Runtime.InteropServices;
namespace Ghost.Graphics.RHI;
internal static class RHIUtility
public static class RHIUtility
{
public const int MAX_RENDER_TARGETS = 8;

View File

@@ -1,6 +1,6 @@
using Ghost.Core;
namespace Ghost.Graphics.Core;
namespace Ghost.Graphics.RHI;
public readonly struct GPUResource;
public readonly struct Texture;
@@ -20,12 +20,12 @@ public static class ResourceHandleExtensions
return new Handle<GPUResource>(buffer.ID, buffer.Generation);
}
internal static Handle<Texture> AsTexture(this Handle<GPUResource> resource)
public static Handle<Texture> AsTexture(this Handle<GPUResource> resource)
{
return new Handle<Texture>(resource.ID, resource.Generation);
}
internal static Handle<GraphicsBuffer> AsGraphicsBuffer(this Handle<GPUResource> resource)
public static Handle<GraphicsBuffer> AsGraphicsBuffer(this Handle<GPUResource> resource)
{
return new Handle<GraphicsBuffer>(resource.ID, resource.Generation);
}

View File

@@ -1,7 +1,7 @@
using Misaki.HighPerformance.Mathematics;
using System.Runtime.InteropServices;
namespace Ghost.Graphics.Core;
namespace Ghost.Graphics.RHI;
/// <summary>
/// The layout of the root signature is:

View File

@@ -1,18 +1,10 @@
global using static TerraFX.Interop.DirectX.D3D12;
global using static TerraFX.Interop.DirectX.DirectX;
global using static TerraFX.Interop.DirectX.DXGI;
global using static TerraFX.Interop.Windows.Windows;
using Ghost.Core.Attributes;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
[assembly: InternalsVisibleTo("Ghost.Engine")]
[assembly: InternalsVisibleTo("Ghost.Editor")]
[assembly: InternalsVisibleTo("Ghost.Editor.Core")]
[assembly: InternalsVisibleTo("Ghost.Graphics.Test")]
[assembly: InternalsVisibleTo("Ghost.Graphics.Test-Winui")]
[assembly: SupportedOSPlatform("windows10.0.19041.0")]
[assembly: EngineAssembly]

View File

@@ -1,13 +1,11 @@
using Ghost.Core;
using Ghost.Graphics.Core;
using Ghost.Graphics.RenderGraphModule;
using Ghost.Graphics.RHI;
namespace Ghost.Graphics.Contracts;
namespace Ghost.Graphics.Core.Contracts;
public interface IRenderPass
{
void Initialize(ref readonly RenderingContext ctx);
void Build(RenderGraph graph, Identifier<RGTexture> backbuffer);
void Cleanup(IResourceDatabase resourceDatabase);
void Cleanup(IResourceManager resourceManager);
}

View File

@@ -1,5 +1,72 @@
using Ghost.Core;
using Ghost.Graphics.RenderGraphModule;
using Ghost.Graphics.RHI;
namespace Ghost.Graphics.Core;
public class Camera
{
}
private readonly IRenderer _renderer;
private Handle<Texture> _colorTexture;
private Handle<Texture> _depthTexture;
private uint _actualWidth;
private uint _actualHeight;
private uint _virtualWidth;
private uint _virtualHeight;
public IRenderer Renderer => _renderer;
/// <summary>
/// Gets the actual width of the camera's render target in pixels. If upscaler is used, this is the width before upscaling.
/// </summary>
public uint ActualWidth => _actualWidth;
/// <summary>
/// Gets the actual height of the camera's render target in pixels. If upscaler is used, this is the height before upscaling.
/// </summary>
public uint ActualHeight => _actualHeight;
/// <summary>
/// Gets the virtual width of the camera's render target in pixels. If upscaler is used, this is the width after upscaling.
/// </summary>
public uint VirtualWidth => _virtualWidth;
/// <summary>
/// Gets the virtual height of the camera's render target in pixels. If upscaler is used, this is the height after upscaling.
/// </summary>
public uint VirtualHeight => _virtualHeight;
public RenderGraph? RenderGraph
{
get; set;
}
public Camera(IGraphicsEngine graphicsEngine)
{
_renderer = graphicsEngine.CreateRenderer();
}
public Camera(IGraphicsEngine graphicsEngine, RenderGraph renderGraph)
{
_renderer = graphicsEngine.CreateRenderer();
RenderGraph = renderGraph;
_renderer.RenderFunc = DefaultRenderFunc;
}
private Error DefaultRenderFunc(RenderContext context)
{
if (RenderGraph == null)
{
return Error.None;
}
RenderGraph.Reset();
var view = new ViewState(_virtualWidth, _virtualHeight, _actualWidth, _actualHeight);
RenderGraph.Compile(in view);
RenderGraph.Execute(context.CommandBuffer);
return Error.None;
}
}

View File

@@ -1,150 +0,0 @@
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.Mathematics;
using System.Drawing;
using System.Runtime.InteropServices;
using TerraFX.Interop.DirectX;
namespace Ghost.Graphics.Core;
/// <summary>
/// Represents a color with 4 bytes components.
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = 4)]
public struct Color32 : IEquatable<Color32>
{
public byte r;
public byte g;
public byte b;
public byte a;
public Color32(byte r, byte g, byte b, byte a)
{
this.r = r;
this.g = g;
this.b = b;
this.a = a;
}
public Color32(Color color)
: this(color.R, color.G, color.B, color.A)
{
}
public Color32(Color128 color128)
: this((byte)(color128.r * 255.0f), (byte)(color128.g * 255.0f), (byte)(color128.b * 255.0f), (byte)(color128.a * 255.0f))
{
}
public Color32(float4 v)
: this((byte)(v.x * 255.0f), (byte)(v.y * 255.0f), (byte)(v.z * 255.0f), (byte)(v.w * 255.0f))
{
}
public readonly bool Equals(Color32 other)
{
return r == other.r && g == other.g && b == other.b && a == other.a;
}
public override readonly bool Equals(object? obj)
{
return obj is Color32 color && Equals(color);
}
public override readonly int GetHashCode()
{
return HashCode.Combine(r, g, b, a);
}
public static bool operator ==(Color32 left, Color32 right)
{
return left.Equals(right);
}
public static bool operator !=(Color32 left, Color32 right)
{
return !(left == right);
}
}
/// <summary>
/// Represents a color with 16 bytes components.
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = 16)]
public struct Color128 : IEquatable<Color128>
{
public float r;
public float g;
public float b;
public float a;
public Color128(float r, float g, float b, float a)
{
this.r = r;
this.g = g;
this.b = b;
this.a = a;
}
public Color128(Color color)
: this(color.R / 255.0f, color.G / 255.0f, color.B / 255.0f, color.A / 255.0f)
{
}
public Color128(Color32 color32)
: this(color32.r / 255.0f, color32.g / 255.0f, color32.b / 255.0f, color32.a / 255.0f)
{
}
public Color128(float4 v)
: this(v.x, v.y, v.z, v.w)
{
}
public readonly bool Equals(Color128 other)
{
return r.Equals(other.r) && g.Equals(other.g) && b.Equals(other.b) && a.Equals(other.a);
}
public override readonly bool Equals(object? obj)
{
return obj is Color128 color && Equals(color);
}
public readonly override int GetHashCode()
{
return HashCode.Combine(r, g, b, a);
}
public static bool operator ==(Color128 left, Color128 right)
{
return left.Equals(right);
}
public static bool operator !=(Color128 left, Color128 right)
{
return !(left == right);
}
}
[StructLayout(LayoutKind.Sequential)]
public struct Vertex
{
public static class Semantic
{
public const DXGI_FORMAT ALIGNED_FORMAT = DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_FLOAT;
public const int COUNT = 5;
public static readonly FixedText32 Position = new("POSITION");
public static readonly FixedText32 Normal = new("NORMAL");
public static readonly FixedText32 Tangent = new("TANGENT");
public static readonly FixedText32 Uv = new("TEXCOORD");
public static readonly FixedText32 Color = new("COLOR");
}
public float4 position;
public float4 normal;
public float4 tangent;
public float4 uv;
public Color128 color;
}

View File

@@ -65,17 +65,17 @@ public struct Material : IResourceReleasable
get; set;
}
public Error SetShader(Identifier<Shader> shaderId, IResourceAllocator allocator, IResourceDatabase database)
public Error SetShader(Identifier<Shader> shaderId, IResourceManager manager)
{
if (!shaderId.IsValid)
{
return Error.InvalidArgument;
}
_cBufferCache.ReleaseResource(database);
_cBufferCache.ReleaseResource(manager.ResourceDatabase);
_shader = shaderId;
var r = database.GetShaderReference(shaderId);
var r = manager.GetShaderReference(shaderId);
if (r.IsFailure)
{
return r.Error;
@@ -101,7 +101,7 @@ public struct Material : IResourceReleasable
_passPipelineOverride[i] = new PipelineOverride
{
shaderPass = pass.Key,
options = pass.DeafaultState,
options = pass.DefaultState,
};
}
@@ -114,7 +114,7 @@ public struct Material : IResourceReleasable
MemoryType = ResourceMemoryType.Default,
};
var buffer = allocator.CreateBuffer(ref desc, "MaterialCBuffer");
var buffer = manager.ResourceAllocator.CreateBuffer(ref desc, "MaterialCBuffer");
_cBufferCache = new CBufferCache(buffer, shader.CBufferSize);
}
@@ -201,9 +201,9 @@ public struct Material : IResourceReleasable
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Error SetKeyword(IResourceDatabase resourceDatabase, int keywordId, bool enabled)
public Error SetKeyword(IResourceManager manager, int keywordId, bool enabled)
{
var r = resourceDatabase.GetShaderReference(_shader);
var r = manager.GetShaderReference(_shader);
if (r.IsFailure)
{
return r.Error;
@@ -223,9 +223,9 @@ public struct Material : IResourceReleasable
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool IsKeywordEnabled(IResourceDatabase resourceDatabase, int keywordId)
public readonly bool IsKeywordEnabled(IResourceManager manager, int keywordId)
{
var r = resourceDatabase.GetShaderReference(_shader);
var r = manager.GetShaderReference(_shader);
if (r.IsFailure)
{
return false;

View File

@@ -1,5 +1,4 @@
using Ghost.Core;
using Ghost.Graphics.Contracts;
using Ghost.Graphics.RHI;
namespace Ghost.Graphics.Core;

View File

@@ -1,5 +1,4 @@
using Ghost.Core;
using Ghost.Graphics.Contracts;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;

View File

@@ -7,24 +7,6 @@ using System.Runtime.InteropServices;
namespace Ghost.Graphics.Core;
public readonly struct ShaderPass
{
public Key64<ShaderPass> Key
{
get; init;
}
public PipelineState DeafaultState
{
get; init;
}
public LocalKeywordSet KeywordIDs
{
get; init;
}
}
public struct ShaderProperty;
public partial struct Shader
@@ -146,7 +128,7 @@ public partial struct Shader : IResourceReleasable
_shaderPasses[i] = new ShaderPass
{
Key = passKey,
DeafaultState = pass.localPipeline,
DefaultState = pass.localPipeline,
KeywordIDs = keywords,
};

View File

@@ -1,139 +0,0 @@
using Ghost.Core;
using Ghost.Graphics.RHI;
using Ghost.Graphics.Core;
using Ghost.Graphics.RenderPasses;
using Ghost.Graphics.Contracts;
using Ghost.Graphics.RenderGraphModule;
namespace Ghost.Graphics.D3D12;
/// <summary>
/// D3D12 implementation of the renderer interface using RHI abstractions
/// </summary>
internal class D3D12Renderer : IRenderer
{
private readonly D3D12GraphicsEngine _graphicsEngine;
private readonly D3D12ResourceDatabase _resourceDatabase;
private readonly ICommandBuffer _commandBuffer;
private readonly RenderGraph _renderGraph;
private uint _frameIndex;
private bool _disposed;
// NOTE: Testing only.
private readonly MeshRenderPass _pass;
public IRenderOutput? RenderOutput
{
get; set;
}
// TODO: Add render graph support
public D3D12Renderer(D3D12GraphicsEngine graphicsEngine, D3D12ResourceDatabase resourceDatabase)
{
_graphicsEngine = graphicsEngine;
_resourceDatabase = resourceDatabase;
_commandBuffer = _graphicsEngine.CreateCommandBuffer(CommandBufferType.Graphics);
_renderGraph = new RenderGraph(_graphicsEngine);
// NOTE: Testing only.
_pass = new();
}
~D3D12Renderer()
{
Dispose();
}
public Result Render(ICommandAllocator commandAllocator)
{
if (RenderOutput is null)
{
return Result.Failure("Render target strategy is not set.");
}
var target = RenderOutput.GetRenderTarget();
if (target.IsInvalid)
{
return Result.Failure("Render target is invalid.");
}
_commandBuffer.Begin(commandAllocator);
RenderOutput.BeginRender(_commandBuffer);
// NOTE: Temporary solution: render directly to the swap chain back buffer if available.
// HACK: This is hard coded for testing purposes only.
var error = RenderScene(target, RenderOutput.Viewport, RenderOutput.Scissor);
if (error != Error.None)
{
_commandBuffer.End();
return Result.Failure(error);
}
RenderOutput.EndRender(_commandBuffer);
var r = _commandBuffer.End();
if (r.IsFailure)
{
return r;
}
_graphicsEngine.Device.GraphicsQueue.Submit(_commandBuffer);
RenderOutput.Present();
return Result.Success();
}
// TODO: A proper render graph integration.
private Error RenderScene(Handle<Texture> target, ViewportDesc viewport, RectDesc rect)
{
// NOTE: Testing only.
var ctx = new RenderingContext(_graphicsEngine, _commandBuffer);
if (_frameIndex == 0)
{
_pass.Initialize(ref ctx);
}
//_commandBuffer.BeginRenderPass(rtDesc, depthDesc, false);
_commandBuffer.SetViewport(viewport);
_commandBuffer.SetScissorRect(rect);
_renderGraph.Reset();
var backBuffer = _renderGraph.ImportTexture(target, "Back Buffer");
_pass.Build(_renderGraph, backBuffer);
// Create view state from viewport
var viewState = new ViewState((uint)viewport.Width, (uint)viewport.Height);
// Compile with view state
_renderGraph.Compile(in viewState);
_renderGraph.Execute(_commandBuffer);
//_commandBuffer.EndRenderPass();
_frameIndex++;
return Error.None;
}
public void Dispose()
{
if (_disposed)
{
return;
}
// NOTE: Testing only.
_pass.Cleanup(_resourceDatabase);
_renderGraph.Dispose();
_commandBuffer.Dispose();
_disposed = true;
GC.SuppressFinalize(this);
}
}

View File

@@ -1,24 +0,0 @@
using System.Runtime.CompilerServices;
using TerraFX.Interop.DirectX;
using Ghost.Graphics.Core;
namespace Ghost.Graphics.D3D12.Utilities;
internal unsafe static class D3D12PipelineResource
{
private readonly static D3D12_INPUT_ELEMENT_DESC[] s_inputElementDescs = [
new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Vertex.Semantic.Position.GetUnsafePtr(), SemanticIndex = 0u, Format = Vertex.Semantic.ALIGNED_FORMAT, InputSlot = 0u, AlignedByteOffset = 0u, InputSlotClass = D3D12_INPUT_CLASSIFICATION.D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 },
new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Vertex.Semantic.Normal.GetUnsafePtr(), SemanticIndex = 0u, Format = Vertex.Semantic.ALIGNED_FORMAT, InputSlot = 0u, AlignedByteOffset = 16u, InputSlotClass = D3D12_INPUT_CLASSIFICATION.D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 },
new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Vertex.Semantic.Tangent.GetUnsafePtr(), SemanticIndex = 0u, Format = Vertex.Semantic.ALIGNED_FORMAT, InputSlot = 0u, AlignedByteOffset = 32u, InputSlotClass = D3D12_INPUT_CLASSIFICATION.D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 },
new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Vertex.Semantic.Uv.GetUnsafePtr(), SemanticIndex = 0u, Format = Vertex.Semantic.ALIGNED_FORMAT, InputSlot = 0u, AlignedByteOffset = 48u, InputSlotClass = D3D12_INPUT_CLASSIFICATION.D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 },
new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Vertex.Semantic.Color.GetUnsafePtr(), SemanticIndex = 0u, Format = Vertex.Semantic.ALIGNED_FORMAT, InputSlot = 0u, AlignedByteOffset = 64u, InputSlotClass = D3D12_INPUT_CLASSIFICATION.D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 },
];
public const DXGI_FORMAT SWAP_CHAIN_BACK_BUFFER_FORMAT = DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM;
public static D3D12_INPUT_LAYOUT_DESC InputLayoutDescription => new()
{
pInputElementDescs = (D3D12_INPUT_ELEMENT_DESC*)Unsafe.AsPointer(ref s_inputElementDescs[0]),
NumElements = (uint)s_inputElementDescs.Length
};
}

View File

@@ -9,6 +9,7 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>embedded</DebugType>
<IsAotCompatible>True</IsAotCompatible>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
@@ -16,27 +17,19 @@
<IsTrimmable>True</IsTrimmable>
</PropertyGroup>
<ItemGroup>
<Content Include="runtimes\win-x64\native\dxcompiler.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="runtimes\win-x64\native\dxil.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Misaki.HighPerformance.Analyzer" Version="1.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Misaki.HighPerformance.Image" Version="1.1.0" />
<PackageReference Include="TerraFX.Interop.D3D12MemoryAllocator" Version="3.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../Runtime/Ghost.Core/Ghost.Core.csproj" />
<ProjectReference Include="../../Editor/Ghost.DSL/Ghost.DSL.csproj" />
<ProjectReference Include="..\..\Runtime\Ghost.Core\Ghost.Core.csproj" />
<ProjectReference Include="..\..\Editor\Ghost.DSL\Ghost.DSL.csproj" />
<ProjectReference Include="..\Ghost.Graphics.D3D12\Ghost.Graphics.D3D12.csproj" />
<ProjectReference Include="..\Ghost.Graphics.RHI\Ghost.Graphics.RHI.csproj" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,5 +1,4 @@
using Ghost.Core;
using Ghost.Graphics.Core;
using Ghost.Graphics.RHI;
namespace Ghost.Graphics.RenderGraphModule;
@@ -10,7 +9,7 @@ namespace Ghost.Graphics.RenderGraphModule;
/// </summary>
public sealed class RenderGraph : IDisposable
{
private readonly IGraphicsEngine _graphicsEngine;
private readonly IResourceManager _resourceManager;
private readonly RenderGraphObjectPool _objectPool;
private readonly RenderGraphResourceRegistry _resources;
@@ -31,16 +30,15 @@ public sealed class RenderGraph : IDisposable
private readonly RenderGraphExecutor _executor;
private readonly RenderGraphNativePassBuilder _nativePassBuilder;
private readonly RenderGraphBlackboard _blackboard;
private bool _compiled;
public RenderGraphBlackboard Blackboard
{
get;
}
public RenderGraphBlackboard Blackboard => _blackboard;
public RenderGraph(IGraphicsEngine graphicsEngine)
public RenderGraph(IResourceManager resourceManager, IPipelineLibrary pipelineLibrary, IShaderCompiler shaderCompiler)
{
_graphicsEngine = graphicsEngine;
_resourceManager = resourceManager;
_objectPool = new RenderGraphObjectPool();
_resources = new RenderGraphResourceRegistry(_objectPool);
@@ -50,40 +48,32 @@ public sealed class RenderGraph : IDisposable
_nativePasses = new List<NativeRenderPass>(32);
_builder = new RenderGraphBuilder();
_aliasingManager = new ResourceAliasingManager(graphicsEngine.ResourceAllocator, _objectPool);
_aliasingManager = new ResourceAliasingManager(resourceManager.ResourceAllocator, _objectPool);
_compilationCache = new RenderGraphCompilationCache();
_context = new RenderGraphContext(
_graphicsEngine.ResourceDatabase,
_graphicsEngine.PipelineLibrary,
_graphicsEngine.ShaderCompiler,
resourceManager,
pipelineLibrary,
shaderCompiler,
_resources
);
_nativePassBuilder = new RenderGraphNativePassBuilder(_objectPool, _resources);
_compiler = new RenderGraphCompiler(_graphicsEngine, _resources, _aliasingManager, _nativePassBuilder, _compilationCache);
_executor = new RenderGraphExecutor(_graphicsEngine, _resources, _context);
_compiler = new RenderGraphCompiler(resourceManager, _resources, _aliasingManager, _nativePassBuilder, _compilationCache);
_executor = new RenderGraphExecutor(resourceManager, _resources, _context);
Blackboard = new RenderGraphBlackboard();
_blackboard = new RenderGraphBlackboard();
}
/// <summary>
/// Resets the render graph for a new frame.
/// Reuses existing allocations to minimize GC.
/// </summary>
public void Reset()
{
// Clear blackboard data
Blackboard.Clear();
// Reset resources but keep allocations
_resources.Reset();
// Reset aliasing manager
_aliasingManager.Reset();
// Clear compiled barriers
_blackboard.Clear();
_resources.Clear();
_aliasingManager.Clear();
_compiledBarriers.Clear();
// Return passes to the pool and reset count
@@ -94,11 +84,8 @@ public sealed class RenderGraph : IDisposable
}
_passes.Clear();
// Clear compiled passes list
_compiledPasses.Clear();
// Return native passes to pool
for (var i = 0; i < _nativePasses.Count; i++)
{
_objectPool.Return(_nativePasses[i]);
@@ -117,14 +104,14 @@ public sealed class RenderGraph : IDisposable
Color128 clearColor = default, float clearDepth = 1.0f, byte clearStencil = 0,
bool clearAtFirstUse = true, bool discardAtLastUse = true)
{
var r = _graphicsEngine.ResourceDatabase.GetResourceDescription(texture.AsResource());
var r = _resourceManager.ResourceDatabase.GetResourceDescription(texture.AsResource());
if (r.IsFailure)
{
return Identifier<RGTexture>.Invalid;
}
var desc = r.Value;
return _resources.ImportTexture(in desc._desc.textureDescription, texture, name, clearColor, clearDepth, clearStencil, clearAtFirstUse, discardAtLastUse);
return _resources.ImportTexture(in desc.TextureDescription, texture, name, clearColor, clearDepth, clearStencil, clearAtFirstUse, discardAtLastUse);
}
/// <summary>
@@ -134,14 +121,14 @@ public sealed class RenderGraph : IDisposable
/// <returns>The identifier of the imported render graph buffer. Invalid if import fails.</returns>
public Identifier<RGBuffer> ImportBuffer(Handle<GraphicsBuffer> buffer, string name)
{
var r = _graphicsEngine.ResourceDatabase.GetResourceDescription(buffer.AsResource());
var r = _resourceManager.ResourceDatabase.GetResourceDescription(buffer.AsResource());
if (r.IsFailure)
{
return Identifier<RGBuffer>.Invalid;
}
var desc = r.Value;
return _resources.ImportBuffer(in desc._desc.bufferDescription, buffer, name);
return _resources.ImportBuffer(in desc.BufferDescription, buffer, name);
}
public IRasterRenderGraphBuilder AddRasterRenderPass<TPassData>(string name, out TPassData passData)
@@ -199,7 +186,7 @@ public sealed class RenderGraph : IDisposable
// Compute structural hash for caching
var graphHash = RenderGraphHasher.ComputeGraphHash(_passes, _resources);
// Delegate to compiler
_compiler.Compile(in viewState, graphHash, _passes, _compiledPasses, _nativePasses, _compiledBarriers);
_compiled = true;

View File

@@ -326,7 +326,7 @@ internal sealed class ResourceAliasingManager
_logicalToPlaced = new Dictionary<int, int>(64);
}
public void Reset()
public void Clear()
{
for (var i = 0; i < _placedResources.Count; i++)
{

View File

@@ -74,7 +74,7 @@ internal sealed class ResourceStateTracker
/// <summary>
/// Represents a compiled barrier with only the target state.
/// The before state is always queried from ResourceDatabase at execution time.
/// The before state is always queried from ResourceManager at execution time.
/// </summary>
internal struct CompiledBarrier
{
@@ -208,7 +208,7 @@ internal static class RenderGraphBarriers
/// <summary>
/// Compiles implicit state transitions for all resources accessed by a pass.
/// Stores only the target state - the before state will be queried from ResourceDatabase at execution time.
/// Stores only the target state - the before state will be queried from ResourceManager at execution time.
/// </summary>
private static void CompileImplicitTransitions(
RenderGraphPassBase pass,

View File

@@ -1,5 +1,4 @@
using Ghost.Core;
using Ghost.Graphics.Core;
using Ghost.Graphics.RHI;
using System.Diagnostics.CodeAnalysis;
@@ -23,7 +22,7 @@ internal sealed class CachedCompilation
// Placed resource metadata
public readonly List<PlacedResourceData> placedResources = new(32);
// Compiled barriers (stores only target states, queries before state from ResourceDatabase)
// Compiled barriers (stores only target states, queries before state from ResourceManager)
public readonly List<CompiledBarrier> compiledBarriers = new(128);
// Real gpu resource

View File

@@ -1,5 +1,4 @@
using Ghost.Core;
using Ghost.Graphics.Core;
using Ghost.Graphics.RHI;
namespace Ghost.Graphics.RenderGraphModule;
@@ -10,7 +9,7 @@ namespace Ghost.Graphics.RenderGraphModule;
/// </summary>
internal sealed class RenderGraphCompiler
{
private readonly IGraphicsEngine _graphicsEngine;
private readonly IResourceManager _resourceManager;
private readonly RenderGraphResourceRegistry _resources;
private readonly ResourceAliasingManager _aliasingManager;
private readonly RenderGraphNativePassBuilder _nativePassBuilder;
@@ -19,13 +18,13 @@ internal sealed class RenderGraphCompiler
private Handle<GPUResource> _resourceHeap;
public RenderGraphCompiler(
IGraphicsEngine graphicsEngine,
IResourceManager resourceManager,
RenderGraphResourceRegistry resources,
ResourceAliasingManager aliasingManager,
RenderGraphNativePassBuilder nativePassBuilder,
RenderGraphCompilationCache compilationCache)
{
_graphicsEngine = graphicsEngine;
_resourceManager = resourceManager;
_resources = resources;
_aliasingManager = aliasingManager;
_nativePassBuilder = nativePassBuilder;
@@ -212,10 +211,10 @@ internal sealed class RenderGraphCompiler
continue;
}
_graphicsEngine.ResourceDatabase.ScheduleReleaseResource(res.backingResource);
_resourceManager.ResourceDatabase.ScheduleReleaseResource(res.backingResource);
}
_graphicsEngine.ResourceDatabase.ScheduleReleaseResource(_resourceHeap);
_resourceManager.ResourceDatabase.ScheduleReleaseResource(_resourceHeap);
}
if (_aliasingManager.Heap.size == 0)
@@ -231,7 +230,7 @@ internal sealed class RenderGraphCompiler
HeapType = HeapType.Default
};
_resourceHeap = _graphicsEngine.ResourceAllocator.Allocate(in allocationDesc, "RenderGraphResourceHeap");
_resourceHeap = _resourceManager.ResourceAllocator.Allocate(in allocationDesc, "RenderGraphResourceHeap");
for (var i = 0; i < _resources.Resources.Count; i++)
{
@@ -253,11 +252,11 @@ internal sealed class RenderGraphCompiler
if (res.type == RenderGraphResourceType.Texture)
{
var textureDesc = res.rgTextureDesc.ToTextureDesc(res.resolvedWidth, res.resolvedHeight);
res.backingResource = _graphicsEngine.ResourceAllocator.CreateTexture(in textureDesc, res.name, ops).AsResource();
res.backingResource = _resourceManager.ResourceAllocator.CreateTexture(in textureDesc, res.name, ops).AsResource();
}
else if (res.type == RenderGraphResourceType.Buffer)
{
res.backingResource = _graphicsEngine.ResourceAllocator.CreateBuffer(in res.bufferDesc, res.name, ops).AsResource();
res.backingResource = _resourceManager.ResourceAllocator.CreateBuffer(in res.bufferDesc, res.name, ops).AsResource();
}
else
{
@@ -380,11 +379,11 @@ internal sealed class RenderGraphCompiler
{
if (!res.isImported)
{
_graphicsEngine.ResourceDatabase.ScheduleReleaseResource(res.backingResource);
_resourceManager.ResourceDatabase.ScheduleReleaseResource(res.backingResource);
}
}
_graphicsEngine.ResourceDatabase.ScheduleReleaseResource(_resourceHeap);
_resourceManager.ResourceDatabase.ScheduleReleaseResource(_resourceHeap);
_resourceHeap = Handle<GPUResource>.Invalid;
}
}

View File

@@ -1,5 +1,4 @@
using Ghost.Core;
using Ghost.Graphics.Contracts;
using Ghost.Graphics.Core;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.Mathematics;
@@ -8,7 +7,7 @@ namespace Ghost.Graphics.RenderGraphModule;
public interface IRenderGraphContext
{
IResourceDatabase ResourceDatabase { get; }
IResourceManager ResourceManager { get; }
Handle<GPUResource> GetActualResource(Identifier<RGResource> resource);
Handle<Texture> GetActualTexture(Identifier<RGTexture> texture);
@@ -38,7 +37,7 @@ public interface IUnsafeRenderContext : IRasterRenderContext, IRenderGraphContex
internal sealed class RenderGraphContext : IRasterRenderContext, IComputeRenderContext, IUnsafeRenderContext
{
private readonly IResourceDatabase _resourceDatabase;
private readonly IResourceManager _resourceManager;
private readonly IPipelineLibrary _pipelineLibrary;
private readonly IShaderCompiler _shaderCompiler;
private readonly RenderGraphResourceRegistry _resources;
@@ -53,15 +52,15 @@ internal sealed class RenderGraphContext : IRasterRenderContext, IComputeRenderC
private Handle<GraphicsBuffer> _activePerMeshData;
private int _activeMeshIndexCount;
public IResourceDatabase ResourceDatabase => _resourceDatabase;
public IResourceManager ResourceManager => _resourceManager;
public int ActiveMeshIndexCount => _activeMeshIndexCount;
public ICommandBuffer CommandBuffer => _commandBuffer;
internal RenderGraphContext(IResourceDatabase resourceDatabase, IPipelineLibrary pipelineLibrary, IShaderCompiler shaderCompiler, RenderGraphResourceRegistry resources)
internal RenderGraphContext(IResourceManager resourceManager, IPipelineLibrary pipelineLibrary, IShaderCompiler shaderCompiler, RenderGraphResourceRegistry resources)
{
_resourceDatabase = resourceDatabase;
_resourceManager = resourceManager;
_pipelineLibrary = pipelineLibrary;
_shaderCompiler = shaderCompiler;
_resources = resources;
@@ -103,7 +102,7 @@ internal sealed class RenderGraphContext : IRasterRenderContext, IComputeRenderC
public void SetActiveMaterial(Handle<Material> material)
{
var r = _resourceDatabase.GetMaterialReference(material);
var r = _resourceManager.GetMaterialReference(material);
if (r.IsFailure)
{
_activePerMaterialData = Handle<GraphicsBuffer>.Invalid;
@@ -116,7 +115,7 @@ internal sealed class RenderGraphContext : IRasterRenderContext, IComputeRenderC
public void SetActiveMaterial(ref readonly Material material)
{
var shaderResult = _resourceDatabase.GetShaderReference(material.Shader);
var shaderResult = _resourceManager.GetShaderReference(material.Shader);
if (shaderResult.IsFailure)
{
_activePerMaterialData = Handle<GraphicsBuffer>.Invalid;
@@ -161,7 +160,7 @@ internal sealed class RenderGraphContext : IRasterRenderContext, IComputeRenderC
public void SetActiveMesh(Handle<Mesh> mesh)
{
var r = _resourceDatabase.GetMeshReference(mesh);
var r = _resourceManager.GetMeshReference(mesh);
if (r.IsFailure)
{
_activePerMeshData = Handle<GraphicsBuffer>.Invalid;
@@ -184,8 +183,8 @@ internal sealed class RenderGraphContext : IRasterRenderContext, IComputeRenderC
// TODO: Global and view constants
var data = new PushConstantsData
{
objectIndex = _resourceDatabase.GetBindlessIndex(_activePerMeshData.AsResource()),
materialIndex = _resourceDatabase.GetBindlessIndex(_activePerMaterialData.AsResource()),
objectIndex = _resourceManager.ResourceDatabase.GetBindlessIndex(_activePerMeshData.AsResource()),
materialIndex = _resourceManager.ResourceDatabase.GetBindlessIndex(_activePerMaterialData.AsResource()),
};
var pushConstantSpan = new ReadOnlySpan<uint>(&data, sizeof(PushConstantsData) / sizeof(uint));

View File

@@ -1,5 +1,4 @@
using Ghost.Core;
using Ghost.Graphics.Core;
using Ghost.Graphics.RHI;
namespace Ghost.Graphics.RenderGraphModule;
@@ -9,16 +8,16 @@ namespace Ghost.Graphics.RenderGraphModule;
/// </summary>
internal sealed class RenderGraphExecutor
{
private readonly IGraphicsEngine _graphicsEngine;
private readonly IResourceManager _resourceManager;
private readonly RenderGraphResourceRegistry _resources;
private readonly RenderGraphContext _context;
public RenderGraphExecutor(
IGraphicsEngine graphicsEngine,
IResourceManager resourceManager,
RenderGraphResourceRegistry resources,
RenderGraphContext context)
{
_graphicsEngine = graphicsEngine;
_resourceManager = resourceManager;
_resources = resources;
_context = context;
}
@@ -130,7 +129,7 @@ internal sealed class RenderGraphExecutor
/// <summary>
/// Executes all barriers for a specific pass.
/// Uses pre-compiled barriers and queries before state from ResourceDatabase.
/// Uses pre-compiled barriers and queries before state from ResourceManager.
/// </summary>
private unsafe void ExecuteBarriersForPass(
ICommandBuffer cmd,
@@ -158,8 +157,8 @@ internal sealed class RenderGraphExecutor
var resource = _resources.GetResource(compiledBarrier.Resource);
var resourceHandle = resource.backingResource;
// Always query the before state from ResourceDatabase (single source of truth)
var currentState = _graphicsEngine.ResourceDatabase.GetResourceBarrierData(resourceHandle).GetValueOrThrow();
// Always query the before state from ResourceManager (single source of truth)
var currentState = _resourceManager.ResourceDatabase.GetResourceBarrierData(resourceHandle).GetValueOrThrow();
BarrierLayout layoutBefore;
BarrierAccess accessBefore;
@@ -169,7 +168,7 @@ internal sealed class RenderGraphExecutor
if (compiledBarrier.AliasingPredecessor.IsValid)
{
var predHandle = _resources.GetResource(compiledBarrier.AliasingPredecessor).backingResource;
var predState = _graphicsEngine.ResourceDatabase.GetResourceBarrierData(predHandle).GetValueOrThrow();
var predState = _resourceManager.ResourceDatabase.GetResourceBarrierData(predHandle).GetValueOrThrow();
layoutBefore = BarrierLayout.Undefined;
accessBefore = BarrierAccess.NoAccess;

View File

@@ -168,7 +168,7 @@ internal sealed class RenderGraphResourceRegistry
}
}
public void Reset()
public void Clear()
{
// Return all resources to pool
for (var i = 0; i < _resources.Count; i++)

View File

@@ -36,16 +36,23 @@ public struct ViewState : IEquatable<ViewState>
{
public uint viewportWidth;
public uint viewportHeight;
// For upscalers that need to know the original render target size before upscaling
public uint actualWidth;
public uint actualHeight;
public ViewState(uint width, uint height)
public ViewState(uint width, uint height, uint actualWidth, uint actualHeight)
{
viewportWidth = width;
viewportHeight = height;
this.actualWidth = actualWidth;
this.actualHeight = actualHeight;
}
public readonly bool Equals(ViewState other)
{
return viewportWidth == other.viewportWidth && viewportHeight == other.viewportHeight;
return viewportWidth == other.viewportWidth && viewportHeight == other.viewportHeight
&& actualWidth == other.actualWidth && actualHeight == other.actualHeight;
}
public override readonly bool Equals(object? obj)

View File

@@ -1,8 +1,8 @@
using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.DSL.ShaderCompiler;
using Ghost.Graphics.Contracts;
using Ghost.Graphics.Core;
using Ghost.Graphics.Core.Contracts;
using Ghost.Graphics.RenderGraphModule;
using Ghost.Graphics.RHI;
using Ghost.Graphics.Utilities;
@@ -159,7 +159,7 @@ internal class MeshRenderPass : IRenderPass
}
else
{
var shaderResult = ctx.ResourceDatabase.GetShaderReference(_shader);
var shaderResult = ctx.ResourceManager.GetShaderReference(_shader);
if (shaderResult.IsFailure)
{
throw new InvalidOperationException("Failed to get shader reference.");
@@ -201,7 +201,6 @@ internal class MeshRenderPass : IRenderPass
{
using var stream = File.OpenRead(_textureFiles[i]);
using var imageData = ImageResult.FromStream(stream, ColorComponents.RGBA);
var desc = new TextureDesc
{
Width = imageData.Width,
@@ -227,7 +226,7 @@ internal class MeshRenderPass : IRenderPass
_sampler = ctx.ResourceAllocator.CreateSampler(in samplerDesc);
var meshResult = ctx.ResourceDatabase.GetMaterialReference(_material);
var meshResult = ctx.ResourceManager.GetMaterialReference(_material);
if (meshResult.IsFailure)
{
throw new InvalidOperationException("Failed to get material reference.");
@@ -283,7 +282,7 @@ internal class MeshRenderPass : IRenderPass
builder.SetRenderFunc<BlitPassData>(static (data, ctx) =>
{
var r = ctx.ResourceDatabase.GetMaterialReference(data.blitMaterial);
var r = ctx.ResourceManager.GetMaterialReference(data.blitMaterial);
if (r.IsFailure)
{
return;
@@ -292,12 +291,12 @@ internal class MeshRenderPass : IRenderPass
ref var matRef = ref r.Value;
var blitProps = new ShaderProperties_Hidden_Blit
{
mainTex = ctx.ResourceDatabase.GetBindlessIndex(ctx.GetActualResource(data.source.AsResource())),
mainTex = ctx.ResourceManager.ResourceDatabase.GetBindlessIndex(ctx.GetActualResource(data.source.AsResource())),
sampler_mainTex = (uint)data.sampler.Value,
};
matRef.SetPropertyCache(in blitProps).ThrowIfFailed();
matRef.UploadData(ctx.CommandBuffer, ctx.ResourceDatabase);
matRef.UploadData(ctx.CommandBuffer, ctx.ResourceManager.ResourceDatabase);
ctx.CommandBuffer.SetRenderTargets([ctx.GetActualTexture(data.destination)], Handle<Texture>.Invalid);
@@ -308,20 +307,20 @@ internal class MeshRenderPass : IRenderPass
}
}
public void Cleanup(IResourceDatabase resourceDatabase)
public void Cleanup(IResourceManager resourceManager)
{
resourceDatabase.ReleaseMaterial(_blitMaterial);
resourceManager.ReleaseMaterial(_blitMaterial);
resourceDatabase.ReleaseMaterial(_material);
resourceDatabase.ReleaseShader(_shader);
resourceDatabase.ReleaseMesh(_mesh);
resourceDatabase.ReleaseSampler(_sampler);
resourceManager.ReleaseMaterial(_material);
resourceManager.ReleaseShader(_shader);
resourceManager.ReleaseMesh(_mesh);
resourceManager.ResourceDatabase.ReleaseSampler(_sampler);
if (_textures != null)
{
foreach (var texture in _textures)
{
resourceDatabase.ScheduleReleaseResource(texture.AsResource());
resourceManager.ResourceDatabase.ScheduleReleaseResource(texture.AsResource());
}
}
}

View File

@@ -1,4 +1,5 @@
using Ghost.Core;
using Ghost.Graphics.D3D12;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.Mathematics;
using System.Collections.Concurrent;
@@ -23,50 +24,6 @@ public struct RenderingConfig
}
}
public interface IFenceSynchronizer
{
uint CPUFenceValue
{
get;
}
uint GPUFenceValue
{
get;
}
uint FrameIndex
{
get;
}
uint MaxFrameLatency
{
get;
}
bool WaitForGPUReady(int timeOut = -1);
void SignalCPUReady();
void WaitIdle();
}
public interface IRenderSystem : IFenceSynchronizer, IDisposable
{
IGraphicsEngine GraphicsEngine
{
get;
}
bool IsRunning
{
get;
}
void Start();
void Stop();
void RequestSwapChainResize(ISwapChain swapChain, uint2 newSize);
}
/// <summary>
/// Application-level render system that orchestrates multiple renderers
/// and handles frame synchronization
@@ -130,11 +87,25 @@ internal class RenderSystem : IRenderSystem
public RenderSystem(RenderingConfig config)
{
_config = config;
_graphicsEngine = config.GraphicsAPI switch
switch (config.GraphicsAPI)
{
GraphicsAPI.Direct3D12 => new D3D12.D3D12GraphicsEngine(this),
_ => throw new NotSupportedException($"Graphics API {config.GraphicsAPI} is not supported.")
};
case GraphicsAPI.Direct3D12:
if (OperatingSystem.IsWindowsVersionAtLeast(10, 0, 19041))
{
_graphicsEngine = D3D12GraphicsEngineFactory.Create(this);
}
else
{
// TODO: Fallback to Vulkan once it's implemented.
throw new PlatformNotSupportedException("Direct3D12 requires Windows 10 version 2004 (build 19041) or later.");
}
break;
default:
throw new NotSupportedException($"The specified graphics API '{config.GraphicsAPI}' is not supported.");
}
// Create frame resources for synchronization
_frameResources = new FrameResource[config.FrameBufferCount];

View File

@@ -0,0 +1,330 @@
using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.Graphics.Core;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
namespace Ghost.Graphics;
public interface IResourceManager
{
IResourceAllocator ResourceAllocator
{
get;
}
IResourceDatabase ResourceDatabase
{
get;
}
/// <summary>
/// Creates a new mesh from the specified vertex and index data.
/// </summary>
/// <param name="vertices">A UnsafeList containing the vertices that define the geometry of the mesh. Must contain at least one vertex.</param>
/// <param name="indices">A UnsafeList containing the indices that specify how vertices are connected to form primitives. Must contain at least one index.</param>
/// <returns>An <see cref="Identifier{Mesh}"/> representing the newly created mesh.</returns>
Handle<Mesh> CreateMesh(UnsafeList<Vertex> vertices, UnsafeList<uint> indices);
/// <summary>
/// Creates a new material instance using the specified shader.
/// </summary>
/// <param name="shader">The identifier of the shader to associate with the new material.</param>
/// <returns>An <see cref="Identifier{Material}"/> representing the newly created material.</returns>
Handle<Material> CreateMaterial(Identifier<Shader> shader);
/// <summary>
/// Creates a new shader and returns its unique identifier.
/// </summary>
/// <returns>An <see cref="Identifier{Shader}"/> representing the newly created shader.</returns>
/// <param name="descriptor">The viewGroup containing the shader's properties and passes.</param>
Identifier<Shader> CreateGraphicsShader(ShaderDescriptor descriptor);
/// <summary>
/// Determines whether a mesh with the specified Handle exists.
/// </summary>
/// <param name="handle">The handle of the mesh to check for existence. Cannot be null.</param>
/// <returns>true if a mesh with the specified Handle exists; otherwise, false.</returns>
bool HasMesh(Handle<Mesh> handle);
/// <summary>
/// Returns a reference to the mesh associated with the specified handle.
/// </summary>
/// <param name="handle">The handle of the mesh to retrieve. Must refer to a valid mesh; otherwise, the behavior is undefined.</param>
/// <returns>A result containing a reference to the mesh corresponding to the specified handle, or an error status if the handle is invalid.</returns>
RefResult<Mesh, Error> GetMeshReference(Handle<Mesh> handle);
/// <summary>
/// Releases the mesh resource associated with the specified handle, freeing any resources held by it. Includes both CPU and GPU resources.
/// </summary>
/// <param name="handle">The handle of the mesh to release. Must refer to a mesh that was previously created and not already released.</param>
void ReleaseMesh(Handle<Mesh> handle);
/// <summary>
/// Determines whether a material with the specified handle exists in the collection.
/// </summary>
/// <param name="handle">The handle of the material to check for existence.</param>
/// <returns>true if a material with the specified handle exists; otherwise, false.</returns>
bool HasMaterial(Handle<Material> handle);
/// <summary>
/// Gets a reference to the material associated with the specified handle.
/// </summary>
/// <param name="handle">The handle of the material to retrieve. Must refer to a valid material.</param>
/// <returns>A result containing a reference to the material corresponding to the specified handle, or an error status if the handle is invalid.</returns>
RefResult<Material, Error> GetMaterialReference(Handle<Material> handle);
/// <summary>
/// Releases the material associated with the specified handle, making it available for reuse or disposal.
/// </summary>
/// <param name="handle">The handle of the material to release. Must refer to a material that has been previously acquired.</param>
void ReleaseMaterial(Handle<Material> handle);
/// <summary>
/// Determines whether a shader with the specified identifier exists in the collection.
/// </summary>
/// <param name="id">The identifier of the shader to check for existence.</param>
/// <returns>true if a shader with the specified identifier exists; otherwise, false.</returns>
bool HasShader(Identifier<Shader> id);
/// <summary>
/// Returns a reference to the shader associated with the specified identifier.
/// </summary>
/// <param name="id">The identifier of the shader to retrieve. Must refer to a valid shader.</param>
/// <returns>A result containing a reference to the shader corresponding to the specified identifier, or an error status if the identifier is invalid.</returns>
RefResult<Shader, Error> GetShaderReference(Identifier<Shader> id);
/// <summary>
/// Releases the shader associated with the specified identifier, freeing any resources allocated to it.
/// </summary>
/// <param name="id">The identifier of the shader to release. Must refer to a valid, previously created shader.</param>
void ReleaseShader(Identifier<Shader> id);
}
internal sealed class ResourceManager : IResourceManager, IDisposable
{
private readonly IResourceAllocator _resourceAllocator;
private readonly IResourceDatabase _resourceDatabase;
private UnsafeSlotMap<Mesh> _meshes;
private UnsafeSlotMap<Material> _materials;
private UnsafeList<Shader> _shaders; // TODO: Use SlotMap?
private bool _disposed;
public IResourceAllocator ResourceAllocator => _resourceAllocator;
public IResourceDatabase ResourceDatabase => _resourceDatabase;
public ResourceManager(IResourceAllocator resourceAllocator, IResourceDatabase resourceDatabase)
{
_resourceAllocator = resourceAllocator;
_resourceDatabase = resourceDatabase;
_meshes = new UnsafeSlotMap<Mesh>(64, Allocator.Persistent);
_materials = new UnsafeSlotMap<Material>(16, Allocator.Persistent);
_shaders = new UnsafeList<Shader>(16, Allocator.Persistent);
}
~ResourceManager()
{
Dispose();
}
public unsafe Handle<Mesh> CreateMesh(UnsafeList<Vertex> vertices, UnsafeList<uint> indices)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var vertexBufferDesc = new BufferDesc
{
Size = (uint)(vertices.Count * sizeof(Vertex)),
Stride = (uint)sizeof(Vertex),
Usage = BufferUsage.Vertex | BufferUsage.ShaderResource | BufferUsage.Raw,
MemoryType = ResourceMemoryType.Default,
};
var indexBufferDesc = new BufferDesc
{
Size = (uint)(indices.Count * sizeof(uint)),
Stride = sizeof(uint),
Usage = BufferUsage.Index | BufferUsage.ShaderResource | BufferUsage.Raw,
MemoryType = ResourceMemoryType.Default,
};
var objectBufferDesc = new BufferDesc
{
Size = (uint)sizeof(PerObjectData),
Stride = (uint)sizeof(PerObjectData),
Usage = BufferUsage.Raw | BufferUsage.ShaderResource,
MemoryType = ResourceMemoryType.Default,
};
var vertexBuffer = _resourceAllocator.CreateBuffer(in vertexBufferDesc, "VertexBuffer");
var indexBuffer = _resourceAllocator.CreateBuffer(in indexBufferDesc, "IndexBuffer");
var objectBuffer = _resourceAllocator.CreateBuffer(in objectBufferDesc, "ObjectBuffer");
var mesh = new Mesh
{
Vertices = vertices,
Indices = indices,
VertexBuffer = vertexBuffer,
IndexBuffer = indexBuffer,
ObjectDataBuffer = objectBuffer,
};
var id = _meshes.Add(mesh, out var generation);
return new Handle<Mesh>(id, generation);
}
public Handle<Material> CreateMaterial(Identifier<Shader> shader)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var material = new Material();
if (material.SetShader(shader, this) != Error.None)
{
return Handle<Material>.Invalid;
}
var id = _materials.Add(material, out var generation);
return new Handle<Material>(id, generation);
}
public Identifier<Shader> CreateGraphicsShader(ShaderDescriptor descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var shader = new Shader(descriptor);
var id = _shaders.Count;
_shaders.Add(shader);
return new Identifier<Shader>(id);
}
public bool HasMesh(Handle<Mesh> handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _meshes.Contains(handle.ID, handle.Generation);
}
public RefResult<Mesh, Error> GetMeshReference(Handle<Mesh> handle)
{
ref var mesh = ref _meshes.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
if (!exist)
{
return Error.NotFound;
}
return RefResult<Mesh, Error>.Success(ref mesh);
}
public void ReleaseMesh(Handle<Mesh> handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
ref var mesh = ref _meshes.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
if (!exist)
{
return;
}
ReleaseResource(mesh);
_meshes.Remove(handle.ID, handle.Generation);
}
public bool HasMaterial(Handle<Material> handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _materials.Contains(handle.ID, handle.Generation);
}
public RefResult<Material, Error> GetMaterialReference(Handle<Material> handle)
{
ref var material = ref _materials.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
if (!exist)
{
return Error.NotFound;
}
return RefResult<Material, Error>.Success(ref material);
}
public void ReleaseMaterial(Handle<Material> handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
ref var material = ref _materials.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
if (!exist)
{
return;
}
ReleaseResource(material);
_materials.Remove(handle.ID, handle.Generation);
}
public bool HasShader(Identifier<Shader> id)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return id.Value >= 0 && id.Value < _shaders.Count;
}
public RefResult<Shader, Error> GetShaderReference(Identifier<Shader> id)
{
if (!HasShader(id))
{
return Error.NotFound;
}
return RefResult<Shader, Error>.Success(ref _shaders[id.Value]);
}
public void ReleaseShader(Identifier<Shader> id)
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (!HasShader(id))
{
return;
}
ref var shader = ref _shaders[id.Value]!;
ReleaseResource(shader);
}
private void ReleaseResource<T>(T resource)
where T : IResourceReleasable
{
resource.ReleaseResource(_resourceDatabase);
}
public void Dispose()
{
if (_disposed)
{
return;
}
foreach (var mesh in _meshes)
{
ReleaseResource(mesh);
}
foreach (var material in _materials)
{
ReleaseResource(material);
}
foreach (var shader in _shaders)
{
ReleaseResource(shader);
}
_meshes.Dispose();
_materials.Dispose();
_shaders.Dispose();
_disposed = true;
GC.SuppressFinalize(this);
}
}

View File

@@ -1,11 +1,11 @@
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.Mathematics;
using Ghost.Graphics.Core;
namespace Ghost.Graphics.Utilities;
public unsafe static class MeshBuilder
public static unsafe class MeshBuilder
{
/// <summary>
/// Creates a unit cube centered at the origin with size 1.