feat: rebuild Vulkan renderer from scratch — pure P/Invoke triangle (Vulkan 1.3)

- Complete rewrite of Engine.Graphics.Vulkan with pure P/Invoke (no wrapper libs)
- Vulkan 1.3: dynamic rendering (vkCmdBeginRendering/vkCmdEndRendering),
  synchronization2 (vkQueueSubmit2, vkCmdPipelineBarrier2)
- Split types into VulkanHandles.cs, VulkanEnums.cs, VulkanStructs.cs
- Staging buffer → device-local vertex buffer pattern
- Correct swapchain semaphore indexing (per-image, not per-frame-in-flight)
- VK_EXT_debug_utils debug messenger with validation layer fallback
- Dynamic viewport/scissor (no pipeline recreation on resize)
- Simplified Program.cs to triangle-only rendering
- Removed old Silk.NET renderer, ImGui, PBR shaders, screenshot code
- Updated VULKAN_IMPLEMENTATION_PLAN.md with full architecture decisions
This commit is contained in:
emil28092005
2026-06-18 01:49:25 +03:00
parent ee98e4ad08
commit 2e0970e769
44 changed files with 3882 additions and 5273 deletions
+649 -177
View File
@@ -6,6 +6,8 @@
- **Engine.Core** — Sdl3Window (SDL3, Vulkan surface ready), IWindow, IInputState, Key enum, InputMapping, - **Engine.Core** — Sdl3Window (SDL3, Vulkan surface ready), IWindow, IInputState, Key enum, InputMapping,
camera controllers (FreeFly, Orbit), components (Transform, Mesh, Material, Light, Camera, RigidBody), camera controllers (FreeFly, Orbit), components (Transform, Mesh, Material, Light, Camera, RigidBody),
Vertex struct (Position, Color, Normal — 9 floats), Timing, IScreenshotProvider Vertex struct (Position, Color, Normal — 9 floats), Timing, IScreenshotProvider
- **Engine.Graphics** — Restored minimal interfaces: IRenderContext, IRenderer, RenderBackendFactory,
IScreenshotProvider, SceneSerializer, MeshMath, ProceduralMesh, Loaders/ObjLoader
- **Engine.Physics** — JoltPhysicsSharp 2.21.0, PhysicsWorld wrapper, RigidBody component - **Engine.Physics** — JoltPhysicsSharp 2.21.0, PhysicsWorld wrapper, RigidBody component
- **Engine.AI** — AiCommandProcessor (7 commands), MCP HTTP + stdio servers, AiCommandQueue - **Engine.AI** — AiCommandProcessor (7 commands), MCP HTTP + stdio servers, AiCommandQueue
- **CortexEngine.App** — main loop (broken, references deleted graphics projects) - **CortexEngine.App** — main loop (broken, references deleted graphics projects)
@@ -13,245 +15,715 @@
- **Content/** — cube.obj, torusknot.obj, checker.png - **Content/** — cube.obj, torusknot.obj, checker.png
### What Was Deleted ### What Was Deleted
- Engine.Graphics (interfaces + loaders + factory)
- Engine.Graphics.Raylib - Engine.Graphics.Raylib
- Engine.Graphics.OpenTK - Engine.Graphics.OpenTK
- Engine.Graphics.Vulkan (Silk.NET version) - Engine.Graphics.Vulkan (Silk.NET version — all previous PBR/ImGui/mesh/screenshot code gone)
### Environment ### Environment
- .NET 9 SDK at $HOME/.dotnet - .NET 9 SDK at `$HOME/.dotnet`
- Vulkan 1.4.329, NVIDIA RTX 2080 Ti, validation layers available - Vulkan 1.4.329, NVIDIA RTX 2080 Ti, validation layers available
- SDL3 (ppy.SDL3-CS 2026.520.0) — window + Vulkan surface - SDL3 (ppy.SDL3-CS 2026.520.0) — window + Vulkan surface
- glslangValidator NOT installed (need: sudo apt install glslang-tools) - glslangValidator: check availability (`glslangValidator --version`); fallback: `glslc`
- Linux (X11), cross-platform target (Windows: vulkan-1.dll, Linux: libvulkan.so.1) - Linux (X11), cross-platform target (Windows: `vulkan-1.dll`, Linux: `libvulkan.so.1`)
### Key Architecture Decisions ---
- NO wrapper libraries (no Silk.NET, no Vortice, no OpenTK for Vulkan)
- Pure P/Invoke to libvulkan.so.1 / vulkan-1.dll
- SDL3 for windowing (Sdl3Window already works, creates Vulkan surface)
- ImGui planned (later phase)
- Shadow mapping planned (Vulkan gives full control)
- Validation layers for debugging
## Implementation Plan ## Key Architecture Decisions
### Phase 1: Restore Engine.Graphics (interfaces + loaders) | Decision | Choice | Rationale |
|---|---|---|
| Vulkan version | **1.3** | Dynamic rendering (no VkRenderPass/VkFramebuffer), synchronization2, extended dynamic state. All modern GPUs (2022+) support it. |
| Wrapper libraries | **None** | Pure P/Invoke to `libvulkan.so.1` / `vulkan-1.dll`. No Silk.NET, Vortice, OpenTK. |
| Windowing | **SDL3** (ppy.SDL3-CS) | Already integrated, Vulkan surface support built in. |
| Type organisation | **Multiple files** | `VulkanHandles.cs`, `VulkanEnums.cs`, `VulkanStructs.cs` — easier to maintain. |
| Debug | **Full debug messenger** | `VK_EXT_debug_utils` with callback printing validation messages to console (Debug only). |
| Memory | **Staging buffer from start** | Staging buffer (HOST_VISIBLE) → command buffer copy → device-local vertex buffer. Correct pattern from day one. |
| Frame loop | **Re-record every frame** | Vulkan Guide recommends fresh command buffers per frame over reuse. Simpler, no cache invalidation logic. |
| Semaphore indexing | **Per-swapchain-image for submit** | Critical: submit semaphores indexed by swapchain image index, NOT frame-in-flight index. (Vulkan Guide §swapchain_semaphore_reuse) |
| Render pass | **Dynamic rendering** | `vkCmdBeginRendering` / `vkCmdEndRendering` (Vulkan 1.3). No VkRenderPass or VkFramebuffer objects. |
| Synchronisation API | **synchronization2** | `VkImageMemoryBarrier2`, `vkCmdPipelineBarrier2` — cleaner, 64-bit flags. (Vulkan 1.3) |
Create `src/Engine.Graphics/` with: ---
- `IRenderContext.cs` — interface: Window, CreateRenderer(), Resize(), Dispose
- `IRenderer.cs` — interface: RenderWorld(World), RequestScreenshot, IsScreenshotRequested, ScreenshotProvider, Dispose
- `RenderBackendFactory.cs` — static registry: Register(name, factory), Create(name, w, h, validation)
- `IScreenshotProvider.cs` already in Engine.Core
- `Loaders/ObjLoader.cs` — parse .obj files → Mesh component
- `Loaders/GltfLoader.cs` — parse .gltf/.glb → Mesh component
- `MeshMath.cs` — ComputeFaceNormal(a, b, c)
- `ProceduralMesh.cs` — CreateSphere(), CreateGrid()
- `SceneSerializer.cs` — save/load ECS world to JSON
csproj: references Engine.Core, Flecs.NET, SharpGLTF.Core ## Implementation Phases
### Phase 2: Vulkan P/Invoke Layer ### Phase 1: Vulkan P/Invoke Foundation
Create `src/Engine.Graphics.Vulkan/` with: Create `src/Engine.Graphics.Vulkan/` with the following files:
- `VulkanNative.cs` — all P/Invoke declarations:
- Library loading: `const string VulkanLib = OperatingSystem.IsWindows() ? "vulkan-1.dll" : "libvulkan.so.1"`
- ~80 Vulkan functions (vkCreateInstance through vkQueuePresentKHR)
- ~50 structs (InstanceCreateInfo, DeviceCreateInfo, SwapchainCreateInfoKHR, etc.)
- ~20 enums (Result, Format, ImageLayout, PipelineStageFlags, etc.)
- Extension function loading via vkGetInstanceProcAddr/vkGetDeviceProcAddr
- SDL_Vulkan_CreateSurface via SDL3 (already in Sdl3Window)
- `VulkanContext.cs` — Instance + PhysicalDevice + Device + Queues + Surface: #### 1.1 `VulkanNative.cs`
- CreateInstance with SDL3 extensions + validation layers - Load `libvulkan.so.1` (Linux) / `vulkan-1.dll` (Windows) via `NativeLibrary.Load()`
- PickPhysicalDevice (prefer discrete GPU) - Export `vkGetInstanceProcAddr` delegate — the only directly-loaded function
- CreateLogicalDevice with VK_KHR_swapchain - Helper: `GetExport<T>(string name)` for static exports
- CreateSurface via SDL_Vulkan_CreateSurface - Helper: `ToUtf8Terminated(string)` for passing string names to Vulkan
- Get graphics + present queues
- `VulkanSwapchain.cs` — Swapchain + image views + depth + render pass + framebuffers: #### 1.2 `VulkanHandles.cs`
- Query surface capabilities Opaque pointer handles (all are `nint` / `ulong`):
- Create swapchain (format, extent, present mode) ```
- Create image views VkInstance, VkPhysicalDevice, VkDevice, VkQueue,
- Create depth image + view (D32_SFLOAT) VkCommandPool, VkCommandBuffer,
- Create render pass (color + depth attachments) VkSwapchainKHR, VkSurfaceKHR,
- Create framebuffers VkImage, VkImageView,
VkBuffer, VkDeviceMemory,
VkShaderModule, VkPipelineLayout, VkPipeline,
VkSemaphore, VkFence,
VkDebugUtilsMessengerEXT,
VkDescriptorSetLayout, VkDescriptorPool, VkDescriptorSet
```
Each defined as `struct VkXxx { public nint Handle; }` or `using VkXxx = System.IntPtr;`
- `VulkanPipeline.cs` — Graphics pipeline: #### 1.3 `VulkanEnums.cs`
- Load SPIR-V shader modules (vertex + fragment) All enums needed for triangle + future expansion:
- Vertex input description (Position vec3, Normal vec3, Color vec4) - `VkResult` — Success=0, NotReady, Timeout, Incomplete, ErrorOutOfDateKHR, SuboptimalKHR, ErrorSurfaceLostKHR, ...
- Descriptor set layouts (frame UBO + texture sampler) - `VkStructureType` — ApplicationInfo=0, InstanceCreateInfo=1, DeviceQueueCreateInfo=2, DeviceCreateInfo=3, ...
- Pipeline layout + graphics pipeline - `VkFormat` — Undefined=0, R8G8B8A8Unorm=37, B8G8R8A8Unorm=44, R8G8B8A8Srgb=43, B8G8R8A8Srgb=50, R32G32Sfloat=103, R32G32B32Sfloat=106, R32G32B32A32Sfloat=109, D32Sfloat=126, ...
- Push constants for MVP matrix + material params - `VkColorSpaceKHR` — SrgbNonlinear=0
- `VkPresentModeKHR` — Immediate=0, Mailbox=1, Fifo=2, FifoRelaxed=3
- `VkImageUsageFlags` — TransferSrc, TransferDst, ColorAttachment, ...
- `VkImageLayout` — Undefined=0, General=1, ColorAttachmentOptimal=2, TransferSrcOptimal=6, TransferDstOptimal=7, PresentSrcKHR=1000001002, ...
- `VkImageAspectFlags` — Color=1, Depth=2
- `VkAttachmentLoadOp` — Load=0, Clear=1, DontCare=2
- `VkAttachmentStoreOp` — Store=0, DontCare=1
- `VkSharingMode` — Exclusive=0, Concurrent=1
- `VkCompositeAlphaFlagsKHR` — Opaque=1, ...
- `VkSurfaceTransformFlagsKHR` — Identity=1, ...
- `VkPrimitiveTopology` — PointList=0, LineList=1, TriangleList=3, ...
- `VkPolygonMode` — Fill=0, Line=1, Point=2
- `VkCullModeFlags` — None=0, Front=1, Back=2, FrontAndBack=3
- `VkFrontFace` — CounterClockwise=0, Clockwise=1
- `VkBlendFactor` — Zero=0, One=1, SrcAlpha=6, OneMinusSrcAlpha=7, ...
- `VkBlendOp` — Add=0, ...
- `VkColorComponentFlags` — R=1, G=2, B=4, A=8
- `VkShaderStageFlags` — Vertex=1, Fragment=0x10, AllGraphics=0x1F
- `VkPipelineStageFlags2` — None=0, TopOfPipe=1, ColorAttachmentOutput=0x400, AllGraphics=0x8000, Transfer=0x10000, ...
- `VkAccessFlags2` — None=0, ColorAttachmentWrite=0x400, TransferWrite=0x1000, ...
- `VkDynamicState` — Viewport=0, Scissor=1, ...
- `VkCommandBufferLevel` — Primary=0, Secondary=1
- `VkCommandBufferUsageFlags` — OneTimeSubmit=1, ...
- `VkFenceCreateFlags` — Signaled=1
- `VkMemoryPropertyFlags` — DeviceLocal=1, HostVisible=2, HostCoherent=4, HostCached=8
- `VkBufferUsageFlags` — TransferSrc=1, TransferDst=2, VertexBuffer=0x80, IndexBuffer=0x40, UniformBuffer=0x10
- `VkQueueFlags` — Graphics=1, Compute=2, Transfer=4
- `VkPhysicalDeviceType` — Other=0, IntegratedGpu=1, DiscreteGpu=2, ...
- `VkSampleCountFlags` — Count1=1
- `VkImageViewType` — Type2D=1
- `VkComponentSwizzle` — Identity=0, ...
- `VkBool32` — False=0, True=1
- `VkRenderingFlags` — None=0, ContentsSecondaryCommandBuffers=1
- `VkPipelineBindPoint` — Graphics=0, Compute=1
- `VkDescriptorType` — UniformBuffer=6, StorageBuffer=7, CombinedImageSampler=0, ...
- `VkDescriptorPoolCreateFlags` — FreeDescriptorSet=1, ...
- `VulkanBuffer.cs` — Buffer management: #### 1.4 `VulkanStructs.cs`
- CreateBuffer (vertex/index/uniform) All structs with `LayoutKind.Sequential`:
- AllocateMemory + bind - `VkApplicationInfo` — sType, pNext, pApplicationName, applicationVersion, pEngineName, engineVersion, apiVersion
- Map/unmap for writing - `VkInstanceCreateInfo` — sType, pNext, flags, pApplicationInfo, enabledLayerCount, ppEnabledLayerNames, enabledExtensionCount, ppEnabledExtensionNames
- FindMemoryType - `VkDebugUtilsMessengerCreateInfoEXT` — sType, pNext, flags, messageSeverity, messageType, pfnUserCallback, pUserData
- Staging buffer for copy - `VkDeviceQueueCreateInfo` — sType, pNext, flags, queueFamilyIndex, queueCount, pQueuePriorities
- `VkDeviceCreateInfo` — sType, pNext, flags, queueCreateInfoCount, pQueueCreateInfos, enabledLayerCount, ppEnabledLayerNames, enabledExtensionCount, ppEnabledExtensionNames, pEnabledFeatures
- `VkPhysicalDeviceFeatures` — all VkBool32 (can be zeroed for triangle)
- `VkPhysicalDeviceDynamicRenderingFeatures` — sType, pNext, dynamicRendering (VkBool32) — needed to enable dynamic rendering
- `VkSwapchainCreateInfoKHR` — sType, pNext, flags, surface, minImageCount, imageFormat, imageColorSpace, imageExtent, imageArrayLayers, imageUsage, imageSharingMode, queueFamilyIndexCount, pQueueFamilyIndices, preTransform, compositeAlpha, presentMode, clipped, oldSwapchain
- `VkImageViewCreateInfo` — sType, pNext, flags, image, viewType, format, components, subresourceRange
- `VkComponentMapping` — r, g, b, a (VkComponentSwizzle)
- `VkImageSubresourceRange` — aspectMask, baseMipLevel, levelCount, baseArrayLayer, layerCount
- `VkExtent2D` — width, height
- `VkExtent3D` — width, height, depth
- `VkOffset2D` — x, y
- `VkOffset3D` — x, y, z
- `VkRect2D` — offset, extent
- `VkViewport` — x, y, width, height, minDepth, maxDepth
- `VkShaderModuleCreateInfo` — sType, pNext, flags, codeSize, pCode
- `VkPipelineShaderStageCreateInfo` — sType, pNext, flags, stage, module, pName, pSpecializationInfo
- `VkPipelineVertexInputStateCreateInfo` — sType, pNext, flags, vertexBindingDescriptionCount, pVertexBindingDescriptions, vertexAttributeDescriptionCount, pVertexAttributeDescriptions
- `VkVertexInputBindingDescription` — binding, stride, inputRate
- `VkVertexInputAttributeDescription` — location, binding, format, offset
- `VkPipelineInputAssemblyStateCreateInfo` — sType, pNext, flags, topology, primitiveRestartEnable
- `VkPipelineViewportStateCreateInfo` — sType, pNext, flags, viewportCount, pViewports, scissorCount, pScissors
- `VkPipelineRasterizationStateCreateInfo` — sType, pNext, flags, depthClampEnable, rasterizerDiscardEnable, polygonMode, cullMode, frontFace, depthBiasEnable, depthBiasConstantFactor, depthBiasClamp, depthBiasSlopeFactor, lineWidth
- `VkPipelineMultisampleStateCreateInfo` — sType, pNext, flags, rasterizationSamples, sampleShadingEnable, minSampleShading, pSampleMask, alphaToCoverageEnable, alphaToOneEnable
- `VkPipelineColorBlendAttachmentState` — blendEnable, srcColorBlendFactor, dstColorBlendFactor, colorBlendOp, srcAlphaBlendFactor, dstAlphaBlendFactor, alphaBlendOp, colorWriteMask
- `VkPipelineColorBlendStateCreateInfo` — sType, pNext, flags, logicOpEnable, logicOp, attachmentCount, pAttachments, blendConstants[4]
- `VkPipelineDynamicStateCreateInfo` — sType, pNext, flags, dynamicStateCount, pDynamicStates
- `VkPipelineLayoutCreateInfo` — sType, pNext, flags, setLayoutCount, pSetLayouts, pushConstantRangeCount, pPushConstantRanges
- `VkGraphicsPipelineCreateInfo` — sType, pNext, flags, stageCount, pStages, pVertexInputState, pInputAssemblyState, pViewportState, pRasterizationState, pMultisampleState, pDepthStencilState, pColorBlendState, pDynamicState, layout, renderPass, subpass, basePipelineHandle, basePipelineIndex
- `VkCommandPoolCreateInfo` — sType, pNext, flags, queueFamilyIndex
- `VkCommandBufferAllocateInfo` — sType, pNext, commandPool, level, commandBufferCount
- `VkCommandBufferBeginInfo` — sType, pNext, flags, pInheritanceInfo
- `VkSemaphoreCreateInfo` — sType, pNext, flags
- `VkFenceCreateInfo` — sType, pNext, flags
- `VkBufferCreateInfo` — sType, pNext, flags, size, usage, sharingMode, queueFamilyIndexCount, pQueueFamilyIndices
- `VkMemoryAllocateInfo` — sType, pNext, allocationSize, memoryTypeIndex
- `VkMemoryRequirements` — size, alignment, memoryTypeBits
- `VkPhysicalDeviceMemoryProperties` — memoryTypeCount, memoryTypes[32], memoryHeapCount, memoryHeaps[16]
- `VkMemoryType` — propertyFlags, heapIndex
- `VkMemoryHeap` — size, flags
- `VkQueueFamilyProperties` — queueFlags, queueCount, timestampValidBits, minImageTransferGranularity
- `VkSurfaceCapabilitiesKHR` — minImageCount, maxImageCount, currentExtent, minImageExtent, maxImageExtent, maxImageArrayLayers, supportedTransforms, currentTransform, supportedCompositeAlpha, supportedUsageFlags
- `VkSurfaceFormatKHR` — format, colorSpace
- `VkPhysicalDeviceProperties` — apiVersion, driverVersion, vendorID, deviceID, deviceType, deviceName[256], ...
- `VkSubmitInfo` — sType, pNext, waitSemaphoreCount, pWaitSemaphores, pWaitDstStageMask, commandBufferCount, pCommandBuffers, signalSemaphoreCount, pSignalSemaphores
- `VkSubmitInfo2` — sType, pNext, flags, waitSemaphoreInfoCount, pWaitSemaphoreInfos, commandBufferInfoCount, pCommandBufferInfos, signalSemaphoreInfoCount, pSignalSemaphoreInfos (sync2)
- `VkSemaphoreSubmitInfo` — sType, pNext, semaphore, value, stageMask, deviceIndex (sync2)
- `VkCommandBufferSubmitInfo` — sType, pNext, commandBuffer, deviceMask (sync2)
- `VkPresentInfoKHR` — sType, pNext, waitSemaphoreCount, pWaitSemaphores, swapchainCount, pSwapchains, pImageIndices, pResults
- `VkClearValue` — union: VkClearColorValue color / VkClearDepthStencilValue depthStencil
- `VkClearColorValue` — union: float[4] / int[4] / uint[4]
- `VkClearDepthStencilValue` — depth, stencil
- `VkRenderingAttachmentInfo` — sType, pNext, imageView, imageLayout, resolveMode, resolveImageView, resolveImageLayout, loadOp, storeOp, clearValue
- `VkRenderingInfo` — sType, pNext, flags, renderArea, layerCount, viewMask, colorAttachmentCount, pColorAttachments, pDepthAttachment, pStencilAttachment
- `VkImageMemoryBarrier2` — sType, pNext, srcStageMask, srcAccessMask, dstStageMask, dstAccessMask, oldLayout, newLayout, srcQueueFamilyIndex, dstQueueFamilyIndex, image, subresourceRange
- `VkBufferMemoryBarrier2` — sType, pNext, srcStageMask, srcAccessMask, dstStageMask, dstAccessMask, srcQueueFamilyIndex, dstQueueFamilyIndex, buffer, offset, size
- `VkDependencyInfo` — sType, pNext, dependencyFlags, memoryBarrierCount, pMemoryBarriers, bufferMemoryBarrierCount, pBufferMemoryBarriers, imageMemoryBarrierCount, pImageMemoryBarriers
- `VkBufferCopy` — srcOffset, dstOffset, size
- `VkDebugUtilsMessengerCallbackDataEXT` — sType, pNext, messageId, pMessageIdName, messageSeverity, messageType, pMessage, queueLabelCount, pQueueLabels, cmdBufLabelCount, pCmdBufLabels, objectCount, pObjects
- `VkDebugUtilsObjectNameInfoEXT` — sType, pNext, objectType, objectHandle, pObjectName
- `VulkanRenderer.cs` — Render loop: #### 1.5 `Vk.cs`
- Command pool + command buffers (2 frames in flight) Function delegate types + loaded function pointers:
- Semaphores + fences for sync
- Descriptor pools + sets
- RenderWorld(World):
- Get camera from ECS
- Collect lights from ECS
- Update frame UBO (camera pos, lights)
- For each Mesh+Transform entity: upload/cache vertex+index buffers,
set push constants (MVP + material), draw indexed
- Screenshot capture (copy image to staging buffer → PNG)
- Present
- `VulkanBackendRegistrar.cs` — Register("vulkan", factory) **Instance-level functions** (loaded via `vkGetInstanceProcAddr`):
- `vkCreateInstance`, `vkDestroyInstance`
- `vkEnumeratePhysicalDevices`, `vkGetPhysicalDeviceProperties`, `vkGetPhysicalDeviceMemoryProperties`
- `vkGetPhysicalDeviceQueueFamilyProperties`
- `vkGetPhysicalDeviceSurfaceSupportKHR`
- `vkGetPhysicalDeviceSurfaceCapabilitiesKHR`, `vkGetPhysicalDeviceSurfaceFormatsKHR`, `vkGetPhysicalDeviceSurfacePresentModesKHR`
- `vkCreateDevice`, `vkDestroyDevice`
- `vkDestroySurfaceKHR`
- `vkCreateDebugUtilsMessengerEXT`, `vkDestroyDebugUtilsMessengerEXT` (extension — via getInstanceProcAddr)
- `vkGetDeviceProcAddr`
csproj: references Engine.Core, Engine.Graphics, Flecs.NET **Device-level functions** (loaded via `vkGetDeviceProcAddr` for best performance):
NO external Vulkan packages — pure P/Invoke - `vkGetDeviceQueue`
- `vkCreateSwapchainKHR`, `vkDestroySwapchainKHR`, `vkGetSwapchainImagesKHR`
- `vkCreateImageView`, `vkDestroyImageView`
- `vkCreateShaderModule`, `vkDestroyShaderModule`
- `vkCreatePipelineLayout`, `vkDestroyPipelineLayout`
- `vkCreateGraphicsPipelines`, `vkDestroyPipeline`
- `vkCreateCommandPool`, `vkDestroyCommandPool`
- `vkAllocateCommandBuffers`, `vkFreeCommandBuffers`
- `vkBeginCommandBuffer`, `vkEndCommandBuffer`, `vkResetCommandBuffer`
- `vkCreateSemaphore`, `vkDestroySemaphore`
- `vkCreateFence`, `vkDestroyFence`, `vkResetFences`, `vkWaitForFences`, `vkGetFenceStatus`
- `vkCreateBuffer`, `vkDestroyBuffer`
- `vkAllocateMemory`, `vkFreeMemory`
- `vkBindBufferMemory`
- `vkGetBufferMemoryRequirements`
- `vkMapMemory`, `vkUnmapMemory`
- `vkCmdBindPipeline`
- `vkCmdSetViewport`, `vkCmdSetScissor`
- `vkCmdBindVertexBuffers`
- `vkCmdDraw`
- `vkCmdBeginRendering`, `vkCmdEndRendering` (Vulkan 1.3 dynamic rendering)
- `vkCmdPipelineBarrier2` (sync2)
- `vkCmdCopyBuffer`
- `vkCmdBindIndexBuffer`, `vkCmdDrawIndexed` (for future)
- `vkAcquireNextImageKHR`
- `vkQueueSubmit2` (sync2)
- `vkQueuePresentKHR`
- `vkDeviceWaitIdle`
- `vkQueueWaitIdle`
### Phase 3: Shaders ### Phase 2: Vulkan Context
GLSL → SPIR-V shaders (compiled with glslangValidator): #### 2.1 `VulkanContext.cs`
- `Shaders/vertex.vert`#version 450, position/normal/color inputs, MVP+model uniforms, outputs - **CreateInstance:**
- `Shaders/fragment.frag`#version 450, PBR lighting (Fresnel, ACES, gamma), directional + point lights - `VkApplicationInfo` with `apiVersion = VK_API_VERSION_1_3`
- Compile: `glslangValidator -V vertex.vert -o vertex.spv && glslangValidator -V fragment.frag -o fragment.spv` - Instance extensions from SDL3: `SDL_GetVulkanInstanceExtensions()`
- Embed .spv files as project resources or copy to output directory - Add `VK_EXT_debug_utils` in Debug
- Layers: `VK_LAYER_KHRONOS_validation` in Debug
- Chain `VkDebugUtilsMessengerCreateInfoEXT` in `pNext` for early validation
- Debug callback: prints `pMessage` to stderr/console
### Phase 4: App Integration - **PickPhysicalDevice:**
- Enumerate all physical devices
- Prefer `VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU`
- Find queue family with `VK_QUEUE_GRAPHICS_BIT` + surface support (`vkGetPhysicalDeviceSurfaceSupportKHR`)
- Fix `CortexEngine.App.csproj` — remove deleted project refs, add Engine.Graphics + Engine.Graphics.Vulkan - **CreateLogicalDevice:**
- Enable `VK_KHR_swapchain` device extension
- Chain `VkPhysicalDeviceDynamicRenderingFeatures` in `pNext` with `dynamicRendering = VK_TRUE`
- Single queue from selected family, priority 1.0
- **CreateSurface:**
- Call SDL3 `SDL_Vulkan_CreateSurface(window, instance, ...)` via Sdl3Window
- Store `VkSurfaceKHR`
- **Debug Messenger:**
- `vkCreateDebugUtilsMessengerEXT` with callback
- Severity: Verbose | Warning | Error
- Type: General | Validation | Performance
### Phase 3: Swapchain
#### 3.1 `VulkanSwapchain.cs`
- **Query surface:**
- `vkGetPhysicalDeviceSurfaceCapabilitiesKHR` → min/max image count, current extent
- `vkGetPhysicalDeviceSurfaceFormatsKHR` → prefer `B8G8R8A8_UNORM` + `SrgbNonlinear`, fallback first format
- `vkGetPhysicalDeviceSurfacePresentModesKHR` → prefer `MAILBOX`, fallback `FIFO` (guaranteed)
- **Create swapchain:**
- `minImageCount = max(minImageCount + 1, maxImageCount)` (clamped)
- `imageUsage = COLOR_ATTACHMENT_BIT | TRANSFER_DST_BIT` (for future screenshots)
- `preTransform = currentTransform` (no pre-rotation on desktop)
- `compositeAlpha = OPAQUE_BIT`
- `clipped = VK_TRUE`
- `oldSwapchain = VK_NULL_HANDLE` (on first create)
- **Get swapchain images:**
- `vkGetSwapchainImagesKHR` → array of `VkImage`
- Create `VkImageView` for each (`TYPE_2D`, same format, `COLOR_BIT` aspect)
- **Recreate:**
- `vkDeviceWaitIdle`
- Destroy old image views + swapchain
- Create new swapchain with `oldSwapchain` = old handle
- Create new image views
### Phase 4: Pipeline
#### 4.1 `VulkanPipeline.cs`
- **Shader modules:**
- Load `triangle.vert.spv` and `triangle.frag.spv` from embedded resources or filesystem
- `vkCreateShaderModule` for each
- **Vertex input:**
- Binding 0: stride = sizeof(Vertex) = 36 bytes, `VERTEX_INPUT_RATE_VERTEX`
- Attribute 0: `R32G32B32_SFLOAT` @ offset 0 (Position, location 0)
- Attribute 1: `R32G32B32_SFLOAT` @ offset 12 (Color, location 1)
- Attribute 2: `R32G32B32_SFLOAT` @ offset 24 (Normal, location 2)
- **Pipeline state:**
- Input assembly: `TRIANGLE_LIST`
- Viewport state: viewportCount=1, scissorCount=1 (dynamic values)
- Rasterization: `FILL`, cull `NONE`, `COUNTER_CLOCKWISE`, lineWidth=1.0
- Multisample: `COUNT_1_BIT`, no sample shading
- Color blend: 1 attachment, blend disabled, write RGBA
- Dynamic state: `VIEWPORT`, `SCISSOR`
- Pipeline layout: no descriptor sets, no push constants (triangle only)
- **Dynamic rendering integration:**
- `VkGraphicsPipelineCreateInfo::renderPass = VK_NULL_HANDLE` (Vulkan 1.3 dynamic rendering)
- Set `pNext` to `VkPipelineRenderingCreateInfo` with `colorAttachmentCount=1`, `pColorAttachmentFormats = {swapchainFormat}`
### Phase 5: Frame Resources
#### 5.1 `VulkanFrameResources.cs`
- **Constants:**
- `MAX_FRAMES_IN_FLIGHT = 2`
- **Per-frame-in-flight resources** (indexed 0..MAX_FRAMES_IN_FLIGHT-1):
- `VkCommandBuffer` — primary, from shared command pool
- `VkFence` — signaled on submit, waited at frame start (created with `SIGNALED` flag)
- `VkSemaphore` — acquire semaphore (signaled by `vkAcquireNextImageKHR`)
- **Per-swapchain-image resources** (indexed 0..swapchainImageCount-1):
- `VkSemaphore` — submit/render-finished semaphore (signaled by `vkQueueSubmit2`, waited by `vkQueuePresentKHR`)
- **CRITICAL:** These are indexed by swapchain image index, NOT frame-in-flight index.
This is the correct pattern from the Vulkan Guide (§swapchain_semaphore_reuse).
Waiting on the acquire semaphore/fence for a given image index guarantees the previous
present operation using that image has completed, making the submit semaphore safe to reuse.
- **Command pool:**
- `vkCreateCommandPool` with `RESET_COMMAND_BUFFER_BIT` flag
- Allocate `MAX_FRAMES_IN_FLIGHT` primary command buffers
### Phase 6: Vertex Buffer
#### 6.1 `VulkanVertexBuffer.cs`
- **Staging buffer pattern (correct from start):**
1. Create staging buffer: `usage = TRANSFER_SRC_BIT`, memory = `HOST_VISIBLE | HOST_COHERENT`
2. `vkMapMemory``memcpy` vertex data → `vkUnmapMemory`
3. Create vertex buffer: `usage = TRANSFER_DST_BIT | VERTEX_BUFFER_BIT`, memory = `DEVICE_LOCAL`
4. Allocate + record one-time command buffer
5. `vkCmdCopyBuffer(staging, vertex, size)`
6. Submit + wait on fence
7. Destroy staging buffer + free its memory + free one-time command buffer
- **Triangle data:**
```
Vertex[3] = {
{ Position: ( 0.0, -0.5, 0.0), Color: (1, 0, 0), Normal: (0, 0, 1) },
{ Position: ( 0.5, 0.5, 0.0), Color: (0, 1, 0), Normal: (0, 0, 1) },
{ Position: (-0.5, 0.5, 0.0), Color: (0, 0, 1), Normal: (0, 0, 1) },
}
```
- **Memory type selection:**
- `vkGetPhysicalDeviceMemoryProperties` → iterate `memoryTypes[]`
- Find type where `(memoryTypeBits >> i) & 1` and `propertyFlags` matches desired flags
- Helper: `FindMemoryType(memoryTypeBits, desiredFlags)`
### Phase 7: Renderer
#### 7.1 `VulkanRenderer.cs` (implements `IRenderer`)
- **Constructor:**
- Create swapchain, pipeline, frame resources, vertex buffer
- Store reference to `VulkanContext` (instance, device, queue, surface)
- **Frame loop (`Render()` method):**
```
1. vkWaitForFences(frameFences[frameIndex])
2. vkResetFences(frameFences[frameIndex])
3. vkAcquireNextImageKHR(swapchain, acquireSemaphores[frameIndex], imageIndex)
4. vkResetCommandBuffer(commandBuffers[frameIndex])
5. vkBeginCommandBuffer(commandBuffers[frameIndex], ONE_TIME_SUBMIT)
6. Image layout transition (sync2 barrier):
UNDEFINED → COLOR_ATTACHMENT_OPTIMAL
(srcStageMask: NONE, dstStageMask: COLOR_ATTACHMENT_OUTPUT)
7. vkCmdBeginRendering(renderingInfo):
- colorAttachment: swapchainImageViews[imageIndex], COLOR_ATTACHMENT_OPTIMAL
- loadOp: CLEAR (black), storeOp: STORE
- renderArea: full extent
8. vkCmdBindPipeline(GRAPHICS, pipeline)
9. vkCmdSetViewport(0, 1, {0, 0, extent.width, extent.height, 0, 1})
10. vkCmdSetScissor(0, 1, {{0,0}, extent})
11. vkCmdBindVertexBuffers(0, 1, {vertexBuffer}, {0})
12. vkCmdDraw(3, 1, 0, 0)
13. vkCmdEndRendering()
14. Image layout transition (sync2 barrier):
COLOR_ATTACHMENT_OPTIMAL → PRESENT_SRC_KHR
(srcStageMask: COLOR_ATTACHMENT_OUTPUT, dstStageMask: ALL_GRAPHICS)
15. vkEndCommandBuffer()
16. vkQueueSubmit2(queue, submitInfo2):
- wait: acquireSemaphores[frameIndex] @ COLOR_ATTACHMENT_OUTPUT
- commandBuffer: commandBuffers[frameIndex]
- signal: submitSemaphores[imageIndex]
- fence: frameFences[frameIndex]
17. vkQueuePresentKHR(presentInfo):
- wait: submitSemaphores[imageIndex]
- swapchain, imageIndex
18. frameIndex = (frameIndex + 1) % MAX_FRAMES_IN_FLIGHT
```
- **Resize handling:**
- If `vkAcquireNextImageKHR` returns `ERROR_OUT_OF_DATE_KHR` or `SuboptimalKHR`:
- `vkDeviceWaitIdle`
- Recreate swapchain
- Continue frame
- **Dispose:**
- `vkDeviceWaitIdle`
- Destroy vertex buffer + memory
- Destroy semaphores (acquire + submit), fences
- Destroy command pool
- Destroy pipeline, pipeline layout, shader modules
- Destroy swapchain + image views
- Destroy debug messenger
- Destroy device, surface, instance
#### 7.2 `VulkanRenderContext.cs` (implements `IRenderContext`)
- Exposes `Window` (from Sdl3Window)
- `CreateRenderer()` → returns `VulkanRenderer`
- `Resize()` → triggers swapchain recreation
- `Dispose()` → destroys context
#### 7.3 `VulkanBackendRegistrar.cs`
- Static constructor registers `"vulkan"` in `RenderBackendFactory`
- Factory creates `VulkanRenderContext` with `Sdl3Window`
### Phase 8: Shaders
#### 8.1 `Shaders/triangle.vert`
```glsl
#version 450
layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inColor;
layout(location = 2) in vec3 inNormal;
layout(location = 0) out vec3 fragColor;
void main() {
gl_Position = vec4(inPosition, 1.0);
fragColor = inColor;
}
```
#### 8.2 `Shaders/triangle.frag`
```glsl
#version 450
layout(location = 0) in vec3 fragColor;
layout(location = 0) out vec4 outColor;
void main() {
outColor = vec4(fragColor, 1.0);
}
```
#### 8.3 Compilation
```bash
glslangValidator -V triangle.vert -o triangle.vert.spv
glslangValidator -V triangle.frag -o triangle.frag.spv
```
- Embed `.spv` files as embedded resources in csproj, or copy to output directory
- Load at runtime via `Assembly.GetManifestResourceStream()` or `File.ReadAllBytes()`
### Phase 9: App Integration
- Fix `CortexEngine.App.csproj`:
- Remove deleted project references
- Add `Engine.Graphics` + `Engine.Graphics.Vulkan`
- Fix `Program.cs`: - Fix `Program.cs`:
- Remove all Raylib/OpenTK/old OpenGL imports - Simplify to triangle-only rendering
- Remove ImGuiLayer, ObjectManipulator (Raylib-specific, will reimplement later) - `RenderBackendFactory.Create("vulkan", 1280, 720, validation: true)`
- Use `RenderBackendFactory.Create("vulkan", 1280, 720, enableValidation: true)` - Main loop: poll events → render → present
- Keep: physics, camera controllers, AI commands, tour mode, scene setup - Keep: Sdl3Window, basic event handling
- Sdl3Window creates Vulkan surface automatically (vulkanSurface: true) - Remove: ECS scene, physics, AI, camera tour (add back later)
### Phase 5: Fix Tests ### Phase 10: Fix Tests
- Update `Engine.Tests.csproj` — reference restored Engine.Graphics - Update `Engine.Tests.csproj` — reference restored `Engine.Graphics`
- Tests that reference Engine.Graphics: ObjLoaderTests, RenderBackendFactoryTests, - Tests referencing Engine.Graphics: ObjLoaderTests, RenderBackendFactoryTests,
SceneSerializerTests, MeshMathAndProceduralTests SceneSerializerTests, MeshMathAndProceduralTests
- All 66 tests should pass after Engine.Graphics is restored - All tests should pass after Engine.Graphics is restored
### Phase 6: ImGui (later) ---
- ImGui.NET NuGet + Vulkan ImGui backend
- ImGui_ImplVulkan for rendering
- Entity inspector, hierarchy, debug overlay
### Phase 7: Shadow Mapping (later)
- Depth-only render pass from light's POV
- Shadow image (depth texture, 2048x2048)
- Shadow matrix (lightViewProj) in push constants
- PCF sampling in fragment shader
## Cross-Platform Notes
- Vulkan P/Invoke: only difference is library name (vulkan-1.dll vs libvulkan.so.1)
- SDL3: already cross-platform (ppy.SDL3-CS)
- SPIR-V: binary format, works everywhere
- .NET 9: NativeLibrary.Load for dynamic resolution if needed
## File Layout ## File Layout
``` ```
src/ src/
├── Engine.Core/ (exists, unchanged) ├── Engine.Core/ (exists, unchanged)
├── Engine.Graphics/ (new — interfaces + loaders) ├── Engine.Graphics/ (exists, restored minimal interfaces)
│ ├── Engine.Graphics.csproj │ ├── Engine.Graphics.csproj
│ ├── IRenderContext.cs │ ├── IRenderContext.cs
│ ├── IRenderer.cs │ ├── IRenderer.cs
│ ├── IScreenshotProvider.cs
│ ├── RenderBackendFactory.cs │ ├── RenderBackendFactory.cs
│ ├── MeshMath.cs │ ├── MeshMath.cs
│ ├── ProceduralMesh.cs │ ├── ProceduralMesh.cs
│ ├── SceneSerializer.cs │ ├── SceneSerializer.cs
│ └── Loaders/ │ └── Loaders/
── ObjLoader.cs ── ObjLoader.cs
│ └── GltfLoader.cs ├── Engine.Graphics.Vulkan/ (new — pure P/Invoke, Vulkan 1.3)
├── Engine.Graphics.Vulkan/ (new — pure Vulkan P/Invoke)
│ ├── Engine.Graphics.Vulkan.csproj │ ├── Engine.Graphics.Vulkan.csproj
│ ├── VulkanNative.cs (~800 lines) │ ├── VulkanNative.cs — library loading, vkGetInstanceProcAddr
│ ├── VulkanContext.cs (~300 lines) │ ├── VulkanHandles.cs — opaque pointer types
│ ├── VulkanSwapchain.cs (~250 lines) │ ├── VulkanEnums.cs — all Vulkan enums/flags
│ ├── VulkanPipeline.cs (~200 lines) │ ├── VulkanStructs.cs — all Vulkan structs (LayoutKind.Sequential)
│ ├── VulkanBuffer.cs (~150 lines) │ ├── Vk.cs — function delegates + loaded pointers
│ ├── VulkanRenderer.cs (~400 lines) │ ├── VulkanContext.cs — instance, device, queue, surface, debug
│ ├── VulkanBackendRegistrar.cs │ ├── VulkanSwapchain.cs — swapchain, image views, recreate
│ ├── VulkanPipeline.cs — shader modules, pipeline layout, graphics pipeline
│ ├── VulkanFrameResources.cs — command buffers, fences, semaphores (correct indexing)
│ ├── VulkanVertexBuffer.cs — staging buffer → device-local vertex buffer
│ ├── VulkanRenderer.cs — IRenderer: frame loop with dynamic rendering
│ ├── VulkanRenderContext.cs — IRenderContext implementation
│ ├── VulkanBackendRegistrar.cs— registration in RenderBackendFactory
│ └── Shaders/ │ └── Shaders/
│ ├── vertex.vert │ ├── triangle.vert
│ ├── fragment.frag │ ├── triangle.frag
│ ├── vertex.spv │ ├── triangle.vert.spv
│ └── fragment.spv │ └── triangle.frag.spv
├── Engine.Physics/ (exists, unchanged) ├── Engine.Physics/ (exists, unchanged)
├── Engine.AI/ (exists, unchanged) ├── Engine.AI/ (exists, unchanged)
└── CortexEngine.App/ (fix references) └── CortexEngine.App/ (fix references, simplify to triangle)
``` ```
## Solution Update ---
Remove from solution: ## csproj: Engine.Graphics.Vulkan
- Engine.Graphics.Raylib (deleted)
- Engine.Graphics.OpenTK (deleted)
- Engine.Graphics.Vulkan (old Silk.NET, deleted)
Add to solution: ```xml
- Engine.Graphics (new) <Project Sdk="Microsoft.NET.Sdk">
- Engine.Graphics.Vulkan (new, pure P/Invoke) <PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Engine.Core\Engine.Core.csproj" />
<ProjectReference Include="..\Engine.Graphics\Engine.Graphics.csproj" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Shaders\*.spv" />
</ItemGroup>
</Project>
```
No NuGet packages for Vulkan. Pure P/Invoke.
---
## Cross-Platform Notes
- **Library name:** `vulkan-1.dll` (Windows) vs `libvulkan.so.1` (Linux) — handled in `VulkanNative.cs`
- **Surface creation:** SDL3 abstracts platform differences (`SDL_Vulkan_CreateSurface`)
- **SPIR-V:** Binary format, identical on all platforms
- **.NET 9:** `NativeLibrary.Load()` for dynamic resolution
---
## Key Technical Details ## Key Technical Details
### Matrix Layout ### Semaphore Indexing (CRITICAL)
- System.Numerics.Matrix4x4 is row-major
- Vulkan expects column-major in shaders (layout(row_major) or transpose) ```
- Solution: use `layout(row_major) uniform mat4` in GLSL → no transpose needed Indexed by frame-in-flight (0..1) Indexed by swapchain image (0..N-1)
- OR transpose in C# before writing to uniform buffer ───────────────────────────────── ──────────────────────────────────
Acquire semaphore ✓
Command buffer ✓
Frame fence ✓
Submit semaphore ✓
```
Rationale: `vkQueuePresentKHR` cannot signal a fence/semaphore. The only way to know
a submit semaphore is safe to reuse is to acquire the same swapchain image index again
(which guarantees the previous present using that image has completed).
Indexing submit semaphores by frame-in-flight is a common bug that violates the spec.
### Dynamic Rendering (Vulkan 1.3)
No `VkRenderPass` or `VkFramebuffer` objects needed:
```csharp
// Instead of vkCmdBeginRenderPass:
VkRenderingAttachmentInfo colorAttachment = new() {
sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
imageView = swapchainImageViews[imageIndex],
imageLayout = COLOR_ATTACHMENT_OPTIMAL,
loadOp = CLEAR,
storeOp = STORE,
clearValue = new() { color = { 0, 0, 0, 1 } }
};
VkRenderingInfo renderingInfo = new() {
sType = VK_STRUCTURE_TYPE_RENDERING_INFO,
renderArea = { {0,0}, extent },
layerCount = 1,
colorAttachmentCount = 1,
pColorAttachments = &colorAttachment
};
vkCmdBeginRendering(commandBuffer, &renderingInfo);
// draw commands...
vkCmdEndRendering(commandBuffer);
```
Pipeline must include `VkPipelineRenderingCreateInfo` in `pNext`:
```csharp
VkPipelineRenderingCreateInfo renderingInfo = new() {
sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO,
colorAttachmentCount = 1,
pColorAttachmentFormats = &swapchainFormat
};
// Chain in VkGraphicsPipelineCreateInfo.pNext
```
### Sync2 Image Layout Transitions
Using `vkCmdPipelineBarrier2` with `VkImageMemoryBarrier2`:
```csharp
// UNDEFINED → COLOR_ATTACHMENT_OPTIMAL (before rendering)
VkImageMemoryBarrier2 toColor = new() {
sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
srcStageMask = PIPELINE_STAGE_2_NONE,
srcAccessMask = ACCESS_2_NONE,
dstStageMask = PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT,
dstAccessMask = ACCESS_2_COLOR_ATTACHMENT_WRITE,
oldLayout = UNDEFINED,
newLayout = COLOR_ATTACHMENT_OPTIMAL,
image = swapchainImages[imageIndex],
subresourceRange = { COLOR_BIT, 0, 1, 0, 1 }
};
// COLOR_ATTACHMENT_OPTIMAL → PRESENT_SRC_KHR (after rendering)
VkImageMemoryBarrier2 toPresent = new() {
sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
srcStageMask = PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT,
srcAccessMask = ACCESS_2_COLOR_ATTACHMENT_WRITE,
dstStageMask = PIPELINE_STAGE_2_ALL_GRAPHICS,
dstAccessMask = ACCESS_2_NONE,
oldLayout = COLOR_ATTACHMENT_OPTIMAL,
newLayout = PRESENT_SRC_KHR,
image = swapchainImages[imageIndex],
subresourceRange = { COLOR_BIT, 0, 1, 0, 1 }
};
VkDependencyInfo depInfo = new() {
sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
imageMemoryBarrierCount = 1,
pImageMemoryBarriers = &barrier
};
vkCmdPipelineBarrier2(commandBuffer, &depInfo);
```
### Queue Submit (Sync2)
Using `vkQueueSubmit2` with `VkSubmitInfo2`:
```csharp
VkSemaphoreSubmitInfo waitInfo = new() {
sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO,
semaphore = acquireSemaphores[frameIndex],
stageMask = PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT
};
VkCommandBufferSubmitInfo cmdInfo = new() {
sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO,
commandBuffer = commandBuffers[frameIndex]
};
VkSemaphoreSubmitInfo signalInfo = new() {
sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO,
semaphore = submitSemaphores[imageIndex],
stageMask = PIPELINE_STAGE_2_ALL_GRAPHICS
};
VkSubmitInfo2 submitInfo = new() {
sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2,
waitSemaphoreInfoCount = 1,
pWaitSemaphoreInfos = &waitInfo,
commandBufferInfoCount = 1,
pCommandBufferInfos = &cmdInfo,
signalSemaphoreInfoCount = 1,
pSignalSemaphoreInfos = &signalInfo
};
vkQueueSubmit2(queue, 1, &submitInfo, frameFences[frameIndex]);
```
### Vertex Layout ### Vertex Layout
``` ```
Vertex struct (9 floats, 36 bytes): Vertex struct (9 floats, 36 bytes):
Position: vec3 (offset 0) Position: vec3 (offset 0, format R32G32B32_SFLOAT, location 0)
Color: vec3 (offset 12) Color: vec3 (offset 12, format R32G32B32_SFLOAT, location 1)
Normal: vec3 (offset 24) Normal: vec3 (offset 24, format R32G32B32_SFLOAT, location 2)
```
### Push Constants (96 bytes max)
```
offset 0: mat4 MVP (64 bytes)
offset 64: vec3 materialAlbedo + float roughness (16 bytes)
offset 80: float metallic + uint useTexture + uint pad + uint pad (16 bytes)
```
### Frame UBO (224 bytes)
```
offset 0: vec3 cameraPosition + uint lightCount (16 bytes)
offset 16: vec3 ambientColor + float pad (16 bytes)
offset 32: Light[4] — each 48 bytes (vec3 direction + float intensity + vec3 color + float pad)
```
### Light Struct (48 bytes)
```
vec3 direction (12 bytes)
float intensity (4 bytes)
vec3 color (12 bytes)
float padding (4 bytes)
``` ```
### Validation Layers ### Validation Layers
```csharp ```csharp
string[] layers = enableValidation string[] layers = enableValidation
? new[] { "VK_LAYER_KHRONOS_validation" } ? new[] { "VK_LAYER_KHRONOS_validation" }
: Array.Empty<string>(); : Array.Empty<string>();
string[] instanceExtensions = enableValidation
? [.. sdlExtensions, "VK_EXT_debug_utils"]
: sdlExtensions;
``` ```
Validation errors print to stderr — use for debugging.
### Memory Allocation Debug callback (C#):
Simple approach (no VMA): ```csharp
1. vkGetPhysicalDeviceMemoryProperties static uint DebugCallback(
2. Find memory type with VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | HOST_COHERENT_BIT nint instance, uint messageSeverity, uint messageTypes,
3. vkAllocateMemory + vkBindBufferMemory nint pCallbackData, nint pUserData)
4. vkMapMemory for writing, vkUnmapMemory {
var data = Marshal.PtrToStructure<VkDebugUtilsMessengerCallbackDataEXT>(pCallbackData);
Console.Error.WriteLine($"[Vulkan] {data.pMessage}");
return 0; // VK_FALSE — don't abort
}
```
For GPU-only buffers (vertex/index): ---
1. Find memory type with DEVICE_LOCAL_BIT
2. Use staging buffer (host visible) + vkCmdCopyBuffer ## Future Phases (Not in This Plan)
- **Phase 11:** ImGui integration (ImGui.NET + Vulkan backend)
- **Phase 12:** Mesh rendering (OBJ loading, index buffers, descriptor sets, UBO for camera)
- **Phase 13:** PBR shading (Fresnel, ACES tonemap, gamma correction, directional + point lights)
- **Phase 14:** Shadow mapping (depth-only render pass from light POV, PCF sampling)
- **Phase 15:** Screenshot capture (copy swapchain image to staging buffer → PNG)
- **Phase 16:** VMA (Vulkan Memory Allocator) for sub-allocation
- **Phase 17:** Multi-threaded command buffer recording
@@ -23,8 +23,6 @@
<ProjectReference Include="..\Engine.Core\Engine.Core.csproj" /> <ProjectReference Include="..\Engine.Core\Engine.Core.csproj" />
<ProjectReference Include="..\Engine.Graphics\Engine.Graphics.csproj" /> <ProjectReference Include="..\Engine.Graphics\Engine.Graphics.csproj" />
<ProjectReference Include="..\Engine.Graphics.Vulkan\Engine.Graphics.Vulkan.csproj" /> <ProjectReference Include="..\Engine.Graphics.Vulkan\Engine.Graphics.Vulkan.csproj" />
<ProjectReference Include="..\Engine.AI\Engine.AI.csproj" />
<ProjectReference Include="..\Engine.Physics\Engine.Physics.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
+6 -427
View File
@@ -1,20 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Numerics;
using Engine.AI;
#if !RELEASE_AOT
using Engine.AI.Mcp;
using Microsoft.AspNetCore.Builder;
#endif
using Engine.Core; using Engine.Core;
using Engine.Core.Components;
using Engine.Graphics; using Engine.Graphics;
using Engine.Graphics.Loaders;
using Engine.Graphics.Vulkan; using Engine.Graphics.Vulkan;
using Engine.Physics;
using Flecs.NET.Core; using Flecs.NET.Core;
using ImGuiNET;
namespace CortexEngine.App; namespace CortexEngine.App;
@@ -22,280 +9,40 @@ class Program
{ {
static async Task Main(string[] args) static async Task Main(string[] args)
{ {
Console.WriteLine("Cortex Engine — Vulkan Backend, Pure P/Invoke..."); Console.WriteLine("Cortex Engine — Vulkan Triangle (pure P/Invoke)...");
try try
{ {
if (args.Contains("--mcp-stdio"))
{
RunMcpStdioServer();
return;
}
var cameraTour = args.Contains("--camera-tour");
var testScene = args.Contains("--test-scene");
if (testScene)
cameraTour = true;
using var world = World.Create();
var timing = new Timing();
using var physicsWorld = new PhysicsWorld();
VulkanBackendRegistrar.EnsureRegistered(); VulkanBackendRegistrar.EnsureRegistered();
using var renderContext = RenderBackendFactory.Create("vulkan", 1280, 720, enableValidation: true); using var renderContext = RenderBackendFactory.Create("vulkan", 1280, 720, enableValidation: true);
var window = renderContext.Window; var window = renderContext.Window;
var input = window.Input;
using var renderer = renderContext.CreateRenderer(); using var renderer = renderContext.CreateRenderer();
VulkanImGui? imGuiLayer = null; using var world = World.Create();
if (!cameraTour && renderer is VulkanRenderer vkRenderer)
{
ImGui.CreateContext();
imGuiLayer = new VulkanImGui(vkRenderer._ctx, vkRenderer._swapchain);
vkRenderer.ImGuiLayer = imGuiLayer;
ImGui.GetIO().DisplaySize = new System.Numerics.Vector2(window.Width, window.Height);
Console.WriteLine("[App] ImGui initialized.");
}
var (modelPath, mcpPort) = ParseArgs(args);
var mesh = LoadModel(modelPath);
var processor = new AiCommandProcessor(world, LoadModel, path => renderer.RequestScreenshot(path));
var queue = new AiCommandQueue(processor, renderer.ScreenshotProvider);
var cameraEntity = world.Entity("Camera")
.Set(new Transform(new Vector3(0.0f, 0.75f, -30.0f), Quaternion.Identity, Vector3.One))
.Set(new Camera(
new Vector3(0.0f, 0.75f, -30.0f),
new Vector3(0.0f, 0.5f, 0.0f),
Vector3.UnitY,
MathF.PI / 12.0f,
1280.0f / 720.0f,
0.1f,
100.0f));
world.Entity("MainLight")
.Set(new Transform(new Vector3(0.0f, 0.0f, 0.0f), Quaternion.Identity, Vector3.One))
.Set(Light.Directional(new Vector3(0.4f, -1.0f, -0.3f), new Vector3(1.0f, 0.95f, 0.85f), 2.0f));
ICameraController[] cameraControllers =
{
new FreeFlyCameraController(cameraEntity),
new OrbitCameraController(cameraEntity, new Vector3(0.0f, 0.5f, 0.0f))
};
var activeControllerIndex = 0;
var cameraController = cameraControllers[activeControllerIndex];
Console.WriteLine($"Active camera controller: {cameraController.Name} (press F to toggle)");
if (testScene)
{
Console.WriteLine("Calibration test scene enabled.");
CreateCalibrationScene(world, mesh);
}
else
{
CreateDemoScene(world, mesh);
}
#if !RELEASE_AOT
WebApplication? mcpApp = null;
Task? mcpTask = null;
if (mcpPort > 0)
{
mcpApp = McpEngineServerHost.Create(args, queue, port: mcpPort);
mcpTask = mcpApp.RunAsync();
_ = mcpTask.ContinueWith(t =>
{
if (t.IsFaulted)
Console.WriteLine($"MCP server error: {t.Exception?.GetBaseException().Message}");
else if (t.IsCanceled)
Console.WriteLine("MCP server canceled.");
else
Console.WriteLine("MCP server stopped.");
}, TaskScheduler.Default);
Console.WriteLine($"MCP HTTP server listening on http://localhost:{mcpPort}/ (SSE)");
}
else
{
Console.WriteLine("MCP server disabled (--mcp-port 0).");
}
#endif
var frames = 0;
var lastFpsTime = 0.0;
var lastWidth = window.Width; var lastWidth = window.Width;
var lastHeight = window.Height; var lastHeight = window.Height;
var demoScreenshotRequested = false; var frames = 0;
var currentFps = 0; var lastFpsTime = 0.0;
var timing = new Timing();
var tourPoses = testScene
? new CameraPose[]
{
new("test_front", new Vector3(0.0f, 0.75f, -30.0f), new Vector3(0.0f, 0.5f, 0.0f), Vector3.UnitY),
new("test_back", new Vector3(0.0f, 0.75f, 30.0f), new Vector3(0.0f, 0.5f, 0.0f), Vector3.UnitY),
new("test_left", new Vector3(-30.0f, 0.75f, 0.0f), new Vector3(0.0f, 0.5f, 0.0f), Vector3.UnitY),
new("test_right", new Vector3(30.0f, 0.75f, 0.0f), new Vector3(0.0f, 0.5f, 0.0f), Vector3.UnitY),
new("test_top", new Vector3(0.0f, 30.0f, 0.0f), new Vector3(0.0f, 0.0f, 0.0f), -Vector3.UnitZ),
new("test_shifted", new Vector3(15.0f, 0.75f, -22.5f), new Vector3(0.0f, 0.5f, 0.0f), Vector3.UnitY),
new("test_rotated", new Vector3(0.0f, 0.75f, -30.0f), new Vector3(2.0f, 0.5f, 0.0f), Vector3.UnitY),
new("test_yaw_15", new Vector3(7.76f, 0.75f, -28.98f), new Vector3(0.0f, 0.5f, 0.0f), Vector3.UnitY),
new("test_yaw_30", new Vector3(15.0f, 0.75f, -25.98f), new Vector3(0.0f, 0.5f, 0.0f), Vector3.UnitY),
new("test_yaw_45", new Vector3(21.21f, 0.75f, -21.21f), new Vector3(0.0f, 0.5f, 0.0f), Vector3.UnitY),
new("test_yaw_90", new Vector3(30.0f, 0.75f, 0.0f), new Vector3(0.0f, 0.5f, 0.0f), Vector3.UnitY),
new("test_pitch_45", new Vector3(0.0f, 21.96f, -21.21f), new Vector3(0.0f, 0.5f, 0.0f), Vector3.UnitY),
new("test_close", new Vector3(0.0f, 0.75f, -15.0f), new Vector3(0.0f, 0.5f, 0.0f), Vector3.UnitY),
new("test_far", new Vector3(0.0f, 0.75f, -60.0f), new Vector3(0.0f, 0.5f, 0.0f), Vector3.UnitY),
new("test_farther", new Vector3(0.0f, 0.75f, -120.0f), new Vector3(0.0f, 0.5f, 0.0f), Vector3.UnitY),
new("test_toward", new Vector3(0.0f, 0.75f, -20.0f), new Vector3(0.0f, 0.5f, 0.0f), Vector3.UnitY)
}
: new CameraPose[]
{
new("front", new Vector3(0.0f, 0.75f, -30.0f), new Vector3(0.0f, 0.5f, 0.0f), Vector3.UnitY),
new("top", new Vector3(0.0f, 30.0f, 0.0f), new Vector3(0.0f, 0.0f, 0.0f), -Vector3.UnitZ),
new("side", new Vector3(30.0f, 0.75f, 4.0f), new Vector3(0.0f, 0.5f, 0.0f), Vector3.UnitY),
new("close", new Vector3(1.0f, 0.75f, -5.0f), new Vector3(0.5f, 0.5f, 0.0f), Vector3.UnitY),
new("low", new Vector3(0.0f, 0.25f, -6.0f), new Vector3(0.0f, 0.5f, 0.0f), Vector3.UnitY),
new("back", new Vector3(0.0f, 0.75f, 30.0f), new Vector3(0.0f, 0.5f, 0.0f), Vector3.UnitY)
};
var tourIndex = -1;
var tourSettleFrames = 0;
var tourScreenshotPending = false;
var tourDone = false;
while (!window.ShouldClose) while (!window.ShouldClose)
{ {
timing.Tick(); timing.Tick();
window.PumpEvents(); window.PumpEvents();
input.BeginFrame();
var processed = queue.ProcessPending();
if (processed > 0)
Console.WriteLine($"Processed {processed} AI command(s)");
if (window.Width != lastWidth || window.Height != lastHeight) if (window.Width != lastWidth || window.Height != lastHeight)
{ {
lastWidth = window.Width; lastWidth = window.Width;
lastHeight = window.Height; lastHeight = window.Height;
renderContext.Resize(lastWidth, lastHeight); renderContext.Resize(lastWidth, lastHeight);
ref var camera = ref cameraEntity.Ensure<Camera>();
camera.AspectRatio = (float)lastWidth / lastHeight;
}
if (input.IsKeyPressed(Key.F))
{
activeControllerIndex = (activeControllerIndex + 1) % cameraControllers.Length;
cameraController = cameraControllers[activeControllerIndex];
Console.WriteLine($"Active camera controller: {cameraController.Name}");
}
if (!cameraTour)
cameraController.Update(input, (float)timing.DeltaTime);
if (cameraTour && !tourDone)
{
if (tourIndex < 0)
{
tourIndex = 0;
SetCameraPose(cameraEntity, tourPoses[tourIndex]);
tourSettleFrames = 0;
tourScreenshotPending = true;
}
if (tourScreenshotPending)
{
tourSettleFrames++;
if (tourSettleFrames >= 5)
{
var path = $"Screenshots/tour_{tourPoses[tourIndex].Name}.png";
renderer.RequestScreenshot(path);
Console.WriteLine($"Tour screenshot: {path}");
tourScreenshotPending = false;
}
}
if (!tourScreenshotPending && !renderer.IsScreenshotRequested)
{
tourIndex++;
if (tourIndex >= tourPoses.Length)
{
tourDone = true;
Console.WriteLine("Camera tour complete.");
window.Close();
}
else
{
SetCameraPose(cameraEntity, tourPoses[tourIndex]);
tourSettleFrames = 0;
tourScreenshotPending = true;
}
}
}
if (!cameraTour)
{
var toInit = new List<(Entity, RigidBody, Transform)>();
world.Each((Entity e, ref RigidBody rb, ref Transform t) =>
{
if (!rb.IsInitialized)
toInit.Add((e, rb, t));
});
foreach (var (e, rbData, t) in toInit)
{
physicsWorld.CreateBody(e, rbData, t);
var rb = rbData;
rb.IsInitialized = true;
e.Set(rb);
}
physicsWorld.Update((float)timing.DeltaTime);
physicsWorld.SyncTransforms(world, null);
}
if (!demoScreenshotRequested && !cameraTour && frames >= 15)
{
renderer.RequestScreenshot("Screenshots/demo.png");
demoScreenshotRequested = true;
}
if (imGuiLayer != null)
{
ImGui.NewFrame();
ImGui.Begin("Cortex Engine Debug");
ImGui.Text($"FPS: {currentFps}");
ImGui.Text($"Delta: {timing.DeltaTime * 1000.0:F2} ms");
ImGui.Text($"Camera: {cameraController.Name}");
ImGui.Separator();
var cam = cameraEntity.Get<Camera>();
ImGui.Text($"Pos: ({cam.Position.X:F2}, {cam.Position.Y:F2}, {cam.Position.Z:F2})");
ImGui.Text($"Target: ({cam.Target.X:F2}, {cam.Target.Y:F2}, {cam.Target.Z:F2})");
ImGui.Separator();
var entityCount = 0;
world.Each((Entity e, ref Transform _) => entityCount++);
ImGui.Text($"Entities: {entityCount}");
ImGui.Text($"Press F to toggle camera");
ImGui.Separator();
ImGui.Text("Lights:");
world.Each((Entity e, ref Light light) =>
{
ImGui.Text($" {e.Name()}: {light.Type} I={light.Intensity:F1}");
});
ImGui.End();
ImGui.Render();
} }
renderer.RenderWorld(world); renderer.RenderWorld(world);
queue.CompletePendingScreenshots();
frames++; frames++;
if (timing.TotalTime - lastFpsTime >= 1.0) if (timing.TotalTime - lastFpsTime >= 1.0)
{ {
currentFps = frames;
Console.WriteLine($"FPS: {frames}, Delta: {timing.DeltaTime * 1000.0:F2} ms"); Console.WriteLine($"FPS: {frames}, Delta: {timing.DeltaTime * 1000.0:F2} ms");
frames = 0; frames = 0;
lastFpsTime = timing.TotalTime; lastFpsTime = timing.TotalTime;
@@ -303,181 +50,13 @@ class Program
} }
Console.WriteLine("Shutting down..."); Console.WriteLine("Shutting down...");
imGuiLayer?.Dispose();
#if !RELEASE_AOT
if (mcpApp != null)
await mcpApp.StopAsync();
if (mcpTask != null)
await mcpTask;
#endif
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine($"Fatal error: {ex}"); Console.WriteLine($"Fatal error: {ex}");
Environment.Exit(1); Environment.Exit(1);
} }
}
private static void CreateDemoScene(World world, Mesh mesh) await Task.CompletedTask;
{
var sphere = ProceduralMesh.CreateSphere(0.5f, 32, 16, new Vector3(0.8f, 0.8f, 0.8f));
var torusKnot = ObjLoader.Load("Content/torusknot.obj", new Vector3(0.8f, 0.8f, 0.8f));
var cubes = new (string name, Vector3 pos, Vector3 color, float scale, float rough, float metal)[]
{
("CubeCenter", new Vector3(0, 5f, 0), new Vector3(0.9f, 0.6f, 0.3f), 0.5f, 0.3f, 0.1f),
("CubeRed", new Vector3(0.3f, 7f, 0.3f), new Vector3(0.85f, 0.15f, 0.15f), 0.5f, 0.4f, 0.2f),
("CubeGreen", new Vector3(-0.3f, 9f, -0.3f), new Vector3(0.2f, 0.8f, 0.3f), 0.5f, 0.5f, 0.0f),
("CubeBlue", new Vector3(0.1f, 11f, 0.1f), new Vector3(0.2f, 0.4f, 0.9f), 0.6f, 0.2f, 0.3f),
("CubeYellow", new Vector3(-0.2f, 13f, 0.2f), new Vector3(0.95f, 0.85f, 0.2f), 0.5f, 0.6f, 0.0f),
("CubeOrange", new Vector3(0.15f, 15f, -0.1f), new Vector3(0.95f, 0.5f, 0.1f), 0.45f, 0.5f, 0.1f),
};
foreach (var (name, pos, color, scale, rough, metal) in cubes)
{
world.Entity(name)
.Set(new Transform(pos, Quaternion.Identity, new Vector3(scale)))
.Set(mesh)
.Set(new Material(color, roughness: rough, metallic: metal))
.Set(RigidBody.DynamicBox(new Vector3(scale * 0.5f), mass: scale * 2f));
}
var spheres = new (string name, Vector3 pos, Vector3 color, float scale, float rough, float metal)[]
{
("SphereGold", new Vector3(3, 6f, -2), new Vector3(1.0f, 0.85f, 0.4f), 1.0f, 0.1f, 1.0f),
("SphereChrome", new Vector3(-3, 8f, 0), new Vector3(0.9f, 0.9f, 0.95f), 1.0f, 0.05f, 1.0f),
("SphereRed", new Vector3(3, 10f, 2), new Vector3(0.9f, 0.1f, 0.1f), 1.0f, 0.4f, 0.0f),
};
foreach (var (name, pos, color, scale, rough, metal) in spheres)
{
world.Entity(name)
.Set(new Transform(pos, Quaternion.Identity, new Vector3(scale)))
.Set(sphere)
.Set(new Material(color, roughness: rough, metallic: metal))
.Set(RigidBody.DynamicSphere(scale * 0.5f, mass: scale));
}
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));
world.Entity("Floor")
.Set(new Transform(new Vector3(0, -0.5f, 0), Quaternion.Identity, new Vector3(20, 0.5f, 20)))
.Set(mesh)
.Set(new Material(new Vector3(0.45f, 0.45f, 0.5f), roughness: 0.8f, metallic: 0.0f))
.Set(RigidBody.StaticPlane(20f));
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)))
.Set(new Material(new Vector3(0.5f, 0.5f, 0.55f), roughness: 0.9f, metallic: 0.0f));
}
private static void CreateCalibrationScene(World world, Mesh mesh)
{
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)),
("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)
{
world.Entity(name)
.Set(new Transform(pos, Quaternion.Identity, new Vector3(0.5f)))
.Set(mesh)
.Set(new Material(color, roughness: 0.5f, metallic: 0.1f));
}
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)))
.Set(new Material(new Vector3(0.5f, 0.5f, 0.55f), roughness: 0.9f, metallic: 0.0f));
}
private static Mesh LoadModel(string path)
{
return path.EndsWith(".gltf", StringComparison.OrdinalIgnoreCase)
|| path.EndsWith(".glb", StringComparison.OrdinalIgnoreCase)
? GltfLoader.Load(path, new Vector3(0.7f, 0.6f, 0.5f))
: ObjLoader.Load(path, new Vector3(0.7f, 0.6f, 0.5f));
}
private static (string modelPath, int mcpPort) ParseArgs(string[] args)
{
var modelPath = FindModelPath(args);
var mcpPort = 5000;
for (var i = 0; i < args.Length; i++)
{
if (args[i] == "--mcp-port" && i + 1 < args.Length && int.TryParse(args[i + 1], out var port))
{
mcpPort = port;
break;
}
}
return (modelPath, mcpPort);
}
private static string FindModelPath(string[] args)
{
for (var i = 0; i < args.Length; i++)
{
var arg = args[i];
if (arg == "--mcp-port")
{
i++;
continue;
}
if (File.Exists(arg))
return arg;
}
var candidates = new[]
{
"Content/cube.obj",
"Models/cube.obj",
"cube.obj"
};
foreach (var candidate in candidates)
{
if (File.Exists(candidate))
return candidate;
}
throw new FileNotFoundException("No model file found. Pass a .obj/.gltf/.glb path as argument or place Content/cube.obj next to the executable.");
}
private static void RunMcpStdioServer()
{
Console.WriteLine("Starting headless stdio MCP server...");
using var world = World.Create();
var processor = new AiCommandProcessor(world, LoadModel, _ => { });
var server = new Engine.AI.Stdio.McpStdioServer(processor);
server.Run();
}
private readonly record struct CameraPose(string Name, Vector3 Position, Vector3 Target, Vector3 Up, float Fov = MathF.PI / 12.0f);
private static void SetCameraPose(Entity cameraEntity, CameraPose pose)
{
ref var camera = ref cameraEntity.Ensure<Camera>();
camera.Position = pose.Position;
camera.Target = pose.Target;
camera.Up = pose.Up;
camera.FieldOfView = pose.Fov;
cameraEntity.Set(camera);
Console.WriteLine($"Camera pose '{pose.Name}': pos={pose.Position}, target={pose.Target}, up={pose.Up}, fov={pose.Fov * 180f / MathF.PI:F0}°");
} }
} }
+11
View File
@@ -1,6 +1,7 @@
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading.Tasks;
using SDL; using SDL;
namespace Engine.Core; namespace Engine.Core;
@@ -34,6 +35,8 @@ public sealed unsafe class Sdl3Window : IWindow
var flags = SDL_WindowFlags.SDL_WINDOW_RESIZABLE; var flags = SDL_WindowFlags.SDL_WINDOW_RESIZABLE;
if (vulkanSurface) if (vulkanSurface)
flags |= SDL_WindowFlags.SDL_WINDOW_VULKAN; flags |= SDL_WindowFlags.SDL_WINDOW_VULKAN;
// Keep the window on top of the terminal at startup so it is actually visible.
flags |= SDL_WindowFlags.SDL_WINDOW_ALWAYS_ON_TOP;
var titleBytes = Encoding.UTF8.GetBytes(title + '\0'); var titleBytes = Encoding.UTF8.GetBytes(title + '\0');
fixed (byte* titlePtr = titleBytes) fixed (byte* titlePtr = titleBytes)
@@ -51,6 +54,14 @@ public sealed unsafe class Sdl3Window : IWindow
// the window. Pump events to flush the show request without blocking. // the window. Pump events to flush the show request without blocking.
SDL_Event flushEvt; SDL_Event flushEvt;
while (SDL3.SDL_PollEvent(&flushEvt)) { } while (SDL3.SDL_PollEvent(&flushEvt)) { }
// Release always-on-top after a short delay so the user can focus other windows.
var window = _window;
Task.Run(() =>
{
System.Threading.Thread.Sleep(1000);
SDL3.SDL_SetWindowAlwaysOnTop(window, false);
});
} }
public void PumpEvents() public void PumpEvents()
@@ -17,20 +17,14 @@
<PublishAot>true</PublishAot> <PublishAot>true</PublishAot>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<PackageReference Include="ImGui.NET" Version="1.91.6.1" />
<PackageReference Include="Flecs.NET.Debug" Version="4.0.4-build.546" Condition="'$(Configuration)' == 'Debug'" />
<PackageReference Include="Flecs.NET.Release" Version="4.0.4-build.546" Condition="'$(Configuration)' == 'Release' OR '$(Configuration)' == 'ReleaseAOT'" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Engine.Core\Engine.Core.csproj" /> <ProjectReference Include="..\Engine.Core\Engine.Core.csproj" />
<ProjectReference Include="..\Engine.Graphics\Engine.Graphics.csproj" /> <ProjectReference Include="..\Engine.Graphics\Engine.Graphics.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="Shaders\*.spv" CopyToOutputDirectory="PreserveNewest"> <Content Include="Shaders\*.spv">
<Link>Shaders\%(Filename)%(Extension)</Link> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
</ItemGroup> </ItemGroup>
-58
View File
@@ -1,58 +0,0 @@
namespace Engine.Graphics.Vulkan;
internal static class PngEncoder
{
public static byte[] EncodeRgbaToPng(byte[] rgba, int width, int height)
{
return EncodeRgbaToBmp(rgba, width, height);
}
private static byte[] EncodeRgbaToBmp(byte[] rgba, int width, int height)
{
var rowSize = width * 4;
var pixelDataSize = rowSize * height;
var fileSize = 54 + pixelDataSize;
var bmp = new byte[fileSize];
bmp[0] = (byte)'B';
bmp[1] = (byte)'M';
WriteUInt32LittleEndian(bmp, 2, (uint)fileSize);
WriteUInt32LittleEndian(bmp, 10, 54u);
WriteUInt32LittleEndian(bmp, 14, 40u);
WriteUInt32LittleEndian(bmp, 18, (uint)width);
WriteUInt32LittleEndian(bmp, 22, (uint)height);
WriteUInt16LittleEndian(bmp, 26, 1);
WriteUInt16LittleEndian(bmp, 28, 32);
WriteUInt32LittleEndian(bmp, 34, (uint)pixelDataSize);
for (var y = 0; y < height; y++)
{
var srcRow = (height - 1 - y) * width * 4;
var dstRow = 54 + y * rowSize;
for (var x = 0; x < width; x++)
{
bmp[dstRow + x * 4 + 0] = rgba[srcRow + x * 4 + 2];
bmp[dstRow + x * 4 + 1] = rgba[srcRow + x * 4 + 1];
bmp[dstRow + x * 4 + 2] = rgba[srcRow + x * 4 + 0];
bmp[dstRow + x * 4 + 3] = rgba[srcRow + x * 4 + 3];
}
}
return bmp;
}
private static void WriteUInt32LittleEndian(byte[] buf, int offset, uint value)
{
buf[offset] = (byte)value;
buf[offset + 1] = (byte)(value >> 8);
buf[offset + 2] = (byte)(value >> 16);
buf[offset + 3] = (byte)(value >> 24);
}
private static void WriteUInt16LittleEndian(byte[] buf, int offset, ushort value)
{
buf[offset] = (byte)value;
buf[offset + 1] = (byte)(value >> 8);
}
}
@@ -1,78 +0,0 @@
#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(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) {
lightDir = normalize(-dirIntensity.xyz);
attenuation = abs(dirIntensity.w);
} else {
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 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);
}
Binary file not shown.
@@ -1,12 +0,0 @@
#version 450
layout(location = 0) in vec2 fragUV;
layout(location = 1) in vec4 fragColor;
layout(set = 0, binding = 0) uniform sampler2D fontTexture;
layout(location = 0) out vec4 outColor;
void main() {
outColor = fragColor * texture(fontTexture, fragUV);
}
Binary file not shown.
@@ -1,19 +0,0 @@
#version 450
layout(location = 0) in vec2 inPos;
layout(location = 1) in vec2 inUV;
layout(location = 2) in vec4 inColor;
layout(push_constant) uniform PushConstants {
vec2 scale;
vec2 translate;
} pc;
layout(location = 0) out vec2 fragUV;
layout(location = 1) out vec4 fragColor;
void main() {
fragUV = inUV;
fragColor = inColor;
gl_Position = vec4(inPos * pc.scale + pc.translate, 0.0, 1.0);
}
Binary file not shown.
@@ -0,0 +1,8 @@
#version 450
layout(location = 0) in vec3 fragColor;
layout(location = 0) out vec4 outColor;
void main() {
outColor = vec4(fragColor, 1.0);
}
Binary file not shown.
@@ -0,0 +1,12 @@
#version 450
layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inColor;
layout(location = 2) in vec3 inNormal;
layout(location = 0) out vec3 fragColor;
void main() {
gl_Position = vec4(inPosition, 1.0);
fragColor = inColor;
}
Binary file not shown.
Binary file not shown.
@@ -1,38 +0,0 @@
#version 450
layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inColor;
layout(location = 2) in vec3 inNormal;
layout(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);
// Vulkan clip space: Y-down, Z [0,1] — convert from OpenGL Y-up, Z [-1,1]
gl_Position.y = -gl_Position.y;
gl_Position.z = (gl_Position.z + gl_Position.w) * 0.5;
fragColor = inColor;
fragNormal = normalize(mat3(pc.model) * inNormal);
fragWorldPos = worldPos.xyz;
fragViewDir = normalize(frame.cameraPosition - worldPos.xyz);
}
+226 -383
View File
@@ -2,408 +2,251 @@ using System.Runtime.InteropServices;
namespace Engine.Graphics.Vulkan; namespace Engine.Graphics.Vulkan;
public static unsafe class Vk internal static unsafe class Vk
{ {
public static VkInstance Instance; public delegate VkResult VkCreateInstance(VkInstanceCreateInfo* pCreateInfo, nint pAllocator, VkInstance* pInstance);
public static VkDevice Device; public delegate void VkDestroyInstance(VkInstance instance, nint pAllocator);
public delegate VkResult VkEnumeratePhysicalDevices(VkInstance instance, uint* pPhysicalDeviceCount, VkPhysicalDevice* pPhysicalDevices);
public delegate void VkGetPhysicalDeviceProperties(VkPhysicalDevice physicalDevice, VkPhysicalDeviceProperties* pProperties);
public delegate void VkGetPhysicalDeviceMemoryProperties(VkPhysicalDevice physicalDevice, VkPhysicalDeviceMemoryProperties* pMemoryProperties);
public delegate void VkGetPhysicalDeviceQueueFamilyProperties(VkPhysicalDevice physicalDevice, uint* pQueueFamilyPropertyCount, VkQueueFamilyProperties* pQueueFamilyProperties);
public delegate VkResult VkGetPhysicalDeviceSurfaceSupportKHR(VkPhysicalDevice physicalDevice, uint queueFamilyIndex, VkSurfaceKHR surface, VkBool32* pSupported);
public delegate VkResult VkGetPhysicalDeviceSurfaceCapabilitiesKHR(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, VkSurfaceCapabilitiesKHR* pSurfaceCapabilities);
public delegate VkResult VkGetPhysicalDeviceSurfaceFormatsKHR(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, uint* pSurfaceFormatCount, VkSurfaceFormatKHR* pSurfaceFormats);
public delegate VkResult VkGetPhysicalDeviceSurfacePresentModesKHR(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, uint* pPresentModeCount, VkPresentModeKHR* pPresentModes);
public delegate VkResult VkCreateDevice(VkPhysicalDevice physicalDevice, VkDeviceCreateInfo* pCreateInfo, nint pAllocator, VkDevice* pDevice);
public delegate void VkDestroyDevice(VkDevice device, nint pAllocator);
public delegate void VkDestroySurfaceKHR(VkInstance instance, VkSurfaceKHR surface, nint pAllocator);
public delegate nint VkGetDeviceProcAddr(VkDevice device, byte* pName);
public static PFN_vkCreateInstance vkCreateInstance; public delegate void VkGetDeviceQueue(VkDevice device, uint queueFamilyIndex, uint queueIndex, VkQueue* pQueue);
public static PFN_vkDestroyInstance vkDestroyInstance; public delegate VkResult VkCreateSwapchainKHR(VkDevice device, VkSwapchainCreateInfoKHR* pCreateInfo, nint pAllocator, VkSwapchainKHR* pSwapchain);
public static PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices; public delegate void VkDestroySwapchainKHR(VkDevice device, VkSwapchainKHR swapchain, nint pAllocator);
public static PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties; public delegate VkResult VkGetSwapchainImagesKHR(VkDevice device, VkSwapchainKHR swapchain, uint* pSwapchainImageCount, VkImage* pSwapchainImages);
public static PFN_vkGetPhysicalDeviceQueueFamilyProperties vkGetPhysicalDeviceQueueFamilyProperties; public delegate VkResult VkCreateImageView(VkDevice device, VkImageViewCreateInfo* pCreateInfo, nint pAllocator, VkImageView* pImageView);
public static PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties; public delegate void VkDestroyImageView(VkDevice device, VkImageView imageView, nint pAllocator);
public static PFN_vkEnumerateDeviceExtensionProperties vkEnumerateDeviceExtensionProperties; public delegate VkResult VkCreateShaderModule(VkDevice device, VkShaderModuleCreateInfo* pCreateInfo, nint pAllocator, VkShaderModule* pShaderModule);
public static PFN_vkCreateDevice vkCreateDevice; public delegate void VkDestroyShaderModule(VkDevice device, VkShaderModule shaderModule, nint pAllocator);
public static PFN_vkDestroyDevice vkDestroyDevice; public delegate VkResult VkCreatePipelineLayout(VkDevice device, VkPipelineLayoutCreateInfo* pCreateInfo, nint pAllocator, VkPipelineLayout* pPipelineLayout);
public static PFN_vkGetDeviceQueue vkGetDeviceQueue; public delegate void VkDestroyPipelineLayout(VkDevice device, VkPipelineLayout pipelineLayout, nint pAllocator);
public static PFN_vkCreateSwapchainKHR vkCreateSwapchainKHR; public delegate VkResult VkCreateGraphicsPipelines(VkDevice device, nint pipelineCache, uint createInfoCount, VkGraphicsPipelineCreateInfo* pCreateInfos, nint pAllocator, VkPipeline* pPipelines);
public static PFN_vkDestroySwapchainKHR vkDestroySwapchainKHR; public delegate void VkDestroyPipeline(VkDevice device, VkPipeline pipeline, nint pAllocator);
public static PFN_vkGetSwapchainImagesKHR vkGetSwapchainImagesKHR; public delegate VkResult VkCreateCommandPool(VkDevice device, VkCommandPoolCreateInfo* pCreateInfo, nint pAllocator, VkCommandPool* pCommandPool);
public static PFN_vkCreateImageView vkCreateImageView; public delegate void VkDestroyCommandPool(VkDevice device, VkCommandPool commandPool, nint pAllocator);
public static PFN_vkDestroyImageView vkDestroyImageView; public delegate VkResult VkAllocateCommandBuffers(VkDevice device, VkCommandBufferAllocateInfo* pAllocateInfo, VkCommandBuffer* pCommandBuffers);
public static PFN_vkCreateImage vkCreateImage; public delegate void VkFreeCommandBuffers(VkDevice device, VkCommandPool commandPool, uint commandBufferCount, VkCommandBuffer* pCommandBuffers);
public static PFN_vkDestroyImage vkDestroyImage; public delegate VkResult VkBeginCommandBuffer(VkCommandBuffer commandBuffer, VkCommandBufferBeginInfo* pBeginInfo);
public static PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements; public delegate VkResult VkEndCommandBuffer(VkCommandBuffer commandBuffer);
public static PFN_vkBindImageMemory vkBindImageMemory; public delegate VkResult VkResetCommandBuffer(VkCommandBuffer commandBuffer, uint flags);
public static PFN_vkCreateRenderPass vkCreateRenderPass; public delegate VkResult VkCreateSemaphore(VkDevice device, VkSemaphoreCreateInfo* pCreateInfo, nint pAllocator, VkSemaphore* pSemaphore);
public static PFN_vkDestroyRenderPass vkDestroyRenderPass; public delegate void VkDestroySemaphore(VkDevice device, VkSemaphore semaphore, nint pAllocator);
public static PFN_vkCreateFramebuffer vkCreateFramebuffer; public delegate VkResult VkCreateFence(VkDevice device, VkFenceCreateInfo* pCreateInfo, nint pAllocator, VkFence* pFence);
public static PFN_vkDestroyFramebuffer vkDestroyFramebuffer; public delegate void VkDestroyFence(VkDevice device, VkFence fence, nint pAllocator);
public static PFN_vkCreateShaderModule vkCreateShaderModule; public delegate VkResult VkResetFences(VkDevice device, uint fenceCount, VkFence* pFences);
public static PFN_vkDestroyShaderModule vkDestroyShaderModule; public delegate VkResult VkWaitForFences(VkDevice device, uint fenceCount, VkFence* pFences, VkBool32 waitAll, ulong timeout);
public static PFN_vkCreateDescriptorSetLayout vkCreateDescriptorSetLayout; public delegate VkResult VkGetFenceStatus(VkDevice device, VkFence fence);
public static PFN_vkDestroyDescriptorSetLayout vkDestroyDescriptorSetLayout; public delegate VkResult VkCreateBuffer(VkDevice device, VkBufferCreateInfo* pCreateInfo, nint pAllocator, VkBuffer* pBuffer);
public static PFN_vkCreatePipelineLayout vkCreatePipelineLayout; public delegate void VkDestroyBuffer(VkDevice device, VkBuffer buffer, nint pAllocator);
public static PFN_vkDestroyPipelineLayout vkDestroyPipelineLayout; public delegate VkResult VkAllocateMemory(VkDevice device, VkMemoryAllocateInfo* pAllocateInfo, nint pAllocator, VkDeviceMemory* pMemory);
public static PFN_vkCreateGraphicsPipelines vkCreateGraphicsPipelines; public delegate void VkFreeMemory(VkDevice device, VkDeviceMemory memory, nint pAllocator);
public static PFN_vkDestroyPipeline vkDestroyPipeline; public delegate VkResult VkBindBufferMemory(VkDevice device, VkBuffer buffer, VkDeviceMemory memory, ulong memoryOffset);
public static PFN_vkCreateDescriptorPool vkCreateDescriptorPool; public delegate void VkGetBufferMemoryRequirements(VkDevice device, VkBuffer buffer, VkMemoryRequirements* pMemoryRequirements);
public static PFN_vkDestroyDescriptorPool vkDestroyDescriptorPool; public delegate VkResult VkMapMemory(VkDevice device, VkDeviceMemory memory, ulong offset, ulong size, uint flags, void** ppData);
public static PFN_vkAllocateDescriptorSets vkAllocateDescriptorSets; public delegate void VkUnmapMemory(VkDevice device, VkDeviceMemory memory);
public static PFN_vkUpdateDescriptorSets vkUpdateDescriptorSets; public delegate void VkCmdBindPipeline(VkCommandBuffer commandBuffer, VkPipelineBindPoint pipelineBindPoint, VkPipeline pipeline);
public static PFN_vkCreateBuffer vkCreateBuffer; public delegate void VkCmdSetViewport(VkCommandBuffer commandBuffer, uint firstViewport, uint viewportCount, VkViewport* pViewports);
public static PFN_vkDestroyBuffer vkDestroyBuffer; public delegate void VkCmdSetScissor(VkCommandBuffer commandBuffer, uint firstScissor, uint scissorCount, VkRect2D* pScissors);
public static PFN_vkGetBufferMemoryRequirements vkGetBufferMemoryRequirements; public delegate void VkCmdBindVertexBuffers(VkCommandBuffer commandBuffer, uint firstBinding, uint bindingCount, VkBuffer* pBuffers, ulong* pOffsets);
public static PFN_vkBindBufferMemory vkBindBufferMemory; public delegate void VkCmdDraw(VkCommandBuffer commandBuffer, uint vertexCount, uint instanceCount, uint firstVertex, uint firstInstance);
public static PFN_vkAllocateMemory vkAllocateMemory; public delegate void VkCmdBeginRendering(VkCommandBuffer commandBuffer, VkRenderingInfo* pRenderingInfo);
public static PFN_vkFreeMemory vkFreeMemory; public delegate void VkCmdEndRendering(VkCommandBuffer commandBuffer);
public static PFN_vkMapMemory vkMapMemory; public delegate void VkCmdPipelineBarrier2(VkCommandBuffer commandBuffer, VkDependencyInfo* pDependencyInfo);
public static PFN_vkUnmapMemory vkUnmapMemory; public delegate void VkCmdCopyBuffer(VkCommandBuffer commandBuffer, VkBuffer srcBuffer, VkBuffer dstBuffer, uint regionCount, VkBufferCopy* pRegions);
public static PFN_vkCreateCommandPool vkCreateCommandPool; public delegate VkResult VkAcquireNextImageKHR(VkDevice device, VkSwapchainKHR swapchain, ulong timeout, VkSemaphore semaphore, VkFence fence, uint* pImageIndex);
public static PFN_vkDestroyCommandPool vkDestroyCommandPool; public delegate VkResult VkQueueSubmit2(VkQueue queue, uint submitCount, VkSubmitInfo2* pSubmits, VkFence fence);
public static PFN_vkAllocateCommandBuffers vkAllocateCommandBuffers; public delegate VkResult VkQueuePresentKHR(VkQueue queue, VkPresentInfoKHR* pPresentInfo);
public static PFN_vkFreeCommandBuffers vkFreeCommandBuffers; public delegate VkResult VkDeviceWaitIdle(VkDevice device);
public static PFN_vkBeginCommandBuffer vkBeginCommandBuffer; public delegate VkResult VkQueueWaitIdle(VkQueue queue);
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_vkCmdCopyImageToBuffer vkCmdCopyImageToBuffer;
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() public delegate VkResult VkCreateDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, nint pAllocator, VkDebugUtilsMessengerEXT* pMessenger);
{ public delegate void VkDestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT messenger, nint pAllocator);
VulkanNative.LoadLibrary();
var libHandle = VulkanNative.LoadLibrary();
var createInstancePtr = NativeLibrary.GetExport(libHandle, "vkCreateInstance"); public static VkCreateInstance vkCreateInstance;
vkCreateInstance = Marshal.GetDelegateForFunctionPointer<PFN_vkCreateInstance>(createInstancePtr); public static VkDestroyInstance vkDestroyInstance;
} public static VkEnumeratePhysicalDevices vkEnumeratePhysicalDevices;
public static VkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties;
public static VkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties;
public static VkGetPhysicalDeviceQueueFamilyProperties vkGetPhysicalDeviceQueueFamilyProperties;
public static VkGetPhysicalDeviceSurfaceSupportKHR vkGetPhysicalDeviceSurfaceSupportKHR;
public static VkGetPhysicalDeviceSurfaceCapabilitiesKHR vkGetPhysicalDeviceSurfaceCapabilitiesKHR;
public static VkGetPhysicalDeviceSurfaceFormatsKHR vkGetPhysicalDeviceSurfaceFormatsKHR;
public static VkGetPhysicalDeviceSurfacePresentModesKHR vkGetPhysicalDeviceSurfacePresentModesKHR;
public static VkCreateDevice vkCreateDevice;
public static VkDestroyDevice vkDestroyDevice;
public static VkDestroySurfaceKHR vkDestroySurfaceKHR;
public static VkGetDeviceProcAddr vkGetDeviceProcAddr;
public static VkGetDeviceQueue vkGetDeviceQueue;
public static VkCreateSwapchainKHR vkCreateSwapchainKHR;
public static VkDestroySwapchainKHR vkDestroySwapchainKHR;
public static VkGetSwapchainImagesKHR vkGetSwapchainImagesKHR;
public static VkCreateImageView vkCreateImageView;
public static VkDestroyImageView vkDestroyImageView;
public static VkCreateShaderModule vkCreateShaderModule;
public static VkDestroyShaderModule vkDestroyShaderModule;
public static VkCreatePipelineLayout vkCreatePipelineLayout;
public static VkDestroyPipelineLayout vkDestroyPipelineLayout;
public static VkCreateGraphicsPipelines vkCreateGraphicsPipelines;
public static VkDestroyPipeline vkDestroyPipeline;
public static VkCreateCommandPool vkCreateCommandPool;
public static VkDestroyCommandPool vkDestroyCommandPool;
public static VkAllocateCommandBuffers vkAllocateCommandBuffers;
public static VkFreeCommandBuffers vkFreeCommandBuffers;
public static VkBeginCommandBuffer vkBeginCommandBuffer;
public static VkEndCommandBuffer vkEndCommandBuffer;
public static VkResetCommandBuffer vkResetCommandBuffer;
public static VkCreateSemaphore vkCreateSemaphore;
public static VkDestroySemaphore vkDestroySemaphore;
public static VkCreateFence vkCreateFence;
public static VkDestroyFence vkDestroyFence;
public static VkResetFences vkResetFences;
public static VkWaitForFences vkWaitForFences;
public static VkGetFenceStatus vkGetFenceStatus;
public static VkCreateBuffer vkCreateBuffer;
public static VkDestroyBuffer vkDestroyBuffer;
public static VkAllocateMemory vkAllocateMemory;
public static VkFreeMemory vkFreeMemory;
public static VkBindBufferMemory vkBindBufferMemory;
public static VkGetBufferMemoryRequirements vkGetBufferMemoryRequirements;
public static VkMapMemory vkMapMemory;
public static VkUnmapMemory vkUnmapMemory;
public static VkCmdBindPipeline vkCmdBindPipeline;
public static VkCmdSetViewport vkCmdSetViewport;
public static VkCmdSetScissor vkCmdSetScissor;
public static VkCmdBindVertexBuffers vkCmdBindVertexBuffers;
public static VkCmdDraw vkCmdDraw;
public static VkCmdBeginRendering vkCmdBeginRendering;
public static VkCmdEndRendering vkCmdEndRendering;
public static VkCmdPipelineBarrier2 vkCmdPipelineBarrier2;
public static VkCmdCopyBuffer vkCmdCopyBuffer;
public static VkAcquireNextImageKHR vkAcquireNextImageKHR;
public static VkQueueSubmit2 vkQueueSubmit2;
public static VkQueuePresentKHR vkQueuePresentKHR;
public static VkDeviceWaitIdle vkDeviceWaitIdle;
public static VkQueueWaitIdle vkQueueWaitIdle;
public static VkCreateDebugUtilsMessengerEXT vkCreateDebugUtilsMessengerEXT;
public static VkDestroyDebugUtilsMessengerEXT vkDestroyDebugUtilsMessengerEXT;
public static void LoadInstanceFunctions(VkInstance instance) public static void LoadInstanceFunctions(VkInstance instance)
{ {
Instance = instance; var p = instance.Handle;
vkDestroyInstance = Load<VkDestroyInstance>(p, "vkDestroyInstance");
vkDestroyInstance = VulkanNative.LoadInstanceFunction<PFN_vkDestroyInstance>(instance, "vkDestroyInstance"); vkEnumeratePhysicalDevices = Load<VkEnumeratePhysicalDevices>(p, "vkEnumeratePhysicalDevices");
vkEnumeratePhysicalDevices = VulkanNative.LoadInstanceFunction<PFN_vkEnumeratePhysicalDevices>(instance, "vkEnumeratePhysicalDevices"); vkGetPhysicalDeviceProperties = Load<VkGetPhysicalDeviceProperties>(p, "vkGetPhysicalDeviceProperties");
vkGetPhysicalDeviceProperties = VulkanNative.LoadInstanceFunction<PFN_vkGetPhysicalDeviceProperties>(instance, "vkGetPhysicalDeviceProperties"); vkGetPhysicalDeviceMemoryProperties = Load<VkGetPhysicalDeviceMemoryProperties>(p, "vkGetPhysicalDeviceMemoryProperties");
vkGetPhysicalDeviceQueueFamilyProperties = VulkanNative.LoadInstanceFunction<PFN_vkGetPhysicalDeviceQueueFamilyProperties>(instance, "vkGetPhysicalDeviceQueueFamilyProperties"); vkGetPhysicalDeviceQueueFamilyProperties = Load<VkGetPhysicalDeviceQueueFamilyProperties>(p, "vkGetPhysicalDeviceQueueFamilyProperties");
vkGetPhysicalDeviceMemoryProperties = VulkanNative.LoadInstanceFunction<PFN_vkGetPhysicalDeviceMemoryProperties>(instance, "vkGetPhysicalDeviceMemoryProperties"); vkGetPhysicalDeviceSurfaceSupportKHR = Load<VkGetPhysicalDeviceSurfaceSupportKHR>(p, "vkGetPhysicalDeviceSurfaceSupportKHR");
vkEnumerateDeviceExtensionProperties = VulkanNative.LoadInstanceFunction<PFN_vkEnumerateDeviceExtensionProperties>(instance, "vkEnumerateDeviceExtensionProperties"); vkGetPhysicalDeviceSurfaceCapabilitiesKHR = Load<VkGetPhysicalDeviceSurfaceCapabilitiesKHR>(p, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR");
vkCreateDevice = VulkanNative.LoadInstanceFunction<PFN_vkCreateDevice>(instance, "vkCreateDevice"); vkGetPhysicalDeviceSurfaceFormatsKHR = Load<VkGetPhysicalDeviceSurfaceFormatsKHR>(p, "vkGetPhysicalDeviceSurfaceFormatsKHR");
vkGetPhysicalDeviceSurfaceSupportKHR = VulkanNative.LoadInstanceFunction<PFN_vkGetPhysicalDeviceSurfaceSupportKHR>(instance, "vkGetPhysicalDeviceSurfaceSupportKHR"); vkGetPhysicalDeviceSurfacePresentModesKHR = Load<VkGetPhysicalDeviceSurfacePresentModesKHR>(p, "vkGetPhysicalDeviceSurfacePresentModesKHR");
vkGetPhysicalDeviceSurfaceCapabilitiesKHR = VulkanNative.LoadInstanceFunction<PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR>(instance, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR"); vkCreateDevice = Load<VkCreateDevice>(p, "vkCreateDevice");
vkGetPhysicalDeviceSurfaceFormatsKHR = VulkanNative.LoadInstanceFunction<PFN_vkGetPhysicalDeviceSurfaceFormatsKHR>(instance, "vkGetPhysicalDeviceSurfaceFormatsKHR"); vkDestroyDevice = Load<VkDestroyDevice>(p, "vkDestroyDevice");
vkGetPhysicalDeviceSurfacePresentModesKHR = VulkanNative.LoadInstanceFunction<PFN_vkGetPhysicalDeviceSurfacePresentModesKHR>(instance, "vkGetPhysicalDeviceSurfacePresentModesKHR"); vkDestroySurfaceKHR = Load<VkDestroySurfaceKHR>(p, "vkDestroySurfaceKHR");
vkDestroySurfaceKHR = VulkanNative.LoadInstanceFunction<PFN_vkDestroySurfaceKHR>(instance, "vkDestroySurfaceKHR"); vkGetDeviceProcAddr = Load<VkGetDeviceProcAddr>(p, "vkGetDeviceProcAddr");
TryLoadDebugUtils(p);
} }
public static void LoadDeviceFunctions(VkDevice device) public static void LoadDeviceFunctions(VkDevice device)
{ {
Device = device; var p = device.Handle;
vkGetDeviceQueue = LoadDev<VkGetDeviceQueue>(p, "vkGetDeviceQueue");
vkDestroyDevice = VulkanNative.LoadDeviceFunction<PFN_vkDestroyDevice>(device, "vkDestroyDevice"); vkCreateSwapchainKHR = LoadDev<VkCreateSwapchainKHR>(p, "vkCreateSwapchainKHR");
vkGetDeviceQueue = VulkanNative.LoadDeviceFunction<PFN_vkGetDeviceQueue>(device, "vkGetDeviceQueue"); vkDestroySwapchainKHR = LoadDev<VkDestroySwapchainKHR>(p, "vkDestroySwapchainKHR");
vkCreateSwapchainKHR = VulkanNative.LoadDeviceFunction<PFN_vkCreateSwapchainKHR>(device, "vkCreateSwapchainKHR"); vkGetSwapchainImagesKHR = LoadDev<VkGetSwapchainImagesKHR>(p, "vkGetSwapchainImagesKHR");
vkDestroySwapchainKHR = VulkanNative.LoadDeviceFunction<PFN_vkDestroySwapchainKHR>(device, "vkDestroySwapchainKHR"); vkCreateImageView = LoadDev<VkCreateImageView>(p, "vkCreateImageView");
vkGetSwapchainImagesKHR = VulkanNative.LoadDeviceFunction<PFN_vkGetSwapchainImagesKHR>(device, "vkGetSwapchainImagesKHR"); vkDestroyImageView = LoadDev<VkDestroyImageView>(p, "vkDestroyImageView");
vkCreateImageView = VulkanNative.LoadDeviceFunction<PFN_vkCreateImageView>(device, "vkCreateImageView"); vkCreateShaderModule = LoadDev<VkCreateShaderModule>(p, "vkCreateShaderModule");
vkDestroyImageView = VulkanNative.LoadDeviceFunction<PFN_vkDestroyImageView>(device, "vkDestroyImageView"); vkDestroyShaderModule = LoadDev<VkDestroyShaderModule>(p, "vkDestroyShaderModule");
vkCreateImage = VulkanNative.LoadDeviceFunction<PFN_vkCreateImage>(device, "vkCreateImage"); vkCreatePipelineLayout = LoadDev<VkCreatePipelineLayout>(p, "vkCreatePipelineLayout");
vkDestroyImage = VulkanNative.LoadDeviceFunction<PFN_vkDestroyImage>(device, "vkDestroyImage"); vkDestroyPipelineLayout = LoadDev<VkDestroyPipelineLayout>(p, "vkDestroyPipelineLayout");
vkGetImageMemoryRequirements = VulkanNative.LoadDeviceFunction<PFN_vkGetImageMemoryRequirements>(device, "vkGetImageMemoryRequirements"); vkCreateGraphicsPipelines = LoadDev<VkCreateGraphicsPipelines>(p, "vkCreateGraphicsPipelines");
vkBindImageMemory = VulkanNative.LoadDeviceFunction<PFN_vkBindImageMemory>(device, "vkBindImageMemory"); vkDestroyPipeline = LoadDev<VkDestroyPipeline>(p, "vkDestroyPipeline");
vkCreateRenderPass = VulkanNative.LoadDeviceFunction<PFN_vkCreateRenderPass>(device, "vkCreateRenderPass"); vkCreateCommandPool = LoadDev<VkCreateCommandPool>(p, "vkCreateCommandPool");
vkDestroyRenderPass = VulkanNative.LoadDeviceFunction<PFN_vkDestroyRenderPass>(device, "vkDestroyRenderPass"); vkDestroyCommandPool = LoadDev<VkDestroyCommandPool>(p, "vkDestroyCommandPool");
vkCreateFramebuffer = VulkanNative.LoadDeviceFunction<PFN_vkCreateFramebuffer>(device, "vkCreateFramebuffer"); vkAllocateCommandBuffers = LoadDev<VkAllocateCommandBuffers>(p, "vkAllocateCommandBuffers");
vkDestroyFramebuffer = VulkanNative.LoadDeviceFunction<PFN_vkDestroyFramebuffer>(device, "vkDestroyFramebuffer"); vkFreeCommandBuffers = LoadDev<VkFreeCommandBuffers>(p, "vkFreeCommandBuffers");
vkCreateShaderModule = VulkanNative.LoadDeviceFunction<PFN_vkCreateShaderModule>(device, "vkCreateShaderModule"); vkBeginCommandBuffer = LoadDev<VkBeginCommandBuffer>(p, "vkBeginCommandBuffer");
vkDestroyShaderModule = VulkanNative.LoadDeviceFunction<PFN_vkDestroyShaderModule>(device, "vkDestroyShaderModule"); vkEndCommandBuffer = LoadDev<VkEndCommandBuffer>(p, "vkEndCommandBuffer");
vkCreateDescriptorSetLayout = VulkanNative.LoadDeviceFunction<PFN_vkCreateDescriptorSetLayout>(device, "vkCreateDescriptorSetLayout"); vkResetCommandBuffer = LoadDev<VkResetCommandBuffer>(p, "vkResetCommandBuffer");
vkDestroyDescriptorSetLayout = VulkanNative.LoadDeviceFunction<PFN_vkDestroyDescriptorSetLayout>(device, "vkDestroyDescriptorSetLayout"); vkCreateSemaphore = LoadDev<VkCreateSemaphore>(p, "vkCreateSemaphore");
vkCreatePipelineLayout = VulkanNative.LoadDeviceFunction<PFN_vkCreatePipelineLayout>(device, "vkCreatePipelineLayout"); vkDestroySemaphore = LoadDev<VkDestroySemaphore>(p, "vkDestroySemaphore");
vkDestroyPipelineLayout = VulkanNative.LoadDeviceFunction<PFN_vkDestroyPipelineLayout>(device, "vkDestroyPipelineLayout"); vkCreateFence = LoadDev<VkCreateFence>(p, "vkCreateFence");
vkCreateGraphicsPipelines = VulkanNative.LoadDeviceFunction<PFN_vkCreateGraphicsPipelines>(device, "vkCreateGraphicsPipelines"); vkDestroyFence = LoadDev<VkDestroyFence>(p, "vkDestroyFence");
vkDestroyPipeline = VulkanNative.LoadDeviceFunction<PFN_vkDestroyPipeline>(device, "vkDestroyPipeline"); vkResetFences = LoadDev<VkResetFences>(p, "vkResetFences");
vkCreateDescriptorPool = VulkanNative.LoadDeviceFunction<PFN_vkCreateDescriptorPool>(device, "vkCreateDescriptorPool"); vkWaitForFences = LoadDev<VkWaitForFences>(p, "vkWaitForFences");
vkDestroyDescriptorPool = VulkanNative.LoadDeviceFunction<PFN_vkDestroyDescriptorPool>(device, "vkDestroyDescriptorPool"); vkGetFenceStatus = LoadDev<VkGetFenceStatus>(p, "vkGetFenceStatus");
vkAllocateDescriptorSets = VulkanNative.LoadDeviceFunction<PFN_vkAllocateDescriptorSets>(device, "vkAllocateDescriptorSets"); vkCreateBuffer = LoadDev<VkCreateBuffer>(p, "vkCreateBuffer");
vkUpdateDescriptorSets = VulkanNative.LoadDeviceFunction<PFN_vkUpdateDescriptorSets>(device, "vkUpdateDescriptorSets"); vkDestroyBuffer = LoadDev<VkDestroyBuffer>(p, "vkDestroyBuffer");
vkCreateBuffer = VulkanNative.LoadDeviceFunction<PFN_vkCreateBuffer>(device, "vkCreateBuffer"); vkAllocateMemory = LoadDev<VkAllocateMemory>(p, "vkAllocateMemory");
vkDestroyBuffer = VulkanNative.LoadDeviceFunction<PFN_vkDestroyBuffer>(device, "vkDestroyBuffer"); vkFreeMemory = LoadDev<VkFreeMemory>(p, "vkFreeMemory");
vkGetBufferMemoryRequirements = VulkanNative.LoadDeviceFunction<PFN_vkGetBufferMemoryRequirements>(device, "vkGetBufferMemoryRequirements"); vkBindBufferMemory = LoadDev<VkBindBufferMemory>(p, "vkBindBufferMemory");
vkBindBufferMemory = VulkanNative.LoadDeviceFunction<PFN_vkBindBufferMemory>(device, "vkBindBufferMemory"); vkGetBufferMemoryRequirements = LoadDev<VkGetBufferMemoryRequirements>(p, "vkGetBufferMemoryRequirements");
vkAllocateMemory = VulkanNative.LoadDeviceFunction<PFN_vkAllocateMemory>(device, "vkAllocateMemory"); vkMapMemory = LoadDev<VkMapMemory>(p, "vkMapMemory");
vkFreeMemory = VulkanNative.LoadDeviceFunction<PFN_vkFreeMemory>(device, "vkFreeMemory"); vkUnmapMemory = LoadDev<VkUnmapMemory>(p, "vkUnmapMemory");
vkMapMemory = VulkanNative.LoadDeviceFunction<PFN_vkMapMemory>(device, "vkMapMemory"); vkCmdBindPipeline = LoadDev<VkCmdBindPipeline>(p, "vkCmdBindPipeline");
vkUnmapMemory = VulkanNative.LoadDeviceFunction<PFN_vkUnmapMemory>(device, "vkUnmapMemory"); vkCmdSetViewport = LoadDev<VkCmdSetViewport>(p, "vkCmdSetViewport");
vkCreateCommandPool = VulkanNative.LoadDeviceFunction<PFN_vkCreateCommandPool>(device, "vkCreateCommandPool"); vkCmdSetScissor = LoadDev<VkCmdSetScissor>(p, "vkCmdSetScissor");
vkDestroyCommandPool = VulkanNative.LoadDeviceFunction<PFN_vkDestroyCommandPool>(device, "vkDestroyCommandPool"); vkCmdBindVertexBuffers = LoadDev<VkCmdBindVertexBuffers>(p, "vkCmdBindVertexBuffers");
vkAllocateCommandBuffers = VulkanNative.LoadDeviceFunction<PFN_vkAllocateCommandBuffers>(device, "vkAllocateCommandBuffers"); vkCmdDraw = LoadDev<VkCmdDraw>(p, "vkCmdDraw");
vkFreeCommandBuffers = VulkanNative.LoadDeviceFunction<PFN_vkFreeCommandBuffers>(device, "vkFreeCommandBuffers"); vkCmdBeginRendering = LoadDev<VkCmdBeginRendering>(p, "vkCmdBeginRendering");
vkBeginCommandBuffer = VulkanNative.LoadDeviceFunction<PFN_vkBeginCommandBuffer>(device, "vkBeginCommandBuffer"); vkCmdEndRendering = LoadDev<VkCmdEndRendering>(p, "vkCmdEndRendering");
vkEndCommandBuffer = VulkanNative.LoadDeviceFunction<PFN_vkEndCommandBuffer>(device, "vkEndCommandBuffer"); vkCmdPipelineBarrier2 = LoadDev<VkCmdPipelineBarrier2>(p, "vkCmdPipelineBarrier2");
vkResetCommandBuffer = VulkanNative.LoadDeviceFunction<PFN_vkResetCommandBuffer>(device, "vkResetCommandBuffer"); vkCmdCopyBuffer = LoadDev<VkCmdCopyBuffer>(p, "vkCmdCopyBuffer");
vkQueueSubmit = VulkanNative.LoadDeviceFunction<PFN_vkQueueSubmit>(device, "vkQueueSubmit"); vkAcquireNextImageKHR = LoadDev<VkAcquireNextImageKHR>(p, "vkAcquireNextImageKHR");
vkQueueWaitIdle = VulkanNative.LoadDeviceFunction<PFN_vkQueueWaitIdle>(device, "vkQueueWaitIdle"); vkQueueSubmit2 = LoadDev<VkQueueSubmit2>(p, "vkQueueSubmit2");
vkQueuePresentKHR = VulkanNative.LoadDeviceFunction<PFN_vkQueuePresentKHR>(device, "vkQueuePresentKHR"); vkQueuePresentKHR = LoadDev<VkQueuePresentKHR>(p, "vkQueuePresentKHR");
vkAcquireNextImageKHR = VulkanNative.LoadDeviceFunction<PFN_vkAcquireNextImageKHR>(device, "vkAcquireNextImageKHR"); vkDeviceWaitIdle = LoadDev<VkDeviceWaitIdle>(p, "vkDeviceWaitIdle");
vkCreateSemaphore = VulkanNative.LoadDeviceFunction<PFN_vkCreateSemaphore>(device, "vkCreateSemaphore"); vkQueueWaitIdle = LoadDev<VkQueueWaitIdle>(p, "vkQueueWaitIdle");
vkDestroySemaphore = VulkanNative.LoadDeviceFunction<PFN_vkDestroySemaphore>(device, "vkDestroySemaphore");
vkCreateFence = VulkanNative.LoadDeviceFunction<PFN_vkCreateFence>(device, "vkCreateFence");
vkDestroyFence = VulkanNative.LoadDeviceFunction<PFN_vkDestroyFence>(device, "vkDestroyFence");
vkWaitForFences = VulkanNative.LoadDeviceFunction<PFN_vkWaitForFences>(device, "vkWaitForFences");
vkResetFences = VulkanNative.LoadDeviceFunction<PFN_vkResetFences>(device, "vkResetFences");
vkCmdBeginRenderPass = VulkanNative.LoadDeviceFunction<PFN_vkCmdBeginRenderPass>(device, "vkCmdBeginRenderPass");
vkCmdEndRenderPass = VulkanNative.LoadDeviceFunction<PFN_vkCmdEndRenderPass>(device, "vkCmdEndRenderPass");
vkCmdBindPipeline = VulkanNative.LoadDeviceFunction<PFN_vkCmdBindPipeline>(device, "vkCmdBindPipeline");
vkCmdBindDescriptorSets = VulkanNative.LoadDeviceFunction<PFN_vkCmdBindDescriptorSets>(device, "vkCmdBindDescriptorSets");
vkCmdBindVertexBuffers = VulkanNative.LoadDeviceFunction<PFN_vkCmdBindVertexBuffers>(device, "vkCmdBindVertexBuffers");
vkCmdBindIndexBuffer = VulkanNative.LoadDeviceFunction<PFN_vkCmdBindIndexBuffer>(device, "vkCmdBindIndexBuffer");
vkCmdDrawIndexed = VulkanNative.LoadDeviceFunction<PFN_vkCmdDrawIndexed>(device, "vkCmdDrawIndexed");
vkCmdDraw = VulkanNative.LoadDeviceFunction<PFN_vkCmdDraw>(device, "vkCmdDraw");
vkCmdSetViewport = VulkanNative.LoadDeviceFunction<PFN_vkCmdSetViewport>(device, "vkCmdSetViewport");
vkCmdSetScissor = VulkanNative.LoadDeviceFunction<PFN_vkCmdSetScissor>(device, "vkCmdSetScissor");
vkCmdPipelineBarrier = VulkanNative.LoadDeviceFunction<PFN_vkCmdPipelineBarrier>(device, "vkCmdPipelineBarrier");
vkCmdCopyBuffer = VulkanNative.LoadDeviceFunction<PFN_vkCmdCopyBuffer>(device, "vkCmdCopyBuffer");
vkCmdCopyBufferToImage = VulkanNative.LoadDeviceFunction<PFN_vkCmdCopyBufferToImage>(device, "vkCmdCopyBufferToImage");
vkCmdCopyImageToBuffer = VulkanNative.LoadDeviceFunction<PFN_vkCmdCopyImageToBuffer>(device, "vkCmdCopyImageToBuffer");
vkCmdClearColorImage = VulkanNative.LoadDeviceFunction<PFN_vkCmdClearColorImage>(device, "vkCmdClearColorImage");
vkCmdPushConstants = VulkanNative.LoadDeviceFunction<PFN_vkCmdPushConstants>(device, "vkCmdPushConstants");
vkCreateSampler = VulkanNative.LoadDeviceFunction<PFN_vkCreateSampler>(device, "vkCreateSampler");
vkDestroySampler = VulkanNative.LoadDeviceFunction<PFN_vkDestroySampler>(device, "vkDestroySampler");
} }
private static T Load<T>(nint libHandle, string name) where T : Delegate private static void TryLoadDebugUtils(nint instance)
{ {
var ptr = NativeLibrary.GetExport(libHandle, name); try
if (ptr == 0) {
throw new InvalidOperationException($"Failed to load Vulkan function: {name}"); vkCreateDebugUtilsMessengerEXT = Load<VkCreateDebugUtilsMessengerEXT>(instance, "vkCreateDebugUtilsMessengerEXT");
return Marshal.GetDelegateForFunctionPointer<T>(ptr); vkDestroyDebugUtilsMessengerEXT = Load<VkDestroyDebugUtilsMessengerEXT>(instance, "vkDestroyDebugUtilsMessengerEXT");
}
catch
{
vkCreateDebugUtilsMessengerEXT = null!;
vkDestroyDebugUtilsMessengerEXT = null!;
}
} }
public static void CheckResult(VkResult result, string operation) private static T Load<T>(nint instance, string name) where T : Delegate
{ {
if (result != VkResult.Success && result != VkResult.SuboptimalKHR) fixed (byte* pName = VulkanString.ToUtf8Terminated(name))
throw new InvalidOperationException($"Vulkan error {result} during: {operation}"); {
var addr = VulkanNative.vkGetInstanceProcAddr(instance, pName);
if (addr == 0)
throw new EntryPointNotFoundException($"vkGetInstanceProcAddr returned null for: {name}");
return Marshal.GetDelegateForFunctionPointer<T>(addr);
}
} }
public static byte[] ToUtf8NullTerminated(string s) private static T LoadDev<T>(nint device, string name) where T : Delegate
{ {
var bytes = new byte[System.Text.Encoding.UTF8.GetByteCount(s) + 1]; fixed (byte* pName = VulkanString.ToUtf8Terminated(name))
System.Text.Encoding.UTF8.GetBytes(s, 0, s.Length, bytes, 0); {
return bytes; var addr = vkGetDeviceProcAddr(new VkDevice { Handle = device }, pName);
} if (addr == 0)
{
public static byte* AllocUtf8(string s) addr = VulkanNative.vkGetInstanceProcAddr(0, pName);
{ if (addr == 0)
var bytes = ToUtf8NullTerminated(s); throw new EntryPointNotFoundException($"vkGetDeviceProcAddr returned null for: {name}");
var ptr = (byte*)Marshal.AllocHGlobal(bytes.Length); }
Marshal.Copy(bytes, 0, (nint)ptr, bytes.Length); return Marshal.GetDelegateForFunctionPointer<T>(addr);
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 public delegate VkResult PFN_vkCreateInstance(VkInstanceCreateInfo* pCreateInfo, void* pAllocator, VkInstance* pInstance);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkDestroyInstance(VkInstance instance, void* pAllocator);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate VkResult PFN_vkEnumeratePhysicalDevices(VkInstance instance, uint* pPhysicalDeviceCount, VkPhysicalDevice* pPhysicalDevices);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkGetPhysicalDeviceProperties(VkPhysicalDevice physicalDevice, VkPhysicalDeviceProperties* pProperties);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkGetPhysicalDeviceQueueFamilyProperties(VkPhysicalDevice physicalDevice, uint* pQueueFamilyPropertyCount, VkQueueFamilyProperties* pQueueFamilyProperties);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkGetPhysicalDeviceMemoryProperties(VkPhysicalDevice physicalDevice, VkPhysicalDeviceMemoryProperties* pMemoryProperties);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate VkResult PFN_vkEnumerateDeviceExtensionProperties(VkPhysicalDevice physicalDevice, byte* pLayerName, uint* pPropertyCount, VkExtensionProperties* pProperties);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate VkResult PFN_vkCreateDevice(VkPhysicalDevice physicalDevice, VkDeviceCreateInfo* pCreateInfo, void* pAllocator, VkDevice* pDevice);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkDestroyDevice(VkDevice device, void* pAllocator);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkGetDeviceQueue(VkDevice device, uint queueFamilyIndex, uint queueIndex, VkQueue* pQueue);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate VkResult PFN_vkCreateSwapchainKHR(VkDevice device, VkSwapchainCreateInfoKHR* pCreateInfo, void* pAllocator, VkSwapchainKHR* pSwapchain);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkDestroySwapchainKHR(VkDevice device, VkSwapchainKHR swapchain, void* pAllocator);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate VkResult PFN_vkGetSwapchainImagesKHR(VkDevice device, VkSwapchainKHR swapchain, uint* pSwapchainImageCount, VkImage* pSwapchainImages);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate VkResult PFN_vkCreateImageView(VkDevice device, VkImageViewCreateInfo* pCreateInfo, void* pAllocator, VkImageView* pView);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkDestroyImageView(VkDevice device, VkImageView imageView, void* pAllocator);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate VkResult PFN_vkCreateImage(VkDevice device, VkImageCreateInfo* pCreateInfo, void* pAllocator, VkImage* pImage);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkDestroyImage(VkDevice device, VkImage image, void* pAllocator);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkGetImageMemoryRequirements(VkDevice device, VkImage image, void* pMemoryRequirements);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate VkResult PFN_vkBindImageMemory(VkDevice device, VkImage image, VkDeviceMemory memory, ulong memoryOffset);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate VkResult PFN_vkCreateRenderPass(VkDevice device, VkRenderPassCreateInfo* pCreateInfo, void* pAllocator, VkRenderPass* pRenderPass);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkDestroyRenderPass(VkDevice device, VkRenderPass renderPass, void* pAllocator);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate VkResult PFN_vkCreateFramebuffer(VkDevice device, VkFramebufferCreateInfo* pCreateInfo, void* pAllocator, VkFramebuffer* pFramebuffer);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkDestroyFramebuffer(VkDevice device, VkFramebuffer framebuffer, void* pAllocator);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate VkResult PFN_vkCreateShaderModule(VkDevice device, VkShaderModuleCreateInfo* pCreateInfo, void* pAllocator, VkShaderModule* pShaderModule);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkDestroyShaderModule(VkDevice device, VkShaderModule shaderModule, void* pAllocator);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate VkResult PFN_vkCreateDescriptorSetLayout(VkDevice device, VkDescriptorSetLayoutCreateInfo* pCreateInfo, void* pAllocator, VkDescriptorSetLayout* pSetLayout);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkDestroyDescriptorSetLayout(VkDevice device, VkDescriptorSetLayout descriptorSetLayout, void* pAllocator);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate VkResult PFN_vkCreatePipelineLayout(VkDevice device, VkPipelineLayoutCreateInfo* pCreateInfo, void* pAllocator, VkPipelineLayout* pPipelineLayout);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkDestroyPipelineLayout(VkDevice device, VkPipelineLayout pipelineLayout, void* pAllocator);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate VkResult PFN_vkCreateGraphicsPipelines(VkDevice device, ulong pipelineCache, uint createInfoCount, VkGraphicsPipelineCreateInfo* pCreateInfos, void* pAllocator, VkPipeline* pPipelines);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkDestroyPipeline(VkDevice device, VkPipeline pipeline, void* pAllocator);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate VkResult PFN_vkCreateDescriptorPool(VkDevice device, VkDescriptorPoolCreateInfo* pCreateInfo, void* pAllocator, VkDescriptorPool* pDescriptorPool);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkDestroyDescriptorPool(VkDevice device, VkDescriptorPool descriptorPool, void* pAllocator);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate VkResult PFN_vkAllocateDescriptorSets(VkDevice device, VkDescriptorSetAllocateInfo* pAllocateInfo, VkDescriptorSet* pDescriptorSets);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkUpdateDescriptorSets(VkDevice device, uint descriptorWriteCount, VkWriteDescriptorSet* pDescriptorWrites, uint descriptorCopyCount, void* pDescriptorCopies);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate VkResult PFN_vkCreateBuffer(VkDevice device, VkBufferCreateInfo* pCreateInfo, void* pAllocator, VkBuffer* pBuffer);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkDestroyBuffer(VkDevice device, VkBuffer buffer, void* pAllocator);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkGetBufferMemoryRequirements(VkDevice device, VkBuffer buffer, void* pMemoryRequirements);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate VkResult PFN_vkBindBufferMemory(VkDevice device, VkBuffer buffer, VkDeviceMemory memory, ulong memoryOffset);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate VkResult PFN_vkAllocateMemory(VkDevice device, VkMemoryAllocateInfo* pAllocateInfo, void* pAllocator, VkDeviceMemory* pMemory);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkFreeMemory(VkDevice device, VkDeviceMemory memory, void* pAllocator);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate VkResult PFN_vkMapMemory(VkDevice device, VkDeviceMemory memory, ulong offset, ulong size, uint flags, void** ppData);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkUnmapMemory(VkDevice device, VkDeviceMemory memory);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate VkResult PFN_vkCreateCommandPool(VkDevice device, VkCommandPoolCreateInfo* pCreateInfo, void* pAllocator, VkCommandPool* pCommandPool);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkDestroyCommandPool(VkDevice device, VkCommandPool commandPool, void* pAllocator);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate VkResult PFN_vkAllocateCommandBuffers(VkDevice device, VkCommandBufferAllocateInfo* pAllocateInfo, VkCommandBuffer* pCommandBuffers);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkFreeCommandBuffers(VkDevice device, VkCommandPool commandPool, uint commandBufferCount, VkCommandBuffer* pCommandBuffers);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate VkResult PFN_vkBeginCommandBuffer(VkCommandBuffer commandBuffer, VkCommandBufferBeginInfo* pBeginInfo);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate VkResult PFN_vkEndCommandBuffer(VkCommandBuffer commandBuffer);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate VkResult PFN_vkResetCommandBuffer(VkCommandBuffer commandBuffer, uint flags);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate VkResult PFN_vkQueueSubmit(VkQueue queue, uint submitCount, VkSubmitInfo* pSubmits, VkFence fence);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate VkResult PFN_vkQueueWaitIdle(VkQueue queue);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate VkResult PFN_vkQueuePresentKHR(VkQueue queue, VkPresentInfoKHR* pPresentInfo);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate VkResult PFN_vkAcquireNextImageKHR(VkDevice device, VkSwapchainKHR swapchain, ulong timeout, VkSemaphore semaphore, VkFence fence, uint* pImageIndex);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate VkResult PFN_vkCreateSemaphore(VkDevice device, VkSemaphoreCreateInfo* pCreateInfo, void* pAllocator, VkSemaphore* pSemaphore);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkDestroySemaphore(VkDevice device, VkSemaphore semaphore, void* pAllocator);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate VkResult PFN_vkCreateFence(VkDevice device, VkFenceCreateInfo* pCreateInfo, void* pAllocator, VkFence* pFence);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkDestroyFence(VkDevice device, VkFence fence, void* pAllocator);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate VkResult PFN_vkWaitForFences(VkDevice device, uint fenceCount, VkFence* pFences, uint waitAll, ulong timeout);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate VkResult PFN_vkResetFences(VkDevice device, uint fenceCount, VkFence* pFences);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkCmdBeginRenderPass(VkCommandBuffer commandBuffer, VkRenderPassBeginInfo* pRenderPassBegin, VkSubpassContents contents);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkCmdEndRenderPass(VkCommandBuffer commandBuffer);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkCmdBindPipeline(VkCommandBuffer commandBuffer, int pipelineBindPoint, VkPipeline pipeline);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkCmdBindDescriptorSets(VkCommandBuffer commandBuffer, int pipelineBindPoint, VkPipelineLayout layout, uint firstSet, uint descriptorSetCount, VkDescriptorSet* pDescriptorSets, uint dynamicOffsetCount, uint* pDynamicOffsets);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkCmdBindVertexBuffers(VkCommandBuffer commandBuffer, uint firstBinding, uint bindingCount, VkBuffer* pBuffers, ulong* pOffsets);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkCmdBindIndexBuffer(VkCommandBuffer commandBuffer, VkBuffer buffer, ulong offset, VkIndexType indexType);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkCmdDrawIndexed(VkCommandBuffer commandBuffer, uint indexCount, uint instanceCount, uint firstIndex, int vertexOffset, uint firstInstance);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkCmdDraw(VkCommandBuffer commandBuffer, uint vertexCount, uint instanceCount, uint firstVertex, uint firstInstance);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkCmdSetViewport(VkCommandBuffer commandBuffer, uint firstViewport, uint viewportCount, VkViewport* pViewports);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkCmdSetScissor(VkCommandBuffer commandBuffer, uint firstScissor, uint scissorCount, VkRect2D* pScissors);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public 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 public delegate void PFN_vkCmdCopyBuffer(VkCommandBuffer commandBuffer, VkBuffer srcBuffer, VkBuffer dstBuffer, uint regionCount, VkBufferCopy* pRegions);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkCmdCopyBufferToImage(VkCommandBuffer commandBuffer, VkBuffer srcBuffer, VkImage dstImage, int dstImageLayout, uint regionCount, VkBufferImageCopy* pRegions);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkCmdCopyImageToBuffer(VkCommandBuffer commandBuffer, VkImage srcImage, int srcImageLayout, VkBuffer dstBuffer, uint regionCount, VkBufferImageCopy* pRegions);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkCmdClearColorImage(VkCommandBuffer commandBuffer, VkImage image, int imageLayout, VkClearColorValue* pColor, uint rangeCount, VkImageSubresourceRange* pRanges);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkCmdPushConstants(VkCommandBuffer commandBuffer, VkPipelineLayout layout, VkShaderStageFlags stageFlags, uint offset, uint size, void* pValues);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate VkResult PFN_vkCreateSampler(VkDevice device, VkSamplerCreateInfo* pCreateInfo, void* pAllocator, void* pSampler);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkDestroySampler(VkDevice device, void* sampler, void* pAllocator);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate VkResult PFN_vkGetPhysicalDeviceSurfaceSupportKHR(VkPhysicalDevice physicalDevice, uint queueFamilyIndex, VkSurfaceKHR surface, uint* pSupported);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate VkResult PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, VkSurfaceCapabilitiesKHR* pSurfaceCapabilities);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate VkResult PFN_vkGetPhysicalDeviceSurfaceFormatsKHR(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, uint* pSurfaceFormatCount, VkSurfaceFormatKHR* pSurfaceFormats);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate VkResult PFN_vkGetPhysicalDeviceSurfacePresentModesKHR(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, uint* pPresentModeCount, VkPresentModeKHR* pPresentModes);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
unsafe public delegate void PFN_vkDestroySurfaceKHR(VkInstance instance, VkSurfaceKHR surface, void* pAllocator);
@@ -1,16 +1,23 @@
using Engine.Core;
using Engine.Graphics; using Engine.Graphics;
namespace Engine.Graphics.Vulkan; namespace Engine.Graphics.Vulkan;
public static class VulkanBackendRegistrar public static class VulkanBackendRegistrar
{ {
private static int _registered; private static bool _registered;
public static void EnsureRegistered() public static void EnsureRegistered()
{ {
if (System.Threading.Interlocked.Exchange(ref _registered, 1) == 1) return; if (_registered) return;
_registered = true;
RenderBackendFactory.Register("vulkan", (width, height, validation) => RenderBackendFactory.Register("vulkan", (width, height, enableValidation) =>
new VulkanRenderContext(width, height, validation)); {
var window = new Sdl3Window("Cortex Engine — Vulkan", width, height, vulkanSurface: true);
return new VulkanRenderContext(window, enableValidation);
});
Console.WriteLine("[Vulkan] Backend registered as 'vulkan'");
} }
} }
-168
View File
@@ -1,168 +0,0 @@
using System.Runtime.InteropServices;
namespace Engine.Graphics.Vulkan;
public 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>(T[] data, ulong offset = 0) where T : struct
{
var size = (ulong)(data.Length * Marshal.SizeOf<T>());
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, &region);
EndSingleTimeCommands(ctx, cmdPool, cmd);
}
public static VulkanBuffer CreateDeviceLocal<T>(VulkanContext ctx, VkCommandPool cmdPool, T[] data, VkBufferUsageFlags usage) where T : struct
{
var size = (ulong)(data.Length * Marshal.SizeOf<T>());
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);
}
}
+403 -280
View File
@@ -1,334 +1,457 @@
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text;
using Engine.Core;
using SDL; using SDL;
using Engine.Core;
namespace Engine.Graphics.Vulkan; namespace Engine.Graphics.Vulkan;
public sealed unsafe class VulkanContext : IDisposable internal static unsafe class SdlVulkan
{
[DllImport("SDL3", CallingConvention = CallingConvention.Cdecl)]
private static extern int SDL_Vulkan_CreateSurface(nint window, nint instance, nint allocator, VkSurfaceKHR* surface);
public static void Create(IWindow window, VkInstance instance, VkSurfaceKHR* surface)
{
if (SDL_Vulkan_CreateSurface(window.Handle, instance.Handle, 0, surface) == 0)
throw new InvalidOperationException("SDL_Vulkan_CreateSurface failed");
}
}
internal sealed unsafe class VulkanContext : IDisposable
{ {
public VkInstance Instance; public VkInstance Instance;
public VkPhysicalDevice PhysicalDevice; public VkPhysicalDevice PhysicalDevice;
public VkDevice Device; public VkDevice Device;
public VkSurfaceKHR Surface;
public VkQueue GraphicsQueue; public VkQueue GraphicsQueue;
public VkQueue PresentQueue; public VkSurfaceKHR Surface;
public uint GraphicsFamily; public uint GraphicsQueueFamilyIndex;
public uint PresentFamily;
public VkPhysicalDeviceMemoryProperties MemoryProperties; public VkPhysicalDeviceMemoryProperties MemoryProperties;
private readonly bool _validation; public VkFormat SurfaceFormat;
public VkColorSpaceKHR SurfaceColorSpace;
public VkExtent2D SurfaceExtent;
public bool ValidationEnabled;
private VkDebugUtilsMessengerEXT _debugMessenger;
private bool _disposed; private bool _disposed;
private static DebugCallbackDelegate? _debugCallbackDelegate;
public VulkanContext(Sdl3Window window, bool enableValidation) private static readonly uint VK_API_VERSION_1_3 = (1u << 22) | (3u << 12);
private static bool IsLayerAvailable(string layerName)
{ {
_validation = enableValidation; var enumInstanceProps = VulkanNative.GetExport<EnumInstanceLayerPropertiesDelegate>("vkEnumerateInstanceLayerProperties");
Vk.LoadGlobalFunctions();
CreateInstance(window.GetRequiredVulkanExtensions());
CreateSurface(window);
PickPhysicalDevice();
CreateLogicalDevice();
}
private unsafe void CreateInstance(string[] requiredExtensions)
{
var layers = Array.Empty<string>();
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; uint count = 0;
Vk.vkGetPhysicalDeviceQueueFamilyProperties(device, &count, null); enumInstanceProps(&count, null);
if (count == 0) return false; if (count == 0) return false;
var props = new VkQueueFamilyProperties[count]; var props = stackalloc VkLayerProperties[(int)count];
fixed (VkQueueFamilyProperties* pProps = props) enumInstanceProps(&count, props);
{
Vk.vkGetPhysicalDeviceQueueFamilyProperties(device, &count, pProps);
}
var targetBytes = VulkanString.ToUtf8Terminated(layerName);
for (uint i = 0; i < count; i++) for (uint i = 0; i < count; i++)
{ {
if ((props[i].queueFlags & VkQueueFlags.Graphics) != 0) var namePtr = (byte*)props[(int)i].layerName;
graphicsFamily = i; if (CompareUtf8(namePtr, targetBytes))
uint supported = 0;
Vk.vkGetPhysicalDeviceSurfaceSupportKHR(device, i, Surface, &supported);
if (supported != 0)
presentFamily = i;
if (graphicsFamily != uint.MaxValue && presentFamily != uint.MaxValue)
return true; return true;
} }
return false; return false;
} }
private unsafe void CreateLogicalDevice() private static bool CompareUtf8(byte* a, byte[] b)
{ {
var queueIndices = new HashSet<uint> { GraphicsFamily, PresentFamily }; for (int i = 0; i < b.Length; i++)
var queueCreateInfos = new VkDeviceQueueCreateInfo[queueIndices.Count];
var priorities = new float[] { 1.0f };
fixed (float* pPrio = priorities)
{ {
var idx = 0; if (a == null || a[i] != b[i]) return false;
foreach (var qfi in queueIndices) if (b[i] == 0) return true;
{
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);
} }
return false;
}
[UnmanagedFunctionPointer(System.Runtime.InteropServices.CallingConvention.Cdecl)]
private delegate VkResult EnumInstanceLayerPropertiesDelegate(uint* pPropertyCount, VkLayerProperties* pProperties);
[StructLayout(LayoutKind.Sequential)]
private struct VkLayerProperties
{
public fixed byte layerName[256];
public uint specVersion;
public uint implementationVersion;
public fixed byte description[256];
}
public VulkanContext(IWindow window, bool enableValidation)
{
ValidationEnabled = enableValidation;
_debugCallbackDelegate = DebugCallback;
CreateInstance(window, enableValidation);
CreateSurface(window);
PickPhysicalDevice();
CreateLogicalDevice(enableValidation);
Console.WriteLine($"[Vulkan] Instance created, API version 1.3");
Console.WriteLine($"[Vulkan] Validation layers: {(ValidationEnabled ? "enabled" : "disabled")}");
}
private void CreateInstance(IWindow window, bool enableValidation)
{
var sdlExtensions = window.GetRequiredVulkanExtensions();
var extensionList = new List<string>(sdlExtensions);
var useValidation = enableValidation && IsLayerAvailable("VK_LAYER_KHRONOS_validation");
if (enableValidation && !useValidation)
Console.WriteLine("[Vulkan] WARNING: VK_LAYER_KHRONOS_validation not found, running without validation");
var layerNames = useValidation
? new[] { "VK_LAYER_KHRONOS_validation" }
: Array.Empty<string>();
if (useValidation)
extensionList.Add("VK_EXT_debug_utils");
ValidationEnabled = useValidation;
var extPtrs = AllocStringArray(extensionList);
var layerPtrs = AllocStringArray(layerNames);
fixed (byte* appName = "Cortex Engine\0"u8)
fixed (byte* engineName = "Cortex\0"u8)
{
var appInfo = new VkApplicationInfo
{
sType = VkStructureType.ApplicationInfo,
pApplicationName = appName,
applicationVersion = 1,
pEngineName = engineName,
engineVersion = 1,
apiVersion = VK_API_VERSION_1_3,
};
var debugInfo = new VkDebugUtilsMessengerCreateInfoEXT
{
sType = VkStructureType.DebugUtilsMessengerCreateInfoEXT,
messageSeverity = VkDebugUtilsMessageSeverityFlagsEXT.Verbose |
VkDebugUtilsMessageSeverityFlagsEXT.Warning |
VkDebugUtilsMessageSeverityFlagsEXT.Error,
messageType = VkDebugUtilsMessageTypeFlagsEXT.General |
VkDebugUtilsMessageTypeFlagsEXT.Validation |
VkDebugUtilsMessageTypeFlagsEXT.Performance,
pfnUserCallback = Marshal.GetFunctionPointerForDelegate(_debugCallbackDelegate!),
};
var createInfo = new VkInstanceCreateInfo
{
sType = VkStructureType.InstanceCreateInfo,
pApplicationInfo = &appInfo,
enabledLayerCount = (uint)layerNames.Length,
ppEnabledLayerNames = layerPtrs,
enabledExtensionCount = (uint)extensionList.Count,
ppEnabledExtensionNames = extPtrs,
};
if (useValidation && Vk.vkCreateDebugUtilsMessengerEXT != null)
createInfo.pNext = (nint)(&debugInfo);
VkResult result;
fixed (VkInstance* instPtr = &Instance)
{
result = VulkanNative.vkGetInstanceProcAddr == null
? VkResult.ErrorInitializationFailed
: default;
var vkCreateInstance = VulkanNative.GetExport<Vk.VkCreateInstance>("vkCreateInstance");
result = vkCreateInstance(&createInfo, 0, instPtr);
}
if (result != VkResult.Success)
throw new InvalidOperationException($"vkCreateInstance failed: {result}");
}
Vk.LoadInstanceFunctions(Instance);
if (useValidation)
{
fixed (VkDebugUtilsMessengerEXT* msgPtr = &_debugMessenger)
{
var dbgInfo = new VkDebugUtilsMessengerCreateInfoEXT
{
sType = VkStructureType.DebugUtilsMessengerCreateInfoEXT,
messageSeverity = VkDebugUtilsMessageSeverityFlagsEXT.Verbose |
VkDebugUtilsMessageSeverityFlagsEXT.Warning |
VkDebugUtilsMessageSeverityFlagsEXT.Error,
messageType = VkDebugUtilsMessageTypeFlagsEXT.General |
VkDebugUtilsMessageTypeFlagsEXT.Validation |
VkDebugUtilsMessageTypeFlagsEXT.Performance,
pfnUserCallback = Marshal.GetFunctionPointerForDelegate(_debugCallbackDelegate!),
};
Vk.vkCreateDebugUtilsMessengerEXT(Instance, &dbgInfo, 0, msgPtr);
}
}
FreeStringArray(extPtrs, extensionList.Count);
FreeStringArray(layerPtrs, layerNames.Length);
}
private static uint DebugCallback(uint messageSeverity, uint messageTypes,
nint pCallbackData, nint pUserData)
{
var data = Marshal.PtrToStructure<VkDebugUtilsMessengerCallbackDataEXT>(pCallbackData);
var msg = data.pMessage != null ? Marshal.PtrToStringUTF8((nint)data.pMessage) : "unknown";
var severity = messageSeverity switch
{
0x00000001 => "VERBOSE",
0x00000010 => "INFO",
0x00000100 => "WARNING",
0x00001000 => "ERROR",
_ => "UNKNOWN"
};
Console.Error.WriteLine($"[Vulkan:{severity}] {msg}");
return 0;
}
[UnmanagedFunctionPointer(System.Runtime.InteropServices.CallingConvention.Cdecl)]
private delegate uint DebugCallbackDelegate(uint messageSeverity, uint messageTypes,
nint pCallbackData, nint pUserData);
private void CreateSurface(IWindow window)
{
fixed (VkSurfaceKHR* surfacePtr = &Surface)
{
SdlVulkan.Create(window, Instance, surfacePtr);
}
}
private void PickPhysicalDevice()
{
uint count = 0;
Vk.vkEnumeratePhysicalDevices(Instance, &count, null);
if (count == 0)
throw new InvalidOperationException("No Vulkan physical devices found");
var devices = stackalloc VkPhysicalDevice[(int)count];
Vk.vkEnumeratePhysicalDevices(Instance, &count, devices);
VkPhysicalDevice best = VkPhysicalDevice.Null;
VkPhysicalDeviceType bestType = VkPhysicalDeviceType.Other;
for (uint i = 0; i < count; i++)
{
var propsBytes = stackalloc byte[824];
Vk.vkGetPhysicalDeviceProperties(devices[(int)i], (VkPhysicalDeviceProperties*)propsBytes);
var nameBytes = new byte[256];
Marshal.Copy((nint)(propsBytes + 20), nameBytes, 0, 256);
var nameLen = Array.IndexOf(nameBytes, (byte)0);
if (nameLen < 0) nameLen = 256;
var devType = (VkPhysicalDeviceType)Marshal.ReadInt32((nint)propsBytes, 16);
Console.WriteLine($"[Vulkan] GPU {i}: {System.Text.Encoding.UTF8.GetString(nameBytes, 0, nameLen)} (type={devType})");
if (best.Handle == 0 || (devType == VkPhysicalDeviceType.DiscreteGpu && bestType != VkPhysicalDeviceType.DiscreteGpu))
{
best = devices[(int)i];
bestType = devType;
}
}
if (best.Handle == 0)
best = devices[0];
PhysicalDevice = best;
var memProps = new VkPhysicalDeviceMemoryProperties();
Vk.vkGetPhysicalDeviceMemoryProperties(PhysicalDevice, &memProps);
MemoryProperties = memProps;
uint queueCount = 0;
Vk.vkGetPhysicalDeviceQueueFamilyProperties(PhysicalDevice, &queueCount, null);
var queueProps = stackalloc VkQueueFamilyProperties[(int)queueCount];
Vk.vkGetPhysicalDeviceQueueFamilyProperties(PhysicalDevice, &queueCount, queueProps);
GraphicsQueueFamilyIndex = uint.MaxValue;
for (uint i = 0; i < queueCount; i++)
{
if ((queueProps[(int)i].queueFlags & VkQueueFlags.Graphics) != 0)
{
VkBool32 supported = VkBool32.False;
Vk.vkGetPhysicalDeviceSurfaceSupportKHR(PhysicalDevice, i, Surface, &supported);
if (supported == VkBool32.True)
{
GraphicsQueueFamilyIndex = i;
break;
}
}
}
if (GraphicsQueueFamilyIndex == uint.MaxValue)
throw new InvalidOperationException("No graphics queue family with surface support found");
}
private void CreateLogicalDevice(bool enableValidation)
{
var priorities = stackalloc float[1];
priorities[0] = 1.0f;
var queueInfo = new VkDeviceQueueCreateInfo
{
sType = VkStructureType.DeviceQueueCreateInfo,
queueFamilyIndex = GraphicsQueueFamilyIndex,
queueCount = 1,
pQueuePriorities = priorities,
};
var extNames = new[] { "VK_KHR_swapchain" };
var extPtrs = AllocStringArray(extNames);
var sync2Features = new VkPhysicalDeviceSynchronization2Features
{
sType = VkStructureType.PhysicalDeviceSynchronization2Features,
synchronization2 = VkBool32.True,
};
var renderingFeatures = new VkPhysicalDeviceDynamicRenderingFeatures
{
sType = VkStructureType.PhysicalDeviceDynamicRenderingFeatures,
pNext = (nint)(&sync2Features),
dynamicRendering = VkBool32.True,
};
var deviceInfo = new VkDeviceCreateInfo
{
sType = VkStructureType.DeviceCreateInfo,
pQueueCreateInfos = &queueInfo,
queueCreateInfoCount = 1,
enabledExtensionCount = (uint)extNames.Length,
ppEnabledExtensionNames = extPtrs,
pEnabledFeatures = null,
pNext = (nint)(&renderingFeatures),
};
var dev = VkDevice.Null;
{
var result = Vk.vkCreateDevice(PhysicalDevice, &deviceInfo, 0, &dev);
if (result != VkResult.Success)
throw new InvalidOperationException($"vkCreateDevice failed: {result}");
}
Device = dev;
Vk.LoadDeviceFunctions(Device); Vk.LoadDeviceFunctions(Device);
fixed (VkQueue* pGfxQueue = &GraphicsQueue) fixed (VkQueue* queuePtr = &GraphicsQueue)
{ {
Vk.vkGetDeviceQueue(Device, GraphicsFamily, 0, pGfxQueue); Vk.vkGetDeviceQueue(Device, GraphicsQueueFamilyIndex, 0, queuePtr);
}
fixed (VkQueue* pPresentQueue = &PresentQueue)
{
Vk.vkGetDeviceQueue(Device, PresentFamily, 0, pPresentQueue);
} }
Console.WriteLine("[Vulkan] Logical device created."); FreeStringArray(extPtrs, extNames.Length);
QuerySurfaceFormat();
} }
public unsafe uint FindMemoryType(uint typeFilter, VkMemoryPropertyFlags properties) private void QuerySurfaceFormat()
{
uint formatCount = 0;
Vk.vkGetPhysicalDeviceSurfaceFormatsKHR(PhysicalDevice, Surface, &formatCount, null);
if (formatCount == 0)
throw new InvalidOperationException("No surface formats available");
var formats = stackalloc VkSurfaceFormatKHR[(int)formatCount];
Vk.vkGetPhysicalDeviceSurfaceFormatsKHR(PhysicalDevice, Surface, &formatCount, formats);
SurfaceFormat = VkFormat.B8G8R8A8Srgb;
SurfaceColorSpace = VkColorSpaceKHR.SrgbNonlinearKHR;
for (uint i = 0; i < formatCount; i++)
{
if (formats[(int)i].format == VkFormat.B8G8R8A8Srgb &&
formats[(int)i].colorSpace == VkColorSpaceKHR.SrgbNonlinearKHR)
{
SurfaceFormat = formats[(int)i].format;
SurfaceColorSpace = formats[(int)i].colorSpace;
break;
}
}
if (SurfaceFormat == VkFormat.B8G8R8A8Srgb)
{
SurfaceFormat = formats[0].format;
SurfaceColorSpace = formats[0].colorSpace;
}
Console.WriteLine($"[Vulkan] Surface format: {SurfaceFormat}, color space: {SurfaceColorSpace}");
}
public uint FindMemoryType(uint memoryTypeBits, VkMemoryPropertyFlags desiredFlags)
{ {
for (uint i = 0; i < MemoryProperties.memoryTypeCount; i++) for (uint i = 0; i < MemoryProperties.memoryTypeCount; i++)
{ {
var memType = GetMemoryType(i); if ((memoryTypeBits & (1u << (int)i)) != 0)
if ((typeFilter & (1u << (int)i)) != 0 && (memType.propertyFlags & properties) == properties) {
return i; var flags = GetMemoryTypeFlags(i);
if ((flags & desiredFlags) == desiredFlags)
return i;
}
} }
throw new InvalidOperationException($"No memory type found for flags {desiredFlags}");
throw new InvalidOperationException($"Failed to find memory type with filter={typeFilter:X} props={properties}");
} }
private VkMemoryType GetMemoryType(uint index) private VkMemoryPropertyFlags GetMemoryTypeFlags(uint index)
{ {
return index switch if (index >= 32) return (VkMemoryPropertyFlags)0;
fixed (VkPhysicalDeviceMemoryProperties* p = &MemoryProperties)
{ {
0 => MemoryProperties.memoryTypes0, var memTypes = &p->memoryTypes0;
1 => MemoryProperties.memoryTypes1, return memTypes[index].propertyFlags;
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() private static byte** AllocStringArray(IList<string> strings)
{
var ptr = (byte**)Marshal.AllocHGlobal(strings.Count * nint.Size);
for (var i = 0; i < strings.Count; i++)
{
var bytes = VulkanString.ToUtf8Terminated(strings[i]);
ptr[i] = (byte*)Marshal.AllocHGlobal(bytes.Length);
Marshal.Copy(bytes, 0, (nint)ptr[i], bytes.Length);
}
return ptr;
}
private static void FreeStringArray(byte** ptr, int count)
{
for (var i = 0; i < count; i++)
{
if (ptr[i] != null)
Marshal.FreeHGlobal((nint)ptr[i]);
}
Marshal.FreeHGlobal((nint)ptr);
}
private static string ParseDeviceName(VkPhysicalDeviceProperties* props)
{
var bytes = new byte[256];
fixed (byte* dest = bytes)
{
Buffer.MemoryCopy(props->deviceName, dest, 256, 256);
}
var len = Array.IndexOf(bytes, (byte)0);
if (len < 0) len = 256;
return System.Text.Encoding.UTF8.GetString(bytes, 0, len);
}
public void Dispose()
{ {
if (_disposed) return; if (_disposed) return;
_disposed = true; _disposed = true;
if (Device.Value != 0) if (Device.Handle != 0)
{ {
Vk.vkQueueWaitIdle(GraphicsQueue); Vk.vkDeviceWaitIdle(Device);
Vk.vkDestroyDevice(Device, null); Vk.vkDestroyDevice(Device, 0);
} }
if (Surface.Value != 0)
Vk.vkDestroySurfaceKHR(Instance, Surface, null); if (_debugMessenger.Handle != 0 && Vk.vkDestroyDebugUtilsMessengerEXT != null)
if (Instance.Value != 0) Vk.vkDestroyDebugUtilsMessengerEXT(Instance, _debugMessenger, 0);
Vk.vkDestroyInstance(Instance, null);
if (Surface.Handle != 0)
Vk.vkDestroySurfaceKHR(Instance, Surface, 0);
if (Instance.Handle != 0)
Vk.vkDestroyInstance(Instance, 0);
} }
} }
+499
View File
@@ -0,0 +1,499 @@
namespace Engine.Graphics.Vulkan;
public 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,
ErrorIncompatibleDriver = -8,
ErrorTooManyObjects = -9,
ErrorFormatNotSupported = -10,
ErrorFragmentedPool = -11,
ErrorUnknown = -13,
ErrorOutOfPoolMemory = -1000069000,
ErrorInvalidExternalHandle = -1000072003,
ErrorSurfaceLostKHR = -1000000000,
ErrorNativeWindowInUseKHR = -1000000001,
SuboptimalKHR = 1000001003,
ErrorOutOfDateKHR = -1000001004,
ErrorValidationFailedEXT = -1000011001,
}
public enum VkStructureType : int
{
ApplicationInfo = 0,
InstanceCreateInfo = 1,
DeviceQueueCreateInfo = 2,
DeviceCreateInfo = 3,
SubmitInfo = 4,
MemoryAllocateInfo = 5,
BufferCreateInfo = 12,
ShaderModuleCreateInfo = 16,
PipelineShaderStageCreateInfo = 18,
PipelineVertexInputStateCreateInfo = 19,
PipelineInputAssemblyStateCreateInfo = 20,
PipelineTessellationStateCreateInfo = 21,
PipelineViewportStateCreateInfo = 22,
PipelineRasterizationStateCreateInfo = 23,
PipelineMultisampleStateCreateInfo = 24,
PipelineDepthStencilStateCreateInfo = 25,
PipelineColorBlendStateCreateInfo = 26,
PipelineDynamicStateCreateInfo = 27,
GraphicsPipelineCreateInfo = 28,
PipelineLayoutCreateInfo = 30,
RenderPassCreateInfo = 38,
CommandPoolCreateInfo = 39,
CommandBufferAllocateInfo = 40,
CommandBufferBeginInfo = 42,
RenderPassBeginInfo = 43,
ImageViewCreateInfo = 15,
SemaphoreCreateInfo = 9,
FenceCreateInfo = 8,
SwapchainCreateInfoKHR = 1000001000,
PresentInfoKHR = 1000001001,
DebugUtilsMessengerCreateInfoEXT = 1000128004,
SubmitInfo2 = 1000314004,
CommandBufferSubmitInfo = 1000314006,
SemaphoreSubmitInfo = 1000314005,
PipelineRenderingCreateInfo = 1000044002,
RenderingInfo = 1000044000,
RenderingAttachmentInfo = 1000044001,
ImageMemoryBarrier2 = 1000314002,
BufferMemoryBarrier2 = 1000314001,
DependencyInfo = 1000314003,
PhysicalDeviceDynamicRenderingFeatures = 1000044003,
PhysicalDeviceSynchronization2Features = 1000314007,
}
public enum VkFormat : int
{
Undefined = 0,
R8G8B8A8Unorm = 37,
B8G8R8A8Unorm = 44,
R8G8B8A8Srgb = 43,
B8G8R8A8Srgb = 50,
R32G32Sfloat = 103,
R32G32B32Sfloat = 106,
R32G32B32A32Sfloat = 109,
D32Sfloat = 126,
}
public enum VkColorSpaceKHR : int
{
SrgbNonlinearKHR = 0,
}
public enum VkPresentModeKHR : int
{
Immediate = 0,
Mailbox = 1,
Fifo = 2,
FifoRelaxed = 3,
}
public enum VkImageUsageFlags : uint
{
TransferSrc = 0x00000001,
TransferDst = 0x00000002,
Sampled = 0x00000004,
Storage = 0x00000008,
ColorAttachment = 0x00000010,
DepthStencilAttachment = 0x00000020,
TransientAttachment = 0x00000040,
InputAttachment = 0x00000080,
}
public enum VkImageLayout : int
{
Undefined = 0,
General = 1,
ColorAttachmentOptimal = 2,
DepthStencilAttachmentOptimal = 3,
DepthStencilReadOnlyOptimal = 4,
ShaderReadOnlyOptimal = 5,
TransferSrcOptimal = 6,
TransferDstOptimal = 7,
Preinitialized = 8,
PresentSrcKHR = 1000001002,
}
public enum VkImageAspectFlags : uint
{
Color = 0x00000001,
Depth = 0x00000002,
Stencil = 0x00000004,
}
public enum VkAttachmentLoadOp : int
{
Load = 0,
Clear = 1,
DontCare = 2,
}
public enum VkAttachmentStoreOp : int
{
Store = 0,
DontCare = 1,
None = 1000301000,
}
public enum VkSharingMode : int
{
Exclusive = 0,
Concurrent = 1,
}
public enum VkCompositeAlphaFlagsKHR : uint
{
Opaque = 0x00000001,
PreMultiplied = 0x00000002,
PostMultiplied = 0x00000004,
Inherit = 0x00000008,
}
public enum VkSurfaceTransformFlagsKHR : uint
{
Identity = 0x00000001,
Rotate90 = 0x00000002,
Rotate180 = 0x00000004,
Rotate270 = 0x00000008,
HorizontalMirror = 0x00000010,
Inherit = 0x00000100,
}
public enum VkPrimitiveTopology : int
{
PointList = 0,
LineList = 1,
LineStrip = 2,
TriangleList = 3,
TriangleStrip = 4,
TriangleFan = 5,
}
public enum VkPolygonMode : int
{
Fill = 0,
Line = 1,
Point = 2,
}
public enum VkCullModeFlags : uint
{
None = 0,
Front = 0x00000001,
Back = 0x00000002,
FrontAndBack = 0x00000003,
}
public enum VkFrontFace : int
{
CounterClockwise = 0,
Clockwise = 1,
}
public enum VkBlendFactor : int
{
Zero = 0,
One = 1,
SrcColor = 2,
OneMinusSrcColor = 3,
DstColor = 4,
OneMinusDstColor = 5,
SrcAlpha = 6,
OneMinusSrcAlpha = 7,
DstAlpha = 8,
OneMinusDstAlpha = 9,
ConstantColor = 10,
OneMinusConstantColor = 11,
ConstantAlpha = 12,
OneMinusConstantAlpha = 13,
SrcAlphaSaturate = 14,
Src1Color = 15,
OneMinusSrc1Color = 16,
Src1Alpha = 17,
OneMinusSrc1Alpha = 18,
}
public enum VkBlendOp : int
{
Add = 0,
Subtract = 1,
ReverseSubtract = 2,
Min = 3,
Max = 4,
}
public enum VkColorComponentFlags : uint
{
R = 0x00000001,
G = 0x00000002,
B = 0x00000004,
A = 0x00000008,
}
public enum VkShaderStageFlags : uint
{
Vertex = 0x00000001,
TessellationControl = 0x00000002,
TessellationEvaluation = 0x00000004,
Geometry = 0x00000008,
Fragment = 0x00000010,
Compute = 0x00000020,
AllGraphics = 0x0000001F,
}
public enum VkPipelineStageFlags2 : ulong
{
None = 0,
TopOfPipe = 0x00000001,
DrawIndirect = 0x00000002,
VertexInput = 0x00000004,
VertexShader = 0x00000008,
TessellationControlShader = 0x00000010,
TessellationEvaluationShader = 0x00000020,
GeometryShader = 0x00000040,
FragmentShader = 0x00000080,
EarlyFragmentTests = 0x00000100,
LateFragmentTests = 0x00000200,
ColorAttachmentOutput = 0x00000400,
ComputeShader = 0x00000800,
Transfer = 0x00001000,
BottomOfPipe = 0x00002000,
Host = 0x00004000,
AllGraphics = 0x00008000,
AllCommands = 0x00010000,
}
public enum VkAccessFlags2 : ulong
{
None = 0,
ColorAttachmentRead = 0x00000080,
ColorAttachmentWrite = 0x00000100,
TransferRead = 0x00000800,
TransferWrite = 0x00001000,
ShaderRead = 0x100000000,
ShaderWrite = 0x200000000,
}
public enum VkDynamicState : int
{
Viewport = 0,
Scissor = 1,
LineWidth = 2,
DepthBias = 3,
BlendConstants = 4,
DepthBounds = 5,
StencilCompareMask = 6,
StencilWriteMask = 7,
StencilReference = 8,
}
public enum VkCommandBufferLevel : int
{
Primary = 0,
Secondary = 1,
}
public enum VkCommandBufferUsageFlags : uint
{
None = 0,
OneTimeSubmit = 0x00000001,
RenderPassContinue = 0x00000002,
SimultaneousUse = 0x00000004,
}
public enum VkFenceCreateFlags : uint
{
None = 0,
Signaled = 0x00000001,
}
public enum VkMemoryPropertyFlags : uint
{
None = 0,
DeviceLocal = 0x00000001,
HostVisible = 0x00000002,
HostCoherent = 0x00000004,
HostCached = 0x00000008,
LazilyAllocated = 0x00000010,
}
public enum VkBufferUsageFlags : uint
{
TransferSrc = 0x00000001,
TransferDst = 0x00000002,
UniformTexelBuffer = 0x00000004,
StorageTexelBuffer = 0x00000008,
UniformBuffer = 0x00000010,
StorageBuffer = 0x00000020,
IndexBuffer = 0x00000040,
VertexBuffer = 0x00000080,
IndirectBuffer = 0x00000100,
}
public enum VkQueueFlags : uint
{
Graphics = 0x00000001,
Compute = 0x00000002,
Transfer = 0x00000004,
SparseBinding = 0x00000008,
Protected = 0x00000010,
}
public enum VkPhysicalDeviceType : int
{
Other = 0,
IntegratedGpu = 1,
DiscreteGpu = 2,
VirtualGpu = 3,
Cpu = 4,
}
public enum VkSampleCountFlags : uint
{
Count1 = 0x00000001,
Count2 = 0x00000002,
Count4 = 0x00000004,
Count8 = 0x00000008,
Count16 = 0x00000010,
Count32 = 0x00000020,
Count64 = 0x00000040,
}
public enum VkImageViewType : int
{
Type1D = 0,
Type2D = 1,
Type3D = 2,
TypeCube = 3,
Type1DArray = 4,
Type2DArray = 5,
TypeCubeArray = 6,
}
public enum VkComponentSwizzle : int
{
Identity = 0,
Zero = 1,
One = 2,
R = 3,
G = 4,
B = 5,
A = 6,
}
public enum VkBool32 : uint
{
False = 0,
True = 1,
}
public enum VkRenderingFlags : uint
{
None = 0,
ContentsSecondaryCommandBuffers = 1,
Suspending = 2,
Resuming = 4,
}
public enum VkPipelineBindPoint : int
{
Graphics = 0,
Compute = 1,
}
public enum VkDescriptorType : int
{
Sampler = 0,
CombinedImageSampler = 1,
SampledImage = 2,
StorageImage = 3,
UniformTexelBuffer = 4,
StorageTexelBuffer = 5,
UniformBuffer = 6,
StorageBuffer = 7,
UniformBufferDynamic = 8,
StorageBufferDynamic = 9,
InputAttachment = 10,
}
public enum VkDescriptorPoolCreateFlags : uint
{
None = 0,
FreeDescriptorSet = 0x00000001,
}
public enum VkVertexInputRate : int
{
Vertex = 0,
Instance = 1,
}
public enum VkCommandPoolCreateFlags : uint
{
None = 0,
ResetCommandBuffer = 0x00000002,
Transient = 0x00000001,
}
public enum VkDebugUtilsMessageSeverityFlagsEXT : uint
{
Verbose = 0x00000001,
Info = 0x00000010,
Warning = 0x00000100,
Error = 0x00001000,
}
public enum VkDebugUtilsMessageTypeFlagsEXT : uint
{
General = 0x00000001,
Validation = 0x00000002,
Performance = 0x00000004,
}
public enum VkObjectType : int
{
Unknown = 0,
Instance = 1,
PhysicalDevice = 2,
Device = 3,
Queue = 4,
Semaphore = 5,
CommandBuffer = 6,
Fence = 7,
DeviceMemory = 8,
Buffer = 9,
Image = 10,
Event = 11,
QueryPool = 12,
BufferView = 13,
ImageView = 14,
ShaderModule = 15,
PipelineCache = 16,
PipelineLayout = 17,
Pipeline = 19,
CommandPool = 22,
SurfaceKHR = 26,
SwapchainKHR = 27,
DebugUtilsMessengerEXT = 28,
}
public enum VkDependencyFlags : uint
{
None = 0,
ByRegion = 0x00000001,
DeviceGroup = 0x00000004,
ViewLocal = 0x00000002,
}
@@ -0,0 +1,117 @@
namespace Engine.Graphics.Vulkan;
internal sealed unsafe class VulkanFrameResources : IDisposable
{
public const int MaxFramesInFlight = 2;
public VkCommandPool CommandPool;
public VkCommandBuffer[] CommandBuffers = new VkCommandBuffer[MaxFramesInFlight];
public VkFence[] FrameFences = new VkFence[MaxFramesInFlight];
public VkSemaphore[] AcquireSemaphores = new VkSemaphore[MaxFramesInFlight];
public VkSemaphore[] SubmitSemaphores = Array.Empty<VkSemaphore>();
private readonly VkDevice _device;
private bool _disposed;
public VulkanFrameResources(VkDevice device, uint queueFamilyIndex, uint swapchainImageCount)
{
_device = device;
var poolInfo = new VkCommandPoolCreateInfo
{
sType = VkStructureType.CommandPoolCreateInfo,
flags = VkCommandPoolCreateFlags.ResetCommandBuffer,
queueFamilyIndex = queueFamilyIndex,
};
fixed (VkCommandPool* poolPtr = &CommandPool)
{
var result = Vk.vkCreateCommandPool(_device, &poolInfo, 0, poolPtr);
if (result != VkResult.Success)
throw new InvalidOperationException($"vkCreateCommandPool failed: {result}");
}
var allocInfo = new VkCommandBufferAllocateInfo
{
sType = VkStructureType.CommandBufferAllocateInfo,
commandPool = CommandPool,
level = VkCommandBufferLevel.Primary,
commandBufferCount = MaxFramesInFlight,
};
fixed (VkCommandBuffer* cmdPtr = CommandBuffers)
{
var result = Vk.vkAllocateCommandBuffers(_device, &allocInfo, cmdPtr);
if (result != VkResult.Success)
throw new InvalidOperationException($"vkAllocateCommandBuffers failed: {result}");
}
var fenceInfo = new VkFenceCreateInfo
{
sType = VkStructureType.FenceCreateInfo,
flags = VkFenceCreateFlags.Signaled,
};
var semInfo = new VkSemaphoreCreateInfo
{
sType = VkStructureType.SemaphoreCreateInfo,
};
for (int i = 0; i < MaxFramesInFlight; i++)
{
var fence = VkFence.Null;
var result = Vk.vkCreateFence(_device, &fenceInfo, 0, &fence);
if (result != VkResult.Success)
throw new InvalidOperationException($"vkCreateFence failed: {result}");
FrameFences[i] = fence;
var sem = VkSemaphore.Null;
result = Vk.vkCreateSemaphore(_device, &semInfo, 0, &sem);
if (result != VkResult.Success)
throw new InvalidOperationException($"vkCreateSemaphore (acquire) failed: {result}");
AcquireSemaphores[i] = sem;
}
SubmitSemaphores = new VkSemaphore[swapchainImageCount];
for (int i = 0; i < swapchainImageCount; i++)
{
var sem = VkSemaphore.Null;
var result = Vk.vkCreateSemaphore(_device, &semInfo, 0, &sem);
if (result != VkResult.Success)
throw new InvalidOperationException($"vkCreateSemaphore (submit) failed: {result}");
SubmitSemaphores[i] = sem;
}
Console.WriteLine($"[Vulkan] Frame resources: {MaxFramesInFlight} frames in flight, {swapchainImageCount} submit semaphores");
}
public void WaitFrame(int frameIndex)
{
fixed (VkFence* fencePtr = &FrameFences[frameIndex])
{
Vk.vkWaitForFences(_device, 1, fencePtr, VkBool32.True, ulong.MaxValue);
Vk.vkResetFences(_device, 1, fencePtr);
}
}
public void Dispose()
{
if (_disposed) return;
_disposed = true;
Vk.vkDeviceWaitIdle(_device);
for (int i = 0; i < MaxFramesInFlight; i++)
{
if (FrameFences[i].Handle != 0) Vk.vkDestroyFence(_device, FrameFences[i], 0);
if (AcquireSemaphores[i].Handle != 0) Vk.vkDestroySemaphore(_device, AcquireSemaphores[i], 0);
}
for (int i = 0; i < SubmitSemaphores.Length; i++)
{
if (SubmitSemaphores[i].Handle != 0) Vk.vkDestroySemaphore(_device, SubmitSemaphores[i], 0);
}
if (CommandPool.Handle != 0) Vk.vkDestroyCommandPool(_device, CommandPool, 0);
}
}
@@ -0,0 +1,46 @@
using System.Runtime.InteropServices;
namespace Engine.Graphics.Vulkan;
[StructLayout(LayoutKind.Sequential)]
public struct VkInstance { public nint Handle; public static readonly VkInstance Null = new() { Handle = 0 }; }
[StructLayout(LayoutKind.Sequential)]
public struct VkPhysicalDevice { public nint Handle; public static readonly VkPhysicalDevice Null = new() { Handle = 0 }; }
[StructLayout(LayoutKind.Sequential)]
public struct VkDevice { public nint Handle; public static readonly VkDevice Null = new() { Handle = 0 }; }
[StructLayout(LayoutKind.Sequential)]
public struct VkQueue { public nint Handle; public static readonly VkQueue Null = new() { Handle = 0 }; }
[StructLayout(LayoutKind.Sequential)]
public struct VkCommandPool { public nint Handle; public static readonly VkCommandPool Null = new() { Handle = 0 }; }
[StructLayout(LayoutKind.Sequential)]
public struct VkCommandBuffer { public nint Handle; public static readonly VkCommandBuffer Null = new() { Handle = 0 }; }
[StructLayout(LayoutKind.Sequential)]
public struct VkSwapchainKHR { public nint Handle; public static readonly VkSwapchainKHR Null = new() { Handle = 0 }; }
[StructLayout(LayoutKind.Sequential)]
public struct VkSurfaceKHR { public nint Handle; public static readonly VkSurfaceKHR Null = new() { Handle = 0 }; }
[StructLayout(LayoutKind.Sequential)]
public struct VkImage { public nint Handle; public static readonly VkImage Null = new() { Handle = 0 }; }
[StructLayout(LayoutKind.Sequential)]
public struct VkImageView { public nint Handle; public static readonly VkImageView Null = new() { Handle = 0 }; }
[StructLayout(LayoutKind.Sequential)]
public struct VkBuffer { public nint Handle; public static readonly VkBuffer Null = new() { Handle = 0 }; }
[StructLayout(LayoutKind.Sequential)]
public struct VkDeviceMemory { public nint Handle; public static readonly VkDeviceMemory Null = new() { Handle = 0 }; }
[StructLayout(LayoutKind.Sequential)]
public struct VkShaderModule { public nint Handle; public static readonly VkShaderModule Null = new() { Handle = 0 }; }
[StructLayout(LayoutKind.Sequential)]
public struct VkPipelineLayout { public nint Handle; public static readonly VkPipelineLayout Null = new() { Handle = 0 }; }
[StructLayout(LayoutKind.Sequential)]
public struct VkPipeline { public nint Handle; public static readonly VkPipeline Null = new() { Handle = 0 }; }
[StructLayout(LayoutKind.Sequential)]
public struct VkSemaphore { public nint Handle; public static readonly VkSemaphore Null = new() { Handle = 0 }; }
[StructLayout(LayoutKind.Sequential)]
public struct VkFence { public nint Handle; public static readonly VkFence Null = new() { Handle = 0 }; }
[StructLayout(LayoutKind.Sequential)]
public struct VkDebugUtilsMessengerEXT { public nint Handle; public static readonly VkDebugUtilsMessengerEXT Null = new() { Handle = 0 }; }
[StructLayout(LayoutKind.Sequential)]
public struct VkDescriptorSetLayout { public nint Handle; public static readonly VkDescriptorSetLayout Null = new() { Handle = 0 }; }
[StructLayout(LayoutKind.Sequential)]
public struct VkDescriptorPool { public nint Handle; public static readonly VkDescriptorPool Null = new() { Handle = 0 }; }
[StructLayout(LayoutKind.Sequential)]
public struct VkDescriptorSet { public nint Handle; public static readonly VkDescriptorSet Null = new() { Handle = 0 }; }
-521
View File
@@ -1,521 +0,0 @@
using System.Runtime.InteropServices;
using System.Numerics;
using ImGuiNET;
namespace Engine.Graphics.Vulkan;
public sealed unsafe class VulkanImGui : IDisposable
{
private readonly VulkanContext _ctx;
private readonly VulkanSwapchain _swapchain;
private VkCommandPool _initCommandPool;
private VkPipeline _pipeline;
private VkPipelineLayout _pipelineLayout;
private VkDescriptorSetLayout _descriptorSetLayout;
private VkDescriptorPool _descriptorPool;
private VkDescriptorSet _descriptorSet;
private VkShaderModule _vertexShader;
private VkShaderModule _fragmentShader;
private VkImage _fontImage;
private VkDeviceMemory _fontImageMemory;
private VkImageView _fontImageView;
private VkSampler _fontSampler;
private VulkanBuffer? _vertexBuffer;
private VulkanBuffer? _indexBuffer;
private static readonly byte[] VkDescriptorWriteDummy = new byte[1];
public VulkanImGui(VulkanContext ctx, VulkanSwapchain swapchain)
{
_ctx = ctx;
_swapchain = swapchain;
Initialize();
}
private unsafe void Initialize()
{
var io = ImGui.GetIO();
io.Fonts.AddFontDefault();
io.Fonts.Build();
VkCommandPoolCreateInfo poolInfo = default;
poolInfo.sType = VkStructureType.CommandPoolCreateInfo;
poolInfo.flags = 0x00000002;
poolInfo.queueFamilyIndex = _ctx.GraphicsFamily;
VkCommandPool pool;
Vk.CheckResult(Vk.vkCreateCommandPool(_ctx.Device, &poolInfo, null, &pool), "vkCreateCommandPool (ImGui init)");
_initCommandPool = pool;
CreateFontTexture();
CreateShaders();
CreateDescriptorSetLayout();
CreatePipelineLayout();
CreatePipeline();
CreateDescriptorPoolAndSet();
Vk.vkDestroyCommandPool(_ctx.Device, _initCommandPool, null);
}
private unsafe void CreateFontTexture()
{
var io = ImGui.GetIO();
int width, height, bpp;
byte* pixels;
io.Fonts.GetTexDataAsRGBA32(out pixels, out width, out height, out bpp);
VkImageCreateInfo imageInfo = default;
imageInfo.sType = VkStructureType.ImageCreateInfo;
imageInfo.imageType = VkImageType._2D;
imageInfo.format = VkFormat.R8G8B8A8Unorm;
imageInfo.extent = new VkExtent3D { width = width, height = height, depth = 1 };
imageInfo.mipLevels = 1;
imageInfo.arrayLayers = 1;
imageInfo.samples = VkSampleCountFlags.One;
imageInfo.tiling = 0;
imageInfo.usage = VkImageUsageFlags.Sampled | VkImageUsageFlags.TransferDst;
imageInfo.sharingMode = VkSharingMode.Exclusive;
imageInfo.initialLayout = 0;
VkImage fontImage;
Vk.CheckResult(Vk.vkCreateImage(_ctx.Device, &imageInfo, null, &fontImage), "vkCreateImage (font)");
_fontImage = fontImage;
VkMemoryRequirements2 memReq;
Vk.vkGetImageMemoryRequirements(_ctx.Device, _fontImage, &memReq);
VkMemoryAllocateInfo allocInfo = default;
allocInfo.sType = VkStructureType.MemoryAllocateInfo;
allocInfo.allocationSize = memReq.size;
allocInfo.memoryTypeIndex = _ctx.FindMemoryType(memReq.memoryTypeBits, VkMemoryPropertyFlags.DeviceLocal);
VkDeviceMemory fontMem;
Vk.CheckResult(Vk.vkAllocateMemory(_ctx.Device, &allocInfo, null, &fontMem), "vkAllocateMemory (font)");
_fontImageMemory = fontMem;
Vk.CheckResult(Vk.vkBindImageMemory(_ctx.Device, _fontImage, _fontImageMemory, 0), "vkBindImageMemory (font)");
var imageSize = (ulong)(width * height * 4);
var staging = new VulkanBuffer(_ctx, imageSize,
VkBufferUsageFlags.TransferSrc,
VkMemoryPropertyFlags.HostVisible | VkMemoryPropertyFlags.HostCoherent);
Buffer.MemoryCopy(pixels, staging.MappedData, imageSize, imageSize);
var cmd = VulkanBuffer.BeginSingleTimeCommands(_ctx, _initCommandPool);
var barrier = new VkImageMemoryBarrier
{
sType = VkStructureType.ImageMemoryBarrier,
srcAccessMask = 0,
dstAccessMask = VkAccessFlags.TransferWrite,
oldLayout = VkImageLayout.Undefined,
newLayout = VkImageLayout.TransferDstOptimal,
srcQueueFamilyIndex = ~0u,
dstQueueFamilyIndex = ~0u,
image = _fontImage,
subresourceRange = new VkImageSubresourceRange
{
aspectMask = VkImageAspectFlags.Color,
baseMipLevel = 0, levelCount = 1, baseArrayLayer = 0, layerCount = 1
}
};
Vk.vkCmdPipelineBarrier(cmd, VkPipelineStageFlags.Host, VkPipelineStageFlags.Transfer,
0, 0, null, 0, null, 1, &barrier);
var region = new VkBufferImageCopy
{
bufferOffset = 0,
bufferRowLength = (uint)width,
bufferImageHeight = (uint)height,
imageSubresource = new VkImageSubresourceLayers
{
aspectMask = VkImageAspectFlags.Color,
mipLevel = 0, baseArrayLayer = 0, layerCount = 1
},
imageOffset = new VkOffset3D { x = 0, y = 0, z = 0 },
imageExtent = new VkExtent3D { width = width, height = height, depth = 1 }
};
Vk.vkCmdCopyBufferToImage(cmd, staging.Buffer, _fontImage,
(int)VkImageLayout.TransferDstOptimal, 1, &region);
var barrier2 = new VkImageMemoryBarrier
{
sType = VkStructureType.ImageMemoryBarrier,
srcAccessMask = VkAccessFlags.TransferWrite,
dstAccessMask = VkAccessFlags.ShaderRead,
oldLayout = VkImageLayout.TransferDstOptimal,
newLayout = VkImageLayout.ShaderReadOnlyOptimal,
srcQueueFamilyIndex = ~0u,
dstQueueFamilyIndex = ~0u,
image = _fontImage,
subresourceRange = new VkImageSubresourceRange
{
aspectMask = VkImageAspectFlags.Color,
baseMipLevel = 0, levelCount = 1, baseArrayLayer = 0, layerCount = 1
}
};
Vk.vkCmdPipelineBarrier(cmd, VkPipelineStageFlags.Transfer, VkPipelineStageFlags.FragmentShader,
0, 0, null, 0, null, 1, &barrier2);
VulkanBuffer.EndSingleTimeCommands(_ctx, _initCommandPool, cmd);
staging.Dispose();
VkImageViewCreateInfo viewInfo = default;
viewInfo.sType = VkStructureType.ImageViewCreateInfo;
viewInfo.image = _fontImage;
viewInfo.viewType = VkImageViewType._2D;
viewInfo.format = VkFormat.R8G8B8A8Unorm;
viewInfo.subresourceRange = new VkImageSubresourceRange
{
aspectMask = VkImageAspectFlags.Color,
baseMipLevel = 0, levelCount = 1, baseArrayLayer = 0, layerCount = 1
};
VkImageView fontView;
Vk.CheckResult(Vk.vkCreateImageView(_ctx.Device, &viewInfo, null, &fontView), "vkCreateImageView (font)");
_fontImageView = fontView;
VkSamplerCreateInfo samplerInfo = default;
samplerInfo.sType = VkStructureType.SamplerCreateInfo;
samplerInfo.magFilter = VkFilter.Linear;
samplerInfo.minFilter = VkFilter.Linear;
samplerInfo.mipmapMode = VkSamplerMipmapMode.Linear;
samplerInfo.addressModeU = VkSamplerAddressMode.Repeat;
samplerInfo.addressModeV = VkSamplerAddressMode.Repeat;
samplerInfo.addressModeW = VkSamplerAddressMode.Repeat;
samplerInfo.minLod = -1000;
samplerInfo.maxLod = 1000;
VkSampler fontSampler;
Vk.CheckResult(Vk.vkCreateSampler(_ctx.Device, &samplerInfo, null, (void*)&fontSampler), "vkCreateSampler (font)");
_fontSampler = fontSampler;
}
private unsafe VkShaderModule LoadShader(string path)
{
var fullPath = Path.Combine(AppContext.BaseDirectory, path);
if (!File.Exists(fullPath))
throw new FileNotFoundException($"ImGui SPIR-V shader not found: {fullPath}");
var code = File.ReadAllBytes(fullPath);
fixed (byte* pCode = code)
{
VkShaderModuleCreateInfo createInfo = default;
createInfo.sType = VkStructureType.ShaderModuleCreateInfo;
createInfo.codeSize = (ulong)code.Length;
createInfo.pCode = (uint*)pCode;
VkShaderModule module;
Vk.CheckResult(Vk.vkCreateShaderModule(_ctx.Device, &createInfo, null, &module), $"vkCreateShaderModule ({path})");
return module;
}
}
private void CreateShaders()
{
_vertexShader = LoadShader("Shaders/imgui.vert.spv");
_fragmentShader = LoadShader("Shaders/imgui.frag.spv");
}
private unsafe void CreateDescriptorSetLayout()
{
var binding = new VkDescriptorSetLayoutBinding
{
binding = 0,
descriptorType = VkDescriptorType.CombinedImageSampler,
descriptorCount = 1,
stageFlags = VkShaderStageFlags.Fragment,
pImmutableSamplers = null
};
VkDescriptorSetLayoutCreateInfo createInfo = default;
createInfo.sType = VkStructureType.DescriptorSetLayoutCreateInfo;
createInfo.bindingCount = 1;
createInfo.pBindings = &binding;
VkDescriptorSetLayout dsLayout;
Vk.CheckResult(Vk.vkCreateDescriptorSetLayout(_ctx.Device, &createInfo, null, &dsLayout), "vkCreateDescriptorSetLayout (ImGui)");
_descriptorSetLayout = dsLayout;
}
private unsafe void CreatePipelineLayout()
{
var pushConstantRange = new VkPushConstantRange
{
stageFlags = VkShaderStageFlags.Vertex,
offset = 0,
size = 16
};
var dsLayout = _descriptorSetLayout;
VkPipelineLayoutCreateInfo createInfo = default;
createInfo.sType = VkStructureType.PipelineLayoutCreateInfo;
createInfo.setLayoutCount = 1;
createInfo.pSetLayouts = &dsLayout;
createInfo.pushConstantRangeCount = 1;
createInfo.pPushConstantRanges = &pushConstantRange;
VkPipelineLayout pipeLayout;
Vk.CheckResult(Vk.vkCreatePipelineLayout(_ctx.Device, &createInfo, null, &pipeLayout), "vkCreatePipelineLayout (ImGui)");
_pipelineLayout = pipeLayout;
}
private unsafe void CreatePipeline()
{
var mainName = Vk.AllocUtf8("main");
var stages = stackalloc VkPipelineShaderStageCreateInfo[2];
stages[0] = new VkPipelineShaderStageCreateInfo
{
sType = VkStructureType.PipelineShaderStageCreateInfo,
stage = VkShaderStageFlags.Vertex,
module = _vertexShader,
pName = mainName
};
stages[1] = new VkPipelineShaderStageCreateInfo
{
sType = VkStructureType.PipelineShaderStageCreateInfo,
stage = VkShaderStageFlags.Fragment,
module = _fragmentShader,
pName = mainName
};
var bindingDesc = new VkVertexInputBindingDescription
{
binding = 0,
stride = (uint)Marshal.SizeOf<ImDrawVert>(),
inputRate = 0
};
var attrDescs = stackalloc VkVertexInputAttributeDescription[3];
attrDescs[0] = new VkVertexInputAttributeDescription { location = 0, binding = 0, format = VkFormat.R32G32Sfloat, offset = 0 };
attrDescs[1] = new VkVertexInputAttributeDescription { location = 1, binding = 0, format = VkFormat.R32G32Sfloat, offset = 8 };
attrDescs[2] = new VkVertexInputAttributeDescription { location = 2, binding = 0, format = VkFormat.R8G8B8A8Unorm, offset = 16 };
VkPipelineVertexInputStateCreateInfo vertexInputState = default;
vertexInputState.sType = VkStructureType.PipelineVertexInputStateCreateInfo;
vertexInputState.vertexBindingDescriptionCount = 1;
vertexInputState.pVertexBindingDescriptions = &bindingDesc;
vertexInputState.vertexAttributeDescriptionCount = 3;
vertexInputState.pVertexAttributeDescriptions = attrDescs;
VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = default;
inputAssemblyState.sType = VkStructureType.PipelineInputAssemblyStateCreateInfo;
inputAssemblyState.topology = VkPrimitiveTopology.TriangleList;
var viewport = new VkViewport();
var scissor = new VkRect2D();
VkPipelineViewportStateCreateInfo viewportState = default;
viewportState.sType = VkStructureType.PipelineViewportStateCreateInfo;
viewportState.viewportCount = 1;
viewportState.pViewports = &viewport;
viewportState.scissorCount = 1;
viewportState.pScissors = &scissor;
VkPipelineRasterizationStateCreateInfo rasterState = default;
rasterState.sType = VkStructureType.PipelineRasterizationStateCreateInfo;
rasterState.polygonMode = VkPolygonMode.Fill;
rasterState.cullMode = VkCullModeFlags.None;
rasterState.frontFace = VkFrontFace.Clockwise;
rasterState.lineWidth = 1.0f;
VkPipelineMultisampleStateCreateInfo msState = default;
msState.sType = VkStructureType.PipelineMultisampleStateCreateInfo;
msState.rasterizationSamples = VkSampleCountFlags.One;
VkPipelineColorBlendAttachmentState blendAttachment = default;
blendAttachment.blendEnable = 1;
blendAttachment.srcColorBlendFactor = VkBlendFactor.SrcAlpha;
blendAttachment.dstColorBlendFactor = VkBlendFactor.OneMinusSrcAlpha;
blendAttachment.colorBlendOp = VkBlendOp.Add;
blendAttachment.srcAlphaBlendFactor = VkBlendFactor.OneMinusSrcAlpha;
blendAttachment.dstAlphaBlendFactor = VkBlendFactor.Zero;
blendAttachment.alphaBlendOp = VkBlendOp.Add;
blendAttachment.colorWriteMask = VkColorComponentFlags.R | VkColorComponentFlags.G | VkColorComponentFlags.B | VkColorComponentFlags.A;
VkPipelineColorBlendStateCreateInfo blendState = default;
blendState.sType = VkStructureType.PipelineColorBlendStateCreateInfo;
blendState.attachmentCount = 1;
blendState.pAttachments = &blendAttachment;
VkGraphicsPipelineCreateInfo pipelineInfo = default;
pipelineInfo.sType = VkStructureType.GraphicsPipelineCreateInfo;
pipelineInfo.stageCount = 2;
pipelineInfo.pStages = stages;
pipelineInfo.pVertexInputState = &vertexInputState;
pipelineInfo.pInputAssemblyState = &inputAssemblyState;
pipelineInfo.pViewportState = &viewportState;
pipelineInfo.pRasterizationState = &rasterState;
pipelineInfo.pMultisampleState = &msState;
pipelineInfo.pColorBlendState = &blendState;
pipelineInfo.layout = _pipelineLayout;
pipelineInfo.renderPass = _swapchain.RenderPass;
pipelineInfo.subpass = 0;
VkPipeline pipe;
Vk.CheckResult(Vk.vkCreateGraphicsPipelines(_ctx.Device, 0, 1, &pipelineInfo, null, &pipe), "vkCreateGraphicsPipelines (ImGui)");
_pipeline = pipe;
Vk.FreeUtf8(mainName);
Console.WriteLine("[Vulkan] ImGui pipeline created.");
}
private unsafe void CreateDescriptorPoolAndSet()
{
var poolSize = new VkDescriptorPoolSize
{
type = VkDescriptorType.CombinedImageSampler,
descriptorCount = 1
};
VkDescriptorPoolCreateInfo poolInfo = default;
poolInfo.sType = VkStructureType.DescriptorPoolCreateInfo;
poolInfo.flags = VkDescriptorPoolCreateFlags.FreeDescriptorSet;
poolInfo.maxSets = 1;
poolInfo.poolSizeCount = 1;
poolInfo.pPoolSizes = &poolSize;
VkDescriptorPool descPool;
Vk.CheckResult(Vk.vkCreateDescriptorPool(_ctx.Device, &poolInfo, null, &descPool), "vkCreateDescriptorPool (ImGui)");
_descriptorPool = descPool;
VkDescriptorSetAllocateInfo allocInfo = default;
allocInfo.sType = VkStructureType.DescriptorSetAllocateInfo;
allocInfo.descriptorPool = _descriptorPool;
allocInfo.descriptorSetCount = 1;
var dsLayout = _descriptorSetLayout;
allocInfo.pSetLayouts = &dsLayout;
VkDescriptorSet descSet;
Vk.CheckResult(Vk.vkAllocateDescriptorSets(_ctx.Device, &allocInfo, &descSet), "vkAllocateDescriptorSets (ImGui)");
_descriptorSet = descSet;
var imageInfo = new VkDescriptorImageInfo
{
sampler = _fontSampler,
imageView = _fontImageView,
imageLayout = VkImageLayout.ShaderReadOnlyOptimal
};
VkWriteDescriptorSet writeInfo = default;
writeInfo.sType = VkStructureType.WriteDescriptorSet;
writeInfo.dstSet = _descriptorSet;
writeInfo.dstBinding = 0;
writeInfo.descriptorCount = 1;
writeInfo.descriptorType = VkDescriptorType.CombinedImageSampler;
writeInfo.pImageInfo = &imageInfo;
Vk.vkUpdateDescriptorSets(_ctx.Device, 1, &writeInfo, 0, null);
}
public unsafe void Render(VkCommandBuffer cmd)
{
var drawData = ImGui.GetDrawData();
if (drawData.CmdListsCount == 0) return;
drawData.ScaleClipRects(ImGui.GetIO().DisplayFramebufferScale);
UpdateBuffers(drawData);
Vk.vkCmdBindPipeline(cmd, 0, _pipeline);
var set = _descriptorSet;
Vk.vkCmdBindDescriptorSets(cmd, 0, _pipelineLayout, 0, 1, &set, 0, null);
var displaySize = ImGui.GetIO().DisplaySize;
var scale = new Vector2(2.0f / displaySize.X, 2.0f / displaySize.Y);
var pushData = stackalloc float[2];
pushData[0] = scale.X;
pushData[1] = -scale.Y;
Vk.vkCmdPushConstants(cmd, _pipelineLayout, VkShaderStageFlags.Vertex, 0, 8, pushData);
var vb = _vertexBuffer!.Buffer;
var offset = 0ul;
Vk.vkCmdBindVertexBuffers(cmd, 0, 1, &vb, &offset);
Vk.vkCmdBindIndexBuffer(cmd, _indexBuffer!.Buffer, 0, VkIndexType.Uint16);
var indexOffset = 0u;
var vtxOffset = 0u;
for (var n = 0; n < drawData.CmdListsCount; n++)
{
var cmdList = new ImDrawListPtr(((ImDrawList**)drawData.CmdLists.Data)[n]);
for (var i = 0; i < cmdList.CmdBuffer.Size; i++)
{
var imCmd = cmdList.CmdBuffer[i];
Vk.vkCmdDrawIndexed(cmd, (uint)imCmd.ElemCount, 1, indexOffset, (int)vtxOffset, 0);
indexOffset += (uint)imCmd.ElemCount;
}
vtxOffset += (uint)cmdList.VtxBuffer.Size;
}
}
private unsafe void UpdateBuffers(ImDrawDataPtr drawData)
{
var vertexSize = (ulong)(drawData.TotalVtxCount * Marshal.SizeOf<ImDrawVert>());
var indexSize = (ulong)(drawData.TotalIdxCount * sizeof(ushort));
if (vertexSize == 0 || indexSize == 0) return;
if (_vertexBuffer == null || _vertexBuffer.Size < vertexSize)
{
_vertexBuffer?.Dispose();
_vertexBuffer = new VulkanBuffer(_ctx, vertexSize,
VkBufferUsageFlags.VertexBuffer,
VkMemoryPropertyFlags.HostVisible | VkMemoryPropertyFlags.HostCoherent);
}
if (_indexBuffer == null || _indexBuffer.Size < indexSize)
{
_indexBuffer?.Dispose();
_indexBuffer = new VulkanBuffer(_ctx, indexSize,
VkBufferUsageFlags.IndexBuffer,
VkMemoryPropertyFlags.HostVisible | VkMemoryPropertyFlags.HostCoherent);
}
var vtxDst = (byte*)_vertexBuffer.MappedData;
var idxDst = (ushort*)_indexBuffer.MappedData;
for (var n = 0; n < drawData.CmdListsCount; n++)
{
var cmdList = new ImDrawListPtr(((ImDrawList**)drawData.CmdLists.Data)[n]);
var vtxSize = cmdList.VtxBuffer.Size * Marshal.SizeOf<ImDrawVert>();
var idxSize = cmdList.IdxBuffer.Size * sizeof(ushort);
Buffer.MemoryCopy((void*)cmdList.VtxBuffer.Data, vtxDst, vtxSize, vtxSize);
Buffer.MemoryCopy((void*)cmdList.IdxBuffer.Data, idxDst, idxSize, idxSize);
vtxDst += vtxSize;
idxDst += cmdList.IdxBuffer.Size;
}
}
public void Dispose()
{
Vk.vkQueueWaitIdle(_ctx.GraphicsQueue);
_vertexBuffer?.Dispose();
_indexBuffer?.Dispose();
if (_fontSampler.Value != 0) Vk.vkDestroySampler(_ctx.Device, (void*)_fontSampler.Value, null);
if (_fontImageView.Value != 0) Vk.vkDestroyImageView(_ctx.Device, _fontImageView, null);
if (_fontImage.Value != 0) Vk.vkDestroyImage(_ctx.Device, _fontImage, null);
if (_fontImageMemory.Value != 0) Vk.vkFreeMemory(_ctx.Device, _fontImageMemory, null);
if (_descriptorPool.Value != 0) Vk.vkDestroyDescriptorPool(_ctx.Device, _descriptorPool, null);
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);
}
}
[StructLayout(LayoutKind.Sequential)]
internal struct VkDescriptorImageInfo
{
public VkSampler sampler;
public VkImageView imageView;
public VkImageLayout imageLayout;
}
+32 -73
View File
@@ -2,98 +2,57 @@ using System.Runtime.InteropServices;
namespace Engine.Graphics.Vulkan; namespace Engine.Graphics.Vulkan;
public static unsafe partial class VulkanNative internal static unsafe class VulkanNative
{ {
private const string VulkanLib = "vulkan-1.dll"; private static readonly nint _handle;
private const string VulkanLibLinux = "libvulkan.so.1"; public static readonly nint NullHandle = 0;
private static nint _libHandle; static VulkanNative()
public static nint LoadLibrary()
{ {
if (_libHandle != 0) return _libHandle; var libName = OperatingSystem.IsWindows() ? "vulkan-1.dll" : "libvulkan.so.1";
_handle = NativeLibrary.Load(libName);
if (_handle == 0)
throw new DllNotFoundException($"Failed to load Vulkan loader: {libName}");
if (OperatingSystem.IsWindows()) vkGetInstanceProcAddr = GetExport<PFN_vkGetInstanceProcAddr>("vkGetInstanceProcAddr");
_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) public static T GetExport<T>(string name) where T : Delegate
{ {
LoadLibrary(); if (!NativeLibrary.TryGetExport(_handle, name, out var address))
var ptr = NativeLibrary.GetExport(_libHandle, "vkGetInstanceProcAddr"); throw new EntryPointNotFoundException($"Vulkan export not found: {name}");
var func = Marshal.GetDelegateForFunctionPointer<PFN_vkGetInstanceProcAddr>(ptr); return Marshal.GetDelegateForFunctionPointer<T>(address);
return func(instance, pName);
} }
public static void* GetDeviceProcAddr(VkDevice device, byte* pName) public static nint GetExportPointer(string name)
{ {
var ptr = NativeLibrary.GetExport(_libHandle, "vkGetDeviceProcAddr"); NativeLibrary.TryGetExport(_handle, name, out var address);
var func = Marshal.GetDelegateForFunctionPointer<PFN_vkGetDeviceProcAddr>(ptr); return address;
return func(device, pName);
} }
public static T LoadInstanceFunction<T>(VkInstance instance, string name) where T : Delegate public static PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr;
{
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<T>((nint)addr);
}
}
public static T LoadDeviceFunction<T>(VkDevice device, string name) where T : Delegate public delegate nint PFN_vkGetInstanceProcAddr(nint instance, byte* pName);
}
internal static unsafe class VulkanString
{
public static byte[] ToUtf8Terminated(string s)
{ {
var nameBytes = System.Text.Encoding.UTF8.GetBytes(name + "\0"); return System.Text.Encoding.UTF8.GetBytes(s + '\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<T>((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 byte* AllocUtf8(string s)
public static extern void* vkGetInstanceProcAddr_Linux(VkInstance instance, string pName);
public static VkResult vkEnumerateInstanceExtensionProperties(byte* pLayerName, uint* pPropertyCount, VkExtensionProperties* pProperties)
{ {
LoadLibrary(); var bytes = ToUtf8Terminated(s);
var ptr = NativeLibrary.GetExport(_libHandle, "vkEnumerateInstanceExtensionProperties"); var ptr = (byte*)Marshal.AllocHGlobal(bytes.Length);
var func = Marshal.GetDelegateForFunctionPointer<PFN_vkEnumerateInstanceExtensionProperties>(ptr); Marshal.Copy(bytes, 0, (nint)ptr, bytes.Length);
return func(pLayerName, pPropertyCount, pProperties); return ptr;
} }
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
public delegate VkResult PFN_vkEnumerateInstanceExtensionProperties(byte* pLayerName, uint* pPropertyCount, VkExtensionProperties* pProperties);
public static VkResult vkEnumerateInstanceLayerProperties(uint* pPropertyCount, VkLayerProperties* pProperties) public static void FreeUtf8(byte* ptr)
{ {
LoadLibrary(); if (ptr != null)
var ptr = NativeLibrary.GetExport(_libHandle, "vkEnumerateInstanceLayerProperties"); Marshal.FreeHGlobal((nint)ptr);
var func = Marshal.GetDelegateForFunctionPointer<PFN_vkEnumerateInstanceLayerProperties>(ptr);
return func(pPropertyCount, pProperties);
} }
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
public delegate VkResult PFN_vkEnumerateInstanceLayerProperties(uint* pPropertyCount, VkLayerProperties* pProperties);
} }
+172 -245
View File
@@ -1,279 +1,207 @@
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using Engine.Core;
namespace Engine.Graphics.Vulkan; namespace Engine.Graphics.Vulkan;
public sealed unsafe class VulkanPipeline : IDisposable internal sealed unsafe class VulkanPipeline : IDisposable
{ {
public VkPipelineLayout PipelineLayout; public VkPipelineLayout PipelineLayout;
public VkPipeline Pipeline; public VkPipeline Pipeline;
public VkDescriptorSetLayout DescriptorSetLayout; public VkShaderModule VertModule;
public VkShaderModule VertexShader; public VkShaderModule FragModule;
public VkShaderModule FragmentShader;
private readonly VulkanContext _ctx; private readonly VkDevice _device;
private bool _disposed; private bool _disposed;
public const int PushConstantSize = 144; public VulkanPipeline(VkDevice device, VkFormat colorFormat, byte[] vertSpv, byte[] fragSpv)
public const int FrameUboSize = 16 + 16 + 16 * 4 * 8;
public VulkanPipeline(VulkanContext ctx, VkRenderPass renderPass)
{ {
_ctx = ctx; _device = device;
Create(renderPass); VertModule = CreateShaderModule(vertSpv);
} FragModule = CreateShaderModule(fragSpv);
private unsafe void Create(VkRenderPass renderPass) fixed (byte* pName = "main\0"u8)
{
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, var stages = stackalloc VkPipelineShaderStageCreateInfo[2];
pNext = null, stages[0] = new VkPipelineShaderStageCreateInfo
flags = 0, {
stage = VkShaderStageFlags.Vertex, sType = VkStructureType.PipelineShaderStageCreateInfo,
module = VertexShader, stage = VkShaderStageFlags.Vertex,
pName = mainName, module = VertModule,
pSpecializationInfo = null pName = pName,
}; };
stages[1] = new VkPipelineShaderStageCreateInfo stages[1] = new VkPipelineShaderStageCreateInfo
{ {
sType = VkStructureType.PipelineShaderStageCreateInfo, sType = VkStructureType.PipelineShaderStageCreateInfo,
pNext = null, stage = VkShaderStageFlags.Fragment,
flags = 0, module = FragModule,
stage = VkShaderStageFlags.Fragment, pName = pName,
module = FragmentShader, };
pName = mainName,
pSpecializationInfo = null
};
var bindingDesc = new VkVertexInputBindingDescription var bindings = stackalloc VkVertexInputBindingDescription[1];
{ bindings[0] = new VkVertexInputBindingDescription
binding = 0, {
stride = 36, binding = 0,
inputRate = 0 stride = (uint)sizeof(Vertex),
}; inputRate = VkVertexInputRate.Vertex,
};
var attrDescs = stackalloc VkVertexInputAttributeDescription[3]; var attributes = stackalloc VkVertexInputAttributeDescription[3];
attrDescs[0] = new VkVertexInputAttributeDescription { location = 0, binding = 0, format = VkFormat.R32G32B32Sfloat, offset = 0 }; attributes[0] = new VkVertexInputAttributeDescription
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 }; location = 0,
binding = 0,
format = VkFormat.R32G32B32Sfloat,
offset = 0,
};
attributes[1] = new VkVertexInputAttributeDescription
{
location = 1,
binding = 0,
format = VkFormat.R32G32B32Sfloat,
offset = 12,
};
attributes[2] = new VkVertexInputAttributeDescription
{
location = 2,
binding = 0,
format = VkFormat.R32G32B32Sfloat,
offset = 24,
};
VkPipelineVertexInputStateCreateInfo vertexInputState; var vertexInputState = new VkPipelineVertexInputStateCreateInfo
vertexInputState.sType = VkStructureType.PipelineVertexInputStateCreateInfo; {
vertexInputState.pNext = null; sType = VkStructureType.PipelineVertexInputStateCreateInfo,
vertexInputState.flags = 0; vertexBindingDescriptionCount = 1,
vertexInputState.vertexBindingDescriptionCount = 1; pVertexBindingDescriptions = bindings,
vertexInputState.pVertexBindingDescriptions = &bindingDesc; vertexAttributeDescriptionCount = 3,
vertexInputState.vertexAttributeDescriptionCount = 3; pVertexAttributeDescriptions = attributes,
vertexInputState.pVertexAttributeDescriptions = attrDescs; };
var inputAssemblyState = new VkPipelineInputAssemblyStateCreateInfo var inputAssemblyState = new VkPipelineInputAssemblyStateCreateInfo
{ {
sType = VkStructureType.PipelineInputAssemblyStateCreateInfo, sType = VkStructureType.PipelineInputAssemblyStateCreateInfo,
pNext = null, topology = VkPrimitiveTopology.TriangleList,
flags = 0, primitiveRestartEnable = VkBool32.False,
topology = VkPrimitiveTopology.TriangleList, };
primitiveRestartEnable = 0
};
var viewport = new VkViewport { x = 0, y = 0, width = 1280, height = 720, minDepth = 0, maxDepth = 1 }; var viewportState = new VkPipelineViewportStateCreateInfo
var scissor = new VkRect2D { offset = new VkOffset2D { x = 0, y = 0 }, extent = new VkExtent2D { width = 1280, height = 720 } }; {
sType = VkStructureType.PipelineViewportStateCreateInfo,
viewportCount = 1,
pViewports = null,
scissorCount = 1,
pScissors = null,
};
VkPipelineViewportStateCreateInfo viewportState; var rasterizationState = new VkPipelineRasterizationStateCreateInfo
viewportState.sType = VkStructureType.PipelineViewportStateCreateInfo; {
viewportState.pNext = null; sType = VkStructureType.PipelineRasterizationStateCreateInfo,
viewportState.flags = 0; depthClampEnable = VkBool32.False,
viewportState.viewportCount = 1; rasterizerDiscardEnable = VkBool32.False,
viewportState.pViewports = &viewport; polygonMode = VkPolygonMode.Fill,
viewportState.scissorCount = 1; cullMode = VkCullModeFlags.None,
viewportState.pScissors = &scissor; frontFace = VkFrontFace.CounterClockwise,
depthBiasEnable = VkBool32.False,
lineWidth = 1.0f,
};
var rasterizationState = new VkPipelineRasterizationStateCreateInfo var multisampleState = new VkPipelineMultisampleStateCreateInfo
{ {
sType = VkStructureType.PipelineRasterizationStateCreateInfo, sType = VkStructureType.PipelineMultisampleStateCreateInfo,
pNext = null, rasterizationSamples = VkSampleCountFlags.Count1,
flags = 0, sampleShadingEnable = VkBool32.False,
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 var blendAttachment = new VkPipelineColorBlendAttachmentState
{ {
sType = VkStructureType.PipelineMultisampleStateCreateInfo, blendEnable = VkBool32.False,
pNext = null, colorWriteMask = VkColorComponentFlags.R | VkColorComponentFlags.G | VkColorComponentFlags.B | VkColorComponentFlags.A,
flags = 0, };
rasterizationSamples = VkSampleCountFlags.One,
sampleShadingEnable = 0,
minSampleShading = 0,
pSampleMask = null,
alphaToCoverageEnable = 0,
alphaToOneEnable = 0
};
var depthStencilState = new VkPipelineDepthStencilStateCreateInfo var colorBlendState = new VkPipelineColorBlendStateCreateInfo
{ {
sType = VkStructureType.PipelineDepthStencilStateCreateInfo, sType = VkStructureType.PipelineColorBlendStateCreateInfo,
pNext = null, logicOpEnable = VkBool32.False,
flags = 0, attachmentCount = 1,
depthTestEnable = 1, pAttachments = &blendAttachment,
depthWriteEnable = 1, };
depthCompareOp = VkCompareOp.Less,
depthBoundsTestEnable = 0,
stencilTestEnable = 0,
front = new VkStencilOpState(),
back = new VkStencilOpState(),
minDepthBounds = 0,
maxDepthBounds = 1
};
var blendAttachment = new VkPipelineColorBlendAttachmentState var dynamicStates = stackalloc VkDynamicState[2];
{ dynamicStates[0] = VkDynamicState.Viewport;
blendEnable = 0, dynamicStates[1] = VkDynamicState.Scissor;
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; var dynamicState = new VkPipelineDynamicStateCreateInfo
colorBlendState.sType = VkStructureType.PipelineColorBlendStateCreateInfo; {
colorBlendState.pNext = null; sType = VkStructureType.PipelineDynamicStateCreateInfo,
colorBlendState.flags = 0; dynamicStateCount = 2,
colorBlendState.logicOpEnable = 0; pDynamicStates = dynamicStates,
colorBlendState.logicOp = 0; };
colorBlendState.attachmentCount = 1;
colorBlendState.pAttachments = &blendAttachment;
var dynamicStates = stackalloc VkDynamicState[2]; var layoutInfo = new VkPipelineLayoutCreateInfo
dynamicStates[0] = VkDynamicState.Viewport; {
dynamicStates[1] = VkDynamicState.Scissor; sType = VkStructureType.PipelineLayoutCreateInfo,
setLayoutCount = 0,
pushConstantRangeCount = 0,
};
VkPipelineDynamicStateCreateInfo dynamicState = default; fixed (VkPipelineLayout* layoutPtr = &PipelineLayout)
dynamicState.sType = VkStructureType.PipelineDynamicStateCreateInfo; {
dynamicState.dynamicStateCount = 2; var result = Vk.vkCreatePipelineLayout(_device, &layoutInfo, 0, layoutPtr);
dynamicState.pDynamicStates = dynamicStates; if (result != VkResult.Success)
throw new InvalidOperationException($"vkCreatePipelineLayout failed: {result}");
}
var uboBinding = new VkDescriptorSetLayoutBinding var renderingInfo = new VkPipelineRenderingCreateInfo
{ {
binding = 0, sType = VkStructureType.PipelineRenderingCreateInfo,
descriptorType = VkDescriptorType.UniformBuffer, colorAttachmentCount = 1,
descriptorCount = 1, pColorAttachmentFormats = &colorFormat,
stageFlags = VkShaderStageFlags.Vertex | VkShaderStageFlags.Fragment, };
pImmutableSamplers = null
};
Console.WriteLine("[Vulkan] Creating descriptor set layout..."); var pipelineInfo = new VkGraphicsPipelineCreateInfo
VkDescriptorSetLayoutCreateInfo dsLayoutInfo; {
dsLayoutInfo.sType = VkStructureType.DescriptorSetLayoutCreateInfo; sType = VkStructureType.GraphicsPipelineCreateInfo,
dsLayoutInfo.pNext = null; pNext = (nint)(&renderingInfo),
dsLayoutInfo.flags = 0; stageCount = 2,
dsLayoutInfo.bindingCount = 1; pStages = stages,
dsLayoutInfo.pBindings = &uboBinding; pVertexInputState = &vertexInputState,
pInputAssemblyState = &inputAssemblyState,
pViewportState = &viewportState,
pRasterizationState = &rasterizationState,
pMultisampleState = &multisampleState,
pColorBlendState = &colorBlendState,
pDynamicState = &dynamicState,
layout = PipelineLayout,
renderPass = new VkRenderPass { Handle = 0 },
subpass = 0,
};
VkDescriptorSetLayout dsLayout; fixed (VkPipeline* pipePtr = &Pipeline)
VkResult result = Vk.vkCreateDescriptorSetLayout(_ctx.Device, &dsLayoutInfo, null, &dsLayout); {
Vk.CheckResult(result, "vkCreateDescriptorSetLayout"); var result = Vk.vkCreateGraphicsPipelines(_device, 0, 1, &pipelineInfo, 0, pipePtr);
DescriptorSetLayout = dsLayout; if (result != VkResult.Success)
Console.WriteLine("[Vulkan] Descriptor set layout created."); throw new InvalidOperationException($"vkCreateGraphicsPipelines failed: {result}");
}
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 = &dynamicState;
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 (dynamic rendering)");
Console.WriteLine("[Vulkan] Graphics pipeline created.");
} }
private VkShaderModule CreateShaderModule(string path) private VkShaderModule CreateShaderModule(byte[] spv)
{ {
var fullPath = Path.Combine(AppContext.BaseDirectory, path); fixed (byte* pCode = spv)
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; var info = new VkShaderModuleCreateInfo
createInfo.sType = VkStructureType.ShaderModuleCreateInfo; {
createInfo.pNext = null; sType = VkStructureType.ShaderModuleCreateInfo,
createInfo.flags = 0; codeSize = (nuint)spv.Length,
createInfo.codeSize = codeSize; pCode = (uint*)pCode,
createInfo.pCode = (uint*)pCode; };
VkShaderModule module; var module = VkShaderModule.Null;
var result = Vk.vkCreateShaderModule(_ctx.Device, &createInfo, null, &module); var result = Vk.vkCreateShaderModule(_device, &info, 0, &module);
Vk.CheckResult(result, $"vkCreateShaderModule ({path})"); if (result != VkResult.Success)
throw new InvalidOperationException($"vkCreateShaderModule failed: {result}");
return module; return module;
} }
} }
@@ -283,10 +211,9 @@ public sealed unsafe class VulkanPipeline : IDisposable
if (_disposed) return; if (_disposed) return;
_disposed = true; _disposed = true;
if (Pipeline.Value != 0) Vk.vkDestroyPipeline(_ctx.Device, Pipeline, null); if (Pipeline.Handle != 0) Vk.vkDestroyPipeline(_device, Pipeline, 0);
if (PipelineLayout.Value != 0) Vk.vkDestroyPipelineLayout(_ctx.Device, PipelineLayout, null); if (PipelineLayout.Handle != 0) Vk.vkDestroyPipelineLayout(_device, PipelineLayout, 0);
if (DescriptorSetLayout.Value != 0) Vk.vkDestroyDescriptorSetLayout(_ctx.Device, DescriptorSetLayout, null); if (FragModule.Handle != 0) Vk.vkDestroyShaderModule(_device, FragModule, 0);
if (VertexShader.Value != 0) Vk.vkDestroyShaderModule(_ctx.Device, VertexShader, null); if (VertModule.Handle != 0) Vk.vkDestroyShaderModule(_device, VertModule, 0);
if (FragmentShader.Value != 0) Vk.vkDestroyShaderModule(_ctx.Device, FragmentShader, null);
} }
} }
@@ -1,40 +1,48 @@
using Engine.Core; using Engine.Core;
using Engine.Graphics;
namespace Engine.Graphics.Vulkan; namespace Engine.Graphics.Vulkan;
public sealed class VulkanRenderContext : IRenderContext internal sealed class VulkanRenderContext : IRenderContext
{ {
private readonly VulkanContext _context; private readonly VulkanContext _ctx;
private readonly VulkanSwapchain _swapchain; private readonly VulkanSwapchain _swapchain;
private readonly VulkanPipeline _pipeline; private readonly IWindow _window;
private readonly VulkanRenderer _renderer; private bool _disposed;
private readonly Sdl3Window _window;
public IWindow Window => _window; public IWindow Window => _window;
public VulkanRenderContext(int width, int height, bool enableValidation) public VulkanRenderContext(IWindow window, bool enableValidation)
{ {
_window = new Sdl3Window("Cortex Engine", width, height, vulkanSurface: true); _window = window;
_context = new VulkanContext(_window, enableValidation); _ctx = new VulkanContext(window, enableValidation);
_swapchain = new VulkanSwapchain(_context, width, height);
_pipeline = new VulkanPipeline(_context, _swapchain.RenderPass); var surfaceFormat = new VkSurfaceFormatKHR
_renderer = new VulkanRenderer(_context, _swapchain, _pipeline); {
format = _ctx.SurfaceFormat,
colorSpace = _ctx.SurfaceColorSpace,
};
_swapchain = new VulkanSwapchain(_ctx.Device, _ctx.PhysicalDevice, _ctx.Surface,
surfaceFormat, window.Width, window.Height);
} }
public IRenderer CreateRenderer() => _renderer; public IRenderer CreateRenderer()
{
return new VulkanRenderer(_ctx, _swapchain);
}
public void Resize(int width, int height) public void Resize(int width, int height)
{ {
_renderer.OnResize(); _swapchain.Recreate(width, height);
} }
public void Dispose() public void Dispose()
{ {
_renderer.Dispose(); if (_disposed) return;
_pipeline.Dispose(); _disposed = true;
_swapchain.Dispose();
_context.Dispose(); _swapchain?.Dispose();
_window.Dispose(); _ctx?.Dispose();
_window?.Dispose();
} }
} }
+204 -619
View File
@@ -1,704 +1,289 @@
using System.Numerics; using System.Numerics;
using System.Runtime.InteropServices;
using Engine.Core; using Engine.Core;
using Engine.Core.Components; using Engine.Core.Components;
using Engine.Graphics;
using Flecs.NET.Core; using Flecs.NET.Core;
using System.Runtime.InteropServices;
namespace Engine.Graphics.Vulkan; namespace Engine.Graphics.Vulkan;
public sealed unsafe class VulkanRenderer : IRenderer, IScreenshotProvider internal sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreenshotProvider
{ {
public readonly VulkanContext _ctx; private readonly VulkanContext _ctx;
public readonly VulkanSwapchain _swapchain; private readonly VulkanSwapchain _swapchain;
private readonly VulkanPipeline _pipeline; private readonly VulkanPipeline _pipeline;
public VulkanImGui? ImGuiLayer; private readonly VulkanFrameResources _frameResources;
private readonly VulkanVertexBuffer _vertexBuffer;
private VkCommandPool _commandPool;
private VkCommandBuffer[] _commandBuffers = Array.Empty<VkCommandBuffer>();
private VkSemaphore[] _imageAvailableSemaphores = Array.Empty<VkSemaphore>();
private VkSemaphore[] _renderFinishedSemaphores = Array.Empty<VkSemaphore>();
private VkFence[] _inFlightFences = Array.Empty<VkFence>();
private VkDescriptorPool _descriptorPool;
private VkDescriptorSet[] _descriptorSets = Array.Empty<VkDescriptorSet>();
private VulkanBuffer[] _uboBuffers = Array.Empty<VulkanBuffer>();
private const int MaxFramesInFlight = 2;
private int _currentFrame;
private uint _imageIndex;
private bool _resized;
private readonly Dictionary<ulong, (VulkanBuffer vertex, VulkanBuffer index, uint indexCount)> _meshCache = new();
private int _frameIndex;
private bool _disposed;
private bool _screenshotRequested; private bool _screenshotRequested;
private string _screenshotPath = ""; private string? _screenshotPath;
private TaskCompletionSource<byte[]>? _screenshotTcs;
private VulkanBuffer? _screenshotStaging;
private uint _screenshotImageIndex;
private bool _screenshotPending;
public bool IsScreenshotRequested => _screenshotRequested; public bool IsScreenshotRequested => _screenshotRequested;
public IScreenshotProvider ScreenshotProvider => this; public IScreenshotProvider ScreenshotProvider => this;
public VulkanRenderer(VulkanContext ctx, VulkanSwapchain swapchain, VulkanPipeline pipeline) public VulkanRenderer(VulkanContext ctx, VulkanSwapchain swapchain)
{ {
_ctx = ctx; _ctx = ctx;
_swapchain = swapchain; _swapchain = swapchain;
_pipeline = pipeline;
CreateCommandPool(); var vertSpv = LoadShader("Shaders/triangle.vert.spv");
CreateSyncObjects(); var fragSpv = LoadShader("Shaders/triangle.frag.spv");
CreateDescriptorPool();
CreateDescriptorSets();
CreateCommandBuffers();
}
private unsafe void CreateCommandPool() _pipeline = new VulkanPipeline(ctx.Device, swapchain.Format, vertSpv, fragSpv);
{
VkCommandPoolCreateInfo createInfo;
createInfo.sType = VkStructureType.CommandPoolCreateInfo;
createInfo.pNext = null;
createInfo.flags = 0x00000002;
createInfo.queueFamilyIndex = _ctx.GraphicsFamily;
VkCommandPool pool; _frameResources = new VulkanFrameResources(ctx.Device, ctx.GraphicsQueueFamilyIndex, swapchain.ImageCount);
VkResult result = Vk.vkCreateCommandPool(_ctx.Device, &createInfo, null, &pool);
Vk.CheckResult(result, "vkCreateCommandPool");
_commandPool = pool;
}
private unsafe void CreateSyncObjects() var vertices = new Vertex[]
{
_imageAvailableSemaphores = new VkSemaphore[MaxFramesInFlight];
_renderFinishedSemaphores = new VkSemaphore[MaxFramesInFlight];
_inFlightFences = new VkFence[MaxFramesInFlight];
for (var i = 0; i < MaxFramesInFlight; i++)
{ {
VkSemaphoreCreateInfo semInfo; new(new Vector3( 0.0f, -0.5f, 0.0f), new Vector3(1.0f, 0.0f, 0.0f), new Vector3(0, 0, 1)),
semInfo.sType = VkStructureType.SemaphoreCreateInfo; new(new Vector3( 0.5f, 0.5f, 0.0f), new Vector3(0.0f, 1.0f, 0.0f), new Vector3(0, 0, 1)),
semInfo.pNext = null; new(new Vector3(-0.5f, 0.5f, 0.0f), new Vector3(0.0f, 0.0f, 1.0f), new Vector3(0, 0, 1)),
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; _vertexBuffer = new VulkanVertexBuffer(ctx.Device, ctx.PhysicalDevice,
createInfo.sType = VkStructureType.DescriptorPoolCreateInfo; _frameResources.CommandPool, ctx.GraphicsQueue, ctx, vertices);
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() public void RenderWorld(World world)
{ {
_uboBuffers = new VulkanBuffer[MaxFramesInFlight]; Render();
_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() private void Render()
{ {
_commandBuffers = new VkCommandBuffer[MaxFramesInFlight]; _frameResources.WaitFrame(_frameIndex);
VkCommandBufferAllocateInfo allocInfo; uint imageIndex;
allocInfo.sType = VkStructureType.CommandBufferAllocateInfo; var acquireResult = Vk.vkAcquireNextImageKHR(_ctx.Device, _swapchain.Swapchain,
allocInfo.pNext = null; ulong.MaxValue, _frameResources.AcquireSemaphores[_frameIndex], VkFence.Null, &imageIndex);
allocInfo.commandPool = _commandPool;
allocInfo.level = VkCommandBufferLevel.Primary;
allocInfo.commandBufferCount = (uint)MaxFramesInFlight;
fixed (VkCommandBuffer* pCmds = _commandBuffers) if (acquireResult == VkResult.ErrorOutOfDateKHR || acquireResult == VkResult.SuboptimalKHR)
{ {
Vk.CheckResult(Vk.vkAllocateCommandBuffers(_ctx.Device, &allocInfo, pCmds), "vkAllocateCommandBuffers"); _swapchain.Recreate(_ctx.SurfaceExtent.Width == 0 ? 1280 : (int)_ctx.SurfaceExtent.Width,
} _ctx.SurfaceExtent.Height == 0 ? 720 : (int)_ctx.SurfaceExtent.Height);
} Render();
public unsafe void RenderWorld(World world)
{
VkFence fence = _inFlightFences[_currentFrame];
Vk.CheckResult(Vk.vkWaitForFences(_ctx.Device, 1, &fence, 1, ulong.MaxValue), "vkWaitForFences");
if (_screenshotPending && _screenshotStaging != null)
{
FinishScreenshot();
}
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; return;
} }
Vk.CheckResult(acquireResult, "vkAcquireNextImageKHR");
_imageIndex = imageIndex; if (acquireResult != VkResult.Success)
throw new InvalidOperationException($"vkAcquireNextImageKHR failed: {acquireResult}");
VkFence fenceReset = _inFlightFences[_currentFrame]; var cmd = _frameResources.CommandBuffers[_frameIndex];
Vk.CheckResult(Vk.vkResetFences(_ctx.Device, 1, &fenceReset), "vkResetFences"); Vk.vkResetCommandBuffer(cmd, 0);
var cmd = _commandBuffers[_currentFrame]; var beginInfo = new VkCommandBufferBeginInfo
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; sType = VkStructureType.CommandBufferBeginInfo,
_swapchain.Recreate(_swapchain.Extent.width, _swapchain.Extent.height); flags = VkCommandBufferUsageFlags.OneTimeSubmit,
RecreateCommandBuffers(); };
} Vk.vkBeginCommandBuffer(cmd, &beginInfo);
else
TransitionImageLayout(cmd, _swapchain.Images[imageIndex],
VkImageLayout.Undefined, VkImageLayout.ColorAttachmentOptimal,
0, 0,
0x400, 0x100);
var clearValue = new VkClearValue
{ {
Vk.CheckResult(presentResult, "vkQueuePresentKHR"); Color = new VkClearColorValue { Float0 = 0.02f, Float1 = 0.02f, Float2 = 0.02f, Float3 = 1.0f },
}
_currentFrame = (_currentFrame + 1) % MaxFramesInFlight;
}
private unsafe void FinishScreenshot()
{
try
{
var width = _swapchain.Extent.width;
var height = _swapchain.Extent.height;
var dir = Path.GetDirectoryName(_screenshotPath);
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
Directory.CreateDirectory(dir);
var src = (byte*)_screenshotStaging!.MappedData;
if (src == null) throw new InvalidOperationException("Screenshot staging buffer not mapped");
var srcFormat = _swapchain.ImageFormat;
var rowSize = width * 4;
var pixelDataSize = rowSize * height;
var fileSize = 54u + (uint)pixelDataSize;
using (var fs = new FileStream(_screenshotPath, FileMode.Create))
using (var bw = new BinaryWriter(fs))
{
bw.Write((byte)'B');
bw.Write((byte)'M');
bw.Write(fileSize);
bw.Write(0u);
bw.Write(54u);
bw.Write(40u);
bw.Write((uint)width);
bw.Write((uint)height);
bw.Write((ushort)1);
bw.Write((ushort)32);
bw.Write((uint)pixelDataSize);
bw.Write(0u);
bw.Write(0u);
bw.Write(0u);
bw.Write(0u);
var rowBuf = new byte[rowSize];
for (var y = 0; y < height; y++)
{
var srcRow = (height - 1 - y) * width * 4;
if (srcFormat == VkFormat.B8G8R8A8Srgb || srcFormat == VkFormat.B8G8R8A8Unorm)
{
for (var x = 0; x < width; x++)
{
rowBuf[x * 4 + 0] = src[srcRow + x * 4 + 0];
rowBuf[x * 4 + 1] = src[srcRow + x * 4 + 1];
rowBuf[x * 4 + 2] = src[srcRow + x * 4 + 2];
rowBuf[x * 4 + 3] = src[srcRow + x * 4 + 3];
}
}
else
{
for (var x = 0; x < width; x++)
{
rowBuf[x * 4 + 0] = src[srcRow + x * 4 + 2];
rowBuf[x * 4 + 1] = src[srcRow + x * 4 + 1];
rowBuf[x * 4 + 2] = src[srcRow + x * 4 + 0];
rowBuf[x * 4 + 3] = src[srcRow + x * 4 + 3];
}
}
bw.Write(rowBuf, 0, rowSize);
}
}
_screenshotStaging.Dispose();
_screenshotStaging = null;
_screenshotPending = false;
Console.WriteLine($"Screenshot saved: {_screenshotPath} ({fileSize} bytes, {width}x{height})");
_screenshotTcs?.TrySetResult(Array.Empty<byte>());
_screenshotTcs = null;
}
catch (Exception ex)
{
Console.WriteLine($"Screenshot capture failed: {ex}");
_screenshotStaging?.Dispose();
_screenshotStaging = null;
_screenshotPending = false;
_screenshotTcs?.TrySetException(ex);
_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]; var colorAttachment = new VkRenderingAttachmentInfo
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; sType = VkStructureType.RenderingAttachmentInfo,
rpBegin.pClearValues = pClear; imageView = _swapchain.ImageViews[imageIndex],
imageLayout = VkImageLayout.ColorAttachmentOptimal,
loadOp = VkAttachmentLoadOp.Clear,
storeOp = VkAttachmentStoreOp.Store,
clearValue = clearValue,
};
Vk.vkCmdBeginRenderPass(cmd, &rpBegin, VkSubpassContents.Inline); var renderingInfo = new VkRenderingInfo
} {
sType = VkStructureType.RenderingInfo,
renderArea = new VkRect2D
{
Offset = new VkOffset2D { X = 0, Y = 0 },
Extent = _swapchain.Extent,
},
layerCount = 1,
colorAttachmentCount = 1,
pColorAttachments = &colorAttachment,
};
Vk.vkCmdBindPipeline(cmd, 0, _pipeline.Pipeline); Vk.vkCmdBeginRendering(cmd, &renderingInfo);
Vk.vkCmdBindPipeline(cmd, VkPipelineBindPoint.Graphics, _pipeline.Pipeline);
var viewport = new VkViewport var viewport = new VkViewport
{ {
x = 0, y = 0, X = 0, Y = 0,
width = _swapchain.Extent.width, Width = _swapchain.Extent.Width,
height = _swapchain.Extent.height, Height = _swapchain.Extent.Height,
minDepth = 0, maxDepth = 1 MinDepth = 0, MaxDepth = 1,
}; };
Vk.vkCmdSetViewport(cmd, 0, 1, &viewport); Vk.vkCmdSetViewport(cmd, 0, 1, &viewport);
var scissor = new VkRect2D var scissor = new VkRect2D
{ {
offset = new VkOffset2D { x = 0, y = 0 }, Offset = new VkOffset2D { X = 0, Y = 0 },
extent = _swapchain.Extent Extent = _swapchain.Extent,
}; };
Vk.vkCmdSetScissor(cmd, 0, 1, &scissor); Vk.vkCmdSetScissor(cmd, 0, 1, &scissor);
var ds = _descriptorSets[_currentFrame]; var bufferHandle = _vertexBuffer.Buffer;
Vk.vkCmdBindDescriptorSets(cmd, 0, _pipeline.PipelineLayout, 0, 1, &ds, 0, null); ulong offset = 0;
Vk.vkCmdBindVertexBuffers(cmd, 0, 1, &bufferHandle, &offset);
world.Each((Entity e, ref Transform transform, ref Mesh mesh, ref Material material) => Vk.vkCmdDraw(cmd, 3, 1, 0, 0);
Vk.vkCmdEndRendering(cmd);
TransitionImageLayout(cmd, _swapchain.Images[imageIndex],
VkImageLayout.ColorAttachmentOptimal, VkImageLayout.PresentSrcKHR,
0x400, 0x100,
0x8000, 0);
Vk.vkEndCommandBuffer(cmd);
var waitInfo = new VkSemaphoreSubmitInfo
{ {
var meshKey = (ulong)mesh.GetHashCode(); sType = VkStructureType.SemaphoreSubmitInfo,
if (!_meshCache.TryGetValue(meshKey, out var meshBuffers)) semaphore = _frameResources.AcquireSemaphores[_frameIndex],
{ stageMask = 0x400,
if (mesh.Vertices.Length == 0 || mesh.Indices.Length == 0) return; };
var vertexBuffer = VulkanBuffer.CreateDeviceLocal(_ctx, _commandPool, mesh.Vertices, VkBufferUsageFlags.VertexBuffer); var cmdInfo = new VkCommandBufferSubmitInfo
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 = proj * view * model;
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);
}
});
if (ImGuiLayer != null)
{ {
ImGuiLayer.Render(cmd); sType = VkStructureType.CommandBufferSubmitInfo,
commandBuffer = cmd,
};
var signalInfo = new VkSemaphoreSubmitInfo
{
sType = VkStructureType.SemaphoreSubmitInfo,
semaphore = _frameResources.SubmitSemaphores[imageIndex],
stageMask = 0x8000,
};
var submitInfo = new VkSubmitInfo2
{
sType = VkStructureType.SubmitInfo2,
waitSemaphoreInfoCount = 1,
pWaitSemaphoreInfos = &waitInfo,
commandBufferInfoCount = 1,
pCommandBufferInfos = &cmdInfo,
signalSemaphoreInfoCount = 1,
pSignalSemaphoreInfos = &signalInfo,
};
var submitResult = Vk.vkQueueSubmit2(_ctx.GraphicsQueue, 1, &submitInfo,
_frameResources.FrameFences[_frameIndex]);
if (submitResult != VkResult.Success)
throw new InvalidOperationException($"vkQueueSubmit2 failed: {submitResult}");
var presentSwapchain = _swapchain.Swapchain;
var presentSemaphore = _frameResources.SubmitSemaphores[imageIndex];
var presentInfo = new VkPresentInfoKHR
{
sType = VkStructureType.PresentInfoKHR,
waitSemaphoreCount = 1,
pWaitSemaphores = &presentSemaphore,
swapchainCount = 1,
pSwapchains = &presentSwapchain,
pImageIndices = &imageIndex,
};
var presentResult = Vk.vkQueuePresentKHR(_ctx.GraphicsQueue, &presentInfo);
if (presentResult == VkResult.ErrorOutOfDateKHR || presentResult == VkResult.SuboptimalKHR)
{
_swapchain.Recreate(_ctx.SurfaceExtent.Width == 0 ? 1280 : (int)_ctx.SurfaceExtent.Width,
_ctx.SurfaceExtent.Height == 0 ? 720 : (int)_ctx.SurfaceExtent.Height);
} }
Vk.vkCmdEndRenderPass(cmd); _frameIndex = (_frameIndex + 1) % VulkanFrameResources.MaxFramesInFlight;
if (_screenshotRequested)
{
var width = _swapchain.Extent.width;
var height = _swapchain.Extent.height;
var bufferSize = (ulong)(width * height * 4);
_screenshotStaging = new VulkanBuffer(_ctx, bufferSize,
VkBufferUsageFlags.TransferDst,
VkMemoryPropertyFlags.HostVisible | VkMemoryPropertyFlags.HostCoherent);
_screenshotImageIndex = imageIndex;
_screenshotPending = true;
_screenshotRequested = false;
var barrier = new VkImageMemoryBarrier
{
sType = VkStructureType.ImageMemoryBarrier,
pNext = null,
srcAccessMask = VkAccessFlags.ColorAttachmentWrite,
dstAccessMask = VkAccessFlags.TransferRead,
oldLayout = VkImageLayout.PresentSrcKHR,
newLayout = VkImageLayout.TransferSrcOptimal,
srcQueueFamilyIndex = ~0u,
dstQueueFamilyIndex = ~0u,
image = _swapchain.SwapchainImages[imageIndex],
subresourceRange = new VkImageSubresourceRange
{
aspectMask = VkImageAspectFlags.Color,
baseMipLevel = 0,
levelCount = 1,
baseArrayLayer = 0,
layerCount = 1
}
};
Vk.vkCmdPipelineBarrier(cmd,
VkPipelineStageFlags.ColorAttachmentOutput,
VkPipelineStageFlags.Transfer,
0, 0, null, 0, null, 1, &barrier);
var region = new VkBufferImageCopy
{
bufferOffset = 0,
bufferRowLength = (uint)width,
bufferImageHeight = (uint)height,
imageSubresource = new VkImageSubresourceLayers
{
aspectMask = VkImageAspectFlags.Color,
mipLevel = 0,
baseArrayLayer = 0,
layerCount = 1
},
imageOffset = new VkOffset3D { x = 0, y = 0, z = 0 },
imageExtent = new VkExtent3D { width = width, height = height, depth = 1 }
};
var stagingBuf = _screenshotStaging.Buffer;
Vk.vkCmdCopyImageToBuffer(cmd,
_swapchain.SwapchainImages[imageIndex],
(int)VkImageLayout.TransferSrcOptimal,
stagingBuf, 1, &region);
var barrier2 = new VkImageMemoryBarrier
{
sType = VkStructureType.ImageMemoryBarrier,
pNext = null,
srcAccessMask = VkAccessFlags.TransferRead,
dstAccessMask = VkAccessFlags.MemoryRead,
oldLayout = VkImageLayout.TransferSrcOptimal,
newLayout = VkImageLayout.PresentSrcKHR,
srcQueueFamilyIndex = ~0u,
dstQueueFamilyIndex = ~0u,
image = _swapchain.SwapchainImages[imageIndex],
subresourceRange = new VkImageSubresourceRange
{
aspectMask = VkImageAspectFlags.Color,
baseMipLevel = 0,
levelCount = 1,
baseArrayLayer = 0,
layerCount = 1
}
};
Vk.vkCmdPipelineBarrier(cmd,
VkPipelineStageFlags.Transfer,
VkPipelineStageFlags.BottomOfPipe,
0, 0, null, 0, null, 1, &barrier2);
}
Vk.CheckResult(Vk.vkEndCommandBuffer(cmd), "vkEndCommandBuffer");
} }
private unsafe void RecreateCommandBuffers() private static void TransitionImageLayout(VkCommandBuffer cmd, VkImage image,
VkImageLayout oldLayout, VkImageLayout newLayout,
ulong srcStage, ulong srcAccess,
ulong dstStage, ulong dstAccess)
{ {
fixed (VkCommandBuffer* pCmds = _commandBuffers) var barrier = new VkImageMemoryBarrier2
{ {
Vk.vkFreeCommandBuffers(_ctx.Device, _commandPool, (uint)_commandBuffers.Length, pCmds); sType = VkStructureType.ImageMemoryBarrier2,
} srcStageMask = srcStage,
srcAccessMask = srcAccess,
dstStageMask = dstStage,
dstAccessMask = dstAccess,
oldLayout = oldLayout,
newLayout = newLayout,
image = image,
subresourceRange = new VkImageSubresourceRange
{
AspectMask = VkImageAspectFlags.Color,
LevelCount = 1,
LayerCount = 1,
},
};
VkCommandBufferAllocateInfo allocInfo; var depInfo = new VkDependencyInfo
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)"); sType = VkStructureType.DependencyInfo,
} imageMemoryBarrierCount = 1,
pImageMemoryBarriers = &barrier,
};
Vk.vkCmdPipelineBarrier2(cmd, &depInfo);
} }
public void RequestScreenshot(string outputPath) private static byte[] LoadShader(string path)
{
if (!File.Exists(path))
{
var altPath = Path.Combine(AppContext.BaseDirectory, path);
if (!File.Exists(altPath))
{
altPath = Path.Combine(AppContext.BaseDirectory, "Shaders", Path.GetFileName(path));
if (!File.Exists(altPath))
throw new FileNotFoundException($"Shader file not found: {path}");
}
return File.ReadAllBytes(altPath);
}
return File.ReadAllBytes(path);
}
public void RequestScreenshot(string path)
{ {
_screenshotPath = outputPath;
_screenshotRequested = true; _screenshotRequested = true;
_screenshotPath = path;
} }
public Task<byte[]> CaptureAsync(string outputPath) public Task<byte[]> CaptureAsync(string outputPath)
{ {
_screenshotPath = outputPath; _screenshotRequested = false;
_screenshotTcs = new TaskCompletionSource<byte[]>(); return Task.FromResult(Array.Empty<byte>());
_screenshotRequested = true;
return _screenshotTcs.Task;
} }
public void OnResize() => _resized = true;
public unsafe void Dispose() public string? TryTakeScreenshotPath()
{ {
Vk.vkQueueWaitIdle(_ctx.GraphicsQueue); var path = _screenshotRequested ? _screenshotPath : null;
_screenshotRequested = false;
return path;
}
foreach (var mesh in _meshCache.Values) public void Dispose()
{ {
mesh.vertex.Dispose(); if (_disposed) return;
mesh.index.Dispose(); _disposed = true;
}
_meshCache.Clear();
foreach (var ubo in _uboBuffers) Vk.vkDeviceWaitIdle(_ctx.Device);
ubo?.Dispose();
if (_descriptorPool.Value != 0) _vertexBuffer?.Dispose();
Vk.vkDestroyDescriptorPool(_ctx.Device, _descriptorPool, null); _frameResources?.Dispose();
_pipeline?.Dispose();
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);
}
} }
} }
+909
View File
@@ -0,0 +1,909 @@
using System.Runtime.InteropServices;
namespace Engine.Graphics.Vulkan;
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkExtent2D
{
public uint Width;
public uint Height;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkExtent3D
{
public uint Width;
public uint Height;
public uint Depth;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkOffset2D
{
public int X;
public int Y;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkOffset3D
{
public int X;
public int Y;
public int Z;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkRect2D
{
public VkOffset2D Offset;
public VkExtent2D Extent;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkViewport
{
public float X;
public float Y;
public float Width;
public float Height;
public float MinDepth;
public float MaxDepth;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkComponentMapping
{
public VkComponentSwizzle R;
public VkComponentSwizzle G;
public VkComponentSwizzle B;
public VkComponentSwizzle A;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkImageSubresourceRange
{
public VkImageAspectFlags AspectMask;
public uint BaseMipLevel;
public uint LevelCount;
public uint BaseArrayLayer;
public uint LayerCount;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkClearColorValue
{
public float Float0;
public float Float1;
public float Float2;
public float Float3;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkClearDepthStencilValue
{
public float Depth;
public uint Stencil;
}
[StructLayout(LayoutKind.Explicit)]
public unsafe struct VkClearValue
{
[FieldOffset(0)] public VkClearColorValue Color;
[FieldOffset(0)] public VkClearDepthStencilValue DepthStencil;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkApplicationInfo
{
public VkStructureType sType;
public nint pNext;
public byte* pApplicationName;
public uint applicationVersion;
public byte* pEngineName;
public uint engineVersion;
public uint apiVersion;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkInstanceCreateInfo
{
public VkStructureType sType;
public nint pNext;
public uint flags;
public VkApplicationInfo* pApplicationInfo;
public uint enabledLayerCount;
public byte** ppEnabledLayerNames;
public uint enabledExtensionCount;
public byte** ppEnabledExtensionNames;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkDebugUtilsMessengerCreateInfoEXT
{
public VkStructureType sType;
public nint pNext;
public uint flags;
public VkDebugUtilsMessageSeverityFlagsEXT messageSeverity;
public VkDebugUtilsMessageTypeFlagsEXT messageType;
public nint pfnUserCallback;
public nint pUserData;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkDeviceQueueCreateInfo
{
public VkStructureType sType;
public nint pNext;
public uint flags;
public uint queueFamilyIndex;
public uint queueCount;
public float* pQueuePriorities;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkPhysicalDeviceFeatures
{
public VkBool32 robustBufferAccess;
public VkBool32 fullDrawIndexUint32;
public VkBool32 imageCubeArray;
public VkBool32 independentBlend;
public VkBool32 geometryShader;
public VkBool32 tessellationShader;
public VkBool32 sampleRateShading;
public VkBool32 dualSrcBlend;
public VkBool32 logicOp;
public VkBool32 multiDrawIndirect;
public VkBool32 drawIndirectFirstInstance;
public VkBool32 depthClamp;
public VkBool32 depthBiasClamp;
public VkBool32 fillModeNonSolid;
public VkBool32 depthBounds;
public VkBool32 wideLines;
public VkBool32 largePoints;
public VkBool32 alphaToOne;
public VkBool32 multiViewport;
public VkBool32 samplerAnisotropy;
public VkBool32 textureCompressionETC2;
public VkBool32 textureCompressionASTC_LDR;
public VkBool32 textureCompressionBC;
public VkBool32 occlusionQueryPrecise;
public VkBool32 pipelineStatisticsQuery;
public VkBool32 vertexPipelineStoresAndAtomics;
public VkBool32 fragmentStoresAndAtomics;
public VkBool32 shaderTessellationAndGeometryPointSize;
public VkBool32 shaderImageGatherExtended;
public VkBool32 shaderStorageImageExtendedFormats;
public VkBool32 shaderStorageImageMultisample;
public VkBool32 shaderStorageImageReadWithoutFormat;
public VkBool32 shaderStorageImageWriteWithoutFormat;
public VkBool32 shaderUniformBufferArrayDynamicIndexing;
public VkBool32 shaderSampledImageArrayDynamicIndexing;
public VkBool32 shaderStorageBufferArrayDynamicIndexing;
public VkBool32 shaderStorageImageArrayDynamicIndexing;
public VkBool32 shaderClipDistance;
public VkBool32 shaderCullDistance;
public VkBool32 shaderFloat64;
public VkBool32 shaderInt64;
public VkBool32 shaderInt16;
public VkBool32 shaderResourceResidency;
public VkBool32 shaderResourceMinLod;
public VkBool32 sparseBinding;
public VkBool32 sparseResidencyBuffer;
public VkBool32 sparseResidencyImage2D;
public VkBool32 sparseResidencyImage3D;
public VkBool32 sparseResidency2Samples;
public VkBool32 sparseResidency4Samples;
public VkBool32 sparseResidency8Samples;
public VkBool32 sparseResidency16Samples;
public VkBool32 sparseResidencyAliased;
public VkBool32 variableMultisampleRate;
public VkBool32 inheritedQueries;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkPhysicalDeviceDynamicRenderingFeatures
{
public VkStructureType sType;
public nint pNext;
public VkBool32 dynamicRendering;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkPhysicalDeviceSynchronization2Features
{
public VkStructureType sType;
public nint pNext;
public VkBool32 synchronization2;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkDeviceCreateInfo
{
public VkStructureType sType;
public nint pNext;
public uint flags;
public uint queueCreateInfoCount;
public VkDeviceQueueCreateInfo* pQueueCreateInfos;
public uint enabledLayerCount;
public byte** ppEnabledLayerNames;
public uint enabledExtensionCount;
public byte** ppEnabledExtensionNames;
public VkPhysicalDeviceFeatures* pEnabledFeatures;
}
[StructLayout(LayoutKind.Sequential)]
public 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 byte pipelineCacheUUID[16];
public VkPhysicalDeviceLimits Limits;
}
[StructLayout(LayoutKind.Sequential)]
public 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 uint bufferImageGranularity;
public uint 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 uint maxComputeWorkGroupCount0;
public uint maxComputeWorkGroupCount1;
public uint maxComputeWorkGroupCount2;
public uint maxComputeWorkGroupInvocations;
public uint maxComputeWorkGroupSize0;
public uint maxComputeWorkGroupSize1;
public uint maxComputeWorkGroupSize2;
public uint subPixelPrecisionBits;
public uint subTexelPrecisionBits;
public uint mipMapPrecisionBits;
public uint maxDrawIndexedIndexValue;
public uint maxDrawIndirectCount;
public float maxSamplerLodBias;
public float maxSamplerAnisotropy;
public uint maxViewports;
public uint maxViewportDimensions0;
public uint maxViewportDimensions1;
public float viewportBoundsRange0;
public float viewportBoundsRange1;
public uint viewportSubPixelBits;
public ulong minMemoryMapAlignment;
public ulong minTexelBufferOffsetAlignment;
public ulong minUniformBufferOffsetAlignment;
public ulong 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 maxColorAttachments;
public uint maxSampleMaskWords;
public float timestampPeriod;
public uint maxClipDistances;
public uint maxCullDistances;
public uint maxCombinedClipAndCullDistances;
public uint discreteQueuePriorities;
public float pointSizeRange0;
public float pointSizeRange1;
public float lineWidthRange0;
public float lineWidthRange1;
public float pointSizeGranularity;
public float lineWidthGranularity;
public VkBool32 strictLines;
public VkBool32 standardSampleLocations;
public ulong optimalBufferCopyOffsetAlignment;
public ulong optimalBufferCopyRowPitchAlignment;
public ulong nonCoherentAtomSize;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkQueueFamilyProperties
{
public VkQueueFlags queueFlags;
public uint queueCount;
public uint timestampValidBits;
public VkExtent3D minImageTransferGranularity;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkMemoryType
{
public VkMemoryPropertyFlags propertyFlags;
public uint heapIndex;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkMemoryHeap
{
public ulong size;
public uint flags;
}
[StructLayout(LayoutKind.Sequential)]
public 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 VkMemoryType memoryTypes16;
public VkMemoryType memoryTypes17;
public VkMemoryType memoryTypes18;
public VkMemoryType memoryTypes19;
public VkMemoryType memoryTypes20;
public VkMemoryType memoryTypes21;
public VkMemoryType memoryTypes22;
public VkMemoryType memoryTypes23;
public VkMemoryType memoryTypes24;
public VkMemoryType memoryTypes25;
public VkMemoryType memoryTypes26;
public VkMemoryType memoryTypes27;
public VkMemoryType memoryTypes28;
public VkMemoryType memoryTypes29;
public VkMemoryType memoryTypes30;
public VkMemoryType memoryTypes31;
public uint memoryHeapCount;
public VkMemoryHeap memoryHeaps0;
public VkMemoryHeap memoryHeaps1;
public VkMemoryHeap memoryHeaps2;
public VkMemoryHeap memoryHeaps3;
public VkMemoryHeap memoryHeaps4;
public VkMemoryHeap memoryHeaps5;
public VkMemoryHeap memoryHeaps6;
public VkMemoryHeap memoryHeaps7;
public VkMemoryHeap memoryHeaps8;
public VkMemoryHeap memoryHeaps9;
public VkMemoryHeap memoryHeaps10;
public VkMemoryHeap memoryHeaps11;
public VkMemoryHeap memoryHeaps12;
public VkMemoryHeap memoryHeaps13;
public VkMemoryHeap memoryHeaps14;
public VkMemoryHeap memoryHeaps15;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkSurfaceCapabilitiesKHR
{
public uint minImageCount;
public uint maxImageCount;
public VkExtent2D currentExtent;
public VkExtent2D minImageExtent;
public VkExtent2D maxImageExtent;
public uint maxImageArrayLayers;
public VkSurfaceTransformFlagsKHR supportedTransforms;
public VkSurfaceTransformFlagsKHR currentTransform;
public VkCompositeAlphaFlagsKHR supportedCompositeAlpha;
public VkImageUsageFlags supportedUsageFlags;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkSurfaceFormatKHR
{
public VkFormat format;
public VkColorSpaceKHR colorSpace;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkSwapchainCreateInfoKHR
{
public VkStructureType sType;
public nint 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 VkCompositeAlphaFlagsKHR compositeAlpha;
public VkPresentModeKHR presentMode;
public VkBool32 clipped;
public VkSwapchainKHR oldSwapchain;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkImageViewCreateInfo
{
public VkStructureType sType;
public nint pNext;
public uint flags;
public VkImage image;
public VkImageViewType viewType;
public VkFormat format;
public VkComponentMapping components;
public VkImageSubresourceRange subresourceRange;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkShaderModuleCreateInfo
{
public VkStructureType sType;
public nint pNext;
public uint flags;
public nuint codeSize;
public uint* pCode;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkPipelineShaderStageCreateInfo
{
public VkStructureType sType;
public nint pNext;
public uint flags;
public VkShaderStageFlags stage;
public VkShaderModule module;
public byte* pName;
public nint pSpecializationInfo;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkVertexInputBindingDescription
{
public uint binding;
public uint stride;
public VkVertexInputRate inputRate;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkVertexInputAttributeDescription
{
public uint location;
public uint binding;
public VkFormat format;
public uint offset;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkPipelineVertexInputStateCreateInfo
{
public VkStructureType sType;
public nint pNext;
public uint flags;
public uint vertexBindingDescriptionCount;
public VkVertexInputBindingDescription* pVertexBindingDescriptions;
public uint vertexAttributeDescriptionCount;
public VkVertexInputAttributeDescription* pVertexAttributeDescriptions;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkPipelineInputAssemblyStateCreateInfo
{
public VkStructureType sType;
public nint pNext;
public uint flags;
public VkPrimitiveTopology topology;
public VkBool32 primitiveRestartEnable;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkPipelineViewportStateCreateInfo
{
public VkStructureType sType;
public nint pNext;
public uint flags;
public uint viewportCount;
public VkViewport* pViewports;
public uint scissorCount;
public VkRect2D* pScissors;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkPipelineRasterizationStateCreateInfo
{
public VkStructureType sType;
public nint pNext;
public uint flags;
public VkBool32 depthClampEnable;
public VkBool32 rasterizerDiscardEnable;
public VkPolygonMode polygonMode;
public VkCullModeFlags cullMode;
public VkFrontFace frontFace;
public VkBool32 depthBiasEnable;
public float depthBiasConstantFactor;
public float depthBiasClamp;
public float depthBiasSlopeFactor;
public float lineWidth;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkPipelineMultisampleStateCreateInfo
{
public VkStructureType sType;
public nint pNext;
public uint flags;
public VkSampleCountFlags rasterizationSamples;
public VkBool32 sampleShadingEnable;
public float minSampleShading;
public nint pSampleMask;
public VkBool32 alphaToCoverageEnable;
public VkBool32 alphaToOneEnable;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkPipelineColorBlendAttachmentState
{
public VkBool32 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)]
public unsafe struct VkPipelineColorBlendStateCreateInfo
{
public VkStructureType sType;
public nint pNext;
public uint flags;
public VkBool32 logicOpEnable;
public int logicOp;
public uint attachmentCount;
public VkPipelineColorBlendAttachmentState* pAttachments;
public float blendConstants0;
public float blendConstants1;
public float blendConstants2;
public float blendConstants3;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkPipelineDynamicStateCreateInfo
{
public VkStructureType sType;
public nint pNext;
public uint flags;
public uint dynamicStateCount;
public VkDynamicState* pDynamicStates;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkPipelineLayoutCreateInfo
{
public VkStructureType sType;
public nint pNext;
public uint flags;
public uint setLayoutCount;
public VkDescriptorSetLayout* pSetLayouts;
public uint pushConstantRangeCount;
public nint pPushConstantRanges;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkPipelineRenderingCreateInfo
{
public VkStructureType sType;
public nint pNext;
public uint viewMask;
public uint colorAttachmentCount;
public VkFormat* pColorAttachmentFormats;
public VkFormat depthAttachmentFormat;
public VkFormat stencilAttachmentFormat;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkGraphicsPipelineCreateInfo
{
public VkStructureType sType;
public nint pNext;
public uint flags;
public uint stageCount;
public VkPipelineShaderStageCreateInfo* pStages;
public VkPipelineVertexInputStateCreateInfo* pVertexInputState;
public VkPipelineInputAssemblyStateCreateInfo* pInputAssemblyState;
public nint pTessellationState;
public VkPipelineViewportStateCreateInfo* pViewportState;
public VkPipelineRasterizationStateCreateInfo* pRasterizationState;
public VkPipelineMultisampleStateCreateInfo* pMultisampleState;
public nint pDepthStencilState;
public VkPipelineColorBlendStateCreateInfo* pColorBlendState;
public VkPipelineDynamicStateCreateInfo* pDynamicState;
public VkPipelineLayout layout;
public VkRenderPass renderPass;
public uint subpass;
public VkPipeline basePipelineHandle;
public int basePipelineIndex;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkRenderPass { public nint Handle; }
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkCommandPoolCreateInfo
{
public VkStructureType sType;
public nint pNext;
public VkCommandPoolCreateFlags flags;
public uint queueFamilyIndex;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkCommandBufferAllocateInfo
{
public VkStructureType sType;
public nint pNext;
public VkCommandPool commandPool;
public VkCommandBufferLevel level;
public uint commandBufferCount;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkCommandBufferBeginInfo
{
public VkStructureType sType;
public nint pNext;
public VkCommandBufferUsageFlags flags;
public nint pInheritanceInfo;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkSemaphoreCreateInfo
{
public VkStructureType sType;
public nint pNext;
public uint flags;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkFenceCreateInfo
{
public VkStructureType sType;
public nint pNext;
public VkFenceCreateFlags flags;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkBufferCreateInfo
{
public VkStructureType sType;
public nint pNext;
public uint flags;
public ulong size;
public VkBufferUsageFlags usage;
public VkSharingMode sharingMode;
public uint queueFamilyIndexCount;
public uint* pQueueFamilyIndices;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkMemoryAllocateInfo
{
public VkStructureType sType;
public nint pNext;
public ulong allocationSize;
public uint memoryTypeIndex;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkMemoryRequirements
{
public ulong size;
public ulong alignment;
public uint memoryTypeBits;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkBufferCopy
{
public ulong srcOffset;
public ulong dstOffset;
public ulong size;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkRenderingAttachmentInfo
{
public VkStructureType sType;
public nint pNext;
public VkImageView imageView;
public VkImageLayout imageLayout;
public int resolveMode;
public VkImageView resolveImageView;
public VkImageLayout resolveImageLayout;
public VkAttachmentLoadOp loadOp;
public VkAttachmentStoreOp storeOp;
public VkClearValue clearValue;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkRenderingInfo
{
public VkStructureType sType;
public nint pNext;
public VkRenderingFlags flags;
public VkRect2D renderArea;
public uint layerCount;
public uint viewMask;
public uint colorAttachmentCount;
public VkRenderingAttachmentInfo* pColorAttachments;
public nint pDepthAttachment;
public nint pStencilAttachment;
}
[StructLayout(LayoutKind.Explicit)]
public unsafe struct VkImageMemoryBarrier2
{
[FieldOffset(0)] public VkStructureType sType;
[FieldOffset(8)] public nint pNext;
[FieldOffset(16)] public ulong srcStageMask;
[FieldOffset(24)] public ulong srcAccessMask;
[FieldOffset(32)] public ulong dstStageMask;
[FieldOffset(40)] public ulong dstAccessMask;
[FieldOffset(48)] public VkImageLayout oldLayout;
[FieldOffset(52)] public VkImageLayout newLayout;
[FieldOffset(56)] public uint srcQueueFamilyIndex;
[FieldOffset(60)] public uint dstQueueFamilyIndex;
[FieldOffset(64)] public VkImage image;
[FieldOffset(72)] public VkImageSubresourceRange subresourceRange;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkBufferMemoryBarrier2
{
public VkStructureType sType;
public nint pNext;
public VkPipelineStageFlags2 srcStageMask;
public VkAccessFlags2 srcAccessMask;
public VkPipelineStageFlags2 dstStageMask;
public VkAccessFlags2 dstAccessMask;
public uint srcQueueFamilyIndex;
public uint dstQueueFamilyIndex;
public VkBuffer buffer;
public ulong offset;
public ulong size;
}
[StructLayout(LayoutKind.Explicit)]
public unsafe struct VkDependencyInfo
{
[FieldOffset(0)] public VkStructureType sType;
[FieldOffset(8)] public nint pNext;
[FieldOffset(16)] public VkDependencyFlags dependencyFlags;
[FieldOffset(20)] public uint memoryBarrierCount;
[FieldOffset(24)] public nint pMemoryBarriers;
[FieldOffset(32)] public uint bufferMemoryBarrierCount;
[FieldOffset(40)] public VkBufferMemoryBarrier2* pBufferMemoryBarriers;
[FieldOffset(48)] public uint imageMemoryBarrierCount;
[FieldOffset(56)] public VkImageMemoryBarrier2* pImageMemoryBarriers;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkSemaphoreSubmitInfo
{
public VkStructureType sType;
public nint pNext;
public VkSemaphore semaphore;
public ulong value;
public ulong stageMask;
public uint deviceIndex;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkCommandBufferSubmitInfo
{
public VkStructureType sType;
public nint pNext;
public VkCommandBuffer commandBuffer;
public uint deviceMask;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkSubmitInfo2
{
public VkStructureType sType;
public nint pNext;
public uint flags;
public uint waitSemaphoreInfoCount;
public VkSemaphoreSubmitInfo* pWaitSemaphoreInfos;
public uint commandBufferInfoCount;
public VkCommandBufferSubmitInfo* pCommandBufferInfos;
public uint signalSemaphoreInfoCount;
public VkSemaphoreSubmitInfo* pSignalSemaphoreInfos;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkPresentInfoKHR
{
public VkStructureType sType;
public nint pNext;
public uint waitSemaphoreCount;
public VkSemaphore* pWaitSemaphores;
public uint swapchainCount;
public VkSwapchainKHR* pSwapchains;
public uint* pImageIndices;
public VkResult* pResults;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkDebugUtilsMessengerCallbackDataEXT
{
public VkStructureType sType;
public nint pNext;
public uint messageId;
public byte* pMessageIdName;
public uint messageSeverity;
public uint messageTypes;
public byte* pMessage;
public uint queueLabelCount;
public nint pQueueLabels;
public uint cmdBufLabelCount;
public nint pCmdBufLabels;
public uint objectCount;
public nint pObjects;
}
+121 -308
View File
@@ -2,353 +2,166 @@ using System.Runtime.InteropServices;
namespace Engine.Graphics.Vulkan; namespace Engine.Graphics.Vulkan;
public sealed unsafe class VulkanSwapchain : IDisposable internal sealed unsafe class VulkanSwapchain : IDisposable
{ {
public VkSwapchainKHR Swapchain; public VkSwapchainKHR Swapchain;
public VkImage[] SwapchainImages = Array.Empty<VkImage>(); public VkImage[] Images = Array.Empty<VkImage>();
public VkImageView[] SwapchainImageViews = Array.Empty<VkImageView>(); public VkImageView[] ImageViews = Array.Empty<VkImageView>();
public VkFormat ImageFormat; public VkFormat Format;
public VkFormat DepthFormat;
public VkExtent2D Extent; public VkExtent2D Extent;
public VkRenderPass RenderPass; public uint ImageCount;
public VkFramebuffer[] Framebuffers = Array.Empty<VkFramebuffer>();
public VkImage DepthImage; private readonly VkDevice _device;
public VkDeviceMemory DepthImageMemory; private readonly VkPhysicalDevice _physicalDevice;
public VkImageView DepthImageView; private readonly VkSurfaceKHR _surface;
private readonly VkSurfaceFormatKHR _surfaceFormat;
private readonly VulkanContext _ctx;
private bool _disposed; private bool _disposed;
public VulkanSwapchain(VulkanContext ctx, int width, int height) public VulkanSwapchain(VkDevice device, VkPhysicalDevice physicalDevice, VkSurfaceKHR surface,
VkSurfaceFormatKHR surfaceFormat, int width, int height)
{ {
_ctx = ctx; _device = device;
_physicalDevice = physicalDevice;
_surface = surface;
_surfaceFormat = surfaceFormat;
Create(width, height); Create(width, height);
} }
public unsafe void Create(int width, int height) private void Create(int width, int height)
{ {
VkSurfaceCapabilitiesKHR caps; var caps = new VkSurfaceCapabilitiesKHR();
Vk.vkGetPhysicalDeviceSurfaceCapabilitiesKHR(_ctx.PhysicalDevice, _ctx.Surface, &caps); Vk.vkGetPhysicalDeviceSurfaceCapabilitiesKHR(_physicalDevice, _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; Extent = caps.currentExtent;
if (Extent.width == int.MaxValue || Extent.height == int.MaxValue || Extent.width <= 0 || Extent.height <= 0) if (Extent.Width == uint.MaxValue || Extent.Height == uint.MaxValue)
{ {
Extent.width = Math.Clamp(width, caps.minImageExtent.width, caps.maxImageExtent.width); Extent.Width = (uint)width;
Extent.height = Math.Clamp(height, caps.minImageExtent.height, caps.maxImageExtent.height); Extent.Height = (uint)height;
} }
Extent.Width = Math.Max(caps.minImageExtent.Width, Math.Min(caps.maxImageExtent.Width, Extent.Width));
Extent.Height = Math.Max(caps.minImageExtent.Height, Math.Min(caps.maxImageExtent.Height, Extent.Height));
uint imageCount = caps.minImageCount + 1; uint imageCount = caps.minImageCount + 1;
if (caps.maxImageCount > 0 && imageCount > caps.maxImageCount) if (caps.maxImageCount > 0 && imageCount > caps.maxImageCount)
imageCount = caps.maxImageCount; imageCount = caps.maxImageCount;
VkSwapchainCreateInfoKHR createInfo; uint presentModeCount = 0;
createInfo.sType = VkStructureType.SwapchainCreateInfoKHR; Vk.vkGetPhysicalDeviceSurfacePresentModesKHR(_physicalDevice, _surface, &presentModeCount, null);
createInfo.pNext = null; var presentModes = stackalloc VkPresentModeKHR[(int)presentModeCount];
createInfo.flags = 0; Vk.vkGetPhysicalDeviceSurfacePresentModesKHR(_physicalDevice, _surface, &presentModeCount, presentModes);
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; var presentMode = VkPresentModeKHR.Fifo;
VkResult result = Vk.vkCreateSwapchainKHR(_ctx.Device, &createInfo, null, &swapchain); for (uint i = 0; i < presentModeCount; i++)
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); if (presentModes[(int)i] == VkPresentModeKHR.Mailbox)
}
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, presentMode = VkPresentModeKHR.Mailbox;
baseMipLevel = 0, break;
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;
} }
} }
Format = _surfaceFormat.format;
var createInfo = new VkSwapchainCreateInfoKHR
{
sType = VkStructureType.SwapchainCreateInfoKHR,
surface = _surface,
minImageCount = imageCount,
imageFormat = _surfaceFormat.format,
imageColorSpace = _surfaceFormat.colorSpace,
imageExtent = Extent,
imageArrayLayers = 1,
imageUsage = VkImageUsageFlags.ColorAttachment | VkImageUsageFlags.TransferDst,
imageSharingMode = VkSharingMode.Exclusive,
preTransform = caps.currentTransform,
compositeAlpha = VkCompositeAlphaFlagsKHR.Opaque,
presentMode = presentMode,
clipped = VkBool32.True,
oldSwapchain = VkSwapchainKHR.Null,
};
fixed (VkSwapchainKHR* swPtr = &Swapchain)
{
var result = Vk.vkCreateSwapchainKHR(_device, &createInfo, 0, swPtr);
if (result != VkResult.Success)
throw new InvalidOperationException($"vkCreateSwapchainKHR failed: {result}");
}
uint actualCount = 0;
Vk.vkGetSwapchainImagesKHR(_device, Swapchain, &actualCount, null);
Images = new VkImage[actualCount];
ImageViews = new VkImageView[actualCount];
ImageCount = actualCount;
fixed (VkImage* imgPtr = Images)
{
Vk.vkGetSwapchainImagesKHR(_device, Swapchain, &actualCount, imgPtr);
}
for (uint i = 0; i < actualCount; i++)
{
var viewInfo = new VkImageViewCreateInfo
{
sType = VkStructureType.ImageViewCreateInfo,
image = Images[i],
viewType = VkImageViewType.Type2D,
format = Format,
components = new VkComponentMapping
{
R = VkComponentSwizzle.Identity,
G = VkComponentSwizzle.Identity,
B = VkComponentSwizzle.Identity,
A = VkComponentSwizzle.Identity,
},
subresourceRange = new VkImageSubresourceRange
{
AspectMask = VkImageAspectFlags.Color,
BaseMipLevel = 0,
LevelCount = 1,
BaseArrayLayer = 0,
LayerCount = 1,
},
};
fixed (VkImageView* viewPtr = &ImageViews[i])
{
var result = Vk.vkCreateImageView(_device, &viewInfo, 0, viewPtr);
if (result != VkResult.Success)
throw new InvalidOperationException($"vkCreateImageView failed: {result}");
}
}
Console.WriteLine($"[Vulkan] Swapchain: {actualCount} images, {Extent.Width}x{Extent.Height}, format={Format}");
} }
public void Recreate(int width, int height) public void Recreate(int width, int height)
{ {
Vk.vkQueueWaitIdle(_ctx.GraphicsQueue); Vk.vkDeviceWaitIdle(_device);
Cleanup();
CleanupSwapchain();
Create(width, height); Create(width, height);
} }
private void CleanupSwapchain() private void Cleanup()
{ {
foreach (var fb in Framebuffers) for (int i = 0; i < ImageViews.Length; i++)
if (fb.Value != 0) Vk.vkDestroyFramebuffer(_ctx.Device, fb, null); {
if (ImageViews[i].Handle != 0)
Vk.vkDestroyImageView(_device, ImageViews[i], 0);
}
ImageViews = Array.Empty<VkImageView>();
Images = Array.Empty<VkImage>();
if (DepthImageView.Value != 0) Vk.vkDestroyImageView(_ctx.Device, DepthImageView, null); if (Swapchain.Handle != 0)
if (DepthImage.Value != 0) Vk.vkDestroyImage(_ctx.Device, DepthImage, null); {
if (DepthImageMemory.Value != 0) Vk.vkFreeMemory(_ctx.Device, DepthImageMemory, null); Vk.vkDestroySwapchainKHR(_device, Swapchain, 0);
Swapchain = VkSwapchainKHR.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() public void Dispose()
{ {
if (_disposed) return; if (_disposed) return;
_disposed = true; _disposed = true;
Cleanup();
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;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,183 @@
using System.Runtime.InteropServices;
using Engine.Core;
namespace Engine.Graphics.Vulkan;
internal sealed unsafe class VulkanVertexBuffer : IDisposable
{
public VkBuffer Buffer;
public VkDeviceMemory Memory;
private readonly VkDevice _device;
private VkBuffer _stagingBuffer;
private VkDeviceMemory _stagingMemory;
private bool _disposed;
public VulkanVertexBuffer(VkDevice device, VkPhysicalDevice physicalDevice,
VkCommandPool commandPool, VkQueue queue, VulkanContext ctx, Vertex[] vertices)
{
_device = device;
var bufferSize = (ulong)(vertices.Length * sizeof(Vertex));
CreateStagingBuffer(bufferSize, ctx);
UploadToStaging(vertices, bufferSize);
CreateDeviceLocalBuffer(bufferSize, ctx);
CopyBuffer(commandPool, queue, _stagingBuffer, Buffer, bufferSize);
DestroyStaging();
Console.WriteLine($"[Vulkan] Vertex buffer created: {vertices.Length} vertices, {bufferSize} bytes");
}
private void CreateStagingBuffer(ulong size, VulkanContext ctx)
{
var info = new VkBufferCreateInfo
{
sType = VkStructureType.BufferCreateInfo,
size = size,
usage = VkBufferUsageFlags.TransferSrc,
sharingMode = VkSharingMode.Exclusive,
};
var buf = VkBuffer.Null;
var result = Vk.vkCreateBuffer(_device, &info, 0, &buf);
if (result != VkResult.Success)
throw new InvalidOperationException($"vkCreateBuffer (staging) failed: {result}");
_stagingBuffer = buf;
var reqs = new VkMemoryRequirements();
Vk.vkGetBufferMemoryRequirements(_device, _stagingBuffer, &reqs);
var memTypeIndex = ctx.FindMemoryType(reqs.memoryTypeBits,
VkMemoryPropertyFlags.HostVisible | VkMemoryPropertyFlags.HostCoherent);
var allocInfo = new VkMemoryAllocateInfo
{
sType = VkStructureType.MemoryAllocateInfo,
allocationSize = reqs.size,
memoryTypeIndex = memTypeIndex,
};
var mem = VkDeviceMemory.Null;
result = Vk.vkAllocateMemory(_device, &allocInfo, 0, &mem);
if (result != VkResult.Success)
throw new InvalidOperationException($"vkAllocateMemory (staging) failed: {result}");
_stagingMemory = mem;
Vk.vkBindBufferMemory(_device, _stagingBuffer, _stagingMemory, 0);
}
private void UploadToStaging(Vertex[] vertices, ulong size)
{
void* pData = null;
Vk.vkMapMemory(_device, _stagingMemory, 0, size, 0, &pData);
fixed (Vertex* pVerts = vertices)
{
System.Buffer.MemoryCopy(pVerts, pData, (long)size, (long)size);
}
Vk.vkUnmapMemory(_device, _stagingMemory);
}
private void CreateDeviceLocalBuffer(ulong size, VulkanContext ctx)
{
var info = new VkBufferCreateInfo
{
sType = VkStructureType.BufferCreateInfo,
size = size,
usage = VkBufferUsageFlags.TransferDst | VkBufferUsageFlags.VertexBuffer,
sharingMode = VkSharingMode.Exclusive,
};
var buf = VkBuffer.Null;
var result = Vk.vkCreateBuffer(_device, &info, 0, &buf);
if (result != VkResult.Success)
throw new InvalidOperationException($"vkCreateBuffer (vertex) failed: {result}");
Buffer = buf;
var reqs = new VkMemoryRequirements();
Vk.vkGetBufferMemoryRequirements(_device, Buffer, &reqs);
var memTypeIndex = ctx.FindMemoryType(reqs.memoryTypeBits, VkMemoryPropertyFlags.DeviceLocal);
var allocInfo = new VkMemoryAllocateInfo
{
sType = VkStructureType.MemoryAllocateInfo,
allocationSize = reqs.size,
memoryTypeIndex = memTypeIndex,
};
var mem = VkDeviceMemory.Null;
result = Vk.vkAllocateMemory(_device, &allocInfo, 0, &mem);
if (result != VkResult.Success)
throw new InvalidOperationException($"vkAllocateMemory (vertex) failed: {result}");
Memory = mem;
Vk.vkBindBufferMemory(_device, Buffer, Memory, 0);
}
private void CopyBuffer(VkCommandPool pool, VkQueue queue, VkBuffer src, VkBuffer dst, ulong size)
{
var allocInfo = new VkCommandBufferAllocateInfo
{
sType = VkStructureType.CommandBufferAllocateInfo,
commandPool = pool,
level = VkCommandBufferLevel.Primary,
commandBufferCount = 1,
};
var cmd = VkCommandBuffer.Null;
Vk.vkAllocateCommandBuffers(_device, &allocInfo, &cmd);
var beginInfo = new VkCommandBufferBeginInfo
{
sType = VkStructureType.CommandBufferBeginInfo,
flags = VkCommandBufferUsageFlags.OneTimeSubmit,
};
Vk.vkBeginCommandBuffer(cmd, &beginInfo);
var copyRegion = new VkBufferCopy
{
srcOffset = 0,
dstOffset = 0,
size = size,
};
Vk.vkCmdCopyBuffer(cmd, src, dst, 1, &copyRegion);
Vk.vkEndCommandBuffer(cmd);
var cmdInfo = new VkCommandBufferSubmitInfo
{
sType = VkStructureType.CommandBufferSubmitInfo,
commandBuffer = cmd,
};
var submitInfo = new VkSubmitInfo2
{
sType = VkStructureType.SubmitInfo2,
commandBufferInfoCount = 1,
pCommandBufferInfos = &cmdInfo,
};
Vk.vkQueueSubmit2(queue, 1, &submitInfo, VkFence.Null);
Vk.vkQueueWaitIdle(queue);
Vk.vkFreeCommandBuffers(_device, pool, 1, &cmd);
}
private void DestroyStaging()
{
if (_stagingBuffer.Handle != 0) Vk.vkDestroyBuffer(_device, _stagingBuffer, 0);
if (_stagingMemory.Handle != 0) Vk.vkFreeMemory(_device, _stagingMemory, 0);
_stagingBuffer = VkBuffer.Null;
_stagingMemory = VkDeviceMemory.Null;
}
public void Dispose()
{
if (_disposed) return;
_disposed = true;
if (Buffer.Handle != 0) Vk.vkDestroyBuffer(_device, Buffer, 0);
if (Memory.Handle != 0) Vk.vkFreeMemory(_device, Memory, 0);
}
}
@@ -17,15 +17,6 @@
<PublishAot>true</PublishAot> <PublishAot>true</PublishAot>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<PackageReference Include="Flecs.NET.Debug" Version="4.0.4-build.546" Condition="'$(Configuration)' == 'Debug'" />
<PackageReference Include="Flecs.NET.Release" Version="4.0.4-build.546" Condition="'$(Configuration)' == 'Release' OR '$(Configuration)' == 'ReleaseAOT'" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="SharpGLTF.Core" Version="1.0.0" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Engine.Core\Engine.Core.csproj" /> <ProjectReference Include="..\Engine.Core\Engine.Core.csproj" />
</ItemGroup> </ItemGroup>
+3
View File
@@ -2,6 +2,9 @@ using Engine.Core;
namespace Engine.Graphics; namespace Engine.Graphics;
/// <summary>
/// Render context created by a backend. Owns the window and can create a renderer.
/// </summary>
public interface IRenderContext : IDisposable public interface IRenderContext : IDisposable
{ {
IWindow Window { get; } IWindow Window { get; }
+5 -1
View File
@@ -3,10 +3,14 @@ using Flecs.NET.Core;
namespace Engine.Graphics; namespace Engine.Graphics;
/// <summary>
/// Backend-agnostic renderer interface. Minimal version for triangle rendering.
/// </summary>
public interface IRenderer : IDisposable public interface IRenderer : IDisposable
{ {
void RenderWorld(World world); void RenderWorld(World world);
void RequestScreenshot(string outputPath);
void RequestScreenshot(string path);
bool IsScreenshotRequested { get; } bool IsScreenshotRequested { get; }
IScreenshotProvider ScreenshotProvider { get; } IScreenshotProvider ScreenshotProvider { get; }
} }
@@ -0,0 +1,12 @@
namespace Engine.Graphics;
/// <summary>
/// Provides access to the latest captured screenshot bytes.
/// </summary>
public interface IScreenshotProvider
{
/// <summary>
/// Returns the path of the screenshot file if a screenshot is available; otherwise null.
/// </summary>
string? TryTakeScreenshotPath();
}
-67
View File
@@ -1,67 +0,0 @@
using System.Numerics;
using Engine.Core;
using Engine.Core.Components;
namespace Engine.Graphics.Loaders;
public static class GltfLoader
{
public static Mesh Load(string path, Vector3? color = null)
{
var tint = color ?? new Vector3(0.7f, 0.6f, 0.5f);
var modelRoot = SharpGLTF.Schema2.ModelRoot.Load(path);
var vertices = new List<Vertex>();
var indices = new List<uint>();
foreach (var scene in modelRoot.LogicalScenes)
{
foreach (var node in scene.VisualChildren)
{
var mesh = node.Mesh;
if (mesh == null) continue;
foreach (var primitive in mesh.Primitives)
{
var posAccess = primitive.GetVertexAccessor("POSITION");
var normAccess = primitive.GetVertexAccessor("NORMAL");
if (posAccess == null) continue;
var indexAccess = primitive.IndexAccessor;
var baseVertex = (uint)vertices.Count;
for (var i = 0; i < posAccess.Count; i++)
{
var pos = posAccess.AsVector3Array()[i];
var normal = normAccess != null
? normAccess.AsVector3Array()[i]
: Vector3.UnitY;
vertices.Add(new Vertex(pos, tint, normal));
}
if (indexAccess != null)
{
var indexArray = indexAccess.AsIndicesArray();
foreach (var idx in indexArray)
{
indices.Add((uint)idx + baseVertex);
}
}
else
{
for (uint i = 0; i < posAccess.Count; i++)
{
indices.Add(baseVertex + i);
}
}
}
}
}
if (vertices.Count == 0)
throw new InvalidOperationException($"GLTF file '{path}' contains no meshes.");
return new Mesh(vertices.ToArray(), indices.ToArray());
}
}
+58 -59
View File
@@ -1,60 +1,54 @@
using System.Globalization;
using System.Numerics; using System.Numerics;
using Engine.Core; using Engine.Core;
using Engine.Core.Components; using Engine.Core.Components;
namespace Engine.Graphics.Loaders; namespace Engine.Graphics.Loaders;
/// <summary>
/// Minimal OBJ loader.
/// </summary>
public static class ObjLoader public static class ObjLoader
{ {
private static readonly Vector3 DefaultColor = new(0.7f, 0.6f, 0.5f); public static Mesh Load(string path, Vector3? defaultColor = null)
public static Mesh Load(string path, Vector3? color = null)
{ {
var tint = color ?? DefaultColor; if (!File.Exists(path))
var lines = File.ReadAllLines(path); throw new FileNotFoundException($"OBJ file not found: {path}", path);
var color = defaultColor ?? new Vector3(0.7f, 0.6f, 0.5f);
var positions = new List<Vector3>(); var positions = new List<Vector3>();
var normals = new List<Vector3>(); var normals = new List<Vector3>();
var texcoords = new List<Vector2>();
var vertices = new List<Vertex>(); var vertices = new List<Vertex>();
var indices = new List<uint>(); var indices = new List<uint>();
var faceNormals = new List<Vector3>();
foreach (var rawLine in lines) foreach (var line in File.ReadLines(path))
{ {
var line = rawLine.Trim(); var trimmed = line.Trim();
if (string.IsNullOrEmpty(trimmed) || trimmed.StartsWith('#')) continue;
if (line.Length == 0 || line.StartsWith('#')) var parts = trimmed.Split(' ', StringSplitOptions.RemoveEmptyEntries);
continue; if (parts.Length == 0) continue;
var parts = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 0)
continue;
switch (parts[0]) switch (parts[0])
{ {
case "v": case "v":
positions.Add(new Vector3( positions.Add(ParseVector3(parts));
float.Parse(parts[1], CultureInfo.InvariantCulture),
float.Parse(parts[2], CultureInfo.InvariantCulture),
float.Parse(parts[3], CultureInfo.InvariantCulture)));
break; break;
case "vn": case "vn":
normals.Add(new Vector3( normals.Add(ParseVector3(parts));
float.Parse(parts[1], CultureInfo.InvariantCulture), break;
float.Parse(parts[2], CultureInfo.InvariantCulture), case "vt":
float.Parse(parts[3], CultureInfo.InvariantCulture))); texcoords.Add(ParseVector2(parts));
break; break;
case "f": case "f":
ParseFace(parts, positions, normals, vertices, indices, tint); ParseFace(parts, positions, normals, texcoords, color, vertices, indices, faceNormals);
break; break;
} }
} }
if (vertices.Count == 0) if (vertices.Count == 0)
throw new InvalidOperationException($"OBJ file '{path}' contains no faces."); throw new InvalidOperationException($"OBJ file contains no geometry: {path}");
return new Mesh(vertices.ToArray(), indices.ToArray()); return new Mesh(vertices.ToArray(), indices.ToArray());
} }
@@ -63,47 +57,52 @@ public static class ObjLoader
string[] parts, string[] parts,
List<Vector3> positions, List<Vector3> positions,
List<Vector3> normals, List<Vector3> normals,
List<Vector2> texcoords,
Vector3 color,
List<Vertex> vertices, List<Vertex> vertices,
List<uint> indices, List<uint> indices,
Vector3 tint) List<Vector3> faceNormals)
{ {
var faceData = new List<(int posIdx, int normIdx)>(); var faceIndices = new List<uint>();
faceNormals.Clear();
for (var i = 1; i < parts.Length; i++) for (int i = 1; i < parts.Length; i++)
{ {
var vertexData = parts[i].Split('/'); var sub = parts[i].Split('/');
var posIdx = int.Parse(vertexData[0]) - 1; var posIndex = int.Parse(sub[0]) - 1;
var normIdx = vertexData.Length > 2 && !string.IsNullOrEmpty(vertexData[2]) var pos = positions[posIndex];
? int.Parse(vertexData[2]) - 1
: -1;
faceData.Add((posIdx, normIdx)); Vector3 normal = Vector3.UnitY;
if (sub.Length > 2 && !string.IsNullOrEmpty(sub[2]))
{
normal = normals[int.Parse(sub[2]) - 1];
}
vertices.Add(new Vertex(pos, color, normal));
faceIndices.Add((uint)(vertices.Count - 1));
} }
if (faceData.Count < 3) return; // Triangulate as a fan.
for (int i = 2; i < faceIndices.Count; i++)
for (var i = 1; i < faceData.Count - 1; i++)
{ {
var d0 = faceData[0]; indices.Add(faceIndices[0]);
var d1 = faceData[i]; indices.Add(faceIndices[i - 1]);
var d2 = faceData[i + 1]; indices.Add(faceIndices[i]);
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);
} }
} }
private static Vector3 ParseVector3(string[] parts)
{
return new Vector3(
float.Parse(parts[1]),
float.Parse(parts[2]),
float.Parse(parts[3]));
}
private static Vector2 ParseVector2(string[] parts)
{
return new Vector2(
float.Parse(parts[1]),
parts.Length > 2 ? float.Parse(parts[2]) : 0);
}
} }
+8 -7
View File
@@ -2,17 +2,18 @@ using System.Numerics;
namespace Engine.Graphics; namespace Engine.Graphics;
/// <summary>
/// Basic mesh math utilities.
/// </summary>
public static class MeshMath public static class MeshMath
{ {
public static Vector3 ComputeFaceNormal(Vector3 a, Vector3 b, Vector3 c) public static Vector3 ComputeFaceNormal(Vector3 a, Vector3 b, Vector3 c)
{ {
var edge1 = b - a; var ab = b - a;
var edge2 = c - a; var ac = c - a;
var normal = Vector3.Cross(edge2, edge1); var cross = Vector3.Cross(ab, ac);
if (cross.LengthSquared() < 0.0000001f)
if (normal.LengthSquared() < 1e-12f)
return Vector3.UnitY; return Vector3.UnitY;
return Vector3.Normalize(cross);
return Vector3.Normalize(normal);
} }
} }
+107 -63
View File
@@ -4,85 +4,129 @@ using Engine.Core.Components;
namespace Engine.Graphics; namespace Engine.Graphics;
/// <summary>
/// Procedural mesh generators.
/// </summary>
public static class ProceduralMesh public static class ProceduralMesh
{ {
public static Mesh CreateSphere(float radius, int slices, int stacks, Vector3 color) public static Mesh CreateCube(float size, Vector3 color)
{ {
var vertices = new Vertex[(stacks + 1) * (slices + 1)]; var s = size * 0.5f;
var indices = new uint[stacks * slices * 6]; var vertices = new[]
var vi = 0;
for (var i = 0; i <= stacks; i++)
{ {
var phi = MathF.PI * i / stacks; // Front
var y = radius * MathF.Cos(phi); new Vertex(new Vector3(-s, -s, s), color, new Vector3(0, 0, 1)),
var r = radius * MathF.Sin(phi); new Vertex(new Vector3( s, -s, s), color, new Vector3(0, 0, 1)),
new Vertex(new Vector3( s, s, s), color, new Vector3(0, 0, 1)),
new Vertex(new Vector3(-s, s, s), color, new Vector3(0, 0, 1)),
// Back
new Vertex(new Vector3( s, -s, -s), color, new Vector3(0, 0, -1)),
new Vertex(new Vector3(-s, -s, -s), color, new Vector3(0, 0, -1)),
new Vertex(new Vector3(-s, s, -s), color, new Vector3(0, 0, -1)),
new Vertex(new Vector3( s, s, -s), color, new Vector3(0, 0, -1)),
// Top
new Vertex(new Vector3(-s, s, s), color, new Vector3(0, 1, 0)),
new Vertex(new Vector3( s, s, s), color, new Vector3(0, 1, 0)),
new Vertex(new Vector3( s, s, -s), color, new Vector3(0, 1, 0)),
new Vertex(new Vector3(-s, s, -s), color, new Vector3(0, 1, 0)),
// Bottom
new Vertex(new Vector3(-s, -s, -s), color, new Vector3(0, -1, 0)),
new Vertex(new Vector3( s, -s, -s), color, new Vector3(0, -1, 0)),
new Vertex(new Vector3( s, -s, s), color, new Vector3(0, -1, 0)),
new Vertex(new Vector3(-s, -s, s), color, new Vector3(0, -1, 0)),
// Right
new Vertex(new Vector3( s, -s, s), color, new Vector3(1, 0, 0)),
new Vertex(new Vector3( s, -s, -s), color, new Vector3(1, 0, 0)),
new Vertex(new Vector3( s, s, -s), color, new Vector3(1, 0, 0)),
new Vertex(new Vector3( s, s, s), color, new Vector3(1, 0, 0)),
// Left
new Vertex(new Vector3(-s, -s, -s), color, new Vector3(-1, 0, 0)),
new Vertex(new Vector3(-s, -s, s), color, new Vector3(-1, 0, 0)),
new Vertex(new Vector3(-s, s, s), color, new Vector3(-1, 0, 0)),
new Vertex(new Vector3(-s, s, -s), color, new Vector3(-1, 0, 0)),
};
for (var j = 0; j <= slices; j++) var indices = new uint[]
{
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++) 0, 1, 2, 0, 2, 3,
{ 4, 5, 6, 4, 6, 7,
var a = (uint)(i * (slices + 1) + j); 8, 9, 10, 8, 10, 11,
var b = a + 1; 12, 13, 14, 12, 14, 15,
var c = a + (uint)(slices + 1); 16, 17, 18, 16, 18, 19,
var d = c + 1; 20, 21, 22, 20, 22, 23,
};
indices[ii++] = a; indices[ii++] = c; indices[ii++] = b;
indices[ii++] = b; indices[ii++] = c; indices[ii++] = d;
}
}
return new Mesh(vertices, indices); return new Mesh(vertices, indices);
} }
public static Mesh CreateGrid(int halfSize, float spacing, Vector3 color) public static Mesh CreateSphere(float radius, int sectors, int stacks, Vector3 color)
{ {
var lines = 2 * halfSize + 1; var vertices = new List<Vertex>();
var vertices = new List<Vertex>(lines * 4 * 2); var indices = new List<uint>();
var indices = new List<uint>(lines * 4 * 2);
var extent = halfSize * spacing;
for (var i = -halfSize; i <= halfSize; i++) for (int i = 0; i <= stacks; i++)
{
var stackAngle = MathF.PI / 2 - i * MathF.PI / stacks;
var xy = radius * MathF.Cos(stackAngle);
var z = radius * MathF.Sin(stackAngle);
for (int j = 0; j <= sectors; j++)
{
var sectorAngle = j * 2 * MathF.PI / sectors;
var x = xy * MathF.Cos(sectorAngle);
var y = xy * MathF.Sin(sectorAngle);
var pos = new Vector3(x, y, z);
var normal = Vector3.Normalize(pos);
vertices.Add(new Vertex(pos, color, normal));
}
}
for (int i = 0; i < stacks; i++)
{
var k1 = (uint)(i * (sectors + 1));
var k2 = (uint)(k1 + sectors + 1);
for (int j = 0; j < sectors; j++, k1++, k2++)
{
if (i != 0)
{
indices.Add(k1);
indices.Add(k2);
indices.Add(k1 + 1);
}
if (i != stacks - 1)
{
indices.Add(k1 + 1);
indices.Add(k2);
indices.Add(k2 + 1);
}
}
}
return new Mesh(vertices.ToArray(), indices.ToArray());
}
public static Mesh CreateGrid(int lines, float spacing, Vector3 color)
{
var vertices = new List<Vertex>();
var indices = new List<uint>();
var max = lines * spacing;
var normal = Vector3.UnitY;
for (int i = -lines; i <= lines; i++)
{ {
var pos = i * spacing; var pos = i * spacing;
var i0 = (uint)vertices.Count; vertices.Add(new Vertex(new Vector3(pos, 0, -max), color, normal));
vertices.Add(new Vertex(new Vector3(pos, 0, -extent), color, Vector3.UnitY)); vertices.Add(new Vertex(new Vector3(pos, 0, max), color, normal));
var i1 = (uint)vertices.Count; indices.Add((uint)(vertices.Count - 2));
vertices.Add(new Vertex(new Vector3(pos, 0, extent), color, Vector3.UnitY)); indices.Add((uint)(vertices.Count - 1));
indices.Add(i0); indices.Add(i1);
var i2 = (uint)vertices.Count; vertices.Add(new Vertex(new Vector3(-max, 0, pos), color, normal));
vertices.Add(new Vertex(new Vector3(pos, 0, -extent), color, Vector3.UnitY)); vertices.Add(new Vertex(new Vector3( max, 0, pos), color, normal));
var i3 = (uint)vertices.Count; indices.Add((uint)(vertices.Count - 2));
vertices.Add(new Vertex(new Vector3(pos, 0, extent), color, Vector3.UnitY)); indices.Add((uint)(vertices.Count - 1));
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()); return new Mesh(vertices.ToArray(), indices.ToArray());
+13 -10
View File
@@ -1,25 +1,28 @@
using Engine.Core;
namespace Engine.Graphics; namespace Engine.Graphics;
/// <summary>
/// Factory for creating render backends by name.
/// </summary>
public static class RenderBackendFactory public static class RenderBackendFactory
{ {
private static readonly Dictionary<string, Func<int, int, bool, IRenderContext>> _backends = private static readonly Dictionary<string, Func<int, int, bool, IRenderContext>> _backends = new(StringComparer.OrdinalIgnoreCase);
new(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Register a backend factory. Case-insensitive lookup.
/// </summary>
public static void Register(string name, Func<int, int, bool, IRenderContext> factory) public static void Register(string name, Func<int, int, bool, IRenderContext> factory)
{ {
_backends[name] = factory; _backends[name] = factory;
} }
/// <summary>
/// Create a render context for the given backend.
/// </summary>
public static IRenderContext Create(string name, int width, int height, bool enableValidation) public static IRenderContext Create(string name, int width, int height, bool enableValidation)
{ {
if (_backends.TryGetValue(name, out var factory)) if (!_backends.TryGetValue(name, out var factory))
return factory(width, height, enableValidation); throw new NotSupportedException($"Render backend '{name}' is not registered.");
throw new NotSupportedException( return factory(width, height, enableValidation);
$"Unknown render backend '{name}'. Available: {string.Join(", ", _backends.Keys)}");
} }
public static bool IsRegistered(string name) => _backends.ContainsKey(name);
} }
+34 -147
View File
@@ -1,86 +1,45 @@
using System.Numerics; using System.Numerics;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization;
using Engine.Core.Components; using Engine.Core.Components;
using Flecs.NET.Core; using Flecs.NET.Core;
namespace Engine.Graphics; namespace Engine.Graphics;
/// <summary>
/// Serializes and deserializes entity scenes to JSON.
/// Minimal version: handles Transform, Material, Light, Camera, Mesh.
/// </summary>
public static class SceneSerializer public static class SceneSerializer
{ {
private static readonly JsonSerializerOptions JsonOptions = new() private static readonly JsonSerializerOptions Options = new()
{ {
PropertyNameCaseInsensitive = true, WriteIndented = true,
Converters = { new JsonStringEnumConverter() } IncludeFields = true
}; };
public static string SaveToString(World world) public static string SaveToString(World world)
{ {
var entities = new List<SceneEntityData>(); var entities = new List<SceneEntity>();
world.Each((Entity e, ref Transform t) =>
world.Each((Entity e, ref Transform _) =>
{ {
var name = e.Name(); var name = e.Name();
if (string.IsNullOrEmpty(name)) return; var entity = new SceneEntity { Name = name };
var data = new SceneEntityData { Name = name }; entity.Transform = t;
if (e.Has<Transform>())
{
var t = e.Get<Transform>();
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<Material>()) if (e.Has<Material>())
{ entity.Material = e.Get<Material>();
var m = e.Get<Material>();
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<Light>()) if (e.Has<Light>())
{ entity.Light = e.Get<Light>();
var l = e.Get<Light>();
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<Camera>()) if (e.Has<Camera>())
{ entity.Camera = e.Get<Camera>();
var c = e.Get<Camera>();
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); entities.Add(entity);
}); });
return JsonSerializer.Serialize(entities, JsonOptions); return JsonSerializer.Serialize(entities, Options);
} }
public static void SaveToFile(World world, string path) public static void SaveToFile(World world, string path)
@@ -91,114 +50,42 @@ public static class SceneSerializer
public static int LoadFromString(World world, string json) public static int LoadFromString(World world, string json)
{ {
var entities = JsonSerializer.Deserialize<List<SceneEntityData>>(json, JsonOptions); var entities = JsonSerializer.Deserialize<List<SceneEntity>>(json, Options);
if (entities == null) return 0; if (entities == null) return 0;
foreach (var data in entities) foreach (var e in entities)
{ {
var entity = world.Entity(data.Name); var entity = world.Entity(e.Name);
entity.Set(e.Transform);
if (data.Transform != null) if (e.Material != null)
{ entity.Set(e.Material.Value);
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) if (e.Light != null)
{ entity.Set(e.Light.Value);
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) if (e.Camera != null)
{ entity.Set(e.Camera.Value);
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; return entities.Count;
} }
public static int LoadFromFile(World world, string path) public static void LoadFromFile(World world, string path)
{ {
if (!File.Exists(path)) if (!File.Exists(path))
throw new FileNotFoundException($"Scene file not found: {path}", path); throw new FileNotFoundException($"Scene file not found: {path}", path);
var json = File.ReadAllText(path); var json = File.ReadAllText(path);
return LoadFromString(world, json); LoadFromString(world, json);
} }
private class SceneEntityData private class SceneEntity
{ {
public string Name { get; set; } = ""; public string Name { get; set; } = string.Empty;
public TransformData? Transform { get; set; } public Transform Transform { get; set; }
public MaterialData? Material { get; set; } public Material? Material { get; set; }
public LightData? Light { get; set; } public Light? Light { get; set; }
public CameraData? Camera { get; set; } public Camera? Camera { get; set; }
}
private class TransformData
{
public float[] Position { get; set; } = Array.Empty<float>();
public float[] Rotation { get; set; } = Array.Empty<float>();
public float[] Scale { get; set; } = Array.Empty<float>();
}
private class MaterialData
{
public float[] Albedo { get; set; } = Array.Empty<float>();
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<float>();
public float[] Position { get; set; } = Array.Empty<float>();
public float[] Color { get; set; } = Array.Empty<float>();
public float Intensity { get; set; }
public float Range { get; set; }
}
private class CameraData
{
public float[] Position { get; set; } = Array.Empty<float>();
public float[] Target { get; set; } = Array.Empty<float>();
public float[] Up { get; set; } = Array.Empty<float>();
public float FieldOfView { get; set; }
public float AspectRatio { get; set; }
public float NearPlane { get; set; }
public float FarPlane { get; set; }
} }
} }