Index: shaders/laser.frag
===================================================================
--- shaders/laser.frag	(revision 237cbec06d86b83bf2bf33cd9ca41842b69650bf)
+++ shaders/laser.frag	(revision 237cbec06d86b83bf2bf33cd9ca41842b69650bf)
@@ -0,0 +1,26 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+struct Object {
+   mat4 model;
+   vec3 color;
+   bool deleted;
+};
+
+layout(binding = 1) readonly buffer StorageBufferObject {
+   Object objects[];
+} sbo;
+
+layout(binding = 2) uniform sampler2D laser_texture;
+
+layout(location = 0) in vec2 texcoords_fs;
+layout(location = 1) flat in uint obj_index_fs;
+
+layout(location = 0) out vec4 frag_color;
+
+void main() {
+   vec4 texel = texture(laser_texture, texcoords_fs);
+   vec3 laser_color = sbo.objects[obj_index_fs].color;
+
+   frag_color = vec4(texel.r * laser_color.r, texel.g * laser_color.g, texel.b * laser_color.b, texel.a);
+}
Index: shaders/laser.vert
===================================================================
--- shaders/laser.vert	(revision 237cbec06d86b83bf2bf33cd9ca41842b69650bf)
+++ shaders/laser.vert	(revision 237cbec06d86b83bf2bf33cd9ca41842b69650bf)
@@ -0,0 +1,37 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+struct Object {
+   mat4 model;
+   vec3 color;
+   bool deleted;
+};
+
+layout (binding = 0) uniform UniformBufferObject {
+   mat4 view;
+   mat4 proj;
+} ubo;
+
+layout(binding = 1) readonly buffer StorageBufferObject {
+   Object objects[];
+} sbo;
+
+layout(location = 0) in vec3 vertex_position;
+layout(location = 1) in vec2 texcoords_vs;
+layout(location = 2) in uint obj_index_vs;
+
+layout(location = 0) out vec2 texcoords_fs;
+layout(location = 1) out uint obj_index_fs;
+
+void main() {
+   vec3 position_eye = vec3(ubo.view * sbo.objects[obj_index_vs].model * vec4(vertex_position, 1.0));
+
+   texcoords_fs = texcoords_vs;
+   obj_index_fs = obj_index_vs;
+
+   if (sbo.objects[obj_index_vs].deleted) {
+      gl_Position = vec4(0.0, 0.0, 2.0, 1.0);
+   } else {
+      gl_Position = ubo.proj * vec4(position_eye, 1.0);
+   }
+}
Index: vulkan-game.cpp
===================================================================
--- vulkan-game.cpp	(revision 6104594eaf6ed43ec207e10ac8199d9e321f928f)
+++ vulkan-game.cpp	(revision 237cbec06d86b83bf2bf33cd9ca41842b69650bf)
@@ -27,4 +27,5 @@
    this->ship_VP_mats = {};
    this->asteroid_VP_mats = {};
+   this->laser_VP_mats = {};
 }
 
@@ -540,4 +541,22 @@
    asteroidPipeline.createDescriptorSets(swapChainImages);
 
+   laserPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&LaserVertex::pos));
+   laserPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&LaserVertex::texCoord));
+   laserPipeline.addAttribute(VK_FORMAT_R32_UINT, offset_of(&LaserVertex::objIndex));
+
+   createBufferSet(sizeof(UBO_VP_mats), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+      uniformBuffers_laserPipeline, uniformBuffersMemory_laserPipeline, uniformBufferInfoList_laserPipeline);
+
+   laserPipeline.addDescriptorInfo(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+      VK_SHADER_STAGE_VERTEX_BIT, &uniformBufferInfoList_laserPipeline);
+   laserPipeline.addStorageDescriptor(VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT);
+   laserPipeline.addDescriptorInfo(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+      VK_SHADER_STAGE_FRAGMENT_BIT, &laserTextureImageDescriptor);
+
+   laserPipeline.createDescriptorSetLayout();
+   laserPipeline.createPipeline("shaders/laser-vert.spv", "shaders/laser-frag.spv");
+   laserPipeline.createDescriptorPool(swapChainImages);
+   laserPipeline.createDescriptorSets(swapChainImages);
+
    cout << "Created all the graphics pipelines" << endl;
 
@@ -564,4 +583,7 @@
    asteroidPipeline = GraphicsPipeline_Vulkan<AsteroidVertex, SSBO_Asteroid>(physicalDevice, device, renderPass,
       { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height }, swapChainImages, 24, 36, 10);
+
+   laserPipeline = GraphicsPipeline_Vulkan<LaserVertex, SSBO_Laser>(physicalDevice, device, renderPass,
+      { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height }, swapChainImages, 8, 18, 2);
 }
 
@@ -591,4 +613,7 @@
    asteroid_VP_mats.view = viewMat;
    asteroid_VP_mats.proj = projMat;
+
+   laser_VP_mats.view = viewMat;
+   laser_VP_mats.proj = projMat;
 }
 
@@ -827,4 +852,10 @@
    }
 
+   for (size_t i = 0; i < laserObjects.size(); i++) {
+      if (laserObjects[i].modified) {
+         updateObject(laserObjects, laserPipeline, i);
+      }
+   }
+
    VulkanUtils::copyDataToMemory(device, uniformBuffersMemory_modelPipeline[currentImage], 0, object_VP_mats);
 
@@ -832,4 +863,6 @@
 
    VulkanUtils::copyDataToMemory(device, uniformBuffersMemory_asteroidPipeline[currentImage], 0, asteroid_VP_mats);
+
+   VulkanUtils::copyDataToMemory(device, uniformBuffersMemory_laserPipeline[currentImage], 0, laser_VP_mats);
 }
 
@@ -929,4 +962,5 @@
    VulkanUtils::destroyVulkanImage(device, sdlOverlayImage);
    VulkanUtils::destroyVulkanImage(device, floorTextureImage);
+   VulkanUtils::destroyVulkanImage(device, laserTextureImage);
 
    vkDestroySampler(device, textureSampler, nullptr);
@@ -936,4 +970,5 @@
    shipPipeline.cleanupBuffers();
    asteroidPipeline.cleanupBuffers();
+   laserPipeline.cleanupBuffers();
 
    for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
@@ -1325,4 +1360,12 @@
    floorTextureImageDescriptor.imageView = floorTextureImage.imageView;
    floorTextureImageDescriptor.sampler = textureSampler;
+
+   VulkanUtils::createVulkanImageFromFile(device, physicalDevice, commandPool, "textures/laser.png",
+      laserTextureImage, graphicsQueue);
+
+   laserTextureImageDescriptor = {};
+   laserTextureImageDescriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+   laserTextureImageDescriptor.imageView = laserTextureImage.imageView;
+   laserTextureImageDescriptor.sampler = textureSampler;
 }
 
@@ -1419,4 +1462,5 @@
       shipPipeline.createRenderCommands(commandBuffers[i], i);
       asteroidPipeline.createRenderCommands(commandBuffers[i], i);
+      laserPipeline.createRenderCommands(commandBuffers[i], i);
 
       // Always render this pipeline last
@@ -1523,4 +1567,12 @@
    asteroidPipeline.createDescriptorSets(swapChainImages);
 
+   createBufferSet(sizeof(UBO_VP_mats), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+      uniformBuffers_laserPipeline, uniformBuffersMemory_laserPipeline, uniformBufferInfoList_laserPipeline);
+
+   laserPipeline.updateRenderPass(renderPass);
+   laserPipeline.createPipeline("shaders/laser-vert.spv", "shaders/laser-frag.spv");
+   laserPipeline.createDescriptorPool(swapChainImages);
+   laserPipeline.createDescriptorSets(swapChainImages);
+
    createCommandBuffers();
 }
@@ -1539,4 +1591,5 @@
    shipPipeline.cleanup();
    asteroidPipeline.cleanup();
+   laserPipeline.cleanup();
 
    for (size_t i = 0; i < uniformBuffers_modelPipeline.size(); i++) {
@@ -1555,4 +1608,9 @@
    }
 
+   for (size_t i = 0; i < uniformBuffers_laserPipeline.size(); i++) {
+      vkDestroyBuffer(device, uniformBuffers_laserPipeline[i], nullptr);
+      vkFreeMemory(device, uniformBuffersMemory_laserPipeline[i], nullptr);
+   }
+
    vkDestroyRenderPass(device, renderPass, nullptr);
 
Index: vulkan-game.hpp
===================================================================
--- vulkan-game.hpp	(revision 6104594eaf6ed43ec207e10ac8199d9e321f928f)
+++ vulkan-game.hpp	(revision 237cbec06d86b83bf2bf33cd9ca41842b69650bf)
@@ -51,4 +51,10 @@
 };
 
+struct LaserVertex {
+   vec3 pos;
+   vec2 texCoord;
+   unsigned int objIndex;
+};
+
 struct UBO_VP_mats {
    alignas(16) mat4 view;
@@ -63,4 +69,10 @@
    alignas(16) mat4 model;
    alignas(4) float hp;
+   alignas(4) unsigned int deleted;
+};
+
+struct SSBO_Laser {
+   alignas(16) mat4 model;
+   alignas(4) vec3 color;
    alignas(4) unsigned int deleted;
 };
@@ -148,4 +160,7 @@
       VkDescriptorImageInfo floorTextureImageDescriptor;
 
+      VulkanImage laserTextureImage;
+      VkDescriptorImageInfo laserTextureImageDescriptor;
+
       TTF_Font* font;
       SDL_Texture* fontSDLTexture;
@@ -200,4 +215,13 @@
 
       UBO_VP_mats asteroid_VP_mats;
+
+      GraphicsPipeline_Vulkan<LaserVertex, SSBO_Laser> laserPipeline;
+      vector<SceneObject<LaserVertex, SSBO_Laser>> laserObjects;
+
+      vector<VkBuffer> uniformBuffers_laserPipeline;
+      vector<VkDeviceMemory> uniformBuffersMemory_laserPipeline;
+      vector<VkDescriptorBufferInfo> uniformBufferInfoList_laserPipeline;
+
+      UBO_VP_mats laser_VP_mats;
 
       time_point<steady_clock> startTime;
