/* * Copyright © 2025 Erico Nunes * * based on simple-egl.c: * Copyright © 2011 Benjamin Franzke * Copyright (c) 2012 Arvin Schnell * Copyright (c) 2012 Rob Clark * Copyright © 2015 Intel Corporation * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include "fractional-scale-v1-client-protocol.h" #include "viewporter-client-protocol.h" #include "xdg-shell-client-protocol.h" #include "tearing-control-v1-client-protocol.h" #include #include #include #include "shared/helpers.h" #include "shared/platform.h" #include "shared/xalloc.h" #define VK_USE_PLATFORM_WAYLAND_KHR #define VK_PROTOTYPES #include #include #define MAX_NUM_IMAGES 5 #define MAX_CONCURRENT_FRAMES 2 /* const uint32_t simple_vulkan_vertex_shader[]; simple_vulkan_vertex_shader.frag */ #include "simple_vulkan_vertex_shader.spv.h" /* const uint32_t simple_vulkan_fragment_shader[]; simple_vulkan_fragment_shader.frag */ #include "simple_vulkan_fragment_shader.spv.h" struct window; struct seat; struct display { struct wl_display *display; struct wl_registry *registry; struct wl_compositor *compositor; struct xdg_wm_base *wm_base; struct wl_seat *seat; struct wl_pointer *pointer; struct wl_touch *touch; struct wl_keyboard *keyboard; struct wl_shm *shm; struct wl_cursor_theme *cursor_theme; struct wl_cursor *default_cursor; struct wl_surface *cursor_surface; struct wp_tearing_control_manager_v1 *tearing_manager; struct wp_viewporter *viewporter; struct wp_fractional_scale_manager_v1 *fractional_scale_manager; struct window *window; struct wl_list output_list; /* struct output::link */ }; struct geometry { int width, height; }; struct window_image { VkImageView image_view; VkFramebuffer framebuffer; VkSemaphore render_done; }; struct window_buffer { VkBuffer buffer; VkDeviceMemory mem; void *map; }; struct window_frame { VkSemaphore image_acquired; VkFence fence; VkCommandBuffer cmd_buffer; VkDescriptorSet descriptor_set; struct window_buffer ubo_buffer; }; struct window_vulkan_pipeline { VkDescriptorSetLayout descriptor_set_layout; VkPipelineLayout pipeline_layout; VkPipeline pipeline; }; struct window_vulkan { VkInstance inst; VkPhysicalDevice phys_dev; VkDevice dev; VkQueue queue; uint32_t queue_family; VkRenderPass renderpass; VkDescriptorPool descriptor_pool; VkCommandPool cmd_pool; struct window_vulkan_pipeline pipeline; VkSwapchainKHR swapchain; VkPresentModeKHR present_mode; VkSurfaceKHR surface; VkFormat format; uint32_t image_count; struct window_image images[MAX_NUM_IMAGES]; uint32_t frame_index; struct window_frame frames[MAX_CONCURRENT_FRAMES]; struct window_buffer vertex_buffer; bool has_incremental_present; PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR get_wayland_presentation_support; PFN_vkCreateWaylandSurfaceKHR create_wayland_surface; }; struct window { struct display *display; struct geometry window_size; struct geometry logical_size; struct geometry buffer_size; int32_t buffer_scale; double fractional_buffer_scale; enum wl_output_transform buffer_transform; bool needs_buffer_geometry_update; uint32_t frames; uint32_t initial_frame_time; uint32_t benchmark_time; struct wl_surface *surface; struct xdg_surface *xdg_surface; struct xdg_toplevel *xdg_toplevel; int fullscreen, maximized, opaque, delay; struct wp_tearing_control_v1 *tear_control; struct wp_viewport *viewport; struct wp_fractional_scale_v1 *fractional_scale_obj; bool tearing, toggled_tearing, tear_enabled; bool fullscreen_ratio; bool wait_for_configure; struct window_vulkan vk; struct wl_list window_output_list; /* struct window_output::link */ }; struct output { struct display *display; struct wl_output *wl_output; uint32_t name; struct wl_list link; /* struct display::output_list */ enum wl_output_transform transform; int32_t scale; }; struct window_output { struct output *output; struct wl_list link; /* struct window::window_output_list */ }; static inline void _check_vk_success(const char *file, int line, const char *func, VkResult result, const char *vk_func) { if (result == VK_SUCCESS) return; fprintf(stderr, "%s %d %s Error: %s failed with VkResult %d\n", file, line, func, vk_func, result); abort(); } #define check_vk_success(result, vk_func) \ _check_vk_success(__FILE__, __LINE__, __func__, (result), (vk_func)) static void pnext(void *base, void *next) { VkBaseOutStructure *b = base; VkBaseOutStructure *n = next; n->pNext = b->pNext; b->pNext = n; } static int running = 1; static int32_t compute_buffer_scale(struct window *window) { struct window_output *window_output; int32_t scale = 1; wl_list_for_each(window_output, &window->window_output_list, link) { if (window_output->output->scale > scale) scale = window_output->output->scale; } return scale; } static enum wl_output_transform compute_buffer_transform(struct window *window) { struct window_output *window_output; enum wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL; wl_list_for_each(window_output, &window->window_output_list, link) { /* If the surface spans over multiple outputs the optimal * transform value can be ambiguous. Thus just return the value * from the oldest entered output. */ transform = window_output->output->transform; break; } return transform; } static void create_image_view(VkDevice device, VkImage image, VkFormat format, VkImageView *image_view) { VkResult result; const VkImageViewCreateInfo view_info = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = image, .viewType = VK_IMAGE_VIEW_TYPE_2D, .format = format, .subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .subresourceRange.baseMipLevel = 0, .subresourceRange.levelCount = 1, .subresourceRange.baseArrayLayer = 0, .subresourceRange.layerCount = 1, }; result = vkCreateImageView(device, &view_info, NULL, image_view); check_vk_success(result, "vkCreateImageView"); } static void create_framebuffer(VkDevice device, VkRenderPass renderpass, VkImageView image_view, uint32_t width, uint32_t height, VkFramebuffer *framebuffer) { VkResult result; const VkFramebufferCreateInfo framebuffer_create_info = { .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, .renderPass = renderpass, .attachmentCount = 1, .pAttachments = &image_view, .width = width, .height = height, .layers = 1, }; result = vkCreateFramebuffer(device, &framebuffer_create_info, NULL, framebuffer); check_vk_success(result, "vkCreateFramebuffer"); } static void create_swapchain(struct window *window) { VkSurfaceCapabilitiesKHR surface_caps; vkGetPhysicalDeviceSurfaceCapabilitiesKHR(window->vk.phys_dev, window->vk.surface, &surface_caps); VkBool32 supported; vkGetPhysicalDeviceSurfaceSupportKHR(window->vk.phys_dev, 0, window->vk.surface, &supported); assert(supported); uint32_t present_mode_count; vkGetPhysicalDeviceSurfacePresentModesKHR(window->vk.phys_dev, window->vk.surface, &present_mode_count, NULL); VkPresentModeKHR present_modes[present_mode_count]; vkGetPhysicalDeviceSurfacePresentModesKHR(window->vk.phys_dev, window->vk.surface, &present_mode_count, present_modes); assert(window->vk.present_mode >= 0 && window->vk.present_mode < 4); supported = false; for (size_t i = 0; i < present_mode_count; ++i) { if (present_modes[i] == window->vk.present_mode) { supported = true; break; } } if (!supported) { fprintf(stderr, "Present mode %d unsupported\n", window->vk.present_mode); abort(); } uint32_t min_image_count = 2; if (min_image_count < surface_caps.minImageCount) min_image_count = surface_caps.minImageCount; if (surface_caps.maxImageCount > 0 && min_image_count > surface_caps.maxImageCount) min_image_count = surface_caps.maxImageCount; const VkExtent2D swapchain_extent = { window->buffer_size.width, window->buffer_size.height }; VkSwapchainCreateInfoKHR swapchain_create_info = { .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, .flags = 0, .surface = window->vk.surface, .minImageCount = min_image_count, .imageFormat = window->vk.format, .imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR, .imageExtent = swapchain_extent, .imageArrayLayers = 1, .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, .queueFamilyIndexCount = 1, .pQueueFamilyIndices = &window->vk.queue_family, .preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR, }; if (surface_caps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR && !window->opaque) swapchain_create_info.compositeAlpha = VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR; else swapchain_create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; swapchain_create_info.presentMode = window->vk.present_mode; vkCreateSwapchainKHR(window->vk.dev, &swapchain_create_info, NULL, &window->vk.swapchain); vkGetSwapchainImagesKHR(window->vk.dev, window->vk.swapchain, &window->vk.image_count, NULL); assert(window->vk.image_count > 0); VkImage swapchain_images[window->vk.image_count]; vkGetSwapchainImagesKHR(window->vk.dev, window->vk.swapchain, &window->vk.image_count, swapchain_images); assert(window->vk.image_count <= ARRAY_LENGTH(window->vk.images)); for (uint32_t i = 0; i < window->vk.image_count; i++) { VkResult result; create_image_view(window->vk.dev, swapchain_images[i], window->vk.format, &window->vk.images[i].image_view); create_framebuffer(window->vk.dev, window->vk.renderpass, window->vk.images[i].image_view, window->buffer_size.width, window->buffer_size.height, &window->vk.images[i].framebuffer); const VkSemaphoreCreateInfo semaphore_create_info = { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, }; result = vkCreateSemaphore(window->vk.dev, &semaphore_create_info, NULL, &window->vk.images[i].render_done); check_vk_success(result, "vkCreateSemaphore"); } } static void destroy_swapchain(struct window *window) { vkDeviceWaitIdle(window->vk.dev); for (uint32_t i = 0; i < window->vk.image_count; i++) { vkDestroySemaphore(window->vk.dev, window->vk.images[i].render_done, NULL); vkDestroyFramebuffer(window->vk.dev, window->vk.images[i].framebuffer, NULL); vkDestroyImageView(window->vk.dev, window->vk.images[i].image_view, NULL); } vkDestroySwapchainKHR(window->vk.dev, window->vk.swapchain, NULL); } static void recreate_swapchain(struct window *window) { destroy_swapchain(window); create_swapchain(window); } static void update_buffer_geometry(struct window *window) { enum wl_output_transform new_buffer_transform; struct geometry new_buffer_size; struct geometry new_viewport_dest_size; new_buffer_transform = compute_buffer_transform(window); if (window->buffer_transform != new_buffer_transform) { window->buffer_transform = new_buffer_transform; wl_surface_set_buffer_transform(window->surface, window->buffer_transform); } switch (window->buffer_transform) { case WL_OUTPUT_TRANSFORM_NORMAL: case WL_OUTPUT_TRANSFORM_180: case WL_OUTPUT_TRANSFORM_FLIPPED: case WL_OUTPUT_TRANSFORM_FLIPPED_180: new_buffer_size.width = window->logical_size.width; new_buffer_size.height = window->logical_size.height; break; case WL_OUTPUT_TRANSFORM_90: case WL_OUTPUT_TRANSFORM_270: case WL_OUTPUT_TRANSFORM_FLIPPED_90: case WL_OUTPUT_TRANSFORM_FLIPPED_270: new_buffer_size.width = window->logical_size.height; new_buffer_size.height = window->logical_size.width; break; } if (window->fractional_buffer_scale > 0.0) { if (window->buffer_scale > 1) { window->buffer_scale = 1; wl_surface_set_buffer_scale(window->surface, window->buffer_scale); } new_buffer_size.width = ceil(new_buffer_size.width * window->fractional_buffer_scale); new_buffer_size.height = ceil(new_buffer_size.height * window->fractional_buffer_scale); } else { int32_t new_buffer_scale; new_buffer_scale = compute_buffer_scale(window); if (window->buffer_scale != new_buffer_scale) { window->buffer_scale = new_buffer_scale; wl_surface_set_buffer_scale(window->surface, window->buffer_scale); } new_buffer_size.width *= window->buffer_scale; new_buffer_size.height *= window->buffer_scale; } if (window->fullscreen && window->fullscreen_ratio) { int new_buffer_size_min; int new_viewport_dest_size_min; new_buffer_size_min = MIN(new_buffer_size.width, new_buffer_size.height); new_buffer_size.width = new_buffer_size_min; new_buffer_size.height = new_buffer_size_min; new_viewport_dest_size_min = MIN(window->logical_size.width, window->logical_size.height); new_viewport_dest_size.width = new_viewport_dest_size_min; new_viewport_dest_size.height = new_viewport_dest_size_min; } else { new_viewport_dest_size.width = window->logical_size.width; new_viewport_dest_size.height = window->logical_size.height; } if (window->buffer_size.width != new_buffer_size.width || window->buffer_size.height != new_buffer_size.height) { window->buffer_size = new_buffer_size; } if (window->fractional_buffer_scale > 0.0) wp_viewport_set_destination(window->viewport, new_viewport_dest_size.width, new_viewport_dest_size.height); window->needs_buffer_geometry_update = false; } static VkFormat choose_surface_format(struct window *window) { uint32_t num_formats = 0; vkGetPhysicalDeviceSurfaceFormatsKHR(window->vk.phys_dev, window->vk.surface, &num_formats, NULL); assert(num_formats > 0); VkSurfaceFormatKHR formats[num_formats]; vkGetPhysicalDeviceSurfaceFormatsKHR(window->vk.phys_dev, window->vk.surface, &num_formats, formats); VkFormat format = VK_FORMAT_UNDEFINED; for (int i = 0; i < (int)num_formats; i++) { switch (formats[i].format) { case VK_FORMAT_B8G8R8A8_UNORM: format = formats[i].format; break; default: continue; } } assert(format != VK_FORMAT_UNDEFINED); return format; } static int find_memory_type(struct window *window, uint32_t allowed, VkMemoryPropertyFlags properties) { VkPhysicalDeviceMemoryProperties mem_properties; vkGetPhysicalDeviceMemoryProperties(window->vk.phys_dev, &mem_properties); for (unsigned i = 0; (1u << i) <= allowed && i <= mem_properties.memoryTypeCount; ++i) { if ((allowed & (1u << i)) && (mem_properties.memoryTypes[i].propertyFlags & properties)) return i; } return -1; } static void create_buffer(struct window *window, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer *buffer, VkDeviceMemory *buffer_memory) { VkResult result; const VkBufferCreateInfo buffer_info = { .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .size = size, .usage = usage, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, }; result = vkCreateBuffer(window->vk.dev, &buffer_info, NULL, buffer); check_vk_success(result, "vkCreateBuffer"); VkMemoryRequirements mem_requirements; vkGetBufferMemoryRequirements(window->vk.dev, *buffer, &mem_requirements); int memory_type = find_memory_type(window, mem_requirements.memoryTypeBits, properties); assert(memory_type >= 0); const VkMemoryAllocateInfo alloc_info = { .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .allocationSize = mem_requirements.size, .memoryTypeIndex = memory_type, }; result = vkAllocateMemory(window->vk.dev, &alloc_info, NULL, buffer_memory); check_vk_success(result, "vkAllocateMemory"); result = vkBindBufferMemory(window->vk.dev, *buffer, *buffer_memory, 0); check_vk_success(result, "vkBindBufferMemory"); } static void create_descriptor_set(struct window *window, struct window_frame *frame) { struct window_vulkan_pipeline *pipeline = &window->vk.pipeline; VkResult result; const VkDescriptorSetAllocateInfo descriptor_set_allocate_info = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, .descriptorPool = window->vk.descriptor_pool, .descriptorSetCount = 1, .pSetLayouts = &pipeline->descriptor_set_layout, }; result = vkAllocateDescriptorSets(window->vk.dev, &descriptor_set_allocate_info, &frame->descriptor_set); check_vk_success(result, "vkAllocateDescriptorSets"); struct window_buffer *ubo_buffer = &frame->ubo_buffer; const VkDescriptorBufferInfo descriptor_buffer_info = { .buffer = ubo_buffer->buffer, .range = VK_WHOLE_SIZE, }; const VkWriteDescriptorSet descriptor_writes[] = { { .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstSet = frame->descriptor_set, .dstBinding = 0, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .pBufferInfo = &descriptor_buffer_info, }, }; vkUpdateDescriptorSets(window->vk.dev, ARRAY_LENGTH(descriptor_writes), descriptor_writes, 0, NULL); } static void create_renderpass(struct window *window) { VkResult result; const VkAttachmentDescription attachment_description = { .format = window->vk.format, .samples = VK_SAMPLE_COUNT_1_BIT, .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, .storeOp = VK_ATTACHMENT_STORE_OP_STORE, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, .finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, }; const VkAttachmentReference attachment_reference = { .attachment = 0, .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }; const VkSubpassDescription subpass_description = { .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, .colorAttachmentCount = 1, .pColorAttachments = &attachment_reference, }; const VkRenderPassCreateInfo renderpass_create_info = { .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, .attachmentCount = 1, .pAttachments = &attachment_description, .subpassCount = 1, .pSubpasses = &subpass_description, }; result = vkCreateRenderPass(window->vk.dev, &renderpass_create_info, NULL, &window->vk.renderpass); check_vk_success(result, "vkCreateRenderPass"); } static void create_descriptor_set_layout(struct window *window) { struct window_vulkan_pipeline *pipeline = &window->vk.pipeline; VkResult result; const VkDescriptorSetLayoutBinding vs_ubo_layout_binding = { .binding = 0, .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .descriptorCount = 1, .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, }; const VkDescriptorSetLayoutBinding bindings[] = { vs_ubo_layout_binding, }; const VkDescriptorSetLayoutCreateInfo layout_info = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, .bindingCount = ARRAY_LENGTH(bindings), .pBindings = bindings, }; result = vkCreateDescriptorSetLayout(window->vk.dev, &layout_info, NULL, &pipeline->descriptor_set_layout); check_vk_success(result, "vkCreateDescriptorSetLayout"); } static void create_pipeline(struct window *window) { struct window_vulkan_pipeline *pipeline = &window->vk.pipeline; VkResult result; VkShaderModule vs_module; const VkShaderModuleCreateInfo vs_shader_module_create_info = { .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, .codeSize = sizeof(simple_vulkan_vertex_shader), .pCode = (uint32_t *)simple_vulkan_vertex_shader, }; vkCreateShaderModule(window->vk.dev, &vs_shader_module_create_info, NULL, &vs_module); VkShaderModule fs_module; const VkShaderModuleCreateInfo fs_shader_module_create_info = { .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, .codeSize = sizeof(simple_vulkan_fragment_shader), .pCode = (uint32_t *)simple_vulkan_fragment_shader, }; vkCreateShaderModule(window->vk.dev, &fs_shader_module_create_info, NULL, &fs_module); const VkPipelineVertexInputStateCreateInfo pipeline_vertex_input_state_create_info = { .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, .vertexBindingDescriptionCount = 2, .pVertexBindingDescriptions = (VkVertexInputBindingDescription[]) { { .binding = 0, .stride = 3 * sizeof(float), .inputRate = VK_VERTEX_INPUT_RATE_VERTEX }, { .binding = 1, .stride = 3 * sizeof(float), .inputRate = VK_VERTEX_INPUT_RATE_VERTEX }, }, .vertexAttributeDescriptionCount = 2, .pVertexAttributeDescriptions = (VkVertexInputAttributeDescription[]) { { .location = 0, .binding = 0, .format = VK_FORMAT_R32G32B32_SFLOAT, .offset = 0 }, { .location = 1, .binding = 1, .format = VK_FORMAT_R32G32B32_SFLOAT, .offset = 0 }, } }; const VkPipelineInputAssemblyStateCreateInfo pipeline_input_assembly_state_create_info = { .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, .primitiveRestartEnable = false, }; const VkPipelineViewportStateCreateInfo pipeline_viewport_state_create_info = { .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, .viewportCount = 1, .scissorCount = 1, }; const VkPipelineRasterizationStateCreateInfo pipeline_rasterization_state_create_info = { .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, .rasterizerDiscardEnable = false, .polygonMode = VK_POLYGON_MODE_FILL, .cullMode = VK_CULL_MODE_NONE, .frontFace = VK_FRONT_FACE_CLOCKWISE, .depthBiasEnable = VK_FALSE, .depthClampEnable = VK_FALSE, .lineWidth = 1.0f, }; const VkPipelineMultisampleStateCreateInfo pipeline_multisample_state_create_info = { .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, .rasterizationSamples = 1, }; const VkPipelineColorBlendStateCreateInfo pipeline_color_blend_state_create_info = { .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, .attachmentCount = 1, .pAttachments = (VkPipelineColorBlendAttachmentState []) { { .colorWriteMask = VK_COLOR_COMPONENT_A_BIT | VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT }, } }; const VkPipelineDynamicStateCreateInfo pipeline_dynamic_state_create_info = { .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, .dynamicStateCount = 2, .pDynamicStates = (VkDynamicState[]) { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, }, }; const VkPipelineLayoutCreateInfo pipeline_layout_create_info = { .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .setLayoutCount = 1, .pSetLayouts = &pipeline->descriptor_set_layout, }; result = vkCreatePipelineLayout(window->vk.dev, &pipeline_layout_create_info, NULL, &pipeline->pipeline_layout); check_vk_success(result, "vkCreatePipelineLayout"); const VkGraphicsPipelineCreateInfo graphics_pipeline_create_info = { .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .stageCount = 2, .pStages = (VkPipelineShaderStageCreateInfo[]) { { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_VERTEX_BIT, .module = vs_module, .pName = "main", }, { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_FRAGMENT_BIT, .module = fs_module, .pName = "main", }, }, .pVertexInputState = &pipeline_vertex_input_state_create_info, .pInputAssemblyState = &pipeline_input_assembly_state_create_info, .pViewportState = &pipeline_viewport_state_create_info, .pRasterizationState = &pipeline_rasterization_state_create_info, .pMultisampleState = &pipeline_multisample_state_create_info, .pColorBlendState = &pipeline_color_blend_state_create_info, .pDynamicState = &pipeline_dynamic_state_create_info, .flags = 0, .layout = pipeline->pipeline_layout, .renderPass = window->vk.renderpass, .subpass = 0, }; result = vkCreateGraphicsPipelines(window->vk.dev, VK_NULL_HANDLE, 1, &graphics_pipeline_create_info, NULL, &pipeline->pipeline); check_vk_success(result, "vkCreateGraphicsPipelines"); vkDestroyShaderModule(window->vk.dev, fs_module, NULL); vkDestroyShaderModule(window->vk.dev, vs_module, NULL); } static void create_vertex_buffer(struct window *window) { VkResult result; /* This can be created statically and shared across * frames since it doesn't change at all */ const float vertices[] = { -0.5f, -0.5f, 0.0, 0.5f, -0.5f, 0.0, 0.0f, 0.5f, 0.0, }; const float colors[] = { 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, }; uint32_t vertex_buffer_size = sizeof(vertices) + sizeof(colors); create_buffer(window, vertex_buffer_size, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &window->vk.vertex_buffer.buffer, &window->vk.vertex_buffer.mem); result = vkMapMemory(window->vk.dev, window->vk.vertex_buffer.mem, 0, vertex_buffer_size, 0, &window->vk.vertex_buffer.map); check_vk_success(result, "vkMapMemory"); memcpy(window->vk.vertex_buffer.map, vertices, sizeof(vertices)); memcpy(window->vk.vertex_buffer.map + sizeof(vertices), colors, sizeof(colors)); } static void create_descriptor_pool(struct window *window, VkDescriptorPool *descriptor_pool, uint32_t base_count, uint32_t maxsets) { VkResult result; const VkDescriptorPoolSize pool_sizes[] = { { .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .descriptorCount = 1 * base_count, }, }; const VkDescriptorPoolCreateInfo pool_info = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, .poolSizeCount = ARRAY_LENGTH(pool_sizes), .pPoolSizes = pool_sizes, .maxSets = maxsets, }; result = vkCreateDescriptorPool(window->vk.dev, &pool_info, NULL, descriptor_pool); check_vk_success(result, "vkCreateDescriptorPool"); } static bool check_extension(const VkExtensionProperties *avail, uint32_t avail_len, const char *name) { for (size_t i = 0; i < avail_len; i++) { if (strcmp(avail[i].extensionName, name) == 0) { return true; } } return false; } static void load_inst_proc(struct window *window, const char *func, void *proc_ptr) { void *proc = (void *)vkGetInstanceProcAddr(window->vk.inst, func); if (proc == NULL) { char err[256]; snprintf(err, sizeof(err), "Failed to vkGetInstanceProcAddr %s\n", func); err[sizeof(err)-1] = '\0'; fprintf(stderr, "%s", err); abort(); } *(void **)proc_ptr = proc; } static void create_instance(struct window *window) { uint32_t num_avail_inst_extns; uint32_t num_inst_extns = 0; VkResult result; result = vkEnumerateInstanceExtensionProperties(NULL, &num_avail_inst_extns, NULL); check_vk_success(result, "vkEnumerateInstanceExtensionProperties"); assert(num_avail_inst_extns > 0); VkExtensionProperties *avail_inst_extns = xmalloc(num_avail_inst_extns * sizeof(VkExtensionProperties)); result = vkEnumerateInstanceExtensionProperties(NULL, &num_avail_inst_extns, avail_inst_extns); check_vk_success(result, "vkEnumerateInstanceExtensionProperties"); const char **inst_extns = xmalloc(num_avail_inst_extns * sizeof(*inst_extns)); inst_extns[num_inst_extns++] = VK_EXT_DEBUG_UTILS_EXTENSION_NAME; inst_extns[num_inst_extns++] = VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME; inst_extns[num_inst_extns++] = VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME; inst_extns[num_inst_extns++] = VK_KHR_SURFACE_EXTENSION_NAME; inst_extns[num_inst_extns++] = VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME; for (uint32_t i = 0; i < num_inst_extns; i++) { uint32_t j; for (j = 0; j < num_avail_inst_extns; j++) { if (strcmp(inst_extns[i], avail_inst_extns[j].extensionName) == 0) { break; } } if (j == num_avail_inst_extns) { fprintf(stderr, "Unsupported instance extension: %s\n", inst_extns[i]); abort(); } } const VkApplicationInfo app_info = { .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .pApplicationName = "simple-vulkan", .apiVersion = VK_MAKE_VERSION(1, 0, 0), }; const VkInstanceCreateInfo inst_create_info = { .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pApplicationInfo = &app_info, .ppEnabledExtensionNames = inst_extns, .enabledExtensionCount = num_inst_extns, }; result = vkCreateInstance(&inst_create_info, NULL, &window->vk.inst); check_vk_success(result, "vkCreateInstance"); load_inst_proc(window, "vkCreateWaylandSurfaceKHR", &window->vk.create_wayland_surface); load_inst_proc(window, "vkGetPhysicalDeviceWaylandPresentationSupportKHR", &window->vk.get_wayland_presentation_support); free(avail_inst_extns); free(inst_extns); } static void choose_physical_device(struct window *window) { uint32_t n_phys_devs; VkPhysicalDevice *phys_devs = NULL; VkResult result; result = vkEnumeratePhysicalDevices(window->vk.inst, &n_phys_devs, NULL); check_vk_success(result, "vkEnumeratePhysicalDevices"); assert(n_phys_devs != 0); phys_devs = xmalloc(n_phys_devs * sizeof(*phys_devs)); result = vkEnumeratePhysicalDevices(window->vk.inst, &n_phys_devs, phys_devs); check_vk_success(result, "vkEnumeratePhysicalDevices"); VkPhysicalDevice physical_device = VK_NULL_HANDLE; /* Pick the first one */ for (uint32_t i = 0; i < n_phys_devs; ++i) { VkPhysicalDeviceProperties props; vkGetPhysicalDeviceProperties(phys_devs[i], &props); if (physical_device == VK_NULL_HANDLE) { physical_device = phys_devs[i]; break; } } if (physical_device == VK_NULL_HANDLE) { fprintf(stderr, "Unable to find a suitable physical device\n"); abort(); } window->vk.phys_dev = physical_device; free(phys_devs); } static void choose_queue_family(struct window *window) { uint32_t n_props = 0; VkQueueFamilyProperties *props = NULL; vkGetPhysicalDeviceQueueFamilyProperties(window->vk.phys_dev, &n_props, NULL); props = xmalloc(n_props * sizeof(*props)); vkGetPhysicalDeviceQueueFamilyProperties(window->vk.phys_dev, &n_props, props); uint32_t family_idx = UINT32_MAX; /* Pick the first graphics queue */ for (uint32_t i = 0; i < n_props; ++i) { if ((props[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) && props[i].queueCount > 0) { family_idx = i; break; } } if (family_idx == UINT32_MAX) { fprintf(stderr, "Physical device exposes no queue with graphics\n"); abort(); } window->vk.queue_family = family_idx; free(props); } static void create_device(struct window *window) { uint32_t num_avail_device_extns; uint32_t num_device_extns = 0; VkResult result; result = vkEnumerateDeviceExtensionProperties(window->vk.phys_dev, NULL, &num_avail_device_extns, NULL); check_vk_success(result, "vkEnumerateDeviceExtensionProperties"); VkExtensionProperties *avail_device_extns = xmalloc(num_avail_device_extns * sizeof(VkExtensionProperties)); result = vkEnumerateDeviceExtensionProperties(window->vk.phys_dev, NULL, &num_avail_device_extns, avail_device_extns); check_vk_success(result, "vkEnumerateDeviceExtensionProperties"); const char **device_extns = xmalloc(num_avail_device_extns * sizeof(*device_extns)); device_extns[num_device_extns++] = VK_KHR_SWAPCHAIN_EXTENSION_NAME; if (check_extension(avail_device_extns, num_avail_device_extns, VK_KHR_INCREMENTAL_PRESENT_EXTENSION_NAME)) { device_extns[num_device_extns++] = VK_KHR_INCREMENTAL_PRESENT_EXTENSION_NAME; window->vk.has_incremental_present = true; } for (uint32_t i = 0; i < num_device_extns; i++) { uint32_t j; for (j = 0; j < num_avail_device_extns; j++) { if (strcmp(device_extns[i], avail_device_extns[j].extensionName) == 0) { break; } } if (j == num_avail_device_extns) { fprintf(stderr, "Unsupported device extension: %s\n", device_extns[i]); abort(); } } const VkDeviceQueueCreateInfo device_queue_info = { .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, .queueFamilyIndex = window->vk.queue_family, .queueCount = 1, .pQueuePriorities = (float[]){ 1.0f }, }; const VkDeviceCreateInfo device_create_info = { .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, .queueCreateInfoCount = 1, .pQueueCreateInfos = &device_queue_info, .enabledExtensionCount = num_device_extns, .ppEnabledExtensionNames = device_extns, }; result = vkCreateDevice(window->vk.phys_dev, &device_create_info, NULL, &window->vk.dev); check_vk_success(result, "vkCreateDevice"); free(avail_device_extns); free(device_extns); } static void init_vulkan(struct window *window) { VkResult result; if (window->needs_buffer_geometry_update) update_buffer_geometry(window); create_instance(window); choose_physical_device(window); choose_queue_family(window); create_device(window); vkGetDeviceQueue(window->vk.dev, 0, 0, &window->vk.queue); if (!window->vk.get_wayland_presentation_support(window->vk.phys_dev, 0, window->display->display)) { fprintf(stderr, "Vulkan not supported on given Wayland surface"); } const VkWaylandSurfaceCreateInfoKHR wayland_surface_create_info = { .sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, .display = window->display->display, .surface = window->surface, }; result = window->vk.create_wayland_surface(window->vk.inst, &wayland_surface_create_info, NULL, &window->vk.surface); check_vk_success(result, "vkCreateWaylandSurfaceKHR"); window->vk.format = choose_surface_format(window); create_renderpass(window); create_descriptor_set_layout(window); create_pipeline(window); create_vertex_buffer(window); create_descriptor_pool(window, &window->vk.descriptor_pool, MAX_CONCURRENT_FRAMES, MAX_CONCURRENT_FRAMES); const VkCommandPoolCreateInfo cmd_pool_create_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, .flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT | VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, .queueFamilyIndex = window->vk.queue_family, }; result = vkCreateCommandPool(window->vk.dev, &cmd_pool_create_info, NULL, &window->vk.cmd_pool); check_vk_success(result, "vkCreateCommandPool"); for (uint32_t i = 0; i < MAX_CONCURRENT_FRAMES; ++i) { struct window_frame *frame = &window->vk.frames[i]; const VkFenceCreateInfo fence_create_info = { .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, .flags = VK_FENCE_CREATE_SIGNALED_BIT }; result = vkCreateFence(window->vk.dev, &fence_create_info, NULL, &frame->fence); check_vk_success(result, "vkCreateFence"); const VkCommandBufferAllocateInfo cmd_alloc_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .commandPool = window->vk.cmd_pool, .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandBufferCount = 1, }; result = vkAllocateCommandBuffers(window->vk.dev, &cmd_alloc_info, &frame->cmd_buffer); check_vk_success(result, "vkAllocateCommandBuffers"); const VkSemaphoreCreateInfo semaphore_create_info = { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, }; result = vkCreateSemaphore(window->vk.dev, &semaphore_create_info, NULL, &frame->image_acquired); check_vk_success(result, "vkCreateSemaphore"); struct window_buffer *ubo_buffer = &frame->ubo_buffer; uint32_t ubo_size = sizeof(float[16]); create_buffer(window, ubo_size, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &ubo_buffer->buffer, &ubo_buffer->mem); result = vkMapMemory(window->vk.dev, ubo_buffer->mem, 0, ubo_size, 0, &ubo_buffer->map); check_vk_success(result, "vkMapMemory"); create_descriptor_set(window, frame); } if (window->vk.has_incremental_present) printf("has %s\n", VK_KHR_INCREMENTAL_PRESENT_EXTENSION_NAME); } static void fini_vulkan(struct window *window) { for (uint32_t i = 0; i < MAX_CONCURRENT_FRAMES; ++i) { struct window_frame *frame = &window->vk.frames[i]; vkDestroySemaphore(window->vk.dev, frame->image_acquired, NULL); vkFreeCommandBuffers(window->vk.dev, window->vk.cmd_pool, 1, &frame->cmd_buffer); vkDestroyFence(window->vk.dev, frame->fence, NULL); struct window_buffer *ubo_buffer = &frame->ubo_buffer; vkUnmapMemory(window->vk.dev, ubo_buffer->mem); vkDestroyBuffer(window->vk.dev, ubo_buffer->buffer, NULL); vkFreeMemory(window->vk.dev, ubo_buffer->mem, NULL); } struct window_vulkan_pipeline *pipeline = &window->vk.pipeline; vkDestroyPipelineLayout(window->vk.dev, pipeline->pipeline_layout, NULL); vkDestroyPipeline(window->vk.dev, pipeline->pipeline, NULL); vkDestroyDescriptorSetLayout(window->vk.dev, pipeline->descriptor_set_layout, NULL); vkDestroyRenderPass(window->vk.dev, window->vk.renderpass, NULL); vkDestroyDescriptorPool(window->vk.dev, window->vk.descriptor_pool, NULL); vkUnmapMemory(window->vk.dev, window->vk.vertex_buffer.mem); vkDestroyBuffer(window->vk.dev, window->vk.vertex_buffer.buffer, NULL); vkFreeMemory(window->vk.dev, window->vk.vertex_buffer.mem, NULL); vkDestroyCommandPool(window->vk.dev, window->vk.cmd_pool, NULL); vkDestroyDevice(window->vk.dev, NULL); vkDestroySurfaceKHR(window->vk.inst, window->vk.surface, NULL); vkDestroyInstance(window->vk.inst, NULL); } static void handle_surface_configure(void *data, struct xdg_surface *surface, uint32_t serial) { struct window *window = data; xdg_surface_ack_configure(surface, serial); window->wait_for_configure = false; } static const struct xdg_surface_listener xdg_surface_listener = { handle_surface_configure }; static void handle_toplevel_configure(void *data, struct xdg_toplevel *toplevel, int32_t width, int32_t height, struct wl_array *states) { struct window *window = data; uint32_t *p; window->fullscreen = 0; window->maximized = 0; wl_array_for_each(p, states) { uint32_t state = *p; switch (state) { case XDG_TOPLEVEL_STATE_FULLSCREEN: window->fullscreen = 1; break; case XDG_TOPLEVEL_STATE_MAXIMIZED: window->maximized = 1; break; } } if (width > 0 && height > 0) { if (!window->fullscreen && !window->maximized) { window->window_size.width = width; window->window_size.height = height; } window->logical_size.width = width; window->logical_size.height = height; } else if (!window->fullscreen && !window->maximized) { window->logical_size = window->window_size; } window->needs_buffer_geometry_update = true; } static void handle_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel) { running = 0; } static const struct xdg_toplevel_listener xdg_toplevel_listener = { handle_toplevel_configure, handle_toplevel_close, }; static void add_window_output(struct window *window, struct wl_output *wl_output) { struct output *output; struct output *output_found = NULL; struct window_output *window_output; wl_list_for_each(output, &window->display->output_list, link) { if (output->wl_output == wl_output) { output_found = output; break; } } if (!output_found) return; window_output = xmalloc(sizeof *window_output); window_output->output = output_found; wl_list_insert(window->window_output_list.prev, &window_output->link); window->needs_buffer_geometry_update = true; } static void destroy_window_output(struct window *window, struct wl_output *wl_output) { struct window_output *window_output; struct window_output *window_output_found = NULL; wl_list_for_each(window_output, &window->window_output_list, link) { if (window_output->output->wl_output == wl_output) { window_output_found = window_output; break; } } if (window_output_found) { wl_list_remove(&window_output_found->link); free(window_output_found); window->needs_buffer_geometry_update = true; } } static void draw_triangle(struct window *window, struct window_frame *frame, struct window_image *image) { VkResult result; const VkCommandBufferBeginInfo command_buffer_begin_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, .flags = 0 }; VkCommandBuffer cmd_buffer = frame->cmd_buffer; result = vkBeginCommandBuffer(cmd_buffer, &command_buffer_begin_info); check_vk_success(result, "vkBeginCommandBuffer"); const VkClearValue clear_color = {{{0.0f, 0.0f, 0.0f, 0.5f}}}; const VkRenderPassBeginInfo renderpass_begin_info = { .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, .renderPass = window->vk.renderpass, .framebuffer = image->framebuffer, .renderArea.offset = {0, 0}, .renderArea.extent = { window->buffer_size.width, window->buffer_size.height }, .clearValueCount = 1, .pClearValues = &clear_color, }; vkCmdBeginRenderPass(cmd_buffer, &renderpass_begin_info, VK_SUBPASS_CONTENTS_INLINE); const VkBuffer buffers[] = { window->vk.vertex_buffer.buffer, window->vk.vertex_buffer.buffer, }; const VkDeviceSize offsets[] = { 0, 3 * sizeof(float[3]), }; vkCmdBindVertexBuffers(cmd_buffer, 0, ARRAY_LENGTH(buffers), buffers, offsets); struct window_vulkan_pipeline *pipeline = &window->vk.pipeline; vkCmdBindPipeline(cmd_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->pipeline); vkCmdBindDescriptorSets(cmd_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->pipeline_layout, 0, 1, &frame->descriptor_set, 0, NULL); const VkViewport viewport = { .x = 0, .y = 0, .width = window->buffer_size.width, .height = window->buffer_size.height, .minDepth = 0, .maxDepth = 1, }; vkCmdSetViewport(cmd_buffer, 0, 1, &viewport); const VkRect2D scissor = { .offset = { 0, 0 }, .extent = { window->buffer_size.width, window->buffer_size.height }, }; vkCmdSetScissor(cmd_buffer, 0, 1, &scissor); vkCmdDraw(cmd_buffer, 3, 1, 0, 0); vkCmdEndRenderPass(cmd_buffer); result = vkEndCommandBuffer(cmd_buffer); check_vk_success(result, "vkEndCommandBuffer"); const VkPipelineStageFlags wait_stages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; const VkSubmitInfo submit_info = { .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .waitSemaphoreCount = 1, .pWaitSemaphores = &frame->image_acquired, .pWaitDstStageMask = wait_stages, .commandBufferCount = 1, .pCommandBuffers = &cmd_buffer, .signalSemaphoreCount = 1, .pSignalSemaphores = &image->render_done, }; result = vkQueueSubmit(window->vk.queue, 1, &submit_info, frame->fence); check_vk_success(result, "vkQueueSubmit"); } static void set_tearing(struct window *window, bool enable) { if (!window->tear_control) return; if (enable) { wp_tearing_control_v1_set_presentation_hint(window->tear_control, WP_TEARING_CONTROL_V1_PRESENTATION_HINT_ASYNC); } else { wp_tearing_control_v1_set_presentation_hint(window->tear_control, WP_TEARING_CONTROL_V1_PRESENTATION_HINT_VSYNC); } window->tear_enabled = enable; } static void surface_enter(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output) { struct window *window = data; add_window_output(window, wl_output); } static void surface_leave(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output) { struct window *window = data; destroy_window_output(window, wl_output); } static const struct wl_surface_listener surface_listener = { surface_enter, surface_leave }; static void fractional_scale_handle_preferred_scale(void *data, struct wp_fractional_scale_v1 *info, uint32_t wire_scale) { struct window *window = data; window->fractional_buffer_scale = wire_scale / 120.0; window->needs_buffer_geometry_update = true; } static const struct wp_fractional_scale_v1_listener fractional_scale_listener = { .preferred_scale = fractional_scale_handle_preferred_scale, }; static void create_surface(struct window *window) { struct display *display = window->display; window->surface = wl_compositor_create_surface(display->compositor); wl_surface_add_listener(window->surface, &surface_listener, window); if (display->tearing_manager && window->tearing) { window->tear_control = wp_tearing_control_manager_v1_get_tearing_control( display->tearing_manager, window->surface); set_tearing(window, true); } window->xdg_surface = xdg_wm_base_get_xdg_surface(display->wm_base, window->surface); xdg_surface_add_listener(window->xdg_surface, &xdg_surface_listener, window); 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, "simple-vulkan"); xdg_toplevel_set_app_id(window->xdg_toplevel, "org.freedesktop.weston.simple-vulkan"); if (window->fullscreen) xdg_toplevel_set_fullscreen(window->xdg_toplevel, NULL); else if (window->maximized) xdg_toplevel_set_maximized(window->xdg_toplevel); if (display->viewporter && display->fractional_scale_manager) { window->viewport = wp_viewporter_get_viewport(display->viewporter, window->surface); window->fractional_scale_obj = wp_fractional_scale_manager_v1_get_fractional_scale(display->fractional_scale_manager, window->surface); wp_fractional_scale_v1_add_listener(window->fractional_scale_obj, &fractional_scale_listener, window); } window->wait_for_configure = true; wl_surface_commit(window->surface); } static void destroy_surface(struct window *window) { if (window->xdg_toplevel) xdg_toplevel_destroy(window->xdg_toplevel); if (window->xdg_surface) xdg_surface_destroy(window->xdg_surface); if (window->viewport) wp_viewport_destroy(window->viewport); if (window->fractional_scale_obj) wp_fractional_scale_v1_destroy(window->fractional_scale_obj); wl_surface_destroy(window->surface); } static void redraw(struct window *window) { float angle; struct weston_matrix rotation; static const uint32_t speed_div = 5, benchmark_interval = 5; struct timeval tv; VkResult result; if (window->needs_buffer_geometry_update) { update_buffer_geometry(window); recreate_swapchain(window); } gettimeofday(&tv, NULL); uint32_t time = tv.tv_sec * 1000 + tv.tv_usec / 1000; if (window->frames == 0) { window->initial_frame_time = time; window->benchmark_time = time; } if (time - window->benchmark_time > (benchmark_interval * 1000)) { printf("%d frames in %d seconds: %f fps\n", window->frames, benchmark_interval, (float) window->frames / benchmark_interval); window->benchmark_time = time; window->frames = 0; if (window->toggled_tearing) set_tearing(window, window->tear_enabled ^ true); } weston_matrix_init(&rotation); angle = ((time - window->initial_frame_time) / speed_div) % 360 * M_PI / 180.0; rotation.M.col[0].el[0] = cos(angle); rotation.M.col[0].el[2] = sin(angle); rotation.M.col[2].el[0] = -sin(angle); rotation.M.col[2].el[2] = cos(angle); /* Flip from OpenGL to Vulkan coordinates */ weston_matrix_scale(&rotation, 1.0, -1.0, 1.0); /* Keep it inside the Vulkan clip volume (z 0..1) */ weston_matrix_translate(&rotation, 0.0, 0.0, 0.5); switch (window->buffer_transform) { default: case WL_OUTPUT_TRANSFORM_NORMAL: case WL_OUTPUT_TRANSFORM_FLIPPED: break; case WL_OUTPUT_TRANSFORM_90: case WL_OUTPUT_TRANSFORM_FLIPPED_90: weston_matrix_rotate_xy(&rotation, 0, 1); break; case WL_OUTPUT_TRANSFORM_180: case WL_OUTPUT_TRANSFORM_FLIPPED_180: weston_matrix_rotate_xy(&rotation, -1, 0); break; case WL_OUTPUT_TRANSFORM_270: case WL_OUTPUT_TRANSFORM_FLIPPED_270: weston_matrix_rotate_xy(&rotation, 0, -1); break; } struct window_frame *frame = &window->vk.frames[window->vk.frame_index]; memcpy(frame->ubo_buffer.map, &rotation.M.colmaj, sizeof(rotation.M.colmaj)); assert(window->vk.frame_index < ARRAY_LENGTH(window->vk.frames)); vkWaitForFences(window->vk.dev, 1, &frame->fence, VK_TRUE, UINT64_MAX); uint32_t image_index; result = vkAcquireNextImageKHR(window->vk.dev, window->vk.swapchain, UINT64_MAX, frame->image_acquired, VK_NULL_HANDLE, &image_index); if (result == VK_SUBOPTIMAL_KHR) { recreate_swapchain(window); return; } assert(result == VK_SUCCESS); vkResetFences(window->vk.dev, 1, &frame->fence); assert(image_index < ARRAY_LENGTH(window->vk.images)); struct window_image *image = &window->vk.images[image_index]; draw_triangle(window, frame, image); VkPresentInfoKHR present_info = { .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, .waitSemaphoreCount = 1, .pWaitSemaphores = &image->render_done, .swapchainCount = 1, .pSwapchains = &window->vk.swapchain, .pImageIndices = &image_index, .pResults = NULL, }; if (window->vk.has_incremental_present) { const VkRectLayerKHR rect = { .offset = { window->buffer_size.width / 4 - 1, window->buffer_size.height / 4 - 1 }, .extent = { window->buffer_size.width / 2 + 2, window->buffer_size.height / 2 + 2 }, }; const VkPresentRegionKHR region = { .rectangleCount = 1, .pRectangles = &rect, }; VkPresentRegionsKHR present_regions = { .sType = VK_STRUCTURE_TYPE_PRESENT_REGIONS_KHR, .swapchainCount = 1, .pRegions = ®ion, }; pnext(&present_info, &present_regions); result = vkQueuePresentKHR(window->vk.queue, &present_info); } else { result = vkQueuePresentKHR(window->vk.queue, &present_info); } if (result != VK_SUCCESS) return; if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { recreate_swapchain(window); return; } else if (result != VK_SUCCESS) { assert(0); } window->frames++; window->vk.frame_index = (window->vk.frame_index + 1) % MAX_CONCURRENT_FRAMES; } static void pointer_handle_enter(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t sx, wl_fixed_t sy) { struct display *display = data; struct wl_buffer *buffer; struct wl_cursor *cursor = display->default_cursor; struct wl_cursor_image *image; if (display->window->fullscreen) wl_pointer_set_cursor(pointer, serial, NULL, 0, 0); else if (cursor) { image = display->default_cursor->images[0]; buffer = wl_cursor_image_get_buffer(image); if (!buffer) return; wl_pointer_set_cursor(pointer, serial, display->cursor_surface, image->hotspot_x, image->hotspot_y); wl_surface_attach(display->cursor_surface, buffer, 0, 0); wl_surface_damage(display->cursor_surface, 0, 0, image->width, image->height); wl_surface_commit(display->cursor_surface); } } static void pointer_handle_leave(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface) { } static void pointer_handle_motion(void *data, struct wl_pointer *pointer, uint32_t time, wl_fixed_t sx, wl_fixed_t sy) { } static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { struct display *display = data; if (!display->window->xdg_toplevel) return; if (button == BTN_LEFT && state == WL_POINTER_BUTTON_STATE_PRESSED) xdg_toplevel_move(display->window->xdg_toplevel, display->seat, serial); } static void pointer_handle_axis(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { } static const struct wl_pointer_listener pointer_listener = { pointer_handle_enter, pointer_handle_leave, pointer_handle_motion, pointer_handle_button, pointer_handle_axis, }; static void touch_handle_down(void *data, struct wl_touch *wl_touch, uint32_t serial, uint32_t time, struct wl_surface *surface, int32_t id, wl_fixed_t x_w, wl_fixed_t y_w) { struct display *d = (struct display *)data; if (!d->wm_base) return; xdg_toplevel_move(d->window->xdg_toplevel, d->seat, serial); } static void touch_handle_up(void *data, struct wl_touch *wl_touch, uint32_t serial, uint32_t time, int32_t id) { } static void touch_handle_motion(void *data, struct wl_touch *wl_touch, uint32_t time, int32_t id, wl_fixed_t x_w, wl_fixed_t y_w) { } static void touch_handle_frame(void *data, struct wl_touch *wl_touch) { } static void touch_handle_cancel(void *data, struct wl_touch *wl_touch) { } static const struct wl_touch_listener touch_listener = { touch_handle_down, touch_handle_up, touch_handle_motion, touch_handle_frame, touch_handle_cancel, }; static void keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard, uint32_t format, int fd, uint32_t size) { /* Just so we don’t leak the keymap fd */ close(fd); } static void keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) { } static void keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface) { } static void keyboard_handle_key(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { struct display *d = data; if (!d->wm_base) return; if (key == KEY_F11 && state) { if (d->window->fullscreen) xdg_toplevel_unset_fullscreen(d->window->xdg_toplevel); else xdg_toplevel_set_fullscreen(d->window->xdg_toplevel, NULL); } else if (key == KEY_ESC && state) running = 0; } static void keyboard_handle_modifiers(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { } static const struct wl_keyboard_listener keyboard_listener = { keyboard_handle_keymap, keyboard_handle_enter, keyboard_handle_leave, keyboard_handle_key, keyboard_handle_modifiers, }; static void seat_handle_capabilities(void *data, struct wl_seat *seat, enum wl_seat_capability caps) { struct display *d = data; if ((caps & WL_SEAT_CAPABILITY_POINTER) && !d->pointer) { d->pointer = wl_seat_get_pointer(seat); wl_pointer_add_listener(d->pointer, &pointer_listener, d); } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && d->pointer) { wl_pointer_destroy(d->pointer); d->pointer = NULL; } if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !d->keyboard) { d->keyboard = wl_seat_get_keyboard(seat); wl_keyboard_add_listener(d->keyboard, &keyboard_listener, d); } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && d->keyboard) { wl_keyboard_destroy(d->keyboard); d->keyboard = NULL; } if ((caps & WL_SEAT_CAPABILITY_TOUCH) && !d->touch) { d->touch = wl_seat_get_touch(seat); wl_touch_set_user_data(d->touch, d); wl_touch_add_listener(d->touch, &touch_listener, d); } else if (!(caps & WL_SEAT_CAPABILITY_TOUCH) && d->touch) { wl_touch_destroy(d->touch); d->touch = NULL; } } static const struct wl_seat_listener seat_listener = { seat_handle_capabilities, }; static void xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, uint32_t serial) { xdg_wm_base_pong(shell, serial); } static const struct xdg_wm_base_listener wm_base_listener = { xdg_wm_base_ping, }; static void display_handle_geometry(void *data, struct wl_output *wl_output, int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char *make, const char *model, int32_t transform) { struct output *output = data; output->transform = transform; output->display->window->needs_buffer_geometry_update = true; } static void display_handle_mode(void *data, struct wl_output *wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) { } static void display_handle_done(void *data, struct wl_output *wl_output) { } static void display_handle_scale(void *data, struct wl_output *wl_output, int32_t scale) { struct output *output = data; output->scale = scale; output->display->window->needs_buffer_geometry_update = true; } static const struct wl_output_listener output_listener = { display_handle_geometry, display_handle_mode, display_handle_done, display_handle_scale }; static void display_add_output(struct display *d, uint32_t name) { struct output *output; output = xzalloc(sizeof *output); output->display = d; output->scale = 1; output->wl_output = wl_registry_bind(d->registry, name, &wl_output_interface, 2); output->name = name; wl_list_insert(d->output_list.prev, &output->link); wl_output_add_listener(output->wl_output, &output_listener, output); } static void display_destroy_output(struct display *d, struct output *output) { destroy_window_output(d->window, output->wl_output); wl_output_destroy(output->wl_output); wl_list_remove(&output->link); free(output); } static void display_destroy_outputs(struct display *d) { struct output *tmp; struct output *output; wl_list_for_each_safe(output, tmp, &d->output_list, link) display_destroy_output(d, output); } static void registry_handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { struct display *d = data; if (strcmp(interface, wl_compositor_interface.name) == 0) { d->compositor = wl_registry_bind(registry, name, &wl_compositor_interface, MIN(version, 4)); } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { d->wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 1); xdg_wm_base_add_listener(d->wm_base, &wm_base_listener, d); } else if (strcmp(interface, wl_seat_interface.name) == 0) { d->seat = wl_registry_bind(registry, name, &wl_seat_interface, 1); wl_seat_add_listener(d->seat, &seat_listener, d); } else if (strcmp(interface, wl_shm_interface.name) == 0) { d->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); d->cursor_theme = wl_cursor_theme_load(NULL, 32, d->shm); if (!d->cursor_theme) { fprintf(stderr, "unable to load default theme\n"); return; } d->default_cursor = wl_cursor_theme_get_cursor(d->cursor_theme, "left_ptr"); if (!d->default_cursor) { fprintf(stderr, "unable to load default left pointer\n"); // TODO: abort ? } } else if (strcmp(interface, wl_output_interface.name) == 0 && version >= 2) { display_add_output(d, name); } else if (strcmp(interface, wp_tearing_control_manager_v1_interface.name) == 0) { d->tearing_manager = wl_registry_bind(registry, name, &wp_tearing_control_manager_v1_interface, 1); } else if (strcmp(interface, wp_viewporter_interface.name) == 0) { d->viewporter = wl_registry_bind(registry, name, &wp_viewporter_interface, 1); } else if (strcmp(interface, wp_fractional_scale_manager_v1_interface.name) == 0) { d->fractional_scale_manager = wl_registry_bind(registry, name, &wp_fractional_scale_manager_v1_interface, 1); } } static void registry_handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { struct display *d = data; struct output *output; wl_list_for_each(output, &d->output_list, link) { if (output->name == name) { display_destroy_output(d, output); break; } } } static const struct wl_registry_listener registry_listener = { registry_handle_global, registry_handle_global_remove }; static void signal_int(int signum) { running = 0; } static void usage(int error_code) { fprintf(stderr, "Usage: simple-vulkan [OPTIONS]\n\n" " -d \tBuffer swap delay in microseconds\n" " -p \tSet presentation mode\n" " immediate = 0\n" " mailbox = 1\n" " fifo = 2 (default)\n" " fifo_relaxed = 3\n" " -f\tRun in fullscreen mode\n" " -r\tUse fixed width/height ratio when run in fullscreen mode\n" " -m\tRun in maximized mode\n" " -o\tCreate an opaque surface\n" " -t\tEnable tearing via the tearing_control protocol\n" " -T\tEnable and disable tearing every 5 seconds\n" " -h\tThis help text\n\n"); exit(error_code); } int main(int argc, char **argv) { struct sigaction sigint; struct display display = {}; struct window window = {}; int i, ret = 0; window.display = &display; display.window = &window; window.buffer_size.width = 250; window.buffer_size.height = 250; window.window_size = window.buffer_size; window.buffer_scale = 1; window.buffer_transform = WL_OUTPUT_TRANSFORM_NORMAL; window.needs_buffer_geometry_update = false; window.delay = 0; window.fullscreen_ratio = false; window.vk.present_mode = VK_PRESENT_MODE_FIFO_KHR; wl_list_init(&display.output_list); wl_list_init(&window.window_output_list); for (i = 1; i < argc; i++) { if (strcmp("-d", argv[i]) == 0 && i+1 < argc) window.delay = atoi(argv[++i]); else if (strcmp("-p", argv[i]) == 0 && i+1 < argc) { window.vk.present_mode = atoi(argv[++i]); assert(window.vk.present_mode >= 0 && window.vk.present_mode < 4); } else if (strcmp("-f", argv[i]) == 0) window.fullscreen = 1; else if (strcmp("-r", argv[i]) == 0) window.fullscreen_ratio = true; else if (strcmp("-m", argv[i]) == 0) window.maximized = 1; else if (strcmp("-o", argv[i]) == 0) window.opaque = 1; else if (strcmp("-t", argv[i]) == 0) { window.tearing = true; } else if (strcmp("-T", argv[i]) == 0) { window.tearing = true; window.toggled_tearing = true; } else if (strcmp("-h", argv[i]) == 0) usage(EXIT_SUCCESS); else usage(EXIT_FAILURE); } display.display = wl_display_connect(NULL); assert(display.display); display.registry = wl_display_get_registry(display.display); wl_registry_add_listener(display.registry, ®istry_listener, &display); wl_display_roundtrip(display.display); if (!display.wm_base) { fprintf(stderr, "xdg-shell support required. simple-vulkan exiting\n"); goto out_no_xdg_shell; } create_surface(&window); /* we already have wait_for_configure set after create_surface() */ while (running && ret != -1 && window.wait_for_configure) { ret = wl_display_dispatch(display.display); /* wait until xdg_surface::configure acks the new dimensions */ if (window.wait_for_configure) continue; init_vulkan(&window); } create_swapchain(&window); display.cursor_surface = wl_compositor_create_surface(display.compositor); sigint.sa_handler = signal_int; sigemptyset(&sigint.sa_mask); sigint.sa_flags = SA_RESETHAND; sigaction(SIGINT, &sigint, NULL); while (running && ret != -1) { ret = wl_display_dispatch_pending(display.display); redraw(&window); } fprintf(stderr, "simple-vulkan exiting\n"); destroy_surface(&window); destroy_swapchain(&window); fini_vulkan(&window); wl_surface_destroy(display.cursor_surface); out_no_xdg_shell: display_destroy_outputs(&display); if (display.cursor_theme) wl_cursor_theme_destroy(display.cursor_theme); if (display.shm) wl_shm_destroy(display.shm); if (display.pointer) wl_pointer_destroy(display.pointer); if (display.keyboard) wl_keyboard_destroy(display.keyboard); if (display.touch) wl_touch_destroy(display.touch); if (display.seat) wl_seat_destroy(display.seat); if (display.wm_base) xdg_wm_base_destroy(display.wm_base); if (display.compositor) wl_compositor_destroy(display.compositor); if (display.tearing_manager) wp_tearing_control_manager_v1_destroy(display.tearing_manager); if (display.viewporter) wp_viewporter_destroy(display.viewporter); if (display.fractional_scale_manager) wp_fractional_scale_manager_v1_destroy(display.fractional_scale_manager); wl_registry_destroy(display.registry); wl_display_flush(display.display); wl_display_disconnect(display.display); return 0; }