vOOlkan
An object oriented approach to Vulkan
Drawer.h
Go to the documentation of this file.
1#ifndef VULKAN_DRAWER
2#define VULKAN_DRAWER
3
4#include <vulkan/vulkan.h>
5#include <vector>
6
7#include "LogicalDevice.h"
8#include "Swapchain.h"
9#include "RenderPass.h"
10#include "Pipeline.h"
11#include "Framebuffer.h"
12#include "CommandBuffer.h"
13#include "CommandBufferPool.h"
14#include "Window.h"
15#include "Queue.h"
16#include "Fence.h"
17#include "Semaphore.h"
18#include "VertexBuffer.h"
19#include "IndexBuffer.h"
20#include "DescriptorSetPool.h"
21#include "DescriptorSet.h"
22#include "VulkanException.h"
23#include "Set.h"
24#include "DynamicSet.h"
25#include "StaticSet.h"
26
27
28namespace Vulkan { class Drawer; }
29
30
35public:
36
37 //TODO rewrite this Doxygen
50 Drawer(const LogicalDevice& virtualGpu,
51 const PhysicalDevice& realGpu,
52 const Window& window,
53 const WindowSurface& windowSurface,
54 Swapchain& swapchain,
55 DepthImage& depthBuffer,
56 const CommandBufferPool& commandBufferPool,
57 const PipelineOptions::RenderPass& renderPass,
58 std::vector<Pipeline*> pipelines,
59 const std::vector<std::reference_wrapper<StaticSet>>& globalSets,
60 const std::vector<std::reference_wrapper<DynamicSet>>& perObjectSets,
61 unsigned int framesInFlight = 2)
62 :
63 framesInFlight{ framesInFlight },
64 currentFrame{ 0 },
65 virtualGpu{ virtualGpu },
66 realGpu{ realGpu },
67 window{ window },
68 windowSurface{ windowSurface },
69 depthBuffer{ depthBuffer },
70 renderPass{ renderPass },
71 pipelines{ pipelines },
72 globalDescriptorSets{},
73 perObjectDescriptorSets{},
74 swapchain{ swapchain },
75 commandBufferPool{ commandBufferPool } ,
76 descriptorSetPool{ virtualGpu, framesInFlight*10, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER }{
77
78 framebuffers = Framebuffer::generateFramebufferForEachSwapchainImageView(virtualGpu, renderPass, swapchain, depthBuffer["base"]);
79 //for each frame in flight instantiate the required objects
80 for (unsigned int i = 0; i < framesInFlight; ++i) {
81 fences.emplace_back(virtualGpu);
82 imageAvailableSemaphores.emplace_back(virtualGpu);
83 renderFinishedSemaphores.emplace_back(virtualGpu);
84 commandBuffers.emplace_back(virtualGpu, commandBufferPool);
85 globalDescriptorSets.emplace_back();
86 perObjectDescriptorSets.emplace_back();
87
88 //globalSets and perObjectSets must have the same size
89 //for each pipeline instantiate the descriptor sets
90 for (unsigned int j = 0; j < globalSets.size(); ++j) {
91 globalDescriptorSets[i].emplace_back(virtualGpu, descriptorSetPool, globalSets[j]);
92 perObjectDescriptorSets[i].emplace_back(virtualGpu, descriptorSetPool, perObjectSets[j]);
93 }
94 }
95 }
96
97
98 //TODO specify which are the global commands and which are the per vertex commands
107 /*template<typename... Args, template<typename...> class... Command> requires (std::same_as<Command<>, std::tuple<>> && ...)
108 void draw(const Buffers::VertexBuffer& vertexBuffer, const Buffers::IndexBuffer& indexBuffer, Command<void(*)(VkCommandBuffer, Args...), Args...>&&... commands) {
109 uint32_t obtainedSwapchainImageIndex; //the index of the image of the swapchain we'll draw to
110 vkWaitForFences(+virtualGpu, 1, &+fences[currentFrame], VK_TRUE, UINT64_MAX); //wait until a swapchain image is free
111 //get an image from the swapchain
112 if (VkResult result = vkAcquireNextImageKHR(+virtualGpu, +swapchain, UINT64_MAX, +imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &obtainedSwapchainImageIndex); result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) {
113 recreateSwapchain();
114 return;
115 }
116 else if (result != VK_SUCCESS) {
117 throw VulkanException{ "Failed to acquire swapchain image", result };
118 }
119 vkResetFences(+virtualGpu, 1, &+fences[currentFrame]); //reset the just signaled fence to an unsignalled state
120
121 //fill the command buffer
122 commandBuffers[currentFrame].reset(renderPass, framebuffers[obtainedSwapchainImageIndex], pipeline);
123 commandBuffers[currentFrame].addCommands(std::forward<Command...>(commands...));
124 commandBuffers[currentFrame].endCommand();
125
126 //struct to submit a command buffer to a queue
127 VkSemaphore waitSemaphores[] = { +imageAvailableSemaphores[currentFrame] }; //semaphore used to signal that an image is available to render to
128 VkSemaphore signalSemaphores[] = { +renderFinishedSemaphores[currentFrame]}; //semaphore used to signal that the render finished
129 VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; //where to wait for an image (first semaphore). You can still run the vertex shader without an image, but you have to wait for an image for the fragment shader
130
131 VkSubmitInfo submitInfo{};
132 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
133 submitInfo.waitSemaphoreCount = 1;
134 submitInfo.pWaitSemaphores = waitSemaphores;
135 submitInfo.pWaitDstStageMask = waitStages;
136 submitInfo.commandBufferCount = 1;
137 submitInfo.pCommandBuffers = &+commandBuffers[currentFrame];
138 submitInfo.signalSemaphoreCount = 1;
139 submitInfo.pSignalSemaphores = signalSemaphores;
140
141 if (VkResult result = vkQueueSubmit(+virtualGpu[QueueFamily::GRAPHICS], 1, &submitInfo, +fences[currentFrame]); result != VK_SUCCESS) {
142 throw VulkanException{ "Failed to submit the command buffer for the current frame", result };
143 }
144
145
146 VkSwapchainKHR swapchains[] = { +swapchain };
147 VkPresentInfoKHR presentInfo{};
148 presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
149 presentInfo.waitSemaphoreCount = 1;
150 presentInfo.pWaitSemaphores = signalSemaphores;
151 presentInfo.swapchainCount = 1;
152 presentInfo.pSwapchains = swapchains;
153 presentInfo.pImageIndices = &obtainedSwapchainImageIndex;
154
155 if (VkResult result = vkQueuePresentKHR(+virtualGpu[QueueFamily::PRESENTATION], &presentInfo); result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) {
156 recreateSwapchain();
157 }
158 else if (result != VK_SUCCESS) {
159 throw VulkanException{ "Failed to present image", result };
160 }
161
162 increaseCurrentFrame();
163 }
164 */
165
166 //TODO probably it should be a pair of references
174 template<template<typename, typename>class... P> requires (std::same_as<P<int, int>, std::pair<int, int>> && ...)
175 void draw(const P<std::reference_wrapper<Buffers::VertexBuffer>, std::reference_wrapper<Buffers::IndexBuffer>>&... buffers) {
176 uint32_t obtainedSwapchainImageIndex{}; //the index of the image of the swapchain we'll draw to
177 vkWaitForFences(+virtualGpu, 1, &+fences[currentFrame], VK_TRUE, UINT64_MAX); //wait until a swapchain image is free
178 //get an image from the swapchain
179 if (VkResult result = vkAcquireNextImageKHR(+virtualGpu, +swapchain, UINT64_MAX, +imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &obtainedSwapchainImageIndex); result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) {
180 recreateSwapchain();
181 return;
182 }
183 else if (result != VK_SUCCESS) {
184 throw VulkanException{ "Failed to acquire swapchain image", result };
185 }
186 vkResetFences(+virtualGpu, 1, &+fences[currentFrame]); //reset the just signaled fence to an unsignalled state
187
188 commandBuffers[currentFrame].reset(renderPass, framebuffers[obtainedSwapchainImageIndex]);
189 //fill the command buffer (for each pipeline)
190 unsigned int counter = 0;
191 ([&](const Buffers::VertexBuffer& vertexBuffer, const Buffers::IndexBuffer& indexBuffer) {
192 VkDeviceSize offsets[] = { 0 };
193 commandBuffers[currentFrame].addCommand(vkCmdBindPipeline, VK_PIPELINE_BIND_POINT_GRAPHICS, +*pipelines[counter]);
194 commandBuffers[currentFrame].addCommand(vkCmdBindVertexBuffers, 0, 1, &+vertexBuffer, offsets);
195 commandBuffers[currentFrame].addCommand(vkCmdBindIndexBuffer, +indexBuffer, 0, VK_INDEX_TYPE_UINT32);
196 commandBuffers[currentFrame].addCommand(vkCmdBindDescriptorSets, VK_PIPELINE_BIND_POINT_GRAPHICS, +pipelines[counter]->getLayout(), 0, 1, &+globalDescriptorSets[currentFrame][counter], 0, nullptr); //TODO at the moment the binding of the global descriptors is dynamic, but it should be static (not a big deal really)
197 for (int i = 0; i < indexBuffer.getModelsCount(); ++i) {
198 commandBuffers[currentFrame].addCommand(vkCmdBindDescriptorSets, VK_PIPELINE_BIND_POINT_GRAPHICS, +pipelines[counter]->getLayout(), 1, 1, &+perObjectDescriptorSets[currentFrame][counter], perObjectDescriptorSets[currentFrame][counter].getSet().getAmountOfBindings(), perObjectDescriptorSets[currentFrame][counter].getSet().getDynamicDistances(i).data());
199 commandBuffers[currentFrame].addCommand(vkCmdDrawIndexed, indexBuffer.getModelIndexesCount(i), 1, indexBuffer.getModelOffset(i), 0, 0);
200 }
201 counter++;
202 }(buffers.first, buffers.second), ...);
203
204 commandBuffers[currentFrame].addCommand(vkCmdEndRenderPass);
205 commandBuffers[currentFrame].endCommand();
206
207 //submit the command buffer to a queue
208 std::vector<VkSemaphore> waitSemaphores = { +imageAvailableSemaphores[currentFrame] }; //semaphore used to signal that an image is available to render to
209 std::vector<VkSemaphore> signalSemaphores = { +renderFinishedSemaphores[currentFrame] }; //semaphore used to signal that the render finished
210 std::vector<VkPipelineStageFlags> waitStages = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; //where to wait for an image (first semaphore). You can still run the vertex shader without an image, but you have to wait for an image for the fragment shader
211 commandBuffers[currentFrame].sendCommand(virtualGpu[QueueFamily::GRAPHICS], waitSemaphores, signalSemaphores, waitStages, fences[currentFrame]);
212
213 //submit the rendered image back to the swapchain for presentation
214 std::vector<VkSwapchainKHR> swapchains = { +swapchain };
215 VkPresentInfoKHR presentInfo{};
216 presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
217 presentInfo.waitSemaphoreCount = signalSemaphores.size();
218 presentInfo.pWaitSemaphores = signalSemaphores.data();
219 presentInfo.swapchainCount = swapchains.size();
220 presentInfo.pSwapchains = swapchains.data();
221 presentInfo.pImageIndices = &obtainedSwapchainImageIndex;
222
223 if (VkResult result = vkQueuePresentKHR(+virtualGpu[QueueFamily::PRESENTATION], &presentInfo); result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) {
224 recreateSwapchain();
225 }
226 else if (result != VK_SUCCESS) {
227 throw VulkanException{ "Failed to present image", result };
228 }
229
230 increaseCurrentFrame();
231 }
232
233
234
235
236private:
237
238 void increaseCurrentFrame() {
239 currentFrame = (currentFrame + 1) % framesInFlight;
240 }
241
242
243 void recreateSwapchain() {
244 //if window is minimized, pause application
245 int width = 0, height = 0;
246 do {
247 glfwGetFramebufferSize(+window, &width, &height);
248 glfwWaitEvents();
249 } while (width == 0 || height == 0);
250
251 vkDeviceWaitIdle(+virtualGpu); //wait for all the resources to be free
252
253 //recreate new swapchain
254 swapchain = Swapchain(realGpu, virtualGpu, windowSurface, window, swapchain);
255 depthBuffer = DepthImage{ virtualGpu, realGpu, swapchain.getResolution() };
256 framebuffers = Framebuffer::generateFramebufferForEachSwapchainImageView(virtualGpu, renderPass, swapchain, depthBuffer["base"]);
257 commandBuffers = std::vector<CommandBuffer>{};
258
259 for (unsigned int i = 0; i < framesInFlight; ++i) {
260 commandBuffers.emplace_back(virtualGpu, commandBufferPool);
261 }
262 }
263
264 //Each string (name) is bound to a vector of synch primitives. Each element in the vector is a different primitive, and each one is used for a specific frame in flight.
265 //In reality each name is bound to a pair, where the second element is the actual primitive hold by the first one, for performance reasons.
266 //std::map<std::string, std::pair<std::vector<Semaphore>, std::vector<VkSemaphore>>> semaphores;
267 //std::map<std::string, std::pair<std::vector<Fence>, std::vector<VkFence>>> fences;
268
269 unsigned int currentFrame; //indicates which set of resources to use for the current frame (0 < x < maxFramesInFlight)
270 unsigned int framesInFlight; //maximum number of frames that can be rendered at the same time(of course no more than the number of swap chain images)
271
272 const LogicalDevice& virtualGpu;
273 const PhysicalDevice& realGpu;
274 const Window& window;
275 const WindowSurface& windowSurface;
276 const PipelineOptions::RenderPass& renderPass;
277 std::vector<Pipeline*> pipelines;
278 const CommandBufferPool& commandBufferPool;
279
280 Swapchain& swapchain;
281 DepthImage& depthBuffer;
282 std::vector<Framebuffer> framebuffers;
283 DescriptorSetPool descriptorSetPool;
284
285 std::vector<SynchronizationPrimitives::Fence> fences;
286 std::vector<SynchronizationPrimitives::Semaphore> imageAvailableSemaphores; //tells when an image is occupied by rendering
287 std::vector<SynchronizationPrimitives::Semaphore> renderFinishedSemaphores; //tells when the rendeing of the image ends
288 std::vector<CommandBuffer> commandBuffers;
289 std::vector<std::vector<DescriptorSet<StaticSet>>> globalDescriptorSets; //descriptor sets (one per frame in flight x one per pipeline) for the global info
290 std::vector<std::vector<DescriptorSet<DynamicSet>>> perObjectDescriptorSets; //descriptor sets (one per frame in flight x one per pipeline) for the per-object info
291};
292
293#endif
Definition: IndexBuffer.h:15
Definition: VertexBuffer.h:16
A CommandBufferPool is an object which is used to allocate CommandBuffers.
Definition: CommandBufferPool.h:14
Definition: DepthImage.h:13
A Drawer is a class which holds all of the resources to draw frames on screen, such as the synchroniz...
Definition: Drawer.h:34
Drawer(const LogicalDevice &virtualGpu, const PhysicalDevice &realGpu, const Window &window, const WindowSurface &windowSurface, Swapchain &swapchain, DepthImage &depthBuffer, const CommandBufferPool &commandBufferPool, const PipelineOptions::RenderPass &renderPass, std::vector< Pipeline * > pipelines, const std::vector< std::reference_wrapper< StaticSet > > &globalSets, const std::vector< std::reference_wrapper< DynamicSet > > &perObjectSets, unsigned int framesInFlight=2)
Creates all the resources needed to draw something on screen.
Definition: Drawer.h:50
void draw(const P< std::reference_wrapper< Buffers::VertexBuffer >, std::reference_wrapper< Buffers::IndexBuffer > > &... buffers)
Draws the vertexBuffer by running the specified commands.
Definition: Drawer.h:175
static std::vector< Framebuffer > generateFramebufferForEachSwapchainImageView(const LogicalDevice &virtualGpu, const PipelineOptions::RenderPass &renderPass, const Swapchain &swapchain, const IV &... otherAttachments)
Creates a framebuffer for each image view in the specified swapchain. It also attaches the specified ...
Definition: Framebuffer.h:88
A logical device is an abstraction of the physical GPU which we can mainly use to send commands.
Definition: LogicalDevice.h:15
Represents the GPU (or any other device) that will be used with Vulkan to perform computer graphics.
Definition: PhysicalDevice.h:17
A RenderPass is the set of attachments, the way they are used, and the rendering work that is perform...
Definition: RenderPass.h:20
Object which holds images to be presented to the WindowSurface.
Definition: Swapchain.h:19
std::pair< unsigned int, unsigned int > getResolution() const
Returns the width and height of the images in this swapchain.
Definition: Swapchain.cpp:95
Generic runtime exception thrown by Vulkan-related functions.
Definition: VulkanException.h:13
Manages the creation and lifetime of an OS window.
Definition: Window.h:15
A window surface is the connection between Vulkan and the OS windows environment.
Definition: WindowSurface.h:13
Types of queue families.
Definition: Animations.h:17