fix: migrate to Silk.NET.Vulkan so the window actually runs on Kubuntu
This commit is contained in:
@@ -12,15 +12,17 @@ class Program
|
||||
|
||||
try
|
||||
{
|
||||
using var window = new Sdl3Window("Cortex Engine — Step 1", 1280, 720);
|
||||
using var window = new Sdl3Window("Cortex Engine — Step 2", 1280, 720);
|
||||
var timing = new Timing();
|
||||
var input = new InputMapping();
|
||||
using var vulkan = new VulkanContext(window, enableValidation: true);
|
||||
using var vulkan = new VulkanContext(window, enableValidation: false);
|
||||
using var swapchain = new Swapchain(vulkan);
|
||||
using var renderer = new TriangleRenderer(vulkan, swapchain);
|
||||
|
||||
var frames = 0;
|
||||
var lastFpsTime = 0.0;
|
||||
var lastWidth = window.Width;
|
||||
var lastHeight = window.Height;
|
||||
|
||||
while (!window.ShouldClose)
|
||||
{
|
||||
@@ -30,6 +32,13 @@ class Program
|
||||
// Note: SDL events are already polled in PumpEvents.
|
||||
// In a real engine, the window would expose an event iterator.
|
||||
|
||||
if (window.Width != lastWidth || window.Height != lastHeight)
|
||||
{
|
||||
lastWidth = window.Width;
|
||||
lastHeight = window.Height;
|
||||
swapchain.Recreate(lastWidth, lastHeight);
|
||||
}
|
||||
|
||||
renderer.RenderFrame();
|
||||
|
||||
frames++;
|
||||
|
||||
@@ -1,183 +0,0 @@
|
||||
using System;
|
||||
using Vortice.Vulkan;
|
||||
|
||||
namespace Engine.Graphics;
|
||||
|
||||
/// <summary>
|
||||
/// Minimal renderer that clears the swapchain image to a solid color.
|
||||
/// Serves as the foundational Step 1 rendering proof-of-concept.
|
||||
/// </summary>
|
||||
public sealed unsafe class ClearRenderer : IDisposable
|
||||
{
|
||||
private readonly VulkanContext _context;
|
||||
private readonly Swapchain _swapchain;
|
||||
private VkCommandPool _commandPool;
|
||||
private VkCommandBuffer[] _commandBuffers = null!;
|
||||
private VkSemaphore[] _imageAvailableSemaphores = null!;
|
||||
private VkSemaphore[] _renderFinishedSemaphores = null!;
|
||||
private VkFence[] _inFlightFences = null!;
|
||||
private int _currentFrame;
|
||||
|
||||
public ClearRenderer(VulkanContext context, Swapchain swapchain)
|
||||
{
|
||||
_context = context;
|
||||
_swapchain = swapchain;
|
||||
CreateCommandPool();
|
||||
CreateCommandBuffers();
|
||||
CreateSyncObjects();
|
||||
}
|
||||
|
||||
private void CreateCommandPool()
|
||||
{
|
||||
var createInfo = new VkCommandPoolCreateInfo
|
||||
{
|
||||
sType = VkStructureType.CommandPoolCreateInfo,
|
||||
queueFamilyIndex = _context.GraphicsFamilyIndex,
|
||||
flags = VkCommandPoolCreateFlags.ResetCommandBuffer
|
||||
};
|
||||
|
||||
var result = _context.DeviceApi.vkCreateCommandPool(&createInfo, null, out _commandPool);
|
||||
if (result != VkResult.Success)
|
||||
throw new InvalidOperationException($"vkCreateCommandPool failed: {result}");
|
||||
}
|
||||
|
||||
private void CreateCommandBuffers()
|
||||
{
|
||||
_commandBuffers = new VkCommandBuffer[2];
|
||||
for (var i = 0; i < _commandBuffers.Length; i++)
|
||||
{
|
||||
var allocInfo = new VkCommandBufferAllocateInfo
|
||||
{
|
||||
sType = VkStructureType.CommandBufferAllocateInfo,
|
||||
commandPool = _commandPool,
|
||||
level = VkCommandBufferLevel.Primary,
|
||||
commandBufferCount = 1
|
||||
};
|
||||
|
||||
var result = _context.DeviceApi.vkAllocateCommandBuffer(&allocInfo, out _commandBuffers[i]);
|
||||
if (result != VkResult.Success)
|
||||
throw new InvalidOperationException($"vkAllocateCommandBuffers failed: {result}");
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateSyncObjects()
|
||||
{
|
||||
_imageAvailableSemaphores = new VkSemaphore[2];
|
||||
_renderFinishedSemaphores = new VkSemaphore[2];
|
||||
_inFlightFences = new VkFence[2];
|
||||
|
||||
var semaphoreInfo = new VkSemaphoreCreateInfo { sType = VkStructureType.SemaphoreCreateInfo };
|
||||
var fenceInfo = new VkFenceCreateInfo
|
||||
{
|
||||
sType = VkStructureType.FenceCreateInfo,
|
||||
flags = VkFenceCreateFlags.Signaled
|
||||
};
|
||||
|
||||
for (var i = 0; i < 2; i++)
|
||||
{
|
||||
_context.DeviceApi.vkCreateSemaphore(&semaphoreInfo, null, out _imageAvailableSemaphores[i]);
|
||||
_context.DeviceApi.vkCreateSemaphore(&semaphoreInfo, null, out _renderFinishedSemaphores[i]);
|
||||
_context.DeviceApi.vkCreateFence(&fenceInfo, null, out _inFlightFences[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public void RenderFrame(float r, float g, float b)
|
||||
{
|
||||
var frame = _currentFrame % 2;
|
||||
|
||||
_context.DeviceApi.vkWaitForFences(_inFlightFences[frame], true, ulong.MaxValue);
|
||||
_context.DeviceApi.vkResetFences(_inFlightFences[frame]);
|
||||
|
||||
var result = _context.DeviceApi.vkAcquireNextImageKHR(
|
||||
_swapchain.Handle,
|
||||
ulong.MaxValue,
|
||||
_imageAvailableSemaphores[frame],
|
||||
VkFence.Null,
|
||||
out var imageIndex);
|
||||
|
||||
if (result == VkResult.ErrorOutOfDateKHR)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var cmd = _commandBuffers[frame];
|
||||
_context.DeviceApi.vkResetCommandBuffer(cmd, VkCommandBufferResetFlags.None);
|
||||
|
||||
var beginInfo = new VkCommandBufferBeginInfo
|
||||
{
|
||||
sType = VkStructureType.CommandBufferBeginInfo,
|
||||
flags = VkCommandBufferUsageFlags.OneTimeSubmit
|
||||
};
|
||||
_context.DeviceApi.vkBeginCommandBuffer(cmd, &beginInfo);
|
||||
|
||||
var clearColor = new VkClearValue(r, g, b, 1.0f);
|
||||
|
||||
var renderPassInfo = new VkRenderPassBeginInfo
|
||||
{
|
||||
sType = VkStructureType.RenderPassBeginInfo,
|
||||
renderPass = _swapchain.RenderPass,
|
||||
framebuffer = _swapchain.Framebuffers[imageIndex],
|
||||
renderArea = new VkRect2D(0, 0, _swapchain.Extent.width, _swapchain.Extent.height),
|
||||
clearValueCount = 1,
|
||||
pClearValues = &clearColor
|
||||
};
|
||||
|
||||
_context.DeviceApi.vkCmdBeginRenderPass(cmd, &renderPassInfo, VkSubpassContents.Inline);
|
||||
_context.DeviceApi.vkCmdEndRenderPass(cmd);
|
||||
_context.DeviceApi.vkEndCommandBuffer(cmd);
|
||||
|
||||
var waitSemaphore = _imageAvailableSemaphores[frame];
|
||||
var signalSemaphore = _renderFinishedSemaphores[frame];
|
||||
var stageMask = VkPipelineStageFlags.ColorAttachmentOutput;
|
||||
var submitInfo = new VkSubmitInfo
|
||||
{
|
||||
sType = VkStructureType.SubmitInfo,
|
||||
waitSemaphoreCount = 1,
|
||||
pWaitSemaphores = &waitSemaphore,
|
||||
pWaitDstStageMask = &stageMask,
|
||||
commandBufferCount = 1,
|
||||
pCommandBuffers = &cmd,
|
||||
signalSemaphoreCount = 1,
|
||||
pSignalSemaphores = &signalSemaphore
|
||||
};
|
||||
|
||||
_context.DeviceApi.vkQueueSubmit(_context.GraphicsQueue, 1, &submitInfo, _inFlightFences[frame]);
|
||||
|
||||
var swapchain = _swapchain.Handle;
|
||||
var presentInfo = new VkPresentInfoKHR
|
||||
{
|
||||
sType = VkStructureType.PresentInfoKHR,
|
||||
waitSemaphoreCount = 1,
|
||||
pWaitSemaphores = &signalSemaphore,
|
||||
swapchainCount = 1,
|
||||
pSwapchains = &swapchain,
|
||||
pImageIndices = &imageIndex
|
||||
};
|
||||
|
||||
var presentResult = _context.DeviceApi.vkQueuePresentKHR(_context.PresentQueue, &presentInfo);
|
||||
if (presentResult == VkResult.ErrorOutOfDateKHR || presentResult == VkResult.SuboptimalKHR)
|
||||
{
|
||||
// Recreate handled externally.
|
||||
}
|
||||
|
||||
_currentFrame++;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_context.DeviceApi.vkDeviceWaitIdle();
|
||||
|
||||
for (var i = 0; i < 2; i++)
|
||||
{
|
||||
if (_renderFinishedSemaphores[i] != VkSemaphore.Null)
|
||||
_context.DeviceApi.vkDestroySemaphore(_renderFinishedSemaphores[i]);
|
||||
if (_imageAvailableSemaphores[i] != VkSemaphore.Null)
|
||||
_context.DeviceApi.vkDestroySemaphore(_imageAvailableSemaphores[i]);
|
||||
if (_inFlightFences[i] != VkFence.Null)
|
||||
_context.DeviceApi.vkDestroyFence(_inFlightFences[i]);
|
||||
}
|
||||
|
||||
if (_commandPool != VkCommandPool.Null)
|
||||
_context.DeviceApi.vkDestroyCommandPool(_commandPool);
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Vortice.Vulkan" Version="3.2.3" />
|
||||
<PackageReference Include="Silk.NET.Vulkan" Version="2.21.0" />
|
||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.21.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
+144
-137
@@ -1,28 +1,30 @@
|
||||
using System;
|
||||
using Vortice.Vulkan;
|
||||
using Silk.NET.Core;
|
||||
using Silk.NET.Vulkan;
|
||||
using Silk.NET.Vulkan.Extensions.KHR;
|
||||
|
||||
namespace Engine.Graphics;
|
||||
|
||||
/// <summary>
|
||||
/// Manages the Vulkan swapchain, image views, render pass, and framebuffers.
|
||||
/// Recreates itself automatically when the window is resized.
|
||||
/// Uses Silk.NET.Vulkan.
|
||||
/// </summary>
|
||||
public sealed unsafe class Swapchain : IDisposable
|
||||
{
|
||||
private readonly VulkanContext _context;
|
||||
private VkRenderPass _renderPass;
|
||||
private VkSwapchainKHR _swapchain;
|
||||
private VkImage[] _images = null!;
|
||||
private VkImageView[] _imageViews = null!;
|
||||
private VkFramebuffer[] _framebuffers = null!;
|
||||
private VkSurfaceFormatKHR _surfaceFormat;
|
||||
private VkPresentModeKHR _presentMode;
|
||||
private VkExtent2D _extent;
|
||||
private RenderPass _renderPass;
|
||||
private SwapchainKHR _swapchain;
|
||||
private Image[] _images = null!;
|
||||
private ImageView[] _imageViews = null!;
|
||||
private Framebuffer[] _framebuffers = null!;
|
||||
private SurfaceFormatKHR _surfaceFormat;
|
||||
private PresentModeKHR _presentMode;
|
||||
private Extent2D _extent;
|
||||
|
||||
public VkRenderPass RenderPass => _renderPass;
|
||||
public VkFramebuffer[] Framebuffers => _framebuffers;
|
||||
public VkExtent2D Extent => _extent;
|
||||
public VkSwapchainKHR Handle => _swapchain;
|
||||
public RenderPass RenderPass => _renderPass;
|
||||
public Framebuffer[] Framebuffers => _framebuffers;
|
||||
public Extent2D Extent => _extent;
|
||||
public SwapchainKHR Handle => _swapchain;
|
||||
public uint ImageCount => (uint)_images.Length;
|
||||
|
||||
public Swapchain(VulkanContext context)
|
||||
@@ -35,7 +37,7 @@ public sealed unsafe class Swapchain : IDisposable
|
||||
|
||||
public void Recreate(int width, int height)
|
||||
{
|
||||
_context.DeviceApi.vkDeviceWaitIdle();
|
||||
_context.Vk.DeviceWaitIdle(_context.Device);
|
||||
CleanupSwapchain();
|
||||
|
||||
var capabilities = GetSurfaceCapabilities();
|
||||
@@ -43,60 +45,63 @@ public sealed unsafe class Swapchain : IDisposable
|
||||
_presentMode = ChoosePresentMode();
|
||||
_extent = ChooseExtent(capabilities, (uint)width, (uint)height);
|
||||
|
||||
var imageCount = capabilities.minImageCount + 1;
|
||||
if (capabilities.maxImageCount > 0 && imageCount > capabilities.maxImageCount)
|
||||
imageCount = capabilities.maxImageCount;
|
||||
var imageCount = capabilities.MinImageCount + 1;
|
||||
if (capabilities.MaxImageCount > 0 && imageCount > capabilities.MaxImageCount)
|
||||
imageCount = capabilities.MaxImageCount;
|
||||
|
||||
var createInfo = new VkSwapchainCreateInfoKHR
|
||||
var createInfo = new SwapchainCreateInfoKHR
|
||||
{
|
||||
sType = VkStructureType.SwapchainCreateInfoKHR,
|
||||
surface = _context.Surface,
|
||||
minImageCount = imageCount,
|
||||
imageFormat = _surfaceFormat.format,
|
||||
imageColorSpace = _surfaceFormat.colorSpace,
|
||||
imageExtent = _extent,
|
||||
imageArrayLayers = 1,
|
||||
imageUsage = VkImageUsageFlags.ColorAttachment,
|
||||
imageSharingMode = VkSharingMode.Exclusive,
|
||||
preTransform = capabilities.currentTransform,
|
||||
compositeAlpha = VkCompositeAlphaFlagsKHR.Opaque,
|
||||
presentMode = _presentMode,
|
||||
clipped = true,
|
||||
oldSwapchain = _swapchain
|
||||
SType = StructureType.SwapchainCreateInfoKhr,
|
||||
Surface = _context.Surface,
|
||||
MinImageCount = imageCount,
|
||||
ImageFormat = _surfaceFormat.Format,
|
||||
ImageColorSpace = _surfaceFormat.ColorSpace,
|
||||
ImageExtent = _extent,
|
||||
ImageArrayLayers = 1,
|
||||
ImageUsage = ImageUsageFlags.ColorAttachmentBit,
|
||||
ImageSharingMode = SharingMode.Exclusive,
|
||||
PreTransform = capabilities.CurrentTransform,
|
||||
CompositeAlpha = CompositeAlphaFlagsKHR.OpaqueBitKhr,
|
||||
PresentMode = _presentMode,
|
||||
Clipped = true,
|
||||
OldSwapchain = _swapchain
|
||||
};
|
||||
|
||||
var result = _context.DeviceApi.vkCreateSwapchainKHR(&createInfo, null, out _swapchain);
|
||||
if (result != VkResult.Success)
|
||||
SwapchainKHR swapchain;
|
||||
var result = _context.KhrSwapchain!.CreateSwapchain(_context.Device, &createInfo, null, &swapchain);
|
||||
if (result != Result.Success)
|
||||
throw new InvalidOperationException($"vkCreateSwapchainKHR failed: {result}");
|
||||
_swapchain = swapchain;
|
||||
|
||||
_images = GetSwapchainImages();
|
||||
_imageViews = new VkImageView[_images.Length];
|
||||
_framebuffers = new VkFramebuffer[_images.Length];
|
||||
_imageViews = new ImageView[_images.Length];
|
||||
_framebuffers = new Framebuffer[_images.Length];
|
||||
|
||||
for (var i = 0; i < _images.Length; i++)
|
||||
{
|
||||
_imageViews[i] = CreateImageView(_images[i], _surfaceFormat.format);
|
||||
_imageViews[i] = CreateImageView(_images[i], _surfaceFormat.Format);
|
||||
_framebuffers[i] = CreateFramebuffer(_imageViews[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private VkSurfaceCapabilitiesKHR GetSurfaceCapabilities()
|
||||
private SurfaceCapabilitiesKHR GetSurfaceCapabilities()
|
||||
{
|
||||
var result = _context.InstanceApi.vkGetPhysicalDeviceSurfaceCapabilitiesKHR(_context.PhysicalDevice, _context.Surface, out var capabilities);
|
||||
if (result != VkResult.Success)
|
||||
SurfaceCapabilitiesKHR capabilities;
|
||||
var result = _context.KhrSurface!.GetPhysicalDeviceSurfaceCapabilities(_context.PhysicalDevice, _context.Surface, &capabilities);
|
||||
if (result != Result.Success)
|
||||
throw new InvalidOperationException($"vkGetPhysicalDeviceSurfaceCapabilitiesKHR failed: {result}");
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
private VkImage[] GetSwapchainImages()
|
||||
private Image[] GetSwapchainImages()
|
||||
{
|
||||
uint count = 0;
|
||||
_context.DeviceApi.vkGetSwapchainImagesKHR(_swapchain, &count, null);
|
||||
var images = new VkImage[count];
|
||||
fixed (VkImage* p = images)
|
||||
_context.KhrSwapchain!.GetSwapchainImages(_context.Device, _swapchain, &count, null);
|
||||
var images = new Image[count];
|
||||
fixed (Image* p = images)
|
||||
{
|
||||
var result = _context.DeviceApi.vkGetSwapchainImagesKHR(_swapchain, &count, p);
|
||||
if (result != VkResult.Success)
|
||||
var result = _context.KhrSwapchain!.GetSwapchainImages(_context.Device, _swapchain, &count, p);
|
||||
if (result != Result.Success)
|
||||
throw new InvalidOperationException($"vkGetSwapchainImagesKHR failed: {result}");
|
||||
}
|
||||
return images;
|
||||
@@ -104,167 +109,169 @@ public sealed unsafe class Swapchain : IDisposable
|
||||
|
||||
private void CreateRenderPass()
|
||||
{
|
||||
var colorAttachment = new VkAttachmentDescription
|
||||
var colorAttachment = new AttachmentDescription
|
||||
{
|
||||
format = _surfaceFormat.format != VkFormat.Undefined ? _surfaceFormat.format : VkFormat.B8G8R8A8Unorm,
|
||||
samples = VkSampleCountFlags.Count1,
|
||||
loadOp = VkAttachmentLoadOp.Clear,
|
||||
storeOp = VkAttachmentStoreOp.Store,
|
||||
stencilLoadOp = VkAttachmentLoadOp.DontCare,
|
||||
stencilStoreOp = VkAttachmentStoreOp.DontCare,
|
||||
initialLayout = VkImageLayout.Undefined,
|
||||
finalLayout = VkImageLayout.PresentSrcKHR
|
||||
Format = _surfaceFormat.Format != Format.Undefined ? _surfaceFormat.Format : Format.B8G8R8A8Unorm,
|
||||
Samples = SampleCountFlags.Count1Bit,
|
||||
LoadOp = AttachmentLoadOp.Clear,
|
||||
StoreOp = AttachmentStoreOp.Store,
|
||||
StencilLoadOp = AttachmentLoadOp.DontCare,
|
||||
StencilStoreOp = AttachmentStoreOp.DontCare,
|
||||
InitialLayout = ImageLayout.Undefined,
|
||||
FinalLayout = ImageLayout.PresentSrcKhr
|
||||
};
|
||||
|
||||
var colorAttachmentRef = new VkAttachmentReference
|
||||
var colorAttachmentRef = new AttachmentReference
|
||||
{
|
||||
attachment = 0,
|
||||
layout = VkImageLayout.ColorAttachmentOptimal
|
||||
Attachment = 0,
|
||||
Layout = ImageLayout.ColorAttachmentOptimal
|
||||
};
|
||||
|
||||
var subpass = new VkSubpassDescription
|
||||
var subpass = new SubpassDescription
|
||||
{
|
||||
pipelineBindPoint = VkPipelineBindPoint.Graphics,
|
||||
colorAttachmentCount = 1,
|
||||
pColorAttachments = &colorAttachmentRef
|
||||
PipelineBindPoint = PipelineBindPoint.Graphics,
|
||||
ColorAttachmentCount = 1,
|
||||
PColorAttachments = &colorAttachmentRef
|
||||
};
|
||||
|
||||
var dependency = new VkSubpassDependency
|
||||
var dependency = new SubpassDependency
|
||||
{
|
||||
srcSubpass = Vulkan.VK_SUBPASS_EXTERNAL,
|
||||
dstSubpass = 0,
|
||||
srcStageMask = VkPipelineStageFlags.ColorAttachmentOutput,
|
||||
dstStageMask = VkPipelineStageFlags.ColorAttachmentOutput,
|
||||
srcAccessMask = VkAccessFlags.None,
|
||||
dstAccessMask = VkAccessFlags.ColorAttachmentWrite
|
||||
SrcSubpass = ~0u,
|
||||
DstSubpass = 0,
|
||||
SrcStageMask = PipelineStageFlags.ColorAttachmentOutputBit,
|
||||
DstStageMask = PipelineStageFlags.ColorAttachmentOutputBit,
|
||||
SrcAccessMask = AccessFlags.None,
|
||||
DstAccessMask = AccessFlags.ColorAttachmentWriteBit
|
||||
};
|
||||
|
||||
var createInfo = new VkRenderPassCreateInfo
|
||||
var createInfo = new RenderPassCreateInfo
|
||||
{
|
||||
sType = VkStructureType.RenderPassCreateInfo,
|
||||
attachmentCount = 1,
|
||||
pAttachments = &colorAttachment,
|
||||
subpassCount = 1,
|
||||
pSubpasses = &subpass,
|
||||
dependencyCount = 1,
|
||||
pDependencies = &dependency
|
||||
SType = StructureType.RenderPassCreateInfo,
|
||||
AttachmentCount = 1,
|
||||
PAttachments = &colorAttachment,
|
||||
SubpassCount = 1,
|
||||
PSubpasses = &subpass,
|
||||
DependencyCount = 1,
|
||||
PDependencies = &dependency
|
||||
};
|
||||
|
||||
var result = _context.DeviceApi.vkCreateRenderPass(&createInfo, null, out _renderPass);
|
||||
if (result != VkResult.Success)
|
||||
RenderPass renderPass;
|
||||
var result = _context.Vk.CreateRenderPass(_context.Device, &createInfo, null, &renderPass);
|
||||
if (result != Result.Success)
|
||||
throw new InvalidOperationException($"vkCreateRenderPass failed: {result}");
|
||||
_renderPass = renderPass;
|
||||
}
|
||||
|
||||
private VkImageView CreateImageView(VkImage image, VkFormat format)
|
||||
private ImageView CreateImageView(Image image, Format format)
|
||||
{
|
||||
var createInfo = new VkImageViewCreateInfo
|
||||
var createInfo = new ImageViewCreateInfo
|
||||
{
|
||||
sType = VkStructureType.ImageViewCreateInfo,
|
||||
image = image,
|
||||
viewType = VkImageViewType.Image2D,
|
||||
format = format,
|
||||
components = new VkComponentMapping(VkComponentSwizzle.R, VkComponentSwizzle.G, VkComponentSwizzle.B, VkComponentSwizzle.A),
|
||||
subresourceRange = new VkImageSubresourceRange(VkImageAspectFlags.Color, 0, 1, 0, 1)
|
||||
SType = StructureType.ImageViewCreateInfo,
|
||||
Image = image,
|
||||
ViewType = ImageViewType.Type2D,
|
||||
Format = format,
|
||||
Components = new ComponentMapping(ComponentSwizzle.R, ComponentSwizzle.G, ComponentSwizzle.B, ComponentSwizzle.A),
|
||||
SubresourceRange = new ImageSubresourceRange(ImageAspectFlags.ColorBit, 0, 1, 0, 1)
|
||||
};
|
||||
|
||||
var result = _context.DeviceApi.vkCreateImageView(&createInfo, null, out var imageView);
|
||||
if (result != VkResult.Success)
|
||||
ImageView imageView;
|
||||
var result = _context.Vk.CreateImageView(_context.Device, &createInfo, null, &imageView);
|
||||
if (result != Result.Success)
|
||||
throw new InvalidOperationException($"vkCreateImageView failed: {result}");
|
||||
|
||||
return imageView;
|
||||
}
|
||||
|
||||
private VkFramebuffer CreateFramebuffer(VkImageView imageView)
|
||||
private Framebuffer CreateFramebuffer(ImageView imageView)
|
||||
{
|
||||
var createInfo = new VkFramebufferCreateInfo
|
||||
var createInfo = new FramebufferCreateInfo
|
||||
{
|
||||
sType = VkStructureType.FramebufferCreateInfo,
|
||||
renderPass = _renderPass,
|
||||
attachmentCount = 1,
|
||||
pAttachments = &imageView,
|
||||
width = _extent.width,
|
||||
height = _extent.height,
|
||||
layers = 1
|
||||
SType = StructureType.FramebufferCreateInfo,
|
||||
RenderPass = _renderPass,
|
||||
AttachmentCount = 1,
|
||||
PAttachments = &imageView,
|
||||
Width = _extent.Width,
|
||||
Height = _extent.Height,
|
||||
Layers = 1
|
||||
};
|
||||
|
||||
var result = _context.DeviceApi.vkCreateFramebuffer(&createInfo, null, out var framebuffer);
|
||||
if (result != VkResult.Success)
|
||||
Framebuffer framebuffer;
|
||||
var result = _context.Vk.CreateFramebuffer(_context.Device, &createInfo, null, &framebuffer);
|
||||
if (result != Result.Success)
|
||||
throw new InvalidOperationException($"vkCreateFramebuffer failed: {result}");
|
||||
|
||||
return framebuffer;
|
||||
}
|
||||
|
||||
private VkSurfaceFormatKHR ChooseSurfaceFormat()
|
||||
private SurfaceFormatKHR ChooseSurfaceFormat()
|
||||
{
|
||||
var formats = GetSurfaceFormats();
|
||||
foreach (var format in formats)
|
||||
{
|
||||
if (format.format == VkFormat.B8G8R8A8Unorm && format.colorSpace == VkColorSpaceKHR.SrgbNonLinear)
|
||||
if (format.Format == Format.B8G8R8A8Unorm && format.ColorSpace == ColorSpaceKHR.SpaceSrgbNonlinearKhr)
|
||||
return format;
|
||||
}
|
||||
return formats[0];
|
||||
}
|
||||
|
||||
private VkSurfaceFormatKHR[] GetSurfaceFormats()
|
||||
private SurfaceFormatKHR[] GetSurfaceFormats()
|
||||
{
|
||||
uint count = 0;
|
||||
_context.InstanceApi.vkGetPhysicalDeviceSurfaceFormatsKHR(_context.PhysicalDevice, _context.Surface, &count, null);
|
||||
var formats = new VkSurfaceFormatKHR[count];
|
||||
fixed (VkSurfaceFormatKHR* p = formats)
|
||||
_context.KhrSurface!.GetPhysicalDeviceSurfaceFormats(_context.PhysicalDevice, _context.Surface, &count, null);
|
||||
var formats = new SurfaceFormatKHR[count];
|
||||
fixed (SurfaceFormatKHR* p = formats)
|
||||
{
|
||||
var result = _context.InstanceApi.vkGetPhysicalDeviceSurfaceFormatsKHR(_context.PhysicalDevice, _context.Surface, &count, p);
|
||||
if (result != VkResult.Success)
|
||||
var result = _context.KhrSurface!.GetPhysicalDeviceSurfaceFormats(_context.PhysicalDevice, _context.Surface, &count, p);
|
||||
if (result != Result.Success)
|
||||
throw new InvalidOperationException($"vkGetPhysicalDeviceSurfaceFormatsKHR failed: {result}");
|
||||
}
|
||||
return formats;
|
||||
}
|
||||
|
||||
private VkPresentModeKHR ChoosePresentMode()
|
||||
private PresentModeKHR ChoosePresentMode()
|
||||
{
|
||||
var modes = GetSurfacePresentModes();
|
||||
if (Array.Exists(modes, m => m == VkPresentModeKHR.Mailbox))
|
||||
return VkPresentModeKHR.Mailbox;
|
||||
return VkPresentModeKHR.Fifo;
|
||||
if (Array.Exists(modes, m => m == PresentModeKHR.MailboxKhr))
|
||||
return PresentModeKHR.MailboxKhr;
|
||||
return PresentModeKHR.FifoKhr;
|
||||
}
|
||||
|
||||
private VkPresentModeKHR[] GetSurfacePresentModes()
|
||||
private PresentModeKHR[] GetSurfacePresentModes()
|
||||
{
|
||||
uint count = 0;
|
||||
_context.InstanceApi.vkGetPhysicalDeviceSurfacePresentModesKHR(_context.PhysicalDevice, _context.Surface, &count, null);
|
||||
var modes = new VkPresentModeKHR[count];
|
||||
fixed (VkPresentModeKHR* p = modes)
|
||||
_context.KhrSurface!.GetPhysicalDeviceSurfacePresentModes(_context.PhysicalDevice, _context.Surface, &count, null);
|
||||
var modes = new PresentModeKHR[count];
|
||||
fixed (PresentModeKHR* p = modes)
|
||||
{
|
||||
var result = _context.InstanceApi.vkGetPhysicalDeviceSurfacePresentModesKHR(_context.PhysicalDevice, _context.Surface, &count, p);
|
||||
if (result != VkResult.Success)
|
||||
var result = _context.KhrSurface!.GetPhysicalDeviceSurfacePresentModes(_context.PhysicalDevice, _context.Surface, &count, p);
|
||||
if (result != Result.Success)
|
||||
throw new InvalidOperationException($"vkGetPhysicalDeviceSurfacePresentModesKHR failed: {result}");
|
||||
}
|
||||
return modes;
|
||||
}
|
||||
|
||||
private VkExtent2D ChooseExtent(VkSurfaceCapabilitiesKHR capabilities, uint width, uint height)
|
||||
private Extent2D ChooseExtent(SurfaceCapabilitiesKHR capabilities, uint width, uint height)
|
||||
{
|
||||
if (capabilities.currentExtent.width != uint.MaxValue)
|
||||
return capabilities.currentExtent;
|
||||
if (capabilities.CurrentExtent.Width != uint.MaxValue)
|
||||
return capabilities.CurrentExtent;
|
||||
|
||||
var extent = new VkExtent2D
|
||||
var extent = new Extent2D
|
||||
{
|
||||
width = Math.Clamp(width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width),
|
||||
height = Math.Clamp(height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height)
|
||||
Width = Math.Clamp(width, capabilities.MinImageExtent.Width, capabilities.MaxImageExtent.Width),
|
||||
Height = Math.Clamp(height, capabilities.MinImageExtent.Height, capabilities.MaxImageExtent.Height)
|
||||
};
|
||||
return extent;
|
||||
}
|
||||
|
||||
private void CleanupSwapchain()
|
||||
{
|
||||
if (_context.Device == VkDevice.Null)
|
||||
if (_context.Device.Handle == 0)
|
||||
return;
|
||||
|
||||
if (_framebuffers != null)
|
||||
{
|
||||
foreach (var fb in _framebuffers)
|
||||
{
|
||||
if (fb != VkFramebuffer.Null)
|
||||
_context.DeviceApi.vkDestroyFramebuffer(fb);
|
||||
if (fb.Handle != 0)
|
||||
_context.Vk.DestroyFramebuffer(_context.Device, fb, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,21 +279,21 @@ public sealed unsafe class Swapchain : IDisposable
|
||||
{
|
||||
foreach (var view in _imageViews)
|
||||
{
|
||||
if (view != VkImageView.Null)
|
||||
_context.DeviceApi.vkDestroyImageView(view);
|
||||
if (view.Handle != 0)
|
||||
_context.Vk.DestroyImageView(_context.Device, view, null);
|
||||
}
|
||||
}
|
||||
|
||||
if (_swapchain != VkSwapchainKHR.Null)
|
||||
_context.DeviceApi.vkDestroySwapchainKHR(_swapchain);
|
||||
if (_swapchain.Handle != 0)
|
||||
_context.KhrSwapchain!.DestroySwapchain(_context.Device, _swapchain, null);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_context.DeviceApi.vkDeviceWaitIdle();
|
||||
_context.Vk.DeviceWaitIdle(_context.Device);
|
||||
CleanupSwapchain();
|
||||
|
||||
if (_renderPass != VkRenderPass.Null)
|
||||
_context.DeviceApi.vkDestroyRenderPass(_renderPass);
|
||||
if (_renderPass.Handle != 0)
|
||||
_context.Vk.DestroyRenderPass(_context.Device, _renderPass, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using System;
|
||||
using Vortice.Vulkan;
|
||||
using Silk.NET.Core;
|
||||
using Silk.NET.Vulkan;
|
||||
|
||||
namespace Engine.Graphics;
|
||||
|
||||
/// <summary>
|
||||
/// Renders a colored triangle using a vertex buffer and a simple graphics pipeline.
|
||||
/// Uses Silk.NET.Vulkan.
|
||||
/// </summary>
|
||||
public sealed unsafe class TriangleRenderer : IDisposable
|
||||
{
|
||||
@@ -12,11 +14,11 @@ public sealed unsafe class TriangleRenderer : IDisposable
|
||||
private readonly Swapchain _swapchain;
|
||||
private readonly VulkanPipeline _pipeline;
|
||||
private readonly VertexBuffer _vertexBuffer;
|
||||
private VkCommandPool _commandPool;
|
||||
private VkCommandBuffer[] _commandBuffers = null!;
|
||||
private VkSemaphore[] _imageAvailableSemaphores = null!;
|
||||
private VkSemaphore[] _renderFinishedSemaphores = null!;
|
||||
private VkFence[] _inFlightFences = null!;
|
||||
private CommandPool _commandPool;
|
||||
private CommandBuffer[] _commandBuffers = null!;
|
||||
private Silk.NET.Vulkan.Semaphore[] _imageAvailableSemaphores = null!;
|
||||
private Silk.NET.Vulkan.Semaphore[] _renderFinishedSemaphores = null!;
|
||||
private Silk.NET.Vulkan.Fence[] _inFlightFences = null!;
|
||||
private int _currentFrame;
|
||||
|
||||
public TriangleRenderer(VulkanContext context, Swapchain swapchain)
|
||||
@@ -26,7 +28,6 @@ public sealed unsafe class TriangleRenderer : IDisposable
|
||||
|
||||
_pipeline = new VulkanPipeline(context, swapchain);
|
||||
_vertexBuffer = CreateTriangleBuffer();
|
||||
|
||||
CreateCommandPool();
|
||||
CreateCommandBuffers();
|
||||
CreateSyncObjects();
|
||||
@@ -36,7 +37,6 @@ public sealed unsafe class TriangleRenderer : IDisposable
|
||||
{
|
||||
var vertices = new[]
|
||||
{
|
||||
// Position (vec2) + Color (vec3)
|
||||
0.0f, -0.5f, 1.0f, 0.0f, 0.0f,
|
||||
0.5f, 0.5f, 0.0f, 1.0f, 0.0f,
|
||||
-0.5f, 0.5f, 0.0f, 0.0f, 1.0f
|
||||
@@ -46,7 +46,7 @@ public sealed unsafe class TriangleRenderer : IDisposable
|
||||
fixed (byte* p = bytes)
|
||||
fixed (float* v = vertices)
|
||||
{
|
||||
Buffer.MemoryCopy(v, p, bytes.Length, vertices.Length * sizeof(float));
|
||||
global::System.Buffer.MemoryCopy(v, p, bytes.Length, vertices.Length * sizeof(float));
|
||||
}
|
||||
|
||||
return new VertexBuffer(_context, bytes);
|
||||
@@ -54,55 +54,64 @@ public sealed unsafe class TriangleRenderer : IDisposable
|
||||
|
||||
private void CreateCommandPool()
|
||||
{
|
||||
var createInfo = new VkCommandPoolCreateInfo
|
||||
var createInfo = new CommandPoolCreateInfo
|
||||
{
|
||||
sType = VkStructureType.CommandPoolCreateInfo,
|
||||
queueFamilyIndex = _context.GraphicsFamilyIndex,
|
||||
flags = VkCommandPoolCreateFlags.ResetCommandBuffer
|
||||
SType = StructureType.CommandPoolCreateInfo,
|
||||
QueueFamilyIndex = _context.GraphicsFamilyIndex,
|
||||
Flags = CommandPoolCreateFlags.ResetCommandBufferBit
|
||||
};
|
||||
|
||||
var result = _context.DeviceApi.vkCreateCommandPool(&createInfo, null, out _commandPool);
|
||||
if (result != VkResult.Success)
|
||||
CommandPool commandPool;
|
||||
var result = _context.Vk.CreateCommandPool(_context.Device, &createInfo, null, &commandPool);
|
||||
if (result != Result.Success)
|
||||
throw new InvalidOperationException($"vkCreateCommandPool failed: {result}");
|
||||
_commandPool = commandPool;
|
||||
}
|
||||
|
||||
private void CreateCommandBuffers()
|
||||
{
|
||||
_commandBuffers = new VkCommandBuffer[2];
|
||||
_commandBuffers = new CommandBuffer[2];
|
||||
for (var i = 0; i < _commandBuffers.Length; i++)
|
||||
{
|
||||
var allocInfo = new VkCommandBufferAllocateInfo
|
||||
var allocInfo = new CommandBufferAllocateInfo
|
||||
{
|
||||
sType = VkStructureType.CommandBufferAllocateInfo,
|
||||
commandPool = _commandPool,
|
||||
level = VkCommandBufferLevel.Primary,
|
||||
commandBufferCount = 1
|
||||
SType = StructureType.CommandBufferAllocateInfo,
|
||||
CommandPool = _commandPool,
|
||||
Level = CommandBufferLevel.Primary,
|
||||
CommandBufferCount = 1
|
||||
};
|
||||
|
||||
var result = _context.DeviceApi.vkAllocateCommandBuffer(&allocInfo, out _commandBuffers[i]);
|
||||
if (result != VkResult.Success)
|
||||
CommandBuffer cmd;
|
||||
var result = _context.Vk.AllocateCommandBuffers(_context.Device, &allocInfo, &cmd);
|
||||
if (result != Result.Success)
|
||||
throw new InvalidOperationException($"vkAllocateCommandBuffers failed: {result}");
|
||||
_commandBuffers[i] = cmd;
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateSyncObjects()
|
||||
{
|
||||
_imageAvailableSemaphores = new VkSemaphore[2];
|
||||
_renderFinishedSemaphores = new VkSemaphore[2];
|
||||
_inFlightFences = new VkFence[2];
|
||||
_imageAvailableSemaphores = new Silk.NET.Vulkan.Semaphore[2];
|
||||
_renderFinishedSemaphores = new Silk.NET.Vulkan.Semaphore[2];
|
||||
_inFlightFences = new Silk.NET.Vulkan.Fence[2];
|
||||
|
||||
var semaphoreInfo = new VkSemaphoreCreateInfo { sType = VkStructureType.SemaphoreCreateInfo };
|
||||
var fenceInfo = new VkFenceCreateInfo
|
||||
var semaphoreInfo = new SemaphoreCreateInfo { SType = StructureType.SemaphoreCreateInfo };
|
||||
var fenceInfo = new FenceCreateInfo
|
||||
{
|
||||
sType = VkStructureType.FenceCreateInfo,
|
||||
flags = VkFenceCreateFlags.Signaled
|
||||
SType = StructureType.FenceCreateInfo,
|
||||
Flags = FenceCreateFlags.SignaledBit
|
||||
};
|
||||
|
||||
for (var i = 0; i < 2; i++)
|
||||
{
|
||||
_context.DeviceApi.vkCreateSemaphore(&semaphoreInfo, null, out _imageAvailableSemaphores[i]);
|
||||
_context.DeviceApi.vkCreateSemaphore(&semaphoreInfo, null, out _renderFinishedSemaphores[i]);
|
||||
_context.DeviceApi.vkCreateFence(&fenceInfo, null, out _inFlightFences[i]);
|
||||
Silk.NET.Vulkan.Semaphore imageAvailable, renderFinished;
|
||||
Silk.NET.Vulkan.Fence fence;
|
||||
_context.Vk.CreateSemaphore(_context.Device, &semaphoreInfo, null, &imageAvailable);
|
||||
_context.Vk.CreateSemaphore(_context.Device, &semaphoreInfo, null, &renderFinished);
|
||||
_context.Vk.CreateFence(_context.Device, &fenceInfo, null, &fence);
|
||||
_imageAvailableSemaphores[i] = imageAvailable;
|
||||
_renderFinishedSemaphores[i] = renderFinished;
|
||||
_inFlightFences[i] = fence;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,106 +119,96 @@ public sealed unsafe class TriangleRenderer : IDisposable
|
||||
{
|
||||
var frame = _currentFrame % 2;
|
||||
|
||||
_context.DeviceApi.vkWaitForFences(_inFlightFences[frame], true, ulong.MaxValue);
|
||||
_context.DeviceApi.vkResetFences(_inFlightFences[frame]);
|
||||
var fence = _inFlightFences[frame];
|
||||
_context.Vk.WaitForFences(_context.Device, 1, &fence, true, ulong.MaxValue);
|
||||
_context.Vk.ResetFences(_context.Device, 1, &fence);
|
||||
|
||||
var result = _context.DeviceApi.vkAcquireNextImageKHR(
|
||||
_swapchain.Handle,
|
||||
ulong.MaxValue,
|
||||
_imageAvailableSemaphores[frame],
|
||||
VkFence.Null,
|
||||
out var imageIndex);
|
||||
|
||||
if (result == VkResult.ErrorOutOfDateKHR)
|
||||
uint imageIndex;
|
||||
var result = _context.KhrSwapchain!.AcquireNextImage(_context.Device, _swapchain.Handle, ulong.MaxValue, _imageAvailableSemaphores[frame], new Silk.NET.Vulkan.Fence(), &imageIndex);
|
||||
if (result == Result.ErrorOutOfDateKhr)
|
||||
return;
|
||||
|
||||
var cmd = _commandBuffers[frame];
|
||||
_context.DeviceApi.vkResetCommandBuffer(cmd, VkCommandBufferResetFlags.None);
|
||||
_context.Vk.ResetCommandBuffer(cmd, CommandBufferResetFlags.None);
|
||||
|
||||
var beginInfo = new VkCommandBufferBeginInfo
|
||||
var beginInfo = new CommandBufferBeginInfo
|
||||
{
|
||||
sType = VkStructureType.CommandBufferBeginInfo,
|
||||
flags = VkCommandBufferUsageFlags.OneTimeSubmit
|
||||
SType = StructureType.CommandBufferBeginInfo,
|
||||
Flags = CommandBufferUsageFlags.OneTimeSubmitBit
|
||||
};
|
||||
_context.DeviceApi.vkBeginCommandBuffer(cmd, &beginInfo);
|
||||
_context.Vk.BeginCommandBuffer(cmd, &beginInfo);
|
||||
|
||||
var clearColor = new VkClearValue(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
|
||||
var renderPassInfo = new VkRenderPassBeginInfo
|
||||
var clearColor = new ClearValue(new ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f));
|
||||
var renderPassInfo = new RenderPassBeginInfo
|
||||
{
|
||||
sType = VkStructureType.RenderPassBeginInfo,
|
||||
renderPass = _swapchain.RenderPass,
|
||||
framebuffer = _swapchain.Framebuffers[imageIndex],
|
||||
renderArea = new VkRect2D(0, 0, _swapchain.Extent.width, _swapchain.Extent.height),
|
||||
clearValueCount = 1,
|
||||
pClearValues = &clearColor
|
||||
SType = StructureType.RenderPassBeginInfo,
|
||||
RenderPass = _swapchain.RenderPass,
|
||||
Framebuffer = _swapchain.Framebuffers[imageIndex],
|
||||
RenderArea = new Rect2D(new Offset2D(0, 0), _swapchain.Extent),
|
||||
ClearValueCount = 1,
|
||||
PClearValues = &clearColor
|
||||
};
|
||||
|
||||
_context.DeviceApi.vkCmdBeginRenderPass(cmd, &renderPassInfo, VkSubpassContents.Inline);
|
||||
_context.Vk.CmdBeginRenderPass(cmd, &renderPassInfo, SubpassContents.Inline);
|
||||
_context.Vk.CmdBindPipeline(cmd, PipelineBindPoint.Graphics, _pipeline.Handle);
|
||||
|
||||
_context.DeviceApi.vkCmdBindPipeline(cmd, VkPipelineBindPoint.Graphics, _pipeline.Handle);
|
||||
|
||||
var viewport = new VkViewport(0, 0, _swapchain.Extent.width, _swapchain.Extent.height, 0, 1);
|
||||
var scissor = new VkRect2D(0, 0, _swapchain.Extent.width, _swapchain.Extent.height);
|
||||
_context.DeviceApi.vkCmdSetViewport(cmd, 0, viewport);
|
||||
_context.DeviceApi.vkCmdSetScissor(cmd, 0, scissor);
|
||||
var viewport = new Viewport(0, 0, _swapchain.Extent.Width, _swapchain.Extent.Height, 0, 1);
|
||||
var scissor = new Rect2D(new Offset2D(0, 0), _swapchain.Extent);
|
||||
_context.Vk.CmdSetViewport(cmd, 0, 1, &viewport);
|
||||
_context.Vk.CmdSetScissor(cmd, 0, 1, &scissor);
|
||||
|
||||
var vertexBuffer = _vertexBuffer.Buffer;
|
||||
var offset = 0ul;
|
||||
_context.DeviceApi.vkCmdBindVertexBuffers(cmd, 0, 1, &vertexBuffer, &offset);
|
||||
_context.DeviceApi.vkCmdDraw(cmd, 3, 1, 0, 0);
|
||||
_context.Vk.CmdBindVertexBuffers(cmd, 0, 1, &vertexBuffer, &offset);
|
||||
_context.Vk.CmdDraw(cmd, 3, 1, 0, 0);
|
||||
|
||||
_context.DeviceApi.vkCmdEndRenderPass(cmd);
|
||||
_context.DeviceApi.vkEndCommandBuffer(cmd);
|
||||
_context.Vk.CmdEndRenderPass(cmd);
|
||||
_context.Vk.EndCommandBuffer(cmd);
|
||||
|
||||
var waitSemaphore = _imageAvailableSemaphores[frame];
|
||||
var signalSemaphore = _renderFinishedSemaphores[frame];
|
||||
var stageMask = VkPipelineStageFlags.ColorAttachmentOutput;
|
||||
var submitInfo = new VkSubmitInfo
|
||||
var stageMask = PipelineStageFlags.ColorAttachmentOutputBit;
|
||||
var submitInfo = new SubmitInfo
|
||||
{
|
||||
sType = VkStructureType.SubmitInfo,
|
||||
waitSemaphoreCount = 1,
|
||||
pWaitSemaphores = &waitSemaphore,
|
||||
pWaitDstStageMask = &stageMask,
|
||||
commandBufferCount = 1,
|
||||
pCommandBuffers = &cmd,
|
||||
signalSemaphoreCount = 1,
|
||||
pSignalSemaphores = &signalSemaphore
|
||||
SType = StructureType.SubmitInfo,
|
||||
WaitSemaphoreCount = 1,
|
||||
PWaitSemaphores = &waitSemaphore,
|
||||
PWaitDstStageMask = &stageMask,
|
||||
CommandBufferCount = 1,
|
||||
PCommandBuffers = &cmd,
|
||||
SignalSemaphoreCount = 1,
|
||||
PSignalSemaphores = &signalSemaphore
|
||||
};
|
||||
|
||||
_context.DeviceApi.vkQueueSubmit(_context.GraphicsQueue, 1, &submitInfo, _inFlightFences[frame]);
|
||||
_context.Vk.QueueSubmit(_context.GraphicsQueue, 1, &submitInfo, _inFlightFences[frame]);
|
||||
|
||||
var swapchain = _swapchain.Handle;
|
||||
var presentInfo = new VkPresentInfoKHR
|
||||
var presentInfo = new PresentInfoKHR
|
||||
{
|
||||
sType = VkStructureType.PresentInfoKHR,
|
||||
waitSemaphoreCount = 1,
|
||||
pWaitSemaphores = &signalSemaphore,
|
||||
swapchainCount = 1,
|
||||
pSwapchains = &swapchain,
|
||||
pImageIndices = &imageIndex
|
||||
SType = StructureType.PresentInfoKhr,
|
||||
WaitSemaphoreCount = 1,
|
||||
PWaitSemaphores = &signalSemaphore,
|
||||
SwapchainCount = 1,
|
||||
PSwapchains = &swapchain,
|
||||
PImageIndices = &imageIndex
|
||||
};
|
||||
|
||||
_context.DeviceApi.vkQueuePresentKHR(_context.PresentQueue, &presentInfo);
|
||||
_context.KhrSwapchain!.QueuePresent(_context.PresentQueue, &presentInfo);
|
||||
_currentFrame++;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_context.DeviceApi.vkDeviceWaitIdle();
|
||||
_context.Vk.DeviceWaitIdle(_context.Device);
|
||||
|
||||
for (var i = 0; i < 2; i++)
|
||||
{
|
||||
if (_renderFinishedSemaphores[i] != VkSemaphore.Null)
|
||||
_context.DeviceApi.vkDestroySemaphore(_renderFinishedSemaphores[i]);
|
||||
if (_imageAvailableSemaphores[i] != VkSemaphore.Null)
|
||||
_context.DeviceApi.vkDestroySemaphore(_imageAvailableSemaphores[i]);
|
||||
if (_inFlightFences[i] != VkFence.Null)
|
||||
_context.DeviceApi.vkDestroyFence(_inFlightFences[i]);
|
||||
_context.Vk.DestroySemaphore(_context.Device, _renderFinishedSemaphores[i], null);
|
||||
_context.Vk.DestroySemaphore(_context.Device, _imageAvailableSemaphores[i], null);
|
||||
_context.Vk.DestroyFence(_context.Device, _inFlightFences[i], null);
|
||||
}
|
||||
|
||||
if (_commandPool != VkCommandPool.Null)
|
||||
_context.DeviceApi.vkDestroyCommandPool(_commandPool);
|
||||
_context.Vk.DestroyCommandPool(_context.Device, _commandPool, null);
|
||||
|
||||
_vertexBuffer.Dispose();
|
||||
_pipeline.Dispose();
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
using System;
|
||||
using Vortice.Vulkan;
|
||||
using Silk.NET.Core;
|
||||
using Silk.NET.Vulkan;
|
||||
|
||||
namespace Engine.Graphics;
|
||||
|
||||
/// <summary>
|
||||
/// Interleaved vertex: vec2 position + vec3 color.
|
||||
/// Interleaved vertex buffer: vec2 position + vec3 color.
|
||||
/// Uses Silk.NET.Vulkan.
|
||||
/// </summary>
|
||||
public sealed unsafe class VertexBuffer : IDisposable
|
||||
{
|
||||
private readonly VulkanContext _context;
|
||||
public VkBuffer Buffer { get; }
|
||||
public VkDeviceMemory Memory { get; }
|
||||
public Silk.NET.Vulkan.Buffer Buffer { get; }
|
||||
public DeviceMemory Memory { get; }
|
||||
public ulong Size { get; }
|
||||
|
||||
public VertexBuffer(VulkanContext context, ReadOnlySpan<byte> data)
|
||||
@@ -18,62 +20,66 @@ public sealed unsafe class VertexBuffer : IDisposable
|
||||
_context = context;
|
||||
Size = (ulong)data.Length;
|
||||
|
||||
Buffer = CreateBuffer(Size, VkBufferUsageFlags.VertexBuffer);
|
||||
Buffer = CreateBuffer(Size, BufferUsageFlags.VertexBufferBit);
|
||||
var memoryRequirements = GetMemoryRequirements(Buffer);
|
||||
Memory = AllocateMemory(memoryRequirements, VkMemoryPropertyFlags.HostVisible | VkMemoryPropertyFlags.HostCoherent);
|
||||
Memory = AllocateMemory(memoryRequirements, MemoryPropertyFlags.HostVisibleBit | MemoryPropertyFlags.HostCoherentBit);
|
||||
|
||||
var result = _context.DeviceApi.vkBindBufferMemory(Buffer, Memory, 0);
|
||||
if (result != VkResult.Success)
|
||||
var result = _context.Vk.BindBufferMemory(_context.Device, Buffer, Memory, 0);
|
||||
if (result != Result.Success)
|
||||
throw new InvalidOperationException($"vkBindBufferMemory failed: {result}");
|
||||
|
||||
CopyData(data);
|
||||
}
|
||||
|
||||
private VkBuffer CreateBuffer(ulong size, VkBufferUsageFlags usage)
|
||||
private Silk.NET.Vulkan.Buffer CreateBuffer(ulong size, BufferUsageFlags usage)
|
||||
{
|
||||
var createInfo = new VkBufferCreateInfo
|
||||
var createInfo = new BufferCreateInfo
|
||||
{
|
||||
sType = VkStructureType.BufferCreateInfo,
|
||||
size = size,
|
||||
usage = usage,
|
||||
sharingMode = VkSharingMode.Exclusive
|
||||
SType = StructureType.BufferCreateInfo,
|
||||
Size = size,
|
||||
Usage = usage,
|
||||
SharingMode = SharingMode.Exclusive
|
||||
};
|
||||
|
||||
var result = _context.DeviceApi.vkCreateBuffer(&createInfo, null, out var buffer);
|
||||
if (result != VkResult.Success)
|
||||
Silk.NET.Vulkan.Buffer buffer;
|
||||
var result = _context.Vk.CreateBuffer(_context.Device, &createInfo, null, &buffer);
|
||||
if (result != Result.Success)
|
||||
throw new InvalidOperationException($"vkCreateBuffer failed: {result}");
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private VkMemoryRequirements GetMemoryRequirements(VkBuffer buffer)
|
||||
private MemoryRequirements GetMemoryRequirements(Silk.NET.Vulkan.Buffer buffer)
|
||||
{
|
||||
_context.DeviceApi.vkGetBufferMemoryRequirements(buffer, out var requirements);
|
||||
MemoryRequirements requirements;
|
||||
_context.Vk.GetBufferMemoryRequirements(_context.Device, buffer, &requirements);
|
||||
return requirements;
|
||||
}
|
||||
|
||||
private VkDeviceMemory AllocateMemory(VkMemoryRequirements requirements, VkMemoryPropertyFlags properties)
|
||||
private DeviceMemory AllocateMemory(MemoryRequirements requirements, MemoryPropertyFlags properties)
|
||||
{
|
||||
var memoryTypeIndex = FindMemoryType(requirements.memoryTypeBits, properties);
|
||||
var allocateInfo = new VkMemoryAllocateInfo
|
||||
var memoryTypeIndex = FindMemoryType(requirements.MemoryTypeBits, properties);
|
||||
var allocateInfo = new MemoryAllocateInfo
|
||||
{
|
||||
sType = VkStructureType.MemoryAllocateInfo,
|
||||
allocationSize = requirements.size,
|
||||
memoryTypeIndex = memoryTypeIndex
|
||||
SType = StructureType.MemoryAllocateInfo,
|
||||
AllocationSize = requirements.Size,
|
||||
MemoryTypeIndex = memoryTypeIndex
|
||||
};
|
||||
|
||||
var result = _context.DeviceApi.vkAllocateMemory(&allocateInfo, null, out var memory);
|
||||
if (result != VkResult.Success)
|
||||
DeviceMemory memory;
|
||||
var result = _context.Vk.AllocateMemory(_context.Device, &allocateInfo, null, &memory);
|
||||
if (result != Result.Success)
|
||||
throw new InvalidOperationException($"vkAllocateMemory failed: {result}");
|
||||
return memory;
|
||||
}
|
||||
|
||||
private uint FindMemoryType(uint typeFilter, VkMemoryPropertyFlags properties)
|
||||
private uint FindMemoryType(uint typeFilter, MemoryPropertyFlags properties)
|
||||
{
|
||||
_context.InstanceApi.vkGetPhysicalDeviceMemoryProperties(_context.PhysicalDevice, out var memoryProperties);
|
||||
for (var i = 0; i < memoryProperties.memoryTypeCount; i++)
|
||||
PhysicalDeviceMemoryProperties memoryProperties;
|
||||
_context.Vk.GetPhysicalDeviceMemoryProperties(_context.PhysicalDevice, &memoryProperties);
|
||||
for (var i = 0; i < memoryProperties.MemoryTypeCount; i++)
|
||||
{
|
||||
if ((typeFilter & (1u << i)) != 0 &&
|
||||
(memoryProperties.memoryTypes[i].propertyFlags & properties) == properties)
|
||||
(memoryProperties.MemoryTypes[i].PropertyFlags & properties) == properties)
|
||||
{
|
||||
return (uint)i;
|
||||
}
|
||||
@@ -84,22 +90,22 @@ public sealed unsafe class VertexBuffer : IDisposable
|
||||
private void CopyData(ReadOnlySpan<byte> data)
|
||||
{
|
||||
void* mappedData;
|
||||
var result = _context.DeviceApi.vkMapMemory(Memory, 0, Size, VkMemoryMapFlags.None, &mappedData);
|
||||
if (result != VkResult.Success)
|
||||
var result = _context.Vk.MapMemory(_context.Device, Memory, 0, Size, MemoryMapFlags.None, &mappedData);
|
||||
if (result != Result.Success)
|
||||
throw new InvalidOperationException($"vkMapMemory failed: {result}");
|
||||
|
||||
fixed (byte* src = data)
|
||||
{
|
||||
System.Buffer.MemoryCopy(src, mappedData, (long)Size, data.Length);
|
||||
global::System.Buffer.MemoryCopy(src, mappedData, (long)Size, data.Length);
|
||||
}
|
||||
|
||||
_context.DeviceApi.vkUnmapMemory(Memory);
|
||||
_context.Vk.UnmapMemory(_context.Device, Memory);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_context.DeviceApi.vkDeviceWaitIdle();
|
||||
_context.DeviceApi.vkDestroyBuffer(Buffer);
|
||||
_context.DeviceApi.vkFreeMemory(Memory);
|
||||
_context.Vk.DeviceWaitIdle(_context.Device);
|
||||
_context.Vk.DestroyBuffer(_context.Device, Buffer, null);
|
||||
_context.Vk.FreeMemory(_context.Device, Memory, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,37 +5,42 @@ using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Engine.Core;
|
||||
using SDL;
|
||||
using Vortice.Vulkan;
|
||||
using Silk.NET.Core;
|
||||
using Silk.NET.Core.Native;
|
||||
using Silk.NET.Vulkan;
|
||||
using Silk.NET.Vulkan.Extensions.KHR;
|
||||
|
||||
namespace Engine.Graphics;
|
||||
|
||||
/// <summary>
|
||||
/// Owns the Vulkan instance, physical device, logical device, queues, and API handles.
|
||||
/// Created once per application lifetime.
|
||||
/// Owns the Vulkan instance, physical device, logical device, queues, and surface.
|
||||
/// Uses Silk.NET.Vulkan because Vortice.Vulkan's loader segfaulted on this Kubuntu setup.
|
||||
/// </summary>
|
||||
public sealed unsafe class VulkanContext : IDisposable
|
||||
{
|
||||
private bool _disposed;
|
||||
|
||||
public VkInstance Instance { get; private set; }
|
||||
public VkInstanceApi InstanceApi { get; private set; }
|
||||
public VkPhysicalDevice PhysicalDevice { get; private set; }
|
||||
public VkDevice Device { get; private set; }
|
||||
public VkDeviceApi DeviceApi { get; private set; }
|
||||
public VkQueue GraphicsQueue { get; private set; }
|
||||
public VkQueue PresentQueue { get; private set; }
|
||||
public Vk Vk { get; }
|
||||
public KhrSurface? KhrSurface { get; private set; }
|
||||
public KhrSwapchain? KhrSwapchain { get; private set; }
|
||||
public Instance Instance { get; private set; }
|
||||
public PhysicalDevice PhysicalDevice { get; private set; }
|
||||
public Device Device { get; private set; }
|
||||
public Queue GraphicsQueue { get; private set; }
|
||||
public Queue PresentQueue { get; private set; }
|
||||
public SurfaceKHR Surface { get; private set; }
|
||||
public uint GraphicsFamilyIndex { get; private set; }
|
||||
public uint PresentFamilyIndex { get; private set; }
|
||||
public VkSurfaceKHR Surface { get; private set; }
|
||||
|
||||
public VulkanContext(Sdl3Window window, bool enableValidation = true)
|
||||
{
|
||||
Vk = Vk.GetApi();
|
||||
CreateInstance(window, enableValidation);
|
||||
InstanceApi = Vulkan.GetApi(Instance);
|
||||
LoadInstanceExtensions();
|
||||
CreateSurface(window);
|
||||
PickPhysicalDevice();
|
||||
CreateLogicalDevice();
|
||||
DeviceApi = Vulkan.GetApi(Instance, Device);
|
||||
LoadDeviceExtensions();
|
||||
GetQueues();
|
||||
}
|
||||
|
||||
@@ -51,55 +56,74 @@ public sealed unsafe class VulkanContext : IDisposable
|
||||
? new[] { "VK_LAYER_KHRONOS_validation" }
|
||||
: Array.Empty<string>();
|
||||
|
||||
var appName = VkStringInterop.ConvertToUnmanaged("Cortex Engine");
|
||||
var engineName = VkStringInterop.ConvertToUnmanaged("CortexEngine");
|
||||
var appName = SilkMarshal.StringToMemory("Cortex Engine", NativeStringEncoding.UTF8);
|
||||
var engineName = SilkMarshal.StringToMemory("CortexEngine", NativeStringEncoding.UTF8);
|
||||
var extensionMemory = SilkMarshal.StringArrayToMemory(requiredExtensions, NativeStringEncoding.UTF8);
|
||||
var layerMemory = SilkMarshal.StringArrayToMemory(layerNames, NativeStringEncoding.UTF8);
|
||||
|
||||
var appInfo = new VkApplicationInfo
|
||||
try
|
||||
{
|
||||
sType = VkStructureType.ApplicationInfo,
|
||||
pApplicationName = appName,
|
||||
pEngineName = engineName,
|
||||
apiVersion = VkVersion.Version_1_3
|
||||
};
|
||||
|
||||
using var extensionPin = new StringArrayPin(requiredExtensions);
|
||||
using var layerPin = new StringArrayPin(layerNames);
|
||||
|
||||
{
|
||||
var createInfo = new VkInstanceCreateInfo
|
||||
var appInfo = new ApplicationInfo
|
||||
{
|
||||
sType = VkStructureType.InstanceCreateInfo,
|
||||
pApplicationInfo = &appInfo,
|
||||
enabledExtensionCount = (uint)requiredExtensions.Count,
|
||||
ppEnabledExtensionNames = extensionPin.Pointers,
|
||||
enabledLayerCount = (uint)layerNames.Length,
|
||||
ppEnabledLayerNames = layerPin.Pointers
|
||||
SType = StructureType.ApplicationInfo,
|
||||
PApplicationName = (byte*)appName.Handle,
|
||||
PEngineName = (byte*)engineName.Handle,
|
||||
ApiVersion = Vk.Version13
|
||||
};
|
||||
|
||||
var result = Vulkan.vkCreateInstance(&createInfo, null, out var instance);
|
||||
if (result != VkResult.Success)
|
||||
var createInfo = new InstanceCreateInfo
|
||||
{
|
||||
SType = StructureType.InstanceCreateInfo,
|
||||
PApplicationInfo = &appInfo,
|
||||
EnabledExtensionCount = (uint)requiredExtensions.Count,
|
||||
PpEnabledExtensionNames = (byte**)extensionMemory.Handle,
|
||||
EnabledLayerCount = (uint)layerNames.Length,
|
||||
PpEnabledLayerNames = (byte**)layerMemory.Handle
|
||||
};
|
||||
|
||||
Instance instance;
|
||||
var result = Vk.CreateInstance(&createInfo, null, &instance);
|
||||
if (result != Result.Success)
|
||||
throw new InvalidOperationException($"vkCreateInstance failed: {result}");
|
||||
Instance = instance;
|
||||
}
|
||||
finally
|
||||
{
|
||||
appName.Dispose();
|
||||
engineName.Dispose();
|
||||
extensionMemory.Dispose();
|
||||
layerMemory.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
VkStringInterop.Free(appName);
|
||||
VkStringInterop.Free(engineName);
|
||||
private void LoadInstanceExtensions()
|
||||
{
|
||||
if (!Vk.TryGetInstanceExtension(Instance, out KhrSurface khrSurface))
|
||||
throw new InvalidOperationException("VK_KHR_surface not available.");
|
||||
KhrSurface = khrSurface;
|
||||
}
|
||||
|
||||
private void LoadDeviceExtensions()
|
||||
{
|
||||
if (!Vk.TryGetDeviceExtension(Instance, Device, out KhrSwapchain khrSwapchain))
|
||||
throw new InvalidOperationException("VK_KHR_swapchain not available.");
|
||||
KhrSwapchain = khrSwapchain;
|
||||
}
|
||||
|
||||
private void CreateSurface(Sdl3Window window)
|
||||
{
|
||||
var sdlInstance = (SDL.VkInstance_T*)Instance.Handle;
|
||||
var sdlSurface = (SDL.VkSurfaceKHR_T*)null;
|
||||
var result = SDL3.SDL_Vulkan_CreateSurface(
|
||||
var sdlResult = SDL3.SDL_Vulkan_CreateSurface(
|
||||
(SDL_Window*)window.Handle,
|
||||
sdlInstance,
|
||||
null,
|
||||
&sdlSurface);
|
||||
|
||||
if (result != true)
|
||||
if (sdlResult != true)
|
||||
throw new InvalidOperationException($"SDL_Vulkan_CreateSurface failed: {SDL3.SDL_GetError()}");
|
||||
|
||||
Surface = new VkSurfaceKHR((ulong)sdlSurface);
|
||||
Surface = new SurfaceKHR((ulong)sdlSurface);
|
||||
}
|
||||
|
||||
private void PickPhysicalDevice()
|
||||
@@ -110,93 +134,108 @@ public sealed unsafe class VulkanContext : IDisposable
|
||||
|
||||
foreach (var device in devices)
|
||||
{
|
||||
var properties = InstanceApi.vkGetPhysicalDeviceProperties(device);
|
||||
var properties = Vk.GetPhysicalDeviceProperties(device);
|
||||
var queueFamilies = GetPhysicalDeviceQueueFamilyProperties(device);
|
||||
|
||||
var hasGraphics = false;
|
||||
var hasPresent = false;
|
||||
for (var i = 0; i < queueFamilies.Length; i++)
|
||||
{
|
||||
if (queueFamilies[i].queueFlags.HasFlag(VkQueueFlags.Graphics))
|
||||
if (queueFamilies[i].QueueFlags.HasFlag(QueueFlags.GraphicsBit))
|
||||
hasGraphics = true;
|
||||
var supportResult = InstanceApi.vkGetPhysicalDeviceSurfaceSupportKHR(device, (uint)i, Surface, out VkBool32 supported);
|
||||
if (supportResult == VkResult.Success && supported)
|
||||
|
||||
Bool32 supported;
|
||||
KhrSurface!.GetPhysicalDeviceSurfaceSupport(device, (uint)i, Surface, &supported);
|
||||
if (supported)
|
||||
hasPresent = true;
|
||||
}
|
||||
|
||||
if (hasGraphics && hasPresent)
|
||||
{
|
||||
PhysicalDevice = device;
|
||||
if (properties.deviceType == VkPhysicalDeviceType.DiscreteGpu)
|
||||
if (properties.DeviceType == PhysicalDeviceType.DiscreteGpu)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (PhysicalDevice == VkPhysicalDevice.Null)
|
||||
if (PhysicalDevice.Handle == 0)
|
||||
throw new InvalidOperationException("No suitable Vulkan physical device found.");
|
||||
}
|
||||
|
||||
private VkPhysicalDevice[] EnumeratePhysicalDevices()
|
||||
private PhysicalDevice[] EnumeratePhysicalDevices()
|
||||
{
|
||||
uint count = 0;
|
||||
InstanceApi.vkEnumeratePhysicalDevices(&count, null);
|
||||
Vk.EnumeratePhysicalDevices(Instance, &count, null);
|
||||
if (count == 0)
|
||||
return Array.Empty<VkPhysicalDevice>();
|
||||
return Array.Empty<PhysicalDevice>();
|
||||
|
||||
var devices = new VkPhysicalDevice[count];
|
||||
fixed (VkPhysicalDevice* p = devices)
|
||||
var devices = new PhysicalDevice[count];
|
||||
fixed (PhysicalDevice* p = devices)
|
||||
{
|
||||
var result = InstanceApi.vkEnumeratePhysicalDevices(&count, p);
|
||||
if (result != VkResult.Success)
|
||||
var result = Vk.EnumeratePhysicalDevices(Instance, &count, p);
|
||||
if (result != Result.Success)
|
||||
throw new InvalidOperationException($"vkEnumeratePhysicalDevices failed: {result}");
|
||||
}
|
||||
return devices;
|
||||
}
|
||||
|
||||
private QueueFamilyProperties[] GetPhysicalDeviceQueueFamilyProperties(PhysicalDevice device)
|
||||
{
|
||||
uint count = 0;
|
||||
Vk.GetPhysicalDeviceQueueFamilyProperties(device, &count, null);
|
||||
var properties = new QueueFamilyProperties[count];
|
||||
fixed (QueueFamilyProperties* p = properties)
|
||||
{
|
||||
Vk.GetPhysicalDeviceQueueFamilyProperties(device, &count, p);
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
private void CreateLogicalDevice()
|
||||
{
|
||||
var queueFamilies = GetPhysicalDeviceQueueFamilyProperties(PhysicalDevice);
|
||||
GraphicsFamilyIndex = FindQueueFamilyIndex(queueFamilies, VkQueueFlags.Graphics);
|
||||
GraphicsFamilyIndex = FindQueueFamilyIndex(queueFamilies, QueueFlags.GraphicsBit);
|
||||
PresentFamilyIndex = FindPresentQueueFamilyIndex(queueFamilies);
|
||||
|
||||
var uniqueFamilies = new HashSet<uint> { GraphicsFamilyIndex, PresentFamilyIndex };
|
||||
var queueCreateInfos = uniqueFamilies.Select(family => new VkDeviceQueueCreateInfo
|
||||
var queueCreateInfos = uniqueFamilies.Select(family => new DeviceQueueCreateInfo
|
||||
{
|
||||
sType = VkStructureType.DeviceQueueCreateInfo,
|
||||
queueFamilyIndex = family,
|
||||
queueCount = 1
|
||||
SType = StructureType.DeviceQueueCreateInfo,
|
||||
QueueFamilyIndex = family,
|
||||
QueueCount = 1
|
||||
}).ToArray();
|
||||
|
||||
var priorityHandles = new GCHandle[queueCreateInfos.Length];
|
||||
var extensionNames = new[] { "VK_KHR_swapchain" };
|
||||
using var extensionPin = new StringArrayPin(extensionNames);
|
||||
var extensionMemory = SilkMarshal.StringArrayToMemory(extensionNames, NativeStringEncoding.UTF8);
|
||||
|
||||
var priorityHandles = new GCHandle[queueCreateInfos.Length];
|
||||
try
|
||||
{
|
||||
var deviceFeatures = new VkPhysicalDeviceFeatures();
|
||||
var deviceFeatures = new PhysicalDeviceFeatures();
|
||||
|
||||
for (var i = 0; i < queueCreateInfos.Length; i++)
|
||||
{
|
||||
var priority = new[] { 1.0f };
|
||||
var handle = GCHandle.Alloc(priority, GCHandleType.Pinned);
|
||||
priorityHandles[i] = handle;
|
||||
queueCreateInfos[i].pQueuePriorities = (float*)handle.AddrOfPinnedObject();
|
||||
queueCreateInfos[i].PQueuePriorities = (float*)handle.AddrOfPinnedObject();
|
||||
}
|
||||
|
||||
fixed (VkDeviceQueueCreateInfo* pQueue = queueCreateInfos)
|
||||
fixed (DeviceQueueCreateInfo* pQueue = queueCreateInfos)
|
||||
{
|
||||
var createInfo = new VkDeviceCreateInfo
|
||||
var createInfo = new DeviceCreateInfo
|
||||
{
|
||||
sType = VkStructureType.DeviceCreateInfo,
|
||||
queueCreateInfoCount = (uint)queueCreateInfos.Length,
|
||||
pQueueCreateInfos = pQueue,
|
||||
pEnabledFeatures = &deviceFeatures,
|
||||
enabledExtensionCount = 1,
|
||||
ppEnabledExtensionNames = extensionPin.Pointers
|
||||
SType = StructureType.DeviceCreateInfo,
|
||||
QueueCreateInfoCount = (uint)queueCreateInfos.Length,
|
||||
PQueueCreateInfos = pQueue,
|
||||
PEnabledFeatures = &deviceFeatures,
|
||||
EnabledExtensionCount = 1,
|
||||
PpEnabledExtensionNames = (byte**)extensionMemory.Handle
|
||||
};
|
||||
|
||||
var result = InstanceApi.vkCreateDevice(PhysicalDevice, &createInfo, null, out var device);
|
||||
if (result != VkResult.Success)
|
||||
Device device;
|
||||
var result = Vk.CreateDevice(PhysicalDevice, &createInfo, null, &device);
|
||||
if (result != Result.Success)
|
||||
throw new InvalidOperationException($"vkCreateDevice failed: {result}");
|
||||
Device = device;
|
||||
}
|
||||
@@ -208,101 +247,55 @@ public sealed unsafe class VulkanContext : IDisposable
|
||||
if (handle.IsAllocated)
|
||||
handle.Free();
|
||||
}
|
||||
extensionMemory.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void GetQueues()
|
||||
{
|
||||
DeviceApi.vkGetDeviceQueue(GraphicsFamilyIndex, 0, out var graphicsQueue);
|
||||
DeviceApi.vkGetDeviceQueue(PresentFamilyIndex, 0, out var presentQueue);
|
||||
Queue graphicsQueue;
|
||||
Vk.GetDeviceQueue(Device, GraphicsFamilyIndex, 0, &graphicsQueue);
|
||||
GraphicsQueue = graphicsQueue;
|
||||
|
||||
Queue presentQueue;
|
||||
Vk.GetDeviceQueue(Device, PresentFamilyIndex, 0, &presentQueue);
|
||||
PresentQueue = presentQueue;
|
||||
}
|
||||
|
||||
private uint FindQueueFamilyIndex(VkQueueFamilyProperties[] properties, VkQueueFlags flags)
|
||||
private uint FindQueueFamilyIndex(QueueFamilyProperties[] properties, QueueFlags flags)
|
||||
{
|
||||
for (var i = 0; i < properties.Length; i++)
|
||||
{
|
||||
if (properties[i].queueFlags.HasFlag(flags))
|
||||
if (properties[i].QueueFlags.HasFlag(flags))
|
||||
return (uint)i;
|
||||
}
|
||||
throw new InvalidOperationException($"No queue family with flags {flags} found.");
|
||||
}
|
||||
|
||||
private uint FindPresentQueueFamilyIndex(VkQueueFamilyProperties[] properties)
|
||||
private uint FindPresentQueueFamilyIndex(QueueFamilyProperties[] properties)
|
||||
{
|
||||
for (var i = 0; i < properties.Length; i++)
|
||||
{
|
||||
var supportResult = InstanceApi.vkGetPhysicalDeviceSurfaceSupportKHR(PhysicalDevice, (uint)i, Surface, out VkBool32 supported);
|
||||
if (supportResult == VkResult.Success && supported)
|
||||
Bool32 supported;
|
||||
KhrSurface!.GetPhysicalDeviceSurfaceSupport(PhysicalDevice, (uint)i, Surface, &supported);
|
||||
if (supported)
|
||||
return (uint)i;
|
||||
}
|
||||
throw new InvalidOperationException("No present queue family found.");
|
||||
}
|
||||
|
||||
private VkQueueFamilyProperties[] GetPhysicalDeviceQueueFamilyProperties(VkPhysicalDevice device)
|
||||
{
|
||||
uint count = 0;
|
||||
InstanceApi.vkGetPhysicalDeviceQueueFamilyProperties(device, &count, null);
|
||||
var properties = new VkQueueFamilyProperties[count];
|
||||
fixed (VkQueueFamilyProperties* p = properties)
|
||||
{
|
||||
InstanceApi.vkGetPhysicalDeviceQueueFamilyProperties(device, &count, p);
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
private sealed unsafe class StringArrayPin : IDisposable
|
||||
{
|
||||
public byte** Pointers;
|
||||
private readonly GCHandle[] _handles;
|
||||
|
||||
public StringArrayPin(IReadOnlyList<string> strings)
|
||||
{
|
||||
if (strings.Count == 0)
|
||||
{
|
||||
Pointers = null;
|
||||
_handles = Array.Empty<GCHandle>();
|
||||
return;
|
||||
}
|
||||
|
||||
Pointers = (byte**)Marshal.AllocHGlobal(strings.Count * sizeof(byte*));
|
||||
_handles = new GCHandle[strings.Count];
|
||||
|
||||
for (var i = 0; i < strings.Count; i++)
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes(strings[i] + '\0');
|
||||
_handles[i] = GCHandle.Alloc(bytes, GCHandleType.Pinned);
|
||||
Pointers[i] = (byte*)_handles[i].AddrOfPinnedObject();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Pointers == null)
|
||||
return;
|
||||
|
||||
foreach (var handle in _handles)
|
||||
{
|
||||
if (handle.IsAllocated)
|
||||
handle.Free();
|
||||
}
|
||||
|
||||
Marshal.FreeHGlobal((nint)Pointers);
|
||||
Pointers = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
|
||||
if (Device != VkDevice.Null)
|
||||
DeviceApi.vkDestroyDevice();
|
||||
if (Instance != VkInstance.Null && Surface != VkSurfaceKHR.Null)
|
||||
InstanceApi.vkDestroySurfaceKHR(Surface);
|
||||
if (Instance != VkInstance.Null)
|
||||
InstanceApi.vkDestroyInstance();
|
||||
Vk.DeviceWaitIdle(Device);
|
||||
if (Device.Handle != 0)
|
||||
Vk.DestroyDevice(Device, null);
|
||||
if (Surface.Handle != 0)
|
||||
KhrSurface?.DestroySurface(Instance, Surface, null);
|
||||
if (Instance.Handle != 0)
|
||||
Vk.DestroyInstance(Instance, null);
|
||||
Vk.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
using System;
|
||||
using Vortice.Vulkan;
|
||||
using Silk.NET.Core.Native;
|
||||
using Silk.NET.Vulkan;
|
||||
|
||||
namespace Engine.Graphics;
|
||||
|
||||
/// <summary>
|
||||
/// A simple graphics pipeline for a single vertex/fragment shader pair.
|
||||
/// Assumes a triangle with vec2 position + vec3 color per vertex.
|
||||
/// Simple graphics pipeline for a triangle with vec2 position + vec3 color.
|
||||
/// Uses Silk.NET.Vulkan.
|
||||
/// </summary>
|
||||
public sealed unsafe class VulkanPipeline : IDisposable
|
||||
{
|
||||
private readonly VulkanContext _context;
|
||||
private readonly Swapchain _swapchain;
|
||||
|
||||
public VkPipeline Handle { get; }
|
||||
public VkPipelineLayout Layout { get; }
|
||||
private readonly VkShaderModule _vertexModule;
|
||||
private readonly VkShaderModule _fragmentModule;
|
||||
public Pipeline Handle { get; }
|
||||
public PipelineLayout Layout { get; }
|
||||
private readonly ShaderModule _vertexModule;
|
||||
private readonly ShaderModule _fragmentModule;
|
||||
|
||||
public VulkanPipeline(VulkanContext context, Swapchain swapchain)
|
||||
{
|
||||
@@ -29,7 +30,7 @@ public sealed unsafe class VulkanPipeline : IDisposable
|
||||
Handle = CreateGraphicsPipeline();
|
||||
}
|
||||
|
||||
private VkShaderModule CreateShaderModule(string resourceName)
|
||||
private ShaderModule CreateShaderModule(string resourceName)
|
||||
{
|
||||
var code = ShaderLoader.Load(resourceName);
|
||||
if (code.Length % 4 != 0)
|
||||
@@ -37,189 +38,185 @@ public sealed unsafe class VulkanPipeline : IDisposable
|
||||
|
||||
fixed (byte* pCode = code)
|
||||
{
|
||||
var createInfo = new VkShaderModuleCreateInfo
|
||||
var createInfo = new ShaderModuleCreateInfo
|
||||
{
|
||||
sType = VkStructureType.ShaderModuleCreateInfo,
|
||||
codeSize = (nuint)code.Length,
|
||||
pCode = (uint*)pCode
|
||||
SType = StructureType.ShaderModuleCreateInfo,
|
||||
CodeSize = (nuint)code.Length,
|
||||
PCode = (uint*)pCode
|
||||
};
|
||||
|
||||
var result = _context.DeviceApi.vkCreateShaderModule(&createInfo, null, out var module);
|
||||
if (result != VkResult.Success)
|
||||
ShaderModule module;
|
||||
var result = _context.Vk.CreateShaderModule(_context.Device, &createInfo, null, &module);
|
||||
if (result != Result.Success)
|
||||
throw new InvalidOperationException($"vkCreateShaderModule failed for {resourceName}: {result}");
|
||||
return module;
|
||||
}
|
||||
}
|
||||
|
||||
private VkPipelineLayout CreatePipelineLayout()
|
||||
private PipelineLayout CreatePipelineLayout()
|
||||
{
|
||||
var createInfo = new VkPipelineLayoutCreateInfo
|
||||
var createInfo = new PipelineLayoutCreateInfo
|
||||
{
|
||||
sType = VkStructureType.PipelineLayoutCreateInfo,
|
||||
setLayoutCount = 0,
|
||||
pushConstantRangeCount = 0
|
||||
SType = StructureType.PipelineLayoutCreateInfo,
|
||||
SetLayoutCount = 0,
|
||||
PushConstantRangeCount = 0
|
||||
};
|
||||
|
||||
var result = _context.DeviceApi.vkCreatePipelineLayout(&createInfo, null, out var layout);
|
||||
if (result != VkResult.Success)
|
||||
PipelineLayout layout;
|
||||
var result = _context.Vk.CreatePipelineLayout(_context.Device, &createInfo, null, &layout);
|
||||
if (result != Result.Success)
|
||||
throw new InvalidOperationException($"vkCreatePipelineLayout failed: {result}");
|
||||
return layout;
|
||||
}
|
||||
|
||||
private VkPipeline CreateGraphicsPipeline()
|
||||
private Pipeline CreateGraphicsPipeline()
|
||||
{
|
||||
var entryName = SilkMarshal.StringToPtr("main", NativeStringEncoding.UTF8);
|
||||
var stages = new[]
|
||||
{
|
||||
new VkPipelineShaderStageCreateInfo
|
||||
new PipelineShaderStageCreateInfo
|
||||
{
|
||||
sType = VkStructureType.PipelineShaderStageCreateInfo,
|
||||
stage = VkShaderStageFlags.Vertex,
|
||||
module = _vertexModule,
|
||||
pName = VkStringInterop.ConvertToUnmanaged("main")
|
||||
SType = StructureType.PipelineShaderStageCreateInfo,
|
||||
Stage = ShaderStageFlags.VertexBit,
|
||||
Module = _vertexModule,
|
||||
PName = (byte*)entryName
|
||||
},
|
||||
new VkPipelineShaderStageCreateInfo
|
||||
new PipelineShaderStageCreateInfo
|
||||
{
|
||||
sType = VkStructureType.PipelineShaderStageCreateInfo,
|
||||
stage = VkShaderStageFlags.Fragment,
|
||||
module = _fragmentModule,
|
||||
pName = VkStringInterop.ConvertToUnmanaged("main")
|
||||
SType = StructureType.PipelineShaderStageCreateInfo,
|
||||
Stage = ShaderStageFlags.FragmentBit,
|
||||
Module = _fragmentModule,
|
||||
PName = (byte*)entryName
|
||||
}
|
||||
};
|
||||
|
||||
var bindingDescription = new VkVertexInputBindingDescription
|
||||
var bindingDescription = new VertexInputBindingDescription
|
||||
{
|
||||
binding = 0,
|
||||
stride = (uint)(5 * sizeof(float)),
|
||||
inputRate = VkVertexInputRate.Vertex
|
||||
Binding = 0,
|
||||
Stride = (uint)(5 * sizeof(float)),
|
||||
InputRate = VertexInputRate.Vertex
|
||||
};
|
||||
|
||||
var attributeDescriptions = new[]
|
||||
{
|
||||
new VkVertexInputAttributeDescription
|
||||
new VertexInputAttributeDescription
|
||||
{
|
||||
binding = 0,
|
||||
location = 0,
|
||||
format = VkFormat.R32G32Sfloat,
|
||||
offset = 0
|
||||
Binding = 0,
|
||||
Location = 0,
|
||||
Format = Format.R32G32Sfloat,
|
||||
Offset = 0
|
||||
},
|
||||
new VkVertexInputAttributeDescription
|
||||
new VertexInputAttributeDescription
|
||||
{
|
||||
binding = 0,
|
||||
location = 1,
|
||||
format = VkFormat.R32G32B32Sfloat,
|
||||
offset = (uint)(2 * sizeof(float))
|
||||
Binding = 0,
|
||||
Location = 1,
|
||||
Format = Format.R32G32B32Sfloat,
|
||||
Offset = (uint)(2 * sizeof(float))
|
||||
}
|
||||
};
|
||||
|
||||
VkPipelineVertexInputStateCreateInfo vertexInputInfo;
|
||||
fixed (VkVertexInputAttributeDescription* pAttributes = attributeDescriptions)
|
||||
PipelineVertexInputStateCreateInfo vertexInputInfo;
|
||||
fixed (VertexInputAttributeDescription* pAttributes = attributeDescriptions)
|
||||
{
|
||||
vertexInputInfo = new VkPipelineVertexInputStateCreateInfo
|
||||
vertexInputInfo = new PipelineVertexInputStateCreateInfo
|
||||
{
|
||||
sType = VkStructureType.PipelineVertexInputStateCreateInfo,
|
||||
vertexBindingDescriptionCount = 1,
|
||||
pVertexBindingDescriptions = &bindingDescription,
|
||||
vertexAttributeDescriptionCount = (uint)attributeDescriptions.Length,
|
||||
pVertexAttributeDescriptions = pAttributes
|
||||
SType = StructureType.PipelineVertexInputStateCreateInfo,
|
||||
VertexBindingDescriptionCount = 1,
|
||||
PVertexBindingDescriptions = &bindingDescription,
|
||||
VertexAttributeDescriptionCount = (uint)attributeDescriptions.Length,
|
||||
PVertexAttributeDescriptions = pAttributes
|
||||
};
|
||||
}
|
||||
|
||||
var inputAssembly = new VkPipelineInputAssemblyStateCreateInfo
|
||||
var inputAssembly = new PipelineInputAssemblyStateCreateInfo
|
||||
{
|
||||
sType = VkStructureType.PipelineInputAssemblyStateCreateInfo,
|
||||
topology = VkPrimitiveTopology.TriangleList,
|
||||
primitiveRestartEnable = false
|
||||
SType = StructureType.PipelineInputAssemblyStateCreateInfo,
|
||||
Topology = PrimitiveTopology.TriangleList,
|
||||
PrimitiveRestartEnable = false
|
||||
};
|
||||
|
||||
var viewport = new VkViewport(0, 0, _swapchain.Extent.width, _swapchain.Extent.height, 0, 1);
|
||||
var scissor = new VkRect2D(0, 0, _swapchain.Extent.width, _swapchain.Extent.height);
|
||||
|
||||
var viewportState = new VkPipelineViewportStateCreateInfo
|
||||
var viewportState = new PipelineViewportStateCreateInfo
|
||||
{
|
||||
sType = VkStructureType.PipelineViewportStateCreateInfo,
|
||||
viewportCount = 1,
|
||||
pViewports = &viewport,
|
||||
scissorCount = 1,
|
||||
pScissors = &scissor
|
||||
SType = StructureType.PipelineViewportStateCreateInfo,
|
||||
ViewportCount = 1,
|
||||
ScissorCount = 1
|
||||
};
|
||||
|
||||
var rasterizer = new VkPipelineRasterizationStateCreateInfo
|
||||
var rasterizer = new PipelineRasterizationStateCreateInfo
|
||||
{
|
||||
sType = VkStructureType.PipelineRasterizationStateCreateInfo,
|
||||
polygonMode = VkPolygonMode.Fill,
|
||||
cullMode = VkCullModeFlags.None,
|
||||
frontFace = VkFrontFace.Clockwise,
|
||||
lineWidth = 1.0f
|
||||
SType = StructureType.PipelineRasterizationStateCreateInfo,
|
||||
PolygonMode = PolygonMode.Fill,
|
||||
CullMode = CullModeFlags.None,
|
||||
FrontFace = FrontFace.Clockwise,
|
||||
LineWidth = 1.0f
|
||||
};
|
||||
|
||||
var multisampling = new VkPipelineMultisampleStateCreateInfo
|
||||
var multisampling = new PipelineMultisampleStateCreateInfo
|
||||
{
|
||||
sType = VkStructureType.PipelineMultisampleStateCreateInfo,
|
||||
rasterizationSamples = VkSampleCountFlags.Count1,
|
||||
sampleShadingEnable = false
|
||||
SType = StructureType.PipelineMultisampleStateCreateInfo,
|
||||
RasterizationSamples = SampleCountFlags.Count1Bit,
|
||||
SampleShadingEnable = false
|
||||
};
|
||||
|
||||
var colorBlendAttachment = new VkPipelineColorBlendAttachmentState
|
||||
var colorBlendAttachment = new PipelineColorBlendAttachmentState
|
||||
{
|
||||
colorWriteMask = VkColorComponentFlags.R | VkColorComponentFlags.G | VkColorComponentFlags.B | VkColorComponentFlags.A
|
||||
ColorWriteMask = ColorComponentFlags.RBit | ColorComponentFlags.GBit | ColorComponentFlags.BBit | ColorComponentFlags.ABit
|
||||
};
|
||||
|
||||
var colorBlending = new VkPipelineColorBlendStateCreateInfo
|
||||
var colorBlending = new PipelineColorBlendStateCreateInfo
|
||||
{
|
||||
sType = VkStructureType.PipelineColorBlendStateCreateInfo,
|
||||
attachmentCount = 1,
|
||||
pAttachments = &colorBlendAttachment
|
||||
SType = StructureType.PipelineColorBlendStateCreateInfo,
|
||||
AttachmentCount = 1,
|
||||
PAttachments = &colorBlendAttachment
|
||||
};
|
||||
|
||||
var dynamicStates = new[] { VkDynamicState.Viewport, VkDynamicState.Scissor };
|
||||
VkPipelineDynamicStateCreateInfo dynamicState;
|
||||
fixed (VkDynamicState* pDynamic = dynamicStates)
|
||||
var dynamicStates = new[] { DynamicState.Viewport, DynamicState.Scissor };
|
||||
PipelineDynamicStateCreateInfo dynamicState;
|
||||
fixed (DynamicState* pDynamic = dynamicStates)
|
||||
{
|
||||
dynamicState = new VkPipelineDynamicStateCreateInfo
|
||||
dynamicState = new PipelineDynamicStateCreateInfo
|
||||
{
|
||||
sType = VkStructureType.PipelineDynamicStateCreateInfo,
|
||||
dynamicStateCount = (uint)dynamicStates.Length,
|
||||
pDynamicStates = pDynamic
|
||||
SType = StructureType.PipelineDynamicStateCreateInfo,
|
||||
DynamicStateCount = (uint)dynamicStates.Length,
|
||||
PDynamicStates = pDynamic
|
||||
};
|
||||
}
|
||||
|
||||
VkPipeline pipeline;
|
||||
fixed (VkPipelineShaderStageCreateInfo* pStages = stages)
|
||||
Pipeline pipeline;
|
||||
fixed (PipelineShaderStageCreateInfo* pStages = stages)
|
||||
{
|
||||
var createInfo = new VkGraphicsPipelineCreateInfo
|
||||
var createInfo = new GraphicsPipelineCreateInfo
|
||||
{
|
||||
sType = VkStructureType.GraphicsPipelineCreateInfo,
|
||||
stageCount = (uint)stages.Length,
|
||||
pStages = pStages,
|
||||
pVertexInputState = &vertexInputInfo,
|
||||
pInputAssemblyState = &inputAssembly,
|
||||
pViewportState = &viewportState,
|
||||
pRasterizationState = &rasterizer,
|
||||
pMultisampleState = &multisampling,
|
||||
pColorBlendState = &colorBlending,
|
||||
pDynamicState = &dynamicState,
|
||||
layout = Layout,
|
||||
renderPass = _swapchain.RenderPass,
|
||||
subpass = 0
|
||||
SType = StructureType.GraphicsPipelineCreateInfo,
|
||||
StageCount = (uint)stages.Length,
|
||||
PStages = pStages,
|
||||
PVertexInputState = &vertexInputInfo,
|
||||
PInputAssemblyState = &inputAssembly,
|
||||
PViewportState = &viewportState,
|
||||
PRasterizationState = &rasterizer,
|
||||
PMultisampleState = &multisampling,
|
||||
PColorBlendState = &colorBlending,
|
||||
PDynamicState = &dynamicState,
|
||||
Layout = Layout,
|
||||
RenderPass = _swapchain.RenderPass,
|
||||
Subpass = 0
|
||||
};
|
||||
|
||||
var result = _context.DeviceApi.vkCreateGraphicsPipelines(VkPipelineCache.Null, 1, &createInfo, null, &pipeline);
|
||||
if (result != VkResult.Success)
|
||||
var result = _context.Vk.CreateGraphicsPipelines(_context.Device, default, 1, &createInfo, null, &pipeline);
|
||||
if (result != Result.Success)
|
||||
throw new InvalidOperationException($"vkCreateGraphicsPipelines failed: {result}");
|
||||
}
|
||||
|
||||
VkStringInterop.Free(stages[0].pName);
|
||||
VkStringInterop.Free(stages[1].pName);
|
||||
|
||||
SilkMarshal.FreeString(entryName, NativeStringEncoding.UTF8);
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_context.DeviceApi.vkDeviceWaitIdle();
|
||||
_context.DeviceApi.vkDestroyPipeline(Handle);
|
||||
_context.DeviceApi.vkDestroyPipelineLayout(Layout);
|
||||
_context.DeviceApi.vkDestroyShaderModule(_vertexModule);
|
||||
_context.DeviceApi.vkDestroyShaderModule(_fragmentModule);
|
||||
_context.Vk.DeviceWaitIdle(_context.Device);
|
||||
_context.Vk.DestroyPipeline(_context.Device, Handle, null);
|
||||
_context.Vk.DestroyPipelineLayout(_context.Device, Layout, null);
|
||||
_context.Vk.DestroyShaderModule(_context.Device, _vertexModule, null);
|
||||
_context.Vk.DestroyShaderModule(_context.Device, _fragmentModule, null);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user