commit ea788935bfaf799f34d659f9943f225529bbc374
Author: MikoĊaj Lenczewski <mikolaj@lenczewski.org>
Date: Mon, 12 Oct 2026 01:44:10 +0100
Initial commit
Diffstat:
| A | .gitignore | | | 2 | ++ |
| A | NOTES | | | 2 | ++ |
| A | README | | | 3 | +++ |
| A | build.sh | | | 30 | ++++++++++++++++++++++++++++++ |
| A | clean.sh | | | 5 | +++++ |
| A | shader.frag | | | 8 | ++++++++ |
| A | shader.vert | | | 20 | ++++++++++++++++++++ |
| A | vulkan.c | | | 718 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | wl-egl.c | | | 214 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | wl-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, ®istry_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, ®istry_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;
+}