├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── README.md ├── compile_shaders.sh ├── screenshots └── screenshot.png ├── third_party └── glfw_files_here.txt └── vkstarter ├── main.cpp └── shaders ├── shader.frag └── shader.vert /.gitignore: -------------------------------------------------------------------------------- 1 | # CMake artifacts, etc. 2 | build 3 | cmake-build-debug 4 | cmake-build-release 5 | .idea 6 | 7 | # SPIR-V shaders 8 | vkstarter/shaders/*.spv 9 | 10 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "third_party/glfw"] 2 | path = third_party/glfw 3 | url = https://github.com/glfw/glfw 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 2.6) 2 | project (VkStarter) 3 | 4 | # enable C++11 5 | add_definitions(-D_CRT_SECURE_NO_WARNINGS) 6 | add_definitions(-std=c++14) 7 | set_directory_properties(PROPERTIES COMPILE_DEFINITIONS_DEBUG "_DEBUG") 8 | 9 | # include source files 10 | include_directories("${PROJECT_SOURCE_DIR}/vkstarter") 11 | file(GLOB SOURCES "vkstarter/*.cpp") 12 | 13 | # setup GLFW 14 | add_subdirectory("${PROJECT_SOURCE_DIR}/third_party/glfw") 15 | include_directories("${PROJECT_SOURCE_DIR}/third_party/glfw/include/GLFW") 16 | 17 | # create the executable 18 | add_executable(VkStarter ${SOURCES}) 19 | 20 | # add libraries (note that as of version 3.7, CMake supports Vulkan out-of-the-box) 21 | find_package(Vulkan REQUIRED) 22 | target_link_libraries(VkStarter Vulkan::Vulkan) 23 | target_link_libraries(VkStarter glfw ${GLFW_LIBRARIES}) 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vkstarter 2 | 🔨 Simple starter code for building applications with Vulkan and C++. 3 | 4 |

5 | screenshot 6 |

7 | 8 | ## Description 9 | This is meant to be a sort of "template" for creating Vulkan applications. It uses `vulkan.hpp`, which is included in LunarG's Vulkan SDK. Note that there are many checks and features that are omitted in this project that would otherwise be present in a "real" application. 10 | 11 | By default, `vkstarter` simply sets up a full-screen quad, as seen in the screenshot above. For simplicity sake, vertex positions are hard-coded in the vertex shader. Push constants are used to communicate application time and window resolution to the fragment shader stage. Window resizing (and subsequent swapchain re-creation) is also enabled by default. 12 | 13 | ## Tested On 14 | - Ubuntu 18.04 15 | - NVIDIA GeForce GTX 1070 16 | - Vulkan SDK `1.1.106.0` 17 | 18 | ## To Build 19 | 1. Clone this repo and initialize submodules (GLFW): 20 | ```shell 21 | git submodule init 22 | git submodule update 23 | ``` 24 | 3. Download the [Vulkan SDK](https://vulkan.lunarg.com/sdk/home) for your OS. Make sure the `VULKAN_SDK` environment variable is defined on your system. 25 | 4. Compile the included shader files using `glslangValidator`: 26 | ```shell 27 | sh ./compile_shaders.sh 28 | ``` 29 | 4. Finally, from the root directory, run the following commands: 30 | ```shell 31 | mkdir build 32 | cd build 33 | cmake .. 34 | make 35 | 36 | ./VkStarter 37 | ``` 38 | 39 | ### License 40 | [Creative Commons Attribution 4.0 International License](https://creativecommons.org/licenses/by/4.0/) 41 | 42 | -------------------------------------------------------------------------------- /compile_shaders.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | $VULKAN_SDK/bin/glslangValidator -V -o vkstarter/shaders/vert.spv vkstarter/shaders/shader.vert 4 | $VULKAN_SDK/bin/glslangValidator -V -o vkstarter/shaders/frag.spv vkstarter/shaders/shader.frag -------------------------------------------------------------------------------- /screenshots/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwalczyk/vkstarter/c882a46daa0de70848a41049e8fe0faaa27bddaf/screenshots/screenshot.png -------------------------------------------------------------------------------- /third_party/glfw_files_here.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwalczyk/vkstarter/c882a46daa0de70848a41049e8fe0faaa27bddaf/third_party/glfw_files_here.txt -------------------------------------------------------------------------------- /vkstarter/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #if defined(_WIN32) 8 | #define VK_USE_PLATFORM_WIN32 9 | #else 10 | #define VK_USE_PLATFORM_XCB_KHR 11 | #endif 12 | #include "vulkan/vulkan.hpp" 13 | 14 | #define NOMINMAX 15 | #include "glfw3.h" 16 | 17 | #ifdef _DEBUG 18 | #define LOG_DEBUG(x) std::cout << x << "\n" 19 | #else 20 | #define LOG_DEBUG(x) 21 | #endif 22 | 23 | struct alignas(8) PushConstants 24 | { 25 | float time; 26 | float __padding; 27 | float resolution[2]; 28 | /* Add more members here: mind the struct alignment */ 29 | }; 30 | 31 | float get_elapsed_time() 32 | { 33 | static std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); 34 | std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); 35 | 36 | auto ms = std::chrono::duration_cast(end - begin).count(); 37 | 38 | return static_cast(ms) / 1000.0f; 39 | } 40 | 41 | vk::UniqueShaderModule load_spv_into_module(const vk::UniqueDevice& device, const std::string& filename) 42 | { 43 | std::ifstream file(filename, std::ios::ate | std::ios::binary); 44 | 45 | if (!file.is_open()) 46 | { 47 | throw std::runtime_error("Failed to open file"); 48 | } 49 | 50 | size_t file_size = static_cast(file.tellg()); 51 | std::vector buffer(file_size); 52 | file.seekg(0); 53 | file.read(buffer.data(), file_size); 54 | file.close(); 55 | 56 | auto shader_module_create_info = vk::ShaderModuleCreateInfo{ {}, static_cast(buffer.size()), reinterpret_cast(buffer.data()) }; 57 | 58 | return device->createShaderModuleUnique(shader_module_create_info); 59 | } 60 | 61 | VKAPI_ATTR VkBool32 VKAPI_CALL debug_callback(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT object_type, uint64_t object, size_t location, int32_t code, const char* layer_prefix, const char* msg, void* user_data) 62 | { 63 | std::ostringstream message; 64 | 65 | if (flags & VK_DEBUG_REPORT_ERROR_BIT_EXT) 66 | { 67 | message << "ERROR: "; 68 | } 69 | else if (flags & VK_DEBUG_REPORT_WARNING_BIT_EXT) 70 | { 71 | message << "WARNING: "; 72 | } 73 | else if (flags & VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT) 74 | { 75 | message << "PERFORMANCE WARNING: "; 76 | } 77 | else if (flags & VK_DEBUG_REPORT_INFORMATION_BIT_EXT) 78 | { 79 | message << "INFO: "; 80 | } 81 | else if (flags & VK_DEBUG_REPORT_DEBUG_BIT_EXT) 82 | { 83 | message << "DEBUG: "; 84 | } 85 | message << "[" << layer_prefix << "] Code " << code << " : " << msg; 86 | std::cout << message.str() << std::endl; 87 | 88 | return VK_FALSE; 89 | } 90 | 91 | class Application 92 | { 93 | public: 94 | Application(uint32_t width, uint32_t height, const std::string& name) : 95 | width{ width }, height{ height }, name{ name } 96 | { 97 | setup(); 98 | } 99 | 100 | ~Application() 101 | { 102 | // Wait for all work on the GPU to finish 103 | device->waitIdle(); 104 | 105 | // Clean up GLFW objects 106 | glfwDestroyWindow(window); 107 | glfwTerminate(); 108 | } 109 | 110 | static void on_window_resized(GLFWwindow* window, int width, int height) 111 | { 112 | Application* app = reinterpret_cast(glfwGetWindowUserPointer(window)); 113 | app->resize(); 114 | } 115 | 116 | void resize() 117 | { 118 | device->waitIdle(); 119 | 120 | int new_width; 121 | int new_height; 122 | glfwGetWindowSize(window, &new_width, &new_height); 123 | width = new_width; 124 | height = new_height; 125 | LOG_DEBUG("Window resized to " + std::to_string(width) + " x " + std::to_string(height)); 126 | 127 | swapchain.reset(); 128 | render_pass.reset(); 129 | pipeline.reset(); 130 | 131 | // We do not need to explicitly clear the framebuffers or swapchain image views, since that is taken 132 | // care of by the `initialize_*()` methods below 133 | 134 | initialize_swapchain(); 135 | initialize_render_pass(); 136 | initialize_pipeline(); 137 | initialize_framebuffers(); 138 | } 139 | 140 | void setup() 141 | { 142 | initialize_window(); 143 | initialize_instance(); 144 | initialize_device(); 145 | initialize_surface(); 146 | initialize_swapchain(); 147 | initialize_render_pass(); 148 | initialize_pipeline(); 149 | initialize_framebuffers(); 150 | initialize_command_pool(); 151 | initialize_command_buffers(); 152 | initialize_synchronization_primitives(); 153 | } 154 | 155 | void initialize_window() 156 | { 157 | glfwInit(); 158 | glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); 159 | 160 | window = glfwCreateWindow(width, height, name.c_str(), nullptr, nullptr); 161 | 162 | glfwSetWindowUserPointer(window, this); 163 | glfwSetWindowSizeCallback(window, on_window_resized); 164 | } 165 | 166 | void initialize_instance() 167 | { 168 | std::vector layers; 169 | std::vector extensions{ VK_EXT_DEBUG_REPORT_EXTENSION_NAME }; 170 | #if _DEBUG 171 | // TODO: switch to VK_LAYER_KHRONOS_validation 172 | layers.push_back("VK_LAYER_LUNARG_standard_validation"); 173 | #endif 174 | // Add any instance-level extensions required by GLFW (based on current windowing environment, etc.) 175 | uint32_t glfw_extensions_count = 0; 176 | const char ** instance_extensions_buffer = glfwGetRequiredInstanceExtensions(&glfw_extensions_count); 177 | for(size_t i = 0; i < glfw_extensions_count; ++i) 178 | { 179 | extensions.push_back(instance_extensions_buffer[i]); 180 | std::cout << "Adding extension required by GLFW: " << instance_extensions_buffer[i] << std::endl; 181 | } 182 | 183 | auto application_info = vk::ApplicationInfo{ name.c_str(), VK_MAKE_VERSION(1, 0, 0), name.c_str(), VK_MAKE_VERSION(1, 0, 0), VK_API_VERSION_1_1 }; 184 | 185 | instance = vk::createInstanceUnique(vk::InstanceCreateInfo{ {}, &application_info, static_cast(layers.size()), layers.data(), static_cast(extensions.size()), extensions.data() }); 186 | 187 | #if _DEBUG 188 | // TODO: check out VK_EXT_DEBUG_UTILS_EXTENSION_NAME 189 | auto dynamic_dispatch_loader = vk::DispatchLoaderDynamic{ instance.get() }; 190 | auto debug_report_callback_create_info = vk::DebugReportCallbackCreateInfoEXT{ vk::DebugReportFlagBitsEXT::eError | vk::DebugReportFlagBitsEXT::eWarning, debug_callback }; 191 | 192 | debug_report_callback = instance->createDebugReportCallbackEXT(debug_report_callback_create_info, nullptr, dynamic_dispatch_loader); 193 | LOG_DEBUG("Initializing debug report callback"); 194 | #endif 195 | } 196 | 197 | void initialize_device() 198 | { 199 | // First, we select a physical device 200 | auto physical_devices = instance->enumeratePhysicalDevices(); 201 | assert(!physical_devices.empty()); 202 | physical_device = physical_devices[0]; 203 | 204 | auto queue_family_properties = physical_device.getQueueFamilyProperties(); 205 | 206 | const float priority = 0.0f; 207 | auto predicate = [](const vk::QueueFamilyProperties& item) { return item.queueFlags & vk::QueueFlagBits::eGraphics; }; 208 | auto queue_create_info = vk::DeviceQueueCreateInfo{} 209 | .setPQueuePriorities(&priority) 210 | .setQueueCount(1) 211 | .setQueueFamilyIndex(static_cast(std::distance(queue_family_properties.begin(), std::find_if(queue_family_properties.begin(), queue_family_properties.end(), predicate)))); 212 | LOG_DEBUG("Using queue family at index [ " << queue_create_info.queueFamilyIndex << " ], which supports graphics operations"); 213 | 214 | // Save the index of the chosen queue family 215 | queue_family_index = queue_create_info.queueFamilyIndex; 216 | 217 | // Then, we construct a logical device around the chosen physical device 218 | const std::vector device_extensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME }; 219 | auto device_create_info = vk::DeviceCreateInfo{} 220 | .setPQueueCreateInfos(&queue_create_info) 221 | .setQueueCreateInfoCount(1) 222 | .setPpEnabledExtensionNames(device_extensions.data()) 223 | .setEnabledExtensionCount(static_cast(device_extensions.size())); 224 | 225 | device = physical_device.createDeviceUnique(device_create_info); 226 | 227 | const uint32_t queue_index = 0; 228 | queue = device->getQueue(queue_family_index, queue_index); 229 | } 230 | 231 | void initialize_surface() 232 | { 233 | LOG_DEBUG("Trying to initialize surface..."); 234 | 235 | VkSurfaceKHR temp_surface; 236 | glfwCreateWindowSurface(instance.get(), window, nullptr, &temp_surface); 237 | 238 | surface = vk::UniqueSurfaceKHR{ temp_surface }; 239 | LOG_DEBUG("Created window surface"); 240 | } 241 | 242 | void initialize_swapchain() 243 | { 244 | surface_capabilities = physical_device.getSurfaceCapabilitiesKHR(surface.get()); 245 | surface_formats = physical_device.getSurfaceFormatsKHR(surface.get()); 246 | surface_present_modes = physical_device.getSurfacePresentModesKHR(surface.get()); 247 | auto surface_support = physical_device.getSurfaceSupportKHR(queue_family_index, surface.get()); 248 | 249 | swapchain_image_format = vk::Format::eB8G8R8A8Unorm; 250 | swapchain_extent = vk::Extent2D{ width, height }; 251 | 252 | auto swapchain_create_info = vk::SwapchainCreateInfoKHR{} 253 | .setPresentMode(vk::PresentModeKHR::eMailbox) 254 | .setImageExtent(swapchain_extent) 255 | .setImageFormat(swapchain_image_format) 256 | .setImageArrayLayers(1) 257 | .setImageUsage(vk::ImageUsageFlagBits::eColorAttachment) 258 | .setMinImageCount(surface_capabilities.minImageCount + 1) 259 | .setPreTransform(surface_capabilities.currentTransform) 260 | .setClipped(true) 261 | .setSurface(surface.get()); 262 | 263 | swapchain = device->createSwapchainKHRUnique(swapchain_create_info); 264 | 265 | // Retrieve the images from the swapchain 266 | swapchain_images = device->getSwapchainImagesKHR(swapchain.get()); 267 | LOG_DEBUG("There are [ " << swapchain_images.size() << " ] images in the swapchain"); 268 | 269 | // Create an image view for each image in the swapchain 270 | swapchain_image_views.clear(); 271 | 272 | const auto subresource_range = vk::ImageSubresourceRange{ vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 }; 273 | for (const auto& image : swapchain_images) 274 | { 275 | auto image_view_create_info = vk::ImageViewCreateInfo{ {}, image, vk::ImageViewType::e2D, swapchain_image_format, {}, subresource_range }; 276 | swapchain_image_views.push_back(device->createImageViewUnique(image_view_create_info)); 277 | } 278 | LOG_DEBUG("Created [ " << swapchain_image_views.size() << " ] image views"); 279 | } 280 | 281 | void initialize_render_pass() 282 | { 283 | auto attachment_description = vk::AttachmentDescription{} 284 | .setFormat(swapchain_image_format) 285 | .setLoadOp(vk::AttachmentLoadOp::eClear) 286 | .setStoreOp(vk::AttachmentStoreOp::eStore) 287 | .setStencilLoadOp(vk::AttachmentLoadOp::eDontCare) 288 | .setStencilStoreOp(vk::AttachmentStoreOp::eDontCare) 289 | .setFinalLayout(vk::ImageLayout::ePresentSrcKHR); 290 | 291 | const uint32_t attachment_index = 0; 292 | auto attachment_reference = vk::AttachmentReference{ attachment_index, vk::ImageLayout::eColorAttachmentOptimal }; 293 | 294 | auto subpass_description = vk::SubpassDescription{} 295 | .setPColorAttachments(&attachment_reference) 296 | .setColorAttachmentCount(1); 297 | 298 | auto subpass_dependency = vk::SubpassDependency{} 299 | .setSrcSubpass(VK_SUBPASS_EXTERNAL) 300 | .setDstSubpass(0) 301 | .setSrcStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput) 302 | .setSrcAccessMask({}) 303 | .setDstStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput) 304 | .setDstAccessMask(vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite); 305 | 306 | auto render_pass_create_info = vk::RenderPassCreateInfo{ {}, 1, &attachment_description, 1, &subpass_description, 1, &subpass_dependency }; 307 | 308 | render_pass = device->createRenderPassUnique(render_pass_create_info); 309 | } 310 | 311 | void initialize_pipeline() 312 | { 313 | // First, load the shader modules 314 | const std::string path_prefix = "../vkstarter/shaders/"; 315 | const std::string vs_spv_path = path_prefix + "vert.spv"; 316 | const std::string fs_spv_path = path_prefix + "frag.spv"; 317 | auto vs_module = load_spv_into_module(device, vs_spv_path); 318 | auto fs_module = load_spv_into_module(device, fs_spv_path); 319 | LOG_DEBUG("Successfully loaded shader modules"); 320 | 321 | // Then, create a pipeline layout 322 | auto push_constant_range = vk::PushConstantRange{ vk::ShaderStageFlagBits::eFragment, 0, sizeof(float) * 4 }; 323 | auto pipeline_layout_create_info = vk::PipelineLayoutCreateInfo{} 324 | .setPPushConstantRanges(&push_constant_range) 325 | .setPushConstantRangeCount(1); 326 | 327 | pipeline_layout = device->createPipelineLayoutUnique(pipeline_layout_create_info /* Add additional push constants or descriptor sets here */ ); 328 | 329 | // Finally, create the pipeline 330 | const char* entry_point = "main"; 331 | auto vs_stage_create_info = vk::PipelineShaderStageCreateInfo{ {}, vk::ShaderStageFlagBits::eVertex, vs_module.get(), entry_point }; 332 | auto fs_stage_create_info = vk::PipelineShaderStageCreateInfo{ {}, vk::ShaderStageFlagBits::eFragment, fs_module.get(), entry_point }; 333 | const vk::PipelineShaderStageCreateInfo shader_stage_create_infos[] = { vs_stage_create_info, fs_stage_create_info }; 334 | 335 | auto vertex_input_state_create_info = vk::PipelineVertexInputStateCreateInfo{}; 336 | 337 | auto input_assembly_create_info = vk::PipelineInputAssemblyStateCreateInfo{ {}, vk::PrimitiveTopology::eTriangleList }; 338 | 339 | const float min_depth = 0.0f; 340 | const float max_depth = 1.0f; 341 | const vk::Viewport viewport{ 0.0f, 0.0f, static_cast(width), static_cast(height), min_depth, max_depth }; 342 | const vk::Rect2D scissor{ { 0, 0 }, swapchain_extent }; 343 | auto viewport_state_create_info = vk::PipelineViewportStateCreateInfo{ {}, 1, &viewport, 1, &scissor }; 344 | 345 | auto rasterization_state_create_info = vk::PipelineRasterizationStateCreateInfo{} 346 | .setFrontFace(vk::FrontFace::eClockwise) 347 | .setCullMode(vk::CullModeFlagBits::eBack) 348 | .setLineWidth(1.0f); 349 | 350 | auto multisample_state_create_info = vk::PipelineMultisampleStateCreateInfo{}; 351 | 352 | auto color_blend_attachment_state = vk::PipelineColorBlendAttachmentState{} 353 | .setColorWriteMask(vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA); 354 | 355 | auto color_blend_state_create_info = vk::PipelineColorBlendStateCreateInfo{} 356 | .setPAttachments(&color_blend_attachment_state) 357 | .setAttachmentCount(1); 358 | 359 | auto graphics_pipeline_create_info = vk::GraphicsPipelineCreateInfo{ 360 | {}, 361 | 2, 362 | shader_stage_create_infos, 363 | &vertex_input_state_create_info, 364 | &input_assembly_create_info, 365 | nullptr, /* Add tessellation state here */ 366 | &viewport_state_create_info, 367 | &rasterization_state_create_info, 368 | &multisample_state_create_info, 369 | nullptr, /* Add depth stencil state here */ 370 | &color_blend_state_create_info, 371 | nullptr, /* Add dynamic state here */ 372 | pipeline_layout.get(), 373 | render_pass.get() 374 | }; 375 | 376 | pipeline = device->createGraphicsPipelineUnique({}, graphics_pipeline_create_info); 377 | LOG_DEBUG("Created graphics pipeline"); 378 | } 379 | 380 | void initialize_framebuffers() 381 | { 382 | framebuffers.clear(); 383 | 384 | const uint32_t framebuffer_layers = 1; 385 | for (const auto& image_view : swapchain_image_views) 386 | { 387 | auto framebuffer_create_info = vk::FramebufferCreateInfo{ {}, render_pass.get(), 1, &image_view.get(), width, height, framebuffer_layers }; 388 | framebuffers.push_back(device->createFramebufferUnique(framebuffer_create_info)); 389 | } 390 | LOG_DEBUG("Created [ " << framebuffers.size() << " ] framebuffers"); 391 | } 392 | 393 | void initialize_command_pool() 394 | { 395 | command_pool = device->createCommandPoolUnique(vk::CommandPoolCreateInfo{ vk::CommandPoolCreateFlagBits::eResetCommandBuffer, queue_family_index }); 396 | } 397 | 398 | void initialize_command_buffers() 399 | { 400 | command_buffers = device->allocateCommandBuffersUnique(vk::CommandBufferAllocateInfo{ command_pool.get(), vk::CommandBufferLevel::ePrimary, static_cast(framebuffers.size()) }); 401 | LOG_DEBUG("Allocated [ " << command_buffers.size() << " ] command buffers"); 402 | } 403 | 404 | void record_command_buffer(uint32_t index) 405 | { 406 | vk::ClearValue clear; 407 | clear.color = vk::ClearColorValue(std::array({ 0.0f, 0.0f, 0.0f, 1.0f })); 408 | 409 | const vk::Rect2D render_area{ { 0, 0 }, swapchain_extent }; 410 | 411 | PushConstants push_constants = 412 | { 413 | get_elapsed_time(), 414 | 0.0f, /* Padding */ 415 | static_cast(width), 416 | static_cast(height) 417 | }; 418 | 419 | command_buffers[index]->begin(vk::CommandBufferBeginInfo{ vk::CommandBufferUsageFlagBits::eSimultaneousUse }); 420 | command_buffers[index]->beginRenderPass(vk::RenderPassBeginInfo{ render_pass.get(), framebuffers[index].get(), render_area, 1, &clear }, vk::SubpassContents::eInline); 421 | command_buffers[index]->bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline.get()); 422 | command_buffers[index]->pushConstants(pipeline_layout.get(), vk::ShaderStageFlagBits::eFragment, 0, sizeof(push_constants), &push_constants); 423 | command_buffers[index]->draw(6, 1, 0, 0); 424 | command_buffers[index]->endRenderPass(); 425 | command_buffers[index]->end(); 426 | } 427 | 428 | void initialize_synchronization_primitives() 429 | { 430 | semaphore_image_available = device->createSemaphoreUnique({}); 431 | sempahore_render_finished = device->createSemaphoreUnique({}); 432 | 433 | for (size_t i = 0; i < command_buffers.size(); ++i) 434 | { 435 | // Create each fence in a signaled state, so that the first call to `waitForFences` in the draw loop doesn't throw any errors 436 | fences.push_back(device->createFenceUnique({ vk::FenceCreateFlagBits::eSignaled })); 437 | } 438 | } 439 | 440 | void draw() 441 | { 442 | while (!glfwWindowShouldClose(window)) 443 | { 444 | glfwPollEvents(); 445 | 446 | // Submit a command buffer after acquiring the index of the next available swapchain image 447 | auto index = device->acquireNextImageKHR(swapchain.get(), (std::numeric_limits::max)(), semaphore_image_available.get(), {}).value; 448 | 449 | // If the command buffer we want to (re)use is still pending on the GPU, wait for it then reset its fence 450 | device->waitForFences(fences[index].get(), true, (std::numeric_limits::max)()); 451 | device->resetFences(fences[index].get()); 452 | 453 | // Now, we know that we can safely (re)use this command buffer 454 | record_command_buffer(index); 455 | 456 | const vk::PipelineStageFlags wait_stages[] = { vk::PipelineStageFlagBits::eColorAttachmentOutput }; 457 | auto submit_info = vk::SubmitInfo{ 1, &semaphore_image_available.get(), wait_stages, 1, &command_buffers[index].get(), 1, &sempahore_render_finished.get() }; 458 | queue.submit(submit_info, fences[index].get()); 459 | 460 | // Present the final rendered image to the swapchain 461 | auto present_info = vk::PresentInfoKHR{ 1, &sempahore_render_finished.get(), 1, &swapchain.get(), &index }; 462 | queue.presentKHR(present_info); 463 | } 464 | } 465 | 466 | private: 467 | uint32_t width; 468 | uint32_t height; 469 | std::string name; 470 | 471 | GLFWwindow* window; 472 | 473 | vk::SurfaceCapabilitiesKHR surface_capabilities; 474 | std::vector surface_formats; 475 | std::vector surface_present_modes; 476 | 477 | vk::Format swapchain_image_format; 478 | vk::Extent2D swapchain_extent; 479 | 480 | vk::PhysicalDevice physical_device; 481 | vk::DebugReportCallbackEXT debug_report_callback; 482 | vk::Queue queue; 483 | uint32_t queue_family_index; 484 | 485 | vk::UniqueInstance instance; 486 | vk::UniqueDevice device; 487 | vk::UniqueSurfaceKHR surface; 488 | vk::UniqueSwapchainKHR swapchain; 489 | vk::UniqueRenderPass render_pass; 490 | vk::UniquePipelineLayout pipeline_layout; 491 | vk::UniquePipeline pipeline; 492 | vk::UniqueCommandPool command_pool; 493 | vk::UniqueSemaphore semaphore_image_available; 494 | vk::UniqueSemaphore sempahore_render_finished; 495 | std::vector swapchain_images; 496 | std::vector swapchain_image_views; 497 | std::vector framebuffers; 498 | std::vector command_buffers; 499 | std::vector fences; 500 | }; 501 | 502 | int main() 503 | { 504 | Application app{ 800, 600, "vkstarter" }; 505 | app.draw(); 506 | 507 | return EXIT_SUCCESS; 508 | } -------------------------------------------------------------------------------- /vkstarter/shaders/shader.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(location = 0) in vec3 color; 5 | 6 | layout(location = 0) out vec4 o_color; 7 | 8 | layout(push_constant) uniform PushConstants 9 | { 10 | float time; 11 | float padding; 12 | vec2 resolution; 13 | } push_constants; 14 | 15 | void main() 16 | { 17 | float p = push_constants.padding; 18 | 19 | float modulate = sin(push_constants.time) * 0.5 + 0.5; 20 | vec2 screen_color = gl_FragCoord.xy / push_constants.resolution; 21 | 22 | o_color = vec4(screen_color * modulate, 0.0, 1.0); 23 | } -------------------------------------------------------------------------------- /vkstarter/shaders/shader.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | out gl_PerVertex 5 | { 6 | vec4 gl_Position; 7 | }; 8 | 9 | layout(location = 0) out vec3 color; 10 | 11 | // draws a fullscreen quad 12 | const vec2 positions[6] = vec2[](vec2(-1.0, 1.0), // lower left 13 | vec2(-1.0, -1.0), // upper left 14 | vec2( 1.0, -1.0), // upper right 15 | 16 | vec2(-1.0, 1.0), // lower left 17 | vec2( 1.0, -1.0), // upper right 18 | vec2( 1.0, 1.0)); // lower right 19 | 20 | void main() 21 | { 22 | gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); 23 | } --------------------------------------------------------------------------------