using Ghost.Graphics.D3D12;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Helpers;
using Misaki.HighPerformance.Mathematics.Geometry;
using System.Numerics;
using System.Runtime.CompilerServices;
using Win32.Graphics.Direct3D12;
using Win32.Graphics.Dxgi.Common;
namespace Ghost.Graphics.Data;
///
/// The CPU-side mesh data structure.
///
public struct MeshData
{
public UnsafeList vertices;
public UnsafeList indices;
public AABB boundingBox;
public MeshHandle handle;
public MeshData()
{
handle = MeshHandle.Invalid;
}
}
///
/// The GPU-side mesh handle containing buffer references.
///
public struct MeshHandle
{
public BufferHandle vertexBuffer;
public BufferHandle indexBuffer;
public static MeshHandle Invalid => new() { vertexBuffer = BufferHandle.Invalid, indexBuffer = BufferHandle.Invalid };
public readonly bool IsValid => vertexBuffer.IsValid && indexBuffer.IsValid;
}
public struct BatchMeshID : IEquatable
{
public int value;
public static BatchMeshID Null => new() { value = -1 };
public readonly override int GetHashCode()
{
return value.GetHashCode();
}
public readonly override bool Equals(object? obj)
{
return obj is BatchMeshID id && Equals(id);
}
public readonly bool Equals(BatchMeshID other)
{
return value == other.value;
}
public readonly int CompareTo(BatchMeshID other)
{
return value.CompareTo(other.value);
}
public static bool operator ==(BatchMeshID a, BatchMeshID b)
{
return a.Equals(b);
}
public static bool operator !=(BatchMeshID a, BatchMeshID b)
{
return !a.Equals(b);
}
}
public unsafe sealed class Mesh : IDisposable
{
private UnsafeList _vertices;
private UnsafeList _indices;
private AABB _boundingBox;
private IBuffer? _vertexBuffer;
private IBuffer? _indexBuffer;
public Span Vertices => _vertices.AsSpan();
public Span Indices => _indices.AsSpan();
public AABB BoundingBox => _boundingBox;
public uint VertexCount => (uint)_vertices.Count;
public uint IndexCount => (uint)_indices.Count;
public uint VertexBufferDescriptorIndex
{
get
{
if (_vertexBuffer == null || !_vertexBuffer.Handle.IsValid)
{
throw new InvalidOperationException("Vertex buffer is not created.");
}
var bindlessDesc = _vertexBuffer.Handle.BindlessDescriptor;
if (!bindlessDesc.IsValid)
{
throw new InvalidOperationException("Vertex buffer is not created with bindless.");
}
return bindlessDesc.Index;
}
}
public uint IndexBufferDescriptorIndex
{
get
{
if (_indexBuffer == null || !_indexBuffer.Handle.IsValid)
{
throw new InvalidOperationException("Index buffer is not created.");
}
var bindlessDesc = _indexBuffer.Handle.BindlessDescriptor;
if (!bindlessDesc.IsValid)
{
throw new InvalidOperationException("Index buffer is not created with bindless.");
}
return bindlessDesc.Index;
}
}
public Mesh(int initialVertexCapacity = 256, int initialIndexCapacity = 512)
{
_vertices = new(initialVertexCapacity, Allocator.Persistent);
_indices = new(initialIndexCapacity, Allocator.Persistent);
}
public Mesh(ReadOnlySpan vertices, ReadOnlySpan indices)
: this(vertices.Length, indices.Length)
{
_vertices = new(vertices.Length, Allocator.Persistent);
_indices = new(indices.Length, Allocator.Persistent);
_vertices.CopyFrom(vertices);
_indices.CopyFrom(indices);
}
~Mesh()
{
Dispose();
}
///
/// Adds a vertex to the mesh with the specified attributes.
///
/// The vertex data to add
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddVertex(Vertex vertex)
{
_vertices.Add(vertex);
}
///
/// Adds a triangle to the mesh by specifying the indices of its three vertices.
///
/// The index of the first vertex in the triangle. Must be within the range of the current vertex count.
/// The index of the second vertex in the triangle. Must be within the range of the current vertex count.
/// The index of the third vertex in the triangle. Must be within the range of the current vertex count.
/// Thrown if any of the specified indices are out of range for the current vertex count.
public void AddTriangle(int index0, int index1, int index2)
{
if (index0 >= _vertices.Count || index1 >= _vertices.Count || index2 >= _vertices.Count)
{
throw new ArgumentOutOfRangeException("Index out of range for the current vertex count.");
}
_indices.Add(index0);
_indices.Add(index1);
_indices.Add(index2);
}
public void AddTriangles(params ReadOnlySpan indices)
{
if (indices.Length % 3 != 0)
{
throw new ArgumentException("The number of indices must be a multiple of 3 to form triangles.");
}
foreach (var index in indices)
{
if (index < 0 || index >= _vertices.Count)
{
throw new ArgumentOutOfRangeException(nameof(indices), "Index out of range for the current vertex count.");
}
_indices.Add(index);
}
}
///
/// Reduces the memory usage of the internal collections by resizing them to match their current element count.
///
public void TrimExcess()
{
_vertices.Resize(_vertices.Count);
_indices.Resize(_indices.Count);
}
///
/// Auto-compute smooth per-vertex normals.
///
///
/// Call this method before vertices and indices are valid.
///
public void ComputeNormal()
{
if (!_vertices.IsCreated || _vertices.Count < 3
|| !_indices.IsCreated || _indices.Count < 3)
{
return;
}
for (var i = 0; i < _indices.Count; i += 3)
{
var i0 = _indices[i];
var i1 = _indices[i + 1];
var i2 = _indices[i + 2];
var v0 = _vertices[i0];
var v1 = _vertices[i1];
var v2 = _vertices[i2];
var edge1 = v1.Position - v0.Position;
var edge2 = v2.Position - v0.Position;
var faceNormal = Vector3.Cross(edge1.AsVector3(), edge2.AsVector3());
_vertices[i0].Normal += faceNormal.AsVector4();
_vertices[i1].Normal += faceNormal.AsVector4();
_vertices[i2].Normal += faceNormal.AsVector4();
}
for (var i = 0; i < _vertices.Count; i++)
{
_vertices[i].Normal = Vector4.Normalize(_vertices[i].Normal);
}
}
///
/// Auto-compute per-vertex tangents.
///
///
/// Call this method before vertices, normals, and UVs are valid.
///
public void ComputeTangents()
{
var bitangents = new Vector4[_vertices.Count];
for (var i = 0; i < _indices.Count; i += 3)
{
var i0 = _indices[i];
var i1 = _indices[i + 1];
var i2 = _indices[i + 2];
var v0 = _vertices[i0];
var v1 = _vertices[i1];
var v2 = _vertices[i2];
var uv0 = _vertices[i0].UV;
var uv1 = _vertices[i1].UV;
var uv2 = _vertices[i2].UV;
var deltaPos1 = v1.Position - v0.Position;
var deltaPos2 = v2.Position - v0.Position;
var deltaUV1 = uv1 - uv0;
var deltaUV2 = uv2 - uv0;
var r = 1.0f / (deltaUV1.X * deltaUV2.Y - deltaUV1.Y * deltaUV2.X);
var tangent = (deltaPos1 * deltaUV2.Y - deltaPos2 * deltaUV1.Y) * r;
var bitangent = (deltaPos2 * deltaUV1.X - deltaPos1 * deltaUV2.X) * r;
for (var j = 0; j < 3; j++)
{
var idx = _indices[i + j];
var t = _vertices[idx].Tangent;
_vertices[idx].Tangent = new Vector4(
t.X + tangent.X,
t.Y + tangent.Y,
t.Z + tangent.Z,
0.0f // we’ll fill w later
);
bitangents[idx] += bitangent;
}
}
for (var i = 0; i < _vertices.Count; i++)
{
var n = _vertices[i].Normal;
var t = _vertices[i].Tangent;
var n3 = n.AsVector3();
var t3 = t.AsVector3();
var proj = n3 * Vector3.Dot(n3, t3);
t3 = Vector3.Normalize(t3 - proj);
var b = bitangents[i];
var w = Vector3.Dot(Vector3.Cross(n3, t3), b.AsVector3()) < 0.0f ? -1.0f : 1.0f;
_vertices[i].Tangent = new Vector4(t3.X, t3.Y, t3.Z, w);
}
}
///
/// Computes the bounding box of the mesh based on its vertices.
///
public void ComputeBounds()
{
if (_vertices.Count == 0)
{
_boundingBox = Bounds.Zero;
return;
}
var min = new Vector3(float.MaxValue);
var max = new Vector3(float.MinValue);
foreach (var vertex in _vertices)
{
var pos = vertex.Position.AsVector3();
min = Vector3.Min(min, pos);
max = Vector3.Max(max, pos);
}
_boundingBox = new Bounds(min, max);
}
///
/// Uploads the mesh data to GPU resources.
///
public unsafe void UploadMeshData()
{
if (VertexCount == 0 || IndexCount == 0)
{
return;
}
DisposeGpuResources();
var vertexBufferSize = (uint)(VertexCount * sizeof(Vertex));
var indexBufferSize = IndexCount * sizeof(int);
_vertexBuffer = GraphicsBuffer.Create(vertexBufferSize, GraphicsBuffer.Usage.CopyDestination);
_indexBuffer = GraphicsBuffer.Create(indexBufferSize, GraphicsBuffer.Usage.CopyDestination);
var uploadBatch = GraphicsPipeline.UploadBatch;
uploadBatch.Upload(_vertexBuffer.NativeResource, _vertices.AsSpan());
uploadBatch.Upload(_indexBuffer.NativeResource, _indices.AsSpan());
uploadBatch.Transition(_vertexBuffer.NativeResource, ResourceStates.CopyDest, ResourceStates.VertexAndConstantBuffer);
uploadBatch.Transition(_indexBuffer.NativeResource, ResourceStates.CopyDest, ResourceStates.IndexBuffer);
// Create bindless descriptors for vertex and index buffers
CreateBindlessDescriptors();
}
internal void MarkNoLongerReadable()
{
_vertices.Dispose();
_indices.Dispose();
}
///
/// Clears all vertex and index data and releases associated GPU resources.
///
public void Clear()
{
_vertices.Clear();
_indices.Clear();
DisposeGpuResources();
}
private void DisposeGpuResources()
{
_vertexBuffer?.Dispose();
_vertexBuffer = null;
_indexBuffer?.Dispose();
_indexBuffer = null;
if (_vertexBufferDescriptor.IsValid)
{
RenderSystem.GraphicsEngine.DescriptorAllocator.Release(_vertexBufferDescriptor);
}
if (_indexBufferDescriptor.IsValid)
{
RenderSystem.GraphicsEngine.DescriptorAllocator.Release(_indexBufferDescriptor);
}
}
public void Dispose()
{
_vertices.Dispose();
_indices.Dispose();
DisposeGpuResources();
GC.SuppressFinalize(this);
}
}