Migrate rendering from oop to dod

This commit is contained in:
2025-09-16 20:55:20 +09:00
parent 74bb2ccda5
commit 6a504cefc8
20 changed files with 263 additions and 449 deletions

View File

@@ -1,251 +0,0 @@
using System.Numerics;
using System.Runtime.CompilerServices;
namespace Ghost.Graphics.Data;
public struct Bounds : IEquatable<Bounds>
{
/// <summary>
/// The minimum point contained by the AABB.
/// </summary>
/// <remarks>
/// If any component of <see cref="Min"/> is greater than <see cref="Max"/> then this AABB is invalid.
/// </remarks>
/// <seealso cref="IsValid"/>
public Vector3 Min
{
get;
set;
}
/// <summary>
/// The maximum point contained by the AABB.
/// </summary>
/// <remarks>
/// If any component of <see cref="Max"/> is less than <see cref="Min"/> then this AABB is invalid.
/// </remarks>
/// <seealso cref="IsValid"/>
public Vector3 Max
{
get;
set;
}
/// <summary>
/// Constructs the AABB with the given minimum and maximum.
/// </summary>
/// <remarks>
/// If you have a center and extents, you can call <see cref="CreateFromCenterAndExtents"/> or <see cref="CreateFromCenterAndHalfExtents"/>
/// to create the AABB.
/// </remarks>
/// <param name="min">Minimum point inside AABB.</param>
/// <param name="max">Maximum point inside AABB.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Bounds(Vector3 min, Vector3 max)
{
Min = min;
Max = max;
}
/// <summary>
/// Creates the AABB from a center and extents.
/// </summary>
/// <remarks>
/// This function takes full extents. It is the distance between <see cref="Min"/> and <see cref="Max"/>.
/// If you have half extents, you can call <see cref="CreateFromCenterAndHalfExtents"/>.
/// </remarks>
/// <param name="center">Center of AABB.</param>
/// <param name="extents">Full extents of AABB.</param>
/// <returns>AABB created from inputs.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Bounds CreateFromCenterAndExtents(Vector3 center, Vector3 extents)
{
return CreateFromCenterAndHalfExtents(center, extents * 0.5f);
}
/// <summary>
/// Creates the AABB from a center and half extents.
/// </summary>
/// <remarks>
/// This function takes half extents. It is half the distance between <see cref="Min"/> and <see cref="Max"/>.
/// If you have full extents, you can call <see cref="CreateFromCenterAndExtents"/>.
/// </remarks>
/// <param name="center">Center of AABB.</param>
/// <param name="halfExtents">Half extents of AABB.</param>
/// <returns>AABB created from inputs.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Bounds CreateFromCenterAndHalfExtents(Vector3 center, Vector3 halfExtents)
{
return new Bounds(center - halfExtents, center + halfExtents);
}
/// <summary>
/// Creates a new AABB with zero extents, centered at the origin.
/// </summary>
public static Bounds Zero
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return new Bounds(Vector3.Zero, Vector3.Zero);
}
}
/// <summary>
/// Computes the extents of the AABB.
/// </summary>
/// <remarks>
/// Extents is the componentwise distance between min and max.
/// </remarks>
public readonly Vector3 Extents => Max - Min;
/// <summary>
/// Computes the half extents of the AABB.
/// </summary>
/// <remarks>
/// HalfExtents is half of the componentwise distance between min and max. Subtracting HalfExtents from Center
/// gives Min and adding HalfExtents to Center gives Max.
/// </remarks>
public readonly Vector3 HalfExtents => (Max - Min) * 0.5f;
/// <summary>
/// Computes the center of the AABB.
/// </summary>
public readonly Vector3 Center => (Max + Min) * 0.5f;
/// <summary>
/// Check if the AABB is valid.
/// </summary>
/// <remarks>
/// An AABB is considered valid if <see cref="Min"/> is componentwise less than or equal to <see cref="Max"/>.
/// </remarks>
/// <returns>True if <see cref="Min"/> is componentwise less than or equal to <see cref="Max"/>.</returns>
public readonly bool IsValid => Vector3.Dot(Min, Min) <= Vector3.Dot(Max, Max);
/// <summary>
/// Computes the surface area for this axis aligned bounding box.
/// </summary>
public readonly float SurfaceArea
{
get
{
var diff = Max - Min;
return 2 * Vector3.Dot(diff, new Vector3(diff.Y, diff.Z, diff.X));
}
}
/// <summary>
/// Tests if the input point is contained by the AABB.
/// </summary>
/// <param name="point">Point to test.</param>
/// <returns>True if AABB contains the input point.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool Contains(Vector3 point) => Vector3.Dot(point, point) >= Vector3.Dot(Min, Min) && Vector3.Dot(point, point) <= Vector3.Dot(Max, Max);
/// <summary>
/// Tests if the input AABB is contained entirely by this AABB.
/// </summary>
/// <param name="aabb">AABB to test.</param>
/// <returns>True if input AABB is contained entirely by this AABB.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool Contains(Bounds aabb) => Vector3.Dot(Min, Min) <= Vector3.Dot(aabb.Min, aabb.Min) && Vector3.Dot(Max, Max) >= Vector3.Dot(aabb.Max, aabb.Max);
/// <summary>
/// Tests if the input AABB overlaps this AABB.
/// </summary>
/// <param name="aabb">AABB to test.</param>
/// <returns>True if input AABB overlaps with this AABB.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool Overlaps(Bounds aabb)
{
return Vector3.Dot(Max, Max) >= Vector3.Dot(aabb.Min, aabb.Min) && Vector3.Dot(Min, Min) <= Vector3.Dot(aabb.Max, aabb.Max);
}
/// <summary>
/// Expands the AABB by the given signed distance.
/// </summary>
/// <remarks>
/// Positive distance expands the AABB while negative distance shrinks the AABB.
/// </remarks>
/// <param name="signedDistance">Signed distance to expand the AABB with.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Expand(float signedDistance)
{
Min -= new Vector3(signedDistance);
Max += new Vector3(signedDistance);
}
/// <summary>
/// Encapsulates the given AABB.
/// </summary>
/// <remarks>
/// Modifies this AABB so that it contains the given AABB. If the given AABB is already contained by this AABB,
/// then this AABB doesn't change.
/// </remarks>
/// <seealso cref="Contains(Unity.Mathematics.Geometry.MinMaxAABB)"/>
/// <param name="aabb">AABB to encapsulate.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Encapsulate(Bounds aabb)
{
Min = Vector3.Min(Min, aabb.Min);
Max = Vector3.Max(Max, aabb.Max);
}
/// <summary>
/// Encapsulate the given point.
/// </summary>
/// <remarks>
/// Modifies this AABB so that it contains the given point. If the given point is already contained by this AABB,
/// then this AABB doesn't change.
/// </remarks>
/// <seealso cref="Contains(Unity.Mathematics.float3)"/>
/// <param name="point">Point to encapsulate.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Encapsulate(Vector3 point)
{
Min = Vector3.Min(Min, point);
Max = Vector3.Max(Max, point);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Bounds other)
{
return Min.Equals(other.Min) && Max.Equals(other.Max);
}
public override bool Equals(object? obj)
{
if (obj is Bounds bounds)
{
return Equals(bounds);
}
return false;
}
public static bool operator ==(Bounds left, Bounds right)
{
return left.Equals(right);
}
public static bool operator !=(Bounds left, Bounds right)
{
return !(left == right);
}
public override int GetHashCode()
{
unchecked
{
var hash = 17;
hash = hash * 31 + Min.GetHashCode();
hash = hash * 31 + Max.GetHashCode();
return hash;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly override string ToString()
{
return string.Format("Bounds({0}, {1})", Min, Max);
}
}

View File

@@ -4,17 +4,17 @@ using System.Runtime.InteropServices;
namespace Ghost.Graphics.Data;
/// <summary>
/// Represents a color with 32-bit components."/>
/// Represents a color with 4 bytes components.
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = 4)]
public struct Color32 : IEquatable<Color32>
public struct Color4 : IEquatable<Color4>
{
public byte r;
public byte g;
public byte b;
public byte a;
public Color32(byte r, byte g, byte b, byte a)
public Color4(byte r, byte g, byte b, byte a)
{
this.r = r;
this.g = g;
@@ -22,24 +22,24 @@ public struct Color32 : IEquatable<Color32>
this.a = a;
}
public Color32(Color color)
public Color4(Color color)
: this(color.R, color.G, color.B, color.A)
{
}
public Color32(Color128 color128)
public Color4(Color16 color128)
: this((byte)(color128.r * 255.0f), (byte)(color128.g * 255.0f), (byte)(color128.b * 255.0f), (byte)(color128.a * 255.0f))
{
}
public readonly bool Equals(Color32 other)
public readonly bool Equals(Color4 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);
return obj is Color4 color && Equals(color);
}
public override readonly int GetHashCode()
@@ -47,29 +47,29 @@ public struct Color32 : IEquatable<Color32>
return HashCode.Combine(r, g, b, a);
}
public static bool operator ==(Color32 left, Color32 right)
public static bool operator ==(Color4 left, Color4 right)
{
return left.Equals(right);
}
public static bool operator !=(Color32 left, Color32 right)
public static bool operator !=(Color4 left, Color4 right)
{
return !(left == right);
}
}
/// <summary>
/// Represents a color with 128-bit components.
/// Represents a color with 16 bytes components.
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = 16)]
public struct Color128 : IEquatable<Color128>
public struct Color16 : IEquatable<Color16>
{
public float r;
public float g;
public float b;
public float a;
public Color128(float r, float g, float b, float a)
public Color16(float r, float g, float b, float a)
{
this.r = r;
this.g = g;
@@ -77,24 +77,24 @@ public struct Color128 : IEquatable<Color128>
this.a = a;
}
public Color128(Color color)
public Color16(Color color)
: this(color.R / 255.0f, color.G / 255.0f, color.B / 255.0f, color.A / 255.0f)
{
}
public Color128(Color32 color32)
public Color16(Color4 color32)
: this(color32.r / 255.0f, color32.g / 255.0f, color32.b / 255.0f, color32.a / 255.0f)
{
}
public readonly bool Equals(Color128 other)
public readonly bool Equals(Color16 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);
return obj is Color16 color && Equals(color);
}
public readonly override int GetHashCode()
@@ -102,12 +102,12 @@ public struct Color128 : IEquatable<Color128>
return HashCode.Combine(r, g, b, a);
}
public static bool operator ==(Color128 left, Color128 right)
public static bool operator ==(Color16 left, Color16 right)
{
return left.Equals(right);
}
public static bool operator !=(Color128 left, Color128 right)
public static bool operator !=(Color16 left, Color16 right)
{
return !(left == right);
}

View File

@@ -0,0 +1,54 @@
namespace Ghost.Graphics.Data;
public struct BatchMaterialID : IEquatable<BatchMaterialID>
{
public uint value;
public static BatchMaterialID Null => new() { value = 0 };
public override int GetHashCode()
{
return value.GetHashCode();
}
public readonly override bool Equals(object? obj)
{
return obj is BatchMaterialID id && Equals(id);
}
public readonly bool Equals(BatchMaterialID other)
{
return value == other.value;
}
public readonly int CompareTo(BatchMaterialID other)
{
return value.CompareTo(other.value);
}
public static bool operator ==(BatchMaterialID a, BatchMaterialID b)
{
return a.Equals(b);
}
public static bool operator !=(BatchMaterialID a, BatchMaterialID b)
{
return !a.Equals(b);
}
}
internal class GraphicsResourceManager
{
private readonly Dictionary<BatchMaterialID, Material> _materials = new();
private readonly Dictionary<BatchMeshID, MeshHandle> _meshes = new();
public Material GetMaterial(BatchMaterialID id)
{
return _materials.TryGetValue(id, out var material) ? material : Material.Null;
}
public MeshHandle GetMesh(BatchMeshID id)
{
return _meshes.TryGetValue(id, out var mesh) ? mesh : MeshHandle.Invalid;
}
}

View File

@@ -3,6 +3,7 @@ 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;
@@ -10,19 +11,85 @@ using Win32.Graphics.Dxgi.Common;
namespace Ghost.Graphics.Data;
/// <summary>
/// The CPU-side mesh data structure.
/// </summary>
public struct MeshData
{
public UnsafeList<Vertex> vertices;
public UnsafeList<int> indices;
public AABB boundingBox;
public MeshHandle handle;
public MeshData()
{
handle = MeshHandle.Invalid;
}
}
/// <summary>
/// The GPU-side mesh handle containing buffer references.
/// </summary>
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<BatchMeshID>
{
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<Vertex> _vertices;
private UnsafeList<int> _indices;
private Bounds _boundingBox;
private AABB _boundingBox;
private IBuffer? _vertexBuffer;
private IBuffer? _indexBuffer;
public Span<Vertex> Vertices => _vertices.AsSpan();
public Span<int> Indices => _indices.AsSpan();
public Bounds BoundingBox => _boundingBox;
public AABB BoundingBox => _boundingBox;
public uint VertexCount => (uint)_vertices.Count;
public uint IndexCount => (uint)_indices.Count;
@@ -296,62 +363,6 @@ public unsafe sealed class Mesh : IDisposable
CreateBindlessDescriptors();
}
/// <summary>
/// Creates SRVs for vertex and index buffers in the bindless descriptor heap
/// </summary>
private void CreateBindlessDescriptors()
{
if (_vertexBuffer == null || _indexBuffer == null)
{
return;
}
// Allocate new descriptors from the descriptor allocator
_vertexBufferDescriptor = GraphicsPipeline.DescriptorAllocator.AllocateBindless();
_indexBufferDescriptor = GraphicsPipeline.DescriptorAllocator.AllocateBindless();
var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr;
var vertexSrvDesc = new ShaderResourceViewDescription
{
Format = Format.R32Typeless,
ViewDimension = SrvDimension.Buffer,
Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
Anonymous = new()
{
Buffer = new()
{
FirstElement = 0,
NumElements = (uint)(_vertexBuffer.GPUAddress != 0 ? (VertexCount * sizeof(Vertex)) / 4 : 0), // Divide by 4 for R32 format
StructureByteStride = 0,
Flags = BufferSrvFlags.Raw
}
}
};
device->CreateShaderResourceView(_vertexBuffer.NativeResource.Ptr, &vertexSrvDesc, _vertexBufferDescriptor.CpuHandle);
var indexSrvDesc = new ShaderResourceViewDescription
{
Format = Format.R32Typeless,
ViewDimension = SrvDimension.Buffer,
Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
Anonymous = new()
{
Buffer = new()
{
FirstElement = 0,
NumElements = IndexCount,
StructureByteStride = 0,
Flags = BufferSrvFlags.Raw
}
}
};
device->CreateShaderResourceView(_indexBuffer.NativeResource.Ptr, &indexSrvDesc, _indexBufferDescriptor.CpuHandle);
}
internal void MarkNoLongerReadable()
{
_vertices.Dispose();

View File

@@ -206,7 +206,7 @@ public unsafe class RenderTexture : Texture
/// </summary>
/// <param name="commandList">Command list to record clear commands</param>
/// <param name="clearColor">Color to clear to</param>
public void ClearColor(CommandList commandList, Color128 clearColor)
public void ClearColor(CommandList commandList, Color16 clearColor)
{
ThrowIfDisposed();

View File

@@ -8,17 +8,17 @@ public readonly struct ResourceHandle : IEquatable<ResourceHandle>
private const int _INVALID_ID = -1;
public readonly int id;
public readonly uint generation;
public readonly int generation;
public static ResourceHandle Invalid => new(-1, 0);
public static ResourceHandle Invalid => new(_INVALID_ID, _INVALID_ID);
internal ResourceHandle(int id, uint generation)
internal ResourceHandle(int id, int generation)
{
this.id = id;
this.generation = generation;
}
public bool IsValid => id != _INVALID_ID && generation >= 0;
public bool IsValid => id != _INVALID_ID && generation != _INVALID_ID;
public bool Equals(ResourceHandle other)
{

View File

@@ -1,4 +1,4 @@
using System.Numerics;
using Misaki.HighPerformance.Mathematics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
@@ -7,7 +7,7 @@ using Win32.Graphics.Dxgi.Common;
namespace Ghost.Graphics.Data;
[StructLayout(LayoutKind.Sequential)]
public struct Vertex(Vector4 position, Vector4 normal, Vector4 tangent, Color128 color, Vector4 uv)
public struct Vertex
{
public unsafe struct Semantic
{
@@ -19,40 +19,49 @@ public struct Vertex(Vector4 position, Vector4 normal, Vector4 tangent, Color128
private static readonly byte[] s_colorBytes = Encoding.UTF8.GetBytes("COLOR");
private static readonly byte[] s_uvBytes = Encoding.UTF8.GetBytes("TEXCOORD");
public static byte* PositionName => (byte*)Unsafe.AsPointer(ref s_positionBytes[0]);
public static byte* NormalName => (byte*)Unsafe.AsPointer(ref s_normalBytes[0]);
public static byte* TangentName => (byte*)Unsafe.AsPointer(ref s_tangentBytes[0]);
public static byte* ColorName => (byte*)Unsafe.AsPointer(ref s_colorBytes[0]);
public static byte* UVName => (byte*)Unsafe.AsPointer(ref s_uvBytes[0]);
public static byte* pPositionName => (byte*)Unsafe.AsPointer(ref s_positionBytes[0]);
public static byte* pNormalName => (byte*)Unsafe.AsPointer(ref s_normalBytes[0]);
public static byte* pTangentName => (byte*)Unsafe.AsPointer(ref s_tangentBytes[0]);
public static byte* pColorName => (byte*)Unsafe.AsPointer(ref s_colorBytes[0]);
public static byte* pUVName => (byte*)Unsafe.AsPointer(ref s_uvBytes[0]);
}
public Vector4 Position
public float4 Position
{
get;
set;
} = position;
}
public Vector4 Normal
public float4 Normal
{
get;
set;
} = normal;
}
public Vector4 Tangent
public float4 Tangent
{
get;
set;
} = tangent;
}
public Color128 Color
public Color16 Color
{
get;
set;
} = color;
}
public Vector4 UV
public float4 UV
{
get;
set;
} = uv;
}
public Vertex(float4 position, float4 normal, float4 tangent, Color16 color, float4 uv)
{
Position = position;
Normal = normal;
Tangent = tangent;
Color = color;
UV = uv;
}
}