├── .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 |
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 | }
--------------------------------------------------------------------------------