summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorErik Faye-Lund <erik.faye-lund@collabora.com>2022-12-12 10:38:09 +0100
committerantonino <antonino.maniscalco@collabora.com>2023-01-30 10:45:28 +0100
commita92d6f622d13768435b48bcb7dc573f3523cfacc (patch)
tree27b6836fe1a628d042597dfdddda4e8ec50269b1
parentc4aada2ae78a1ce8a4b7543435d484b44a61b4fc (diff)
downloadmesa-demos-a92d6f622d13768435b48bcb7dc573f3523cfacc.tar.gz
vulkan: add vkgears demo
Reviewed-by: Antonino Maniscalco <antonino.maniscalco@collabora.com>
-rw-r--r--.gitlab-ci.yml4
-rw-r--r--meson.build5
-rw-r--r--meson_options.txt1
-rw-r--r--src/meson.build4
-rw-r--r--src/vulkan/gear.frag9
-rw-r--r--src/vulkan/gear.vert29
-rw-r--r--src/vulkan/meson.build58
-rw-r--r--src/vulkan/vkgears.c1292
-rw-r--r--src/vulkan/wayland.c243
9 files changed, 1644 insertions, 1 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 290d751f..c046a9f3 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -35,12 +35,13 @@ x86_build:
# No need to pull the whole repo to build the container image
GIT_STRATEGY: none
# /!\ Bump the TAG when modifying the DEBS
- FDO_DISTRIBUTION_TAG: &x86_build "2023-01-27-mingw-w64-tools"
+ FDO_DISTRIBUTION_TAG: &x86_build "2023-01-12-vkgears"
FDO_DISTRIBUTION_PACKAGES: >-
build-essential
python3-pip
pkg-config
ninja-build
+ glslang-tools
mingw-w64
mingw-w64-tools
wine
@@ -51,6 +52,7 @@ x86_build:
libgbm-dev
libgl-dev
libgles-dev
+ libvulkan-dev
libudev-dev
libwayland-dev
libx11-dev
diff --git a/meson.build b/meson.build
index b01193ef..84ef1203 100644
--- a/meson.build
+++ b/meson.build
@@ -44,6 +44,7 @@ dep_gles1 = dependency('glesv1_cm', required : get_option('gles1'))
dep_gles2 = dependency('glesv2', required : get_option('gles2'))
dep_osmesa = dependency('osmesa', required : get_option('osmesa'))
dep_egl = dependency('egl', required : get_option('egl'))
+dep_vulkan = dependency('vulkan', required : get_option('vulkan'))
dep_drm = dependency('libdrm',
required : get_option('libdrm'),
@@ -127,6 +128,10 @@ if dep_glut.found() and cc.has_function('glutInitContextProfile',
add_project_arguments('-DHAVE_FREEGLUT', language : ['c', 'cpp'])
endif
+if get_option('vulkan').enabled()
+ prog_glslang = find_program('glslangValidator')
+endif
+
if host_machine.system() == 'darwin'
add_project_arguments('-DGL_SILENCE_DEPRECATION', language: 'c')
endif
diff --git a/meson_options.txt b/meson_options.txt
index 04a55cfb..832b916a 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -6,5 +6,6 @@ option('glut', type : 'feature')
option('osmesa', type : 'feature')
option('libdrm', type : 'feature')
option('x11', type : 'feature')
+option('vulkan', type : 'feature')
option('wayland', type : 'feature')
option('with-system-data-files', type : 'boolean', value : false)
diff --git a/src/meson.build b/src/meson.build
index 39cac78d..0e1ba704 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -52,6 +52,10 @@ if dep_osmesa.found()
subdir('osdemos')
endif
+if get_option('vulkan').enabled()
+ subdir('vulkan')
+endif
+
if host_machine.system() == 'windows'
subdir('wgl')
endif
diff --git a/src/vulkan/gear.frag b/src/vulkan/gear.frag
new file mode 100644
index 00000000..052f0006
--- /dev/null
+++ b/src/vulkan/gear.frag
@@ -0,0 +1,9 @@
+#version 450
+
+layout(location = 0) in vec4 in_color;
+layout(location = 0) out vec4 out_color;
+
+void main()
+{
+ out_color = in_color;
+}
diff --git a/src/vulkan/gear.vert b/src/vulkan/gear.vert
new file mode 100644
index 00000000..0e1d4d7f
--- /dev/null
+++ b/src/vulkan/gear.vert
@@ -0,0 +1,29 @@
+#version 450
+
+layout(set = 0, binding = 0) uniform block {
+ uniform mat4 projection;
+};
+
+layout(push_constant) uniform constants
+{
+ uniform mat4 modelview;
+ vec3 material_color;
+};
+
+layout(location = 0) in vec4 in_position;
+layout(location = 1) in vec3 in_normal;
+
+layout(location = 0) out vec4 out_color;
+
+const vec3 L = normalize(vec3(5.0, 5.0, 10.0));
+
+void main()
+{
+ vec3 N = normalize(mat3(modelview) * in_normal);
+
+ float diffuse = max(0.0, dot(N, L));
+ float ambient = 0.2;
+ out_color = vec4((ambient + diffuse) * material_color, 1.0);
+
+ gl_Position = projection * (modelview * in_position);
+}
diff --git a/src/vulkan/meson.build b/src/vulkan/meson.build
new file mode 100644
index 00000000..3c390625
--- /dev/null
+++ b/src/vulkan/meson.build
@@ -0,0 +1,58 @@
+# Copyright © 2022 Collabora Ltd
+
+# 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 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.
+
+glsl_shaders = files(
+ 'gear.frag',
+ 'gear.vert',
+)
+
+_gen = generator(
+ prog_glslang,
+ output : '@PLAINNAME@.spv.h',
+ arguments : [ '@INPUT@', '-V', '-x', '-o', '@OUTPUT@' ]
+)
+
+spirv_shaders = _gen.process(glsl_shaders)
+
+sources = files('vkgears.c')
+
+if dep_wayland.found()
+ sources += files('wayland.c')
+ sources += [
+ custom_target(
+ 'wayland-xdg-shell-protocol.c',
+ input : wayland_xdg_shell_xml,
+ output : 'wayland-xdg-shell-protocol.c',
+ command : [prog_wl_scanner, wl_scanner_arg, '@INPUT@', '@OUTPUT@'],
+ ),
+ custom_target(
+ 'wayland-xdg-shell-client-protocol.h',
+ input : wayland_xdg_shell_xml,
+ output : 'wayland-xdg-shell-client-protocol.h',
+ command : [prog_wl_scanner, 'client-header', '@INPUT@', '@OUTPUT@'],
+ )
+ ]
+endif
+
+executable(
+ 'vkgears', files('vkgears.c'), sources,
+ spirv_shaders,
+ dependencies: [dep_vulkan, dep_wayland, idep_util]
+)
diff --git a/src/vulkan/vkgears.c b/src/vulkan/vkgears.c
new file mode 100644
index 00000000..750b45b7
--- /dev/null
+++ b/src/vulkan/vkgears.c
@@ -0,0 +1,1292 @@
+#include "matrix.h"
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <math.h>
+
+#include <sys/time.h>
+
+#include <vulkan/vulkan.h>
+#include <vulkan/vulkan_wayland.h>
+
+#ifndef VK_API_VERSION_MAJOR
+/* retain compatibility with old vulkan headers */
+#define VK_API_VERSION_MAJOR VK_VERSION_MAJOR
+#define VK_API_VERSION_MINOR VK_VERSION_MINOR
+#define VK_API_VERSION_PATCH VK_VERSION_PATCH
+#endif
+
+void init_display();
+void fini_display();
+
+void init_window(const char *title);
+bool update_window();
+void fini_window();
+
+bool
+create_surface(VkPhysicalDevice physical_device, VkInstance instance,
+ VkSurfaceKHR *surface);
+
+static VkInstance instance;
+static VkPhysicalDevice physical_device;
+static VkPhysicalDeviceMemoryProperties mem_props;
+static VkDevice device;
+static VkQueue queue;
+
+/* swap chain */
+static int width, height;
+static uint32_t image_count;
+static VkRenderPass render_pass;
+static VkCommandPool cmd_pool;
+static VkFormat image_format;
+static VkFormat depth_format;
+static VkSurfaceKHR surface;
+static VkSwapchainKHR swap_chain;
+static VkImage depth_image;
+static VkImageView depth_view;
+static VkSemaphore back_buffer_semaphore, present_semaphore;
+
+struct {
+ VkImage image;
+ VkImageView view;
+ VkFramebuffer framebuffer;
+ VkFence fence;
+ VkCommandBuffer cmd_buffer;
+} swap_chain_data[5];
+
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+
+/* gear data */
+static VkDescriptorSet descriptor_set;
+static VkDeviceMemory ubo_mem;
+static VkDeviceMemory vertex_mem;
+static VkBuffer ubo_buffer;
+static VkBuffer vertex_buffer;
+static VkPipelineLayout pipeline_layout;
+static VkPipeline pipeline;
+size_t vertex_offset, normals_offset;
+
+struct {
+ uint32_t first_vertex;
+ uint32_t vertex_count;
+} gears[3];
+
+
+static void
+errorv(const char *format, va_list args)
+{
+ vfprintf(stderr, format, args);
+ fprintf(stderr, "\n");
+ exit(1);
+}
+
+static void
+error(const char *format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ errorv(format, args);
+ va_end(args);
+}
+
+static double
+current_time(void)
+{
+ struct timeval tv;
+ (void) gettimeofday(&tv, NULL );
+ return (double) tv.tv_sec + tv.tv_usec / 1000000.0;
+}
+
+static void
+init_vk(const char *extension)
+{
+ VkResult res = vkCreateInstance(
+ &(VkInstanceCreateInfo) {
+ .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
+ .pApplicationInfo = &(VkApplicationInfo) {
+ .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
+ .pApplicationName = "vkgears",
+ .apiVersion = VK_MAKE_VERSION(1, 1, 0),
+ },
+ .enabledExtensionCount = extension ? 2 : 0,
+ .ppEnabledExtensionNames = (const char *[2]) {
+ VK_KHR_SURFACE_EXTENSION_NAME,
+ extension,
+ },
+ },
+ NULL,
+ &instance);
+
+ if (res != VK_SUCCESS)
+ error("Failed to create Vulkan instance.\n");
+
+ uint32_t count;
+ res = vkEnumeratePhysicalDevices(instance, &count, NULL);
+ if (res != VK_SUCCESS || count == 0)
+ error("No Vulkan devices found.\n");
+
+ VkPhysicalDevice pd[count];
+ res = vkEnumeratePhysicalDevices(instance, &count, pd);
+ assert(res == VK_SUCCESS);
+ physical_device = pd[0];
+
+ vkGetPhysicalDeviceMemoryProperties(physical_device, &mem_props);
+
+ vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &count, NULL);
+ assert(count > 0);
+ VkQueueFamilyProperties props[count];
+ vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &count, props);
+ assert(props[0].queueFlags & VK_QUEUE_GRAPHICS_BIT);
+
+ vkCreateDevice(physical_device,
+ &(VkDeviceCreateInfo) {
+ .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
+ .queueCreateInfoCount = 1,
+ .pQueueCreateInfos = &(VkDeviceQueueCreateInfo) {
+ .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
+ .queueFamilyIndex = 0,
+ .queueCount = 1,
+ .flags = 0,
+ .pQueuePriorities = (float []) { 1.0f },
+ },
+ .enabledExtensionCount = 1,
+ .ppEnabledExtensionNames = (const char * const []) {
+ VK_KHR_SWAPCHAIN_EXTENSION_NAME,
+ },
+ },
+ NULL,
+ &device);
+
+ vkGetDeviceQueue2(device,
+ &(VkDeviceQueueInfo2) {
+ .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_INFO_2,
+ .flags = 0,
+ .queueFamilyIndex = 0,
+ .queueIndex = 0,
+ },
+ &queue);
+}
+
+static int
+find_memory_type(const VkMemoryRequirements *reqs,
+ VkMemoryPropertyFlags flags)
+{
+ for (unsigned i = 0; (1u << i) <= reqs->memoryTypeBits &&
+ i <= mem_props.memoryTypeCount; ++i) {
+ if ((reqs->memoryTypeBits & (1u << i)) &&
+ (mem_props.memoryTypes[i].propertyFlags & flags) == flags)
+ return i;
+ }
+ return -1;
+}
+
+static void
+create_swapchain()
+{
+ VkSurfaceCapabilitiesKHR surface_caps;
+ vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device, surface,
+ &surface_caps);
+ assert(surface_caps.supportedCompositeAlpha &
+ VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR);
+
+ VkBool32 supported;
+ vkGetPhysicalDeviceSurfaceSupportKHR(physical_device, 0, surface,
+ &supported);
+ assert(supported);
+
+ uint32_t count;
+ vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface,
+ &count, NULL);
+ VkPresentModeKHR present_modes[count];
+ vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface,
+ &count, present_modes);
+ int i;
+ VkPresentModeKHR present_mode = VK_PRESENT_MODE_FIFO_KHR;
+ for (i = 0; i < count; i++) {
+ if (present_modes[i] == VK_PRESENT_MODE_MAILBOX_KHR) {
+ present_mode = VK_PRESENT_MODE_MAILBOX_KHR;
+ break;
+ }
+ }
+
+ uint32_t minImageCount = 2;
+ if (minImageCount < surface_caps.minImageCount) {
+ if (surface_caps.minImageCount > ARRAY_SIZE(swap_chain_data))
+ error("surface_caps.minImageCount is too large (is: %d, max: %d)",
+ surface_caps.minImageCount, ARRAY_SIZE(swap_chain_data));
+ minImageCount = surface_caps.minImageCount;
+ }
+
+ if (surface_caps.maxImageCount > 0 &&
+ minImageCount > surface_caps.maxImageCount) {
+ minImageCount = surface_caps.maxImageCount;
+ }
+
+ vkCreateSwapchainKHR(device,
+ &(VkSwapchainCreateInfoKHR) {
+ .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
+ .flags = 0,
+ .surface = surface,
+ .minImageCount = minImageCount,
+ .imageFormat = image_format,
+ .imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR,
+ .imageExtent = { width, height },
+ .imageArrayLayers = 1,
+ .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
+ .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ .queueFamilyIndexCount = 1,
+ .pQueueFamilyIndices = (uint32_t[]) { 0 },
+ .preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR,
+ .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
+ .presentMode = present_mode,
+ }, NULL, &swap_chain);
+
+ vkGetSwapchainImagesKHR(device, swap_chain,
+ &image_count, NULL);
+ assert(image_count > 0);
+ VkImage swap_chain_images[image_count];
+ vkGetSwapchainImagesKHR(device, swap_chain,
+ &image_count, swap_chain_images);
+
+ VkResult res = vkCreateImage(device,
+ &(VkImageCreateInfo) {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
+ .flags = 0,
+ .imageType = VK_IMAGE_TYPE_2D,
+ .format = depth_format,
+ .extent = {
+ .width = width,
+ .height = height,
+ .depth = 1,
+ },
+ .mipLevels = 1,
+ .arrayLayers = 1,
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .tiling = VK_IMAGE_TILING_OPTIMAL,
+ .usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT,
+ .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ }, 0, &depth_image);
+ if (res != VK_SUCCESS)
+ error("Failed to create Vulkan instance.\n");
+
+ VkMemoryRequirements depth_reqs;
+ vkGetImageMemoryRequirements(device, depth_image, &depth_reqs);
+
+ int memory_type = find_memory_type(&depth_reqs, VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT);
+ if (memory_type < 0) {
+ memory_type = find_memory_type(&depth_reqs, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+ if (memory_type < 0)
+ error("find_memory_type failed");
+ }
+
+ VkDeviceMemory depth_mem;
+ vkAllocateMemory(device,
+ &(VkMemoryAllocateInfo) {
+ .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
+ .allocationSize = depth_reqs.size,
+ .memoryTypeIndex = memory_type,
+ },
+ NULL,
+ &depth_mem);
+ vkBindImageMemory(device, depth_image, depth_mem, 0);
+
+ vkCreateImageView(device,
+ &(VkImageViewCreateInfo) {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+ .image = depth_image,
+ .viewType = VK_IMAGE_VIEW_TYPE_2D,
+ .format = depth_format,
+ .components = {
+ .r = VK_COMPONENT_SWIZZLE_R,
+ .g = VK_COMPONENT_SWIZZLE_G,
+ .b = VK_COMPONENT_SWIZZLE_B,
+ .a = VK_COMPONENT_SWIZZLE_A,
+ },
+ .subresourceRange = {
+ .aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT,
+ .baseMipLevel = 0,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = 1,
+ },
+ },
+ NULL,
+ &depth_view);
+
+ vkCreateRenderPass(device,
+ &(VkRenderPassCreateInfo) {
+ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
+ .attachmentCount = 2,
+ .pAttachments = (VkAttachmentDescription[]) {
+ {
+ .format = image_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,
+ },
+ {
+ .format = depth_format,
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
+ .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
+ .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
+ .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
+ .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ .finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
+ }
+ },
+ .subpassCount = 1,
+ .pSubpasses = (VkSubpassDescription []) {
+ {
+ .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
+ .inputAttachmentCount = 0,
+ .colorAttachmentCount = 1,
+ .pColorAttachments = (VkAttachmentReference []) {
+ {
+ .attachment = 0,
+ .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
+ }
+ },
+ .pResolveAttachments = (VkAttachmentReference []) {
+ {
+ .attachment = VK_ATTACHMENT_UNUSED,
+ .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
+ }
+ },
+ .pDepthStencilAttachment = (VkAttachmentReference []) {
+ {
+ .attachment = 1,
+ .layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
+ }
+ },
+ .preserveAttachmentCount = 0,
+ .pPreserveAttachments = NULL,
+ }
+ },
+ .dependencyCount = 0
+ },
+ NULL,
+ &render_pass);
+
+ vkCreateCommandPool(device,
+ &(const VkCommandPoolCreateInfo) {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
+ .queueFamilyIndex = 0,
+ .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT
+ },
+ NULL,
+ &cmd_pool);
+
+ for (uint32_t i = 0; i < image_count; i++) {
+ swap_chain_data[i].image = swap_chain_images[i];
+ vkCreateImageView(device,
+ &(VkImageViewCreateInfo) {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+ .image = swap_chain_images[i],
+ .viewType = VK_IMAGE_VIEW_TYPE_2D,
+ .format = image_format,
+ .components = {
+ .r = VK_COMPONENT_SWIZZLE_R,
+ .g = VK_COMPONENT_SWIZZLE_G,
+ .b = VK_COMPONENT_SWIZZLE_B,
+ .a = VK_COMPONENT_SWIZZLE_A,
+ },
+ .subresourceRange = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = 0,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = 1,
+ },
+ },
+ NULL,
+ &swap_chain_data[i].view);
+
+ vkCreateFramebuffer(device,
+ &(VkFramebufferCreateInfo) {
+ .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
+ .renderPass = render_pass,
+ .attachmentCount = 2,
+ .pAttachments = (VkImageView[]) {
+ swap_chain_data[i].view,
+ depth_view,
+ },
+ .width = width,
+ .height = height,
+ .layers = 1
+ },
+ NULL,
+ &swap_chain_data[i].framebuffer);
+
+ vkCreateFence(device,
+ &(VkFenceCreateInfo) {
+ .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
+ .flags = VK_FENCE_CREATE_SIGNALED_BIT
+ },
+ NULL,
+ &swap_chain_data[i].fence);
+
+ vkAllocateCommandBuffers(device,
+ &(VkCommandBufferAllocateInfo) {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+ .commandPool = cmd_pool,
+ .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+ .commandBufferCount = 1,
+ },
+ &swap_chain_data[i].cmd_buffer);
+ }
+
+ vkCreateSemaphore(device,
+ &(VkSemaphoreCreateInfo) {
+ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
+ },
+ NULL,
+ &back_buffer_semaphore);
+
+ vkCreateSemaphore(device,
+ &(VkSemaphoreCreateInfo) {
+ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
+ },
+ NULL,
+ &present_semaphore);
+}
+
+static void
+free_swapchain_data()
+{
+ for (uint32_t i = 0; i < image_count; i++) {
+ vkFreeCommandBuffers(device, cmd_pool, 1, &swap_chain_data[i].cmd_buffer);
+ vkDestroyFence(device, swap_chain_data[i].fence, NULL);
+ vkDestroyFramebuffer(device, swap_chain_data[i].framebuffer, NULL);
+ vkDestroyImageView(device, swap_chain_data[i].view, NULL);
+ }
+}
+
+static void
+recreate_swapchain()
+{
+ free_swapchain_data();
+ vkDestroySwapchainKHR(device, swap_chain, NULL);
+ create_swapchain();
+}
+
+static VkBuffer
+create_buffer(VkDeviceSize size, VkBufferUsageFlags usage)
+{
+ VkBuffer buffer;
+
+ VkResult result =
+ vkCreateBuffer(device,
+ &(VkBufferCreateInfo) {
+ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+ .size = size,
+ .usage = usage,
+ .flags = 0
+ },
+ NULL,
+ &buffer);
+
+ if (result != VK_SUCCESS)
+ error("Failed to create buffer");
+
+ return buffer;
+}
+
+static VkDeviceMemory
+allocate_buffer_mem(VkBuffer buffer, VkDeviceSize mem_size)
+{
+ VkMemoryRequirements reqs;
+ vkGetBufferMemoryRequirements(device, buffer, &reqs);
+
+ int memory_type = find_memory_type(&reqs,
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+ VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+ if (memory_type < 0)
+ error("failed to find coherent memory type");
+
+ VkDeviceMemory mem;
+ vkAllocateMemory(device,
+ &(VkMemoryAllocateInfo) {
+ .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
+ .allocationSize = mem_size,
+ .memoryTypeIndex = memory_type,
+ },
+ NULL,
+ &mem);
+ return mem;
+}
+
+static uint32_t vs_spirv_source[] = {
+#include "gear.vert.spv.h"
+};
+
+static uint32_t fs_spirv_source[] = {
+#include "gear.frag.spv.h"
+};
+
+struct ubo {
+ float projection[16];
+};
+
+struct push_constants {
+ float modelview[16];
+ float material_color[3];
+};
+
+struct gear {
+ int nvertices;
+};
+
+#define GEAR_VERTEX_STRIDE 6
+
+static int
+create_gear(float verts[],
+ float inner_radius, float outer_radius, float width,
+ int teeth, float tooth_depth)
+{
+ // normal state
+ float current_normal[3];
+#define SET_NORMAL(x, y, z) do { \
+ current_normal[0] = x; \
+ current_normal[1] = y; \
+ current_normal[2] = z; \
+ } while (0)
+
+ // vertex buffer state
+ unsigned num_verts = 0;
+
+#define EMIT_VERTEX(x, y, z) do { \
+ verts[num_verts * GEAR_VERTEX_STRIDE + 0] = x; \
+ verts[num_verts * GEAR_VERTEX_STRIDE + 1] = y; \
+ verts[num_verts * GEAR_VERTEX_STRIDE + 2] = z; \
+ memcpy(verts + num_verts * GEAR_VERTEX_STRIDE + 3, \
+ current_normal, sizeof(current_normal)); \
+ num_verts++; \
+ } while (0)
+
+ // strip restart-logic
+ int cur_strip_start = 0;
+#define START_STRIP() do { \
+ cur_strip_start = num_verts; \
+ if (cur_strip_start) \
+ num_verts += 2; \
+} while(0);
+
+#define END_STRIP() do { \
+ if (cur_strip_start) { \
+ memcpy(verts + cur_strip_start * GEAR_VERTEX_STRIDE, \
+ verts + (cur_strip_start - 1) * GEAR_VERTEX_STRIDE, \
+ sizeof(float) * GEAR_VERTEX_STRIDE); \
+ memcpy(verts + (cur_strip_start + 1) * GEAR_VERTEX_STRIDE, \
+ verts + (cur_strip_start + 2) * GEAR_VERTEX_STRIDE, \
+ sizeof(float) * GEAR_VERTEX_STRIDE); \
+ } \
+} while (0)
+
+ float r0 = inner_radius;
+ float r1 = outer_radius - tooth_depth / 2.0;
+ float r2 = outer_radius + tooth_depth / 2.0;
+
+ float da = 2.0 * M_PI / teeth / 4.0;
+
+ SET_NORMAL(0.0, 0.0, 1.0);
+
+ /* draw front face */
+ START_STRIP();
+ for (int i = 0; i <= teeth; i++) {
+ float angle = i * 2.0 * M_PI / teeth;
+ EMIT_VERTEX(r0 * cos(angle), r0 * sin(angle), width * 0.5);
+ EMIT_VERTEX(r1 * cos(angle), r1 * sin(angle), width * 0.5);
+ if (i < teeth) {
+ EMIT_VERTEX(r0 * cos(angle), r0 * sin(angle), width * 0.5);
+ EMIT_VERTEX(r1 * cos(angle + 3 * da),
+ r1 * sin(angle + 3 * da), width * 0.5);
+ }
+ }
+ END_STRIP();
+
+ /* draw front sides of teeth */
+ for (int i = 0; i < teeth; i++) {
+ float angle = i * 2.0 * M_PI / teeth;
+ START_STRIP();
+ EMIT_VERTEX(r1 * cos(angle), r1 * sin(angle), width * 0.5);
+ EMIT_VERTEX(r2 * cos(angle + da), r2 * sin(angle + da), width * 0.5);
+ EMIT_VERTEX(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
+ width * 0.5);
+ EMIT_VERTEX(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da),
+ width * 0.5);
+ END_STRIP();
+ }
+
+ SET_NORMAL(0.0, 0.0, -1.0);
+
+ /* draw back face */
+ START_STRIP();
+ for (int i = 0; i <= teeth; i++) {
+ float angle = i * 2.0 * M_PI / teeth;
+ EMIT_VERTEX(r1 * cos(angle), r1 * sin(angle), -width * 0.5);
+ EMIT_VERTEX(r0 * cos(angle), r0 * sin(angle), -width * 0.5);
+ if (i < teeth) {
+ EMIT_VERTEX(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
+ -width * 0.5);
+ EMIT_VERTEX(r0 * cos(angle), r0 * sin(angle), -width * 0.5);
+ }
+ }
+ END_STRIP();
+
+ /* draw back sides of teeth */
+ for (int i = 0; i < teeth; i++) {
+ float angle = i * 2.0 * M_PI / teeth;
+ START_STRIP();
+ EMIT_VERTEX(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
+ -width * 0.5);
+ EMIT_VERTEX(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da),
+ -width * 0.5);
+ EMIT_VERTEX(r1 * cos(angle), r1 * sin(angle), -width * 0.5);
+ EMIT_VERTEX(r2 * cos(angle + da), r2 * sin(angle + da), -width * 0.5);
+ END_STRIP();
+ }
+
+ /* draw outward faces of teeth */
+ for (int i = 0; i < teeth; i++) {
+ float angle = i * 2.0 * M_PI / teeth;
+ float u = r2 * cos(angle + da) - r1 * cos(angle);
+ float v = r2 * sin(angle + da) - r1 * sin(angle);
+ float len = sqrt(u * u + v * v);
+ u /= len;
+ v /= len;
+ SET_NORMAL(v, -u, 0.0);
+ START_STRIP();
+ EMIT_VERTEX(r1 * cos(angle), r1 * sin(angle), width * 0.5);
+ EMIT_VERTEX(r1 * cos(angle), r1 * sin(angle), -width * 0.5);
+
+ EMIT_VERTEX(r2 * cos(angle + da), r2 * sin(angle + da), width * 0.5);
+ EMIT_VERTEX(r2 * cos(angle + da), r2 * sin(angle + da), -width * 0.5);
+ END_STRIP();
+
+ SET_NORMAL(cos(angle), sin(angle), 0.0);
+ START_STRIP();
+ EMIT_VERTEX(r2 * cos(angle + da), r2 * sin(angle + da),
+ width * 0.5);
+ EMIT_VERTEX(r2 * cos(angle + da), r2 * sin(angle + da),
+ -width * 0.5);
+ EMIT_VERTEX(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da),
+ width * 0.5);
+ EMIT_VERTEX(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da),
+ -width * 0.5);
+ END_STRIP();
+
+ u = r1 * cos(angle + 3 * da) - r2 * cos(angle + 2 * da);
+ v = r1 * sin(angle + 3 * da) - r2 * sin(angle + 2 * da);
+ SET_NORMAL(v, -u, 0.0);
+ START_STRIP();
+ EMIT_VERTEX(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da),
+ width * 0.5);
+ EMIT_VERTEX(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da),
+ -width * 0.5);
+ EMIT_VERTEX(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
+ width * 0.5);
+ EMIT_VERTEX(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
+ -width * 0.5);
+ END_STRIP();
+
+ SET_NORMAL(cos(angle), sin(angle), 0.0);
+ START_STRIP();
+ EMIT_VERTEX(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
+ width * 0.5);
+ EMIT_VERTEX(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
+ -width * 0.5);
+ EMIT_VERTEX(r1 * cos(angle + 4 * da), r1 * sin(angle + 4 * da),
+ width * 0.5);
+ EMIT_VERTEX(r1 * cos(angle + 4 * da), r1 * sin(angle + 4 * da),
+ -width * 0.5);
+ END_STRIP();
+ }
+
+ /* draw inside radius cylinder */
+ START_STRIP();
+ for (int i = 0; i <= teeth; i++) {
+ float angle = i * 2.0 * M_PI / teeth;
+ SET_NORMAL(-cos(angle), -sin(angle), 0.0);
+ EMIT_VERTEX(r0 * cos(angle), r0 * sin(angle), -width * 0.5);
+ EMIT_VERTEX(r0 * cos(angle), r0 * sin(angle), width * 0.5);
+ }
+ END_STRIP();
+
+ return num_verts;
+}
+
+
+static void
+init_gears()
+{
+ VkResult r;
+
+ VkDescriptorSetLayout set_layout;
+ vkCreateDescriptorSetLayout(device,
+ &(VkDescriptorSetLayoutCreateInfo) {
+ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
+ .bindingCount = 1,
+ .pBindings = (VkDescriptorSetLayoutBinding[]) {
+ {
+ .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+ .descriptorCount = 1,
+ .stageFlags = VK_SHADER_STAGE_VERTEX_BIT,
+ .pImmutableSamplers = NULL
+ }
+ }
+ },
+ NULL,
+ &set_layout);
+
+ vkCreatePipelineLayout(device,
+ &(VkPipelineLayoutCreateInfo) {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
+ .setLayoutCount = 1,
+ .pSetLayouts = &set_layout,
+ .pPushConstantRanges = (VkPushConstantRange[]) {
+ {
+ .offset = 0,
+ .size = sizeof(struct push_constants),
+ .stageFlags = VK_SHADER_STAGE_VERTEX_BIT,
+ },
+ },
+ .pushConstantRangeCount = 1,
+ },
+ NULL,
+ &pipeline_layout);
+
+ VkShaderModule vs_module;
+ vkCreateShaderModule(device,
+ &(VkShaderModuleCreateInfo) {
+ .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
+ .codeSize = sizeof(vs_spirv_source),
+ .pCode = vs_spirv_source,
+ },
+ NULL,
+ &vs_module);
+
+ VkShaderModule fs_module;
+ vkCreateShaderModule(device,
+ &(VkShaderModuleCreateInfo) {
+ .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
+ .codeSize = sizeof(fs_spirv_source),
+ .pCode = fs_spirv_source,
+ },
+ NULL,
+ &fs_module);
+
+ vkCreateGraphicsPipelines(device,
+ (VkPipelineCache) { VK_NULL_HANDLE },
+ 1,
+ &(VkGraphicsPipelineCreateInfo) {
+ .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 = &(VkPipelineVertexInputStateCreateInfo) {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
+ .vertexBindingDescriptionCount = 2,
+ .pVertexBindingDescriptions = (VkVertexInputBindingDescription[]) {
+ {
+ .binding = 0,
+ .stride = 6 * sizeof(float),
+ .inputRate = VK_VERTEX_INPUT_RATE_VERTEX
+ },
+ {
+ .binding = 1,
+ .stride = 6 * 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
+ },
+ }
+ },
+ .pInputAssemblyState = &(VkPipelineInputAssemblyStateCreateInfo) {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+ .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP,
+ .primitiveRestartEnable = false,
+ },
+
+ .pViewportState = &(VkPipelineViewportStateCreateInfo) {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
+ .viewportCount = 1,
+ .scissorCount = 1,
+ },
+
+ .pRasterizationState = &(VkPipelineRasterizationStateCreateInfo) {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
+ .rasterizerDiscardEnable = false,
+ .polygonMode = VK_POLYGON_MODE_FILL,
+ .cullMode = VK_CULL_MODE_BACK_BIT,
+ .frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE,
+ .lineWidth = 1.0f,
+ },
+
+ .pMultisampleState = &(VkPipelineMultisampleStateCreateInfo) {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
+ .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
+ },
+ .pDepthStencilState = &(VkPipelineDepthStencilStateCreateInfo) {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
+ .depthTestEnable = VK_TRUE,
+ .depthWriteEnable = VK_TRUE,
+ .depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL,
+ },
+
+ .pColorBlendState = &(VkPipelineColorBlendStateCreateInfo) {
+ .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 },
+ }
+ },
+
+ .pDynamicState = &(VkPipelineDynamicStateCreateInfo) {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
+ .dynamicStateCount = 2,
+ .pDynamicStates = (VkDynamicState[]) {
+ VK_DYNAMIC_STATE_VIEWPORT,
+ VK_DYNAMIC_STATE_SCISSOR,
+ },
+ },
+
+ .flags = 0,
+ .layout = pipeline_layout,
+ .renderPass = render_pass,
+ .subpass = 0,
+ .basePipelineHandle = (VkPipeline) { 0 },
+ .basePipelineIndex = 0
+ },
+ NULL,
+ &pipeline);
+
+#define MAX_VERTS 10000
+ float verts[MAX_VERTS * GEAR_VERTEX_STRIDE];
+
+ gears[0].first_vertex = 0;
+ gears[0].vertex_count = create_gear(verts + gears[0].first_vertex * GEAR_VERTEX_STRIDE,
+ 1.0, 4.0, 1.0, 20, 0.7);
+ gears[1].first_vertex = gears[0].first_vertex + gears[0].vertex_count;
+ gears[1].vertex_count = create_gear(verts + gears[1].first_vertex * GEAR_VERTEX_STRIDE,
+ 0.5, 2.0, 2.0, 10, 0.7);
+ gears[2].first_vertex = gears[1].first_vertex + gears[1].vertex_count;
+ gears[2].vertex_count = create_gear(verts + gears[2].first_vertex * GEAR_VERTEX_STRIDE,
+ 1.3, 2.0, 0.5, 10, 0.7);
+
+ unsigned num_verts = gears[2].first_vertex + gears[2].vertex_count;
+ unsigned mem_size = sizeof(float) * GEAR_VERTEX_STRIDE * num_verts;
+ vertex_offset = 0;
+ normals_offset = sizeof(float) * 3;
+ ubo_buffer = create_buffer(sizeof(struct ubo), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT |
+ VK_BUFFER_USAGE_TRANSFER_DST_BIT);
+ vertex_buffer = create_buffer(mem_size, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT);
+
+ ubo_mem = allocate_buffer_mem(ubo_buffer, sizeof(struct ubo));
+ vertex_mem = allocate_buffer_mem(vertex_buffer, mem_size);
+
+ void *map;
+ r = vkMapMemory(device, vertex_mem, 0, mem_size, 0, &map);
+ if (r != VK_SUCCESS)
+ error("vkMapMemory failed");
+ memcpy(map, verts, mem_size);
+ vkUnmapMemory(device, vertex_mem);
+
+ vkBindBufferMemory(device, ubo_buffer, ubo_mem, 0);
+ vkBindBufferMemory(device, vertex_buffer, vertex_mem, 0);
+
+ VkDescriptorPool desc_pool;
+ const VkDescriptorPoolCreateInfo create_info = {
+ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
+ .pNext = NULL,
+ .flags = 0,
+ .maxSets = 1,
+ .poolSizeCount = 1,
+ .pPoolSizes = (VkDescriptorPoolSize[]) {
+ {
+ .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+ .descriptorCount = 1
+ },
+ }
+ };
+
+ vkCreateDescriptorPool(device, &create_info, NULL, &desc_pool);
+
+ vkAllocateDescriptorSets(device,
+ &(VkDescriptorSetAllocateInfo) {
+ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
+ .descriptorPool = desc_pool,
+ .descriptorSetCount = 1,
+ .pSetLayouts = &set_layout,
+ }, &descriptor_set);
+
+ vkUpdateDescriptorSets(device, 1,
+ (VkWriteDescriptorSet []) {
+ {
+ .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
+ .dstSet = descriptor_set,
+ .dstBinding = 0,
+ .dstArrayElement = 0,
+ .descriptorCount = 1,
+ .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+ .pBufferInfo = &(VkDescriptorBufferInfo) {
+ .buffer = ubo_buffer,
+ .offset = 0,
+ .range = sizeof(struct ubo),
+ }
+ }
+ },
+ 0, NULL);
+}
+
+static void
+draw_gear(VkCommandBuffer cmdbuf, const float view[16],
+ float position[2], float angle,
+ const float material_color[3],
+ unsigned first_vertex, unsigned vertex_count)
+{
+ /* Translate and rotate the gear */
+ float modelview[16];
+ mat4_identity(modelview);
+ mat4_multiply(modelview, view);
+ mat4_translate(modelview, position[0], position[1], 0);
+ mat4_rotate(modelview, 2 * M_PI * angle / 360.0, 0, 0, 1);
+
+ float h = (float)height / width;
+ float projection[16];
+ mat4_identity(projection);
+ mat4_frustum_vk(projection, -1.0, 1.0, -h, +h, 5.0f, 60.0f);
+
+ struct push_constants push_constants;
+ mat4_identity(push_constants.modelview);
+ mat4_multiply(push_constants.modelview, modelview);
+ memcpy(push_constants.material_color, material_color, sizeof(push_constants.material_color));
+
+ vkCmdPushConstants(cmdbuf, pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT,
+ 0, sizeof(push_constants), &push_constants);
+ vkCmdDraw(cmdbuf, vertex_count, 1, first_vertex, 0);
+}
+
+float angle = 0.0;
+
+#define G2L(x) ((x) < 0.04045 ? (x) / 12.92 : powf(((x) + 0.055) / 1.055, 2.4))
+
+const float material_colors[3][3] = {
+ { G2L(0.8), G2L(0.1), G2L(0.0) },
+ { G2L(0.0), G2L(0.8), G2L(0.2) },
+ { G2L(0.2), G2L(0.2), G2L(1.0) },
+};
+
+static void
+draw_gears(VkCommandBuffer cmdbuf, const float view[16])
+{
+ vkCmdBindVertexBuffers(cmdbuf, 0, 2,
+ (VkBuffer[]) {
+ vertex_buffer,
+ vertex_buffer,
+ },
+ (VkDeviceSize[]) {
+ vertex_offset,
+ normals_offset
+ });
+
+ vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+
+ vkCmdBindDescriptorSets(cmdbuf,
+ VK_PIPELINE_BIND_POINT_GRAPHICS,
+ pipeline_layout,
+ 0, 1,
+ &descriptor_set, 0, NULL);
+
+ vkCmdSetViewport(cmdbuf, 0, 1,
+ &(VkViewport) {
+ .x = 0,
+ .y = 0,
+ .width = width,
+ .height = height,
+ .minDepth = 0,
+ .maxDepth = 1,
+ });
+
+ vkCmdSetScissor(cmdbuf, 0, 1,
+ &(VkRect2D) {
+ .offset = { 0, 0 },
+ .extent = { width, height },
+ });
+
+ float positions[3][2] = {
+ {-3.0, -2.0},
+ { 3.1, -2.0},
+ {-3.1, 4.2},
+ };
+
+ float angles[3] = {
+ angle,
+ -2.0 * angle - 9.0,
+ -2.0 * angle - 25.0,
+ };
+
+ for (int i = 0; i < 3; ++i) {
+ draw_gear(cmdbuf, view, positions[i], angles[i], material_colors[i],
+ gears[i].first_vertex, gears[i].vertex_count);
+ }
+}
+
+static const char *
+get_devtype_str(VkPhysicalDeviceType devtype)
+{
+ static char buf[256];
+ switch (devtype) {
+ case VK_PHYSICAL_DEVICE_TYPE_OTHER:
+ return "other";
+ case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU:
+ return "integrated GPU";
+ case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU:
+ return "discrete GPU";
+ case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU:
+ return "virtual GPU";
+ case VK_PHYSICAL_DEVICE_TYPE_CPU:
+ return "CPU";
+ default:
+ snprintf(buf, sizeof(buf), "Unknown (%08x)", devtype);
+ return buf;
+ }
+}
+
+static void
+usage(void)
+{
+ printf("Usage:\n");
+ printf(" -info display Vulkan device info\n");
+}
+
+static void
+print_info()
+{
+ VkPhysicalDeviceProperties properties;
+ vkGetPhysicalDeviceProperties(physical_device, &properties);
+ printf("apiVersion = %d.%d.%d\n",
+ VK_API_VERSION_MAJOR(properties.apiVersion),
+ VK_API_VERSION_MINOR(properties.apiVersion),
+ VK_API_VERSION_PATCH(properties.apiVersion));
+ printf("driverVersion = %04x\n", properties.driverVersion);
+ printf("vendorID = %04x\n", properties.vendorID);
+ printf("deviceID = %04x\n", properties.deviceID);
+ printf("deviceType = %s\n", get_devtype_str(properties.deviceType));
+ printf("deviceName = %s\n", properties.deviceName);
+
+ uint32_t num_extensions = 0;
+ VkExtensionProperties *extensions;
+ vkEnumerateDeviceExtensionProperties(physical_device, NULL, &num_extensions, NULL);
+ if (num_extensions > 0) {
+ extensions = calloc(num_extensions, sizeof(VkExtensionProperties));
+ if (!extensions)
+ error("Failed to allocate memory");
+
+ vkEnumerateDeviceExtensionProperties(physical_device, NULL, &num_extensions, extensions);
+ printf("deviceExtensions:\n");
+ for (int i = 0; i < num_extensions; ++i)
+ printf("\t%s\n", extensions[i].extensionName);
+ }
+}
+
+static VkFormat
+find_depth_format()
+{
+ // either VK_FORMAT_D32_SFLOAT or VK_FORMAT_X8_D24_UNORM_PACK32 needs to be supported; find out which one
+ VkFormatProperties props;
+ vkGetPhysicalDeviceFormatProperties(physical_device, VK_FORMAT_D32_SFLOAT, &props);
+ return (props.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) ?
+ VK_FORMAT_D32_SFLOAT : VK_FORMAT_X8_D24_UNORM_PACK32;
+}
+
+int
+main(int argc, char *argv[])
+{
+ bool printInfo = false;
+ for (int i = 1; i < argc; i++) {
+ if (strcmp(argv[i], "-info") == 0) {
+ printInfo = true;
+ }
+ else {
+ usage();
+ return -1;
+ }
+ }
+
+ init_display();
+ init_window("vkgears");
+
+ init_vk(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME);
+
+ width = 300;
+ height = 300;
+ image_format = VK_FORMAT_B8G8R8A8_SRGB;
+
+ if (printInfo)
+ print_info();
+
+ depth_format = find_depth_format();
+
+ if (!create_surface(physical_device, instance, &surface))
+ error("Failed to create surface!");
+
+ create_swapchain();
+ init_gears();
+
+ while (1) {
+ static int frames = 0;
+ static double tRot0 = -1.0, tRate0 = -1.0;
+ double dt, t = current_time();
+
+ if (tRot0 < 0.0)
+ tRot0 = t;
+ dt = t - tRot0;
+ tRot0 = t;
+
+ /* advance rotation for next frame */
+ angle += 70.0 * dt; /* 70 degrees per second */
+ if (angle > 3600.0)
+ angle -= 3600.0;
+
+ if (update_window()) {
+ printf("update window failed\n");
+ break;
+ }
+
+ uint32_t index;
+ VkResult result =
+ vkAcquireNextImageKHR(device, swap_chain, UINT64_MAX,
+ back_buffer_semaphore, VK_NULL_HANDLE,
+ &index);
+ if (result == VK_SUBOPTIMAL_KHR) {
+ recreate_swapchain();
+ continue;
+ }
+ assert(result == VK_SUCCESS);
+
+ assert(index < ARRAY_SIZE(swap_chain_data));
+
+ vkWaitForFences(device, 1, &swap_chain_data[index].fence, VK_TRUE, UINT64_MAX);
+ vkResetFences(device, 1, &swap_chain_data[index].fence);
+
+ vkBeginCommandBuffer(swap_chain_data[index].cmd_buffer,
+ &(VkCommandBufferBeginInfo) {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+ .flags = 0
+ });
+
+ /* projection matrix */
+ float h = (float)height / width;
+ struct ubo ubo;
+ mat4_identity(ubo.projection);
+ mat4_frustum_vk(ubo.projection, -1.0, 1.0, -h, +h, 5.0f, 60.0f);
+ vkCmdUpdateBuffer(swap_chain_data[index].cmd_buffer, ubo_buffer, 0, sizeof(ubo), &ubo);
+
+ /* Translate and rotate the view */
+ float view[16];
+ mat4_identity(view);
+ mat4_translate(view, 0, 0, -40);
+ static float view_rot[3] = { 20.0, 30.0, 0.0 };
+ mat4_rotate(view, 2 * M_PI * view_rot[0] / 360.0, 1, 0, 0);
+ mat4_rotate(view, 2 * M_PI * view_rot[1] / 360.0, 0, 1, 0);
+ mat4_rotate(view, 2 * M_PI * view_rot[2] / 360.0, 0, 0, 1);
+
+ vkCmdBeginRenderPass(swap_chain_data[index].cmd_buffer,
+ &(VkRenderPassBeginInfo) {
+ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
+ .renderPass = render_pass,
+ .framebuffer = swap_chain_data[index].framebuffer,
+ .renderArea = { { 0, 0 }, { width, height } },
+ .clearValueCount = 2,
+ .pClearValues = (VkClearValue []) {
+ { .color = { .float32 = { 0.0f, 0.0f, 0.0f, 1.0f } } },
+ { .depthStencil.depth = 1.0f },
+ }
+ },
+ VK_SUBPASS_CONTENTS_INLINE);
+
+ draw_gears(swap_chain_data[index].cmd_buffer, view);
+
+ vkCmdEndRenderPass(swap_chain_data[index].cmd_buffer);
+ vkEndCommandBuffer(swap_chain_data[index].cmd_buffer);
+
+ vkQueueSubmit(queue, 1,
+ &(VkSubmitInfo) {
+ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
+ .waitSemaphoreCount = 1,
+ .pWaitSemaphores = &back_buffer_semaphore,
+ .signalSemaphoreCount = 1,
+ .pSignalSemaphores = &present_semaphore,
+ .pWaitDstStageMask = (VkPipelineStageFlags []) {
+ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+ },
+ .commandBufferCount = 1,
+ .pCommandBuffers = &swap_chain_data[index].cmd_buffer,
+ }, swap_chain_data[index].fence);
+
+ vkQueuePresentKHR(queue,
+ &(VkPresentInfoKHR) {
+ .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
+ .pWaitSemaphores = &present_semaphore,
+ .waitSemaphoreCount = 1,
+ .swapchainCount = 1,
+ .pSwapchains = (VkSwapchainKHR[]) { swap_chain, },
+ .pImageIndices = (uint32_t[]) { index, },
+ .pResults = &result,
+ });
+
+ frames++;
+
+ if (tRate0 < 0.0)
+ tRate0 = t;
+ if (t - tRate0 >= 5.0) {
+ float seconds = t - tRate0;
+ float fps = frames / seconds;
+ printf("%d frames in %3.1f seconds = %6.3f FPS\n", frames, seconds,
+ fps);
+ fflush(stdout);
+ tRate0 = t;
+ frames = 0;
+ }
+ }
+
+ fini_window();
+ fini_display();
+ return 0;
+}
diff --git a/src/vulkan/wayland.c b/src/vulkan/wayland.c
new file mode 100644
index 00000000..f52ef8bf
--- /dev/null
+++ b/src/vulkan/wayland.c
@@ -0,0 +1,243 @@
+#include <wayland-client.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <poll.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "wayland-xdg-shell-client-protocol.h"
+
+#include <vulkan/vulkan.h>
+#include <vulkan/vulkan_wayland.h>
+
+static struct wl_display *display;
+static struct wl_compositor *compositor;
+static struct xdg_wm_base *xdg_wm_base;
+
+struct wl_surface *surface;
+struct xdg_surface *xdg_surface;
+struct xdg_toplevel *xdg_toplevel;
+bool configured;
+
+static void
+xdg_wm_base_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial)
+{
+ xdg_wm_base_pong(xdg_wm_base, serial);
+}
+
+static const struct xdg_wm_base_listener xdg_wm_base_listener = {
+ xdg_wm_base_ping
+};
+
+static void
+registry_handle_global(void *data, struct wl_registry *registry, uint32_t id,
+ const char *interface, uint32_t version)
+{
+ if (strcmp(interface, wl_compositor_interface.name) == 0) {
+ assert(!compositor);
+ compositor =
+ wl_registry_bind(registry, id, &wl_compositor_interface, 1);
+ } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
+ assert(!xdg_wm_base);
+ xdg_wm_base =
+ wl_registry_bind(registry, id, &xdg_wm_base_interface, 1);
+ xdg_wm_base_add_listener(xdg_wm_base, &xdg_wm_base_listener, NULL);
+ }
+}
+
+static void
+registry_handle_global_remove(void *data, struct wl_registry *registry,
+ uint32_t name)
+{
+}
+
+static const struct wl_registry_listener registry_listener = {
+ registry_handle_global,
+ registry_handle_global_remove
+};
+
+void init_display()
+{
+ assert(!display);
+ display = wl_display_connect(NULL);
+ if (!display) {
+ fprintf(stderr, "failed to connect to display");
+ abort();
+ }
+
+ struct wl_registry *registry = wl_display_get_registry(display);
+ wl_registry_add_listener(registry, &registry_listener, NULL);
+ wl_display_roundtrip(display);
+ wl_registry_destroy(registry);
+
+ if (!compositor) {
+ fprintf(stderr, "failed to bind compositor");
+ abort();
+ }
+
+ if (!xdg_wm_base) {
+ fprintf(stderr, "xdg-shell not supported");
+ abort();
+ }
+}
+
+void fini_display()
+{
+ xdg_wm_base_destroy(xdg_wm_base);
+ wl_compositor_destroy(compositor);
+ wl_display_flush(display);
+ wl_display_disconnect(display);
+}
+
+static void
+xdg_surface_configure(void *data, struct xdg_surface *xdg_surface,
+ uint32_t serial)
+{
+ xdg_surface_ack_configure(xdg_surface, serial);
+ configured = true;
+}
+
+static const struct xdg_surface_listener xdg_surface_listener = {
+ xdg_surface_configure
+};
+
+static void
+xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel,
+ int32_t width, int32_t height,
+ struct wl_array *states)
+{
+ if (width <= 0 || height <= 0) {
+ return;
+ }
+
+ /* TODO: handle resize */
+}
+
+static void
+xdg_toplevel_close(void *data, struct xdg_toplevel *toplevel)
+{
+ /* TODO: handle close event */
+}
+
+static const struct xdg_toplevel_listener xdg_toplevel_listener = {
+ xdg_toplevel_configure,
+ xdg_toplevel_close
+};
+
+void init_window(const char *title)
+{
+ assert(xdg_wm_base && compositor);
+
+ surface = wl_compositor_create_surface(compositor);
+ xdg_surface =
+ xdg_wm_base_get_xdg_surface(xdg_wm_base, surface);
+ xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, NULL);
+
+ xdg_toplevel = xdg_surface_get_toplevel(xdg_surface);
+ xdg_toplevel_add_listener(xdg_toplevel, &xdg_toplevel_listener, NULL);
+ xdg_toplevel_set_title(xdg_toplevel, title);
+ xdg_toplevel_set_app_id(xdg_toplevel, title);
+ wl_surface_commit(surface);
+
+ while (!configured)
+ wl_display_dispatch(display);
+}
+
+void fini_window()
+{
+ xdg_toplevel_destroy(xdg_toplevel);
+ xdg_surface_destroy(xdg_surface);
+}
+
+
+bool update_window()
+{
+ struct pollfd pollfd;
+ int ret;
+
+ pollfd.fd = wl_display_get_fd(display);
+ pollfd.events = POLLIN;
+ pollfd.revents = 0;
+
+ while (1) {
+ /* If we need to flush but can't, don't do anything at all which could
+ * push further events into the socket. */
+ if (!(pollfd.events & POLLOUT)) {
+ wl_display_dispatch_pending(display);
+
+ return false;
+ }
+
+ ret = wl_display_flush(display);
+ if (ret < 0 && errno != EAGAIN)
+ break; /* fatal error; socket is broken */
+ else if (ret < 0 && errno == EAGAIN)
+ pollfd.events |= POLLOUT; /* need to wait until we can flush */
+ else
+ pollfd.events &= ~POLLOUT; /* successfully flushed */
+
+ if (poll(&pollfd, 1, -1) == -1)
+ break;
+
+ if (pollfd.revents & (POLLERR | POLLHUP | POLLNVAL))
+ break;
+
+ if (pollfd.events & POLLOUT) {
+ if (!(pollfd.revents & POLLOUT))
+ continue; /* block until we can flush */
+ pollfd.events &= ~POLLOUT;
+ }
+
+ if (pollfd.revents & POLLIN) {
+ ret = wl_display_dispatch(display);
+ if (ret == -1)
+ break;
+ }
+
+ ret = wl_display_flush(display);
+ if (ret < 0 && errno != EAGAIN)
+ break; /* fatal error; socket is broken */
+ else if (ret < 0 && errno == EAGAIN)
+ pollfd.events |= POLLOUT; /* need to wait until we can flush */
+ else
+ pollfd.events &= ~POLLOUT; /* successfully flushed */
+ }
+
+ return true;
+}
+
+#define GET_INSTANCE_PROC(name) \
+ PFN_ ## name name = (PFN_ ## name)vkGetInstanceProcAddr(instance, #name);
+
+bool
+create_surface(VkPhysicalDevice physical_device, VkInstance instance,
+ VkSurfaceKHR *psurface)
+{
+ GET_INSTANCE_PROC(vkGetPhysicalDeviceWaylandPresentationSupportKHR)
+ GET_INSTANCE_PROC(vkCreateWaylandSurfaceKHR)
+
+ if (!vkGetPhysicalDeviceWaylandPresentationSupportKHR ||
+ !vkCreateWaylandSurfaceKHR) {
+ fprintf(stderr, "Failed to load extension functions\n");
+ return VK_NULL_HANDLE;
+ }
+
+ if (!vkGetPhysicalDeviceWaylandPresentationSupportKHR(
+ physical_device, 0, display)) {
+ fprintf(stderr, "Vulkan not supported on given Wayland surface\n");
+ return false;
+ }
+
+ VkResult result =
+ vkCreateWaylandSurfaceKHR(instance,
+ &(VkWaylandSurfaceCreateInfoKHR) {
+ .sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR,
+ .display = display,
+ .surface = surface,
+ }, NULL, psurface);
+
+ return result == VK_SUCCESS;
+}