From 7eadd11e3e79730d85db73915cc139d877e48c2e Mon Sep 17 00:00:00 2001 From: Eduardo Lima Mitev Date: Thu, 22 Sep 2016 10:24:42 +0200 Subject: [PATCH] Adds a vulkan minimal example --- .gitignore | 4 + Makefile | 2 + vulkan-minimal/Makefile | 26 + vulkan-minimal/main.c | 947 +++++++++++++++++++++++++++++++++++++ vulkan-minimal/shader.frag | 9 + vulkan-minimal/shader.vert | 25 + 6 files changed, 1013 insertions(+) create mode 100644 vulkan-minimal/Makefile create mode 100644 vulkan-minimal/main.c create mode 100644 vulkan-minimal/shader.frag create mode 100644 vulkan-minimal/shader.vert diff --git a/.gitignore b/.gitignore index 571c08f..941bbaa 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ +vert.spv +frag.spv + render-nodes-minimal/render-nodes-minimal +vulkan-minimal/vulkan-minimal diff --git a/Makefile b/Makefile index 98327ff..ce76c08 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,7 @@ all: Makefile make -C render-nodes-minimal all + make -C vulkan-minimal all clean: make -C render-nodes-minimal clean + make -C vulkan-minimal clean diff --git a/vulkan-minimal/Makefile b/vulkan-minimal/Makefile new file mode 100644 index 0000000..ddd7bc1 --- /dev/null +++ b/vulkan-minimal/Makefile @@ -0,0 +1,26 @@ +TARGET=vulkan-minimal + +GLSL_VALIDATOR=../glslangValidator + +VULKAN_SO_NAME=vulkan_intel +VULKAN_SO_PATH=~/devel/build/lib + +all: Makefile $(TARGET) vert.spv frag.spv + +vert.spv: shader.vert + $(GLSL_VALIDATOR) -V shader.vert + +frag.spv: shader.frag + $(GLSL_VALIDATOR) -V shader.frag + +$(TARGET): main.c vert.spv frag.spv + gcc -ggdb -O0 -Wall -std=c99 \ + -DCURRENT_DIR=\"`pwd`\" \ + `pkg-config --libs --cflags xcb` \ + -L $(VULKAN_SO_PATH) \ + -l$(VULKAN_SO_NAME) \ + -o $(TARGET) \ + main.c + +clean: + rm -f $(TARGET) vert.spv frag.spv diff --git a/vulkan-minimal/main.c b/vulkan-minimal/main.c new file mode 100644 index 0000000..80467ea --- /dev/null +++ b/vulkan-minimal/main.c @@ -0,0 +1,947 @@ +/* + * Example: + * + * Vulkan minimal: An absolute minimal Vulkan demo. + * + * This example renders a triangle using the Vulkan API on an X11 window. + * It does the minimum required to put pixels on the screen, and not much + * more (e.g, doesn't support resizing the window). + * + * Tested on Linux 4.7, Mesa 12.0, Intel GPU (gen7+). + * + * Authors: + * * Eduardo Lima Mitev + * + * This code is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * version 3, or (at your option) any later version as published by + * the Free Software Foundation. + * + * THIS CODE IS PROVIDED AS-IS, WITHOUT WARRANTY OF ANY KIND, OR POSSIBLE + * LIABILITY TO THE AUTHORS FOR ANY CLAIM OR DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define VK_USE_PLATFORM_XCB_KHR +#include + +#define WIDTH 640 +#define HEIGHT 480 + +PFN_vkVoidFunction vk_icdGetInstanceProcAddr (VkInstance instance, + const char* pName); + +#define GET_ICD_PROC_ADDR(api, symbol) \ + api.symbol = (PFN_vk ##symbol) vk_icdGetInstanceProcAddr(NULL, "vk" #symbol); + +#define GET_PROC_ADDR(api, symbol) \ + api.symbol = (PFN_vk ##symbol) api.GetInstanceProcAddr(NULL, "vk" #symbol); + +#define GET_INSTANCE_PROC_ADDR(api, instance, symbol) \ + api.symbol = (PFN_vk ##symbol) api.GetInstanceProcAddr(instance, "vk" #symbol); + +#define GET_DEVICE_PROC_ADDR(api, device, symbol) \ + api.symbol = (PFN_vk ##symbol) api.GetDeviceProcAddr(device, "vk" #symbol); + +struct vk_api { + PFN_vkGetInstanceProcAddr GetInstanceProcAddr; + PFN_vkGetDeviceProcAddr GetDeviceProcAddr; + PFN_vkEnumerateInstanceLayerProperties EnumerateInstanceLayerProperties; + PFN_vkEnumerateInstanceExtensionProperties EnumerateInstanceExtensionProperties; + PFN_vkCreateInstance CreateInstance; + PFN_vkEnumeratePhysicalDevices EnumeratePhysicalDevices; + PFN_vkGetPhysicalDeviceProperties GetPhysicalDeviceProperties; + PFN_vkGetPhysicalDeviceQueueFamilyProperties GetPhysicalDeviceQueueFamilyProperties; + PFN_vkCreateDevice CreateDevice; + PFN_vkEnumerateDeviceExtensionProperties EnumerateDeviceExtensionProperties; + PFN_vkGetDeviceQueue GetDeviceQueue; + PFN_vkCreateCommandPool CreateCommandPool; + PFN_vkAllocateCommandBuffers AllocateCommandBuffers; + PFN_vkFreeCommandBuffers FreeCommandBuffers; + PFN_vkCreateRenderPass CreateRenderPass; + PFN_vkDestroyRenderPass DestroyRenderPass; + PFN_vkDestroyCommandPool DestroyCommandPool; + PFN_vkDestroyDevice DestroyDevice; + PFN_vkDestroyInstance DestroyInstance; + PFN_vkCreateGraphicsPipelines CreateGraphicsPipelines; + PFN_vkDestroyPipeline DestroyPipeline; + PFN_vkCreateShaderModule CreateShaderModule; + PFN_vkDestroyShaderModule DestroyShaderModule; + PFN_vkCreatePipelineLayout CreatePipelineLayout; + PFN_vkDestroyPipelineLayout DestroyPipelineLayout; + PFN_vkCreateImageView CreateImageView; + PFN_vkDestroyImageView DestroyImageView; + PFN_vkCreateFramebuffer CreateFramebuffer; + PFN_vkDestroyFramebuffer DestroyFramebuffer; + PFN_vkBeginCommandBuffer BeginCommandBuffer; + PFN_vkEndCommandBuffer EndCommandBuffer; + PFN_vkCmdBeginRenderPass CmdBeginRenderPass; + PFN_vkCmdBindPipeline CmdBindPipeline; + PFN_vkCmdDraw CmdDraw; + PFN_vkCmdEndRenderPass CmdEndRenderPass; + PFN_vkCreateSemaphore CreateSemaphore; + PFN_vkDestroySemaphore DestroySemaphore; + PFN_vkQueueSubmit QueueSubmit; + PFN_vkDeviceWaitIdle DeviceWaitIdle; + + PFN_vkCreateXcbSurfaceKHR CreateXcbSurfaceKHR; + PFN_vkDestroySurfaceKHR DestroySurfaceKHR; + PFN_vkGetPhysicalDeviceSurfaceSupportKHR GetPhysicalDeviceSurfaceSupportKHR; + PFN_vkGetPhysicalDeviceSurfaceFormatsKHR GetPhysicalDeviceSurfaceFormatsKHR; + PFN_vkGetPhysicalDeviceSurfacePresentModesKHR GetPhysicalDeviceSurfacePresentModesKHR; + PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR GetPhysicalDeviceSurfaceCapabilitiesKHR; + PFN_vkCreateSwapchainKHR CreateSwapchainKHR; + PFN_vkDestroySwapchainKHR DestroySwapchainKHR; + PFN_vkGetSwapchainImagesKHR GetSwapchainImagesKHR; + PFN_vkAcquireNextImageKHR AcquireNextImageKHR; + PFN_vkQueuePresentKHR QueuePresentKHR; +}; + +static uint32_t* +load_file (const char* filename, size_t* file_size) +{ + char* data = NULL; + size_t size = 0; + size_t read_size = 0; + size_t alloc_size = 0; + int32_t fd = open (filename, O_RDONLY); + uint8_t buf[1024]; + + while ((read_size = read (fd, buf, 1024)) > 0) { + if (size + read_size > alloc_size) { + alloc_size = read_size + size; + data = realloc (data, alloc_size); + } + + memcpy (data + size, buf, read_size); + size += read_size; + } + + if (read_size == 0) { + if (file_size) + *file_size = size; + + return (uint32_t*) data; + } + else { + return NULL; + } +} + +int32_t +main (int32_t argc, char* argv[]) +{ + VkResult result; + struct vk_api vk; + + /* XCB setup */ + /* ======================================================================= */ + + /* connection to the X server */ + xcb_connection_t* xcb_conn = xcb_connect (NULL, NULL); + assert (xcb_conn != NULL); + + /* Get the first screen */ + const xcb_setup_t* xcb_setup = xcb_get_setup (xcb_conn); + xcb_screen_iterator_t iter = xcb_setup_roots_iterator (xcb_setup); + xcb_screen_t* xcb_screen = iter.data; + assert (xcb_screen != NULL); + + /* Create the window */ + xcb_window_t xcb_win = xcb_generate_id (xcb_conn); + + uint32_t value_mask, value_list[32]; + value_mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK; + value_list[0] = xcb_screen->black_pixel; + value_list[1] = XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_EXPOSURE | + XCB_EVENT_MASK_STRUCTURE_NOTIFY; + + xcb_create_window (xcb_conn, /* Connection */ + XCB_COPY_FROM_PARENT, /* depth (same as root)*/ + xcb_win, /* window Id */ + xcb_screen->root, /* parent window */ + 0, 0, /* x, y */ + WIDTH, HEIGHT, /* width, height */ + 0, /* border_width */ + XCB_WINDOW_CLASS_INPUT_OUTPUT, /* class */ + xcb_screen->root_visual, /* visual */ + value_mask, value_list); /* masks, not used yet */ + + /* Map the window onto the screen */ + xcb_map_window (xcb_conn, xcb_win); + + /* Make sure commands are sent before we pause so that the window gets + * shown. + */ + xcb_flush (xcb_conn); + + /* Vulkan setup */ + /* ======================================================================= */ + + /* load inital API entry points */ + GET_ICD_PROC_ADDR (vk, GetInstanceProcAddr); + GET_PROC_ADDR (vk, EnumerateInstanceLayerProperties); + GET_PROC_ADDR (vk, EnumerateInstanceExtensionProperties); + GET_PROC_ADDR (vk, CreateInstance); + + /* enummerate supported instance extensions, where we normally check for + * support for window systems integration and presence of VK_KHR_surface. + */ + VkExtensionProperties ext_props[16]; + uint32_t ext_props_count = 16; + result = vk.EnumerateInstanceExtensionProperties (NULL, + &ext_props_count, + ext_props); + assert (result == VK_SUCCESS); + printf ("Instance extensions:\n"); + for (unsigned i = 0; i < ext_props_count; i++) + printf (" %s(%u)\n", + ext_props[i].extensionName, + ext_props[i].specVersion); + + /* the memory allocation callbacks (default by now) */ + const VkAllocationCallbacks* allocator = NULL; + + /* Vulkan application info */ + VkApplicationInfo app_info = { + .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pApplicationName = "Vulkan minimal example", + .apiVersion = VK_API_VERSION_1_0, + }; + + /* create the Vulkan instance */ + VkInstance instance = VK_NULL_HANDLE; + + const char* enabled_extensions[2] = { + VK_KHR_SURFACE_EXTENSION_NAME, + VK_KHR_XCB_SURFACE_EXTENSION_NAME + }; + + VkInstanceCreateInfo instance_info = { + .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .pApplicationInfo = &app_info, + .enabledExtensionCount = 2, + .ppEnabledExtensionNames = enabled_extensions + }; + result = vk.CreateInstance (&instance_info, + allocator, + &instance); + assert (result == VK_SUCCESS); + + /* load instance-dependent API entry points */ + GET_INSTANCE_PROC_ADDR (vk, instance, DestroyInstance); + GET_INSTANCE_PROC_ADDR (vk, instance, EnumeratePhysicalDevices); + GET_INSTANCE_PROC_ADDR (vk, instance, GetPhysicalDeviceQueueFamilyProperties); + GET_INSTANCE_PROC_ADDR (vk, instance, CreateDevice); + GET_INSTANCE_PROC_ADDR (vk, instance, EnumerateDeviceExtensionProperties); + GET_INSTANCE_PROC_ADDR (vk, instance, GetPhysicalDeviceProperties); + + GET_INSTANCE_PROC_ADDR (vk, instance, CreateXcbSurfaceKHR); + GET_INSTANCE_PROC_ADDR (vk, instance, DestroySurfaceKHR); + GET_INSTANCE_PROC_ADDR (vk, instance, GetPhysicalDeviceSurfaceSupportKHR); + GET_INSTANCE_PROC_ADDR (vk, instance, GetPhysicalDeviceSurfaceFormatsKHR); + GET_INSTANCE_PROC_ADDR (vk, instance, + GetPhysicalDeviceSurfacePresentModesKHR); + GET_INSTANCE_PROC_ADDR (vk, instance, + GetPhysicalDeviceSurfaceCapabilitiesKHR); + + /* query physical devices */ + uint32_t num_devices = 5; + VkPhysicalDevice devices[5] = {0}; + result = vk.EnumeratePhysicalDevices (instance, + &num_devices, + devices); + assert (result == VK_SUCCESS); + assert (num_devices > 0); + + VkPhysicalDevice physical_device = devices[0]; + + /* query physical device's queue families */ + uint32_t num_queue_families = 5; + VkQueueFamilyProperties queue_families[5] = {0}; + /* get queue families with NULL first, to retrieve count */ + vk.GetPhysicalDeviceQueueFamilyProperties (physical_device, + &num_queue_families, + NULL); + vk.GetPhysicalDeviceQueueFamilyProperties (devices[0], + &num_queue_families, + queue_families); + assert (num_queue_families >= 1); + + uint32_t queue_family_index = 0; + assert (queue_families[queue_family_index].queueFlags & + VK_QUEUE_GRAPHICS_BIT); + + /* Enummerate supported device extensions, where we would normally check for + * the presence of VK_KHR_swapchain. + */ + ext_props_count = 16; + result = vk.EnumerateDeviceExtensionProperties (physical_device, + NULL, + &ext_props_count, + ext_props); + assert (result == VK_SUCCESS); + printf ("Device extensions: \n"); + for (unsigned i = 0; i < ext_props_count; i++) + printf (" %s(%u)\n", + ext_props[i].extensionName, + ext_props[i].specVersion); + + /* create a vulkan surface, from the XCB window (VkSurfaceKHR) */ + VkSurfaceKHR surface = VK_NULL_HANDLE; + VkXcbSurfaceCreateInfoKHR surface_info = { + .sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR, + .connection = xcb_conn, + .window = xcb_win + }; + result = vk.CreateXcbSurfaceKHR (instance, + &surface_info, + allocator, + &surface); + assert (result == VK_SUCCESS); + + /* check for present support in the selected queue family */ + VkBool32 support_present = VK_FALSE; + vk.GetPhysicalDeviceSurfaceSupportKHR (physical_device, + queue_family_index, + surface, + &support_present); + assert (support_present); + + /* physical device capabilities (needed by swapchain for + * minImageCount and maxImageCount. + */ + VkSurfaceCapabilitiesKHR surface_caps; + result = vk.GetPhysicalDeviceSurfaceCapabilitiesKHR (physical_device, + surface, + &surface_caps); + assert (result == VK_SUCCESS); + + /* choose a surface format */ + uint32_t surface_formats_count; + result = vk.GetPhysicalDeviceSurfaceFormatsKHR (physical_device, + surface, + &surface_formats_count, + NULL); + assert (surface_formats_count > 0); + surface_formats_count = 1; + VkSurfaceFormatKHR surface_format; + result = vk.GetPhysicalDeviceSurfaceFormatsKHR (physical_device, + surface, + &surface_formats_count, + &surface_format); + assert (result == VK_SUCCESS || result == VK_INCOMPLETE); + + /* choose a present mode */ + VkPresentModeKHR present_mode; + uint32_t present_mode_count = 1; + result = vk.GetPhysicalDeviceSurfacePresentModesKHR (physical_device, + surface, + &present_mode_count, + &present_mode); + assert (result == VK_SUCCESS || result == VK_INCOMPLETE); + assert (present_mode_count > 0); + + /* create logical device */ + VkDevice device = VK_NULL_HANDLE; + + const float queue_priorities = 1.0; + VkDeviceQueueCreateInfo queue_info = { + .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + .flags = 0, + .queueCount = 1, + .pQueuePriorities = &queue_priorities, + .queueFamilyIndex = queue_family_index + }; + + const char* device_extensions[1] = {VK_KHR_SWAPCHAIN_EXTENSION_NAME}; + + VkDeviceCreateInfo device_info = { + .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .pQueueCreateInfos = &queue_info, + .queueCreateInfoCount = 1, + .enabledExtensionCount = 1, + .ppEnabledExtensionNames = device_extensions, + }; + result = vk.CreateDevice (devices[0], + &device_info, + allocator, + &device); + assert (result == VK_SUCCESS); + + /* load device-dependent API entry points */ + GET_INSTANCE_PROC_ADDR (vk, instance, GetDeviceProcAddr); + GET_DEVICE_PROC_ADDR (vk, device, CreateCommandPool); + GET_DEVICE_PROC_ADDR (vk, device, CmdBeginRenderPass); + GET_DEVICE_PROC_ADDR (vk, device, CmdDraw); + GET_DEVICE_PROC_ADDR (vk, device, CmdEndRenderPass); + GET_DEVICE_PROC_ADDR (vk, device, AllocateCommandBuffers); + GET_DEVICE_PROC_ADDR (vk, device, FreeCommandBuffers); + GET_DEVICE_PROC_ADDR (vk, device, GetDeviceQueue); + GET_DEVICE_PROC_ADDR (vk, device, CreateRenderPass); + GET_DEVICE_PROC_ADDR (vk, device, DestroyRenderPass); + GET_DEVICE_PROC_ADDR (vk, device, DestroyCommandPool); + GET_DEVICE_PROC_ADDR (vk, device, DestroyDevice); + GET_DEVICE_PROC_ADDR (vk, device, CreateGraphicsPipelines); + GET_DEVICE_PROC_ADDR (vk, device, DestroyPipeline); + GET_DEVICE_PROC_ADDR (vk, device, CreateShaderModule); + GET_DEVICE_PROC_ADDR (vk, device, DestroyShaderModule); + GET_DEVICE_PROC_ADDR (vk, device, CreatePipelineLayout); + GET_DEVICE_PROC_ADDR (vk, device, DestroyPipelineLayout); + GET_DEVICE_PROC_ADDR (vk, device, CreateImageView); + GET_DEVICE_PROC_ADDR (vk, device, DestroyImageView); + GET_DEVICE_PROC_ADDR (vk, device, CreateFramebuffer); + GET_DEVICE_PROC_ADDR (vk, device, DestroyFramebuffer); + GET_DEVICE_PROC_ADDR (vk, device, BeginCommandBuffer); + GET_DEVICE_PROC_ADDR (vk, device, EndCommandBuffer); + GET_DEVICE_PROC_ADDR (vk, device, CmdBindPipeline); + GET_DEVICE_PROC_ADDR (vk, device, CreateSemaphore); + GET_DEVICE_PROC_ADDR (vk, device, DestroySemaphore); + GET_DEVICE_PROC_ADDR (vk, device, QueueSubmit); + GET_DEVICE_PROC_ADDR (vk, device, DeviceWaitIdle); + + GET_DEVICE_PROC_ADDR (vk, device, CreateSwapchainKHR); + GET_DEVICE_PROC_ADDR (vk, device, DestroySwapchainKHR); + GET_DEVICE_PROC_ADDR (vk, device, GetSwapchainImagesKHR); + GET_DEVICE_PROC_ADDR (vk, device, AcquireNextImageKHR); + GET_DEVICE_PROC_ADDR (vk, device, QueuePresentKHR); + + /* create the vertex shader module */ + size_t shader_code_size; + uint32_t* shader_code = load_file (CURRENT_DIR "/vert.spv", + &shader_code_size); + assert (shader_code != NULL); + + VkShaderModuleCreateInfo shader_info = { + .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + .codeSize = shader_code_size, + .pCode = shader_code, + }; + VkShaderModule vert_shader_module; + result = vk.CreateShaderModule (device, + &shader_info, + allocator, + &vert_shader_module); + assert (result == VK_SUCCESS); + free (shader_code); + + /* create the fragment shader module */ + shader_code = load_file (CURRENT_DIR "/frag.spv", &shader_code_size); + assert (shader_code != NULL); + + shader_info.codeSize = shader_code_size; + shader_info.pCode = shader_code; + + VkShaderModule frag_shader_module; + result = vk.CreateShaderModule (device, + &shader_info, + allocator, + &frag_shader_module); + assert (result == VK_SUCCESS); + free (shader_code); + + /* create the shader stages */ + VkPipelineShaderStageCreateInfo vert_stage_info = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .stage = VK_SHADER_STAGE_VERTEX_BIT, + .module = vert_shader_module, + .pName = "main" + }; + + VkPipelineShaderStageCreateInfo frag_stage_info = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .stage = VK_SHADER_STAGE_FRAGMENT_BIT, + .module = frag_shader_module, + .pName = "main" + }; + + VkPipelineShaderStageCreateInfo shader_stages[2] = + {vert_stage_info, frag_stage_info}; + + /* get first device queue */ + VkQueue queue = VK_NULL_HANDLE; + vk.GetDeviceQueue (device, queue_family_index, 0, &queue); + assert (queue != VK_NULL_HANDLE); + + /* create a command pool */ + VkCommandPool cmd_pool = VK_NULL_HANDLE;; + VkCommandPoolCreateInfo cmd_pool_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .flags = 0, + .queueFamilyIndex = queue_family_index, + }; + result = vk.CreateCommandPool (device, + &cmd_pool_info, + allocator, + &cmd_pool); + assert (result == VK_SUCCESS); + + /* create rendering semaphores */ + VkSemaphore image_available_semaphore = VK_NULL_HANDLE; + VkSemaphore render_finished_semaphore = VK_NULL_HANDLE; + + VkSemaphoreCreateInfo semaphore_info = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO + }; + + result = vk.CreateSemaphore (device, + &semaphore_info, + allocator, + &image_available_semaphore); + assert (result == VK_SUCCESS); + + result = vk.CreateSemaphore (device, + &semaphore_info, + allocator, + &render_finished_semaphore); + assert (result == VK_SUCCESS); + + /* create the swapchain. This is normally done in a reusable way, because + * the swapchain needs to be recreated quite often (e.g window resize). + */ + VkSwapchainKHR swapchain = VK_NULL_HANDLE; + + /* @FIXME: check extent against surface capabilities */ + VkExtent2D swapchain_extent = {WIDTH, HEIGHT}; + VkSwapchainCreateInfoKHR swap_chain_info = { + .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, + .surface = surface, + .minImageCount = surface_caps.minImageCount, + .imageFormat = surface_format.format, + .imageColorSpace = surface_format.colorSpace, + .imageExtent = swapchain_extent, + .imageArrayLayers = 1, + .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + + .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = NULL, + .preTransform = surface_caps.currentTransform, + .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, + .presentMode = present_mode, + .clipped = VK_TRUE, + .oldSwapchain = VK_NULL_HANDLE + }; + + result = vk.CreateSwapchainKHR (device, + &swap_chain_info, + allocator, + &swapchain); + assert (result == VK_SUCCESS); + + /* get the images from the swap chain */ + uint32_t swapchain_images_count = 0; + result = vk.GetSwapchainImagesKHR (device, + swapchain, + &swapchain_images_count, + NULL); + assert (result == VK_SUCCESS); + assert (swapchain_images_count > 0 && swapchain_images_count <= 8); + + VkImage swapchain_images[8] = {VK_NULL_HANDLE}; + vk.GetSwapchainImagesKHR (device, + swapchain, + &swapchain_images_count, + swapchain_images); + + /* create an image view for each swap chain image */ + VkImageView image_views[8]; + for (uint32_t i = 0; i < swapchain_images_count; i++) { + VkImageViewCreateInfo image_view_info = { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .image = swapchain_images[i], + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = surface_format.format, + .components.r = VK_COMPONENT_SWIZZLE_IDENTITY, + .components.g = VK_COMPONENT_SWIZZLE_IDENTITY, + .components.b = VK_COMPONENT_SWIZZLE_IDENTITY, + .components.a = VK_COMPONENT_SWIZZLE_IDENTITY, + .subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .subresourceRange.baseMipLevel = 0, + .subresourceRange.levelCount = 1, + .subresourceRange.baseArrayLayer = 0, + .subresourceRange.layerCount = 1, + }; + + result = vk.CreateImageView (device, + &image_view_info, + allocator, + &image_views[i]); + assert (result == VK_SUCCESS); + } + + /* config a color attachment */ + VkAttachmentDescription color_attachment = { + .format = surface_format.format, + .samples = VK_SAMPLE_COUNT_1_BIT, + .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, + .storeOp = VK_ATTACHMENT_STORE_OP_STORE, + .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, + .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + }; + + /* an attachment reference */ + VkAttachmentReference color_attachment_ref = { + .attachment = 0, + .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL + }; + + /* a render sub-pass */ + VkSubpassDescription render_subpass = { + .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, + .colorAttachmentCount = 1, + .pColorAttachments = &color_attachment_ref, + }; + + VkSubpassDependency dependency = { + .srcSubpass = VK_SUBPASS_EXTERNAL, + .dstSubpass = 0, + .srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + .srcAccessMask = VK_ACCESS_MEMORY_READ_BIT, + .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT + }; + + /* create a render pass */ + VkRenderPass renderpass = VK_NULL_HANDLE; + VkRenderPassCreateInfo render_pass_info = { + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, + .attachmentCount = 1, + .pAttachments = &color_attachment, + .subpassCount = 1, + .pSubpasses = &render_subpass, + .dependencyCount = 1, + .pDependencies = &dependency + }; + + result = vk.CreateRenderPass (device, + &render_pass_info, + allocator, + &renderpass); + assert (result == VK_SUCCESS); + + /* create framebuffers for each image view */ + VkFramebuffer framebuffers[4] = {VK_NULL_HANDLE}; + for (uint32_t i = 0; i < swapchain_images_count; i++) { + VkImageView attachments[] = { + image_views[i] + }; + + VkFramebufferCreateInfo framebuffer_info = { + .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, + .renderPass = renderpass, + .attachmentCount = 1, + .pAttachments = attachments, + .width = swapchain_extent.width, + .height = swapchain_extent.height, + .layers = 1 + }; + + result = vk.CreateFramebuffer (device, + &framebuffer_info, + allocator, + &framebuffers[i]); + assert (result == VK_SUCCESS); + } + + /* create the graphics pipeline */ + + /* specify the vertex input (all default to zero, since we won't use it here) */ + VkPipelineVertexInputStateCreateInfo vertex_input_info = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO + }; + + /* specify the input assembly (type of primitives) */ + VkPipelineInputAssemblyStateCreateInfo input_assembly_info = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, + .primitiveRestartEnable = VK_FALSE, + }; + + /* viewport and scissors */ + VkViewport viewport = { + .x = 0.0f, + .y = 0.0f, + .width = (float) swapchain_extent.width, + .height = (float) swapchain_extent.height, + .minDepth = 0.0f, + .maxDepth = 1.0f + }; + + VkRect2D scissor = { + .offset.x = 0, + .offset.y = 0, + .extent = swapchain_extent + }; + + VkPipelineViewportStateCreateInfo viewport_state_info = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .viewportCount = 1, + .pViewports = &viewport, + .scissorCount = 1, + .pScissors = &scissor + }; + + /* configure rasterizer */ + VkPipelineRasterizationStateCreateInfo rasterizer = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + .depthClampEnable = VK_FALSE, + .rasterizerDiscardEnable = VK_FALSE, + .polygonMode = VK_POLYGON_MODE_FILL, + .lineWidth = 1.0f, + + .cullMode = VK_CULL_MODE_BACK_BIT, + .frontFace = VK_FRONT_FACE_CLOCKWISE, + + .depthBiasEnable = VK_FALSE, + .depthBiasConstantFactor = 0.0f, + .depthBiasClamp = 0.0f, + .depthBiasSlopeFactor = 0.0f + }; + + /* configure multisampling */ + VkPipelineMultisampleStateCreateInfo multisampling = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .sampleShadingEnable = VK_FALSE, + .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT, + .minSampleShading = 1.0f, + .pSampleMask = NULL, + .alphaToCoverageEnable = VK_FALSE, + .alphaToOneEnable = VK_FALSE + }; + + /* color blending */ + VkPipelineColorBlendAttachmentState color_blend_attachment = { + .colorWriteMask = + VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT + | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT, + .blendEnable = VK_FALSE, + .srcColorBlendFactor = VK_BLEND_FACTOR_ONE, + .dstColorBlendFactor = VK_BLEND_FACTOR_ZERO, + .colorBlendOp = VK_BLEND_OP_ADD, + .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE, + .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, + .alphaBlendOp = VK_BLEND_OP_ADD + }; + + VkPipelineColorBlendStateCreateInfo color_blending_info = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, + .logicOpEnable = VK_FALSE, + .logicOp = VK_LOGIC_OP_COPY, + .attachmentCount = 1, + .pAttachments = &color_blend_attachment, + .blendConstants[0] = 0.0f, + .blendConstants[1] = 0.0f, + .blendConstants[2] = 0.0f, + .blendConstants[3] = 0.0f, + }; + + /* pipeline layout */ + VkPipelineLayout pipeline_layout = VK_NULL_HANDLE; + VkPipelineLayoutCreateInfo pipeline_layout_info = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + .setLayoutCount = 0, + .pSetLayouts = NULL, + .pushConstantRangeCount = 0, + .pPushConstantRanges = 0 + }; + result = vk.CreatePipelineLayout (device, + &pipeline_layout_info, + NULL, + &pipeline_layout); + assert (result == VK_SUCCESS); + + /* the pipeline, finally */ + VkPipeline pipeline = VK_NULL_HANDLE; + VkGraphicsPipelineCreateInfo pipeline_info = { + .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + .stageCount = 2, + .pStages = shader_stages, + .pVertexInputState = &vertex_input_info, + .pInputAssemblyState = &input_assembly_info, + .pViewportState = &viewport_state_info, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pDepthStencilState = NULL, + .pColorBlendState = &color_blending_info, + .pDynamicState = NULL, + .layout = pipeline_layout, + .renderPass = renderpass, + .subpass = 0, + .basePipelineHandle = VK_NULL_HANDLE, + .basePipelineIndex = -1 + }; + result = vk.CreateGraphicsPipelines (device, + VK_NULL_HANDLE, + 1, + &pipeline_info, + allocator, + &pipeline); + assert (result == VK_SUCCESS); + + /* create command buffers */ + VkCommandBuffer cmd_buffers[8] = {VK_NULL_HANDLE,}; + VkCommandBufferAllocateInfo cmd_buffer_alloc_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = swapchain_images_count, + .commandPool = cmd_pool + }; + result = vk.AllocateCommandBuffers (device, + &cmd_buffer_alloc_info, + cmd_buffers); + assert (result == VK_SUCCESS); + + /* start recording command buffers */ + for (uint32_t i = 0; i < swapchain_images_count; i++) { + VkCommandBufferBeginInfo begin_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT, + .pInheritanceInfo = VK_NULL_HANDLE + }; + + result = vk.BeginCommandBuffer (cmd_buffers[i], &begin_info); + assert (result == VK_SUCCESS); + + /* start a render pass */ + VkClearValue clear_color = {{{0.01f, 0.01f, 0.01f, 1.0f}}}; + VkOffset2D swapchain_offset = {0, 0}; + VkRenderPassBeginInfo renderpass_begin_info = { + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, + .renderPass = renderpass, + .framebuffer = framebuffers[i], + .renderArea.offset = swapchain_offset, + .renderArea.extent = swapchain_extent, + .clearValueCount = 1, + .pClearValues = &clear_color + }; + vk.CmdBeginRenderPass (cmd_buffers[i], + &renderpass_begin_info, + VK_SUBPASS_CONTENTS_INLINE); + + vk.CmdBindPipeline (cmd_buffers[i], + VK_PIPELINE_BIND_POINT_GRAPHICS, + pipeline); + + vk.CmdDraw (cmd_buffers[i], 3, 1, 0, 0); + + vk.CmdEndRenderPass (cmd_buffers[i]); + + vk.EndCommandBuffer (cmd_buffers[i]); + } + + + /* start the show (mainloop) */ + + while (VK_TRUE) { + /* acquire next image */ + uint32_t image_index; + result = vk.AcquireNextImageKHR (device, + swapchain, + 1000, + image_available_semaphore, + VK_NULL_HANDLE, + &image_index); + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { + /* swapchain needs recreation */ + printf ("EXPOSE event, swapchain needs to be recreated\n"); + continue; + } + + assert (result == VK_SUCCESS); + + /* submit the command buffer to the graphics queue */ + VkSemaphore wait_semaphores[] = {image_available_semaphore}; + VkSemaphore signal_semaphores[] = {render_finished_semaphore}; + + VkPipelineStageFlags wait_stages[] = + {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; + + VkSubmitInfo submit_info = { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .waitSemaphoreCount = 1, + .pWaitSemaphores = wait_semaphores, + .pWaitDstStageMask = wait_stages, + .commandBufferCount = 1, + .pCommandBuffers = &cmd_buffers[image_index], + .signalSemaphoreCount = 1, + .pSignalSemaphores = signal_semaphores + }; + + result = vk.QueueSubmit (queue, + 1, + &submit_info, + VK_NULL_HANDLE); + assert (result == VK_SUCCESS); + + /* present the frame */ + VkSwapchainKHR swap_chains[] = {swapchain}; + VkPresentInfoKHR present_info = { + .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + .waitSemaphoreCount = 1, + .pWaitSemaphores = signal_semaphores, + .swapchainCount = 1, + .pSwapchains = swap_chains, + .pImageIndices = &image_index, + .pResults = NULL + }; + + /* present the render pass onto the surface. or... DRAW! */ + result = vk.QueuePresentKHR (queue, &present_info); + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { + /* swapchain needs recreation */ + printf ("EXPOSE event, swapchain needs to be recreated\n"); + continue; + } + + assert (result == VK_SUCCESS); + } + + /* free all allocated objects */ + + /* wait for all async ops on device */ + if (device != VK_NULL_HANDLE) + vk.DeviceWaitIdle (device); + + /* destroy state */ + for (uint32_t i = 0; i < swapchain_images_count; i++) + vk.DestroyFramebuffer (device, framebuffers[i], allocator); + + for (uint32_t i = 0; i < swapchain_images_count; i++) + vk.DestroyImageView (device, image_views[i], allocator); + + vk.DestroyPipeline (device, pipeline, allocator); + vk.DestroyPipelineLayout (device, pipeline_layout, allocator); + vk.DestroyRenderPass (device, renderpass, allocator); + vk.DestroySwapchainKHR (device, swapchain, allocator); + + /* destroy immutable objects */ + vk.DestroySemaphore (device, image_available_semaphore, allocator); + vk.DestroySemaphore (device, render_finished_semaphore, allocator); + vk.DestroyCommandPool (device, cmd_pool, allocator); + vk.DestroyShaderModule (device, vert_shader_module, allocator); + vk.DestroyShaderModule (device, frag_shader_module, allocator); + vk.DestroyDevice (device, allocator); + vk.DestroySurfaceKHR (instance, surface, allocator); + vk.DestroyInstance (instance, allocator); + + /* Unmap the window from the screen */ + xcb_unmap_window (xcb_conn, xcb_win); + + /* disconnect from the X server */ + if (xcb_conn != NULL) + xcb_disconnect (xcb_conn); + + return 0; +} diff --git a/vulkan-minimal/shader.frag b/vulkan-minimal/shader.frag new file mode 100644 index 0000000..d2494b0 --- /dev/null +++ b/vulkan-minimal/shader.frag @@ -0,0 +1,9 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable + +layout(location = 0) out highp vec4 outColor; +layout(location = 0) in vec3 color; + +void main() { + outColor = vec4(color, 1.0); +} diff --git a/vulkan-minimal/shader.vert b/vulkan-minimal/shader.vert new file mode 100644 index 0000000..bdd1540 --- /dev/null +++ b/vulkan-minimal/shader.vert @@ -0,0 +1,25 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable + +out gl_PerVertex { + vec4 gl_Position; +}; + +vec2 positions[3] = vec2[]( + vec2( 0.0, -0.5), + vec2( 0.5, 0.5), + vec2(-0.5, 0.5) +); + +vec3 colors[3] = vec3[] ( + vec3(1.0, 0.0, 0.0), + vec3(0.0, 1.0, 0.0), + vec3(0.0, 0.0, 1.0) +); + +layout(location = 0) out vec3 color; + +void main() { + gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); + color = colors[gl_VertexIndex]; +}