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:
+653
-181
@@ -3,9 +3,11 @@
|
|||||||
## Project State (June 2026)
|
## Project State (June 2026)
|
||||||
|
|
||||||
### What Exists
|
### What Exists
|
||||||
- **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>
|
||||||
|
|||||||
@@ -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}°");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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, ®ion);
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 }; }
|
||||||
@@ -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, ®ion);
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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, ®ion);
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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, ©Region);
|
||||||
|
|
||||||
|
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>
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
@@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user