├── .dir-locals.el ├── .gitignore ├── Makefile ├── README.md ├── common ├── vk-api.c ├── vk-api.h ├── wsi-xcb.c └── wsi.h ├── gl-image-loader ├── .dir-locals.el ├── Makefile ├── igalia-white-text.png ├── image.c ├── image.h ├── jpeg.c ├── jpeg.h ├── main.c ├── png.c └── png.h ├── glslangValidator ├── render-nodes-minimal ├── Makefile └── main.c ├── vulkan-minimal ├── Makefile ├── main.c ├── shader.frag └── shader.vert └── vulkan-triangle ├── Makefile ├── common ├── main.c ├── shader.frag └── shader.vert /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ((prog-mode 2 | (indent-tabs-mode . nil) 3 | (tab-width . 8) 4 | (c-basic-offset . 3) 5 | (c-file-style . "stroustrup") 6 | (fill-column . 78) 7 | (eval . (progn 8 | (c-set-offset 'case-label '0) 9 | (c-set-offset 'innamespace '0) 10 | (c-set-offset 'inline-open '0))) 11 | ) 12 | (makefile-mode (indent-tabs-mode . t)) 13 | ) 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vert.spv 2 | frag.spv 3 | 4 | render-nodes-minimal/render-nodes-minimal 5 | vulkan-minimal/vulkan-minimal 6 | vulkan-triangle/vulkan-triangle 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: Makefile 2 | make -C render-nodes-minimal all 3 | make -C vulkan-minimal all 4 | make -C vulkan-triangle all 5 | 6 | clean: 7 | make -C render-nodes-minimal clean 8 | make -C vulkan-minimal clean 9 | make -C vulkan-triangle clean 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GPU Playground 2 | 3 | This is my personal collection of examples, code-snippets and algorithm implementations related to graphics and GPU programming in general. I use this repository for prototyping, quick reference, code sharing, and as a resource for my articles on the topic. 4 | 5 | The examples labeled with a _minimal_ suffix are expected to be the minimum possible (but readable) code to achieve a particular goal. These are not intended to be complete or functional, but just provide the highest signal vs. noise ratio, with minimum boilerplate and dependencies. They are, in a way, the examples I wish existed when I started looking into the thing. 6 | 7 | All examples are written in standard C99, unless not possible for some practical reason. 8 | 9 | Have fun! 10 | -------------------------------------------------------------------------------- /common/vk-api.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Vulkan API loader helper 3 | * 4 | * This code is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public License 6 | * version 3, or (at your option) any later version as published by 7 | * the Free Software Foundation. 8 | * 9 | * THIS CODE IS PROVIDED AS-IS, WITHOUT WARRANTY OF ANY KIND, OR POSSIBLE 10 | * LIABILITY TO THE AUTHORS FOR ANY CLAIM OR DAMAGE. 11 | */ 12 | 13 | #include 14 | #include "vk-api.h" 15 | 16 | void 17 | vk_api_load_from_icd (struct vk_api* vk) 18 | { 19 | /* load API entry points from ICD */ 20 | 21 | /* if program is linked against a Vulkan loader */ 22 | vk->GetInstanceProcAddr = vkGetInstanceProcAddr; 23 | /* otherwise, */ 24 | /* GET_ICD_PROC_ADDR (vk, GetInstanceProcAddr); */ 25 | 26 | GET_PROC_ADDR (*vk, EnumerateInstanceLayerProperties); 27 | GET_PROC_ADDR (*vk, EnumerateInstanceExtensionProperties); 28 | GET_PROC_ADDR (*vk, CreateInstance); 29 | } 30 | 31 | void 32 | vk_api_load_from_instance (struct vk_api* vk, VkInstance* instance) 33 | { 34 | assert (instance != VK_NULL_HANDLE); 35 | if (vk->GetInstanceProcAddr == NULL) 36 | vk_api_load_from_icd (vk); 37 | 38 | /* load instance-dependent API entry points */ 39 | GET_INSTANCE_PROC_ADDR (*vk, *instance, GetDeviceProcAddr); 40 | 41 | GET_INSTANCE_PROC_ADDR (*vk, *instance, DestroyInstance); 42 | GET_INSTANCE_PROC_ADDR (*vk, *instance, EnumeratePhysicalDevices); 43 | GET_INSTANCE_PROC_ADDR (*vk, *instance, GetPhysicalDeviceQueueFamilyProperties); 44 | GET_INSTANCE_PROC_ADDR (*vk, *instance, CreateDevice); 45 | GET_INSTANCE_PROC_ADDR (*vk, *instance, EnumerateDeviceExtensionProperties); 46 | GET_INSTANCE_PROC_ADDR (*vk, *instance, GetPhysicalDeviceProperties); 47 | 48 | GET_INSTANCE_PROC_ADDR (*vk, *instance, DestroySurfaceKHR); 49 | GET_INSTANCE_PROC_ADDR (*vk, *instance, GetPhysicalDeviceSurfaceSupportKHR); 50 | GET_INSTANCE_PROC_ADDR (*vk, *instance, GetPhysicalDeviceSurfaceFormatsKHR); 51 | GET_INSTANCE_PROC_ADDR (*vk, *instance, 52 | GetPhysicalDeviceSurfacePresentModesKHR); 53 | GET_INSTANCE_PROC_ADDR (*vk, (*instance), 54 | GetPhysicalDeviceSurfaceCapabilitiesKHR); 55 | 56 | #ifdef VK_USE_PLATFORM_XCB_KHR 57 | GET_INSTANCE_PROC_ADDR (*vk, *instance, CreateXcbSurfaceKHR); 58 | #endif 59 | } 60 | 61 | void 62 | vk_api_load_from_device (struct vk_api* vk, VkDevice* device) 63 | { 64 | assert (device != VK_NULL_HANDLE); 65 | assert (vk->GetDeviceProcAddr != NULL); 66 | 67 | /* load device-dependent API entry points */ 68 | GET_DEVICE_PROC_ADDR (*vk, *device, CreateCommandPool); 69 | GET_DEVICE_PROC_ADDR (*vk, *device, CmdBeginRenderPass); 70 | GET_DEVICE_PROC_ADDR (*vk, *device, CmdDraw); 71 | GET_DEVICE_PROC_ADDR (*vk, *device, CmdEndRenderPass); 72 | GET_DEVICE_PROC_ADDR (*vk, *device, AllocateCommandBuffers); 73 | GET_DEVICE_PROC_ADDR (*vk, *device, FreeCommandBuffers); 74 | GET_DEVICE_PROC_ADDR (*vk, *device, GetDeviceQueue); 75 | GET_DEVICE_PROC_ADDR (*vk, *device, CreateRenderPass); 76 | GET_DEVICE_PROC_ADDR (*vk, *device, DestroyRenderPass); 77 | GET_DEVICE_PROC_ADDR (*vk, *device, DestroyCommandPool); 78 | GET_DEVICE_PROC_ADDR (*vk, *device, DestroyDevice); 79 | GET_DEVICE_PROC_ADDR (*vk, *device, CreateGraphicsPipelines); 80 | GET_DEVICE_PROC_ADDR (*vk, *device, DestroyPipeline); 81 | GET_DEVICE_PROC_ADDR (*vk, *device, CreateShaderModule); 82 | GET_DEVICE_PROC_ADDR (*vk, *device, DestroyShaderModule); 83 | GET_DEVICE_PROC_ADDR (*vk, *device, CreatePipelineLayout); 84 | GET_DEVICE_PROC_ADDR (*vk, *device, DestroyPipelineLayout); 85 | GET_DEVICE_PROC_ADDR (*vk, *device, CreateImageView); 86 | GET_DEVICE_PROC_ADDR (*vk, *device, DestroyImageView); 87 | GET_DEVICE_PROC_ADDR (*vk, *device, CreateFramebuffer); 88 | GET_DEVICE_PROC_ADDR (*vk, *device, DestroyFramebuffer); 89 | GET_DEVICE_PROC_ADDR (*vk, *device, BeginCommandBuffer); 90 | GET_DEVICE_PROC_ADDR (*vk, *device, EndCommandBuffer); 91 | GET_DEVICE_PROC_ADDR (*vk, *device, CmdBindPipeline); 92 | GET_DEVICE_PROC_ADDR (*vk, *device, CreateSemaphore); 93 | GET_DEVICE_PROC_ADDR (*vk, *device, DestroySemaphore); 94 | GET_DEVICE_PROC_ADDR (*vk, *device, QueueSubmit); 95 | GET_DEVICE_PROC_ADDR (*vk, *device, DeviceWaitIdle); 96 | 97 | GET_DEVICE_PROC_ADDR (*vk, *device, CreateSwapchainKHR); 98 | GET_DEVICE_PROC_ADDR (*vk, *device, DestroySwapchainKHR); 99 | GET_DEVICE_PROC_ADDR (*vk, *device, GetSwapchainImagesKHR); 100 | GET_DEVICE_PROC_ADDR (*vk, *device, AcquireNextImageKHR); 101 | GET_DEVICE_PROC_ADDR (*vk, *device, QueuePresentKHR); 102 | } 103 | -------------------------------------------------------------------------------- /common/vk-api.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Vulkan API loader helper 3 | * 4 | * This code is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public License 6 | * version 3, or (at your option) any later version as published by 7 | * the Free Software Foundation. 8 | * 9 | * THIS CODE IS PROVIDED AS-IS, WITHOUT WARRANTY OF ANY KIND, OR POSSIBLE 10 | * LIABILITY TO THE AUTHORS FOR ANY CLAIM OR DAMAGE. 11 | */ 12 | 13 | #pragma once 14 | 15 | #include 16 | 17 | /* This is only necessary if program is linked to a Vulkan vendor driver\ 18 | * directly 19 | */ 20 | PFN_vkVoidFunction vk_icdGetInstanceProcAddr (VkInstance instance, 21 | const char* pName); 22 | #define GET_ICD_PROC_ADDR(api, symbol) \ 23 | api.symbol = (PFN_vk ##symbol) vk_icdGetInstanceProcAddr(NULL, "vk" #symbol); 24 | 25 | 26 | #define GET_PROC_ADDR(api, symbol) \ 27 | (api).symbol = (PFN_vk ##symbol) (api).GetInstanceProcAddr(NULL, "vk" #symbol); 28 | 29 | #define GET_INSTANCE_PROC_ADDR(api, instance, symbol) \ 30 | (api).symbol = (PFN_vk ##symbol) (api).GetInstanceProcAddr(instance, "vk" #symbol); 31 | 32 | #define GET_DEVICE_PROC_ADDR(api, device, symbol) \ 33 | (api).symbol = (PFN_vk ##symbol) (api).GetDeviceProcAddr(device, "vk" #symbol); 34 | 35 | struct vk_api { 36 | PFN_vkGetInstanceProcAddr GetInstanceProcAddr; 37 | PFN_vkGetDeviceProcAddr GetDeviceProcAddr; 38 | PFN_vkEnumerateInstanceLayerProperties EnumerateInstanceLayerProperties; 39 | PFN_vkEnumerateInstanceExtensionProperties EnumerateInstanceExtensionProperties; 40 | PFN_vkCreateInstance CreateInstance; 41 | PFN_vkEnumeratePhysicalDevices EnumeratePhysicalDevices; 42 | PFN_vkGetPhysicalDeviceProperties GetPhysicalDeviceProperties; 43 | PFN_vkGetPhysicalDeviceQueueFamilyProperties GetPhysicalDeviceQueueFamilyProperties; 44 | PFN_vkCreateDevice CreateDevice; 45 | PFN_vkEnumerateDeviceExtensionProperties EnumerateDeviceExtensionProperties; 46 | PFN_vkGetDeviceQueue GetDeviceQueue; 47 | PFN_vkCreateCommandPool CreateCommandPool; 48 | PFN_vkAllocateCommandBuffers AllocateCommandBuffers; 49 | PFN_vkFreeCommandBuffers FreeCommandBuffers; 50 | PFN_vkCreateRenderPass CreateRenderPass; 51 | PFN_vkDestroyRenderPass DestroyRenderPass; 52 | PFN_vkDestroyCommandPool DestroyCommandPool; 53 | PFN_vkDestroyDevice DestroyDevice; 54 | PFN_vkDestroyInstance DestroyInstance; 55 | PFN_vkCreateGraphicsPipelines CreateGraphicsPipelines; 56 | PFN_vkDestroyPipeline DestroyPipeline; 57 | PFN_vkCreateShaderModule CreateShaderModule; 58 | PFN_vkDestroyShaderModule DestroyShaderModule; 59 | PFN_vkCreatePipelineLayout CreatePipelineLayout; 60 | PFN_vkDestroyPipelineLayout DestroyPipelineLayout; 61 | PFN_vkCreateImageView CreateImageView; 62 | PFN_vkDestroyImageView DestroyImageView; 63 | PFN_vkCreateFramebuffer CreateFramebuffer; 64 | PFN_vkDestroyFramebuffer DestroyFramebuffer; 65 | PFN_vkBeginCommandBuffer BeginCommandBuffer; 66 | PFN_vkEndCommandBuffer EndCommandBuffer; 67 | PFN_vkCmdBeginRenderPass CmdBeginRenderPass; 68 | PFN_vkCmdBindPipeline CmdBindPipeline; 69 | PFN_vkCmdDraw CmdDraw; 70 | PFN_vkCmdEndRenderPass CmdEndRenderPass; 71 | PFN_vkCreateSemaphore CreateSemaphore; 72 | PFN_vkDestroySemaphore DestroySemaphore; 73 | PFN_vkQueueSubmit QueueSubmit; 74 | PFN_vkDeviceWaitIdle DeviceWaitIdle; 75 | 76 | PFN_vkDestroySurfaceKHR DestroySurfaceKHR; 77 | PFN_vkGetPhysicalDeviceSurfaceSupportKHR GetPhysicalDeviceSurfaceSupportKHR; 78 | PFN_vkGetPhysicalDeviceSurfaceFormatsKHR GetPhysicalDeviceSurfaceFormatsKHR; 79 | PFN_vkGetPhysicalDeviceSurfacePresentModesKHR GetPhysicalDeviceSurfacePresentModesKHR; 80 | PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR GetPhysicalDeviceSurfaceCapabilitiesKHR; 81 | PFN_vkCreateSwapchainKHR CreateSwapchainKHR; 82 | PFN_vkDestroySwapchainKHR DestroySwapchainKHR; 83 | PFN_vkGetSwapchainImagesKHR GetSwapchainImagesKHR; 84 | PFN_vkAcquireNextImageKHR AcquireNextImageKHR; 85 | PFN_vkQueuePresentKHR QueuePresentKHR; 86 | 87 | #ifdef VK_USE_PLATFORM_XCB_KHR 88 | PFN_vkCreateXcbSurfaceKHR CreateXcbSurfaceKHR; 89 | #endif 90 | }; 91 | 92 | void vk_api_load_from_icd (struct vk_api* vk); 93 | 94 | void vk_api_load_from_instance (struct vk_api* vk, VkInstance* instance); 95 | 96 | void vk_api_load_from_device (struct vk_api* vk, VkDevice* device); 97 | -------------------------------------------------------------------------------- /common/wsi-xcb.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "wsi.h" 6 | 7 | static struct { 8 | xcb_connection_t* conn; 9 | xcb_screen_t* screen; 10 | xcb_window_t win; 11 | xcb_intern_atom_reply_t* atom_wm_delete_window; 12 | WsiExposeEvent expose_event; 13 | } xcb_data = { 0, }; 14 | 15 | static bool 16 | wsi_handle_event_xcb (xcb_generic_event_t* event) 17 | { 18 | while (event != NULL) { 19 | uint8_t event_code = event->response_type & 0x7f; 20 | 21 | switch (event_code) { 22 | case XCB_EXPOSE: 23 | if (xcb_data.expose_event != NULL) 24 | xcb_data.expose_event (); 25 | break; 26 | 27 | case XCB_CLIENT_MESSAGE: 28 | if ((* (xcb_client_message_event_t*) event).data.data32[0] == 29 | (* xcb_data.atom_wm_delete_window).atom) { 30 | return false; 31 | } 32 | break; 33 | 34 | case XCB_KEY_RELEASE: { 35 | const xcb_key_release_event_t* key = 36 | (const xcb_key_release_event_t*) event; 37 | switch (key->detail) { 38 | case 0x9: 39 | /* ESC key */ 40 | return false; 41 | break; 42 | 43 | case 0x29: 44 | /* F key */ 45 | wsi_toggle_fullscreen (); 46 | break; 47 | 48 | default: 49 | printf ("key pressed: %x\n", key->detail); 50 | break; 51 | } 52 | break; 53 | } 54 | 55 | default: 56 | break; 57 | } 58 | 59 | free (event); 60 | event = xcb_poll_for_event (xcb_data.conn); 61 | } 62 | 63 | return true; 64 | } 65 | 66 | void 67 | wsi_toggle_fullscreen (void) 68 | { 69 | static bool fullscreen_mode = false; 70 | 71 | fullscreen_mode = ! fullscreen_mode; 72 | 73 | xcb_intern_atom_cookie_t cookie = 74 | xcb_intern_atom (xcb_data.conn, 0, 13, "_NET_WM_STATE"); 75 | xcb_intern_atom_reply_t* reply = xcb_intern_atom_reply (xcb_data.conn, 76 | cookie, 77 | 0); 78 | xcb_atom_t atom1 = reply->atom; 79 | free (reply); 80 | 81 | cookie = xcb_intern_atom (xcb_data.conn, 0, 24, "_NET_WM_STATE_FULLSCREEN"); 82 | reply = xcb_intern_atom_reply (xcb_data.conn, cookie, 0); 83 | xcb_atom_t atom2 = reply->atom; 84 | free (reply); 85 | 86 | xcb_client_message_event_t msg = {0}; 87 | msg.response_type = XCB_CLIENT_MESSAGE; 88 | msg.window = xcb_data.win; 89 | msg.format = 32; 90 | msg.type = atom1; 91 | memset (msg.data.data32, 0, 5 * sizeof (uint32_t)); 92 | msg.data.data32[0] = fullscreen_mode ? 1 : 0; 93 | msg.data.data32[1] = atom2; 94 | 95 | xcb_send_event (xcb_data.conn, 96 | true, 97 | xcb_data.screen->root, 98 | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY, 99 | (const char *) &msg); 100 | xcb_flush (xcb_data.conn); 101 | } 102 | 103 | bool 104 | wsi_init (const char* win_title, 105 | uint32_t width, 106 | uint32_t height, 107 | WsiExposeEvent expose_event) 108 | { 109 | /* connection to the X server */ 110 | xcb_data.conn = xcb_connect (NULL, NULL); 111 | if (xcb_data.conn == NULL) { 112 | printf ("XCB: Error: Failed to connect to X server\n"); 113 | return false; 114 | } 115 | printf ("XCB: Connected to the X server\n"); 116 | 117 | /* Get the first screen */ 118 | const xcb_setup_t* xcb_setup = xcb_get_setup (xcb_data.conn); 119 | xcb_screen_iterator_t iter = xcb_setup_roots_iterator (xcb_setup); 120 | xcb_data.screen = iter.data; 121 | 122 | /* Create the window */ 123 | xcb_data.win = xcb_generate_id (xcb_data.conn); 124 | 125 | uint32_t value_mask, value_list[32]; 126 | value_mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK; 127 | value_list[0] = xcb_data.screen->black_pixel; 128 | value_list[1] = XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_EXPOSURE | 129 | XCB_EVENT_MASK_STRUCTURE_NOTIFY; 130 | 131 | xcb_create_window (xcb_data.conn, /* Connection */ 132 | XCB_COPY_FROM_PARENT, /* depth (same as root)*/ 133 | xcb_data.win, /* window Id */ 134 | xcb_data.screen->root, /* parent window */ 135 | 0, 0, /* x, y */ 136 | width, height, /* width, height */ 137 | 0, /* border_width */ 138 | XCB_WINDOW_CLASS_INPUT_OUTPUT, /* class */ 139 | xcb_data.screen->root_visual, /* visual */ 140 | value_mask, value_list); /* masks, not used yet */ 141 | 142 | xcb_intern_atom_cookie_t cookie = 143 | xcb_intern_atom (xcb_data.conn, 1, 12, "WM_PROTOCOLS"); 144 | xcb_intern_atom_reply_t* reply = xcb_intern_atom_reply (xcb_data.conn, 145 | cookie, 146 | 0); 147 | 148 | xcb_intern_atom_cookie_t cookie1 = 149 | xcb_intern_atom (xcb_data.conn, 0, 16, "WM_DELETE_WINDOW"); 150 | xcb_data.atom_wm_delete_window = xcb_intern_atom_reply (xcb_data.conn, 151 | cookie1, 152 | 0); 153 | 154 | xcb_change_property (xcb_data.conn, 155 | XCB_PROP_MODE_REPLACE, 156 | xcb_data.win, 157 | (*reply).atom, 158 | 4, 32, 1, 159 | &(*xcb_data.atom_wm_delete_window).atom); 160 | 161 | free (reply); 162 | 163 | /* Make sure commands are sent before we pause so that the window gets 164 | * shown. 165 | */ 166 | xcb_flush (xcb_data.conn); 167 | 168 | xcb_data.expose_event = expose_event; 169 | 170 | return true; 171 | } 172 | 173 | void 174 | wsi_get_connection_and_window (const void** conn, const void** win) 175 | { 176 | if (conn != NULL) 177 | *conn = (const void*) xcb_data.conn; 178 | 179 | if (win != NULL) 180 | *win = (const void*) &xcb_data.win; 181 | } 182 | 183 | bool 184 | wsi_wait_for_events (void) 185 | { 186 | xcb_generic_event_t* event; 187 | 188 | event = xcb_wait_for_event (xcb_data.conn); 189 | return wsi_handle_event_xcb (event); 190 | } 191 | 192 | void 193 | wsi_window_show (void) 194 | { 195 | xcb_map_window (xcb_data.conn, xcb_data.win); 196 | } 197 | 198 | void 199 | wsi_finish (void) 200 | { 201 | if (xcb_data.conn == NULL) 202 | return; 203 | 204 | /* Unmap the window from the screen */ 205 | xcb_unmap_window (xcb_data.conn, xcb_data.win); 206 | 207 | free (xcb_data.atom_wm_delete_window); 208 | 209 | /* disconnect from the X server */ 210 | xcb_disconnect (xcb_data.conn); 211 | } 212 | -------------------------------------------------------------------------------- /common/wsi.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | typedef void (* WsiExposeEvent) (void); 7 | 8 | bool wsi_init (const char* win_title, 9 | uint32_t width, 10 | uint32_t height, 11 | WsiExposeEvent expose_event); 12 | 13 | void wsi_get_connection_and_window (const void** conn, 14 | const void** win); 15 | 16 | void wsi_toggle_fullscreen (void); 17 | 18 | bool wsi_wait_for_events (void); 19 | 20 | void wsi_window_show (void); 21 | 22 | void wsi_finish (void); 23 | -------------------------------------------------------------------------------- /gl-image-loader/.dir-locals.el: -------------------------------------------------------------------------------- 1 | ((prog-mode 2 | (indent-tabs-mode . nil) 3 | (tab-width . 8) 4 | (c-basic-offset . 3) 5 | (c-file-style . "stroustrup") 6 | (fill-column . 78) 7 | (eval . (progn 8 | (c-set-offset 'case-label '0) 9 | (c-set-offset 'innamespace '0) 10 | (c-set-offset 'inline-open '0))) 11 | ) 12 | (makefile-mode (indent-tabs-mode . t)) 13 | ) 14 | -------------------------------------------------------------------------------- /gl-image-loader/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all clean 2 | 3 | CFLAGS = -std=c99 -g -ggdb -O0 -Wall 4 | 5 | LDFLAGS = -lm 6 | 7 | PKG_CONFIG_LIBS = \ 8 | glfw3 \ 9 | glesv2 \ 10 | libpng \ 11 | libjpeg \ 12 | $(NULL) 13 | 14 | CFLAGS += $(shell pkg-config --cflags $(PKG_CONFIG_LIBS)) 15 | LDFLAGS += $(shell pkg-config --libs $(PKG_CONFIG_LIBS)) 16 | 17 | OBJS = png.o jpeg.o image.o 18 | 19 | all: gl-image-loader 20 | 21 | png.o: png.c png.h 22 | jpeg.o: jpeg.c jpeg.h 23 | image.o: image.c image.h 24 | 25 | gl-image-loader: main.c $(OBJS) 26 | $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ 27 | 28 | clean: 29 | rm -f ./*.o 30 | rm -f gl-image-loader 31 | -------------------------------------------------------------------------------- /gl-image-loader/igalia-white-text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elima/gpu-playground/e07985e898d77a52125ba5a4931c75d9579f0f67/gl-image-loader/igalia-white-text.png -------------------------------------------------------------------------------- /gl-image-loader/image.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "image.h" 4 | 5 | bool 6 | o_image_init_from_filename (struct o_image *self, 7 | const char *filename) 8 | { 9 | assert (self != NULL); 10 | assert (filename != NULL); 11 | 12 | /* Try PNG. */ 13 | bool ok = png_decoder_init_from_filename (&self->png, filename); 14 | if (ok) { 15 | self->type = O_IMAGE_TYPE_PNG; 16 | self->width = self->png.width; 17 | self->height = self->png.height; 18 | 19 | switch (self->png.format) { 20 | case PNG_COLOR_TYPE_RGB: 21 | self->format = O_IMAGE_FORMAT_RGB; 22 | break; 23 | case PNG_COLOR_TYPE_RGB_ALPHA: 24 | self->format = O_IMAGE_FORMAT_RGBA; 25 | break; 26 | default: 27 | assert (!"PNG image format not handled\n"); 28 | } 29 | } else { 30 | png_clear (&self->png); 31 | 32 | /* Try JPEG. */ 33 | bool ok = jpeg_decoder_init_from_filename (&self->jpeg, filename); 34 | if (ok) { 35 | assert (self->jpeg.status == JPEG_STATUS_DECODE_READY); 36 | 37 | self->type = O_IMAGE_TYPE_JPEG; 38 | self->width = self->jpeg.width; 39 | self->height = self->jpeg.height; 40 | 41 | switch (self->jpeg.format) { 42 | case JPEG_FORMAT_RGB: 43 | case JPEG_FORMAT_EXT_RGB: 44 | self->format = O_IMAGE_FORMAT_RGB; 45 | break; 46 | case JPEG_FORMAT_EXT_RGBA: 47 | self->format = O_IMAGE_FORMAT_RGBA; 48 | break; 49 | default: 50 | printf ("JPEG format: %d\n", self->jpeg.format); 51 | assert (!"JPEG image format not handled\n"); 52 | } 53 | } else { 54 | printf ("Unknown or unhandled image format.\n"); 55 | return false; 56 | } 57 | } 58 | 59 | return true; 60 | } 61 | 62 | void 63 | o_image_clear (struct o_image *self) 64 | { 65 | assert (self != NULL); 66 | 67 | if (self->type == O_IMAGE_TYPE_PNG) 68 | png_clear (&self->png); 69 | else if (self->type == O_IMAGE_TYPE_JPEG) 70 | jpeg_clear (&self->jpeg); 71 | } 72 | 73 | ssize_t 74 | o_image_read (struct o_image *self, 75 | void *buffer, 76 | size_t size, 77 | size_t *first_row, 78 | size_t *num_rows) 79 | { 80 | assert (self != NULL); 81 | 82 | switch (self->type) { 83 | case O_IMAGE_TYPE_PNG: 84 | return png_read (&self->png, 85 | buffer, 86 | size, 87 | first_row, 88 | num_rows); 89 | 90 | case O_IMAGE_TYPE_JPEG: 91 | return jpeg_read (&self->jpeg, 92 | buffer, 93 | size, 94 | first_row, 95 | num_rows); 96 | 97 | default: 98 | errno = ENXIO; 99 | return -1; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /gl-image-loader/image.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "jpeg.h" 4 | #include "png.h" 5 | #include 6 | #include 7 | 8 | enum o_image_format { 9 | O_IMAGE_FORMAT_INVALID, 10 | O_IMAGE_FORMAT_RGB, 11 | O_IMAGE_FORMAT_RGBA, 12 | }; 13 | 14 | enum o_image_type { 15 | O_IMAGE_TYPE_INVALID, 16 | O_IMAGE_TYPE_PNG, 17 | O_IMAGE_TYPE_JPEG, 18 | }; 19 | 20 | struct o_image { 21 | uint32_t width; 22 | uint32_t height; 23 | 24 | uint8_t type; 25 | uint32_t format; 26 | 27 | struct png_ctx png; 28 | struct jpeg_ctx jpeg; 29 | }; 30 | 31 | bool 32 | o_image_init_from_filename (struct o_image *self, 33 | const char *filename); 34 | 35 | void 36 | o_image_clear (struct o_image *self); 37 | 38 | ssize_t 39 | o_image_read (struct o_image *self, 40 | void *buffer, 41 | size_t size, 42 | size_t *first_row, 43 | size_t *num_rows); 44 | -------------------------------------------------------------------------------- /gl-image-loader/jpeg.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "jpeg.h" 4 | #include 5 | 6 | static void 7 | handle_error_exit (j_common_ptr cinfo) 8 | { 9 | struct jpeg_error_handler *err_handler = 10 | (struct jpeg_error_handler *) cinfo->err; 11 | 12 | /* Display the message. */ 13 | (*cinfo->err->output_message) (cinfo); 14 | 15 | err_handler->ctx->status = JPEG_STATUS_ERROR; 16 | 17 | longjmp (err_handler->setjmp_buffer, 1); 18 | } 19 | 20 | /* public API */ 21 | 22 | bool 23 | jpeg_decoder_init_from_filename (struct jpeg_ctx *self, 24 | const char *filename) 25 | { 26 | assert (self != NULL); 27 | assert (filename != NULL); 28 | 29 | memset (self, 0x00, sizeof (struct jpeg_ctx)); 30 | 31 | self->file_obj = fopen (filename, "rb"); 32 | if (self->file_obj == NULL) 33 | return false; 34 | 35 | /* Set an error manager. */ 36 | self->cinfo.err = 37 | jpeg_std_error (&self->err_handler.jpeg_error_mgr); 38 | self->err_handler.jpeg_error_mgr.error_exit = 39 | handle_error_exit; 40 | 41 | self->err_handler.ctx = self; 42 | 43 | if (setjmp (self->err_handler.setjmp_buffer) != 0) { 44 | /* @FIXME: check the error code and fill errno accordingly */ 45 | jpeg_clear (self); 46 | return false; 47 | } 48 | 49 | /* Create and set up the decompression object. */ 50 | jpeg_create_decompress (&self->cinfo); 51 | jpeg_stdio_src (&self->cinfo, self->file_obj); 52 | 53 | /* Read JPEG header. */ 54 | int result = jpeg_read_header (&self->cinfo, true); 55 | if (result != JPEG_HEADER_OK) { 56 | jpeg_clear (self); 57 | return false; 58 | } 59 | 60 | jpeg_start_decompress (&self->cinfo); 61 | 62 | self->row_stride = self->cinfo.output_width * self->cinfo.output_components; 63 | self->width = self->cinfo.output_width; 64 | self->height = self->cinfo.output_height; 65 | self->format = self->cinfo.out_color_space; 66 | 67 | self->status = JPEG_STATUS_DECODE_READY; 68 | 69 | return true; 70 | } 71 | 72 | void 73 | jpeg_clear (struct jpeg_ctx *self) 74 | { 75 | if (self->file_obj != NULL) { 76 | fclose (self->file_obj); 77 | self->file_obj = NULL; 78 | } 79 | 80 | if (self->status != JPEG_STATUS_NONE) 81 | jpeg_destroy_decompress (&self->cinfo); 82 | 83 | self->status = JPEG_STATUS_NONE; 84 | } 85 | 86 | ssize_t 87 | jpeg_read (struct jpeg_ctx *self, 88 | void *buffer, 89 | size_t size, 90 | size_t *first_row, 91 | size_t *num_rows) 92 | { 93 | assert (self != NULL); 94 | assert (self->status == JPEG_STATUS_DECODE_READY || 95 | self->status == JPEG_STATUS_DONE); 96 | assert (size == 0 || buffer != NULL); 97 | assert (self->row_stride > 0 && size >= self->row_stride); 98 | 99 | size_t _num_rows = 0; 100 | size_t _first_row = 0; 101 | size_t result = 0; 102 | 103 | if (self->status == JPEG_STATUS_DONE) 104 | goto out; 105 | 106 | _first_row = self->cinfo.output_scanline; 107 | 108 | uint32_t lines = size / self->row_stride; 109 | for (int32_t i = 0; i < lines; i++) { 110 | uint8_t *rowptr[1]; 111 | rowptr[0] = buffer + self->row_stride * i; 112 | 113 | jpeg_read_scanlines (&self->cinfo, rowptr, 1); 114 | if (self->status == JPEG_STATUS_ERROR) { 115 | /* @FIXME: handle exit errors here */ 116 | return -1; 117 | } 118 | 119 | _num_rows++; 120 | } 121 | 122 | _num_rows = self->cinfo.output_scanline - _first_row; 123 | 124 | if (self->cinfo.output_scanline == self->cinfo.output_height) { 125 | jpeg_finish_decompress (&self->cinfo); 126 | self->status = JPEG_STATUS_DONE; 127 | } 128 | 129 | result = _num_rows * self->row_stride; 130 | 131 | out: 132 | if (first_row != NULL) 133 | *first_row = _first_row; 134 | 135 | if (num_rows != NULL) 136 | *num_rows = _num_rows; 137 | 138 | return result; 139 | } 140 | -------------------------------------------------------------------------------- /gl-image-loader/jpeg.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | enum jpeg_status { 11 | JPEG_STATUS_NONE = 0, 12 | JPEG_STATUS_DECODE_READY, 13 | JPEG_STATUS_ENCODE_READY, 14 | JPEG_STATUS_ERROR, 15 | JPEG_STATUS_DONE, 16 | }; 17 | 18 | enum jpeg_format { 19 | JPEG_FORMAT_UNKNOWN, /* error/unspecified */ 20 | JPEG_FORMAT_GRAYSCALE, /* monochrome */ 21 | JPEG_FORMAT_RGB, /* red/green/blue as specified by the RGB_RED, 22 | RGB_GREEN, RGB_BLUE, and RGB_PIXELSIZE macros */ 23 | JPEG_FORMAT_YCbCr, /* Y/Cb/Cr (also known as YUV) */ 24 | JPEG_FORMAT_CMYK, /* C/M/Y/K */ 25 | JPEG_FORMAT_YCCK, /* Y/Cb/Cr/K */ 26 | JPEG_FORMAT_EXT_RGB, /* red/green/blue */ 27 | JPEG_FORMAT_EXT_RGBX, /* red/green/blue/x */ 28 | JPEG_FORMAT_EXT_BGR, /* blue/green/red */ 29 | JPEG_FORMAT_EXT_BGRX, /* blue/green/red/x */ 30 | JPEG_FORMAT_EXT_XBGR, /* x/blue/green/red */ 31 | JPEG_FORMAT_EXT_XRGB, /* x/red/green/blue */ 32 | /* When out_color_space is set to JCS_EXT_RGBX, JCS_EXT_BGRX, JCS_EXT_XBGR, 33 | or JCS_EXT_XRGB during decompression, the X byte is undefined, and in 34 | order to ensure the best performance, libjpeg-turbo can set that byte to 35 | whatever value it wishes. Use the following colorspace constants to 36 | ensure that the X byte is set to 0xFF, so that it can be interpreted as an 37 | opaque alpha channel. */ 38 | JPEG_FORMAT_EXT_RGBA, /* red/green/blue/alpha */ 39 | JPEG_FORMAT_EXT_BGRA, /* blue/green/red/alpha */ 40 | JPEG_FORMAT_EXT_ABGR, /* alpha/blue/green/red */ 41 | JPEG_FORMAT_EXT_ARGB, /* alpha/red/green/blue */ 42 | JPEG_FORMAT_RGB565 /* 5-bit red/6-bit green/5-bit blue */ 43 | }; 44 | 45 | struct jpeg_ctx; 46 | 47 | struct jpeg_error_handler { 48 | struct jpeg_error_mgr jpeg_error_mgr; 49 | 50 | struct jpeg_ctx *ctx; 51 | jmp_buf setjmp_buffer; 52 | }; 53 | 54 | struct jpeg_ctx { 55 | FILE *file_obj; 56 | struct jpeg_decompress_struct cinfo; 57 | 58 | struct jpeg_error_mgr err_manager; 59 | 60 | enum jpeg_status status; 61 | 62 | uint32_t width; 63 | uint32_t height; 64 | uint32_t row_stride; 65 | enum jpeg_format format; 66 | 67 | struct jpeg_error_handler err_handler; 68 | }; 69 | 70 | bool 71 | jpeg_decoder_init_from_filename (struct jpeg_ctx *self, 72 | const char *filename); 73 | 74 | void 75 | jpeg_clear (struct jpeg_ctx *self); 76 | 77 | ssize_t 78 | jpeg_read (struct jpeg_ctx *self, 79 | void *buffer, 80 | size_t size, 81 | size_t *first_row, 82 | size_t *num_rows); 83 | -------------------------------------------------------------------------------- /gl-image-loader/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "image.h" 10 | 11 | #define IMAGE_FILENAME_DEFAULT "./igalia-white-text.png" 12 | 13 | static bool 14 | gl_utils_print_shader_log (GLuint shader) 15 | { 16 | GLint length; 17 | char buffer[4096] = {0}; 18 | GLint success; 19 | 20 | glGetShaderiv (shader, GL_INFO_LOG_LENGTH, &length); 21 | if (length == 0) 22 | return true; 23 | 24 | glGetShaderInfoLog (shader, 4096, NULL, buffer); 25 | if (strlen (buffer) > 0) 26 | printf ("Shader compilation log: %s\n", buffer); 27 | 28 | glGetShaderiv (shader, GL_COMPILE_STATUS, &success); 29 | 30 | return success == GL_TRUE; 31 | } 32 | 33 | static GLuint 34 | gl_utils_load_shader (const char *shader_source, GLenum type) 35 | { 36 | GLuint shader = glCreateShader (type); 37 | 38 | glShaderSource (shader, 1, &shader_source, NULL); 39 | assert (glGetError () == GL_NO_ERROR); 40 | glCompileShader (shader); 41 | assert (glGetError () == GL_NO_ERROR); 42 | 43 | gl_utils_print_shader_log (shader); 44 | 45 | return shader; 46 | } 47 | 48 | static GLuint 49 | create_shader_program (void) 50 | { 51 | const char *VERTEX_SOURCE = 52 | "attribute vec2 pos;\n" 53 | "attribute vec2 texture;\n" 54 | "varying vec2 v_texture;\n" 55 | "void main() {\n" 56 | " v_texture = texture;\n" 57 | " gl_Position = vec4(pos, 0, 1);\n" 58 | "}\n"; 59 | 60 | const char *FRAGMENT_SOURCE = 61 | "precision mediump float;\n" 62 | "uniform sampler2D u_tex;\n" 63 | "varying vec2 v_texture;\n" 64 | "void main() {\n" 65 | " gl_FragColor = texture2D(u_tex, v_texture);\n" 66 | "}\n"; 67 | 68 | GLuint vertex_shader = gl_utils_load_shader (VERTEX_SOURCE, 69 | GL_VERTEX_SHADER); 70 | assert (vertex_shader >= 0); 71 | assert (glGetError () == GL_NO_ERROR); 72 | 73 | GLuint fragment_shader = gl_utils_load_shader (FRAGMENT_SOURCE, 74 | GL_FRAGMENT_SHADER); 75 | assert (fragment_shader >= 0); 76 | assert (glGetError () == GL_NO_ERROR); 77 | 78 | GLuint program = glCreateProgram (); 79 | assert (glGetError () == GL_NO_ERROR); 80 | glAttachShader (program, vertex_shader); 81 | assert (glGetError () == GL_NO_ERROR); 82 | glAttachShader (program, fragment_shader); 83 | assert (glGetError () == GL_NO_ERROR); 84 | 85 | glBindAttribLocation (program, 0, "pos"); 86 | glBindAttribLocation (program, 1, "texture"); 87 | 88 | glLinkProgram (program); 89 | assert (glGetError () == GL_NO_ERROR); 90 | 91 | glDeleteShader (vertex_shader); 92 | glDeleteShader (fragment_shader); 93 | 94 | return program; 95 | } 96 | 97 | int32_t 98 | main (int32_t argc, char *argv[]) 99 | { 100 | printf ("Usage: %s \n", argv[0]); 101 | 102 | /* Load an decode an image. */ 103 | static struct o_image image; 104 | 105 | const char *image_url; 106 | if (argc > 1) 107 | image_url = argv[1]; 108 | else 109 | image_url = IMAGE_FILENAME_DEFAULT; 110 | 111 | /* This loads the image header (metadata), but doesn't load any pixel 112 | * data or do any decoding. 113 | */ 114 | if (! o_image_init_from_filename (&image, image_url)) 115 | return -1; 116 | 117 | GLFWwindow* window; 118 | 119 | /* Initialize GLFW. */ 120 | if (! glfwInit ()) 121 | return -1; 122 | 123 | /* Select an OpenGL-ES 2.0 profile. */ 124 | glfwWindowHint (GLFW_CLIENT_API, GLFW_OPENGL_ES_API); 125 | glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 2); 126 | glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 0); 127 | 128 | /* Create a windowed mode window and its OpenGL context */ 129 | window = glfwCreateWindow (image.width, 130 | image.height, 131 | "GL Image Loader", 132 | NULL, 133 | NULL); 134 | if (window == NULL) { 135 | glfwTerminate (); 136 | return -1; 137 | } 138 | 139 | /* Make the window's context current */ 140 | glfwMakeContextCurrent (window); 141 | 142 | /* Dump some GL capabilities. */ 143 | const GLubyte *gles_version = glGetString (GL_VERSION); 144 | printf ("%s\n", (char *) gles_version); 145 | 146 | /* Create a texture for the image. */ 147 | GLuint tex; 148 | glGenTextures (1, &tex); 149 | assert (glGetError () == GL_NO_ERROR); 150 | assert (tex > 0); 151 | glBindTexture (GL_TEXTURE_2D, tex); 152 | assert (glGetError () == GL_NO_ERROR); 153 | glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 154 | glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 155 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 156 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 157 | 158 | /* Load the image into the texture, progressively in chunks of max 159 | * BLOCK_SIZE bytes. */ 160 | #define BLOCK_SIZE (8192 * 1) 161 | uint8_t buf[BLOCK_SIZE] = {0, }; 162 | size_t first_row; 163 | size_t num_rows; 164 | 165 | GLuint format = image.format == O_IMAGE_FORMAT_RGB ? 166 | GL_RGB : GL_RGBA; 167 | 168 | /* Allocate the texture size. */ 169 | glTexImage2D (GL_TEXTURE_2D, 170 | 0, 171 | format, 172 | image.width, image.height, 173 | 0, 174 | format, 175 | GL_UNSIGNED_BYTE, 176 | NULL); 177 | assert (glGetError () == GL_NO_ERROR); 178 | 179 | ssize_t size_read; 180 | do { 181 | size_read = o_image_read (&image, 182 | buf, 183 | BLOCK_SIZE, 184 | &first_row, 185 | &num_rows); 186 | assert (size_read >= 0); 187 | if (size_read > 0) { 188 | glTexSubImage2D (GL_TEXTURE_2D, 189 | 0, 190 | 0, first_row, 191 | image.width, num_rows, 192 | format, 193 | GL_UNSIGNED_BYTE, 194 | buf); 195 | assert (glGetError () == GL_NO_ERROR); 196 | } 197 | } while (size_read > 0); 198 | 199 | glBindTexture (GL_TEXTURE_2D, 0); 200 | #undef BLOCK_SIZE 201 | 202 | /* Create shader program to sample the texture. */ 203 | GLuint program = create_shader_program (); 204 | glUseProgram (program); 205 | assert (glGetError () == GL_NO_ERROR); 206 | 207 | /* Loop until the user closes the window */ 208 | while (! glfwWindowShouldClose (window)) { 209 | /* Render here */ 210 | glClearColor (0.25, 0.25, 0.25, 0.5); 211 | glClear (GL_COLOR_BUFFER_BIT); 212 | 213 | /* Bind the texture. */ 214 | glActiveTexture (GL_TEXTURE0); 215 | glBindTexture (GL_TEXTURE_2D, tex); 216 | assert (glGetError () == GL_NO_ERROR); 217 | 218 | /* Enable blending for transparent PNGs. */ 219 | glEnable (GL_BLEND); 220 | glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 221 | 222 | /* Draw a quad. */ 223 | static const GLfloat s_vertices[4][2] = { 224 | { -1.0, 1.0 }, 225 | { 1.0, 1.0 }, 226 | { -1.0, -1.0 }, 227 | { 1.0, -1.0 }, 228 | }; 229 | 230 | static const GLfloat s_texturePos[4][2] = { 231 | { 0, 0 }, 232 | { 1, 0 }, 233 | { 0, 1 }, 234 | { 1, 1 }, 235 | }; 236 | 237 | glVertexAttribPointer (0, 2, GL_FLOAT, GL_FALSE, 0, s_vertices); 238 | glVertexAttribPointer (1, 2, GL_FLOAT, GL_FALSE, 0, s_texturePos); 239 | 240 | glEnableVertexAttribArray (0); 241 | glEnableVertexAttribArray (1); 242 | 243 | glDrawArrays (GL_TRIANGLE_STRIP, 0, 4); 244 | 245 | glDisableVertexAttribArray (0); 246 | glDisableVertexAttribArray (1); 247 | 248 | /* Swap front and back buffers */ 249 | glfwSwapBuffers (window); 250 | 251 | /* Poll and process events */ 252 | glfwPollEvents (); 253 | } 254 | 255 | glfwTerminate (); 256 | 257 | return 0; 258 | } 259 | -------------------------------------------------------------------------------- /gl-image-loader/png.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "png.h" 4 | #include 5 | #include 6 | 7 | bool 8 | png_decoder_init_from_filename (struct png_ctx *self, 9 | const char *filename) 10 | { 11 | assert (self != NULL); 12 | assert (filename != NULL); 13 | 14 | memset (self, 0x00, sizeof (struct png_ctx)); 15 | 16 | self->file_obj = fopen (filename, "rb"); 17 | if (self->file_obj == NULL) 18 | return false; 19 | 20 | /* Check PNG signature. */ 21 | uint8_t header[8]; 22 | ssize_t read_size = fread (header, 1, 8, self->file_obj); 23 | assert (read_size > 0); 24 | 25 | if (png_sig_cmp ((png_const_bytep) header, 0, 8) != 0) { 26 | png_clear (self); 27 | errno = EINVAL; 28 | return false; 29 | } 30 | 31 | /* Create the PNG decoder object. */ 32 | self->png_ptr = 33 | png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); 34 | if (self->png_ptr == NULL) { 35 | png_clear (self); 36 | errno = ENOMEM; 37 | return false; 38 | } 39 | 40 | /* Create the PNG info object. */ 41 | self->info_ptr = png_create_info_struct (self->png_ptr); 42 | if (self->info_ptr == NULL) { 43 | png_clear (self); 44 | errno = ENOMEM; 45 | return false; 46 | } 47 | 48 | /* Initialize error handling. */ 49 | if (setjmp (png_jmpbuf (self->png_ptr)) != 0) { 50 | png_clear (self); 51 | errno = ENOMEM; 52 | return false; 53 | } 54 | 55 | png_init_io (self->png_ptr, self->file_obj); 56 | png_set_sig_bytes (self->png_ptr, 8); 57 | 58 | /* @FIXME: does this generates errors? */ 59 | png_read_info (self->png_ptr, self->info_ptr); 60 | 61 | self->width = png_get_image_width (self->png_ptr, self->info_ptr); 62 | self->height = png_get_image_height (self->png_ptr, self->info_ptr); 63 | assert (self->width > 0 && self->height > 0); 64 | 65 | self->format = png_get_color_type (self->png_ptr, self->info_ptr); 66 | self->row_stride = png_get_rowbytes (self->png_ptr, self->info_ptr); 67 | 68 | self->status = PNG_STATUS_DECODE_READY; 69 | 70 | return true; 71 | } 72 | 73 | void 74 | png_clear (struct png_ctx *self) 75 | { 76 | assert (self != NULL); 77 | 78 | if (self->png_ptr != NULL) { 79 | if (self->info_ptr != NULL) 80 | png_destroy_info_struct (self->png_ptr, &self->info_ptr); 81 | 82 | png_destroy_read_struct (&self->png_ptr, &self->info_ptr, NULL); 83 | } 84 | 85 | if (self->file_obj != NULL) { 86 | fclose (self->file_obj); 87 | self->file_obj = NULL; 88 | } 89 | 90 | self->status = PNG_STATUS_NONE; 91 | } 92 | 93 | ssize_t 94 | png_read (struct png_ctx *self, 95 | void *buffer, 96 | size_t size, 97 | size_t *first_row, 98 | size_t *num_rows) 99 | { 100 | assert (self != NULL); 101 | assert (self->status == PNG_STATUS_DECODE_READY || 102 | self->status == PNG_STATUS_DONE); 103 | assert (size == 0 || buffer != NULL); 104 | assert (self->row_stride > 0 && size >= self->row_stride); 105 | 106 | size_t _num_rows = 0; 107 | size_t _first_row = 0; 108 | size_t result = 0; 109 | 110 | if (self->status == PNG_STATUS_DONE) 111 | goto out; 112 | 113 | _first_row = self->last_decoded_row; 114 | uint32_t max_read_rows = size / self->row_stride; 115 | 116 | #define MIN(a,b) (a > b ? b : a) 117 | _num_rows = MIN (self->height - self->last_decoded_row, 118 | max_read_rows); 119 | #undef MIN 120 | 121 | png_bytepp rows = calloc (_num_rows, sizeof (png_bytep)); 122 | for (int32_t i = 0; i < _num_rows; i++) 123 | rows[i] = buffer + (i * self->row_stride); 124 | 125 | png_read_rows (self->png_ptr, 126 | rows, 127 | NULL, 128 | _num_rows); 129 | free (rows); 130 | 131 | self->last_decoded_row += _num_rows; 132 | result = _num_rows * self->row_stride; 133 | 134 | if (self->last_decoded_row == self->height) { 135 | png_read_end (self->png_ptr, self->info_ptr); 136 | 137 | self->status = PNG_STATUS_DONE; 138 | } 139 | 140 | out: 141 | if (first_row != NULL) 142 | *first_row = _first_row; 143 | 144 | if (num_rows != NULL) 145 | *num_rows = _num_rows; 146 | 147 | return result; 148 | } 149 | -------------------------------------------------------------------------------- /gl-image-loader/png.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define PNG_DEBUG 3 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | enum png_status { 10 | PNG_STATUS_NONE = 0, 11 | PNG_STATUS_DECODE_READY, 12 | PNG_STATUS_ENCODE_READY, 13 | PNG_STATUS_ERROR, 14 | PNG_STATUS_DONE, 15 | }; 16 | 17 | struct png_ctx { 18 | FILE *file_obj; 19 | 20 | png_structp png_ptr; 21 | png_infop info_ptr; 22 | 23 | enum png_status status; 24 | 25 | uint32_t width; 26 | uint32_t height; 27 | size_t row_stride; 28 | uint8_t format; 29 | 30 | uint32_t last_decoded_row; 31 | }; 32 | 33 | bool 34 | png_decoder_init_from_filename (struct png_ctx *self, 35 | const char *filename); 36 | 37 | void 38 | png_clear (struct png_ctx *self); 39 | 40 | ssize_t 41 | png_read (struct png_ctx *self, 42 | void *buffer, 43 | size_t size, 44 | size_t *first_row, 45 | size_t *num_rows); 46 | -------------------------------------------------------------------------------- /glslangValidator: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elima/gpu-playground/e07985e898d77a52125ba5a4931c75d9579f0f67/glslangValidator -------------------------------------------------------------------------------- /render-nodes-minimal/Makefile: -------------------------------------------------------------------------------- 1 | TARGET=render-nodes-minimal 2 | 3 | all: Makefile $(TARGET) 4 | 5 | $(TARGET): main.c 6 | gcc -ggdb -O0 -Wall -std=c99 \ 7 | `pkg-config --libs --cflags glesv2 egl gbm` \ 8 | -o $(TARGET) \ 9 | main.c 10 | 11 | clean: 12 | rm -f $(TARGET) 13 | -------------------------------------------------------------------------------- /render-nodes-minimal/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Example: 3 | * 4 | * Render nodes (minimal): Running a compute shader in a window-less 5 | * EGL + GLES 3.1 context. 6 | * 7 | * This example shows the minimum code necessary to run an OpenGL (ES) compute 8 | * shader (aka, a general purpose program on the GPU), on Linux. 9 | * It uses the DRM render nodes API to gain unprivileged, shared access to the 10 | * GPU. 11 | * 12 | * See and 13 | * . 14 | * 15 | * Tested on Linux 4.0, Mesa 12.0, Intel GPU (gen7+). 16 | * 17 | * Authors: 18 | * * Eduardo Lima Mitev 19 | * 20 | * This code is free software; you can redistribute it and/or 21 | * modify it under the terms of the GNU Lesser General Public License 22 | * version 3, or (at your option) any later version as published by 23 | * the Free Software Foundation. 24 | * 25 | * THIS CODE IS PROVIDED AS-IS, WITHOUT WARRANTY OF ANY KIND, OR POSSIBLE 26 | * LIABILITY TO THE AUTHORS FOR ANY CLAIM OR DAMAGE. 27 | */ 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | /* a dummy compute shader that does nothing */ 41 | #define COMPUTE_SHADER_SRC " \ 42 | #version 310 es\n \ 43 | \ 44 | layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in; \ 45 | \ 46 | void main(void) { \ 47 | /* awesome compute code here */ \ 48 | } \ 49 | " 50 | 51 | int32_t 52 | main (int32_t argc, char* argv[]) 53 | { 54 | bool res; 55 | 56 | int32_t fd = open ("/dev/dri/renderD128", O_RDWR); 57 | assert (fd > 0); 58 | 59 | struct gbm_device *gbm = gbm_create_device (fd); 60 | assert (gbm != NULL); 61 | 62 | /* setup EGL from the GBM device */ 63 | EGLDisplay egl_dpy = eglGetPlatformDisplay (EGL_PLATFORM_GBM_MESA, gbm, NULL); 64 | assert (egl_dpy != NULL); 65 | 66 | res = eglInitialize (egl_dpy, NULL, NULL); 67 | assert (res); 68 | 69 | const char *egl_extension_st = eglQueryString (egl_dpy, EGL_EXTENSIONS); 70 | assert (strstr (egl_extension_st, "EGL_KHR_create_context") != NULL); 71 | assert (strstr (egl_extension_st, "EGL_KHR_surfaceless_context") != NULL); 72 | 73 | static const EGLint config_attribs[] = { 74 | EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT_KHR, 75 | EGL_NONE 76 | }; 77 | EGLConfig cfg; 78 | EGLint count; 79 | 80 | res = eglChooseConfig (egl_dpy, config_attribs, &cfg, 1, &count); 81 | assert (res); 82 | 83 | res = eglBindAPI (EGL_OPENGL_ES_API); 84 | assert (res); 85 | 86 | static const EGLint attribs[] = { 87 | EGL_CONTEXT_CLIENT_VERSION, 3, 88 | EGL_NONE 89 | }; 90 | EGLContext core_ctx = eglCreateContext (egl_dpy, 91 | cfg, 92 | EGL_NO_CONTEXT, 93 | attribs); 94 | assert (core_ctx != EGL_NO_CONTEXT); 95 | 96 | res = eglMakeCurrent (egl_dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, core_ctx); 97 | assert (res); 98 | 99 | /* print some compute limits (not strictly necessary) */ 100 | GLint work_group_count[3] = {0}; 101 | for (unsigned i = 0; i < 3; i++) 102 | glGetIntegeri_v (GL_MAX_COMPUTE_WORK_GROUP_COUNT, 103 | i, 104 | &work_group_count[i]); 105 | printf ("GL_MAX_COMPUTE_WORK_GROUP_COUNT: %d, %d, %d\n", 106 | work_group_count[0], 107 | work_group_count[1], 108 | work_group_count[2]); 109 | 110 | GLint work_group_size[3] = {0}; 111 | for (unsigned i = 0; i < 3; i++) 112 | glGetIntegeri_v (GL_MAX_COMPUTE_WORK_GROUP_SIZE, i, &work_group_size[i]); 113 | printf ("GL_MAX_COMPUTE_WORK_GROUP_SIZE: %d, %d, %d\n", 114 | work_group_size[0], 115 | work_group_size[1], 116 | work_group_size[2]); 117 | 118 | GLint max_invocations; 119 | glGetIntegerv (GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS, &max_invocations); 120 | printf ("GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS: %d\n", max_invocations); 121 | 122 | GLint mem_size; 123 | glGetIntegerv (GL_MAX_COMPUTE_SHARED_MEMORY_SIZE, &mem_size); 124 | printf ("GL_MAX_COMPUTE_SHARED_MEMORY_SIZE: %d\n", mem_size); 125 | 126 | /* setup a compute shader */ 127 | GLuint compute_shader = glCreateShader (GL_COMPUTE_SHADER); 128 | 129 | assert (glGetError () == GL_NO_ERROR); 130 | const char *shader_source = COMPUTE_SHADER_SRC; 131 | 132 | glShaderSource (compute_shader, 1, &shader_source, NULL); 133 | assert (glGetError () == GL_NO_ERROR); 134 | 135 | glCompileShader (compute_shader); 136 | assert (glGetError () == GL_NO_ERROR); 137 | 138 | GLuint shader_program = glCreateProgram (); 139 | 140 | glAttachShader (shader_program, compute_shader); 141 | assert (glGetError () == GL_NO_ERROR); 142 | 143 | glLinkProgram (shader_program); 144 | assert (glGetError () == GL_NO_ERROR); 145 | 146 | glDeleteShader (compute_shader); 147 | 148 | glUseProgram (shader_program); 149 | assert (glGetError () == GL_NO_ERROR); 150 | 151 | /* dispatch computation */ 152 | glDispatchCompute (1, 1, 1); 153 | assert (glGetError () == GL_NO_ERROR); 154 | 155 | printf ("Compute shader dispatched and finished successfully\n"); 156 | 157 | /* free stuff */ 158 | glDeleteProgram (shader_program); 159 | eglDestroyContext (egl_dpy, core_ctx); 160 | eglTerminate (egl_dpy); 161 | gbm_device_destroy (gbm); 162 | close (fd); 163 | 164 | return 0; 165 | } 166 | -------------------------------------------------------------------------------- /vulkan-minimal/Makefile: -------------------------------------------------------------------------------- 1 | TARGET=vulkan-minimal 2 | 3 | GLSL_VALIDATOR=../glslangValidator 4 | 5 | all: $(TARGET) vert.spv frag.spv 6 | 7 | vert.spv: shader.vert 8 | $(GLSL_VALIDATOR) -V shader.vert 9 | 10 | frag.spv: shader.frag 11 | $(GLSL_VALIDATOR) -V shader.frag 12 | 13 | $(TARGET): Makefile main.c vert.spv frag.spv 14 | gcc -ggdb -O0 -Wall -std=c99 \ 15 | -DCURRENT_DIR=\"`pwd`\" \ 16 | `pkg-config --libs --cflags xcb` \ 17 | -lvulkan \ 18 | -o $(TARGET) \ 19 | main.c 20 | 21 | clean: 22 | rm -f $(TARGET) vert.spv frag.spv 23 | -------------------------------------------------------------------------------- /vulkan-minimal/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Example: 3 | * 4 | * Vulkan minimal: An absolute minimal Vulkan demo. 5 | * 6 | * This example renders a triangle using the Vulkan API on an X11 window. 7 | * It does the minimum required to put pixels on the screen, and not much 8 | * more (e.g, doesn't support resizing the window). 9 | * 10 | * Tested on Linux 4.7, Mesa 12.0, Intel GPU (gen7+). 11 | * 12 | * Authors: 13 | * * Eduardo Lima Mitev 14 | * 15 | * This code is free software; you can redistribute it and/or 16 | * modify it under the terms of the GNU Lesser General Public License 17 | * version 3, or (at your option) any later version as published by 18 | * the Free Software Foundation. 19 | * 20 | * THIS CODE IS PROVIDED AS-IS, WITHOUT WARRANTY OF ANY KIND, OR POSSIBLE 21 | * LIABILITY TO THE AUTHORS FOR ANY CLAIM OR DAMAGE. 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #define VK_USE_PLATFORM_XCB_KHR 34 | #include 35 | 36 | #define WIDTH 640 37 | #define HEIGHT 480 38 | 39 | /* This is only necessary if program is linked to a Vulkan vendor driver\ 40 | * directly 41 | */ 42 | PFN_vkVoidFunction vk_icdGetInstanceProcAddr (VkInstance instance, 43 | const char* pName); 44 | #define GET_ICD_PROC_ADDR(api, symbol) \ 45 | api.symbol = (PFN_vk ##symbol) vk_icdGetInstanceProcAddr(NULL, "vk" #symbol); 46 | 47 | 48 | #define GET_PROC_ADDR(api, symbol) \ 49 | api.symbol = (PFN_vk ##symbol) api.GetInstanceProcAddr(NULL, "vk" #symbol); 50 | 51 | #define GET_INSTANCE_PROC_ADDR(api, instance, symbol) \ 52 | api.symbol = (PFN_vk ##symbol) api.GetInstanceProcAddr(instance, "vk" #symbol); 53 | 54 | #define GET_DEVICE_PROC_ADDR(api, device, symbol) \ 55 | api.symbol = (PFN_vk ##symbol) api.GetDeviceProcAddr(device, "vk" #symbol); 56 | 57 | struct vk_api { 58 | PFN_vkGetInstanceProcAddr GetInstanceProcAddr; 59 | PFN_vkGetDeviceProcAddr GetDeviceProcAddr; 60 | PFN_vkEnumerateInstanceLayerProperties EnumerateInstanceLayerProperties; 61 | PFN_vkEnumerateInstanceExtensionProperties EnumerateInstanceExtensionProperties; 62 | PFN_vkCreateInstance CreateInstance; 63 | PFN_vkEnumeratePhysicalDevices EnumeratePhysicalDevices; 64 | PFN_vkGetPhysicalDeviceProperties GetPhysicalDeviceProperties; 65 | PFN_vkGetPhysicalDeviceQueueFamilyProperties GetPhysicalDeviceQueueFamilyProperties; 66 | PFN_vkCreateDevice CreateDevice; 67 | PFN_vkEnumerateDeviceExtensionProperties EnumerateDeviceExtensionProperties; 68 | PFN_vkGetDeviceQueue GetDeviceQueue; 69 | PFN_vkCreateCommandPool CreateCommandPool; 70 | PFN_vkAllocateCommandBuffers AllocateCommandBuffers; 71 | PFN_vkFreeCommandBuffers FreeCommandBuffers; 72 | PFN_vkCreateRenderPass CreateRenderPass; 73 | PFN_vkDestroyRenderPass DestroyRenderPass; 74 | PFN_vkDestroyCommandPool DestroyCommandPool; 75 | PFN_vkDestroyDevice DestroyDevice; 76 | PFN_vkDestroyInstance DestroyInstance; 77 | PFN_vkCreateGraphicsPipelines CreateGraphicsPipelines; 78 | PFN_vkDestroyPipeline DestroyPipeline; 79 | PFN_vkCreateShaderModule CreateShaderModule; 80 | PFN_vkDestroyShaderModule DestroyShaderModule; 81 | PFN_vkCreatePipelineLayout CreatePipelineLayout; 82 | PFN_vkDestroyPipelineLayout DestroyPipelineLayout; 83 | PFN_vkCreateImageView CreateImageView; 84 | PFN_vkDestroyImageView DestroyImageView; 85 | PFN_vkCreateFramebuffer CreateFramebuffer; 86 | PFN_vkDestroyFramebuffer DestroyFramebuffer; 87 | PFN_vkBeginCommandBuffer BeginCommandBuffer; 88 | PFN_vkEndCommandBuffer EndCommandBuffer; 89 | PFN_vkCmdBeginRenderPass CmdBeginRenderPass; 90 | PFN_vkCmdBindPipeline CmdBindPipeline; 91 | PFN_vkCmdDraw CmdDraw; 92 | PFN_vkCmdEndRenderPass CmdEndRenderPass; 93 | PFN_vkCreateSemaphore CreateSemaphore; 94 | PFN_vkDestroySemaphore DestroySemaphore; 95 | PFN_vkQueueSubmit QueueSubmit; 96 | PFN_vkDeviceWaitIdle DeviceWaitIdle; 97 | 98 | PFN_vkCreateXcbSurfaceKHR CreateXcbSurfaceKHR; 99 | PFN_vkDestroySurfaceKHR DestroySurfaceKHR; 100 | PFN_vkGetPhysicalDeviceSurfaceSupportKHR GetPhysicalDeviceSurfaceSupportKHR; 101 | PFN_vkGetPhysicalDeviceSurfaceFormatsKHR GetPhysicalDeviceSurfaceFormatsKHR; 102 | PFN_vkGetPhysicalDeviceSurfacePresentModesKHR GetPhysicalDeviceSurfacePresentModesKHR; 103 | PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR GetPhysicalDeviceSurfaceCapabilitiesKHR; 104 | PFN_vkCreateSwapchainKHR CreateSwapchainKHR; 105 | PFN_vkDestroySwapchainKHR DestroySwapchainKHR; 106 | PFN_vkGetSwapchainImagesKHR GetSwapchainImagesKHR; 107 | PFN_vkAcquireNextImageKHR AcquireNextImageKHR; 108 | PFN_vkQueuePresentKHR QueuePresentKHR; 109 | }; 110 | 111 | static uint32_t* 112 | load_file (const char* filename, size_t* file_size) 113 | { 114 | char* data = NULL; 115 | size_t size = 0; 116 | size_t read_size = 0; 117 | size_t alloc_size = 0; 118 | int32_t fd = open (filename, O_RDONLY); 119 | uint8_t buf[1024]; 120 | 121 | while ((read_size = read (fd, buf, 1024)) > 0) { 122 | if (size + read_size > alloc_size) { 123 | alloc_size = read_size + size; 124 | data = realloc (data, alloc_size); 125 | } 126 | 127 | memcpy (data + size, buf, read_size); 128 | size += read_size; 129 | } 130 | 131 | if (read_size == 0) { 132 | if (file_size) 133 | *file_size = size; 134 | 135 | return (uint32_t*) data; 136 | } 137 | else { 138 | return NULL; 139 | } 140 | } 141 | 142 | int32_t 143 | main (int32_t argc, char* argv[]) 144 | { 145 | VkResult result; 146 | struct vk_api vk; 147 | 148 | /* XCB setup */ 149 | /* ======================================================================= */ 150 | 151 | /* connection to the X server */ 152 | xcb_connection_t* xcb_conn = xcb_connect (NULL, NULL); 153 | assert (xcb_conn != NULL); 154 | 155 | /* Get the first screen */ 156 | const xcb_setup_t* xcb_setup = xcb_get_setup (xcb_conn); 157 | xcb_screen_iterator_t iter = xcb_setup_roots_iterator (xcb_setup); 158 | xcb_screen_t* xcb_screen = iter.data; 159 | assert (xcb_screen != NULL); 160 | 161 | /* Create the window */ 162 | xcb_window_t xcb_win = xcb_generate_id (xcb_conn); 163 | 164 | uint32_t value_mask, value_list[32]; 165 | value_mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK; 166 | value_list[0] = xcb_screen->black_pixel; 167 | value_list[1] = XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_EXPOSURE | 168 | XCB_EVENT_MASK_STRUCTURE_NOTIFY; 169 | 170 | xcb_create_window (xcb_conn, /* Connection */ 171 | XCB_COPY_FROM_PARENT, /* depth (same as root)*/ 172 | xcb_win, /* window Id */ 173 | xcb_screen->root, /* parent window */ 174 | 0, 0, /* x, y */ 175 | WIDTH, HEIGHT, /* width, height */ 176 | 0, /* border_width */ 177 | XCB_WINDOW_CLASS_INPUT_OUTPUT, /* class */ 178 | xcb_screen->root_visual, /* visual */ 179 | value_mask, value_list); /* masks, not used yet */ 180 | 181 | /* Map the window onto the screen */ 182 | xcb_map_window (xcb_conn, xcb_win); 183 | 184 | /* Make sure commands are sent before we pause so that the window gets 185 | * shown. 186 | */ 187 | xcb_flush (xcb_conn); 188 | 189 | /* Vulkan setup */ 190 | /* ======================================================================= */ 191 | 192 | /* load inital API entry points */ 193 | 194 | /* if program is linked against a Vulkan loader */ 195 | vk.GetInstanceProcAddr = vkGetInstanceProcAddr; 196 | /* otherwise, */ 197 | /* GET_ICD_PROC_ADDR (vk, GetInstanceProcAddr); */ 198 | 199 | GET_PROC_ADDR (vk, EnumerateInstanceLayerProperties); 200 | GET_PROC_ADDR (vk, EnumerateInstanceExtensionProperties); 201 | GET_PROC_ADDR (vk, CreateInstance); 202 | 203 | /* enummerate supported instance extensions, where we normally check for 204 | * support for window systems integration and presence of VK_KHR_surface. 205 | */ 206 | VkExtensionProperties ext_props[16]; 207 | uint32_t ext_props_count = 16; 208 | result = vk.EnumerateInstanceExtensionProperties (NULL, 209 | &ext_props_count, 210 | ext_props); 211 | assert (result == VK_SUCCESS); 212 | printf ("Instance extensions:\n"); 213 | for (unsigned i = 0; i < ext_props_count; i++) 214 | printf (" %s(%u)\n", 215 | ext_props[i].extensionName, 216 | ext_props[i].specVersion); 217 | 218 | /* the memory allocation callbacks (default by now) */ 219 | const VkAllocationCallbacks* allocator = NULL; 220 | 221 | /* Vulkan application info */ 222 | VkApplicationInfo app_info = { 223 | .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, 224 | .pApplicationName = "Vulkan minimal example", 225 | .apiVersion = VK_API_VERSION_1_0, 226 | }; 227 | 228 | /* create the Vulkan instance */ 229 | VkInstance instance = VK_NULL_HANDLE; 230 | 231 | const char* enabled_extensions[2] = { 232 | VK_KHR_SURFACE_EXTENSION_NAME, 233 | VK_KHR_XCB_SURFACE_EXTENSION_NAME 234 | }; 235 | 236 | VkInstanceCreateInfo instance_info = { 237 | .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, 238 | .pApplicationInfo = &app_info, 239 | .enabledExtensionCount = 2, 240 | .ppEnabledExtensionNames = enabled_extensions 241 | }; 242 | result = vk.CreateInstance (&instance_info, 243 | allocator, 244 | &instance); 245 | assert (result == VK_SUCCESS); 246 | 247 | /* load instance-dependent API entry points */ 248 | GET_INSTANCE_PROC_ADDR (vk, instance, DestroyInstance); 249 | GET_INSTANCE_PROC_ADDR (vk, instance, EnumeratePhysicalDevices); 250 | GET_INSTANCE_PROC_ADDR (vk, instance, GetPhysicalDeviceQueueFamilyProperties); 251 | GET_INSTANCE_PROC_ADDR (vk, instance, CreateDevice); 252 | GET_INSTANCE_PROC_ADDR (vk, instance, EnumerateDeviceExtensionProperties); 253 | GET_INSTANCE_PROC_ADDR (vk, instance, GetPhysicalDeviceProperties); 254 | 255 | GET_INSTANCE_PROC_ADDR (vk, instance, CreateXcbSurfaceKHR); 256 | GET_INSTANCE_PROC_ADDR (vk, instance, DestroySurfaceKHR); 257 | GET_INSTANCE_PROC_ADDR (vk, instance, GetPhysicalDeviceSurfaceSupportKHR); 258 | GET_INSTANCE_PROC_ADDR (vk, instance, GetPhysicalDeviceSurfaceFormatsKHR); 259 | GET_INSTANCE_PROC_ADDR (vk, instance, 260 | GetPhysicalDeviceSurfacePresentModesKHR); 261 | GET_INSTANCE_PROC_ADDR (vk, instance, 262 | GetPhysicalDeviceSurfaceCapabilitiesKHR); 263 | 264 | /* query physical devices */ 265 | uint32_t num_devices = 5; 266 | VkPhysicalDevice devices[5] = {0}; 267 | result = vk.EnumeratePhysicalDevices (instance, 268 | &num_devices, 269 | devices); 270 | assert (result == VK_SUCCESS); 271 | assert (num_devices > 0); 272 | 273 | VkPhysicalDevice physical_device = devices[0]; 274 | 275 | /* query physical device's queue families */ 276 | uint32_t num_queue_families = 5; 277 | VkQueueFamilyProperties queue_families[5] = {0}; 278 | /* get queue families with NULL first, to retrieve count */ 279 | vk.GetPhysicalDeviceQueueFamilyProperties (physical_device, 280 | &num_queue_families, 281 | NULL); 282 | vk.GetPhysicalDeviceQueueFamilyProperties (devices[0], 283 | &num_queue_families, 284 | queue_families); 285 | assert (num_queue_families >= 1); 286 | 287 | uint32_t queue_family_index = 0; 288 | assert (queue_families[queue_family_index].queueFlags & 289 | VK_QUEUE_GRAPHICS_BIT); 290 | 291 | /* Enummerate supported device extensions, where we would normally check for 292 | * the presence of VK_KHR_swapchain. 293 | */ 294 | ext_props_count = 16; 295 | result = vk.EnumerateDeviceExtensionProperties (physical_device, 296 | NULL, 297 | &ext_props_count, 298 | ext_props); 299 | assert (result == VK_SUCCESS); 300 | printf ("Device extensions: \n"); 301 | for (unsigned i = 0; i < ext_props_count; i++) 302 | printf (" %s(%u)\n", 303 | ext_props[i].extensionName, 304 | ext_props[i].specVersion); 305 | 306 | /* create a vulkan surface, from the XCB window (VkSurfaceKHR) */ 307 | VkSurfaceKHR surface = VK_NULL_HANDLE; 308 | VkXcbSurfaceCreateInfoKHR surface_info = { 309 | .sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR, 310 | .connection = xcb_conn, 311 | .window = xcb_win 312 | }; 313 | result = vk.CreateXcbSurfaceKHR (instance, 314 | &surface_info, 315 | allocator, 316 | &surface); 317 | assert (result == VK_SUCCESS); 318 | 319 | /* check for present support in the selected queue family */ 320 | VkBool32 support_present = VK_FALSE; 321 | vk.GetPhysicalDeviceSurfaceSupportKHR (physical_device, 322 | queue_family_index, 323 | surface, 324 | &support_present); 325 | assert (support_present); 326 | 327 | /* physical device capabilities (needed by swapchain for 328 | * minImageCount and maxImageCount. 329 | */ 330 | VkSurfaceCapabilitiesKHR surface_caps; 331 | result = vk.GetPhysicalDeviceSurfaceCapabilitiesKHR (physical_device, 332 | surface, 333 | &surface_caps); 334 | assert (result == VK_SUCCESS); 335 | 336 | /* choose a surface format */ 337 | uint32_t surface_formats_count; 338 | result = vk.GetPhysicalDeviceSurfaceFormatsKHR (physical_device, 339 | surface, 340 | &surface_formats_count, 341 | NULL); 342 | assert (surface_formats_count > 0); 343 | surface_formats_count = 1; 344 | VkSurfaceFormatKHR surface_format; 345 | result = vk.GetPhysicalDeviceSurfaceFormatsKHR (physical_device, 346 | surface, 347 | &surface_formats_count, 348 | &surface_format); 349 | assert (result == VK_SUCCESS || result == VK_INCOMPLETE); 350 | 351 | /* choose a present mode */ 352 | VkPresentModeKHR present_mode; 353 | uint32_t present_mode_count = 1; 354 | result = vk.GetPhysicalDeviceSurfacePresentModesKHR (physical_device, 355 | surface, 356 | &present_mode_count, 357 | &present_mode); 358 | assert (result == VK_SUCCESS || result == VK_INCOMPLETE); 359 | assert (present_mode_count > 0); 360 | 361 | /* create logical device */ 362 | VkDevice device = VK_NULL_HANDLE; 363 | 364 | const float queue_priorities = 1.0; 365 | VkDeviceQueueCreateInfo queue_info = { 366 | .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, 367 | .flags = 0, 368 | .queueCount = 1, 369 | .pQueuePriorities = &queue_priorities, 370 | .queueFamilyIndex = queue_family_index 371 | }; 372 | 373 | const char* device_extensions[1] = {VK_KHR_SWAPCHAIN_EXTENSION_NAME}; 374 | 375 | VkDeviceCreateInfo device_info = { 376 | .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, 377 | .pQueueCreateInfos = &queue_info, 378 | .queueCreateInfoCount = 1, 379 | .enabledExtensionCount = 1, 380 | .ppEnabledExtensionNames = device_extensions, 381 | }; 382 | result = vk.CreateDevice (devices[0], 383 | &device_info, 384 | allocator, 385 | &device); 386 | assert (result == VK_SUCCESS); 387 | 388 | /* load device-dependent API entry points */ 389 | GET_INSTANCE_PROC_ADDR (vk, instance, GetDeviceProcAddr); 390 | GET_DEVICE_PROC_ADDR (vk, device, CreateCommandPool); 391 | GET_DEVICE_PROC_ADDR (vk, device, CmdBeginRenderPass); 392 | GET_DEVICE_PROC_ADDR (vk, device, CmdDraw); 393 | GET_DEVICE_PROC_ADDR (vk, device, CmdEndRenderPass); 394 | GET_DEVICE_PROC_ADDR (vk, device, AllocateCommandBuffers); 395 | GET_DEVICE_PROC_ADDR (vk, device, FreeCommandBuffers); 396 | GET_DEVICE_PROC_ADDR (vk, device, GetDeviceQueue); 397 | GET_DEVICE_PROC_ADDR (vk, device, CreateRenderPass); 398 | GET_DEVICE_PROC_ADDR (vk, device, DestroyRenderPass); 399 | GET_DEVICE_PROC_ADDR (vk, device, DestroyCommandPool); 400 | GET_DEVICE_PROC_ADDR (vk, device, DestroyDevice); 401 | GET_DEVICE_PROC_ADDR (vk, device, CreateGraphicsPipelines); 402 | GET_DEVICE_PROC_ADDR (vk, device, DestroyPipeline); 403 | GET_DEVICE_PROC_ADDR (vk, device, CreateShaderModule); 404 | GET_DEVICE_PROC_ADDR (vk, device, DestroyShaderModule); 405 | GET_DEVICE_PROC_ADDR (vk, device, CreatePipelineLayout); 406 | GET_DEVICE_PROC_ADDR (vk, device, DestroyPipelineLayout); 407 | GET_DEVICE_PROC_ADDR (vk, device, CreateImageView); 408 | GET_DEVICE_PROC_ADDR (vk, device, DestroyImageView); 409 | GET_DEVICE_PROC_ADDR (vk, device, CreateFramebuffer); 410 | GET_DEVICE_PROC_ADDR (vk, device, DestroyFramebuffer); 411 | GET_DEVICE_PROC_ADDR (vk, device, BeginCommandBuffer); 412 | GET_DEVICE_PROC_ADDR (vk, device, EndCommandBuffer); 413 | GET_DEVICE_PROC_ADDR (vk, device, CmdBindPipeline); 414 | GET_DEVICE_PROC_ADDR (vk, device, CreateSemaphore); 415 | GET_DEVICE_PROC_ADDR (vk, device, DestroySemaphore); 416 | GET_DEVICE_PROC_ADDR (vk, device, QueueSubmit); 417 | GET_DEVICE_PROC_ADDR (vk, device, DeviceWaitIdle); 418 | 419 | GET_DEVICE_PROC_ADDR (vk, device, CreateSwapchainKHR); 420 | GET_DEVICE_PROC_ADDR (vk, device, DestroySwapchainKHR); 421 | GET_DEVICE_PROC_ADDR (vk, device, GetSwapchainImagesKHR); 422 | GET_DEVICE_PROC_ADDR (vk, device, AcquireNextImageKHR); 423 | GET_DEVICE_PROC_ADDR (vk, device, QueuePresentKHR); 424 | 425 | /* create the vertex shader module */ 426 | size_t shader_code_size; 427 | uint32_t* shader_code = load_file (CURRENT_DIR "/vert.spv", 428 | &shader_code_size); 429 | assert (shader_code != NULL); 430 | 431 | VkShaderModuleCreateInfo shader_info = { 432 | .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, 433 | .codeSize = shader_code_size, 434 | .pCode = shader_code, 435 | }; 436 | VkShaderModule vert_shader_module; 437 | result = vk.CreateShaderModule (device, 438 | &shader_info, 439 | allocator, 440 | &vert_shader_module); 441 | assert (result == VK_SUCCESS); 442 | free (shader_code); 443 | 444 | /* create the fragment shader module */ 445 | shader_code = load_file (CURRENT_DIR "/frag.spv", &shader_code_size); 446 | assert (shader_code != NULL); 447 | 448 | shader_info.codeSize = shader_code_size; 449 | shader_info.pCode = shader_code; 450 | 451 | VkShaderModule frag_shader_module; 452 | result = vk.CreateShaderModule (device, 453 | &shader_info, 454 | allocator, 455 | &frag_shader_module); 456 | assert (result == VK_SUCCESS); 457 | free (shader_code); 458 | 459 | /* create the shader stages */ 460 | VkPipelineShaderStageCreateInfo vert_stage_info = { 461 | .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, 462 | .stage = VK_SHADER_STAGE_VERTEX_BIT, 463 | .module = vert_shader_module, 464 | .pName = "main" 465 | }; 466 | 467 | VkPipelineShaderStageCreateInfo frag_stage_info = { 468 | .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, 469 | .stage = VK_SHADER_STAGE_FRAGMENT_BIT, 470 | .module = frag_shader_module, 471 | .pName = "main" 472 | }; 473 | 474 | VkPipelineShaderStageCreateInfo shader_stages[2] = 475 | {vert_stage_info, frag_stage_info}; 476 | 477 | /* get first device queue */ 478 | VkQueue queue = VK_NULL_HANDLE; 479 | vk.GetDeviceQueue (device, queue_family_index, 0, &queue); 480 | assert (queue != VK_NULL_HANDLE); 481 | 482 | /* create a command pool */ 483 | VkCommandPool cmd_pool = VK_NULL_HANDLE;; 484 | VkCommandPoolCreateInfo cmd_pool_info = { 485 | .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, 486 | .flags = 0, 487 | .queueFamilyIndex = queue_family_index, 488 | }; 489 | result = vk.CreateCommandPool (device, 490 | &cmd_pool_info, 491 | allocator, 492 | &cmd_pool); 493 | assert (result == VK_SUCCESS); 494 | 495 | /* create rendering semaphores */ 496 | VkSemaphore image_available_semaphore = VK_NULL_HANDLE; 497 | VkSemaphore render_finished_semaphore = VK_NULL_HANDLE; 498 | 499 | VkSemaphoreCreateInfo semaphore_info = { 500 | .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO 501 | }; 502 | 503 | result = vk.CreateSemaphore (device, 504 | &semaphore_info, 505 | allocator, 506 | &image_available_semaphore); 507 | assert (result == VK_SUCCESS); 508 | 509 | result = vk.CreateSemaphore (device, 510 | &semaphore_info, 511 | allocator, 512 | &render_finished_semaphore); 513 | assert (result == VK_SUCCESS); 514 | 515 | /* create the swapchain. This is normally done in a reusable way, because 516 | * the swapchain needs to be recreated quite often (e.g window resize). 517 | */ 518 | VkSwapchainKHR swapchain = VK_NULL_HANDLE; 519 | 520 | /* @FIXME: check extent against surface capabilities */ 521 | VkExtent2D swapchain_extent = {WIDTH, HEIGHT}; 522 | VkSwapchainCreateInfoKHR swap_chain_info = { 523 | .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, 524 | .surface = surface, 525 | .minImageCount = surface_caps.minImageCount, 526 | .imageFormat = surface_format.format, 527 | .imageColorSpace = surface_format.colorSpace, 528 | .imageExtent = swapchain_extent, 529 | .imageArrayLayers = 1, 530 | .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, 531 | 532 | .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, 533 | .queueFamilyIndexCount = 0, 534 | .pQueueFamilyIndices = NULL, 535 | .preTransform = surface_caps.currentTransform, 536 | .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, 537 | .presentMode = present_mode, 538 | .clipped = VK_TRUE, 539 | .oldSwapchain = VK_NULL_HANDLE 540 | }; 541 | 542 | result = vk.CreateSwapchainKHR (device, 543 | &swap_chain_info, 544 | allocator, 545 | &swapchain); 546 | assert (result == VK_SUCCESS); 547 | 548 | /* get the images from the swap chain */ 549 | uint32_t swapchain_images_count = 0; 550 | result = vk.GetSwapchainImagesKHR (device, 551 | swapchain, 552 | &swapchain_images_count, 553 | NULL); 554 | assert (result == VK_SUCCESS); 555 | assert (swapchain_images_count > 0 && swapchain_images_count <= 8); 556 | 557 | VkImage swapchain_images[8] = {VK_NULL_HANDLE}; 558 | vk.GetSwapchainImagesKHR (device, 559 | swapchain, 560 | &swapchain_images_count, 561 | swapchain_images); 562 | 563 | /* create an image view for each swap chain image */ 564 | VkImageView image_views[8]; 565 | for (uint32_t i = 0; i < swapchain_images_count; i++) { 566 | VkImageViewCreateInfo image_view_info = { 567 | .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, 568 | .image = swapchain_images[i], 569 | .viewType = VK_IMAGE_VIEW_TYPE_2D, 570 | .format = surface_format.format, 571 | .components.r = VK_COMPONENT_SWIZZLE_IDENTITY, 572 | .components.g = VK_COMPONENT_SWIZZLE_IDENTITY, 573 | .components.b = VK_COMPONENT_SWIZZLE_IDENTITY, 574 | .components.a = VK_COMPONENT_SWIZZLE_IDENTITY, 575 | .subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, 576 | .subresourceRange.baseMipLevel = 0, 577 | .subresourceRange.levelCount = 1, 578 | .subresourceRange.baseArrayLayer = 0, 579 | .subresourceRange.layerCount = 1, 580 | }; 581 | 582 | result = vk.CreateImageView (device, 583 | &image_view_info, 584 | allocator, 585 | &image_views[i]); 586 | assert (result == VK_SUCCESS); 587 | } 588 | 589 | /* config a color attachment */ 590 | VkAttachmentDescription color_attachment = { 591 | .format = surface_format.format, 592 | .samples = VK_SAMPLE_COUNT_1_BIT, 593 | .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, 594 | .storeOp = VK_ATTACHMENT_STORE_OP_STORE, 595 | .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, 596 | .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, 597 | .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, 598 | .finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, 599 | }; 600 | 601 | /* an attachment reference */ 602 | VkAttachmentReference color_attachment_ref = { 603 | .attachment = 0, 604 | .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL 605 | }; 606 | 607 | /* a render sub-pass */ 608 | VkSubpassDescription render_subpass = { 609 | .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, 610 | .colorAttachmentCount = 1, 611 | .pColorAttachments = &color_attachment_ref, 612 | }; 613 | 614 | VkSubpassDependency dependency = { 615 | .srcSubpass = VK_SUBPASS_EXTERNAL, 616 | .dstSubpass = 0, 617 | .srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 618 | .srcAccessMask = VK_ACCESS_MEMORY_READ_BIT, 619 | .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 620 | .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | 621 | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT 622 | }; 623 | 624 | /* create a render pass */ 625 | VkRenderPass renderpass = VK_NULL_HANDLE; 626 | VkRenderPassCreateInfo render_pass_info = { 627 | .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, 628 | .attachmentCount = 1, 629 | .pAttachments = &color_attachment, 630 | .subpassCount = 1, 631 | .pSubpasses = &render_subpass, 632 | .dependencyCount = 1, 633 | .pDependencies = &dependency 634 | }; 635 | 636 | result = vk.CreateRenderPass (device, 637 | &render_pass_info, 638 | allocator, 639 | &renderpass); 640 | assert (result == VK_SUCCESS); 641 | 642 | /* create framebuffers for each image view */ 643 | VkFramebuffer framebuffers[4] = {VK_NULL_HANDLE}; 644 | for (uint32_t i = 0; i < swapchain_images_count; i++) { 645 | VkImageView attachments[] = { 646 | image_views[i] 647 | }; 648 | 649 | VkFramebufferCreateInfo framebuffer_info = { 650 | .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, 651 | .renderPass = renderpass, 652 | .attachmentCount = 1, 653 | .pAttachments = attachments, 654 | .width = swapchain_extent.width, 655 | .height = swapchain_extent.height, 656 | .layers = 1 657 | }; 658 | 659 | result = vk.CreateFramebuffer (device, 660 | &framebuffer_info, 661 | allocator, 662 | &framebuffers[i]); 663 | assert (result == VK_SUCCESS); 664 | } 665 | 666 | /* create the graphics pipeline */ 667 | 668 | /* specify the vertex input (all default to zero, since we won't use it here) */ 669 | VkPipelineVertexInputStateCreateInfo vertex_input_info = { 670 | .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO 671 | }; 672 | 673 | /* specify the input assembly (type of primitives) */ 674 | VkPipelineInputAssemblyStateCreateInfo input_assembly_info = { 675 | .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, 676 | .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 677 | .primitiveRestartEnable = VK_FALSE, 678 | }; 679 | 680 | /* viewport and scissors */ 681 | VkViewport viewport = { 682 | .x = 0.0f, 683 | .y = 0.0f, 684 | .width = (float) swapchain_extent.width, 685 | .height = (float) swapchain_extent.height, 686 | .minDepth = 0.0f, 687 | .maxDepth = 1.0f 688 | }; 689 | 690 | VkRect2D scissor = { 691 | .offset.x = 0, 692 | .offset.y = 0, 693 | .extent = swapchain_extent 694 | }; 695 | 696 | VkPipelineViewportStateCreateInfo viewport_state_info = { 697 | .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, 698 | .viewportCount = 1, 699 | .pViewports = &viewport, 700 | .scissorCount = 1, 701 | .pScissors = &scissor 702 | }; 703 | 704 | /* configure rasterizer */ 705 | VkPipelineRasterizationStateCreateInfo rasterizer = { 706 | .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, 707 | .depthClampEnable = VK_FALSE, 708 | .rasterizerDiscardEnable = VK_FALSE, 709 | .polygonMode = VK_POLYGON_MODE_FILL, 710 | .lineWidth = 1.0f, 711 | 712 | .cullMode = VK_CULL_MODE_BACK_BIT, 713 | .frontFace = VK_FRONT_FACE_CLOCKWISE, 714 | 715 | .depthBiasEnable = VK_FALSE, 716 | .depthBiasConstantFactor = 0.0f, 717 | .depthBiasClamp = 0.0f, 718 | .depthBiasSlopeFactor = 0.0f 719 | }; 720 | 721 | /* configure multisampling */ 722 | VkPipelineMultisampleStateCreateInfo multisampling = { 723 | .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, 724 | .sampleShadingEnable = VK_FALSE, 725 | .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT, 726 | .minSampleShading = 1.0f, 727 | .pSampleMask = NULL, 728 | .alphaToCoverageEnable = VK_FALSE, 729 | .alphaToOneEnable = VK_FALSE 730 | }; 731 | 732 | /* color blending */ 733 | VkPipelineColorBlendAttachmentState color_blend_attachment = { 734 | .colorWriteMask = 735 | VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT 736 | | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT, 737 | .blendEnable = VK_FALSE, 738 | .srcColorBlendFactor = VK_BLEND_FACTOR_ONE, 739 | .dstColorBlendFactor = VK_BLEND_FACTOR_ZERO, 740 | .colorBlendOp = VK_BLEND_OP_ADD, 741 | .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE, 742 | .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, 743 | .alphaBlendOp = VK_BLEND_OP_ADD 744 | }; 745 | 746 | VkPipelineColorBlendStateCreateInfo color_blending_info = { 747 | .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, 748 | .logicOpEnable = VK_FALSE, 749 | .logicOp = VK_LOGIC_OP_COPY, 750 | .attachmentCount = 1, 751 | .pAttachments = &color_blend_attachment, 752 | .blendConstants[0] = 0.0f, 753 | .blendConstants[1] = 0.0f, 754 | .blendConstants[2] = 0.0f, 755 | .blendConstants[3] = 0.0f, 756 | }; 757 | 758 | /* pipeline layout */ 759 | VkPipelineLayout pipeline_layout = VK_NULL_HANDLE; 760 | VkPipelineLayoutCreateInfo pipeline_layout_info = { 761 | .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, 762 | .setLayoutCount = 0, 763 | .pSetLayouts = NULL, 764 | .pushConstantRangeCount = 0, 765 | .pPushConstantRanges = 0 766 | }; 767 | result = vk.CreatePipelineLayout (device, 768 | &pipeline_layout_info, 769 | NULL, 770 | &pipeline_layout); 771 | assert (result == VK_SUCCESS); 772 | 773 | /* the pipeline, finally */ 774 | VkPipeline pipeline = VK_NULL_HANDLE; 775 | VkGraphicsPipelineCreateInfo pipeline_info = { 776 | .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, 777 | .stageCount = 2, 778 | .pStages = shader_stages, 779 | .pVertexInputState = &vertex_input_info, 780 | .pInputAssemblyState = &input_assembly_info, 781 | .pViewportState = &viewport_state_info, 782 | .pRasterizationState = &rasterizer, 783 | .pMultisampleState = &multisampling, 784 | .pDepthStencilState = NULL, 785 | .pColorBlendState = &color_blending_info, 786 | .pDynamicState = NULL, 787 | .layout = pipeline_layout, 788 | .renderPass = renderpass, 789 | .subpass = 0, 790 | .basePipelineHandle = VK_NULL_HANDLE, 791 | .basePipelineIndex = -1 792 | }; 793 | result = vk.CreateGraphicsPipelines (device, 794 | VK_NULL_HANDLE, 795 | 1, 796 | &pipeline_info, 797 | allocator, 798 | &pipeline); 799 | assert (result == VK_SUCCESS); 800 | 801 | /* create command buffers */ 802 | VkCommandBuffer cmd_buffers[8] = {VK_NULL_HANDLE,}; 803 | VkCommandBufferAllocateInfo cmd_buffer_alloc_info = { 804 | .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, 805 | .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, 806 | .commandBufferCount = swapchain_images_count, 807 | .commandPool = cmd_pool 808 | }; 809 | result = vk.AllocateCommandBuffers (device, 810 | &cmd_buffer_alloc_info, 811 | cmd_buffers); 812 | assert (result == VK_SUCCESS); 813 | 814 | /* start recording command buffers */ 815 | for (uint32_t i = 0; i < swapchain_images_count; i++) { 816 | VkCommandBufferBeginInfo begin_info = { 817 | .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, 818 | .flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT, 819 | .pInheritanceInfo = VK_NULL_HANDLE 820 | }; 821 | 822 | result = vk.BeginCommandBuffer (cmd_buffers[i], &begin_info); 823 | assert (result == VK_SUCCESS); 824 | 825 | /* start a render pass */ 826 | VkClearValue clear_color = {{{0.01f, 0.01f, 0.01f, 1.0f}}}; 827 | VkOffset2D swapchain_offset = {0, 0}; 828 | VkRenderPassBeginInfo renderpass_begin_info = { 829 | .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, 830 | .renderPass = renderpass, 831 | .framebuffer = framebuffers[i], 832 | .renderArea.offset = swapchain_offset, 833 | .renderArea.extent = swapchain_extent, 834 | .clearValueCount = 1, 835 | .pClearValues = &clear_color 836 | }; 837 | vk.CmdBeginRenderPass (cmd_buffers[i], 838 | &renderpass_begin_info, 839 | VK_SUBPASS_CONTENTS_INLINE); 840 | 841 | vk.CmdBindPipeline (cmd_buffers[i], 842 | VK_PIPELINE_BIND_POINT_GRAPHICS, 843 | pipeline); 844 | 845 | vk.CmdDraw (cmd_buffers[i], 3, 1, 0, 0); 846 | 847 | vk.CmdEndRenderPass (cmd_buffers[i]); 848 | 849 | vk.EndCommandBuffer (cmd_buffers[i]); 850 | } 851 | 852 | 853 | /* start the show (mainloop) */ 854 | 855 | while (VK_TRUE) { 856 | /* acquire next image */ 857 | uint32_t image_index; 858 | result = vk.AcquireNextImageKHR (device, 859 | swapchain, 860 | 1000, 861 | image_available_semaphore, 862 | VK_NULL_HANDLE, 863 | &image_index); 864 | if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { 865 | /* swapchain needs recreation */ 866 | printf ("EXPOSE event, swapchain needs to be recreated\n"); 867 | continue; 868 | } 869 | 870 | /* submit the command buffer to the graphics queue */ 871 | VkSemaphore wait_semaphores[] = {image_available_semaphore}; 872 | VkSemaphore signal_semaphores[] = {render_finished_semaphore}; 873 | 874 | VkPipelineStageFlags wait_stages[] = 875 | {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; 876 | 877 | VkSubmitInfo submit_info = { 878 | .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, 879 | .waitSemaphoreCount = 1, 880 | .pWaitSemaphores = wait_semaphores, 881 | .pWaitDstStageMask = wait_stages, 882 | .commandBufferCount = 1, 883 | .pCommandBuffers = &cmd_buffers[image_index], 884 | .signalSemaphoreCount = 1, 885 | .pSignalSemaphores = signal_semaphores 886 | }; 887 | 888 | result = vk.QueueSubmit (queue, 889 | 1, 890 | &submit_info, 891 | VK_NULL_HANDLE); 892 | assert (result == VK_SUCCESS); 893 | 894 | /* present the frame */ 895 | VkSwapchainKHR swap_chains[] = {swapchain}; 896 | VkPresentInfoKHR present_info = { 897 | .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, 898 | .waitSemaphoreCount = 1, 899 | .pWaitSemaphores = signal_semaphores, 900 | .swapchainCount = 1, 901 | .pSwapchains = swap_chains, 902 | .pImageIndices = &image_index, 903 | .pResults = NULL 904 | }; 905 | 906 | /* present the render pass onto the surface. or... DRAW! */ 907 | result = vk.QueuePresentKHR (queue, &present_info); 908 | if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { 909 | /* swapchain needs recreation */ 910 | printf ("EXPOSE event, swapchain needs to be recreated\n"); 911 | continue; 912 | } 913 | 914 | assert (result == VK_SUCCESS); 915 | } 916 | 917 | /* free all allocated objects */ 918 | 919 | /* wait for all async ops on device */ 920 | if (device != VK_NULL_HANDLE) 921 | vk.DeviceWaitIdle (device); 922 | 923 | /* destroy state */ 924 | for (uint32_t i = 0; i < swapchain_images_count; i++) 925 | vk.DestroyFramebuffer (device, framebuffers[i], allocator); 926 | 927 | for (uint32_t i = 0; i < swapchain_images_count; i++) 928 | vk.DestroyImageView (device, image_views[i], allocator); 929 | 930 | vk.DestroyPipeline (device, pipeline, allocator); 931 | vk.DestroyPipelineLayout (device, pipeline_layout, allocator); 932 | vk.DestroyRenderPass (device, renderpass, allocator); 933 | vk.DestroySwapchainKHR (device, swapchain, allocator); 934 | 935 | /* destroy immutable objects */ 936 | vk.DestroySemaphore (device, image_available_semaphore, allocator); 937 | vk.DestroySemaphore (device, render_finished_semaphore, allocator); 938 | vk.DestroyCommandPool (device, cmd_pool, allocator); 939 | vk.DestroyShaderModule (device, vert_shader_module, allocator); 940 | vk.DestroyShaderModule (device, frag_shader_module, allocator); 941 | vk.DestroyDevice (device, allocator); 942 | vk.DestroySurfaceKHR (instance, surface, allocator); 943 | vk.DestroyInstance (instance, allocator); 944 | 945 | /* Unmap the window from the screen */ 946 | xcb_unmap_window (xcb_conn, xcb_win); 947 | 948 | /* disconnect from the X server */ 949 | if (xcb_conn != NULL) 950 | xcb_disconnect (xcb_conn); 951 | 952 | return 0; 953 | } 954 | -------------------------------------------------------------------------------- /vulkan-minimal/shader.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(location = 0) out highp vec4 outColor; 5 | layout(location = 0) in vec3 color; 6 | 7 | void main() { 8 | outColor = vec4(color, 1.0); 9 | } 10 | -------------------------------------------------------------------------------- /vulkan-minimal/shader.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | out gl_PerVertex { 5 | vec4 gl_Position; 6 | }; 7 | 8 | vec2 positions[3] = vec2[]( 9 | vec2( 0.0, -0.5), 10 | vec2( 0.5, 0.5), 11 | vec2(-0.5, 0.5) 12 | ); 13 | 14 | vec3 colors[3] = vec3[] ( 15 | vec3(1.0, 0.0, 0.0), 16 | vec3(0.0, 1.0, 0.0), 17 | vec3(0.0, 0.0, 1.0) 18 | ); 19 | 20 | layout(location = 0) out vec3 color; 21 | 22 | void main() { 23 | gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); 24 | color = colors[gl_VertexIndex]; 25 | } 26 | -------------------------------------------------------------------------------- /vulkan-triangle/Makefile: -------------------------------------------------------------------------------- 1 | TARGET=vulkan-triangle 2 | 3 | GLSL_VALIDATOR=../glslangValidator 4 | 5 | all: $(TARGET) vert.spv frag.spv 6 | 7 | vert.spv: shader.vert 8 | $(GLSL_VALIDATOR) -V shader.vert 9 | 10 | frag.spv: shader.frag 11 | $(GLSL_VALIDATOR) -V shader.frag 12 | 13 | $(TARGET): Makefile main.c vert.spv frag.spv \ 14 | common/wsi.h common/wsi-xcb.c \ 15 | common/vk-api.h common/vk-api.c 16 | gcc -ggdb -O0 -Wall -std=c99 \ 17 | -DCURRENT_DIR=\"`pwd`\" \ 18 | `pkg-config --libs --cflags xcb` \ 19 | -lvulkan \ 20 | -DVK_USE_PLATFORM_XCB_KHR \ 21 | -o $(TARGET) \ 22 | common/wsi-xcb.c \ 23 | common/vk-api.c \ 24 | main.c 25 | 26 | clean: 27 | rm -f $(TARGET) vert.spv frag.spv 28 | -------------------------------------------------------------------------------- /vulkan-triangle/common: -------------------------------------------------------------------------------- 1 | ../common -------------------------------------------------------------------------------- /vulkan-triangle/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Example: 3 | * 4 | * Vulkan triangle: (yet another) Vulkan triangle demo. 5 | * 6 | * This example shows a triangle rendered by Vulkan API on an X11 window. It 7 | * supports resizing the window, and toggling fullscreen mode (F-key). 8 | * 9 | * Tested on Linux 4.7, Mesa 12.0, Intel Haswell (gen7+). 10 | * 11 | * Authors: 12 | * * Eduardo Lima Mitev 13 | * 14 | * This code is free software; you can redistribute it and/or 15 | * modify it under the terms of the GNU Lesser General Public License 16 | * version 3, or (at your option) any later version as published by 17 | * the Free Software Foundation. 18 | * 19 | * THIS CODE IS PROVIDED AS-IS, WITHOUT WARRANTY OF ANY KIND, OR POSSIBLE 20 | * LIABILITY TO THE AUTHORS FOR ANY CLAIM OR DAMAGE. 21 | */ 22 | 23 | #include 24 | #include "common/wsi.h" 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | /* 'VK_USE_PLATFORM_X_KHR' currently defined as flag in Makefile */ 35 | #include 36 | #include "common/vk-api.h" 37 | 38 | #define WIDTH 640 39 | #define HEIGHT 480 40 | 41 | static struct vk_api vk = { NULL, }; 42 | static const VkAllocationCallbacks* allocator = VK_NULL_HANDLE; 43 | 44 | struct vk_objects { 45 | VkPhysicalDevice physical_device; 46 | VkDevice device; 47 | 48 | VkSurfaceKHR surface; 49 | VkQueue graphics_queue; 50 | VkCommandPool cmd_pool; 51 | VkPipelineShaderStageCreateInfo shader_stages[2]; 52 | 53 | VkSemaphore image_available_semaphore; 54 | VkSemaphore render_finished_semaphore; 55 | }; 56 | 57 | struct vk_config { 58 | VkSurfaceCapabilitiesKHR surface_caps; 59 | VkSurfaceFormatKHR surface_format; 60 | VkPresentModeKHR present_mode; 61 | }; 62 | 63 | #define MAX_SWAPCHAIN_IMAGES 8 64 | 65 | struct vk_state { 66 | VkExtent2D surface_extent; 67 | 68 | VkSwapchainKHR swapchain; 69 | VkSwapchainKHR previous_swapchain; 70 | uint32_t swapchain_images_count; 71 | VkImageView image_views[MAX_SWAPCHAIN_IMAGES]; 72 | VkFramebuffer framebuffers[MAX_SWAPCHAIN_IMAGES]; 73 | 74 | VkRenderPass renderpass; 75 | VkPipelineLayout pipeline_layout; 76 | VkPipeline pipeline; 77 | VkCommandBuffer cmd_buffers[MAX_SWAPCHAIN_IMAGES]; 78 | }; 79 | 80 | static struct vk_objects objs = {VK_NULL_HANDLE,}; 81 | static struct vk_config config = {0,}; 82 | static struct vk_state state = {0,}; 83 | 84 | static bool running = false; 85 | static bool damaged = false; 86 | static bool expose = false; 87 | 88 | static bool 89 | recreate_swapchain (struct vk_objects* objs, 90 | struct vk_config* config, 91 | struct vk_state* state); 92 | 93 | static uint32_t* 94 | load_file (const char* filename, size_t* file_size) 95 | { 96 | char *data = NULL; 97 | size_t size = 0; 98 | size_t read_size = 0; 99 | size_t alloc_size = 0; 100 | int fd = open (filename, O_RDONLY); 101 | uint8_t buf[1024]; 102 | 103 | while ((read_size = read (fd, buf, 1024)) > 0) { 104 | if (size + read_size > alloc_size) { 105 | alloc_size = read_size + size; 106 | data = realloc (data, alloc_size); 107 | } 108 | 109 | memcpy (data + size, buf, read_size); 110 | size += read_size; 111 | } 112 | 113 | if (read_size == 0) { 114 | if (file_size) 115 | *file_size = size; 116 | 117 | return (uint32_t*) data; 118 | } 119 | else { 120 | return NULL; 121 | } 122 | } 123 | 124 | static void 125 | ctrl_c_handler (int32_t dummy) 126 | { 127 | running = false; 128 | signal (SIGINT, NULL); 129 | } 130 | 131 | static bool 132 | create_renderpass (struct vk_objects* objs, 133 | struct vk_config* config, 134 | struct vk_state* state) 135 | { 136 | assert (objs->device != VK_NULL_HANDLE); 137 | 138 | state->renderpass = VK_NULL_HANDLE; 139 | 140 | /* config a color attachment */ 141 | VkAttachmentDescription color_attachment = { 142 | .format = config->surface_format.format, 143 | .samples = VK_SAMPLE_COUNT_1_BIT, 144 | .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, 145 | .storeOp = VK_ATTACHMENT_STORE_OP_STORE, 146 | .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, 147 | .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, 148 | .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, 149 | .finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, 150 | }; 151 | 152 | /* an attachment reference */ 153 | VkAttachmentReference color_attachment_ref = { 154 | .attachment = 0, 155 | .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL 156 | }; 157 | 158 | /* a render sub-pass */ 159 | VkSubpassDescription render_subpass = { 160 | .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, 161 | .colorAttachmentCount = 1, 162 | .pColorAttachments = &color_attachment_ref, 163 | }; 164 | 165 | VkSubpassDependency dependency = { 166 | .srcSubpass = VK_SUBPASS_EXTERNAL, 167 | .dstSubpass = 0, 168 | .srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 169 | .srcAccessMask = VK_ACCESS_MEMORY_READ_BIT, 170 | .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 171 | .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | 172 | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT 173 | }; 174 | 175 | /* create a render pass */ 176 | VkRenderPassCreateInfo render_pass_info = { 177 | .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, 178 | .attachmentCount = 1, 179 | .pAttachments = &color_attachment, 180 | .subpassCount = 1, 181 | .pSubpasses = &render_subpass, 182 | .dependencyCount = 1, 183 | .pDependencies = &dependency 184 | }; 185 | 186 | if (vk.CreateRenderPass (objs->device, 187 | &render_pass_info, 188 | allocator, 189 | &state->renderpass) != VK_SUCCESS) { 190 | printf ("Error: Failed to create render pass\n"); 191 | return false; 192 | } 193 | printf ("Render pass created\n"); 194 | 195 | return true; 196 | } 197 | 198 | static bool 199 | create_pipeline (struct vk_objects* objs, 200 | struct vk_config* config, 201 | struct vk_state* state) 202 | { 203 | assert (objs->device != VK_NULL_HANDLE); 204 | assert (state->renderpass != VK_NULL_HANDLE); 205 | 206 | /* specify the vertex input */ 207 | VkPipelineVertexInputStateCreateInfo vertex_input_info = { 208 | .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, 209 | .vertexBindingDescriptionCount = 0, 210 | .pVertexBindingDescriptions = NULL, 211 | .vertexAttributeDescriptionCount = 0, 212 | .pVertexAttributeDescriptions = NULL 213 | }; 214 | 215 | /* specify the input assembly (type of primitives) */ 216 | VkPipelineInputAssemblyStateCreateInfo input_assembly_info = { 217 | .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, 218 | .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 219 | .primitiveRestartEnable = VK_FALSE, 220 | }; 221 | 222 | /* viewport and scissors */ 223 | VkViewport viewport = { 224 | .x = 0.0f, 225 | .y = 0.0f, 226 | .width = (float) state->surface_extent.width, 227 | .height = (float) state->surface_extent.height, 228 | .minDepth = 0.0f, 229 | .maxDepth = 1.0f 230 | }; 231 | 232 | VkRect2D scissor = { 233 | .offset.x = 0, 234 | .offset.y = 0, 235 | .extent = state->surface_extent 236 | }; 237 | 238 | VkPipelineViewportStateCreateInfo viewport_state_info = { 239 | .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, 240 | .viewportCount = 1, 241 | .pViewports = &viewport, 242 | .scissorCount = 1, 243 | .pScissors = &scissor 244 | }; 245 | 246 | /* configure rasterizer */ 247 | VkPipelineRasterizationStateCreateInfo rasterizer = { 248 | .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, 249 | .depthClampEnable = VK_FALSE, 250 | .rasterizerDiscardEnable = VK_FALSE, 251 | .polygonMode = VK_POLYGON_MODE_FILL, 252 | .lineWidth = 1.0f, 253 | 254 | .cullMode = VK_CULL_MODE_BACK_BIT, 255 | .frontFace = VK_FRONT_FACE_CLOCKWISE, 256 | 257 | .depthBiasEnable = VK_FALSE, 258 | .depthBiasConstantFactor = 0.0f, 259 | .depthBiasClamp = 0.0f, 260 | .depthBiasSlopeFactor = 0.0f 261 | }; 262 | 263 | /* configure multisampling */ 264 | VkPipelineMultisampleStateCreateInfo multisampling = { 265 | .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, 266 | .sampleShadingEnable = VK_FALSE, 267 | .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT, 268 | .minSampleShading = 1.0f, 269 | .pSampleMask = NULL, 270 | .alphaToCoverageEnable = VK_FALSE, 271 | .alphaToOneEnable = VK_FALSE 272 | }; 273 | 274 | /* color blending */ 275 | VkPipelineColorBlendAttachmentState color_blend_attachment = { 276 | .colorWriteMask = 277 | VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT 278 | | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT, 279 | .blendEnable = VK_FALSE, 280 | .srcColorBlendFactor = VK_BLEND_FACTOR_ONE, 281 | .dstColorBlendFactor = VK_BLEND_FACTOR_ZERO, 282 | .colorBlendOp = VK_BLEND_OP_ADD, 283 | .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE, 284 | .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, 285 | .alphaBlendOp = VK_BLEND_OP_ADD 286 | }; 287 | 288 | VkPipelineColorBlendStateCreateInfo color_blending_info = { 289 | .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, 290 | .logicOpEnable = VK_FALSE, 291 | .logicOp = VK_LOGIC_OP_COPY, 292 | .attachmentCount = 1, 293 | .pAttachments = &color_blend_attachment, 294 | .blendConstants[0] = 0.0f, 295 | .blendConstants[1] = 0.0f, 296 | .blendConstants[2] = 0.0f, 297 | .blendConstants[3] = 0.0f, 298 | }; 299 | 300 | vk.DestroyPipelineLayout (objs->device, 301 | state->pipeline_layout, 302 | allocator); 303 | 304 | /* pipeline layout */ 305 | VkPipelineLayout pipeline_layout = VK_NULL_HANDLE; 306 | VkPipelineLayoutCreateInfo pipeline_layout_info = { 307 | .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, 308 | .setLayoutCount = 0, 309 | .pSetLayouts = NULL, 310 | .pushConstantRangeCount = 0, 311 | .pPushConstantRanges = 0 312 | }; 313 | if (vk.CreatePipelineLayout (objs->device, 314 | &pipeline_layout_info, 315 | NULL, 316 | &pipeline_layout) != VK_SUCCESS) { 317 | printf ("Error: Failed to create a pipeline layout\n"); 318 | return false; 319 | } 320 | state->pipeline_layout = pipeline_layout; 321 | printf ("Pipeline layout created\n"); 322 | 323 | /* the graphics pipeline */ 324 | VkPipeline pipeline = VK_NULL_HANDLE; 325 | VkGraphicsPipelineCreateInfo pipeline_info = { 326 | .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, 327 | .stageCount = 2, 328 | .pStages = objs->shader_stages, 329 | .pVertexInputState = &vertex_input_info, 330 | .pInputAssemblyState = &input_assembly_info, 331 | .pViewportState = &viewport_state_info, 332 | .pRasterizationState = &rasterizer, 333 | .pMultisampleState = &multisampling, 334 | .pDepthStencilState = NULL, 335 | .pColorBlendState = &color_blending_info, 336 | .pDynamicState = NULL, 337 | .layout = pipeline_layout, 338 | .renderPass = state->renderpass, 339 | .subpass = 0, 340 | .basePipelineHandle = VK_NULL_HANDLE, 341 | .basePipelineIndex = -1 342 | }; 343 | if (vk.CreateGraphicsPipelines (objs->device, 344 | VK_NULL_HANDLE, 345 | 1, 346 | &pipeline_info, 347 | allocator, 348 | &pipeline) != VK_SUCCESS) { 349 | printf ("Error: Failed to create the graphics pipeline\n"); 350 | return false; 351 | } 352 | state->pipeline = pipeline; 353 | printf ("Graphics pipeline created\n"); 354 | 355 | return true; 356 | } 357 | 358 | static bool 359 | create_command_buffers (struct vk_objects* objs, 360 | struct vk_config* config, 361 | struct vk_state* state) 362 | { 363 | assert (objs->device != VK_NULL_HANDLE); 364 | assert (objs->cmd_pool != VK_NULL_HANDLE); 365 | assert (state->renderpass != VK_NULL_HANDLE); 366 | 367 | /* create command buffers */ 368 | VkCommandBufferAllocateInfo cmd_buffer_alloc_info = { 369 | .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, 370 | .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, 371 | .commandBufferCount = state->swapchain_images_count, 372 | .commandPool = objs->cmd_pool 373 | }; 374 | if (vk.AllocateCommandBuffers (objs->device, 375 | &cmd_buffer_alloc_info, 376 | state->cmd_buffers) != VK_SUCCESS) { 377 | printf ("Error: Failed to allocate command buffers\n"); 378 | return false; 379 | } 380 | printf ("Command buffers allocated\n"); 381 | 382 | /* start recording to command buffers */ 383 | for (uint32_t i = 0; i < state->swapchain_images_count; i++) { 384 | assert (state->framebuffers[i] != VK_NULL_HANDLE); 385 | 386 | VkCommandBufferBeginInfo begin_info = { 387 | .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, 388 | .flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT, 389 | .pInheritanceInfo = NULL 390 | }; 391 | 392 | if (vk.BeginCommandBuffer (state->cmd_buffers[i], 393 | &begin_info) != VK_SUCCESS) { 394 | printf ("Error: Failed to begin recording of command buffer\n"); 395 | return false; 396 | } 397 | 398 | /* start a render pass */ 399 | VkClearValue clear_color = {{{0.01f, 0.01f, 0.01f, 1.0f}}}; 400 | VkOffset2D swapchain_offset = {0, 0}; 401 | VkRenderPassBeginInfo renderpass_begin_info = { 402 | .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, 403 | .renderPass = state->renderpass, 404 | .framebuffer = state->framebuffers[i], 405 | .renderArea.offset = swapchain_offset, 406 | .renderArea.extent = state->surface_extent, 407 | .clearValueCount = 1, 408 | .pClearValues = &clear_color 409 | }; 410 | vk.CmdBeginRenderPass (state->cmd_buffers[i], 411 | &renderpass_begin_info, 412 | VK_SUBPASS_CONTENTS_INLINE); 413 | 414 | vk.CmdBindPipeline (state->cmd_buffers[i], 415 | VK_PIPELINE_BIND_POINT_GRAPHICS, 416 | state->pipeline); 417 | 418 | vk.CmdDraw (state->cmd_buffers[i], 3, 1, 0, 0); 419 | 420 | vk.CmdEndRenderPass (state->cmd_buffers[i]); 421 | 422 | vk.EndCommandBuffer (state->cmd_buffers[i]); 423 | } 424 | printf ("Render pass commands recorded in buffer\n"); 425 | 426 | return true; 427 | } 428 | 429 | static bool 430 | recreate_swapchain (struct vk_objects* objs, 431 | struct vk_config* config, 432 | struct vk_state* state) 433 | { 434 | uint32_t width, height; 435 | 436 | assert (objs->physical_device != VK_NULL_HANDLE); 437 | assert (objs->device != VK_NULL_HANDLE); 438 | assert (objs->surface != VK_NULL_HANDLE); 439 | 440 | /* wait for all async ops on device */ 441 | vk.DeviceWaitIdle (objs->device); 442 | 443 | /* resolve swap image size */ 444 | VkSurfaceCapabilitiesKHR surface_caps; 445 | vk.GetPhysicalDeviceSurfaceCapabilitiesKHR (objs->physical_device, 446 | objs->surface, 447 | &surface_caps); 448 | config->surface_caps = surface_caps; 449 | printf ("Surface's image count (min, max): (%u, %u)\n", 450 | surface_caps.minImageCount, 451 | surface_caps.maxImageCount); 452 | printf ("Surface's current extent (width, height): (%u, %u)\n", 453 | surface_caps.currentExtent.width, 454 | surface_caps.currentExtent.height); 455 | 456 | width = surface_caps.currentExtent.width; 457 | height = surface_caps.currentExtent.height; 458 | 459 | state->surface_extent.width = width; 460 | state->surface_extent.height = height; 461 | 462 | /* destroy previous swapchain */ 463 | if (state->swapchain != VK_NULL_HANDLE) { 464 | /* keep record of the previous swapchain to link it to the new one */ 465 | if (state->previous_swapchain != VK_NULL_HANDLE) 466 | vk.DestroySwapchainKHR (objs->device, 467 | state->previous_swapchain, 468 | allocator); 469 | state->previous_swapchain = state->swapchain; 470 | 471 | state->swapchain = VK_NULL_HANDLE; 472 | } 473 | 474 | /* create a swapchain */ 475 | VkSwapchainKHR swapchain = VK_NULL_HANDLE; 476 | VkExtent2D swapchain_extent = {width, height}; 477 | 478 | VkSwapchainCreateInfoKHR swapchain_info = { 479 | .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, 480 | .surface = objs->surface, 481 | .minImageCount = config->surface_caps.minImageCount, 482 | .imageFormat = config->surface_format.format, 483 | .imageColorSpace = config->surface_format.colorSpace, 484 | .imageExtent = swapchain_extent, 485 | .imageArrayLayers = 1, 486 | .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, 487 | .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, 488 | .queueFamilyIndexCount = 0, 489 | .pQueueFamilyIndices = NULL, 490 | .preTransform = config->surface_caps.currentTransform, 491 | .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, 492 | .presentMode = config->present_mode, 493 | .clipped = VK_TRUE, 494 | .oldSwapchain = state->previous_swapchain 495 | }; 496 | 497 | if (vk.CreateSwapchainKHR (objs->device, 498 | &swapchain_info, 499 | allocator, 500 | &swapchain) != VK_SUCCESS) { 501 | printf ("Error: Failed to create a swap chain\n"); 502 | return false; 503 | } 504 | state->swapchain = swapchain; 505 | printf ("Swap chain created\n"); 506 | 507 | /* get the images from the swap chain */ 508 | uint32_t swapchain_images_count = 0; 509 | if (vk.GetSwapchainImagesKHR (objs->device, 510 | state->swapchain, 511 | &swapchain_images_count, 512 | NULL) != VK_SUCCESS) { 513 | printf ("Error: Failed to get the images from the swap chain\n"); 514 | return false; 515 | } 516 | if (swapchain_images_count > MAX_SWAPCHAIN_IMAGES) { 517 | printf ("Too many images in the swapchain. I can handle only %u\n", 518 | swapchain_images_count); 519 | return false; 520 | } 521 | uint32_t old_swapchain_images_count = state->swapchain_images_count; 522 | state->swapchain_images_count = swapchain_images_count; 523 | printf ("%u images in the swap chain\n", swapchain_images_count); 524 | 525 | VkImage swapchain_images[MAX_SWAPCHAIN_IMAGES] = {VK_NULL_HANDLE,}; 526 | vk.GetSwapchainImagesKHR (objs->device, 527 | state->swapchain, 528 | &swapchain_images_count, 529 | swapchain_images); 530 | 531 | /* destroy previous image views */ 532 | for (uint32_t i = 0; i < old_swapchain_images_count; i++) { 533 | if (state->image_views[i] != VK_NULL_HANDLE) { 534 | vk.DestroyImageView (objs->device, 535 | state->image_views[i], 536 | allocator); 537 | state->image_views[i] = VK_NULL_HANDLE; 538 | } 539 | } 540 | 541 | /* create an image view for each swapchain image */ 542 | VkImageView image_views[8] = {VK_NULL_HANDLE,}; 543 | for (uint32_t i = 0; i < swapchain_images_count; i++) { 544 | VkImageViewCreateInfo image_view_info = { 545 | .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, 546 | .image = swapchain_images[i], 547 | .viewType = VK_IMAGE_VIEW_TYPE_2D, 548 | .format = config->surface_format.format, 549 | .components.r = VK_COMPONENT_SWIZZLE_IDENTITY, 550 | .components.g = VK_COMPONENT_SWIZZLE_IDENTITY, 551 | .components.b = VK_COMPONENT_SWIZZLE_IDENTITY, 552 | .components.a = VK_COMPONENT_SWIZZLE_IDENTITY, 553 | .subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, 554 | .subresourceRange.baseMipLevel = 0, 555 | .subresourceRange.levelCount = 1, 556 | .subresourceRange.baseArrayLayer = 0, 557 | .subresourceRange.layerCount = 1, 558 | }; 559 | 560 | image_views[i] = VK_NULL_HANDLE; 561 | if (vk.CreateImageView (objs->device, 562 | &image_view_info, 563 | allocator, 564 | &image_views[i]) != VK_SUCCESS) { 565 | printf ("Error: Failed to create image view\n"); 566 | return false; 567 | } 568 | state->image_views[i] = image_views[i]; 569 | } 570 | printf ("Image views created\n"); 571 | 572 | /* create a new renderpass */ 573 | if (state->renderpass != VK_NULL_HANDLE) 574 | vk.DestroyRenderPass (objs->device, state->renderpass, allocator); 575 | if (! create_renderpass (objs, config, state)) 576 | return false; 577 | 578 | /* destroy any previous framebuffers */ 579 | for (uint32_t i = 0; i < old_swapchain_images_count; i++) { 580 | if (state->framebuffers[i] != VK_NULL_HANDLE) { 581 | vk.DestroyFramebuffer (objs->device, 582 | state->framebuffers[i], 583 | allocator); 584 | state->framebuffers[i] = VK_NULL_HANDLE; 585 | } 586 | } 587 | 588 | /* create framebuffers for each image view */ 589 | VkFramebuffer framebuffers[MAX_SWAPCHAIN_IMAGES] = {VK_NULL_HANDLE,}; 590 | for (uint32_t i = 0; i < swapchain_images_count; i++) { 591 | VkImageView attachments[] = { 592 | state->image_views[i] 593 | }; 594 | 595 | VkFramebufferCreateInfo framebuffer_info = { 596 | .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, 597 | .renderPass = state->renderpass, 598 | .attachmentCount = 1, 599 | .pAttachments = attachments, 600 | .width = swapchain_extent.width, 601 | .height = swapchain_extent.height, 602 | .layers = 1 603 | }; 604 | 605 | if (vk.CreateFramebuffer (objs->device, 606 | &framebuffer_info, 607 | allocator, 608 | &framebuffers[i]) != VK_SUCCESS) { 609 | printf ("Error: Failed to create a framebuffer\n"); 610 | return false; 611 | } 612 | 613 | state->framebuffers[i] = framebuffers[i]; 614 | } 615 | printf ("Framebuffers created\n"); 616 | 617 | /* destroy any previous pipeline */ 618 | if (state->pipeline != VK_NULL_HANDLE) 619 | vk.DestroyPipeline (objs->device, state->pipeline, allocator); 620 | 621 | /* create a new pipeline */ 622 | if (! create_pipeline (objs, config, state)) 623 | return false; 624 | 625 | /* free any previous command buffers */ 626 | vk.FreeCommandBuffers (objs->device, 627 | objs->cmd_pool, 628 | old_swapchain_images_count, 629 | state->cmd_buffers); 630 | 631 | /* create new command buffers */ 632 | if (! create_command_buffers (objs, config, state)) 633 | return false; 634 | 635 | return true; 636 | } 637 | 638 | static bool 639 | draw_frame (struct vk_objects* objs, struct vk_state* state) 640 | { 641 | VkResult result; 642 | 643 | /* acquire swapchain's next image */ 644 | uint32_t image_index; 645 | result = vk.AcquireNextImageKHR (objs->device, 646 | state->swapchain, 647 | 1000000, 648 | objs->image_available_semaphore, 649 | VK_NULL_HANDLE, 650 | &image_index); 651 | if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { 652 | expose = true; 653 | return true; 654 | } else if (result != VK_SUCCESS) { 655 | printf ("Error: Failed to acquire next image from swap chain\n"); 656 | return false; 657 | } 658 | 659 | /* submit graphics queue */ 660 | VkSemaphore wait_semaphores[] = {objs->image_available_semaphore}; 661 | VkSemaphore signal_semaphores[] = {objs->render_finished_semaphore}; 662 | 663 | VkPipelineStageFlags wait_stages[] = 664 | {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; 665 | 666 | VkSubmitInfo submit_info = { 667 | .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, 668 | .waitSemaphoreCount = 1, 669 | .pWaitSemaphores = wait_semaphores, 670 | .pWaitDstStageMask = wait_stages, 671 | .commandBufferCount = 1, 672 | .pCommandBuffers = &state->cmd_buffers[image_index], 673 | .signalSemaphoreCount = 1, 674 | .pSignalSemaphores = signal_semaphores 675 | }; 676 | 677 | if (vk.QueueSubmit (objs->graphics_queue, 678 | 1, 679 | &submit_info, 680 | VK_NULL_HANDLE) != VK_SUCCESS) { 681 | printf ("Error: Failed to submit queue\n"); 682 | return false; 683 | } 684 | 685 | /* present the frame */ 686 | VkSwapchainKHR swapchains[] = {state->swapchain}; 687 | VkPresentInfoKHR present_info = { 688 | .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, 689 | .waitSemaphoreCount = 1, 690 | .pWaitSemaphores = signal_semaphores, 691 | .swapchainCount = 1, 692 | .pSwapchains = swapchains, 693 | .pImageIndices = &image_index, 694 | .pResults = NULL 695 | }; 696 | 697 | result = vk.QueuePresentKHR (objs->graphics_queue, &present_info); 698 | if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { 699 | expose = true; 700 | return true; 701 | } else if (result != VK_SUCCESS) { 702 | printf ("Error: Failed to present the queue\n"); 703 | return false; 704 | } 705 | 706 | printf ("Frame!\n"); 707 | 708 | return true; 709 | } 710 | 711 | static void 712 | wsi_on_expose (void) 713 | { 714 | expose = true; 715 | damaged = true; 716 | } 717 | 718 | int32_t 719 | main (int32_t argc, char* argv[]) 720 | { 721 | /* XCB setup */ 722 | /* ======================================================================= */ 723 | wsi_init (NULL, WIDTH, HEIGHT, wsi_on_expose); 724 | 725 | /* Vulkan setup */ 726 | /* ======================================================================= */ 727 | 728 | /* load API entry points from ICD */ 729 | vk_api_load_from_icd (&vk); 730 | 731 | /* enummerate available layers */ 732 | uint32_t layers_count; 733 | printf ("%p\n", &vk); 734 | vk.EnumerateInstanceLayerProperties (&layers_count, NULL); 735 | printf ("Found %u instance layers\n", layers_count); 736 | 737 | if (layers_count > 0) { 738 | VkLayerProperties available_layers[16]; 739 | vk.EnumerateInstanceLayerProperties (&layers_count, available_layers); 740 | } 741 | 742 | /* enummerate supported instance extensions */ 743 | VkExtensionProperties ext_props[16]; 744 | uint32_t ext_props_count = 16; 745 | if (vk.EnumerateInstanceExtensionProperties (NULL, 746 | &ext_props_count, 747 | ext_props) != VK_SUCCESS) { 748 | printf ("Error: Failed to enummerate instance extension properties\n"); 749 | return -1; 750 | } 751 | printf ("Instance extensions: \n"); 752 | for (unsigned i = 0; i < ext_props_count; i++) 753 | printf (" %s(%u)\n", 754 | ext_props[i].extensionName, 755 | ext_props[i].specVersion); 756 | 757 | /* the memory allocation callbacks (default by now) */ 758 | allocator = VK_NULL_HANDLE; 759 | 760 | /* Vulkan application info */ 761 | VkApplicationInfo app_info = { 762 | .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, 763 | .pApplicationName = "Vulkan triangle example", 764 | .applicationVersion = 0, 765 | .apiVersion = VK_API_VERSION_1_0 766 | }; 767 | 768 | /* create Vulkan instance */ 769 | VkInstance instance = VK_NULL_HANDLE; 770 | 771 | const char* enabled_extensions[2] = { 772 | VK_KHR_SURFACE_EXTENSION_NAME, 773 | VK_KHR_XCB_SURFACE_EXTENSION_NAME 774 | }; 775 | 776 | VkInstanceCreateInfo instance_info = { 777 | .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, 778 | .pApplicationInfo = &app_info, 779 | .enabledExtensionCount = 2, 780 | .ppEnabledExtensionNames = enabled_extensions, 781 | }; 782 | if (vk.CreateInstance (&instance_info, 783 | allocator, 784 | &instance) != VK_SUCCESS) { 785 | printf ("Error: Failed to create Vulkan instance\n"); 786 | return -1; 787 | } 788 | printf ("Vulkan instance created\n"); 789 | 790 | /* load instance-dependent API entry points */ 791 | vk_api_load_from_instance (&vk, &instance); 792 | 793 | /* query physical devices */ 794 | uint32_t num_devices = 5; 795 | VkPhysicalDevice devices[5] = {0}; 796 | if (vk.EnumeratePhysicalDevices (instance, 797 | &num_devices, 798 | devices) != VK_SUCCESS) { 799 | printf ("Error: Failed to enummerate Vulkan physical devices\n"); 800 | return -1; 801 | } 802 | printf ("Found %u physical devices\n", num_devices); 803 | if (num_devices == 0) 804 | goto free_stuff; 805 | VkPhysicalDevice physical_device = devices[0]; 806 | objs.physical_device = physical_device; 807 | 808 | /* physical device properties (informative only) */ 809 | VkPhysicalDeviceProperties physical_device_props; 810 | vk.GetPhysicalDeviceProperties (physical_device, &physical_device_props); 811 | printf ("Physical device: %s\n", physical_device_props.deviceName); 812 | 813 | /* query physical device's queue families */ 814 | #define NUM_QUEUE_FAMILIES 5 815 | uint32_t num_queue_families = NUM_QUEUE_FAMILIES; 816 | VkQueueFamilyProperties queue_families[NUM_QUEUE_FAMILIES] = {0}; 817 | /* get queue families with NULL first, to retrieve count */ 818 | vk.GetPhysicalDeviceQueueFamilyProperties (physical_device, 819 | &num_queue_families, 820 | NULL); 821 | vk.GetPhysicalDeviceQueueFamilyProperties (devices[0], 822 | &num_queue_families, 823 | queue_families); 824 | assert (num_queue_families >= 1); 825 | printf ("Physical device has %u queue families\n", num_queue_families); 826 | for (unsigned i = 0; i < num_queue_families; i++) { 827 | printf ("Queue family index: %u, flags: %u, count: %u\n", 828 | i, 829 | queue_families[i].queueFlags, 830 | queue_families[i].queueCount); 831 | } 832 | uint32_t queue_family_index = 0; 833 | 834 | /* Enummerate supported device extensions */ 835 | ext_props_count = 16; 836 | if (vk.EnumerateDeviceExtensionProperties (physical_device, 837 | NULL, 838 | &ext_props_count, 839 | ext_props) != VK_SUCCESS) { 840 | printf ("Error: Failed to enummerate device extension properties\n"); 841 | return -1; 842 | } 843 | printf ("Device extensions: \n"); 844 | for (unsigned i = 0; i < ext_props_count; i++) 845 | printf (" %s(%u)\n", 846 | ext_props[i].extensionName, 847 | ext_props[i].specVersion); 848 | 849 | /* create a vulkan surface, from the XCB window (VkSurfaceKHR) */ 850 | xcb_window_t* xcb_win = 0; 851 | xcb_connection_t* xcb_conn = NULL; 852 | wsi_get_connection_and_window ((const void**) &xcb_conn, 853 | (const void**) &xcb_win); 854 | 855 | VkSurfaceKHR surface = VK_NULL_HANDLE; 856 | VkXcbSurfaceCreateInfoKHR surface_info = { 857 | .sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR, 858 | .connection = xcb_conn, 859 | .window = *xcb_win, 860 | }; 861 | if (vk.CreateXcbSurfaceKHR (instance, 862 | &surface_info, 863 | allocator, 864 | &surface) != VK_SUCCESS) { 865 | printf ("Error: Failed to create a vulkan surface from an XCB window\n "); 866 | goto free_stuff; 867 | } 868 | objs.surface = surface; 869 | printf ("Vulkan surface created from the XCB window\n"); 870 | 871 | /* check for present support in the selected queue family */ 872 | VkBool32 support_present = VK_FALSE; 873 | vk.GetPhysicalDeviceSurfaceSupportKHR (physical_device, 874 | queue_family_index, 875 | surface, 876 | &support_present); 877 | if (support_present) { 878 | printf ("Queue family supports presentation\n"); 879 | } else { 880 | printf ("Queue family doesn't support presentation\n"); 881 | goto free_stuff; 882 | } 883 | 884 | /* create logical device */ 885 | VkDevice device = VK_NULL_HANDLE; 886 | 887 | const float queue_priorities = 1.0; 888 | VkDeviceQueueCreateInfo queue_info = { 889 | .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, 890 | .flags = 0, 891 | .queueCount = 1, 892 | .pQueuePriorities = &queue_priorities, 893 | .queueFamilyIndex = queue_family_index 894 | }; 895 | 896 | const char* device_extensions[1] = {VK_KHR_SWAPCHAIN_EXTENSION_NAME}; 897 | 898 | VkDeviceCreateInfo device_info = { 899 | .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, 900 | .pQueueCreateInfos = &queue_info, 901 | .queueCreateInfoCount = 1, 902 | .enabledExtensionCount = 1, 903 | .ppEnabledExtensionNames = device_extensions, 904 | }; 905 | if (vk.CreateDevice (devices[0], 906 | &device_info, 907 | allocator, 908 | &device) != VK_SUCCESS) { 909 | printf ("Error: Failed to create Vulkan device\n"); 910 | goto free_stuff; 911 | } 912 | objs.device = device; 913 | printf ("Logical device created\n"); 914 | 915 | /* choose a surface format */ 916 | uint32_t surface_formats_count = 0; 917 | vk.GetPhysicalDeviceSurfaceFormatsKHR (physical_device, 918 | surface, 919 | &surface_formats_count, 920 | NULL); 921 | printf ("Found %u surface format(s). Choosing first.\n", surface_formats_count); 922 | if (surface_formats_count == 0) { 923 | printf ("Error: No suitable surface format found\n"); 924 | goto free_stuff; 925 | } 926 | surface_formats_count = 1; 927 | VkSurfaceFormatKHR surface_format; 928 | vk.GetPhysicalDeviceSurfaceFormatsKHR (physical_device, 929 | surface, 930 | &surface_formats_count, 931 | &surface_format); 932 | config.surface_format = surface_format; 933 | 934 | /* choose a present mode */ 935 | uint32_t present_mode_count = 0; 936 | vk.GetPhysicalDeviceSurfacePresentModesKHR (physical_device, 937 | surface, 938 | &present_mode_count, 939 | NULL); 940 | printf ("Found %u present mode(s). Choosing first.\n", present_mode_count); 941 | if (present_mode_count == 0) { 942 | printf ("Error: No suitable present modes found\n"); 943 | goto free_stuff; 944 | } 945 | present_mode_count = 1; 946 | VkPresentModeKHR present_mode; 947 | vk.GetPhysicalDeviceSurfacePresentModesKHR (physical_device, 948 | surface, 949 | &present_mode_count, 950 | &present_mode); 951 | config.present_mode = present_mode; 952 | 953 | /* load device-dependent API entry points */ 954 | vk_api_load_from_device (&vk, &device); 955 | 956 | /* create the vertex shader module */ 957 | size_t shader_code_size; 958 | uint32_t* shader_code = load_file (CURRENT_DIR "/vert.spv", 959 | &shader_code_size); 960 | if (shader_code == NULL) { 961 | printf ("Error: Failed to load vertex shader code from 'vert.spv'\n"); 962 | goto free_stuff; 963 | } 964 | 965 | VkShaderModuleCreateInfo shader_info = { 966 | .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, 967 | .codeSize = shader_code_size, 968 | .pCode = shader_code, 969 | }; 970 | VkShaderModule vert_shader_module; 971 | if (vk.CreateShaderModule (device, 972 | &shader_info, 973 | allocator, 974 | &vert_shader_module) != VK_SUCCESS) { 975 | printf ("Error: Failed to create vertex shader module\n"); 976 | goto free_stuff; 977 | } 978 | free (shader_code); 979 | printf ("Vertex shader created\n"); 980 | 981 | /* create the fragment shader module */ 982 | shader_code = load_file (CURRENT_DIR "/frag.spv", 983 | &shader_code_size); 984 | if (shader_code == NULL) { 985 | printf ("Error: Failed to load fragment shader code from 'frag.spv'\n"); 986 | goto free_stuff; 987 | } 988 | 989 | shader_info.codeSize = shader_code_size; 990 | shader_info.pCode = shader_code; 991 | VkShaderModule frag_shader_module; 992 | if (vk.CreateShaderModule (device, 993 | &shader_info, 994 | allocator, 995 | &frag_shader_module) != VK_SUCCESS) { 996 | printf ("Error: Failed to create fragment shader module\n"); 997 | goto free_stuff; 998 | } 999 | free (shader_code); 1000 | printf ("Fragment shader created\n"); 1001 | 1002 | /* create the shader stages */ 1003 | VkPipelineShaderStageCreateInfo vert_stage_info = { 1004 | .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, 1005 | .stage = VK_SHADER_STAGE_VERTEX_BIT, 1006 | .module = vert_shader_module, 1007 | .pName = "main" 1008 | }; 1009 | 1010 | VkPipelineShaderStageCreateInfo frag_stage_info = { 1011 | .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, 1012 | .stage = VK_SHADER_STAGE_FRAGMENT_BIT, 1013 | .module = frag_shader_module, 1014 | .pName = "main" 1015 | }; 1016 | 1017 | objs.shader_stages[0] = vert_stage_info; 1018 | objs.shader_stages[1] = frag_stage_info; 1019 | 1020 | /* get first device queue */ 1021 | VkQueue queue = VK_NULL_HANDLE; 1022 | vk.GetDeviceQueue (device, queue_family_index, 0, &queue); 1023 | if (queue == NULL) { 1024 | printf ("Error: Failed to get a device queue\n"); 1025 | goto free_stuff; 1026 | } 1027 | objs.graphics_queue = queue; 1028 | printf ("Got a device queue\n"); 1029 | 1030 | /* create command pool */ 1031 | VkCommandPool cmd_pool = VK_NULL_HANDLE;; 1032 | VkCommandPoolCreateInfo cmd_pool_info = { 1033 | .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, 1034 | .flags = 0, 1035 | .queueFamilyIndex = queue_family_index, 1036 | }; 1037 | if (vk.CreateCommandPool (device, 1038 | &cmd_pool_info, 1039 | allocator, 1040 | &cmd_pool) != VK_SUCCESS) { 1041 | printf ("Error: Failed to create Vulkan device\n"); 1042 | goto free_stuff; 1043 | } 1044 | objs.cmd_pool = cmd_pool; 1045 | printf ("Command pool created\n"); 1046 | 1047 | /* create semaphores */ 1048 | VkSemaphore image_available_semaphore = VK_NULL_HANDLE; 1049 | VkSemaphore render_finished_semaphore = VK_NULL_HANDLE; 1050 | 1051 | VkSemaphoreCreateInfo semaphore_info = { 1052 | .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO \ 1053 | }; 1054 | if (vk.CreateSemaphore (device, 1055 | &semaphore_info, 1056 | allocator, 1057 | &image_available_semaphore) != VK_SUCCESS || 1058 | vk.CreateSemaphore (device, 1059 | &semaphore_info, 1060 | allocator, 1061 | &render_finished_semaphore) != VK_SUCCESS) { 1062 | printf ("Error: Failed to create semaphores\n"); 1063 | goto free_stuff; 1064 | } 1065 | objs.image_available_semaphore = image_available_semaphore; 1066 | objs.image_available_semaphore = image_available_semaphore; 1067 | printf ("Semaphores create\n"); 1068 | 1069 | /* create the first swapchain */ 1070 | if (! recreate_swapchain (&objs, &config, &state)) { 1071 | printf ("Error: Failed to create a swap chain\n"); 1072 | goto free_stuff; 1073 | } 1074 | 1075 | /* start the show */ 1076 | signal (SIGINT, ctrl_c_handler); 1077 | 1078 | running = true; 1079 | damaged = true; 1080 | expose = true; 1081 | 1082 | /* Map the window onto the screen */ 1083 | wsi_window_show (); 1084 | 1085 | while (running) { 1086 | if (! damaged && ! expose) { 1087 | if (! wsi_wait_for_events ()) 1088 | break; 1089 | } 1090 | 1091 | if (expose) { 1092 | if (! recreate_swapchain (&objs, &config, &state)) { 1093 | printf ("Error: Failed to create a swap chain\n"); 1094 | break; 1095 | } 1096 | expose = false; 1097 | damaged = true; 1098 | } 1099 | 1100 | if (damaged) { 1101 | if (! draw_frame (&objs, &state)) 1102 | break; 1103 | damaged = false; 1104 | } 1105 | } 1106 | printf ("Main-loop ended\n"); 1107 | 1108 | free_stuff: 1109 | /* free all allocated objects, in the right order */ 1110 | 1111 | /* wait for all async ops on device */ 1112 | if (device != VK_NULL_HANDLE) 1113 | vk.DeviceWaitIdle (device); 1114 | 1115 | /* destroy state */ 1116 | 1117 | /* command buffers are implicitly freed when the command pool is 1118 | * destroyed. 1119 | */ 1120 | 1121 | vk.DestroyPipeline (device, state.pipeline, allocator); 1122 | vk.DestroyPipelineLayout (device, state.pipeline_layout, allocator); 1123 | 1124 | for (uint32_t i = 0; i < state.swapchain_images_count; i++) 1125 | vk.DestroyFramebuffer (device, state.framebuffers[i], allocator); 1126 | 1127 | for (uint32_t i = 0; i < state.swapchain_images_count; i++) 1128 | vk.DestroyImageView (device, state.image_views[i], allocator); 1129 | 1130 | vk.DestroyRenderPass (device, state.renderpass, allocator); 1131 | vk.DestroySwapchainKHR (device, state.previous_swapchain, allocator); 1132 | vk.DestroySwapchainKHR (device, state.swapchain, allocator); 1133 | 1134 | /* destroy immutable objects */ 1135 | vk.DestroySemaphore (device, image_available_semaphore, allocator); 1136 | vk.DestroySemaphore (device, render_finished_semaphore, allocator); 1137 | vk.DestroyCommandPool (device, cmd_pool, allocator); 1138 | vk.DestroyShaderModule (device, vert_shader_module, allocator); 1139 | vk.DestroyShaderModule (device, frag_shader_module, allocator); 1140 | vk.DestroyDevice (device, allocator); 1141 | vk.DestroySurfaceKHR (instance, surface, allocator); 1142 | vk.DestroyInstance (instance, allocator); 1143 | 1144 | /* teardown WSI */ 1145 | wsi_finish (); 1146 | 1147 | printf ("Clean exit\n"); 1148 | 1149 | return 0; 1150 | } 1151 | -------------------------------------------------------------------------------- /vulkan-triangle/shader.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(location = 0) out highp vec4 outColor; 5 | layout(location = 0) in vec3 color; 6 | 7 | void main() { 8 | outColor = vec4(color, 1.0); 9 | } 10 | -------------------------------------------------------------------------------- /vulkan-triangle/shader.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | out gl_PerVertex { 5 | vec4 gl_Position; 6 | }; 7 | 8 | vec2 positions[3] = vec2[]( 9 | vec2( 0.0, -0.5), 10 | vec2( 0.5, 0.5), 11 | vec2(-0.5, 0.5) 12 | ); 13 | 14 | vec3 colors[3] = vec3[] ( 15 | vec3(1.0, 0.0, 0.0), 16 | vec3(0.0, 1.0, 0.0), 17 | vec3(0.0, 0.0, 1.0) 18 | ); 19 | 20 | layout(location = 0) out vec3 color; 21 | 22 | void main() { 23 | gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); 24 | color = colors[gl_VertexIndex]; 25 | } 26 | --------------------------------------------------------------------------------