source: opengl-game/sdl-game.hpp@ 4a777d2

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

Add the model pipeline and the spinning, textured squares to SDLGame

  • Property mode set to 100644
File size: 15.1 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, SSBO_ModelObject> 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 void createBufferSet(VkDeviceSize bufferSize, VkBufferUsageFlags flags,
279 vector<VkBuffer>& buffers, vector<VkDeviceMemory>& buffersMemory,
280 vector<VkDescriptorBufferInfo>& bufferInfoList);
281
282 // TODO: Since addObject() returns a reference to the new object now,
283 // stop using objects.back() to access the object that was just created
284 template<class VertexType, class SSBOType>
285 SceneObject<VertexType, SSBOType>& addObject(
286 vector<SceneObject<VertexType, SSBOType>>& objects,
287 GraphicsPipeline_Vulkan<VertexType, SSBOType>& pipeline,
288 const vector<VertexType>& vertices, vector<uint16_t> indices, SSBOType ssbo,
289 bool pipelinesCreated);
290
291 template<class VertexType>
292 vector<VertexType> addObjectIndex(unsigned int objIndex, vector<VertexType> vertices);
293
294 template<class VertexType>
295 vector<VertexType> addVertexNormals(vector<VertexType> vertices);
296
297 template<class VertexType, class SSBOType>
298 void centerObject(SceneObject<VertexType, SSBOType>& object);
299
300 template<class VertexType, class SSBOType>
301 void updateObject(vector<SceneObject<VertexType, SSBOType>>& objects,
302 GraphicsPipeline_Vulkan<VertexType, SSBOType>& pipeline, size_t index);
303
304 void renderFrame(ImDrawData* draw_data);
305 void presentFrame();
306
307 void recreateSwapChain();
308
309 void cleanupSwapChain();
310
311 /*** High-level functions ***/
312
313 void renderMainScreen(int width, int height);
314 void renderGameScreen(int width, int height);
315
316 void initGuiValueLists(map<string, vector<UIValue>>& valueLists);
317 void renderGuiValueList(vector<UIValue>& values);
318
319 void goToScreen(void (VulkanGame::* renderScreenFn)(int width, int height));
320 void quitGame();
321};
322
323// TODO: Right now, it's basically necessary to pass the identity matrix in for ssbo.model
324// and to change the model matrix later by setting model_transform and then calling updateObject()
325// Figure out a better way to allow the model matrix to be set during objecting creation
326
327// TODO: Maybe return a reference to the object from this method if I decide that updating it
328// immediately after creation is a good idea (such as setting model_base)
329// Currently, model_base is set like this in a few places and the radius is set for asteroids
330// to account for scaling
331template<class VertexType, class SSBOType>
332SceneObject<VertexType, SSBOType>& VulkanGame::addObject(
333 vector<SceneObject<VertexType, SSBOType>>& objects,
334 GraphicsPipeline_Vulkan<VertexType, SSBOType>& pipeline,
335 const vector<VertexType>& vertices, vector<uint16_t> indices, SSBOType ssbo,
336 bool pipelinesCreated) {
337 // TODO: Use the model field of ssbo to set the object's model_base
338 // currently, the passed in model is useless since it gets overridden in updateObject() anyway
339 size_t numVertices = pipeline.getNumVertices();
340
341 for (uint16_t& idx : indices) {
342 idx += numVertices;
343 }
344
345 objects.push_back({ vertices, indices, ssbo, mat4(1.0f), mat4(1.0f), false });
346
347 SceneObject<VertexType, SSBOType>& obj = objects.back();
348
349 // TODO: Specify whether to center the object outside of this function or, worst case, maybe
350 // with a boolean being passed in here, so that I don't have to rely on checking the specific object
351 // type
352 if (!is_same_v<VertexType, LaserVertex> && !is_same_v<VertexType, ExplosionVertex>) {
353 centerObject(obj);
354 }
355
356 bool storageBufferResized = pipeline.addObject(obj.vertices, obj.indices, obj.ssbo,
357 resourceCommandPool, graphicsQueue);
358
359 if (pipelinesCreated) {
360 vkDeviceWaitIdle(device);
361
362 for (uint32_t i = 0; i < swapChainImageCount; i++) {
363 vkFreeCommandBuffers(device, commandPools[i], 1, &commandBuffers[i]);
364 }
365
366 // TODO: The pipeline recreation only has to be done once per frame where at least
367 // one SSBO is resized.
368 // Refactor the logic to check for any resized SSBOs after all objects for the frame
369 // are created and then recreate each of the corresponding pipelines only once per frame
370 if (storageBufferResized) {
371 pipeline.createPipeline(pipeline.vertShaderFile, pipeline.fragShaderFile);
372 pipeline.createDescriptorPool(swapChainImages);
373 pipeline.createDescriptorSets(swapChainImages);
374 }
375
376 createCommandBuffers();
377 }
378
379 return obj;
380}
381
382template<class VertexType>
383vector<VertexType> VulkanGame::addObjectIndex(unsigned int objIndex, vector<VertexType> vertices) {
384 for (VertexType& vertex : vertices) {
385 vertex.objIndex = objIndex;
386 }
387
388 return vertices;
389}
390
391template<class VertexType>
392vector<VertexType> VulkanGame::addVertexNormals(vector<VertexType> vertices) {
393 for (unsigned int i = 0; i < vertices.size(); i += 3) {
394 vec3 p1 = vertices[i].pos;
395 vec3 p2 = vertices[i + 1].pos;
396 vec3 p3 = vertices[i + 2].pos;
397
398 vec3 normal = normalize(cross(p2 - p1, p3 - p1));
399
400 // Add the same normal for all 3 vertices
401 vertices[i].normal = normal;
402 vertices[i + 1].normal = normal;
403 vertices[i + 2].normal = normal;
404 }
405
406 return vertices;
407}
408
409template<class VertexType, class SSBOType>
410void VulkanGame::centerObject(SceneObject<VertexType, SSBOType>& object) {
411 vector<VertexType>& vertices = object.vertices;
412
413 float min_x = vertices[0].pos.x;
414 float max_x = vertices[0].pos.x;
415 float min_y = vertices[0].pos.y;
416 float max_y = vertices[0].pos.y;
417 float min_z = vertices[0].pos.z;
418 float max_z = vertices[0].pos.z;
419
420 // start from the second point
421 for (unsigned int i = 1; i < vertices.size(); i++) {
422 vec3& pos = vertices[i].pos;
423
424 if (min_x > pos.x) {
425 min_x = pos.x;
426 }
427 else if (max_x < pos.x) {
428 max_x = pos.x;
429 }
430
431 if (min_y > pos.y) {
432 min_y = pos.y;
433 }
434 else if (max_y < pos.y) {
435 max_y = pos.y;
436 }
437
438 if (min_z > pos.z) {
439 min_z = pos.z;
440 }
441 else if (max_z < pos.z) {
442 max_z = pos.z;
443 }
444 }
445
446 vec3 center = vec3(min_x + max_x, min_y + max_y, min_z + max_z) / 2.0f;
447
448 for (unsigned int i = 0; i < vertices.size(); i++) {
449 vertices[i].pos -= center;
450 }
451
452 object.radius = std::max(max_x - center.x, max_y - center.y);
453 object.radius = std::max(object.radius, max_z - center.z);
454
455 object.center = vec3(0.0f, 0.0f, 0.0f);
456}
457
458// TODO: Just pass in the single object instead of a list of all of them
459template<class VertexType, class SSBOType>
460void VulkanGame::updateObject(vector<SceneObject<VertexType, SSBOType>>& objects,
461 GraphicsPipeline_Vulkan<VertexType, SSBOType>& pipeline, size_t index) {
462 SceneObject<VertexType, SSBOType>& obj = objects[index];
463
464 obj.ssbo.model = obj.model_transform * obj.model_base;
465 obj.center = vec3(obj.ssbo.model * vec4(0.0f, 0.0f, 0.0f, 1.0f));
466
467 pipeline.updateObject(index, obj.ssbo);
468
469 obj.modified = false;
470}
471
472#endif // _SDL_GAME_H
Note: See TracBrowser for help on using the repository browser.