source: opengl-game/sdl-game.hpp@ 9d21aac

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

Remove the SSBOType template parameter from GraphicsPipeline_Vulkan

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