feat: rebuild Vulkan renderer from scratch — pure P/Invoke triangle (Vulkan 1.3)

- Complete rewrite of Engine.Graphics.Vulkan with pure P/Invoke (no wrapper libs)
- Vulkan 1.3: dynamic rendering (vkCmdBeginRendering/vkCmdEndRendering),
  synchronization2 (vkQueueSubmit2, vkCmdPipelineBarrier2)
- Split types into VulkanHandles.cs, VulkanEnums.cs, VulkanStructs.cs
- Staging buffer → device-local vertex buffer pattern
- Correct swapchain semaphore indexing (per-image, not per-frame-in-flight)
- VK_EXT_debug_utils debug messenger with validation layer fallback
- Dynamic viewport/scissor (no pipeline recreation on resize)
- Simplified Program.cs to triangle-only rendering
- Removed old Silk.NET renderer, ImGui, PBR shaders, screenshot code
- Updated VULKAN_IMPLEMENTATION_PLAN.md with full architecture decisions
This commit is contained in:
emil28092005
2026-06-18 01:49:25 +03:00
parent ee98e4ad08
commit 2e0970e769
44 changed files with 3882 additions and 5273 deletions
-67
View File
@@ -1,67 +0,0 @@
using System.Numerics;
using Engine.Core;
using Engine.Core.Components;
namespace Engine.Graphics.Loaders;
public static class GltfLoader
{
public static Mesh Load(string path, Vector3? color = null)
{
var tint = color ?? new Vector3(0.7f, 0.6f, 0.5f);
var modelRoot = SharpGLTF.Schema2.ModelRoot.Load(path);
var vertices = new List<Vertex>();
var indices = new List<uint>();
foreach (var scene in modelRoot.LogicalScenes)
{
foreach (var node in scene.VisualChildren)
{
var mesh = node.Mesh;
if (mesh == null) continue;
foreach (var primitive in mesh.Primitives)
{
var posAccess = primitive.GetVertexAccessor("POSITION");
var normAccess = primitive.GetVertexAccessor("NORMAL");
if (posAccess == null) continue;
var indexAccess = primitive.IndexAccessor;
var baseVertex = (uint)vertices.Count;
for (var i = 0; i < posAccess.Count; i++)
{
var pos = posAccess.AsVector3Array()[i];
var normal = normAccess != null
? normAccess.AsVector3Array()[i]
: Vector3.UnitY;
vertices.Add(new Vertex(pos, tint, normal));
}
if (indexAccess != null)
{
var indexArray = indexAccess.AsIndicesArray();
foreach (var idx in indexArray)
{
indices.Add((uint)idx + baseVertex);
}
}
else
{
for (uint i = 0; i < posAccess.Count; i++)
{
indices.Add(baseVertex + i);
}
}
}
}
}
if (vertices.Count == 0)
throw new InvalidOperationException($"GLTF file '{path}' contains no meshes.");
return new Mesh(vertices.ToArray(), indices.ToArray());
}
}
+58 -59
View File
@@ -1,60 +1,54 @@
using System.Globalization;
using System.Numerics;
using Engine.Core;
using Engine.Core.Components;
namespace Engine.Graphics.Loaders;
/// <summary>
/// Minimal OBJ loader.
/// </summary>
public static class ObjLoader
{
private static readonly Vector3 DefaultColor = new(0.7f, 0.6f, 0.5f);
public static Mesh Load(string path, Vector3? color = null)
public static Mesh Load(string path, Vector3? defaultColor = null)
{
var tint = color ?? DefaultColor;
var lines = File.ReadAllLines(path);
if (!File.Exists(path))
throw new FileNotFoundException($"OBJ file not found: {path}", path);
var color = defaultColor ?? new Vector3(0.7f, 0.6f, 0.5f);
var positions = new List<Vector3>();
var normals = new List<Vector3>();
var texcoords = new List<Vector2>();
var vertices = new List<Vertex>();
var indices = new List<uint>();
var faceNormals = new List<Vector3>();
foreach (var rawLine in lines)
foreach (var line in File.ReadLines(path))
{
var line = rawLine.Trim();
var trimmed = line.Trim();
if (string.IsNullOrEmpty(trimmed) || trimmed.StartsWith('#')) continue;
if (line.Length == 0 || line.StartsWith('#'))
continue;
var parts = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 0)
continue;
var parts = trimmed.Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 0) continue;
switch (parts[0])
{
case "v":
positions.Add(new Vector3(
float.Parse(parts[1], CultureInfo.InvariantCulture),
float.Parse(parts[2], CultureInfo.InvariantCulture),
float.Parse(parts[3], CultureInfo.InvariantCulture)));
positions.Add(ParseVector3(parts));
break;
case "vn":
normals.Add(new Vector3(
float.Parse(parts[1], CultureInfo.InvariantCulture),
float.Parse(parts[2], CultureInfo.InvariantCulture),
float.Parse(parts[3], CultureInfo.InvariantCulture)));
normals.Add(ParseVector3(parts));
break;
case "vt":
texcoords.Add(ParseVector2(parts));
break;
case "f":
ParseFace(parts, positions, normals, vertices, indices, tint);
ParseFace(parts, positions, normals, texcoords, color, vertices, indices, faceNormals);
break;
}
}
if (vertices.Count == 0)
throw new InvalidOperationException($"OBJ file '{path}' contains no faces.");
throw new InvalidOperationException($"OBJ file contains no geometry: {path}");
return new Mesh(vertices.ToArray(), indices.ToArray());
}
@@ -63,47 +57,52 @@ public static class ObjLoader
string[] parts,
List<Vector3> positions,
List<Vector3> normals,
List<Vector2> texcoords,
Vector3 color,
List<Vertex> vertices,
List<uint> indices,
Vector3 tint)
List<Vector3> faceNormals)
{
var faceData = new List<(int posIdx, int normIdx)>();
var faceIndices = new List<uint>();
faceNormals.Clear();
for (var i = 1; i < parts.Length; i++)
for (int i = 1; i < parts.Length; i++)
{
var vertexData = parts[i].Split('/');
var posIdx = int.Parse(vertexData[0]) - 1;
var normIdx = vertexData.Length > 2 && !string.IsNullOrEmpty(vertexData[2])
? int.Parse(vertexData[2]) - 1
: -1;
var sub = parts[i].Split('/');
var posIndex = int.Parse(sub[0]) - 1;
var pos = positions[posIndex];
faceData.Add((posIdx, normIdx));
Vector3 normal = Vector3.UnitY;
if (sub.Length > 2 && !string.IsNullOrEmpty(sub[2]))
{
normal = normals[int.Parse(sub[2]) - 1];
}
vertices.Add(new Vertex(pos, color, normal));
faceIndices.Add((uint)(vertices.Count - 1));
}
if (faceData.Count < 3) return;
for (var i = 1; i < faceData.Count - 1; i++)
// Triangulate as a fan.
for (int i = 2; i < faceIndices.Count; i++)
{
var d0 = faceData[0];
var d1 = faceData[i];
var d2 = faceData[i + 1];
var p0 = positions[d0.posIdx];
var p1 = positions[d1.posIdx];
var p2 = positions[d2.posIdx];
var normal = d0.normIdx >= 0 && d0.normIdx < normals.Count
? normals[d0.normIdx]
: MeshMath.ComputeFaceNormal(p0, p1, p2);
var i0 = (uint)vertices.Count;
vertices.Add(new Vertex(p0, tint, normal));
var i1 = (uint)vertices.Count;
vertices.Add(new Vertex(p1, tint, normal));
var i2 = (uint)vertices.Count;
vertices.Add(new Vertex(p2, tint, normal));
indices.Add(i0); indices.Add(i1); indices.Add(i2);
indices.Add(faceIndices[0]);
indices.Add(faceIndices[i - 1]);
indices.Add(faceIndices[i]);
}
}
private static Vector3 ParseVector3(string[] parts)
{
return new Vector3(
float.Parse(parts[1]),
float.Parse(parts[2]),
float.Parse(parts[3]));
}
private static Vector2 ParseVector2(string[] parts)
{
return new Vector2(
float.Parse(parts[1]),
parts.Length > 2 ? float.Parse(parts[2]) : 0);
}
}