diff --git a/clients/meson.build b/clients/meson.build index f86413761..e789670f8 100644 --- a/clients/meson.build +++ b/clients/meson.build @@ -172,6 +172,47 @@ simple_clients = [ }, ] +if dep_vulkan.found() and prog_glslang.found() + srcs_simple_vulkan_shaders = [ + 'simple_vulkan_vertex_shader.vert', + 'simple_vulkan_fragment_shader.frag', + ] + + simple_vulkan_shaders = [] + foreach s : srcs_simple_vulkan_shaders + simple_vulkan_shaders += custom_target(s + '.spv.h', + command: [ prog_glslang, '@INPUT@', '--quiet', '--variable-name', '@BASENAME@', '-V', '-x', '-o', '@OUTPUT@' ], + input: s, + output: '@BASENAME@.spv.h', + ) + endforeach + + simple_clients += { + 'name': 'vulkan', + 'sources': [ + 'simple-vulkan.c', + simple_vulkan_shaders, + fractional_scale_v1_client_protocol_h, + fractional_scale_v1_protocol_c, + tearing_control_v1_client_protocol_h, + tearing_control_v1_protocol_c, + viewporter_client_protocol_h, + viewporter_protocol_c, + xdg_shell_client_protocol_h, + xdg_shell_protocol_c, + ], + 'dep_objs': [ + dep_vulkan, + dep_libm, + dep_libshared, + dep_matrix_c, + dep_wayland_client, + ], + 'deps': [ 'vulkan', 'wayland-cursor' ], + 'options': [ 'renderer-gl' ] + } +endif + foreach t : simple_clients if simple_build_all or simple_clients_enabled.contains(t.get('name')) t_name = 'weston-simple-' + t.get('name') diff --git a/clients/simple-vulkan.c b/clients/simple-vulkan.c new file mode 100644 index 000000000..b9865fb90 --- /dev/null +++ b/clients/simple-vulkan.c @@ -0,0 +1,2163 @@ +/* + * Copyright © 2025 Erico Nunes + * + * based on simple-egl.c: + * Copyright © 2011 Benjamin Franzke + * Copyright (c) 2012 Arvin Schnell + * Copyright (c) 2012 Rob Clark + * Copyright © 2015 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "fractional-scale-v1-client-protocol.h" +#include "viewporter-client-protocol.h" +#include "xdg-shell-client-protocol.h" +#include "tearing-control-v1-client-protocol.h" + +#include +#include + +#include +#include "shared/helpers.h" +#include "shared/platform.h" +#include "shared/xalloc.h" + +#define VK_USE_PLATFORM_WAYLAND_KHR +#define VK_PROTOTYPES +#include +#include + +#define MAX_NUM_IMAGES 5 +#define MAX_CONCURRENT_FRAMES 2 + +/* const uint32_t simple_vulkan_vertex_shader[]; simple_vulkan_vertex_shader.frag */ +#include "simple_vulkan_vertex_shader.spv.h" + +/* const uint32_t simple_vulkan_fragment_shader[]; simple_vulkan_fragment_shader.frag */ +#include "simple_vulkan_fragment_shader.spv.h" + +struct window; +struct seat; + +struct display { + struct wl_display *display; + struct wl_registry *registry; + struct wl_compositor *compositor; + struct xdg_wm_base *wm_base; + struct wl_seat *seat; + struct wl_pointer *pointer; + struct wl_touch *touch; + struct wl_keyboard *keyboard; + struct wl_shm *shm; + struct wl_cursor_theme *cursor_theme; + struct wl_cursor *default_cursor; + struct wl_surface *cursor_surface; + struct wp_tearing_control_manager_v1 *tearing_manager; + struct wp_viewporter *viewporter; + struct wp_fractional_scale_manager_v1 *fractional_scale_manager; + struct window *window; + + struct wl_list output_list; /* struct output::link */ +}; + +struct geometry { + int width, height; +}; + +struct window_image { + VkImageView image_view; + VkFramebuffer framebuffer; +}; + +struct window_buffer { + VkBuffer buffer; + VkDeviceMemory mem; + void *map; +}; + +struct window_frame { + VkSemaphore render_done; + 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++) { + 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); + } +} + +static void +destroy_swapchain(struct window *window) +{ + vkDeviceWaitIdle(window->vk.dev); + + for (uint32_t i = 0; i < window->vk.image_count; i++) { + 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->render_done); + check_vk_success(result, "vkCreateSemaphore"); + 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); + vkDestroySemaphore(window->vk.dev, frame->render_done, 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 = &frame->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)); + + draw_triangle(window, frame, &window->vk.images[image_index]); + + VkPresentInfoKHR present_info = { + .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + .waitSemaphoreCount = 1, + .pWaitSemaphores = &frame->render_done, + .swapchainCount = 1, + .pSwapchains = &window->vk.swapchain, + .pImageIndices = &image_index, + .pResults = NULL, + }; + + if (window->vk.has_incremental_present) { + const VkRectLayerKHR rect = { + .offset = { + window->buffer_size.width / 4 - 1, + window->buffer_size.height / 4 - 1 + }, + .extent = { + window->buffer_size.width / 2 + 2, + window->buffer_size.height / 2 + 2 + }, + }; + const VkPresentRegionKHR region = { + .rectangleCount = 1, + .pRectangles = &rect, + }; + VkPresentRegionsKHR present_regions = { + .sType = VK_STRUCTURE_TYPE_PRESENT_REGIONS_KHR, + .swapchainCount = 1, + .pRegions = ®ion, + }; + pnext(&present_info, &present_regions); + + result = vkQueuePresentKHR(window->vk.queue, &present_info); + } else { + result = vkQueuePresentKHR(window->vk.queue, &present_info); + } + + if (result != VK_SUCCESS) + return; + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { + recreate_swapchain(window); + return; + } else if (result != VK_SUCCESS) { + assert(0); + } + + window->frames++; + window->vk.frame_index = (window->vk.frame_index + 1) % MAX_CONCURRENT_FRAMES; +} + +static void +pointer_handle_enter(void *data, struct wl_pointer *pointer, + uint32_t serial, struct wl_surface *surface, + wl_fixed_t sx, wl_fixed_t sy) +{ + struct display *display = data; + struct wl_buffer *buffer; + struct wl_cursor *cursor = display->default_cursor; + struct wl_cursor_image *image; + + if (display->window->fullscreen) + wl_pointer_set_cursor(pointer, serial, NULL, 0, 0); + else if (cursor) { + image = display->default_cursor->images[0]; + buffer = wl_cursor_image_get_buffer(image); + if (!buffer) + return; + wl_pointer_set_cursor(pointer, serial, + display->cursor_surface, + image->hotspot_x, + image->hotspot_y); + wl_surface_attach(display->cursor_surface, buffer, 0, 0); + wl_surface_damage(display->cursor_surface, 0, 0, + image->width, image->height); + wl_surface_commit(display->cursor_surface); + } +} + +static void +pointer_handle_leave(void *data, struct wl_pointer *pointer, + uint32_t serial, struct wl_surface *surface) +{ +} + +static void +pointer_handle_motion(void *data, struct wl_pointer *pointer, + uint32_t time, wl_fixed_t sx, wl_fixed_t sy) +{ +} + +static void +pointer_handle_button(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, uint32_t time, uint32_t button, + uint32_t state) +{ + struct display *display = data; + + if (!display->window->xdg_toplevel) + return; + + if (button == BTN_LEFT && state == WL_POINTER_BUTTON_STATE_PRESSED) + xdg_toplevel_move(display->window->xdg_toplevel, + display->seat, serial); +} + +static void +pointer_handle_axis(void *data, struct wl_pointer *wl_pointer, + uint32_t time, uint32_t axis, wl_fixed_t value) +{ +} + +static const struct wl_pointer_listener pointer_listener = { + pointer_handle_enter, + pointer_handle_leave, + pointer_handle_motion, + pointer_handle_button, + pointer_handle_axis, +}; + +static void +touch_handle_down(void *data, struct wl_touch *wl_touch, + uint32_t serial, uint32_t time, struct wl_surface *surface, + int32_t id, wl_fixed_t x_w, wl_fixed_t y_w) +{ + struct display *d = (struct display *)data; + + if (!d->wm_base) + return; + + xdg_toplevel_move(d->window->xdg_toplevel, d->seat, serial); +} + +static void +touch_handle_up(void *data, struct wl_touch *wl_touch, + uint32_t serial, uint32_t time, int32_t id) +{ +} + +static void +touch_handle_motion(void *data, struct wl_touch *wl_touch, + uint32_t time, int32_t id, wl_fixed_t x_w, wl_fixed_t y_w) +{ +} + +static void +touch_handle_frame(void *data, struct wl_touch *wl_touch) +{ +} + +static void +touch_handle_cancel(void *data, struct wl_touch *wl_touch) +{ +} + +static const struct wl_touch_listener touch_listener = { + touch_handle_down, + touch_handle_up, + touch_handle_motion, + touch_handle_frame, + touch_handle_cancel, +}; + +static void +keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard, + uint32_t format, int fd, uint32_t size) +{ + /* Just so we don’t leak the keymap fd */ + close(fd); +} + +static void +keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, + uint32_t serial, struct wl_surface *surface, + struct wl_array *keys) +{ +} + +static void +keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, + uint32_t serial, struct wl_surface *surface) +{ +} + +static void +keyboard_handle_key(void *data, struct wl_keyboard *keyboard, + uint32_t serial, uint32_t time, uint32_t key, + uint32_t state) +{ + struct display *d = data; + + if (!d->wm_base) + return; + + if (key == KEY_F11 && state) { + if (d->window->fullscreen) + xdg_toplevel_unset_fullscreen(d->window->xdg_toplevel); + else + xdg_toplevel_set_fullscreen(d->window->xdg_toplevel, NULL); + } else if (key == KEY_ESC && state) + running = 0; +} + +static void +keyboard_handle_modifiers(void *data, struct wl_keyboard *keyboard, + uint32_t serial, uint32_t mods_depressed, + uint32_t mods_latched, uint32_t mods_locked, + uint32_t group) +{ +} + +static const struct wl_keyboard_listener keyboard_listener = { + keyboard_handle_keymap, + keyboard_handle_enter, + keyboard_handle_leave, + keyboard_handle_key, + keyboard_handle_modifiers, +}; + +static void +seat_handle_capabilities(void *data, struct wl_seat *seat, + enum wl_seat_capability caps) +{ + struct display *d = data; + + if ((caps & WL_SEAT_CAPABILITY_POINTER) && !d->pointer) { + d->pointer = wl_seat_get_pointer(seat); + wl_pointer_add_listener(d->pointer, &pointer_listener, d); + } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && d->pointer) { + wl_pointer_destroy(d->pointer); + d->pointer = NULL; + } + + if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !d->keyboard) { + d->keyboard = wl_seat_get_keyboard(seat); + wl_keyboard_add_listener(d->keyboard, &keyboard_listener, d); + } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && d->keyboard) { + wl_keyboard_destroy(d->keyboard); + d->keyboard = NULL; + } + + if ((caps & WL_SEAT_CAPABILITY_TOUCH) && !d->touch) { + d->touch = wl_seat_get_touch(seat); + wl_touch_set_user_data(d->touch, d); + wl_touch_add_listener(d->touch, &touch_listener, d); + } else if (!(caps & WL_SEAT_CAPABILITY_TOUCH) && d->touch) { + wl_touch_destroy(d->touch); + d->touch = NULL; + } +} + +static const struct wl_seat_listener seat_listener = { + seat_handle_capabilities, +}; + +static void +xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, uint32_t serial) +{ + xdg_wm_base_pong(shell, serial); +} + +static const struct xdg_wm_base_listener wm_base_listener = { + xdg_wm_base_ping, +}; + +static void +display_handle_geometry(void *data, + struct wl_output *wl_output, + int32_t x, int32_t y, + int32_t physical_width, + int32_t physical_height, + int32_t subpixel, + const char *make, + const char *model, + int32_t transform) +{ + struct output *output = data; + + output->transform = transform; + output->display->window->needs_buffer_geometry_update = true; +} + +static void +display_handle_mode(void *data, + struct wl_output *wl_output, + uint32_t flags, + int32_t width, + int32_t height, + int32_t refresh) +{ +} + +static void +display_handle_done(void *data, + struct wl_output *wl_output) +{ +} + +static void +display_handle_scale(void *data, + struct wl_output *wl_output, + int32_t scale) +{ + struct output *output = data; + + output->scale = scale; + output->display->window->needs_buffer_geometry_update = true; +} + +static const struct wl_output_listener output_listener = { + display_handle_geometry, + display_handle_mode, + display_handle_done, + display_handle_scale +}; + +static void +display_add_output(struct display *d, uint32_t name) +{ + struct output *output; + + output = xzalloc(sizeof *output); + output->display = d; + output->scale = 1; + output->wl_output = + wl_registry_bind(d->registry, name, &wl_output_interface, 2); + output->name = name; + wl_list_insert(d->output_list.prev, &output->link); + + wl_output_add_listener(output->wl_output, &output_listener, output); +} + +static void +display_destroy_output(struct display *d, struct output *output) +{ + destroy_window_output(d->window, output->wl_output); + wl_output_destroy(output->wl_output); + wl_list_remove(&output->link); + free(output); +} + +static void +display_destroy_outputs(struct display *d) +{ + struct output *tmp; + struct output *output; + + wl_list_for_each_safe(output, tmp, &d->output_list, link) + display_destroy_output(d, output); +} + +static void +registry_handle_global(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, uint32_t version) +{ + struct display *d = data; + + if (strcmp(interface, wl_compositor_interface.name) == 0) { + d->compositor = + wl_registry_bind(registry, name, + &wl_compositor_interface, + MIN(version, 4)); + } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { + d->wm_base = wl_registry_bind(registry, name, + &xdg_wm_base_interface, 1); + xdg_wm_base_add_listener(d->wm_base, &wm_base_listener, d); + } else if (strcmp(interface, wl_seat_interface.name) == 0) { + d->seat = wl_registry_bind(registry, name, + &wl_seat_interface, 1); + wl_seat_add_listener(d->seat, &seat_listener, d); + } else if (strcmp(interface, wl_shm_interface.name) == 0) { + d->shm = wl_registry_bind(registry, name, + &wl_shm_interface, 1); + d->cursor_theme = wl_cursor_theme_load(NULL, 32, d->shm); + if (!d->cursor_theme) { + fprintf(stderr, "unable to load default theme\n"); + return; + } + d->default_cursor = + wl_cursor_theme_get_cursor(d->cursor_theme, "left_ptr"); + if (!d->default_cursor) { + fprintf(stderr, "unable to load default left pointer\n"); + // TODO: abort ? + } + } else if (strcmp(interface, wl_output_interface.name) == 0 && version >= 2) { + display_add_output(d, name); + } else if (strcmp(interface, wp_tearing_control_manager_v1_interface.name) == 0) { + d->tearing_manager = wl_registry_bind(registry, name, + &wp_tearing_control_manager_v1_interface, + 1); + } else if (strcmp(interface, wp_viewporter_interface.name) == 0) { + d->viewporter = wl_registry_bind(registry, name, + &wp_viewporter_interface, + 1); + } else if (strcmp(interface, wp_fractional_scale_manager_v1_interface.name) == 0) { + d->fractional_scale_manager = + wl_registry_bind(registry, name, + &wp_fractional_scale_manager_v1_interface, + 1); + } +} + +static void +registry_handle_global_remove(void *data, struct wl_registry *registry, + uint32_t name) +{ + struct display *d = data; + struct output *output; + + wl_list_for_each(output, &d->output_list, link) { + if (output->name == name) { + display_destroy_output(d, output); + break; + } + } +} + +static const struct wl_registry_listener registry_listener = { + registry_handle_global, + registry_handle_global_remove +}; + +static void +signal_int(int signum) +{ + running = 0; +} + +static void +usage(int error_code) +{ + fprintf(stderr, "Usage: simple-vulkan [OPTIONS]\n\n" + " -d \tBuffer swap delay in microseconds\n" + " -p \tSet presentation mode\n" + " immediate = 0\n" + " mailbox = 1\n" + " fifo = 2 (default)\n" + " fifo_relaxed = 3\n" + " -f\tRun in fullscreen mode\n" + " -r\tUse fixed width/height ratio when run in fullscreen mode\n" + " -m\tRun in maximized mode\n" + " -o\tCreate an opaque surface\n" + " -t\tEnable tearing via the tearing_control protocol\n" + " -T\tEnable and disable tearing every 5 seconds\n" + " -h\tThis help text\n\n"); + + exit(error_code); +} + +int +main(int argc, char **argv) +{ + struct sigaction sigint; + struct display display = {}; + struct window window = {}; + int i, ret = 0; + + window.display = &display; + display.window = &window; + window.buffer_size.width = 250; + window.buffer_size.height = 250; + window.window_size = window.buffer_size; + window.buffer_scale = 1; + window.buffer_transform = WL_OUTPUT_TRANSFORM_NORMAL; + window.needs_buffer_geometry_update = false; + window.delay = 0; + window.fullscreen_ratio = false; + window.vk.present_mode = VK_PRESENT_MODE_FIFO_KHR; + + wl_list_init(&display.output_list); + wl_list_init(&window.window_output_list); + + for (i = 1; i < argc; i++) { + if (strcmp("-d", argv[i]) == 0 && i+1 < argc) + window.delay = atoi(argv[++i]); + else if (strcmp("-p", argv[i]) == 0 && i+1 < argc) { + window.vk.present_mode = atoi(argv[++i]); + assert(window.vk.present_mode >= 0 && window.vk.present_mode < 4); + } else if (strcmp("-f", argv[i]) == 0) + window.fullscreen = 1; + else if (strcmp("-r", argv[i]) == 0) + window.fullscreen_ratio = true; + else if (strcmp("-m", argv[i]) == 0) + window.maximized = 1; + else if (strcmp("-o", argv[i]) == 0) + window.opaque = 1; + else if (strcmp("-t", argv[i]) == 0) { + window.tearing = true; + } else if (strcmp("-T", argv[i]) == 0) { + window.tearing = true; + window.toggled_tearing = true; + } + else if (strcmp("-h", argv[i]) == 0) + usage(EXIT_SUCCESS); + else + usage(EXIT_FAILURE); + } + + display.display = wl_display_connect(NULL); + assert(display.display); + + display.registry = wl_display_get_registry(display.display); + wl_registry_add_listener(display.registry, + ®istry_listener, &display); + + wl_display_roundtrip(display.display); + + if (!display.wm_base) { + fprintf(stderr, "xdg-shell support required. simple-vulkan exiting\n"); + goto out_no_xdg_shell; + } + + create_surface(&window); + + /* we already have wait_for_configure set after create_surface() */ + while (running && ret != -1 && window.wait_for_configure) { + ret = wl_display_dispatch(display.display); + + /* wait until xdg_surface::configure acks the new dimensions */ + if (window.wait_for_configure) + continue; + + init_vulkan(&window); + } + + create_swapchain(&window); + + display.cursor_surface = + wl_compositor_create_surface(display.compositor); + + sigint.sa_handler = signal_int; + sigemptyset(&sigint.sa_mask); + sigint.sa_flags = SA_RESETHAND; + sigaction(SIGINT, &sigint, NULL); + + while (running && ret != -1) { + ret = wl_display_dispatch_pending(display.display); + redraw(&window); + } + + fprintf(stderr, "simple-vulkan exiting\n"); + + destroy_surface(&window); + destroy_swapchain(&window); + fini_vulkan(&window); + + wl_surface_destroy(display.cursor_surface); +out_no_xdg_shell: + display_destroy_outputs(&display); + + if (display.cursor_theme) + wl_cursor_theme_destroy(display.cursor_theme); + + if (display.shm) + wl_shm_destroy(display.shm); + + if (display.pointer) + wl_pointer_destroy(display.pointer); + + if (display.keyboard) + wl_keyboard_destroy(display.keyboard); + + if (display.touch) + wl_touch_destroy(display.touch); + + if (display.seat) + wl_seat_destroy(display.seat); + + if (display.wm_base) + xdg_wm_base_destroy(display.wm_base); + + if (display.compositor) + wl_compositor_destroy(display.compositor); + + if (display.tearing_manager) + wp_tearing_control_manager_v1_destroy(display.tearing_manager); + + if (display.viewporter) + wp_viewporter_destroy(display.viewporter); + + if (display.fractional_scale_manager) + wp_fractional_scale_manager_v1_destroy(display.fractional_scale_manager); + + wl_registry_destroy(display.registry); + wl_display_flush(display.display); + wl_display_disconnect(display.display); + + return 0; +} diff --git a/clients/simple_vulkan_fragment_shader.frag b/clients/simple_vulkan_fragment_shader.frag new file mode 100644 index 000000000..1558675b7 --- /dev/null +++ b/clients/simple_vulkan_fragment_shader.frag @@ -0,0 +1,9 @@ +#version 450 core + +layout(location = 0) in vec4 vVaryingColor; +layout(location = 0) out vec4 f_color; + +void main() +{ + f_color = vVaryingColor; +} diff --git a/clients/simple_vulkan_vertex_shader.vert b/clients/simple_vulkan_vertex_shader.vert new file mode 100644 index 000000000..7d3cc34da --- /dev/null +++ b/clients/simple_vulkan_vertex_shader.vert @@ -0,0 +1,16 @@ +#version 450 core + +layout(std140, set = 0, binding = 0) uniform block { + uniform mat4 rotation; +}; + +layout(location = 0) in vec4 in_position; +layout(location = 1) in vec4 in_color; + +layout(location = 0) out vec4 vVaryingColor; + +void main() +{ + gl_Position = rotation * in_position; + vVaryingColor = vec4(in_color.rgba); +}