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; } + } +}