weston/clients/simple-vulkan.c

2172 lines
63 KiB
C
Raw Normal View History

/*
* 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;
}