From 05703f820f0333befbe0e711cc0eb62fde69d3fe Mon Sep 17 00:00:00 2001 From: emil28092005 Date: Tue, 16 Jun 2026 19:15:20 +0300 Subject: [PATCH] feat: Step 4 load .obj and glTF models into ECS - Add Vertex struct and Mesh ECS component (vertices + indices). - Add Vulkan IndexBuffer for indexed draws. - Add MeshRenderer that draws Mesh + Transform entities with vkCmdDrawIndexed. - Add minimal .obj loader (positions, faces) and glTF/glTF-binary loader via SharpGLTF.Core. - Rewrite shaders to 3D position + color; recompile to SPIR-V. - Update VulkanPipeline for new vertex format. - Ship a sample Content/cube.obj and wire Program.cs to load it. - Remove TriangleRenderer (replaced by MeshRenderer). --- Content/cube.obj | 33 +++++ src/CortexEngine.App/CortexEngine.App.csproj | 7 + src/CortexEngine.App/Program.cs | 52 +++++-- src/Engine.Core/Components/Mesh.cs | 19 +++ src/Engine.Core/Vertex.cs | 19 +++ src/Engine.Graphics/Engine.Graphics.csproj | 1 + src/Engine.Graphics/IndexBuffer.cs | 113 ++++++++++++++ src/Engine.Graphics/Loaders/GltfLoader.cs | 58 ++++++++ src/Engine.Graphics/Loaders/ObjLoader.cs | 76 ++++++++++ .../{TriangleRenderer.cs => MeshRenderer.cs} | 140 +++++++++++------- src/Engine.Graphics/Shaders/fragment.frag | 10 ++ src/Engine.Graphics/Shaders/vertex.spv | Bin 1008 -> 980 bytes src/Engine.Graphics/Shaders/vertex.vert | 12 ++ src/Engine.Graphics/VulkanPipeline.cs | 8 +- 14 files changed, 475 insertions(+), 73 deletions(-) create mode 100644 Content/cube.obj create mode 100644 src/Engine.Core/Components/Mesh.cs create mode 100644 src/Engine.Core/Vertex.cs create mode 100644 src/Engine.Graphics/IndexBuffer.cs create mode 100644 src/Engine.Graphics/Loaders/GltfLoader.cs create mode 100644 src/Engine.Graphics/Loaders/ObjLoader.cs rename src/Engine.Graphics/{TriangleRenderer.cs => MeshRenderer.cs} (69%) create mode 100644 src/Engine.Graphics/Shaders/fragment.frag create mode 100644 src/Engine.Graphics/Shaders/vertex.vert 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 791f44ad0f2e9337698a7d7d7391eb1397110276..5a0dd5216a87feae9408543a7713081f84e3cbe9 100644 GIT binary patch delta 249 zcmeyseubTvnMs+Qfq{{Mn}LHtej=|ws|*7JgY3k_K5s=Nwz``Iq z*_N?d5F{f9#30}f7Cp&$o{@R-OeRC?273jdh$K*k8Hj;KGl2NgP`&_=wubUSrhwFd z_<}%McCsn6HKXX{LS|u0F(6+Uh#7%65NI@*>w$ z5fgrRKD_t%c0Nu`TYX1FNmVM+%W50DBO+0@dw3N4baz;tj;9pbmXbtfrp|RXUBG9Z zr%&}#mbqJ_i^vd38I70s*F_8;LTi923E*3J^BZRD+l6L6Ej(BLJ=YZY_w-Sbjqzs| ubP0$Fb)tN;)b1dFbpg+9vCQoz@BzC9@XSpoF>j3lZ=7S3%k*EIoq!)z>lq^e 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)) } };