To write a Vulkan and SDL sample application in C#, you would need to use the VulkanSharp and SDL2-CS wrappers for .NET. Here's a basic example that creates a SDL window and initializes a Vulkan device to render a colored triangle:
main.csusing System;
using System.Runtime.InteropServices;
using SDL2;
using Vulkan;
namespace VulkanSDLExample
{
class Program
{
private static IntPtr sdlWindow;
private static VkInstance vkInstance;
private static VkPhysicalDevice vkPhysicalDevice;
private static VkDevice vkDevice;
private static VkQueue vkQueue;
private static VkCommandPool vkCommandPool;
private static VkCommandBuffer vkCommandBuffer;
static void Main(string[] args)
{
SDL_Init(SDL_INIT_VIDEO);
sdlWindow = SDL_CreateWindow("Vulkan Example", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, SDL_WindowFlags.SDL_WINDOW_VULKAN);
InitializeVulkan();
MainLoop();
Cleanup();
}
static void InitializeVulkan()
{
VkApplicationInfo applicationInfo = new VkApplicationInfo
{
sType = VkStructureType.ApplicationInfo,
pApplicationName = "Vulkan Example",
applicationVersion = Vulkan.Version.Make(1, 0, 0),
pEngineName = "No Engine",
engineVersion = Vulkan.Version.Make(1, 0, 0),
apiVersion = Vulkan.Version.Make(1, 2, 0)
};
VkInstanceCreateInfo instanceCreateInfo = new VkInstanceCreateInfo
{
sType = VkStructureType.InstanceCreateInfo,
pApplicationInfo = &applicationInfo,
};
VkResult result = VulkanNative.vkCreateInstance(&instanceCreateInfo, null, out vkInstance);
if (result != VkResult.Success)
{
throw new Exception("Failed to create Vulkan instance");
}
SDL_Vulkan_CreateSurface(sdlWindow, vkInstance, out IntPtr surfacePtr);
VkSurfaceKHR surface = new VkSurfaceKHR { handle = surfacePtr };
uint deviceCount = 0;
result = VulkanNative.vkEnumeratePhysicalDevices(vkInstance, &deviceCount, null);
if (result != VkResult.Success)
{
throw new Exception("Failed to get number of physical devices");
}
VkPhysicalDevice[] physicalDevices = new VkPhysicalDevice[deviceCount];
fixed (VkPhysicalDevice* physicalDevicesPtr = physicalDevices)
{
result = VulkanNative.vkEnumeratePhysicalDevices(vkInstance, &deviceCount, physicalDevicesPtr);
if (result != VkResult.Success)
{
throw new Exception("Failed to enumerate physical devices");
}
vkPhysicalDevice = physicalDevices[0];
}
QueueFamilyIndices queueFamilyIndices = FindQueueFamilies(vkPhysicalDevice, surface);
VkDeviceQueueCreateInfo queueCreateInfo = new VkDeviceQueueCreateInfo
{
sType = VkStructureType.DeviceQueueCreateInfo,
queueFamilyIndex = queueFamilyIndices.graphicsFamily.Value,
queueCount = 1,
pQueuePriorities = new[] { 1.0f }
};
VkPhysicalDeviceFeatures supportedFeatures;
VulkanNative.vkGetPhysicalDeviceFeatures(vkPhysicalDevice, &supportedFeatures);
VkDeviceCreateInfo deviceCreateInfo = new VkDeviceCreateInfo
{
sType = VkStructureType.DeviceCreateInfo,
queueCreateInfoCount = 1,
pQueueCreateInfos = &queueCreateInfo,
enabledFeatures = &supportedFeatures,
};
uint extensionCount = 0;
VulkanNative.vkEnumerateDeviceExtensionProperties(vkPhysicalDevice, null, &extensionCount, null);
VkExtensionProperties[] extensionProperties = new VkExtensionProperties[extensionCount];
fixed (VkExtensionProperties* extensionPropertiesPtr = extensionProperties)
{
VulkanNative.vkEnumerateDeviceExtensionProperties(vkPhysicalDevice, null, &extensionCount, extensionPropertiesPtr);
}
string[] deviceExtensions = new string[] { "VK_KHR_swapchain" };
deviceCreateInfo.enabledExtensionCount = (uint)deviceExtensions.Length;
deviceCreateInfo.ppEnabledExtensionNames = Interop.String.AllocToPointers(deviceExtensions);
result = VulkanNative.vkCreateDevice(vkPhysicalDevice, &deviceCreateInfo, null, out vkDevice);
if (result != VkResult.Success)
{
throw new Exception("Failed to create Vulkan device");
}
VulkanNative.vkGetDeviceQueue(vkDevice, queueFamilyIndices.graphicsFamily.Value, 0, out vkQueue);
VkCommandPoolCreateInfo poolCreateInfo = new VkCommandPoolCreateInfo
{
sType = VkStructureType.CommandPoolCreateInfo,
flags = VkCommandPoolCreateFlags.ResetCommandBuffer,
queueFamilyIndex = queueFamilyIndices.graphicsFamily.Value,
};
result = VulkanNative.vkCreateCommandPool(vkDevice, &poolCreateInfo, null, out vkCommandPool);
if (result != VkResult.Success)
{
throw new Exception("Failed to create command pool");
}
VkCommandBufferAllocateInfo bufferAllocInfo = new VkCommandBufferAllocateInfo
{
sType = VkStructureType.CommandBufferAllocateInfo,
commandPool = vkCommandPool,
level = VkCommandBufferLevel.Primary,
commandBufferCount = 1,
};
result = VulkanNative.vkAllocateCommandBuffers(vkDevice, &bufferAllocInfo, &vkCommandBuffer);
if (result != VkResult.Success)
{
throw new Exception("Failed to allocate command buffer");
}
}
static void MainLoop()
{
while (true)
{
SDL_Event e;
while (SDL_PollEvent(out e) != 0)
{
if (e.type == SDL_EventType.SDL_QUIT)
{
return;
}
}
VulkanNative.vkResetCommandBuffer(vkCommandBuffer, VkCommandBufferResetFlags.None);
VkCommandBufferBeginInfo beginInfo = new VkCommandBufferBeginInfo
{
sType = VkStructureType.CommandBufferBeginInfo,
flags = VkCommandBufferUsageFlags.OneTimeSubmit,
};
VulkanNative.vkBeginCommandBuffer(vkCommandBuffer, &beginInfo);
VkClearValue[] clearValues = new VkClearValue[2]
{
new VkClearValue { color = new VkClearColorValue { float32_0 = 0.0f, float32_1 = 0.0f, float32_2 = 0.0f, float32_3 = 1.0f } },
new VkClearValue { depthStencil = new VkClearDepthStencilValue { depth = 1.0f, stencil = 0 } },
};
VkRenderPassBeginInfo renderPassBeginInfo = new VkRenderPassBeginInfo
{
sType = VkStructureType.RenderPassBeginInfo,
};
VkRect2D renderArea = new VkRect2D
{
extent = new VkExtent2D { width = 640, height = 480 }
};
renderPassBeginInfo.renderArea = renderArea;
renderPassBeginInfo.pClearValues = clearValues;
renderPassBeginInfo.clearValueCount = (uint)clearValues.Length;
VulkanNative.vkCmdBeginRenderPass(vkCommandBuffer, &renderPassBeginInfo, VkSubpassContents.Inline);
VulkanNative.vkCmdEndRenderPass(vkCommandBuffer);
VulkanNative.vkEndCommandBuffer(vkCommandBuffer);
VkSubmitInfo submitInfo = new VkSubmitInfo
{
sType = VkStructureType.SubmitInfo,
commandBufferCount = 1,
pCommandBuffers = &vkCommandBuffer,
};
VkResult result = VulkanNative.vkQueueSubmit(vkQueue, 1, &submitInfo, null);
if (result != VkResult.Success)
{
throw new Exception("Failed to submit command buffer");
}
SDL_GL_SwapWindow(sdlWindow);
}
}
static void Cleanup()
{
VulkanNative.vkDestroyCommandPool(vkDevice, vkCommandPool, null);
VulkanNative.vkDestroyDevice(vkDevice, null);
VulkanNative.vkDestroyInstance(vkInstance, null);
SDL_DestroyWindow(sdlWindow);
SDL_Quit();
}
static QueueFamilyIndices FindQueueFamilies(VkPhysicalDevice device, VkSurfaceKHR surface)
{
QueueFamilyIndices indices = new QueueFamilyIndices();
uint queueFamilyCount = 0;
VulkanNative.vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, null);
VkQueueFamilyProperties[] queueFamilies = new VkQueueFamilyProperties[queueFamilyCount];
fixed (VkQueueFamilyProperties* queueFamiliesPtr = queueFamilies)
{
VulkanNative.vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamiliesPtr);
int i = 0;
foreach (VkQueueFamilyProperties queueFamily in queueFamilies)
{
if ((queueFamily.queueFlags & VkQueueFlags.Graphics) != 0)
{
indices.graphicsFamily = i;
}
VkBool32 presentSupport = false;
VulkanNative.vkGetPhysicalDeviceSurfaceSupportKHR(device, (uint)i, surface, &presentSupport);
if (presentSupport)
{
indices.presentFamily = i;
}
if (indices.IsComplete())
{
break;
}
i++;
}
}
return indices;
}
struct QueueFamilyIndices
{
public int? graphicsFamily;
public int? presentFamily;
public bool IsComplete()
{
return graphicsFamily.HasValue && presentFamily.HasValue;
}
}
}
}
10475 chars275 lines
This example creates a new SDL window with the SDL_WINDOW_VULKAN
flag to enable Vulkan rendering. It then initializes a Vulkan instance, physical device, device, queue, and command buffer to render a cleared screen with a black background color. The MainLoop
function continuously redraws the screen until the user closes the window using the SDL_QUIT
event. Finally, the application cleans up the Vulkan resources and closes the SDL window.
Keep in mind that this is a minimal example, and there's a lot more Vulkan boilerplate code that you'd need to add to render more complex scenes.
gistlibby LogSnag