diff --git a/VULKAN_IMPLEMENTATION_PLAN.md b/VULKAN_IMPLEMENTATION_PLAN.md index a811228..24bf981 100644 --- a/VULKAN_IMPLEMENTATION_PLAN.md +++ b/VULKAN_IMPLEMENTATION_PLAN.md @@ -3,9 +3,11 @@ ## Project State (June 2026) ### 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), 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.AI** — AiCommandProcessor (7 commands), MCP HTTP + stdio servers, AiCommandQueue - **CortexEngine.App** — main loop (broken, references deleted graphics projects) @@ -13,245 +15,715 @@ - **Content/** — cube.obj, torusknot.obj, checker.png ### What Was Deleted -- Engine.Graphics (interfaces + loaders + factory) - Engine.Graphics.Raylib -- Engine.Graphics.OpenTK -- Engine.Graphics.Vulkan (Silk.NET version) +- Engine.Graphics.OpenTK +- Engine.Graphics.Vulkan (Silk.NET version — all previous PBR/ImGui/mesh/screenshot code gone) ### Environment -- .NET 9 SDK at $HOME/.dotnet +- .NET 9 SDK at `$HOME/.dotnet` - Vulkan 1.4.329, NVIDIA RTX 2080 Ti, validation layers available - SDL3 (ppy.SDL3-CS 2026.520.0) — window + Vulkan surface -- glslangValidator NOT installed (need: sudo apt install glslang-tools) -- Linux (X11), cross-platform target (Windows: vulkan-1.dll, Linux: libvulkan.so.1) +- glslangValidator: check availability (`glslangValidator --version`); fallback: `glslc` +- 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: -- `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) +Create `src/Engine.Graphics.Vulkan/` with the following files: -- `VulkanContext.cs` — Instance + PhysicalDevice + Device + Queues + Surface: - - CreateInstance with SDL3 extensions + validation layers - - PickPhysicalDevice (prefer discrete GPU) - - CreateLogicalDevice with VK_KHR_swapchain - - CreateSurface via SDL_Vulkan_CreateSurface - - Get graphics + present queues +#### 1.1 `VulkanNative.cs` +- Load `libvulkan.so.1` (Linux) / `vulkan-1.dll` (Windows) via `NativeLibrary.Load()` +- Export `vkGetInstanceProcAddr` delegate — the only directly-loaded function +- Helper: `GetExport(string name)` for static exports +- Helper: `ToUtf8Terminated(string)` for passing string names to Vulkan -- `VulkanSwapchain.cs` — Swapchain + image views + depth + render pass + framebuffers: - - Query surface capabilities - - Create swapchain (format, extent, present mode) - - Create image views - - Create depth image + view (D32_SFLOAT) - - Create render pass (color + depth attachments) - - Create framebuffers +#### 1.2 `VulkanHandles.cs` +Opaque pointer handles (all are `nint` / `ulong`): +``` +VkInstance, VkPhysicalDevice, VkDevice, VkQueue, +VkCommandPool, VkCommandBuffer, +VkSwapchainKHR, VkSurfaceKHR, +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: - - Load SPIR-V shader modules (vertex + fragment) - - Vertex input description (Position vec3, Normal vec3, Color vec4) - - Descriptor set layouts (frame UBO + texture sampler) - - Pipeline layout + graphics pipeline - - Push constants for MVP matrix + material params +#### 1.3 `VulkanEnums.cs` +All enums needed for triangle + future expansion: +- `VkResult` — Success=0, NotReady, Timeout, Incomplete, ErrorOutOfDateKHR, SuboptimalKHR, ErrorSurfaceLostKHR, ... +- `VkStructureType` — ApplicationInfo=0, InstanceCreateInfo=1, DeviceQueueCreateInfo=2, DeviceCreateInfo=3, ... +- `VkFormat` — Undefined=0, R8G8B8A8Unorm=37, B8G8R8A8Unorm=44, R8G8B8A8Srgb=43, B8G8R8A8Srgb=50, R32G32Sfloat=103, R32G32B32Sfloat=106, R32G32B32A32Sfloat=109, D32Sfloat=126, ... +- `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: - - CreateBuffer (vertex/index/uniform) - - AllocateMemory + bind - - Map/unmap for writing - - FindMemoryType - - Staging buffer for copy +#### 1.4 `VulkanStructs.cs` +All structs with `LayoutKind.Sequential`: +- `VkApplicationInfo` — sType, pNext, pApplicationName, applicationVersion, pEngineName, engineVersion, apiVersion +- `VkInstanceCreateInfo` — sType, pNext, flags, pApplicationInfo, enabledLayerCount, ppEnabledLayerNames, enabledExtensionCount, ppEnabledExtensionNames +- `VkDebugUtilsMessengerCreateInfoEXT` — sType, pNext, flags, messageSeverity, messageType, pfnUserCallback, pUserData +- `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: - - Command pool + command buffers (2 frames in flight) - - 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 +#### 1.5 `Vk.cs` +Function delegate types + loaded function pointers: -- `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 -NO external Vulkan packages — pure P/Invoke +**Device-level functions** (loaded via `vkGetDeviceProcAddr` for best performance): +- `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): -- `Shaders/vertex.vert` — #version 450, position/normal/color inputs, MVP+model uniforms, outputs -- `Shaders/fragment.frag` — #version 450, PBR lighting (Fresnel, ACES, gamma), directional + point lights -- Compile: `glslangValidator -V vertex.vert -o vertex.spv && glslangValidator -V fragment.frag -o fragment.spv` -- Embed .spv files as project resources or copy to output directory +#### 2.1 `VulkanContext.cs` +- **CreateInstance:** + - `VkApplicationInfo` with `apiVersion = VK_API_VERSION_1_3` + - Instance extensions from SDL3: `SDL_GetVulkanInstanceExtensions()` + - 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`: - - Remove all Raylib/OpenTK/old OpenGL imports - - Remove ImGuiLayer, ObjectManipulator (Raylib-specific, will reimplement later) - - Use `RenderBackendFactory.Create("vulkan", 1280, 720, enableValidation: true)` - - Keep: physics, camera controllers, AI commands, tour mode, scene setup - - Sdl3Window creates Vulkan surface automatically (vulkanSurface: true) + - Simplify to triangle-only rendering + - `RenderBackendFactory.Create("vulkan", 1280, 720, validation: true)` + - Main loop: poll events → render → present + - Keep: Sdl3Window, basic event handling + - 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 -- Tests that reference Engine.Graphics: ObjLoaderTests, RenderBackendFactoryTests, +- Update `Engine.Tests.csproj` — reference restored `Engine.Graphics` +- Tests referencing Engine.Graphics: ObjLoaderTests, RenderBackendFactoryTests, 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 ``` src/ -├── Engine.Core/ (exists, unchanged) -├── Engine.Graphics/ (new — interfaces + loaders) +├── Engine.Core/ (exists, unchanged) +├── Engine.Graphics/ (exists, restored minimal interfaces) │ ├── Engine.Graphics.csproj │ ├── IRenderContext.cs │ ├── IRenderer.cs +│ ├── IScreenshotProvider.cs │ ├── RenderBackendFactory.cs │ ├── MeshMath.cs │ ├── ProceduralMesh.cs │ ├── SceneSerializer.cs │ └── Loaders/ -│ ├── ObjLoader.cs -│ └── GltfLoader.cs -├── Engine.Graphics.Vulkan/ (new — pure Vulkan P/Invoke) +│ └── ObjLoader.cs +├── Engine.Graphics.Vulkan/ (new — pure P/Invoke, Vulkan 1.3) │ ├── Engine.Graphics.Vulkan.csproj -│ ├── VulkanNative.cs (~800 lines) -│ ├── VulkanContext.cs (~300 lines) -│ ├── VulkanSwapchain.cs (~250 lines) -│ ├── VulkanPipeline.cs (~200 lines) -│ ├── VulkanBuffer.cs (~150 lines) -│ ├── VulkanRenderer.cs (~400 lines) -│ ├── VulkanBackendRegistrar.cs +│ ├── VulkanNative.cs — library loading, vkGetInstanceProcAddr +│ ├── VulkanHandles.cs — opaque pointer types +│ ├── VulkanEnums.cs — all Vulkan enums/flags +│ ├── VulkanStructs.cs — all Vulkan structs (LayoutKind.Sequential) +│ ├── Vk.cs — function delegates + loaded pointers +│ ├── VulkanContext.cs — instance, device, queue, surface, debug +│ ├── 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/ -│ ├── vertex.vert -│ ├── fragment.frag -│ ├── vertex.spv -│ └── fragment.spv -├── Engine.Physics/ (exists, unchanged) -├── Engine.AI/ (exists, unchanged) -└── CortexEngine.App/ (fix references) +│ ├── triangle.vert +│ ├── triangle.frag +│ ├── triangle.vert.spv +│ └── triangle.frag.spv +├── Engine.Physics/ (exists, unchanged) +├── Engine.AI/ (exists, unchanged) +└── CortexEngine.App/ (fix references, simplify to triangle) ``` -## Solution Update +--- -Remove from solution: -- Engine.Graphics.Raylib (deleted) -- Engine.Graphics.OpenTK (deleted) -- Engine.Graphics.Vulkan (old Silk.NET, deleted) +## csproj: Engine.Graphics.Vulkan -Add to solution: -- Engine.Graphics (new) -- Engine.Graphics.Vulkan (new, pure P/Invoke) +```xml + + + net9.0 + true + true + + + + + + + + + +``` + +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 -### Matrix Layout -- 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 -- OR transpose in C# before writing to uniform buffer +### Semaphore Indexing (CRITICAL) + +``` + Indexed by frame-in-flight (0..1) Indexed by swapchain image (0..N-1) + ───────────────────────────────── ────────────────────────────────── +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 struct (9 floats, 36 bytes): - Position: vec3 (offset 0) - Color: vec3 (offset 12) - Normal: vec3 (offset 24) -``` - -### 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) + Position: vec3 (offset 0, format R32G32B32_SFLOAT, location 0) + Color: vec3 (offset 12, format R32G32B32_SFLOAT, location 1) + Normal: vec3 (offset 24, format R32G32B32_SFLOAT, location 2) ``` ### Validation Layers + ```csharp -string[] layers = enableValidation - ? new[] { "VK_LAYER_KHRONOS_validation" } +string[] layers = enableValidation + ? new[] { "VK_LAYER_KHRONOS_validation" } : Array.Empty(); + +string[] instanceExtensions = enableValidation + ? [.. sdlExtensions, "VK_EXT_debug_utils"] + : sdlExtensions; ``` -Validation errors print to stderr — use for debugging. -### Memory Allocation -Simple approach (no VMA): -1. vkGetPhysicalDeviceMemoryProperties -2. Find memory type with VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | HOST_COHERENT_BIT -3. vkAllocateMemory + vkBindBufferMemory -4. vkMapMemory for writing, vkUnmapMemory +Debug callback (C#): +```csharp +static uint DebugCallback( + nint instance, uint messageSeverity, uint messageTypes, + nint pCallbackData, nint pUserData) +{ + var data = Marshal.PtrToStructure(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 diff --git a/src/CortexEngine.App/CortexEngine.App.csproj b/src/CortexEngine.App/CortexEngine.App.csproj index 3b05d66..51de3c3 100644 --- a/src/CortexEngine.App/CortexEngine.App.csproj +++ b/src/CortexEngine.App/CortexEngine.App.csproj @@ -23,8 +23,6 @@ - - diff --git a/src/CortexEngine.App/Program.cs b/src/CortexEngine.App/Program.cs index 05e8e1c..ceb2e3b 100644 --- a/src/CortexEngine.App/Program.cs +++ b/src/CortexEngine.App/Program.cs @@ -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.Components; using Engine.Graphics; -using Engine.Graphics.Loaders; using Engine.Graphics.Vulkan; -using Engine.Physics; using Flecs.NET.Core; -using ImGuiNET; namespace CortexEngine.App; @@ -22,280 +9,40 @@ class Program { 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 { - 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(); using var renderContext = RenderBackendFactory.Create("vulkan", 1280, 720, enableValidation: true); var window = renderContext.Window; - var input = window.Input; using var renderer = renderContext.CreateRenderer(); - VulkanImGui? imGuiLayer = null; - 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."); - } + using var world = World.Create(); - 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 lastHeight = window.Height; - var demoScreenshotRequested = false; - var currentFps = 0; - - 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; + var frames = 0; + var lastFpsTime = 0.0; + var timing = new Timing(); while (!window.ShouldClose) { timing.Tick(); 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) { lastWidth = window.Width; lastHeight = window.Height; renderContext.Resize(lastWidth, lastHeight); - ref var camera = ref cameraEntity.Ensure(); - 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(); - 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); - queue.CompletePendingScreenshots(); frames++; if (timing.TotalTime - lastFpsTime >= 1.0) { - currentFps = frames; Console.WriteLine($"FPS: {frames}, Delta: {timing.DeltaTime * 1000.0:F2} ms"); frames = 0; lastFpsTime = timing.TotalTime; @@ -303,181 +50,13 @@ class Program } Console.WriteLine("Shutting down..."); - imGuiLayer?.Dispose(); -#if !RELEASE_AOT - if (mcpApp != null) - await mcpApp.StopAsync(); - if (mcpTask != null) - await mcpTask; -#endif } catch (Exception ex) { Console.WriteLine($"Fatal error: {ex}"); Environment.Exit(1); } - } - private static void CreateDemoScene(World world, Mesh mesh) - { - 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.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}°"); + await Task.CompletedTask; } } diff --git a/src/Engine.Core/Sdl3Window.cs b/src/Engine.Core/Sdl3Window.cs index d8e9e9a..27f6f5b 100644 --- a/src/Engine.Core/Sdl3Window.cs +++ b/src/Engine.Core/Sdl3Window.cs @@ -1,6 +1,7 @@ using System; using System.Runtime.InteropServices; using System.Text; +using System.Threading.Tasks; using SDL; namespace Engine.Core; @@ -34,6 +35,8 @@ public sealed unsafe class Sdl3Window : IWindow var flags = SDL_WindowFlags.SDL_WINDOW_RESIZABLE; if (vulkanSurface) 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'); 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. SDL_Event 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() diff --git a/src/Engine.Graphics.Vulkan/Engine.Graphics.Vulkan.csproj b/src/Engine.Graphics.Vulkan/Engine.Graphics.Vulkan.csproj index f63c967..655e09e 100644 --- a/src/Engine.Graphics.Vulkan/Engine.Graphics.Vulkan.csproj +++ b/src/Engine.Graphics.Vulkan/Engine.Graphics.Vulkan.csproj @@ -17,20 +17,14 @@ true - - - - - - - - Shaders\%(Filename)%(Extension) + + PreserveNewest diff --git a/src/Engine.Graphics.Vulkan/PngEncoder.cs b/src/Engine.Graphics.Vulkan/PngEncoder.cs deleted file mode 100644 index 570abdf..0000000 --- a/src/Engine.Graphics.Vulkan/PngEncoder.cs +++ /dev/null @@ -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); - } -} diff --git a/src/Engine.Graphics.Vulkan/Shaders/fragment.frag b/src/Engine.Graphics.Vulkan/Shaders/fragment.frag deleted file mode 100644 index 5a99f7b..0000000 --- a/src/Engine.Graphics.Vulkan/Shaders/fragment.frag +++ /dev/null @@ -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); -} diff --git a/src/Engine.Graphics.Vulkan/Shaders/fragment.spv b/src/Engine.Graphics.Vulkan/Shaders/fragment.spv deleted file mode 100644 index 1517e3d..0000000 Binary files a/src/Engine.Graphics.Vulkan/Shaders/fragment.spv and /dev/null differ diff --git a/src/Engine.Graphics.Vulkan/Shaders/imgui.frag b/src/Engine.Graphics.Vulkan/Shaders/imgui.frag deleted file mode 100644 index ce8cf37..0000000 --- a/src/Engine.Graphics.Vulkan/Shaders/imgui.frag +++ /dev/null @@ -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); -} diff --git a/src/Engine.Graphics.Vulkan/Shaders/imgui.frag.spv b/src/Engine.Graphics.Vulkan/Shaders/imgui.frag.spv deleted file mode 100644 index f72ca97..0000000 Binary files a/src/Engine.Graphics.Vulkan/Shaders/imgui.frag.spv and /dev/null differ diff --git a/src/Engine.Graphics.Vulkan/Shaders/imgui.vert b/src/Engine.Graphics.Vulkan/Shaders/imgui.vert deleted file mode 100644 index a883eb3..0000000 --- a/src/Engine.Graphics.Vulkan/Shaders/imgui.vert +++ /dev/null @@ -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); -} diff --git a/src/Engine.Graphics.Vulkan/Shaders/imgui.vert.spv b/src/Engine.Graphics.Vulkan/Shaders/imgui.vert.spv deleted file mode 100644 index a712fb2..0000000 Binary files a/src/Engine.Graphics.Vulkan/Shaders/imgui.vert.spv and /dev/null differ diff --git a/src/Engine.Graphics.Vulkan/Shaders/triangle.frag b/src/Engine.Graphics.Vulkan/Shaders/triangle.frag new file mode 100644 index 0000000..7122ce8 --- /dev/null +++ b/src/Engine.Graphics.Vulkan/Shaders/triangle.frag @@ -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); +} diff --git a/src/Engine.Graphics.Vulkan/Shaders/triangle.frag.spv b/src/Engine.Graphics.Vulkan/Shaders/triangle.frag.spv new file mode 100644 index 0000000..828f1e5 Binary files /dev/null and b/src/Engine.Graphics.Vulkan/Shaders/triangle.frag.spv differ diff --git a/src/Engine.Graphics.Vulkan/Shaders/triangle.vert b/src/Engine.Graphics.Vulkan/Shaders/triangle.vert new file mode 100644 index 0000000..ef6021e --- /dev/null +++ b/src/Engine.Graphics.Vulkan/Shaders/triangle.vert @@ -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; +} diff --git a/src/Engine.Graphics.Vulkan/Shaders/triangle.vert.spv b/src/Engine.Graphics.Vulkan/Shaders/triangle.vert.spv new file mode 100644 index 0000000..fd38ea1 Binary files /dev/null and b/src/Engine.Graphics.Vulkan/Shaders/triangle.vert.spv differ diff --git a/src/Engine.Graphics.Vulkan/Shaders/vertex.spv b/src/Engine.Graphics.Vulkan/Shaders/vertex.spv deleted file mode 100644 index a19b2c2..0000000 Binary files a/src/Engine.Graphics.Vulkan/Shaders/vertex.spv and /dev/null differ diff --git a/src/Engine.Graphics.Vulkan/Shaders/vertex.vert b/src/Engine.Graphics.Vulkan/Shaders/vertex.vert deleted file mode 100644 index ae10582..0000000 --- a/src/Engine.Graphics.Vulkan/Shaders/vertex.vert +++ /dev/null @@ -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); -} diff --git a/src/Engine.Graphics.Vulkan/Vk.cs b/src/Engine.Graphics.Vulkan/Vk.cs index f576ebd..286aaaa 100644 --- a/src/Engine.Graphics.Vulkan/Vk.cs +++ b/src/Engine.Graphics.Vulkan/Vk.cs @@ -2,408 +2,251 @@ using System.Runtime.InteropServices; namespace Engine.Graphics.Vulkan; -public static unsafe class Vk +internal static unsafe class Vk { - public static VkInstance Instance; - public static VkDevice Device; + public delegate VkResult VkCreateInstance(VkInstanceCreateInfo* pCreateInfo, nint pAllocator, VkInstance* pInstance); + 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 static PFN_vkDestroyInstance vkDestroyInstance; - public static PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices; - public static PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties; - public static PFN_vkGetPhysicalDeviceQueueFamilyProperties vkGetPhysicalDeviceQueueFamilyProperties; - public static PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties; - public static PFN_vkEnumerateDeviceExtensionProperties vkEnumerateDeviceExtensionProperties; - public static PFN_vkCreateDevice vkCreateDevice; - public static PFN_vkDestroyDevice vkDestroyDevice; - public static PFN_vkGetDeviceQueue vkGetDeviceQueue; - public static PFN_vkCreateSwapchainKHR vkCreateSwapchainKHR; - public static PFN_vkDestroySwapchainKHR vkDestroySwapchainKHR; - public static PFN_vkGetSwapchainImagesKHR vkGetSwapchainImagesKHR; - public static PFN_vkCreateImageView vkCreateImageView; - public static PFN_vkDestroyImageView vkDestroyImageView; - public static PFN_vkCreateImage vkCreateImage; - public static PFN_vkDestroyImage vkDestroyImage; - public static PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements; - public static PFN_vkBindImageMemory vkBindImageMemory; - public static PFN_vkCreateRenderPass vkCreateRenderPass; - public static PFN_vkDestroyRenderPass vkDestroyRenderPass; - public static PFN_vkCreateFramebuffer vkCreateFramebuffer; - public static PFN_vkDestroyFramebuffer vkDestroyFramebuffer; - public static PFN_vkCreateShaderModule vkCreateShaderModule; - public static PFN_vkDestroyShaderModule vkDestroyShaderModule; - public static PFN_vkCreateDescriptorSetLayout vkCreateDescriptorSetLayout; - public static PFN_vkDestroyDescriptorSetLayout vkDestroyDescriptorSetLayout; - public static PFN_vkCreatePipelineLayout vkCreatePipelineLayout; - public static PFN_vkDestroyPipelineLayout vkDestroyPipelineLayout; - public static PFN_vkCreateGraphicsPipelines vkCreateGraphicsPipelines; - public static PFN_vkDestroyPipeline vkDestroyPipeline; - public static PFN_vkCreateDescriptorPool vkCreateDescriptorPool; - public static PFN_vkDestroyDescriptorPool vkDestroyDescriptorPool; - public static PFN_vkAllocateDescriptorSets vkAllocateDescriptorSets; - public static PFN_vkUpdateDescriptorSets vkUpdateDescriptorSets; - public static PFN_vkCreateBuffer vkCreateBuffer; - public static PFN_vkDestroyBuffer vkDestroyBuffer; - public static PFN_vkGetBufferMemoryRequirements vkGetBufferMemoryRequirements; - public static PFN_vkBindBufferMemory vkBindBufferMemory; - public static PFN_vkAllocateMemory vkAllocateMemory; - public static PFN_vkFreeMemory vkFreeMemory; - public static PFN_vkMapMemory vkMapMemory; - public static PFN_vkUnmapMemory vkUnmapMemory; - public static PFN_vkCreateCommandPool vkCreateCommandPool; - public static PFN_vkDestroyCommandPool vkDestroyCommandPool; - public static PFN_vkAllocateCommandBuffers vkAllocateCommandBuffers; - public static PFN_vkFreeCommandBuffers vkFreeCommandBuffers; - public static PFN_vkBeginCommandBuffer vkBeginCommandBuffer; - public static PFN_vkEndCommandBuffer vkEndCommandBuffer; - public static PFN_vkResetCommandBuffer vkResetCommandBuffer; - public static PFN_vkQueueSubmit vkQueueSubmit; - public static PFN_vkQueueWaitIdle vkQueueWaitIdle; - public static PFN_vkQueuePresentKHR vkQueuePresentKHR; - public static PFN_vkAcquireNextImageKHR vkAcquireNextImageKHR; - public static PFN_vkCreateSemaphore vkCreateSemaphore; - public static PFN_vkDestroySemaphore vkDestroySemaphore; - public static PFN_vkCreateFence vkCreateFence; - public static PFN_vkDestroyFence vkDestroyFence; - public static PFN_vkWaitForFences vkWaitForFences; - public static PFN_vkResetFences vkResetFences; - public static PFN_vkCmdBeginRenderPass vkCmdBeginRenderPass; - public static PFN_vkCmdEndRenderPass vkCmdEndRenderPass; - public static PFN_vkCmdBindPipeline vkCmdBindPipeline; - public static PFN_vkCmdBindDescriptorSets vkCmdBindDescriptorSets; - public static PFN_vkCmdBindVertexBuffers vkCmdBindVertexBuffers; - public static PFN_vkCmdBindIndexBuffer vkCmdBindIndexBuffer; - public static PFN_vkCmdDrawIndexed vkCmdDrawIndexed; - public static PFN_vkCmdDraw vkCmdDraw; - public static PFN_vkCmdSetViewport vkCmdSetViewport; - public static PFN_vkCmdSetScissor vkCmdSetScissor; - public static PFN_vkCmdPipelineBarrier vkCmdPipelineBarrier; - public static PFN_vkCmdCopyBuffer vkCmdCopyBuffer; - public static PFN_vkCmdCopyBufferToImage vkCmdCopyBufferToImage; - public static PFN_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 delegate void VkGetDeviceQueue(VkDevice device, uint queueFamilyIndex, uint queueIndex, VkQueue* pQueue); + public delegate VkResult VkCreateSwapchainKHR(VkDevice device, VkSwapchainCreateInfoKHR* pCreateInfo, nint pAllocator, VkSwapchainKHR* pSwapchain); + public delegate void VkDestroySwapchainKHR(VkDevice device, VkSwapchainKHR swapchain, nint pAllocator); + public delegate VkResult VkGetSwapchainImagesKHR(VkDevice device, VkSwapchainKHR swapchain, uint* pSwapchainImageCount, VkImage* pSwapchainImages); + public delegate VkResult VkCreateImageView(VkDevice device, VkImageViewCreateInfo* pCreateInfo, nint pAllocator, VkImageView* pImageView); + public delegate void VkDestroyImageView(VkDevice device, VkImageView imageView, nint pAllocator); + public delegate VkResult VkCreateShaderModule(VkDevice device, VkShaderModuleCreateInfo* pCreateInfo, nint pAllocator, VkShaderModule* pShaderModule); + public delegate void VkDestroyShaderModule(VkDevice device, VkShaderModule shaderModule, nint pAllocator); + public delegate VkResult VkCreatePipelineLayout(VkDevice device, VkPipelineLayoutCreateInfo* pCreateInfo, nint pAllocator, VkPipelineLayout* pPipelineLayout); + public delegate void VkDestroyPipelineLayout(VkDevice device, VkPipelineLayout pipelineLayout, nint pAllocator); + public delegate VkResult VkCreateGraphicsPipelines(VkDevice device, nint pipelineCache, uint createInfoCount, VkGraphicsPipelineCreateInfo* pCreateInfos, nint pAllocator, VkPipeline* pPipelines); + public delegate void VkDestroyPipeline(VkDevice device, VkPipeline pipeline, nint pAllocator); + public delegate VkResult VkCreateCommandPool(VkDevice device, VkCommandPoolCreateInfo* pCreateInfo, nint pAllocator, VkCommandPool* pCommandPool); + public delegate void VkDestroyCommandPool(VkDevice device, VkCommandPool commandPool, nint pAllocator); + public delegate VkResult VkAllocateCommandBuffers(VkDevice device, VkCommandBufferAllocateInfo* pAllocateInfo, VkCommandBuffer* pCommandBuffers); + public delegate void VkFreeCommandBuffers(VkDevice device, VkCommandPool commandPool, uint commandBufferCount, VkCommandBuffer* pCommandBuffers); + public delegate VkResult VkBeginCommandBuffer(VkCommandBuffer commandBuffer, VkCommandBufferBeginInfo* pBeginInfo); + public delegate VkResult VkEndCommandBuffer(VkCommandBuffer commandBuffer); + public delegate VkResult VkResetCommandBuffer(VkCommandBuffer commandBuffer, uint flags); + public delegate VkResult VkCreateSemaphore(VkDevice device, VkSemaphoreCreateInfo* pCreateInfo, nint pAllocator, VkSemaphore* pSemaphore); + public delegate void VkDestroySemaphore(VkDevice device, VkSemaphore semaphore, nint pAllocator); + public delegate VkResult VkCreateFence(VkDevice device, VkFenceCreateInfo* pCreateInfo, nint pAllocator, VkFence* pFence); + public delegate void VkDestroyFence(VkDevice device, VkFence fence, nint pAllocator); + public delegate VkResult VkResetFences(VkDevice device, uint fenceCount, VkFence* pFences); + public delegate VkResult VkWaitForFences(VkDevice device, uint fenceCount, VkFence* pFences, VkBool32 waitAll, ulong timeout); + public delegate VkResult VkGetFenceStatus(VkDevice device, VkFence fence); + public delegate VkResult VkCreateBuffer(VkDevice device, VkBufferCreateInfo* pCreateInfo, nint pAllocator, VkBuffer* pBuffer); + public delegate void VkDestroyBuffer(VkDevice device, VkBuffer buffer, nint pAllocator); + public delegate VkResult VkAllocateMemory(VkDevice device, VkMemoryAllocateInfo* pAllocateInfo, nint pAllocator, VkDeviceMemory* pMemory); + public delegate void VkFreeMemory(VkDevice device, VkDeviceMemory memory, nint pAllocator); + public delegate VkResult VkBindBufferMemory(VkDevice device, VkBuffer buffer, VkDeviceMemory memory, ulong memoryOffset); + public delegate void VkGetBufferMemoryRequirements(VkDevice device, VkBuffer buffer, VkMemoryRequirements* pMemoryRequirements); + public delegate VkResult VkMapMemory(VkDevice device, VkDeviceMemory memory, ulong offset, ulong size, uint flags, void** ppData); + public delegate void VkUnmapMemory(VkDevice device, VkDeviceMemory memory); + public delegate void VkCmdBindPipeline(VkCommandBuffer commandBuffer, VkPipelineBindPoint pipelineBindPoint, VkPipeline pipeline); + public delegate void VkCmdSetViewport(VkCommandBuffer commandBuffer, uint firstViewport, uint viewportCount, VkViewport* pViewports); + public delegate void VkCmdSetScissor(VkCommandBuffer commandBuffer, uint firstScissor, uint scissorCount, VkRect2D* pScissors); + public delegate void VkCmdBindVertexBuffers(VkCommandBuffer commandBuffer, uint firstBinding, uint bindingCount, VkBuffer* pBuffers, ulong* pOffsets); + public delegate void VkCmdDraw(VkCommandBuffer commandBuffer, uint vertexCount, uint instanceCount, uint firstVertex, uint firstInstance); + public delegate void VkCmdBeginRendering(VkCommandBuffer commandBuffer, VkRenderingInfo* pRenderingInfo); + public delegate void VkCmdEndRendering(VkCommandBuffer commandBuffer); + public delegate void VkCmdPipelineBarrier2(VkCommandBuffer commandBuffer, VkDependencyInfo* pDependencyInfo); + public delegate void VkCmdCopyBuffer(VkCommandBuffer commandBuffer, VkBuffer srcBuffer, VkBuffer dstBuffer, uint regionCount, VkBufferCopy* pRegions); + public delegate VkResult VkAcquireNextImageKHR(VkDevice device, VkSwapchainKHR swapchain, ulong timeout, VkSemaphore semaphore, VkFence fence, uint* pImageIndex); + public delegate VkResult VkQueueSubmit2(VkQueue queue, uint submitCount, VkSubmitInfo2* pSubmits, VkFence fence); + public delegate VkResult VkQueuePresentKHR(VkQueue queue, VkPresentInfoKHR* pPresentInfo); + public delegate VkResult VkDeviceWaitIdle(VkDevice device); + public delegate VkResult VkQueueWaitIdle(VkQueue queue); - public static void LoadGlobalFunctions() - { - VulkanNative.LoadLibrary(); - var libHandle = VulkanNative.LoadLibrary(); + public delegate VkResult VkCreateDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, nint pAllocator, VkDebugUtilsMessengerEXT* pMessenger); + public delegate void VkDestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT messenger, nint pAllocator); - var createInstancePtr = NativeLibrary.GetExport(libHandle, "vkCreateInstance"); - vkCreateInstance = Marshal.GetDelegateForFunctionPointer(createInstancePtr); - } + public static VkCreateInstance vkCreateInstance; + 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) { - Instance = instance; - - vkDestroyInstance = VulkanNative.LoadInstanceFunction(instance, "vkDestroyInstance"); - vkEnumeratePhysicalDevices = VulkanNative.LoadInstanceFunction(instance, "vkEnumeratePhysicalDevices"); - vkGetPhysicalDeviceProperties = VulkanNative.LoadInstanceFunction(instance, "vkGetPhysicalDeviceProperties"); - vkGetPhysicalDeviceQueueFamilyProperties = VulkanNative.LoadInstanceFunction(instance, "vkGetPhysicalDeviceQueueFamilyProperties"); - vkGetPhysicalDeviceMemoryProperties = VulkanNative.LoadInstanceFunction(instance, "vkGetPhysicalDeviceMemoryProperties"); - vkEnumerateDeviceExtensionProperties = VulkanNative.LoadInstanceFunction(instance, "vkEnumerateDeviceExtensionProperties"); - vkCreateDevice = VulkanNative.LoadInstanceFunction(instance, "vkCreateDevice"); - vkGetPhysicalDeviceSurfaceSupportKHR = VulkanNative.LoadInstanceFunction(instance, "vkGetPhysicalDeviceSurfaceSupportKHR"); - vkGetPhysicalDeviceSurfaceCapabilitiesKHR = VulkanNative.LoadInstanceFunction(instance, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR"); - vkGetPhysicalDeviceSurfaceFormatsKHR = VulkanNative.LoadInstanceFunction(instance, "vkGetPhysicalDeviceSurfaceFormatsKHR"); - vkGetPhysicalDeviceSurfacePresentModesKHR = VulkanNative.LoadInstanceFunction(instance, "vkGetPhysicalDeviceSurfacePresentModesKHR"); - vkDestroySurfaceKHR = VulkanNative.LoadInstanceFunction(instance, "vkDestroySurfaceKHR"); + var p = instance.Handle; + vkDestroyInstance = Load(p, "vkDestroyInstance"); + vkEnumeratePhysicalDevices = Load(p, "vkEnumeratePhysicalDevices"); + vkGetPhysicalDeviceProperties = Load(p, "vkGetPhysicalDeviceProperties"); + vkGetPhysicalDeviceMemoryProperties = Load(p, "vkGetPhysicalDeviceMemoryProperties"); + vkGetPhysicalDeviceQueueFamilyProperties = Load(p, "vkGetPhysicalDeviceQueueFamilyProperties"); + vkGetPhysicalDeviceSurfaceSupportKHR = Load(p, "vkGetPhysicalDeviceSurfaceSupportKHR"); + vkGetPhysicalDeviceSurfaceCapabilitiesKHR = Load(p, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR"); + vkGetPhysicalDeviceSurfaceFormatsKHR = Load(p, "vkGetPhysicalDeviceSurfaceFormatsKHR"); + vkGetPhysicalDeviceSurfacePresentModesKHR = Load(p, "vkGetPhysicalDeviceSurfacePresentModesKHR"); + vkCreateDevice = Load(p, "vkCreateDevice"); + vkDestroyDevice = Load(p, "vkDestroyDevice"); + vkDestroySurfaceKHR = Load(p, "vkDestroySurfaceKHR"); + vkGetDeviceProcAddr = Load(p, "vkGetDeviceProcAddr"); + TryLoadDebugUtils(p); } public static void LoadDeviceFunctions(VkDevice device) { - Device = device; - - vkDestroyDevice = VulkanNative.LoadDeviceFunction(device, "vkDestroyDevice"); - vkGetDeviceQueue = VulkanNative.LoadDeviceFunction(device, "vkGetDeviceQueue"); - vkCreateSwapchainKHR = VulkanNative.LoadDeviceFunction(device, "vkCreateSwapchainKHR"); - vkDestroySwapchainKHR = VulkanNative.LoadDeviceFunction(device, "vkDestroySwapchainKHR"); - vkGetSwapchainImagesKHR = VulkanNative.LoadDeviceFunction(device, "vkGetSwapchainImagesKHR"); - vkCreateImageView = VulkanNative.LoadDeviceFunction(device, "vkCreateImageView"); - vkDestroyImageView = VulkanNative.LoadDeviceFunction(device, "vkDestroyImageView"); - vkCreateImage = VulkanNative.LoadDeviceFunction(device, "vkCreateImage"); - vkDestroyImage = VulkanNative.LoadDeviceFunction(device, "vkDestroyImage"); - vkGetImageMemoryRequirements = VulkanNative.LoadDeviceFunction(device, "vkGetImageMemoryRequirements"); - vkBindImageMemory = VulkanNative.LoadDeviceFunction(device, "vkBindImageMemory"); - vkCreateRenderPass = VulkanNative.LoadDeviceFunction(device, "vkCreateRenderPass"); - vkDestroyRenderPass = VulkanNative.LoadDeviceFunction(device, "vkDestroyRenderPass"); - vkCreateFramebuffer = VulkanNative.LoadDeviceFunction(device, "vkCreateFramebuffer"); - vkDestroyFramebuffer = VulkanNative.LoadDeviceFunction(device, "vkDestroyFramebuffer"); - vkCreateShaderModule = VulkanNative.LoadDeviceFunction(device, "vkCreateShaderModule"); - vkDestroyShaderModule = VulkanNative.LoadDeviceFunction(device, "vkDestroyShaderModule"); - vkCreateDescriptorSetLayout = VulkanNative.LoadDeviceFunction(device, "vkCreateDescriptorSetLayout"); - vkDestroyDescriptorSetLayout = VulkanNative.LoadDeviceFunction(device, "vkDestroyDescriptorSetLayout"); - vkCreatePipelineLayout = VulkanNative.LoadDeviceFunction(device, "vkCreatePipelineLayout"); - vkDestroyPipelineLayout = VulkanNative.LoadDeviceFunction(device, "vkDestroyPipelineLayout"); - vkCreateGraphicsPipelines = VulkanNative.LoadDeviceFunction(device, "vkCreateGraphicsPipelines"); - vkDestroyPipeline = VulkanNative.LoadDeviceFunction(device, "vkDestroyPipeline"); - vkCreateDescriptorPool = VulkanNative.LoadDeviceFunction(device, "vkCreateDescriptorPool"); - vkDestroyDescriptorPool = VulkanNative.LoadDeviceFunction(device, "vkDestroyDescriptorPool"); - vkAllocateDescriptorSets = VulkanNative.LoadDeviceFunction(device, "vkAllocateDescriptorSets"); - vkUpdateDescriptorSets = VulkanNative.LoadDeviceFunction(device, "vkUpdateDescriptorSets"); - vkCreateBuffer = VulkanNative.LoadDeviceFunction(device, "vkCreateBuffer"); - vkDestroyBuffer = VulkanNative.LoadDeviceFunction(device, "vkDestroyBuffer"); - vkGetBufferMemoryRequirements = VulkanNative.LoadDeviceFunction(device, "vkGetBufferMemoryRequirements"); - vkBindBufferMemory = VulkanNative.LoadDeviceFunction(device, "vkBindBufferMemory"); - vkAllocateMemory = VulkanNative.LoadDeviceFunction(device, "vkAllocateMemory"); - vkFreeMemory = VulkanNative.LoadDeviceFunction(device, "vkFreeMemory"); - vkMapMemory = VulkanNative.LoadDeviceFunction(device, "vkMapMemory"); - vkUnmapMemory = VulkanNative.LoadDeviceFunction(device, "vkUnmapMemory"); - vkCreateCommandPool = VulkanNative.LoadDeviceFunction(device, "vkCreateCommandPool"); - vkDestroyCommandPool = VulkanNative.LoadDeviceFunction(device, "vkDestroyCommandPool"); - vkAllocateCommandBuffers = VulkanNative.LoadDeviceFunction(device, "vkAllocateCommandBuffers"); - vkFreeCommandBuffers = VulkanNative.LoadDeviceFunction(device, "vkFreeCommandBuffers"); - vkBeginCommandBuffer = VulkanNative.LoadDeviceFunction(device, "vkBeginCommandBuffer"); - vkEndCommandBuffer = VulkanNative.LoadDeviceFunction(device, "vkEndCommandBuffer"); - vkResetCommandBuffer = VulkanNative.LoadDeviceFunction(device, "vkResetCommandBuffer"); - vkQueueSubmit = VulkanNative.LoadDeviceFunction(device, "vkQueueSubmit"); - vkQueueWaitIdle = VulkanNative.LoadDeviceFunction(device, "vkQueueWaitIdle"); - vkQueuePresentKHR = VulkanNative.LoadDeviceFunction(device, "vkQueuePresentKHR"); - vkAcquireNextImageKHR = VulkanNative.LoadDeviceFunction(device, "vkAcquireNextImageKHR"); - vkCreateSemaphore = VulkanNative.LoadDeviceFunction(device, "vkCreateSemaphore"); - vkDestroySemaphore = VulkanNative.LoadDeviceFunction(device, "vkDestroySemaphore"); - vkCreateFence = VulkanNative.LoadDeviceFunction(device, "vkCreateFence"); - vkDestroyFence = VulkanNative.LoadDeviceFunction(device, "vkDestroyFence"); - vkWaitForFences = VulkanNative.LoadDeviceFunction(device, "vkWaitForFences"); - vkResetFences = VulkanNative.LoadDeviceFunction(device, "vkResetFences"); - vkCmdBeginRenderPass = VulkanNative.LoadDeviceFunction(device, "vkCmdBeginRenderPass"); - vkCmdEndRenderPass = VulkanNative.LoadDeviceFunction(device, "vkCmdEndRenderPass"); - vkCmdBindPipeline = VulkanNative.LoadDeviceFunction(device, "vkCmdBindPipeline"); - vkCmdBindDescriptorSets = VulkanNative.LoadDeviceFunction(device, "vkCmdBindDescriptorSets"); - vkCmdBindVertexBuffers = VulkanNative.LoadDeviceFunction(device, "vkCmdBindVertexBuffers"); - vkCmdBindIndexBuffer = VulkanNative.LoadDeviceFunction(device, "vkCmdBindIndexBuffer"); - vkCmdDrawIndexed = VulkanNative.LoadDeviceFunction(device, "vkCmdDrawIndexed"); - vkCmdDraw = VulkanNative.LoadDeviceFunction(device, "vkCmdDraw"); - vkCmdSetViewport = VulkanNative.LoadDeviceFunction(device, "vkCmdSetViewport"); - vkCmdSetScissor = VulkanNative.LoadDeviceFunction(device, "vkCmdSetScissor"); - vkCmdPipelineBarrier = VulkanNative.LoadDeviceFunction(device, "vkCmdPipelineBarrier"); - vkCmdCopyBuffer = VulkanNative.LoadDeviceFunction(device, "vkCmdCopyBuffer"); - vkCmdCopyBufferToImage = VulkanNative.LoadDeviceFunction(device, "vkCmdCopyBufferToImage"); - vkCmdCopyImageToBuffer = VulkanNative.LoadDeviceFunction(device, "vkCmdCopyImageToBuffer"); - vkCmdClearColorImage = VulkanNative.LoadDeviceFunction(device, "vkCmdClearColorImage"); - vkCmdPushConstants = VulkanNative.LoadDeviceFunction(device, "vkCmdPushConstants"); - vkCreateSampler = VulkanNative.LoadDeviceFunction(device, "vkCreateSampler"); - vkDestroySampler = VulkanNative.LoadDeviceFunction(device, "vkDestroySampler"); + var p = device.Handle; + vkGetDeviceQueue = LoadDev(p, "vkGetDeviceQueue"); + vkCreateSwapchainKHR = LoadDev(p, "vkCreateSwapchainKHR"); + vkDestroySwapchainKHR = LoadDev(p, "vkDestroySwapchainKHR"); + vkGetSwapchainImagesKHR = LoadDev(p, "vkGetSwapchainImagesKHR"); + vkCreateImageView = LoadDev(p, "vkCreateImageView"); + vkDestroyImageView = LoadDev(p, "vkDestroyImageView"); + vkCreateShaderModule = LoadDev(p, "vkCreateShaderModule"); + vkDestroyShaderModule = LoadDev(p, "vkDestroyShaderModule"); + vkCreatePipelineLayout = LoadDev(p, "vkCreatePipelineLayout"); + vkDestroyPipelineLayout = LoadDev(p, "vkDestroyPipelineLayout"); + vkCreateGraphicsPipelines = LoadDev(p, "vkCreateGraphicsPipelines"); + vkDestroyPipeline = LoadDev(p, "vkDestroyPipeline"); + vkCreateCommandPool = LoadDev(p, "vkCreateCommandPool"); + vkDestroyCommandPool = LoadDev(p, "vkDestroyCommandPool"); + vkAllocateCommandBuffers = LoadDev(p, "vkAllocateCommandBuffers"); + vkFreeCommandBuffers = LoadDev(p, "vkFreeCommandBuffers"); + vkBeginCommandBuffer = LoadDev(p, "vkBeginCommandBuffer"); + vkEndCommandBuffer = LoadDev(p, "vkEndCommandBuffer"); + vkResetCommandBuffer = LoadDev(p, "vkResetCommandBuffer"); + vkCreateSemaphore = LoadDev(p, "vkCreateSemaphore"); + vkDestroySemaphore = LoadDev(p, "vkDestroySemaphore"); + vkCreateFence = LoadDev(p, "vkCreateFence"); + vkDestroyFence = LoadDev(p, "vkDestroyFence"); + vkResetFences = LoadDev(p, "vkResetFences"); + vkWaitForFences = LoadDev(p, "vkWaitForFences"); + vkGetFenceStatus = LoadDev(p, "vkGetFenceStatus"); + vkCreateBuffer = LoadDev(p, "vkCreateBuffer"); + vkDestroyBuffer = LoadDev(p, "vkDestroyBuffer"); + vkAllocateMemory = LoadDev(p, "vkAllocateMemory"); + vkFreeMemory = LoadDev(p, "vkFreeMemory"); + vkBindBufferMemory = LoadDev(p, "vkBindBufferMemory"); + vkGetBufferMemoryRequirements = LoadDev(p, "vkGetBufferMemoryRequirements"); + vkMapMemory = LoadDev(p, "vkMapMemory"); + vkUnmapMemory = LoadDev(p, "vkUnmapMemory"); + vkCmdBindPipeline = LoadDev(p, "vkCmdBindPipeline"); + vkCmdSetViewport = LoadDev(p, "vkCmdSetViewport"); + vkCmdSetScissor = LoadDev(p, "vkCmdSetScissor"); + vkCmdBindVertexBuffers = LoadDev(p, "vkCmdBindVertexBuffers"); + vkCmdDraw = LoadDev(p, "vkCmdDraw"); + vkCmdBeginRendering = LoadDev(p, "vkCmdBeginRendering"); + vkCmdEndRendering = LoadDev(p, "vkCmdEndRendering"); + vkCmdPipelineBarrier2 = LoadDev(p, "vkCmdPipelineBarrier2"); + vkCmdCopyBuffer = LoadDev(p, "vkCmdCopyBuffer"); + vkAcquireNextImageKHR = LoadDev(p, "vkAcquireNextImageKHR"); + vkQueueSubmit2 = LoadDev(p, "vkQueueSubmit2"); + vkQueuePresentKHR = LoadDev(p, "vkQueuePresentKHR"); + vkDeviceWaitIdle = LoadDev(p, "vkDeviceWaitIdle"); + vkQueueWaitIdle = LoadDev(p, "vkQueueWaitIdle"); } - private static T Load(nint libHandle, string name) where T : Delegate + private static void TryLoadDebugUtils(nint instance) { - var ptr = NativeLibrary.GetExport(libHandle, name); - if (ptr == 0) - throw new InvalidOperationException($"Failed to load Vulkan function: {name}"); - return Marshal.GetDelegateForFunctionPointer(ptr); + try + { + vkCreateDebugUtilsMessengerEXT = Load(instance, "vkCreateDebugUtilsMessengerEXT"); + vkDestroyDebugUtilsMessengerEXT = Load(instance, "vkDestroyDebugUtilsMessengerEXT"); + } + catch + { + vkCreateDebugUtilsMessengerEXT = null!; + vkDestroyDebugUtilsMessengerEXT = null!; + } } - public static void CheckResult(VkResult result, string operation) + private static T Load(nint instance, string name) where T : Delegate { - if (result != VkResult.Success && result != VkResult.SuboptimalKHR) - throw new InvalidOperationException($"Vulkan error {result} during: {operation}"); + fixed (byte* pName = VulkanString.ToUtf8Terminated(name)) + { + var addr = VulkanNative.vkGetInstanceProcAddr(instance, pName); + if (addr == 0) + throw new EntryPointNotFoundException($"vkGetInstanceProcAddr returned null for: {name}"); + return Marshal.GetDelegateForFunctionPointer(addr); + } } - public static byte[] ToUtf8NullTerminated(string s) + private static T LoadDev(nint device, string name) where T : Delegate { - var bytes = new byte[System.Text.Encoding.UTF8.GetByteCount(s) + 1]; - System.Text.Encoding.UTF8.GetBytes(s, 0, s.Length, bytes, 0); - return bytes; - } - - public static byte* AllocUtf8(string s) - { - var bytes = ToUtf8NullTerminated(s); - var ptr = (byte*)Marshal.AllocHGlobal(bytes.Length); - Marshal.Copy(bytes, 0, (nint)ptr, bytes.Length); - return ptr; - } - - public static void FreeUtf8(byte* ptr) => Marshal.FreeHGlobal((nint)ptr); - - public static byte** AllocStringArray(string[] strings) - { - var ptrArray = (byte**)Marshal.AllocHGlobal(strings.Length * sizeof(nint)); - for (var i = 0; i < strings.Length; i++) - ptrArray[i] = AllocUtf8(strings[i]); - return ptrArray; - } - - public static void FreeStringArray(byte** ptrArray, int count) - { - for (var i = 0; i < count; i++) - FreeUtf8(ptrArray[i]); - Marshal.FreeHGlobal((nint)ptrArray); + fixed (byte* pName = VulkanString.ToUtf8Terminated(name)) + { + var addr = vkGetDeviceProcAddr(new VkDevice { Handle = device }, pName); + if (addr == 0) + { + addr = VulkanNative.vkGetInstanceProcAddr(0, pName); + if (addr == 0) + throw new EntryPointNotFoundException($"vkGetDeviceProcAddr returned null for: {name}"); + } + return Marshal.GetDelegateForFunctionPointer(addr); + } } } - -[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); diff --git a/src/Engine.Graphics.Vulkan/VulkanBackendRegistrar.cs b/src/Engine.Graphics.Vulkan/VulkanBackendRegistrar.cs index 3750b1f..8d4d34d 100644 --- a/src/Engine.Graphics.Vulkan/VulkanBackendRegistrar.cs +++ b/src/Engine.Graphics.Vulkan/VulkanBackendRegistrar.cs @@ -1,16 +1,23 @@ +using Engine.Core; using Engine.Graphics; namespace Engine.Graphics.Vulkan; public static class VulkanBackendRegistrar { - private static int _registered; + private static bool _registered; 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) => - new VulkanRenderContext(width, height, validation)); + RenderBackendFactory.Register("vulkan", (width, height, enableValidation) => + { + var window = new Sdl3Window("Cortex Engine — Vulkan", width, height, vulkanSurface: true); + return new VulkanRenderContext(window, enableValidation); + }); + + Console.WriteLine("[Vulkan] Backend registered as 'vulkan'"); } } diff --git a/src/Engine.Graphics.Vulkan/VulkanBuffer.cs b/src/Engine.Graphics.Vulkan/VulkanBuffer.cs deleted file mode 100644 index 716fc40..0000000 --- a/src/Engine.Graphics.Vulkan/VulkanBuffer.cs +++ /dev/null @@ -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[] data, ulong offset = 0) where T : struct - { - var size = (ulong)(data.Length * Marshal.SizeOf()); - fixed (T* pData = data) - { - Write(pData, size, offset); - } - } - - public static unsafe VulkanBuffer CreateStaging(VulkanContext ctx, void* data, ulong size) - { - var staging = new VulkanBuffer(ctx, size, VkBufferUsageFlags.TransferSrc, VkMemoryPropertyFlags.HostVisible | VkMemoryPropertyFlags.HostCoherent); - staging.Write(data, size); - return staging; - } - - public static unsafe void CopyBuffer(VulkanContext ctx, VkCommandPool cmdPool, VkBuffer src, VkBuffer dst, ulong size) - { - VkCommandBuffer cmd = BeginSingleTimeCommands(ctx, cmdPool); - - var region = new VkBufferCopy { srcOffset = 0, dstOffset = 0, size = size }; - Vk.vkCmdCopyBuffer(cmd, src, dst, 1, ®ion); - - EndSingleTimeCommands(ctx, cmdPool, cmd); - } - - public static VulkanBuffer CreateDeviceLocal(VulkanContext ctx, VkCommandPool cmdPool, T[] data, VkBufferUsageFlags usage) where T : struct - { - var size = (ulong)(data.Length * Marshal.SizeOf()); - var staging = new VulkanBuffer(ctx, size, VkBufferUsageFlags.TransferSrc, VkMemoryPropertyFlags.HostVisible | VkMemoryPropertyFlags.HostCoherent); - - fixed (T* pData = data) - { - staging.Write(pData, size); - } - - var deviceBuffer = new VulkanBuffer(ctx, size, usage | VkBufferUsageFlags.TransferDst, VkMemoryPropertyFlags.DeviceLocal); - CopyBuffer(ctx, cmdPool, staging.Buffer, deviceBuffer.Buffer, size); - - staging.Dispose(); - return deviceBuffer; - } - - public static unsafe VkCommandBuffer BeginSingleTimeCommands(VulkanContext ctx, VkCommandPool cmdPool) - { - VkCommandBufferAllocateInfo allocInfo; - allocInfo.sType = VkStructureType.CommandBufferAllocateInfo; - allocInfo.pNext = null; - allocInfo.commandPool = cmdPool; - allocInfo.level = VkCommandBufferLevel.Primary; - allocInfo.commandBufferCount = 1; - - VkCommandBuffer cmd; - Vk.vkAllocateCommandBuffers(ctx.Device, &allocInfo, &cmd); - - VkCommandBufferBeginInfo beginInfo; - beginInfo.sType = VkStructureType.CommandBufferBeginInfo; - beginInfo.pNext = null; - beginInfo.flags = VkCommandBufferUsageFlags.OneTimeSubmit; - beginInfo.pInheritanceInfo = null; - - Vk.vkBeginCommandBuffer(cmd, &beginInfo); - return cmd; - } - - public static unsafe void EndSingleTimeCommands(VulkanContext ctx, VkCommandPool cmdPool, VkCommandBuffer cmd) - { - Vk.vkEndCommandBuffer(cmd); - - VkSubmitInfo submitInfo; - submitInfo.sType = VkStructureType.SubmitInfo; - submitInfo.pNext = null; - submitInfo.waitSemaphoreCount = 0; - submitInfo.pWaitSemaphores = null; - submitInfo.pWaitDstStageMask = null; - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &cmd; - submitInfo.signalSemaphoreCount = 0; - submitInfo.pSignalSemaphores = null; - - Vk.vkQueueSubmit(ctx.GraphicsQueue, 1, &submitInfo, default); - Vk.vkQueueWaitIdle(ctx.GraphicsQueue); - - Vk.vkFreeCommandBuffers(ctx.Device, cmdPool, 1, &cmd); - } - - public unsafe void Dispose() - { - if (_disposed) return; - _disposed = true; - - if (MappedData != null) - { - Vk.vkUnmapMemory(_ctx.Device, Memory); - MappedData = null; - } - if (Buffer.Value != 0) Vk.vkDestroyBuffer(_ctx.Device, Buffer, null); - if (Memory.Value != 0) Vk.vkFreeMemory(_ctx.Device, Memory, null); - } -} diff --git a/src/Engine.Graphics.Vulkan/VulkanContext.cs b/src/Engine.Graphics.Vulkan/VulkanContext.cs index 0d316d4..51dd16e 100644 --- a/src/Engine.Graphics.Vulkan/VulkanContext.cs +++ b/src/Engine.Graphics.Vulkan/VulkanContext.cs @@ -1,334 +1,457 @@ using System.Runtime.InteropServices; -using System.Text; -using Engine.Core; using SDL; +using Engine.Core; 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 VkPhysicalDevice PhysicalDevice; public VkDevice Device; - public VkSurfaceKHR Surface; public VkQueue GraphicsQueue; - public VkQueue PresentQueue; - public uint GraphicsFamily; - public uint PresentFamily; + public VkSurfaceKHR Surface; + public uint GraphicsQueueFamilyIndex; 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 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; - Vk.LoadGlobalFunctions(); - CreateInstance(window.GetRequiredVulkanExtensions()); - CreateSurface(window); - PickPhysicalDevice(); - CreateLogicalDevice(); - } - - private unsafe void CreateInstance(string[] requiredExtensions) - { - var layers = Array.Empty(); - if (_validation) - { - uint layerCount = 0; - VulkanNative.vkEnumerateInstanceLayerProperties(&layerCount, null); - if (layerCount > 0) - { - var availableLayers = new VkLayerProperties[layerCount]; - fixed (VkLayerProperties* pLayers = availableLayers) - { - VulkanNative.vkEnumerateInstanceLayerProperties(&layerCount, pLayers); - } - - for (var i = 0; i < layerCount; i++) - { - fixed (VkLayerProperties* pLayer = &availableLayers[i]) - { - var nameLen = 0; - while (nameLen < 256 && pLayer->layerName[nameLen] != 0) nameLen++; - var layerName = Encoding.UTF8.GetString(pLayer->layerName, nameLen); - - if (layerName == "VK_LAYER_KHRONOS_validation") - { - layers = new[] { "VK_LAYER_KHRONOS_validation" }; - Console.WriteLine("[Vulkan] Validation layers enabled."); - break; - } - } - } - } - - if (layers.Length == 0) - Console.WriteLine("[Vulkan] Validation layers requested but not available."); - } - - var extensions = requiredExtensions; - - var appNameBytes = Encoding.UTF8.GetBytes("Cortex Engine\0"); - var engineNameBytes = Encoding.UTF8.GetBytes("CortexEngine\0"); - - VkApplicationInfo appInfo; - appInfo.sType = VkStructureType.ApplicationInfo; - appInfo.pNext = null; - fixed (byte* pAppName = appNameBytes, pEngineName = engineNameBytes) - { - appInfo.pApplicationName = pAppName; - appInfo.applicationVersion = 0; - appInfo.pEngineName = pEngineName; - appInfo.engineVersion = 0; - appInfo.apiVersion = (1 << 22) | (3 << 12); - - var extPtrs = Vk.AllocStringArray(extensions); - var layerPtrs = Vk.AllocStringArray(layers); - - VkInstanceCreateInfo createInfo; - createInfo.sType = VkStructureType.InstanceCreateInfo; - createInfo.pNext = null; - createInfo.flags = 0; - createInfo.pApplicationInfo = &appInfo; - createInfo.enabledLayerCount = (uint)layers.Length; - createInfo.ppEnabledLayerNames = layerPtrs; - createInfo.enabledExtensionCount = (uint)extensions.Length; - createInfo.ppEnabledExtensionNames = extPtrs; - - VkResult result; - fixed (VkInstance* pInstance = &Instance) - { - result = Vk.vkCreateInstance(&createInfo, null, pInstance); - } - - Vk.FreeStringArray(extPtrs, extensions.Length); - Vk.FreeStringArray(layerPtrs, layers.Length); - - if (result != VkResult.Success) - throw new InvalidOperationException($"vkCreateInstance failed: {result}. " + - $"Extensions: [{string.Join(", ", extensions)}], Layers: [{string.Join(", ", layers)}]"); - } - - Vk.LoadInstanceFunctions(Instance); - Console.WriteLine("[Vulkan] Instance created."); - } - - private unsafe void CreateSurface(Sdl3Window window) - { - var sdlWindow = (SDL_Window*)window.Handle; - - var instancePtr = (SDL.VkInstance_T*)Instance.Value; - SDL.VkSurfaceKHR_T* surfacePtr; - if (!SDL3.SDL_Vulkan_CreateSurface(sdlWindow, instancePtr, null, &surfacePtr)) - throw new InvalidOperationException($"SDL_Vulkan_CreateSurface failed: {SDL3.SDL_GetError()}"); - - Surface = new VkSurfaceKHR { Value = (ulong)surfacePtr }; - Console.WriteLine("[Vulkan] Surface created."); - } - - private unsafe void PickPhysicalDevice() - { - uint deviceCount = 0; - Vk.vkEnumeratePhysicalDevices(Instance, &deviceCount, null); - if (deviceCount == 0) - throw new InvalidOperationException("No GPU with Vulkan support found."); - - var devices = new VkPhysicalDevice[deviceCount]; - fixed (VkPhysicalDevice* pDevices = devices) - { - Vk.vkEnumeratePhysicalDevices(Instance, &deviceCount, pDevices); - } - - VkPhysicalDevice bestDevice = default; - uint bestGraphicsFamily = uint.MaxValue; - uint bestPresentFamily = uint.MaxValue; - int bestScore = -1; - - for (uint i = 0; i < deviceCount; i++) - { - VkPhysicalDeviceProperties props; - Vk.vkGetPhysicalDeviceProperties(devices[i], &props); - - byte* pName = props.deviceName; - var nameLen = 0; - while (nameLen < 256 && pName[nameLen] != 0) nameLen++; - var deviceName = Encoding.UTF8.GetString(pName, nameLen); - - var score = (int)props.deviceType; - if (props.deviceType == VkPhysicalDeviceType.DiscreteGpu) score = 1000; - else if (props.deviceType == VkPhysicalDeviceType.IntegratedGpu) score = 500; - - if (!FindQueueFamilies(devices[i], out var graphicsFamily, out var presentFamily)) - continue; - - if (score > bestScore) - { - bestScore = score; - bestDevice = devices[i]; - bestGraphicsFamily = graphicsFamily; - bestPresentFamily = presentFamily; - Console.WriteLine($"[Vulkan] Selected GPU: {deviceName} (score {score})"); - } - } - - if (bestScore < 0) - throw new InvalidOperationException("No suitable GPU found with graphics + present queues."); - - PhysicalDevice = bestDevice; - GraphicsFamily = bestGraphicsFamily; - PresentFamily = bestPresentFamily; - - VkPhysicalDeviceMemoryProperties memProps; - Vk.vkGetPhysicalDeviceMemoryProperties(PhysicalDevice, &memProps); - MemoryProperties = memProps; - } - - private unsafe bool FindQueueFamilies(VkPhysicalDevice device, out uint graphicsFamily, out uint presentFamily) - { - graphicsFamily = uint.MaxValue; - presentFamily = uint.MaxValue; - + var enumInstanceProps = VulkanNative.GetExport("vkEnumerateInstanceLayerProperties"); uint count = 0; - Vk.vkGetPhysicalDeviceQueueFamilyProperties(device, &count, null); + enumInstanceProps(&count, null); if (count == 0) return false; - var props = new VkQueueFamilyProperties[count]; - fixed (VkQueueFamilyProperties* pProps = props) - { - Vk.vkGetPhysicalDeviceQueueFamilyProperties(device, &count, pProps); - } + var props = stackalloc VkLayerProperties[(int)count]; + enumInstanceProps(&count, props); + var targetBytes = VulkanString.ToUtf8Terminated(layerName); for (uint i = 0; i < count; i++) { - if ((props[i].queueFlags & VkQueueFlags.Graphics) != 0) - graphicsFamily = i; - - uint supported = 0; - Vk.vkGetPhysicalDeviceSurfaceSupportKHR(device, i, Surface, &supported); - if (supported != 0) - presentFamily = i; - - if (graphicsFamily != uint.MaxValue && presentFamily != uint.MaxValue) + var namePtr = (byte*)props[(int)i].layerName; + if (CompareUtf8(namePtr, targetBytes)) return true; } - return false; } - private unsafe void CreateLogicalDevice() + private static bool CompareUtf8(byte* a, byte[] b) { - var queueIndices = new HashSet { GraphicsFamily, PresentFamily }; - var queueCreateInfos = new VkDeviceQueueCreateInfo[queueIndices.Count]; - var priorities = new float[] { 1.0f }; - - fixed (float* pPrio = priorities) + for (int i = 0; i < b.Length; i++) { - var idx = 0; - foreach (var qfi in queueIndices) - { - queueCreateInfos[idx] = new VkDeviceQueueCreateInfo - { - sType = VkStructureType.DeviceQueueCreateInfo, - pNext = null, - flags = 0, - queueFamilyIndex = qfi, - queueCount = 1, - pQueuePriorities = pPrio - }; - idx++; - } - - var extNameBytes = System.Text.Encoding.UTF8.GetBytes("VK_KHR_swapchain\0"); - var extNamePtr = (byte*)Marshal.AllocHGlobal(extNameBytes.Length); - Marshal.Copy(extNameBytes, 0, (nint)extNamePtr, extNameBytes.Length); - - fixed (VkDeviceQueueCreateInfo* pQueueCreateInfos = queueCreateInfos) - { - byte* features = stackalloc byte[228]; - VkDeviceCreateInfo createInfo; - createInfo.sType = VkStructureType.DeviceCreateInfo; - createInfo.pNext = null; - createInfo.flags = 0; - createInfo.queueCreateInfoCount = (uint)queueCreateInfos.Length; - createInfo.pQueueCreateInfos = pQueueCreateInfos; - createInfo.enabledLayerCount = 0; - createInfo.ppEnabledLayerNames = null; - createInfo.enabledExtensionCount = 1; - createInfo.ppEnabledExtensionNames = &extNamePtr; - createInfo.pEnabledFeatures = features; - - VkDevice device; - var result = Vk.vkCreateDevice(PhysicalDevice, &createInfo, null, &device); - Vk.CheckResult(result, "vkCreateDevice"); - Device = device; - } - - Marshal.FreeHGlobal((nint)extNamePtr); + if (a == null || a[i] != b[i]) return false; + if (b[i] == 0) return true; } + 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(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(); + + 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("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(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); - fixed (VkQueue* pGfxQueue = &GraphicsQueue) + fixed (VkQueue* queuePtr = &GraphicsQueue) { - Vk.vkGetDeviceQueue(Device, GraphicsFamily, 0, pGfxQueue); - } - fixed (VkQueue* pPresentQueue = &PresentQueue) - { - Vk.vkGetDeviceQueue(Device, PresentFamily, 0, pPresentQueue); + Vk.vkGetDeviceQueue(Device, GraphicsQueueFamilyIndex, 0, queuePtr); } - 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++) { - var memType = GetMemoryType(i); - if ((typeFilter & (1u << (int)i)) != 0 && (memType.propertyFlags & properties) == properties) - return i; + if ((memoryTypeBits & (1u << (int)i)) != 0) + { + var flags = GetMemoryTypeFlags(i); + if ((flags & desiredFlags) == desiredFlags) + return i; + } } - - throw new InvalidOperationException($"Failed to find memory type with filter={typeFilter:X} props={properties}"); + throw new InvalidOperationException($"No memory type found for flags {desiredFlags}"); } - 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, - 1 => MemoryProperties.memoryTypes1, - 2 => MemoryProperties.memoryTypes2, - 3 => MemoryProperties.memoryTypes3, - 4 => MemoryProperties.memoryTypes4, - 5 => MemoryProperties.memoryTypes5, - 6 => MemoryProperties.memoryTypes6, - 7 => MemoryProperties.memoryTypes7, - 8 => MemoryProperties.memoryTypes8, - 9 => MemoryProperties.memoryTypes9, - 10 => MemoryProperties.memoryTypes10, - 11 => MemoryProperties.memoryTypes11, - 12 => MemoryProperties.memoryTypes12, - 13 => MemoryProperties.memoryTypes13, - 14 => MemoryProperties.memoryTypes14, - 15 => MemoryProperties.memoryTypes15, - _ => throw new IndexOutOfRangeException() - }; + var memTypes = &p->memoryTypes0; + return memTypes[index].propertyFlags; + } } - public unsafe void Dispose() + private static byte** AllocStringArray(IList 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; _disposed = true; - if (Device.Value != 0) + if (Device.Handle != 0) { - Vk.vkQueueWaitIdle(GraphicsQueue); - Vk.vkDestroyDevice(Device, null); + Vk.vkDeviceWaitIdle(Device); + Vk.vkDestroyDevice(Device, 0); } - if (Surface.Value != 0) - Vk.vkDestroySurfaceKHR(Instance, Surface, null); - if (Instance.Value != 0) - Vk.vkDestroyInstance(Instance, null); + + if (_debugMessenger.Handle != 0 && Vk.vkDestroyDebugUtilsMessengerEXT != null) + Vk.vkDestroyDebugUtilsMessengerEXT(Instance, _debugMessenger, 0); + + if (Surface.Handle != 0) + Vk.vkDestroySurfaceKHR(Instance, Surface, 0); + + if (Instance.Handle != 0) + Vk.vkDestroyInstance(Instance, 0); } } diff --git a/src/Engine.Graphics.Vulkan/VulkanEnums.cs b/src/Engine.Graphics.Vulkan/VulkanEnums.cs new file mode 100644 index 0000000..47cfa7a --- /dev/null +++ b/src/Engine.Graphics.Vulkan/VulkanEnums.cs @@ -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, +} diff --git a/src/Engine.Graphics.Vulkan/VulkanFrameResources.cs b/src/Engine.Graphics.Vulkan/VulkanFrameResources.cs new file mode 100644 index 0000000..6b0006c --- /dev/null +++ b/src/Engine.Graphics.Vulkan/VulkanFrameResources.cs @@ -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(); + + 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); + } +} diff --git a/src/Engine.Graphics.Vulkan/VulkanHandles.cs b/src/Engine.Graphics.Vulkan/VulkanHandles.cs new file mode 100644 index 0000000..dd29647 --- /dev/null +++ b/src/Engine.Graphics.Vulkan/VulkanHandles.cs @@ -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 }; } diff --git a/src/Engine.Graphics.Vulkan/VulkanImGui.cs b/src/Engine.Graphics.Vulkan/VulkanImGui.cs deleted file mode 100644 index 373d6ab..0000000 --- a/src/Engine.Graphics.Vulkan/VulkanImGui.cs +++ /dev/null @@ -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(), - 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()); - 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(); - 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; -} diff --git a/src/Engine.Graphics.Vulkan/VulkanNative.cs b/src/Engine.Graphics.Vulkan/VulkanNative.cs index 3089a92..b25772b 100644 --- a/src/Engine.Graphics.Vulkan/VulkanNative.cs +++ b/src/Engine.Graphics.Vulkan/VulkanNative.cs @@ -2,98 +2,57 @@ using System.Runtime.InteropServices; namespace Engine.Graphics.Vulkan; -public static unsafe partial class VulkanNative +internal static unsafe class VulkanNative { - private const string VulkanLib = "vulkan-1.dll"; - private const string VulkanLibLinux = "libvulkan.so.1"; + private static readonly nint _handle; + public static readonly nint NullHandle = 0; - private static nint _libHandle; - - public static nint LoadLibrary() + static VulkanNative() { - 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()) - _libHandle = NativeLibrary.Load(VulkanLib); - else - _libHandle = NativeLibrary.Load(VulkanLibLinux); - - if (_libHandle == 0) - throw new InvalidOperationException("Failed to load Vulkan library."); - - return _libHandle; + vkGetInstanceProcAddr = GetExport("vkGetInstanceProcAddr"); } - public static void* GetInstanceProcAddr(VkInstance instance, byte* pName) + public static T GetExport(string name) where T : Delegate { - LoadLibrary(); - var ptr = NativeLibrary.GetExport(_libHandle, "vkGetInstanceProcAddr"); - var func = Marshal.GetDelegateForFunctionPointer(ptr); - return func(instance, pName); + if (!NativeLibrary.TryGetExport(_handle, name, out var address)) + throw new EntryPointNotFoundException($"Vulkan export not found: {name}"); + return Marshal.GetDelegateForFunctionPointer(address); } - public static void* GetDeviceProcAddr(VkDevice device, byte* pName) + public static nint GetExportPointer(string name) { - var ptr = NativeLibrary.GetExport(_libHandle, "vkGetDeviceProcAddr"); - var func = Marshal.GetDelegateForFunctionPointer(ptr); - return func(device, pName); + NativeLibrary.TryGetExport(_handle, name, out var address); + return address; } - public static T LoadInstanceFunction(VkInstance instance, string name) where T : Delegate - { - var nameBytes = System.Text.Encoding.UTF8.GetBytes(name + "\0"); - fixed (byte* pName = nameBytes) - { - var addr = GetInstanceProcAddr(instance, pName); - if (addr == null) - throw new InvalidOperationException($"Failed to load Vulkan instance function: {name}"); - return Marshal.GetDelegateForFunctionPointer((nint)addr); - } - } + public static PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr; - public static T LoadDeviceFunction(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"); - fixed (byte* pName = nameBytes) - { - var addr = GetDeviceProcAddr(device, pName); - if (addr == null) - throw new InvalidOperationException($"Failed to load Vulkan device function: {name}"); - return Marshal.GetDelegateForFunctionPointer((nint)addr); - } + return System.Text.Encoding.UTF8.GetBytes(s + '\0'); } - - [UnmanagedFunctionPointer(CallingConvention.Winapi)] - public delegate void* PFN_vkGetInstanceProcAddr(VkInstance instance, byte* pName); - - [UnmanagedFunctionPointer(CallingConvention.Winapi)] - public delegate void* PFN_vkGetDeviceProcAddr(VkDevice device, byte* pName); - - [LibraryImport("vulkan-1.dll", EntryPoint = "vkGetInstanceProcAddr", StringMarshalling = StringMarshalling.Utf8)] - public static partial void* vkGetInstanceProcAddr_Win(VkInstance instance, string pName); - [DllImport("libvulkan.so.1", EntryPoint = "vkGetInstanceProcAddr", CharSet = CharSet.Ansi)] - public static extern void* vkGetInstanceProcAddr_Linux(VkInstance instance, string pName); - - public static VkResult vkEnumerateInstanceExtensionProperties(byte* pLayerName, uint* pPropertyCount, VkExtensionProperties* pProperties) + public static byte* AllocUtf8(string s) { - LoadLibrary(); - var ptr = NativeLibrary.GetExport(_libHandle, "vkEnumerateInstanceExtensionProperties"); - var func = Marshal.GetDelegateForFunctionPointer(ptr); - return func(pLayerName, pPropertyCount, pProperties); + var bytes = ToUtf8Terminated(s); + var ptr = (byte*)Marshal.AllocHGlobal(bytes.Length); + Marshal.Copy(bytes, 0, (nint)ptr, bytes.Length); + 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(); - var ptr = NativeLibrary.GetExport(_libHandle, "vkEnumerateInstanceLayerProperties"); - var func = Marshal.GetDelegateForFunctionPointer(ptr); - return func(pPropertyCount, pProperties); + if (ptr != null) + Marshal.FreeHGlobal((nint)ptr); } - - [UnmanagedFunctionPointer(CallingConvention.Winapi)] - public delegate VkResult PFN_vkEnumerateInstanceLayerProperties(uint* pPropertyCount, VkLayerProperties* pProperties); } diff --git a/src/Engine.Graphics.Vulkan/VulkanPipeline.cs b/src/Engine.Graphics.Vulkan/VulkanPipeline.cs index 333b2ad..17ab47c 100644 --- a/src/Engine.Graphics.Vulkan/VulkanPipeline.cs +++ b/src/Engine.Graphics.Vulkan/VulkanPipeline.cs @@ -1,279 +1,207 @@ using System.Runtime.InteropServices; -using System.Text; +using Engine.Core; namespace Engine.Graphics.Vulkan; -public sealed unsafe class VulkanPipeline : IDisposable +internal sealed unsafe class VulkanPipeline : IDisposable { public VkPipelineLayout PipelineLayout; public VkPipeline Pipeline; - public VkDescriptorSetLayout DescriptorSetLayout; - public VkShaderModule VertexShader; - public VkShaderModule FragmentShader; + public VkShaderModule VertModule; + public VkShaderModule FragModule; - private readonly VulkanContext _ctx; + private readonly VkDevice _device; private bool _disposed; - public const int PushConstantSize = 144; - public const int FrameUboSize = 16 + 16 + 16 * 4 * 8; - - public VulkanPipeline(VulkanContext ctx, VkRenderPass renderPass) + public VulkanPipeline(VkDevice device, VkFormat colorFormat, byte[] vertSpv, byte[] fragSpv) { - _ctx = ctx; - Create(renderPass); - } + _device = device; + VertModule = CreateShaderModule(vertSpv); + FragModule = CreateShaderModule(fragSpv); - private unsafe void Create(VkRenderPass renderPass) - { - Console.WriteLine("[Vulkan] Loading shaders..."); - VertexShader = CreateShaderModule("Shaders/vertex.spv"); - FragmentShader = CreateShaderModule("Shaders/fragment.spv"); - Console.WriteLine("[Vulkan] Shaders loaded."); - - var mainName = Vk.AllocUtf8("main"); - - var stages = new VkPipelineShaderStageCreateInfo[2]; - stages[0] = new VkPipelineShaderStageCreateInfo + fixed (byte* pName = "main\0"u8) { - sType = VkStructureType.PipelineShaderStageCreateInfo, - pNext = null, - flags = 0, - stage = VkShaderStageFlags.Vertex, - module = VertexShader, - pName = mainName, - pSpecializationInfo = null - }; - stages[1] = new VkPipelineShaderStageCreateInfo - { - sType = VkStructureType.PipelineShaderStageCreateInfo, - pNext = null, - flags = 0, - stage = VkShaderStageFlags.Fragment, - module = FragmentShader, - pName = mainName, - pSpecializationInfo = null - }; + var stages = stackalloc VkPipelineShaderStageCreateInfo[2]; + stages[0] = new VkPipelineShaderStageCreateInfo + { + sType = VkStructureType.PipelineShaderStageCreateInfo, + stage = VkShaderStageFlags.Vertex, + module = VertModule, + pName = pName, + }; + stages[1] = new VkPipelineShaderStageCreateInfo + { + sType = VkStructureType.PipelineShaderStageCreateInfo, + stage = VkShaderStageFlags.Fragment, + module = FragModule, + pName = pName, + }; - var bindingDesc = new VkVertexInputBindingDescription - { - binding = 0, - stride = 36, - inputRate = 0 - }; + var bindings = stackalloc VkVertexInputBindingDescription[1]; + bindings[0] = new VkVertexInputBindingDescription + { + binding = 0, + stride = (uint)sizeof(Vertex), + inputRate = VkVertexInputRate.Vertex, + }; - var attrDescs = stackalloc VkVertexInputAttributeDescription[3]; - attrDescs[0] = new VkVertexInputAttributeDescription { location = 0, binding = 0, format = VkFormat.R32G32B32Sfloat, offset = 0 }; - attrDescs[1] = new VkVertexInputAttributeDescription { location = 1, binding = 0, format = VkFormat.R32G32B32Sfloat, offset = 12 }; - attrDescs[2] = new VkVertexInputAttributeDescription { location = 2, binding = 0, format = VkFormat.R32G32B32Sfloat, offset = 24 }; + var attributes = stackalloc VkVertexInputAttributeDescription[3]; + attributes[0] = new VkVertexInputAttributeDescription + { + 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; - vertexInputState.sType = VkStructureType.PipelineVertexInputStateCreateInfo; - vertexInputState.pNext = null; - vertexInputState.flags = 0; - vertexInputState.vertexBindingDescriptionCount = 1; - vertexInputState.pVertexBindingDescriptions = &bindingDesc; - vertexInputState.vertexAttributeDescriptionCount = 3; - vertexInputState.pVertexAttributeDescriptions = attrDescs; + var vertexInputState = new VkPipelineVertexInputStateCreateInfo + { + sType = VkStructureType.PipelineVertexInputStateCreateInfo, + vertexBindingDescriptionCount = 1, + pVertexBindingDescriptions = bindings, + vertexAttributeDescriptionCount = 3, + pVertexAttributeDescriptions = attributes, + }; - var inputAssemblyState = new VkPipelineInputAssemblyStateCreateInfo - { - sType = VkStructureType.PipelineInputAssemblyStateCreateInfo, - pNext = null, - flags = 0, - topology = VkPrimitiveTopology.TriangleList, - primitiveRestartEnable = 0 - }; + var inputAssemblyState = new VkPipelineInputAssemblyStateCreateInfo + { + sType = VkStructureType.PipelineInputAssemblyStateCreateInfo, + topology = VkPrimitiveTopology.TriangleList, + primitiveRestartEnable = VkBool32.False, + }; - var viewport = new VkViewport { x = 0, y = 0, width = 1280, height = 720, minDepth = 0, maxDepth = 1 }; - var scissor = new VkRect2D { offset = new VkOffset2D { x = 0, y = 0 }, extent = new VkExtent2D { width = 1280, height = 720 } }; + var viewportState = new VkPipelineViewportStateCreateInfo + { + sType = VkStructureType.PipelineViewportStateCreateInfo, + viewportCount = 1, + pViewports = null, + scissorCount = 1, + pScissors = null, + }; - VkPipelineViewportStateCreateInfo viewportState; - viewportState.sType = VkStructureType.PipelineViewportStateCreateInfo; - viewportState.pNext = null; - viewportState.flags = 0; - viewportState.viewportCount = 1; - viewportState.pViewports = &viewport; - viewportState.scissorCount = 1; - viewportState.pScissors = &scissor; + var rasterizationState = new VkPipelineRasterizationStateCreateInfo + { + sType = VkStructureType.PipelineRasterizationStateCreateInfo, + depthClampEnable = VkBool32.False, + rasterizerDiscardEnable = VkBool32.False, + polygonMode = VkPolygonMode.Fill, + cullMode = VkCullModeFlags.None, + frontFace = VkFrontFace.CounterClockwise, + depthBiasEnable = VkBool32.False, + lineWidth = 1.0f, + }; - var rasterizationState = new VkPipelineRasterizationStateCreateInfo - { - sType = VkStructureType.PipelineRasterizationStateCreateInfo, - pNext = null, - flags = 0, - depthClampEnable = 0, - rasterizerDiscardEnable = 0, - polygonMode = VkPolygonMode.Fill, - cullMode = VkCullModeFlags.None, - frontFace = VkFrontFace.Clockwise, - depthBiasEnable = 0, - depthBiasConstantFactor = 0, - depthBiasClamp = 0, - depthBiasSlopeFactor = 0, - lineWidth = 1.0f - }; + var multisampleState = new VkPipelineMultisampleStateCreateInfo + { + sType = VkStructureType.PipelineMultisampleStateCreateInfo, + rasterizationSamples = VkSampleCountFlags.Count1, + sampleShadingEnable = VkBool32.False, + }; - var multisampleState = new VkPipelineMultisampleStateCreateInfo - { - sType = VkStructureType.PipelineMultisampleStateCreateInfo, - pNext = null, - flags = 0, - rasterizationSamples = VkSampleCountFlags.One, - sampleShadingEnable = 0, - minSampleShading = 0, - pSampleMask = null, - alphaToCoverageEnable = 0, - alphaToOneEnable = 0 - }; + var blendAttachment = new VkPipelineColorBlendAttachmentState + { + blendEnable = VkBool32.False, + colorWriteMask = VkColorComponentFlags.R | VkColorComponentFlags.G | VkColorComponentFlags.B | VkColorComponentFlags.A, + }; - var depthStencilState = new VkPipelineDepthStencilStateCreateInfo - { - sType = VkStructureType.PipelineDepthStencilStateCreateInfo, - pNext = null, - flags = 0, - depthTestEnable = 1, - depthWriteEnable = 1, - depthCompareOp = VkCompareOp.Less, - depthBoundsTestEnable = 0, - stencilTestEnable = 0, - front = new VkStencilOpState(), - back = new VkStencilOpState(), - minDepthBounds = 0, - maxDepthBounds = 1 - }; + var colorBlendState = new VkPipelineColorBlendStateCreateInfo + { + sType = VkStructureType.PipelineColorBlendStateCreateInfo, + logicOpEnable = VkBool32.False, + attachmentCount = 1, + pAttachments = &blendAttachment, + }; - var blendAttachment = new VkPipelineColorBlendAttachmentState - { - blendEnable = 0, - srcColorBlendFactor = VkBlendFactor.One, - dstColorBlendFactor = VkBlendFactor.Zero, - colorBlendOp = VkBlendOp.Add, - srcAlphaBlendFactor = VkBlendFactor.One, - dstAlphaBlendFactor = VkBlendFactor.Zero, - alphaBlendOp = VkBlendOp.Add, - colorWriteMask = VkColorComponentFlags.R | VkColorComponentFlags.G | VkColorComponentFlags.B | VkColorComponentFlags.A - }; + var dynamicStates = stackalloc VkDynamicState[2]; + dynamicStates[0] = VkDynamicState.Viewport; + dynamicStates[1] = VkDynamicState.Scissor; - VkPipelineColorBlendStateCreateInfo colorBlendState = default; - colorBlendState.sType = VkStructureType.PipelineColorBlendStateCreateInfo; - colorBlendState.pNext = null; - colorBlendState.flags = 0; - colorBlendState.logicOpEnable = 0; - colorBlendState.logicOp = 0; - colorBlendState.attachmentCount = 1; - colorBlendState.pAttachments = &blendAttachment; + var dynamicState = new VkPipelineDynamicStateCreateInfo + { + sType = VkStructureType.PipelineDynamicStateCreateInfo, + dynamicStateCount = 2, + pDynamicStates = dynamicStates, + }; - var dynamicStates = stackalloc VkDynamicState[2]; - dynamicStates[0] = VkDynamicState.Viewport; - dynamicStates[1] = VkDynamicState.Scissor; + var layoutInfo = new VkPipelineLayoutCreateInfo + { + sType = VkStructureType.PipelineLayoutCreateInfo, + setLayoutCount = 0, + pushConstantRangeCount = 0, + }; - VkPipelineDynamicStateCreateInfo dynamicState = default; - dynamicState.sType = VkStructureType.PipelineDynamicStateCreateInfo; - dynamicState.dynamicStateCount = 2; - dynamicState.pDynamicStates = dynamicStates; + fixed (VkPipelineLayout* layoutPtr = &PipelineLayout) + { + var result = Vk.vkCreatePipelineLayout(_device, &layoutInfo, 0, layoutPtr); + if (result != VkResult.Success) + throw new InvalidOperationException($"vkCreatePipelineLayout failed: {result}"); + } - var uboBinding = new VkDescriptorSetLayoutBinding - { - binding = 0, - descriptorType = VkDescriptorType.UniformBuffer, - descriptorCount = 1, - stageFlags = VkShaderStageFlags.Vertex | VkShaderStageFlags.Fragment, - pImmutableSamplers = null - }; + var renderingInfo = new VkPipelineRenderingCreateInfo + { + sType = VkStructureType.PipelineRenderingCreateInfo, + colorAttachmentCount = 1, + pColorAttachmentFormats = &colorFormat, + }; - Console.WriteLine("[Vulkan] Creating descriptor set layout..."); - VkDescriptorSetLayoutCreateInfo dsLayoutInfo; - dsLayoutInfo.sType = VkStructureType.DescriptorSetLayoutCreateInfo; - dsLayoutInfo.pNext = null; - dsLayoutInfo.flags = 0; - dsLayoutInfo.bindingCount = 1; - dsLayoutInfo.pBindings = &uboBinding; + var pipelineInfo = new VkGraphicsPipelineCreateInfo + { + sType = VkStructureType.GraphicsPipelineCreateInfo, + pNext = (nint)(&renderingInfo), + stageCount = 2, + pStages = stages, + 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; - VkResult result = Vk.vkCreateDescriptorSetLayout(_ctx.Device, &dsLayoutInfo, null, &dsLayout); - Vk.CheckResult(result, "vkCreateDescriptorSetLayout"); - DescriptorSetLayout = dsLayout; - Console.WriteLine("[Vulkan] Descriptor set layout created."); - - var pushConstantRange = new VkPushConstantRange - { - stageFlags = VkShaderStageFlags.Vertex | VkShaderStageFlags.Fragment, - offset = 0, - size = PushConstantSize - }; - - Console.WriteLine("[Vulkan] Creating pipeline layout..."); - VkPipelineLayoutCreateInfo layoutInfo; - layoutInfo.sType = VkStructureType.PipelineLayoutCreateInfo; - layoutInfo.pNext = null; - layoutInfo.flags = 0; - layoutInfo.setLayoutCount = 1; - layoutInfo.pSetLayouts = &dsLayout; - layoutInfo.pushConstantRangeCount = 1; - layoutInfo.pPushConstantRanges = &pushConstantRange; - - VkPipelineLayout pipeLayout; - result = Vk.vkCreatePipelineLayout(_ctx.Device, &layoutInfo, null, &pipeLayout); - Vk.CheckResult(result, "vkCreatePipelineLayout"); - PipelineLayout = pipeLayout; - Console.WriteLine("[Vulkan] Pipeline layout created."); - - Console.WriteLine("[Vulkan] Creating graphics pipeline..."); - fixed (VkPipelineShaderStageCreateInfo* pStages = stages) - { - VkGraphicsPipelineCreateInfo pipelineInfo; - pipelineInfo.sType = VkStructureType.GraphicsPipelineCreateInfo; - pipelineInfo.pNext = null; - pipelineInfo.flags = 0; - pipelineInfo.stageCount = 2; - pipelineInfo.pStages = pStages; - pipelineInfo.pVertexInputState = &vertexInputState; - pipelineInfo.pInputAssemblyState = &inputAssemblyState; - pipelineInfo.pTessellationState = null; - pipelineInfo.pViewportState = &viewportState; - pipelineInfo.pRasterizationState = &rasterizationState; - pipelineInfo.pMultisampleState = &multisampleState; - pipelineInfo.pDepthStencilState = &depthStencilState; - pipelineInfo.pColorBlendState = &colorBlendState; - pipelineInfo.pDynamicState = &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; + fixed (VkPipeline* pipePtr = &Pipeline) + { + var result = Vk.vkCreateGraphicsPipelines(_device, 0, 1, &pipelineInfo, 0, pipePtr); + if (result != VkResult.Success) + throw new InvalidOperationException($"vkCreateGraphicsPipelines failed: {result}"); + } } - Vk.FreeUtf8(mainName); - - Console.WriteLine("[Vulkan] Graphics pipeline created."); + Console.WriteLine("[Vulkan] Graphics pipeline created (dynamic rendering)"); } - private VkShaderModule CreateShaderModule(string path) + private VkShaderModule CreateShaderModule(byte[] spv) { - var fullPath = Path.Combine(AppContext.BaseDirectory, path); - if (!File.Exists(fullPath)) - throw new FileNotFoundException($"SPIR-V shader not found: {fullPath}"); - - var code = File.ReadAllBytes(fullPath); - var codeSize = (ulong)code.Length; - - fixed (byte* pCode = code) + fixed (byte* pCode = spv) { - VkShaderModuleCreateInfo createInfo; - createInfo.sType = VkStructureType.ShaderModuleCreateInfo; - createInfo.pNext = null; - createInfo.flags = 0; - createInfo.codeSize = codeSize; - createInfo.pCode = (uint*)pCode; + var info = new VkShaderModuleCreateInfo + { + sType = VkStructureType.ShaderModuleCreateInfo, + codeSize = (nuint)spv.Length, + pCode = (uint*)pCode, + }; - VkShaderModule module; - var result = Vk.vkCreateShaderModule(_ctx.Device, &createInfo, null, &module); - Vk.CheckResult(result, $"vkCreateShaderModule ({path})"); + var module = VkShaderModule.Null; + var result = Vk.vkCreateShaderModule(_device, &info, 0, &module); + if (result != VkResult.Success) + throw new InvalidOperationException($"vkCreateShaderModule failed: {result}"); return module; } } @@ -283,10 +211,9 @@ public sealed unsafe class VulkanPipeline : IDisposable if (_disposed) return; _disposed = true; - if (Pipeline.Value != 0) Vk.vkDestroyPipeline(_ctx.Device, Pipeline, null); - if (PipelineLayout.Value != 0) Vk.vkDestroyPipelineLayout(_ctx.Device, PipelineLayout, null); - if (DescriptorSetLayout.Value != 0) Vk.vkDestroyDescriptorSetLayout(_ctx.Device, DescriptorSetLayout, null); - if (VertexShader.Value != 0) Vk.vkDestroyShaderModule(_ctx.Device, VertexShader, null); - if (FragmentShader.Value != 0) Vk.vkDestroyShaderModule(_ctx.Device, FragmentShader, null); + if (Pipeline.Handle != 0) Vk.vkDestroyPipeline(_device, Pipeline, 0); + if (PipelineLayout.Handle != 0) Vk.vkDestroyPipelineLayout(_device, PipelineLayout, 0); + if (FragModule.Handle != 0) Vk.vkDestroyShaderModule(_device, FragModule, 0); + if (VertModule.Handle != 0) Vk.vkDestroyShaderModule(_device, VertModule, 0); } } diff --git a/src/Engine.Graphics.Vulkan/VulkanRenderContext.cs b/src/Engine.Graphics.Vulkan/VulkanRenderContext.cs index e4c8737..6a5b3e5 100644 --- a/src/Engine.Graphics.Vulkan/VulkanRenderContext.cs +++ b/src/Engine.Graphics.Vulkan/VulkanRenderContext.cs @@ -1,40 +1,48 @@ using Engine.Core; -using Engine.Graphics; 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 VulkanPipeline _pipeline; - private readonly VulkanRenderer _renderer; - private readonly Sdl3Window _window; + private readonly IWindow _window; + private bool _disposed; 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); - _context = new VulkanContext(_window, enableValidation); - _swapchain = new VulkanSwapchain(_context, width, height); - _pipeline = new VulkanPipeline(_context, _swapchain.RenderPass); - _renderer = new VulkanRenderer(_context, _swapchain, _pipeline); + _window = window; + _ctx = new VulkanContext(window, enableValidation); + + var surfaceFormat = new VkSurfaceFormatKHR + { + 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) { - _renderer.OnResize(); + _swapchain.Recreate(width, height); } public void Dispose() { - _renderer.Dispose(); - _pipeline.Dispose(); - _swapchain.Dispose(); - _context.Dispose(); - _window.Dispose(); + if (_disposed) return; + _disposed = true; + + _swapchain?.Dispose(); + _ctx?.Dispose(); + _window?.Dispose(); } } diff --git a/src/Engine.Graphics.Vulkan/VulkanRenderer.cs b/src/Engine.Graphics.Vulkan/VulkanRenderer.cs index 66209a8..7909b67 100644 --- a/src/Engine.Graphics.Vulkan/VulkanRenderer.cs +++ b/src/Engine.Graphics.Vulkan/VulkanRenderer.cs @@ -1,704 +1,289 @@ using System.Numerics; -using System.Runtime.InteropServices; using Engine.Core; using Engine.Core.Components; -using Engine.Graphics; using Flecs.NET.Core; +using System.Runtime.InteropServices; namespace Engine.Graphics.Vulkan; -public sealed unsafe class VulkanRenderer : IRenderer, IScreenshotProvider +internal sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreenshotProvider { - public readonly VulkanContext _ctx; - public readonly VulkanSwapchain _swapchain; + private readonly VulkanContext _ctx; + private readonly VulkanSwapchain _swapchain; private readonly VulkanPipeline _pipeline; - public VulkanImGui? ImGuiLayer; - - private VkCommandPool _commandPool; - private VkCommandBuffer[] _commandBuffers = Array.Empty(); - private VkSemaphore[] _imageAvailableSemaphores = Array.Empty(); - private VkSemaphore[] _renderFinishedSemaphores = Array.Empty(); - private VkFence[] _inFlightFences = Array.Empty(); - - private VkDescriptorPool _descriptorPool; - private VkDescriptorSet[] _descriptorSets = Array.Empty(); - private VulkanBuffer[] _uboBuffers = Array.Empty(); - - private const int MaxFramesInFlight = 2; - private int _currentFrame; - private uint _imageIndex; - private bool _resized; - - private readonly Dictionary _meshCache = new(); + private readonly VulkanFrameResources _frameResources; + private readonly VulkanVertexBuffer _vertexBuffer; + private int _frameIndex; + private bool _disposed; private bool _screenshotRequested; - private string _screenshotPath = ""; - private TaskCompletionSource? _screenshotTcs; - - private VulkanBuffer? _screenshotStaging; - private uint _screenshotImageIndex; - private bool _screenshotPending; + private string? _screenshotPath; public bool IsScreenshotRequested => _screenshotRequested; public IScreenshotProvider ScreenshotProvider => this; - public VulkanRenderer(VulkanContext ctx, VulkanSwapchain swapchain, VulkanPipeline pipeline) + public VulkanRenderer(VulkanContext ctx, VulkanSwapchain swapchain) { _ctx = ctx; _swapchain = swapchain; - _pipeline = pipeline; - CreateCommandPool(); - CreateSyncObjects(); - CreateDescriptorPool(); - CreateDescriptorSets(); - CreateCommandBuffers(); - } + var vertSpv = LoadShader("Shaders/triangle.vert.spv"); + var fragSpv = LoadShader("Shaders/triangle.frag.spv"); - private unsafe void CreateCommandPool() - { - VkCommandPoolCreateInfo createInfo; - createInfo.sType = VkStructureType.CommandPoolCreateInfo; - createInfo.pNext = null; - createInfo.flags = 0x00000002; - createInfo.queueFamilyIndex = _ctx.GraphicsFamily; + _pipeline = new VulkanPipeline(ctx.Device, swapchain.Format, vertSpv, fragSpv); - VkCommandPool pool; - VkResult result = Vk.vkCreateCommandPool(_ctx.Device, &createInfo, null, &pool); - Vk.CheckResult(result, "vkCreateCommandPool"); - _commandPool = pool; - } + _frameResources = new VulkanFrameResources(ctx.Device, ctx.GraphicsQueueFamilyIndex, swapchain.ImageCount); - private unsafe void CreateSyncObjects() - { - _imageAvailableSemaphores = new VkSemaphore[MaxFramesInFlight]; - _renderFinishedSemaphores = new VkSemaphore[MaxFramesInFlight]; - _inFlightFences = new VkFence[MaxFramesInFlight]; - - for (var i = 0; i < MaxFramesInFlight; i++) + var vertices = new Vertex[] { - VkSemaphoreCreateInfo semInfo; - semInfo.sType = VkStructureType.SemaphoreCreateInfo; - semInfo.pNext = null; - semInfo.flags = 0; - - VkSemaphore sem1, sem2; - Vk.CheckResult(Vk.vkCreateSemaphore(_ctx.Device, &semInfo, null, &sem1), "vkCreateSemaphore"); - Vk.CheckResult(Vk.vkCreateSemaphore(_ctx.Device, &semInfo, null, &sem2), "vkCreateSemaphore"); - _imageAvailableSemaphores[i] = sem1; - _renderFinishedSemaphores[i] = sem2; - - VkFenceCreateInfo fenceInfo; - fenceInfo.sType = VkStructureType.FenceCreateInfo; - fenceInfo.pNext = null; - fenceInfo.flags = VkFenceCreateFlags.Signaled; - - VkFence fence; - Vk.CheckResult(Vk.vkCreateFence(_ctx.Device, &fenceInfo, null, &fence), "vkCreateFence"); - _inFlightFences[i] = fence; - } - } - - private unsafe void CreateDescriptorPool() - { - var poolSize = new VkDescriptorPoolSize - { - type = VkDescriptorType.UniformBuffer, - descriptorCount = (uint)MaxFramesInFlight + new(new Vector3( 0.0f, -0.5f, 0.0f), new Vector3(1.0f, 0.0f, 0.0f), new Vector3(0, 0, 1)), + new(new Vector3( 0.5f, 0.5f, 0.0f), new Vector3(0.0f, 1.0f, 0.0f), new Vector3(0, 0, 1)), + new(new Vector3(-0.5f, 0.5f, 0.0f), new Vector3(0.0f, 0.0f, 1.0f), new Vector3(0, 0, 1)), }; - VkDescriptorPoolCreateInfo createInfo; - createInfo.sType = VkStructureType.DescriptorPoolCreateInfo; - createInfo.pNext = null; - createInfo.flags = VkDescriptorPoolCreateFlags.FreeDescriptorSet; - createInfo.maxSets = (uint)MaxFramesInFlight; - createInfo.poolSizeCount = 1; - createInfo.pPoolSizes = &poolSize; - - VkDescriptorPool pool; - Vk.CheckResult(Vk.vkCreateDescriptorPool(_ctx.Device, &createInfo, null, &pool), "vkCreateDescriptorPool"); - _descriptorPool = pool; + _vertexBuffer = new VulkanVertexBuffer(ctx.Device, ctx.PhysicalDevice, + _frameResources.CommandPool, ctx.GraphicsQueue, ctx, vertices); } - private unsafe void CreateDescriptorSets() + public void RenderWorld(World world) { - _uboBuffers = new VulkanBuffer[MaxFramesInFlight]; - _descriptorSets = new VkDescriptorSet[MaxFramesInFlight]; - - var layouts = new VkDescriptorSetLayout[MaxFramesInFlight]; - for (var i = 0; i < MaxFramesInFlight; i++) - layouts[i] = _pipeline.DescriptorSetLayout; - - VkDescriptorSetAllocateInfo allocInfo; - allocInfo.sType = VkStructureType.DescriptorSetAllocateInfo; - allocInfo.pNext = null; - allocInfo.descriptorPool = _descriptorPool; - allocInfo.descriptorSetCount = (uint)MaxFramesInFlight; - - fixed (VkDescriptorSetLayout* pLayouts = layouts) - { - allocInfo.pSetLayouts = pLayouts; - - fixed (VkDescriptorSet* pSets = _descriptorSets) - { - Vk.CheckResult(Vk.vkAllocateDescriptorSets(_ctx.Device, &allocInfo, pSets), "vkAllocateDescriptorSets"); - } - } - - for (var i = 0; i < MaxFramesInFlight; i++) - { - _uboBuffers[i] = new VulkanBuffer(_ctx, (ulong)VulkanPipeline.FrameUboSize, - VkBufferUsageFlags.UniformBuffer, - VkMemoryPropertyFlags.HostVisible | VkMemoryPropertyFlags.HostCoherent); - - var bufferInfo = new VkDescriptorBufferInfo - { - buffer = _uboBuffers[i].Buffer, - offset = 0, - range = (ulong)VulkanPipeline.FrameUboSize - }; - - VkWriteDescriptorSet writeInfo; - writeInfo.sType = VkStructureType.WriteDescriptorSet; - writeInfo.pNext = null; - writeInfo.dstSet = _descriptorSets[i]; - writeInfo.dstBinding = 0; - writeInfo.dstArrayElement = 0; - writeInfo.descriptorCount = 1; - writeInfo.descriptorType = VkDescriptorType.UniformBuffer; - writeInfo.pImageInfo = null; - writeInfo.pBufferInfo = &bufferInfo; - writeInfo.pTexelBufferView = null; - - Vk.vkUpdateDescriptorSets(_ctx.Device, 1, &writeInfo, 0, null); - } + Render(); } - private unsafe void CreateCommandBuffers() + private void Render() { - _commandBuffers = new VkCommandBuffer[MaxFramesInFlight]; + _frameResources.WaitFrame(_frameIndex); - VkCommandBufferAllocateInfo allocInfo; - allocInfo.sType = VkStructureType.CommandBufferAllocateInfo; - allocInfo.pNext = null; - allocInfo.commandPool = _commandPool; - allocInfo.level = VkCommandBufferLevel.Primary; - allocInfo.commandBufferCount = (uint)MaxFramesInFlight; + uint imageIndex; + var acquireResult = Vk.vkAcquireNextImageKHR(_ctx.Device, _swapchain.Swapchain, + ulong.MaxValue, _frameResources.AcquireSemaphores[_frameIndex], VkFence.Null, &imageIndex); - fixed (VkCommandBuffer* pCmds = _commandBuffers) + if (acquireResult == VkResult.ErrorOutOfDateKHR || acquireResult == VkResult.SuboptimalKHR) { - Vk.CheckResult(Vk.vkAllocateCommandBuffers(_ctx.Device, &allocInfo, pCmds), "vkAllocateCommandBuffers"); - } - } - - public unsafe void RenderWorld(World world) - { - VkFence fence = _inFlightFences[_currentFrame]; - Vk.CheckResult(Vk.vkWaitForFences(_ctx.Device, 1, &fence, 1, ulong.MaxValue), "vkWaitForFences"); - - 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(); + _swapchain.Recreate(_ctx.SurfaceExtent.Width == 0 ? 1280 : (int)_ctx.SurfaceExtent.Width, + _ctx.SurfaceExtent.Height == 0 ? 720 : (int)_ctx.SurfaceExtent.Height); + Render(); return; } - Vk.CheckResult(acquireResult, "vkAcquireNextImageKHR"); - _imageIndex = imageIndex; + if (acquireResult != VkResult.Success) + throw new InvalidOperationException($"vkAcquireNextImageKHR failed: {acquireResult}"); - VkFence fenceReset = _inFlightFences[_currentFrame]; - Vk.CheckResult(Vk.vkResetFences(_ctx.Device, 1, &fenceReset), "vkResetFences"); + var cmd = _frameResources.CommandBuffers[_frameIndex]; + Vk.vkResetCommandBuffer(cmd, 0); - var cmd = _commandBuffers[_currentFrame]; - Vk.CheckResult(Vk.vkResetCommandBuffer(cmd, 0), "vkResetCommandBuffer"); - - UpdateFrameUbo(world); - - RecordCommandBuffer(cmd, imageIndex, world); - - VkSemaphore renderDoneSem = _renderFinishedSemaphores[_currentFrame]; - VkSemaphore imgAvailSem2 = _imageAvailableSemaphores[_currentFrame]; - VkFence submitFence = _inFlightFences[_currentFrame]; - - VkSubmitInfo submitInfo; - submitInfo.sType = VkStructureType.SubmitInfo; - submitInfo.pNext = null; - submitInfo.waitSemaphoreCount = 1; - submitInfo.pWaitSemaphores = &imgAvailSem2; - - var waitStage = (ulong)VkPipelineStageFlags.ColorAttachmentOutput; - submitInfo.pWaitDstStageMask = &waitStage; - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &cmd; - submitInfo.signalSemaphoreCount = 1; - submitInfo.pSignalSemaphores = &renderDoneSem; - - Vk.CheckResult(Vk.vkQueueSubmit(_ctx.GraphicsQueue, 1, &submitInfo, submitFence), "vkQueueSubmit"); - - VkSemaphore renderDoneSem2 = _renderFinishedSemaphores[_currentFrame]; - VkSwapchainKHR swapchain = _swapchain.Swapchain; - - VkPresentInfoKHR presentInfo; - presentInfo.sType = VkStructureType.PresentInfoKHR; - presentInfo.pNext = null; - presentInfo.waitSemaphoreCount = 1; - presentInfo.pWaitSemaphores = &renderDoneSem2; - presentInfo.swapchainCount = 1; - presentInfo.pSwapchains = &swapchain; - presentInfo.pImageIndices = &imageIndex; - presentInfo.pResults = null; - - var presentResult = Vk.vkQueuePresentKHR(_ctx.GraphicsQueue, &presentInfo); - if (presentResult == VkResult.ErrorOutOfDateKHR || presentResult == VkResult.SuboptimalKHR || _resized) + var beginInfo = new VkCommandBufferBeginInfo { - _resized = false; - _swapchain.Recreate(_swapchain.Extent.width, _swapchain.Extent.height); - RecreateCommandBuffers(); - } - else + sType = VkStructureType.CommandBufferBeginInfo, + flags = VkCommandBufferUsageFlags.OneTimeSubmit, + }; + Vk.vkBeginCommandBuffer(cmd, &beginInfo); + + TransitionImageLayout(cmd, _swapchain.Images[imageIndex], + VkImageLayout.Undefined, VkImageLayout.ColorAttachmentOptimal, + 0, 0, + 0x400, 0x100); + + var clearValue = new VkClearValue { - Vk.CheckResult(presentResult, "vkQueuePresentKHR"); - } - - _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()); - _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 + Color = new VkClearColorValue { Float0 = 0.02f, Float1 = 0.02f, Float2 = 0.02f, Float3 = 1.0f }, }; - var clearValues = new VkClearValue[2]; - clearValues[0] = new VkClearValue { color = new VkClearColorValue { r = 0.05f, g = 0.05f, b = 0.08f, a = 1.0f } }; - clearValues[1] = new VkClearValue { depthStencil = new VkClearDepthStencilValue { depth = 1.0f, stencil = 0 } }; - - fixed (VkClearValue* pClear = clearValues) + var colorAttachment = new VkRenderingAttachmentInfo { - rpBegin.clearValueCount = 2; - rpBegin.pClearValues = pClear; + sType = VkStructureType.RenderingAttachmentInfo, + 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 { - x = 0, y = 0, - width = _swapchain.Extent.width, - height = _swapchain.Extent.height, - minDepth = 0, maxDepth = 1 + X = 0, Y = 0, + Width = _swapchain.Extent.Width, + Height = _swapchain.Extent.Height, + MinDepth = 0, MaxDepth = 1, }; Vk.vkCmdSetViewport(cmd, 0, 1, &viewport); var scissor = new VkRect2D { - offset = new VkOffset2D { x = 0, y = 0 }, - extent = _swapchain.Extent + Offset = new VkOffset2D { X = 0, Y = 0 }, + Extent = _swapchain.Extent, }; Vk.vkCmdSetScissor(cmd, 0, 1, &scissor); - var ds = _descriptorSets[_currentFrame]; - Vk.vkCmdBindDescriptorSets(cmd, 0, _pipeline.PipelineLayout, 0, 1, &ds, 0, null); + var bufferHandle = _vertexBuffer.Buffer; + 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(); - if (!_meshCache.TryGetValue(meshKey, out var meshBuffers)) - { - if (mesh.Vertices.Length == 0 || mesh.Indices.Length == 0) return; + sType = VkStructureType.SemaphoreSubmitInfo, + semaphore = _frameResources.AcquireSemaphores[_frameIndex], + stageMask = 0x400, + }; - var vertexBuffer = VulkanBuffer.CreateDeviceLocal(_ctx, _commandPool, mesh.Vertices, VkBufferUsageFlags.VertexBuffer); - var indexBuffer = VulkanBuffer.CreateDeviceLocal(_ctx, _commandPool, mesh.Indices, VkBufferUsageFlags.IndexBuffer); - - meshBuffers = (vertexBuffer, indexBuffer, (uint)mesh.Indices.Length); - _meshCache[meshKey] = meshBuffers; - } - - var model = transform.GetMatrix(); - var view = Matrix4x4.Identity; - var proj = Matrix4x4.Identity; - - world.Each((Entity camE, ref Camera cam) => - { - view = cam.GetViewMatrix(); - proj = cam.GetProjectionMatrix(); - }); - - var mvp = 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) + var cmdInfo = new VkCommandBufferSubmitInfo { - 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); - - 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"); + _frameIndex = (_frameIndex + 1) % VulkanFrameResources.MaxFramesInFlight; } - 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; - allocInfo.sType = VkStructureType.CommandBufferAllocateInfo; - allocInfo.pNext = null; - allocInfo.commandPool = _commandPool; - allocInfo.level = VkCommandBufferLevel.Primary; - allocInfo.commandBufferCount = (uint)_commandBuffers.Length; - - fixed (VkCommandBuffer* pCmds = _commandBuffers) + var depInfo = new VkDependencyInfo { - 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; + _screenshotPath = path; } public Task CaptureAsync(string outputPath) { - _screenshotPath = outputPath; - _screenshotTcs = new TaskCompletionSource(); - _screenshotRequested = true; - return _screenshotTcs.Task; + _screenshotRequested = false; + return Task.FromResult(Array.Empty()); } - 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) - { - mesh.vertex.Dispose(); - mesh.index.Dispose(); - } - _meshCache.Clear(); + public void Dispose() + { + if (_disposed) return; + _disposed = true; - foreach (var ubo in _uboBuffers) - ubo?.Dispose(); + Vk.vkDeviceWaitIdle(_ctx.Device); - if (_descriptorPool.Value != 0) - Vk.vkDestroyDescriptorPool(_ctx.Device, _descriptorPool, null); - - fixed (VkCommandBuffer* pCmds = _commandBuffers) - { - if (_commandPool.Value != 0 && _commandBuffers.Length > 0) - Vk.vkFreeCommandBuffers(_ctx.Device, _commandPool, (uint)_commandBuffers.Length, pCmds); - } - if (_commandPool.Value != 0) Vk.vkDestroyCommandPool(_ctx.Device, _commandPool, null); - - for (var i = 0; i < MaxFramesInFlight; i++) - { - if (_imageAvailableSemaphores[i].Value != 0) Vk.vkDestroySemaphore(_ctx.Device, _imageAvailableSemaphores[i], null); - if (_renderFinishedSemaphores[i].Value != 0) Vk.vkDestroySemaphore(_ctx.Device, _renderFinishedSemaphores[i], null); - if (_inFlightFences[i].Value != 0) Vk.vkDestroyFence(_ctx.Device, _inFlightFences[i], null); - } + _vertexBuffer?.Dispose(); + _frameResources?.Dispose(); + _pipeline?.Dispose(); } } diff --git a/src/Engine.Graphics.Vulkan/VulkanStructs.cs b/src/Engine.Graphics.Vulkan/VulkanStructs.cs new file mode 100644 index 0000000..2d351ff --- /dev/null +++ b/src/Engine.Graphics.Vulkan/VulkanStructs.cs @@ -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; +} diff --git a/src/Engine.Graphics.Vulkan/VulkanSwapchain.cs b/src/Engine.Graphics.Vulkan/VulkanSwapchain.cs index 74357b0..95c99b8 100644 --- a/src/Engine.Graphics.Vulkan/VulkanSwapchain.cs +++ b/src/Engine.Graphics.Vulkan/VulkanSwapchain.cs @@ -2,353 +2,166 @@ using System.Runtime.InteropServices; namespace Engine.Graphics.Vulkan; -public sealed unsafe class VulkanSwapchain : IDisposable +internal sealed unsafe class VulkanSwapchain : IDisposable { public VkSwapchainKHR Swapchain; - public VkImage[] SwapchainImages = Array.Empty(); - public VkImageView[] SwapchainImageViews = Array.Empty(); - public VkFormat ImageFormat; - public VkFormat DepthFormat; + public VkImage[] Images = Array.Empty(); + public VkImageView[] ImageViews = Array.Empty(); + public VkFormat Format; public VkExtent2D Extent; - public VkRenderPass RenderPass; - public VkFramebuffer[] Framebuffers = Array.Empty(); + public uint ImageCount; - public VkImage DepthImage; - public VkDeviceMemory DepthImageMemory; - public VkImageView DepthImageView; - - private readonly VulkanContext _ctx; + private readonly VkDevice _device; + private readonly VkPhysicalDevice _physicalDevice; + private readonly VkSurfaceKHR _surface; + private readonly VkSurfaceFormatKHR _surfaceFormat; 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); } - public unsafe void Create(int width, int height) + private void Create(int width, int height) { - VkSurfaceCapabilitiesKHR caps; - Vk.vkGetPhysicalDeviceSurfaceCapabilitiesKHR(_ctx.PhysicalDevice, _ctx.Surface, &caps); - - uint formatCount = 0; - Vk.vkGetPhysicalDeviceSurfaceFormatsKHR(_ctx.PhysicalDevice, _ctx.Surface, &formatCount, null); - var formats = new VkSurfaceFormatKHR[formatCount]; - fixed (VkSurfaceFormatKHR* pFormats = formats) - { - Vk.vkGetPhysicalDeviceSurfaceFormatsKHR(_ctx.PhysicalDevice, _ctx.Surface, &formatCount, pFormats); - } - - ImageFormat = formats[0].format; - foreach (var f in formats) - { - if (f.format == VkFormat.B8G8R8A8Srgb && f.colorSpace == VkColorSpaceKHR.SrgbNonlinear) - { - ImageFormat = f.format; - break; - } - } - if (ImageFormat == VkFormat.Undefined) - ImageFormat = VkFormat.B8G8R8A8Unorm; + var caps = new VkSurfaceCapabilitiesKHR(); + Vk.vkGetPhysicalDeviceSurfaceCapabilitiesKHR(_physicalDevice, _surface, &caps); 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.height = Math.Clamp(height, caps.minImageExtent.height, caps.maxImageExtent.height); + Extent.Width = (uint)width; + 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; if (caps.maxImageCount > 0 && imageCount > caps.maxImageCount) imageCount = caps.maxImageCount; - VkSwapchainCreateInfoKHR createInfo; - createInfo.sType = VkStructureType.SwapchainCreateInfoKHR; - createInfo.pNext = null; - createInfo.flags = 0; - createInfo.surface = _ctx.Surface; - createInfo.minImageCount = imageCount; - createInfo.imageFormat = ImageFormat; - createInfo.imageColorSpace = VkColorSpaceKHR.SrgbNonlinear; - createInfo.imageExtent = Extent; - createInfo.imageArrayLayers = 1; - createInfo.imageUsage = VkImageUsageFlags.ColorAttachment | VkImageUsageFlags.TransferSrc; - createInfo.imageSharingMode = VkSharingMode.Exclusive; - createInfo.queueFamilyIndexCount = 0; - createInfo.pQueueFamilyIndices = null; - createInfo.preTransform = caps.currentTransform; - createInfo.compositeAlpha = 0x00000001; - createInfo.presentMode = VkPresentModeKHR.Fifo; - createInfo.clipped = 1; - createInfo.oldSwapchain = default; + uint presentModeCount = 0; + Vk.vkGetPhysicalDeviceSurfacePresentModesKHR(_physicalDevice, _surface, &presentModeCount, null); + var presentModes = stackalloc VkPresentModeKHR[(int)presentModeCount]; + Vk.vkGetPhysicalDeviceSurfacePresentModesKHR(_physicalDevice, _surface, &presentModeCount, presentModes); - VkSwapchainKHR swapchain; - VkResult result = Vk.vkCreateSwapchainKHR(_ctx.Device, &createInfo, null, &swapchain); - Vk.CheckResult(result, "vkCreateSwapchainKHR"); - Swapchain = swapchain; - - uint actualCount = 0; - Vk.vkGetSwapchainImagesKHR(_ctx.Device, Swapchain, &actualCount, null); - SwapchainImages = new VkImage[actualCount]; - fixed (VkImage* pImages = SwapchainImages) + var presentMode = VkPresentModeKHR.Fifo; + for (uint i = 0; i < presentModeCount; i++) { - Vk.vkGetSwapchainImagesKHR(_ctx.Device, Swapchain, &actualCount, pImages); - } - - SwapchainImageViews = new VkImageView[actualCount]; - for (uint i = 0; i < actualCount; i++) - { - VkImageViewCreateInfo viewInfo; - viewInfo.sType = VkStructureType.ImageViewCreateInfo; - viewInfo.pNext = null; - viewInfo.flags = 0; - viewInfo.image = SwapchainImages[i]; - viewInfo.viewType = VkImageViewType._2D; - viewInfo.format = ImageFormat; - viewInfo.components = new VkComponentMapping { r = 0, g = 0, b = 0, a = 0 }; - viewInfo.subresourceRange = new VkImageSubresourceRange + if (presentModes[(int)i] == VkPresentModeKHR.Mailbox) { - aspectMask = VkImageAspectFlags.Color, - baseMipLevel = 0, - levelCount = 1, - baseArrayLayer = 0, - layerCount = 1 - }; - - VkImageView view; - result = Vk.vkCreateImageView(_ctx.Device, &viewInfo, null, &view); - Vk.CheckResult(result, "vkCreateImageView (swapchain)"); - SwapchainImageViews[i] = view; - } - - DepthFormat = VkFormat.D32Sfloat; - CreateDepthImage(); - - CreateRenderPass(); - CreateFramebuffers(); - - Console.WriteLine($"[Vulkan] Swapchain: {actualCount} images, {Extent.width}x{Extent.height}, format {ImageFormat}"); - } - - private unsafe void CreateDepthImage() - { - VkImageCreateInfo imageInfo; - imageInfo.sType = VkStructureType.ImageCreateInfo; - imageInfo.pNext = null; - imageInfo.flags = 0; - imageInfo.imageType = VkImageType._2D; - imageInfo.format = DepthFormat; - imageInfo.extent = new VkExtent3D { width = Extent.width, height = Extent.height, depth = 1 }; - imageInfo.mipLevels = 1; - imageInfo.arrayLayers = 1; - imageInfo.samples = VkSampleCountFlags.One; - imageInfo.tiling = 0; - imageInfo.usage = VkImageUsageFlags.DepthStencilAttachment; - imageInfo.sharingMode = VkSharingMode.Exclusive; - imageInfo.queueFamilyIndexCount = 0; - imageInfo.pQueueFamilyIndices = null; - imageInfo.initialLayout = 0; - - VkImage depthImage; - VkResult result = Vk.vkCreateImage(_ctx.Device, &imageInfo, null, &depthImage); - Vk.CheckResult(result, "vkCreateImage (depth)"); - DepthImage = depthImage; - - VkMemoryRequirements2 memReq; - Vk.vkGetImageMemoryRequirements(_ctx.Device, DepthImage, &memReq); - - VkMemoryAllocateInfo allocInfo; - allocInfo.sType = VkStructureType.MemoryAllocateInfo; - allocInfo.pNext = null; - allocInfo.allocationSize = memReq.size; - allocInfo.memoryTypeIndex = _ctx.FindMemoryType(memReq.memoryTypeBits, VkMemoryPropertyFlags.DeviceLocal); - - VkDeviceMemory depthMem; - result = Vk.vkAllocateMemory(_ctx.Device, &allocInfo, null, &depthMem); - Vk.CheckResult(result, "vkAllocateMemory (depth)"); - DepthImageMemory = depthMem; - - result = Vk.vkBindImageMemory(_ctx.Device, DepthImage, DepthImageMemory, 0); - Vk.CheckResult(result, "vkBindImageMemory (depth)"); - - VkImageViewCreateInfo viewInfo; - viewInfo.sType = VkStructureType.ImageViewCreateInfo; - viewInfo.pNext = null; - viewInfo.flags = 0; - viewInfo.image = DepthImage; - viewInfo.viewType = VkImageViewType._2D; - viewInfo.format = DepthFormat; - viewInfo.components = new VkComponentMapping { r = 0, g = 0, b = 0, a = 0 }; - viewInfo.subresourceRange = new VkImageSubresourceRange - { - aspectMask = VkImageAspectFlags.Depth, - baseMipLevel = 0, - levelCount = 1, - baseArrayLayer = 0, - layerCount = 1 - }; - - VkImageView depthView; - result = Vk.vkCreateImageView(_ctx.Device, &viewInfo, null, &depthView); - Vk.CheckResult(result, "vkCreateImageView (depth)"); - DepthImageView = depthView; - } - - private unsafe void CreateRenderPass() - { - var attachments = new VkAttachmentDescription[2]; - attachments[0] = new VkAttachmentDescription - { - flags = 0, - format = ImageFormat, - samples = (uint)VkSampleCountFlags.One, - loadOp = VkAttachmentLoadOp.Clear, - storeOp = VkAttachmentStoreOp.Store, - stencilLoadOp = VkAttachmentLoadOp.DontCare, - stencilStoreOp = VkAttachmentStoreOp.DontCare, - initialLayout = VkImageLayout.Undefined, - finalLayout = VkImageLayout.PresentSrcKHR - }; - attachments[1] = new VkAttachmentDescription - { - flags = 0, - format = DepthFormat, - samples = (uint)VkSampleCountFlags.One, - loadOp = VkAttachmentLoadOp.Clear, - storeOp = VkAttachmentStoreOp.DontCare, - stencilLoadOp = VkAttachmentLoadOp.DontCare, - stencilStoreOp = VkAttachmentStoreOp.DontCare, - initialLayout = VkImageLayout.Undefined, - finalLayout = VkImageLayout.DepthStencilAttachmentOptimal - }; - - var colorRef = new VkAttachmentReference { attachment = 0, layout = VkImageLayout.ColorAttachmentOptimal }; - var depthRef = new VkAttachmentReference { attachment = 1, layout = VkImageLayout.DepthStencilAttachmentOptimal }; - - VkSubpassDescription subpass; - subpass.flags = 0; - subpass.pipelineBindPoint = 0; - subpass.inputAttachmentCount = 0; - subpass.pInputAttachments = null; - subpass.colorAttachmentCount = 1; - subpass.pColorAttachments = &colorRef; - subpass.pResolveAttachments = null; - subpass.pDepthStencilAttachment = &depthRef; - subpass.preserveAttachmentCount = 0; - subpass.pPreserveAttachments = null; - - var dependencies = new VkSubpassDependency[2]; - dependencies[0] = new VkSubpassDependency - { - srcSubpass = ~0u, - dstSubpass = 0, - srcStageMask = VkPipelineStageFlags.ColorAttachmentOutput | VkPipelineStageFlags.EarlyFragmentTests, - dstStageMask = VkPipelineStageFlags.ColorAttachmentOutput | VkPipelineStageFlags.EarlyFragmentTests, - srcAccessMask = 0, - dstAccessMask = VkAccessFlags.ColorAttachmentWrite | VkAccessFlags.DepthStencilAttachmentWrite, - dependencyFlags = 0, - viewOffset = 0 - }; - dependencies[1] = new VkSubpassDependency - { - srcSubpass = 0, - dstSubpass = ~0u, - srcStageMask = VkPipelineStageFlags.ColorAttachmentOutput | VkPipelineStageFlags.EarlyFragmentTests, - dstStageMask = VkPipelineStageFlags.BottomOfPipe, - srcAccessMask = VkAccessFlags.ColorAttachmentWrite | VkAccessFlags.DepthStencilAttachmentWrite, - dstAccessMask = 0, - dependencyFlags = 0, - viewOffset = 0 - }; - - fixed (VkAttachmentDescription* pAttachments = attachments) - fixed (VkSubpassDependency* pDeps = dependencies) - { - VkRenderPassCreateInfo createInfo; - createInfo.sType = VkStructureType.RenderPassCreateInfo; - createInfo.pNext = null; - createInfo.flags = 0; - createInfo.attachmentCount = 2; - createInfo.pAttachments = pAttachments; - createInfo.subpassCount = 1; - createInfo.pSubpasses = &subpass; - createInfo.dependencyCount = 2; - createInfo.pDependencies = pDeps; - - VkRenderPass renderPass; - VkResult result = Vk.vkCreateRenderPass(_ctx.Device, &createInfo, null, &renderPass); - Vk.CheckResult(result, "vkCreateRenderPass"); - RenderPass = renderPass; - } - } - - private unsafe void CreateFramebuffers() - { - Framebuffers = new VkFramebuffer[SwapchainImageViews.Length]; - - for (uint i = 0; i < SwapchainImageViews.Length; i++) - { - var attachments = new VkImageView[] { SwapchainImageViews[i], DepthImageView }; - - fixed (VkImageView* pAttachments = attachments) - { - VkFramebufferCreateInfo createInfo; - createInfo.sType = VkStructureType.FramebufferCreateInfo; - createInfo.pNext = null; - createInfo.flags = 0; - createInfo.renderPass = RenderPass; - createInfo.attachmentCount = 2; - createInfo.pAttachments = pAttachments; - createInfo.width = (uint)Extent.width; - createInfo.height = (uint)Extent.height; - createInfo.layers = 1; - - VkFramebuffer fb; - VkResult result = Vk.vkCreateFramebuffer(_ctx.Device, &createInfo, null, &fb); - Vk.CheckResult(result, "vkCreateFramebuffer"); - Framebuffers[i] = fb; + presentMode = VkPresentModeKHR.Mailbox; + break; } } + + 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) { - Vk.vkQueueWaitIdle(_ctx.GraphicsQueue); - - CleanupSwapchain(); + Vk.vkDeviceWaitIdle(_device); + Cleanup(); Create(width, height); } - private void CleanupSwapchain() + private void Cleanup() { - foreach (var fb in Framebuffers) - if (fb.Value != 0) Vk.vkDestroyFramebuffer(_ctx.Device, fb, null); + for (int i = 0; i < ImageViews.Length; i++) + { + if (ImageViews[i].Handle != 0) + Vk.vkDestroyImageView(_device, ImageViews[i], 0); + } + ImageViews = Array.Empty(); + Images = Array.Empty(); - if (DepthImageView.Value != 0) Vk.vkDestroyImageView(_ctx.Device, DepthImageView, null); - if (DepthImage.Value != 0) Vk.vkDestroyImage(_ctx.Device, DepthImage, null); - if (DepthImageMemory.Value != 0) Vk.vkFreeMemory(_ctx.Device, DepthImageMemory, null); - - foreach (var iv in SwapchainImageViews) - if (iv.Value != 0) Vk.vkDestroyImageView(_ctx.Device, iv, null); - - if (Swapchain.Value != 0) Vk.vkDestroySwapchainKHR(_ctx.Device, Swapchain, null); + if (Swapchain.Handle != 0) + { + Vk.vkDestroySwapchainKHR(_device, Swapchain, 0); + Swapchain = VkSwapchainKHR.Null; + } } public void Dispose() { if (_disposed) return; _disposed = true; - - CleanupSwapchain(); - if (RenderPass.Value != 0) Vk.vkDestroyRenderPass(_ctx.Device, RenderPass, null); + Cleanup(); } } - -[StructLayout(LayoutKind.Sequential)] -internal struct VkMemoryRequirements2 -{ - public ulong size; - public ulong alignment; - public uint memoryTypeBits; - public uint _pad; -} diff --git a/src/Engine.Graphics.Vulkan/VulkanTypes.cs b/src/Engine.Graphics.Vulkan/VulkanTypes.cs deleted file mode 100644 index e2faaa2..0000000 --- a/src/Engine.Graphics.Vulkan/VulkanTypes.cs +++ /dev/null @@ -1,1467 +0,0 @@ -using System.Runtime.InteropServices; - -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, - ErrorFeatureNotPresent = -8, - ErrorIncompatibleDriver = -9, - ErrorTooManyObjects = -10, - ErrorFormatNotSupported = -11, - 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, - MappedMemoryRange = 6, - BindSparseInfo = 7, - FenceCreateInfo = 8, - SemaphoreCreateInfo = 9, - EventCreateInfo = 10, - QueryPoolCreateInfo = 11, - BufferCreateInfo = 12, - BufferViewCreateInfo = 13, - ImageCreateInfo = 14, - ImageViewCreateInfo = 15, - ShaderModuleCreateInfo = 16, - PipelineCacheCreateInfo = 17, - PipelineShaderStageCreateInfo = 18, - PipelineVertexInputStateCreateInfo = 19, - PipelineInputAssemblyStateCreateInfo = 20, - PipelineTessellationStateCreateInfo = 21, - PipelineViewportStateCreateInfo = 22, - PipelineRasterizationStateCreateInfo = 23, - PipelineMultisampleStateCreateInfo = 24, - PipelineDepthStencilStateCreateInfo = 25, - PipelineColorBlendStateCreateInfo = 26, - PipelineDynamicStateCreateInfo = 27, - GraphicsPipelineCreateInfo = 28, - ComputePipelineCreateInfo = 29, - PipelineLayoutCreateInfo = 30, - SamplerCreateInfo = 31, - DescriptorSetLayoutCreateInfo = 32, - DescriptorPoolCreateInfo = 33, - DescriptorSetAllocateInfo = 34, - WriteDescriptorSet = 35, - CopyDescriptorSet = 36, - FramebufferCreateInfo = 37, - RenderPassCreateInfo = 38, - CommandPoolCreateInfo = 39, - CommandBufferAllocateInfo = 40, - CommandBufferInheritanceInfo = 41, - CommandBufferBeginInfo = 42, - RenderPassBeginInfo = 43, - BufferMemoryBarrier = 44, - ImageMemoryBarrier = 45, - SwapchainCreateInfoKHR = 1000001000, - PresentInfoKHR = 1000001001, - SurfaceCapabilitiesKHR = 1000000000, - SurfaceFormatKHR = 1000000001, -} - -public enum VkFormat : int -{ - Undefined = 0, - R8G8B8A8Unorm = 37, - B8G8R8A8Unorm = 44, - R8G8B8A8Srgb = 43, - B8G8R8A8Srgb = 50, - R32G32B32A32Sfloat = 109, - R32G32B32Sfloat = 106, - R16G16B16A16Sfloat = 97, - R32G32Sfloat = 103, - D32Sfloat = 126, - D32SfloatS8Uint = 127, - D24UnormS8Uint = 129, - D16Unorm = 124, -} - -public enum VkImageUsageFlags : uint -{ - TransferSrc = 0x00000001, - TransferDst = 0x00000002, - Sampled = 0x00000004, - Storage = 0x00000008, - ColorAttachment = 0x00000010, - DepthStencilAttachment = 0x00000020, -} - -public enum VkImageAspectFlags : uint -{ - Color = 0x00000001, - Depth = 0x00000002, - Stencil = 0x00000004, -} - -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 VkPipelineStageFlags : uint -{ - TopOfPipe = 0x00000001, - DrawIndirect = 0x00000002, - VertexInput = 0x00000004, - VertexShader = 0x00000008, - FragmentShader = 0x00000010, - EarlyFragmentTests = 0x00000040, - LateFragmentTests = 0x00000080, - ColorAttachmentOutput = 0x00000100, - Transfer = 0x00001000, - BottomOfPipe = 0x00002000, - Host = 0x00004000, - AllGraphics = 0x00008000, - AllCommands = 0x00010000, -} - -public enum VkAccessFlags : uint -{ - IndirectCommandRead = 0x00000001, - IndexRead = 0x00000002, - VertexAttributeRead = 0x00000004, - UniformRead = 0x00000008, - InputAttachmentRead = 0x00000010, - ShaderRead = 0x00000020, - ShaderWrite = 0x00000040, - ColorAttachmentRead = 0x00000080, - ColorAttachmentWrite = 0x00000100, - DepthStencilAttachmentRead = 0x00000200, - DepthStencilAttachmentWrite = 0x00000400, - TransferRead = 0x00000800, - TransferWrite = 0x00001000, - HostRead = 0x00002000, - HostWrite = 0x00004000, - MemoryRead = 0x00008000, - MemoryWrite = 0x00010000, -} - -public enum VkCommandBufferLevel : int -{ - Primary = 0, - Secondary = 1, -} - -public enum VkCommandBufferUsageFlags : uint -{ - OneTimeSubmit = 0x00000001, - RenderPassContinue = 0x00000002, - SimultaneousUse = 0x00000004, -} - -public enum VkIndexType : int -{ - Uint16 = 0, - Uint32 = 1, -} - -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 VkDynamicState : int -{ - Viewport = 0, - Scissor = 1, -} - -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, -} - -public enum VkBlendOp : int -{ - Add = 0, - Subtract = 1, - ReverseSubtract = 2, -} - -public enum VkColorComponentFlags : uint -{ - R = 0x00000001, - G = 0x00000002, - B = 0x00000004, - A = 0x00000008, -} - -public enum VkCompareOp : int -{ - Never = 0, - Less = 1, - Equal = 2, - LessOrEqual = 3, - Greater = 4, - NotEqual = 5, - GreaterOrEqual = 6, - Always = 7, -} - -public enum VkDescriptorType : int -{ - Sampler = 0, - CombinedImageSampler = 1, - SampledImage = 2, - StorageImage = 3, - UniformTexelBuffer = 4, - StorageTexelBuffer = 5, - UniformBuffer = 6, - StorageBuffer = 7, - UniformBufferDynamic = 8, - StorageBufferDynamic = 9, -} - -public enum VkShaderStageFlags : uint -{ - Vertex = 0x00000001, - Fragment = 0x00000010, - AllGraphics = 0x0000001F, -} - -public enum VkBufferUsageFlags : uint -{ - TransferSrc = 0x00000001, - TransferDst = 0x00000002, - UniformTexelBuffer = 0x00000004, - StorageTexelBuffer = 0x00000008, - UniformBuffer = 0x00000010, - StorageBuffer = 0x00000020, - IndexBuffer = 0x00000040, - VertexBuffer = 0x00000080, - IndirectBuffer = 0x00000100, -} - -public enum VkMemoryPropertyFlags : uint -{ - DeviceLocal = 0x00000001, - HostVisible = 0x00000002, - HostCoherent = 0x00000004, - HostCached = 0x00000008, - LazilyAllocated = 0x00000010, -} - -public enum VkQueueFlags : uint -{ - Graphics = 0x00000001, - Compute = 0x00000002, - Transfer = 0x00000004, - SparseBinding = 0x00000008, -} - -public enum VkPhysicalDeviceType : int -{ - Other = 0, - IntegratedGpu = 1, - DiscreteGpu = 2, - VirtualGpu = 3, - Cpu = 4, -} - -public enum VkPresentModeKHR : int -{ - Immediate = 0, - Fifo = 1, - FifoRelaxed = 2, - Mailbox = 3, -} - -public enum VkColorSpaceKHR : int -{ - SrgbNonlinear = 0, -} - -public enum VkSurfaceTransformFlagsKHR : uint -{ - Identity = 0x00000001, -} - -public enum VkAttachmentLoadOp : int -{ - Load = 0, - Clear = 1, - DontCare = 2, -} - -public enum VkAttachmentStoreOp : int -{ - Store = 0, - DontCare = 1, -} - -public enum VkSampleCountFlags : uint -{ - One = 0x00000001, -} - -public enum VkSharingMode : int -{ - Exclusive = 0, - Concurrent = 1, -} - -public enum VkFenceCreateFlags : uint -{ - Signaled = 0x00000001, -} - -public enum VkDescriptorPoolCreateFlags : uint -{ - FreeDescriptorSet = 0x00000001, -} - -public enum VkSubpassContents : int -{ - Inline = 0, - SecondaryCommandBuffers = 1, -} - -public enum VkImageViewType : int -{ - _1D = 0, - _2D = 1, - _3D = 2, - Cube = 3, - _1DArray = 4, - _2DArray = 5, - CubeArray = 6, -} - -public enum VkImageType : int -{ - _1D = 0, - _2D = 1, - _3D = 2, -} - -public enum VkSamplerAddressMode : int -{ - Repeat = 0, - MirroredRepeat = 1, - ClampToEdge = 2, - ClampToBorder = 3, -} - -public enum VkFilter : int -{ - Nearest = 0, - Linear = 1, -} - -public enum VkSamplerMipmapMode : int -{ - Nearest = 0, - Linear = 1, -} - -public enum VkBorderColor : int -{ - FloatTransparentBlack = 4, -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkHandle { public ulong Value; } - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkInstance { public ulong Value; public static implicit operator ulong(VkInstance h) => h.Value; } - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkPhysicalDevice { public ulong Value; public static implicit operator ulong(VkPhysicalDevice h) => h.Value; } - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkDevice { public ulong Value; public static implicit operator ulong(VkDevice h) => h.Value; } - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkQueue { public ulong Value; public static implicit operator ulong(VkQueue h) => h.Value; } - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkCommandBuffer { public ulong Value; public static implicit operator ulong(VkCommandBuffer h) => h.Value; } - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkCommandPool { public ulong Value; public static implicit operator ulong(VkCommandPool h) => h.Value; } - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkBuffer { public ulong Value; public static implicit operator ulong(VkBuffer h) => h.Value; } - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkDeviceMemory { public ulong Value; public static implicit operator ulong(VkDeviceMemory h) => h.Value; } - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkImage { public ulong Value; public static implicit operator ulong(VkImage h) => h.Value; } - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkImageView { public ulong Value; public static implicit operator ulong(VkImageView h) => h.Value; } - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkShaderModule { public ulong Value; public static implicit operator ulong(VkShaderModule h) => h.Value; } - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkPipelineLayout { public ulong Value; public static implicit operator ulong(VkPipelineLayout h) => h.Value; } - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkPipeline { public ulong Value; public static implicit operator ulong(VkPipeline h) => h.Value; } - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkSampler { public ulong Value; public static implicit operator ulong(VkSampler h) => h.Value; } - -unsafe public struct VkRenderPass { public ulong Value; public static implicit operator ulong(VkRenderPass h) => h.Value; } - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkFramebuffer { public ulong Value; public static implicit operator ulong(VkFramebuffer h) => h.Value; } - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkDescriptorSetLayout { public ulong Value; public static implicit operator ulong(VkDescriptorSetLayout h) => h.Value; } - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkDescriptorPool { public ulong Value; public static implicit operator ulong(VkDescriptorPool h) => h.Value; } - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkDescriptorSet { public ulong Value; public static implicit operator ulong(VkDescriptorSet h) => h.Value; } - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkSemaphore { public ulong Value; public static implicit operator ulong(VkSemaphore h) => h.Value; } - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkFence { public ulong Value; public static implicit operator ulong(VkFence h) => h.Value; } - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkSwapchainKHR { public ulong Value; public static implicit operator ulong(VkSwapchainKHR h) => h.Value; } - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkSurfaceKHR { public ulong Value; public static implicit operator ulong(VkSurfaceKHR h) => h.Value; } - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkApplicationInfo -{ - public VkStructureType sType; - public void* pNext; - public byte* pApplicationName; - public uint applicationVersion; - public byte* pEngineName; - public uint engineVersion; - public uint apiVersion; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkInstanceCreateInfo -{ - public VkStructureType sType; - public void* pNext; - public uint flags; - public VkApplicationInfo* pApplicationInfo; - public uint enabledLayerCount; - public byte** ppEnabledLayerNames; - public uint enabledExtensionCount; - public byte** ppEnabledExtensionNames; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkDeviceQueueCreateInfo -{ - public VkStructureType sType; - public void* pNext; - public uint flags; - public uint queueFamilyIndex; - public uint queueCount; - public float* pQueuePriorities; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkDeviceCreateInfo -{ - public VkStructureType sType; - public void* pNext; - public uint flags; - public uint queueCreateInfoCount; - public VkDeviceQueueCreateInfo* pQueueCreateInfos; - public uint enabledLayerCount; - public byte** ppEnabledLayerNames; - public uint enabledExtensionCount; - public byte** ppEnabledExtensionNames; - public void* pEnabledFeatures; -} - -[StructLayout(LayoutKind.Sequential, Size = 228)] -public struct VkPhysicalDeviceFeatures -{ -} - -[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 uint pipelineCacheUUID[16]; - public VkPhysicalDeviceLimits limits; - public VkPhysicalDeviceSparseProperties sparseProperties; -} - -[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 ulong bufferImageGranularity; - public ulong sparseAddressSpaceSize; - public uint maxBoundDescriptorSets; - public uint maxPerStageDescriptorSamplers; - public uint maxPerStageDescriptorUniformBuffers; - public uint maxPerStageDescriptorStorageBuffers; - public uint maxPerStageDescriptorSampledImages; - public uint maxPerStageDescriptorStorageImages; - public uint maxPerStageDescriptorInputAttachments; - public uint maxPerStageResources; - public uint maxDescriptorSetSamplers; - public uint maxDescriptorSetUniformBuffers; - public uint maxDescriptorSetUniformBuffersDynamic; - public uint maxDescriptorSetStorageBuffers; - public uint maxDescriptorSetStorageBuffersDynamic; - public uint maxDescriptorSetSampledImages; - public uint maxDescriptorSetStorageImages; - public uint maxDescriptorSetInputAttachments; - public uint maxVertexInputAttributes; - public uint maxVertexInputBindings; - public uint maxVertexInputAttributeOffset; - public uint maxVertexInputBindingStride; - public uint maxVertexOutputComponents; - public uint maxTessellationGenerationLevel; - public uint maxTessellationPatchSize; - public uint maxTessellationControlPerVertexInputComponents; - public uint maxTessellationControlPerVertexOutputComponents; - public uint maxTessellationControlPerPatchOutputComponents; - public uint maxTessellationControlTotalOutputComponents; - public uint maxTessellationEvaluationInputComponents; - public uint maxTessellationEvaluationOutputComponents; - public uint maxGeometryShaderInvocations; - public uint maxGeometryInputComponents; - public uint maxGeometryOutputComponents; - public uint maxGeometryOutputVertices; - public uint maxGeometryTotalOutputComponents; - public uint maxFragmentInputComponents; - public uint maxFragmentOutputAttachments; - public uint maxFragmentDualSrcAttachments; - public uint maxFragmentCombinedOutputResources; - public uint maxComputeSharedMemorySize; - public fixed uint maxComputeWorkGroupCount[3]; - public uint maxComputeWorkGroupInvocations; - public fixed uint maxComputeWorkGroupSize[3]; - public float subPixelPrecisionBits; - public float subTexelPrecisionBits; - public float mipmapPrecisionBits; - public uint maxDrawIndexedIndexValue; - public uint maxDrawIndirectCount; - public float maxSamplerLodBias; - public float maxSamplerAnisotropy; - public uint maxViewports; - public fixed uint maxViewportDimensions[2]; - public fixed float viewportBoundsRange[2]; - public uint viewportSubPixelBits; - public uint minMemoryMapAlignment; - public uint minTexelBufferOffsetAlignment; - public uint minUniformBufferOffsetAlignment; - public uint minStorageBufferOffsetAlignment; - public int minTexelOffset; - public uint maxTexelOffset; - public int minTexelGatherOffset; - public uint maxTexelGatherOffset; - public float minInterpolationOffset; - public float maxInterpolationOffset; - public uint subPixelInterpolationOffsetBits; - public uint maxFramebufferWidth; - public uint maxFramebufferHeight; - public uint maxFramebufferLayers; - public uint framebufferColorSampleCounts; - public uint framebufferDepthSampleCounts; - public uint framebufferStencilSampleCounts; - public uint framebufferNoAttachmentsSampleCounts; - public uint maxColorAttachments; - public uint sampledImageColorSampleCounts; - public uint sampledImageIntegerSampleCounts; - public uint sampledImageDepthSampleCounts; - public uint sampledImageStencilSampleCounts; - public uint storageImageSampleCounts; - public uint maxSampleMaskWords; - public uint timestampComputeAndGraphics; - public float timestampPeriod; - public uint maxClipDistances; - public uint maxCullDistances; - public uint maxCombinedClipAndCullDistances; - public uint discreteQueuePriorities; - public fixed float pointSizeRange[2]; - public fixed float lineWidthRange[2]; - public float pointSizeGranularity; - public float lineWidthGranularity; - public uint strictLines; - public uint standardSampleLocations; - public uint optimalBufferCopyOffsetAlignment; - public uint optimalBufferCopyRowPitchAlignment; - public uint nonCoherentAtomSize; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkPhysicalDeviceSparseProperties -{ - public uint residencyStandard2DBlockShape; - public uint residencyStandard2DMultisampleBlockShape; - public uint residencyStandard3DBlockShape; - public uint residencyAlignedMipSize; - public uint residencyNonResidentStrict; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkQueueFamilyProperties -{ - public VkQueueFlags queueFlags; - public uint queueCount; - public uint timestampValidBits; - public VkExtent3D minImageTransferGranularity; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkExtent3D -{ - public int width; - public int height; - public int depth; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkExtent2D -{ - public int width; - public int height; -} - -[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 uint memoryHeapCount; - public VkMemoryHeap memoryHeaps0; - public VkMemoryHeap memoryHeaps1; - public VkMemoryHeap memoryHeaps2; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkMemoryType -{ - public VkMemoryPropertyFlags propertyFlags; - public uint heapIndex; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkMemoryHeap -{ - public ulong size; - public uint flags; - public uint _pad; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkSurfaceCapabilitiesKHR -{ - public uint minImageCount; - public uint maxImageCount; - public VkExtent2D currentExtent; - public VkExtent2D minImageExtent; - public VkExtent2D maxImageExtent; - public uint maxImageArrayLayers; - public VkSurfaceTransformFlagsKHR currentTransform; - public uint supportedTransforms; - public uint supportedCompositeAlpha; - public uint supportedUsageFlags; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkSurfaceFormatKHR -{ - public VkFormat format; - public VkColorSpaceKHR colorSpace; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkSwapchainCreateInfoKHR -{ - public VkStructureType sType; - public void* pNext; - public uint flags; - public VkSurfaceKHR surface; - public uint minImageCount; - public VkFormat imageFormat; - public VkColorSpaceKHR imageColorSpace; - public VkExtent2D imageExtent; - public uint imageArrayLayers; - public VkImageUsageFlags imageUsage; - public VkSharingMode imageSharingMode; - public uint queueFamilyIndexCount; - public uint* pQueueFamilyIndices; - public VkSurfaceTransformFlagsKHR preTransform; - public uint compositeAlpha; - public VkPresentModeKHR presentMode; - public uint clipped; - public VkSwapchainKHR oldSwapchain; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkPresentInfoKHR -{ - public VkStructureType sType; - public void* pNext; - public uint waitSemaphoreCount; - public VkSemaphore* pWaitSemaphores; - public uint swapchainCount; - public VkSwapchainKHR* pSwapchains; - public uint* pImageIndices; - public VkResult* pResults; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkSubmitInfo -{ - public VkStructureType sType; - public void* pNext; - public uint waitSemaphoreCount; - public VkSemaphore* pWaitSemaphores; - public ulong* pWaitDstStageMask; - public uint commandBufferCount; - public VkCommandBuffer* pCommandBuffers; - public uint signalSemaphoreCount; - public VkSemaphore* pSignalSemaphores; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkImageCreateInfo -{ - public VkStructureType sType; - public void* pNext; - public uint flags; - public VkImageType imageType; - public VkFormat format; - public VkExtent3D extent; - public uint mipLevels; - public uint arrayLayers; - public VkSampleCountFlags samples; - public uint tiling; - public VkImageUsageFlags usage; - public VkSharingMode sharingMode; - public uint queueFamilyIndexCount; - public uint* pQueueFamilyIndices; - public int initialLayout; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkImageViewCreateInfo -{ - public VkStructureType sType; - public void* pNext; - public uint flags; - public VkImage image; - public VkImageViewType viewType; - public VkFormat format; - public VkComponentMapping components; - public VkImageSubresourceRange subresourceRange; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkComponentMapping -{ - public int r; - public int g; - public int b; - public int a; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkImageSubresourceRange -{ - public VkImageAspectFlags aspectMask; - public uint baseMipLevel; - public uint levelCount; - public uint baseArrayLayer; - public uint layerCount; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkMemoryAllocateInfo -{ - public VkStructureType sType; - public void* pNext; - public ulong allocationSize; - public uint memoryTypeIndex; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkBufferCreateInfo -{ - public VkStructureType sType; - public void* pNext; - public uint flags; - public ulong size; - public VkBufferUsageFlags usage; - public VkSharingMode sharingMode; - public uint queueFamilyIndexCount; - public uint* pQueueFamilyIndices; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkShaderModuleCreateInfo -{ - public VkStructureType sType; - public void* pNext; - public uint flags; - public ulong codeSize; - public uint* pCode; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkPipelineShaderStageCreateInfo -{ - public VkStructureType sType; - public void* pNext; - public uint flags; - public VkShaderStageFlags stage; - public VkShaderModule module; - public byte* pName; - public void* pSpecializationInfo; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkPipelineLayoutCreateInfo -{ - public VkStructureType sType; - public void* pNext; - public uint flags; - public uint setLayoutCount; - public VkDescriptorSetLayout* pSetLayouts; - public uint pushConstantRangeCount; - public VkPushConstantRange* pPushConstantRanges; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkPushConstantRange -{ - public VkShaderStageFlags stageFlags; - public uint offset; - public uint size; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkDescriptorSetLayoutCreateInfo -{ - public VkStructureType sType; - public void* pNext; - public uint flags; - public uint bindingCount; - public VkDescriptorSetLayoutBinding* pBindings; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkDescriptorSetLayoutBinding -{ - public uint binding; - public VkDescriptorType descriptorType; - public uint descriptorCount; - public VkShaderStageFlags stageFlags; - public void* pImmutableSamplers; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkDescriptorPoolCreateInfo -{ - public VkStructureType sType; - public void* pNext; - public VkDescriptorPoolCreateFlags flags; - public uint maxSets; - public uint poolSizeCount; - public VkDescriptorPoolSize* pPoolSizes; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkDescriptorPoolSize -{ - public VkDescriptorType type; - public uint descriptorCount; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkDescriptorSetAllocateInfo -{ - public VkStructureType sType; - public void* pNext; - public VkDescriptorPool descriptorPool; - public uint descriptorSetCount; - public VkDescriptorSetLayout* pSetLayouts; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkWriteDescriptorSet -{ - public VkStructureType sType; - public void* pNext; - public VkDescriptorSet dstSet; - public uint dstBinding; - public uint dstArrayElement; - public uint descriptorCount; - public VkDescriptorType descriptorType; - public void* pImageInfo; - public void* pBufferInfo; - public void* pTexelBufferView; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkDescriptorBufferInfo -{ - public VkBuffer buffer; - public ulong offset; - public ulong range; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkVertexInputBindingDescription -{ - public uint binding; - public uint stride; - public int inputRate; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkVertexInputAttributeDescription -{ - public uint location; - public uint binding; - public VkFormat format; - public uint offset; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkPipelineVertexInputStateCreateInfo -{ - public VkStructureType sType; - public void* pNext; - public uint flags; - public uint vertexBindingDescriptionCount; - public VkVertexInputBindingDescription* pVertexBindingDescriptions; - public uint vertexAttributeDescriptionCount; - public VkVertexInputAttributeDescription* pVertexAttributeDescriptions; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkPipelineInputAssemblyStateCreateInfo -{ - public VkStructureType sType; - public void* pNext; - public uint flags; - public VkPrimitiveTopology topology; - public uint primitiveRestartEnable; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkViewport -{ - public float x; - public float y; - public float width; - public float height; - public float minDepth; - public float maxDepth; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkRect2D -{ - public VkOffset2D offset; - public VkExtent2D extent; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkOffset2D -{ - public int x; - public int y; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkPipelineViewportStateCreateInfo -{ - public VkStructureType sType; - public void* pNext; - public uint flags; - public uint viewportCount; - public VkViewport* pViewports; - public uint scissorCount; - public VkRect2D* pScissors; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkPipelineRasterizationStateCreateInfo -{ - public VkStructureType sType; - public void* pNext; - public uint flags; - public uint depthClampEnable; - public uint rasterizerDiscardEnable; - public VkPolygonMode polygonMode; - public VkCullModeFlags cullMode; - public VkFrontFace frontFace; - public uint depthBiasEnable; - public float depthBiasConstantFactor; - public float depthBiasClamp; - public float depthBiasSlopeFactor; - public float lineWidth; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkPipelineMultisampleStateCreateInfo -{ - public VkStructureType sType; - public void* pNext; - public uint flags; - public VkSampleCountFlags rasterizationSamples; - public uint sampleShadingEnable; - public float minSampleShading; - public void* pSampleMask; - public uint alphaToCoverageEnable; - public uint alphaToOneEnable; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkPipelineDepthStencilStateCreateInfo -{ - public VkStructureType sType; - public void* pNext; - public uint flags; - public uint depthTestEnable; - public uint depthWriteEnable; - public VkCompareOp depthCompareOp; - public uint depthBoundsTestEnable; - public uint stencilTestEnable; - public VkStencilOpState front; - public VkStencilOpState back; - public float minDepthBounds; - public float maxDepthBounds; -} - -[StructLayout(LayoutKind.Sequential)] -public struct VkStencilOpState -{ - public int failOp; - public int passOp; - public int depthFailOp; - public VkCompareOp compareOp; - public uint compareMask; - public uint writeMask; - public uint reference; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkPipelineColorBlendAttachmentState -{ - public uint blendEnable; - public VkBlendFactor srcColorBlendFactor; - public VkBlendFactor dstColorBlendFactor; - public VkBlendOp colorBlendOp; - public VkBlendFactor srcAlphaBlendFactor; - public VkBlendFactor dstAlphaBlendFactor; - public VkBlendOp alphaBlendOp; - public VkColorComponentFlags colorWriteMask; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkPipelineColorBlendStateCreateInfo -{ - public VkStructureType sType; - public void* pNext; - public uint flags; - public uint logicOpEnable; - public int logicOp; - public uint attachmentCount; - public VkPipelineColorBlendAttachmentState* pAttachments; - public fixed float blendConstants[4]; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkPipelineDynamicStateCreateInfo -{ - public VkStructureType sType; - public void* pNext; - public uint flags; - public uint dynamicStateCount; - public VkDynamicState* pDynamicStates; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkGraphicsPipelineCreateInfo -{ - public VkStructureType sType; - public void* pNext; - public uint flags; - public uint stageCount; - public VkPipelineShaderStageCreateInfo* pStages; - public VkPipelineVertexInputStateCreateInfo* pVertexInputState; - public VkPipelineInputAssemblyStateCreateInfo* pInputAssemblyState; - public void* pTessellationState; - public VkPipelineViewportStateCreateInfo* pViewportState; - public VkPipelineRasterizationStateCreateInfo* pRasterizationState; - public VkPipelineMultisampleStateCreateInfo* pMultisampleState; - public VkPipelineDepthStencilStateCreateInfo* pDepthStencilState; - public VkPipelineColorBlendStateCreateInfo* pColorBlendState; - public void* pDynamicState; - public VkPipelineLayout layout; - public VkRenderPass renderPass; - public uint subpass; - public VkPipeline basePipelineHandle; - public int basePipelineIndex; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkRenderPassCreateInfo -{ - public VkStructureType sType; - public void* pNext; - public uint flags; - public uint attachmentCount; - public VkAttachmentDescription* pAttachments; - public uint subpassCount; - public VkSubpassDescription* pSubpasses; - public uint dependencyCount; - public VkSubpassDependency* pDependencies; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkAttachmentDescription -{ - public uint flags; - public VkFormat format; - public uint samples; - public VkAttachmentLoadOp loadOp; - public VkAttachmentStoreOp storeOp; - public VkAttachmentLoadOp stencilLoadOp; - public VkAttachmentStoreOp stencilStoreOp; - public VkImageLayout initialLayout; - public VkImageLayout finalLayout; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkAttachmentReference -{ - public uint attachment; - public VkImageLayout layout; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkSubpassDescription -{ - public uint flags; - public int pipelineBindPoint; - public uint inputAttachmentCount; - public VkAttachmentReference* pInputAttachments; - public uint colorAttachmentCount; - public VkAttachmentReference* pColorAttachments; - public VkAttachmentReference* pResolveAttachments; - public VkAttachmentReference* pDepthStencilAttachment; - public uint preserveAttachmentCount; - public uint* pPreserveAttachments; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkSubpassDependency -{ - public uint srcSubpass; - public uint dstSubpass; - public VkPipelineStageFlags srcStageMask; - public VkPipelineStageFlags dstStageMask; - public VkAccessFlags srcAccessMask; - public VkAccessFlags dstAccessMask; - public int dependencyFlags; - public int viewOffset; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkFramebufferCreateInfo -{ - public VkStructureType sType; - public void* pNext; - public uint flags; - public VkRenderPass renderPass; - public uint attachmentCount; - public VkImageView* pAttachments; - public uint width; - public uint height; - public uint layers; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkCommandPoolCreateInfo -{ - public VkStructureType sType; - public void* pNext; - public uint flags; - public uint queueFamilyIndex; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkCommandBufferAllocateInfo -{ - public VkStructureType sType; - public void* pNext; - public VkCommandPool commandPool; - public VkCommandBufferLevel level; - public uint commandBufferCount; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkCommandBufferBeginInfo -{ - public VkStructureType sType; - public void* pNext; - public VkCommandBufferUsageFlags flags; - public void* pInheritanceInfo; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkRenderPassBeginInfo -{ - public VkStructureType sType; - public void* pNext; - public VkRenderPass renderPass; - public VkFramebuffer framebuffer; - public VkRect2D renderArea; - public uint clearValueCount; - public VkClearValue* pClearValues; -} - -[StructLayout(LayoutKind.Explicit)] -public struct VkClearValue -{ - [FieldOffset(0)] public VkClearColorValue color; - [FieldOffset(0)] public VkClearDepthStencilValue depthStencil; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkClearColorValue -{ - public float r, g, b, a; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkClearDepthStencilValue -{ - public float depth; - public uint stencil; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkBufferCopy -{ - public ulong srcOffset; - public ulong dstOffset; - public ulong size; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkBufferImageCopy -{ - public ulong bufferOffset; - public uint bufferRowLength; - public uint bufferImageHeight; - public VkImageSubresourceLayers imageSubresource; - public VkOffset3D imageOffset; - public VkExtent3D imageExtent; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkImageSubresourceLayers -{ - public VkImageAspectFlags aspectMask; - public uint mipLevel; - public uint baseArrayLayer; - public uint layerCount; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkOffset3D -{ - public int x; - public int y; - public int z; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkImageMemoryBarrier -{ - public VkStructureType sType; - public void* pNext; - public VkAccessFlags srcAccessMask; - public VkAccessFlags dstAccessMask; - public VkImageLayout oldLayout; - public VkImageLayout newLayout; - public uint srcQueueFamilyIndex; - public uint dstQueueFamilyIndex; - public VkImage image; - public VkImageSubresourceRange subresourceRange; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkBufferMemoryBarrier -{ - public VkStructureType sType; - public void* pNext; - public VkAccessFlags srcAccessMask; - public VkAccessFlags dstAccessMask; - public uint srcQueueFamilyIndex; - public uint dstQueueFamilyIndex; - public VkBuffer buffer; - public ulong offset; - public ulong size; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkSemaphoreCreateInfo -{ - public VkStructureType sType; - public void* pNext; - public uint flags; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkFenceCreateInfo -{ - public VkStructureType sType; - public void* pNext; - public VkFenceCreateFlags flags; -} - -[StructLayout(LayoutKind.Sequential, Size = 260)] -public unsafe struct VkExtensionProperties -{ - public fixed byte extensionName[256]; - public uint specVersion; -} - -[StructLayout(LayoutKind.Sequential, Size = 264)] -public unsafe struct VkLayerProperties -{ - public fixed byte layerName[256]; - public uint specVersion; - public uint implementationVersion; - public uint _pad; -} - -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkSamplerCreateInfo -{ - public VkStructureType sType; - public void* pNext; - public uint flags; - public VkFilter magFilter; - public VkFilter minFilter; - public VkSamplerMipmapMode mipmapMode; - public VkSamplerAddressMode addressModeU; - public VkSamplerAddressMode addressModeV; - public VkSamplerAddressMode addressModeW; - public float mipLodBias; - public uint anisotropyEnable; - public float maxAnisotropy; - public uint compareEnable; - public VkCompareOp compareOp; - public float minLod; - public float maxLod; - public VkBorderColor borderColor; - public uint unnormalizedCoordinates; -} diff --git a/src/Engine.Graphics.Vulkan/VulkanVertexBuffer.cs b/src/Engine.Graphics.Vulkan/VulkanVertexBuffer.cs new file mode 100644 index 0000000..7cdc961 --- /dev/null +++ b/src/Engine.Graphics.Vulkan/VulkanVertexBuffer.cs @@ -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); + } +} diff --git a/src/Engine.Graphics/Engine.Graphics.csproj b/src/Engine.Graphics/Engine.Graphics.csproj index 70c3869..b003cff 100644 --- a/src/Engine.Graphics/Engine.Graphics.csproj +++ b/src/Engine.Graphics/Engine.Graphics.csproj @@ -17,15 +17,6 @@ true - - - - - - - - - diff --git a/src/Engine.Graphics/IRenderContext.cs b/src/Engine.Graphics/IRenderContext.cs index cce7e0b..6f50c62 100644 --- a/src/Engine.Graphics/IRenderContext.cs +++ b/src/Engine.Graphics/IRenderContext.cs @@ -2,6 +2,9 @@ using Engine.Core; namespace Engine.Graphics; +/// +/// Render context created by a backend. Owns the window and can create a renderer. +/// public interface IRenderContext : IDisposable { IWindow Window { get; } diff --git a/src/Engine.Graphics/IRenderer.cs b/src/Engine.Graphics/IRenderer.cs index 28edfea..b1466b3 100644 --- a/src/Engine.Graphics/IRenderer.cs +++ b/src/Engine.Graphics/IRenderer.cs @@ -3,10 +3,14 @@ using Flecs.NET.Core; namespace Engine.Graphics; +/// +/// Backend-agnostic renderer interface. Minimal version for triangle rendering. +/// public interface IRenderer : IDisposable { void RenderWorld(World world); - void RequestScreenshot(string outputPath); + + void RequestScreenshot(string path); bool IsScreenshotRequested { get; } IScreenshotProvider ScreenshotProvider { get; } } diff --git a/src/Engine.Graphics/IScreenshotProvider.cs b/src/Engine.Graphics/IScreenshotProvider.cs new file mode 100644 index 0000000..b51288f --- /dev/null +++ b/src/Engine.Graphics/IScreenshotProvider.cs @@ -0,0 +1,12 @@ +namespace Engine.Graphics; + +/// +/// Provides access to the latest captured screenshot bytes. +/// +public interface IScreenshotProvider +{ + /// + /// Returns the path of the screenshot file if a screenshot is available; otherwise null. + /// + string? TryTakeScreenshotPath(); +} diff --git a/src/Engine.Graphics/Loaders/GltfLoader.cs b/src/Engine.Graphics/Loaders/GltfLoader.cs deleted file mode 100644 index e7fa688..0000000 --- a/src/Engine.Graphics/Loaders/GltfLoader.cs +++ /dev/null @@ -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(); - var indices = new List(); - - foreach (var scene in modelRoot.LogicalScenes) - { - foreach (var node in scene.VisualChildren) - { - var mesh = node.Mesh; - if (mesh == null) continue; - - foreach (var primitive in mesh.Primitives) - { - var posAccess = primitive.GetVertexAccessor("POSITION"); - var normAccess = primitive.GetVertexAccessor("NORMAL"); - if (posAccess == null) continue; - - var indexAccess = primitive.IndexAccessor; - var baseVertex = (uint)vertices.Count; - - for (var i = 0; i < posAccess.Count; i++) - { - var pos = posAccess.AsVector3Array()[i]; - var normal = normAccess != null - ? normAccess.AsVector3Array()[i] - : Vector3.UnitY; - - vertices.Add(new Vertex(pos, tint, normal)); - } - - if (indexAccess != null) - { - var indexArray = indexAccess.AsIndicesArray(); - foreach (var idx in indexArray) - { - indices.Add((uint)idx + baseVertex); - } - } - else - { - for (uint i = 0; i < posAccess.Count; i++) - { - indices.Add(baseVertex + i); - } - } - } - } - } - - if (vertices.Count == 0) - throw new InvalidOperationException($"GLTF file '{path}' contains no meshes."); - - return new Mesh(vertices.ToArray(), indices.ToArray()); - } -} diff --git a/src/Engine.Graphics/Loaders/ObjLoader.cs b/src/Engine.Graphics/Loaders/ObjLoader.cs index 3f8dd6a..a7acbc9 100644 --- a/src/Engine.Graphics/Loaders/ObjLoader.cs +++ b/src/Engine.Graphics/Loaders/ObjLoader.cs @@ -1,60 +1,54 @@ -using System.Globalization; using System.Numerics; using Engine.Core; using Engine.Core.Components; namespace Engine.Graphics.Loaders; +/// +/// Minimal OBJ loader. +/// public static class ObjLoader { - private static readonly Vector3 DefaultColor = new(0.7f, 0.6f, 0.5f); - - public static Mesh Load(string path, Vector3? color = null) + public static Mesh Load(string path, Vector3? defaultColor = null) { - var tint = color ?? DefaultColor; - var lines = File.ReadAllLines(path); + if (!File.Exists(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(); var normals = new List(); - + var texcoords = new List(); var vertices = new List(); var indices = new List(); + var faceNormals = new List(); - 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('#')) - continue; - - var parts = line.Split(' ', StringSplitOptions.RemoveEmptyEntries); - if (parts.Length == 0) - continue; + var parts = trimmed.Split(' ', StringSplitOptions.RemoveEmptyEntries); + if (parts.Length == 0) continue; switch (parts[0]) { case "v": - positions.Add(new Vector3( - float.Parse(parts[1], CultureInfo.InvariantCulture), - float.Parse(parts[2], CultureInfo.InvariantCulture), - float.Parse(parts[3], CultureInfo.InvariantCulture))); + positions.Add(ParseVector3(parts)); break; - case "vn": - normals.Add(new Vector3( - float.Parse(parts[1], CultureInfo.InvariantCulture), - float.Parse(parts[2], CultureInfo.InvariantCulture), - float.Parse(parts[3], CultureInfo.InvariantCulture))); + normals.Add(ParseVector3(parts)); + break; + case "vt": + texcoords.Add(ParseVector2(parts)); break; - case "f": - ParseFace(parts, positions, normals, vertices, indices, tint); + ParseFace(parts, positions, normals, texcoords, color, vertices, indices, faceNormals); break; } } 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()); } @@ -63,47 +57,52 @@ public static class ObjLoader string[] parts, List positions, List normals, + List texcoords, + Vector3 color, List vertices, List indices, - Vector3 tint) + List faceNormals) { - var faceData = new List<(int posIdx, int normIdx)>(); + var faceIndices = new List(); + faceNormals.Clear(); - for (var i = 1; i < parts.Length; i++) + for (int i = 1; i < parts.Length; i++) { - var vertexData = parts[i].Split('/'); - var posIdx = int.Parse(vertexData[0]) - 1; - var normIdx = vertexData.Length > 2 && !string.IsNullOrEmpty(vertexData[2]) - ? int.Parse(vertexData[2]) - 1 - : -1; + var sub = parts[i].Split('/'); + var posIndex = int.Parse(sub[0]) - 1; + var pos = positions[posIndex]; - 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; - - for (var i = 1; i < faceData.Count - 1; i++) + // Triangulate as a fan. + for (int i = 2; i < faceIndices.Count; i++) { - var d0 = faceData[0]; - var d1 = faceData[i]; - var d2 = faceData[i + 1]; - - var p0 = positions[d0.posIdx]; - var p1 = positions[d1.posIdx]; - var p2 = positions[d2.posIdx]; - - var normal = d0.normIdx >= 0 && d0.normIdx < normals.Count - ? normals[d0.normIdx] - : MeshMath.ComputeFaceNormal(p0, p1, p2); - - var i0 = (uint)vertices.Count; - vertices.Add(new Vertex(p0, tint, normal)); - var i1 = (uint)vertices.Count; - vertices.Add(new Vertex(p1, tint, normal)); - var i2 = (uint)vertices.Count; - vertices.Add(new Vertex(p2, tint, normal)); - - indices.Add(i0); indices.Add(i1); indices.Add(i2); + indices.Add(faceIndices[0]); + indices.Add(faceIndices[i - 1]); + indices.Add(faceIndices[i]); } } + + 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); + } } diff --git a/src/Engine.Graphics/MeshMath.cs b/src/Engine.Graphics/MeshMath.cs index 36868bd..4e174fd 100644 --- a/src/Engine.Graphics/MeshMath.cs +++ b/src/Engine.Graphics/MeshMath.cs @@ -2,17 +2,18 @@ using System.Numerics; namespace Engine.Graphics; +/// +/// Basic mesh math utilities. +/// public static class MeshMath { public static Vector3 ComputeFaceNormal(Vector3 a, Vector3 b, Vector3 c) { - var edge1 = b - a; - var edge2 = c - a; - var normal = Vector3.Cross(edge2, edge1); - - if (normal.LengthSquared() < 1e-12f) + var ab = b - a; + var ac = c - a; + var cross = Vector3.Cross(ab, ac); + if (cross.LengthSquared() < 0.0000001f) return Vector3.UnitY; - - return Vector3.Normalize(normal); + return Vector3.Normalize(cross); } } diff --git a/src/Engine.Graphics/ProceduralMesh.cs b/src/Engine.Graphics/ProceduralMesh.cs index cd842e2..c5855a5 100644 --- a/src/Engine.Graphics/ProceduralMesh.cs +++ b/src/Engine.Graphics/ProceduralMesh.cs @@ -4,85 +4,129 @@ using Engine.Core.Components; namespace Engine.Graphics; +/// +/// Procedural mesh generators. +/// 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 indices = new uint[stacks * slices * 6]; - - var vi = 0; - for (var i = 0; i <= stacks; i++) + var s = size * 0.5f; + var vertices = new[] { - var phi = MathF.PI * i / stacks; - var y = radius * MathF.Cos(phi); - var r = radius * MathF.Sin(phi); + // Front + 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)), + // 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 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++) + var indices = new uint[] { - for (var j = 0; j < slices; j++) - { - var a = (uint)(i * (slices + 1) + j); - var b = a + 1; - var c = a + (uint)(slices + 1); - var d = c + 1; - - indices[ii++] = a; indices[ii++] = c; indices[ii++] = b; - indices[ii++] = b; indices[ii++] = c; indices[ii++] = d; - } - } + 0, 1, 2, 0, 2, 3, + 4, 5, 6, 4, 6, 7, + 8, 9, 10, 8, 10, 11, + 12, 13, 14, 12, 14, 15, + 16, 17, 18, 16, 18, 19, + 20, 21, 22, 20, 22, 23, + }; 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(lines * 4 * 2); - var indices = new List(lines * 4 * 2); - var extent = halfSize * spacing; + var vertices = new List(); + var indices = new List(); - 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(); + var indices = new List(); + var max = lines * spacing; + var normal = Vector3.UnitY; + + for (int i = -lines; i <= lines; i++) { var pos = i * spacing; - var i0 = (uint)vertices.Count; - vertices.Add(new Vertex(new Vector3(pos, 0, -extent), color, Vector3.UnitY)); - var i1 = (uint)vertices.Count; - vertices.Add(new Vertex(new Vector3(pos, 0, extent), color, Vector3.UnitY)); - indices.Add(i0); indices.Add(i1); + vertices.Add(new Vertex(new Vector3(pos, 0, -max), color, normal)); + vertices.Add(new Vertex(new Vector3(pos, 0, max), color, normal)); + indices.Add((uint)(vertices.Count - 2)); + indices.Add((uint)(vertices.Count - 1)); - var i2 = (uint)vertices.Count; - vertices.Add(new Vertex(new Vector3(pos, 0, -extent), color, Vector3.UnitY)); - var i3 = (uint)vertices.Count; - vertices.Add(new Vertex(new Vector3(pos, 0, extent), color, Vector3.UnitY)); - indices.Add(i2); indices.Add(i3); - - var i4 = (uint)vertices.Count; - vertices.Add(new Vertex(new Vector3(-extent, 0, pos), color, Vector3.UnitY)); - var i5 = (uint)vertices.Count; - vertices.Add(new Vertex(new Vector3(extent, 0, pos), color, Vector3.UnitY)); - indices.Add(i4); indices.Add(i5); - - var i6 = (uint)vertices.Count; - vertices.Add(new Vertex(new Vector3(-extent, 0, pos), color, Vector3.UnitY)); - var i7 = (uint)vertices.Count; - vertices.Add(new Vertex(new Vector3(extent, 0, pos), color, Vector3.UnitY)); - indices.Add(i6); indices.Add(i7); + vertices.Add(new Vertex(new Vector3(-max, 0, pos), color, normal)); + vertices.Add(new Vertex(new Vector3( max, 0, pos), color, normal)); + indices.Add((uint)(vertices.Count - 2)); + indices.Add((uint)(vertices.Count - 1)); } return new Mesh(vertices.ToArray(), indices.ToArray()); diff --git a/src/Engine.Graphics/RenderBackendFactory.cs b/src/Engine.Graphics/RenderBackendFactory.cs index 2ab73db..a19e2a7 100644 --- a/src/Engine.Graphics/RenderBackendFactory.cs +++ b/src/Engine.Graphics/RenderBackendFactory.cs @@ -1,25 +1,28 @@ -using Engine.Core; - namespace Engine.Graphics; +/// +/// Factory for creating render backends by name. +/// public static class RenderBackendFactory { - private static readonly Dictionary> _backends = - new(StringComparer.OrdinalIgnoreCase); + private static readonly Dictionary> _backends = new(StringComparer.OrdinalIgnoreCase); + /// + /// Register a backend factory. Case-insensitive lookup. + /// public static void Register(string name, Func factory) { _backends[name] = factory; } + /// + /// Create a render context for the given backend. + /// public static IRenderContext Create(string name, int width, int height, bool enableValidation) { - if (_backends.TryGetValue(name, out var factory)) - return factory(width, height, enableValidation); + if (!_backends.TryGetValue(name, out var factory)) + throw new NotSupportedException($"Render backend '{name}' is not registered."); - throw new NotSupportedException( - $"Unknown render backend '{name}'. Available: {string.Join(", ", _backends.Keys)}"); + return factory(width, height, enableValidation); } - - public static bool IsRegistered(string name) => _backends.ContainsKey(name); } diff --git a/src/Engine.Graphics/SceneSerializer.cs b/src/Engine.Graphics/SceneSerializer.cs index c9cb57d..ed3af9d 100644 --- a/src/Engine.Graphics/SceneSerializer.cs +++ b/src/Engine.Graphics/SceneSerializer.cs @@ -1,86 +1,45 @@ using System.Numerics; using System.Text.Json; -using System.Text.Json.Serialization; using Engine.Core.Components; using Flecs.NET.Core; namespace Engine.Graphics; +/// +/// Serializes and deserializes entity scenes to JSON. +/// Minimal version: handles Transform, Material, Light, Camera, Mesh. +/// public static class SceneSerializer { - private static readonly JsonSerializerOptions JsonOptions = new() + private static readonly JsonSerializerOptions Options = new() { - PropertyNameCaseInsensitive = true, - Converters = { new JsonStringEnumConverter() } + WriteIndented = true, + IncludeFields = true }; public static string SaveToString(World world) { - var entities = new List(); - - world.Each((Entity e, ref Transform _) => + var entities = new List(); + world.Each((Entity e, ref Transform t) => { var name = e.Name(); - if (string.IsNullOrEmpty(name)) return; + var entity = new SceneEntity { Name = name }; - var data = new SceneEntityData { Name = name }; - - if (e.Has()) - { - var t = e.Get(); - data.Transform = new TransformData - { - Position = new float[] { t.Position.X, t.Position.Y, t.Position.Z }, - Rotation = new float[] { t.Rotation.X, t.Rotation.Y, t.Rotation.Z, t.Rotation.W }, - Scale = new float[] { t.Scale.X, t.Scale.Y, t.Scale.Z } - }; - } + entity.Transform = t; if (e.Has()) - { - var m = e.Get(); - data.Material = new MaterialData - { - Albedo = new float[] { m.Albedo.X, m.Albedo.Y, m.Albedo.Z }, - Roughness = m.Roughness, - Metallic = m.Metallic, - TexturePath = m.TexturePath - }; - } + entity.Material = e.Get(); if (e.Has()) - { - var l = e.Get(); - data.Light = new LightData - { - Type = l.Type.ToString(), - Direction = new float[] { l.Direction.X, l.Direction.Y, l.Direction.Z }, - Position = new float[] { l.Position.X, l.Position.Y, l.Position.Z }, - Color = new float[] { l.Color.X, l.Color.Y, l.Color.Z }, - Intensity = l.Intensity, - Range = l.Range - }; - } + entity.Light = e.Get(); if (e.Has()) - { - var c = e.Get(); - data.Camera = new CameraData - { - Position = new float[] { c.Position.X, c.Position.Y, c.Position.Z }, - Target = new float[] { c.Target.X, c.Target.Y, c.Target.Z }, - Up = new float[] { c.Up.X, c.Up.Y, c.Up.Z }, - FieldOfView = c.FieldOfView, - AspectRatio = c.AspectRatio, - NearPlane = c.NearPlane, - FarPlane = c.FarPlane - }; - } + entity.Camera = e.Get(); - entities.Add(data); + entities.Add(entity); }); - return JsonSerializer.Serialize(entities, JsonOptions); + return JsonSerializer.Serialize(entities, Options); } public static void SaveToFile(World world, string path) @@ -91,114 +50,42 @@ public static class SceneSerializer public static int LoadFromString(World world, string json) { - var entities = JsonSerializer.Deserialize>(json, JsonOptions); + var entities = JsonSerializer.Deserialize>(json, Options); 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) - { - 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 (e.Material != null) + entity.Set(e.Material.Value); - if (data.Material != null) - { - var m = data.Material; - entity.Set(new Material( - new Vector3(m.Albedo[0], m.Albedo[1], m.Albedo[2]), - m.Roughness, m.Metallic, m.TexturePath)); - } + if (e.Light != null) + entity.Set(e.Light.Value); - if (data.Light != null) - { - var l = data.Light; - if (l.Type == "Directional") - { - entity.Set(Light.Directional( - new Vector3(l.Direction[0], l.Direction[1], l.Direction[2]), - new Vector3(l.Color[0], l.Color[1], l.Color[2]), - l.Intensity)); - } - else - { - entity.Set(Light.Point( - new Vector3(l.Position[0], l.Position[1], l.Position[2]), - new Vector3(l.Color[0], l.Color[1], l.Color[2]), - l.Intensity, l.Range)); - } - } - - if (data.Camera != null) - { - var c = data.Camera; - entity.Set(new Camera( - new Vector3(c.Position[0], c.Position[1], c.Position[2]), - new Vector3(c.Target[0], c.Target[1], c.Target[2]), - new Vector3(c.Up[0], c.Up[1], c.Up[2]), - c.FieldOfView, c.AspectRatio, c.NearPlane, c.FarPlane)); - } + if (e.Camera != null) + entity.Set(e.Camera.Value); } return entities.Count; } - public static int LoadFromFile(World world, string path) + public static void LoadFromFile(World world, string path) { if (!File.Exists(path)) throw new FileNotFoundException($"Scene file not found: {path}", path); var json = File.ReadAllText(path); - return LoadFromString(world, json); + LoadFromString(world, json); } - private class SceneEntityData + private class SceneEntity { - public string Name { get; set; } = ""; - public TransformData? Transform { get; set; } - public MaterialData? Material { get; set; } - public LightData? Light { get; set; } - public CameraData? Camera { get; set; } - } - - private class TransformData - { - public float[] Position { get; set; } = Array.Empty(); - public float[] Rotation { get; set; } = Array.Empty(); - public float[] Scale { get; set; } = Array.Empty(); - } - - private class MaterialData - { - public float[] Albedo { get; set; } = Array.Empty(); - public float Roughness { get; set; } - public float Metallic { get; set; } - public string? TexturePath { get; set; } - } - - private class LightData - { - public string Type { get; set; } = ""; - public float[] Direction { get; set; } = Array.Empty(); - public float[] Position { get; set; } = Array.Empty(); - public float[] Color { get; set; } = Array.Empty(); - public float Intensity { get; set; } - public float Range { get; set; } - } - - private class CameraData - { - public float[] Position { get; set; } = Array.Empty(); - public float[] Target { get; set; } = Array.Empty(); - public float[] Up { get; set; } = Array.Empty(); - public float FieldOfView { get; set; } - public float AspectRatio { get; set; } - public float NearPlane { get; set; } - public float FarPlane { get; set; } + public string Name { get; set; } = string.Empty; + public Transform Transform { get; set; } + public Material? Material { get; set; } + public Light? Light { get; set; } + public Camera? Camera { get; set; } } }