commit db90bb2c31f5fdd2fb16a3c7a85b177531f29fa4 Author: matthias@arch Date: Fri Oct 7 23:30:44 2022 +0200 initial commit diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..3e986e3 --- /dev/null +++ b/.clangd @@ -0,0 +1,3 @@ +CompileFlags: # Tweak the parse settings + Add: [-std=c++2a, -Wall, -I/usr/include/freetype2, -I.., -I.] +# https://clangd.llvm.org/config diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..276397c --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +build +fonts +models +old +shaders +stb_image.h +textures +tiny_obj_loader.h +vk_enum_string.h +vulkan.log +vulkan_specs.html +vulkan_test +Vulkan Tutorial en.pdf + diff --git a/.vimspector.json b/.vimspector.json new file mode 100644 index 0000000..85ea0e3 --- /dev/null +++ b/.vimspector.json @@ -0,0 +1,22 @@ +{ + "configurations": { + "gze": { + "adapter": "vscode-cpptools", + "configuration": { + "name": "vulkan (cpp)", + "type": "cppdbg", + "request": "launch", + "externalConsole": true, + "logging": { + "engineLogging": true + }, + "stopOnEntry": true, + "stopAtEntry": true, + "debugOptions": [], + "MIMode": "gdb", + "cwd": "~/c++/vulkan", + "program": "vulkan_test" + } + } + } +} diff --git a/Makefile b/Makefile new file mode 100755 index 0000000..ffbda2c --- /dev/null +++ b/Makefile @@ -0,0 +1,65 @@ +CXX = /usr/bin/g++ +CXXFLAGS = -std=c++20 -MMD -MP -Wextra #-O3: optimierungsstufe 3, -MMD .d files, -MP leeres Target, Wextra alle Warnungen +# CXXFLAGS += -ftemplate-backtrace-limit=4 #-fno-pretty-templates +# most stuff glfw deps +LDFLAGS = -lglfw -lvulkan -ldl -lpthread -lX11 -lXxf86vm -lXrandr -lXi -lgzutil -lfreetype +IFLAGS = -I/usr/include/freetype2 + +OBJECT_DIR = build +EXEC = vulkan_test + +SRC = $(wildcard *.cpp) +OBJECTS = $($(notdir SRC):%.cpp=$(OBJECT_DIR)/%.o) +DEPENDS = ${OBJECTS:.o=.d} + +CXXFLAGS += $(IFLAGS) + + +default: $(EXEC) + echo $(OBJECTS) + +release: CXXFLAGS += -O3 +release : default + +# rule for the executable +$(EXEC): $(OBJECTS) + $(CXX) $(OBJECTS) -o $@ $(CXXFLAGS) $(LDFLAGS) +# include the makefiles generated by the -M flag +-include $(DEPENDS) + +# rule for all ../build/*.o files +$(OBJECT_DIR)/%.o: %.cpp $(OBJECT_DIR)/.dirstamp + $(CXX) -c $< -o $@ $(CXXFLAGS) $(LDFLAGS) + +# if build dir does not exist, create and put stamp inside +$(OBJECT_DIR)/.dirstamp: + mkdir -p $(OBJECT_DIR) + touch $@ + +# +# Extras Options +# +# with debug flags +debug: CXXFLAGS += -g # -DDEBUG +debug: default + +# make with debug flags and run afterwards +run: CXXFLAGS += -g +run: default + $(CXX) $(OBJECTS) -o $(EXEC) $(CXXFLAGS) $(LDFLAGS) + ./$(EXEC) + +# with debug flags and run gnu debugger +gdb: CXXFLAGS += -g +gdb: default + gdb $(EXEC) -ex "layout src" + +# build pch +pch: + $(CXX) pch.hpp -std=c++20 -O3 -g $(IFLAGS) + +# remove all object and dependecy files +clean: + # -rm -f $(OBJECT_DIR)/*.o + # -rm -f $(OBJECT_DIR)/*.d + -rm -r $(OBJECT_DIR) diff --git a/compile.sh b/compile.sh new file mode 100755 index 0000000..5a1f797 --- /dev/null +++ b/compile.sh @@ -0,0 +1,12 @@ +#!/bin/sh +WDIR=$HOME/c++/vulkan + +mkdir -p $WDIR/shaders + +glslc $WDIR/shader.vert -o $WDIR/shaders/vert.spv +glslc $WDIR/shader.frag -o $WDIR/shaders/frag.spv + +glslc $WDIR/shader2D.vert -o $WDIR/shaders/vert2D.spv +glslc $WDIR/shader2D.frag -o $WDIR/shaders/frag2D.spv + + diff --git a/exceptions.cpp b/exceptions.cpp new file mode 100644 index 0000000..828fb3d --- /dev/null +++ b/exceptions.cpp @@ -0,0 +1,21 @@ +#include "exceptions.hpp" + +#include "vk_enum_string.h" + +namespace gz { + VkException getVkException(VkResult result, std::string&& what, std::string&& functionName) { + std::string whatStr; + if (!functionName.empty()) { + whatStr += "Error in function: " + functionName + ": "; + } + else { + whatStr += "Error: "; + } + if (!what.empty()) { + whatStr += what + ": "; + } + whatStr += "VkResult="; + whatStr += STR_VK_RESULT(result); + return VkException(whatStr); + } +} diff --git a/exceptions.hpp b/exceptions.hpp new file mode 100644 index 0000000..c8d8584 --- /dev/null +++ b/exceptions.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include + +#include + +#include +#include + +namespace gz { + /** + * @brief An error that has something to with vulkan + */ + class VkException : public Exception { + public: + VkException(const std::string& what) : Exception(what) {}; + VkException(const std::string&& what, const std::string&& functionName) + : Exception(std::move(what), std::move(functionName)) {}; + }; + + /** + * @brief A user error that has something to do with vulkan + * @details + * This error comes may come from bad function parameters + */ + class VkUserError : public VkException { + public: + VkUserError(const std::string&& what, const std::string&& functionName) + : VkException(std::move(what), std::move(functionName)) {}; + }; + + /** + * @brief Return a VkException with a formatted string + */ + VkException getVkException(VkResult result, std::string&& what="", std::string&& functionName=""); +} diff --git a/font.cpp b/font.cpp new file mode 100644 index 0000000..5b4a8d0 --- /dev/null +++ b/font.cpp @@ -0,0 +1,28 @@ +#include "font.hpp" + +#include + +namespace gz { + + FontManager::FontManager(const std::string&& fontDir) + : fontDir(fontDir) { + FT_Error error = FT_Init_FreeType(&freetype); + if (error != FT_Err_Ok) { + throw Exception("Could not load freetype library", "FontManager"); + } + + } + + + void FontManager::loadFont(const std::string& font) { + FT_Error error = FT_New_Face(freetype, (fontDir + "/" + font).c_str(), 0, &faces[font]); + if (error != FT_Err_Ok) { + throw Exception("Could not load font.", "FontManager::loadFont"); + } + FT_Set_Pixel_Sizes(faces[font], 0, 48); + if (FT_Load_Char(faces[font], 'X', FT_LOAD_RENDER)) { + throw Exception("Could not load char.", "FontManager::loadFont"); + } + } + +} diff --git a/font.hpp b/font.hpp new file mode 100644 index 0000000..5dd7d88 --- /dev/null +++ b/font.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +#include +#include FT_FREETYPE_H + +namespace gz { + class FontManager { + public: + FontManager(const std::string&& fontDir); + void loadFont(const std::string& font); + const std::unordered_map& getFaces() const { return faces; } + private: + FT_Library freetype; + std::string fontDir; + std::unordered_map faces; + }; +} diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..64a292a --- /dev/null +++ b/main.cpp @@ -0,0 +1,60 @@ +#include "main.hpp" + +#include "vulkan_instance.hpp" +#include "renderer2D.hpp" +#include + +namespace gz::vk { + int mainLoop() { + gz::SettingsManagerCreateInfo smCI{}; + smCI.filepath = gz::vk::CONFIG_FILE; + smCI.readFileOnCreation = true; + smCI.writeFileOnExit = true; + smCI.initialValues = gz::vk::INITIAL_SETTINGS; + smCI.throwExceptionWhenNewValueNotAllowed = true; + + VulkanInstance vulkanInstance(smCI); + vulkanInstance.init(); + Renderer2D r2D(vulkanInstance); + + Rectangle rect1( 90, 91, 92, 93, { 1.0f, 0.9f, 0.8f}); + Rectangle rect2(190, 191, 192, 193, { 0.7f, 0.6f, 0.5f}); + Rectangle rect3(420, 64, 512, 64, { 0.0f, 1.0f, 0.0f}); + Rectangle rect4( 32, 120, 400, 400, { 0.0f, 0.0f, 1.0f}); + Rectangle rect5( -600, -600, 800, 400, { 1.0f, 0.0f, 0.0f}); + r2D.drawShape(rect1); + r2D.drawShape(rect2); + r2D.drawShape(rect3); + r2D.drawShape(rect4); + r2D.drawShape(rect5); + r2D.fillVertexBufferWithShapes(); + r2D.fillIndexBufferWithShapes(); + /* gz::FontManager fm("fonts"); */ + /* fm.loadFont("menu.ttf"); */ + /* /1* fm.getFaces().at("menu.ttf"). *1/ */ + try { + uint32_t imageIndex; + while (! glfwWindowShouldClose(vulkanInstance.window)) { + glfwPollEvents(); + imageIndex = vulkanInstance.beginFrameDraw(); + r2D.drawFrame(imageIndex); + vulkanInstance.endFrameDraw(imageIndex); + auto SLEEP_TIME = std::chrono::milliseconds(1000 / vulkanInstance.settings.get("framerate")); + std::this_thread::sleep_for(SLEEP_TIME); + } + r2D.cleanup(); + vulkanInstance.cleanup(); + } + catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return 1; + } + + return 0; + } +} + + +int main() { + return gz::vk::mainLoop(); +} diff --git a/main.hpp b/main.hpp new file mode 100644 index 0000000..d914d89 --- /dev/null +++ b/main.hpp @@ -0,0 +1,19 @@ +#pragma once + + +namespace gz::vk { + + + + + + + + + + + + + + +} // namespace gz::vk diff --git a/renderer.cpp b/renderer.cpp new file mode 100644 index 0000000..ec38d17 --- /dev/null +++ b/renderer.cpp @@ -0,0 +1,17 @@ +#include "renderer.hpp" +#include + +namespace gz::vk { + void Renderer::cleanup_(){ + vkFreeCommandBuffers(vk.device, vk.commandPoolGraphics, static_cast(commandBuffers.size()), commandBuffers.data()); + + vkDestroyBuffer(vk.device, indexBuffer, nullptr); + vkFreeMemory(vk.device, indexBufferMemory, nullptr); + + vkDestroyBuffer(vk.device, vertexBuffer, nullptr); + vkFreeMemory(vk.device, vertexBufferMemory, nullptr); + + + } + +} diff --git a/renderer.hpp b/renderer.hpp new file mode 100644 index 0000000..d61ad3d --- /dev/null +++ b/renderer.hpp @@ -0,0 +1,19 @@ +#include "vulkan_instance.hpp" + +namespace gz::vk { + class Renderer { + public: + Renderer(VulkanInstance& instance) : vk(instance) {}; + protected: + void cleanup_(); + VulkanInstance& vk; + std::vector commandBuffers; + /// On device local memory + VkBuffer vertexBuffer; + VkDeviceMemory vertexBufferMemory; + VkDeviceSize vertexBufferSize; + VkBuffer indexBuffer; + VkDeviceMemory indexBufferMemory; + VkDeviceSize indexBufferSize; + }; // class RendererBase +} // namespace gz::vk diff --git a/renderer2D.cpp b/renderer2D.cpp new file mode 100644 index 0000000..a5fd63d --- /dev/null +++ b/renderer2D.cpp @@ -0,0 +1,247 @@ +#include "renderer2D.hpp" + +#include "exceptions.hpp" +#include "vk_enum_string.h" + +#include +#include + + +namespace gz::vk { + +Renderer2D::Renderer2D(VulkanInstance& instance) : + Renderer(instance), + rLog("renderer2D.log", true, false, "2D-Renderer", Color::BMAGENTA, true, 100) { + vk.createCommandBuffers(commandBuffers); + const size_t vertexCount = 500; + const size_t indexCount = 1000; + vk.createVertexBuffer(vertexCount, vertexBuffer, vertexBufferMemory, vertexBufferSize); + vk.createIndexBuffer(indexCount, indexBuffer, indexBufferMemory, indexBufferSize); + createRenderPass(); + createImages(); + renderPassID = vk.createFramebuffers(imageViews, renderPass); + rLog("Created Renderer2D"); +} + + +void Renderer2D::cleanup() { + cleanup_(); +} + +void Renderer2D::createImages() { + images.resize(vk.scImages.size()); + imageMemory.resize(vk.scImages.size()); + imageViews.resize(vk.scImages.size()); + VkImageUsageFlags usage= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + for (size_t i = 0; i < images.size(); i++) { + vk.createImage(vk.scExtent.width, vk.scExtent.height, vk.scImageFormat, VK_IMAGE_TILING_OPTIMAL, usage, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, images[i], imageMemory[i]); + vk.createImageView(vk.scImageFormat, images[i], imageViews[i], VK_IMAGE_ASPECT_COLOR_BIT); + } + +} + +void Renderer2D::createRenderPass() { + VkAttachmentDescription colorBlendAttachment{}; + colorBlendAttachment.format = vk.scImageFormat; + colorBlendAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorBlendAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorBlendAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorBlendAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorBlendAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorBlendAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorBlendAttachment.finalLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + + VkAttachmentReference colorAttachmentRef{}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + /* VkAttachmentDescription depthAttachment{}; */ + /* depthAttachment.format = findDepthFormat(); */ + /* depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT; */ + /* depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; */ + /* depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; */ + /* depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; */ + /* depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; */ + /* depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; */ + /* depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; */ + + /* VkAttachmentReference depthAttachmentRef{}; */ + /* depthAttachmentRef.attachment = 1; */ + /* depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; */ + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + /* subpass.pDepthStencilAttachment = &depthAttachmentRef; */ + + VkSubpassDependency dependency{}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.srcAccessMask = 0; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + /* VkSubpassDependency dependency{}; */ + /* dependency.srcSubpass = VK_SUBPASS_EXTERNAL; */ + /* dependency.dstSubpass = 0; */ + /* dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; */ + /* dependency.srcAccessMask = 0; */ + /* dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; */ + /* dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; */ + + /* std::array attachments = { colorBlendAttachment, depthAttachment }; */ + std::vector attachments = { colorBlendAttachment }; + VkRenderPassCreateInfo renderPassCI{}; + renderPassCI.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassCI.attachmentCount = static_cast(attachments.size()); + renderPassCI.pAttachments = attachments.data(); + renderPassCI.subpassCount = 1; + renderPassCI.pSubpasses = &subpass; + renderPassCI.dependencyCount = 1; + renderPassCI.pDependencies = &dependency; + /* renderPassCI.dependencyCount = 0; */ + /* renderPassCI.pDependencies = nullptr; */ + /* renderPassCI.correlatedViewMaskCount = 0; */ + /* renderPassCI.pCorrelatedViewMasks = nullptr; */ + VkResult result = vkCreateRenderPass(vk.device, &renderPassCI, nullptr, &renderPass); + if (result != VK_SUCCESS) { + throw getVkException(result, "Could not create render pass", "Renderer2D::createRenderPass"); + } + rLog("createRenderPass: Created render pass."); + +} + + +void Renderer2D::recordCommandBuffer(uint32_t imageIndex, uint32_t currentFrame) { + VkCommandBufferBeginInfo commandBufferBI{}; + commandBufferBI.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + /* commandBufferBI.flags = 0; */ + /* commandBufferBI.pInheritanceInfo = nullptr; */ + VkResult result = vkBeginCommandBuffer(commandBuffers[currentFrame], &commandBufferBI); + if (result != VK_SUCCESS) { + throw getVkException(result, "Failed to begin 2D command buffer", "Renderer2D::recordCommandBuffer"); + } + + VkRenderPassBeginInfo renderPassBI{}; + renderPassBI.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassBI.renderPass = renderPass; + renderPassBI.framebuffer = vk.getFramebuffers(renderPassID)[imageIndex]; + renderPassBI.renderArea.offset = { 0, 0 }; + renderPassBI.renderArea.extent = vk.scExtent; + // clear + std::array clearValues{}; + clearValues[0].color = {{1.0f, 0.0f, 0.0f, 1.0f}}; + clearValues[1].depthStencil = {1.0f, 0}; + renderPassBI.clearValueCount = static_cast(clearValues.size()); + renderPassBI.pClearValues = clearValues.data(); + + vkCmdBeginRenderPass(commandBuffers[currentFrame], &renderPassBI, VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline(commandBuffers[currentFrame], VK_PIPELINE_BIND_POINT_GRAPHICS, vk.pipelines[PL_2D].pipeline); + VkBuffer vertexBuffers[] = { vertexBuffer }; + VkDeviceSize offsets[] = {0}; + uint32_t bindingCount = 1; + vkCmdBindVertexBuffers(commandBuffers[currentFrame], BINDING, bindingCount, vertexBuffers, offsets); + // TODO use correct index type! + vkCmdBindIndexBuffer(commandBuffers[currentFrame], indexBuffer, NO_OFFSET, VK_INDEX_TYPE_UINT32); + + /* uint32_t descriptorCount = 1; */ + /* uint32_t firstSet = 0; */ + /* uint32_t dynamicOffsetCount = 0; */ + /* uint32_t* dynamicOffsets = nullptr; */ + /* vkCmdBindDescriptorSets(commandBuffers[currentFrame], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines[PL_2D].layout, firstSet, descriptorCount, &descriptorSets[currentFrame], dynamicOffsetCount, dynamicOffsets); */ + + int instanceCount = 1; + int firstIndex = 0; + int firstInstance = 0; + vkCmdDrawIndexed(commandBuffers[currentFrame], static_cast(shapesIndicesCount), instanceCount, firstIndex, NO_OFFSET, firstInstance); + + vkCmdEndRenderPass(commandBuffers[currentFrame]); + vk.copyImageToImage(commandBuffers[currentFrame], images[imageIndex], vk.scImages[imageIndex], vk.scExtent.width, vk.scExtent.height); + result = vkEndCommandBuffer(commandBuffers[currentFrame]); + if (result != VK_SUCCESS) { + rLog.error("Failed to record 2D - command buffer", "VkResult:", STR_VK_RESULT(result)); + throw getVkException(result, "Failed to record 2D - command buffer", "Renderer2D::recordCommandBuffer"); + } + vk.commandBuffersToSubmitThisFrame.push_back(commandBuffers[currentFrame]); +} + + +void Renderer2D::fillVertexBufferWithShapes() { + rLog("fillVertexBufferWithShapes"); + if (vertexBufferSize < shapesVerticesCount * sizeof(Vertex2D)) { + throw VkException("vertex buffer too small! vertexBufferSize: " + std::to_string(vertexBufferSize) + ", required size: " + std::to_string(shapesVerticesCount), "fillVertexBufferWithShapes"); + } + + // create staging buffer + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + vk.createBuffer(vertexBufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + // fill staging buffer + void* data; + vkMapMemory(vk.device, stagingBufferMemory, NO_OFFSET, vertexBufferSize, NO_FLAGS, &data); + Vertex2D* vdata = reinterpret_cast(data); + size_t offset = 0; + for (auto it = shapes.begin(); it != shapes.end(); it++) { + rLog("fillVertexBufferWithShapes: copying vertex buffer nr", it - shapes.begin(), "-", it->getVertices(), "to address:", long(vdata + offset), " offset:", offset); + memcpy(vdata+offset, it->getVertices().data(), it->getVertices().size() * sizeof(Vertex2D)); + offset += it->getVertices().size(); + } + vkUnmapMemory(vk.device, stagingBufferMemory); + // fill vertex buffer + vk.copyBuffer(stagingBuffer, vertexBuffer, vertexBufferSize); + vkDestroyBuffer(vk.device, stagingBuffer, nullptr); + vkFreeMemory(vk.device, stagingBufferMemory, nullptr); +} +void Renderer2D::fillIndexBufferWithShapes() { + rLog("fillIndexBufferWithShapes"); + if (indexBufferSize < shapesIndicesCount * sizeof(uint32_t)) { + throw VkException("index buffer too small! indexBufferSize: " + std::to_string(vertexBufferSize) + ", required size: " + std::to_string(shapesVerticesCount), "fillVertexBufferWithShapes"); + } + + // create staging buffer + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + vk.createBuffer(indexBufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + // fill staging buffer + void* data; + vkMapMemory(vk.device, stagingBufferMemory, NO_OFFSET, indexBufferSize, NO_FLAGS, &data); + uint32_t* idata = reinterpret_cast(data); + size_t offset = 0; + for (auto it = shapes.begin(); it != shapes.end(); it++) { + rLog("fillIndexBufferWithShapes: copying index buffer nr", it - shapes.begin(), "-", it->getIndices(), "to address:", long(idata + offset), " offset:", offset); + memcpy(idata+offset, it->getIndices().data(), it->getIndices().size() * sizeof(uint32_t)); + offset += it->getIndices().size(); + } + rLog("fillIndexBufferWithShapes: indices count:", shapesIndicesCount); + vkUnmapMemory(vk.device, stagingBufferMemory); + + // fill index buffer + vk.copyBuffer(stagingBuffer, indexBuffer, indexBufferSize); + vkDestroyBuffer(vk.device, stagingBuffer, nullptr); + vkFreeMemory(vk.device, stagingBufferMemory, nullptr); + +} + + +void Renderer2D::drawShape(const Shape& shape) { + shapes.emplace_back(shape); + // make indices valid + shapes.rbegin()->setIndexOffset(shapesVerticesCount); + shapes.rbegin()->setNormalize(vk.scExtent.width, vk.scExtent.height); + shapesVerticesCount += shape.getVertices().size(); + shapesIndicesCount += shape.getIndices().size(); +} + + +void Renderer2D::drawFrame(uint32_t imageIndex) { + vkResetCommandBuffer(commandBuffers[vk.currentFrame], NO_FLAGS); + recordCommandBuffer(imageIndex, vk.currentFrame); + +} + + +} // namespace gz::vk diff --git a/renderer2D.hpp b/renderer2D.hpp new file mode 100644 index 0000000..1fdf3c5 --- /dev/null +++ b/renderer2D.hpp @@ -0,0 +1,62 @@ +#include "renderer.hpp" + +namespace gz::vk { + class Renderer2D : public Renderer { + public: + Renderer2D(VulkanInstance& instance); + void drawShape(const Shape& shape); + /** + * @brief Copies the vertices from shapes into the vertex buffer, using a staging buffer + */ + void fillVertexBufferWithShapes(); + void fillIndexBufferWithShapes(); + void drawFrame(uint32_t imageIndex); + void cleanup(); + /** + * @brief Create a render pass + * @details + * Attachments: + * - color blend: + * - loadOp = VK_ATTACHMENT_LOAD_OP_LOAD (not clear!) + * - storeOp = VK_ATTACHMENT_STORE_OP_STORE + * - initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL + * - finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL + * - stencil load/store = dont care + */ + void createRenderPass(); + + private: + std::vector shapes; + size_t shapesVerticesCount = 0; + size_t shapesIndicesCount = 0; + /** + * @brief Render everything + * @details + * As of now, this does (in the same command buffer from graphicsPool) + * -# begin render pass + * - image layout: VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL + * - clear image + * -# bind 2d pipeline, vertex and index buffer + * -# draw indexed: draw the shapes from shapes vector + * -# end render pass + * - image layout: VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL + * -# copy image to swapChain image with same imageIndex + * + */ + void recordCommandBuffer(uint32_t imageIndex, uint32_t currentFrame); + // IMAGE + std::vector images; + std::vector imageMemory; + std::vector imageViews; + /** + * @brief Creates the images and imageViews with the format of the VulkanInstance::swapChain images + */ + void createImages(); + VkRenderPass renderPass; + int renderPassID; + + Log rLog; + + }; + +} // namespace gz::vk diff --git a/renderer3D.cpp b/renderer3D.cpp new file mode 100644 index 0000000..9e9d4a6 --- /dev/null +++ b/renderer3D.cpp @@ -0,0 +1,105 @@ +#include "renderer3D.hpp" + +#include "exceptions.hpp" +#include "vk_enum_string.h" + +#include + +namespace gz::vk { + +Renderer3D::Renderer3D(VulkanInstance& instance) : + Renderer(instance), + rLog("renderer3D.log", true, false, "3D-Renderer", Color::BCYAN, true, 100) { + vk.createCommandBuffers(commandBuffers); + const size_t vertexCount = 500; + const size_t indexCount = 1000; + vk.createVertexBuffer(vertexCount, vertexBuffer, vertexBufferMemory, vertexBufferSize); + vk.createIndexBuffer(indexCount, indexBuffer, indexBufferMemory, indexBufferSize); + rLog("Created Renderer3D"); +} + +void Renderer3D::cleanup() { + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroyBuffer(vk.device, uniformBuffers[i], nullptr); + vkFreeMemory(vk.device, uniformBuffersMemory[i], nullptr); + } + cleanup_(); +} + + +void Renderer3D::updateUniformBuffer() { + static auto startTime = std::chrono::high_resolution_clock::now(); + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + // TODO use push constant instead of ubo + UniformBufferObject ubo{}; + /* ubo.model = glm::rotate(glm::mat4(1.0f), time * std::numbers::pi_v / 2, glm::vec3(0.0f, 0.0f, 1.0f)); */ + /* ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); */ + /* ubo.projection = glm::perspective(glm::radians(45.0f), static_cast(scExtent.width) / scExtent.height, 1.0f, 10.0f); */ + ubo.model = glm::mat4(1); + ubo.view = glm::mat4(1); + ubo.projection = glm::mat4(1); + /* ubo.projection[1][1] *= -1; // y coordinate inverted in opengl */ + void* data; + vkMapMemory(vk.device, uniformBuffersMemory[vk.currentFrame], NO_OFFSET, sizeof(ubo), NO_FLAGS, &data); + memcpy(data, &ubo, sizeof(ubo)); + vkUnmapMemory(vk.device, uniformBuffersMemory[vk.currentFrame]); +} + + +void Renderer3D::recordCommandBuffer(uint32_t imageIndex, uint32_t currentFrame) { + VkCommandBufferBeginInfo commandBufferBI{}; + commandBufferBI.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + /* commandBufferBI.flags = 0; */ + /* commandBufferBI.pInheritanceInfo = nullptr; */ + VkResult result = vkBeginCommandBuffer(commandBuffers[currentFrame], &commandBufferBI); + if (result != VK_SUCCESS) { + throw getVkException(result, "Failed to begin 3D - command buffer", "recordCommandBuffer"); + } + + VkRenderPassBeginInfo renderPassBI{}; + renderPassBI.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassBI.renderPass = vk.renderPassBegin; + // TODO + /* renderPassBI.framebuffer = vk.scFramebuffers[0][imageIndex]; */ + renderPassBI.renderArea.offset = { 0, 0 }; + renderPassBI.renderArea.extent = vk.scExtent; + std::array clearValues{}; + clearValues[0].color = {{0.0f, 0.0f, 0.0f, 1.0f}}; + clearValues[1].depthStencil = {1.0f, 0}; + renderPassBI.clearValueCount = static_cast(clearValues.size()); + renderPassBI.pClearValues = clearValues.data(); + + vkCmdBeginRenderPass(commandBuffers[currentFrame], &renderPassBI, VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline(commandBuffers[currentFrame], VK_PIPELINE_BIND_POINT_GRAPHICS, vk.pipelines[PL_3D].pipeline); + VkBuffer vertexBuffers[] = { vertexBuffer }; + VkDeviceSize offsets[] = {0}; + uint32_t bindingCount = 1; + vkCmdBindVertexBuffers(commandBuffers[currentFrame], BINDING, bindingCount, vertexBuffers, offsets); + // TODO use correct index type! + vkCmdBindIndexBuffer(commandBuffers[currentFrame], indexBuffer, NO_OFFSET, VK_INDEX_TYPE_UINT32); + + uint32_t descriptorCount = 1; + uint32_t firstSet = 0; + uint32_t dynamicOffsetCount = 0; + uint32_t* dynamicOffsets = nullptr; + vkCmdBindDescriptorSets(commandBuffers[currentFrame], VK_PIPELINE_BIND_POINT_GRAPHICS, vk.pipelines[PL_3D].layout, firstSet, descriptorCount, &vk.descriptorSets[currentFrame], dynamicOffsetCount, dynamicOffsets); + + int instanceCount = 1; + int firstIndex = 0; + int firstInstance = 0; + /* vkCmdDrawIndexed(commandBuffers[currentFrame], static_cast(shapesIndicesCount), instanceCount, firstIndex, NO_OFFSET, firstInstance); */ + + vkCmdEndRenderPass(commandBuffers[currentFrame]); + result = vkEndCommandBuffer(commandBuffers[currentFrame]); + if (result != VK_SUCCESS) { + rLog.error("Failed to record command buffer", "VkResult:", STR_VK_RESULT(result)); + throw getVkException(result, "Failed to record command buffer"); + } +} + + + +} // namespace gz::vk diff --git a/renderer3D.hpp b/renderer3D.hpp new file mode 100644 index 0000000..46c2f75 --- /dev/null +++ b/renderer3D.hpp @@ -0,0 +1,50 @@ +#include "renderer.hpp" + +namespace gz::vk { + struct UniformBufferObject { + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 projection; + }; + + + const std::vector vertices2 = { + {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 1.0f}, {0.0f, 0.0f}}, + {{ 0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, + {{ 0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, + {{-0.5f, 0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}}, + + {{-0.5f, -0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, + {{ 0.5f, -0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, + {{ 0.5f, 0.5f, -0.5f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, + {{-0.5f, 0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}}, + + }; + /* const std::vector vertices = { */ + /* {{-1.0f, -1.0f}, {0.0f, 0.0f, 1.0f}}, */ + /* {{0.5f, 0.5f}, {0.0, 1.0f, 1.0f}}, */ + /* {{-1.0, 0.0f}, {1.0f, 0.0f, 0.0f}}, */ + /* {{1.0f, 1.0f}, {1.0f, 0.0f, 0.0f}}, */ + /* {{-0.5f, -0.5f}, {0.0, 1.0f, 1.0f}}, */ + /* {{1.0, 0.0f}, {1.0f, 0.0f, 1.0f}}, */ + /* }; */ + + + const std::vector indices2 = { + 0, 1, 2, 2, 3, 0, + 4, 5, 6, 6, 7, 4 + }; + + class Renderer3D : public Renderer { + public: + Renderer3D(VulkanInstance& instance); + void cleanup(); + protected: + void recordCommandBuffer(uint32_t imageIndex, uint32_t currentFrame); + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + void updateUniformBuffer(); + Log rLog; + }; + +} diff --git a/shader.frag b/shader.frag new file mode 100644 index 0000000..6bf4554 --- /dev/null +++ b/shader.frag @@ -0,0 +1,16 @@ +#version 450 +#extension GL_EXT_debug_printf : enable + +layout(binding = 1) uniform sampler2D textureSampler; + +layout(location = 0) in vec3 fragColor; +layout(location = 1) in vec2 fragTextureCoordinate; + +layout(location = 0) out vec4 outColor; + +void main() { + /* outColor = vec4(fragColor, 1.0); */ + /* debugPrintfEXT("outColor %v3f", fragColor); */ + outColor = vec4(fragTextureCoordinate, 0.0, 1.0); + /* outColor = vec4(fragColor * texture(textureSampler, fragTextureCoordinate).rgb, 1.0); */ +} diff --git a/shader.vert b/shader.vert new file mode 100644 index 0000000..dd5c8bc --- /dev/null +++ b/shader.vert @@ -0,0 +1,25 @@ +#version 450 +#extension GL_EXT_debug_printf : enable + +layout(binding = 0) uniform UniformBufferObject { + mat4 model; + mat4 view; + mat4 proj; +} ubo; + +layout(location = 0) in vec3 inPosition; +layout(location = 1) in vec3 inColor; +layout(location = 2) in vec2 inTextureCoordinate; + +layout(location = 0) out vec3 fragColor; +layout(location = 1) out vec2 fragTextureCoordinate; + + +void main() { + gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 1.0); + /* gl_Position = vec4(inPosition, 1.0); */ + + debugPrintfEXT("inPosition %v3f, inColor %v3f", inPosition, inColor); + fragColor = inColor; + fragTextureCoordinate = inTextureCoordinate; +} diff --git a/shader2D.frag b/shader2D.frag new file mode 100644 index 0000000..ab8245a --- /dev/null +++ b/shader2D.frag @@ -0,0 +1,16 @@ +#version 450 +#extension GL_EXT_debug_printf : enable + +/* layout(binding = 1) uniform sampler2D textureSampler; */ + +layout(location = 0) in vec3 fragColor; +layout(location = 1) in vec2 fragTextureCoordinate; + +layout(location = 0) out vec4 outColor; + +void main() { + outColor = vec4(fragColor, 1.0); + /* debugPrintfEXT("outColor %v3f", fragColor); */ + /* outColor = vec4(fragTextureCoordinate, 0.0, 1.0); */ + /* outColor = vec4(fragColor * texture(textureSampler, fragTextureCoordinate).rgb, 1.0); */ +} diff --git a/shader2D.vert b/shader2D.vert new file mode 100644 index 0000000..eb7ed66 --- /dev/null +++ b/shader2D.vert @@ -0,0 +1,25 @@ +#version 450 +#extension GL_EXT_debug_printf : enable + +/* layout(binding = 0) uniform UniformBufferObject { */ +/* mat4 model; */ +/* mat4 view; */ +/* mat4 proj; */ +/* } ubo; */ + +layout(location = 0) in vec2 inPosition; +layout(location = 1) in vec3 inColor; +layout(location = 2) in vec2 inTextureCoordinate; + +layout(location = 0) out vec3 fragColor; +layout(location = 1) out vec2 fragTextureCoordinate; + + +void main() { + /* gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0); */ + gl_Position = vec4(inPosition, 0.0, 1.0); + + debugPrintfEXT("inPosition %v2f, inColor %v3f", inPosition, inColor); + fragColor = inColor; + fragTextureCoordinate = inTextureCoordinate; +} diff --git a/shape.cpp b/shape.cpp new file mode 100644 index 0000000..319a392 --- /dev/null +++ b/shape.cpp @@ -0,0 +1,34 @@ +#include "shape.hpp" + +namespace gz::vk { + Rectangle::Rectangle(float top, float left, uint32_t width, uint32_t height, glm::vec3 color) + : top(top), left(left), width(width), height(height), color(color) { + generateVertices(); + + } + + + void Rectangle::generateVertices() { + vertices.clear(); + vertices.emplace_back(Vertex2D{glm::vec2(top, left), color, glm::vec2(0, 0)}); + vertices.emplace_back(Vertex2D{glm::vec2(top, left + width), color, glm::vec2(1, 0)}); + vertices.emplace_back(Vertex2D{glm::vec2(top + height, left + width), color, glm::vec2(1, 1)}); + vertices.emplace_back(Vertex2D{glm::vec2(top + height, left), color, glm::vec2(0, 1)}); + indices = { 0, 1, 2, 2, 3, 0 }; + /* indices = { 2, 1, 0, 2, 3, 0 }; */ + } + + void Shape::setIndexOffset(uint32_t offset) { + for (size_t i = 0; i < indices.size(); i++) { + indices[i] += offset; + } + + } + + void Shape::setNormalize(float width, float height) { + for (size_t i = 0; i < vertices.size(); i++) { + vertices[i].pos.x /= width; + vertices[i].pos.y /= height; + } + } +} // namespaec gz::vk diff --git a/shape.hpp b/shape.hpp new file mode 100644 index 0000000..9eb139c --- /dev/null +++ b/shape.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include "vertex.hpp" +#include + +namespace gz::vk { + class Shape { + public: + const std::vector& getVertices() const { return vertices; } + const std::vector& getIndices() const { return indices; } + void setIndexOffset(uint32_t offset); + void setNormalize(float width, float height); + + protected: + std::vector vertices; + std::vector indices; + + }; + + class Rectangle : public Shape { + public: + Rectangle(float top, float left, uint32_t width, uint32_t height, glm::vec3 color); + private: + float top, left; + uint32_t width, height; + glm::vec3 color; + void generateVertices(); + }; +} // namespace gz::vk diff --git a/vertex.cpp b/vertex.cpp new file mode 100644 index 0000000..bdc489b --- /dev/null +++ b/vertex.cpp @@ -0,0 +1,73 @@ +#include "vertex.hpp" + +#include +#include + +#include + +namespace gz::vk { +const uint32_t BINDING = 0; + +template +VkFormat getVkFormat() { + if (std::same_as) { + return VK_FORMAT_R32G32B32_SFLOAT; + } + else if (std::same_as) { + return VK_FORMAT_R32G32_SFLOAT; + } +} + + +// +// 3D VERTEX +// + template + std::string Vertex::toString() const { + return "pos: " + gz::toString(pos) + ", color: " + gz::toString(color) + ", texCoords: "; // + gz::toString(texCoord); + }; + + + template + VkVertexInputBindingDescription Vertex::getBindingDescription() { + VkVertexInputBindingDescription bindingD{}; + bindingD.binding = BINDING; + bindingD.stride = sizeof(Vertex); + bindingD.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + return bindingD; + } + + template + std::array Vertex::getAttributeDescriptions() { + std::array inputAttributeD{}; + inputAttributeD[0].binding = BINDING; + inputAttributeD[0].location = 0; + inputAttributeD[0].format = getVkFormat(); + inputAttributeD[0].offset = offsetof(Vertex, pos); + inputAttributeD[1].binding = BINDING; + inputAttributeD[1].location = 1; + inputAttributeD[1].format = VK_FORMAT_R32G32B32_SFLOAT; + inputAttributeD[1].offset = offsetof(Vertex, color); + inputAttributeD[2].binding = BINDING; + inputAttributeD[2].location = 2; + inputAttributeD[2].format = VK_FORMAT_R32G32_SFLOAT; + inputAttributeD[2].offset = offsetof(Vertex, texCoord); + + return inputAttributeD; + } + + template + bool Vertex::operator==(const Vertex& other) const { + return pos == other.pos and color == other.color and texCoord == other.texCoord; + } + + template std::string Vertex::toString() const; + template std::string Vertex::toString() const; + template VkVertexInputBindingDescription Vertex::getBindingDescription(); + template VkVertexInputBindingDescription Vertex::getBindingDescription(); + template std::array Vertex::getAttributeDescriptions(); + template std::array Vertex::getAttributeDescriptions(); + template bool Vertex::operator==(const Vertex& other) const; + template bool Vertex::operator==(const Vertex& other) const; + +} // namespace gz::vk diff --git a/vertex.hpp b/vertex.hpp new file mode 100644 index 0000000..99ccd50 --- /dev/null +++ b/vertex.hpp @@ -0,0 +1,59 @@ +#pragma once + +/* #include */ +#include +#include + +#include +#include +/* #include */ + +struct VkVertexInputBindingDescription; +struct VkVertexInputAttributeDescription; + +namespace gz::vk { + template + concept VertexType = requires { + requires std::same_as; + requires std::ranges::forward_range; + requires std::same_as, VkVertexInputAttributeDescription>; + }; + + template + concept GLM_vec2_or_3 = std::same_as or std::same_as; + + // + // VERTEX + // + /** + * @brief Base for 2D and 3D vertices with texture and/or color + */ + template + struct Vertex { + PosVec pos; + glm::vec3 color; + glm::vec2 texCoord; + std::string toString() const; + bool operator==(const Vertex& other) const; + static VkVertexInputBindingDescription getBindingDescription(); + static std::array getAttributeDescriptions(); + }; // struct Vertex + using Vertex3D = Vertex; + using Vertex2D = Vertex; +} // namespace gz::vk + + + +// +// HASHES +// +namespace std { + template + struct hash> { + size_t operator()(gz::vk::Vertex const& vertex) const { + return ((hash()(vertex.pos)) ^ + (hash()(vertex.color) << 1) >> 1 ) ^ + (hash()(vertex.texCoord) << 1); + } + }; +} diff --git a/vk_convert.cpp b/vk_convert.cpp new file mode 100644 index 0000000..ba4e83c --- /dev/null +++ b/vk_convert.cpp @@ -0,0 +1,37 @@ +#include "vk_convert.hpp" + +bool vkBool2Bool(const VkBool32& b) { + if (b == VK_TRUE) { + return true; + } + else { + return false; + } +}; + +VkBool32 bool2VkBool(const bool& b) { + if (b) { + return VK_TRUE; + } + else { + return VK_FALSE; + } +} + +std::string vkBool2String(const VkBool32& b) { + if (b == VK_TRUE) { + return "true"; + } + else { + return "false"; + } +}; + +VkBool32 string2VkBool(const std::string& s) { + if (s == "true") { + return VK_TRUE; + } + else { + return VK_FALSE; + } +} diff --git a/vk_convert.hpp b/vk_convert.hpp new file mode 100644 index 0000000..a7dd343 --- /dev/null +++ b/vk_convert.hpp @@ -0,0 +1,9 @@ +#include +#include + +VkBool32 bool2VkBool(const bool& b); +bool vkBool2Bool(const VkBool32& b); + +VkBool32 string2VkBool(const std::string& s); +std::string vkBool2String(const VkBool32& b); + diff --git a/vk_layer_settings.txt b/vk_layer_settings.txt new file mode 100644 index 0000000..b395f32 --- /dev/null +++ b/vk_layer_settings.txt @@ -0,0 +1,2 @@ +# https://github.com/KhronosGroup/Vulkan-ValidationLayers/blob/master/layers/vk_layer_settings.txt +khronos_validation.enables = VK_LAYER_ENABLES=VK_VALIDATION_FEATURE_ENABLE_BEST_PRACTICES_EXT;VALIDATION_CHECK_ENABLE_VENDOR_SPECIFIC_AMD diff --git a/vulkan.conf b/vulkan.conf new file mode 100644 index 0000000..ab5c247 --- /dev/null +++ b/vulkan.conf @@ -0,0 +1,4 @@ +# Written by writeKeyValueFile +max_anisotropy = 1 +anisotropy_enable = false +framerate = 60 diff --git a/vulkan_instance.cpp b/vulkan_instance.cpp new file mode 100644 index 0000000..9a9578c --- /dev/null +++ b/vulkan_instance.cpp @@ -0,0 +1,2200 @@ +#include "vulkan_instance.hpp" + +#include "exceptions.hpp" +#include "font.hpp" + +#include "vk_enum_string.h" +#include +#include +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" + +#define TINYOBJLOADER_IMPLEMENTATION +#include "tiny_obj_loader.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace gz::vk { + +using std::uint32_t; + +const unsigned int WIDTH = 600; +const unsigned int HEIGHT = 600; + +const std::vector extensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + VK_KHR_SHADER_NON_SEMANTIC_INFO_EXTENSION_NAME +}; +const std::vector instanceExtensions = { +}; + +using gz::Exception; +using gz::VkException; +using gz::VkUserError; +using gz::getVkException; + + +// +// DEBUG +// +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation", +}; +#ifdef NDEBUG + constexpr bool enableValidationLayers = false; +#else + constexpr bool enableValidationLayers = true; +#endif + +constexpr VkDebugUtilsMessageSeverityFlagsEXT debugMessageSevereties = + VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + +constexpr VkDebugUtilsMessageTypeFlagsEXT debugMessageTypes = + VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; +const std::vector validationFeaturesEnable { + VK_VALIDATION_FEATURE_ENABLE_DEBUG_PRINTF_EXT +}; +const VkValidationFeaturesEXT validationFeatures { + VK_STRUCTURE_TYPE_VALIDATION_FEATURES_EXT, // sType + nullptr, // pNext + static_cast(validationFeaturesEnable.size()), // enabled feature count + validationFeaturesEnable.data(), // enable features + 0, // disabled feature count + nullptr // disabled features +}; + +/* constexpr VkDebugUtilsMessageTypeFlagsEXT debugFlags = */ +/* VK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXT | */ +/* VK_VALIDATION_FEATURE_ENABLE_BEST_PRACTICES_EXT; */ + +constexpr gz::Color VULKAN_MESSAGE_PREFIX_COLOR = gz::Color::BBLUE; + +constexpr VkDebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCI { + VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, // sType + &validationFeatures, // pNext + /* nullptr, // pNext */ + NO_FLAGS, // flags + debugMessageSevereties, // messageSeverety + debugMessageTypes, // messageType + VulkanInstance::debugLog, // pfnUserCallback + nullptr, // pUserData + +}; + +// +// EXTENSIONS +// +bool checkRequiredExtensionsAvailable(const std::vector& requiredExtensions, const std::vector& availableExtensions) { + bool extensionAvailable; + for (const auto& requiredExtension : requiredExtensions) { + extensionAvailable = false; + for (const auto& extension : availableExtensions) { + if (strcmp(requiredExtension, extension.extensionName) == 0) { + extensionAvailable = true; + break; + } + } + if (!extensionAvailable) { + return false; + } + } + return true; +} + + +std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + std::vector requiredExtensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + requiredExtensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + return requiredExtensions; +} + + +// check if all extensions are supported by device +bool deviceExtensionsSupported(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + bool extensionAvailable; + for (const char* requiredExtension : extensions) { + extensionAvailable = false; + for (const auto& extensionProperties : availableExtensions) { + if (strcmp(requiredExtension, extensionProperties.extensionName) == 0) { + extensionAvailable = true; + break; + } + } + if (!extensionAvailable) { return false; } + } + return true; +} + +// check if validation layers are supported/installed +bool validationLayersSupported() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + bool layerAvailable; + for (const char* requiredLayer : validationLayers) { + layerAvailable = false; + for (const auto& layerProperties : availableLayers) { + if (strcmp(requiredLayer, layerProperties.layerName) == 0) { + layerAvailable = true; + break; + } + } + if (!layerAvailable) { return false; } + } + return true; +} + + +// +// App +// +void VulkanInstance::createInstance() { + + VkApplicationInfo appInfo; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pEngineName = "Glowzwiebel Engine"; + appInfo.pApplicationName = "Hallo Dreieck"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo instanceCI; + instanceCI.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + instanceCI.pApplicationInfo = &appInfo; + instanceCI.pNext = &debugUtilsMessengerCI; + instanceCI.flags = 0; + instanceCI.ppEnabledExtensionNames = instanceExtensions.data(); + instanceCI.enabledExtensionCount = instanceExtensions.size(); + + if (enableValidationLayers) { + if (!validationLayersSupported()) { + throw std::runtime_error("Validation layers enabled but not available."); + } + instanceCI.enabledLayerCount = static_cast(validationLayers.size()); + instanceCI.ppEnabledLayerNames = validationLayers.data(); + } + else { + instanceCI.enabledLayerCount = 0; + instanceCI.ppEnabledLayerNames = nullptr; + } + + std::vector requiredExtensions = getRequiredExtensions(); + instanceCI.enabledExtensionCount = static_cast(requiredExtensions.size()); + instanceCI.ppEnabledExtensionNames = requiredExtensions.data();; + + // log requiredExtensions + std::string message; + message.reserve(80); + message += "Required extensions ("; + message += std::to_string(instanceCI.enabledExtensionCount) + ')'; + for (uint32_t i = 0; i < requiredExtensions.size(); i++) { + message += "\n\t"; + message += std::to_string(i + 1) + ": " + requiredExtensions[i]; + } + vLog(message); + + VkResult result = vkCreateInstance(&instanceCI, nullptr, &instance); + if (result != VK_SUCCESS) { + vLog("Failed to create instance.", "VkResult:", STR_VK_RESULT(result)); + throw std::runtime_error("Failed to create instance."); + } + + uint32_t extensionCount = 0; + vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr); // get count + std::vector extensions(extensionCount); + vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data()); + + // log available extensions + message = "Available extensions ("; + message += std::to_string(extensionCount) + "):"; + for (uint32_t i = 0; i < extensions.size(); i++) { + message += "\n\t"; + message += std::to_string(i + 1) + ": " + extensions[i].extensionName; + } + vLog(message); + + if (!checkRequiredExtensionsAvailable(requiredExtensions, extensions)) { + throw std::runtime_error("Not all required extensions are available."); + } +} + + +gz::Log VulkanInstance::vLog("vulkan.log", false, true, "Vulkan", VULKAN_MESSAGE_PREFIX_COLOR); +VKAPI_ATTR VkBool32 VKAPI_CALL VulkanInstance::debugLog( + VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverety, + VkDebugUtilsMessageTypeFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, + void* pUserData) { + + gz::Color messageColor; + switch(messageSeverety) { + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT: + messageColor = gz::Color::RESET; + break; + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: + messageColor = gz::Color::WHITE; + break; + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: + messageColor = gz::Color::YELLOW; + break; + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: + messageColor = gz::Color::RED; + break; + default: + messageColor = gz::Color::RESET; + } + + std::string type; + gz::Color typeColor; + switch(messageType) { + case VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT: + type = "General"; + typeColor = gz::Color::GREEN; + break; + case VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT: + type = "Performance"; + typeColor = gz::Color::CYAN; + break; + case VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT: + type = "Validation"; + typeColor = gz::Color::MAGENTA; + break; + default: + type = "None"; + typeColor = gz::Color::WHITE; + } + size_t offset = 0; + if (strcmp(pCallbackData->pMessageIdName, "UNASSIGNED-DEBUG-PRINTF") == 0) { + type = "Shader"; + typeColor = gz::Color::YELLOW; + messageColor = gz::Color::RESET; + std::string_view message(pCallbackData->pMessage); + offset = message.find_first_of('|'); + if (offset == std::string::npos) { offset = 0; } + else { offset += 2; } // skip "| " + } + vLog.clog(typeColor, std::move(type), messageColor, pCallbackData->pMessage+offset); + return VK_FALSE; +} + + + + + +// +// DEVICE +// +void VulkanInstance::selectPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + vLog.error("Could not find any GPU."); + throw std::runtime_error("Could not find any GPU."); + } + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + // find best gpu + std::vector deviceScores; + for (const auto& device : devices) { + deviceScores.push_back(rateDevice(device)); + } + unsigned int bestGPUIndex = std::max_element(deviceScores.begin(), deviceScores.end()) - deviceScores.begin(); + if (deviceScores[bestGPUIndex] == 0) { + vLog.error("Could not find suitable GPU."); + throw std::runtime_error("Could not find suitable GPU."); + } + physicalDevice = devices[bestGPUIndex]; + + vkGetPhysicalDeviceProperties(physicalDevice, &phDevProperties); + vkGetPhysicalDeviceFeatures(physicalDevice, &phDevFeatures); + qFamilyIndices = findQueueFamilies(physicalDevice); + vLog("Selected GPU:", phDevProperties.deviceName, "with the following queue family indices:", qFamilyIndices); +} + + +const unsigned int SCORE_DISCRETE_GPU = 5000; +const unsigned int SCORE_INTEGRATED_GPU = 2000; +const unsigned int SCORE_VIRTUAL_GPU = 1000; +const unsigned int SCORE_HAS_ALL_QUEUE_FAMILIES = 100; +const unsigned int SCORE_HAS_FEATURE = 100; + +unsigned int VulkanInstance::rateDevice(VkPhysicalDevice device) { + unsigned int score; + + // rate type + VkPhysicalDeviceProperties properties{}; + vkGetPhysicalDeviceProperties(device, &properties); + switch(properties.deviceType) { + case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: + score = SCORE_DISCRETE_GPU; + break; + case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: + score = SCORE_INTEGRATED_GPU; + break; + case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: + score = SCORE_VIRTUAL_GPU; + break; + default: + return 0; + } + + qFamilyIndices = findQueueFamilies(device); + // has needed queue families + if (qFamilyIndices.hasAllValues()) { + score += SCORE_HAS_ALL_QUEUE_FAMILIES; + } + else if (!qFamilyIndices.hasNecessaryValues()) { + return 0; + } + + // supports all extensions + if (!deviceExtensionsSupported(device)) { + return 0; + } + + // swap chain support + SwapChainSupport scDetails = querySwapChainSupport(device); + if (scDetails.formats.empty() or scDetails.presentModes.empty()) { + return 0; + } + + // rate memory + VkPhysicalDeviceMemoryProperties memProperties{}; + vkGetPhysicalDeviceMemoryProperties(device, &memProperties); + for (uint32_t i = 0; i < memProperties.memoryHeapCount; i++) { + score += memProperties.memoryHeaps[i].size / 1e6; + } + + VkPhysicalDeviceFeatures features{}; + vkGetPhysicalDeviceFeatures(device, &features); + if (features.samplerAnisotropy == VK_TRUE) { score += SCORE_HAS_FEATURE; } + + vLog("rateDevice: GPU: ", properties.deviceName, " - Score: ", score); + return score; +} + + +void VulkanInstance::setValidSettings() { + // anisotropic filtering + settings.setAllowedValues("anisotropy_enable", { false, vkBool2Bool(phDevFeatures.samplerAnisotropy) }, gz::SM_LIST); + settings.setAllowedValues("max_anisotropy", { 1.0f, phDevProperties.limits.maxSamplerAnisotropy }, gz::SM_RANGE); +} + +VkFormat VulkanInstance::findSupportedFormat(const std::vector& candidates, VkImageTiling tiling, VkFormatFeatureFlags features) { + for (const VkFormat& format : candidates) { + VkFormatProperties formatProperties; + vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &formatProperties); + if (tiling == VK_IMAGE_TILING_LINEAR and + (formatProperties.linearTilingFeatures & features) == features) { + return format; + } + else if (tiling == VK_IMAGE_TILING_OPTIMAL and + (formatProperties.optimalTilingFeatures & features) == features) { + return format; + } + } + throw VkException(std::string("Could not find a suitable format. tiling: ") + STR_VK_IMAGE_TILING(tiling) + ".", "findSupportedFormat"); +} + + +// +// QUEUE +// +QueueFamilyIndices VulkanInstance::findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t qFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &qFamilyCount, nullptr); + std::vector qFamilies(qFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &qFamilyCount, qFamilies.data()); + vLog("findQueueFamilies: found", qFamilyCount, "queue families:"); + + VkBool32 presentSupport = false; + for (unsigned int i = 0; i < qFamilyCount; i++) { + // check for queue with graphic capabilities + if (qFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + else if (qFamilies[i].queueFlags & VK_QUEUE_TRANSFER_BIT) { // only transfer, not graphics + indices.transferFamily = i; + } + + // check for surface support + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + if (presentSupport == VK_TRUE) { + indices.presentFamily = i; + } + + vLog("findQueueFamilies:", i, "-", qFamilies[i].queueFlags); + if (indices.hasAllValues()) { + vLog("findQueueFamilies: Found all wanted families."); + break; + } + } + return indices; +} + + +// +// LOGICAL DEVICE +// +void VulkanInstance::createLogicalDevice() { + + std::unordered_set qFamiliyIndicesSet = { // must be unique + qFamilyIndices.graphicsFamily.value(), qFamilyIndices.presentFamily.value(), + }; + if (qFamilyIndices.transferFamily.has_value()) { + qFamiliyIndicesSet.insert(qFamilyIndices.transferFamily.value()); + } + + std::vector deviceQueueCI; + for (uint32_t index : qFamiliyIndicesSet) { + deviceQueueCI.push_back(VkDeviceQueueCreateInfo()); + deviceQueueCI.back().sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + deviceQueueCI.back().queueFamilyIndex = index; + deviceQueueCI.back().queueCount = 1; + float qPriority = 1.0f; + deviceQueueCI.back().pQueuePriorities = &qPriority; + } + + VkPhysicalDeviceFeatures deviceFeatures{}; + deviceFeatures.samplerAnisotropy = bool2VkBool(settings.getOr("anisotropy_enable", false)); + + VkDeviceCreateInfo deviceCI{}; + deviceCI.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + deviceCI.pQueueCreateInfos = deviceQueueCI.data(); + deviceCI.queueCreateInfoCount = static_cast(deviceQueueCI.size()); + deviceCI.pEnabledFeatures = &deviceFeatures; + deviceCI.enabledExtensionCount = static_cast(extensions.size()); + deviceCI.ppEnabledExtensionNames = extensions.data(); + + VkResult result = vkCreateDevice(physicalDevice, &deviceCI, nullptr, &device); + if (result != VK_SUCCESS) { + vLog.error("createLogicalDevice: Failed to create logical device.", "VkResult:", STR_VK_RESULT(result)); + throw std::runtime_error("createLogicalDevice: Failed to create logical device."); + } + + // get handles + uint32_t index = 0; + vkGetDeviceQueue(device, qFamilyIndices.graphicsFamily.value(), index, &graphicsQ); + vkGetDeviceQueue(device, qFamilyIndices.presentFamily.value(), index, &presentQ); + if (qFamilyIndices.transferFamily.has_value()) { + vkGetDeviceQueue(device, qFamilyIndices.transferFamily.value(), index, &transferQ); + } + else { + transferQ = graphicsQ; + } + vLog("createLogicalDevice: Created logical device."); +} + + +// +// SURFACE +// +void VulkanInstance::createSurface() { + VkResult result = glfwCreateWindowSurface(instance, window, nullptr, &surface); + if (result != VK_SUCCESS) { + vLog.error("createSurface: Failed to create window surface.", "VkResult:", STR_VK_RESULT(result)); + throw std::runtime_error("createSurface: Failed to create window surface."); + } +} + + +// +// SWAP CHAIN +// +SwapChainSupport VulkanInstance::querySwapChainSupport(VkPhysicalDevice physicalDevice) { + SwapChainSupport scDetails; + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &formatCount, nullptr); + scDetails.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &formatCount, scDetails.formats.data()); + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &presentModeCount, nullptr); + scDetails.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &presentModeCount, scDetails.presentModes.data()); + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, surface, &scDetails.capabilities); + + return scDetails; +} + + +VkSurfaceFormatKHR VulkanInstance::selectSwapChainSurfaceFormat(const std::vector& availableFormats) { + vLog("selectSwapChainSurfaceFormat:", availableFormats.size(), "formats available."); + for (const auto& format : availableFormats) { + if (format.format == VK_FORMAT_B8G8R8A8_SRGB and format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return format; + } + } + return availableFormats.front(); +} + + +VkPresentModeKHR VulkanInstance::selectSwapChainPresentMode(const std::vector& availableModes) { + for (const auto& mode : availableModes) { + if (mode == VK_PRESENT_MODE_MAILBOX_KHR) { + return mode; + } + } + return VK_PRESENT_MODE_FIFO_KHR; +} + + +VkExtent2D VulkanInstance::selectSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + // if width = max(uint32_t) then surface size will be determined by a swapchain targeting it + if (capabilities.currentExtent.width == std::numeric_limits::max()) { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + VkExtent2D actualExtent {}; + actualExtent.width = std::clamp(static_cast(width), capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(static_cast(height), capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + return actualExtent; + } + else { + return capabilities.currentExtent; + } +} + + +void VulkanInstance::createSwapChain() { + SwapChainSupport scDetails = querySwapChainSupport(physicalDevice); + VkSurfaceFormatKHR surfaceFormat = selectSwapChainSurfaceFormat(scDetails.formats); + + uint32_t imageCount = scDetails.capabilities.minImageCount + 1; + // maxImageCount = 0 -> no max + if (scDetails.capabilities.maxImageCount > 0 and imageCount < scDetails.capabilities.maxImageCount) { + imageCount = scDetails.capabilities.maxImageCount; + } + + scImageFormat = surfaceFormat.format; + scExtent = selectSwapExtent(scDetails.capabilities); + + VkSwapchainCreateInfoKHR swapChainCI{}; + swapChainCI.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + swapChainCI.surface = surface; + swapChainCI.minImageCount = imageCount; + swapChainCI.imageFormat = scImageFormat; + swapChainCI.imageColorSpace = surfaceFormat.colorSpace; + swapChainCI.imageExtent = scExtent; + swapChainCI.imageArrayLayers = 1; + // if postProcessing: VK_IMAGE_USAGE_TRANSFER_DST_BIT + swapChainCI.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + + /* QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); */ + if (qFamilyIndices.graphicsFamily.value() != qFamilyIndices.presentFamily.value()) { + swapChainCI.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + swapChainCI.queueFamilyIndexCount = 2; + uint32_t qIndices[] = { qFamilyIndices.graphicsFamily.value(), qFamilyIndices.presentFamily.value() }; + swapChainCI.pQueueFamilyIndices = qIndices; + } + else { + swapChainCI.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + swapChainCI.preTransform = scDetails.capabilities.currentTransform; + swapChainCI.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + swapChainCI.presentMode = selectSwapChainPresentMode(scDetails.presentModes); + swapChainCI.clipped = VK_TRUE; + swapChainCI.oldSwapchain = VK_NULL_HANDLE; + + VkResult result = vkCreateSwapchainKHR(device, &swapChainCI, nullptr, &swapChain); + if (result != VK_SUCCESS) { + vLog.error("createSwapChain: Couldn not create swap chain", "VkResult:", STR_VK_RESULT(result)); + throw std::runtime_error("createSwapChain: Couldn not create swap chain"); + } + + // get image handles + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + scImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, scImages.data()); + vLog("createSwapChain: Created swap chain."); +} + + +void VulkanInstance::recreateSwapChain() { + int width, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { // minimized + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + vLog("recreateSwapChain: new framebuffer size:", width, height); + } + vkDeviceWaitIdle(device); + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + createRenderPassBegin(); + createRenderPassEnd(); + /* createDescriptorSetLayout(); */ + /* createGraphicsPipeline("shaders/vert.spv", "shaders/frag.spv", true, renderPass1, pipelines[PL_3D]); */ + createGraphicsPipeline("shaders/vert2D.spv", "shaders/frag2D.spv", false, renderPassBegin, pipelines[PL_2D]); + createDepthImageAndView(); + recreateFramebuffers(); + /* createUniformBuffers(); */ + createCommandBuffers(commandBuffersBegin); + createCommandBuffers(commandBuffersEnd); + /* createDescriptorPool(); */ + /* createDescriptorSets(); */ +} + + +void VulkanInstance::cleanupSwapChain() { + vkDestroyImageView(device, depthImageView, nullptr); + vkDestroyImage(device, depthImage, nullptr); + vkFreeMemory(device, depthImageMemory, nullptr); + + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + vkDestroyDescriptorPool(device, descriptorPool, nullptr); + + for (auto& framebuffer : scFramebuffers) { + destroyFramebuffers(framebuffer.first); + } + + // destroy pipelines + auto it = pipelines.begin(); + while (it != pipelines.end()) { + it = pipelines.erase(it, device); + } + + vkDestroyRenderPass(device, renderPassBegin, nullptr); + vkDestroyRenderPass(device, renderPassEnd, nullptr); + for (auto& imageView : scImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + vkDestroySwapchainKHR(device, swapChain, nullptr); + +} + + +// +// IMAGE VIEW +// +void VulkanInstance::createImageView(VkFormat format, VkImage& image, VkImageView& imageView, VkImageAspectFlags aspectFlags) { + VkImageViewCreateInfo imageViewCI{}; + imageViewCI.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + imageViewCI.viewType = VK_IMAGE_VIEW_TYPE_2D; + imageViewCI.format = format; + imageViewCI.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + imageViewCI.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + imageViewCI.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + imageViewCI.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + imageViewCI.subresourceRange.aspectMask = aspectFlags; + imageViewCI.subresourceRange.baseMipLevel = 0; + imageViewCI.subresourceRange.levelCount = 1; + imageViewCI.subresourceRange.baseArrayLayer = 0; + imageViewCI.subresourceRange.layerCount = 1; + imageViewCI.image = image; + VkResult result = vkCreateImageView(device, &imageViewCI, nullptr, &imageView); + if (result != VK_SUCCESS) { + throw getVkException(result, "Could not create image view", "createImageViews"); + } +} + + +void VulkanInstance::createImageViews() { + scImageViews.resize(scImages.size()); + for (unsigned int i = 0; i < scImageViews.size(); i++) { + createImageView(scImageFormat, scImages[i], scImageViews[i], VK_IMAGE_ASPECT_COLOR_BIT); + } + vLog("createImageViews: Created", scImageViews.size(), "image views."); +} + + +// +// RENDER PASS +// +void VulkanInstance::createRenderPassBegin() { + VkAttachmentDescription colorBlendAttachment{}; + colorBlendAttachment.format = scImageFormat; + colorBlendAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorBlendAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorBlendAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorBlendAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorBlendAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorBlendAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorBlendAttachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkAttachmentReference colorAttachmentRef{}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + /* VkAttachmentDescription depthAttachment{}; */ + /* depthAttachment.format = findDepthFormat(); */ + /* depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT; */ + /* depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; */ + /* depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; */ + /* depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; */ + /* depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; */ + /* depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; */ + /* depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; */ + + /* VkAttachmentReference depthAttachmentRef{}; */ + /* depthAttachmentRef.attachment = 1; */ + /* depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; */ + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + /* subpass.pDepthStencilAttachment = &depthAttachmentRef; */ + + VkSubpassDependency dependency{}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.srcAccessMask = 0; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + /* VkSubpassDependency dependency{}; */ + /* dependency.srcSubpass = VK_SUBPASS_EXTERNAL; */ + /* dependency.dstSubpass = 0; */ + /* dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; */ + /* dependency.srcAccessMask = 0; */ + /* dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; */ + /* dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; */ + + /* std::array attachments = { colorBlendAttachment, depthAttachment }; */ + std::array attachments = { colorBlendAttachment }; + VkRenderPassCreateInfo renderPassCI{}; + renderPassCI.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassCI.attachmentCount = static_cast(attachments.size()); + renderPassCI.pAttachments = attachments.data(); + renderPassCI.subpassCount = 1; + renderPassCI.pSubpasses = &subpass; + renderPassCI.dependencyCount = 1; + renderPassCI.pDependencies = &dependency; + /* renderPassCI.dependencyCount = 0; */ + /* renderPassCI.pDependencies = nullptr; */ + /* renderPassCI.correlatedViewMaskCount = 0; */ + /* renderPassCI.pCorrelatedViewMasks = nullptr; */ + VkResult result = vkCreateRenderPass(device, &renderPassCI, nullptr, &renderPassBegin); + if (result != VK_SUCCESS) { + throw getVkException(result, "Could not create renderPassBegin", "createRenderPassBegin"); + } + vLog("createRenderPass: Created renderpassBegin."); +} + +void VulkanInstance::createRenderPassEnd() { + VkAttachmentDescription colorBlendAttachment{}; + colorBlendAttachment.format = scImageFormat; + colorBlendAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorBlendAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + colorBlendAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorBlendAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorBlendAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorBlendAttachment.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + colorBlendAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 0; + /* subpass.pColorAttachments = &colorAttachmentRef; */ + /* subpass.pDepthStencilAttachment = &depthAttachmentRef; */ + + VkSubpassDependency dependency{}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.srcAccessMask = 0; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + /* VkSubpassDependency dependency{}; */ + /* dependency.srcSubpass = VK_SUBPASS_EXTERNAL; */ + /* dependency.dstSubpass = 0; */ + /* dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; */ + /* dependency.srcAccessMask = 0; */ + /* dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; */ + /* dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; */ + + /* std::array attachments = { colorBlendAttachment, depthAttachment }; */ + std::array attachments = { colorBlendAttachment }; + VkRenderPassCreateInfo renderPassCI{}; + renderPassCI.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassCI.attachmentCount = static_cast(attachments.size()); + renderPassCI.pAttachments = attachments.data(); + renderPassCI.subpassCount = 1; + renderPassCI.pSubpasses = &subpass; + renderPassCI.dependencyCount = 0; + /* renderPassCI.pDependencies = &dependency; */ + /* renderPassCI.dependencyCount = 0; */ + /* renderPassCI.pDependencies = nullptr; */ + /* renderPassCI.correlatedViewMaskCount = 0; */ + /* renderPassCI.pCorrelatedViewMasks = nullptr; */ + VkResult result = vkCreateRenderPass(device, &renderPassCI, nullptr, &renderPassEnd); + if (result != VK_SUCCESS) { + throw getVkException(result, "Could not create renderPassEnd", "createRenderPassEnd"); + } + vLog("createRenderPass: Created renderPassEnd."); + +} + + +// +// DESCRIPTORS +// +void VulkanInstance::createDescriptorSetLayout() { + // 1) uniform buffer object + VkDescriptorSetLayoutBinding uboLayoutBinding{}; + uboLayoutBinding.binding = bindingUniformBuffer; + uboLayoutBinding.descriptorCount = 1; + uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + /* uboLayoutBinding.pImmutableSamplers = nullptr; */ + + + // 2) combined image sampler + VkDescriptorSetLayoutBinding samplerLayoutBinding{}; + samplerLayoutBinding.binding = bindingCombinedImageSampler; + samplerLayoutBinding.descriptorCount = 1; + samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + /* samplerLayoutBinding.pImmutableSamplers = nullptr; */ + + std::array bindings = { uboLayoutBinding, samplerLayoutBinding }; + + VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCI{}; + descriptorSetLayoutCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + descriptorSetLayoutCI.bindingCount = static_cast(bindings.size()); + descriptorSetLayoutCI.pBindings = bindings.data(); + + + VkResult result = vkCreateDescriptorSetLayout(device, &descriptorSetLayoutCI, nullptr, &descriptorSetLayout); + if (result != VK_SUCCESS) { + throw getVkException(result, "Could not create descriptorSetLayout", "createDescriptorSetLayout"); + } + vLog("createDescriptorSetLayout: Created descriptor set layout."); +} + + +void VulkanInstance::createDescriptorPool() { + std::array poolSizes; + poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + poolSizes[0].descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); + poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + poolSizes[1].descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); + + VkDescriptorPoolCreateInfo poolCI{}; + poolCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + poolCI.poolSizeCount = static_cast(poolSizes.size()); + poolCI.pPoolSizes = poolSizes.data(); + poolCI.maxSets = static_cast(MAX_FRAMES_IN_FLIGHT); + VkResult result = vkCreateDescriptorPool(device, &poolCI, nullptr, &descriptorPool); + if (result != VK_SUCCESS) { + throw getVkException(result, "Failed to create descriptor pool", "createDescriptorPool"); + } + vLog("createDescriptorPool: Created descriptor pool."); +} + + +void VulkanInstance::createDescriptorSets() { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); + VkDescriptorSetAllocateInfo setAI{}; + setAI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + setAI.descriptorPool = descriptorPool; + setAI.descriptorSetCount = static_cast(MAX_FRAMES_IN_FLIGHT); + setAI.pSetLayouts = layouts.data(); + + descriptorSets.resize(MAX_FRAMES_IN_FLIGHT); + VkResult result = vkAllocateDescriptorSets(device, &setAI, descriptorSets.data()); + if (result != VK_SUCCESS) { + throw getVkException(result, "Failed to create descriptor sets", "createDescriptorPool"); + } + + // configure sets + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + VkDescriptorBufferInfo bufferI{}; + // TODO + /* bufferI.buffer = uniformBuffers[i]; */ + bufferI.offset = 0; + bufferI.range = VK_WHOLE_SIZE; // sizeof(UniformBufferObject); + + VkDescriptorImageInfo imageI{}; + imageI.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + imageI.imageView = textureImageView; + imageI.sampler = textureSampler; + + std::array descriptorW{}; + descriptorW[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorW[0].dstSet = descriptorSets[i]; + descriptorW[0].dstBinding = bindingUniformBuffer; + descriptorW[0].dstArrayElement = 0; + descriptorW[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptorW[0].descriptorCount = 1; + descriptorW[0].pBufferInfo = &bufferI; + /* descriptorW[0].pImageInfo = nullptr; */ + /* descriptorW[0].pTexelBufferView = nullptr; */ + + descriptorW[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorW[1].dstSet = descriptorSets[i]; + descriptorW[1].dstBinding = bindingCombinedImageSampler; + descriptorW[1].dstArrayElement = 0; + descriptorW[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + descriptorW[1].descriptorCount = 1; + /* descriptorW[1].pBufferInfo = &bufferI; */ + descriptorW[1].pImageInfo = &imageI; + /* descriptorW[1].pTexelBufferView = nullptr; */ + + uint32_t descriptorWriteCount = static_cast(descriptorW.size()); + uint32_t descriptorCopyCount = 0; + vkUpdateDescriptorSets(device, descriptorWriteCount, descriptorW.data(), descriptorCopyCount, nullptr); + } + vLog("createDescriptorSets: Created descriptor sets."); +} + + +// +// PIPELINE +// +/* void App::createGraphicsPipeline() { */ +/* auto vertShaderCode = readFile("shaders/vert.spv"); */ +/* auto fragShaderCode = readFile("shaders/frag.spv"); */ + +/* VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); */ +/* VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); */ + +/* VkPipelineShaderStageCreateInfo vertexShaderStageCI{}; */ +/* vertexShaderStageCI.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; */ +/* vertexShaderStageCI.stage = VK_SHADER_STAGE_VERTEX_BIT; */ +/* vertexShaderStageCI.module = vertShaderModule; */ +/* vertexShaderStageCI.pName = "main"; */ +/* /1* vssCreateInfo.pSpecializationInfo = nullptr; *1/ */ + +/* VkPipelineShaderStageCreateInfo fragmentShaderStageCI{}; */ +/* fragmentShaderStageCI.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; */ +/* fragmentShaderStageCI.stage = VK_SHADER_STAGE_FRAGMENT_BIT; */ +/* fragmentShaderStageCI.module = fragShaderModule; */ +/* fragmentShaderStageCI.pName = "main"; */ +/* /1* vssCreateInfo.pSpecializationInfo = nullptr; *1/ */ + +/* VkPipelineShaderStageCreateInfo shaderStagesCI[] = { vertexShaderStageCI, fragmentShaderStageCI }; */ + +/* VkVertexInputBindingDescription bindingD = Vertex::getBindingDescription(); */ +/* // array */ +/* auto attributeD = Vertex::getAttributeDescriptions(); */ + +/* VkPipelineVertexInputStateCreateInfo vertexInputStateCI{}; */ +/* vertexInputStateCI.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; */ +/* vertexInputStateCI.vertexBindingDescriptionCount = 1; */ +/* vertexInputStateCI.pVertexBindingDescriptions = &bindingD; */ +/* vertexInputStateCI.vertexAttributeDescriptionCount = static_cast(attributeD.size());; */ +/* vertexInputStateCI.pVertexAttributeDescriptions = attributeD.data(); */ + +/* VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCI{}; */ +/* inputAssemblyStateCI.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; */ +/* inputAssemblyStateCI.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; */ +/* inputAssemblyStateCI.primitiveRestartEnable = VK_FALSE; */ + +/* VkViewport viewport{}; */ +/* viewport.x = 0.0f; */ +/* viewport.y = 0.0f; */ +/* viewport.width = static_cast(scExtent.width); */ +/* viewport.height = static_cast(scExtent.height); */ +/* viewport.minDepth = 0.0f; */ +/* viewport.maxDepth = 1.0f; */ + +/* VkRect2D scissor{}; */ +/* scissor.offset = {0, 0}; */ +/* scissor.extent = scExtent; */ + +/* VkPipelineViewportStateCreateInfo viewportStateCI{}; */ +/* viewportStateCI.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; */ +/* viewportStateCI.viewportCount = 1; */ +/* viewportStateCI.pViewports = &viewport; */ +/* viewportStateCI.scissorCount = 1; */ +/* viewportStateCI.pScissors = &scissor; */ + +/* VkPipelineRasterizationStateCreateInfo rasterizerCI{}; */ +/* rasterizerCI.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; */ +/* rasterizerCI.depthClampEnable = VK_FALSE; */ +/* rasterizerCI.rasterizerDiscardEnable = VK_FALSE; */ +/* rasterizerCI.polygonMode = VK_POLYGON_MODE_FILL; */ +/* rasterizerCI.lineWidth = 1.0f; */ +/* rasterizerCI.cullMode = VK_CULL_MODE_BACK_BIT; */ +/* // not clockwise because of inverted y axis from glm */ +/* rasterizerCI.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; */ +/* rasterizerCI.depthBiasEnable = VK_FALSE; */ +/* /1* rasterizerCI.depthBiasConstantFactor = 0.0f; *1/ */ +/* /1* rasterizerCI.depthBiasClamp = 0.0f; *1/ */ +/* /1* rasterizerCI.depthBiasSlopeFactor = 0.0f; *1/ */ + +/* VkPipelineMultisampleStateCreateInfo multisampleStateCI{}; */ +/* multisampleStateCI.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; */ +/* multisampleStateCI.sampleShadingEnable = VK_FALSE; */ +/* multisampleStateCI.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; */ +/* /1* multisampleStateCI.minSampleShading = 1.0f; *1/ */ +/* /1* multisampleStateCI.pSampleMask = nullptr; *1/ */ +/* /1* multisampleStateCI.alphaToCoverageEnable = VK_FALSE; *1/ */ +/* /1* multisampleStateCI.alphaToOneEnable = VK_FALSE; *1/ */ + +/* VkPipelineColorBlendAttachmentState colorBlendAttachment{}; */ +/* colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; */ +/* colorBlendAttachment.blendEnable = VK_FALSE; */ +/* /1* colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; *1/ */ +/* /1* colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO; *1/ */ +/* /1* colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; *1/ */ +/* /1* colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; *1/ */ +/* /1* colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; *1/ */ +/* /1* colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; *1/ */ + +/* VkPipelineColorBlendStateCreateInfo colorBlendCI{}; */ +/* colorBlendCI.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; */ +/* colorBlendCI.logicOpEnable = VK_FALSE; */ +/* /1* colorBlendCI.logicOp = VK_LOGIC_OP_COPY; *1/ */ +/* colorBlendCI.attachmentCount = 1; */ +/* colorBlendCI.pAttachments = &colorBlendAttachment; */ +/* colorBlendCI.blendConstants[0] = 0.0f; */ +/* colorBlendCI.blendConstants[1] = 0.0f; */ +/* colorBlendCI.blendConstants[2] = 0.0f; */ +/* colorBlendCI.blendConstants[3] = 0.0f; */ + +/* /1* std::vector dynamicStates = { *1/ */ +/* /1* VK_DYNAMIC_STATE_VIEWPORT, *1/ */ +/* /1* VK_DYNAMIC_STATE_LINE_WIDTH *1/ */ +/* /1* }; *1/ */ +/* /1* VkPipelineDynamicStateCreateInfo dynamicState{}; *1/ */ +/* /1* dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; *1/ */ +/* /1* dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); *1/ */ +/* /1* dynamicState.pDynamicStates = dynamicStates.data(); *1/ */ + +/* VkPipelineDepthStencilStateCreateInfo depthStencilCI{}; */ +/* depthStencilCI.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; */ +/* depthStencilCI.depthTestEnable = VK_TRUE; */ +/* depthStencilCI.depthWriteEnable = VK_TRUE; */ +/* depthStencilCI.depthCompareOp = VK_COMPARE_OP_LESS; */ +/* depthStencilCI.depthBoundsTestEnable = VK_FALSE; */ +/* /1* depthStencilCI.minDepthBounds = 0.0f; *1/ */ +/* /1* depthStencilCI.maxDepthBounds = 1.0f; *1/ */ +/* depthStencilCI.stencilTestEnable = VK_FALSE; */ +/* /1* depthStencilCI.front = {}; *1/ */ +/* /1* depthStencilCI.back = {}; *1/ */ + +/* VkPipelineLayoutCreateInfo pipelineLayoutCI{}; */ +/* pipelineLayoutCI.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; */ +/* /1* pipelineLayoutCI.pushConstantRangeCount = 0; *1/ */ +/* /1* pipelineLayoutCI.pPushConstantRanges = nullptr; *1/ */ +/* pipelineLayoutCI.setLayoutCount = 1; */ +/* pipelineLayoutCI.pSetLayouts = &descriptorSetLayout; */ +/* VkResult result = vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayout); */ +/* if (result != VK_SUCCESS) { */ +/* throw getVkException(result, "Failed to create pipeline layout", "createGraphicsPipeline"); */ +/* } */ + +/* VkGraphicsPipelineCreateInfo pipelineCI{}; */ +/* pipelineCI.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; */ +/* pipelineCI.stageCount = 2; */ +/* pipelineCI.pStages = shaderStagesCI; */ +/* pipelineCI.pVertexInputState = &vertexInputStateCI; */ +/* pipelineCI.pInputAssemblyState = &inputAssemblyStateCI; */ +/* pipelineCI.pViewportState = &viewportStateCI; */ +/* pipelineCI.pRasterizationState = &rasterizerCI; */ +/* pipelineCI.pMultisampleState = &multisampleStateCI; */ +/* pipelineCI.pDepthStencilState = &depthStencilCI; */ +/* pipelineCI.pColorBlendState = &colorBlendCI; */ +/* /1* pipelineCI.pDynamicState = nullptr; *1/ */ +/* pipelineCI.layout = pipelineLayout; */ +/* pipelineCI.renderPass = renderPass; */ +/* pipelineCI.subpass = 0; */ +/* /1* pipelineCI.basePipelineHandle = VK_NULL_HANDLE; *1/ */ +/* /1* pipelineCI.basePipelineIndex = -1; *1/ */ + +/* result = vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineCI, nullptr, &graphicsPipeline); */ +/* if (result != VK_SUCCESS) { */ +/* throw getVkException(result, "Could not create graphics pipeline", "createGraphicsPipeline"); */ +/* } */ + +/* vkDestroyShaderModule(device, vertShaderModule, nullptr); */ +/* vkDestroyShaderModule(device, fragShaderModule, nullptr); */ + +/* vLog("createGraphicsPipeline: Created graphics pipeline."); */ +/* } */ + + +template +void VulkanInstance::createGraphicsPipeline(const std::string& vertexShader, const std::string& fragmentShader, bool useDepthStencil, VkRenderPass& renderPass, Pipeline& pipeline) { + auto vertShaderCode = readFile(vertexShader); + auto fragShaderCode = readFile(fragmentShader); + + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); + + VkPipelineShaderStageCreateInfo vertexShaderStageCI{}; + vertexShaderStageCI.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertexShaderStageCI.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertexShaderStageCI.module = vertShaderModule; + vertexShaderStageCI.pName = "main"; + /* vssCreateInfo.pSpecializationInfo = nullptr; */ + + VkPipelineShaderStageCreateInfo fragmentShaderStageCI{}; + fragmentShaderStageCI.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragmentShaderStageCI.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragmentShaderStageCI.module = fragShaderModule; + fragmentShaderStageCI.pName = "main"; + /* vssCreateInfo.pSpecializationInfo = nullptr; */ + + VkPipelineShaderStageCreateInfo shaderStagesCI[] = { vertexShaderStageCI, fragmentShaderStageCI }; + + VkVertexInputBindingDescription bindingD = VertexT::getBindingDescription(); + // array + auto attributeD = VertexT::getAttributeDescriptions(); + + VkPipelineVertexInputStateCreateInfo vertexInputStateCI{}; + vertexInputStateCI.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertexInputStateCI.vertexBindingDescriptionCount = 1; + vertexInputStateCI.pVertexBindingDescriptions = &bindingD; + vertexInputStateCI.vertexAttributeDescriptionCount = static_cast(attributeD.size());; + vertexInputStateCI.pVertexAttributeDescriptions = attributeD.data(); + + VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCI{}; + inputAssemblyStateCI.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssemblyStateCI.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssemblyStateCI.primitiveRestartEnable = VK_FALSE; + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = static_cast(scExtent.width); + viewport.height = static_cast(scExtent.height); + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = scExtent; + + VkPipelineViewportStateCreateInfo viewportStateCI{}; + viewportStateCI.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportStateCI.viewportCount = 1; + viewportStateCI.pViewports = &viewport; + viewportStateCI.scissorCount = 1; + viewportStateCI.pScissors = &scissor; + + VkPipelineRasterizationStateCreateInfo rasterizerCI{}; + rasterizerCI.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizerCI.depthClampEnable = VK_FALSE; + rasterizerCI.rasterizerDiscardEnable = VK_FALSE; + rasterizerCI.polygonMode = VK_POLYGON_MODE_FILL; + rasterizerCI.lineWidth = 1.0f; + rasterizerCI.cullMode = VK_CULL_MODE_BACK_BIT; + // not clockwise because of inverted y axis from glm + rasterizerCI.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + rasterizerCI.depthBiasEnable = VK_FALSE; + /* rasterizerCI.depthBiasConstantFactor = 0.0f; */ + /* rasterizerCI.depthBiasClamp = 0.0f; */ + /* rasterizerCI.depthBiasSlopeFactor = 0.0f; */ + + VkPipelineMultisampleStateCreateInfo multisampleStateCI{}; + multisampleStateCI.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampleStateCI.sampleShadingEnable = VK_FALSE; + multisampleStateCI.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + /* multisampleStateCI.minSampleShading = 1.0f; */ + /* multisampleStateCI.pSampleMask = nullptr; */ + /* multisampleStateCI.alphaToCoverageEnable = VK_FALSE; */ + /* multisampleStateCI.alphaToOneEnable = VK_FALSE; */ + + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_FALSE; + /* colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; */ + /* colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO; */ + /* colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; */ + /* colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; */ + /* colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; */ + /* colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; */ + + VkPipelineColorBlendStateCreateInfo colorBlendCI{}; + colorBlendCI.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlendCI.logicOpEnable = VK_FALSE; + /* colorBlendCI.logicOp = VK_LOGIC_OP_COPY; */ + colorBlendCI.attachmentCount = 1; + colorBlendCI.pAttachments = &colorBlendAttachment; + colorBlendCI.blendConstants[0] = 0.0f; + colorBlendCI.blendConstants[1] = 0.0f; + colorBlendCI.blendConstants[2] = 0.0f; + colorBlendCI.blendConstants[3] = 0.0f; + + /* std::vector dynamicStates = { */ + /* VK_DYNAMIC_STATE_VIEWPORT, */ + /* VK_DYNAMIC_STATE_LINE_WIDTH */ + /* }; */ + /* VkPipelineDynamicStateCreateInfo dynamicState{}; */ + /* dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; */ + /* dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); */ + /* dynamicState.pDynamicStates = dynamicStates.data(); */ + + VkPipelineDepthStencilStateCreateInfo depthStencilCI{}; + depthStencilCI.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + depthStencilCI.depthTestEnable = VK_TRUE; + depthStencilCI.depthWriteEnable = VK_TRUE; + depthStencilCI.depthCompareOp = VK_COMPARE_OP_LESS; + depthStencilCI.depthBoundsTestEnable = VK_FALSE; + /* depthStencilCI.minDepthBounds = 0.0f; */ + /* depthStencilCI.maxDepthBounds = 1.0f; */ + depthStencilCI.stencilTestEnable = VK_FALSE; + /* depthStencilCI.front = {}; */ + /* depthStencilCI.back = {}; */ + + VkPipelineLayoutCreateInfo pipelineLayoutCI{}; + pipelineLayoutCI.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + /* pipelineLayoutCI.pushConstantRangeCount = 0; */ + /* pipelineLayoutCI.pPushConstantRanges = nullptr; */ + /* pipelineLayoutCI.setLayoutCount = 1; */ + /* pipelineLayoutCI.pSetLayouts = &descriptorSetLayout; */ + VkResult result = vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipeline.layout); + if (result != VK_SUCCESS) { + throw getVkException(result, "Failed to create pipeline layout", "createGraphicsPipeline"); + } + + VkGraphicsPipelineCreateInfo pipelineCI{}; + pipelineCI.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineCI.stageCount = 2; + pipelineCI.pStages = shaderStagesCI; + pipelineCI.pVertexInputState = &vertexInputStateCI; + pipelineCI.pInputAssemblyState = &inputAssemblyStateCI; + pipelineCI.pViewportState = &viewportStateCI; + pipelineCI.pRasterizationState = &rasterizerCI; + pipelineCI.pMultisampleState = &multisampleStateCI; + if (useDepthStencil) { + pipelineCI.pDepthStencilState = &depthStencilCI; + } + else { + pipelineCI.pDepthStencilState = nullptr; + } + pipelineCI.pColorBlendState = &colorBlendCI; + /* pipelineCI.pDynamicState = nullptr; */ + pipelineCI.layout = pipeline.layout; + pipelineCI.renderPass = renderPass; + pipelineCI.subpass = 0; + /* pipelineCI.basePipelineHandle = VK_NULL_HANDLE; */ + /* pipelineCI.basePipelineIndex = -1; */ + + result = vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineCI, nullptr, &pipeline.pipeline); + if (result != VK_SUCCESS) { + throw getVkException(result, "Could not create graphics pipeline", "createGraphicsPipeline"); + } + + vkDestroyShaderModule(device, vertShaderModule, nullptr); + vkDestroyShaderModule(device, fragShaderModule, nullptr); + + vLog("createGraphicsPipeline: Created graphics pipeline."); +} +template void VulkanInstance::createGraphicsPipeline(const std::string& vertexShader, const std::string& fragmentShader, bool useDepthStencil, VkRenderPass& renderPass, Pipeline& pipeline); +template void VulkanInstance::createGraphicsPipeline(const std::string& vertexShader, const std::string& fragmentShader, bool useDepthStencil, VkRenderPass& renderPass, Pipeline& pipeline); + +// +// BUFFER +// +void VulkanInstance::createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory, VkSharingMode sharingMode, std::vector* qFamiliesWithAccess) { + VkBufferCreateInfo bufferCI{}; + bufferCI.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferCI.size = size; + bufferCI.usage = usage; + bufferCI.sharingMode = sharingMode; + std::vector queueFamiliesWithAccess; + if (sharingMode == VK_SHARING_MODE_CONCURRENT) { + if (qFamiliesWithAccess == nullptr) { + throw VkUserError("Sharing mode is VK_SHARING_MODE_CONCURRENT but qFamiliesWithAccess is nullptr", "createBuffer"); + } + bufferCI.queueFamilyIndexCount = static_cast(qFamiliesWithAccess->size()); + bufferCI.pQueueFamilyIndices = qFamiliesWithAccess->data(); + } + + VkResult result = vkCreateBuffer(device, &bufferCI, nullptr, &buffer); + if (result != VK_SUCCESS) { + throw getVkException(result, "Failed to create buffer", "createBuffer"); + } + + VkMemoryRequirements memoryRequirements{}; + vkGetBufferMemoryRequirements(device, buffer, &memoryRequirements); + + VkMemoryAllocateInfo allocI{}; + allocI.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocI.allocationSize = memoryRequirements.size; + VkMemoryPropertyFlags wantedProperties = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + allocI.memoryTypeIndex = findMemoryType(memoryRequirements.memoryTypeBits, wantedProperties); + + result = vkAllocateMemory(device, &allocI, nullptr, &bufferMemory); + if (result != VK_SUCCESS) { + throw getVkException(result, "Failed to allocate buffer memory!", "createBuffer"); + } + + vkBindBufferMemory(device, buffer, bufferMemory, NO_OFFSET); +} + + +VkCommandBuffer VulkanInstance::beginSingleTimeCommands(VkCommandPool commandPool) { + VkCommandBufferAllocateInfo allocI{}; + allocI.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocI.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocI.commandPool = commandPool; + allocI.commandBufferCount = 1; + + VkCommandBuffer cmdBuffer; + vkAllocateCommandBuffers(device, &allocI, &cmdBuffer); + + VkCommandBufferBeginInfo beginI{}; + beginI.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginI.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + vkBeginCommandBuffer(cmdBuffer, &beginI); + return cmdBuffer; +} + + +void VulkanInstance::endSingleTimeCommands(VkCommandBuffer cmdBuffer, VkCommandPool commandPool, VkQueue q) { + VkResult result = vkEndCommandBuffer(cmdBuffer); + if (result != VK_SUCCESS) { + throw getVkException(result, "Failed to end commandBuffer", "endSingleTimeCommands"); + } + + VkSubmitInfo submitI{}; + submitI.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitI.commandBufferCount = 1; + submitI.pCommandBuffers = &cmdBuffer; + uint32_t submitCount = 1; + result = vkQueueSubmit(q, submitCount, &submitI, VK_NULL_HANDLE); + if (result != VK_SUCCESS) { + throw getVkException(result, "Failed to submit commandBuffer", "endSingleTimeCommands"); + } + result = vkQueueWaitIdle(q); + if (result != VK_SUCCESS) { + throw getVkException(result, "Failed to wait for queue idle", "endSingleTimeCommands"); + } + const uint32_t commandBufferCount = 1; + vkFreeCommandBuffers(device, commandPool, commandBufferCount, &cmdBuffer); +} + + +void VulkanInstance::copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { + VkCommandBuffer cmdBuffer = beginSingleTimeCommands(commandPoolTransfer); + VkBufferCopy copyRegion{}; + copyRegion.srcOffset = 0; + copyRegion.dstOffset = 0; + copyRegion.size = size; + uint32_t copyRegionCount = 1; + vkCmdCopyBuffer(cmdBuffer, srcBuffer, dstBuffer, copyRegionCount, ©Region); + endSingleTimeCommands(cmdBuffer, commandPoolTransfer, transferQ); +} + + +uint32_t VulkanInstance::findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) { + VkPhysicalDeviceMemoryProperties memoryProperties{}; + vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memoryProperties); + for (uint32_t i = 0; i < memoryProperties.memoryTypeCount; i++) { + if ((typeFilter & (1 << i)) and (memoryProperties.memoryTypes[i].propertyFlags & properties) == properties) { // if ith bit of typefilter is set + return i; + } + } + throw VkException("Could not find suitable memory type", "findMemoryType"); +} + + +// +// VERTEX BUFFER +// +template +void VulkanInstance::createVertexBuffer(size_t vertexCount, VkBuffer& vertexBuffer, VkDeviceMemory& vertexBufferMemory, VkDeviceSize& vertexBufferSize) { + vertexBufferSize = vertexCount * sizeof(VertexT); + + // create vertex buffer + VkSharingMode sharingMode = VK_SHARING_MODE_EXCLUSIVE; + std::vector qFamiliesWithAccess; + if (qFamilyIndices.transferFamily.has_value()) { // prefer dedicated transfer family + qFamiliesWithAccess = { qFamilyIndices.graphicsFamily.value(), qFamilyIndices.transferFamily.value() }; + } + createBuffer(vertexBufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vertexBuffer, vertexBufferMemory , sharingMode, &qFamiliesWithAccess); + + vLog("createVertexBuffer: Created vertex buffer with size", vertexBufferSize); +} +template void VulkanInstance::createVertexBuffer(size_t vertexCount, VkBuffer& vertexBuffer, VkDeviceMemory& vertexBufferMemory, VkDeviceSize& vertexBufferSize); +template void VulkanInstance::createVertexBuffer(size_t vertexCount, VkBuffer& vertexBuffer, VkDeviceMemory& vertexBufferMemory, VkDeviceSize& vertexBufferSize); + + +// +// INDEX BUFFER +// +template +void VulkanInstance::createIndexBuffer(size_t indexCount, VkBuffer& indexBuffer, VkDeviceMemory& indexBufferMemory, VkDeviceSize& indexBufferSize) { + indexBufferSize = indexCount * sizeof(T); + + // create index buffer + VkSharingMode sharingMode = VK_SHARING_MODE_EXCLUSIVE; + std::vector qFamiliesWithAccess; + if (qFamilyIndices.transferFamily.has_value()) { // prefer dedicated transfer family + qFamiliesWithAccess = { qFamilyIndices.graphicsFamily.value(), qFamilyIndices.transferFamily.value() }; + } + createBuffer(indexBufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, indexBuffer, indexBufferMemory, sharingMode, &qFamiliesWithAccess); + + vLog("createIndexBuffer: Created index buffer with size:", indexBufferSize); +} +template void VulkanInstance::createIndexBuffer(size_t indexCount, VkBuffer& indexBuffer, VkDeviceMemory& indexBufferMemory, VkDeviceSize& indexBufferSize); +template void VulkanInstance::createIndexBuffer(size_t indexCount, VkBuffer& indexBuffer, VkDeviceMemory& indexBufferMemory, VkDeviceSize& indexBufferSize); + + + +// +// FRAMEBUFFERS +// +int VulkanInstance::createFramebuffers(std::vector& imageViews, VkRenderPass& renderPass) { + if (framebufferIDs.empty()) { + int MAX_FRAMEBUFFER_COUNT = 32; + for (int i = 0; i < MAX_FRAMEBUFFER_COUNT; i++) { + framebufferIDs.insert(i); + } + } + int id = *framebufferIDs.begin(); + framebufferIDs.erase(id); + scFramebuffers[id].second = renderPass; + createFramebuffers_(scFramebuffers[id].first, imageViews, renderPass); + vLog("createFramebuffers: Created frame buffers, id:", id); + return id; +} + + +void VulkanInstance::createFramebuffers_(std::vector& framebuffers, std::vector& imageViews, VkRenderPass& renderPass) { + framebuffers.resize(scImageViews.size()); + + VkFramebufferCreateInfo framebufferCI{}; + framebufferCI.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferCI.renderPass = renderPass; + framebufferCI.width = scExtent.width; + framebufferCI.height = scExtent.height; + framebufferCI.layers = 1; + + for (unsigned int i = 0; i < scImageViews.size(); i++) { + // TODO include depth image view for 3D + std::vector attachments = { imageViews[i] }; //, depthImageView }; + framebufferCI.pAttachments = attachments.data(); + framebufferCI.attachmentCount = static_cast(attachments.size()); + + VkResult result = vkCreateFramebuffer(device, &framebufferCI, nullptr, &framebuffers[i]); + if (result != VK_SUCCESS) { + throw getVkException(result, "Could not create framebuffer", "createFramebuffers_"); + } + } +} + + +void VulkanInstance::recreateFramebuffers() { + for (auto& framebufferPair: scFramebuffers) { + // TODO!!! + /* createFramebuffers_(framebufferPair.second.first, framebufferPair.second.second); */ + } +} + + +std::vector& VulkanInstance::getFramebuffers(int id) { + return scFramebuffers[id].first; +} + + +void VulkanInstance::destroyFramebuffers(int id) { + if (scFramebuffers.contains(id)) { + for (auto& framebuffer : scFramebuffers[id].first) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } + scFramebuffers.erase(id); + framebufferIDs.insert(id); + } +} + + +void VulkanInstance::frameBufferResizedCallback(GLFWwindow* window, int width, int height) { + VulkanInstance* app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->frameBufferResized = true; +} + + +// +// COMMAND POOL +// +void VulkanInstance::createCommandPools() { + /* QueueFamilyIndicjes queueFamilyIndices = findQueueFamilies(physicalDevice); */ + VkCommandPoolCreateInfo commandPoolGraphicsCI{}; + commandPoolGraphicsCI.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + commandPoolGraphicsCI.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + commandPoolGraphicsCI.queueFamilyIndex = qFamilyIndices.graphicsFamily.value(); + VkResult result = vkCreateCommandPool(device, &commandPoolGraphicsCI, nullptr, &commandPoolGraphics); + if (result != VK_SUCCESS) { + throw getVkException(result, "Failed to create graphics command pool", "createCommandPool"); + } + + if (qFamilyIndices.transferFamily.has_value()) { + VkCommandPoolCreateInfo commandPoolTransferCI{}; + commandPoolTransferCI.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + commandPoolTransferCI.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + commandPoolTransferCI.queueFamilyIndex = qFamilyIndices.transferFamily.value(); + result = vkCreateCommandPool(device, &commandPoolTransferCI, nullptr, &commandPoolTransfer); + if (result != VK_SUCCESS) { + throw getVkException(result, "Failed to create transfer command pool", "createCommandPool"); + } + } + else { + commandPoolTransfer = commandPoolGraphics; + } +} + + +// +// COMMAND BUFFER +// +void VulkanInstance::createCommandBuffers(std::vector& commandBuffers) { + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); + + VkCommandBufferAllocateInfo commandBufferAI{}; + commandBufferAI.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + commandBufferAI.commandPool = commandPoolGraphics; + commandBufferAI.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + commandBufferAI.commandBufferCount = static_cast(commandBuffers.size()); + VkResult result = vkAllocateCommandBuffers(device, &commandBufferAI, commandBuffers.data()); + if (result != VK_SUCCESS) { + throw getVkException(result, "Failed to create command buffer", "createCommandBuffer"); + } +} + + + +void VulkanInstance::createSyncObjects() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + + VkSemaphoreCreateInfo semaphoreCI{}; + semaphoreCI.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkFenceCreateInfo fenceCI{}; + fenceCI.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceCI.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + VkResult result = vkCreateSemaphore(device, &semaphoreCI, nullptr, &imageAvailableSemaphores[i]); + if (result != VK_SUCCESS) { + throw getVkException(result, "Failed to create imageAvailableSemaphores", "createSyncObjects"); + } + result = vkCreateSemaphore(device, &semaphoreCI, nullptr, &renderFinishedSemaphores[i]); + if (result != VK_SUCCESS) { + throw getVkException(result, "Failed to create renderFinishedSemaphores", "createSyncObjects"); + } + result = vkCreateFence(device, &fenceCI, nullptr, &inFlightFences[i]); + if (result != VK_SUCCESS) { + throw getVkException(result, "Failed to create inFlightFences", "createSyncObjects"); + } + } +} + +// +// IMAGE UTILITY +// +void VulkanInstance::createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags memoryProperties, VkImage& image, VkDeviceMemory& imageMemory) { + VkImageCreateInfo imageCI{}; + imageCI.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageCI.imageType = VK_IMAGE_TYPE_2D; + imageCI.extent.width = static_cast(width); + imageCI.extent.height = static_cast(height); + imageCI.extent.depth = 1; + imageCI.mipLevels = 1; + imageCI.arrayLayers = 1; + imageCI.format = format; + // use linear when direct texel access is needed + imageCI.tiling = tiling; + imageCI.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageCI.usage = usage; + imageCI.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + imageCI.samples = VK_SAMPLE_COUNT_1_BIT; + /* imageCI.flags = 0; */ + VkResult result = vkCreateImage(device, &imageCI, nullptr, &image); + if (result != VK_SUCCESS) { + throw getVkException(result, "Failed to create image", "createTextureImage"); + } + + VkMemoryRequirements memoryRequirements; + vkGetImageMemoryRequirements(device, image, &memoryRequirements); + + VkMemoryAllocateInfo memoryAI{}; + memoryAI.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + memoryAI.allocationSize = memoryRequirements.size; + memoryAI.memoryTypeIndex = findMemoryType(memoryRequirements.memoryTypeBits, memoryProperties); + + result = vkAllocateMemory(device, &memoryAI, nullptr, &imageMemory); + if (result != VK_SUCCESS) { + throw getVkException(result, "Failed to allocate memory for image", "createTextureImage"); + } + + vkBindImageMemory(device, image, imageMemory, NO_OFFSET); +} + + +void VulkanInstance::copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) { + VkCommandBuffer cmdBuffer = beginSingleTimeCommands(commandPoolTransfer); + VkBufferImageCopy region{}; + region.bufferOffset = NO_OFFSET; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + + region.imageOffset = { 0, 0, 0 }; + region.imageExtent = { width, height, 1 }; + const uint32_t regionCount = 1; + vkCmdCopyBufferToImage(cmdBuffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, regionCount, ®ion); + + endSingleTimeCommands(cmdBuffer, commandPoolTransfer, transferQ); +} + + +void VulkanInstance::copyImageToImage(VkCommandBuffer& cmdBuffer, VkImage srcImage, VkImage dstImage, uint32_t width, uint32_t height) { + VkImageCopy region{}; + region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.srcSubresource.mipLevel = 0; + region.srcSubresource.baseArrayLayer = 0; + region.srcSubresource.layerCount = 1; + + region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.dstSubresource.mipLevel = 0; + region.dstSubresource.baseArrayLayer = 0; + region.dstSubresource.layerCount = 1; + + /* std::array offsets; */ + /* offsets[0] = { 0, 0, 0 }; */ + /* offsets[1] = { 0, 0, 0 }; */ + region.srcOffset = { 0, 0, 0 }; + region.dstOffset = { 0, 0, 0 }; + region.extent = { width, height, 1 }; + const uint32_t regionCount = 1; + vkCmdCopyImage(cmdBuffer, srcImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dstImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, regionCount, ®ion); +} + + +bool hasStencilComponent(VkFormat format) { + return format == VK_FORMAT_D32_SFLOAT_S8_UINT or format == VK_FORMAT_D24_UNORM_S8_UINT; +} + + +void VulkanInstance::transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout, VkCommandBuffer* cmdBuffer) { + + VkImageMemoryBarrier barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.oldLayout = oldLayout; + barrier.newLayout = newLayout; + // not using barrier for queue famlily ownership transfer + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + barrier.srcAccessMask = 0; + barrier.dstAccessMask = 0; + + VkPipelineStageFlags srcStage; + VkPipelineStageFlags dstStage; + VkCommandPool commandPool; + VkQueue q; + if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED and + newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + if (hasStencilComponent(format)) { + barrier.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; + } + + srcStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + dstStage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + commandPool = commandPoolGraphics; + q = graphicsQ; + } + else if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED and + newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + + srcStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + dstStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + commandPool = commandPoolTransfer; + q = transferQ; + } + else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL and + newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + srcStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + dstStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + commandPool = commandPoolGraphics; + q = graphicsQ; + } + else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL and + newLayout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR) { + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT; + + srcStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + dstStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + commandPool = commandPoolGraphics; + q = graphicsQ; + } + else { + throw VkUserError(std::string("transitionImageLayout: Unsupported layout transition") + STR_VK_IMAGE_LAYOUT(oldLayout) + "->" + STR_VK_IMAGE_LAYOUT(newLayout), "transitionImageLayout");; + } + // if not provided, get a single time command buffer + VkCommandBuffer cmdBuffer_; + if (cmdBuffer == nullptr) { + cmdBuffer_ = beginSingleTimeCommands(commandPool); + } + else { + cmdBuffer_ = *cmdBuffer; + } + uint32_t memBarrierCount = 0, bufBarrierCount = 0; + uint32_t imBarrierCount = 1; + vkCmdPipelineBarrier(cmdBuffer_, srcStage, dstStage, NO_FLAGS, + memBarrierCount, nullptr, + bufBarrierCount, nullptr, + imBarrierCount, &barrier); + + if (cmdBuffer == nullptr) { + endSingleTimeCommands(cmdBuffer_, commandPool, q); + } +} + + +// +// DEPTH +// +VkFormat VulkanInstance::findDepthFormat() { + return findSupportedFormat({ VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT }, VK_IMAGE_TILING_OPTIMAL, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT); +} +void VulkanInstance::createDepthImageAndView() { + VkMemoryPropertyFlags memoryProperties = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + VkImageUsageFlags imageUsage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + VkImageTiling tiling = VK_IMAGE_TILING_OPTIMAL; + VkFormat depthFormat = findDepthFormat(); + + createImage(scExtent.width, scExtent.height, depthFormat, tiling, imageUsage, memoryProperties, depthImage, depthImageMemory); + createImageView(depthFormat, depthImage, depthImageView, VK_IMAGE_ASPECT_DEPTH_BIT); + + transitionImageLayout(depthImage, depthFormat, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); + + +} + +// +// TEXTURE +// +void VulkanInstance::createTextureImageAndView() { + int textureWidth, textureHeight, textureChannels; + stbi_uc* pixels = stbi_load(TEXTURE_PATH.c_str(), &textureWidth, &textureHeight, &textureChannels, STBI_rgb_alpha); + if (!pixels) { + throw Exception("Failed to load texture image", "createTextureImage"); + } + + const size_t BYTES_PER_PIXEL = 4; + VkDeviceSize imageSize = textureWidth * textureHeight * BYTES_PER_PIXEL; + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + stagingBuffer, stagingBufferMemory); + void* data; + vkMapMemory(device, stagingBufferMemory, NO_OFFSET, imageSize, NO_FLAGS, &data); + memcpy(data, pixels, static_cast(imageSize)); + vkUnmapMemory(device, stagingBufferMemory); + + stbi_image_free(pixels); + createImage(textureWidth, textureHeight, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_TILING_OPTIMAL, + VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + textureImage, textureImageMemory); + + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + copyBufferToImage(stagingBuffer, textureImage, static_cast(textureWidth), static_cast(textureHeight)); + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); + + createImageView(VK_FORMAT_R8G8B8A8_SRGB, textureImage, textureImageView, VK_IMAGE_ASPECT_COLOR_BIT); +} + + +void VulkanInstance::createTextureSampler() { + VkSamplerCreateInfo samplerCI{}; + samplerCI.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + // TODO: LINEAR or NEAREST + samplerCI.magFilter = VK_FILTER_LINEAR; + samplerCI.minFilter = VK_FILTER_LINEAR; + samplerCI.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerCI.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerCI.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerCI.anisotropyEnable = bool2VkBool(settings.get("anisotropy_enable")); + samplerCI.maxAnisotropy = settings.get("max_anisotropy"); + samplerCI.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; + // TODO + samplerCI.unnormalizedCoordinates = VK_FALSE; + samplerCI.compareEnable = VK_FALSE; + samplerCI.compareOp = VK_COMPARE_OP_ALWAYS; + samplerCI.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + samplerCI.mipLodBias = 0.0f; + samplerCI.minLod = 0.0f; + samplerCI.maxLod = 0.0f; + + VkResult result = vkCreateSampler(device, &samplerCI, nullptr, &textureSampler); + if (result != VK_SUCCESS) { + throw getVkException(result, "Failed to create texture sampler.", "createTextureSampler"); + } + + +} + + +// +// SHADER MODULE +// +VkShaderModule VulkanInstance::createShaderModule(const std::vector& code) { + VkShaderModuleCreateInfo smCreateInfo{}; + smCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + smCreateInfo.codeSize = code.size(); + smCreateInfo.pCode = reinterpret_cast(code.data()); + + VkShaderModule shaderModule{}; + + VkResult result = vkCreateShaderModule(device, &smCreateInfo, nullptr, &shaderModule); + if (result != VK_SUCCESS) { + throw getVkException(result, "Failed to create shader module. Code size: " + std::to_string(code.size()), "createShaderModule"); + } + return shaderModule; +} + + +// +// SHADERS +// +std::vector VulkanInstance::readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + std::vector buffer; + if (file.is_open()) { + size_t fileSize = file.tellg(); + buffer.resize(fileSize); + file.seekg(0); + file.read(buffer.data(), fileSize); + + } + else { + vLog.error("readFile: Failed to read file:", filename); + } + file.close(); + + return buffer; +} + + +// +// MODELS +// +void VulkanInstance::loadModel() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warnings, errors; + if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &warnings, &errors, MODEL_PATH.c_str())) { + vLog.warning("Warning from tinyobj::LoadObj: ", warnings); + throw gz::Exception("Error loading obj: " + errors, "loadModel"); + } + if (!warnings.empty()) { + vLog.warning("Warning from tinyobj::LoadObj: ", warnings); + } + std::unordered_map uniqueVertices; + for (const auto& shape : shapes) { + for (const auto& index : shape.mesh.indices) { + Vertex3D vertex{}; + vertex.pos = { + attrib.vertices[3 * index.vertex_index + 0], + attrib.vertices[3 * index.vertex_index + 1], + attrib.vertices[3 * index.vertex_index + 2] + }; + vertex.texCoord = { + attrib.vertices[2 * index.texcoord_index + 0], + // obj: y = 0 means bottom, vulkan: y = 0 means top + 1.0f - attrib.vertices[2 * index.texcoord_index + 1], + }; + vertex.color = { 1.0f, 1.0f, 1.0f }; + if (!uniqueVertices.contains(vertex)) { + uniqueVertices[vertex] = model.vertices.size(); + model.vertices.push_back(vertex); + } + /* model.vertices.push_back(vertex); */ + model.indices.push_back(uniqueVertices[vertex]); + } + } +} + + +// +// DEBUG +// +void VulkanInstance::setupDebugMessenger() { + // get the address of the vkCreateDebugUtilsMessengerEXT function + VkResult result = runVkResultFunction("vkCreateDebugUtilsMessengerEXT", instance, &debugUtilsMessengerCI, nullptr, &debugMessenger); + if (result != VK_SUCCESS) { + throw getVkException(result, "Failed to initialise debug messenger", "setupDebugMessenger"); + } + /* auto f = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT")); */ + /* if (f == nullptr) { throw std::runtime_error("Failed to initialise debug messenger."); } */ + /* else { */ + /* f(instance, &debugUtilsMessengerCreateInfo, nullptr, &debugMessenger); */ + /* } */ +} + + + +void VulkanInstance::cleanupDebugMessenger() { + runVkVoidFunction("vkDestroyDebugUtilsMessengerEXT", instance, debugMessenger, nullptr); + /* throw std::runtime_error("Failed to destroy debug messenger."); */ +} + + +// +// APP +// +void VulkanInstance::createWindow() { + glfwInit(); + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, frameBufferResizedCallback); +} + + +void VulkanInstance::init() { + createWindow(); + if (glfwVulkanSupported() == GLFW_FALSE) { + throw std::runtime_error("Vulkan not supported."); + } + createInstance(); + setupDebugMessenger(); + createSurface(); + selectPhysicalDevice(); + setValidSettings(); + createLogicalDevice(); + + createSwapChain(); + createImageViews(); + createRenderPassBegin(); + createRenderPassEnd(); + /* createDescriptorSetLayout(); */ + // after descriptorSetLayout + createGraphicsPipeline("shaders/vert2D.spv", "shaders/frag2D.spv", false, renderPassBegin, pipelines[PL_2D]); + /* createGraphicsPipeline("shaders/vert.spv", "shaders/frag.spv", true, renderPass1, pipelines[PL_3D]); */ + createCommandPools(); + createDepthImageAndView(); // after CommandPools + createTextureImageAndView(); // after CommandPools + /* createFramebuffers(); // after depth image */ + createTextureSampler(); + loadModel(); + /* createVertexBuffer(VERTEX_BUFFER_SIZE); */ + /* createIndexBuffer(INDEX_BUFFER_SIZE); */ + /* createUniformBuffers(); */ + createCommandBuffers(commandBuffersBegin); + createCommandBuffers(commandBuffersEnd); + createSyncObjects(); + /* createDescriptorPool(); */ + /* createDescriptorSets(); // after UniformBuffers */ +} + + +void VulkanInstance::cleanup() { + vkDeviceWaitIdle(device); + + vkDestroyImageView(device, textureImageView, nullptr); + vkDestroySampler(device, textureSampler, nullptr); + vkDestroyImage(device, textureImage, nullptr); + vkFreeMemory(device, textureImageMemory, nullptr); + + cleanupSwapChain(); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroyFence(device, inFlightFences[i], nullptr); + } + + /* vkFreeCommandBuffers(device, commandPoolGraphics, static_cast(commandBuffers2D.size()), commandBuffers2D.data()); */ + /* vkFreeCommandBuffers(device, commandPoolGraphics, static_cast(commandBuffers3D.size()), commandBuffers3D.data()); */ + + vkDestroyCommandPool(device, commandPoolGraphics, nullptr); + vkDestroyCommandPool(device, commandPoolTransfer, nullptr); + + vkDestroyDevice(device, nullptr); + vkDestroySurfaceKHR(instance, surface, nullptr); + cleanupDebugMessenger(); + vkDestroyInstance(instance, nullptr); + glfwDestroyWindow(window); + glfwTerminate(); +} + + +// +// APP +// +void VulkanInstance::mainLoop() { + + vkDeviceWaitIdle(device); +} + + +uint32_t VulkanInstance::beginFrameDraw() { + vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX); + + uint32_t imageIndex; + uint32_t blockFor = 0; + /* uint64_t blockFor = UINT64_MAX; */ + VkResult result = vkAcquireNextImageKHR(device, swapChain, blockFor, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + if (result == VK_ERROR_OUT_OF_DATE_KHR or frameBufferResized) { // result == VK_SUBOPTIMAL_KHR or + vLog("drawFrame: result:", STR_VK_RESULT(result), "frameBufferResized:", frameBufferResized); + frameBufferResized = false; + recreateSwapChain(); + return imageIndex; + } + else if (result != VK_SUBOPTIMAL_KHR and result != VK_SUCCESS) { + vLog.error("Failed to acquire swap chain image. Result:", STR_VK_RESULT(result)); + throw std::runtime_error("Failed to acquire swap chain image"); + } + + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + // clear image + vkResetCommandBuffer(commandBuffersBegin[currentFrame], NO_FLAGS); + VkCommandBufferBeginInfo commandBufferBI{}; + commandBufferBI.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + result = vkBeginCommandBuffer(commandBuffersBegin[currentFrame], &commandBufferBI); + if (result != VK_SUCCESS) { + throw getVkException(result, "Failed to begin clear command buffer", "beginFrameDraw"); + } + // transition to transfer dst layout + transitionImageLayout(scImages[imageIndex], scImageFormat, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &commandBuffersBegin[currentFrame]); + /* VkRenderPassBeginInfo renderPassBI{}; */ + /* renderPassBI.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; */ + /* renderPassBI.renderPass = renderPassBegin; */ + /* renderPassBI.framebuffer = scFramebuffers[imageIndex]; */ + /* renderPassBI.renderArea.offset = { 0, 0 }; */ + /* renderPassBI.renderArea.extent = scExtent; */ + /* std::array clearValues{}; */ + /* clearValues[0].color = {{0.0f, 0.0f, 0.0f, 1.0f}}; */ + /* clearValues[1].depthStencil = {1.0f, 0}; */ + /* renderPassBI.clearValueCount = static_cast(clearValues.size()); */ + /* renderPassBI.pClearValues = clearValues.data(); */ + + /* vkCmdBeginRenderPass(commandBuffersBegin[imageIndex], &renderPassBI, VK_SUBPASS_CONTENTS_INLINE); */ + /* vkCmdEndRenderPass(commandBuffersBegin[imageIndex]); */ + result = vkEndCommandBuffer(commandBuffersBegin[currentFrame]); + if (result != VK_SUCCESS) { + vLog.error("Failed to record clear command buffer", "VkResult:", STR_VK_RESULT(result)); + throw getVkException(result, "Failed to record 2D - command buffer", "beginFrameDraw"); + } + commandBuffersToSubmitThisFrame.push_back(commandBuffersBegin[currentFrame]); + + return imageIndex; +} + + +void VulkanInstance::endFrameDraw(uint32_t imageIndex) { + vkResetCommandBuffer(commandBuffersEnd[currentFrame], NO_FLAGS); + VkCommandBufferBeginInfo commandBufferBI{}; + commandBufferBI.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + VkResult result = vkBeginCommandBuffer(commandBuffersEnd[currentFrame], &commandBufferBI); + if (result != VK_SUCCESS) { + throw getVkException(result, "Failed to begin command buffer", "beginFrameDraw"); + } + // transition to present layout + transitionImageLayout(scImages[imageIndex], scImageFormat, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, &commandBuffersEnd[currentFrame]); + /* VkRenderPassBeginInfo renderPassBI{}; */ + /* renderPassBI.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; */ + /* renderPassBI.renderPass = renderPassEnd; */ + /* renderPassBI.framebuffer = scFramebuffers[imageIndex]; */ + /* renderPassBI.renderArea.offset = { 0, 0 }; */ + /* renderPassBI.renderArea.extent = scExtent; */ + + /* vkCmdBeginRenderPass(commandBuffersEnd[imageIndex], &renderPassBI, VK_SUBPASS_CONTENTS_INLINE); */ + /* vkCmdEndRenderPass(commandBuffersEnd[imageIndex]); */ + result = vkEndCommandBuffer(commandBuffersEnd[currentFrame]); + if (result != VK_SUCCESS) { + vLog.error("Failed to record clear command buffer", "VkResult:", STR_VK_RESULT(result)); + throw getVkException(result, "Failed to record command buffer", "beginFrameDraw"); + } + commandBuffersToSubmitThisFrame.push_back(commandBuffersEnd[currentFrame]); + + + VkSubmitInfo submitI{}; + submitI.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + + VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]}; + VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; + submitI.waitSemaphoreCount = 1; + submitI.pWaitSemaphores = waitSemaphores; + submitI.pWaitDstStageMask = waitStages; + submitI.commandBufferCount = static_cast(commandBuffersToSubmitThisFrame.size()); + submitI.pCommandBuffers = commandBuffersToSubmitThisFrame.data(); + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; + submitI.signalSemaphoreCount = 1; + submitI.pSignalSemaphores = signalSemaphores; + + uint32_t submitCount = 1; + result = vkQueueSubmit(graphicsQ, submitCount, &submitI, inFlightFences[currentFrame]); + if (result != VK_SUCCESS) { + vLog.error("Failed to submit draw command buffer", "VkResult:", STR_VK_RESULT(result)); + throw std::runtime_error("Failed to submit draw command buffer"); + } + commandBuffersToSubmitThisFrame.clear(); + + + VkPresentInfoKHR presentI{}; + presentI.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + presentI.waitSemaphoreCount = 1; + presentI.pWaitSemaphores = signalSemaphores; + vLog("endFrameDraw:: imageIndex:", imageIndex); + + VkSwapchainKHR swapChains[] = {swapChain}; + presentI.swapchainCount = 1; + presentI.pSwapchains = swapChains; + presentI.pImageIndices = &imageIndex; + presentI.pResults = nullptr; + vkQueuePresentKHR(presentQ, &presentI); + + currentFrame = ++currentFrame % MAX_FRAMES_IN_FLIGHT; +} + +} // namespace gz::vk + diff --git a/vulkan_instance.hpp b/vulkan_instance.hpp new file mode 100644 index 0000000..d469501 --- /dev/null +++ b/vulkan_instance.hpp @@ -0,0 +1,487 @@ +#pragma once + +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#include + +#define GLFW_INCLUDE_VULKAN +#include + +#include "vk_convert.hpp" + +#include "vertex.hpp" +#include "shape.hpp" +#include "vulkan_util.hpp" + + +#include +#include +#include + +#include + +#include +#include + +namespace gz::vk { +const std::string CONFIG_FILE = "vulkan.conf"; +const int MAX_FRAMES_IN_FLIGHT = 3; +#define SettingsTypes uint32_t, bool, float + +/* const std::string MODEL_PATH = "models/gazebo-3d-model/gazebo.obj"; */ +/* const std::string TEXTURE_PATH = "models/gazebo-3d-model/gazebo_diffuse.png"; */ + +const std::string MODEL_PATH = "models/armoire-3d-model/Armoire.obj"; +const std::string TEXTURE_PATH = "models/armoire-3d-model/Armoire_diffuse.png"; + +const gz::util::unordered_string_map INITIAL_SETTINGS = { + { "framerate", "60" }, + { "anisotropy_enable", "false" }, + { "max_anisotropy", "1" }, + /* { "", "" } */ +}; + + +const int BINDING = 0; +const uint32_t NO_FLAGS = 0; +const uint32_t NO_OFFSET = 0; +const size_t VERTEX_BUFFER_SIZE = 512; +const size_t INDEX_BUFFER_SIZE = 512; + class VulkanInstance { + friend class Renderer; + friend class Renderer2D; + friend class Renderer3D; + public: + VulkanInstance(gz::SettingsManagerCreateInfosmCI) : settings(smCI) {}; + void init(); + uint32_t beginFrameDraw(); + void submitThisFrame(VkCommandBuffer); + void endFrameDraw(uint32_t imageIndex); + void deInit(); + GLFWwindow* window; + void cleanup(); + /// The frame in the swap chain that is currently drawn to + uint32_t currentFrame = 0; + // + // SETTINGS + // + gz::SettingsManager settings; + private: + void mainLoop(); + std::vector commandBuffersToSubmitThisFrame; + + void createWindow(); + + + // + // INSTANCE + // + VkInstance instance; + /** + * @brief Create the vulkan instance + * @details + * - check if validationLayers are available (if enabled) + * - create instance with info + * - check if all extensions required by glfw are available + */ + void createInstance(); + + // + // PHYSICAL DEVICE + // + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkPhysicalDeviceProperties phDevProperties; + VkPhysicalDeviceFeatures phDevFeatures; + /** + * @brief Assign the physicalDevice handle to the @ref rateDevice "best rated" GPU + * @details + * After this method, physicalDevice, phDevProperties and phDevFeatures will be initialized + */ + void selectPhysicalDevice(); + /** + * @brief Assigns a score to a physical device. + * @details + * score = GPU_TYPE_FACTOR + VRAM_SIZE (in MB) + SUPPORTED_QUEUES_FACTOR + FEATURES_FACTORS + * A GPU is unsuitable and gets score 0 if it does not: + * - have the needed queue families + * - support all necessary extensions + * - have swap chain support + */ + unsigned int rateDevice(VkPhysicalDevice device); + /** + * @brief Set valid values for the SettingsManager according to phDevFeatures and phDevProperties + * @details + * Must be called after selectPhysicalDevice + * Sets valid values for: + * - anisotropy_enable + * - max_anisotropy + */ + void setValidSettings(); + /** + * @brief Find the best of the supported formats + * @param candidates Candidate format, from best to worst + * @returns The first format from candidates that is supported + */ + VkFormat findSupportedFormat(const std::vector& candidates, VkImageTiling tiling, VkFormatFeatureFlags features); + + // + // QUEUE + // + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device); + QueueFamilyIndices qFamilyIndices; + VkQueue graphicsQ; + VkQueue presentQ; + // if possible, use dedicated transferQ. if not available, transferQ = graphicsQ + VkQueue transferQ; + // + // LOGICAL DEVICE + // + VkDevice device; + /** + * @details + * request anisotropic sampling feature + */ + void createLogicalDevice(); + + // + // SURFACE + // + VkSurfaceKHR surface; + void createSurface(); + // + // SWAP CHAIN + // + VkSwapchainKHR swapChain; + std::vector scImages; + VkFormat scImageFormat; + VkExtent2D scExtent; + + SwapChainSupport querySwapChainSupport(VkPhysicalDevice device); + /** + * @todo Rate formats if preferred is not available + */ + VkSurfaceFormatKHR selectSwapChainSurfaceFormat(const std::vector& availableFormats); + /** + * @todo Check settings for preferred mode + */ + VkPresentModeKHR selectSwapChainPresentMode(const std::vector& availableModes); + VkExtent2D selectSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities); + void createSwapChain(); + void recreateSwapChain(); + void cleanupSwapChain(); + // + // IMAGE VIEW + // + std::vector scImageViews; + void createImageViews(); + /** + * @brief Create a 2D imageView with format for image. + */ + void createImageView(VkFormat format, VkImage& image, VkImageView& imageView, VkImageAspectFlags aspectFlags); + // + // RENDER PASS + // + VkRenderPass renderPassBegin; + VkRenderPass renderPassEnd; + /** + * @brief Create the render pass + * @details + * The subpass will contain the following attachments + * -# color attachment + * -# depth stencil attachment + */ + void createRenderPassBegin(); + void createRenderPassEnd(); + // + // DESCRIPTORS + // + /** + * @name Create desciptors + * @details These functions create a desciptor with bindings for + * -# UniformBufferObject (DESCRIPTOR_TYPE_UNIFORM_BUFFER) + * -# Combined Image Sampler (DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER) + * @{ + */ + VkDescriptorSetLayout descriptorSetLayout; + /** + * @brief Create a descriptor layout binding for the MVP uniform buffer object + */ + void createDescriptorSetLayout(); + VkDescriptorPool descriptorPool; + /** + * @brief Create a descriptor layout binding for the MVP uniform buffer object + * @details Create a desciptor set layout with bindings for + * -# UniformBufferObject (DESCRIPTOR_TYPE_UNIFORM_BUFFER) + * -# Combined Image Sampler (DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER) + */ + void createDescriptorPool(); + std::vector descriptorSets; + /* + * @bried Create one descriptor set with layout descriptorSetLayout for each frame + */ + void createDescriptorSets(); + const uint32_t bindingUniformBuffer = 0; + const uint32_t bindingCombinedImageSampler = 1; + /* const uint32_t bindingCombinedImageSampler = 1; */ + + /** + * @} + */ + // + // PIPELINE + // + + PipelineContainer pipelines; + /* void createGraphicsPipeline(); */ + VkShaderModule createShaderModule(const std::vector& code); + /** + * @brief Create a new graphics pipeline + * @param vertexShader Path to the SPIR-V vertex shader + * @param fragmentShader Path to the SPIR-V fragment shader + * @param useDepthStencil Wether to use depth // TODO + * @param renderPass The (already created) render pass + * @param pipelineLayout Pipeline layout handle to bind + * @param pipeline Pipeline handle to bind + * @details + * Create a pipeline with: + * - 2 shader stages: vertex and fragment shader + * - VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, vertices are triangles + * - viewport viewing the whole image as described by scExtent + * - scissor with offset (0, 0) + * - rasterizer: + * - triangles are filled with the colors from the vertex (VK_POLYGON_FILL) + * - counter clockwise front face (VK_FRONT_FACE_COUNTER_CLOCKWISE) + * + * + */ + template + void createGraphicsPipeline(const std::string& vertexShader, const std::string& fragmentShader, bool useDepthStencil, VkRenderPass& renderPass, Pipeline& pipeline); + // + // FONT + // + // TODO + /* VkPipeline fontPipeline; */ + /* VkPipelineLayout fontPipelineLayout; */ + /* void createFontPipeline(); */ + // + // BUFFERS + // + void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory, VkSharingMode sharingMode=VK_SHARING_MODE_EXCLUSIVE, std::vector* qFamiliesWithAccess=nullptr); + uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties); + /** + * @brief Begin a command buffer that is going to be used once + * @param commandPool: The command pool from which the buffer should be allocated + */ + VkCommandBuffer beginSingleTimeCommands(VkCommandPool commandPool); + /** + * @brief Submit cmdBuffer on the queue and free it afterwards + * @param cmdBuffer: Command buffer returned by beginSingleTimeCommands() + * @param q: The queue belonging to the commandPool parameter from beginSingleTimeCommands() + */ + void endSingleTimeCommands(VkCommandBuffer cmdBuffer, VkCommandPool commandPool, VkQueue q); + /** + * @brief Copy from srcBuffer to dstBuffer + */ + void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size); + // + // VERTEX BUFFER + // + /** + * @brief Create a vertex buffer + * @param vertexCount Number of vertices the buffer should contain + * @todo Basically the same as createIndexBuffer + */ + template + void createVertexBuffer(size_t vertexCount, VkBuffer& vertexBuffer, VkDeviceMemory& vertexBufferMemory, VkDeviceSize& vertexBufferSize); + // + // INDEX BUFFER + // + template + void createIndexBuffer(size_t indexCount, VkBuffer& indexBuffer, VkDeviceMemory& indexBufferMemory, VkDeviceSize& indexBufferSize); + // + // UNIFORM BUFFER + // + template + void createUniformBuffers(std::vector& uniformBuffers, std::vector& uniformBuffersMemory); + /* void createUniformBuffers(); */ + // + // FRAMEBUFFERS + // + /* std::vector scFramebuffers; */ + std::set framebufferIDs; + // render pass is need for recreation of framebuffer + std::unordered_map, VkRenderPass>> scFramebuffers; + /** + * @brief Create a vector of framebuffers and return the id as a handle + * @returns id that can be used with getFramebuffers and destroyFramebuffers + */ + int createFramebuffers(std::vector& imageViews, VkRenderPass& renderPass); + /** + * @brief Destroy the framebuffers from id + */ + void destroyFramebuffers(int id); + void createFramebuffers_(std::vector& framebuffers, std::vector& imageViews, VkRenderPass& renderPass); + /** + * @brief Recreate all framebuffers in scFramebuffers + */ + void recreateFramebuffers(); + /** + * @brief Get the framebuffer vector to id + */ + std::vector& getFramebuffers(int id); + bool frameBufferResized = false; + static void frameBufferResizedCallback(GLFWwindow* window, int width, int height); + // + // COMMAND POOL + // + VkCommandPool commandPoolGraphics; + VkCommandPool commandPoolTransfer; + void createCommandPools(); + // + // COMMAND BUFFER + // + void createCommandBuffers(std::vector& commandBuffers); + void destroyCommandBuffers(std::vector& commandBuffers); + std::vector commandBuffersBegin; + std::vector commandBuffersEnd; + // + // IMAGE UTILITY + // + void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags memoryProperties, VkImage& image, VkDeviceMemory& imageMemory); + void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height); + /** + * @todo make a version using vkCmdResolveImage for multisampled images + * @brief Copy srcImage to dstImage + * @details + * Both images must have: + * - same extent + * - mipLevel 0 + * - layerCount 1 + * + * Calls vkCmdBlitImage, but does NOT submit the commandBuffer + * @param cmdBuffer The command buffer where the command will be recorded to + * @param srcImage Image with layout TRANSFER_SRC_OPTIMAL + * @param dstImage Image with layout TRANSFER_DST_OPTIMAL + */ + void copyImageToImage(VkCommandBuffer& cmdBuffer, VkImage srcImage, VkImage dstImage, uint32_t width, uint32_t height); //, VkCommandPool& commandPool=commandPoolTransfer); + /** + * @brief Transition the layout of image from oldLayout to newLayout + * @details + * Supported transitions: + * - undefined -> depth stencil attachment (graphics q) + * - undefined -> transfer dst optimal (transfer q) + * - transfer dst optimal -> shader read only optimal (graphics q) + * - transfer dst optimal -> present src (graphics q) + * + * If you do not provide a command buffer, a command buffer from the indicated queue will be created and submitted. + * If you do provide a command buffer, the command is recorded but NOT submitted. + * @param cmdBuffer [Optional] The command buffer where the command will be recorded to + * @throws VkUserError if the transition is not supported. + */ + void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout, VkCommandBuffer* cmdBuffer=nullptr); + + // + // DEPTH + // + VkImage depthImage; + VkDeviceMemory depthImageMemory; + VkImageView depthImageView; + VkFormat findDepthFormat(); + void createDepthImageAndView(); + // + // TEXTURES + // + VkImage textureImage; + VkDeviceMemory textureImageMemory; + VkImageView textureImageView; + VkSampler textureSampler; + /// Load textures/image.png + void createTextureImageAndView(); + void createTextureSampler(); + + // + // SYNCHRONIZATION + // + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + void createSyncObjects(); + // + // SHADERS + // + static std::vector readFile(const std::string& filename); + // + // MODELS + // + void loadModel(); + VerticesAndIndices model; + // + // DEBUG + // + void setupDebugMessenger(); + void cleanupDebugMessenger(); + VkDebugUtilsMessengerEXT debugMessenger; + static gz::Log vLog; + public: + // + // UTILITY + // + /** + * @brief Log messages from validation layers with the Apps logger + * @details + * Using the static vLog to log vulkan messages with a prefix dependant on the messageType. + */ + static VKAPI_ATTR VkBool32 VKAPI_CALL debugLog( + VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverety, + VkDebugUtilsMessageTypeFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, + void* pUserData); + + + template + VkResult runVkResultFunction(const char* name, Args&&... args) { + auto f = reinterpret_cast(vkGetInstanceProcAddr(instance, name)); + if (f == nullptr) { + vLog.error("getVkFunction: Could not get function:", name); + throw std::runtime_error("getVkFunction: Could not get function."); + } + else { + return f(std::forward(args)...); + } + }; + template + void runVkVoidFunction(const char* name, Args&&... args) { + auto f = reinterpret_cast(vkGetInstanceProcAddr(instance, name)); + if (f == nullptr) { + vLog.error("getVkFunction: Could not get function:", name); + throw std::runtime_error("getVkFunction: Could not get function."); + } + else { + f(std::forward(args)...); + } + } + }; // VulkanInstance + +// +// IMPLEMENTATIONS +// + // + // UNIFORM BUFFERS + // + template + void VulkanInstance::createUniformBuffers(std::vector& uniformBuffers, std::vector& uniformBuffersMemory) { + VkDeviceSize bufferSize = sizeof(T); + + uniformBuffers.resize(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMemory.resize(MAX_FRAMES_IN_FLIGHT); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffers[i], uniformBuffersMemory[i]); + } +} + +} +/** + * @file Creating a vulkan instance + * @todo Write/get allocator for buffers + */ + diff --git a/vulkan_util.cpp b/vulkan_util.cpp new file mode 100644 index 0000000..3fce456 --- /dev/null +++ b/vulkan_util.cpp @@ -0,0 +1,21 @@ +#include "vulkan_util.hpp" +#include + + +namespace gz::vk { + void PipelineContainer::erase(const PipelineT& key, VkDevice& device, const VkAllocationCallbacks* pAllocator) { + vkDestroyPipeline(device, pipelines[key].pipeline, pAllocator); + vkDestroyPipelineLayout(device, pipelines[key].layout, pAllocator); + pipelines.erase(pipelines.find(key)); + } + PipelineContainer::iterator PipelineContainer::erase(const PipelineContainer::iterator& it, VkDevice& device, const VkAllocationCallbacks* pAllocator) { + vkDestroyPipeline(device, it->second.pipeline, pAllocator); + vkDestroyPipelineLayout(device, it->second.layout, pAllocator); + return pipelines.erase(it); + + } + +} // namespace gz::vk + + + diff --git a/vulkan_util.hpp b/vulkan_util.hpp new file mode 100644 index 0000000..b7301b0 --- /dev/null +++ b/vulkan_util.hpp @@ -0,0 +1,89 @@ +#include "vertex.hpp" + +#include +#include +#include + +namespace gz::vk { + enum PipelineT { + PL_3D, PL_2D + }; + struct Pipeline { + VkPipeline pipeline; + VkPipelineLayout layout; + void operator=(const Pipeline& other) = delete; + }; + + /** + * @brief Map holding pipelines + */ + class PipelineContainer { + public: + using iterator = std::unordered_map::iterator; + Pipeline& operator[](const PipelineT& key) { return pipelines[key]; } + /** + * @brief Destroy the pipeline+layout and then remove the handles + */ + void erase(const PipelineT& key, VkDevice& device, const VkAllocationCallbacks* pAllocator=nullptr); + iterator erase(const iterator& it, VkDevice& device, const VkAllocationCallbacks* pAllocator=nullptr); + iterator begin() { return pipelines.begin(); } + iterator end() { return pipelines.end(); } + size_t size() const { return pipelines.size(); } + private: + std::unordered_map pipelines; + + }; + + + template + concept SupportedIndexType = std::same_as or std::same_as; + + template + struct VerticesAndIndices { + std::vector vertices; + std::vector indices; + constexpr VkIndexType getIndexType() const { + if (std::same_as) { + return VK_INDEX_TYPE_UINT16; + } + else if (std::same_as) { + return VK_INDEX_TYPE_UINT32; + } + } + }; + + + struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + std::optional transferFamily; + + bool hasNecessaryValues() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } + bool hasAllValues() { + return graphicsFamily.has_value() && presentFamily.has_value() && transferFamily.has_value(); + } + + std::string toString() const { + std::string s = "[ "; + s += "graphicsFamily: "; + if (graphicsFamily.has_value()) { s += std::to_string(graphicsFamily.value()); } + else { s += "not set"; } + s += ", presentFamily: "; + if (presentFamily.has_value()) { s += std::to_string(presentFamily.value()); } + else { s += "not set"; } + s += ", transferFamily: "; + if (transferFamily.has_value()) { s += std::to_string(transferFamily.value()); } + else { s += "not set"; } + return s + " ]"; + } + }; + + + struct SwapChainSupport { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; + }; +} // namespace gz::vk