diff --git a/Content/cube.obj b/Content/cube.obj
new file mode 100644
index 0000000..e263d2c
--- /dev/null
+++ b/Content/cube.obj
@@ -0,0 +1,33 @@
+# Simple unit cube
+v -0.5 -0.5 -0.5
+v 0.5 -0.5 -0.5
+v 0.5 0.5 -0.5
+v -0.5 0.5 -0.5
+v -0.5 -0.5 0.5
+v 0.5 -0.5 0.5
+v 0.5 0.5 0.5
+v -0.5 0.5 0.5
+
+# Back face
+f 1 2 3
+f 1 3 4
+
+# Front face
+f 5 7 6
+f 5 8 7
+
+# Left face
+f 1 5 6
+f 1 6 2
+
+# Right face
+f 3 7 8
+f 3 8 4
+
+# Bottom face
+f 1 4 8
+f 1 8 5
+
+# Top face
+f 2 6 7
+f 2 7 3
diff --git a/src/CortexEngine.App/CortexEngine.App.csproj b/src/CortexEngine.App/CortexEngine.App.csproj
index ad83476..50516b6 100644
--- a/src/CortexEngine.App/CortexEngine.App.csproj
+++ b/src/CortexEngine.App/CortexEngine.App.csproj
@@ -24,4 +24,11 @@
+
+
+ PreserveNewest
+ Content\%(RecursiveDir)%(Filename)%(Extension)
+
+
+
diff --git a/src/CortexEngine.App/Program.cs b/src/CortexEngine.App/Program.cs
index 760689b..120a7b4 100644
--- a/src/CortexEngine.App/Program.cs
+++ b/src/CortexEngine.App/Program.cs
@@ -1,8 +1,10 @@
using System;
+using System.IO;
using System.Numerics;
using Engine.Core;
using Engine.Core.Components;
using Engine.Graphics;
+using Engine.Graphics.Loaders;
using Flecs.NET.Core;
namespace CortexEngine.App;
@@ -11,20 +13,27 @@ class Program
{
static void Main(string[] args)
{
- Console.WriteLine("Cortex Engine Step 3 — Starting up...");
+ Console.WriteLine("Cortex Engine Step 4 — Starting up...");
try
{
using var world = World.Create();
- using var window = new Sdl3Window("Cortex Engine — Step 3", 1280, 720);
+ using var window = new Sdl3Window("Cortex Engine — Step 4", 1280, 720);
var timing = new Timing();
var input = new InputMapping();
using var vulkan = new VulkanContext(window, enableValidation: false);
using var swapchain = new Swapchain(vulkan);
- using var renderer = new TriangleRenderer(vulkan, swapchain);
+ using var renderer = new MeshRenderer(vulkan, swapchain);
- var triangle = world.Entity("Triangle")
- .Set(new Transform(new Vector3(0.0f, 0.0f, 0.0f)));
+ var modelPath = FindModelPath(args);
+ var mesh = modelPath.EndsWith(".gltf", StringComparison.OrdinalIgnoreCase)
+ || modelPath.EndsWith(".glb", StringComparison.OrdinalIgnoreCase)
+ ? GltfLoader.Load(modelPath, new Vector3(0.7f, 0.6f, 0.5f))
+ : ObjLoader.Load(modelPath, new Vector3(0.7f, 0.6f, 0.5f));
+
+ var model = world.Entity("Model")
+ .Set(new Transform(new Vector3(0.0f, 0.0f, 0.0f), Quaternion.Identity, new Vector3(0.5f)))
+ .Set(mesh);
var frames = 0;
var lastFpsTime = 0.0;
@@ -46,14 +55,10 @@ class Program
swapchain.Recreate(lastWidth, lastHeight);
}
- // Animate the entity transform.
- ref var transform = ref triangle.Ensure();
- transform.Position = new Vector3(
- MathF.Sin((float)timing.TotalTime) * 0.5f,
- 0.0f,
- 0.0f);
- transform.Rotation = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, (float)timing.TotalTime);
- transform.Scale = Vector3.One * (1.0f + MathF.Sin((float)timing.TotalTime * 2.0f) * 0.2f);
+ // Slowly rotate the model so we can see it in 3D.
+ ref var transform = ref model.Ensure();
+ transform.Rotation = Quaternion.CreateFromAxisAngle(Vector3.UnitY, (float)timing.TotalTime * 0.5f)
+ * Quaternion.CreateFromAxisAngle(Vector3.UnitX, (float)timing.TotalTime * 0.25f);
renderer.RenderWorld(world);
@@ -74,4 +79,25 @@ class Program
Environment.Exit(1);
}
}
+
+ private static string FindModelPath(string[] args)
+ {
+ if (args.Length > 0 && File.Exists(args[0]))
+ return args[0];
+
+ var candidates = new[]
+ {
+ "Content/cube.obj",
+ "Models/cube.obj",
+ "cube.obj"
+ };
+
+ foreach (var candidate in candidates)
+ {
+ if (File.Exists(candidate))
+ return candidate;
+ }
+
+ throw new FileNotFoundException("No model file found. Pass a .obj/.gltf/.glb path as argument or place Content/cube.obj next to the executable.");
+ }
}
diff --git a/src/Engine.Core/Components/Mesh.cs b/src/Engine.Core/Components/Mesh.cs
new file mode 100644
index 0000000..05db500
--- /dev/null
+++ b/src/Engine.Core/Components/Mesh.cs
@@ -0,0 +1,19 @@
+using Engine.Core;
+
+namespace Engine.Core.Components;
+
+///
+/// A mesh component for the ECS.
+/// Keeps CPU-side vertex/index data that the renderer uploads to GPU buffers.
+///
+public record struct Mesh
+{
+ public Vertex[] Vertices;
+ public uint[] Indices;
+
+ public Mesh(Vertex[] vertices, uint[] indices)
+ {
+ Vertices = vertices;
+ Indices = indices;
+ }
+}
diff --git a/src/Engine.Core/Vertex.cs b/src/Engine.Core/Vertex.cs
new file mode 100644
index 0000000..dc96031
--- /dev/null
+++ b/src/Engine.Core/Vertex.cs
@@ -0,0 +1,19 @@
+using System.Numerics;
+
+namespace Engine.Core;
+
+///
+/// A simple 3D vertex with position and color.
+/// Layout matches the Vulkan vertex input description.
+///
+public struct Vertex
+{
+ public Vector3 Position;
+ public Vector3 Color;
+
+ public Vertex(Vector3 position, Vector3 color)
+ {
+ Position = position;
+ Color = color;
+ }
+}
diff --git a/src/Engine.Graphics/Engine.Graphics.csproj b/src/Engine.Graphics/Engine.Graphics.csproj
index 53ce07f..14b5c6c 100644
--- a/src/Engine.Graphics/Engine.Graphics.csproj
+++ b/src/Engine.Graphics/Engine.Graphics.csproj
@@ -22,6 +22,7 @@
+
diff --git a/src/Engine.Graphics/IndexBuffer.cs b/src/Engine.Graphics/IndexBuffer.cs
new file mode 100644
index 0000000..a2336ae
--- /dev/null
+++ b/src/Engine.Graphics/IndexBuffer.cs
@@ -0,0 +1,113 @@
+using System;
+using Silk.NET.Core;
+using Silk.NET.Vulkan;
+
+namespace Engine.Graphics;
+
+///
+/// GPU index buffer for indexed draws.
+/// Uses 32-bit indices.
+///
+public sealed unsafe class IndexBuffer : IDisposable
+{
+ private readonly VulkanContext _context;
+ public Silk.NET.Vulkan.Buffer Buffer { get; }
+ public DeviceMemory Memory { get; }
+ public ulong Size { get; }
+ public uint Count { get; }
+
+ public IndexBuffer(VulkanContext context, ReadOnlySpan data, uint count)
+ {
+ _context = context;
+ Size = (ulong)data.Length;
+ Count = count;
+
+ Buffer = CreateBuffer(Size, BufferUsageFlags.IndexBufferBit);
+ var memoryRequirements = GetMemoryRequirements(Buffer);
+ Memory = AllocateMemory(memoryRequirements, MemoryPropertyFlags.HostVisibleBit | MemoryPropertyFlags.HostCoherentBit);
+
+ var result = _context.Vk.BindBufferMemory(_context.Device, Buffer, Memory, 0);
+ if (result != Result.Success)
+ throw new InvalidOperationException($"vkBindBufferMemory failed: {result}");
+
+ CopyData(data);
+ }
+
+ private Silk.NET.Vulkan.Buffer CreateBuffer(ulong size, BufferUsageFlags usage)
+ {
+ var createInfo = new BufferCreateInfo
+ {
+ SType = StructureType.BufferCreateInfo,
+ Size = size,
+ Usage = usage,
+ SharingMode = SharingMode.Exclusive
+ };
+
+ Silk.NET.Vulkan.Buffer buffer;
+ var result = _context.Vk.CreateBuffer(_context.Device, &createInfo, null, &buffer);
+ if (result != Result.Success)
+ throw new InvalidOperationException($"vkCreateBuffer failed: {result}");
+ return buffer;
+ }
+
+ private MemoryRequirements GetMemoryRequirements(Silk.NET.Vulkan.Buffer buffer)
+ {
+ MemoryRequirements requirements;
+ _context.Vk.GetBufferMemoryRequirements(_context.Device, buffer, &requirements);
+ return requirements;
+ }
+
+ private DeviceMemory AllocateMemory(MemoryRequirements requirements, MemoryPropertyFlags properties)
+ {
+ var memoryTypeIndex = FindMemoryType(requirements.MemoryTypeBits, properties);
+ var allocateInfo = new MemoryAllocateInfo
+ {
+ SType = StructureType.MemoryAllocateInfo,
+ AllocationSize = requirements.Size,
+ MemoryTypeIndex = memoryTypeIndex
+ };
+
+ DeviceMemory memory;
+ var result = _context.Vk.AllocateMemory(_context.Device, &allocateInfo, null, &memory);
+ if (result != Result.Success)
+ throw new InvalidOperationException($"vkAllocateMemory failed: {result}");
+ return memory;
+ }
+
+ private uint FindMemoryType(uint typeFilter, MemoryPropertyFlags properties)
+ {
+ PhysicalDeviceMemoryProperties memoryProperties;
+ _context.Vk.GetPhysicalDeviceMemoryProperties(_context.PhysicalDevice, &memoryProperties);
+ for (var i = 0; i < memoryProperties.MemoryTypeCount; i++)
+ {
+ if ((typeFilter & (1u << i)) != 0 &&
+ (memoryProperties.MemoryTypes[i].PropertyFlags & properties) == properties)
+ {
+ return (uint)i;
+ }
+ }
+ throw new InvalidOperationException("Failed to find suitable memory type.");
+ }
+
+ private void CopyData(ReadOnlySpan data)
+ {
+ void* mappedData;
+ var result = _context.Vk.MapMemory(_context.Device, Memory, 0, Size, MemoryMapFlags.None, &mappedData);
+ if (result != Result.Success)
+ throw new InvalidOperationException($"vkMapMemory failed: {result}");
+
+ fixed (byte* src = data)
+ {
+ global::System.Buffer.MemoryCopy(src, mappedData, (long)Size, data.Length);
+ }
+
+ _context.Vk.UnmapMemory(_context.Device, Memory);
+ }
+
+ public void Dispose()
+ {
+ _context.Vk.DeviceWaitIdle(_context.Device);
+ _context.Vk.DestroyBuffer(_context.Device, Buffer, null);
+ _context.Vk.FreeMemory(_context.Device, Memory, null);
+ }
+}
diff --git a/src/Engine.Graphics/Loaders/GltfLoader.cs b/src/Engine.Graphics/Loaders/GltfLoader.cs
new file mode 100644
index 0000000..685c12d
--- /dev/null
+++ b/src/Engine.Graphics/Loaders/GltfLoader.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+using Engine.Core;
+using Engine.Core.Components;
+using SharpGLTF.Schema2;
+
+namespace Engine.Graphics.Loaders;
+
+///
+/// glTF/glTF binary loader using SharpGLTF.Core.
+/// Loads the first primitive of the first mesh and converts it to a colored Mesh component.
+///
+public static class GltfLoader
+{
+ public static Engine.Core.Components.Mesh Load(string path, Vector3? defaultColor = null)
+ {
+ var color = defaultColor ?? new Vector3(0.7f, 0.7f, 0.7f);
+
+ var model = ModelRoot.Load(path);
+ if (model.LogicalMeshes.Count == 0)
+ throw new InvalidOperationException($"glTF file has no meshes: {path}");
+
+ var mesh = model.LogicalMeshes[0];
+ if (mesh.Primitives.Count == 0)
+ throw new InvalidOperationException($"glTF mesh has no primitives: {path}");
+
+ var primitive = mesh.Primitives[0];
+
+ if (!primitive.VertexAccessors.TryGetValue("POSITION", out var positionAccessor))
+ throw new InvalidOperationException($"glTF primitive has no POSITION accessor: {path}");
+
+ var positions = positionAccessor.AsVector3Array();
+ var vertices = new Vertex[positions.Count];
+ for (var i = 0; i < positions.Count; i++)
+ {
+ vertices[i] = new Vertex(positions[i], color);
+ }
+
+ uint[] indices;
+ if (primitive.IndexAccessor != null)
+ {
+ var idx = primitive.IndexAccessor.AsIndexArray();
+ indices = new uint[idx.Count];
+ for (var i = 0; i < idx.Count; i++)
+ indices[i] = idx[i];
+ }
+ else
+ {
+ // Non-indexed primitive
+ indices = new uint[positions.Count];
+ for (var i = 0; i < positions.Count; i++)
+ indices[i] = (uint)i;
+ }
+
+ return new Engine.Core.Components.Mesh(vertices, indices);
+ }
+}
diff --git a/src/Engine.Graphics/Loaders/ObjLoader.cs b/src/Engine.Graphics/Loaders/ObjLoader.cs
new file mode 100644
index 0000000..6066d86
--- /dev/null
+++ b/src/Engine.Graphics/Loaders/ObjLoader.cs
@@ -0,0 +1,76 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Numerics;
+using Engine.Core;
+using Engine.Core.Components;
+
+namespace Engine.Graphics.Loaders;
+
+///
+/// Minimal .obj loader.
+/// Supports vertices (v) and faces (f). Ignores normals/UVs for now.
+/// Produces a colored Mesh component.
+///
+public static class ObjLoader
+{
+ public static Mesh Load(string path, Vector3? defaultColor = null)
+ {
+ var color = defaultColor ?? new Vector3(0.7f, 0.7f, 0.7f);
+
+ var positions = new List();
+ var indices = new List();
+
+ foreach (var rawLine in File.ReadLines(path))
+ {
+ var line = rawLine.Trim();
+ if (string.IsNullOrEmpty(line) || line.StartsWith("#"))
+ continue;
+
+ var parts = line.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);
+ if (parts.Length == 0)
+ continue;
+
+ switch (parts[0])
+ {
+ case "v" when parts.Length >= 4:
+ positions.Add(new Vector3(
+ float.Parse(parts[1]),
+ float.Parse(parts[2]),
+ float.Parse(parts[3])));
+ break;
+
+ case "f" when parts.Length >= 4:
+ // Triangulate the face as a fan. Only the position index is used.
+ var baseIndex = ParseFaceIndex(parts[1]);
+ for (var i = 2; i < parts.Length - 1; i++)
+ {
+ indices.Add(baseIndex);
+ indices.Add(ParseFaceIndex(parts[i]));
+ indices.Add(ParseFaceIndex(parts[i + 1]));
+ }
+ break;
+ }
+ }
+
+ if (positions.Count == 0)
+ throw new InvalidOperationException($"OBJ file has no vertices: {path}");
+
+ var vertices = new Vertex[positions.Count];
+ for (var i = 0; i < positions.Count; i++)
+ {
+ vertices[i] = new Vertex(positions[i], color);
+ }
+
+ return new Mesh(vertices, indices.ToArray());
+ }
+
+ private static uint ParseFaceIndex(string part)
+ {
+ // Formats: v, v/vt, v/vt/vn, v//vn
+ var slashIndex = part.IndexOf('/');
+ var indexStr = slashIndex == -1 ? part : part.Substring(0, slashIndex);
+ var index = int.Parse(indexStr);
+ return (uint)(index - 1); // OBJ indices are 1-based
+ }
+}
diff --git a/src/Engine.Graphics/TriangleRenderer.cs b/src/Engine.Graphics/MeshRenderer.cs
similarity index 69%
rename from src/Engine.Graphics/TriangleRenderer.cs
rename to src/Engine.Graphics/MeshRenderer.cs
index 6cbcd4b..ee59e2b 100644
--- a/src/Engine.Graphics/TriangleRenderer.cs
+++ b/src/Engine.Graphics/MeshRenderer.cs
@@ -1,22 +1,24 @@
using System;
+using System.Collections.Generic;
using System.Numerics;
using Flecs.NET.Core;
using Silk.NET.Core;
using Silk.NET.Vulkan;
+using Engine.Core;
using Engine.Core.Components;
namespace Engine.Graphics;
///
-/// Renders a colored triangle using a vertex buffer and a simple graphics pipeline.
-/// Uses Silk.NET.Vulkan and reads entity transforms from the ECS world.
+/// Renders indexed meshes attached to ECS entities.
+/// Uses Silk.NET.Vulkan and reads Mesh + Transform components from the ECS world.
///
-public sealed unsafe class TriangleRenderer : IDisposable
+public sealed unsafe class MeshRenderer : IDisposable
{
private readonly VulkanContext _context;
private readonly Swapchain _swapchain;
private readonly VulkanPipeline _pipeline;
- private readonly VertexBuffer _vertexBuffer;
+ private readonly Dictionary _buffers = new();
private CommandPool _commandPool;
private CommandBuffer[] _commandBuffers = null!;
private Silk.NET.Vulkan.Semaphore[] _imageAvailableSemaphores = null!;
@@ -24,37 +26,35 @@ public sealed unsafe class TriangleRenderer : IDisposable
private Silk.NET.Vulkan.Fence[] _inFlightFences = null!;
private int _currentFrame;
- public TriangleRenderer(VulkanContext context, Swapchain swapchain)
+ private sealed class MeshBuffers : IDisposable
+ {
+ public VertexBuffer VertexBuffer;
+ public IndexBuffer IndexBuffer;
+
+ public MeshBuffers(VertexBuffer vertexBuffer, IndexBuffer indexBuffer)
+ {
+ VertexBuffer = vertexBuffer;
+ IndexBuffer = indexBuffer;
+ }
+
+ public void Dispose()
+ {
+ VertexBuffer.Dispose();
+ IndexBuffer.Dispose();
+ }
+ }
+
+ public MeshRenderer(VulkanContext context, Swapchain swapchain)
{
_context = context;
_swapchain = swapchain;
_pipeline = new VulkanPipeline(context, swapchain);
- _vertexBuffer = CreateTriangleBuffer();
CreateCommandPool();
CreateCommandBuffers();
CreateSyncObjects();
}
- private VertexBuffer CreateTriangleBuffer()
- {
- var vertices = new[]
- {
- 0.0f, -0.5f, 1.0f, 0.0f, 0.0f,
- 0.5f, 0.5f, 0.0f, 1.0f, 0.0f,
- -0.5f, 0.5f, 0.0f, 0.0f, 1.0f
- };
-
- var bytes = new byte[vertices.Length * sizeof(float)];
- fixed (byte* p = bytes)
- fixed (float* v = vertices)
- {
- global::System.Buffer.MemoryCopy(v, p, bytes.Length, vertices.Length * sizeof(float));
- }
-
- return new VertexBuffer(_context, bytes);
- }
-
private void CreateCommandPool()
{
var createInfo = new CommandPoolCreateInfo
@@ -160,16 +160,23 @@ public sealed unsafe class TriangleRenderer : IDisposable
_context.Vk.CmdSetViewport(cmd, 0, 1, &viewport);
_context.Vk.CmdSetScissor(cmd, 0, 1, &scissor);
- var vertexBuffer = _vertexBuffer.Buffer;
- var offset = 0ul;
- _context.Vk.CmdBindVertexBuffers(cmd, 0, 1, &vertexBuffer, &offset);
-
var drawCmd = cmd;
- world.Each((Entity e, ref Transform transform) =>
+ world.Each((Entity e, ref Mesh mesh, ref Transform transform) =>
{
- var bytes = BuildTriangleVertices(transform);
- _vertexBuffer.Update(bytes);
- _context.Vk.CmdDraw(drawCmd, 3, 1, 0, 0);
+ if (!_buffers.TryGetValue(e, out var buffers))
+ {
+ buffers = CreateMeshBuffers(mesh);
+ _buffers[e] = buffers;
+ }
+
+ var bytes = BuildMeshVertices(mesh, transform);
+ buffers.VertexBuffer.Update(bytes);
+
+ var vertexBuffer = buffers.VertexBuffer.Buffer;
+ var offset = 0ul;
+ _context.Vk.CmdBindVertexBuffers(drawCmd, 0, 1, &vertexBuffer, &offset);
+ _context.Vk.CmdBindIndexBuffer(drawCmd, buffers.IndexBuffer.Buffer, 0, IndexType.Uint32);
+ _context.Vk.CmdDrawIndexed(drawCmd, buffers.IndexBuffer.Count, 1, 0, 0, 0);
});
_context.Vk.CmdEndRenderPass(cmd);
@@ -207,34 +214,52 @@ public sealed unsafe class TriangleRenderer : IDisposable
_currentFrame++;
}
- private byte[] BuildTriangleVertices(Transform transform)
+ private MeshBuffers CreateMeshBuffers(Mesh mesh)
+ {
+ var vertexBytes = new byte[mesh.Vertices.Length * 6 * sizeof(float)];
+ fixed (byte* p = vertexBytes)
+ {
+ var dst = (float*)p;
+ for (var i = 0; i < mesh.Vertices.Length; i++)
+ {
+ var v = mesh.Vertices[i];
+ dst[i * 6 + 0] = v.Position.X;
+ dst[i * 6 + 1] = v.Position.Y;
+ dst[i * 6 + 2] = v.Position.Z;
+ dst[i * 6 + 3] = v.Color.X;
+ dst[i * 6 + 4] = v.Color.Y;
+ dst[i * 6 + 5] = v.Color.Z;
+ }
+ }
+
+ var indexBytes = new byte[mesh.Indices.Length * sizeof(uint)];
+ fixed (byte* p = indexBytes)
+ fixed (uint* src = mesh.Indices)
+ {
+ global::System.Buffer.MemoryCopy(src, p, indexBytes.Length, mesh.Indices.Length * sizeof(uint));
+ }
+
+ return new MeshBuffers(
+ new VertexBuffer(_context, vertexBytes),
+ new IndexBuffer(_context, indexBytes, (uint)mesh.Indices.Length));
+ }
+
+ private byte[] BuildMeshVertices(Mesh mesh, Transform transform)
{
var matrix = transform.GetMatrix();
- var positions = new Vector3[]
- {
- new Vector3(0.0f, -0.5f, 0.0f),
- new Vector3(0.5f, 0.5f, 0.0f),
- new Vector3(-0.5f, 0.5f, 0.0f)
- };
- var colors = new[]
- {
- new Vector3(1.0f, 0.0f, 0.0f),
- new Vector3(0.0f, 1.0f, 0.0f),
- new Vector3(0.0f, 0.0f, 1.0f)
- };
-
- var bytes = new byte[3 * 5 * sizeof(float)];
+ var bytes = new byte[mesh.Vertices.Length * 6 * sizeof(float)];
fixed (byte* p = bytes)
{
var dst = (float*)p;
- for (var i = 0; i < 3; i++)
+ for (var i = 0; i < mesh.Vertices.Length; i++)
{
- var transformed = Vector3.Transform(positions[i], matrix);
- dst[i * 5 + 0] = transformed.X;
- dst[i * 5 + 1] = transformed.Y;
- dst[i * 5 + 2] = colors[i].X;
- dst[i * 5 + 3] = colors[i].Y;
- dst[i * 5 + 4] = colors[i].Z;
+ var transformed = Vector3.Transform(mesh.Vertices[i].Position, matrix);
+ dst[i * 6 + 0] = transformed.X;
+ dst[i * 6 + 1] = transformed.Y;
+ dst[i * 6 + 2] = transformed.Z;
+ dst[i * 6 + 3] = mesh.Vertices[i].Color.X;
+ dst[i * 6 + 4] = mesh.Vertices[i].Color.Y;
+ dst[i * 6 + 5] = mesh.Vertices[i].Color.Z;
}
}
return bytes;
@@ -244,6 +269,10 @@ public sealed unsafe class TriangleRenderer : IDisposable
{
_context.Vk.DeviceWaitIdle(_context.Device);
+ foreach (var buffers in _buffers.Values)
+ buffers.Dispose();
+ _buffers.Clear();
+
for (var i = 0; i < 2; i++)
{
_context.Vk.DestroySemaphore(_context.Device, _renderFinishedSemaphores[i], null);
@@ -253,7 +282,6 @@ public sealed unsafe class TriangleRenderer : IDisposable
_context.Vk.DestroyCommandPool(_context.Device, _commandPool, null);
- _vertexBuffer.Dispose();
_pipeline.Dispose();
}
}
diff --git a/src/Engine.Graphics/Shaders/fragment.frag b/src/Engine.Graphics/Shaders/fragment.frag
new file mode 100644
index 0000000..5906065
--- /dev/null
+++ b/src/Engine.Graphics/Shaders/fragment.frag
@@ -0,0 +1,10 @@
+#version 450
+
+layout(location = 0) in vec3 fragColor;
+
+layout(location = 0) out vec4 outColor;
+
+void main()
+{
+ outColor = vec4(fragColor, 1.0);
+}
diff --git a/src/Engine.Graphics/Shaders/vertex.spv b/src/Engine.Graphics/Shaders/vertex.spv
index 791f44a..5a0dd52 100644
Binary files a/src/Engine.Graphics/Shaders/vertex.spv and b/src/Engine.Graphics/Shaders/vertex.spv differ
diff --git a/src/Engine.Graphics/Shaders/vertex.vert b/src/Engine.Graphics/Shaders/vertex.vert
new file mode 100644
index 0000000..0031f34
--- /dev/null
+++ b/src/Engine.Graphics/Shaders/vertex.vert
@@ -0,0 +1,12 @@
+#version 450
+
+layout(location = 0) in vec3 inPosition;
+layout(location = 1) in vec3 inColor;
+
+layout(location = 0) out vec3 fragColor;
+
+void main()
+{
+ gl_Position = vec4(inPosition, 1.0);
+ fragColor = inColor;
+}
diff --git a/src/Engine.Graphics/VulkanPipeline.cs b/src/Engine.Graphics/VulkanPipeline.cs
index 298cec0..b4dfb95 100644
--- a/src/Engine.Graphics/VulkanPipeline.cs
+++ b/src/Engine.Graphics/VulkanPipeline.cs
@@ -5,7 +5,7 @@ using Silk.NET.Vulkan;
namespace Engine.Graphics;
///
-/// Simple graphics pipeline for a triangle with vec2 position + vec3 color.
+/// Simple graphics pipeline for indexed meshes with vec3 position + vec3 color.
/// Uses Silk.NET.Vulkan.
///
public sealed unsafe class VulkanPipeline : IDisposable
@@ -93,7 +93,7 @@ public sealed unsafe class VulkanPipeline : IDisposable
var bindingDescription = new VertexInputBindingDescription
{
Binding = 0,
- Stride = (uint)(5 * sizeof(float)),
+ Stride = (uint)(6 * sizeof(float)),
InputRate = VertexInputRate.Vertex
};
@@ -103,7 +103,7 @@ public sealed unsafe class VulkanPipeline : IDisposable
{
Binding = 0,
Location = 0,
- Format = Format.R32G32Sfloat,
+ Format = Format.R32G32B32Sfloat,
Offset = 0
},
new VertexInputAttributeDescription
@@ -111,7 +111,7 @@ public sealed unsafe class VulkanPipeline : IDisposable
Binding = 0,
Location = 1,
Format = Format.R32G32B32Sfloat,
- Offset = (uint)(2 * sizeof(float))
+ Offset = (uint)(3 * sizeof(float))
}
};