Index: VulkanGame.vcxproj
===================================================================
--- VulkanGame.vcxproj	(revision 996dd3efcf576a5eaf4f04878167c007d853dbac)
+++ VulkanGame.vcxproj	(revision a3cefaa6294c97206ade08805f4ff1b8c13d2d4e)
@@ -181,4 +181,5 @@
     <ClInclude Include="StackWalker.h" />
     <ClInclude Include="utils.hpp" />
+    <ClInclude Include="vulkan-buffer.hpp" />
     <ClInclude Include="vulkan-game.hpp" />
     <ClInclude Include="vulkan-utils.hpp" />
Index: VulkanGame.vcxproj.filters
===================================================================
--- VulkanGame.vcxproj.filters	(revision 996dd3efcf576a5eaf4f04878167c007d853dbac)
+++ VulkanGame.vcxproj.filters	(revision a3cefaa6294c97206ade08805f4ff1b8c13d2d4e)
@@ -86,4 +86,5 @@
       <Filter>gui\imgui</Filter>
     </ClInclude>
+    <ClInclude Include="vulkan-buffer.hpp" />
   </ItemGroup>
   <ItemGroup>
Index: graphics-pipeline_vulkan.hpp
===================================================================
--- graphics-pipeline_vulkan.hpp	(revision 996dd3efcf576a5eaf4f04878167c007d853dbac)
+++ graphics-pipeline_vulkan.hpp	(revision a3cefaa6294c97206ade08805f4ff1b8c13d2d4e)
@@ -36,8 +36,4 @@
    public:
       string vertShaderFile, fragShaderFile;
-
-      // Both of these are only used for managing the SSBO, so move them out as well
-      size_t objectCapacity;
-      size_t numObjects;
 
       GraphicsPipeline_Vulkan();
@@ -48,5 +44,5 @@
       GraphicsPipeline_Vulkan(VkPrimitiveTopology topology, VkPhysicalDevice physicalDevice, VkDevice device,
          VkRenderPass renderPass, Viewport viewport, vector<VkImage>& swapChainImages,
-         size_t vertexCapacity, size_t indexCapacity, size_t objectCapacity);
+         size_t vertexCapacity, size_t indexCapacity);
       ~GraphicsPipeline_Vulkan();
 
@@ -64,5 +60,6 @@
       void addDescriptorInfo(VkDescriptorType type, VkShaderStageFlags stageFlags, VkDescriptorImageInfo* imageData);
 
-      void updateDescriptorInfo(uint32_t index, vector<VkDescriptorBufferInfo>* bufferData);
+      void updateDescriptorInfo(uint32_t index, vector<VkDescriptorBufferInfo>* bufferData,
+                                vector<VkImage>& swapChainImages);
       // TODO: Maybe make an analogous one for updating image info
 
@@ -128,12 +125,17 @@
 // into the constructor. That way, I can also put relevant cleanup code into the destructor
 template<class VertexType>
-GraphicsPipeline_Vulkan<VertexType>::GraphicsPipeline_Vulkan(
-      VkPrimitiveTopology topology, VkPhysicalDevice physicalDevice, VkDevice device,
-      VkRenderPass renderPass, Viewport viewport, vector<VkImage>& swapChainImages,
-      size_t vertexCapacity, size_t indexCapacity, size_t objectCapacity) {
-   this->topology = topology;
-   this->physicalDevice = physicalDevice;
-   this->device = device;
-   this->renderPass = renderPass;
+GraphicsPipeline_Vulkan<VertexType>::GraphicsPipeline_Vulkan(VkPrimitiveTopology topology,
+                                                             VkPhysicalDevice physicalDevice, VkDevice device,
+                                                             VkRenderPass renderPass, Viewport viewport,
+                                                             vector<VkImage>& swapChainImages, size_t vertexCapacity,
+                                                             size_t indexCapacity)
+                                                            : topology(topology)
+                                                            , physicalDevice(physicalDevice)
+                                                            , device(device)
+                                                            , renderPass(renderPass) {
+   // This is a member of the base GraphicsPipeline class
+   // It currently is not used for the OpenGL pipeline. Either figure out why (since OpenGL certainly has the concept of
+   // viewports) and use it there too and add viewport the the base class constructor, or create a second base class
+   // constructor which takes the viewport
    this->viewport = viewport;
 
@@ -158,7 +160,4 @@
       VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
       VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, indexBuffer, indexBufferMemory);
-
-   this->numObjects = 0;
-   this->objectCapacity = objectCapacity;
 }
 
@@ -204,6 +203,44 @@
 template<class VertexType>
 void GraphicsPipeline_Vulkan<VertexType>::updateDescriptorInfo(uint32_t index,
-                                                                         vector<VkDescriptorBufferInfo>* bufferData) {
+                                                               vector<VkDescriptorBufferInfo>* bufferData,
+                                                               vector<VkImage>& swapChainImages) {
    this->descriptorInfoList[index].bufferDataList = bufferData;
+
+   // TODO: This code was mostly copied from createDescriptorSets. I should make some common function they both use
+   // for updating descriptor sets
+   for (size_t i = 0; i < swapChainImages.size(); i++) {
+      VkWriteDescriptorSet descriptorWrite = {};
+
+      descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+      descriptorWrite.dstSet = this->descriptorSets[i];
+      descriptorWrite.dstBinding = index;
+      descriptorWrite.dstArrayElement = 0;
+      descriptorWrite.descriptorType = this->descriptorInfoList[index].type;
+      descriptorWrite.descriptorCount = 1;
+      descriptorWrite.pBufferInfo = nullptr;
+      descriptorWrite.pImageInfo = nullptr;
+      descriptorWrite.pTexelBufferView = nullptr;
+
+      // This method is only intended for updated descriptor sets of type VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+      // but I'm leaving that in here for completeness
+      switch (descriptorWrite.descriptorType) {
+         case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
+         case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
+         case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
+            descriptorWrite.pBufferInfo = &(*this->descriptorInfoList[index].bufferDataList)[i];
+            break;
+         case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
+            descriptorWrite.pImageInfo = this->descriptorInfoList[index].imageData;
+            break;
+         default:
+            throw runtime_error("Unknown descriptor type: " + to_string(descriptorWrite.descriptorType));
+      }
+
+      if (bufferData->size() != swapChainImages.size()) {
+         cout << "ALERT ALERT ALERT: SIZE MISMATCH!!!!!!!" << endl;
+      }
+
+      vkUpdateDescriptorSets(this->device, 1, &descriptorWrite, 0, nullptr);
+   }
 }
 
Index: sdl-game.cpp
===================================================================
--- sdl-game.cpp	(revision 996dd3efcf576a5eaf4f04878167c007d853dbac)
+++ sdl-game.cpp	(revision a3cefaa6294c97206ade08805f4ff1b8c13d2d4e)
@@ -70,4 +70,5 @@
                      , gui(nullptr)
                      , window(nullptr)
+                     , objects_modelPipeline()
                      , score(0)
                      , fps(0.0f) {
@@ -87,4 +88,9 @@
 
    initVulkan();
+
+   VkPhysicalDeviceProperties deviceProperties;
+   vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties);
+
+   objects_modelPipeline = VulkanBuffer<SSBO_ModelObject>(10, deviceProperties.limits.minUniformBufferOffsetAlignment);
 
    initImGuiOverlay();
@@ -131,7 +137,7 @@
       }, {
          mat4(1.0f)
-      }, storageBuffers_modelPipeline, false);
-
-   updateStorageBuffer(storageBuffers_modelPipeline, modelPipeline.numObjects - 1, texturedSquare->ssbo);
+      }, storageBuffers_modelPipeline);
+
+   objects_modelPipeline.numObjects++;
 
    texturedSquare->model_base =
@@ -152,7 +158,7 @@
       }, {
          mat4(1.0f)
-      }, storageBuffers_modelPipeline, false);
-
-   updateStorageBuffer(storageBuffers_modelPipeline, modelPipeline.numObjects - 1, texturedSquare->ssbo);
+      }, storageBuffers_modelPipeline);
+
+   objects_modelPipeline.numObjects++;
 
    texturedSquare->model_base =
@@ -263,7 +269,7 @@
    modelPipeline = GraphicsPipeline_Vulkan<ModelVertex>(
       VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, physicalDevice, device, renderPass,
-      { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height }, swapChainImages, 16, 24, 10);
-
-   createBufferSet(modelPipeline.objectCapacity * sizeof(SSBO_ModelObject),
+      { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height }, swapChainImages, 16, 24);
+
+   createBufferSet(objects_modelPipeline.capacity * sizeof(SSBO_ModelObject),
       VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
       VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
@@ -376,7 +382,7 @@
                         }, {
                            mat4(1.0f)
-                        }, storageBuffers_modelPipeline, true);
-
-                  updateStorageBuffer(storageBuffers_modelPipeline, modelPipeline.numObjects - 1, texturedSquare.ssbo);
+                        }, storageBuffers_modelPipeline);
+
+                  objects_modelPipeline.numObjects++;
 
                   texturedSquare.model_base =
@@ -446,6 +452,5 @@
 
 void VulkanGame::updateScene() {
-   // TODO: These two for loops could probably be combined into one
-
+   // Rotate the textured squares
    for (SceneObject<ModelVertex, SSBO_ModelObject>& model : this->modelObjects) {
       model.model_transform =
@@ -455,13 +460,12 @@
    }
 
-   // TODO: Instead, update entire sections (up to 64k) of the dynamic ubo if some objects there have changed
-   // Also, update the objects modelMat and center in the loop above instead of here
+   // TODO: Probably move the resizing to the VulkanBuffer class
+   if (objects_modelPipeline.numObjects > objects_modelPipeline.capacity) {
+      resizeStorageBufferSet(storageBuffers_modelPipeline, objects_modelPipeline, modelPipeline, resourceCommandPool,
+                             graphicsQueue);
+   }
+
    for (size_t i = 0; i < modelObjects.size(); i++) {
       if (modelObjects[i].modified) {
-         // TODO: Think about changing the SSBO-related code to also use a contiguous array
-         // to store the data on the cpu side instead of storing it per-object
-         // Then, I could also copy it to the GPU in one go
-         // Also, double-check if the alignment restrictions also hold for SSBOs
-
          updateObject(modelObjects, modelPipeline, i);
          updateStorageBuffer(storageBuffers_modelPipeline, i, modelObjects[i].ssbo);
@@ -845,5 +849,5 @@
    return VulkanUtils::findSupportedFormat(
       physicalDevice,
-      { VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT },
+      { VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D32_SFLOAT, VK_FORMAT_D24_UNORM_S8_UINT },
       VK_IMAGE_TILING_OPTIMAL,
       VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT
Index: sdl-game.hpp
===================================================================
--- sdl-game.hpp	(revision 996dd3efcf576a5eaf4f04878167c007d853dbac)
+++ sdl-game.hpp	(revision a3cefaa6294c97206ade08805f4ff1b8c13d2d4e)
@@ -21,4 +21,5 @@
 #include "consts.hpp"
 #include "vulkan-utils.hpp"
+#include "vulkan-buffer.hpp"
 #include "graphics-pipeline_vulkan.hpp"
 #include "game-gui-sdl.hpp"
@@ -77,4 +78,5 @@
 // Also, probably better to make this a vector of structs where each struct
 // has a VkBuffer, VkDeviceMemory, and VkDescriptorBufferInfo
+// TODO: Maybe change the structure here since VkDescriptorBufferInfo already stores a reference to the VkBuffer
 struct StorageBufferSet {
    vector<VkBuffer> buffers;
@@ -219,4 +221,5 @@
 
       StorageBufferSet storageBuffers_modelPipeline;
+      VulkanBuffer<SSBO_ModelObject> objects_modelPipeline;
 
       // TODO: Maybe make the ubo objects part of the pipeline class since there's only one ubo
@@ -296,6 +299,7 @@
       // TODO: Remove the need for templating, which is only there so a GraphicsPupeline_Vulkan can be passed in
       template<class VertexType, class SSBOType>
-      void resizeStorageBufferSet(StorageBufferSet& set, VkCommandPool commandPool, VkQueue graphicsQueue,
-                                  GraphicsPipeline_Vulkan<VertexType>& pipeline);
+      void resizeStorageBufferSet(StorageBufferSet& set, VulkanBuffer<SSBOType>& buffer,
+                                  GraphicsPipeline_Vulkan<VertexType>& pipeline,
+                                  VkCommandPool commandPool, VkQueue graphicsQueue);
 
       template<class SSBOType>
@@ -308,6 +312,5 @@
                                                    GraphicsPipeline_Vulkan<VertexType>& pipeline,
                                                    const vector<VertexType>& vertices, vector<uint16_t> indices,
-                                                   SSBOType ssbo, StorageBufferSet& storageBuffers,
-                                                   bool pipelinesCreated);
+                                                   SSBOType ssbo, StorageBufferSet& storageBuffers);
 
       template<class VertexType>
@@ -344,8 +347,14 @@
 
 template<class VertexType, class SSBOType>
-void VulkanGame::resizeStorageBufferSet(StorageBufferSet& set, VkCommandPool commandPool, VkQueue graphicsQueue,
-                                        GraphicsPipeline_Vulkan<VertexType>& pipeline) {
-   pipeline.objectCapacity *= 2;
-   VkDeviceSize bufferSize = pipeline.objectCapacity * sizeof(SSBOType);
+void VulkanGame::resizeStorageBufferSet(StorageBufferSet& set, VulkanBuffer<SSBOType>& buffer,
+                                        GraphicsPipeline_Vulkan<VertexType>& pipeline,
+                                        VkCommandPool commandPool, VkQueue graphicsQueue) {
+   size_t numObjects = buffer.numObjects < buffer.capacity ? buffer.numObjects : buffer.capacity;
+
+   do {
+      buffer.capacity *= 2;
+   } while (buffer.capacity < buffer.numObjects);
+
+   VkDeviceSize bufferSize = buffer.capacity * sizeof(SSBOType);
 
    for (size_t i = 0; i < set.buffers.size(); i++) {
@@ -359,5 +368,5 @@
 
       VulkanUtils::copyBuffer(device, commandPool, set.buffers[i], newStorageBuffer,
-         0, 0, pipeline.numObjects * sizeof(SSBOType), graphicsQueue);
+         0, 0, numObjects * sizeof(SSBOType), graphicsQueue);
 
       vkDestroyBuffer(device, set.buffers[i], nullptr);
@@ -371,4 +380,8 @@
       set.infoSet[i].range = bufferSize; // Size of the update starting from offset, or VK_WHOLE_SIZE
    }
+
+   // Assume the SSBO is always the 2nd binding
+   // TODO: Figure out a way to make this more flexible
+   pipeline.updateDescriptorInfo(1, &set.infoSet, swapChainImages);
 }
 
@@ -384,15 +397,9 @@
 // and to change the model matrix later by setting model_transform and then calling updateObject()
 // Figure out a better way to allow the model matrix to be set during object creation
-
-// TODO: Maybe return a reference to the object from this method if I decide that updating it
-// immediately after creation is a good idea (such as setting model_base)
-// Currently, model_base is set like this in a few places and the radius is set for asteroids
-// to account for scaling
 template<class VertexType, class SSBOType>
 SceneObject<VertexType, SSBOType>& VulkanGame::addObject(vector<SceneObject<VertexType, SSBOType>>& objects,
                                                          GraphicsPipeline_Vulkan<VertexType>& pipeline,
                                                          const vector<VertexType>& vertices, vector<uint16_t> indices,
-                                                         SSBOType ssbo, StorageBufferSet& storageBuffers,
-                                                         bool pipelinesCreated) {
+                                                         SSBOType ssbo, StorageBufferSet& storageBuffers) {
    // TODO: Use the model field of ssbo to set the object's model_base
    // currently, the passed in model is useless since it gets overridden in updateObject() anyway
@@ -415,46 +422,4 @@
 
    pipeline.addObject(obj.vertices, obj.indices, resourceCommandPool, graphicsQueue);
-
-   // TODO: Probably move the resizing to the VulkanBuffer class
-   // First, try moving this out of addObject
-   bool resizeStorageBuffer = pipeline.numObjects == pipeline.objectCapacity;
-
-   if (resizeStorageBuffer) {
-      resizeStorageBufferSet<VertexType, SSBOType>(storageBuffers, resourceCommandPool, graphicsQueue, pipeline);
-      pipeline.cleanup();
-
-      // Assume the SSBO is always the 2nd binding
-      // TODO: Figure out a way to make this more flexible
-      pipeline.updateDescriptorInfo(1, &storageBuffers.infoSet);
-   }
-
-   pipeline.numObjects++;
-
-   // TODO: Figure out why I am destroying and recreating the ubos when the swap chain is recreated,
-   // but am reusing the same ssbos. Maybe I don't need to recreate the ubos.
-
-   if (pipelinesCreated) {
-      // TODO: See if I can avoid doing this when recreating the pipeline
-      vkDeviceWaitIdle(device);
-
-      for (uint32_t i = 0; i < swapChainImageCount; i++) {
-         vkFreeCommandBuffers(device, commandPools[i], 1, &commandBuffers[i]);
-      }
-
-      // TODO: The pipeline recreation only has to be done once per frame where at least
-      // one SSBO is resized.
-      // Refactor the logic to check for any resized SSBOs after all objects for the frame
-      // are created and then recreate each of the corresponding pipelines only once per frame
-
-      // TODO: Also, verify if I actually need to recreate all of these, or maybe just the descriptor sets, for instance
-
-      if (resizeStorageBuffer) {
-         pipeline.createPipeline(pipeline.vertShaderFile, pipeline.fragShaderFile);
-         pipeline.createDescriptorPool(swapChainImages);
-         pipeline.createDescriptorSets(swapChainImages);
-      }
-
-      createCommandBuffers();
-   }
 
    return obj;
Index: vulkan-buffer.hpp
===================================================================
--- vulkan-buffer.hpp	(revision a3cefaa6294c97206ade08805f4ff1b8c13d2d4e)
+++ vulkan-buffer.hpp	(revision a3cefaa6294c97206ade08805f4ff1b8c13d2d4e)
@@ -0,0 +1,145 @@
+#ifndef _VULKAN_BUFFER_H
+#define _VULKAN_BUFFER_H
+
+#include <iostream>
+#include <vector>
+
+using namespace std;
+
+/* 
+* This class is intended to be used with Storage Buffers and Uniform Buffers.
+*/
+
+template<class T>
+class VulkanBuffer {
+
+   public:
+
+      size_t alignment;
+      size_t capacity;
+      size_t numObjects;
+
+      VulkanBuffer();
+      VulkanBuffer(size_t capacity, size_t minOffsetAlignment);
+      VulkanBuffer(vector<T>* vData, size_t capacity);
+      ~VulkanBuffer();
+
+      VulkanBuffer<T>& operator=(const VulkanBuffer<T>& other);
+
+      T* data();
+      void* mappedData(); // TODO: Maybe rename this to just mapped()
+
+   private:
+
+      T* srcData; // TODO: Rename this to something else probably and rename rawData to data
+      vector<T>* vData;
+
+      // Remember that this is a pointer to the mapped video memory
+      // Maybe rename it to mappedData or something to make that clearer
+      void* rawData;
+};
+
+// Currently, for SSBOs, I store the per-object values (usually just the model matrix), on each object, so they
+// are not in their own array and therefore cannot be pushed to the GPU as one block. The updates must happen
+// separately per object.
+
+// Since Sascha WIllems' dynamic UBO example works the same way (iirc), I can implement dynamic UBOs like that as well
+// for now. Would be nice to plan for potentially storing the ubo data on the CPU in a contiguous block in the future,
+// assuming that would make updates easier. Keep in mind that this only makes sense if all or most of the objects
+// in the ubo get updated every frame.
+
+// ============================= TODO: Also, check when it makes sense to have a staging buffer for copying data to the GPU
+// and see if I actually need to use it everywhere I currently am. I think this is mentioned in Sascha WIllems dubo example
+// or some other Vulkan website I recently bookmarked
+
+template<class T>
+VulkanBuffer<T>::VulkanBuffer()
+                              : alignment(0)
+                              , capacity(0)
+                              , numObjects(0)
+                              , srcData(nullptr)
+                              , rawData(nullptr)
+                              , vData(nullptr) {
+}
+
+template<class T>
+VulkanBuffer<T>::VulkanBuffer(size_t capacity, size_t minOffsetAlignment)
+                              : alignment(sizeof(T))
+                              , capacity(capacity)
+                              , numObjects(0)
+                              , srcData(nullptr)
+                              , rawData(nullptr)
+                              , vData(nullptr) {
+   if (minOffsetAlignment > 0) {
+      alignment = (alignment + minOffsetAlignment - 1) & ~(minOffsetAlignment - 1);
+   }
+
+   srcData = (T*)malloc(capacity * alignment);
+}
+
+template<class T>
+VulkanBuffer<T>::VulkanBuffer(vector<T>* vData, size_t capacity)
+                              : alignment(sizeof(T))
+                              , capacity(capacity)
+                              , numObjects(0)
+                              , srcData(nullptr)
+                              , rawData(nullptr)
+                              , vData(vData) {
+   // TODO: Allocate initial capacity in vector
+}
+
+template<class T>
+VulkanBuffer<T>::~VulkanBuffer() {
+   if (srcData != nullptr) {
+      free(srcData);
+   }
+}
+
+template<class T>
+VulkanBuffer<T>& VulkanBuffer<T>::operator=(const VulkanBuffer<T>& other) {
+   if (this == &other) {
+      return *this;
+   }
+
+   /*
+   // assume *this manages a reusable resource, such as a heap-allocated buffer mArray
+   if (size != other.size) {        // resource in *this cannot be reused
+      delete[] mArray;              // release resource in *this
+      mArray = nullptr;
+      size = 0;                     // preserve invariants in case next line throws
+      mArray = new int[other.size]; // allocate resource in *this
+      size = other.size;
+   }
+   */
+
+   if (srcData != nullptr) {
+      free(srcData);
+      srcData = nullptr;
+   }
+
+   alignment = other.alignment;
+   capacity = other.capacity;
+
+   srcData = (T*)malloc(capacity * alignment);
+   // TODO: Check for failure
+
+   memcpy(srcData, other.srcData, capacity * alignment);
+
+   return *this;
+}
+
+template<class T>
+T* VulkanBuffer<T>::data() {
+   if (srcData != nullptr) {
+      return srcData;
+   } else {
+      return vData->data();
+   }
+}
+
+template<class T>
+void* VulkanBuffer<T>::mappedData() {
+   return rawData;
+}
+
+#endif // _VULKAN_BUFFER_H
Index: vulkan-game.cpp
===================================================================
--- vulkan-game.cpp	(revision 996dd3efcf576a5eaf4f04878167c007d853dbac)
+++ vulkan-game.cpp	(revision a3cefaa6294c97206ade08805f4ff1b8c13d2d4e)
@@ -77,4 +77,9 @@
                      , gui(nullptr)
                      , window(nullptr)
+                     , objects_modelPipeline()
+                     , objects_shipPipeline()
+                     , objects_asteroidPipeline()
+                     , objects_laserPipeline()
+                     , objects_explosionPipeline()
                      , score(0)
                      , fps(0.0f) {
@@ -102,4 +107,13 @@
    initVulkan();
 
+   VkPhysicalDeviceProperties deviceProperties;
+   vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties);
+
+   objects_modelPipeline = VulkanBuffer<SSBO_ModelObject>(10, deviceProperties.limits.minUniformBufferOffsetAlignment);
+   objects_shipPipeline = VulkanBuffer<SSBO_ModelObject>(10, deviceProperties.limits.minUniformBufferOffsetAlignment);
+   objects_asteroidPipeline = VulkanBuffer<SSBO_Asteroid>(10, deviceProperties.limits.minUniformBufferOffsetAlignment);
+   objects_laserPipeline = VulkanBuffer<SSBO_Laser>(2, deviceProperties.limits.minUniformBufferOffsetAlignment);
+   objects_explosionPipeline = VulkanBuffer<SSBO_Explosion>(2, deviceProperties.limits.minUniformBufferOffsetAlignment);
+
    initImGuiOverlay();
 
@@ -131,4 +145,6 @@
 
    SceneObject<ModelVertex, SSBO_ModelObject>* texturedSquare = nullptr;
+
+   // TODO: Ideally, avoid having to make the squares as modified upon creation
 
    texturedSquare = &addObject(modelObjects, modelPipeline,
@@ -145,7 +161,7 @@
          }, {
             mat4(1.0f)
-         }, storageBuffers_modelPipeline, false);
-
-   updateStorageBuffer(storageBuffers_modelPipeline, modelPipeline.numObjects - 1, texturedSquare->ssbo);
+         }, storageBuffers_modelPipeline);
+
+   objects_modelPipeline.numObjects++;
 
    texturedSquare->model_base =
@@ -166,7 +182,7 @@
          }, {
             mat4(1.0f)
-         }, storageBuffers_modelPipeline, false);
-
-   updateStorageBuffer(storageBuffers_modelPipeline, modelPipeline.numObjects - 1, texturedSquare->ssbo);
+         }, storageBuffers_modelPipeline);
+
+   objects_modelPipeline.numObjects++;
 
    texturedSquare->model_base =
@@ -428,7 +444,7 @@
       }, {
          mat4(1.0f)
-      }, storageBuffers_shipPipeline, false);
-
-   updateStorageBuffer(storageBuffers_shipPipeline, shipPipeline.numObjects - 1, ship.ssbo);
+      }, storageBuffers_shipPipeline);
+
+   objects_shipPipeline.numObjects++;
 
    ship.model_base =
@@ -600,7 +616,7 @@
    modelPipeline = GraphicsPipeline_Vulkan<ModelVertex>(
       VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, physicalDevice, device, renderPass,
-      { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height }, swapChainImages, 24, 24, 10);
-
-   createBufferSet(modelPipeline.objectCapacity * sizeof(SSBO_ModelObject),
+      { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height }, swapChainImages, 24, 24);
+
+   createBufferSet(objects_modelPipeline.capacity * sizeof(SSBO_ModelObject),
       VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
       VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
@@ -610,7 +626,7 @@
    shipPipeline = GraphicsPipeline_Vulkan<ModelVertex>(
       VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, physicalDevice, device, renderPass,
-      { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height }, swapChainImages, 138, 138, 10);
-
-   createBufferSet(modelPipeline.objectCapacity * sizeof(SSBO_ModelObject),
+      { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height }, swapChainImages, 138, 138);
+
+   createBufferSet(objects_shipPipeline.capacity * sizeof(SSBO_ModelObject),
       VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
       VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
@@ -620,7 +636,7 @@
    asteroidPipeline = GraphicsPipeline_Vulkan<ModelVertex>(
       VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, physicalDevice, device, renderPass,
-      { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height }, swapChainImages, 24, 36, 10);
-
-   createBufferSet(modelPipeline.objectCapacity * sizeof(SSBO_Asteroid),
+      { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height }, swapChainImages, 24, 36);
+
+   createBufferSet(objects_asteroidPipeline.capacity * sizeof(SSBO_Asteroid),
       VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
       VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
@@ -630,7 +646,7 @@
    laserPipeline = GraphicsPipeline_Vulkan<LaserVertex>(
       VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, physicalDevice, device, renderPass,
-      { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height }, swapChainImages, 8, 18, 2);
-
-   createBufferSet(modelPipeline.objectCapacity * sizeof(SSBO_Laser),
+      { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height }, swapChainImages, 8, 18);
+
+   createBufferSet(objects_laserPipeline.capacity * sizeof(SSBO_Laser),
       VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
       VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
@@ -641,7 +657,7 @@
       VK_PRIMITIVE_TOPOLOGY_POINT_LIST, physicalDevice, device, renderPass,
       { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height },
-      swapChainImages, EXPLOSION_PARTICLE_COUNT, EXPLOSION_PARTICLE_COUNT, 2);
-
-   createBufferSet(modelPipeline.objectCapacity * sizeof(SSBO_Explosion),
+      swapChainImages, EXPLOSION_PARTICLE_COUNT, EXPLOSION_PARTICLE_COUNT);
+
+   createBufferSet(objects_explosionPipeline.capacity * sizeof(SSBO_Explosion),
       VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
       VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
@@ -768,7 +784,7 @@
                            }, {
                               mat4(1.0f)
-                           }, storageBuffers_modelPipeline, true);
-
-                  updateStorageBuffer(storageBuffers_modelPipeline, modelPipeline.numObjects - 1, texturedSquare.ssbo);
+                           }, storageBuffers_modelPipeline);
+
+                  objects_modelPipeline.numObjects++;
 
                   texturedSquare.model_base =
@@ -929,4 +945,5 @@
 // where it will run just once per frame
 void VulkanGame::updateScene() {
+   // Rotate the textured squares
    for (SceneObject<ModelVertex, SSBO_ModelObject>& model : this->modelObjects) {
       model.model_transform =
@@ -1050,7 +1067,7 @@
                   10.0f,
                   false
-               }, storageBuffers_asteroidPipeline, true);
-
-      updateStorageBuffer(storageBuffers_asteroidPipeline, asteroidPipeline.numObjects - 1, asteroid.ssbo);
+               }, storageBuffers_asteroidPipeline);
+
+      objects_asteroidPipeline.numObjects++;
 
       // This accounts for the scaling in model_base.
@@ -1077,4 +1094,23 @@
    }
 
+   // TODO: Probably move the resizing to the VulkanBuffer class
+   if (objects_modelPipeline.numObjects > objects_modelPipeline.capacity) {
+      resizeStorageBufferSet(storageBuffers_modelPipeline, objects_modelPipeline, modelPipeline, resourceCommandPool,
+                             graphicsQueue);
+   }
+
+   for (size_t i = 0; i < modelObjects.size(); i++) {
+      if (modelObjects[i].modified) {
+         updateObject(modelObjects, modelPipeline, i);
+         updateStorageBuffer(storageBuffers_modelPipeline, i, modelObjects[i].ssbo);
+      }
+   }
+
+   // TODO: Probably move the resizing to the VulkanBuffer class
+   if (objects_shipPipeline.numObjects > objects_shipPipeline.capacity) {
+      resizeStorageBufferSet(storageBuffers_shipPipeline, objects_shipPipeline, shipPipeline, resourceCommandPool,
+                             graphicsQueue);
+   }
+
    for (size_t i = 0; i < shipObjects.size(); i++) {
       if (shipObjects[i].modified) {
@@ -1084,9 +1120,8 @@
    }
 
-   for (size_t i = 0; i < modelObjects.size(); i++) {
-      if (modelObjects[i].modified) {
-         updateObject(modelObjects, modelPipeline, i);
-         updateStorageBuffer(storageBuffers_modelPipeline, i, modelObjects[i].ssbo);
-      }
+   // TODO: Probably move the resizing to the VulkanBuffer class
+   if (objects_asteroidPipeline.numObjects > objects_asteroidPipeline.capacity) {
+      resizeStorageBufferSet(storageBuffers_asteroidPipeline, objects_asteroidPipeline, asteroidPipeline,
+                             resourceCommandPool, graphicsQueue);
    }
 
@@ -1098,4 +1133,10 @@
    }
 
+   // TODO: Probably move the resizing to the VulkanBuffer class
+   if (objects_laserPipeline.numObjects > objects_laserPipeline.capacity) {
+      resizeStorageBufferSet(storageBuffers_laserPipeline, objects_laserPipeline, laserPipeline, resourceCommandPool,
+                             graphicsQueue);
+   }
+
    for (size_t i = 0; i < laserObjects.size(); i++) {
       if (laserObjects[i].modified) {
@@ -1103,4 +1144,10 @@
          updateStorageBuffer(storageBuffers_laserPipeline, i, laserObjects[i].ssbo);
       }
+   }
+
+   // TODO: Probably move the resizing to the VulkanBuffer class
+   if (objects_explosionPipeline.numObjects > objects_explosionPipeline.capacity) {
+      resizeStorageBufferSet(storageBuffers_explosionPipeline, objects_explosionPipeline, explosionPipeline,
+                             resourceCommandPool, graphicsQueue);
    }
 
@@ -1525,5 +1572,5 @@
    return VulkanUtils::findSupportedFormat(
       physicalDevice,
-      { VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT },
+      { VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D32_SFLOAT, VK_FORMAT_D24_UNORM_S8_UINT },
       VK_IMAGE_TILING_OPTIMAL,
       VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT
@@ -1929,7 +1976,7 @@
          color,
          false
-      }, storageBuffers_laserPipeline, true);
-
-   updateStorageBuffer(storageBuffers_laserPipeline, laserPipeline.numObjects - 1, laser.ssbo);
+      }, storageBuffers_laserPipeline);
+
+   objects_laserPipeline.numObjects++;
 
    float xAxisRotation = asin(ray.y / length);
@@ -2141,7 +2188,7 @@
          duration,
          false
-      }, storageBuffers_explosionPipeline, true);
-
-   updateStorageBuffer(storageBuffers_explosionPipeline, explosionPipeline.numObjects - 1, explosion.ssbo);
+      }, storageBuffers_explosionPipeline);
+
+   objects_explosionPipeline.numObjects++;
 
    explosion.model_base = model_mat;
Index: vulkan-game.hpp
===================================================================
--- vulkan-game.hpp	(revision 996dd3efcf576a5eaf4f04878167c007d853dbac)
+++ vulkan-game.hpp	(revision a3cefaa6294c97206ade08805f4ff1b8c13d2d4e)
@@ -22,8 +22,9 @@
 
 #include "consts.hpp"
+#include "utils.hpp"
+#include "vulkan-utils.hpp"
+#include "vulkan-buffer.hpp"
 #include "graphics-pipeline_vulkan.hpp"
 #include "game-gui-sdl.hpp"
-#include "utils.hpp"
-#include "vulkan-utils.hpp"
 
 using namespace glm;
@@ -101,4 +102,5 @@
 // Also, probably better to make this a vector of structs where each struct
 // has a VkBuffer, VkDeviceMemory, and VkDescriptorBufferInfo
+// TODO: Maybe change the structure here since VkDescriptorBufferInfo already stores a reference to the VkBuffer
 struct StorageBufferSet {
    vector<VkBuffer> buffers;
@@ -316,8 +318,17 @@
 
       StorageBufferSet storageBuffers_modelPipeline;
+      VulkanBuffer<SSBO_ModelObject> objects_modelPipeline;
+
       StorageBufferSet storageBuffers_shipPipeline;
+      VulkanBuffer<SSBO_ModelObject> objects_shipPipeline;
+
       StorageBufferSet storageBuffers_asteroidPipeline;
+      VulkanBuffer<SSBO_Asteroid> objects_asteroidPipeline;
+
       StorageBufferSet storageBuffers_laserPipeline;
+      VulkanBuffer<SSBO_Laser> objects_laserPipeline;
+
       StorageBufferSet storageBuffers_explosionPipeline;
+      VulkanBuffer<SSBO_Explosion> objects_explosionPipeline;
 
       // TODO: Maybe make the ubo objects part of the pipeline class since there's only one ubo
@@ -443,6 +454,7 @@
       // TODO: Remove the need for templating, which is only there so a GraphicsPupeline_Vulkan can be passed in
       template<class VertexType, class SSBOType>
-      void resizeStorageBufferSet(StorageBufferSet& set, VkCommandPool commandPool, VkQueue graphicsQueue,
-                                  GraphicsPipeline_Vulkan<VertexType>& pipeline);
+      void resizeStorageBufferSet(StorageBufferSet& set, VulkanBuffer<SSBOType>& buffer,
+                                  GraphicsPipeline_Vulkan<VertexType>& pipeline,
+                                  VkCommandPool commandPool, VkQueue graphicsQueue);
 
       template<class SSBOType>
@@ -455,6 +467,5 @@
                                                    GraphicsPipeline_Vulkan<VertexType>& pipeline,
                                                    const vector<VertexType>& vertices, vector<uint16_t> indices,
-                                                   SSBOType ssbo, StorageBufferSet& storageBuffers,
-                                                   bool pipelinesCreated);
+                                                   SSBOType ssbo, StorageBufferSet& storageBuffers);
 
       template<class VertexType>
@@ -511,8 +522,14 @@
 
 template<class VertexType, class SSBOType>
-void VulkanGame::resizeStorageBufferSet(StorageBufferSet& set, VkCommandPool commandPool, VkQueue graphicsQueue,
-                                        GraphicsPipeline_Vulkan<VertexType>& pipeline) {
-   pipeline.objectCapacity *= 2;
-   VkDeviceSize bufferSize = pipeline.objectCapacity * sizeof(SSBOType);
+void VulkanGame::resizeStorageBufferSet(StorageBufferSet& set, VulkanBuffer<SSBOType>& buffer,
+                                        GraphicsPipeline_Vulkan<VertexType>& pipeline,
+                                        VkCommandPool commandPool, VkQueue graphicsQueue) {
+   size_t numObjects = buffer.numObjects < buffer.capacity ? buffer.numObjects : buffer.capacity;
+
+   do {
+      buffer.capacity *= 2;
+   } while (buffer.capacity < buffer.numObjects);
+
+   VkDeviceSize bufferSize = buffer.capacity * sizeof(SSBOType);
 
    for (size_t i = 0; i < set.buffers.size(); i++) {
@@ -526,5 +543,5 @@
 
       VulkanUtils::copyBuffer(device, commandPool, set.buffers[i], newStorageBuffer,
-         0, 0, pipeline.numObjects * sizeof(SSBOType), graphicsQueue);
+         0, 0, numObjects * sizeof(SSBOType), graphicsQueue);
 
       vkDestroyBuffer(device, set.buffers[i], nullptr);
@@ -538,4 +555,8 @@
       set.infoSet[i].range = bufferSize; // Size of the update starting from offset, or VK_WHOLE_SIZE
    }
+
+   // Assume the SSBO is always the 2nd binding
+   // TODO: Figure out a way to make this more flexible
+   pipeline.updateDescriptorInfo(1, &set.infoSet, swapChainImages);
 }
 
@@ -551,15 +572,9 @@
 // and to change the model matrix later by setting model_transform and then calling updateObject()
 // Figure out a better way to allow the model matrix to be set during object creation
-
-// TODO: Maybe return a reference to the object from this method if I decide that updating it
-// immediately after creation is a good idea (such as setting model_base)
-// Currently, model_base is set like this in a few places and the radius is set for asteroids
-// to account for scaling
 template<class VertexType, class SSBOType>
 SceneObject<VertexType, SSBOType>& VulkanGame::addObject(vector<SceneObject<VertexType, SSBOType>>& objects,
                                                          GraphicsPipeline_Vulkan<VertexType>& pipeline,
                                                          const vector<VertexType>& vertices, vector<uint16_t> indices,
-                                                         SSBOType ssbo, StorageBufferSet& storageBuffers,
-                                                         bool pipelinesCreated) {
+                                                         SSBOType ssbo, StorageBufferSet& storageBuffers) {
    // TODO: Use the model field of ssbo to set the object's model_base
    // currently, the passed in model is useless since it gets overridden in updateObject() anyway
@@ -582,46 +597,4 @@
 
    pipeline.addObject(obj.vertices, obj.indices, resourceCommandPool, graphicsQueue);
-
-   // TODO: Probably move the resizing to the VulkanBuffer class
-   // First, try moving this out of addObject
-   bool resizeStorageBuffer = pipeline.numObjects == pipeline.objectCapacity;
-
-   if (resizeStorageBuffer) {
-      resizeStorageBufferSet<VertexType, SSBOType>(storageBuffers, resourceCommandPool, graphicsQueue, pipeline);
-      pipeline.cleanup();
-
-      // Assume the SSBO is always the 2nd binding
-      // TODO: Figure out a way to make this more flexible
-      pipeline.updateDescriptorInfo(1, &storageBuffers.infoSet);
-   }
-
-   pipeline.numObjects++;
-
-   // TODO: Figure out why I am destroying and recreating the ubos when the swap chain is recreated,
-   // but am reusing the same ssbos. Maybe I don't need to recreate the ubos.
-
-   if (pipelinesCreated) {
-      // TODO: See if I can avoid doing this when recreating the pipeline
-      vkDeviceWaitIdle(device);
-
-      for (uint32_t i = 0; i < swapChainImageCount; i++) {
-         vkFreeCommandBuffers(device, commandPools[i], 1, &commandBuffers[i]);
-      }
-
-      // TODO: The pipeline recreation only has to be done once per frame where at least
-      // one SSBO is resized.
-      // Refactor the logic to check for any resized SSBOs after all objects for the frame
-      // are created and then recreate each of the corresponding pipelines only once per frame
-
-      // TODO: Also, verify if I actually need to recreate all of these, or maybe just the descriptor sets, for instance
-
-      if (resizeStorageBuffer) {
-         pipeline.createPipeline(pipeline.vertShaderFile, pipeline.fragShaderFile);
-         pipeline.createDescriptorPool(swapChainImages);
-         pipeline.createDescriptorSets(swapChainImages);
-      }
-
-      createCommandBuffers();
-   }
 
    return obj;
