fix: migrate to Silk.NET.Vulkan so the window actually runs on Kubuntu

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