source: opengl-game/sdl-game.hpp@ 1abebc1

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

Remove the storageBuffers parameter from addObject() since it is no longer used, rename StorageBufferSet, resizeStorageBufferSet(), and updateStorageuffer() to BufferSet, resizeBufferSet(), and updateBufferSet() respectively, and change updateObject() to just take a SceneObject reference.

  • Property mode set to 100644
File size: 17.4 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 BufferSet {
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 BufferSet 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 resizeBufferSet(BufferSet& set, VulkanBuffer<SSBOType>& buffer,
302 GraphicsPipeline_Vulkan<VertexType>& pipeline, VkCommandPool commandPool,
303 VkQueue graphicsQueue);
304
305 template<class SSBOType>
306 void updateBufferSet(BufferSet& set, 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);
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(SceneObject<VertexType, SSBOType>& obj);
327
328 void renderFrame(ImDrawData* draw_data);
329 void presentFrame();
330
331 void recreateSwapChain();
332
333 void cleanupSwapChain();
334
335 /*** High-level functions ***/
336
337 void renderMainScreen(int width, int height);
338 void renderGameScreen(int width, int height);
339
340 void initGuiValueLists(map<string, vector<UIValue>>& valueLists);
341 void renderGuiValueList(vector<UIValue>& values);
342
343 void goToScreen(void (VulkanGame::* renderScreenFn)(int width, int height));
344 void quitGame();
345};
346
347template<class VertexType, class SSBOType>
348void VulkanGame::resizeBufferSet(BufferSet& set, VulkanBuffer<SSBOType>& buffer,
349 GraphicsPipeline_Vulkan<VertexType>& pipeline, VkCommandPool commandPool,
350 VkQueue graphicsQueue) {
351 size_t numObjects = buffer.numObjects < buffer.capacity ? buffer.numObjects : buffer.capacity;
352
353 do {
354 buffer.capacity *= 2;
355 } while (buffer.capacity < buffer.numObjects);
356
357 VkDeviceSize bufferSize = buffer.capacity * sizeof(SSBOType);
358
359 for (size_t i = 0; i < set.buffers.size(); i++) {
360 VkBuffer newStorageBuffer;
361 VkDeviceMemory newStorageBufferMemory;
362
363 VulkanUtils::createBuffer(device, physicalDevice, bufferSize,
364 VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
365 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
366 newStorageBuffer, newStorageBufferMemory);
367
368 VulkanUtils::copyBuffer(device, commandPool, set.buffers[i], newStorageBuffer,
369 0, 0, numObjects * sizeof(SSBOType), graphicsQueue);
370
371 vkDestroyBuffer(device, set.buffers[i], nullptr);
372 vkFreeMemory(device, set.memory[i], nullptr);
373
374 set.buffers[i] = newStorageBuffer;
375 set.memory[i] = newStorageBufferMemory;
376
377 set.infoSet[i].buffer = set.buffers[i];
378 set.infoSet[i].offset = 0; // This is the offset from the start of the buffer, so always 0 for now
379 set.infoSet[i].range = bufferSize; // Size of the update starting from offset, or VK_WHOLE_SIZE
380 }
381
382 // Assume the SSBO is always the 2nd binding
383 // TODO: Figure out a way to make this more flexible
384 pipeline.updateDescriptorInfo(1, &set.infoSet, swapChainImages);
385}
386
387// TODO: See if it makes sense to pass in the current swapchain index instead of updating all of them
388template<class SSBOType>
389void VulkanGame::updateBufferSet(BufferSet& set, size_t objIndex, SSBOType& ssbo) {
390 for (size_t i = 0; i < set.memory.size(); i++) {
391 VulkanUtils::copyDataToMemory(device, ssbo, set.memory[i], objIndex * sizeof(SSBOType));
392 }
393}
394
395// TODO: Right now, it's basically necessary to pass the identity matrix in for ssbo.model
396// and to change the model matrix later by setting model_transform and then calling updateObject()
397// Figure out a better way to allow the model matrix to be set during object creation
398template<class VertexType, class SSBOType>
399SceneObject<VertexType, SSBOType>& VulkanGame::addObject(vector<SceneObject<VertexType, SSBOType>>& objects,
400 GraphicsPipeline_Vulkan<VertexType>& pipeline,
401 const vector<VertexType>& vertices, vector<uint16_t> indices,
402 SSBOType ssbo) {
403 // TODO: Use the model field of ssbo to set the object's model_base
404 // currently, the passed in model is useless since it gets overridden in updateObject() anyway
405 size_t numVertices = pipeline.getNumVertices();
406
407 for (uint16_t& idx : indices) {
408 idx += numVertices;
409 }
410
411 objects.push_back({ vertices, indices, ssbo, mat4(1.0f), mat4(1.0f), false });
412
413 SceneObject<VertexType, SSBOType>& obj = objects.back();
414
415 // TODO: Specify whether to center the object outside of this function or, worst case, maybe
416 // with a boolean being passed in here, so that I don't have to rely on checking the specific object
417 // type
418 if (!is_same_v<VertexType, LaserVertex> && !is_same_v<VertexType, ExplosionVertex>) {
419 centerObject(obj);
420 }
421
422 pipeline.addObject(obj.vertices, obj.indices, resourceCommandPool, graphicsQueue);
423
424 return obj;
425}
426
427template<class VertexType>
428vector<VertexType> VulkanGame::addObjectIndex(unsigned int objIndex, vector<VertexType> vertices) {
429 for (VertexType& vertex : vertices) {
430 vertex.objIndex = objIndex;
431 }
432
433 return vertices;
434}
435
436template<class VertexType>
437vector<VertexType> VulkanGame::addVertexNormals(vector<VertexType> vertices) {
438 for (unsigned int i = 0; i < vertices.size(); i += 3) {
439 vec3 p1 = vertices[i].pos;
440 vec3 p2 = vertices[i + 1].pos;
441 vec3 p3 = vertices[i + 2].pos;
442
443 vec3 normal = normalize(cross(p2 - p1, p3 - p1));
444
445 // Add the same normal for all 3 vertices
446 vertices[i].normal = normal;
447 vertices[i + 1].normal = normal;
448 vertices[i + 2].normal = normal;
449 }
450
451 return vertices;
452}
453
454template<class VertexType, class SSBOType>
455void VulkanGame::centerObject(SceneObject<VertexType, SSBOType>& object) {
456 vector<VertexType>& vertices = object.vertices;
457
458 float min_x = vertices[0].pos.x;
459 float max_x = vertices[0].pos.x;
460 float min_y = vertices[0].pos.y;
461 float max_y = vertices[0].pos.y;
462 float min_z = vertices[0].pos.z;
463 float max_z = vertices[0].pos.z;
464
465 // start from the second point
466 for (unsigned int i = 1; i < vertices.size(); i++) {
467 vec3& pos = vertices[i].pos;
468
469 if (min_x > pos.x) {
470 min_x = pos.x;
471 }
472 else if (max_x < pos.x) {
473 max_x = pos.x;
474 }
475
476 if (min_y > pos.y) {
477 min_y = pos.y;
478 }
479 else if (max_y < pos.y) {
480 max_y = pos.y;
481 }
482
483 if (min_z > pos.z) {
484 min_z = pos.z;
485 }
486 else if (max_z < pos.z) {
487 max_z = pos.z;
488 }
489 }
490
491 vec3 center = vec3(min_x + max_x, min_y + max_y, min_z + max_z) / 2.0f;
492
493 for (unsigned int i = 0; i < vertices.size(); i++) {
494 vertices[i].pos -= center;
495 }
496
497 object.radius = std::max(max_x - center.x, max_y - center.y);
498 object.radius = std::max(object.radius, max_z - center.z);
499
500 object.center = vec3(0.0f, 0.0f, 0.0f);
501}
502
503// TODO: Just pass in the single object instead of a list of all of them
504template<class VertexType, class SSBOType>
505void VulkanGame::updateObject(SceneObject<VertexType, SSBOType>& obj) {
506 obj.ssbo.model = obj.model_transform * obj.model_base;
507 obj.center = vec3(obj.ssbo.model * vec4(0.0f, 0.0f, 0.0f, 1.0f));
508
509 obj.modified = false;
510}
511
512#endif // _SDL_GAME_H
Note: See TracBrowser for help on using the repository browser.