source: opengl-game/sdl-game.hpp@ 996dd3e

feature/imgui-sdl
Last change on this file since 996dd3e was 996dd3e, checked in by Dmitry Portnoy <dportnoy@…>, 3 years ago

Completely remove storage buffers from the GraphicsPipeline_Vulkan class and start moving storage buffer operations out of the addObject() and updateObject() functions

  • Property mode set to 100644
File size: 19.3 KB
Line 
1#ifndef _SDL_GAME_H
2#define _SDL_GAME_H
3
4#include <chrono>
5#include <map>
6#include <vector>
7
8#include <vulkan/vulkan.h>
9
10#include <SDL2/SDL.h>
11
12#define GLM_FORCE_RADIANS
13#define GLM_FORCE_DEPTH_ZERO_TO_ONE // Since, in Vulkan, the depth range is 0 to 1 instead of -1 to 1
14#define GLM_FORCE_RIGHT_HANDED
15
16#include <glm/glm.hpp>
17#include <glm/gtc/matrix_transform.hpp>
18
19#include "IMGUI/imgui_impl_vulkan.h"
20
21#include "consts.hpp"
22#include "vulkan-utils.hpp"
23#include "graphics-pipeline_vulkan.hpp"
24#include "game-gui-sdl.hpp"
25
26using namespace glm;
27using namespace std;
28using namespace std::chrono;
29
30#define VulkanGame NewVulkanGame
31
32#ifdef NDEBUG
33 const bool ENABLE_VALIDATION_LAYERS = false;
34#else
35 const bool ENABLE_VALIDATION_LAYERS = true;
36#endif
37
38// TODO: Consider if there is a better way of dealing with all the vertex types and ssbo types, maybe
39// by consolidating some and trying to keep new ones to a minimum
40
41struct ModelVertex {
42 vec3 pos;
43 vec3 color;
44 vec2 texCoord;
45 vec3 normal;
46 unsigned int objIndex;
47};
48
49struct LaserVertex {
50 vec3 pos;
51 vec2 texCoord;
52 unsigned int objIndex;
53};
54
55struct ExplosionVertex {
56 vec3 particleStartVelocity;
57 float particleStartTime;
58 unsigned int objIndex;
59};
60
61struct SSBO_ModelObject {
62 alignas(16) mat4 model;
63};
64
65struct SSBO_Asteroid {
66 alignas(16) mat4 model;
67 alignas(4) float hp;
68 alignas(4) unsigned int deleted;
69};
70
71struct UBO_VP_mats {
72 alignas(16) mat4 view;
73 alignas(16) mat4 proj;
74};
75
76// TODO: Use this struct for uniform buffers as well and probably combine it with the VulkanBuffer class
77// Also, probably better to make this a vector of structs where each struct
78// has a VkBuffer, VkDeviceMemory, and VkDescriptorBufferInfo
79struct StorageBufferSet {
80 vector<VkBuffer> buffers;
81 vector<VkDeviceMemory> memory;
82 vector<VkDescriptorBufferInfo> infoSet;
83};
84
85// TODO: Change the index type to uint32_t and check the Vulkan Tutorial loading model section as a reference
86// TODO: Create a typedef for index type so I can easily change uin16_t to something else later
87// TODO: Maybe create a typedef for each of the templated SceneObject types
88template<class VertexType, class SSBOType>
89struct SceneObject {
90 vector<VertexType> vertices;
91 vector<uint16_t> indices;
92 SSBOType ssbo;
93
94 mat4 model_base;
95 mat4 model_transform;
96
97 bool modified;
98
99 // TODO: Figure out if I should make child classes that have these fields instead of putting them in the
100 // parent class
101 vec3 center; // currently only matters for asteroids
102 float radius; // currently only matters for asteroids
103 SceneObject<ModelVertex, SSBO_Asteroid>* targetAsteroid; // currently only used for lasers
104};
105
106// TODO: Have to figure out how to include an optional ssbo parameter for each object
107// Could probably use the same approach to make indices optional
108// Figure out if there are sufficient use cases to make either of these optional or is it fine to make
109// them mamdatory
110
111
112// TODO: Look into using dynamic_cast to check types of SceneObject and EffectOverTime
113
114// TODO: Maybe move this to a different header
115
116enum UIValueType {
117 UIVALUE_INT,
118 UIVALUE_DOUBLE,
119};
120
121struct UIValue {
122 UIValueType type;
123 string label;
124 void* value;
125
126 UIValue(UIValueType _type, string _label, void* _value) : type(_type), label(_label), value(_value) {}
127};
128
129class VulkanGame {
130
131 public:
132
133 VulkanGame();
134 ~VulkanGame();
135
136 void run(int width, int height, unsigned char guiFlags);
137
138 private:
139
140 static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(
141 VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
142 VkDebugUtilsMessageTypeFlagsEXT messageType,
143 const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
144 void* pUserData);
145
146 // TODO: Maybe pass these in as parameters to some Camera class
147 const float NEAR_CLIP = 0.1f;
148 const float FAR_CLIP = 100.0f;
149 const float FOV_ANGLE = 67.0f; // means the camera lens goes from -33 deg to 33 deg
150
151 bool done;
152
153 vec3 cam_pos;
154
155 // TODO: Good place to start using smart pointers
156 GameGui* gui;
157
158 SDL_version sdlVersion;
159 SDL_Window* window;
160
161 VkInstance instance;
162 VkDebugUtilsMessengerEXT debugMessenger;
163 VkSurfaceKHR vulkanSurface;
164 VkPhysicalDevice physicalDevice;
165 VkDevice device;
166
167 VkQueue graphicsQueue;
168 VkQueue presentQueue;
169
170 // TODO: Maybe make a swapchain struct for convenience
171 VkSurfaceFormatKHR swapChainSurfaceFormat;
172 VkPresentModeKHR swapChainPresentMode;
173 VkExtent2D swapChainExtent;
174 uint32_t swapChainMinImageCount;
175 uint32_t swapChainImageCount;
176
177 VkSwapchainKHR swapChain;
178 vector<VkImage> swapChainImages;
179 vector<VkImageView> swapChainImageViews;
180 vector<VkFramebuffer> swapChainFramebuffers;
181
182 VkRenderPass renderPass;
183
184 VkCommandPool resourceCommandPool;
185
186 vector<VkCommandPool> commandPools;
187 vector<VkCommandBuffer> commandBuffers;
188
189 VulkanImage depthImage;
190
191 // These are per frame
192 vector<VkSemaphore> imageAcquiredSemaphores;
193 vector<VkSemaphore> renderCompleteSemaphores;
194
195 // These are per swap chain image
196 vector<VkFence> inFlightFences;
197
198 uint32_t imageIndex;
199 uint32_t currentFrame;
200
201 bool shouldRecreateSwapChain;
202
203 VkSampler textureSampler;
204
205 VulkanImage floorTextureImage;
206 VkDescriptorImageInfo floorTextureImageDescriptor;
207
208 mat4 viewMat, projMat;
209
210 // Maybe at some point create an imgui pipeline class, but I don't think it makes sense right now
211 VkDescriptorPool imguiDescriptorPool;
212
213 // TODO: Probably restructure the GraphicsPipeline_Vulkan class based on what I learned about descriptors and textures
214 // while working on graphics-library. Double-check exactly what this was and note it down here.
215 // Basically, I think the point was that if I have several models that all use the same shaders and, therefore,
216 // the same pipeline, but use different textures, the approach I took when initially creating GraphicsPipeline_Vulkan
217 // wouldn't work since the whole pipeline couldn't have a common set of descriptors for the textures
218 GraphicsPipeline_Vulkan<ModelVertex> modelPipeline;
219
220 StorageBufferSet storageBuffers_modelPipeline;
221
222 // TODO: Maybe make the ubo objects part of the pipeline class since there's only one ubo
223 // per pipeline.
224 // Or maybe create a higher level wrapper around GraphicsPipeline_Vulkan to hold things like
225 // the objects vector, the ubo, and the ssbo
226
227 // TODO: Rename *_VP_mats to *_uniforms and possibly use different types for each one
228 // if there is a need to add other uniform variables to one or more of the shaders
229
230 vector<SceneObject<ModelVertex, SSBO_ModelObject>> modelObjects;
231
232 vector<VkBuffer> uniformBuffers_modelPipeline;
233 vector<VkDeviceMemory> uniformBuffersMemory_modelPipeline;
234 vector<VkDescriptorBufferInfo> uniformBufferInfoList_modelPipeline;
235
236 UBO_VP_mats object_VP_mats;
237
238 /*** High-level vars ***/
239
240 // TODO: Just typedef the type of this function to RenderScreenFn or something since it's used in a few places
241 void (VulkanGame::* currentRenderScreenFn)(int width, int height);
242
243 map<string, vector<UIValue>> valueLists;
244
245 int score;
246 float fps;
247
248 // TODO: Make a separate singleton Timer class
249 time_point<steady_clock> startTime;
250 float fpsStartTime, curTime, prevTime, elapsedTime;
251
252 int frameCount;
253
254 /*** Functions ***/
255
256 bool initUI(int width, int height, unsigned char guiFlags);
257 void initVulkan();
258 void initGraphicsPipelines();
259 void initMatrices();
260 void renderLoop();
261 void updateScene();
262 void cleanup();
263
264 void createVulkanInstance(const vector<const char*>& validationLayers);
265 void setupDebugMessenger();
266 void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo);
267 void createVulkanSurface();
268 void pickPhysicalDevice(const vector<const char*>& deviceExtensions);
269 bool isDeviceSuitable(VkPhysicalDevice physicalDevice, const vector<const char*>& deviceExtensions);
270 void createLogicalDevice(const vector<const char*>& validationLayers,
271 const vector<const char*>& deviceExtensions);
272 void chooseSwapChainProperties();
273 void createSwapChain();
274 void createImageViews();
275 void createResourceCommandPool();
276 void createImageResources();
277 VkFormat findDepthFormat(); // TODO: Declare/define (in the cpp file) this function in some util functions section
278 void createRenderPass();
279 void createCommandPools();
280 void createFramebuffers();
281 void createCommandBuffers();
282 void createSyncObjects();
283
284 void createTextureSampler();
285
286 void initImGuiOverlay();
287 void cleanupImGuiOverlay();
288
289 // TODO: Maybe move these to a different class, possibly VulkanBuffer or some new related class
290
291 void createBufferSet(VkDeviceSize bufferSize, VkBufferUsageFlags flags, VkMemoryPropertyFlags properties,
292 vector<VkBuffer>& buffers, vector<VkDeviceMemory>& buffersMemory,
293 vector<VkDescriptorBufferInfo>& bufferInfoList);
294
295 // TODO: See if it makes sense to rename this to resizeBufferSet() and use it to resize other types of buffers as well
296 // TODO: Remove the need for templating, which is only there so a GraphicsPupeline_Vulkan can be passed in
297 template<class VertexType, class SSBOType>
298 void resizeStorageBufferSet(StorageBufferSet& set, VkCommandPool commandPool, VkQueue graphicsQueue,
299 GraphicsPipeline_Vulkan<VertexType>& pipeline);
300
301 template<class SSBOType>
302 void updateStorageBuffer(StorageBufferSet& storageBufferSet, size_t objIndex, SSBOType& ssbo);
303
304 // TODO: Since addObject() returns a reference to the new object now,
305 // stop using objects.back() to access the object that was just created
306 template<class VertexType, class SSBOType>
307 SceneObject<VertexType, SSBOType>& addObject(vector<SceneObject<VertexType, SSBOType>>& objects,
308 GraphicsPipeline_Vulkan<VertexType>& pipeline,
309 const vector<VertexType>& vertices, vector<uint16_t> indices,
310 SSBOType ssbo, StorageBufferSet& storageBuffers,
311 bool pipelinesCreated);
312
313 template<class VertexType>
314 vector<VertexType> addObjectIndex(unsigned int objIndex, vector<VertexType> vertices);
315
316 template<class VertexType>
317 vector<VertexType> addVertexNormals(vector<VertexType> vertices);
318
319 template<class VertexType, class SSBOType>
320 void centerObject(SceneObject<VertexType, SSBOType>& object);
321
322 template<class VertexType, class SSBOType>
323 void updateObject(vector<SceneObject<VertexType, SSBOType>>& objects,
324 GraphicsPipeline_Vulkan<VertexType>& pipeline, size_t index);
325
326 void renderFrame(ImDrawData* draw_data);
327 void presentFrame();
328
329 void recreateSwapChain();
330
331 void cleanupSwapChain();
332
333 /*** High-level functions ***/
334
335 void renderMainScreen(int width, int height);
336 void renderGameScreen(int width, int height);
337
338 void initGuiValueLists(map<string, vector<UIValue>>& valueLists);
339 void renderGuiValueList(vector<UIValue>& values);
340
341 void goToScreen(void (VulkanGame::* renderScreenFn)(int width, int height));
342 void quitGame();
343};
344
345template<class VertexType, class SSBOType>
346void VulkanGame::resizeStorageBufferSet(StorageBufferSet& set, VkCommandPool commandPool, VkQueue graphicsQueue,
347 GraphicsPipeline_Vulkan<VertexType>& pipeline) {
348 pipeline.objectCapacity *= 2;
349 VkDeviceSize bufferSize = pipeline.objectCapacity * sizeof(SSBOType);
350
351 for (size_t i = 0; i < set.buffers.size(); i++) {
352 VkBuffer newStorageBuffer;
353 VkDeviceMemory newStorageBufferMemory;
354
355 VulkanUtils::createBuffer(device, physicalDevice, bufferSize,
356 VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
357 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
358 newStorageBuffer, newStorageBufferMemory);
359
360 VulkanUtils::copyBuffer(device, commandPool, set.buffers[i], newStorageBuffer,
361 0, 0, pipeline.numObjects * sizeof(SSBOType), graphicsQueue);
362
363 vkDestroyBuffer(device, set.buffers[i], nullptr);
364 vkFreeMemory(device, set.memory[i], nullptr);
365
366 set.buffers[i] = newStorageBuffer;
367 set.memory[i] = newStorageBufferMemory;
368
369 set.infoSet[i].buffer = set.buffers[i];
370 set.infoSet[i].offset = 0; // This is the offset from the start of the buffer, so always 0 for now
371 set.infoSet[i].range = bufferSize; // Size of the update starting from offset, or VK_WHOLE_SIZE
372 }
373}
374
375// TODO: See if it makes sense to pass in the current swapchain index instead of updating all of them
376template<class SSBOType>
377void VulkanGame::updateStorageBuffer(StorageBufferSet& storageBufferSet, size_t objIndex, SSBOType& ssbo) {
378 for (size_t i = 0; i < storageBufferSet.memory.size(); i++) {
379 VulkanUtils::copyDataToMemory(device, ssbo, storageBufferSet.memory[i], objIndex * sizeof(SSBOType));
380 }
381}
382
383// TODO: Right now, it's basically necessary to pass the identity matrix in for ssbo.model
384// and to change the model matrix later by setting model_transform and then calling updateObject()
385// Figure out a better way to allow the model matrix to be set during object creation
386
387// TODO: Maybe return a reference to the object from this method if I decide that updating it
388// immediately after creation is a good idea (such as setting model_base)
389// Currently, model_base is set like this in a few places and the radius is set for asteroids
390// to account for scaling
391template<class VertexType, class SSBOType>
392SceneObject<VertexType, SSBOType>& VulkanGame::addObject(vector<SceneObject<VertexType, SSBOType>>& objects,
393 GraphicsPipeline_Vulkan<VertexType>& pipeline,
394 const vector<VertexType>& vertices, vector<uint16_t> indices,
395 SSBOType ssbo, StorageBufferSet& storageBuffers,
396 bool pipelinesCreated) {
397 // TODO: Use the model field of ssbo to set the object's model_base
398 // currently, the passed in model is useless since it gets overridden in updateObject() anyway
399 size_t numVertices = pipeline.getNumVertices();
400
401 for (uint16_t& idx : indices) {
402 idx += numVertices;
403 }
404
405 objects.push_back({ vertices, indices, ssbo, mat4(1.0f), mat4(1.0f), false });
406
407 SceneObject<VertexType, SSBOType>& obj = objects.back();
408
409 // TODO: Specify whether to center the object outside of this function or, worst case, maybe
410 // with a boolean being passed in here, so that I don't have to rely on checking the specific object
411 // type
412 if (!is_same_v<VertexType, LaserVertex> && !is_same_v<VertexType, ExplosionVertex>) {
413 centerObject(obj);
414 }
415
416 pipeline.addObject(obj.vertices, obj.indices, resourceCommandPool, graphicsQueue);
417
418 // TODO: Probably move the resizing to the VulkanBuffer class
419 // First, try moving this out of addObject
420 bool resizeStorageBuffer = pipeline.numObjects == pipeline.objectCapacity;
421
422 if (resizeStorageBuffer) {
423 resizeStorageBufferSet<VertexType, SSBOType>(storageBuffers, resourceCommandPool, graphicsQueue, pipeline);
424 pipeline.cleanup();
425
426 // Assume the SSBO is always the 2nd binding
427 // TODO: Figure out a way to make this more flexible
428 pipeline.updateDescriptorInfo(1, &storageBuffers.infoSet);
429 }
430
431 pipeline.numObjects++;
432
433 // TODO: Figure out why I am destroying and recreating the ubos when the swap chain is recreated,
434 // but am reusing the same ssbos. Maybe I don't need to recreate the ubos.
435
436 if (pipelinesCreated) {
437 // TODO: See if I can avoid doing this when recreating the pipeline
438 vkDeviceWaitIdle(device);
439
440 for (uint32_t i = 0; i < swapChainImageCount; i++) {
441 vkFreeCommandBuffers(device, commandPools[i], 1, &commandBuffers[i]);
442 }
443
444 // TODO: The pipeline recreation only has to be done once per frame where at least
445 // one SSBO is resized.
446 // Refactor the logic to check for any resized SSBOs after all objects for the frame
447 // are created and then recreate each of the corresponding pipelines only once per frame
448
449 // TODO: Also, verify if I actually need to recreate all of these, or maybe just the descriptor sets, for instance
450
451 if (resizeStorageBuffer) {
452 pipeline.createPipeline(pipeline.vertShaderFile, pipeline.fragShaderFile);
453 pipeline.createDescriptorPool(swapChainImages);
454 pipeline.createDescriptorSets(swapChainImages);
455 }
456
457 createCommandBuffers();
458 }
459
460 return obj;
461}
462
463template<class VertexType>
464vector<VertexType> VulkanGame::addObjectIndex(unsigned int objIndex, vector<VertexType> vertices) {
465 for (VertexType& vertex : vertices) {
466 vertex.objIndex = objIndex;
467 }
468
469 return vertices;
470}
471
472template<class VertexType>
473vector<VertexType> VulkanGame::addVertexNormals(vector<VertexType> vertices) {
474 for (unsigned int i = 0; i < vertices.size(); i += 3) {
475 vec3 p1 = vertices[i].pos;
476 vec3 p2 = vertices[i + 1].pos;
477 vec3 p3 = vertices[i + 2].pos;
478
479 vec3 normal = normalize(cross(p2 - p1, p3 - p1));
480
481 // Add the same normal for all 3 vertices
482 vertices[i].normal = normal;
483 vertices[i + 1].normal = normal;
484 vertices[i + 2].normal = normal;
485 }
486
487 return vertices;
488}
489
490template<class VertexType, class SSBOType>
491void VulkanGame::centerObject(SceneObject<VertexType, SSBOType>& object) {
492 vector<VertexType>& vertices = object.vertices;
493
494 float min_x = vertices[0].pos.x;
495 float max_x = vertices[0].pos.x;
496 float min_y = vertices[0].pos.y;
497 float max_y = vertices[0].pos.y;
498 float min_z = vertices[0].pos.z;
499 float max_z = vertices[0].pos.z;
500
501 // start from the second point
502 for (unsigned int i = 1; i < vertices.size(); i++) {
503 vec3& pos = vertices[i].pos;
504
505 if (min_x > pos.x) {
506 min_x = pos.x;
507 }
508 else if (max_x < pos.x) {
509 max_x = pos.x;
510 }
511
512 if (min_y > pos.y) {
513 min_y = pos.y;
514 }
515 else if (max_y < pos.y) {
516 max_y = pos.y;
517 }
518
519 if (min_z > pos.z) {
520 min_z = pos.z;
521 }
522 else if (max_z < pos.z) {
523 max_z = pos.z;
524 }
525 }
526
527 vec3 center = vec3(min_x + max_x, min_y + max_y, min_z + max_z) / 2.0f;
528
529 for (unsigned int i = 0; i < vertices.size(); i++) {
530 vertices[i].pos -= center;
531 }
532
533 object.radius = std::max(max_x - center.x, max_y - center.y);
534 object.radius = std::max(object.radius, max_z - center.z);
535
536 object.center = vec3(0.0f, 0.0f, 0.0f);
537}
538
539// TODO: Just pass in the single object instead of a list of all of them
540template<class VertexType, class SSBOType>
541void VulkanGame::updateObject(vector<SceneObject<VertexType, SSBOType>>& objects,
542 GraphicsPipeline_Vulkan<VertexType>& pipeline, size_t index) {
543 SceneObject<VertexType, SSBOType>& obj = objects[index];
544
545 obj.ssbo.model = obj.model_transform * obj.model_base;
546 obj.center = vec3(obj.ssbo.model * vec4(0.0f, 0.0f, 0.0f, 1.0f));
547
548 obj.modified = false;
549}
550
551#endif // _SDL_GAME_H
Note: See TracBrowser for help on using the repository browser.