feat: pure Vulkan P/Invoke renderer — no wrappers, SDL3 window, SPIR-V shaders
- Engine.Graphics: restored interfaces (IRenderContext, IRenderer, RenderBackendFactory), loaders (ObjLoader, GltfLoader), ProceduralMesh, MeshMath, SceneSerializer - Engine.Graphics.Vulkan: pure P/Invoke to libvulkan.so.1/vulkan-1.dll - VulkanNative: library loading, function pointer loading via vkGetInstanceProcAddr - Vk: static function cache for ~80 Vulkan functions, all delegate types - VulkanTypes: ~50 structs, ~20 enums matching Vulkan C headers - VulkanContext: instance, physical device, logical device, surface, memory types - VulkanSwapchain: swapchain, image views, depth image, render pass, framebuffers - VulkanPipeline: graphics pipeline, descriptor set layout, shader modules - VulkanBuffer: vertex/index/uniform buffers, staging, memory allocation - VulkanRenderer: command buffers, sync (semaphores/fences), render loop, 2 frames in flight - GLSL 450 shaders: PBR lighting (Fresnel, ACES, gamma), directional+point lights - Compiled to SPIR-V via @webgpu/glslang WASM - CortexEngine.App: switched to vulkan backend, removed Raylib/OpenTK/ImGui refs - Tests: all 66 pass (ObjLoader, MeshMath, ProceduralMesh, SceneSerializer, etc.) - Camera tour: 16 poses, screenshots captured, clean shutdown
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
using Engine.Core;
|
||||
using Engine.Core.Components;
|
||||
|
||||
namespace Engine.Graphics.Loaders;
|
||||
|
||||
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)
|
||||
{
|
||||
var tint = color ?? DefaultColor;
|
||||
var lines = File.ReadAllLines(path);
|
||||
|
||||
var positions = new List<Vector3>();
|
||||
var normals = new List<Vector3>();
|
||||
|
||||
var vertices = new List<Vertex>();
|
||||
var indices = new List<uint>();
|
||||
|
||||
foreach (var rawLine in lines)
|
||||
{
|
||||
var line = rawLine.Trim();
|
||||
|
||||
if (line.Length == 0 || line.StartsWith('#'))
|
||||
continue;
|
||||
|
||||
var parts = line.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)));
|
||||
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)));
|
||||
break;
|
||||
|
||||
case "f":
|
||||
ParseFace(parts, positions, normals, vertices, indices, tint);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (vertices.Count == 0)
|
||||
throw new InvalidOperationException($"OBJ file '{path}' contains no faces.");
|
||||
|
||||
return new Mesh(vertices.ToArray(), indices.ToArray());
|
||||
}
|
||||
|
||||
private static void ParseFace(
|
||||
string[] parts,
|
||||
List<Vector3> positions,
|
||||
List<Vector3> normals,
|
||||
List<Vertex> vertices,
|
||||
List<uint> indices,
|
||||
Vector3 tint)
|
||||
{
|
||||
var faceData = new List<(int posIdx, int normIdx)>();
|
||||
|
||||
for (var 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;
|
||||
|
||||
faceData.Add((posIdx, normIdx));
|
||||
}
|
||||
|
||||
if (faceData.Count < 3) return;
|
||||
|
||||
for (var i = 1; i < faceData.Count - 1; 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user