source: opengl-game/sdl-game.hpp@ a3cefaa

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

Move SSBO resizing and pipeline recreation checks out of addObject() and into updateScene() so that those operations are only done at most once per pipeline per frame, using vkUpdateDescriptorSets() instead of recreating the whole graphics pipeline, and create a VulkanBuffer class for managing data related to uniform buffers and shader storage buffers, move objectCapacity and numObjects out of GraphicsPipeline_vulkan and use VulkanBuffer to manage them instead

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