source: opengl-game/sdl-game.hpp@ 756162f

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

Add a VkMemoryPropertyFlags parameter to SDLGame::createBufferSet
instead of hard-coding that value

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