Files
GhostEngine/Ghost.Graphics/Data/Mesh.cs
Misaki 261afa4133 Update rendering and resource management
Changed the `EditorState` class to use a timeout in the `WaitForGPUReady` method for improved responsiveness.
Changed the `nativeDebugging` setting in `launchSettings.json` to `false` for the "Ghost.Editor (Package)" profile.
Changed the `D3D12Renderer` class to set the swap chain only for the composition target type and replaced back buffer reset with dispose.
Changed the mapping of resources in `D3D12Resource` to use a pointer for improved safety and clarity.
Changed the `Mesh` class's upload buffer creation to not use the `true` flag for better memory management.
Added a new `Vertex` struct with a `StructLayout` attribute for improved interoperability with unmanaged code.
Refactored the `GraphicsPipeline` class to replace `IsGpuReady` with `WaitForGPUReady`, including a timeout parameter.
Added a constant buffer to the HLSL source code in `MeshRenderPass` for passing transformation matrices to the vertex shader.
Expanded the `UnitTestAppWindow` class to include event handlers for window activation and size changes for better resource management.
Updated the XAML for `UnitTestAppWindow` to include a `SwapChainPanel` and corrected the XML declaration for formatting consistency.
2025-07-03 23:23:46 +09:00

277 lines
9.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Ghost.Core;
using Ghost.Graphics.Contracts;
using Misaki.HighPerformance.Unsafe.Collections;
using Misaki.HighPerformance.Unsafe.Helpers;
using System.Numerics;
using System.Runtime.CompilerServices;
using Win32.Graphics.Direct3D12;
using Win32.Graphics.Dxgi.Common;
namespace Ghost.Graphics.Data;
public unsafe sealed class Mesh(int initialVertexCapacity = 256, int initialIndexCapacity = 512) : IDisposable
{
private UnsafeList<Vertex> _vertices = new(initialVertexCapacity, Allocator.Persistent);
private UnsafeList<int> _indices = new(initialIndexCapacity, Allocator.Persistent);
private Bounds _boundingBox;
private IResource? _vertexBuffer;
private IResource? _indexBuffer;
private VertexBufferView _vertexBufferView;
private IndexBufferView _indexBufferView;
public Span<Vertex> Vertices => _vertices.AsSpan();
public Span<int> Indices => _indices.AsSpan();
public Bounds BoundingBox => _boundingBox;
public uint VertexCount => (uint)_vertices.Count;
public uint IndexCount => (uint)_indices.Count;
internal ConstPtr<VertexBufferView> VertexBufferView => (VertexBufferView*)Unsafe.AsPointer(ref _vertexBufferView);
internal ConstPtr<IndexBufferView> IndexBufferView => (IndexBufferView*)Unsafe.AsPointer(ref _indexBufferView);
~Mesh()
{
Dispose();
}
/// <summary>
/// Adds a vertex to the mesh with the specified attributes.
/// </summary>
/// <param name="vertex">The vertex data to add</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddVertex(Vertex vertex)
{
_vertices.Add(vertex);
}
/// <summary>
/// Adds a triangle to the mesh by specifying the indices of its three vertices.
/// </summary>
/// <param name="index0">The index of the first vertex in the triangle. Must be within the range of the current vertex count.</param>
/// <param name="index1">The index of the second vertex in the triangle. Must be within the range of the current vertex count.</param>
/// <param name="index2">The index of the third vertex in the triangle. Must be within the range of the current vertex count.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown if any of the specified indices are out of range for the current vertex count.</exception>
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);
}
/// <summary>
/// Reduces the memory usage of the internal collections by resizing them to match their current element count.
/// </summary>
public void TrimExcess()
{
_vertices.Resize(_vertices.Count);
_indices.Resize(_indices.Count);
}
/// <summary>
/// Auto-compute smooth per-vertex normals.
/// </summary>
/// <remarks>
/// Call this method before vertices and indices are valid.
/// </remarks>
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);
}
}
/// <summary>
/// Auto-compute per-vertex tangents.
/// </summary>
/// <remarks>
/// Call this method before vertices, normals, and UVs are valid.
/// </remarks>
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 // well 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);
}
}
/// <summary>
/// Computes the bounding box of the mesh based on its vertices.
/// </summary>
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);
}
/// <summary>
/// Uploads the mesh data to GPU resources.
/// </summary>
/// <param name="cmb">The command buffer to record the upload commands.</param>
public unsafe void UploadMeshData(ICommandBuffer cmb)
{
if (VertexCount == 0 || IndexCount == 0)
{
return;
}
_vertexBuffer?.Dispose();
_indexBuffer?.Dispose();
var vertexBufferSize = (uint)(VertexCount * sizeof(Vertex));
var indexBufferSize = IndexCount * sizeof(int);
_vertexBuffer = GraphicsPipeline.ResourceAllocator.CreateCopyDestinationBuffer(vertexBufferSize);
_indexBuffer = GraphicsPipeline.ResourceAllocator.CreateCopyDestinationBuffer(indexBufferSize);
var vertexUploadBuffer = GraphicsPipeline.ResourceAllocator.CreateUploadBuffer(vertexBufferSize, false);
var indexUploadBuffer = GraphicsPipeline.ResourceAllocator.CreateUploadBuffer(indexBufferSize, false);
vertexUploadBuffer.SetData(_vertices.AsSpan());
indexUploadBuffer.SetData(_indices.AsSpan());
cmb.CopyResource(_vertexBuffer, 0, vertexUploadBuffer, 0, vertexBufferSize);
cmb.CopyResource(_indexBuffer, 0, indexUploadBuffer, 0, indexBufferSize);
cmb.BarrierTransition(_vertexBuffer, ResourceStates.CopyDest, ResourceStates.VertexAndConstantBuffer);
cmb.BarrierTransition(_indexBuffer, ResourceStates.CopyDest, ResourceStates.IndexBuffer);
_vertexBufferView = new VertexBufferView
{
BufferLocation = _vertexBuffer.GPUAddress,
SizeInBytes = vertexBufferSize,
StrideInBytes = (uint)sizeof(Vertex)
};
_indexBufferView = new IndexBufferView
{
BufferLocation = _indexBuffer.GPUAddress,
SizeInBytes = indexBufferSize,
Format = Format.R32Uint
};
}
/// <summary>
/// Clears all vertex and index data and releases associated GPU resources.
/// </summar>
public void Clear()
{
_vertices.Clear();
_indices.Clear();
DisposeGpuResources();
}
private void DisposeGpuResources()
{
_vertexBuffer?.Dispose();
_vertexBuffer = null;
_indexBuffer?.Dispose();
_indexBuffer = null;
}
public void Dispose()
{
_vertices.Dispose();
_indices.Dispose();
DisposeGpuResources();
GC.SuppressFinalize(this);
}
}