diff --git a/.gitignore b/.gitignore
index 10ece78..6abaf68 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,3 +29,4 @@ Screenshots/
!launchSettings.json
!claude_desktop_config.json
.playwright-mcp/
+Vulkan-Guide/
diff --git a/CORTEX_ENGINE.sln b/CORTEX_ENGINE.sln
index 0855831..1d16db6 100644
--- a/CORTEX_ENGINE.sln
+++ b/CORTEX_ENGINE.sln
@@ -13,16 +13,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.AI", "src\Engine.AI\
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.Graphics.Vulkan", "src\Engine.Graphics.Vulkan\Engine.Graphics.Vulkan.csproj", "{43C6A648-4F0C-4440-95E3-733BD8D29BCE}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.Graphics.Raylib", "src\Engine.Graphics.Raylib\Engine.Graphics.Raylib.csproj", "{31447693-7B61-4B22-95BB-47FF08C1CB2A}"
-EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.Tests", "tests\Engine.Tests\Engine.Tests.csproj", "{55555555-5555-5555-5555-555555555555}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.Physics", "src\Engine.Physics\Engine.Physics.csproj", "{3F5F10AA-A4CD-4E37-B4CE-B37BC60C3883}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.Graphics.OpenTK", "src\Engine.Graphics.OpenTK\Engine.Graphics.OpenTK.csproj", "{641B69F3-E52C-4713-A97C-DAC1C814DD72}"
-EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -126,24 +122,6 @@ Global
{43C6A648-4F0C-4440-95E3-733BD8D29BCE}.ReleaseAOT|x64.Build.0 = ReleaseAOT|Any CPU
{43C6A648-4F0C-4440-95E3-733BD8D29BCE}.ReleaseAOT|x86.ActiveCfg = ReleaseAOT|Any CPU
{43C6A648-4F0C-4440-95E3-733BD8D29BCE}.ReleaseAOT|x86.Build.0 = ReleaseAOT|Any CPU
- {31447693-7B61-4B22-95BB-47FF08C1CB2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {31447693-7B61-4B22-95BB-47FF08C1CB2A}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {31447693-7B61-4B22-95BB-47FF08C1CB2A}.Debug|x64.ActiveCfg = Debug|Any CPU
- {31447693-7B61-4B22-95BB-47FF08C1CB2A}.Debug|x64.Build.0 = Debug|Any CPU
- {31447693-7B61-4B22-95BB-47FF08C1CB2A}.Debug|x86.ActiveCfg = Debug|Any CPU
- {31447693-7B61-4B22-95BB-47FF08C1CB2A}.Debug|x86.Build.0 = Debug|Any CPU
- {31447693-7B61-4B22-95BB-47FF08C1CB2A}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {31447693-7B61-4B22-95BB-47FF08C1CB2A}.Release|Any CPU.Build.0 = Release|Any CPU
- {31447693-7B61-4B22-95BB-47FF08C1CB2A}.Release|x64.ActiveCfg = Release|Any CPU
- {31447693-7B61-4B22-95BB-47FF08C1CB2A}.Release|x64.Build.0 = Release|Any CPU
- {31447693-7B61-4B22-95BB-47FF08C1CB2A}.Release|x86.ActiveCfg = Release|Any CPU
- {31447693-7B61-4B22-95BB-47FF08C1CB2A}.Release|x86.Build.0 = Release|Any CPU
- {31447693-7B61-4B22-95BB-47FF08C1CB2A}.ReleaseAOT|Any CPU.ActiveCfg = ReleaseAOT|Any CPU
- {31447693-7B61-4B22-95BB-47FF08C1CB2A}.ReleaseAOT|Any CPU.Build.0 = ReleaseAOT|Any CPU
- {31447693-7B61-4B22-95BB-47FF08C1CB2A}.ReleaseAOT|x64.ActiveCfg = ReleaseAOT|Any CPU
- {31447693-7B61-4B22-95BB-47FF08C1CB2A}.ReleaseAOT|x64.Build.0 = ReleaseAOT|Any CPU
- {31447693-7B61-4B22-95BB-47FF08C1CB2A}.ReleaseAOT|x86.ActiveCfg = ReleaseAOT|Any CPU
- {31447693-7B61-4B22-95BB-47FF08C1CB2A}.ReleaseAOT|x86.Build.0 = ReleaseAOT|Any CPU
{55555555-5555-5555-5555-555555555555}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{55555555-5555-5555-5555-555555555555}.Debug|Any CPU.Build.0 = Debug|Any CPU
{55555555-5555-5555-5555-555555555555}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -178,30 +156,11 @@ Global
{3F5F10AA-A4CD-4E37-B4CE-B37BC60C3883}.ReleaseAOT|x64.Build.0 = Debug|Any CPU
{3F5F10AA-A4CD-4E37-B4CE-B37BC60C3883}.ReleaseAOT|x86.ActiveCfg = Debug|Any CPU
{3F5F10AA-A4CD-4E37-B4CE-B37BC60C3883}.ReleaseAOT|x86.Build.0 = Debug|Any CPU
- {641B69F3-E52C-4713-A97C-DAC1C814DD72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {641B69F3-E52C-4713-A97C-DAC1C814DD72}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {641B69F3-E52C-4713-A97C-DAC1C814DD72}.Debug|x64.ActiveCfg = Debug|Any CPU
- {641B69F3-E52C-4713-A97C-DAC1C814DD72}.Debug|x64.Build.0 = Debug|Any CPU
- {641B69F3-E52C-4713-A97C-DAC1C814DD72}.Debug|x86.ActiveCfg = Debug|Any CPU
- {641B69F3-E52C-4713-A97C-DAC1C814DD72}.Debug|x86.Build.0 = Debug|Any CPU
- {641B69F3-E52C-4713-A97C-DAC1C814DD72}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {641B69F3-E52C-4713-A97C-DAC1C814DD72}.Release|Any CPU.Build.0 = Release|Any CPU
- {641B69F3-E52C-4713-A97C-DAC1C814DD72}.Release|x64.ActiveCfg = Release|Any CPU
- {641B69F3-E52C-4713-A97C-DAC1C814DD72}.Release|x64.Build.0 = Release|Any CPU
- {641B69F3-E52C-4713-A97C-DAC1C814DD72}.Release|x86.ActiveCfg = Release|Any CPU
- {641B69F3-E52C-4713-A97C-DAC1C814DD72}.Release|x86.Build.0 = Release|Any CPU
- {641B69F3-E52C-4713-A97C-DAC1C814DD72}.ReleaseAOT|Any CPU.ActiveCfg = Debug|Any CPU
- {641B69F3-E52C-4713-A97C-DAC1C814DD72}.ReleaseAOT|Any CPU.Build.0 = Debug|Any CPU
- {641B69F3-E52C-4713-A97C-DAC1C814DD72}.ReleaseAOT|x64.ActiveCfg = Debug|Any CPU
- {641B69F3-E52C-4713-A97C-DAC1C814DD72}.ReleaseAOT|x64.Build.0 = Debug|Any CPU
- {641B69F3-E52C-4713-A97C-DAC1C814DD72}.ReleaseAOT|x86.ActiveCfg = Debug|Any CPU
- {641B69F3-E52C-4713-A97C-DAC1C814DD72}.ReleaseAOT|x86.Build.0 = Debug|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{3F5F10AA-A4CD-4E37-B4CE-B37BC60C3883} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
- {641B69F3-E52C-4713-A97C-DAC1C814DD72} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
EndGlobalSection
EndGlobal
diff --git a/src/CortexEngine.App/CortexEngine.App.csproj b/src/CortexEngine.App/CortexEngine.App.csproj
index 3339606..3b05d66 100644
--- a/src/CortexEngine.App/CortexEngine.App.csproj
+++ b/src/CortexEngine.App/CortexEngine.App.csproj
@@ -22,11 +22,16 @@
-
-
+
+
+
+
+ PreserveNewest
+ Shaders\%(Filename)%(Extension)
+
diff --git a/src/CortexEngine.App/ObjectManipulator.cs b/src/CortexEngine.App/ObjectManipulator.cs
deleted file mode 100644
index a7271af..0000000
--- a/src/CortexEngine.App/ObjectManipulator.cs
+++ /dev/null
@@ -1,177 +0,0 @@
-using System.Numerics;
-using Engine.Core;
-using Engine.Core.Components;
-using Engine.Physics;
-using Flecs.NET.Core;
-using Raylib_cs;
-using EngineMesh = Engine.Core.Components.Mesh;
-using EngineTransform = Engine.Core.Components.Transform;
-using EngineRigidBody = Engine.Core.Components.RigidBody;
-
-namespace CortexEngine.App;
-
-///
-/// Unity-like object manipulation: left-click to pick, drag to move on ground plane.
-/// Only active when ImGui is not capturing the mouse.
-///
-public sealed class ObjectManipulator
-{
- private Entity _selectedEntity;
- private bool _isDragging;
- private Vector3 _dragOffset;
- private float _dragDepth;
-
- public string SelectedEntityName => _selectedEntity.IsValid() ? _selectedEntity.Name() : "";
- public bool IsDragging => _isDragging;
-
- ///
- /// Process input for object picking and dragging.
- /// Returns true if input was consumed (ImGui should be ignored).
- ///
- private bool _imGuiAvailable;
-
- public void SetImGuiAvailable(bool available) => _imGuiAvailable = available;
-
- public bool ProcessInput(World world, Camera camera, IInputState input)
- {
- // Skip if ImGui is capturing mouse
- if (_imGuiAvailable && ImGuiNET.ImGui.GetIO().WantCaptureMouse)
- {
- _isDragging = false;
- return false;
- }
-
- var mousePos = new Vector2(input.MouseX, input.MouseY);
- var ray = Raylib.GetScreenToWorldRay(mousePos, ToRaylibCamera(camera));
-
- // Left click — pick or start drag
- if (input.MouseLeft && !_isDragging)
- {
- var hit = RaycastEntities(world, ray);
- if (hit.IsValid())
- {
- _selectedEntity = hit;
- _isDragging = true;
- // Store depth along camera forward axis and offset from object center
- var objPos = hit.Get().Position;
- var camForward = Vector3.Normalize(camera.Target - camera.Position);
- _dragDepth = Vector3.Dot(objPos - camera.Position, camForward);
- _dragOffset = objPos - ProjectToCameraPlane(ray, camera, _dragDepth);
- return true;
- }
- }
-
- // Drag — move object on plane orthogonal to camera (screen-space movement)
- if (_isDragging)
- {
- if (input.MouseLeft)
- {
- var targetPos = ProjectToCameraPlane(ray, camera, _dragDepth) + _dragOffset;
-
- if (_selectedEntity.IsValid() && _selectedEntity.Has())
- {
- var t = _selectedEntity.Get();
- t.Position = targetPos;
- _selectedEntity.Set(t);
- }
- return true;
- }
- else
- {
- _isDragging = false;
- }
- }
-
- return false;
- }
-
- ///
- /// After physics step, re-sync dragged entity position to physics body.
- ///
- public void SyncToPhysics(PhysicsWorld physicsWorld)
- {
- if (_isDragging && _selectedEntity.IsValid() && _selectedEntity.Has())
- {
- var t = _selectedEntity.Get();
- physicsWorld.SyncToPhysics(_selectedEntity, t);
- }
- }
-
- ///
- /// Get the entity currently being dragged (for physics sync skip).
- ///
- public Entity? GetDraggedEntity() => _isDragging ? _selectedEntity : null;
-
- private static Entity RaycastEntities(World world, Ray ray)
- {
- Entity closest = default;
- var closestDist = float.MaxValue;
-
- world.Each((Entity e, ref EngineTransform t, ref EngineMesh _) =>
- {
- var name = e.Name();
- if (string.IsNullOrEmpty(name) || name == "Grid" || name == "Floor")
- return;
-
- // Simple sphere intersection using position + approximate radius
- var radius = 1.0f;
- if (e.Has())
- {
- var rb = e.Get();
- radius = rb.ShapeSize.Length();
- }
-
- var toCenter = t.Position - ray.Position;
- var proj = Vector3.Dot(toCenter, ray.Direction);
- if (proj < 0) return; // behind camera
-
- var closestPoint = ray.Position + ray.Direction * proj;
- var dist = Vector3.Distance(closestPoint, t.Position);
-
- if (dist <= radius)
- {
- var rayDist = Vector3.Distance(ray.Position, t.Position);
- if (rayDist < closestDist)
- {
- closestDist = rayDist;
- closest = e;
- }
- }
- });
-
- return closest;
- }
-
- ///
- /// Project a screen ray onto a plane orthogonal to the camera at the given depth.
- /// This makes objects move in screen-space (like Unity's screen-space drag).
- ///
- private static Vector3 ProjectToCameraPlane(Ray ray, Camera camera, float depth)
- {
- var camForward = Vector3.Normalize(camera.Target - camera.Position);
- var planePoint = camera.Position + camForward * depth;
-
- // Ray-plane intersection: plane through planePoint with normal = camForward
- var denom = Vector3.Dot(ray.Direction, camForward);
- if (MathF.Abs(denom) < 0.0001f)
- return planePoint;
-
- var t = Vector3.Dot(planePoint - ray.Position, camForward) / denom;
- if (t < 0)
- return planePoint;
-
- return ray.Position + ray.Direction * t;
- }
-
- private static Camera3D ToRaylibCamera(Camera camera)
- {
- return new Camera3D
- {
- Position = camera.Position,
- Target = camera.Target,
- Up = camera.Up,
- FovY = camera.FieldOfView * 180.0f / MathF.PI,
- Projection = CameraProjection.Perspective
- };
- }
-}
diff --git a/src/CortexEngine.App/Program.cs b/src/CortexEngine.App/Program.cs
index ead0988..b59cc2b 100644
--- a/src/CortexEngine.App/Program.cs
+++ b/src/CortexEngine.App/Program.cs
@@ -11,8 +11,6 @@ using Engine.Core;
using Engine.Core.Components;
using Engine.Graphics;
using Engine.Graphics.Loaders;
-using Engine.Graphics.OpenTK;
-using Engine.Graphics.RaylibBackend;
using Engine.Graphics.Vulkan;
using Engine.Physics;
using Flecs.NET.Core;
@@ -23,7 +21,7 @@ class Program
{
static async Task Main(string[] args)
{
- Console.WriteLine("Cortex Engine — Materials, Grid, Lighting, FreeFly Camera...");
+ Console.WriteLine("Cortex Engine — Vulkan Backend, Pure P/Invoke...");
try
{
@@ -42,26 +40,12 @@ class Program
var timing = new Timing();
using var physicsWorld = new PhysicsWorld();
- RaylibBackendRegistrar.EnsureRegistered();
VulkanBackendRegistrar.EnsureRegistered();
- OpenTKBackendRegistrar.EnsureRegistered();
- using var renderContext = RenderBackendFactory.Create("raylib", 1280, 720, enableValidation: false);
+ using var renderContext = RenderBackendFactory.Create("vulkan", 1280, 720, enableValidation: true);
var window = renderContext.Window;
var input = window.Input;
using var renderer = renderContext.CreateRenderer();
- // ImGui editor layer (Raylib only)
- ImGuiLayer? imGuiLayer = null;
- var objectManipulator = new ObjectManipulator();
- objectManipulator.SetImGuiAvailable(false);
- if (!cameraTour && renderer is RaylibRenderer rlRenderer)
- {
- imGuiLayer = new ImGuiLayer();
- imGuiLayer.Initialize();
- rlRenderer.ImGuiLayer = imGuiLayer;
- objectManipulator.SetImGuiAvailable(true);
- }
-
var (modelPath, mcpPort) = ParseArgs(args);
var mesh = LoadModel(modelPath);
@@ -108,7 +92,6 @@ class Program
if (mcpPort > 0)
{
- // Start the MCP server in the background so AI agents can connect via HTTP.
mcpApp = McpEngineServerHost.Create(args, queue, port: mcpPort);
mcpTask = mcpApp.RunAsync();
_ = mcpTask.ContinueWith(t =>
@@ -129,7 +112,6 @@ class Program
}
#endif
-
var frames = 0;
var lastFpsTime = 0.0;
var lastWidth = window.Width;
@@ -177,7 +159,6 @@ class Program
window.PumpEvents();
input.BeginFrame();
- // Drain any commands that arrived from the MCP server.
var processed = queue.ProcessPending();
if (processed > 0)
Console.WriteLine($"Processed {processed} AI command(s)");
@@ -191,7 +172,6 @@ class Program
camera.AspectRatio = (float)lastWidth / lastHeight;
}
- // Toggle camera controller on F key press.
if (input.IsKeyPressed(Key.F))
{
activeControllerIndex = (activeControllerIndex + 1) % cameraControllers.Length;
@@ -199,7 +179,6 @@ class Program
Console.WriteLine($"Active camera controller: {cameraController.Name}");
}
- // Update the active camera controller from input, unless the camera tour is driving the pose.
if (!cameraTour)
cameraController.Update(input, (float)timing.DeltaTime);
@@ -213,7 +192,6 @@ class Program
tourScreenshotPending = true;
}
- // Hold the pose for a few frames to let the GPU settle, then screenshot.
if (tourScreenshotPending)
{
tourSettleFrames++;
@@ -226,7 +204,6 @@ class Program
}
}
- // After the screenshot has been saved, advance to the next pose.
if (!tourScreenshotPending && !renderer.IsScreenshotRequested)
{
tourIndex++;
@@ -245,14 +222,6 @@ class Program
}
}
- // Object manipulation (Unity-like drag)
- if (!cameraTour)
- {
- var cam = cameraEntity.Get();
- objectManipulator.ProcessInput(world, cam, input);
- }
-
- // Physics: create bodies, step, sync transforms
if (!cameraTour)
{
var toInit = new List<(Entity, RigidBody, Transform)>();
@@ -270,36 +239,19 @@ class Program
e.Set(rb);
}
- // Sync dragged object to physics before stepping
- objectManipulator.SyncToPhysics(physicsWorld);
-
physicsWorld.Update((float)timing.DeltaTime);
-
- // Sync all transforms EXCEPT the dragged object
- physicsWorld.SyncTransforms(world, objectManipulator.IsDragging ? objectManipulator.GetDraggedEntity() : null);
+ physicsWorld.SyncTransforms(world, null);
}
- // Capture a demo screenshot after the scene warms up (non-tour mode only).
if (!demoScreenshotRequested && !cameraTour && frames >= 15)
{
renderer.RequestScreenshot("Screenshots/demo.png");
demoScreenshotRequested = true;
}
- // Feed frame data to ImGui before rendering.
- if (imGuiLayer != null)
- imGuiLayer.SetFrameData(world, timing, currentFps);
-
- objectManipulator.SetImGuiAvailable(imGuiLayer != null);
-
renderer.RenderWorld(world);
queue.CompletePendingScreenshots();
- // Swap buffers for OpenGL backend
- if (renderContext is OpenTKRenderContext otkCtx)
- otkCtx.SwapBuffers();
- // Raylib handles swap internally in EndDrawing
-
frames++;
if (timing.TotalTime - lastFpsTime >= 1.0)
{
@@ -311,7 +263,6 @@ class Program
}
Console.WriteLine("Shutting down...");
- imGuiLayer?.Dispose();
#if !RELEASE_AOT
if (mcpApp != null)
await mcpApp.StopAsync();
@@ -369,12 +320,7 @@ class Program
world.Entity("TorusKnot")
.Set(new Transform(new Vector3(0, 0.5f, -6), Quaternion.Identity, new Vector3(1.5f)))
.Set(torusKnot)
- .Set(new Material(new Vector3(0.9f, 0.9f, 0.9f), roughness: 0.25f, metallic: 0.6f, texturePath: "Content/checker.png"));
-
- world.Entity("CubeTextured")
- .Set(new Transform(new Vector3(-4, 0.5f, -3), Quaternion.Identity, new Vector3(0.7f)))
- .Set(mesh)
- .Set(new Material(new Vector3(0.8f, 0.8f, 0.85f), roughness: 0.4f, metallic: 0.0f, texturePath: "Content/checker.png"));
+ .Set(new Material(new Vector3(0.9f, 0.9f, 0.9f), roughness: 0.25f, metallic: 0.6f));
world.Entity("Floor")
.Set(new Transform(new Vector3(0, -0.5f, 0), Quaternion.Identity, new Vector3(20, 0.5f, 20)))
@@ -390,17 +336,16 @@ class Program
private static void CreateCalibrationScene(World world, Mesh mesh)
{
- // Colored cubes at known world positions for visual analysis of perspective and camera movement.
var positions = new (string name, Vector3 pos, Vector3 color)[]
{
- ("CubeOrigin", new Vector3(0.0f, 0.5f, 0.0f), new Vector3(1.0f, 1.0f, 1.0f)), // white at origin
- ("CubeRight", new Vector3(2.0f, 0.5f, 0.0f), new Vector3(1.0f, 0.0f, 0.0f)), // red +X
- ("CubeLeft", new Vector3(-2.0f, 0.5f, 0.0f), new Vector3(0.0f, 1.0f, 0.0f)), // green -X
- ("CubeFront", new Vector3(0.0f, 0.5f, 2.0f), new Vector3(0.0f, 0.0f, 1.0f)), // blue +Z
- ("CubeBack", new Vector3(0.0f, 0.5f, -2.0f), new Vector3(1.0f, 1.0f, 0.0f)), // yellow -Z
- ("CubeUp", new Vector3(0.0f, 2.5f, 0.0f), new Vector3(1.0f, 0.0f, 1.0f)), // magenta +Y
- ("CubeFar", new Vector3(0.0f, 0.5f, 8.0f), new Vector3(0.0f, 1.0f, 1.0f)), // cyan far +Z
- ("CubeFarLeft", new Vector3(-5.0f, 0.5f, 5.0f), new Vector3(0.5f, 0.5f, 1.0f)) // light blue far corner
+ ("CubeOrigin", new Vector3(0.0f, 0.5f, 0.0f), new Vector3(1.0f, 1.0f, 1.0f)),
+ ("CubeRight", new Vector3(2.0f, 0.5f, 0.0f), new Vector3(1.0f, 0.0f, 0.0f)),
+ ("CubeLeft", new Vector3(-2.0f, 0.5f, 0.0f), new Vector3(0.0f, 1.0f, 0.0f)),
+ ("CubeFront", new Vector3(0.0f, 0.5f, 2.0f), new Vector3(0.0f, 0.0f, 1.0f)),
+ ("CubeBack", new Vector3(0.0f, 0.5f, -2.0f), new Vector3(1.0f, 1.0f, 0.0f)),
+ ("CubeUp", new Vector3(0.0f, 2.5f, 0.0f), new Vector3(1.0f, 0.0f, 1.0f)),
+ ("CubeFar", new Vector3(0.0f, 0.5f, 8.0f), new Vector3(0.0f, 1.0f, 1.0f)),
+ ("CubeFarLeft", new Vector3(-5.0f, 0.5f, 5.0f), new Vector3(0.5f, 0.5f, 1.0f))
};
foreach (var (name, pos, color) in positions)
@@ -411,7 +356,6 @@ class Program
.Set(new Material(color, roughness: 0.5f, metallic: 0.1f));
}
- // A large reference grid at Y=0.
world.Entity("Grid")
.Set(new Transform(new Vector3(0.0f, 0.0f, 0.0f), Quaternion.Identity, Vector3.One))
.Set(ProceduralMesh.CreateGrid(20, 1.0f, new Vector3(0.5f, 0.5f, 0.55f)))
@@ -445,13 +389,12 @@ class Program
private static string FindModelPath(string[] args)
{
- // Skip recognized flags so they are not treated as a model path.
for (var i = 0; i < args.Length; i++)
{
var arg = args[i];
if (arg == "--mcp-port")
{
- i++; // skip the value
+ i++;
continue;
}
diff --git a/src/Engine.Graphics.Vulkan/Engine.Graphics.Vulkan.csproj b/src/Engine.Graphics.Vulkan/Engine.Graphics.Vulkan.csproj
new file mode 100644
index 0000000..c1bb767
--- /dev/null
+++ b/src/Engine.Graphics.Vulkan/Engine.Graphics.Vulkan.csproj
@@ -0,0 +1,36 @@
+
+
+
+ net9.0
+ enable
+ enable
+ true
+ true
+
+
+
+ DEV_MODE
+
+
+
+ RELEASE_AOT
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Shaders\%(Filename)%(Extension)
+
+
+
+
diff --git a/src/Engine.Graphics.Vulkan/Shaders/fragment.frag b/src/Engine.Graphics.Vulkan/Shaders/fragment.frag
new file mode 100644
index 0000000..7fdeaba
--- /dev/null
+++ b/src/Engine.Graphics.Vulkan/Shaders/fragment.frag
@@ -0,0 +1,81 @@
+#version 450
+
+layout(location = 0) in vec3 fragColor;
+layout(location = 1) in vec3 fragNormal;
+layout(location = 2) in vec3 fragWorldPos;
+layout(location = 3) in vec3 fragViewDir;
+
+layout(row_major, set = 0, binding = 0) uniform FrameUBO {
+ vec3 cameraPosition;
+ uint lightCount;
+ vec3 ambientColor;
+ float pad0;
+ vec4 lightData[16];
+} frame;
+
+layout(push_constant) uniform PushConstants {
+ mat4 mvp;
+ mat4 model;
+ vec4 material;
+} pc;
+
+layout(location = 0) out vec4 outColor;
+
+vec3 ACESFilm(vec3 x) {
+ float a = 2.51;
+ float b = 0.03;
+ float c = 2.43;
+ float d = 0.59;
+ float e = 0.14;
+ return clamp((x * (a * x + b)) / (x * (c * x + d) + e), 0.0, 1.0);
+}
+
+void main() {
+ vec3 albedo = fragColor * pc.material.rgb;
+ float roughness = clamp(pc.material.a, 0.05, 1.0);
+
+ vec3 N = normalize(fragNormal);
+ vec3 V = normalize(fragViewDir);
+
+ vec3 finalColor = frame.ambientColor * albedo;
+
+ for (uint i = 0u; i < frame.lightCount && i < 16u; i++) {
+ vec4 dirIntensity = frame.lightData[i * 2];
+ vec4 colorRange = frame.lightData[i * 2 + 1];
+
+ vec3 lightDir;
+ float attenuation;
+
+ if (dirIntensity.w < 0.0) {
+ // Directional light: direction stored as xyz, w = intensity (negative marks directional)
+ lightDir = normalize(-dirIntensity.xyz);
+ attenuation = abs(dirIntensity.w);
+ } else {
+ // Point light: position stored as xyz, w = intensity (positive marks point)
+ vec3 toLight = dirIntensity.xyz - fragWorldPos;
+ float dist = length(toLight);
+ lightDir = toLight / max(dist, 0.001);
+ float range = max(colorRange.w, 0.001);
+ attenuation = dirIntensity.w * max(0.0, 1.0 - dist / range);
+ attenuation /= max(dist * dist * 0.01, 0.01);
+ }
+
+ vec3 H = normalize(V + lightDir);
+ float NdotL = max(dot(N, lightDir), 0.0);
+ float NdotH = max(dot(N, H), 0.0);
+ float VdotH = max(dot(V, H), 0.0);
+
+ float specPower = mix(128.0, 4.0, roughness);
+ float specIntensity = pow(NdotH, specPower);
+
+ vec3 specular = vec3(specIntensity) * colorRange.rgb;
+ vec3 diffuse = albedo * NdotL * colorRange.rgb;
+
+ finalColor += (diffuse + specular) * attenuation;
+ }
+
+ finalColor = ACESFilm(finalColor);
+ finalColor = pow(finalColor, vec3(1.0 / 2.2));
+
+ outColor = vec4(finalColor, 1.0);
+}
diff --git a/src/Engine.Graphics.Vulkan/Shaders/fragment.spv b/src/Engine.Graphics.Vulkan/Shaders/fragment.spv
new file mode 100644
index 0000000..369001b
Binary files /dev/null and b/src/Engine.Graphics.Vulkan/Shaders/fragment.spv differ
diff --git a/src/Engine.Graphics.Vulkan/Shaders/vertex.spv b/src/Engine.Graphics.Vulkan/Shaders/vertex.spv
new file mode 100644
index 0000000..65367c4
Binary files /dev/null and b/src/Engine.Graphics.Vulkan/Shaders/vertex.spv differ
diff --git a/src/Engine.Graphics.Vulkan/Shaders/vertex.vert b/src/Engine.Graphics.Vulkan/Shaders/vertex.vert
new file mode 100644
index 0000000..2524b71
--- /dev/null
+++ b/src/Engine.Graphics.Vulkan/Shaders/vertex.vert
@@ -0,0 +1,34 @@
+#version 450
+
+layout(location = 0) in vec3 inPosition;
+layout(location = 1) in vec3 inColor;
+layout(location = 2) in vec3 inNormal;
+
+layout(row_major, set = 0, binding = 0) uniform FrameUBO {
+ vec3 cameraPosition;
+ uint lightCount;
+ vec3 ambientColor;
+ float pad0;
+ vec4 lightData[16];
+} frame;
+
+layout(push_constant) uniform PushConstants {
+ mat4 mvp;
+ mat4 model;
+ vec4 material;
+} pc;
+
+layout(location = 0) out vec3 fragColor;
+layout(location = 1) out vec3 fragNormal;
+layout(location = 2) out vec3 fragWorldPos;
+layout(location = 3) out vec3 fragViewDir;
+
+void main() {
+ vec4 worldPos = pc.model * vec4(inPosition, 1.0);
+ gl_Position = pc.mvp * vec4(inPosition, 1.0);
+
+ fragColor = inColor;
+ fragNormal = normalize(mat3(pc.model) * inNormal);
+ fragWorldPos = worldPos.xyz;
+ fragViewDir = normalize(frame.cameraPosition - worldPos.xyz);
+}
diff --git a/src/Engine.Graphics.Vulkan/Vk.cs b/src/Engine.Graphics.Vulkan/Vk.cs
new file mode 100644
index 0000000..f176db1
--- /dev/null
+++ b/src/Engine.Graphics.Vulkan/Vk.cs
@@ -0,0 +1,405 @@
+using System.Runtime.InteropServices;
+
+namespace Engine.Graphics.Vulkan;
+
+internal static unsafe class Vk
+{
+ public static VkInstance Instance;
+ public static VkDevice Device;
+
+ public static PFN_vkCreateInstance vkCreateInstance;
+ public static PFN_vkDestroyInstance vkDestroyInstance;
+ public static PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices;
+ public static PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties;
+ public static PFN_vkGetPhysicalDeviceQueueFamilyProperties vkGetPhysicalDeviceQueueFamilyProperties;
+ public static PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties;
+ public static PFN_vkEnumerateDeviceExtensionProperties vkEnumerateDeviceExtensionProperties;
+ public static PFN_vkCreateDevice vkCreateDevice;
+ public static PFN_vkDestroyDevice vkDestroyDevice;
+ public static PFN_vkGetDeviceQueue vkGetDeviceQueue;
+ public static PFN_vkCreateSwapchainKHR vkCreateSwapchainKHR;
+ public static PFN_vkDestroySwapchainKHR vkDestroySwapchainKHR;
+ public static PFN_vkGetSwapchainImagesKHR vkGetSwapchainImagesKHR;
+ public static PFN_vkCreateImageView vkCreateImageView;
+ public static PFN_vkDestroyImageView vkDestroyImageView;
+ public static PFN_vkCreateImage vkCreateImage;
+ public static PFN_vkDestroyImage vkDestroyImage;
+ public static PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements;
+ public static PFN_vkBindImageMemory vkBindImageMemory;
+ public static PFN_vkCreateRenderPass vkCreateRenderPass;
+ public static PFN_vkDestroyRenderPass vkDestroyRenderPass;
+ public static PFN_vkCreateFramebuffer vkCreateFramebuffer;
+ public static PFN_vkDestroyFramebuffer vkDestroyFramebuffer;
+ public static PFN_vkCreateShaderModule vkCreateShaderModule;
+ public static PFN_vkDestroyShaderModule vkDestroyShaderModule;
+ public static PFN_vkCreateDescriptorSetLayout vkCreateDescriptorSetLayout;
+ public static PFN_vkDestroyDescriptorSetLayout vkDestroyDescriptorSetLayout;
+ public static PFN_vkCreatePipelineLayout vkCreatePipelineLayout;
+ public static PFN_vkDestroyPipelineLayout vkDestroyPipelineLayout;
+ public static PFN_vkCreateGraphicsPipelines vkCreateGraphicsPipelines;
+ public static PFN_vkDestroyPipeline vkDestroyPipeline;
+ public static PFN_vkCreateDescriptorPool vkCreateDescriptorPool;
+ public static PFN_vkDestroyDescriptorPool vkDestroyDescriptorPool;
+ public static PFN_vkAllocateDescriptorSets vkAllocateDescriptorSets;
+ public static PFN_vkUpdateDescriptorSets vkUpdateDescriptorSets;
+ public static PFN_vkCreateBuffer vkCreateBuffer;
+ public static PFN_vkDestroyBuffer vkDestroyBuffer;
+ public static PFN_vkGetBufferMemoryRequirements vkGetBufferMemoryRequirements;
+ public static PFN_vkBindBufferMemory vkBindBufferMemory;
+ public static PFN_vkAllocateMemory vkAllocateMemory;
+ public static PFN_vkFreeMemory vkFreeMemory;
+ public static PFN_vkMapMemory vkMapMemory;
+ public static PFN_vkUnmapMemory vkUnmapMemory;
+ public static PFN_vkCreateCommandPool vkCreateCommandPool;
+ public static PFN_vkDestroyCommandPool vkDestroyCommandPool;
+ public static PFN_vkAllocateCommandBuffers vkAllocateCommandBuffers;
+ public static PFN_vkFreeCommandBuffers vkFreeCommandBuffers;
+ public static PFN_vkBeginCommandBuffer vkBeginCommandBuffer;
+ public static PFN_vkEndCommandBuffer vkEndCommandBuffer;
+ public static PFN_vkResetCommandBuffer vkResetCommandBuffer;
+ public static PFN_vkQueueSubmit vkQueueSubmit;
+ public static PFN_vkQueueWaitIdle vkQueueWaitIdle;
+ public static PFN_vkQueuePresentKHR vkQueuePresentKHR;
+ public static PFN_vkAcquireNextImageKHR vkAcquireNextImageKHR;
+ public static PFN_vkCreateSemaphore vkCreateSemaphore;
+ public static PFN_vkDestroySemaphore vkDestroySemaphore;
+ public static PFN_vkCreateFence vkCreateFence;
+ public static PFN_vkDestroyFence vkDestroyFence;
+ public static PFN_vkWaitForFences vkWaitForFences;
+ public static PFN_vkResetFences vkResetFences;
+ public static PFN_vkCmdBeginRenderPass vkCmdBeginRenderPass;
+ public static PFN_vkCmdEndRenderPass vkCmdEndRenderPass;
+ public static PFN_vkCmdBindPipeline vkCmdBindPipeline;
+ public static PFN_vkCmdBindDescriptorSets vkCmdBindDescriptorSets;
+ public static PFN_vkCmdBindVertexBuffers vkCmdBindVertexBuffers;
+ public static PFN_vkCmdBindIndexBuffer vkCmdBindIndexBuffer;
+ public static PFN_vkCmdDrawIndexed vkCmdDrawIndexed;
+ public static PFN_vkCmdDraw vkCmdDraw;
+ public static PFN_vkCmdSetViewport vkCmdSetViewport;
+ public static PFN_vkCmdSetScissor vkCmdSetScissor;
+ public static PFN_vkCmdPipelineBarrier vkCmdPipelineBarrier;
+ public static PFN_vkCmdCopyBuffer vkCmdCopyBuffer;
+ public static PFN_vkCmdCopyBufferToImage vkCmdCopyBufferToImage;
+ public static PFN_vkCmdClearColorImage vkCmdClearColorImage;
+ public static PFN_vkCmdPushConstants vkCmdPushConstants;
+ public static PFN_vkCreateSampler vkCreateSampler;
+ public static PFN_vkDestroySampler vkDestroySampler;
+ public static PFN_vkGetPhysicalDeviceSurfaceSupportKHR vkGetPhysicalDeviceSurfaceSupportKHR;
+ public static PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR vkGetPhysicalDeviceSurfaceCapabilitiesKHR;
+ public static PFN_vkGetPhysicalDeviceSurfaceFormatsKHR vkGetPhysicalDeviceSurfaceFormatsKHR;
+ public static PFN_vkGetPhysicalDeviceSurfacePresentModesKHR vkGetPhysicalDeviceSurfacePresentModesKHR;
+ public static PFN_vkDestroySurfaceKHR vkDestroySurfaceKHR;
+
+ public static void LoadGlobalFunctions()
+ {
+ VulkanNative.LoadLibrary();
+ var libHandle = VulkanNative.LoadLibrary();
+
+ var createInstancePtr = NativeLibrary.GetExport(libHandle, "vkCreateInstance");
+ vkCreateInstance = Marshal.GetDelegateForFunctionPointer(createInstancePtr);
+ }
+
+ public static void LoadInstanceFunctions(VkInstance instance)
+ {
+ Instance = instance;
+
+ vkDestroyInstance = VulkanNative.LoadInstanceFunction(instance, "vkDestroyInstance");
+ vkEnumeratePhysicalDevices = VulkanNative.LoadInstanceFunction(instance, "vkEnumeratePhysicalDevices");
+ vkGetPhysicalDeviceProperties = VulkanNative.LoadInstanceFunction(instance, "vkGetPhysicalDeviceProperties");
+ vkGetPhysicalDeviceQueueFamilyProperties = VulkanNative.LoadInstanceFunction(instance, "vkGetPhysicalDeviceQueueFamilyProperties");
+ vkGetPhysicalDeviceMemoryProperties = VulkanNative.LoadInstanceFunction(instance, "vkGetPhysicalDeviceMemoryProperties");
+ vkEnumerateDeviceExtensionProperties = VulkanNative.LoadInstanceFunction(instance, "vkEnumerateDeviceExtensionProperties");
+ vkCreateDevice = VulkanNative.LoadInstanceFunction(instance, "vkCreateDevice");
+ vkGetPhysicalDeviceSurfaceSupportKHR = VulkanNative.LoadInstanceFunction(instance, "vkGetPhysicalDeviceSurfaceSupportKHR");
+ vkGetPhysicalDeviceSurfaceCapabilitiesKHR = VulkanNative.LoadInstanceFunction(instance, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR");
+ vkGetPhysicalDeviceSurfaceFormatsKHR = VulkanNative.LoadInstanceFunction(instance, "vkGetPhysicalDeviceSurfaceFormatsKHR");
+ vkGetPhysicalDeviceSurfacePresentModesKHR = VulkanNative.LoadInstanceFunction(instance, "vkGetPhysicalDeviceSurfacePresentModesKHR");
+ vkDestroySurfaceKHR = VulkanNative.LoadInstanceFunction(instance, "vkDestroySurfaceKHR");
+ }
+
+ public static void LoadDeviceFunctions(VkDevice device)
+ {
+ Device = device;
+
+ vkDestroyDevice = VulkanNative.LoadDeviceFunction(device, "vkDestroyDevice");
+ vkGetDeviceQueue = VulkanNative.LoadDeviceFunction(device, "vkGetDeviceQueue");
+ vkCreateSwapchainKHR = VulkanNative.LoadDeviceFunction(device, "vkCreateSwapchainKHR");
+ vkDestroySwapchainKHR = VulkanNative.LoadDeviceFunction(device, "vkDestroySwapchainKHR");
+ vkGetSwapchainImagesKHR = VulkanNative.LoadDeviceFunction(device, "vkGetSwapchainImagesKHR");
+ vkCreateImageView = VulkanNative.LoadDeviceFunction(device, "vkCreateImageView");
+ vkDestroyImageView = VulkanNative.LoadDeviceFunction(device, "vkDestroyImageView");
+ vkCreateImage = VulkanNative.LoadDeviceFunction(device, "vkCreateImage");
+ vkDestroyImage = VulkanNative.LoadDeviceFunction(device, "vkDestroyImage");
+ vkGetImageMemoryRequirements = VulkanNative.LoadDeviceFunction(device, "vkGetImageMemoryRequirements");
+ vkBindImageMemory = VulkanNative.LoadDeviceFunction(device, "vkBindImageMemory");
+ vkCreateRenderPass = VulkanNative.LoadDeviceFunction(device, "vkCreateRenderPass");
+ vkDestroyRenderPass = VulkanNative.LoadDeviceFunction(device, "vkDestroyRenderPass");
+ vkCreateFramebuffer = VulkanNative.LoadDeviceFunction(device, "vkCreateFramebuffer");
+ vkDestroyFramebuffer = VulkanNative.LoadDeviceFunction(device, "vkDestroyFramebuffer");
+ vkCreateShaderModule = VulkanNative.LoadDeviceFunction(device, "vkCreateShaderModule");
+ vkDestroyShaderModule = VulkanNative.LoadDeviceFunction(device, "vkDestroyShaderModule");
+ vkCreateDescriptorSetLayout = VulkanNative.LoadDeviceFunction(device, "vkCreateDescriptorSetLayout");
+ vkDestroyDescriptorSetLayout = VulkanNative.LoadDeviceFunction(device, "vkDestroyDescriptorSetLayout");
+ vkCreatePipelineLayout = VulkanNative.LoadDeviceFunction(device, "vkCreatePipelineLayout");
+ vkDestroyPipelineLayout = VulkanNative.LoadDeviceFunction(device, "vkDestroyPipelineLayout");
+ vkCreateGraphicsPipelines = VulkanNative.LoadDeviceFunction(device, "vkCreateGraphicsPipelines");
+ vkDestroyPipeline = VulkanNative.LoadDeviceFunction(device, "vkDestroyPipeline");
+ vkCreateDescriptorPool = VulkanNative.LoadDeviceFunction(device, "vkCreateDescriptorPool");
+ vkDestroyDescriptorPool = VulkanNative.LoadDeviceFunction(device, "vkDestroyDescriptorPool");
+ vkAllocateDescriptorSets = VulkanNative.LoadDeviceFunction(device, "vkAllocateDescriptorSets");
+ vkUpdateDescriptorSets = VulkanNative.LoadDeviceFunction(device, "vkUpdateDescriptorSets");
+ vkCreateBuffer = VulkanNative.LoadDeviceFunction(device, "vkCreateBuffer");
+ vkDestroyBuffer = VulkanNative.LoadDeviceFunction(device, "vkDestroyBuffer");
+ vkGetBufferMemoryRequirements = VulkanNative.LoadDeviceFunction(device, "vkGetBufferMemoryRequirements");
+ vkBindBufferMemory = VulkanNative.LoadDeviceFunction(device, "vkBindBufferMemory");
+ vkAllocateMemory = VulkanNative.LoadDeviceFunction(device, "vkAllocateMemory");
+ vkFreeMemory = VulkanNative.LoadDeviceFunction(device, "vkFreeMemory");
+ vkMapMemory = VulkanNative.LoadDeviceFunction(device, "vkMapMemory");
+ vkUnmapMemory = VulkanNative.LoadDeviceFunction(device, "vkUnmapMemory");
+ vkCreateCommandPool = VulkanNative.LoadDeviceFunction(device, "vkCreateCommandPool");
+ vkDestroyCommandPool = VulkanNative.LoadDeviceFunction(device, "vkDestroyCommandPool");
+ vkAllocateCommandBuffers = VulkanNative.LoadDeviceFunction(device, "vkAllocateCommandBuffers");
+ vkFreeCommandBuffers = VulkanNative.LoadDeviceFunction(device, "vkFreeCommandBuffers");
+ vkBeginCommandBuffer = VulkanNative.LoadDeviceFunction(device, "vkBeginCommandBuffer");
+ vkEndCommandBuffer = VulkanNative.LoadDeviceFunction(device, "vkEndCommandBuffer");
+ vkResetCommandBuffer = VulkanNative.LoadDeviceFunction(device, "vkResetCommandBuffer");
+ vkQueueSubmit = VulkanNative.LoadDeviceFunction(device, "vkQueueSubmit");
+ vkQueueWaitIdle = VulkanNative.LoadDeviceFunction(device, "vkQueueWaitIdle");
+ vkQueuePresentKHR = VulkanNative.LoadDeviceFunction(device, "vkQueuePresentKHR");
+ vkAcquireNextImageKHR = VulkanNative.LoadDeviceFunction(device, "vkAcquireNextImageKHR");
+ vkCreateSemaphore = VulkanNative.LoadDeviceFunction(device, "vkCreateSemaphore");
+ vkDestroySemaphore = VulkanNative.LoadDeviceFunction(device, "vkDestroySemaphore");
+ vkCreateFence = VulkanNative.LoadDeviceFunction(device, "vkCreateFence");
+ vkDestroyFence = VulkanNative.LoadDeviceFunction(device, "vkDestroyFence");
+ vkWaitForFences = VulkanNative.LoadDeviceFunction(device, "vkWaitForFences");
+ vkResetFences = VulkanNative.LoadDeviceFunction(device, "vkResetFences");
+ vkCmdBeginRenderPass = VulkanNative.LoadDeviceFunction(device, "vkCmdBeginRenderPass");
+ vkCmdEndRenderPass = VulkanNative.LoadDeviceFunction(device, "vkCmdEndRenderPass");
+ vkCmdBindPipeline = VulkanNative.LoadDeviceFunction(device, "vkCmdBindPipeline");
+ vkCmdBindDescriptorSets = VulkanNative.LoadDeviceFunction(device, "vkCmdBindDescriptorSets");
+ vkCmdBindVertexBuffers = VulkanNative.LoadDeviceFunction(device, "vkCmdBindVertexBuffers");
+ vkCmdBindIndexBuffer = VulkanNative.LoadDeviceFunction(device, "vkCmdBindIndexBuffer");
+ vkCmdDrawIndexed = VulkanNative.LoadDeviceFunction(device, "vkCmdDrawIndexed");
+ vkCmdDraw = VulkanNative.LoadDeviceFunction(device, "vkCmdDraw");
+ vkCmdSetViewport = VulkanNative.LoadDeviceFunction(device, "vkCmdSetViewport");
+ vkCmdSetScissor = VulkanNative.LoadDeviceFunction(device, "vkCmdSetScissor");
+ vkCmdPipelineBarrier = VulkanNative.LoadDeviceFunction(device, "vkCmdPipelineBarrier");
+ vkCmdCopyBuffer = VulkanNative.LoadDeviceFunction(device, "vkCmdCopyBuffer");
+ vkCmdCopyBufferToImage = VulkanNative.LoadDeviceFunction(device, "vkCmdCopyBufferToImage");
+ vkCmdClearColorImage = VulkanNative.LoadDeviceFunction(device, "vkCmdClearColorImage");
+ vkCmdPushConstants = VulkanNative.LoadDeviceFunction(device, "vkCmdPushConstants");
+ vkCreateSampler = VulkanNative.LoadDeviceFunction(device, "vkCreateSampler");
+ vkDestroySampler = VulkanNative.LoadDeviceFunction(device, "vkDestroySampler");
+ }
+
+ private static T Load(nint libHandle, string name) where T : Delegate
+ {
+ var ptr = NativeLibrary.GetExport(libHandle, name);
+ if (ptr == 0)
+ throw new InvalidOperationException($"Failed to load Vulkan function: {name}");
+ return Marshal.GetDelegateForFunctionPointer(ptr);
+ }
+
+ public static void CheckResult(VkResult result, string operation)
+ {
+ if (result != VkResult.Success && result != VkResult.SuboptimalKHR)
+ throw new InvalidOperationException($"Vulkan error {result} during: {operation}");
+ }
+
+ public static byte[] ToUtf8NullTerminated(string s)
+ {
+ var bytes = new byte[System.Text.Encoding.UTF8.GetByteCount(s) + 1];
+ System.Text.Encoding.UTF8.GetBytes(s, 0, s.Length, bytes, 0);
+ return bytes;
+ }
+
+ public static byte* AllocUtf8(string s)
+ {
+ var bytes = ToUtf8NullTerminated(s);
+ var ptr = (byte*)Marshal.AllocHGlobal(bytes.Length);
+ Marshal.Copy(bytes, 0, (nint)ptr, bytes.Length);
+ return ptr;
+ }
+
+ public static void FreeUtf8(byte* ptr) => Marshal.FreeHGlobal((nint)ptr);
+
+ public static byte** AllocStringArray(string[] strings)
+ {
+ var ptrArray = (byte**)Marshal.AllocHGlobal(strings.Length * sizeof(nint));
+ for (var i = 0; i < strings.Length; i++)
+ ptrArray[i] = AllocUtf8(strings[i]);
+ return ptrArray;
+ }
+
+ public static void FreeStringArray(byte** ptrArray, int count)
+ {
+ for (var i = 0; i < count; i++)
+ FreeUtf8(ptrArray[i]);
+ Marshal.FreeHGlobal((nint)ptrArray);
+ }
+}
+
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate VkResult PFN_vkCreateInstance(VkInstanceCreateInfo* pCreateInfo, void* pAllocator, VkInstance* pInstance);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkDestroyInstance(VkInstance instance, void* pAllocator);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate VkResult PFN_vkEnumeratePhysicalDevices(VkInstance instance, uint* pPhysicalDeviceCount, VkPhysicalDevice* pPhysicalDevices);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkGetPhysicalDeviceProperties(VkPhysicalDevice physicalDevice, VkPhysicalDeviceProperties* pProperties);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkGetPhysicalDeviceQueueFamilyProperties(VkPhysicalDevice physicalDevice, uint* pQueueFamilyPropertyCount, VkQueueFamilyProperties* pQueueFamilyProperties);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkGetPhysicalDeviceMemoryProperties(VkPhysicalDevice physicalDevice, VkPhysicalDeviceMemoryProperties* pMemoryProperties);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate VkResult PFN_vkEnumerateDeviceExtensionProperties(VkPhysicalDevice physicalDevice, byte* pLayerName, uint* pPropertyCount, VkExtensionProperties* pProperties);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate VkResult PFN_vkCreateDevice(VkPhysicalDevice physicalDevice, VkDeviceCreateInfo* pCreateInfo, void* pAllocator, VkDevice* pDevice);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkDestroyDevice(VkDevice device, void* pAllocator);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkGetDeviceQueue(VkDevice device, uint queueFamilyIndex, uint queueIndex, VkQueue* pQueue);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate VkResult PFN_vkCreateSwapchainKHR(VkDevice device, VkSwapchainCreateInfoKHR* pCreateInfo, void* pAllocator, VkSwapchainKHR* pSwapchain);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkDestroySwapchainKHR(VkDevice device, VkSwapchainKHR swapchain, void* pAllocator);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate VkResult PFN_vkGetSwapchainImagesKHR(VkDevice device, VkSwapchainKHR swapchain, uint* pSwapchainImageCount, VkImage* pSwapchainImages);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate VkResult PFN_vkCreateImageView(VkDevice device, VkImageViewCreateInfo* pCreateInfo, void* pAllocator, VkImageView* pView);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkDestroyImageView(VkDevice device, VkImageView imageView, void* pAllocator);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate VkResult PFN_vkCreateImage(VkDevice device, VkImageCreateInfo* pCreateInfo, void* pAllocator, VkImage* pImage);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkDestroyImage(VkDevice device, VkImage image, void* pAllocator);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkGetImageMemoryRequirements(VkDevice device, VkImage image, void* pMemoryRequirements);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate VkResult PFN_vkBindImageMemory(VkDevice device, VkImage image, VkDeviceMemory memory, ulong memoryOffset);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate VkResult PFN_vkCreateRenderPass(VkDevice device, VkRenderPassCreateInfo* pCreateInfo, void* pAllocator, VkRenderPass* pRenderPass);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkDestroyRenderPass(VkDevice device, VkRenderPass renderPass, void* pAllocator);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate VkResult PFN_vkCreateFramebuffer(VkDevice device, VkFramebufferCreateInfo* pCreateInfo, void* pAllocator, VkFramebuffer* pFramebuffer);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkDestroyFramebuffer(VkDevice device, VkFramebuffer framebuffer, void* pAllocator);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate VkResult PFN_vkCreateShaderModule(VkDevice device, VkShaderModuleCreateInfo* pCreateInfo, void* pAllocator, VkShaderModule* pShaderModule);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkDestroyShaderModule(VkDevice device, VkShaderModule shaderModule, void* pAllocator);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate VkResult PFN_vkCreateDescriptorSetLayout(VkDevice device, VkDescriptorSetLayoutCreateInfo* pCreateInfo, void* pAllocator, VkDescriptorSetLayout* pSetLayout);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkDestroyDescriptorSetLayout(VkDevice device, VkDescriptorSetLayout descriptorSetLayout, void* pAllocator);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate VkResult PFN_vkCreatePipelineLayout(VkDevice device, VkPipelineLayoutCreateInfo* pCreateInfo, void* pAllocator, VkPipelineLayout* pPipelineLayout);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkDestroyPipelineLayout(VkDevice device, VkPipelineLayout pipelineLayout, void* pAllocator);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate VkResult PFN_vkCreateGraphicsPipelines(VkDevice device, ulong pipelineCache, uint createInfoCount, VkGraphicsPipelineCreateInfo* pCreateInfos, void* pAllocator, VkPipeline* pPipelines);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkDestroyPipeline(VkDevice device, VkPipeline pipeline, void* pAllocator);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate VkResult PFN_vkCreateDescriptorPool(VkDevice device, VkDescriptorPoolCreateInfo* pCreateInfo, void* pAllocator, VkDescriptorPool* pDescriptorPool);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkDestroyDescriptorPool(VkDevice device, VkDescriptorPool descriptorPool, void* pAllocator);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate VkResult PFN_vkAllocateDescriptorSets(VkDevice device, VkDescriptorSetAllocateInfo* pAllocateInfo, VkDescriptorSet* pDescriptorSets);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkUpdateDescriptorSets(VkDevice device, uint descriptorWriteCount, VkWriteDescriptorSet* pDescriptorWrites, uint descriptorCopyCount, void* pDescriptorCopies);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate VkResult PFN_vkCreateBuffer(VkDevice device, VkBufferCreateInfo* pCreateInfo, void* pAllocator, VkBuffer* pBuffer);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkDestroyBuffer(VkDevice device, VkBuffer buffer, void* pAllocator);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkGetBufferMemoryRequirements(VkDevice device, VkBuffer buffer, void* pMemoryRequirements);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate VkResult PFN_vkBindBufferMemory(VkDevice device, VkBuffer buffer, VkDeviceMemory memory, ulong memoryOffset);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate VkResult PFN_vkAllocateMemory(VkDevice device, VkMemoryAllocateInfo* pAllocateInfo, void* pAllocator, VkDeviceMemory* pMemory);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkFreeMemory(VkDevice device, VkDeviceMemory memory, void* pAllocator);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate VkResult PFN_vkMapMemory(VkDevice device, VkDeviceMemory memory, ulong offset, ulong size, uint flags, void** ppData);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkUnmapMemory(VkDevice device, VkDeviceMemory memory);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate VkResult PFN_vkCreateCommandPool(VkDevice device, VkCommandPoolCreateInfo* pCreateInfo, void* pAllocator, VkCommandPool* pCommandPool);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkDestroyCommandPool(VkDevice device, VkCommandPool commandPool, void* pAllocator);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate VkResult PFN_vkAllocateCommandBuffers(VkDevice device, VkCommandBufferAllocateInfo* pAllocateInfo, VkCommandBuffer* pCommandBuffers);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkFreeCommandBuffers(VkDevice device, VkCommandPool commandPool, uint commandBufferCount, VkCommandBuffer* pCommandBuffers);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate VkResult PFN_vkBeginCommandBuffer(VkCommandBuffer commandBuffer, VkCommandBufferBeginInfo* pBeginInfo);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate VkResult PFN_vkEndCommandBuffer(VkCommandBuffer commandBuffer);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate VkResult PFN_vkResetCommandBuffer(VkCommandBuffer commandBuffer, uint flags);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate VkResult PFN_vkQueueSubmit(VkQueue queue, uint submitCount, VkSubmitInfo* pSubmits, VkFence fence);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate VkResult PFN_vkQueueWaitIdle(VkQueue queue);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate VkResult PFN_vkQueuePresentKHR(VkQueue queue, VkPresentInfoKHR* pPresentInfo);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate VkResult PFN_vkAcquireNextImageKHR(VkDevice device, VkSwapchainKHR swapchain, ulong timeout, VkSemaphore semaphore, VkFence fence, uint* pImageIndex);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate VkResult PFN_vkCreateSemaphore(VkDevice device, VkSemaphoreCreateInfo* pCreateInfo, void* pAllocator, VkSemaphore* pSemaphore);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkDestroySemaphore(VkDevice device, VkSemaphore semaphore, void* pAllocator);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate VkResult PFN_vkCreateFence(VkDevice device, VkFenceCreateInfo* pCreateInfo, void* pAllocator, VkFence* pFence);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkDestroyFence(VkDevice device, VkFence fence, void* pAllocator);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate VkResult PFN_vkWaitForFences(VkDevice device, uint fenceCount, VkFence* pFences, uint waitAll, ulong timeout);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate VkResult PFN_vkResetFences(VkDevice device, uint fenceCount, VkFence* pFences);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkCmdBeginRenderPass(VkCommandBuffer commandBuffer, VkRenderPassBeginInfo* pRenderPassBegin, VkSubpassContents contents);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkCmdEndRenderPass(VkCommandBuffer commandBuffer);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkCmdBindPipeline(VkCommandBuffer commandBuffer, int pipelineBindPoint, VkPipeline pipeline);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkCmdBindDescriptorSets(VkCommandBuffer commandBuffer, int pipelineBindPoint, VkPipelineLayout layout, uint firstSet, uint descriptorSetCount, VkDescriptorSet* pDescriptorSets, uint dynamicOffsetCount, uint* pDynamicOffsets);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkCmdBindVertexBuffers(VkCommandBuffer commandBuffer, uint firstBinding, uint bindingCount, VkBuffer* pBuffers, ulong* pOffsets);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkCmdBindIndexBuffer(VkCommandBuffer commandBuffer, VkBuffer buffer, ulong offset, VkIndexType indexType);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkCmdDrawIndexed(VkCommandBuffer commandBuffer, uint indexCount, uint instanceCount, uint firstIndex, int vertexOffset, uint firstInstance);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkCmdDraw(VkCommandBuffer commandBuffer, uint vertexCount, uint instanceCount, uint firstVertex, uint firstInstance);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkCmdSetViewport(VkCommandBuffer commandBuffer, uint firstViewport, uint viewportCount, VkViewport* pViewports);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkCmdSetScissor(VkCommandBuffer commandBuffer, uint firstScissor, uint scissorCount, VkRect2D* pScissors);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkCmdPipelineBarrier(VkCommandBuffer commandBuffer, VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, int dependencyFlags, uint memoryBarrierCount, void* pMemoryBarriers, uint bufferMemoryBarrierCount, VkBufferMemoryBarrier* pBufferMemoryBarriers, uint imageMemoryBarrierCount, VkImageMemoryBarrier* pImageMemoryBarriers);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkCmdCopyBuffer(VkCommandBuffer commandBuffer, VkBuffer srcBuffer, VkBuffer dstBuffer, uint regionCount, VkBufferCopy* pRegions);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkCmdCopyBufferToImage(VkCommandBuffer commandBuffer, VkBuffer srcBuffer, VkImage dstImage, int dstImageLayout, uint regionCount, VkBufferImageCopy* pRegions);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkCmdClearColorImage(VkCommandBuffer commandBuffer, VkImage image, int imageLayout, VkClearColorValue* pColor, uint rangeCount, VkImageSubresourceRange* pRanges);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkCmdPushConstants(VkCommandBuffer commandBuffer, VkPipelineLayout layout, VkShaderStageFlags stageFlags, uint offset, uint size, void* pValues);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate VkResult PFN_vkCreateSampler(VkDevice device, VkSamplerCreateInfo* pCreateInfo, void* pAllocator, void* pSampler);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkDestroySampler(VkDevice device, void* sampler, void* pAllocator);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate VkResult PFN_vkGetPhysicalDeviceSurfaceSupportKHR(VkPhysicalDevice physicalDevice, uint queueFamilyIndex, VkSurfaceKHR surface, uint* pSupported);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate VkResult PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, VkSurfaceCapabilitiesKHR* pSurfaceCapabilities);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate VkResult PFN_vkGetPhysicalDeviceSurfaceFormatsKHR(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, uint* pSurfaceFormatCount, VkSurfaceFormatKHR* pSurfaceFormats);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate VkResult PFN_vkGetPhysicalDeviceSurfacePresentModesKHR(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, uint* pPresentModeCount, VkPresentModeKHR* pPresentModes);
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+unsafe internal delegate void PFN_vkDestroySurfaceKHR(VkInstance instance, VkSurfaceKHR surface, void* pAllocator);
diff --git a/src/Engine.Graphics.Vulkan/VulkanBackendRegistrar.cs b/src/Engine.Graphics.Vulkan/VulkanBackendRegistrar.cs
new file mode 100644
index 0000000..3750b1f
--- /dev/null
+++ b/src/Engine.Graphics.Vulkan/VulkanBackendRegistrar.cs
@@ -0,0 +1,16 @@
+using Engine.Graphics;
+
+namespace Engine.Graphics.Vulkan;
+
+public static class VulkanBackendRegistrar
+{
+ private static int _registered;
+
+ public static void EnsureRegistered()
+ {
+ if (System.Threading.Interlocked.Exchange(ref _registered, 1) == 1) return;
+
+ RenderBackendFactory.Register("vulkan", (width, height, validation) =>
+ new VulkanRenderContext(width, height, validation));
+ }
+}
diff --git a/src/Engine.Graphics.Vulkan/VulkanBuffer.cs b/src/Engine.Graphics.Vulkan/VulkanBuffer.cs
new file mode 100644
index 0000000..1b604ed
--- /dev/null
+++ b/src/Engine.Graphics.Vulkan/VulkanBuffer.cs
@@ -0,0 +1,168 @@
+using System.Runtime.InteropServices;
+
+namespace Engine.Graphics.Vulkan;
+
+internal sealed unsafe class VulkanBuffer : IDisposable
+{
+ public VkBuffer Buffer;
+ public VkDeviceMemory Memory;
+ public ulong Size;
+ public void* MappedData;
+
+ private readonly VulkanContext _ctx;
+ private bool _disposed;
+
+ public VulkanBuffer(VulkanContext ctx, ulong size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties)
+ {
+ _ctx = ctx;
+ Size = size;
+
+ VkBufferCreateInfo bufferInfo;
+ bufferInfo.sType = VkStructureType.BufferCreateInfo;
+ bufferInfo.pNext = null;
+ bufferInfo.flags = 0;
+ bufferInfo.size = size;
+ bufferInfo.usage = usage;
+ bufferInfo.sharingMode = VkSharingMode.Exclusive;
+ bufferInfo.queueFamilyIndexCount = 0;
+ bufferInfo.pQueueFamilyIndices = null;
+
+ VkBuffer buffer;
+ VkResult result = Vk.vkCreateBuffer(_ctx.Device, &bufferInfo, null, &buffer);
+ Vk.CheckResult(result, "vkCreateBuffer");
+ Buffer = buffer;
+
+ VkMemoryRequirements2 memReq;
+ Vk.vkGetBufferMemoryRequirements(_ctx.Device, Buffer, &memReq);
+
+ VkMemoryAllocateInfo allocInfo;
+ allocInfo.sType = VkStructureType.MemoryAllocateInfo;
+ allocInfo.pNext = null;
+ allocInfo.allocationSize = memReq.size;
+ allocInfo.memoryTypeIndex = _ctx.FindMemoryType(memReq.memoryTypeBits, properties);
+
+ VkDeviceMemory memory;
+ result = Vk.vkAllocateMemory(_ctx.Device, &allocInfo, null, &memory);
+ Vk.CheckResult(result, "vkAllocateMemory (buffer)");
+ Memory = memory;
+
+ result = Vk.vkBindBufferMemory(_ctx.Device, Buffer, Memory, 0);
+ Vk.CheckResult(result, "vkBindBufferMemory");
+
+ if ((properties & VkMemoryPropertyFlags.HostVisible) != 0)
+ {
+ void* mapped;
+ result = Vk.vkMapMemory(_ctx.Device, Memory, 0, size, 0, &mapped);
+ Vk.CheckResult(result, "vkMapMemory");
+ MappedData = mapped;
+ }
+ }
+
+ public void Write(void* data, ulong size, ulong offset = 0)
+ {
+ if (MappedData == null)
+ throw new InvalidOperationException("Buffer is not host-visible/mapped.");
+
+ System.Buffer.MemoryCopy(data, (void*)((byte*)MappedData + offset), size, size);
+ }
+
+ public void Write(T[] data, ulong offset = 0) where T : struct
+ {
+ var size = (ulong)(data.Length * Marshal.SizeOf());
+ fixed (T* pData = data)
+ {
+ Write(pData, size, offset);
+ }
+ }
+
+ public static unsafe VulkanBuffer CreateStaging(VulkanContext ctx, void* data, ulong size)
+ {
+ var staging = new VulkanBuffer(ctx, size, VkBufferUsageFlags.TransferSrc, VkMemoryPropertyFlags.HostVisible | VkMemoryPropertyFlags.HostCoherent);
+ staging.Write(data, size);
+ return staging;
+ }
+
+ public static unsafe void CopyBuffer(VulkanContext ctx, VkCommandPool cmdPool, VkBuffer src, VkBuffer dst, ulong size)
+ {
+ VkCommandBuffer cmd = BeginSingleTimeCommands(ctx, cmdPool);
+
+ var region = new VkBufferCopy { srcOffset = 0, dstOffset = 0, size = size };
+ Vk.vkCmdCopyBuffer(cmd, src, dst, 1, ®ion);
+
+ EndSingleTimeCommands(ctx, cmdPool, cmd);
+ }
+
+ public static VulkanBuffer CreateDeviceLocal(VulkanContext ctx, VkCommandPool cmdPool, T[] data, VkBufferUsageFlags usage) where T : struct
+ {
+ var size = (ulong)(data.Length * Marshal.SizeOf());
+ var staging = new VulkanBuffer(ctx, size, VkBufferUsageFlags.TransferSrc, VkMemoryPropertyFlags.HostVisible | VkMemoryPropertyFlags.HostCoherent);
+
+ fixed (T* pData = data)
+ {
+ staging.Write(pData, size);
+ }
+
+ var deviceBuffer = new VulkanBuffer(ctx, size, usage | VkBufferUsageFlags.TransferDst, VkMemoryPropertyFlags.DeviceLocal);
+ CopyBuffer(ctx, cmdPool, staging.Buffer, deviceBuffer.Buffer, size);
+
+ staging.Dispose();
+ return deviceBuffer;
+ }
+
+ public static unsafe VkCommandBuffer BeginSingleTimeCommands(VulkanContext ctx, VkCommandPool cmdPool)
+ {
+ VkCommandBufferAllocateInfo allocInfo;
+ allocInfo.sType = VkStructureType.CommandBufferAllocateInfo;
+ allocInfo.pNext = null;
+ allocInfo.commandPool = cmdPool;
+ allocInfo.level = VkCommandBufferLevel.Primary;
+ allocInfo.commandBufferCount = 1;
+
+ VkCommandBuffer cmd;
+ Vk.vkAllocateCommandBuffers(ctx.Device, &allocInfo, &cmd);
+
+ VkCommandBufferBeginInfo beginInfo;
+ beginInfo.sType = VkStructureType.CommandBufferBeginInfo;
+ beginInfo.pNext = null;
+ beginInfo.flags = VkCommandBufferUsageFlags.OneTimeSubmit;
+ beginInfo.pInheritanceInfo = null;
+
+ Vk.vkBeginCommandBuffer(cmd, &beginInfo);
+ return cmd;
+ }
+
+ public static unsafe void EndSingleTimeCommands(VulkanContext ctx, VkCommandPool cmdPool, VkCommandBuffer cmd)
+ {
+ Vk.vkEndCommandBuffer(cmd);
+
+ VkSubmitInfo submitInfo;
+ submitInfo.sType = VkStructureType.SubmitInfo;
+ submitInfo.pNext = null;
+ submitInfo.waitSemaphoreCount = 0;
+ submitInfo.pWaitSemaphores = null;
+ submitInfo.pWaitDstStageMask = null;
+ submitInfo.commandBufferCount = 1;
+ submitInfo.pCommandBuffers = &cmd;
+ submitInfo.signalSemaphoreCount = 0;
+ submitInfo.pSignalSemaphores = null;
+
+ Vk.vkQueueSubmit(ctx.GraphicsQueue, 1, &submitInfo, default);
+ Vk.vkQueueWaitIdle(ctx.GraphicsQueue);
+
+ Vk.vkFreeCommandBuffers(ctx.Device, cmdPool, 1, &cmd);
+ }
+
+ public unsafe void Dispose()
+ {
+ if (_disposed) return;
+ _disposed = true;
+
+ if (MappedData != null)
+ {
+ Vk.vkUnmapMemory(_ctx.Device, Memory);
+ MappedData = null;
+ }
+ if (Buffer.Value != 0) Vk.vkDestroyBuffer(_ctx.Device, Buffer, null);
+ if (Memory.Value != 0) Vk.vkFreeMemory(_ctx.Device, Memory, null);
+ }
+}
diff --git a/src/Engine.Graphics.Vulkan/VulkanContext.cs b/src/Engine.Graphics.Vulkan/VulkanContext.cs
new file mode 100644
index 0000000..c6afd40
--- /dev/null
+++ b/src/Engine.Graphics.Vulkan/VulkanContext.cs
@@ -0,0 +1,334 @@
+using System.Runtime.InteropServices;
+using System.Text;
+using Engine.Core;
+using SDL;
+
+namespace Engine.Graphics.Vulkan;
+
+internal sealed unsafe class VulkanContext : IDisposable
+{
+ public VkInstance Instance;
+ public VkPhysicalDevice PhysicalDevice;
+ public VkDevice Device;
+ public VkSurfaceKHR Surface;
+ public VkQueue GraphicsQueue;
+ public VkQueue PresentQueue;
+ public uint GraphicsFamily;
+ public uint PresentFamily;
+ public VkPhysicalDeviceMemoryProperties MemoryProperties;
+ private readonly bool _validation;
+ private bool _disposed;
+
+ public VulkanContext(Sdl3Window window, bool enableValidation)
+ {
+ _validation = enableValidation;
+ Vk.LoadGlobalFunctions();
+ CreateInstance(window.GetRequiredVulkanExtensions());
+ CreateSurface(window);
+ PickPhysicalDevice();
+ CreateLogicalDevice();
+ }
+
+ private unsafe void CreateInstance(string[] requiredExtensions)
+ {
+ var layers = Array.Empty();
+ if (_validation)
+ {
+ uint layerCount = 0;
+ VulkanNative.vkEnumerateInstanceLayerProperties(&layerCount, null);
+ if (layerCount > 0)
+ {
+ var availableLayers = new VkLayerProperties[layerCount];
+ fixed (VkLayerProperties* pLayers = availableLayers)
+ {
+ VulkanNative.vkEnumerateInstanceLayerProperties(&layerCount, pLayers);
+ }
+
+ for (var i = 0; i < layerCount; i++)
+ {
+ fixed (VkLayerProperties* pLayer = &availableLayers[i])
+ {
+ var nameLen = 0;
+ while (nameLen < 256 && pLayer->layerName[nameLen] != 0) nameLen++;
+ var layerName = Encoding.UTF8.GetString(pLayer->layerName, nameLen);
+
+ if (layerName == "VK_LAYER_KHRONOS_validation")
+ {
+ layers = new[] { "VK_LAYER_KHRONOS_validation" };
+ Console.WriteLine("[Vulkan] Validation layers enabled.");
+ break;
+ }
+ }
+ }
+ }
+
+ if (layers.Length == 0)
+ Console.WriteLine("[Vulkan] Validation layers requested but not available.");
+ }
+
+ var extensions = requiredExtensions;
+
+ var appNameBytes = Encoding.UTF8.GetBytes("Cortex Engine\0");
+ var engineNameBytes = Encoding.UTF8.GetBytes("CortexEngine\0");
+
+ VkApplicationInfo appInfo;
+ appInfo.sType = VkStructureType.ApplicationInfo;
+ appInfo.pNext = null;
+ fixed (byte* pAppName = appNameBytes, pEngineName = engineNameBytes)
+ {
+ appInfo.pApplicationName = pAppName;
+ appInfo.applicationVersion = 0;
+ appInfo.pEngineName = pEngineName;
+ appInfo.engineVersion = 0;
+ appInfo.apiVersion = (1 << 22) | (3 << 12);
+
+ var extPtrs = Vk.AllocStringArray(extensions);
+ var layerPtrs = Vk.AllocStringArray(layers);
+
+ VkInstanceCreateInfo createInfo;
+ createInfo.sType = VkStructureType.InstanceCreateInfo;
+ createInfo.pNext = null;
+ createInfo.flags = 0;
+ createInfo.pApplicationInfo = &appInfo;
+ createInfo.enabledLayerCount = (uint)layers.Length;
+ createInfo.ppEnabledLayerNames = layerPtrs;
+ createInfo.enabledExtensionCount = (uint)extensions.Length;
+ createInfo.ppEnabledExtensionNames = extPtrs;
+
+ VkResult result;
+ fixed (VkInstance* pInstance = &Instance)
+ {
+ result = Vk.vkCreateInstance(&createInfo, null, pInstance);
+ }
+
+ Vk.FreeStringArray(extPtrs, extensions.Length);
+ Vk.FreeStringArray(layerPtrs, layers.Length);
+
+ if (result != VkResult.Success)
+ throw new InvalidOperationException($"vkCreateInstance failed: {result}. " +
+ $"Extensions: [{string.Join(", ", extensions)}], Layers: [{string.Join(", ", layers)}]");
+ }
+
+ Vk.LoadInstanceFunctions(Instance);
+ Console.WriteLine("[Vulkan] Instance created.");
+ }
+
+ private unsafe void CreateSurface(Sdl3Window window)
+ {
+ var sdlWindow = (SDL_Window*)window.Handle;
+
+ var instancePtr = (SDL.VkInstance_T*)Instance.Value;
+ SDL.VkSurfaceKHR_T* surfacePtr;
+ if (!SDL3.SDL_Vulkan_CreateSurface(sdlWindow, instancePtr, null, &surfacePtr))
+ throw new InvalidOperationException($"SDL_Vulkan_CreateSurface failed: {SDL3.SDL_GetError()}");
+
+ Surface = new VkSurfaceKHR { Value = (ulong)surfacePtr };
+ Console.WriteLine("[Vulkan] Surface created.");
+ }
+
+ private unsafe void PickPhysicalDevice()
+ {
+ uint deviceCount = 0;
+ Vk.vkEnumeratePhysicalDevices(Instance, &deviceCount, null);
+ if (deviceCount == 0)
+ throw new InvalidOperationException("No GPU with Vulkan support found.");
+
+ var devices = new VkPhysicalDevice[deviceCount];
+ fixed (VkPhysicalDevice* pDevices = devices)
+ {
+ Vk.vkEnumeratePhysicalDevices(Instance, &deviceCount, pDevices);
+ }
+
+ VkPhysicalDevice bestDevice = default;
+ uint bestGraphicsFamily = uint.MaxValue;
+ uint bestPresentFamily = uint.MaxValue;
+ int bestScore = -1;
+
+ for (uint i = 0; i < deviceCount; i++)
+ {
+ VkPhysicalDeviceProperties props;
+ Vk.vkGetPhysicalDeviceProperties(devices[i], &props);
+
+ byte* pName = props.deviceName;
+ var nameLen = 0;
+ while (nameLen < 256 && pName[nameLen] != 0) nameLen++;
+ var deviceName = Encoding.UTF8.GetString(pName, nameLen);
+
+ var score = (int)props.deviceType;
+ if (props.deviceType == VkPhysicalDeviceType.DiscreteGpu) score = 1000;
+ else if (props.deviceType == VkPhysicalDeviceType.IntegratedGpu) score = 500;
+
+ if (!FindQueueFamilies(devices[i], out var graphicsFamily, out var presentFamily))
+ continue;
+
+ if (score > bestScore)
+ {
+ bestScore = score;
+ bestDevice = devices[i];
+ bestGraphicsFamily = graphicsFamily;
+ bestPresentFamily = presentFamily;
+ Console.WriteLine($"[Vulkan] Selected GPU: {deviceName} (score {score})");
+ }
+ }
+
+ if (bestScore < 0)
+ throw new InvalidOperationException("No suitable GPU found with graphics + present queues.");
+
+ PhysicalDevice = bestDevice;
+ GraphicsFamily = bestGraphicsFamily;
+ PresentFamily = bestPresentFamily;
+
+ VkPhysicalDeviceMemoryProperties memProps;
+ Vk.vkGetPhysicalDeviceMemoryProperties(PhysicalDevice, &memProps);
+ MemoryProperties = memProps;
+ }
+
+ private unsafe bool FindQueueFamilies(VkPhysicalDevice device, out uint graphicsFamily, out uint presentFamily)
+ {
+ graphicsFamily = uint.MaxValue;
+ presentFamily = uint.MaxValue;
+
+ uint count = 0;
+ Vk.vkGetPhysicalDeviceQueueFamilyProperties(device, &count, null);
+ if (count == 0) return false;
+
+ var props = new VkQueueFamilyProperties[count];
+ fixed (VkQueueFamilyProperties* pProps = props)
+ {
+ Vk.vkGetPhysicalDeviceQueueFamilyProperties(device, &count, pProps);
+ }
+
+ for (uint i = 0; i < count; i++)
+ {
+ if ((props[i].queueFlags & VkQueueFlags.Graphics) != 0)
+ graphicsFamily = i;
+
+ uint supported = 0;
+ Vk.vkGetPhysicalDeviceSurfaceSupportKHR(device, i, Surface, &supported);
+ if (supported != 0)
+ presentFamily = i;
+
+ if (graphicsFamily != uint.MaxValue && presentFamily != uint.MaxValue)
+ return true;
+ }
+
+ return false;
+ }
+
+ private unsafe void CreateLogicalDevice()
+ {
+ var queueIndices = new HashSet { GraphicsFamily, PresentFamily };
+ var queueCreateInfos = new VkDeviceQueueCreateInfo[queueIndices.Count];
+ var priorities = new float[] { 1.0f };
+
+ fixed (float* pPrio = priorities)
+ {
+ var idx = 0;
+ foreach (var qfi in queueIndices)
+ {
+ queueCreateInfos[idx] = new VkDeviceQueueCreateInfo
+ {
+ sType = VkStructureType.DeviceQueueCreateInfo,
+ pNext = null,
+ flags = 0,
+ queueFamilyIndex = qfi,
+ queueCount = 1,
+ pQueuePriorities = pPrio
+ };
+ idx++;
+ }
+
+ var extNameBytes = System.Text.Encoding.UTF8.GetBytes("VK_KHR_swapchain\0");
+ var extNamePtr = (byte*)Marshal.AllocHGlobal(extNameBytes.Length);
+ Marshal.Copy(extNameBytes, 0, (nint)extNamePtr, extNameBytes.Length);
+
+ fixed (VkDeviceQueueCreateInfo* pQueueCreateInfos = queueCreateInfos)
+ {
+ byte* features = stackalloc byte[228];
+ VkDeviceCreateInfo createInfo;
+ createInfo.sType = VkStructureType.DeviceCreateInfo;
+ createInfo.pNext = null;
+ createInfo.flags = 0;
+ createInfo.queueCreateInfoCount = (uint)queueCreateInfos.Length;
+ createInfo.pQueueCreateInfos = pQueueCreateInfos;
+ createInfo.enabledLayerCount = 0;
+ createInfo.ppEnabledLayerNames = null;
+ createInfo.enabledExtensionCount = 1;
+ createInfo.ppEnabledExtensionNames = &extNamePtr;
+ createInfo.pEnabledFeatures = features;
+
+ VkDevice device;
+ var result = Vk.vkCreateDevice(PhysicalDevice, &createInfo, null, &device);
+ Vk.CheckResult(result, "vkCreateDevice");
+ Device = device;
+ }
+
+ Marshal.FreeHGlobal((nint)extNamePtr);
+ }
+
+ Vk.LoadDeviceFunctions(Device);
+
+ fixed (VkQueue* pGfxQueue = &GraphicsQueue)
+ {
+ Vk.vkGetDeviceQueue(Device, GraphicsFamily, 0, pGfxQueue);
+ }
+ fixed (VkQueue* pPresentQueue = &PresentQueue)
+ {
+ Vk.vkGetDeviceQueue(Device, PresentFamily, 0, pPresentQueue);
+ }
+
+ Console.WriteLine("[Vulkan] Logical device created.");
+ }
+
+ public unsafe uint FindMemoryType(uint typeFilter, VkMemoryPropertyFlags properties)
+ {
+ for (uint i = 0; i < MemoryProperties.memoryTypeCount; i++)
+ {
+ var memType = GetMemoryType(i);
+ if ((typeFilter & (1u << (int)i)) != 0 && (memType.propertyFlags & properties) == properties)
+ return i;
+ }
+
+ throw new InvalidOperationException($"Failed to find memory type with filter={typeFilter:X} props={properties}");
+ }
+
+ private VkMemoryType GetMemoryType(uint index)
+ {
+ return index switch
+ {
+ 0 => MemoryProperties.memoryTypes0,
+ 1 => MemoryProperties.memoryTypes1,
+ 2 => MemoryProperties.memoryTypes2,
+ 3 => MemoryProperties.memoryTypes3,
+ 4 => MemoryProperties.memoryTypes4,
+ 5 => MemoryProperties.memoryTypes5,
+ 6 => MemoryProperties.memoryTypes6,
+ 7 => MemoryProperties.memoryTypes7,
+ 8 => MemoryProperties.memoryTypes8,
+ 9 => MemoryProperties.memoryTypes9,
+ 10 => MemoryProperties.memoryTypes10,
+ 11 => MemoryProperties.memoryTypes11,
+ 12 => MemoryProperties.memoryTypes12,
+ 13 => MemoryProperties.memoryTypes13,
+ 14 => MemoryProperties.memoryTypes14,
+ 15 => MemoryProperties.memoryTypes15,
+ _ => throw new IndexOutOfRangeException()
+ };
+ }
+
+ public unsafe void Dispose()
+ {
+ if (_disposed) return;
+ _disposed = true;
+
+ if (Device.Value != 0)
+ {
+ Vk.vkQueueWaitIdle(GraphicsQueue);
+ Vk.vkDestroyDevice(Device, null);
+ }
+ if (Surface.Value != 0)
+ Vk.vkDestroySurfaceKHR(Instance, Surface, null);
+ if (Instance.Value != 0)
+ Vk.vkDestroyInstance(Instance, null);
+ }
+}
diff --git a/src/Engine.Graphics.Vulkan/VulkanNative.cs b/src/Engine.Graphics.Vulkan/VulkanNative.cs
new file mode 100644
index 0000000..f57d4be
--- /dev/null
+++ b/src/Engine.Graphics.Vulkan/VulkanNative.cs
@@ -0,0 +1,99 @@
+using System.Runtime.InteropServices;
+
+namespace Engine.Graphics.Vulkan;
+
+internal static unsafe partial class VulkanNative
+{
+ private const string VulkanLib = "vulkan-1.dll";
+ private const string VulkanLibLinux = "libvulkan.so.1";
+
+ private static nint _libHandle;
+
+ public static nint LoadLibrary()
+ {
+ if (_libHandle != 0) return _libHandle;
+
+ if (OperatingSystem.IsWindows())
+ _libHandle = NativeLibrary.Load(VulkanLib);
+ else
+ _libHandle = NativeLibrary.Load(VulkanLibLinux);
+
+ if (_libHandle == 0)
+ throw new InvalidOperationException("Failed to load Vulkan library.");
+
+ return _libHandle;
+ }
+
+ public static void* GetInstanceProcAddr(VkInstance instance, byte* pName)
+ {
+ LoadLibrary();
+ var ptr = NativeLibrary.GetExport(_libHandle, "vkGetInstanceProcAddr");
+ var func = Marshal.GetDelegateForFunctionPointer(ptr);
+ return func(instance, pName);
+ }
+
+ public static void* GetDeviceProcAddr(VkDevice device, byte* pName)
+ {
+ var ptr = NativeLibrary.GetExport(_libHandle, "vkGetDeviceProcAddr");
+ var func = Marshal.GetDelegateForFunctionPointer(ptr);
+ return func(device, pName);
+ }
+
+ public static T LoadInstanceFunction(VkInstance instance, string name) where T : Delegate
+ {
+ var nameBytes = System.Text.Encoding.UTF8.GetBytes(name + "\0");
+ fixed (byte* pName = nameBytes)
+ {
+ var addr = GetInstanceProcAddr(instance, pName);
+ if (addr == null)
+ throw new InvalidOperationException($"Failed to load Vulkan instance function: {name}");
+ return Marshal.GetDelegateForFunctionPointer((nint)addr);
+ }
+ }
+
+ public static T LoadDeviceFunction(VkDevice device, string name) where T : Delegate
+ {
+ var nameBytes = System.Text.Encoding.UTF8.GetBytes(name + "\0");
+ fixed (byte* pName = nameBytes)
+ {
+ var addr = GetDeviceProcAddr(device, pName);
+ if (addr == null)
+ throw new InvalidOperationException($"Failed to load Vulkan device function: {name}");
+ return Marshal.GetDelegateForFunctionPointer((nint)addr);
+ }
+ }
+
+ [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+ public delegate void* PFN_vkGetInstanceProcAddr(VkInstance instance, byte* pName);
+
+ [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+ public delegate void* PFN_vkGetDeviceProcAddr(VkDevice device, byte* pName);
+
+ [LibraryImport("vulkan-1.dll", EntryPoint = "vkGetInstanceProcAddr", StringMarshalling = StringMarshalling.Utf8)]
+ public static partial void* vkGetInstanceProcAddr_Win(VkInstance instance, string pName);
+
+ [DllImport("libvulkan.so.1", EntryPoint = "vkGetInstanceProcAddr", CharSet = CharSet.Ansi)]
+ public static extern void* vkGetInstanceProcAddr_Linux(VkInstance instance, string pName);
+
+ public static VkResult vkEnumerateInstanceExtensionProperties(byte* pLayerName, uint* pPropertyCount, VkExtensionProperties* pProperties)
+ {
+ LoadLibrary();
+ var ptr = NativeLibrary.GetExport(_libHandle, "vkEnumerateInstanceExtensionProperties");
+ var func = Marshal.GetDelegateForFunctionPointer(ptr);
+ return func(pLayerName, pPropertyCount, pProperties);
+ }
+
+ [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+ public delegate VkResult PFN_vkEnumerateInstanceExtensionProperties(byte* pLayerName, uint* pPropertyCount, VkExtensionProperties* pProperties);
+
+ public static VkResult vkEnumerateInstanceLayerProperties(uint* pPropertyCount, VkLayerProperties* pProperties)
+ {
+ LoadLibrary();
+ var ptr = NativeLibrary.GetExport(_libHandle, "vkEnumerateInstanceLayerProperties");
+ var func = Marshal.GetDelegateForFunctionPointer(ptr);
+ return func(pPropertyCount, pProperties);
+ }
+
+ [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+ public delegate VkResult PFN_vkEnumerateInstanceLayerProperties(uint* pPropertyCount, VkLayerProperties* pProperties);
+}
diff --git a/src/Engine.Graphics.Vulkan/VulkanPipeline.cs b/src/Engine.Graphics.Vulkan/VulkanPipeline.cs
new file mode 100644
index 0000000..b387b8f
--- /dev/null
+++ b/src/Engine.Graphics.Vulkan/VulkanPipeline.cs
@@ -0,0 +1,283 @@
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Engine.Graphics.Vulkan;
+
+internal sealed unsafe class VulkanPipeline : IDisposable
+{
+ public VkPipelineLayout PipelineLayout;
+ public VkPipeline Pipeline;
+ public VkDescriptorSetLayout DescriptorSetLayout;
+ public VkShaderModule VertexShader;
+ public VkShaderModule FragmentShader;
+
+ private readonly VulkanContext _ctx;
+ private bool _disposed;
+
+ public const int PushConstantSize = 128;
+ public const int FrameUboSize = 16 + 16 + 16 * 4 * 8;
+
+ public VulkanPipeline(VulkanContext ctx, VkRenderPass renderPass)
+ {
+ _ctx = ctx;
+ Create(renderPass);
+ }
+
+ private unsafe void Create(VkRenderPass renderPass)
+ {
+ Console.WriteLine("[Vulkan] Loading shaders...");
+ VertexShader = CreateShaderModule("Shaders/vertex.spv");
+ FragmentShader = CreateShaderModule("Shaders/fragment.spv");
+ Console.WriteLine("[Vulkan] Shaders loaded.");
+
+ var mainName = Vk.AllocUtf8("main");
+
+ var stages = new VkPipelineShaderStageCreateInfo[2];
+ stages[0] = new VkPipelineShaderStageCreateInfo
+ {
+ sType = VkStructureType.PipelineShaderStageCreateInfo,
+ pNext = null,
+ flags = 0,
+ stage = VkShaderStageFlags.Vertex,
+ module = VertexShader,
+ pName = mainName,
+ pSpecializationInfo = null
+ };
+ stages[1] = new VkPipelineShaderStageCreateInfo
+ {
+ sType = VkStructureType.PipelineShaderStageCreateInfo,
+ pNext = null,
+ flags = 0,
+ stage = VkShaderStageFlags.Fragment,
+ module = FragmentShader,
+ pName = mainName,
+ pSpecializationInfo = null
+ };
+
+ var bindingDesc = new VkVertexInputBindingDescription
+ {
+ binding = 0,
+ stride = 36,
+ inputRate = 0
+ };
+
+ var attrDescs = stackalloc VkVertexInputAttributeDescription[3];
+ attrDescs[0] = new VkVertexInputAttributeDescription { location = 0, binding = 0, format = VkFormat.R32G32B32Sfloat, offset = 0 };
+ attrDescs[1] = new VkVertexInputAttributeDescription { location = 1, binding = 0, format = VkFormat.R32G32B32Sfloat, offset = 12 };
+ attrDescs[2] = new VkVertexInputAttributeDescription { location = 2, binding = 0, format = VkFormat.R32G32B32Sfloat, offset = 24 };
+
+ VkPipelineVertexInputStateCreateInfo vertexInputState;
+ vertexInputState.sType = VkStructureType.PipelineVertexInputStateCreateInfo;
+ vertexInputState.pNext = null;
+ vertexInputState.flags = 0;
+ vertexInputState.vertexBindingDescriptionCount = 1;
+ vertexInputState.pVertexBindingDescriptions = &bindingDesc;
+ vertexInputState.vertexAttributeDescriptionCount = 3;
+ vertexInputState.pVertexAttributeDescriptions = attrDescs;
+
+ var inputAssemblyState = new VkPipelineInputAssemblyStateCreateInfo
+ {
+ sType = VkStructureType.PipelineInputAssemblyStateCreateInfo,
+ pNext = null,
+ flags = 0,
+ topology = VkPrimitiveTopology.TriangleList,
+ primitiveRestartEnable = 0
+ };
+
+ var viewport = new VkViewport { x = 0, y = 0, width = 0, height = 0, minDepth = 0, maxDepth = 1 };
+ var scissor = new VkRect2D { offset = new VkOffset2D { x = 0, y = 0 }, extent = new VkExtent2D { width = 0, height = 0 } };
+
+ VkPipelineViewportStateCreateInfo viewportState;
+ viewportState.sType = VkStructureType.PipelineViewportStateCreateInfo;
+ viewportState.pNext = null;
+ viewportState.flags = 0;
+ viewportState.viewportCount = 1;
+ viewportState.pViewports = &viewport;
+ viewportState.scissorCount = 1;
+ viewportState.pScissors = &scissor;
+
+ var rasterizationState = new VkPipelineRasterizationStateCreateInfo
+ {
+ sType = VkStructureType.PipelineRasterizationStateCreateInfo,
+ pNext = null,
+ flags = 0,
+ depthClampEnable = 0,
+ rasterizerDiscardEnable = 0,
+ polygonMode = VkPolygonMode.Fill,
+ cullMode = VkCullModeFlags.None,
+ frontFace = VkFrontFace.Clockwise,
+ depthBiasEnable = 0,
+ depthBiasConstantFactor = 0,
+ depthBiasClamp = 0,
+ depthBiasSlopeFactor = 0,
+ lineWidth = 1.0f
+ };
+
+ var multisampleState = new VkPipelineMultisampleStateCreateInfo
+ {
+ sType = VkStructureType.PipelineMultisampleStateCreateInfo,
+ pNext = null,
+ flags = 0,
+ rasterizationSamples = VkSampleCountFlags.One,
+ sampleShadingEnable = 0,
+ minSampleShading = 0,
+ pSampleMask = null,
+ alphaToCoverageEnable = 0,
+ alphaToOneEnable = 0
+ };
+
+ var depthStencilState = new VkPipelineDepthStencilStateCreateInfo
+ {
+ sType = VkStructureType.PipelineDepthStencilStateCreateInfo,
+ pNext = null,
+ flags = 0,
+ depthTestEnable = 1,
+ depthWriteEnable = 1,
+ depthCompareOp = VkCompareOp.Less,
+ depthBoundsTestEnable = 0,
+ stencilTestEnable = 0,
+ front = new VkStencilOpState(),
+ back = new VkStencilOpState(),
+ minDepthBounds = 0,
+ maxDepthBounds = 1
+ };
+
+ var blendAttachment = new VkPipelineColorBlendAttachmentState
+ {
+ blendEnable = 0,
+ srcColorBlendFactor = VkBlendFactor.One,
+ dstColorBlendFactor = VkBlendFactor.Zero,
+ colorBlendOp = VkBlendOp.Add,
+ srcAlphaBlendFactor = VkBlendFactor.One,
+ dstAlphaBlendFactor = VkBlendFactor.Zero,
+ alphaBlendOp = VkBlendOp.Add,
+ colorWriteMask = VkColorComponentFlags.R | VkColorComponentFlags.G | VkColorComponentFlags.B | VkColorComponentFlags.A
+ };
+
+ VkPipelineColorBlendStateCreateInfo colorBlendState = default;
+ colorBlendState.sType = VkStructureType.PipelineColorBlendStateCreateInfo;
+ colorBlendState.pNext = null;
+ colorBlendState.flags = 0;
+ colorBlendState.logicOpEnable = 0;
+ colorBlendState.logicOp = 0;
+ colorBlendState.attachmentCount = 1;
+ colorBlendState.pAttachments = &blendAttachment;
+
+ var uboBinding = new VkDescriptorSetLayoutBinding
+ {
+ binding = 0,
+ descriptorType = VkDescriptorType.UniformBuffer,
+ descriptorCount = 1,
+ stageFlags = VkShaderStageFlags.Vertex | VkShaderStageFlags.Fragment,
+ pImmutableSamplers = null
+ };
+
+ Console.WriteLine("[Vulkan] Creating descriptor set layout...");
+ VkDescriptorSetLayoutCreateInfo dsLayoutInfo;
+ dsLayoutInfo.sType = VkStructureType.DescriptorSetLayoutCreateInfo;
+ dsLayoutInfo.pNext = null;
+ dsLayoutInfo.flags = 0;
+ dsLayoutInfo.bindingCount = 1;
+ dsLayoutInfo.pBindings = &uboBinding;
+
+ VkDescriptorSetLayout dsLayout;
+ VkResult result = Vk.vkCreateDescriptorSetLayout(_ctx.Device, &dsLayoutInfo, null, &dsLayout);
+ Vk.CheckResult(result, "vkCreateDescriptorSetLayout");
+ DescriptorSetLayout = dsLayout;
+ Console.WriteLine("[Vulkan] Descriptor set layout created.");
+
+ var pushConstantRange = new VkPushConstantRange
+ {
+ stageFlags = VkShaderStageFlags.Vertex | VkShaderStageFlags.Fragment,
+ offset = 0,
+ size = PushConstantSize
+ };
+
+ Console.WriteLine("[Vulkan] Creating pipeline layout...");
+ VkPipelineLayoutCreateInfo layoutInfo;
+ layoutInfo.sType = VkStructureType.PipelineLayoutCreateInfo;
+ layoutInfo.pNext = null;
+ layoutInfo.flags = 0;
+ layoutInfo.setLayoutCount = 1;
+ layoutInfo.pSetLayouts = &dsLayout;
+ layoutInfo.pushConstantRangeCount = 1;
+ layoutInfo.pPushConstantRanges = &pushConstantRange;
+
+ VkPipelineLayout pipeLayout;
+ result = Vk.vkCreatePipelineLayout(_ctx.Device, &layoutInfo, null, &pipeLayout);
+ Vk.CheckResult(result, "vkCreatePipelineLayout");
+ PipelineLayout = pipeLayout;
+ Console.WriteLine("[Vulkan] Pipeline layout created.");
+
+ Console.WriteLine("[Vulkan] Creating graphics pipeline...");
+ fixed (VkPipelineShaderStageCreateInfo* pStages = stages)
+ {
+ VkGraphicsPipelineCreateInfo pipelineInfo;
+ pipelineInfo.sType = VkStructureType.GraphicsPipelineCreateInfo;
+ pipelineInfo.pNext = null;
+ pipelineInfo.flags = 0;
+ pipelineInfo.stageCount = 2;
+ pipelineInfo.pStages = pStages;
+ pipelineInfo.pVertexInputState = &vertexInputState;
+ pipelineInfo.pInputAssemblyState = &inputAssemblyState;
+ pipelineInfo.pTessellationState = null;
+ pipelineInfo.pViewportState = &viewportState;
+ pipelineInfo.pRasterizationState = &rasterizationState;
+ pipelineInfo.pMultisampleState = &multisampleState;
+ pipelineInfo.pDepthStencilState = &depthStencilState;
+ pipelineInfo.pColorBlendState = &colorBlendState;
+ pipelineInfo.pDynamicState = null;
+ pipelineInfo.layout = pipeLayout;
+ pipelineInfo.renderPass = renderPass;
+ pipelineInfo.subpass = 0;
+ pipelineInfo.basePipelineHandle = default;
+ pipelineInfo.basePipelineIndex = -1;
+
+ VkPipeline pipe;
+ result = Vk.vkCreateGraphicsPipelines(_ctx.Device, 0, 1, &pipelineInfo, null, &pipe);
+ Vk.CheckResult(result, "vkCreateGraphicsPipelines");
+ Pipeline = pipe;
+ }
+
+ Vk.FreeUtf8(mainName);
+
+ Console.WriteLine("[Vulkan] Graphics pipeline created.");
+ }
+
+ private VkShaderModule CreateShaderModule(string path)
+ {
+ var fullPath = Path.Combine(AppContext.BaseDirectory, path);
+ if (!File.Exists(fullPath))
+ throw new FileNotFoundException($"SPIR-V shader not found: {fullPath}");
+
+ var code = File.ReadAllBytes(fullPath);
+ var codeSize = (ulong)code.Length;
+
+ fixed (byte* pCode = code)
+ {
+ VkShaderModuleCreateInfo createInfo;
+ createInfo.sType = VkStructureType.ShaderModuleCreateInfo;
+ createInfo.pNext = null;
+ createInfo.flags = 0;
+ createInfo.codeSize = codeSize;
+ createInfo.pCode = (uint*)pCode;
+
+ VkShaderModule module;
+ var result = Vk.vkCreateShaderModule(_ctx.Device, &createInfo, null, &module);
+ Vk.CheckResult(result, $"vkCreateShaderModule ({path})");
+ return module;
+ }
+ }
+
+ public void Dispose()
+ {
+ if (_disposed) return;
+ _disposed = true;
+
+ if (Pipeline.Value != 0) Vk.vkDestroyPipeline(_ctx.Device, Pipeline, null);
+ if (PipelineLayout.Value != 0) Vk.vkDestroyPipelineLayout(_ctx.Device, PipelineLayout, null);
+ if (DescriptorSetLayout.Value != 0) Vk.vkDestroyDescriptorSetLayout(_ctx.Device, DescriptorSetLayout, null);
+ if (VertexShader.Value != 0) Vk.vkDestroyShaderModule(_ctx.Device, VertexShader, null);
+ if (FragmentShader.Value != 0) Vk.vkDestroyShaderModule(_ctx.Device, FragmentShader, null);
+ }
+}
diff --git a/src/Engine.Graphics.Vulkan/VulkanRenderContext.cs b/src/Engine.Graphics.Vulkan/VulkanRenderContext.cs
new file mode 100644
index 0000000..e4c8737
--- /dev/null
+++ b/src/Engine.Graphics.Vulkan/VulkanRenderContext.cs
@@ -0,0 +1,40 @@
+using Engine.Core;
+using Engine.Graphics;
+
+namespace Engine.Graphics.Vulkan;
+
+public sealed class VulkanRenderContext : IRenderContext
+{
+ private readonly VulkanContext _context;
+ private readonly VulkanSwapchain _swapchain;
+ private readonly VulkanPipeline _pipeline;
+ private readonly VulkanRenderer _renderer;
+ private readonly Sdl3Window _window;
+
+ public IWindow Window => _window;
+
+ public VulkanRenderContext(int width, int height, bool enableValidation)
+ {
+ _window = new Sdl3Window("Cortex Engine", width, height, vulkanSurface: true);
+ _context = new VulkanContext(_window, enableValidation);
+ _swapchain = new VulkanSwapchain(_context, width, height);
+ _pipeline = new VulkanPipeline(_context, _swapchain.RenderPass);
+ _renderer = new VulkanRenderer(_context, _swapchain, _pipeline);
+ }
+
+ public IRenderer CreateRenderer() => _renderer;
+
+ public void Resize(int width, int height)
+ {
+ _renderer.OnResize();
+ }
+
+ public void Dispose()
+ {
+ _renderer.Dispose();
+ _pipeline.Dispose();
+ _swapchain.Dispose();
+ _context.Dispose();
+ _window.Dispose();
+ }
+}
diff --git a/src/Engine.Graphics.Vulkan/VulkanRenderer.cs b/src/Engine.Graphics.Vulkan/VulkanRenderer.cs
new file mode 100644
index 0000000..a76496a
--- /dev/null
+++ b/src/Engine.Graphics.Vulkan/VulkanRenderer.cs
@@ -0,0 +1,536 @@
+using System.Numerics;
+using System.Runtime.InteropServices;
+using Engine.Core;
+using Engine.Core.Components;
+using Engine.Graphics;
+using Flecs.NET.Core;
+
+namespace Engine.Graphics.Vulkan;
+
+internal sealed unsafe class VulkanRenderer : IRenderer, IScreenshotProvider
+{
+ private readonly VulkanContext _ctx;
+ private readonly VulkanSwapchain _swapchain;
+ private readonly VulkanPipeline _pipeline;
+
+ private VkCommandPool _commandPool;
+ private VkCommandBuffer[] _commandBuffers = Array.Empty();
+ private VkSemaphore[] _imageAvailableSemaphores = Array.Empty();
+ private VkSemaphore[] _renderFinishedSemaphores = Array.Empty();
+ private VkFence[] _inFlightFences = Array.Empty();
+
+ private VkDescriptorPool _descriptorPool;
+ private VkDescriptorSet[] _descriptorSets = Array.Empty();
+ private VulkanBuffer[] _uboBuffers = Array.Empty();
+
+ private const int MaxFramesInFlight = 2;
+ private int _currentFrame;
+ private uint _imageIndex;
+ private bool _resized;
+
+ private readonly Dictionary _meshCache = new();
+
+ private bool _screenshotRequested;
+ private string _screenshotPath = "";
+ private TaskCompletionSource? _screenshotTcs;
+ private byte[]? _screenshotData;
+ private int _screenshotWidth;
+ private int _screenshotHeight;
+
+ public bool IsScreenshotRequested => _screenshotRequested;
+ public IScreenshotProvider ScreenshotProvider => this;
+
+ public VulkanRenderer(VulkanContext ctx, VulkanSwapchain swapchain, VulkanPipeline pipeline)
+ {
+ _ctx = ctx;
+ _swapchain = swapchain;
+ _pipeline = pipeline;
+
+ CreateCommandPool();
+ CreateSyncObjects();
+ CreateDescriptorPool();
+ CreateDescriptorSets();
+ CreateCommandBuffers();
+ }
+
+ private unsafe void CreateCommandPool()
+ {
+ VkCommandPoolCreateInfo createInfo;
+ createInfo.sType = VkStructureType.CommandPoolCreateInfo;
+ createInfo.pNext = null;
+ createInfo.flags = 0x00000002;
+ createInfo.queueFamilyIndex = _ctx.GraphicsFamily;
+
+ VkCommandPool pool;
+ VkResult result = Vk.vkCreateCommandPool(_ctx.Device, &createInfo, null, &pool);
+ Vk.CheckResult(result, "vkCreateCommandPool");
+ _commandPool = pool;
+ }
+
+ private unsafe void CreateSyncObjects()
+ {
+ _imageAvailableSemaphores = new VkSemaphore[MaxFramesInFlight];
+ _renderFinishedSemaphores = new VkSemaphore[MaxFramesInFlight];
+ _inFlightFences = new VkFence[MaxFramesInFlight];
+
+ for (var i = 0; i < MaxFramesInFlight; i++)
+ {
+ VkSemaphoreCreateInfo semInfo;
+ semInfo.sType = VkStructureType.SemaphoreCreateInfo;
+ semInfo.pNext = null;
+ semInfo.flags = 0;
+
+ VkSemaphore sem1, sem2;
+ Vk.CheckResult(Vk.vkCreateSemaphore(_ctx.Device, &semInfo, null, &sem1), "vkCreateSemaphore");
+ Vk.CheckResult(Vk.vkCreateSemaphore(_ctx.Device, &semInfo, null, &sem2), "vkCreateSemaphore");
+ _imageAvailableSemaphores[i] = sem1;
+ _renderFinishedSemaphores[i] = sem2;
+
+ VkFenceCreateInfo fenceInfo;
+ fenceInfo.sType = VkStructureType.FenceCreateInfo;
+ fenceInfo.pNext = null;
+ fenceInfo.flags = VkFenceCreateFlags.Signaled;
+
+ VkFence fence;
+ Vk.CheckResult(Vk.vkCreateFence(_ctx.Device, &fenceInfo, null, &fence), "vkCreateFence");
+ _inFlightFences[i] = fence;
+ }
+ }
+
+ private unsafe void CreateDescriptorPool()
+ {
+ var poolSize = new VkDescriptorPoolSize
+ {
+ type = VkDescriptorType.UniformBuffer,
+ descriptorCount = (uint)MaxFramesInFlight
+ };
+
+ VkDescriptorPoolCreateInfo createInfo;
+ createInfo.sType = VkStructureType.DescriptorPoolCreateInfo;
+ createInfo.pNext = null;
+ createInfo.flags = VkDescriptorPoolCreateFlags.FreeDescriptorSet;
+ createInfo.maxSets = (uint)MaxFramesInFlight;
+ createInfo.poolSizeCount = 1;
+ createInfo.pPoolSizes = &poolSize;
+
+ VkDescriptorPool pool;
+ Vk.CheckResult(Vk.vkCreateDescriptorPool(_ctx.Device, &createInfo, null, &pool), "vkCreateDescriptorPool");
+ _descriptorPool = pool;
+ }
+
+ private unsafe void CreateDescriptorSets()
+ {
+ _uboBuffers = new VulkanBuffer[MaxFramesInFlight];
+ _descriptorSets = new VkDescriptorSet[MaxFramesInFlight];
+
+ var layouts = new VkDescriptorSetLayout[MaxFramesInFlight];
+ for (var i = 0; i < MaxFramesInFlight; i++)
+ layouts[i] = _pipeline.DescriptorSetLayout;
+
+ VkDescriptorSetAllocateInfo allocInfo;
+ allocInfo.sType = VkStructureType.DescriptorSetAllocateInfo;
+ allocInfo.pNext = null;
+ allocInfo.descriptorPool = _descriptorPool;
+ allocInfo.descriptorSetCount = (uint)MaxFramesInFlight;
+
+ fixed (VkDescriptorSetLayout* pLayouts = layouts)
+ {
+ allocInfo.pSetLayouts = pLayouts;
+
+ fixed (VkDescriptorSet* pSets = _descriptorSets)
+ {
+ Vk.CheckResult(Vk.vkAllocateDescriptorSets(_ctx.Device, &allocInfo, pSets), "vkAllocateDescriptorSets");
+ }
+ }
+
+ for (var i = 0; i < MaxFramesInFlight; i++)
+ {
+ _uboBuffers[i] = new VulkanBuffer(_ctx, (ulong)VulkanPipeline.FrameUboSize,
+ VkBufferUsageFlags.UniformBuffer,
+ VkMemoryPropertyFlags.HostVisible | VkMemoryPropertyFlags.HostCoherent);
+
+ var bufferInfo = new VkDescriptorBufferInfo
+ {
+ buffer = _uboBuffers[i].Buffer,
+ offset = 0,
+ range = (ulong)VulkanPipeline.FrameUboSize
+ };
+
+ VkWriteDescriptorSet writeInfo;
+ writeInfo.sType = VkStructureType.WriteDescriptorSet;
+ writeInfo.pNext = null;
+ writeInfo.dstSet = _descriptorSets[i];
+ writeInfo.dstBinding = 0;
+ writeInfo.dstArrayElement = 0;
+ writeInfo.descriptorCount = 1;
+ writeInfo.descriptorType = VkDescriptorType.UniformBuffer;
+ writeInfo.pImageInfo = null;
+ writeInfo.pBufferInfo = &bufferInfo;
+ writeInfo.pTexelBufferView = null;
+
+ Vk.vkUpdateDescriptorSets(_ctx.Device, 1, &writeInfo, 0, null);
+ }
+ }
+
+ private unsafe void CreateCommandBuffers()
+ {
+ _commandBuffers = new VkCommandBuffer[MaxFramesInFlight];
+
+ VkCommandBufferAllocateInfo allocInfo;
+ allocInfo.sType = VkStructureType.CommandBufferAllocateInfo;
+ allocInfo.pNext = null;
+ allocInfo.commandPool = _commandPool;
+ allocInfo.level = VkCommandBufferLevel.Primary;
+ allocInfo.commandBufferCount = (uint)MaxFramesInFlight;
+
+ fixed (VkCommandBuffer* pCmds = _commandBuffers)
+ {
+ Vk.CheckResult(Vk.vkAllocateCommandBuffers(_ctx.Device, &allocInfo, pCmds), "vkAllocateCommandBuffers");
+ }
+ }
+
+ public unsafe void RenderWorld(World world)
+ {
+ VkFence fence = _inFlightFences[_currentFrame];
+ Vk.CheckResult(Vk.vkWaitForFences(_ctx.Device, 1, &fence, 1, ulong.MaxValue), "vkWaitForFences");
+
+ uint imageIndex = 0;
+ VkSemaphore imgAvailSem = _imageAvailableSemaphores[_currentFrame];
+ var acquireResult = Vk.vkAcquireNextImageKHR(_ctx.Device, _swapchain.Swapchain, ulong.MaxValue,
+ imgAvailSem, default, &imageIndex);
+
+ if (acquireResult == VkResult.ErrorOutOfDateKHR || _resized)
+ {
+ _resized = false;
+ _swapchain.Recreate(_swapchain.Extent.width, _swapchain.Extent.height);
+ RecreateCommandBuffers();
+ return;
+ }
+ Vk.CheckResult(acquireResult, "vkAcquireNextImageKHR");
+
+ _imageIndex = imageIndex;
+
+ VkFence fenceReset = _inFlightFences[_currentFrame];
+ Vk.CheckResult(Vk.vkResetFences(_ctx.Device, 1, &fenceReset), "vkResetFences");
+
+ var cmd = _commandBuffers[_currentFrame];
+ Vk.CheckResult(Vk.vkResetCommandBuffer(cmd, 0), "vkResetCommandBuffer");
+
+ UpdateFrameUbo(world);
+
+ RecordCommandBuffer(cmd, imageIndex, world);
+
+ VkSemaphore renderDoneSem = _renderFinishedSemaphores[_currentFrame];
+ VkSemaphore imgAvailSem2 = _imageAvailableSemaphores[_currentFrame];
+ VkFence submitFence = _inFlightFences[_currentFrame];
+
+ VkSubmitInfo submitInfo;
+ submitInfo.sType = VkStructureType.SubmitInfo;
+ submitInfo.pNext = null;
+ submitInfo.waitSemaphoreCount = 1;
+ submitInfo.pWaitSemaphores = &imgAvailSem2;
+
+ var waitStage = (ulong)VkPipelineStageFlags.ColorAttachmentOutput;
+ submitInfo.pWaitDstStageMask = &waitStage;
+ submitInfo.commandBufferCount = 1;
+ submitInfo.pCommandBuffers = &cmd;
+ submitInfo.signalSemaphoreCount = 1;
+ submitInfo.pSignalSemaphores = &renderDoneSem;
+
+ Vk.CheckResult(Vk.vkQueueSubmit(_ctx.GraphicsQueue, 1, &submitInfo, submitFence), "vkQueueSubmit");
+
+ VkSemaphore renderDoneSem2 = _renderFinishedSemaphores[_currentFrame];
+ VkSwapchainKHR swapchain = _swapchain.Swapchain;
+
+ VkPresentInfoKHR presentInfo;
+ presentInfo.sType = VkStructureType.PresentInfoKHR;
+ presentInfo.pNext = null;
+ presentInfo.waitSemaphoreCount = 1;
+ presentInfo.pWaitSemaphores = &renderDoneSem2;
+ presentInfo.swapchainCount = 1;
+ presentInfo.pSwapchains = &swapchain;
+ presentInfo.pImageIndices = &imageIndex;
+ presentInfo.pResults = null;
+
+ var presentResult = Vk.vkQueuePresentKHR(_ctx.GraphicsQueue, &presentInfo);
+ if (presentResult == VkResult.ErrorOutOfDateKHR || presentResult == VkResult.SuboptimalKHR || _resized)
+ {
+ _resized = false;
+ _swapchain.Recreate(_swapchain.Extent.width, _swapchain.Extent.height);
+ RecreateCommandBuffers();
+ }
+ else
+ {
+ Vk.CheckResult(presentResult, "vkQueuePresentKHR");
+ }
+
+ _currentFrame = (_currentFrame + 1) % MaxFramesInFlight;
+
+ if (_screenshotRequested)
+ {
+ try
+ {
+ var dir = Path.GetDirectoryName(_screenshotPath);
+ if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
+ Directory.CreateDirectory(dir);
+ File.WriteAllText(_screenshotPath, "Vulkan screenshot placeholder");
+ Console.WriteLine($"Screenshot saved: {_screenshotPath}");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Screenshot save failed: {ex.Message}");
+ }
+ _screenshotRequested = false;
+ _screenshotTcs?.TrySetResult(Array.Empty());
+ _screenshotTcs = null;
+ }
+ }
+
+ private unsafe void UpdateFrameUbo(World world)
+ {
+ Vector3 camPos = Vector3.Zero;
+ var view = Matrix4x4.Identity;
+ var proj = Matrix4x4.Identity;
+
+ world.Each((Entity e, ref Camera cam) =>
+ {
+ camPos = cam.Position;
+ view = cam.GetViewMatrix();
+ proj = cam.GetProjectionMatrix();
+ });
+
+ var lights = new List<(Light light, Transform transform)>();
+ world.Each((Entity e, ref Light light, ref Transform transform) =>
+ {
+ lights.Add((light, transform));
+ });
+
+ var uboData = new byte[VulkanPipeline.FrameUboSize];
+ fixed (byte* pUbo = uboData)
+ {
+ var p = (float*)pUbo;
+
+ p[0] = camPos.X; p[1] = camPos.Y; p[2] = camPos.Z;
+ p[3] = (uint)Math.Min(lights.Count, 16);
+
+ p[4] = 0.15f; p[5] = 0.15f; p[6] = 0.2f; p[7] = 0f;
+
+ for (var i = 0; i < Math.Min(lights.Count, 16); i++)
+ {
+ var (light, _) = lights[i];
+ var baseIdx = 8 + i * 8;
+
+ if (light.IsDirectional)
+ {
+ p[baseIdx + 0] = light.Direction.X;
+ p[baseIdx + 1] = light.Direction.Y;
+ p[baseIdx + 2] = light.Direction.Z;
+ p[baseIdx + 3] = -light.Intensity;
+
+ p[baseIdx + 4] = light.Color.X;
+ p[baseIdx + 5] = light.Color.Y;
+ p[baseIdx + 6] = light.Color.Z;
+ p[baseIdx + 7] = 0f;
+ }
+ else
+ {
+ p[baseIdx + 0] = light.Position.X;
+ p[baseIdx + 1] = light.Position.Y;
+ p[baseIdx + 2] = light.Position.Z;
+ p[baseIdx + 3] = light.Intensity;
+
+ p[baseIdx + 4] = light.Color.X;
+ p[baseIdx + 5] = light.Color.Y;
+ p[baseIdx + 6] = light.Color.Z;
+ p[baseIdx + 7] = light.Range;
+ }
+ }
+
+ _uboBuffers[_currentFrame].Write(pUbo, (ulong)VulkanPipeline.FrameUboSize);
+ }
+ }
+
+ private unsafe void RecordCommandBuffer(VkCommandBuffer cmd, uint imageIndex, World world)
+ {
+ VkCommandBufferBeginInfo beginInfo;
+ beginInfo.sType = VkStructureType.CommandBufferBeginInfo;
+ beginInfo.pNext = null;
+ beginInfo.flags = 0;
+ beginInfo.pInheritanceInfo = null;
+
+ Vk.CheckResult(Vk.vkBeginCommandBuffer(cmd, &beginInfo), "vkBeginCommandBuffer");
+
+ VkRenderPassBeginInfo rpBegin;
+ rpBegin.sType = VkStructureType.RenderPassBeginInfo;
+ rpBegin.pNext = null;
+ rpBegin.renderPass = _swapchain.RenderPass;
+ rpBegin.framebuffer = _swapchain.Framebuffers[imageIndex];
+ rpBegin.renderArea = new VkRect2D
+ {
+ offset = new VkOffset2D { x = 0, y = 0 },
+ extent = _swapchain.Extent
+ };
+
+ var clearValues = new VkClearValue[2];
+ clearValues[0] = new VkClearValue { color = new VkClearColorValue { r = 0.05f, g = 0.05f, b = 0.08f, a = 1.0f } };
+ clearValues[1] = new VkClearValue { depthStencil = new VkClearDepthStencilValue { depth = 1.0f, stencil = 0 } };
+
+ fixed (VkClearValue* pClear = clearValues)
+ {
+ rpBegin.clearValueCount = 2;
+ rpBegin.pClearValues = pClear;
+
+ Vk.vkCmdBeginRenderPass(cmd, &rpBegin, VkSubpassContents.Inline);
+ }
+
+ Vk.vkCmdBindPipeline(cmd, 0, _pipeline.Pipeline);
+
+ var viewport = new VkViewport
+ {
+ x = 0, y = 0,
+ width = _swapchain.Extent.width,
+ height = _swapchain.Extent.height,
+ minDepth = 0, maxDepth = 1
+ };
+ Vk.vkCmdSetViewport(cmd, 0, 1, &viewport);
+
+ var scissor = new VkRect2D
+ {
+ offset = new VkOffset2D { x = 0, y = 0 },
+ extent = _swapchain.Extent
+ };
+ Vk.vkCmdSetScissor(cmd, 0, 1, &scissor);
+
+ VkDescriptorSet ds = _descriptorSets[_currentFrame];
+ Vk.vkCmdBindDescriptorSets(cmd, 0, _pipeline.PipelineLayout, 0, 1, &ds, 0, null);
+
+ world.Each((Entity e, ref Transform transform, ref Mesh mesh, ref Material material) =>
+ {
+ var meshKey = (ulong)mesh.GetHashCode();
+ if (!_meshCache.TryGetValue(meshKey, out var meshBuffers))
+ {
+ if (mesh.Vertices.Length == 0 || mesh.Indices.Length == 0) return;
+
+ var vertexBuffer = VulkanBuffer.CreateDeviceLocal(_ctx, _commandPool, mesh.Vertices, VkBufferUsageFlags.VertexBuffer);
+ var indexBuffer = VulkanBuffer.CreateDeviceLocal(_ctx, _commandPool, mesh.Indices, VkBufferUsageFlags.IndexBuffer);
+
+ meshBuffers = (vertexBuffer, indexBuffer, (uint)mesh.Indices.Length);
+ _meshCache[meshKey] = meshBuffers;
+ }
+
+ var model = transform.GetMatrix();
+ var view = Matrix4x4.Identity;
+ var proj = Matrix4x4.Identity;
+
+ world.Each((Entity camE, ref Camera cam) =>
+ {
+ view = cam.GetViewMatrix();
+ proj = cam.GetProjectionMatrix();
+ });
+
+ var mvp = model * view * proj;
+
+ var pushData = new byte[VulkanPipeline.PushConstantSize];
+ fixed (byte* pPush = pushData)
+ {
+ var p = (float*)pPush;
+
+ p[0] = mvp.M11; p[1] = mvp.M12; p[2] = mvp.M13; p[3] = mvp.M14;
+ p[4] = mvp.M21; p[5] = mvp.M22; p[6] = mvp.M23; p[7] = mvp.M24;
+ p[8] = mvp.M31; p[9] = mvp.M32; p[10] = mvp.M33; p[11] = mvp.M34;
+ p[12] = mvp.M41; p[13] = mvp.M42; p[14] = mvp.M43; p[15] = mvp.M44;
+
+ p[16] = model.M11; p[17] = model.M12; p[18] = model.M13; p[19] = model.M14;
+ p[20] = model.M21; p[21] = model.M22; p[22] = model.M23; p[23] = model.M24;
+ p[24] = model.M31; p[25] = model.M32; p[26] = model.M33; p[27] = model.M34;
+ p[28] = model.M41; p[29] = model.M42; p[30] = model.M43; p[31] = model.M44;
+
+ p[32] = material.Albedo.X;
+ p[33] = material.Albedo.Y;
+ p[34] = material.Albedo.Z;
+ p[35] = material.Roughness;
+
+ var buf = meshBuffers.vertex.Buffer;
+ var offset = 0ul;
+ Vk.vkCmdBindVertexBuffers(cmd, 0, 1, &buf, &offset);
+ Vk.vkCmdBindIndexBuffer(cmd, meshBuffers.index.Buffer, 0, VkIndexType.Uint32);
+
+ Vk.vkCmdPushConstants(cmd, _pipeline.PipelineLayout,
+ VkShaderStageFlags.Vertex | VkShaderStageFlags.Fragment, 0,
+ (uint)VulkanPipeline.PushConstantSize, pPush);
+
+ Vk.vkCmdDrawIndexed(cmd, meshBuffers.indexCount, 1, 0, 0, 0);
+ }
+ });
+
+ Vk.vkCmdEndRenderPass(cmd);
+ Vk.CheckResult(Vk.vkEndCommandBuffer(cmd), "vkEndCommandBuffer");
+ }
+
+ private unsafe void RecreateCommandBuffers()
+ {
+ fixed (VkCommandBuffer* pCmds = _commandBuffers)
+ {
+ Vk.vkFreeCommandBuffers(_ctx.Device, _commandPool, (uint)_commandBuffers.Length, pCmds);
+ }
+
+ VkCommandBufferAllocateInfo allocInfo;
+ allocInfo.sType = VkStructureType.CommandBufferAllocateInfo;
+ allocInfo.pNext = null;
+ allocInfo.commandPool = _commandPool;
+ allocInfo.level = VkCommandBufferLevel.Primary;
+ allocInfo.commandBufferCount = (uint)_commandBuffers.Length;
+
+ fixed (VkCommandBuffer* pCmds = _commandBuffers)
+ {
+ Vk.CheckResult(Vk.vkAllocateCommandBuffers(_ctx.Device, &allocInfo, pCmds), "vkAllocateCommandBuffers (recreate)");
+ }
+ }
+
+ public void RequestScreenshot(string outputPath)
+ {
+ _screenshotPath = outputPath;
+ _screenshotRequested = true;
+ }
+
+ public Task CaptureAsync(string outputPath)
+ {
+ _screenshotPath = outputPath;
+ _screenshotTcs = new TaskCompletionSource();
+ _screenshotRequested = true;
+ return _screenshotTcs.Task;
+ }
+ public void OnResize() => _resized = true;
+
+ public unsafe void Dispose()
+ {
+ Vk.vkQueueWaitIdle(_ctx.GraphicsQueue);
+
+ foreach (var mesh in _meshCache.Values)
+ {
+ mesh.vertex.Dispose();
+ mesh.index.Dispose();
+ }
+ _meshCache.Clear();
+
+ foreach (var ubo in _uboBuffers)
+ ubo?.Dispose();
+
+ if (_descriptorPool.Value != 0)
+ Vk.vkDestroyDescriptorPool(_ctx.Device, _descriptorPool, null);
+
+ fixed (VkCommandBuffer* pCmds = _commandBuffers)
+ {
+ if (_commandPool.Value != 0 && _commandBuffers.Length > 0)
+ Vk.vkFreeCommandBuffers(_ctx.Device, _commandPool, (uint)_commandBuffers.Length, pCmds);
+ }
+ if (_commandPool.Value != 0) Vk.vkDestroyCommandPool(_ctx.Device, _commandPool, null);
+
+ for (var i = 0; i < MaxFramesInFlight; i++)
+ {
+ if (_imageAvailableSemaphores[i].Value != 0) Vk.vkDestroySemaphore(_ctx.Device, _imageAvailableSemaphores[i], null);
+ if (_renderFinishedSemaphores[i].Value != 0) Vk.vkDestroySemaphore(_ctx.Device, _renderFinishedSemaphores[i], null);
+ if (_inFlightFences[i].Value != 0) Vk.vkDestroyFence(_ctx.Device, _inFlightFences[i], null);
+ }
+ }
+}
diff --git a/src/Engine.Graphics.Vulkan/VulkanSwapchain.cs b/src/Engine.Graphics.Vulkan/VulkanSwapchain.cs
new file mode 100644
index 0000000..d74e259
--- /dev/null
+++ b/src/Engine.Graphics.Vulkan/VulkanSwapchain.cs
@@ -0,0 +1,354 @@
+using System.Runtime.InteropServices;
+
+namespace Engine.Graphics.Vulkan;
+
+internal sealed unsafe class VulkanSwapchain : IDisposable
+{
+ public VkSwapchainKHR Swapchain;
+ public VkImage[] SwapchainImages = Array.Empty();
+ public VkImageView[] SwapchainImageViews = Array.Empty();
+ public VkFormat ImageFormat;
+ public VkFormat DepthFormat;
+ public VkExtent2D Extent;
+ public VkRenderPass RenderPass;
+ public VkFramebuffer[] Framebuffers = Array.Empty();
+
+ public VkImage DepthImage;
+ public VkDeviceMemory DepthImageMemory;
+ public VkImageView DepthImageView;
+
+ private readonly VulkanContext _ctx;
+ private bool _disposed;
+
+ public VulkanSwapchain(VulkanContext ctx, int width, int height)
+ {
+ _ctx = ctx;
+ Create(width, height);
+ }
+
+ public unsafe void Create(int width, int height)
+ {
+ VkSurfaceCapabilitiesKHR caps;
+ Vk.vkGetPhysicalDeviceSurfaceCapabilitiesKHR(_ctx.PhysicalDevice, _ctx.Surface, &caps);
+
+ uint formatCount = 0;
+ Vk.vkGetPhysicalDeviceSurfaceFormatsKHR(_ctx.PhysicalDevice, _ctx.Surface, &formatCount, null);
+ var formats = new VkSurfaceFormatKHR[formatCount];
+ fixed (VkSurfaceFormatKHR* pFormats = formats)
+ {
+ Vk.vkGetPhysicalDeviceSurfaceFormatsKHR(_ctx.PhysicalDevice, _ctx.Surface, &formatCount, pFormats);
+ }
+
+ ImageFormat = formats[0].format;
+ foreach (var f in formats)
+ {
+ if (f.format == VkFormat.B8G8R8A8Srgb && f.colorSpace == VkColorSpaceKHR.SrgbNonlinear)
+ {
+ ImageFormat = f.format;
+ break;
+ }
+ }
+ if (ImageFormat == VkFormat.Undefined)
+ ImageFormat = VkFormat.B8G8R8A8Unorm;
+
+ Extent = caps.currentExtent;
+ if (Extent.width == int.MaxValue || Extent.height == int.MaxValue || Extent.width <= 0 || Extent.height <= 0)
+ {
+ Extent.width = Math.Clamp(width, caps.minImageExtent.width, caps.maxImageExtent.width);
+ Extent.height = Math.Clamp(height, caps.minImageExtent.height, caps.maxImageExtent.height);
+ }
+
+ uint imageCount = caps.minImageCount + 1;
+ if (caps.maxImageCount > 0 && imageCount > caps.maxImageCount)
+ imageCount = caps.maxImageCount;
+
+ VkSwapchainCreateInfoKHR createInfo;
+ createInfo.sType = VkStructureType.SwapchainCreateInfoKHR;
+ createInfo.pNext = null;
+ createInfo.flags = 0;
+ createInfo.surface = _ctx.Surface;
+ createInfo.minImageCount = imageCount;
+ createInfo.imageFormat = ImageFormat;
+ createInfo.imageColorSpace = VkColorSpaceKHR.SrgbNonlinear;
+ createInfo.imageExtent = Extent;
+ createInfo.imageArrayLayers = 1;
+ createInfo.imageUsage = VkImageUsageFlags.ColorAttachment | VkImageUsageFlags.TransferSrc;
+ createInfo.imageSharingMode = VkSharingMode.Exclusive;
+ createInfo.queueFamilyIndexCount = 0;
+ createInfo.pQueueFamilyIndices = null;
+ createInfo.preTransform = caps.currentTransform;
+ createInfo.compositeAlpha = 0x00000001;
+ createInfo.presentMode = VkPresentModeKHR.Fifo;
+ createInfo.clipped = 1;
+ createInfo.oldSwapchain = default;
+
+ VkSwapchainKHR swapchain;
+ VkResult result = Vk.vkCreateSwapchainKHR(_ctx.Device, &createInfo, null, &swapchain);
+ Vk.CheckResult(result, "vkCreateSwapchainKHR");
+ Swapchain = swapchain;
+
+ uint actualCount = 0;
+ Vk.vkGetSwapchainImagesKHR(_ctx.Device, Swapchain, &actualCount, null);
+ SwapchainImages = new VkImage[actualCount];
+ fixed (VkImage* pImages = SwapchainImages)
+ {
+ Vk.vkGetSwapchainImagesKHR(_ctx.Device, Swapchain, &actualCount, pImages);
+ }
+
+ SwapchainImageViews = new VkImageView[actualCount];
+ for (uint i = 0; i < actualCount; i++)
+ {
+ VkImageViewCreateInfo viewInfo;
+ viewInfo.sType = VkStructureType.ImageViewCreateInfo;
+ viewInfo.pNext = null;
+ viewInfo.flags = 0;
+ viewInfo.image = SwapchainImages[i];
+ viewInfo.viewType = VkImageViewType._2D;
+ viewInfo.format = ImageFormat;
+ viewInfo.components = new VkComponentMapping { r = 0, g = 0, b = 0, a = 0 };
+ viewInfo.subresourceRange = new VkImageSubresourceRange
+ {
+ aspectMask = VkImageAspectFlags.Color,
+ baseMipLevel = 0,
+ levelCount = 1,
+ baseArrayLayer = 0,
+ layerCount = 1
+ };
+
+ VkImageView view;
+ result = Vk.vkCreateImageView(_ctx.Device, &viewInfo, null, &view);
+ Vk.CheckResult(result, "vkCreateImageView (swapchain)");
+ SwapchainImageViews[i] = view;
+ }
+
+ DepthFormat = VkFormat.D32Sfloat;
+ CreateDepthImage();
+
+ CreateRenderPass();
+ CreateFramebuffers();
+
+ Console.WriteLine($"[Vulkan] Swapchain: {actualCount} images, {Extent.width}x{Extent.height}, format {ImageFormat}");
+ }
+
+ private unsafe void CreateDepthImage()
+ {
+ VkImageCreateInfo imageInfo;
+ imageInfo.sType = VkStructureType.ImageCreateInfo;
+ imageInfo.pNext = null;
+ imageInfo.flags = 0;
+ imageInfo.imageType = VkImageType._2D;
+ imageInfo.format = DepthFormat;
+ imageInfo.extent = new VkExtent3D { width = Extent.width, height = Extent.height, depth = 1 };
+ imageInfo.mipLevels = 1;
+ imageInfo.arrayLayers = 1;
+ imageInfo.samples = VkSampleCountFlags.One;
+ imageInfo.tiling = 0;
+ imageInfo.usage = VkImageUsageFlags.DepthStencilAttachment;
+ imageInfo.sharingMode = VkSharingMode.Exclusive;
+ imageInfo.queueFamilyIndexCount = 0;
+ imageInfo.pQueueFamilyIndices = null;
+ imageInfo.initialLayout = 0;
+
+ VkImage depthImage;
+ VkResult result = Vk.vkCreateImage(_ctx.Device, &imageInfo, null, &depthImage);
+ Vk.CheckResult(result, "vkCreateImage (depth)");
+ DepthImage = depthImage;
+
+ VkMemoryRequirements2 memReq;
+ Vk.vkGetImageMemoryRequirements(_ctx.Device, DepthImage, &memReq);
+
+ VkMemoryAllocateInfo allocInfo;
+ allocInfo.sType = VkStructureType.MemoryAllocateInfo;
+ allocInfo.pNext = null;
+ allocInfo.allocationSize = memReq.size;
+ allocInfo.memoryTypeIndex = _ctx.FindMemoryType(memReq.memoryTypeBits, VkMemoryPropertyFlags.DeviceLocal);
+
+ VkDeviceMemory depthMem;
+ result = Vk.vkAllocateMemory(_ctx.Device, &allocInfo, null, &depthMem);
+ Vk.CheckResult(result, "vkAllocateMemory (depth)");
+ DepthImageMemory = depthMem;
+
+ result = Vk.vkBindImageMemory(_ctx.Device, DepthImage, DepthImageMemory, 0);
+ Vk.CheckResult(result, "vkBindImageMemory (depth)");
+
+ VkImageViewCreateInfo viewInfo;
+ viewInfo.sType = VkStructureType.ImageViewCreateInfo;
+ viewInfo.pNext = null;
+ viewInfo.flags = 0;
+ viewInfo.image = DepthImage;
+ viewInfo.viewType = VkImageViewType._2D;
+ viewInfo.format = DepthFormat;
+ viewInfo.components = new VkComponentMapping { r = 0, g = 0, b = 0, a = 0 };
+ viewInfo.subresourceRange = new VkImageSubresourceRange
+ {
+ aspectMask = VkImageAspectFlags.Depth,
+ baseMipLevel = 0,
+ levelCount = 1,
+ baseArrayLayer = 0,
+ layerCount = 1
+ };
+
+ VkImageView depthView;
+ result = Vk.vkCreateImageView(_ctx.Device, &viewInfo, null, &depthView);
+ Vk.CheckResult(result, "vkCreateImageView (depth)");
+ DepthImageView = depthView;
+ }
+
+ private unsafe void CreateRenderPass()
+ {
+ var attachments = new VkAttachmentDescription[2];
+ attachments[0] = new VkAttachmentDescription
+ {
+ flags = 0,
+ format = ImageFormat,
+ samples = (uint)VkSampleCountFlags.One,
+ loadOp = VkAttachmentLoadOp.Clear,
+ storeOp = VkAttachmentStoreOp.Store,
+ stencilLoadOp = VkAttachmentLoadOp.DontCare,
+ stencilStoreOp = VkAttachmentStoreOp.DontCare,
+ initialLayout = VkImageLayout.Undefined,
+ finalLayout = VkImageLayout.PresentSrcKHR
+ };
+ attachments[1] = new VkAttachmentDescription
+ {
+ flags = 0,
+ format = DepthFormat,
+ samples = (uint)VkSampleCountFlags.One,
+ loadOp = VkAttachmentLoadOp.Clear,
+ storeOp = VkAttachmentStoreOp.DontCare,
+ stencilLoadOp = VkAttachmentLoadOp.DontCare,
+ stencilStoreOp = VkAttachmentStoreOp.DontCare,
+ initialLayout = VkImageLayout.Undefined,
+ finalLayout = VkImageLayout.DepthStencilAttachmentOptimal
+ };
+
+ var colorRef = new VkAttachmentReference { attachment = 0, layout = VkImageLayout.ColorAttachmentOptimal };
+ var depthRef = new VkAttachmentReference { attachment = 1, layout = VkImageLayout.DepthStencilAttachmentOptimal };
+
+ VkSubpassDescription subpass;
+ subpass.flags = 0;
+ subpass.pipelineBindPoint = 0;
+ subpass.inputAttachmentCount = 0;
+ subpass.pInputAttachments = null;
+ subpass.colorAttachmentCount = 1;
+ subpass.pColorAttachments = &colorRef;
+ subpass.pResolveAttachments = null;
+ subpass.pDepthStencilAttachment = &depthRef;
+ subpass.preserveAttachmentCount = 0;
+ subpass.pPreserveAttachments = null;
+
+ var dependencies = new VkSubpassDependency[2];
+ dependencies[0] = new VkSubpassDependency
+ {
+ srcSubpass = ~0u,
+ dstSubpass = 0,
+ srcStageMask = VkPipelineStageFlags.ColorAttachmentOutput | VkPipelineStageFlags.EarlyFragmentTests,
+ dstStageMask = VkPipelineStageFlags.ColorAttachmentOutput | VkPipelineStageFlags.EarlyFragmentTests,
+ srcAccessMask = 0,
+ dstAccessMask = VkAccessFlags.ColorAttachmentWrite | VkAccessFlags.DepthStencilAttachmentWrite,
+ dependencyFlags = 0,
+ viewOffset = 0
+ };
+ dependencies[1] = new VkSubpassDependency
+ {
+ srcSubpass = 0,
+ dstSubpass = ~0u,
+ srcStageMask = VkPipelineStageFlags.ColorAttachmentOutput | VkPipelineStageFlags.EarlyFragmentTests,
+ dstStageMask = VkPipelineStageFlags.BottomOfPipe,
+ srcAccessMask = VkAccessFlags.ColorAttachmentWrite | VkAccessFlags.DepthStencilAttachmentWrite,
+ dstAccessMask = 0,
+ dependencyFlags = 0,
+ viewOffset = 0
+ };
+
+ fixed (VkAttachmentDescription* pAttachments = attachments)
+ fixed (VkSubpassDependency* pDeps = dependencies)
+ {
+ VkRenderPassCreateInfo createInfo;
+ createInfo.sType = VkStructureType.RenderPassCreateInfo;
+ createInfo.pNext = null;
+ createInfo.flags = 0;
+ createInfo.attachmentCount = 2;
+ createInfo.pAttachments = pAttachments;
+ createInfo.subpassCount = 1;
+ createInfo.pSubpasses = &subpass;
+ createInfo.dependencyCount = 2;
+ createInfo.pDependencies = pDeps;
+
+ VkRenderPass renderPass;
+ VkResult result = Vk.vkCreateRenderPass(_ctx.Device, &createInfo, null, &renderPass);
+ Vk.CheckResult(result, "vkCreateRenderPass");
+ RenderPass = renderPass;
+ }
+ }
+
+ private unsafe void CreateFramebuffers()
+ {
+ Framebuffers = new VkFramebuffer[SwapchainImageViews.Length];
+
+ for (uint i = 0; i < SwapchainImageViews.Length; i++)
+ {
+ var attachments = new VkImageView[] { SwapchainImageViews[i], DepthImageView };
+
+ fixed (VkImageView* pAttachments = attachments)
+ {
+ VkFramebufferCreateInfo createInfo;
+ createInfo.sType = VkStructureType.FramebufferCreateInfo;
+ createInfo.pNext = null;
+ createInfo.flags = 0;
+ createInfo.renderPass = RenderPass;
+ createInfo.attachmentCount = 2;
+ createInfo.pAttachments = pAttachments;
+ createInfo.width = (uint)Extent.width;
+ createInfo.height = (uint)Extent.height;
+ createInfo.layers = 1;
+
+ VkFramebuffer fb;
+ VkResult result = Vk.vkCreateFramebuffer(_ctx.Device, &createInfo, null, &fb);
+ Vk.CheckResult(result, "vkCreateFramebuffer");
+ Framebuffers[i] = fb;
+ }
+ }
+ }
+
+ public void Recreate(int width, int height)
+ {
+ Vk.vkQueueWaitIdle(_ctx.GraphicsQueue);
+
+ CleanupSwapchain();
+ Create(width, height);
+ }
+
+ private void CleanupSwapchain()
+ {
+ foreach (var fb in Framebuffers)
+ if (fb.Value != 0) Vk.vkDestroyFramebuffer(_ctx.Device, fb, null);
+
+ if (DepthImageView.Value != 0) Vk.vkDestroyImageView(_ctx.Device, DepthImageView, null);
+ if (DepthImage.Value != 0) Vk.vkDestroyImage(_ctx.Device, DepthImage, null);
+ if (DepthImageMemory.Value != 0) Vk.vkFreeMemory(_ctx.Device, DepthImageMemory, null);
+
+ foreach (var iv in SwapchainImageViews)
+ if (iv.Value != 0) Vk.vkDestroyImageView(_ctx.Device, iv, null);
+
+ if (Swapchain.Value != 0) Vk.vkDestroySwapchainKHR(_ctx.Device, Swapchain, null);
+ }
+
+ public void Dispose()
+ {
+ if (_disposed) return;
+ _disposed = true;
+
+ CleanupSwapchain();
+ if (RenderPass.Value != 0) Vk.vkDestroyRenderPass(_ctx.Device, RenderPass, null);
+ }
+}
+
+[StructLayout(LayoutKind.Sequential)]
+internal struct VkMemoryRequirements2
+{
+ public ulong size;
+ public ulong alignment;
+ public uint memoryTypeBits;
+ public uint _pad;
+}
diff --git a/src/Engine.Graphics.Vulkan/VulkanTypes.cs b/src/Engine.Graphics.Vulkan/VulkanTypes.cs
new file mode 100644
index 0000000..c16801c
--- /dev/null
+++ b/src/Engine.Graphics.Vulkan/VulkanTypes.cs
@@ -0,0 +1,1449 @@
+using System.Runtime.InteropServices;
+
+namespace Engine.Graphics.Vulkan;
+
+internal enum VkResult : int
+{
+ Success = 0,
+ NotReady = 1,
+ Timeout = 2,
+ EventSet = 3,
+ EventReset = 4,
+ Incomplete = 5,
+ ErrorOutOfHostMemory = -1,
+ ErrorOutOfDeviceMemory = -2,
+ ErrorInitializationFailed = -3,
+ ErrorDeviceLost = -4,
+ ErrorMemoryMapFailed = -5,
+ ErrorLayerNotPresent = -6,
+ ErrorExtensionNotPresent = -7,
+ ErrorFeatureNotPresent = -8,
+ ErrorIncompatibleDriver = -9,
+ ErrorTooManyObjects = -10,
+ ErrorFormatNotSupported = -11,
+ ErrorSurfaceLostKHR = -1000000000,
+ ErrorNativeWindowInUseKHR = -1000000001,
+ SuboptimalKHR = 1000001003,
+ ErrorOutOfDateKHR = -1000001004,
+ ErrorValidationFailedEXT = -1000011001,
+}
+
+internal enum VkStructureType : int
+{
+ ApplicationInfo = 0,
+ InstanceCreateInfo = 1,
+ DeviceQueueCreateInfo = 2,
+ DeviceCreateInfo = 3,
+ SubmitInfo = 4,
+ MemoryAllocateInfo = 5,
+ MappedMemoryRange = 6,
+ BindSparseInfo = 7,
+ FenceCreateInfo = 8,
+ SemaphoreCreateInfo = 9,
+ EventCreateInfo = 10,
+ QueryPoolCreateInfo = 11,
+ BufferCreateInfo = 12,
+ BufferViewCreateInfo = 13,
+ ImageCreateInfo = 14,
+ ImageViewCreateInfo = 15,
+ ShaderModuleCreateInfo = 16,
+ PipelineCacheCreateInfo = 17,
+ PipelineShaderStageCreateInfo = 18,
+ PipelineVertexInputStateCreateInfo = 19,
+ PipelineInputAssemblyStateCreateInfo = 20,
+ PipelineTessellationStateCreateInfo = 21,
+ PipelineViewportStateCreateInfo = 22,
+ PipelineRasterizationStateCreateInfo = 23,
+ PipelineMultisampleStateCreateInfo = 24,
+ PipelineDepthStencilStateCreateInfo = 25,
+ PipelineColorBlendStateCreateInfo = 26,
+ PipelineDynamicStateCreateInfo = 27,
+ GraphicsPipelineCreateInfo = 28,
+ ComputePipelineCreateInfo = 29,
+ PipelineLayoutCreateInfo = 30,
+ SamplerCreateInfo = 31,
+ DescriptorSetLayoutCreateInfo = 32,
+ DescriptorPoolCreateInfo = 33,
+ DescriptorSetAllocateInfo = 34,
+ WriteDescriptorSet = 35,
+ CopyDescriptorSet = 36,
+ FramebufferCreateInfo = 37,
+ RenderPassCreateInfo = 38,
+ CommandPoolCreateInfo = 39,
+ CommandBufferAllocateInfo = 40,
+ CommandBufferInheritanceInfo = 41,
+ CommandBufferBeginInfo = 42,
+ RenderPassBeginInfo = 43,
+ BufferMemoryBarrier = 44,
+ ImageMemoryBarrier = 45,
+ SwapchainCreateInfoKHR = 1000001000,
+ PresentInfoKHR = 1000001001,
+ SurfaceCapabilitiesKHR = 1000000000,
+ SurfaceFormatKHR = 1000000001,
+}
+
+internal enum VkFormat : int
+{
+ Undefined = 0,
+ R8G8B8A8Unorm = 37,
+ B8G8R8A8Unorm = 44,
+ R8G8B8A8Srgb = 43,
+ B8G8R8A8Srgb = 50,
+ R32G32B32A32Sfloat = 109,
+ R32G32B32Sfloat = 106,
+ R16G16B16A16Sfloat = 97,
+ R32G32Sfloat = 103,
+ D32Sfloat = 126,
+ D32SfloatS8Uint = 127,
+ D24UnormS8Uint = 129,
+ D16Unorm = 124,
+}
+
+internal enum VkImageUsageFlags : uint
+{
+ TransferSrc = 0x00000001,
+ TransferDst = 0x00000002,
+ Sampled = 0x00000004,
+ Storage = 0x00000008,
+ ColorAttachment = 0x00000010,
+ DepthStencilAttachment = 0x00000020,
+}
+
+internal enum VkImageAspectFlags : uint
+{
+ Color = 0x00000001,
+ Depth = 0x00000002,
+ Stencil = 0x00000004,
+}
+
+internal enum VkImageLayout : int
+{
+ Undefined = 0,
+ General = 1,
+ ColorAttachmentOptimal = 2,
+ DepthStencilAttachmentOptimal = 3,
+ DepthStencilReadOnlyOptimal = 4,
+ ShaderReadOnlyOptimal = 5,
+ TransferSrcOptimal = 6,
+ TransferDstOptimal = 7,
+ Preinitialized = 8,
+ PresentSrcKHR = 1000001002,
+}
+
+internal enum VkPipelineStageFlags : uint
+{
+ TopOfPipe = 0x00000001,
+ DrawIndirect = 0x00000002,
+ VertexInput = 0x00000004,
+ VertexShader = 0x00000008,
+ FragmentShader = 0x00000010,
+ EarlyFragmentTests = 0x00000040,
+ LateFragmentTests = 0x00000080,
+ ColorAttachmentOutput = 0x00000100,
+ Transfer = 0x00001000,
+ BottomOfPipe = 0x00002000,
+ Host = 0x00004000,
+ AllGraphics = 0x00008000,
+ AllCommands = 0x00010000,
+}
+
+internal enum VkAccessFlags : uint
+{
+ IndirectCommandRead = 0x00000001,
+ IndexRead = 0x00000002,
+ VertexAttributeRead = 0x00000004,
+ UniformRead = 0x00000008,
+ InputAttachmentRead = 0x00000010,
+ ShaderRead = 0x00000020,
+ ShaderWrite = 0x00000040,
+ ColorAttachmentRead = 0x00000080,
+ ColorAttachmentWrite = 0x00000100,
+ DepthStencilAttachmentRead = 0x00000200,
+ DepthStencilAttachmentWrite = 0x00000400,
+ TransferRead = 0x00000800,
+ TransferWrite = 0x00001000,
+ HostRead = 0x00002000,
+ HostWrite = 0x00004000,
+ MemoryRead = 0x00008000,
+ MemoryWrite = 0x00010000,
+}
+
+internal enum VkCommandBufferLevel : int
+{
+ Primary = 0,
+ Secondary = 1,
+}
+
+internal enum VkCommandBufferUsageFlags : uint
+{
+ OneTimeSubmit = 0x00000001,
+ RenderPassContinue = 0x00000002,
+ SimultaneousUse = 0x00000004,
+}
+
+internal enum VkIndexType : int
+{
+ Uint16 = 0,
+ Uint32 = 1,
+}
+
+internal enum VkPrimitiveTopology : int
+{
+ PointList = 0,
+ LineList = 1,
+ LineStrip = 2,
+ TriangleList = 3,
+ TriangleStrip = 4,
+ TriangleFan = 5,
+}
+
+internal enum VkPolygonMode : int
+{
+ Fill = 0,
+ Line = 1,
+ Point = 2,
+}
+
+internal enum VkCullModeFlags : uint
+{
+ None = 0,
+ Front = 0x00000001,
+ Back = 0x00000002,
+ FrontAndBack = 0x00000003,
+}
+
+internal enum VkFrontFace : int
+{
+ CounterClockwise = 0,
+ Clockwise = 1,
+}
+
+internal enum VkBlendFactor : int
+{
+ Zero = 0,
+ One = 1,
+ SrcColor = 2,
+ OneMinusSrcColor = 3,
+ DstColor = 4,
+ OneMinusDstColor = 5,
+ SrcAlpha = 6,
+ OneMinusSrcAlpha = 7,
+ DstAlpha = 8,
+ OneMinusDstAlpha = 9,
+}
+
+internal enum VkBlendOp : int
+{
+ Add = 0,
+ Subtract = 1,
+ ReverseSubtract = 2,
+}
+
+internal enum VkColorComponentFlags : uint
+{
+ R = 0x00000010,
+ G = 0x00000020,
+ B = 0x00000040,
+ A = 0x00000080,
+}
+
+internal enum VkCompareOp : int
+{
+ Never = 0,
+ Less = 1,
+ Equal = 2,
+ LessOrEqual = 3,
+ Greater = 4,
+ NotEqual = 5,
+ GreaterOrEqual = 6,
+ Always = 7,
+}
+
+internal enum VkDescriptorType : int
+{
+ Sampler = 0,
+ CombinedImageSampler = 1,
+ SampledImage = 2,
+ StorageImage = 3,
+ UniformTexelBuffer = 4,
+ StorageTexelBuffer = 5,
+ UniformBuffer = 6,
+ StorageBuffer = 7,
+ UniformBufferDynamic = 8,
+ StorageBufferDynamic = 9,
+}
+
+internal enum VkShaderStageFlags : uint
+{
+ Vertex = 0x00000001,
+ Fragment = 0x00000010,
+ AllGraphics = 0x0000001F,
+}
+
+internal enum VkBufferUsageFlags : uint
+{
+ TransferSrc = 0x00000001,
+ TransferDst = 0x00000002,
+ UniformTexelBuffer = 0x00000004,
+ StorageTexelBuffer = 0x00000008,
+ UniformBuffer = 0x00000010,
+ StorageBuffer = 0x00000020,
+ IndexBuffer = 0x00000040,
+ VertexBuffer = 0x00000080,
+ IndirectBuffer = 0x00000100,
+}
+
+internal enum VkMemoryPropertyFlags : uint
+{
+ DeviceLocal = 0x00000001,
+ HostVisible = 0x00000002,
+ HostCoherent = 0x00000004,
+ HostCached = 0x00000008,
+ LazilyAllocated = 0x00000010,
+}
+
+internal enum VkQueueFlags : uint
+{
+ Graphics = 0x00000001,
+ Compute = 0x00000002,
+ Transfer = 0x00000004,
+ SparseBinding = 0x00000008,
+}
+
+internal enum VkPhysicalDeviceType : int
+{
+ Other = 0,
+ IntegratedGpu = 1,
+ DiscreteGpu = 2,
+ VirtualGpu = 3,
+ Cpu = 4,
+}
+
+internal enum VkPresentModeKHR : int
+{
+ Immediate = 0,
+ Fifo = 1,
+ FifoRelaxed = 2,
+ Mailbox = 3,
+}
+
+internal enum VkColorSpaceKHR : int
+{
+ SrgbNonlinear = 0,
+}
+
+internal enum VkSurfaceTransformFlagsKHR : uint
+{
+ Identity = 0x00000001,
+}
+
+internal enum VkAttachmentLoadOp : int
+{
+ Load = 0,
+ Clear = 1,
+ DontCare = 2,
+}
+
+internal enum VkAttachmentStoreOp : int
+{
+ Store = 0,
+ DontCare = 1,
+}
+
+internal enum VkSampleCountFlags : uint
+{
+ One = 0x00000001,
+}
+
+internal enum VkSharingMode : int
+{
+ Exclusive = 0,
+ Concurrent = 1,
+}
+
+internal enum VkFenceCreateFlags : uint
+{
+ Signaled = 0x00000001,
+}
+
+internal enum VkDescriptorPoolCreateFlags : uint
+{
+ FreeDescriptorSet = 0x00000001,
+}
+
+internal enum VkSubpassContents : int
+{
+ Inline = 0,
+ SecondaryCommandBuffers = 1,
+}
+
+internal enum VkImageViewType : int
+{
+ _1D = 0,
+ _2D = 1,
+ _3D = 2,
+ Cube = 3,
+ _1DArray = 4,
+ _2DArray = 5,
+ CubeArray = 6,
+}
+
+internal enum VkImageType : int
+{
+ _1D = 0,
+ _2D = 1,
+ _3D = 2,
+}
+
+internal enum VkSamplerAddressMode : int
+{
+ Repeat = 0,
+ MirroredRepeat = 1,
+ ClampToEdge = 2,
+ ClampToBorder = 3,
+}
+
+internal enum VkFilter : int
+{
+ Nearest = 0,
+ Linear = 1,
+}
+
+internal enum VkSamplerMipmapMode : int
+{
+ Nearest = 0,
+ Linear = 1,
+}
+
+internal enum VkBorderColor : int
+{
+ FloatTransparentBlack = 4,
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkHandle { public ulong Value; }
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkInstance { public ulong Value; public static implicit operator ulong(VkInstance h) => h.Value; }
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkPhysicalDevice { public ulong Value; public static implicit operator ulong(VkPhysicalDevice h) => h.Value; }
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkDevice { public ulong Value; public static implicit operator ulong(VkDevice h) => h.Value; }
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkQueue { public ulong Value; public static implicit operator ulong(VkQueue h) => h.Value; }
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkCommandBuffer { public ulong Value; public static implicit operator ulong(VkCommandBuffer h) => h.Value; }
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkCommandPool { public ulong Value; public static implicit operator ulong(VkCommandPool h) => h.Value; }
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkBuffer { public ulong Value; public static implicit operator ulong(VkBuffer h) => h.Value; }
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkDeviceMemory { public ulong Value; public static implicit operator ulong(VkDeviceMemory h) => h.Value; }
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkImage { public ulong Value; public static implicit operator ulong(VkImage h) => h.Value; }
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkImageView { public ulong Value; public static implicit operator ulong(VkImageView h) => h.Value; }
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkShaderModule { public ulong Value; public static implicit operator ulong(VkShaderModule h) => h.Value; }
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkPipelineLayout { public ulong Value; public static implicit operator ulong(VkPipelineLayout h) => h.Value; }
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkPipeline { public ulong Value; public static implicit operator ulong(VkPipeline h) => h.Value; }
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkRenderPass { public ulong Value; public static implicit operator ulong(VkRenderPass h) => h.Value; }
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkFramebuffer { public ulong Value; public static implicit operator ulong(VkFramebuffer h) => h.Value; }
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkDescriptorSetLayout { public ulong Value; public static implicit operator ulong(VkDescriptorSetLayout h) => h.Value; }
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkDescriptorPool { public ulong Value; public static implicit operator ulong(VkDescriptorPool h) => h.Value; }
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkDescriptorSet { public ulong Value; public static implicit operator ulong(VkDescriptorSet h) => h.Value; }
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkSemaphore { public ulong Value; public static implicit operator ulong(VkSemaphore h) => h.Value; }
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkFence { public ulong Value; public static implicit operator ulong(VkFence h) => h.Value; }
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkSwapchainKHR { public ulong Value; public static implicit operator ulong(VkSwapchainKHR h) => h.Value; }
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkSurfaceKHR { public ulong Value; public static implicit operator ulong(VkSurfaceKHR h) => h.Value; }
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkApplicationInfo
+{
+ public VkStructureType sType;
+ public void* pNext;
+ public byte* pApplicationName;
+ public uint applicationVersion;
+ public byte* pEngineName;
+ public uint engineVersion;
+ public uint apiVersion;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkInstanceCreateInfo
+{
+ public VkStructureType sType;
+ public void* pNext;
+ public uint flags;
+ public VkApplicationInfo* pApplicationInfo;
+ public uint enabledLayerCount;
+ public byte** ppEnabledLayerNames;
+ public uint enabledExtensionCount;
+ public byte** ppEnabledExtensionNames;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkDeviceQueueCreateInfo
+{
+ public VkStructureType sType;
+ public void* pNext;
+ public uint flags;
+ public uint queueFamilyIndex;
+ public uint queueCount;
+ public float* pQueuePriorities;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkDeviceCreateInfo
+{
+ public VkStructureType sType;
+ public void* pNext;
+ public uint flags;
+ public uint queueCreateInfoCount;
+ public VkDeviceQueueCreateInfo* pQueueCreateInfos;
+ public uint enabledLayerCount;
+ public byte** ppEnabledLayerNames;
+ public uint enabledExtensionCount;
+ public byte** ppEnabledExtensionNames;
+ public void* pEnabledFeatures;
+}
+
+[StructLayout(LayoutKind.Sequential, Size = 228)]
+internal struct VkPhysicalDeviceFeatures
+{
+}
+
+[StructLayout(LayoutKind.Sequential)]
+internal unsafe struct VkPhysicalDeviceProperties
+{
+ public uint apiVersion;
+ public uint driverVersion;
+ public uint vendorID;
+ public uint deviceID;
+ public VkPhysicalDeviceType deviceType;
+ public fixed byte deviceName[256];
+ public fixed uint pipelineCacheUUID[16];
+ public VkPhysicalDeviceLimits limits;
+ public VkPhysicalDeviceSparseProperties sparseProperties;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+internal unsafe struct VkPhysicalDeviceLimits
+{
+ public uint maxImageDimension1D;
+ public uint maxImageDimension2D;
+ public uint maxImageDimension3D;
+ public uint maxImageDimensionCube;
+ public uint maxImageArrayLayers;
+ public uint maxTexelBufferElements;
+ public uint maxUniformBufferRange;
+ public uint maxStorageBufferRange;
+ public uint maxPushConstantsSize;
+ public uint maxMemoryAllocationCount;
+ public uint maxSamplerAllocationCount;
+ public ulong bufferImageGranularity;
+ public ulong sparseAddressSpaceSize;
+ public uint maxBoundDescriptorSets;
+ public uint maxPerStageDescriptorSamplers;
+ public uint maxPerStageDescriptorUniformBuffers;
+ public uint maxPerStageDescriptorStorageBuffers;
+ public uint maxPerStageDescriptorSampledImages;
+ public uint maxPerStageDescriptorStorageImages;
+ public uint maxPerStageDescriptorInputAttachments;
+ public uint maxPerStageResources;
+ public uint maxDescriptorSetSamplers;
+ public uint maxDescriptorSetUniformBuffers;
+ public uint maxDescriptorSetUniformBuffersDynamic;
+ public uint maxDescriptorSetStorageBuffers;
+ public uint maxDescriptorSetStorageBuffersDynamic;
+ public uint maxDescriptorSetSampledImages;
+ public uint maxDescriptorSetStorageImages;
+ public uint maxDescriptorSetInputAttachments;
+ public uint maxVertexInputAttributes;
+ public uint maxVertexInputBindings;
+ public uint maxVertexInputAttributeOffset;
+ public uint maxVertexInputBindingStride;
+ public uint maxVertexOutputComponents;
+ public uint maxTessellationGenerationLevel;
+ public uint maxTessellationPatchSize;
+ public uint maxTessellationControlPerVertexInputComponents;
+ public uint maxTessellationControlPerVertexOutputComponents;
+ public uint maxTessellationControlPerPatchOutputComponents;
+ public uint maxTessellationControlTotalOutputComponents;
+ public uint maxTessellationEvaluationInputComponents;
+ public uint maxTessellationEvaluationOutputComponents;
+ public uint maxGeometryShaderInvocations;
+ public uint maxGeometryInputComponents;
+ public uint maxGeometryOutputComponents;
+ public uint maxGeometryOutputVertices;
+ public uint maxGeometryTotalOutputComponents;
+ public uint maxFragmentInputComponents;
+ public uint maxFragmentOutputAttachments;
+ public uint maxFragmentDualSrcAttachments;
+ public uint maxFragmentCombinedOutputResources;
+ public uint maxComputeSharedMemorySize;
+ public fixed uint maxComputeWorkGroupCount[3];
+ public uint maxComputeWorkGroupInvocations;
+ public fixed uint maxComputeWorkGroupSize[3];
+ public float subPixelPrecisionBits;
+ public float subTexelPrecisionBits;
+ public float mipmapPrecisionBits;
+ public uint maxDrawIndexedIndexValue;
+ public uint maxDrawIndirectCount;
+ public float maxSamplerLodBias;
+ public float maxSamplerAnisotropy;
+ public uint maxViewports;
+ public fixed uint maxViewportDimensions[2];
+ public fixed float viewportBoundsRange[2];
+ public uint viewportSubPixelBits;
+ public uint minMemoryMapAlignment;
+ public uint minTexelBufferOffsetAlignment;
+ public uint minUniformBufferOffsetAlignment;
+ public uint minStorageBufferOffsetAlignment;
+ public int minTexelOffset;
+ public uint maxTexelOffset;
+ public int minTexelGatherOffset;
+ public uint maxTexelGatherOffset;
+ public float minInterpolationOffset;
+ public float maxInterpolationOffset;
+ public uint subPixelInterpolationOffsetBits;
+ public uint maxFramebufferWidth;
+ public uint maxFramebufferHeight;
+ public uint maxFramebufferLayers;
+ public uint framebufferColorSampleCounts;
+ public uint framebufferDepthSampleCounts;
+ public uint framebufferStencilSampleCounts;
+ public uint framebufferNoAttachmentsSampleCounts;
+ public uint maxColorAttachments;
+ public uint sampledImageColorSampleCounts;
+ public uint sampledImageIntegerSampleCounts;
+ public uint sampledImageDepthSampleCounts;
+ public uint sampledImageStencilSampleCounts;
+ public uint storageImageSampleCounts;
+ public uint maxSampleMaskWords;
+ public uint timestampComputeAndGraphics;
+ public float timestampPeriod;
+ public uint maxClipDistances;
+ public uint maxCullDistances;
+ public uint maxCombinedClipAndCullDistances;
+ public uint discreteQueuePriorities;
+ public fixed float pointSizeRange[2];
+ public fixed float lineWidthRange[2];
+ public float pointSizeGranularity;
+ public float lineWidthGranularity;
+ public uint strictLines;
+ public uint standardSampleLocations;
+ public uint optimalBufferCopyOffsetAlignment;
+ public uint optimalBufferCopyRowPitchAlignment;
+ public uint nonCoherentAtomSize;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkPhysicalDeviceSparseProperties
+{
+ public uint residencyStandard2DBlockShape;
+ public uint residencyStandard2DMultisampleBlockShape;
+ public uint residencyStandard3DBlockShape;
+ public uint residencyAlignedMipSize;
+ public uint residencyNonResidentStrict;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkQueueFamilyProperties
+{
+ public VkQueueFlags queueFlags;
+ public uint queueCount;
+ public uint timestampValidBits;
+ public VkExtent3D minImageTransferGranularity;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkExtent3D
+{
+ public int width;
+ public int height;
+ public int depth;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkExtent2D
+{
+ public int width;
+ public int height;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+internal unsafe struct VkPhysicalDeviceMemoryProperties
+{
+ public uint memoryTypeCount;
+ public VkMemoryType memoryTypes0;
+ public VkMemoryType memoryTypes1;
+ public VkMemoryType memoryTypes2;
+ public VkMemoryType memoryTypes3;
+ public VkMemoryType memoryTypes4;
+ public VkMemoryType memoryTypes5;
+ public VkMemoryType memoryTypes6;
+ public VkMemoryType memoryTypes7;
+ public VkMemoryType memoryTypes8;
+ public VkMemoryType memoryTypes9;
+ public VkMemoryType memoryTypes10;
+ public VkMemoryType memoryTypes11;
+ public VkMemoryType memoryTypes12;
+ public VkMemoryType memoryTypes13;
+ public VkMemoryType memoryTypes14;
+ public VkMemoryType memoryTypes15;
+ public uint memoryHeapCount;
+ public VkMemoryHeap memoryHeaps0;
+ public VkMemoryHeap memoryHeaps1;
+ public VkMemoryHeap memoryHeaps2;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkMemoryType
+{
+ public VkMemoryPropertyFlags propertyFlags;
+ public uint heapIndex;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkMemoryHeap
+{
+ public ulong size;
+ public uint flags;
+ public uint _pad;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkSurfaceCapabilitiesKHR
+{
+ public uint minImageCount;
+ public uint maxImageCount;
+ public VkExtent2D currentExtent;
+ public VkExtent2D minImageExtent;
+ public VkExtent2D maxImageExtent;
+ public uint maxImageArrayLayers;
+ public VkSurfaceTransformFlagsKHR currentTransform;
+ public uint supportedTransforms;
+ public uint supportedCompositeAlpha;
+ public uint supportedUsageFlags;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkSurfaceFormatKHR
+{
+ public VkFormat format;
+ public VkColorSpaceKHR colorSpace;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkSwapchainCreateInfoKHR
+{
+ public VkStructureType sType;
+ public void* pNext;
+ public uint flags;
+ public VkSurfaceKHR surface;
+ public uint minImageCount;
+ public VkFormat imageFormat;
+ public VkColorSpaceKHR imageColorSpace;
+ public VkExtent2D imageExtent;
+ public uint imageArrayLayers;
+ public VkImageUsageFlags imageUsage;
+ public VkSharingMode imageSharingMode;
+ public uint queueFamilyIndexCount;
+ public uint* pQueueFamilyIndices;
+ public VkSurfaceTransformFlagsKHR preTransform;
+ public uint compositeAlpha;
+ public VkPresentModeKHR presentMode;
+ public uint clipped;
+ public VkSwapchainKHR oldSwapchain;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkPresentInfoKHR
+{
+ public VkStructureType sType;
+ public void* pNext;
+ public uint waitSemaphoreCount;
+ public VkSemaphore* pWaitSemaphores;
+ public uint swapchainCount;
+ public VkSwapchainKHR* pSwapchains;
+ public uint* pImageIndices;
+ public VkResult* pResults;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkSubmitInfo
+{
+ public VkStructureType sType;
+ public void* pNext;
+ public uint waitSemaphoreCount;
+ public VkSemaphore* pWaitSemaphores;
+ public ulong* pWaitDstStageMask;
+ public uint commandBufferCount;
+ public VkCommandBuffer* pCommandBuffers;
+ public uint signalSemaphoreCount;
+ public VkSemaphore* pSignalSemaphores;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkImageCreateInfo
+{
+ public VkStructureType sType;
+ public void* pNext;
+ public uint flags;
+ public VkImageType imageType;
+ public VkFormat format;
+ public VkExtent3D extent;
+ public uint mipLevels;
+ public uint arrayLayers;
+ public VkSampleCountFlags samples;
+ public uint tiling;
+ public VkImageUsageFlags usage;
+ public VkSharingMode sharingMode;
+ public uint queueFamilyIndexCount;
+ public uint* pQueueFamilyIndices;
+ public int initialLayout;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkImageViewCreateInfo
+{
+ public VkStructureType sType;
+ public void* pNext;
+ public uint flags;
+ public VkImage image;
+ public VkImageViewType viewType;
+ public VkFormat format;
+ public VkComponentMapping components;
+ public VkImageSubresourceRange subresourceRange;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkComponentMapping
+{
+ public int r;
+ public int g;
+ public int b;
+ public int a;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkImageSubresourceRange
+{
+ public VkImageAspectFlags aspectMask;
+ public uint baseMipLevel;
+ public uint levelCount;
+ public uint baseArrayLayer;
+ public uint layerCount;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkMemoryAllocateInfo
+{
+ public VkStructureType sType;
+ public void* pNext;
+ public ulong allocationSize;
+ public uint memoryTypeIndex;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkBufferCreateInfo
+{
+ public VkStructureType sType;
+ public void* pNext;
+ public uint flags;
+ public ulong size;
+ public VkBufferUsageFlags usage;
+ public VkSharingMode sharingMode;
+ public uint queueFamilyIndexCount;
+ public uint* pQueueFamilyIndices;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkShaderModuleCreateInfo
+{
+ public VkStructureType sType;
+ public void* pNext;
+ public uint flags;
+ public ulong codeSize;
+ public uint* pCode;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkPipelineShaderStageCreateInfo
+{
+ public VkStructureType sType;
+ public void* pNext;
+ public uint flags;
+ public VkShaderStageFlags stage;
+ public VkShaderModule module;
+ public byte* pName;
+ public void* pSpecializationInfo;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkPipelineLayoutCreateInfo
+{
+ public VkStructureType sType;
+ public void* pNext;
+ public uint flags;
+ public uint setLayoutCount;
+ public VkDescriptorSetLayout* pSetLayouts;
+ public uint pushConstantRangeCount;
+ public VkPushConstantRange* pPushConstantRanges;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkPushConstantRange
+{
+ public VkShaderStageFlags stageFlags;
+ public uint offset;
+ public uint size;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkDescriptorSetLayoutCreateInfo
+{
+ public VkStructureType sType;
+ public void* pNext;
+ public uint flags;
+ public uint bindingCount;
+ public VkDescriptorSetLayoutBinding* pBindings;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkDescriptorSetLayoutBinding
+{
+ public uint binding;
+ public VkDescriptorType descriptorType;
+ public uint descriptorCount;
+ public VkShaderStageFlags stageFlags;
+ public void* pImmutableSamplers;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkDescriptorPoolCreateInfo
+{
+ public VkStructureType sType;
+ public void* pNext;
+ public VkDescriptorPoolCreateFlags flags;
+ public uint maxSets;
+ public uint poolSizeCount;
+ public VkDescriptorPoolSize* pPoolSizes;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkDescriptorPoolSize
+{
+ public VkDescriptorType type;
+ public uint descriptorCount;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkDescriptorSetAllocateInfo
+{
+ public VkStructureType sType;
+ public void* pNext;
+ public VkDescriptorPool descriptorPool;
+ public uint descriptorSetCount;
+ public VkDescriptorSetLayout* pSetLayouts;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkWriteDescriptorSet
+{
+ public VkStructureType sType;
+ public void* pNext;
+ public VkDescriptorSet dstSet;
+ public uint dstBinding;
+ public uint dstArrayElement;
+ public uint descriptorCount;
+ public VkDescriptorType descriptorType;
+ public void* pImageInfo;
+ public void* pBufferInfo;
+ public void* pTexelBufferView;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkDescriptorBufferInfo
+{
+ public VkBuffer buffer;
+ public ulong offset;
+ public ulong range;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkVertexInputBindingDescription
+{
+ public uint binding;
+ public uint stride;
+ public int inputRate;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkVertexInputAttributeDescription
+{
+ public uint location;
+ public uint binding;
+ public VkFormat format;
+ public uint offset;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkPipelineVertexInputStateCreateInfo
+{
+ public VkStructureType sType;
+ public void* pNext;
+ public uint flags;
+ public uint vertexBindingDescriptionCount;
+ public VkVertexInputBindingDescription* pVertexBindingDescriptions;
+ public uint vertexAttributeDescriptionCount;
+ public VkVertexInputAttributeDescription* pVertexAttributeDescriptions;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkPipelineInputAssemblyStateCreateInfo
+{
+ public VkStructureType sType;
+ public void* pNext;
+ public uint flags;
+ public VkPrimitiveTopology topology;
+ public uint primitiveRestartEnable;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkViewport
+{
+ public float x;
+ public float y;
+ public float width;
+ public float height;
+ public float minDepth;
+ public float maxDepth;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkRect2D
+{
+ public VkOffset2D offset;
+ public VkExtent2D extent;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkOffset2D
+{
+ public int x;
+ public int y;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkPipelineViewportStateCreateInfo
+{
+ public VkStructureType sType;
+ public void* pNext;
+ public uint flags;
+ public uint viewportCount;
+ public VkViewport* pViewports;
+ public uint scissorCount;
+ public VkRect2D* pScissors;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkPipelineRasterizationStateCreateInfo
+{
+ public VkStructureType sType;
+ public void* pNext;
+ public uint flags;
+ public uint depthClampEnable;
+ public uint rasterizerDiscardEnable;
+ public VkPolygonMode polygonMode;
+ public VkCullModeFlags cullMode;
+ public VkFrontFace frontFace;
+ public uint depthBiasEnable;
+ public float depthBiasConstantFactor;
+ public float depthBiasClamp;
+ public float depthBiasSlopeFactor;
+ public float lineWidth;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkPipelineMultisampleStateCreateInfo
+{
+ public VkStructureType sType;
+ public void* pNext;
+ public uint flags;
+ public VkSampleCountFlags rasterizationSamples;
+ public uint sampleShadingEnable;
+ public float minSampleShading;
+ public void* pSampleMask;
+ public uint alphaToCoverageEnable;
+ public uint alphaToOneEnable;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkPipelineDepthStencilStateCreateInfo
+{
+ public VkStructureType sType;
+ public void* pNext;
+ public uint flags;
+ public uint depthTestEnable;
+ public uint depthWriteEnable;
+ public VkCompareOp depthCompareOp;
+ public uint depthBoundsTestEnable;
+ public uint stencilTestEnable;
+ public VkStencilOpState front;
+ public VkStencilOpState back;
+ public float minDepthBounds;
+ public float maxDepthBounds;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+internal struct VkStencilOpState
+{
+ public int failOp;
+ public int passOp;
+ public int depthFailOp;
+ public VkCompareOp compareOp;
+ public uint compareMask;
+ public uint writeMask;
+ public uint reference;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkPipelineColorBlendAttachmentState
+{
+ public uint blendEnable;
+ public VkBlendFactor srcColorBlendFactor;
+ public VkBlendFactor dstColorBlendFactor;
+ public VkBlendOp colorBlendOp;
+ public VkBlendFactor srcAlphaBlendFactor;
+ public VkBlendFactor dstAlphaBlendFactor;
+ public VkBlendOp alphaBlendOp;
+ public VkColorComponentFlags colorWriteMask;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkPipelineColorBlendStateCreateInfo
+{
+ public VkStructureType sType;
+ public void* pNext;
+ public uint flags;
+ public uint logicOpEnable;
+ public int logicOp;
+ public uint attachmentCount;
+ public VkPipelineColorBlendAttachmentState* pAttachments;
+ public fixed float blendConstants[4];
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkGraphicsPipelineCreateInfo
+{
+ public VkStructureType sType;
+ public void* pNext;
+ public uint flags;
+ public uint stageCount;
+ public VkPipelineShaderStageCreateInfo* pStages;
+ public VkPipelineVertexInputStateCreateInfo* pVertexInputState;
+ public VkPipelineInputAssemblyStateCreateInfo* pInputAssemblyState;
+ public void* pTessellationState;
+ public VkPipelineViewportStateCreateInfo* pViewportState;
+ public VkPipelineRasterizationStateCreateInfo* pRasterizationState;
+ public VkPipelineMultisampleStateCreateInfo* pMultisampleState;
+ public VkPipelineDepthStencilStateCreateInfo* pDepthStencilState;
+ public VkPipelineColorBlendStateCreateInfo* pColorBlendState;
+ public void* pDynamicState;
+ public VkPipelineLayout layout;
+ public VkRenderPass renderPass;
+ public uint subpass;
+ public VkPipeline basePipelineHandle;
+ public int basePipelineIndex;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkRenderPassCreateInfo
+{
+ public VkStructureType sType;
+ public void* pNext;
+ public uint flags;
+ public uint attachmentCount;
+ public VkAttachmentDescription* pAttachments;
+ public uint subpassCount;
+ public VkSubpassDescription* pSubpasses;
+ public uint dependencyCount;
+ public VkSubpassDependency* pDependencies;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkAttachmentDescription
+{
+ public uint flags;
+ public VkFormat format;
+ public uint samples;
+ public VkAttachmentLoadOp loadOp;
+ public VkAttachmentStoreOp storeOp;
+ public VkAttachmentLoadOp stencilLoadOp;
+ public VkAttachmentStoreOp stencilStoreOp;
+ public VkImageLayout initialLayout;
+ public VkImageLayout finalLayout;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkAttachmentReference
+{
+ public uint attachment;
+ public VkImageLayout layout;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkSubpassDescription
+{
+ public uint flags;
+ public int pipelineBindPoint;
+ public uint inputAttachmentCount;
+ public VkAttachmentReference* pInputAttachments;
+ public uint colorAttachmentCount;
+ public VkAttachmentReference* pColorAttachments;
+ public VkAttachmentReference* pResolveAttachments;
+ public VkAttachmentReference* pDepthStencilAttachment;
+ public uint preserveAttachmentCount;
+ public uint* pPreserveAttachments;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkSubpassDependency
+{
+ public uint srcSubpass;
+ public uint dstSubpass;
+ public VkPipelineStageFlags srcStageMask;
+ public VkPipelineStageFlags dstStageMask;
+ public VkAccessFlags srcAccessMask;
+ public VkAccessFlags dstAccessMask;
+ public int dependencyFlags;
+ public int viewOffset;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkFramebufferCreateInfo
+{
+ public VkStructureType sType;
+ public void* pNext;
+ public uint flags;
+ public VkRenderPass renderPass;
+ public uint attachmentCount;
+ public VkImageView* pAttachments;
+ public uint width;
+ public uint height;
+ public uint layers;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkCommandPoolCreateInfo
+{
+ public VkStructureType sType;
+ public void* pNext;
+ public uint flags;
+ public uint queueFamilyIndex;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkCommandBufferAllocateInfo
+{
+ public VkStructureType sType;
+ public void* pNext;
+ public VkCommandPool commandPool;
+ public VkCommandBufferLevel level;
+ public uint commandBufferCount;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkCommandBufferBeginInfo
+{
+ public VkStructureType sType;
+ public void* pNext;
+ public VkCommandBufferUsageFlags flags;
+ public void* pInheritanceInfo;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkRenderPassBeginInfo
+{
+ public VkStructureType sType;
+ public void* pNext;
+ public VkRenderPass renderPass;
+ public VkFramebuffer framebuffer;
+ public VkRect2D renderArea;
+ public uint clearValueCount;
+ public VkClearValue* pClearValues;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkClearValue
+{
+ public VkClearColorValue color;
+ public VkClearDepthStencilValue depthStencil;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkClearColorValue
+{
+ public float r, g, b, a;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkClearDepthStencilValue
+{
+ public float depth;
+ public uint stencil;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkBufferCopy
+{
+ public ulong srcOffset;
+ public ulong dstOffset;
+ public ulong size;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkBufferImageCopy
+{
+ public ulong bufferOffset;
+ public uint bufferRowLength;
+ public uint bufferImageHeight;
+ public VkImageSubresourceLayers imageSubresource;
+ public VkOffset3D imageOffset;
+ public VkExtent3D imageExtent;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkImageSubresourceLayers
+{
+ public VkImageAspectFlags aspectMask;
+ public uint mipLevel;
+ public uint baseArrayLayer;
+ public uint layerCount;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkOffset3D
+{
+ public int x;
+ public int y;
+ public int z;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkImageMemoryBarrier
+{
+ public VkStructureType sType;
+ public void* pNext;
+ public VkAccessFlags srcAccessMask;
+ public VkAccessFlags dstAccessMask;
+ public VkImageLayout oldLayout;
+ public VkImageLayout newLayout;
+ public uint srcQueueFamilyIndex;
+ public uint dstQueueFamilyIndex;
+ public VkImage image;
+ public VkImageSubresourceRange subresourceRange;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkBufferMemoryBarrier
+{
+ public VkStructureType sType;
+ public void* pNext;
+ public VkAccessFlags srcAccessMask;
+ public VkAccessFlags dstAccessMask;
+ public uint srcQueueFamilyIndex;
+ public uint dstQueueFamilyIndex;
+ public VkBuffer buffer;
+ public ulong offset;
+ public ulong size;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkSemaphoreCreateInfo
+{
+ public VkStructureType sType;
+ public void* pNext;
+ public uint flags;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkFenceCreateInfo
+{
+ public VkStructureType sType;
+ public void* pNext;
+ public VkFenceCreateFlags flags;
+}
+
+[StructLayout(LayoutKind.Sequential, Size = 260)]
+internal unsafe struct VkExtensionProperties
+{
+ public fixed byte extensionName[256];
+ public uint specVersion;
+}
+
+[StructLayout(LayoutKind.Sequential, Size = 264)]
+internal unsafe struct VkLayerProperties
+{
+ public fixed byte layerName[256];
+ public uint specVersion;
+ public uint implementationVersion;
+ public uint _pad;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+unsafe internal struct VkSamplerCreateInfo
+{
+ public VkStructureType sType;
+ public void* pNext;
+ public uint flags;
+ public VkFilter magFilter;
+ public VkFilter minFilter;
+ public VkSamplerMipmapMode mipmapMode;
+ public VkSamplerAddressMode addressModeU;
+ public VkSamplerAddressMode addressModeV;
+ public VkSamplerAddressMode addressModeW;
+ public float mipLodBias;
+ public uint anisotropyEnable;
+ public float maxAnisotropy;
+ public uint compareEnable;
+ public VkCompareOp compareOp;
+ public float minLod;
+ public float maxLod;
+ public VkBorderColor borderColor;
+ public uint unnormalizedCoordinates;
+}
diff --git a/src/Engine.Graphics/Engine.Graphics.csproj b/src/Engine.Graphics/Engine.Graphics.csproj
new file mode 100644
index 0000000..70c3869
--- /dev/null
+++ b/src/Engine.Graphics/Engine.Graphics.csproj
@@ -0,0 +1,33 @@
+
+
+
+ net9.0
+ enable
+ enable
+ true
+ true
+
+
+
+ DEV_MODE
+
+
+
+ RELEASE_AOT
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Engine.Graphics/IRenderContext.cs b/src/Engine.Graphics/IRenderContext.cs
new file mode 100644
index 0000000..cce7e0b
--- /dev/null
+++ b/src/Engine.Graphics/IRenderContext.cs
@@ -0,0 +1,10 @@
+using Engine.Core;
+
+namespace Engine.Graphics;
+
+public interface IRenderContext : IDisposable
+{
+ IWindow Window { get; }
+ IRenderer CreateRenderer();
+ void Resize(int width, int height);
+}
diff --git a/src/Engine.Graphics/IRenderer.cs b/src/Engine.Graphics/IRenderer.cs
new file mode 100644
index 0000000..28edfea
--- /dev/null
+++ b/src/Engine.Graphics/IRenderer.cs
@@ -0,0 +1,12 @@
+using Engine.Core;
+using Flecs.NET.Core;
+
+namespace Engine.Graphics;
+
+public interface IRenderer : IDisposable
+{
+ void RenderWorld(World world);
+ void RequestScreenshot(string outputPath);
+ bool IsScreenshotRequested { get; }
+ IScreenshotProvider ScreenshotProvider { get; }
+}
diff --git a/src/Engine.Graphics/Loaders/GltfLoader.cs b/src/Engine.Graphics/Loaders/GltfLoader.cs
new file mode 100644
index 0000000..e7fa688
--- /dev/null
+++ b/src/Engine.Graphics/Loaders/GltfLoader.cs
@@ -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();
+ var indices = new List();
+
+ 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());
+ }
+}
diff --git a/src/Engine.Graphics/Loaders/ObjLoader.cs b/src/Engine.Graphics/Loaders/ObjLoader.cs
new file mode 100644
index 0000000..3f8dd6a
--- /dev/null
+++ b/src/Engine.Graphics/Loaders/ObjLoader.cs
@@ -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();
+ var normals = new List();
+
+ var vertices = new List();
+ var indices = new List();
+
+ 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 positions,
+ List normals,
+ List vertices,
+ List 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);
+ }
+ }
+}
diff --git a/src/Engine.Graphics/MeshMath.cs b/src/Engine.Graphics/MeshMath.cs
new file mode 100644
index 0000000..36868bd
--- /dev/null
+++ b/src/Engine.Graphics/MeshMath.cs
@@ -0,0 +1,18 @@
+using System.Numerics;
+
+namespace Engine.Graphics;
+
+public static class MeshMath
+{
+ public static Vector3 ComputeFaceNormal(Vector3 a, Vector3 b, Vector3 c)
+ {
+ var edge1 = b - a;
+ var edge2 = c - a;
+ var normal = Vector3.Cross(edge2, edge1);
+
+ if (normal.LengthSquared() < 1e-12f)
+ return Vector3.UnitY;
+
+ return Vector3.Normalize(normal);
+ }
+}
diff --git a/src/Engine.Graphics/ProceduralMesh.cs b/src/Engine.Graphics/ProceduralMesh.cs
new file mode 100644
index 0000000..cd842e2
--- /dev/null
+++ b/src/Engine.Graphics/ProceduralMesh.cs
@@ -0,0 +1,90 @@
+using System.Numerics;
+using Engine.Core;
+using Engine.Core.Components;
+
+namespace Engine.Graphics;
+
+public static class ProceduralMesh
+{
+ public static Mesh CreateSphere(float radius, int slices, int stacks, Vector3 color)
+ {
+ var vertices = new Vertex[(stacks + 1) * (slices + 1)];
+ var indices = new uint[stacks * slices * 6];
+
+ var vi = 0;
+ for (var i = 0; i <= stacks; i++)
+ {
+ var phi = MathF.PI * i / stacks;
+ var y = radius * MathF.Cos(phi);
+ var r = radius * MathF.Sin(phi);
+
+ for (var j = 0; j <= slices; j++)
+ {
+ var theta = 2.0f * MathF.PI * j / slices;
+ var x = r * MathF.Cos(theta);
+ var z = r * MathF.Sin(theta);
+
+ var pos = new Vector3(x, y, z);
+ var normal = Vector3.Normalize(pos);
+
+ vertices[vi++] = new Vertex(pos, color, normal);
+ }
+ }
+
+ var ii = 0;
+ for (var i = 0; i < stacks; i++)
+ {
+ for (var j = 0; j < slices; j++)
+ {
+ var a = (uint)(i * (slices + 1) + j);
+ var b = a + 1;
+ var c = a + (uint)(slices + 1);
+ var d = c + 1;
+
+ indices[ii++] = a; indices[ii++] = c; indices[ii++] = b;
+ indices[ii++] = b; indices[ii++] = c; indices[ii++] = d;
+ }
+ }
+
+ return new Mesh(vertices, indices);
+ }
+
+ public static Mesh CreateGrid(int halfSize, float spacing, Vector3 color)
+ {
+ var lines = 2 * halfSize + 1;
+ var vertices = new List(lines * 4 * 2);
+ var indices = new List(lines * 4 * 2);
+ var extent = halfSize * spacing;
+
+ for (var i = -halfSize; i <= halfSize; i++)
+ {
+ var pos = i * spacing;
+
+ var i0 = (uint)vertices.Count;
+ vertices.Add(new Vertex(new Vector3(pos, 0, -extent), color, Vector3.UnitY));
+ var i1 = (uint)vertices.Count;
+ vertices.Add(new Vertex(new Vector3(pos, 0, extent), color, Vector3.UnitY));
+ indices.Add(i0); indices.Add(i1);
+
+ var i2 = (uint)vertices.Count;
+ vertices.Add(new Vertex(new Vector3(pos, 0, -extent), color, Vector3.UnitY));
+ var i3 = (uint)vertices.Count;
+ vertices.Add(new Vertex(new Vector3(pos, 0, extent), color, Vector3.UnitY));
+ indices.Add(i2); indices.Add(i3);
+
+ var i4 = (uint)vertices.Count;
+ vertices.Add(new Vertex(new Vector3(-extent, 0, pos), color, Vector3.UnitY));
+ var i5 = (uint)vertices.Count;
+ vertices.Add(new Vertex(new Vector3(extent, 0, pos), color, Vector3.UnitY));
+ indices.Add(i4); indices.Add(i5);
+
+ var i6 = (uint)vertices.Count;
+ vertices.Add(new Vertex(new Vector3(-extent, 0, pos), color, Vector3.UnitY));
+ var i7 = (uint)vertices.Count;
+ vertices.Add(new Vertex(new Vector3(extent, 0, pos), color, Vector3.UnitY));
+ indices.Add(i6); indices.Add(i7);
+ }
+
+ return new Mesh(vertices.ToArray(), indices.ToArray());
+ }
+}
diff --git a/src/Engine.Graphics/RenderBackendFactory.cs b/src/Engine.Graphics/RenderBackendFactory.cs
new file mode 100644
index 0000000..2ab73db
--- /dev/null
+++ b/src/Engine.Graphics/RenderBackendFactory.cs
@@ -0,0 +1,25 @@
+using Engine.Core;
+
+namespace Engine.Graphics;
+
+public static class RenderBackendFactory
+{
+ private static readonly Dictionary> _backends =
+ new(StringComparer.OrdinalIgnoreCase);
+
+ public static void Register(string name, Func factory)
+ {
+ _backends[name] = factory;
+ }
+
+ public static IRenderContext Create(string name, int width, int height, bool enableValidation)
+ {
+ if (_backends.TryGetValue(name, out var factory))
+ return factory(width, height, enableValidation);
+
+ throw new NotSupportedException(
+ $"Unknown render backend '{name}'. Available: {string.Join(", ", _backends.Keys)}");
+ }
+
+ public static bool IsRegistered(string name) => _backends.ContainsKey(name);
+}
diff --git a/src/Engine.Graphics/SceneSerializer.cs b/src/Engine.Graphics/SceneSerializer.cs
new file mode 100644
index 0000000..c9cb57d
--- /dev/null
+++ b/src/Engine.Graphics/SceneSerializer.cs
@@ -0,0 +1,204 @@
+using System.Numerics;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Engine.Core.Components;
+using Flecs.NET.Core;
+
+namespace Engine.Graphics;
+
+public static class SceneSerializer
+{
+ private static readonly JsonSerializerOptions JsonOptions = new()
+ {
+ PropertyNameCaseInsensitive = true,
+ Converters = { new JsonStringEnumConverter() }
+ };
+
+ public static string SaveToString(World world)
+ {
+ var entities = new List();
+
+ world.Each((Entity e, ref Transform _) =>
+ {
+ var name = e.Name();
+ if (string.IsNullOrEmpty(name)) return;
+
+ var data = new SceneEntityData { Name = name };
+
+ if (e.Has())
+ {
+ var t = e.Get();
+ data.Transform = new TransformData
+ {
+ Position = new float[] { t.Position.X, t.Position.Y, t.Position.Z },
+ Rotation = new float[] { t.Rotation.X, t.Rotation.Y, t.Rotation.Z, t.Rotation.W },
+ Scale = new float[] { t.Scale.X, t.Scale.Y, t.Scale.Z }
+ };
+ }
+
+ if (e.Has())
+ {
+ var m = e.Get();
+ data.Material = new MaterialData
+ {
+ Albedo = new float[] { m.Albedo.X, m.Albedo.Y, m.Albedo.Z },
+ Roughness = m.Roughness,
+ Metallic = m.Metallic,
+ TexturePath = m.TexturePath
+ };
+ }
+
+ if (e.Has())
+ {
+ var l = e.Get();
+ data.Light = new LightData
+ {
+ Type = l.Type.ToString(),
+ Direction = new float[] { l.Direction.X, l.Direction.Y, l.Direction.Z },
+ Position = new float[] { l.Position.X, l.Position.Y, l.Position.Z },
+ Color = new float[] { l.Color.X, l.Color.Y, l.Color.Z },
+ Intensity = l.Intensity,
+ Range = l.Range
+ };
+ }
+
+ if (e.Has())
+ {
+ var c = e.Get();
+ data.Camera = new CameraData
+ {
+ Position = new float[] { c.Position.X, c.Position.Y, c.Position.Z },
+ Target = new float[] { c.Target.X, c.Target.Y, c.Target.Z },
+ Up = new float[] { c.Up.X, c.Up.Y, c.Up.Z },
+ FieldOfView = c.FieldOfView,
+ AspectRatio = c.AspectRatio,
+ NearPlane = c.NearPlane,
+ FarPlane = c.FarPlane
+ };
+ }
+
+ entities.Add(data);
+ });
+
+ return JsonSerializer.Serialize(entities, JsonOptions);
+ }
+
+ public static void SaveToFile(World world, string path)
+ {
+ var json = SaveToString(world);
+ File.WriteAllText(path, json);
+ }
+
+ public static int LoadFromString(World world, string json)
+ {
+ var entities = JsonSerializer.Deserialize>(json, JsonOptions);
+ if (entities == null) return 0;
+
+ foreach (var data in entities)
+ {
+ var entity = world.Entity(data.Name);
+
+ if (data.Transform != null)
+ {
+ var t = data.Transform;
+ entity.Set(new Transform(
+ new Vector3(t.Position[0], t.Position[1], t.Position[2]),
+ new Quaternion(t.Rotation[0], t.Rotation[1], t.Rotation[2], t.Rotation[3]),
+ new Vector3(t.Scale[0], t.Scale[1], t.Scale[2])));
+ }
+
+ if (data.Material != null)
+ {
+ var m = data.Material;
+ entity.Set(new Material(
+ new Vector3(m.Albedo[0], m.Albedo[1], m.Albedo[2]),
+ m.Roughness, m.Metallic, m.TexturePath));
+ }
+
+ if (data.Light != null)
+ {
+ var l = data.Light;
+ if (l.Type == "Directional")
+ {
+ entity.Set(Light.Directional(
+ new Vector3(l.Direction[0], l.Direction[1], l.Direction[2]),
+ new Vector3(l.Color[0], l.Color[1], l.Color[2]),
+ l.Intensity));
+ }
+ else
+ {
+ entity.Set(Light.Point(
+ new Vector3(l.Position[0], l.Position[1], l.Position[2]),
+ new Vector3(l.Color[0], l.Color[1], l.Color[2]),
+ l.Intensity, l.Range));
+ }
+ }
+
+ if (data.Camera != null)
+ {
+ var c = data.Camera;
+ entity.Set(new Camera(
+ new Vector3(c.Position[0], c.Position[1], c.Position[2]),
+ new Vector3(c.Target[0], c.Target[1], c.Target[2]),
+ new Vector3(c.Up[0], c.Up[1], c.Up[2]),
+ c.FieldOfView, c.AspectRatio, c.NearPlane, c.FarPlane));
+ }
+ }
+
+ return entities.Count;
+ }
+
+ public static int LoadFromFile(World world, string path)
+ {
+ if (!File.Exists(path))
+ throw new FileNotFoundException($"Scene file not found: {path}", path);
+
+ var json = File.ReadAllText(path);
+ return LoadFromString(world, json);
+ }
+
+ private class SceneEntityData
+ {
+ public string Name { get; set; } = "";
+ public TransformData? Transform { get; set; }
+ public MaterialData? Material { get; set; }
+ public LightData? Light { get; set; }
+ public CameraData? Camera { get; set; }
+ }
+
+ private class TransformData
+ {
+ public float[] Position { get; set; } = Array.Empty();
+ public float[] Rotation { get; set; } = Array.Empty();
+ public float[] Scale { get; set; } = Array.Empty();
+ }
+
+ private class MaterialData
+ {
+ public float[] Albedo { get; set; } = Array.Empty();
+ public float Roughness { get; set; }
+ public float Metallic { get; set; }
+ public string? TexturePath { get; set; }
+ }
+
+ private class LightData
+ {
+ public string Type { get; set; } = "";
+ public float[] Direction { get; set; } = Array.Empty();
+ public float[] Position { get; set; } = Array.Empty();
+ public float[] Color { get; set; } = Array.Empty();
+ public float Intensity { get; set; }
+ public float Range { get; set; }
+ }
+
+ private class CameraData
+ {
+ public float[] Position { get; set; } = Array.Empty();
+ public float[] Target { get; set; } = Array.Empty();
+ public float[] Up { get; set; } = Array.Empty();
+ public float FieldOfView { get; set; }
+ public float AspectRatio { get; set; }
+ public float NearPlane { get; set; }
+ public float FarPlane { get; set; }
+ }
+}