GCC Code Coverage Report


Directory: ./
File: src/tide/engine/vk_engine.cpp
Date: 2023-04-27 00:55:30
Exec Total Coverage
Lines: 0 696 0.0%
Functions: 0 61 0.0%
Branches: 0 890 0.0%

Line Branch Exec Source
1
2 #include "vk_engine.h"
3
4 #include <SDL.h>
5 #include <SDL_vulkan.h>
6
7 #include <vk_initializers.h>
8 #include <vk_types.h>
9
10 #include "VkBootstrap.h"
11
12 #include <fstream>
13 #include <iostream>
14
15 #include "vk_textures.h"
16
17 #define VMA_IMPLEMENTATION
18 #include "vk_mem_alloc.h"
19
20 #include "imgui_impl_sdl2.h"
21 #include "imgui_impl_vulkan.h"
22
23 constexpr bool bUseValidationLayers = true;
24
25 VulkanEngine* _engine = nullptr;
26
27 VulkanEngine::VulkanEngine() { _engine = this; };
28
29 VulkanEngine& VulkanEngine::instance() { return *_engine; }
30
31 // we want to immediately abort when there is an error. In normal engines this would give an error
32 // message to the user, or perform a dump of state.
33 using namespace std;
34 #define VK_CHECK(x) \
35 do { \
36 VkResult err = x; \
37 if (err) { \
38 std::cout << "Detected Vulkan error: " << err << std::endl; \
39 abort(); \
40 } \
41 } while (0)
42
43 void VulkanEngine::init() {
44 // We initialize SDL and create a window with it.
45 SDL_Init(SDL_INIT_VIDEO);
46
47 SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_VULKAN);
48
49 assert(_window == nullptr);
50 _window = SDL_CreateWindow("Vulkan Engine",
51 SDL_WINDOWPOS_UNDEFINED,
52 SDL_WINDOWPOS_UNDEFINED,
53 _windowExtent.width,
54 _windowExtent.height,
55 window_flags);
56
57 if (_window == nullptr) {
58 throw std::runtime_error(SDL_GetError());
59 }
60
61 init_vulkan();
62
63 init_swapchain();
64
65 init_default_renderpass();
66
67 init_framebuffers();
68
69 init_commands();
70
71 init_sync_structures();
72
73 init_descriptors();
74
75 init_imgui();
76
77 // init_pipelines();
78 // load_images();
79 // load_meshes();
80 // init_scene();
81
82 // everything went fine
83 _isInitialized = true;
84 }
85 void VulkanEngine::cleanup() {
86 if (_isInitialized) {
87
88 // make sure the gpu has stopped doing its things
89 vkDeviceWaitIdle(_device);
90
91 _mainDeletionQueue.flush();
92
93 vkDestroySurfaceKHR(_instance, _surface, nullptr);
94
95 vkDestroyDevice(_device, nullptr);
96 vkb::destroy_debug_utils_messenger(_instance, _debug_messenger);
97 vkDestroyInstance(_instance, nullptr);
98
99 SDL_DestroyWindow(_window);
100 }
101 }
102
103 void VulkanEngine::start() {}
104 void VulkanEngine::handle_event(SDL_Event const& event) {}
105 void VulkanEngine::tick(float dt) {}
106 void VulkanEngine::end() {}
107
108 void VulkanEngine::draw(float dt) {
109
110 // check if window is minimized and skip drawing
111 if (SDL_GetWindowFlags(_window) & SDL_WINDOW_MINIMIZED)
112 return;
113
114 ImGui_ImplVulkan_NewFrame();
115 ImGui_ImplSDL2_NewFrame(_window);
116 ImGui::NewFrame();
117
118 // wait until the gpu has finished rendering the last frame. Timeout of 1 second
119 VK_CHECK(vkWaitForFences(_device, 1, &get_current_frame()._renderFence, true, 1000000000));
120 VK_CHECK(vkResetFences(_device, 1, &get_current_frame()._renderFence));
121
122 // now that we are sure that the commands finished executing, we can safely reset the command
123 // buffer to begin recording again.
124 VK_CHECK(vkResetCommandBuffer(get_current_frame()._mainCommandBuffer, 0));
125
126 // request image from the swapchain
127 uint32_t swapchainImageIndex;
128 VK_CHECK(vkAcquireNextImageKHR(_device,
129 _swapchain,
130 1000000000,
131 get_current_frame()._presentSemaphore,
132 nullptr,
133 &swapchainImageIndex));
134
135 // naming it cmd for shorter writing
136 VkCommandBuffer cmd = get_current_frame()._mainCommandBuffer;
137
138 // begin the command buffer recording. We will use this command buffer exactly once, so we want
139 // to let vulkan know that
140 VkCommandBufferBeginInfo cmdBeginInfo =
141 vkinit::command_buffer_begin_info(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT);
142
143 VK_CHECK(vkBeginCommandBuffer(cmd, &cmdBeginInfo));
144
145 // make a clear-color from frame number. This will flash with a 120 frame period.
146 VkClearValue clearValue;
147 float flash = abs(sin(_frameNumber / 120.f));
148 clearValue.color = {{0.0f, 0.0f, flash, 1.0f}};
149
150 // clear depth at 1
151 VkClearValue depthClear;
152 depthClear.depthStencil.depth = 1.f;
153
154 // start the main renderpass.
155 // We will use the clear color from above, and the framebuffer of the index the swapchain gave
156 // us
157 VkRenderPassBeginInfo rpInfo = vkinit::renderpass_begin_info(
158 _renderPass, _windowExtent, _framebuffers[swapchainImageIndex]);
159
160 // connect clear values
161 rpInfo.clearValueCount = 2;
162
163 VkClearValue clearValues[] = {clearValue, depthClear};
164
165 rpInfo.pClearValues = &clearValues[0];
166
167 vkCmdBeginRenderPass(cmd, &rpInfo, VK_SUBPASS_CONTENTS_INLINE);
168
169 // draw_objects(cmd, _renderables.data(), _renderables.size());
170 tick(dt);
171
172 ImGui::EndFrame();
173 ImGui::Render();
174
175 ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), cmd);
176 // finalize the render pass
177 vkCmdEndRenderPass(cmd);
178 // finalize the command buffer (we can no longer add commands, but it can now be executed)
179 VK_CHECK(vkEndCommandBuffer(cmd));
180
181 // prepare the submission to the queue.
182 // we want to wait on the _presentSemaphore, as that semaphore is signaled when the swapchain is
183 // ready we will signal the _renderSemaphore, to signal that rendering has finished
184
185 VkSubmitInfo submit = vkinit::submit_info(&cmd);
186 VkPipelineStageFlags waitStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
187
188 submit.pWaitDstStageMask = &waitStage;
189
190 submit.waitSemaphoreCount = 1;
191 submit.pWaitSemaphores = &get_current_frame()._presentSemaphore;
192
193 submit.signalSemaphoreCount = 1;
194 submit.pSignalSemaphores = &get_current_frame()._renderSemaphore;
195
196 // submit command buffer to the queue and execute it.
197 // _renderFence will now block until the graphic commands finish execution
198 VK_CHECK(vkQueueSubmit(_graphicsQueue, 1, &submit, get_current_frame()._renderFence));
199
200 // prepare present
201 // this will put the image we just rendered to into the visible window.
202 // we want to wait on the _renderSemaphore for that,
203 // as its necessary that drawing commands have finished before the image is displayed to the
204 // user
205 VkPresentInfoKHR presentInfo = vkinit::present_info();
206
207 presentInfo.pSwapchains = &_swapchain;
208 presentInfo.swapchainCount = 1;
209
210 presentInfo.pWaitSemaphores = &get_current_frame()._renderSemaphore;
211 presentInfo.waitSemaphoreCount = 1;
212
213 presentInfo.pImageIndices = &swapchainImageIndex;
214
215 VK_CHECK(vkQueuePresentKHR(_graphicsQueue, &presentInfo));
216
217 // increase the number of frames drawn
218 _frameNumber++;
219 }
220
221 void VulkanEngine::run() {
222 SDL_Event e;
223 bool bQuit = false;
224
225 using Clock = std::chrono::high_resolution_clock;
226 using TimePoint = Clock::time_point;
227 using TimeDelta = std::chrono::duration<float, std::ratio<1, 1>>;
228
229 TimePoint now = Clock::now();
230 TimePoint prev = now;
231
232 start();
233
234 // main loop
235 while (!bQuit) {
236 // Handle events on queue
237 while (SDL_PollEvent(&e) != 0) {
238 if (!ImGui_ImplSDL2_ProcessEvent(&e)) {
239 // Standard Event handling
240 handle_event(e);
241 }
242
243 // close the window when user alt-f4s or clicks the X button
244 if (e.type == SDL_QUIT) {
245 bQuit = true;
246 } else if (e.type == SDL_KEYDOWN) {
247 if (e.key.keysym.sym == SDLK_SPACE) {
248 _selectedShader += 1;
249 if (_selectedShader > 1) {
250 _selectedShader = 0;
251 }
252 }
253 }
254 }
255
256 now = Clock::now();
257 float dt = TimeDelta(now - prev).count();
258 draw(dt);
259 prev = now;
260 }
261 }
262
263 FrameData& VulkanEngine::get_current_frame() { return _frames[_frameNumber % FRAME_OVERLAP]; }
264
265 FrameData& VulkanEngine::get_last_frame() { return _frames[(_frameNumber - 1) % 2]; }
266
267 void VulkanEngine::init_vulkan() {
268 vkb::InstanceBuilder builder;
269
270 // make the vulkan instance, with basic debug features
271 auto inst_ret = builder.set_app_name("Example Vulkan Application")
272 .request_validation_layers(bUseValidationLayers)
273 .use_default_debug_messenger()
274 .require_api_version(1, 1, 0)
275 .desire_api_version(1, 3, 0)
276 .build();
277
278 vkb::Instance vkb_inst = inst_ret.value();
279
280 // grab the instance
281 _instance = vkb_inst.instance;
282 _debug_messenger = vkb_inst.debug_messenger;
283
284 if (SDL_Vulkan_CreateSurface(_window, _instance, &_surface) == SDL_FALSE) {
285 const char* msg = SDL_GetError();
286 throw std::runtime_error(std::string("Surface creation failed: ") + std::string(msg));
287 }
288
289 // use vkbootstrap to select a gpu.
290 // We want a gpu that can write to the SDL surface and supports vulkan 1.2
291 vkb::PhysicalDeviceSelector selector{vkb_inst};
292 auto physicalDeviceResult = selector.set_minimum_version(1, 1).set_surface(_surface).select();
293 if (!physicalDeviceResult) {
294 throw std::runtime_error("Physical device failed");
295 }
296 vkb::PhysicalDevice physicalDevice = physicalDeviceResult.value();
297 // ---
298
299 // create the final vulkan device
300 vkb::DeviceBuilder deviceBuilder{physicalDevice};
301 auto vkbDeviceResult = deviceBuilder.build();
302 if (!vkbDeviceResult) {
303 throw std::runtime_error("Device failed");
304 }
305 vkb::Device vkbDevice = vkbDeviceResult.value();
306 // ---
307
308 // Get the VkDevice handle used in the rest of a vulkan application
309 _device = vkbDevice.device;
310 _chosenGPU = physicalDevice.physical_device;
311
312 // use vkbootstrap to get a Graphics queue
313 auto graphicsQueueResult = vkbDevice.get_queue(vkb::QueueType::graphics);
314 if (!graphicsQueueResult) {
315 throw std::runtime_error("Graphic Queue not found");
316 }
317 _graphicsQueue = graphicsQueueResult.value();
318 // ---
319
320 auto graphicsQueueFamilyResult = vkbDevice.get_queue_index(vkb::QueueType::graphics);
321 if (!graphicsQueueFamilyResult) {
322 throw std::runtime_error("Graphic Queue family not found");
323 }
324 _graphicsQueueFamily = graphicsQueueFamilyResult.value();
325 // ---
326
327 // initialize the memory allocator
328 VmaAllocatorCreateInfo allocatorInfo = {};
329 allocatorInfo.physicalDevice = _chosenGPU;
330 allocatorInfo.device = _device;
331 allocatorInfo.instance = _instance;
332 vmaCreateAllocator(&allocatorInfo, &_allocator);
333
334 _mainDeletionQueue.push_function([&]() { vmaDestroyAllocator(_allocator); });
335
336 vkGetPhysicalDeviceProperties(_chosenGPU, &_gpuProperties);
337
338 std::cout << "The gpu has a minimum buffer alignement of "
339 << _gpuProperties.limits.minUniformBufferOffsetAlignment << std::endl;
340 }
341
342 void VulkanEngine::init_swapchain() {
343 vkb::SwapchainBuilder swapchainBuilder{_chosenGPU, _device, _surface};
344
345 vkb::Swapchain vkbSwapchain = swapchainBuilder
346 .use_default_format_selection()
347 // use vsync present mode
348 .set_desired_present_mode(VK_PRESENT_MODE_FIFO_KHR)
349 .set_desired_extent(_windowExtent.width, _windowExtent.height)
350 .build()
351 .value();
352
353 // store swapchain and its related images
354 _swapchain = vkbSwapchain.swapchain;
355 _swapchainImages = vkbSwapchain.get_images().value();
356 _swapchainImageViews = vkbSwapchain.get_image_views().value();
357
358 _swachainImageFormat = vkbSwapchain.image_format;
359
360 _mainDeletionQueue.push_function(
361 [=]() { vkDestroySwapchainKHR(_device, _swapchain, nullptr); });
362
363 // depth image size will match the window
364 VkExtent3D depthImageExtent = {_windowExtent.width, _windowExtent.height, 1};
365
366 // hardcoding the depth format to 32 bit float
367 _depthFormat = VK_FORMAT_D32_SFLOAT;
368
369 // the depth image will be a image with the format we selected and Depth Attachment usage flag
370 VkImageCreateInfo dimg_info = vkinit::image_create_info(
371 _depthFormat, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, depthImageExtent);
372
373 // for the depth image, we want to allocate it from gpu local memory
374 VmaAllocationCreateInfo dimg_allocinfo = {};
375 dimg_allocinfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
376 dimg_allocinfo.requiredFlags = VkMemoryPropertyFlags(VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
377
378 // allocate and create the image
379 vmaCreateImage(_allocator,
380 &dimg_info,
381 &dimg_allocinfo,
382 &_depthImage._image,
383 &_depthImage._allocation,
384 nullptr);
385
386 // build a image-view for the depth image to use for rendering
387 VkImageViewCreateInfo dview_info =
388 vkinit::imageview_create_info(_depthFormat, _depthImage._image, VK_IMAGE_ASPECT_DEPTH_BIT);
389 ;
390
391 VK_CHECK(vkCreateImageView(_device, &dview_info, nullptr, &_depthImageView));
392
393 // add to deletion queues
394 _mainDeletionQueue.push_function([=]() {
395 vkDestroyImageView(_device, _depthImageView, nullptr);
396 vmaDestroyImage(_allocator, _depthImage._image, _depthImage._allocation);
397 });
398 }
399
400 void VulkanEngine::init_imgui() {
401 // 1: create descriptor pool for IMGUI
402 // the size of the pool is very oversize, but it's copied from imgui demo itself.
403 VkDescriptorPoolSize pool_sizes[] = {{VK_DESCRIPTOR_TYPE_SAMPLER, 1000},
404 {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000},
405 {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1000},
406 {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1000},
407 {VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1000},
408 {VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1000},
409 {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1000},
410 {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1000},
411 {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1000},
412 {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1000},
413 {VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000}};
414
415 VkDescriptorPoolCreateInfo pool_info = {};
416 pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
417 pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
418 pool_info.maxSets = 1000;
419 pool_info.poolSizeCount = uint32_t(std::size(pool_sizes));
420 pool_info.pPoolSizes = pool_sizes;
421
422 VkDescriptorPool imguiPool;
423 VK_CHECK(vkCreateDescriptorPool(_device, &pool_info, nullptr, &imguiPool));
424
425 // 2: initialize imgui library
426
427 // this initializes the core structures of imgui
428 ImGui::CreateContext();
429
430 // this initializes imgui for SDL
431 ImGui_ImplSDL2_InitForVulkan(_window);
432
433 // this initializes imgui for Vulkan
434 ImGui_ImplVulkan_InitInfo init_info = {};
435 init_info.Instance = _instance;
436 init_info.PhysicalDevice = _chosenGPU;
437 init_info.Device = _device;
438 init_info.Queue = _graphicsQueue;
439 init_info.DescriptorPool = imguiPool;
440 init_info.MinImageCount = 3;
441 init_info.ImageCount = 3;
442 init_info.MSAASamples = VK_SAMPLE_COUNT_1_BIT;
443
444 ImGui_ImplVulkan_Init(&init_info, _renderPass);
445
446 // execute a gpu command to upload imgui font textures
447 immediate_submit([&](VkCommandBuffer cmd) { ImGui_ImplVulkan_CreateFontsTexture(cmd); });
448
449 // clear font textures from cpu data
450 ImGui_ImplVulkan_DestroyFontUploadObjects();
451
452 // add the destroy the imgui created structures
453 _mainDeletionQueue.push_function([=]() {
454 vkDestroyDescriptorPool(_device, imguiPool, nullptr);
455 ImGui_ImplVulkan_Shutdown();
456 });
457 }
458
459 void VulkanEngine::init_default_renderpass() {
460 // we define an attachment description for our main color image
461 // the attachment is loaded as "clear" when renderpass start
462 // the attachment is stored when renderpass ends
463 // the attachment layout starts as "undefined", and transitions to "Present" so its possible to
464 // display it we dont care about stencil, and dont use multisampling
465
466 VkAttachmentDescription color_attachment = {};
467 color_attachment.format = _swachainImageFormat;
468 color_attachment.samples = VK_SAMPLE_COUNT_1_BIT;
469 color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
470 color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
471 color_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
472 color_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
473 color_attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
474 color_attachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
475
476 VkAttachmentReference color_attachment_ref = {};
477 color_attachment_ref.attachment = 0;
478 color_attachment_ref.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
479
480 VkAttachmentDescription depth_attachment = {};
481 // Depth attachment
482 depth_attachment.flags = 0;
483 depth_attachment.format = _depthFormat;
484 depth_attachment.samples = VK_SAMPLE_COUNT_1_BIT;
485 depth_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
486 depth_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
487 depth_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
488 depth_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
489 depth_attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
490 depth_attachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
491
492 VkAttachmentReference depth_attachment_ref = {};
493 depth_attachment_ref.attachment = 1;
494 depth_attachment_ref.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
495
496 // we are going to create 1 subpass, which is the minimum you can do
497 VkSubpassDescription subpass = {};
498 subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
499 subpass.colorAttachmentCount = 1;
500 subpass.pColorAttachments = &color_attachment_ref;
501 // hook the depth attachment into the subpass
502 subpass.pDepthStencilAttachment = &depth_attachment_ref;
503
504 // 1 dependency, which is from "outside" into the subpass. And we can read or write color
505 VkSubpassDependency dependency = {};
506 dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
507 dependency.dstSubpass = 0;
508 dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
509 dependency.srcAccessMask = 0;
510 dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
511 dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
512
513 // dependency from outside to the subpass, making this subpass dependent on the previous
514 // renderpasses
515 VkSubpassDependency depth_dependency = {};
516 depth_dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
517 depth_dependency.dstSubpass = 0;
518 depth_dependency.srcStageMask =
519 VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
520 depth_dependency.srcAccessMask = 0;
521 depth_dependency.dstStageMask =
522 VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
523 depth_dependency.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
524
525 // array of 2 dependencies, one for color, two for depth
526 VkSubpassDependency dependencies[2] = {dependency, depth_dependency};
527
528 // array of 2 attachments, one for the color, and other for depth
529 VkAttachmentDescription attachments[2] = {color_attachment, depth_attachment};
530
531 VkRenderPassCreateInfo render_pass_info = {};
532 render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
533 // 2 attachments from attachment array
534 render_pass_info.attachmentCount = 2;
535 render_pass_info.pAttachments = &attachments[0];
536 render_pass_info.subpassCount = 1;
537 render_pass_info.pSubpasses = &subpass;
538 // 2 dependencies from dependency array
539 render_pass_info.dependencyCount = 2;
540 render_pass_info.pDependencies = &dependencies[0];
541
542 VK_CHECK(vkCreateRenderPass(_device, &render_pass_info, nullptr, &_renderPass));
543
544 _mainDeletionQueue.push_function([=]() { vkDestroyRenderPass(_device, _renderPass, nullptr); });
545 }
546
547 void VulkanEngine::init_framebuffers() {
548 // create the framebuffers for the swapchain images. This will connect the render-pass to the
549 // images for rendering
550 VkFramebufferCreateInfo fb_info = vkinit::framebuffer_create_info(_renderPass, _windowExtent);
551
552 const std::size_t swapchain_imagecount = _swapchainImages.size();
553 _framebuffers = std::vector<VkFramebuffer>(swapchain_imagecount);
554
555 for (std::size_t i = 0; i < swapchain_imagecount; i++) {
556
557 VkImageView attachments[2];
558 attachments[0] = _swapchainImageViews[i];
559 attachments[1] = _depthImageView;
560
561 fb_info.pAttachments = attachments;
562 fb_info.attachmentCount = 2;
563 VK_CHECK(vkCreateFramebuffer(_device, &fb_info, nullptr, &_framebuffers[i]));
564
565 _mainDeletionQueue.push_function([=]() {
566 vkDestroyFramebuffer(_device, _framebuffers[i], nullptr);
567 vkDestroyImageView(_device, _swapchainImageViews[i], nullptr);
568 });
569 }
570 }
571
572 void VulkanEngine::init_commands() {
573 // create a command pool for commands submitted to the graphics queue.
574 // we also want the pool to allow for resetting of individual command buffers
575 VkCommandPoolCreateInfo commandPoolInfo = vkinit::command_pool_create_info(
576 _graphicsQueueFamily, VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT);
577
578 for (int i = 0; i < FRAME_OVERLAP; i++) {
579
580 VK_CHECK(vkCreateCommandPool(_device, &commandPoolInfo, nullptr, &_frames[i]._commandPool));
581
582 // allocate the default command buffer that we will use for rendering
583 VkCommandBufferAllocateInfo cmdAllocInfo =
584 vkinit::command_buffer_allocate_info(_frames[i]._commandPool, 1);
585
586 VK_CHECK(vkAllocateCommandBuffers(_device, &cmdAllocInfo, &_frames[i]._mainCommandBuffer));
587
588 _mainDeletionQueue.push_function(
589 [=]() { vkDestroyCommandPool(_device, _frames[i]._commandPool, nullptr); });
590 }
591
592 VkCommandPoolCreateInfo uploadCommandPoolInfo =
593 vkinit::command_pool_create_info(_graphicsQueueFamily);
594 // create pool for upload context
595 VK_CHECK(vkCreateCommandPool(
596 _device, &uploadCommandPoolInfo, nullptr, &_uploadContext._commandPool));
597
598 _mainDeletionQueue.push_function(
599 [=]() { vkDestroyCommandPool(_device, _uploadContext._commandPool, nullptr); });
600
601 // allocate the default command buffer that we will use for rendering
602 VkCommandBufferAllocateInfo cmdAllocInfo =
603 vkinit::command_buffer_allocate_info(_uploadContext._commandPool, 1);
604
605 VK_CHECK(vkAllocateCommandBuffers(_device, &cmdAllocInfo, &_uploadContext._commandBuffer));
606 }
607
608 void VulkanEngine::init_sync_structures() {
609 // create syncronization structures
610 // one fence to control when the gpu has finished rendering the frame,
611 // and 2 semaphores to syncronize rendering with swapchain
612 // we want the fence to start signalled so we can wait on it on the first frame
613 VkFenceCreateInfo fenceCreateInfo = vkinit::fence_create_info(VK_FENCE_CREATE_SIGNALED_BIT);
614
615 VkSemaphoreCreateInfo semaphoreCreateInfo = vkinit::semaphore_create_info();
616
617 for (int i = 0; i < FRAME_OVERLAP; i++) {
618
619 VK_CHECK(vkCreateFence(_device, &fenceCreateInfo, nullptr, &_frames[i]._renderFence));
620
621 // enqueue the destruction of the fence
622 _mainDeletionQueue.push_function(
623 [=]() { vkDestroyFence(_device, _frames[i]._renderFence, nullptr); });
624
625 VK_CHECK(vkCreateSemaphore(
626 _device, &semaphoreCreateInfo, nullptr, &_frames[i]._presentSemaphore));
627 VK_CHECK(vkCreateSemaphore(
628 _device, &semaphoreCreateInfo, nullptr, &_frames[i]._renderSemaphore));
629
630 // enqueue the destruction of semaphores
631 _mainDeletionQueue.push_function([=]() {
632 vkDestroySemaphore(_device, _frames[i]._presentSemaphore, nullptr);
633 vkDestroySemaphore(_device, _frames[i]._renderSemaphore, nullptr);
634 });
635 }
636
637 VkFenceCreateInfo uploadFenceCreateInfo = vkinit::fence_create_info();
638
639 VK_CHECK(vkCreateFence(_device, &uploadFenceCreateInfo, nullptr, &_uploadContext._uploadFence));
640 _mainDeletionQueue.push_function(
641 [=]() { vkDestroyFence(_device, _uploadContext._uploadFence, nullptr); });
642 }
643
644 void VulkanEngine::init_pipelines() {
645 VkShaderModule colorMeshShader;
646 std::string p = "E:/work/lython/src/tide/shaders/";
647 p = "E:/work/lython/build/bin/Debug/shaders/";
648
649 if (!load_shader_module(p + std::string("default_lit.frag.spv"), &colorMeshShader)) {
650 std::cout << "Error when building the colored mesh shader" << std::endl;
651 }
652
653 VkShaderModule texturedMeshShader;
654 if (!load_shader_module(p + std::string("textured_lit.frag.spv"), &texturedMeshShader)) {
655 std::cout << "Error when building the colored mesh shader" << std::endl;
656 }
657
658 VkShaderModule meshVertShader;
659 if (!load_shader_module(p + std::string("tri_mesh_ssbo.vert.spv"), &meshVertShader)) {
660 std::cout << "Error when building the mesh vertex shader module" << std::endl;
661 }
662
663 // build the stage-create-info for both vertex and fragment stages. This lets the pipeline know
664 // the shader modules per stage
665 PipelineBuilder pipelineBuilder;
666
667 pipelineBuilder._shaderStages.push_back(
668 vkinit::pipeline_shader_stage_create_info(VK_SHADER_STAGE_VERTEX_BIT, meshVertShader));
669
670 pipelineBuilder._shaderStages.push_back(
671 vkinit::pipeline_shader_stage_create_info(VK_SHADER_STAGE_FRAGMENT_BIT, colorMeshShader));
672
673 // we start from just the default empty pipeline layout info
674 VkPipelineLayoutCreateInfo mesh_pipeline_layout_info = vkinit::pipeline_layout_create_info();
675
676 // setup push constants
677 VkPushConstantRange push_constant;
678 // offset 0
679 push_constant.offset = 0;
680 // size of a MeshPushConstant struct
681 push_constant.size = sizeof(MeshPushConstants);
682 // for the vertex shader
683 push_constant.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
684
685 mesh_pipeline_layout_info.pPushConstantRanges = &push_constant;
686 mesh_pipeline_layout_info.pushConstantRangeCount = 1;
687
688 VkDescriptorSetLayout setLayouts[] = {_globalSetLayout, _objectSetLayout};
689
690 mesh_pipeline_layout_info.setLayoutCount = 2;
691 mesh_pipeline_layout_info.pSetLayouts = setLayouts;
692
693 VkPipelineLayout meshPipLayout;
694 VK_CHECK(vkCreatePipelineLayout(_device, &mesh_pipeline_layout_info, nullptr, &meshPipLayout));
695
696 // we start from the normal mesh layout
697 VkPipelineLayoutCreateInfo textured_pipeline_layout_info = mesh_pipeline_layout_info;
698
699 VkDescriptorSetLayout texturedSetLayouts[] = {
700 _globalSetLayout, _objectSetLayout, _singleTextureSetLayout};
701
702 textured_pipeline_layout_info.setLayoutCount = 3;
703 textured_pipeline_layout_info.pSetLayouts = texturedSetLayouts;
704
705 VkPipelineLayout texturedPipeLayout;
706 VK_CHECK(vkCreatePipelineLayout(
707 _device, &textured_pipeline_layout_info, nullptr, &texturedPipeLayout));
708
709 // hook the push constants layout
710 pipelineBuilder._pipelineLayout = meshPipLayout;
711
712 // vertex input controls how to read vertices from vertex buffers. We arent using it yet
713 pipelineBuilder._vertexInputInfo = vkinit::vertex_input_state_create_info();
714
715 // input assembly is the configuration for drawing triangle lists, strips, or individual points.
716 // we are just going to draw triangle list
717 pipelineBuilder._inputAssembly =
718 vkinit::input_assembly_create_info(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST);
719
720 // build viewport and scissor from the swapchain extents
721 pipelineBuilder._viewport.x = 0.0f;
722 pipelineBuilder._viewport.y = 0.0f;
723 pipelineBuilder._viewport.width = (float)_windowExtent.width;
724 pipelineBuilder._viewport.height = (float)_windowExtent.height;
725 pipelineBuilder._viewport.minDepth = 0.0f;
726 pipelineBuilder._viewport.maxDepth = 1.0f;
727
728 pipelineBuilder._scissor.offset = {0, 0};
729 pipelineBuilder._scissor.extent = _windowExtent;
730
731 // configure the rasterizer to draw filled triangles
732 pipelineBuilder._rasterizer = vkinit::rasterization_state_create_info(VK_POLYGON_MODE_FILL);
733
734 // we dont use multisampling, so just run the default one
735 pipelineBuilder._multisampling = vkinit::multisampling_state_create_info();
736
737 // a single blend attachment with no blending and writing to RGBA
738 pipelineBuilder._colorBlendAttachment = vkinit::color_blend_attachment_state();
739
740 // default depthtesting
741 pipelineBuilder._depthStencil =
742 vkinit::depth_stencil_create_info(true, true, VK_COMPARE_OP_LESS_OR_EQUAL);
743
744 // build the mesh pipeline
745
746 VertexInputDescription vertexDescription = Vertex::get_vertex_description();
747
748 // connect the pipeline builder vertex input info to the one we get from Vertex
749 pipelineBuilder._vertexInputInfo.pVertexAttributeDescriptions =
750 vertexDescription.attributes.data();
751 pipelineBuilder._vertexInputInfo.vertexAttributeDescriptionCount =
752 uint32_t(vertexDescription.attributes.size());
753
754 pipelineBuilder._vertexInputInfo.pVertexBindingDescriptions = vertexDescription.bindings.data();
755 pipelineBuilder._vertexInputInfo.vertexBindingDescriptionCount =
756 uint32_t(vertexDescription.bindings.size());
757
758 // build the mesh triangle pipeline
759 VkPipeline meshPipeline = pipelineBuilder.build_pipeline(_device, _renderPass);
760
761 create_material(meshPipeline, meshPipLayout, "defaultmesh");
762
763 pipelineBuilder._shaderStages.clear();
764 pipelineBuilder._shaderStages.push_back(
765 vkinit::pipeline_shader_stage_create_info(VK_SHADER_STAGE_VERTEX_BIT, meshVertShader));
766
767 pipelineBuilder._shaderStages.push_back(vkinit::pipeline_shader_stage_create_info(
768 VK_SHADER_STAGE_FRAGMENT_BIT, texturedMeshShader));
769
770 pipelineBuilder._pipelineLayout = texturedPipeLayout;
771 VkPipeline texPipeline = pipelineBuilder.build_pipeline(_device, _renderPass);
772 create_material(texPipeline, texturedPipeLayout, "texturedmesh");
773
774 vkDestroyShaderModule(_device, meshVertShader, nullptr);
775 vkDestroyShaderModule(_device, colorMeshShader, nullptr);
776 vkDestroyShaderModule(_device, texturedMeshShader, nullptr);
777
778 _mainDeletionQueue.push_function([=]() {
779 vkDestroyPipeline(_device, meshPipeline, nullptr);
780 vkDestroyPipeline(_device, texPipeline, nullptr);
781
782 vkDestroyPipelineLayout(_device, meshPipLayout, nullptr);
783 vkDestroyPipelineLayout(_device, texturedPipeLayout, nullptr);
784 });
785 }
786
787 bool VulkanEngine::load_shader_module(std::string const& filePath,
788 VkShaderModule* outShaderModule) {
789 // open the file. With cursor at the end
790 std::ifstream file(filePath.c_str(), std::ios::ate | std::ios::binary);
791
792 if (!file.is_open()) {
793 std::cout << "FAIL";
794 return false;
795 }
796
797 // find what the size of the file is by looking up the location of the cursor
798 // because the cursor is at the end, it gives the size directly in bytes
799 size_t fileSize = (size_t)file.tellg();
800
801 // spirv expects the buffer to be on uint32, so make sure to reserve a int vector big enough for
802 // the entire file
803 std::vector<uint32_t> buffer(fileSize / sizeof(uint32_t));
804
805 // put file cursor at beggining
806 file.seekg(0);
807
808 // load the entire file into the buffer
809 file.read((char*)buffer.data(), fileSize);
810
811 // now that the file is loaded into the buffer, we can close it
812 file.close();
813
814 // create a new shader module, using the buffer we loaded
815 VkShaderModuleCreateInfo createInfo = {};
816 createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
817 createInfo.pNext = nullptr;
818
819 // codeSize has to be in bytes, so multply the ints in the buffer by size of int to know the
820 // real size of the buffer
821 createInfo.codeSize = buffer.size() * sizeof(uint32_t);
822 createInfo.pCode = buffer.data();
823
824 // check that the creation goes well.
825 VkShaderModule shaderModule;
826 if (vkCreateShaderModule(_device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) {
827 return false;
828 }
829 *outShaderModule = shaderModule;
830 return true;
831 }
832
833 VkPipeline PipelineBuilder::build_pipeline(VkDevice device, VkRenderPass pass) {
834 // make viewport state from our stored viewport and scissor.
835 // at the moment we wont support multiple viewports or scissors
836 VkPipelineViewportStateCreateInfo viewportState = {};
837 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
838 viewportState.pNext = nullptr;
839
840 viewportState.viewportCount = 1;
841 viewportState.pViewports = &_viewport;
842 viewportState.scissorCount = 1;
843 viewportState.pScissors = &_scissor;
844
845 // setup dummy color blending. We arent using transparent objects yet
846 // the blending is just "no blend", but we do write to the color attachment
847 VkPipelineColorBlendStateCreateInfo colorBlending = {};
848 colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
849 colorBlending.pNext = nullptr;
850
851 colorBlending.logicOpEnable = VK_FALSE;
852 colorBlending.logicOp = VK_LOGIC_OP_COPY;
853 colorBlending.attachmentCount = 1;
854 colorBlending.pAttachments = &_colorBlendAttachment;
855
856 // build the actual pipeline
857 // we now use all of the info structs we have been writing into into this one to create the
858 // pipeline
859 VkGraphicsPipelineCreateInfo pipelineInfo = {};
860 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
861 pipelineInfo.pNext = nullptr;
862
863 pipelineInfo.stageCount = uint32_t(_shaderStages.size());
864 pipelineInfo.pStages = _shaderStages.data();
865 pipelineInfo.pVertexInputState = &_vertexInputInfo;
866 pipelineInfo.pInputAssemblyState = &_inputAssembly;
867 pipelineInfo.pViewportState = &viewportState;
868 pipelineInfo.pRasterizationState = &_rasterizer;
869 pipelineInfo.pMultisampleState = &_multisampling;
870 pipelineInfo.pColorBlendState = &colorBlending;
871 pipelineInfo.pDepthStencilState = &_depthStencil;
872 pipelineInfo.layout = _pipelineLayout;
873 pipelineInfo.renderPass = pass;
874 pipelineInfo.subpass = 0;
875 pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
876
877 // its easy to error out on create graphics pipeline, so we handle it a bit better than the
878 // common VK_CHECK case
879 VkPipeline newPipeline;
880 if (vkCreateGraphicsPipelines(
881 device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &newPipeline) != VK_SUCCESS) {
882 std::cout << "failed to create pipline\n";
883 return VK_NULL_HANDLE; // failed to create graphics pipeline
884 } else {
885 return newPipeline;
886 }
887 }
888
889 void VulkanEngine::load_meshes() {
890 Mesh triMesh{};
891 // make the array 3 vertices long
892 triMesh._vertices.resize(3);
893
894 // vertex positions
895 triMesh._vertices[0].position = {1.f, 1.f, 0.0f};
896 triMesh._vertices[1].position = {-1.f, 1.f, 0.0f};
897 triMesh._vertices[2].position = {0.f, -1.f, 0.0f};
898
899 // vertex colors, all green
900 triMesh._vertices[0].color = {0.f, 1.f, 0.0f}; // pure green
901 triMesh._vertices[1].color = {0.f, 1.f, 0.0f}; // pure green
902 triMesh._vertices[2].color = {0.f, 1.f, 0.0f}; // pure green
903 // we dont care about the vertex normals
904
905 // load the monkey
906 Mesh monkeyMesh{};
907 std::string p = "E:/work/lython/build/bin/Debug/assets/";
908 monkeyMesh.load_from_obj((p + std::string("monkey_smooth.obj")).c_str());
909
910 Mesh lostEmpire{};
911 lostEmpire.load_from_obj((p + std::string("lost_empire.obj")).c_str());
912
913 upload_mesh(triMesh);
914 upload_mesh(monkeyMesh);
915 upload_mesh(lostEmpire);
916
917 _meshes["monkey"] = monkeyMesh;
918 _meshes["triangle"] = triMesh;
919 _meshes["empire"] = lostEmpire;
920 }
921
922 VkSampler VulkanEngine::new_texture_sampler() {
923 VkSamplerCreateInfo samplerInfo{};
924 samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
925 samplerInfo.magFilter = VK_FILTER_LINEAR;
926 samplerInfo.minFilter = VK_FILTER_LINEAR;
927
928 samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
929 samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
930 samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
931
932 samplerInfo.anisotropyEnable = VK_TRUE;
933 samplerInfo.maxAnisotropy = 16.0f;
934 samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
935 samplerInfo.unnormalizedCoordinates = VK_FALSE;
936 samplerInfo.compareEnable = VK_FALSE;
937 samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
938
939 samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
940 samplerInfo.mipLodBias = 0.0f;
941 samplerInfo.minLod = 0.0f;
942 samplerInfo.maxLod = 0.0f;
943
944 VkSampler textureSampler;
945 if (vkCreateSampler(_device, &samplerInfo, nullptr, &textureSampler) != VK_SUCCESS) {
946 throw std::runtime_error("failed to create texture sampler!");
947 }
948 return textureSampler;
949 }
950
951 ImTextureID VulkanEngine::load_imtexture(const char* name) {
952 Texture tex = load_texture(name);
953 VkDescriptorSet set = ImGui_ImplVulkan_AddTexture(
954 get_sampler(), tex.imageView, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
955
956 _mainDeletionQueue.push_function([=]() { ImGui_ImplVulkan_RemoveTexture(set); });
957 _lookup[set] = tex;
958 return (ImTextureID)set;
959 }
960
961 int VulkanEngine::get_height(ImTextureID texture) {
962 return _lookup[(VkDescriptorSet)(texture)].height;
963 }
964 int VulkanEngine::get_width(ImTextureID texture) {
965 return _lookup[(VkDescriptorSet)(texture)].width;
966 }
967
968 VkSampler VulkanEngine::get_sampler() {
969 if (_defaultSampler == nullptr) {
970 _defaultSampler = new_texture_sampler();
971 _mainDeletionQueue.push_function(
972 [=]() { vkDestroySampler(_device, _defaultSampler, nullptr); });
973 }
974 return _defaultSampler;
975 }
976
977 Texture VulkanEngine::load_texture(const char* file) {
978 Texture tex = _loadedTextures[file];
979 if (tex.image._image == nullptr) {
980 vkutil::load_image_from_file(*this, file, tex);
981 _loadedTextures[file] = tex;
982
983 VkImageViewCreateInfo imageinfo = vkinit::imageview_create_info(
984 VK_FORMAT_R8G8B8A8_SRGB, tex.image._image, VK_IMAGE_ASPECT_COLOR_BIT);
985 vkCreateImageView(_device, &imageinfo, nullptr, &tex.imageView);
986
987 _mainDeletionQueue.push_function(
988 [=]() { vkDestroyImageView(_device, tex.imageView, nullptr); });
989 }
990
991 VkDescriptorSet set = ImGui_ImplVulkan_AddTexture(
992 get_sampler(), tex.imageView, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
993
994 return tex;
995 }
996
997 void VulkanEngine::load_images() {
998 Texture lostEmpire;
999
1000 std::string p = "E:/work/lython/build/bin/Debug/assets/";
1001 vkutil::load_image_from_file(
1002 *this, (p + std::string("lost_empire-RGBA.png")).c_str(), lostEmpire);
1003
1004 VkImageViewCreateInfo imageinfo = vkinit::imageview_create_info(
1005 VK_FORMAT_R8G8B8A8_SRGB, lostEmpire.image._image, VK_IMAGE_ASPECT_COLOR_BIT);
1006 vkCreateImageView(_device, &imageinfo, nullptr, &lostEmpire.imageView);
1007
1008 _mainDeletionQueue.push_function(
1009 [=]() { vkDestroyImageView(_device, lostEmpire.imageView, nullptr); });
1010
1011 _loadedTextures["empire_diffuse"] = lostEmpire;
1012 }
1013
1014 void VulkanEngine::upload_mesh(Mesh& mesh) {
1015 const size_t bufferSize = mesh._vertices.size() * sizeof(Vertex);
1016 // allocate vertex buffer
1017 VkBufferCreateInfo stagingBufferInfo = {};
1018 stagingBufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
1019 stagingBufferInfo.pNext = nullptr;
1020 // this is the total size, in bytes, of the buffer we are allocating
1021 stagingBufferInfo.size = bufferSize;
1022 // this buffer is going to be used as a Vertex Buffer
1023 stagingBufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
1024
1025 // let the VMA library know that this data should be writeable by CPU, but also readable by GPU
1026 VmaAllocationCreateInfo vmaallocInfo = {};
1027 vmaallocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY;
1028
1029 AllocatedBuffer stagingBuffer;
1030
1031 // allocate the buffer
1032 VK_CHECK(vmaCreateBuffer(_allocator,
1033 &stagingBufferInfo,
1034 &vmaallocInfo,
1035 &stagingBuffer._buffer,
1036 &stagingBuffer._allocation,
1037 nullptr));
1038
1039 // copy vertex data
1040 void* data;
1041 vmaMapMemory(_allocator, stagingBuffer._allocation, &data);
1042
1043 memcpy(data, mesh._vertices.data(), mesh._vertices.size() * sizeof(Vertex));
1044
1045 vmaUnmapMemory(_allocator, stagingBuffer._allocation);
1046
1047 // allocate vertex buffer
1048 VkBufferCreateInfo vertexBufferInfo = {};
1049 vertexBufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
1050 vertexBufferInfo.pNext = nullptr;
1051 // this is the total size, in bytes, of the buffer we are allocating
1052 vertexBufferInfo.size = bufferSize;
1053 // this buffer is going to be used as a Vertex Buffer
1054 vertexBufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
1055
1056 // let the VMA library know that this data should be gpu native
1057 vmaallocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
1058
1059 // allocate the buffer
1060 VK_CHECK(vmaCreateBuffer(_allocator,
1061 &vertexBufferInfo,
1062 &vmaallocInfo,
1063 &mesh._vertexBuffer._buffer,
1064 &mesh._vertexBuffer._allocation,
1065 nullptr));
1066 // add the destruction of triangle mesh buffer to the deletion queue
1067 _mainDeletionQueue.push_function([=]() {
1068 vmaDestroyBuffer(_allocator, mesh._vertexBuffer._buffer, mesh._vertexBuffer._allocation);
1069 });
1070
1071 immediate_submit([=](VkCommandBuffer cmd) {
1072 VkBufferCopy copy;
1073 copy.dstOffset = 0;
1074 copy.srcOffset = 0;
1075 copy.size = bufferSize;
1076 vkCmdCopyBuffer(cmd, stagingBuffer._buffer, mesh._vertexBuffer._buffer, 1, &copy);
1077 });
1078
1079 vmaDestroyBuffer(_allocator, stagingBuffer._buffer, stagingBuffer._allocation);
1080 }
1081
1082 Material* VulkanEngine::create_material(VkPipeline pipeline,
1083 VkPipelineLayout layout,
1084 const std::string& name) {
1085 Material mat;
1086 mat.pipeline = pipeline;
1087 mat.pipelineLayout = layout;
1088 _materials[name] = mat;
1089 return &_materials[name];
1090 }
1091
1092 Material* VulkanEngine::get_material(const std::string& name) {
1093 // search for the object, and return nullpointer if not found
1094 auto it = _materials.find(name);
1095 if (it == _materials.end()) {
1096 return nullptr;
1097 } else {
1098 return &(*it).second;
1099 }
1100 }
1101
1102 Mesh* VulkanEngine::get_mesh(const std::string& name) {
1103 auto it = _meshes.find(name);
1104 if (it == _meshes.end()) {
1105 return nullptr;
1106 } else {
1107 return &(*it).second;
1108 }
1109 }
1110
1111 void VulkanEngine::draw_objects(VkCommandBuffer cmd, RenderObject* first, int count) {
1112 // make a model view matrix for rendering the object
1113 // camera view
1114 glm::vec3 camPos = {0.f, -6.f, -10.f};
1115
1116 glm::mat4 view = glm::translate(glm::mat4(1.f), camPos);
1117 // camera projection
1118 glm::mat4 projection = glm::perspective(glm::radians(70.f), 1700.f / 900.f, 0.1f, 200.0f);
1119 projection[1][1] *= -1;
1120
1121 GPUCameraData camData;
1122 camData.proj = projection;
1123 camData.view = view;
1124 camData.viewproj = projection * view;
1125
1126 void* data;
1127 vmaMapMemory(_allocator, get_current_frame().cameraBuffer._allocation, &data);
1128
1129 memcpy(data, &camData, sizeof(GPUCameraData));
1130
1131 vmaUnmapMemory(_allocator, get_current_frame().cameraBuffer._allocation);
1132
1133 float framed = (_frameNumber / 120.f);
1134
1135 _sceneParameters.ambientColor = {sin(framed), 0, cos(framed), 1};
1136
1137 char* sceneData;
1138 vmaMapMemory(_allocator, _sceneParameterBuffer._allocation, (void**)&sceneData);
1139
1140 std::size_t frameIndex = _frameNumber % FRAME_OVERLAP;
1141
1142 sceneData += pad_uniform_buffer_size(sizeof(GPUSceneData)) * frameIndex;
1143
1144 memcpy(sceneData, &_sceneParameters, sizeof(GPUSceneData));
1145
1146 vmaUnmapMemory(_allocator, _sceneParameterBuffer._allocation);
1147
1148 void* objectData;
1149 vmaMapMemory(_allocator, get_current_frame().objectBuffer._allocation, &objectData);
1150
1151 GPUObjectData* objectSSBO = (GPUObjectData*)objectData;
1152
1153 for (int i = 0; i < count; i++) {
1154 RenderObject& object = first[i];
1155 objectSSBO[i].modelMatrix = object.transformMatrix;
1156 }
1157
1158 vmaUnmapMemory(_allocator, get_current_frame().objectBuffer._allocation);
1159
1160 Mesh* lastMesh = nullptr;
1161 Material* lastMaterial = nullptr;
1162
1163 for (int i = 0; i < count; i++) {
1164 RenderObject& object = first[i];
1165
1166 // only bind the pipeline if it doesnt match with the already bound one
1167 if (object.material != lastMaterial) {
1168
1169 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, object.material->pipeline);
1170 lastMaterial = object.material;
1171
1172 uint32_t uniform_offset =
1173 uint32_t(pad_uniform_buffer_size(sizeof(GPUSceneData)) * frameIndex);
1174 vkCmdBindDescriptorSets(cmd,
1175 VK_PIPELINE_BIND_POINT_GRAPHICS,
1176 object.material->pipelineLayout,
1177 0,
1178 1,
1179 &get_current_frame().globalDescriptor,
1180 1,
1181 &uniform_offset);
1182
1183 // object data descriptor
1184 vkCmdBindDescriptorSets(cmd,
1185 VK_PIPELINE_BIND_POINT_GRAPHICS,
1186 object.material->pipelineLayout,
1187 1,
1188 1,
1189 &get_current_frame().objectDescriptor,
1190 0,
1191 nullptr);
1192
1193 if (object.material->textureSet != VK_NULL_HANDLE) {
1194 // texture descriptor
1195 vkCmdBindDescriptorSets(cmd,
1196 VK_PIPELINE_BIND_POINT_GRAPHICS,
1197 object.material->pipelineLayout,
1198 2,
1199 1,
1200 &object.material->textureSet,
1201 0,
1202 nullptr);
1203 }
1204 }
1205
1206 glm::mat4 model = object.transformMatrix;
1207 // final render matrix, that we are calculating on the cpu
1208 glm::mat4 mesh_matrix = model;
1209
1210 MeshPushConstants constants;
1211 constants.render_matrix = mesh_matrix;
1212
1213 // upload the mesh to the gpu via pushconstants
1214 vkCmdPushConstants(cmd,
1215 object.material->pipelineLayout,
1216 VK_SHADER_STAGE_VERTEX_BIT,
1217 0,
1218 sizeof(MeshPushConstants),
1219 &constants);
1220
1221 // only bind the mesh if its a different one from last bind
1222 if (object.mesh != lastMesh) {
1223 // bind the mesh vertex buffer with offset 0
1224 VkDeviceSize offset = 0;
1225 vkCmdBindVertexBuffers(cmd, 0, 1, &object.mesh->_vertexBuffer._buffer, &offset);
1226 lastMesh = object.mesh;
1227 }
1228 // we can now draw
1229 vkCmdDraw(cmd, uint32_t(object.mesh->_vertices.size()), 1, 0, uint32_t(i));
1230 }
1231 }
1232
1233 void VulkanEngine::init_scene() {
1234 RenderObject monkey;
1235 monkey.mesh = get_mesh("monkey");
1236 monkey.material = get_material("defaultmesh");
1237 monkey.transformMatrix = glm::mat4{1.0f};
1238
1239 _renderables.push_back(monkey);
1240
1241 RenderObject map;
1242 map.mesh = get_mesh("empire");
1243 map.material = get_material("texturedmesh");
1244 map.transformMatrix = glm::translate(glm::vec3{5, -10, 0}); // glm::mat4{ 1.0f };
1245
1246 _renderables.push_back(map);
1247
1248 for (int x = -20; x <= 20; x++) {
1249 for (int y = -20; y <= 20; y++) {
1250
1251 RenderObject tri;
1252 tri.mesh = get_mesh("triangle");
1253 tri.material = get_material("defaultmesh");
1254 glm::mat4 translation = glm::translate(glm::mat4{1.0}, glm::vec3(x, 0, y));
1255 glm::mat4 scale = glm::scale(glm::mat4{1.0}, glm::vec3(0.2, 0.2, 0.2));
1256 tri.transformMatrix = translation * scale;
1257
1258 _renderables.push_back(tri);
1259 }
1260 }
1261
1262 Material* texturedMat = get_material("texturedmesh");
1263
1264 VkDescriptorSetAllocateInfo allocInfo = {};
1265 allocInfo.pNext = nullptr;
1266 allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
1267 allocInfo.descriptorPool = _descriptorPool;
1268 allocInfo.descriptorSetCount = 1;
1269 allocInfo.pSetLayouts = &_singleTextureSetLayout;
1270
1271 vkAllocateDescriptorSets(_device, &allocInfo, &texturedMat->textureSet);
1272
1273 VkSamplerCreateInfo samplerInfo = vkinit::sampler_create_info(VK_FILTER_NEAREST);
1274
1275 VkSampler blockySampler;
1276 vkCreateSampler(_device, &samplerInfo, nullptr, &blockySampler);
1277
1278 _mainDeletionQueue.push_function([=]() { vkDestroySampler(_device, blockySampler, nullptr); });
1279
1280 VkDescriptorImageInfo imageBufferInfo;
1281 imageBufferInfo.sampler = blockySampler;
1282 imageBufferInfo.imageView = _loadedTextures["empire_diffuse"].imageView;
1283 imageBufferInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1284
1285 VkWriteDescriptorSet texture1 = vkinit::write_descriptor_image(
1286 VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, texturedMat->textureSet, &imageBufferInfo, 0);
1287
1288 vkUpdateDescriptorSets(_device, 1, &texture1, 0, nullptr);
1289 }
1290
1291 AllocatedBuffer VulkanEngine::create_buffer(size_t allocSize,
1292 VkBufferUsageFlags usage,
1293 VmaMemoryUsage memoryUsage) {
1294 // allocate vertex buffer
1295 VkBufferCreateInfo bufferInfo = {};
1296 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
1297 bufferInfo.pNext = nullptr;
1298 bufferInfo.size = allocSize;
1299
1300 bufferInfo.usage = usage;
1301
1302 // let the VMA library know that this data should be writeable by CPU, but also readable by GPU
1303 VmaAllocationCreateInfo vmaallocInfo = {};
1304 vmaallocInfo.usage = memoryUsage;
1305
1306 AllocatedBuffer newBuffer;
1307
1308 // allocate the buffer
1309 VK_CHECK(vmaCreateBuffer(_allocator,
1310 &bufferInfo,
1311 &vmaallocInfo,
1312 &newBuffer._buffer,
1313 &newBuffer._allocation,
1314 nullptr));
1315
1316 return newBuffer;
1317 }
1318
1319 size_t VulkanEngine::pad_uniform_buffer_size(size_t originalSize) {
1320 // Calculate required alignment based on minimum device offset alignment
1321 size_t minUboAlignment = _gpuProperties.limits.minUniformBufferOffsetAlignment;
1322 size_t alignedSize = originalSize;
1323 if (minUboAlignment > 0) {
1324 alignedSize = (alignedSize + minUboAlignment - 1) & ~(minUboAlignment - 1);
1325 }
1326 return alignedSize;
1327 }
1328
1329 void VulkanEngine::immediate_submit(std::function<void(VkCommandBuffer cmd)>&& function) {
1330 VkCommandBuffer cmd = _uploadContext._commandBuffer;
1331 // begin the command buffer recording. We will use this command buffer exactly once, so we want
1332 // to let vulkan know that
1333 VkCommandBufferBeginInfo cmdBeginInfo =
1334 vkinit::command_buffer_begin_info(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT);
1335
1336 VK_CHECK(vkBeginCommandBuffer(cmd, &cmdBeginInfo));
1337
1338 function(cmd);
1339
1340 VK_CHECK(vkEndCommandBuffer(cmd));
1341
1342 VkSubmitInfo submit = vkinit::submit_info(&cmd);
1343
1344 // submit command buffer to the queue and execute it.
1345 // _renderFence will now block until the graphic commands finish execution
1346 VK_CHECK(vkQueueSubmit(_graphicsQueue, 1, &submit, _uploadContext._uploadFence));
1347
1348 vkWaitForFences(_device, 1, &_uploadContext._uploadFence, true, 9999999999);
1349 vkResetFences(_device, 1, &_uploadContext._uploadFence);
1350
1351 vkResetCommandPool(_device, _uploadContext._commandPool, 0);
1352 }
1353
1354 void VulkanEngine::init_descriptors() {
1355
1356 // create a descriptor pool that will hold 10 uniform buffers
1357 std::vector<VkDescriptorPoolSize> sizes = {{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 10},
1358 {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 10},
1359 {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 10},
1360 {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 10}};
1361
1362 VkDescriptorPoolCreateInfo pool_info = {};
1363 pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
1364 pool_info.flags = 0;
1365 pool_info.maxSets = 10;
1366 pool_info.poolSizeCount = (uint32_t)sizes.size();
1367 pool_info.pPoolSizes = sizes.data();
1368
1369 vkCreateDescriptorPool(_device, &pool_info, nullptr, &_descriptorPool);
1370
1371 VkDescriptorSetLayoutBinding cameraBind = vkinit::descriptorset_layout_binding(
1372 VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0);
1373 VkDescriptorSetLayoutBinding sceneBind = vkinit::descriptorset_layout_binding(
1374 VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,
1375 VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
1376 1);
1377
1378 VkDescriptorSetLayoutBinding bindings[] = {cameraBind, sceneBind};
1379
1380 VkDescriptorSetLayoutCreateInfo setinfo = {};
1381 setinfo.bindingCount = 2;
1382 setinfo.flags = 0;
1383 setinfo.pNext = nullptr;
1384 setinfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
1385 setinfo.pBindings = bindings;
1386
1387 vkCreateDescriptorSetLayout(_device, &setinfo, nullptr, &_globalSetLayout);
1388
1389 VkDescriptorSetLayoutBinding objectBind = vkinit::descriptorset_layout_binding(
1390 VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0);
1391
1392 VkDescriptorSetLayoutCreateInfo set2info = {};
1393 set2info.bindingCount = 1;
1394 set2info.flags = 0;
1395 set2info.pNext = nullptr;
1396 set2info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
1397 set2info.pBindings = &objectBind;
1398
1399 vkCreateDescriptorSetLayout(_device, &set2info, nullptr, &_objectSetLayout);
1400
1401 VkDescriptorSetLayoutBinding textureBind = vkinit::descriptorset_layout_binding(
1402 VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0);
1403
1404 VkDescriptorSetLayoutCreateInfo set3info = {};
1405 set3info.bindingCount = 1;
1406 set3info.flags = 0;
1407 set3info.pNext = nullptr;
1408 set3info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
1409 set3info.pBindings = &textureBind;
1410
1411 vkCreateDescriptorSetLayout(_device, &set3info, nullptr, &_singleTextureSetLayout);
1412
1413 const size_t sceneParamBufferSize =
1414 FRAME_OVERLAP * pad_uniform_buffer_size(sizeof(GPUSceneData));
1415
1416 _sceneParameterBuffer = create_buffer(
1417 sceneParamBufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU);
1418
1419 for (int i = 0; i < FRAME_OVERLAP; i++) {
1420 _frames[i].cameraBuffer = create_buffer(
1421 sizeof(GPUCameraData), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU);
1422
1423 const int MAX_OBJECTS = 10000;
1424 _frames[i].objectBuffer = create_buffer(sizeof(GPUObjectData) * MAX_OBJECTS,
1425 VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
1426 VMA_MEMORY_USAGE_CPU_TO_GPU);
1427
1428 VkDescriptorSetAllocateInfo allocInfo = {};
1429 allocInfo.pNext = nullptr;
1430 allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
1431 allocInfo.descriptorPool = _descriptorPool;
1432 allocInfo.descriptorSetCount = 1;
1433 allocInfo.pSetLayouts = &_globalSetLayout;
1434
1435 vkAllocateDescriptorSets(_device, &allocInfo, &_frames[i].globalDescriptor);
1436
1437 VkDescriptorSetAllocateInfo objectSetAlloc = {};
1438 objectSetAlloc.pNext = nullptr;
1439 objectSetAlloc.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
1440 objectSetAlloc.descriptorPool = _descriptorPool;
1441 objectSetAlloc.descriptorSetCount = 1;
1442 objectSetAlloc.pSetLayouts = &_objectSetLayout;
1443
1444 vkAllocateDescriptorSets(_device, &objectSetAlloc, &_frames[i].objectDescriptor);
1445
1446 VkDescriptorBufferInfo cameraInfo;
1447 cameraInfo.buffer = _frames[i].cameraBuffer._buffer;
1448 cameraInfo.offset = 0;
1449 cameraInfo.range = sizeof(GPUCameraData);
1450
1451 VkDescriptorBufferInfo sceneInfo;
1452 sceneInfo.buffer = _sceneParameterBuffer._buffer;
1453 sceneInfo.offset = 0;
1454 sceneInfo.range = sizeof(GPUSceneData);
1455
1456 VkDescriptorBufferInfo objectBufferInfo;
1457 objectBufferInfo.buffer = _frames[i].objectBuffer._buffer;
1458 objectBufferInfo.offset = 0;
1459 objectBufferInfo.range = sizeof(GPUObjectData) * MAX_OBJECTS;
1460
1461 VkWriteDescriptorSet cameraWrite = vkinit::write_descriptor_buffer(
1462 VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, _frames[i].globalDescriptor, &cameraInfo, 0);
1463
1464 VkWriteDescriptorSet sceneWrite = vkinit::write_descriptor_buffer(
1465 VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, _frames[i].globalDescriptor, &sceneInfo, 1);
1466
1467 VkWriteDescriptorSet objectWrite = vkinit::write_descriptor_buffer(
1468 VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, _frames[i].objectDescriptor, &objectBufferInfo, 0);
1469
1470 VkWriteDescriptorSet setWrites[] = {cameraWrite, sceneWrite, objectWrite};
1471
1472 vkUpdateDescriptorSets(_device, 3, setWrites, 0, nullptr);
1473 }
1474
1475 _mainDeletionQueue.push_function([&]() {
1476 vmaDestroyBuffer(
1477 _allocator, _sceneParameterBuffer._buffer, _sceneParameterBuffer._allocation);
1478
1479 vkDestroyDescriptorSetLayout(_device, _objectSetLayout, nullptr);
1480 vkDestroyDescriptorSetLayout(_device, _globalSetLayout, nullptr);
1481 vkDestroyDescriptorSetLayout(_device, _singleTextureSetLayout, nullptr);
1482
1483 vkDestroyDescriptorPool(_device, _descriptorPool, nullptr);
1484
1485 for (int i = 0; i < FRAME_OVERLAP; i++) {
1486 vmaDestroyBuffer(
1487 _allocator, _frames[i].cameraBuffer._buffer, _frames[i].cameraBuffer._allocation);
1488
1489 vmaDestroyBuffer(
1490 _allocator, _frames[i].objectBuffer._buffer, _frames[i].objectBuffer._allocation);
1491 }
1492 });
1493 }
1494