Переглянути джерело

Moved most Vulkan calls into classes

Kajetan Johannes Hammerle 2 місяців тому
батько
коміт
b4e213a8d0

+ 2 - 0
include/core/VulkanBase.hpp

@@ -85,6 +85,8 @@ namespace Core::Vulkan {
             const List<DeviceQueueData> data,
             const List<const char*>& extensions);
 
+        void waitForIdle();
+
         operator VkDevice() {
             return device;
         }

+ 1 - 0
include/core/VulkanUtility.hpp

@@ -23,5 +23,6 @@ namespace Core::Vulkan {
         }                                                                  \
     } while(false)
 #define VK_CHECK_TRUE(a) VK_CHECK(true, a)
+#define VK_CHECK_VOID(a) VK_CHECK(, a)
 
 #endif

+ 47 - 1
include/core/VulkanWrapper.hpp

@@ -77,6 +77,8 @@ namespace Core::Vulkan {
         Fence(Fence&&) = default;
         ~Fence();
         bool init(VkDevice d);
+        void reset();
+        void waitFor(u64 timeout = UINT64_MAX);
     };
 
     struct Swapchain : public BaseWrapper<VkSwapchainKHR> {
@@ -94,6 +96,7 @@ namespace Core::Vulkan {
         };
 
         bool init(Data& d);
+        bool nextImage(u32& imageIndex, Semaphore& s, u64 timeout = UINT64_MAX);
     };
 
     struct ShaderModule : public BaseWrapper<VkShaderModule> {
@@ -117,10 +120,53 @@ namespace Core::Vulkan {
         bool init(const Swapchain& s, VkFormat format);
     };
 
+    struct RenderPass : public BaseWrapper<VkRenderPass> {
+        RenderPass() = default;
+        RenderPass(RenderPass&&) = default;
+        ~RenderPass();
+        bool init(VkDevice d, VkFormat format);
+    };
+
+    struct Pipeline : public BaseWrapper<VkPipeline> {
+        VkViewport viewport;
+        VkRect2D scissor;
+
+        Pipeline();
+        Pipeline(Pipeline&&) = default;
+        ~Pipeline();
+        void updateSize(u32 width, u32 height);
+        bool init(PipelineLayout& pl, RenderPass& rp);
+    };
+
+    struct CommandPool : public BaseWrapper<VkCommandPool> {
+        CommandPool() = default;
+        CommandPool(CommandPool&&) = default;
+        ~CommandPool();
+        bool init(VkDevice d, u32 queueFamily);
+    };
+
     struct CommandBuffer : public BaseWrapper<VkCommandBuffer> {
         CommandBuffer() = default;
         CommandBuffer(CommandBuffer&&) = default;
-        bool init(VkDevice d, VkCommandPool cp);
+        bool init(CommandPool& cp);
+        void reset();
+        void begin();
+        void end();
+        void beginRenderPass(
+            RenderPass& rp, Framebuffer& f, const VkExtent2D& size);
+        void endRenderPass();
+        void bindPipeline(Pipeline& p);
+        void draw(u32 vertices);
+    };
+
+    struct Queue : public BaseWrapper<VkQueue> {
+        Queue() = default;
+        Queue(Queue&&) = default;
+        bool init(VkDevice d, u32 queueFamily);
+        void submit(
+            CommandBuffer& cb, Fence& f, Semaphore& wait, Semaphore& signal,
+            VkPipelineStageFlags flags);
+        void present(Semaphore& signal, Swapchain& s, u32 index);
     };
 
 }

+ 9 - 3
shaders/vertex.vert.glsl

@@ -2,13 +2,19 @@
 
 layout(location = 0) out vec3 fragColor;
 
-vec2 positions[3] = vec2[](
+vec2 positions[6] = vec2[](
     vec2(0.0, -0.5),
     vec2(0.5, 0.5),
-    vec2(-0.5, 0.5)
+    vec2(-0.5, 0.5),
+    vec2(-1.0, -1.0),
+    vec2(-0.5, -1.0),
+    vec2(-1.0, -0.5)
 );
 
-vec3 colors[3] = vec3[](
+vec3 colors[6] = vec3[](
+    vec3(1.0, 0.0, 0.0),
+    vec3(0.0, 1.0, 0.0),
+    vec3(0.0, 0.0, 1.0),
     vec3(1.0, 0.0, 0.0),
     vec3(0.0, 1.0, 0.0),
     vec3(0.0, 0.0, 1.0)

+ 6 - 0
src/VulkanBase.cpp

@@ -285,3 +285,9 @@ bool Base::initDevice(
     VK_CHECK_TRUE(vkCreateDevice(physicalDevice, &info, nullptr, &device));
     return false;
 }
+
+void Base::waitForIdle() {
+    if(device != VK_NULL_HANDLE) {
+        vkDeviceWaitIdle(device);
+    }
+}

+ 302 - 258
src/VulkanWrapper.cpp

@@ -8,6 +8,7 @@
 
 namespace Vulkan = Core::Vulkan;
 using Vulkan::CommandBuffer;
+using Vulkan::Queue;
 using Vulkan::SwapchainImages;
 
 #define WRAPPER_DESTRUCT(Type, ...)                                \
@@ -25,6 +26,9 @@ WRAPPER_DESTRUCT(Fence)
 WRAPPER_DESTRUCT(Swapchain, KHR)
 WRAPPER_DESTRUCT(ShaderModule)
 WRAPPER_DESTRUCT(PipelineLayout)
+WRAPPER_DESTRUCT(Pipeline)
+WRAPPER_DESTRUCT(CommandPool)
+WRAPPER_DESTRUCT(RenderPass)
 
 bool ImageView::init(VkDevice d, VkImage image, VkFormat format) {
     VkImageViewCreateInfo info = {
@@ -75,6 +79,14 @@ bool Fence::init(VkDevice d) {
     return false;
 }
 
+void Fence::reset() {
+    VK_CHECK_VOID(vkResetFences(device, 1, &handle));
+}
+
+void Fence::waitFor(u64 timeout) {
+    VK_CHECK_VOID(vkWaitForFences(device, 1, &handle, true, timeout));
+}
+
 static u32 getSwapImageCount(const VkSurfaceCapabilitiesKHR* caps) {
     u32 c = caps->minImageCount + 1;
     // according to VkSurfaceCapabilitiesKHR doc:
@@ -112,6 +124,12 @@ bool Swapchain::init(Data& d) {
     return false;
 }
 
+bool Swapchain::nextImage(u32& imageIndex, Semaphore& s, u64 timeout) {
+    VK_CHECK_TRUE(vkAcquireNextImageKHR(
+        device, handle, timeout, s, VK_NULL_HANDLE, &imageIndex));
+    return false;
+}
+
 bool ShaderModule::init(VkDevice d, const char* path) {
     List<char> f;
     if(Core::readFile(f, path)) {
@@ -153,17 +171,252 @@ bool SwapchainImages::init(const Swapchain& s, VkFormat format) {
     return false;
 }
 
-bool CommandBuffer::init(VkDevice d, VkCommandPool cp) {
+bool RenderPass::init(VkDevice d, VkFormat format) {
+    VkAttachmentDescription c = {
+        .format = format,
+        .samples = VK_SAMPLE_COUNT_1_BIT,
+        .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
+        .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
+        .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
+        .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
+        .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+        .finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR};
+    VkAttachmentReference ca = {
+        .attachment = 0, .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
+    VkSubpassDescription subpass = {
+        .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
+        .colorAttachmentCount = 1,
+        .pColorAttachments = &ca};
+    VkSubpassDependency dependency = {
+        .srcSubpass = VK_SUBPASS_EXTERNAL,
+        .dstSubpass = 0,
+        .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+        .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+        .srcAccessMask = 0,
+        .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
+    };
+    VkRenderPassCreateInfo info = {
+        .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
+        .attachmentCount = 1,
+        .pAttachments = &c,
+        .subpassCount = 1,
+        .pSubpasses = &subpass,
+        .dependencyCount = 1,
+        .pDependencies = &dependency};
+    VK_CHECK_TRUE(vkCreateRenderPass(d, &info, nullptr, &handle));
+    device = d;
+    return false;
+}
+
+Pipeline::Pipeline() : viewport{.maxDepth = 1.0f}, scissor{} {
+}
+
+void Pipeline::updateSize(u32 width, u32 height) {
+    viewport.width = static_cast<float>(width);
+    viewport.height = static_cast<float>(height);
+    scissor.extent.width = width;
+    scissor.extent.height = height;
+}
+
+bool Pipeline::init(PipelineLayout& pl, RenderPass& rp) {
+    ShaderModule vertexShaderModule;
+    ShaderModule fragmentShaderModule;
+    if(vertexShaderModule.init(pl.device, "shaders/vertex.spv") ||
+       fragmentShaderModule.init(pl.device, "shaders/fragment.spv")) {
+        return true;
+    }
+    VkPipelineShaderStageCreateInfo stages[2] = {
+        {.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+         .stage = VK_SHADER_STAGE_VERTEX_BIT,
+         .module = vertexShaderModule,
+         .pName = "main"},
+        {.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+         .stage = VK_SHADER_STAGE_FRAGMENT_BIT,
+         .module = fragmentShaderModule,
+         .pName = "main"}};
+
+    VkPipelineVertexInputStateCreateInfo vertexInputState = {
+        .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO};
+
+    VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = {
+        .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+        .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
+        .primitiveRestartEnable = false};
+
+    VkPipelineViewportStateCreateInfo viewportState = {
+        .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
+        .viewportCount = 1,
+        .pViewports = &viewport,
+        .scissorCount = 1,
+        .pScissors = &scissor};
+
+    VkPipelineRasterizationStateCreateInfo rasterizationState = {
+        .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
+        .depthClampEnable = false,
+        .rasterizerDiscardEnable = false,
+        .polygonMode = VK_POLYGON_MODE_FILL,
+        .cullMode = VK_CULL_MODE_BACK_BIT,
+        .frontFace = VK_FRONT_FACE_CLOCKWISE,
+        .depthBiasEnable = false,
+        .depthBiasConstantFactor = 0.0f,
+        .depthBiasClamp = 0.0f,
+        .depthBiasSlopeFactor = 0.0f,
+        .lineWidth = 1.0f};
+
+    VkPipelineMultisampleStateCreateInfo multisampleState = {
+        .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
+        .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
+        .sampleShadingEnable = false,
+        .minSampleShading = 1.0f,
+        .pSampleMask = nullptr,
+        .alphaToCoverageEnable = false,
+        .alphaToOneEnable = false};
+
+    VkPipelineColorBlendAttachmentState colorBlendAttachmentState = {
+        .blendEnable = false,
+        .srcColorBlendFactor = VK_BLEND_FACTOR_ONE,
+        .dstColorBlendFactor = VK_BLEND_FACTOR_ZERO,
+        .colorBlendOp = VK_BLEND_OP_ADD,
+        .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE,
+        .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
+        .alphaBlendOp = VK_BLEND_OP_ADD,
+        .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
+                          VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT};
+
+    VkPipelineColorBlendStateCreateInfo colorBlendState = {
+        .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
+        .logicOpEnable = false,
+        .logicOp = VK_LOGIC_OP_COPY,
+        .attachmentCount = 1,
+        .pAttachments = &colorBlendAttachmentState,
+        .blendConstants = {0.0f, 0.0f, 0.0f, 0.0f}};
+
+    Core::Array<VkDynamicState, 2> dynamicStates = {
+        {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}};
+
+    VkPipelineDynamicStateCreateInfo dynamicState = {
+        .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
+        .dynamicStateCount = dynamicStates.getLength(),
+        .pDynamicStates = dynamicStates.begin()};
+
+    VkGraphicsPipelineCreateInfo info = {
+        .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
+        .stageCount = 2,
+        .pStages = stages,
+        .pVertexInputState = &vertexInputState,
+        .pInputAssemblyState = &inputAssemblyState,
+        .pTessellationState = nullptr,
+        .pViewportState = &viewportState,
+        .pRasterizationState = &rasterizationState,
+        .pMultisampleState = &multisampleState,
+        .pDepthStencilState = nullptr,
+        .pColorBlendState = &colorBlendState,
+        .pDynamicState = &dynamicState,
+        .layout = pl,
+        .renderPass = rp,
+        .subpass = 0,
+        .basePipelineHandle = VK_NULL_HANDLE,
+        .basePipelineIndex = -1};
+
+    VK_CHECK_TRUE(vkCreateGraphicsPipelines(
+        pl.device, VK_NULL_HANDLE, 1, &info, nullptr, &handle));
+    device = pl.device;
+    return false;
+}
+
+bool CommandPool::init(VkDevice d, u32 queueFamily) {
+    VkCommandPoolCreateInfo info = {
+        .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
+        .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
+        .queueFamilyIndex = queueFamily};
+    VK_CHECK_TRUE(vkCreateCommandPool(d, &info, nullptr, &handle));
+    device = d;
+    return false;
+}
+
+bool CommandBuffer::init(CommandPool& cp) {
     VkCommandBufferAllocateInfo info = {
         .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
         .commandPool = cp,
         .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
         .commandBufferCount = 1};
-    VK_CHECK_TRUE(vkAllocateCommandBuffers(d, &info, &handle));
-    device = d;
+    VK_CHECK_TRUE(vkAllocateCommandBuffers(cp.device, &info, &handle));
+    device = cp.device;
     return false;
 }
 
+void CommandBuffer::reset() {
+    VK_CHECK_VOID(vkResetCommandBuffer(handle, 0));
+}
+
+void CommandBuffer::begin() {
+    VkCommandBufferBeginInfo info = {
+        .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
+    VK_CHECK_VOID(vkBeginCommandBuffer(handle, &info));
+}
+
+void CommandBuffer::end() {
+    VK_CHECK_VOID(vkEndCommandBuffer(handle));
+}
+
+void CommandBuffer::beginRenderPass(
+    RenderPass& rp, Framebuffer& f, const VkExtent2D& size) {
+    VkClearValue v = {.color = {.float32 = {0.0f, 0.0f, 0.0f, 1.0f}}};
+    VkRenderPassBeginInfo info = {
+        .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
+        .renderPass = rp,
+        .framebuffer = f,
+        .renderArea = {.offset = {0, 0}, .extent = size},
+        .clearValueCount = 1,
+        .pClearValues = &v};
+    vkCmdBeginRenderPass(handle, &info, VK_SUBPASS_CONTENTS_INLINE);
+}
+
+void CommandBuffer::endRenderPass() {
+    vkCmdEndRenderPass(handle);
+}
+
+void CommandBuffer::bindPipeline(Pipeline& p) {
+    vkCmdBindPipeline(handle, VK_PIPELINE_BIND_POINT_GRAPHICS, p);
+    vkCmdSetViewport(handle, 0, 1, &p.viewport);
+    vkCmdSetScissor(handle, 0, 1, &p.scissor);
+}
+
+void CommandBuffer::draw(u32 vertices) {
+    vkCmdDraw(handle, vertices, 1, 0, 0);
+}
+
+bool Queue::init(VkDevice d, u32 queueFamily) {
+    vkGetDeviceQueue(d, queueFamily, 0, &handle);
+    return handle == VK_NULL_HANDLE;
+}
+
+void Queue::submit(
+    CommandBuffer& cb, Fence& f, Semaphore& wait, Semaphore& signal,
+    VkPipelineStageFlags flags) {
+    VkSubmitInfo info = {
+        .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
+        .waitSemaphoreCount = 1,
+        .pWaitSemaphores = wait,
+        .pWaitDstStageMask = &flags,
+        .commandBufferCount = 1,
+        .pCommandBuffers = cb,
+        .signalSemaphoreCount = 1,
+        .pSignalSemaphores = signal};
+    VK_CHECK_VOID(vkQueueSubmit(handle, 1, &info, f));
+}
+
+void Queue::present(Semaphore& signal, Swapchain& s, u32 index) {
+    VkPresentInfoKHR info = {
+        .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
+        .waitSemaphoreCount = 1,
+        .pWaitSemaphores = signal,
+        .swapchainCount = 1,
+        .pSwapchains = s,
+        .pImageIndices = &index};
+    VK_CHECK_VOID(vkQueuePresentKHR(handle, &info));
+}
+
 struct VulkanDummy {
     Core::Vulkan::Base base{};
 
@@ -175,21 +428,16 @@ struct VulkanDummy {
     };
 
     BaseData baseData{};
-    VkQueue graphicsQueue{};
-    VkQueue presentQueue{};
+    Queue graphicsQueue{};
+    Queue presentQueue{};
     VkExtent2D swapchainSize{};
     Swapchain swapchain{};
     SwapchainImages images{};
-    ShaderModule vertexShaderModule{};
-    ShaderModule fragmentShaderModule{};
     PipelineLayout pipelineLayout{};
-    VkRenderPass renderPass{};
-    VkViewport viewport{};
-    VkRect2D scissor{};
-    VkPipeline pipeline{};
+    RenderPass renderPass{};
+    Pipeline pipeline{};
     Core::List<Vulkan::Framebuffer> framebuffers{};
-    VkCommandPool commandPool{};
-    static constexpr size_t MAX_FRAMES = 2;
+    CommandPool commandPool{};
     size_t currentFrame = 0;
     bool shouldWait = false;
 
@@ -200,7 +448,7 @@ struct VulkanDummy {
         Fence inFlightFence{};
     };
 
-    Frame frames[MAX_FRAMES];
+    Core::Array<Frame, 2> frames{};
 
     static int getSurfaceFormatPoints(const VkSurfaceFormatKHR& sf) {
         if(sf.format == VK_FORMAT_B8G8R8A8_UNORM &&
@@ -279,18 +527,10 @@ struct VulkanDummy {
         extensions.add(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
         if(base.initDevice(data, extensions)) {
             return true;
-        }
-        vkGetDeviceQueue(base, baseData.graphicsFamily, 0, &graphicsQueue);
-        if(graphicsQueue == VK_NULL_HANDLE) {
+        } else if(graphicsQueue.init(base, baseData.graphicsFamily)) {
             LOG_ERROR("Cannot get device graphics queue");
             return true;
-        }
-        if(same) {
-            presentQueue = graphicsQueue;
-            return false;
-        }
-        vkGetDeviceQueue(base, baseData.presentFamily, 0, &presentQueue);
-        if(presentQueue == VK_NULL_HANDLE) {
+        } else if(presentQueue.init(base, baseData.presentFamily)) {
             LOG_ERROR("Cannot get device present queue");
             return true;
         }
@@ -360,118 +600,9 @@ struct VulkanDummy {
         return false;
     }
 
-    bool initShaders() {
-        return vertexShaderModule.init(base, "shaders/vertex.spv") ||
-               fragmentShaderModule.init(base, "shaders/fragment.spv");
-    }
-
     bool initPipeline() {
-        VkPipelineShaderStageCreateInfo stages[2] = {
-            {.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
-             .stage = VK_SHADER_STAGE_VERTEX_BIT,
-             .module = vertexShaderModule,
-             .pName = "main"},
-            {.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
-             .stage = VK_SHADER_STAGE_FRAGMENT_BIT,
-             .module = fragmentShaderModule,
-             .pName = "main"}};
-
-        VkPipelineVertexInputStateCreateInfo vertexInputState = {
-            .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO};
-
-        VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = {
-            .sType =
-                VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
-            .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
-            .primitiveRestartEnable = false};
-
-        viewport = {
-            .x = 0.0f,
-            .y = 0.0f,
-            .width = static_cast<float>(swapchainSize.width),
-            .height = static_cast<float>(swapchainSize.height),
-            .minDepth = 0.0f,
-            .maxDepth = 1.0f};
-        scissor = {.offset = {0, 0}, .extent = swapchainSize};
-        VkPipelineViewportStateCreateInfo viewportState = {
-            .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
-            .viewportCount = 1,
-            .pViewports = &viewport,
-            .scissorCount = 1,
-            .pScissors = &scissor};
-
-        VkPipelineRasterizationStateCreateInfo rasterizationState = {
-            .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
-            .depthClampEnable = false,
-            .rasterizerDiscardEnable = false,
-            .polygonMode = VK_POLYGON_MODE_FILL,
-            .cullMode = VK_CULL_MODE_BACK_BIT,
-            .frontFace = VK_FRONT_FACE_CLOCKWISE,
-            .depthBiasEnable = false,
-            .depthBiasConstantFactor = 0.0f,
-            .depthBiasClamp = 0.0f,
-            .depthBiasSlopeFactor = 0.0f,
-            .lineWidth = 1.0f};
-
-        VkPipelineMultisampleStateCreateInfo multisampleState = {
-            .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
-            .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
-            .sampleShadingEnable = false,
-            .minSampleShading = 1.0f,
-            .pSampleMask = nullptr,
-            .alphaToCoverageEnable = false,
-            .alphaToOneEnable = false};
-
-        VkPipelineColorBlendAttachmentState colorBlendAttachmentState = {
-            .blendEnable = false,
-            .srcColorBlendFactor = VK_BLEND_FACTOR_ONE,
-            .dstColorBlendFactor = VK_BLEND_FACTOR_ZERO,
-            .colorBlendOp = VK_BLEND_OP_ADD,
-            .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE,
-            .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
-            .alphaBlendOp = VK_BLEND_OP_ADD,
-            .colorWriteMask =
-                VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
-                VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT};
-
-        VkPipelineColorBlendStateCreateInfo colorBlendState = {
-            .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
-            .logicOpEnable = false,
-            .logicOp = VK_LOGIC_OP_COPY,
-            .attachmentCount = 1,
-            .pAttachments = &colorBlendAttachmentState,
-            .blendConstants = {0.0f, 0.0f, 0.0f, 0.0f}};
-
-        Core::Array<VkDynamicState, 2> dynamicStates = {
-            {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}};
-
-        VkPipelineDynamicStateCreateInfo dynamicState = {
-            .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
-            .dynamicStateCount = dynamicStates.getLength(),
-            .pDynamicStates = dynamicStates.begin()};
-
-        VkGraphicsPipelineCreateInfo info = {
-            .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
-            .stageCount = 2,
-            .pStages = stages,
-            .pVertexInputState = &vertexInputState,
-            .pInputAssemblyState = &inputAssemblyState,
-            .pTessellationState = nullptr,
-            .pViewportState = &viewportState,
-            .pRasterizationState = &rasterizationState,
-            .pMultisampleState = &multisampleState,
-            .pDepthStencilState = nullptr,
-            .pColorBlendState = &colorBlendState,
-            .pDynamicState = &dynamicState,
-            .layout = pipelineLayout,
-            .renderPass = renderPass,
-            .subpass = 0,
-            .basePipelineHandle = VK_NULL_HANDLE,
-            .basePipelineIndex = -1};
-
-        VK_CHECK_TRUE(vkCreateGraphicsPipelines(
-            base, VK_NULL_HANDLE, 1, &info, nullptr, &pipeline));
-        return false;
+        pipeline.updateSize(swapchainSize.width, swapchainSize.height);
+        return pipeline.init(pipelineLayout, renderPass);
     }
 
     bool initFramebuffers() {
@@ -487,80 +618,21 @@ struct VulkanDummy {
         return false;
     }
 
-    bool initRenderPass() {
-        VkAttachmentDescription c = {
-            .format = baseData.surfaceFormat.format,
-            .samples = VK_SAMPLE_COUNT_1_BIT,
-            .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
-            .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
-            .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
-            .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
-            .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
-            .finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR};
-        VkAttachmentReference ca = {
-            .attachment = 0,
-            .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
-        VkSubpassDescription subpass = {
-            .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
-            .colorAttachmentCount = 1,
-            .pColorAttachments = &ca};
-        VkSubpassDependency dependency = {
-            .srcSubpass = VK_SUBPASS_EXTERNAL,
-            .dstSubpass = 0,
-            .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
-            .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
-            .srcAccessMask = 0,
-            .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
-        };
-        VkRenderPassCreateInfo info = {
-            .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
-            .attachmentCount = 1,
-            .pAttachments = &c,
-            .subpassCount = 1,
-            .pSubpasses = &subpass,
-            .dependencyCount = 1,
-            .pDependencies = &dependency};
-        VK_CHECK_TRUE(vkCreateRenderPass(base, &info, nullptr, &renderPass));
-        return false;
-    }
-
-    bool initCommandPool() {
-        VkCommandPoolCreateInfo info = {
-            .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
-            .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
-            .queueFamilyIndex = baseData.graphicsFamily};
-        VK_CHECK_TRUE(vkCreateCommandPool(base, &info, nullptr, &commandPool));
-        return false;
-    }
-
-    bool fillCommandBuffer(VkCommandBuffer cb, u32 index) {
-        VkCommandBufferBeginInfo info = {
-            .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
-        VK_CHECK_TRUE(vkBeginCommandBuffer(cb, &info));
-        VkClearValue v = {.color = {.float32 = {0.0f, 0.5f, 0.0f, 1.0f}}};
-        VkRenderPassBeginInfo rInfo = {
-            .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
-            .renderPass = renderPass,
-            .framebuffer = framebuffers[index],
-            .renderArea = {.offset = {0, 0}, .extent = swapchainSize},
-            .clearValueCount = 1,
-            .pClearValues = &v};
-        vkCmdBeginRenderPass(cb, &rInfo, VK_SUBPASS_CONTENTS_INLINE);
-        vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
-        vkCmdSetViewport(cb, 0, 1, &viewport);
-        vkCmdSetScissor(cb, 0, 1, &scissor);
-        vkCmdDraw(cb, 3, 1, 0, 0);
-        vkCmdEndRenderPass(cb);
-        VK_CHECK_TRUE(vkEndCommandBuffer(cb));
-        return false;
+    void fillCommandBuffer(CommandBuffer& cb, u32 index) {
+        cb.begin();
+        cb.beginRenderPass(renderPass, framebuffers[index], swapchainSize);
+        cb.bindPipeline(pipeline);
+        cb.draw(6);
+        cb.endRenderPass();
+        cb.end();
     }
 
     bool initFrames() {
-        for(size_t i = 0; i < MAX_FRAMES; i++) {
-            if(frames[i].commandBuffer.init(base, commandPool) ||
-               frames[i].imageAvailableSemaphore.init(base) ||
-               frames[i].renderFinishedSemaphore.init(base) ||
-               frames[i].inFlightFence.init(base)) {
+        for(Frame& f : frames) {
+            if(f.commandBuffer.init(commandPool) ||
+               f.imageAvailableSemaphore.init(base) ||
+               f.renderFinishedSemaphore.init(base) ||
+               f.inFlightFence.init(base)) {
                 return true;
             }
         }
@@ -569,64 +641,36 @@ struct VulkanDummy {
 
     bool init() {
         return base.init() || initPhysicalDevice() || initDevice() ||
-               initSwapchain() || initSwapchainImages() || initShaders() ||
-               pipelineLayout.init(base) || initRenderPass() ||
-               initPipeline() || initFramebuffers() || initCommandPool() ||
-               initFrames();
+               initSwapchain() || initSwapchainImages() ||
+               pipelineLayout.init(base) ||
+               renderPass.init(base, baseData.surfaceFormat.format) ||
+               initPipeline() || initFramebuffers() ||
+               commandPool.init(base, baseData.graphicsFamily) || initFrames();
     }
 
     bool render() {
         if(shouldWait) {
             return false;
         }
-        Frame* f = frames + currentFrame;
-        VK_CHECK_TRUE(
-            vkWaitForFences(base, 1, f->inFlightFence, true, UINT64_MAX));
-        VK_CHECK_TRUE(vkResetFences(base, 1, f->inFlightFence));
-
-        uint32_t imageIndex = 0;
-        VK_CHECK_TRUE(vkAcquireNextImageKHR(
-            base, swapchain, UINT64_MAX, f->imageAvailableSemaphore,
-            VK_NULL_HANDLE, &imageIndex));
-
-        vkResetCommandBuffer(f->commandBuffer, 0);
-        fillCommandBuffer(f->commandBuffer, imageIndex);
-
-        VkSemaphore waitSemaphores[] = {f->imageAvailableSemaphore};
-        VkPipelineStageFlags waitStages[] = {
-            VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
-        VkSemaphore signalSemaphores[] = {f->renderFinishedSemaphore};
-        VkSubmitInfo info = {
-            .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
-            .waitSemaphoreCount = 1,
-            .pWaitSemaphores = waitSemaphores,
-            .pWaitDstStageMask = waitStages,
-            .commandBufferCount = 1,
-            .pCommandBuffers = f->commandBuffer,
-            .signalSemaphoreCount = 1,
-            .pSignalSemaphores = signalSemaphores};
-        VK_CHECK_TRUE(vkQueueSubmit(graphicsQueue, 1, &info, f->inFlightFence));
-
-        VkPresentInfoKHR presentInfo = {
-            .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
-            .waitSemaphoreCount = 1,
-            .pWaitSemaphores = signalSemaphores,
-            .swapchainCount = 1,
-            .pSwapchains = swapchain,
-            .pImageIndices = &imageIndex};
-
-        VK_CHECK_TRUE(vkQueuePresentKHR(presentQueue, &presentInfo));
-        currentFrame = (currentFrame + 1) % MAX_FRAMES;
-        return false;
-    }
+        Frame& f = frames[currentFrame];
+        f.inFlightFence.waitFor();
+        f.inFlightFence.reset();
 
-    void destroy() {
-        if(static_cast<VkDevice>(base) != VK_NULL_HANDLE) {
-            vkDeviceWaitIdle(base);
-            vkDestroyCommandPool(base, commandPool, nullptr);
-            vkDestroyPipeline(base, pipeline, nullptr);
-            vkDestroyRenderPass(base, renderPass, nullptr);
+        u32 imageIndex = 0;
+        if(swapchain.nextImage(imageIndex, f.imageAvailableSemaphore)) {
+            return true;
         }
+        f.commandBuffer.reset();
+        fillCommandBuffer(f.commandBuffer, imageIndex);
+
+        graphicsQueue.submit(
+            f.commandBuffer, f.inFlightFence, f.imageAvailableSemaphore,
+            f.renderFinishedSemaphore,
+            VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT);
+
+        presentQueue.present(f.renderFinishedSemaphore, swapchain, imageIndex);
+        currentFrame = (currentFrame + 1) % frames.getLength();
+        return false;
     }
 };
 
@@ -643,7 +687,7 @@ void Vulkan::render() {
     }
 }
 
-void Vulkan::destroy(void) {
-    dummy->destroy();
+void Vulkan::destroy() {
+    dummy->base.waitForIdle();
     dummy = nullptr;
 }