wl-graphics-demo

wl-graphics-demo.git
git clone git://git.lenczewski.org/wl-graphics-demo.git
Log | Files | Refs | README

commit ea788935bfaf799f34d659f9943f225529bbc374
Author: MikoĊ‚aj Lenczewski <mikolaj@lenczewski.org>
Date:   Mon, 12 Oct 2026 01:44:10 +0100

Initial commit

Diffstat:
A.gitignore | 2++
ANOTES | 2++
AREADME | 3+++
Abuild.sh | 30++++++++++++++++++++++++++++++
Aclean.sh | 5+++++
Ashader.frag | 8++++++++
Ashader.vert | 20++++++++++++++++++++
Avulkan.c | 718+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Awl-egl.c | 214+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Awl-vk.c | 382+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
10 files changed, 1384 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,2 @@ +bin/ +obj/ diff --git a/NOTES b/NOTES @@ -0,0 +1,2 @@ +- https://docs.vulkan.org/tutorial/latest/00_Introduction.html (AI slop) +- https://docs.vulkan.org/guide/latest/common_pitfalls.html diff --git a/README b/README @@ -0,0 +1,3 @@ +wl-graphics-demo +============================================================================== +Simple demo implementations of egl and vulkan drawing under wayland. diff --git a/build.sh b/build.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +CC="${CC:-clang}" + +WARNINGS="-Wall -Wextra ${WERROR:+-Werror} -Wno-unused-parameter" +CFLAGS="-std=c11 -O0 -g3" +LDFLAGS="-Wl,--as-needed" + +PROTOCOLS="/usr/share/wayland-protocols" +WL_SCANNER="$(which wayland-scanner)" + +GLSLC="$(which glslc)" + +set -ex + +mkdir -p bin obj + +$WL_SCANNER client-header $PROTOCOLS/stable/xdg-shell/xdg-shell.xml obj/xdg-shell.h +$WL_SCANNER private-code $PROTOCOLS/stable/xdg-shell/xdg-shell.xml obj/xdg-shell.c + +$CC -o bin/wl-egl wl-egl.c obj/xdg-shell.c \ + $WARNINGS $CFLAGS -Iobj $LDFLAGS \ + $(pkg-config --cflags --libs wayland-client wayland-egl egl) + +$GLSLC -mfmt=c shader.vert -o obj/shader.vert.spv.inc +$GLSLC -mfmt=c shader.frag -o obj/shader.frag.spv.inc + +$CC -o bin/wl-vk wl-vk.c obj/xdg-shell.c \ + $WARNINGS $CFLAGS -Iobj $LDFLAGS \ + $(pkg-config --cflags --libs wayland-client vulkan) diff --git a/clean.sh b/clean.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +set -ex + +rm -rf bin/ obj/ diff --git a/shader.frag b/shader.frag @@ -0,0 +1,8 @@ +#version 450 + +layout(location = 0) in vec3 fragColor; +layout(location = 0) out vec4 outColor; + +void main() { + outColor = vec4(fragColor, 1.0); +} diff --git a/shader.vert b/shader.vert @@ -0,0 +1,20 @@ +#version 450 + +layout(location = 0) out vec3 fragColor; + +vec2 positions[3] = vec2[]( + vec2(0.0, -0.5), + vec2(0.5, 0.5), + vec2(-0.5, 0.5) +); + +vec3 colors[3] = vec3[]( + vec3(1.0, 0.0, 0.0), + vec3(0.0, 1.0, 0.0), + vec3(0.0, 0.0, 1.0) +); + +void main() { + gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); + fragColor = colors[gl_VertexIndex]; +} diff --git a/vulkan.c b/vulkan.c @@ -0,0 +1,718 @@ +static uint32_t vert_shader[] = +#include "shader.vert.spv.inc" +; + +static uint32_t frag_shader[] = +#include "shader.frag.spv.inc" +; + +struct queue_family_indices { + uint32_t graphics_family; + bool has_graphics_family; + + uint32_t present_family; + bool has_present_family; +}; + +static struct queue_family_indices +get_device_queue_families(VkPhysicalDevice device, VkSurfaceKHR surface) +{ + struct queue_family_indices result = {0}; + + uint32_t queue_family_count; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, NULL); + + VkQueueFamilyProperties *queue_families = alloca(queue_family_count * sizeof *queue_families); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, queue_families); + + for (uint32_t i = 0; i < queue_family_count; i++) { + if (queue_families[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { + result.has_graphics_family = true; + result.graphics_family = i; + } + + VkBool32 present_support = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &present_support); + if (present_support) { + result.has_present_family = true; + result.present_family = i; + } + + if (result.has_graphics_family && result.has_present_family) + break; + } + + return result; +} + +static bool +is_device_suitable(VkPhysicalDevice device, VkSurfaceKHR surface, const char **device_extensions, uint32_t device_extensions_len) +{ + struct queue_family_indices indices = get_device_queue_families(device, surface); + + // NOTE: we require a device to have both graphics and present capability for its queue + if (!indices.has_graphics_family || !indices.has_present_family) + return false; + + uint32_t extension_count; + vkEnumerateDeviceExtensionProperties(device, NULL, &extension_count, NULL); + + VkExtensionProperties *extensions = alloca(extension_count * sizeof *extensions); + vkEnumerateDeviceExtensionProperties(device, NULL, &extension_count, extensions); + + for (uint32_t i = 0; i < device_extensions_len; i++) { + for (uint32_t j = 0; j < extension_count; j++) { + if (strcmp(device_extensions[i], extensions[j].extensionName) == 0) + goto extension_found; + } + + // NOTE: device does not support a required extension + return false; + +extension_found: + continue; + } + + uint32_t format_count; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &format_count, NULL); + if (!format_count) return false; + + uint32_t present_mode_count; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &present_mode_count, NULL); + if (!present_mode_count) return false; + + return true; +} + +static VkExtent2D +get_swapchain_extent(VkSurfaceCapabilitiesKHR *surface_caps, uint32_t width, uint32_t height) +{ + if (surface_caps->currentExtent.width == UINT32_MAX && surface_caps->currentExtent.height == UINT32_MAX) { + return (VkExtent2D) { + .width = CLAMP(width, surface_caps->minImageExtent.width, surface_caps->maxImageExtent.width), + .height = CLAMP(height, surface_caps->minImageExtent.height, surface_caps->maxImageExtent.height), + }; + } else { + return surface_caps->currentExtent; + } +} + +static VkSurfaceFormatKHR +get_swapchain_format(VkSurfaceFormatKHR *formats, uint64_t len) +{ + for (uint64_t i = 0; i < len; i++) { + if (formats[i].format == VK_FORMAT_B8G8R8A8_SRGB && formats[i].colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) + return formats[i]; + } + + return formats[0]; +} + +static VkPresentModeKHR +get_swapchain_present_mode(VkPresentModeKHR *present_modes, uint64_t len) +{ + for (uint64_t i = 0; i < len; i++) { + if (present_modes[i] == VK_PRESENT_MODE_MAILBOX_KHR) + return present_modes[i]; + } + + return VK_PRESENT_MODE_FIFO_KHR; +} + +static bool +recreate_swapchain(struct window *window, uint32_t width, uint32_t height, VkSwapchainKHR old_swapchain) +{ + struct queue_family_indices indices = get_device_queue_families(window->vk.physical_device, window->vk.surface); + assert(indices.has_graphics_family && indices.has_present_family); + + VkSurfaceCapabilitiesKHR surface_caps; + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(window->vk.physical_device, window->vk.surface, &surface_caps); + + uint32_t format_count; + vkGetPhysicalDeviceSurfaceFormatsKHR(window->vk.physical_device, window->vk.surface, &format_count, NULL); + + assert(format_count); + VkSurfaceFormatKHR *formats = alloca(format_count * sizeof *formats); + vkGetPhysicalDeviceSurfaceFormatsKHR(window->vk.physical_device, window->vk.surface, &format_count, formats); + + uint32_t present_mode_count; + vkGetPhysicalDeviceSurfacePresentModesKHR(window->vk.physical_device, window->vk.surface, &present_mode_count, NULL); + + assert(present_mode_count); + VkPresentModeKHR *present_modes = alloca(present_mode_count * sizeof *present_modes); + vkGetPhysicalDeviceSurfacePresentModesKHR(window->vk.physical_device, window->vk.surface, &present_mode_count, present_modes); + + VkExtent2D extent = get_swapchain_extent(&surface_caps, width, height); + VkSurfaceFormatKHR format = get_swapchain_format(formats, format_count); + VkPresentModeKHR present_mode = get_swapchain_present_mode(present_modes, present_mode_count); + + uint32_t image_count = surface_caps.minImageCount + 1; + if (surface_caps.maxImageCount) + image_count = MIN(image_count, surface_caps.maxImageCount); + + assert(indices.graphics_family == indices.present_family); + VkSwapchainCreateInfoKHR swapchain_info = { + .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, + .surface = window->vk.surface, + .minImageCount = image_count, + .imageFormat = format.format, + .imageColorSpace = format.colorSpace, + .imageExtent = extent, + .imageArrayLayers = 1, + .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, + .preTransform = surface_caps.currentTransform, + .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, + .presentMode = present_mode, + .clipped = VK_TRUE, + .oldSwapchain = old_swapchain, + }; + + window->vk.swapchain_image_format = format.format; + window->vk.swapchain_extent = extent; + + if (vkCreateSwapchainKHR(window->vk.device, &swapchain_info, NULL, &window->vk.swapchain) != VK_SUCCESS) + return false; + + vkGetSwapchainImagesKHR(window->vk.device, window->vk.swapchain, &image_count, NULL); + + window->vk.swapchain_images.len = image_count; + window->vk.swapchain_image_views.len = image_count; + window->vk.swapchain_framebuffers.len = image_count; + window->vk.swapchain_submit_semaphores.len = image_count; + + window->vk.swapchain_images.ptr = malloc(image_count * sizeof *window->vk.swapchain_images.ptr); + assert(window->vk.swapchain_images.ptr); + + vkGetSwapchainImagesKHR(window->vk.device, window->vk.swapchain, &window->vk.swapchain_images.len, window->vk.swapchain_images.ptr); + + window->vk.swapchain_image_views.ptr = malloc(image_count * sizeof *window->vk.swapchain_image_views.ptr); + assert(window->vk.swapchain_image_views.ptr); + + window->vk.swapchain_framebuffers.ptr = malloc(window->vk.swapchain_framebuffers.len * sizeof *window->vk.swapchain_framebuffers.ptr); + assert(window->vk.swapchain_framebuffers.ptr); + + window->vk.swapchain_submit_semaphores.ptr = malloc(image_count * sizeof *window->vk.swapchain_submit_semaphores.ptr); + assert(window->vk.swapchain_submit_semaphores.ptr); + + VkSemaphoreCreateInfo semaphore_info = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + }; + + for (uint32_t i = 0; i < window->vk.swapchain_submit_semaphores.len; i++) { + if (vkCreateSemaphore(window->vk.device, &semaphore_info, NULL, &window->vk.swapchain_submit_semaphores.ptr[i]) != VK_SUCCESS) { + fprintf(stderr, "Failed to create swapchain image sync objects!\n"); + exit(EXIT_FAILURE); + } + } + + for (uint32_t i = 0; i < window->vk.swapchain_images.len; i++) { + VkImageViewCreateInfo image_view_info = { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .image = window->vk.swapchain_images.ptr[i], + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = window->vk.swapchain_image_format, + .components.r = VK_COMPONENT_SWIZZLE_IDENTITY, + .components.g = VK_COMPONENT_SWIZZLE_IDENTITY, + .components.b = VK_COMPONENT_SWIZZLE_IDENTITY, + .components.a = VK_COMPONENT_SWIZZLE_IDENTITY, + .subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .subresourceRange.baseMipLevel = 0, + .subresourceRange.levelCount = 1, + .subresourceRange.baseArrayLayer = 0, + .subresourceRange.layerCount = 1, + }; + + if (vkCreateImageView(window->vk.device, &image_view_info, NULL, &window->vk.swapchain_image_views.ptr[i]) != VK_SUCCESS) + return false; + } + + VkAttachmentDescription color_attachment = { + .format = window->vk.swapchain_image_format, + .samples = VK_SAMPLE_COUNT_1_BIT, + .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, + .storeOp = VK_ATTACHMENT_STORE_OP_STORE, + .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, + .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + }; + + VkAttachmentReference color_attachment_ref = { + .attachment = 0, + .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + }; + + VkSubpassDescription subpass = { + .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, + .colorAttachmentCount = 1, + .pColorAttachments = &color_attachment_ref, + }; + + VkSubpassDependency dependency = { + .srcSubpass = VK_SUBPASS_EXTERNAL, + .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .srcAccessMask = 0, + .dstSubpass = 0, + .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + }; + + VkRenderPassCreateInfo render_pass_info = { + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, + .attachmentCount = 1, + .pAttachments = &color_attachment, + .subpassCount = 1, + .pSubpasses = &subpass, + .dependencyCount = 1, + .pDependencies = &dependency, + }; + + if (vkCreateRenderPass(window->vk.device, &render_pass_info, NULL, &window->vk.render_pass) != VK_SUCCESS) + return false; + + for (uint32_t i = 0; i < window->vk.swapchain_framebuffers.len; i++) { + VkImageView attachments[] = { window->vk.swapchain_image_views.ptr[i], }; + + VkFramebufferCreateInfo framebuffer_info = { + .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, + .renderPass = window->vk.render_pass, + .attachmentCount = ARRLEN(attachments), + .pAttachments = attachments, + .width = window->vk.swapchain_extent.width, + .height = window->vk.swapchain_extent.height, + .layers = 1, + }; + + if (vkCreateFramebuffer(window->vk.device, &framebuffer_info, NULL, &window->vk.swapchain_framebuffers.ptr[i]) != VK_SUCCESS) + return false; + } + + return true; +} + +static void +destroy_swapchain(struct window *window) +{ + // TODO: can this cause deadlock? + vkDeviceWaitIdle(window->vk.device); + + for (uint32_t i = 0; i < window->vk.swapchain_submit_semaphores.len; i++) { + vkDestroySemaphore(window->vk.device, window->vk.swapchain_submit_semaphores.ptr[i], NULL); + } + + free(window->vk.swapchain_submit_semaphores.ptr); + + for (uint32_t i = 0; i < window->vk.swapchain_framebuffers.len; i++) { + vkDestroyFramebuffer(window->vk.device, window->vk.swapchain_framebuffers.ptr[i], NULL); + } + + free(window->vk.swapchain_framebuffers.ptr); + + vkDestroyRenderPass(window->vk.device, window->vk.render_pass, NULL); + + for (uint32_t i = 0; i < window->vk.swapchain_image_views.len; i++) { + vkDestroyImageView(window->vk.device, window->vk.swapchain_image_views.ptr[i], NULL); + } + + free(window->vk.swapchain_image_views.ptr); + + // TODO: is this safe? + free(window->vk.swapchain_images.ptr); + + vkDestroySwapchainKHR(window->vk.device, window->vk.swapchain, NULL); +} + +static bool +create_shader_module(VkDevice device, uint32_t *code, uint64_t size, VkShaderModule *out) +{ + VkShaderModuleCreateInfo module_info = { + .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + .codeSize = size, + .pCode = code, + }; + + return vkCreateShaderModule(device, &module_info, NULL, out) == VK_SUCCESS; +} + +static bool +create_pipeline(struct window *window) +{ + VkShaderModule vert_shader_module; + if (!create_shader_module(window->vk.device, vert_shader, sizeof vert_shader, &vert_shader_module)) { + fprintf(stderr, "Failed to create vertex shader module!\n"); + return false; + } + + VkShaderModule frag_shader_module; + if (!create_shader_module(window->vk.device, frag_shader, sizeof frag_shader, &frag_shader_module)) { + fprintf(stderr, "Failed to create fragment shader module!\n"); + return false; + } + + VkPipelineShaderStageCreateInfo vert_stage_info = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .stage = VK_SHADER_STAGE_VERTEX_BIT, + .module = vert_shader_module, + .pName = "main", + }; + + VkPipelineShaderStageCreateInfo frag_stage_info = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .stage = VK_SHADER_STAGE_FRAGMENT_BIT, + .module = frag_shader_module, + .pName = "main", + }; + + VkPipelineShaderStageCreateInfo shader_stages[] = { + vert_stage_info, + frag_stage_info, + }; + + VkDynamicState dynamic_states[] = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR, + }; + + VkPipelineDynamicStateCreateInfo dynamic_state_info = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, + .dynamicStateCount = ARRLEN(dynamic_states), + .pDynamicStates = dynamic_states, + }; + + VkPipelineVertexInputStateCreateInfo vertex_input_info = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + .vertexBindingDescriptionCount = 0, + .pVertexBindingDescriptions = NULL, + .vertexAttributeDescriptionCount = 0, + .pVertexAttributeDescriptions = NULL, + }; + + VkPipelineInputAssemblyStateCreateInfo input_assembly_info = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, + .primitiveRestartEnable = VK_FALSE, + }; + + VkPipelineViewportStateCreateInfo viewport_info = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .viewportCount = 1, + .scissorCount = 1, + }; + + VkPipelineRasterizationStateCreateInfo rasteriser_info = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + .depthClampEnable = VK_FALSE, + .rasterizerDiscardEnable = VK_FALSE, + .polygonMode = VK_POLYGON_MODE_FILL, + .lineWidth = 1.0f, + .cullMode = VK_CULL_MODE_BACK_BIT, + .frontFace = VK_FRONT_FACE_CLOCKWISE, + .depthBiasEnable = VK_FALSE, + }; + + VkPipelineMultisampleStateCreateInfo multisample_info = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .sampleShadingEnable = VK_FALSE, + .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT, + }; + + VkPipelineColorBlendAttachmentState color_blend_attachment = { + .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT, + .blendEnable = VK_FALSE, + }; + + VkPipelineColorBlendStateCreateInfo color_blend_info = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, + .logicOpEnable = VK_FALSE, + .attachmentCount = 1, + .pAttachments = &color_blend_attachment, + }; + + VkPipelineLayoutCreateInfo pipeline_layout_info = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + }; + + if (vkCreatePipelineLayout(window->vk.device, &pipeline_layout_info, NULL, &window->vk.pipeline_layout) != VK_SUCCESS) { + fprintf(stderr, "Failed to create pipeline layout!\n"); + return false; + } + + VkGraphicsPipelineCreateInfo pipeline_info = { + .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + .stageCount = 2, + .pStages = shader_stages, + .pVertexInputState = &vertex_input_info, + .pInputAssemblyState = &input_assembly_info, + .pViewportState = &viewport_info, + .pRasterizationState = &rasteriser_info, + .pMultisampleState = &multisample_info, + .pColorBlendState = &color_blend_info, + .pDynamicState = &dynamic_state_info, + .layout = window->vk.pipeline_layout, + .renderPass = window->vk.render_pass, + .subpass = 0, + .basePipelineHandle = VK_NULL_HANDLE, + .basePipelineIndex = -1, + }; + + VkPipeline pipeline; + if (vkCreateGraphicsPipelines(window->vk.device, VK_NULL_HANDLE, 1, &pipeline_info, NULL, &pipeline) != VK_SUCCESS) { + fprintf(stderr, "Failed to create pipeline!\n"); + return false; + } + + window->vk.pipeline = pipeline; + + vkDestroyShaderModule(window->vk.device, frag_shader_module, NULL); + vkDestroyShaderModule(window->vk.device, vert_shader_module, NULL); + + return true; +} + +void +vk_init(struct window *window) +{ + window->vk.frame_index = 0; + + uint32_t physical_device_count; + vkEnumeratePhysicalDevices(window->vk.instance, &physical_device_count, NULL); + + if (!physical_device_count) { + fprintf(stderr, "Failed to find physical devices supporting Vulkan 1.0!\n"); + exit(EXIT_FAILURE); + } + + VkPhysicalDevice *physical_devices = alloca(physical_device_count * sizeof *physical_devices); + vkEnumeratePhysicalDevices(window->vk.instance, &physical_device_count, physical_devices); + + const char *device_extensions[] = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + }; + + window->vk.physical_device = VK_NULL_HANDLE; + for (uint32_t i = 0; i < physical_device_count; i++) { + if (is_device_suitable(physical_devices[i], window->vk.surface, device_extensions, ARRLEN(device_extensions))) + window->vk.physical_device = physical_devices[i]; + } + + if (window->vk.physical_device == VK_NULL_HANDLE) { + fprintf(stderr, "Failed to find suitable physical device!\n"); + exit(EXIT_FAILURE); + } + + struct queue_family_indices device_queue_families = get_device_queue_families(window->vk.physical_device, window->vk.surface); + + float queue_priority = 1.0f; + + // TODO: handle case where graphics queue and present queue are separate + VkDeviceQueueCreateInfo queue_info = { + .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + .queueFamilyIndex = device_queue_families.graphics_family, + .queueCount = 1, + .pQueuePriorities = &queue_priority, + }; + + VkPhysicalDeviceFeatures device_features = {0}; + + VkDeviceCreateInfo device_info = { + .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .pQueueCreateInfos = &queue_info, + .queueCreateInfoCount = 1, + .pEnabledFeatures = &device_features, + .enabledExtensionCount = ARRLEN(device_extensions), + .ppEnabledExtensionNames = device_extensions, + }; + + if (vkCreateDevice(window->vk.physical_device, &device_info, NULL, &window->vk.device) != VK_SUCCESS) { + fprintf(stderr, "Failed to create logical device!\n"); + exit(EXIT_FAILURE); + } + + vkGetDeviceQueue(window->vk.device, device_queue_families.graphics_family, 0, &window->vk.graphics_queue); + vkGetDeviceQueue(window->vk.device, device_queue_families.present_family, 0, &window->vk.present_queue); + + if (!recreate_swapchain(window, window->width, window->height, VK_NULL_HANDLE)) { + fprintf(stderr, "Failed to create swapchain!\n"); + exit(EXIT_FAILURE); + } + + if (!create_pipeline(window)) { + fprintf(stderr, "Failed to create graphics pipeline!\n"); + exit(EXIT_FAILURE); + } + + VkCommandPoolCreateInfo command_pool_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, + .queueFamilyIndex = device_queue_families.graphics_family, + }; + + if (vkCreateCommandPool(window->vk.device, &command_pool_info, NULL, &window->vk.command_pool) != VK_SUCCESS) { + fprintf(stderr, "Failed to create command pool!\n"); + exit(EXIT_FAILURE); + } + + VkCommandBufferAllocateInfo command_buffer_alloc_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .commandPool = window->vk.command_pool, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = ARRLEN(window->vk.command_buffers), + }; + + if (vkAllocateCommandBuffers(window->vk.device, &command_buffer_alloc_info, window->vk.command_buffers) != VK_SUCCESS) { + fprintf(stderr, "Failed to allocate command buffers!\n"); + exit(EXIT_FAILURE); + } + + VkSemaphoreCreateInfo semaphore_info = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + }; + + VkFenceCreateInfo fence_info = { + .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, + .flags = VK_FENCE_CREATE_SIGNALED_BIT, + }; + + for (uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(window->vk.device, &semaphore_info, NULL, &window->vk.sync_objects.image_available[i]) != VK_SUCCESS || + vkCreateFence(window->vk.device, &fence_info, NULL, &window->vk.sync_objects.in_flight[i]) != VK_SUCCESS) { + fprintf(stderr, "Failed to create sync objects!\n"); + exit(EXIT_FAILURE); + } + } +} + +void +vk_draw(struct window *window) +{ + uint32_t frame = window->vk.frame_index; + window->vk.frame_index = (frame + 1) % MAX_FRAMES_IN_FLIGHT; + + VkFence frame_fence = window->vk.sync_objects.in_flight[frame]; + vkWaitForFences(window->vk.device, 1, &frame_fence, VK_TRUE, UINT64_MAX); + + uint32_t image_index; + VkSemaphore acquire_semaphore = window->vk.sync_objects.image_available[frame]; + VkResult result = vkAcquireNextImageKHR(window->vk.device, window->vk.swapchain, UINT64_MAX, acquire_semaphore, VK_NULL_HANDLE, &image_index); + + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + destroy_swapchain(window); + if (!recreate_swapchain(window, window->width, window->height, VK_NULL_HANDLE)) + fprintf(stderr, "Failed to recreate swapchain!\n"); + return; + } else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + fprintf(stderr, "Failed to aqcuire swapchain image!\n"); + return; + } + + vkResetFences(window->vk.device, 1, &frame_fence); + + { + VkCommandBuffer buffer = window->vk.command_buffers[frame]; + vkResetCommandBuffer(buffer, 0); + + VkCommandBufferBeginInfo begin_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + }; + + if (vkBeginCommandBuffer(buffer, &begin_info) != VK_SUCCESS) { + fprintf(stderr, "Failed to begin recording command buffer!\n"); + return; + } + + VkClearValue clear_color = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; + VkRenderPassBeginInfo render_pass_begin_info = { + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, + .renderPass = window->vk.render_pass, + .framebuffer = window->vk.swapchain_framebuffers.ptr[image_index], + .renderArea.offset = {0, 0}, + .renderArea.extent = window->vk.swapchain_extent, + .clearValueCount = 1, + .pClearValues = &clear_color, + }; + + vkCmdBeginRenderPass(buffer, &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline(buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, window->vk.pipeline); + + VkViewport viewport = { + .x = 0.0f, + .y = 0.0f, + .width = (float) window->vk.swapchain_extent.width, + .height = (float) window->vk.swapchain_extent.height, + .minDepth = 0.0f, + .maxDepth = 1.0f, + }; + + vkCmdSetViewport(buffer, 0, 1, &viewport); + + VkRect2D scissor = { + .offset = {0, 0}, + .extent = window->vk.swapchain_extent, + }; + + vkCmdSetScissor(buffer, 0, 1, &scissor); + + uint32_t vertex_count = 3, instance_count = 1, first_vertex = 0, first_instance = 0; + vkCmdDraw(buffer, vertex_count, instance_count, first_vertex, first_instance); + + vkCmdEndRenderPass(buffer); + + if (vkEndCommandBuffer(buffer) != VK_SUCCESS) { + fprintf(stderr, "Failed to end recording command buffer!\n"); + return; + } + } + + VkPipelineStageFlags wait_stages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; + + VkSemaphore submit_semaphore = window->vk.swapchain_submit_semaphores.ptr[image_index]; + + VkSubmitInfo submit_info = { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .waitSemaphoreCount = 1, + .pWaitSemaphores = &acquire_semaphore, + .pWaitDstStageMask = wait_stages, + .signalSemaphoreCount = 1, + .pSignalSemaphores = &submit_semaphore, + .commandBufferCount = 1, + .pCommandBuffers = &window->vk.command_buffers[frame], + }; + + if (vkQueueSubmit(window->vk.graphics_queue, 1, &submit_info, frame_fence) != VK_SUCCESS) { + fprintf(stderr, "Failed to submit command buffer!\n"); + return; + } + + VkPresentInfoKHR present_info = { + .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + .waitSemaphoreCount = 1, + .pWaitSemaphores = &submit_semaphore, + .swapchainCount = 1, + .pSwapchains = &window->vk.swapchain, + .pImageIndices = &image_index, + }; + + vkQueuePresentKHR(window->vk.present_queue, &present_info); +} + +void +vk_free(struct window *window) +{ + vkDeviceWaitIdle(window->vk.device); + + destroy_swapchain(window); + + vkDestroyPipeline(window->vk.device, window->vk.pipeline, NULL); + vkDestroyPipelineLayout(window->vk.device, window->vk.pipeline_layout, NULL); + + for (uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(window->vk.device, window->vk.sync_objects.image_available[i], NULL); + vkDestroyFence(window->vk.device, window->vk.sync_objects.in_flight[i], NULL); + } + + vkDestroyCommandPool(window->vk.device, window->vk.command_pool, NULL); + + vkDestroyDevice(window->vk.device, NULL); +} diff --git a/wl-egl.c b/wl-egl.c @@ -0,0 +1,214 @@ +#include <wayland-client.h> +#include "xdg-shell.h" + +#include <wayland-egl.h> +#include <EGL/egl.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +struct wl_display *display; +struct wl_compositor *compositor; +struct xdg_wm_base *wm_base; +EGLDisplay egl_display; +char running; + +#define DEFAULT_WIDTH 256 +#define DEFAULT_HEIGHT 256 + +struct window { + EGLContext egl_context; + struct wl_surface *surface; + struct xdg_surface *xdg_surface; + struct xdg_toplevel *xdg_toplevel; + struct wl_egl_window *egl_window; + EGLSurface egl_surface; +}; + +static void +wm_base_ping(void *data, struct xdg_wm_base *wm_base, uint32_t serial) +{ + xdg_wm_base_pong(wm_base, serial); +} + +static const struct xdg_wm_base_listener xdg_wm_base_listener = { + .ping = wm_base_ping, +}; + +static void +registry_add_global(void *data, struct wl_registry *registry, uint32_t name, char const *interface, uint32_t version) +{ + if (!strcmp(interface, wl_compositor_interface.name)) { + compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 6); + } else if (!strcmp(interface, xdg_wm_base_interface.name)) { + wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 6); + xdg_wm_base_add_listener(wm_base, &xdg_wm_base_listener, NULL); + } +} + +static void +registry_remove_global(void *data, struct wl_registry *registry, uint32_t name) +{ +} + +static const struct wl_registry_listener registry_listener = { + .global = registry_add_global, + .global_remove = registry_remove_global, +}; + +static void +xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial) +{ + xdg_surface_ack_configure(xdg_surface, serial); +} + +static const struct xdg_surface_listener xdg_surface_listener = { + .configure = xdg_surface_configure, +}; + +static void +xdg_toplevel_configure(void *data, struct xdg_toplevel *toplevel, + int32_t width, int32_t height, struct wl_array *states) +{ + fprintf(stderr, "xdg_toplevel_configure: width: %d, height: %d\n", width, height); + + struct window *window = data; + + wl_egl_window_resize(window->egl_window, + width ? width : DEFAULT_WIDTH, + height ? height : DEFAULT_HEIGHT, + 0, 0); + + fprintf(stderr, "Created egl_window\n"); +} + +static void +xdg_toplevel_close(void *data, struct xdg_toplevel *toplevel) +{ + running = 0; + + fprintf(stderr, "Closing egl_window\n"); +} + +static void +xdg_toplevel_configure_bounds(void *data, struct xdg_toplevel *toplevel, + int32_t width, int32_t height) +{ + fprintf(stderr, "xdg_toplevel_configure_bounds: width: %d, height: %d\n", width, height); + + struct window *window = data; + + wl_egl_window_resize(window->egl_window, width, height, 0, 0); + + fprintf(stderr, "Resized egl_window\n"); +} + +static void +xdg_toplevel_wm_capabilities(void *data, struct xdg_toplevel *toplevel, + struct wl_array *caps) +{ +} + +static const struct xdg_toplevel_listener xdg_toplevel_listener = { + .configure = xdg_toplevel_configure, + .close = xdg_toplevel_close, + .configure_bounds = xdg_toplevel_configure_bounds, + .wm_capabilities = xdg_toplevel_wm_capabilities, +}; + +void +create_egl_window(struct window *window) +{ + EGLint major, minor; + eglInitialize(egl_display, &major, &minor); + + fprintf(stderr, "EGL version: %d.%d\n", major, minor); + + eglBindAPI(EGL_OPENGL_API); + + EGLint attrs[] = { + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_NONE, + }; + + EGLConfig config; + EGLint num_config; + eglChooseConfig(egl_display, attrs, &config, 1, &num_config); + + window->egl_context = eglCreateContext(egl_display, config, EGL_NO_CONTEXT, NULL); + + window->surface = wl_compositor_create_surface(compositor); + window->egl_window = wl_egl_window_create(window->surface, DEFAULT_WIDTH, DEFAULT_HEIGHT); + + window->xdg_surface = xdg_wm_base_get_xdg_surface(wm_base, window->surface); + xdg_surface_add_listener(window->xdg_surface, &xdg_surface_listener, NULL); + + window->xdg_toplevel = xdg_surface_get_toplevel(window->xdg_surface); + xdg_toplevel_add_listener(window->xdg_toplevel, &xdg_toplevel_listener, window); + + xdg_toplevel_set_title(window->xdg_toplevel, "wl-egl-example"); + + wl_surface_commit(window->surface); + wl_display_roundtrip(display); + + window->egl_surface = eglCreateWindowSurface(egl_display, config, window->egl_window, NULL); + if (window->egl_surface == EGL_NO_SURFACE) { + fprintf(stderr, "Failed to create surface!\n"); + exit(EXIT_FAILURE); + } + + eglMakeCurrent(egl_display, window->egl_surface, window->egl_surface, window->egl_context); +} + +void +destroy_egl_window(struct window *window) +{ + eglDestroySurface(egl_display, window->egl_surface); + wl_egl_window_destroy(window->egl_window); + xdg_toplevel_destroy(window->xdg_toplevel); + xdg_surface_destroy(window->xdg_surface); + wl_surface_destroy(window->surface); + eglDestroyContext(egl_display, window->egl_context); +} + +void +draw(struct window *window) +{ + eglSwapBuffers(egl_display, window->egl_surface); +} + +int +main(void) +{ + fprintf(stderr, "Hello, World!\n"); + + display = wl_display_connect(NULL); + + struct wl_registry *registry = wl_display_get_registry(display); + wl_registry_add_listener(registry, &registry_listener, NULL); + wl_display_roundtrip(display); + + egl_display = eglGetDisplay(display); + + struct window window; + create_egl_window(&window); + + running = 1; + while (running) { + wl_display_dispatch_pending(display); + draw(&window); + } + + destroy_egl_window(&window); + + eglTerminate(egl_display); + wl_display_disconnect(display); + + fprintf(stderr, "Goodbye, World!\n"); + + return 0; +} diff --git a/wl-vk.c b/wl-vk.c @@ -0,0 +1,382 @@ +#include <wayland-client.h> +#include "xdg-shell.h" + +#define VK_USE_PLATFORM_WAYLAND_KHR 1 +#include <vulkan/vulkan.h> +#include <vulkan/vulkan_wayland.h> + +#include <alloca.h> +#include <assert.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define ARRLEN(arr) (sizeof (arr) / sizeof (arr)[0]) + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define CLAMP(v, min, max) (MIN(MAX(v, min), max)) + +struct wl_display *display; +struct wl_compositor *compositor; +struct xdg_wm_base *wm_base; +char running; + +#define DEFAULT_WIDTH 256 +#define DEFAULT_HEIGHT 256 + +#define MAX_FRAMES_IN_FLIGHT 2 + +struct window { + struct wl_surface *surface; + struct xdg_surface *xdg_surface; + struct xdg_toplevel *xdg_toplevel; + + char resize; + uint32_t width, height; + + struct { + VkInstance instance; + VkDebugUtilsMessengerEXT debug_messenger; + VkSurfaceKHR surface; + + /* boilerplate vulkan state */ + VkPhysicalDevice physical_device; + VkDevice device; + + VkQueue graphics_queue; + VkQueue present_queue; + + VkSwapchainKHR swapchain; + VkFormat swapchain_image_format; + VkExtent2D swapchain_extent; + + struct { + VkImage *ptr; + uint32_t len; + } swapchain_images; + + struct { + VkImageView *ptr; + uint32_t len; + } swapchain_image_views; + + struct { + VkFramebuffer *ptr; + uint32_t len; + } swapchain_framebuffers; + + struct { + VkSemaphore *ptr; + uint32_t len; + } swapchain_submit_semaphores; + + VkRenderPass render_pass; + VkPipelineLayout pipeline_layout; + VkPipeline pipeline; + + VkCommandPool command_pool; + VkCommandBuffer command_buffers[MAX_FRAMES_IN_FLIGHT]; + + struct { + VkSemaphore image_available[MAX_FRAMES_IN_FLIGHT]; + VkFence in_flight[MAX_FRAMES_IN_FLIGHT]; + } sync_objects; + + uint32_t frame_index; + } vk; +}; + +static void +wm_base_ping(void *data, struct xdg_wm_base *wm_base, uint32_t serial) +{ + xdg_wm_base_pong(wm_base, serial); +} + +static const struct xdg_wm_base_listener xdg_wm_base_listener = { + .ping = wm_base_ping, +}; + +static void +registry_add_global(void *data, struct wl_registry *registry, uint32_t name, char const *interface, uint32_t version) +{ + if (!strcmp(interface, wl_compositor_interface.name)) { + compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 6); + } else if (!strcmp(interface, xdg_wm_base_interface.name)) { + wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 6); + xdg_wm_base_add_listener(wm_base, &xdg_wm_base_listener, NULL); + } +} + +static void +registry_remove_global(void *data, struct wl_registry *registry, uint32_t name) +{ +} + +static const struct wl_registry_listener registry_listener = { + .global = registry_add_global, + .global_remove = registry_remove_global, +}; + +static void +xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial) +{ + xdg_surface_ack_configure(xdg_surface, serial); +} + +static const struct xdg_surface_listener xdg_surface_listener = { + .configure = xdg_surface_configure, +}; + +static void +xdg_toplevel_configure(void *data, struct xdg_toplevel *toplevel, + int32_t width, int32_t height, struct wl_array *states) +{ + fprintf(stderr, "xdg_toplevel_configure: width: %d, height: %d\n", width, height); + + struct window *window = data; + + window->resize = 1; + window->width = width ? width : DEFAULT_WIDTH; + window->height = height ? height : DEFAULT_HEIGHT; + + fprintf(stderr, "Created vk_window\n"); +} + +static void +xdg_toplevel_close(void *data, struct xdg_toplevel *toplevel) +{ + running = 0; + + fprintf(stderr, "Closing vk_window\n"); +} + +static void +xdg_toplevel_configure_bounds(void *data, struct xdg_toplevel *toplevel, + int32_t width, int32_t height) +{ + fprintf(stderr, "xdg_toplevel_configure_bounds: width: %d, height: %d\n", width, height); + + struct window *window = data; + + window->resize = 1; + window->width = width ? width : DEFAULT_WIDTH; + window->height = height ? height : DEFAULT_HEIGHT; + + fprintf(stderr, "Resized vk_window\n"); +} + +static void +xdg_toplevel_wm_capabilities(void *data, struct xdg_toplevel *toplevel, + struct wl_array *caps) +{ +} + +static const struct xdg_toplevel_listener xdg_toplevel_listener = { + .configure = xdg_toplevel_configure, + .close = xdg_toplevel_close, + .configure_bounds = xdg_toplevel_configure_bounds, + .wm_capabilities = xdg_toplevel_wm_capabilities, +}; + +static VKAPI_ATTR VkBool32 VKAPI_CALL +vk_debug_callback(VkDebugUtilsMessageSeverityFlagBitsEXT severity, + VkDebugUtilsMessageTypeFlagsEXT type, + const VkDebugUtilsMessengerCallbackDataEXT *callback_data, + void *user_data) +{ + (void) severity; + (void) type; + (void) user_data; + + fprintf(stderr, "vulkan: validation: %s\n", callback_data->pMessage); + + return VK_FALSE; +} + +void +create_vk_window(struct window *window) +{ + window->surface = wl_compositor_create_surface(compositor); + + window->xdg_surface = xdg_wm_base_get_xdg_surface(wm_base, window->surface); + xdg_surface_add_listener(window->xdg_surface, &xdg_surface_listener, NULL); + + window->xdg_toplevel = xdg_surface_get_toplevel(window->xdg_surface); + xdg_toplevel_add_listener(window->xdg_toplevel, &xdg_toplevel_listener, window); + + xdg_toplevel_set_title(window->xdg_toplevel, "wl-vk-example"); + + wl_surface_commit(window->surface); + wl_display_roundtrip(display); + + { + // NOTE: debug code to display available extensions and layers + uint32_t extension_count; + vkEnumerateInstanceExtensionProperties(NULL, &extension_count, NULL); + + fprintf(stderr, "Extension count: %" PRIu32 "\n", extension_count); + + VkExtensionProperties *extensions = alloca(extension_count * sizeof *extensions); + vkEnumerateInstanceExtensionProperties(NULL, &extension_count, extensions); + + for (uint32_t i = 0; i < extension_count; i++) + fprintf(stderr, "\t%s\n", extensions[i].extensionName); + + uint32_t layer_count; + vkEnumerateInstanceLayerProperties(&layer_count, NULL); + + fprintf(stderr, "Layer count: %" PRIu32 "\n", layer_count); + + VkLayerProperties *layers = alloca(layer_count * sizeof *layers); + vkEnumerateInstanceLayerProperties(&layer_count, layers); + + for (uint32_t i = 0; i < layer_count; i++) + fprintf(stderr, "\t%s\n", layers[i].layerName); + } + + VkApplicationInfo app_info = { + .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pApplicationName = "wl-vk-example", + .applicationVersion = VK_MAKE_VERSION(1, 0, 0), + .pEngineName = "No Engine", + .engineVersion = VK_MAKE_VERSION(1, 0, 0), + .apiVersion = VK_API_VERSION_1_0, + }; + + const char *instance_extensions[] = { + VK_KHR_SURFACE_EXTENSION_NAME, + VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME, + VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME, + VK_EXT_DEBUG_UTILS_EXTENSION_NAME, + }; + + const char *instance_layers[] = { + "VK_LAYER_KHRONOS_validation", + }; + + uint32_t debug_severity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + + uint32_t debug_type = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + + VkDebugUtilsMessengerCreateInfoEXT debug_info = { + .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, + .messageSeverity = debug_severity, + .messageType = debug_type, + .pfnUserCallback = vk_debug_callback, + .pUserData = NULL, + }; + + VkInstanceCreateInfo instance_info = { + .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .pApplicationInfo = &app_info, + .enabledExtensionCount = ARRLEN(instance_extensions), + .ppEnabledExtensionNames = instance_extensions, + .enabledLayerCount = ARRLEN(instance_layers), + .ppEnabledLayerNames = instance_layers, + .pNext = &debug_info, + }; + + VkInstance vk_instance; + if (vkCreateInstance(&instance_info, NULL, &window->vk.instance) != VK_SUCCESS) { + fprintf(stderr, "Failed to create Vulkan instance!\n"); + exit(EXIT_FAILURE); + } + + PFN_vkCreateDebugUtilsMessengerEXT vk_create_debug_utils_messenger_ext = + (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(window->vk.instance, + "vkCreateDebugUtilsMessengerEXT"); + if (!vk_create_debug_utils_messenger_ext) { + fprintf(stderr, "Failed to get proc address of vkCreateDebugUtilsMessengerEXT!\n"); + exit(EXIT_FAILURE); + } + + if (vk_create_debug_utils_messenger_ext(window->vk.instance, &debug_info, NULL, &window->vk.debug_messenger) != VK_SUCCESS) { + fprintf(stderr, "Failed to create Vulkan debug messenger!\n"); + exit(EXIT_FAILURE); + } + + VkWaylandSurfaceCreateInfoKHR surface_info = { + .sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, + .display = display, + .surface = window->surface, + }; + + VkSurfaceKHR vk_surface; + if (vkCreateWaylandSurfaceKHR(window->vk.instance, &surface_info, NULL, &window->vk.surface) != VK_SUCCESS) { + fprintf(stderr, "Failed to create Vulkan wayland surface!\n"); + exit(EXIT_FAILURE); + } +} + +void +destroy_vk_window(struct window *window) +{ + vkDestroySurfaceKHR(window->vk.instance, window->vk.surface, NULL); + + PFN_vkDestroyDebugUtilsMessengerEXT vk_destroy_debug_utils_messenger_ext = + (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(window->vk.instance, + "vkDestroyDebugUtilsMessengerEXT"); + if (!vk_destroy_debug_utils_messenger_ext) { + fprintf(stderr, "Failed to get proc address of vkDestroyDebugUtilsMessengerEXT!\n"); + } + + vk_destroy_debug_utils_messenger_ext(window->vk.instance, window->vk.debug_messenger, NULL); + + vkDestroyInstance(window->vk.instance, NULL); + + xdg_toplevel_destroy(window->xdg_toplevel); + xdg_surface_destroy(window->xdg_surface); + wl_surface_destroy(window->surface); +} + +static void +vk_init(struct window *window); + +static void +vk_draw(struct window *window); + +static void +vk_free(struct window *window); + +// vulkan boilerplate code moved out to separate file to avoid clutter +#include "vulkan.c" + +int +main(void) +{ + fprintf(stderr, "Hello, World!\n"); + + display = wl_display_connect(NULL); + + struct wl_registry *registry = wl_display_get_registry(display); + wl_registry_add_listener(registry, &registry_listener, NULL); + wl_display_roundtrip(display); + + struct window window; + create_vk_window(&window); + + vk_init(&window); + + running = 1; + while (running) { + wl_display_dispatch_pending(display); + vk_draw(&window); + } + + vk_free(&window); + + destroy_vk_window(&window); + + wl_display_disconnect(display); + + fprintf(stderr, "Goodbye, World!\n"); + + return 0; +}