weston/clients/simple-vulkan.c
Erico Nunes a79e7c3e8d clients/simple-vulkan: move render_done semaphore to image
In some cases it is possible that a frame becomes available by its
fence but the last used swapchain image has still not been returned to
the swapchain. If the render_done semaphore is part of the frame struct
it will be reused and cause a validation error.
To prevent this from happening, move the render_done semaphore to the
image struct so that each of those semaphores is only used when the
associated image is safely acquired from the swapchain.

Signed-off-by: Erico Nunes <nunes.erico@gmail.com>
2025-06-25 14:12:22 +00:00

2171 lines
63 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright © 2025 Erico Nunes
*
* based on simple-egl.c:
* Copyright © 2011 Benjamin Franzke
* Copyright (c) 2012 Arvin Schnell <arvin.schnell@gmail.com>
* Copyright (c) 2012 Rob Clark <rob@ti.com>
* 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 <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <math.h>
#include <assert.h>
#include <signal.h>
#include <linux/input.h>
#include <wayland-client.h>
#include <wayland-cursor.h>
#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 <sys/types.h>
#include <unistd.h>
#include <libweston/matrix.h>
#include "shared/helpers.h"
#include "shared/platform.h"
#include "shared/xalloc.h"
#define VK_USE_PLATFORM_WAYLAND_KHR
#define VK_PROTOTYPES
#include <vulkan/vulkan.h>
#include <vulkan/vulkan_wayland.h>
#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);
vkResetFences(window->vk.dev, 1, &frame->fence);
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);
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 = &region,
};
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 dont 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 <us>\tBuffer swap delay in microseconds\n"
" -p <presentation mode>\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,
&registry_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;
}