diff --git a/config/Sources.cmake b/config/Sources.cmake index 80fa3a09d163edf3277eb69f91e7b10e57b72c5d..b8c2dcbdf87a08225cebefa3197277715845e961 100644 --- a/config/Sources.cmake +++ b/config/Sources.cmake @@ -7,9 +7,45 @@ set(vkcv_sources ${vkcv_include}/vkcv/Core.hpp ${vkcv_source}/vkcv/Core.cpp + ${vkcv_include}/vkcv/PassConfig.hpp + ${vkcv_source}/vkcv/PassConfig.cpp + + ${vkcv_source}/vkcv/PassManager.hpp + ${vkcv_source}/vkcv/PassManager.cpp + ${vkcv_include}/vkcv/Handles.hpp ${vkcv_source}/vkcv/Handles.cpp ${vkcv_include}/vkcv/Window.hpp ${vkcv_source}/vkcv/Window.cpp + + ${vkcv_include}/vkcv/SwapChain.hpp + ${vkcv_source}/vkcv/SwapChain.cpp + + ${vkcv_include}/vkcv/ShaderProgram.hpp + ${vkcv_source}/vkcv/ShaderProgram.cpp + + ${vkcv_include}/vkcv/PipelineConfig.hpp + ${vkcv_source}/vkcv/PipelineConfig.cpp + + ${vkcv_source}/vkcv/PipelineManager.hpp + ${vkcv_source}/vkcv/PipelineManager.cpp + + ${vkcv_include}/vkcv/CommandResources.hpp + ${vkcv_source}/vkcv/CommandResources.cpp + + ${vkcv_include}/vkcv/SyncResources.hpp + ${vkcv_source}/vkcv/SyncResources.cpp + + ${vkcv_include}/vkcv/QueueManager.hpp + ${vkcv_source}/vkcv/QueueManager.cpp + + ${vkcv_source}/vkcv/Surface.hpp + ${vkcv_source}/vkcv/Surface.cpp + + ${vkcv_source}/vkcv/ImageLayoutTransitions.hpp + ${vkcv_source}/vkcv/ImageLayoutTransitions.cpp + + ${vkcv_source}/vkcv/Framebuffer.hpp + ${vkcv_source}/vkcv/Framebuffer.cpp ) diff --git a/include/vkcv/CommandResources.hpp b/include/vkcv/CommandResources.hpp new file mode 100644 index 0000000000000000000000000000000000000000..05e848294935bcf3642e1712072acf607d153611 --- /dev/null +++ b/include/vkcv/CommandResources.hpp @@ -0,0 +1,12 @@ +#pragma once +#include <vulkan/vulkan.hpp> + +namespace vkcv { + struct CommandResources { + vk::CommandPool commandPool; + vk::CommandBuffer commandBuffer; + }; + + CommandResources createDefaultCommandResources(const vk::Device& device, const int graphicFamilyIndex); + void destroyCommandResources(const vk::Device& device, const CommandResources& resources); +} \ No newline at end of file diff --git a/include/vkcv/Core.hpp b/include/vkcv/Core.hpp index 7a7f3be7d61995462da1f65bd58af098b1dd1f4e..506e72f2363a791bff9aba59348ced3b6ac4c798 100644 --- a/include/vkcv/Core.hpp +++ b/include/vkcv/Core.hpp @@ -4,16 +4,27 @@ * @brief Handling of global states regarding dependencies */ +#include <memory> + #include <vulkan/vulkan.hpp> #include "vkcv/Context.hpp" +#include "vkcv/SwapChain.hpp" +#include "vkcv/Window.hpp" +#include "vkcv/PassConfig.hpp" #include "vkcv/Handles.hpp" +#include "vkcv/PipelineConfig.hpp" +#include "CommandResources.hpp" +#include "SyncResources.hpp" +#include "vkcv/QueueManager.hpp" namespace vkcv { // TODO: class Buffer; - class Renderpass; - class Pipeline; + + // forward declarations + class PassManager; + class PipelineManager; class Core final { @@ -24,16 +35,32 @@ namespace vkcv * * @param context encapsulates various Vulkan objects */ - explicit Core(Context &&context) noexcept; + Core(Context &&context, const Window &window, SwapChain swapChain, std::vector<vk::ImageView> imageViews, + const CommandResources& commandResources, const SyncResources& syncResources, const QueueManager &queues) noexcept; // explicit destruction of default constructor Core() = delete; + uint32_t acquireSwapchainImage(); + void destroyTemporaryFramebuffers(); + Context m_Context; + + SwapChain m_swapchain; + std::vector<vk::ImageView> m_swapchainImageViews; + const Window& m_window; + + std::unique_ptr<PassManager> m_PassManager; + std::unique_ptr<PipelineManager> m_PipelineManager; + CommandResources m_CommandResources; + SyncResources m_SyncResources; + QueueManager m_QueueManager; + uint32_t m_currentSwapchainImageIndex; + std::vector<vk::Framebuffer> m_TemporaryFramebuffers; public: /** * Destructor of #Core destroys the Vulkan objects contained in the core's context. */ - ~Core() noexcept = default; + ~Core() noexcept; /** * Copy-constructor of #Core is deleted! @@ -78,23 +105,59 @@ namespace vkcv * * @param[in] applicationName Name of the application * @param[in] applicationVersion Version of the application - * @param[in] queueCount (optional) Amount of queues which is requested * @param[in] queueFlags (optional) Requested flags of queues * @param[in] instanceExtensions (optional) Requested instance extensions * @param[in] deviceExtensions (optional) Requested device extensions * @return New instance of #Context */ - static Core create(const char *applicationName, + static Core create(const Window &window, + const char *applicationName, uint32_t applicationVersion, - uint32_t queueCount, std::vector<vk::QueueFlagBits> queueFlags = {}, std::vector<const char*> instanceExtensions = {}, std::vector<const char*> deviceExtensions = {}); + /** + * Creates a basic vulkan graphics pipeline using @p config from the pipeline config class and returns it using the @p handle. + * Fixed Functions for pipeline are set with standard values. + * + * @param config a pipeline config object from the pipeline config class + * @param handle a handle to return the created vulkan handle + * @return True if pipeline creation was successful, False if not + */ + [[nodiscard]] + PipelineHandle createGraphicsPipeline(const PipelineConfig &config); + + /** + * Creates a basic vulkan render pass using @p config from the render pass config class and returns it using the @p handle. + * Fixed Functions for pipeline are set with standard values. + * + * @param config a render pass config object from the render pass config class + * @param handle a handle to return the created vulkan handle + * @return True if render pass creation was successful, False if not + */ + [[nodiscard]] + PassHandle createPass(const PassConfig &config); + // TODO: BufferHandle createBuffer(const Buffer &buf); - PassHandle createRenderPass(const Renderpass &pass) ; - PipelineHandle createPipeline(const Pipeline &pipeline); + /** + * @brief start recording command buffers and increment frame index + */ + void beginFrame(); + + /** + * @brief render a beautiful triangle + */ + void renderTriangle(const PassHandle renderpassHandle, const PipelineHandle pipelineHandle, + const int width, const int height); + + /** + * @brief end recording and present image + */ + void endFrame(); + + vk::Format getSwapchainImageFormat(); }; } diff --git a/include/vkcv/PassConfig.hpp b/include/vkcv/PassConfig.hpp new file mode 100644 index 0000000000000000000000000000000000000000..b8e80c67c2b70e3a0e6e2732b950ccaed38da3bf --- /dev/null +++ b/include/vkcv/PassConfig.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include <vector> +#include <vulkan/vulkan.hpp> + +namespace vkcv +{ + enum class AttachmentLayout + { + UNDEFINED, + GENERAL, + + COLOR_ATTACHMENT, + SHADER_READ_ONLY, + + DEPTH_STENCIL_ATTACHMENT, + DEPTH_STENCIL_READ_ONLY, + + TRANSFER_SRC, + TRANSFER_DST, + + PRESENTATION + }; + + enum class AttachmentOperation + { + LOAD, + CLEAR, + STORE, + DONT_CARE + }; + + struct AttachmentDescription + { + AttachmentDescription() = delete; + AttachmentDescription( + AttachmentLayout initial, + AttachmentLayout in_pass, + AttachmentLayout final, + AttachmentOperation store_op, + AttachmentOperation load_op, + vk::Format format) noexcept; + + AttachmentLayout layout_initial; + AttachmentLayout layout_in_pass; + AttachmentLayout layout_final; + + AttachmentOperation store_operation; + AttachmentOperation load_operation; + + vk::Format format; + }; + + struct PassConfig + { + PassConfig() = delete; + explicit PassConfig(std::vector<AttachmentDescription> attachments) noexcept; + std::vector<AttachmentDescription> attachments{}; + }; +} \ No newline at end of file diff --git a/include/vkcv/PipelineConfig.hpp b/include/vkcv/PipelineConfig.hpp new file mode 100644 index 0000000000000000000000000000000000000000..1ad6be8d1979c8a89f7de9dbe24ff13b5f5bb3fa --- /dev/null +++ b/include/vkcv/PipelineConfig.hpp @@ -0,0 +1,43 @@ +/** + * @authors Mara Vogt, Mark Mints + * @file src/vkcv/Pipeline.hpp + * @brief Pipeline class to handle shader stages + */ + +#ifndef VKCV_PIPELINECONFIG_HPP +#define VKCV_PIPELINECONFIG_HPP + +#include <vector> +#include <cstdint> +#include "vkcv/Handles.hpp" +#include "ShaderProgram.hpp" + +namespace vkcv { + + class PipelineConfig { + + public: + /** + * Default constructer is deleted! + */ + PipelineConfig() = delete; + + /** + * Constructor for the pipeline. Creates a pipeline using @p vertexCode, @p fragmentCode as well as the + * dimensions of the application window @p width and @p height. A handle for the Render Pass is also needed, @p passHandle. + * + * @param shaderProgram shaders of the pipeline + * @param height height of the application window + * @param width width of the application window + * @param passHandle handle for Render Pass + */ + PipelineConfig(const ShaderProgram& shaderProgram, uint32_t width, uint32_t height, PassHandle &passHandle); + + ShaderProgram m_ShaderProgram; + uint32_t m_Height; + uint32_t m_Width; + PassHandle m_PassHandle; + }; + +} +#endif //VKCV_PIPELINECONFIG_HPP diff --git a/include/vkcv/QueueManager.hpp b/include/vkcv/QueueManager.hpp new file mode 100644 index 0000000000000000000000000000000000000000..1779fb669eadb67682bdca6610402ce0dcbacff5 --- /dev/null +++ b/include/vkcv/QueueManager.hpp @@ -0,0 +1,36 @@ +#pragma once +#include <vulkan/vulkan.hpp> + +namespace vkcv { + class QueueManager { + public: + static QueueManager create(vk::Device device, + std::vector<std::pair<int, int>> &queuePairsGraphics, + std::vector<std::pair<int, int>> &queuePairsCompute, + std::vector<std::pair<int, int>> &queuePairsTransfer); + + const vk::Queue &getPresentQueue() const; + + const std::vector<vk::Queue> &getGraphicsQueues() const; + + const std::vector<vk::Queue> &getComputeQueues() const; + + const std::vector<vk::Queue> &getTransferQueues() const; + + static void queueCreateInfosQueueHandles(vk::PhysicalDevice &physicalDevice, + std::vector<float> &queuePriorities, + std::vector<vk::QueueFlagBits> &queueFlags, + std::vector<vk::DeviceQueueCreateInfo> &queueCreateInfos, + std::vector<std::pair<int, int>> &queuePairsGraphics, + std::vector<std::pair<int, int>> &queuePairsCompute, + std::vector<std::pair<int, int>> &queuePairsTransfer); + + private: + vk::Queue m_presentQueue; + std::vector<vk::Queue> m_graphicsQueues; + std::vector<vk::Queue> m_computeQueues; + std::vector<vk::Queue> m_transferQueues; + + QueueManager(std::vector<vk::Queue> graphicsQueues, std::vector<vk::Queue> computeQueues, std::vector<vk::Queue> transferQueues, vk::Queue presentQueue); + }; +} diff --git a/include/vkcv/ShaderProgram.hpp b/include/vkcv/ShaderProgram.hpp new file mode 100644 index 0000000000000000000000000000000000000000..172e906fca457c6245855639275054514958b69d --- /dev/null +++ b/include/vkcv/ShaderProgram.hpp @@ -0,0 +1,60 @@ +#pragma once +/** + * @authors Simeon Hermann, Leonie Franken + * @file src/vkcv/ShaderProgram.hpp + * @brief ShaderProgram class to handle and prepare the shader stages for a graphics pipeline + */ + +#include <unordered_map> +#include <fstream> +#include <iostream> +#include <filesystem> +#include <vulkan/vulkan.hpp> + +namespace vkcv { + + enum class ShaderStage + { + VERTEX, + TESS_CONTROL, + TESS_EVAL, + GEOMETRY, + FRAGMENT, + COMPUTE + }; + + struct Shader + { + std::vector<char> shaderCode; + ShaderStage shaderStage; + }; + + class ShaderProgram + { + public: + ShaderProgram() noexcept; // ctor + ~ShaderProgram() = default; // dtor + + /** + * Adds a shader into the shader program. + * The shader is only added if the shader program does not contain the particular shader stage already. + * Contains: (1) reading of the code, (2) creation of a shader module, (3) creation of a shader stage, (4) adding to the shader stage list, (5) destroying of the shader module + * @param[in] flag that signals the respective shaderStage (e.g. VK_SHADER_STAGE_VERTEX_BIT) + * @param[in] relative path to the shader code (e.g. "../../../../../shaders/vert.spv") + */ + bool addShader(ShaderStage shaderStage, const std::filesystem::path &shaderPath); + + /** + * Returns the shader program's shader of the specified shader. + * Needed for the transfer to the pipeline. + * @return Shader object consisting of buffer with shader code and shader stage enum + */ + const Shader &getShader(ShaderStage shaderStage) const; + + bool existsShader(ShaderStage shaderStage) const; + + private: + std::unordered_map<ShaderStage, Shader> m_Shaders; + + }; +} diff --git a/include/vkcv/SwapChain.hpp b/include/vkcv/SwapChain.hpp new file mode 100644 index 0000000000000000000000000000000000000000..1087d6364f6f811b741904d4e2b31fcfeb450901 --- /dev/null +++ b/include/vkcv/SwapChain.hpp @@ -0,0 +1,69 @@ +#pragma once +#include "vulkan/vulkan.hpp" +#include "Context.hpp" +#include "vkcv/Window.hpp" + +namespace vkcv { + class SwapChain final { + private: + + vk::SurfaceKHR m_surface; + vk::SwapchainKHR m_swapchain; + vk::SurfaceFormatKHR m_format; + + uint32_t m_ImageCount; + + /** + * Constructor of a SwapChain object + * glfw is not initialized in this class because ist must be sure that there exists a context first + * glfw is already initialized by the window class + * @param surface used by the swapchain + * @param swapchain to show images in the window + * @param format + */ + SwapChain(vk::SurfaceKHR surface, vk::SwapchainKHR swapchain, vk::SurfaceFormatKHR format, uint32_t imageCount); + + public: + SwapChain(const SwapChain &other) = default; + SwapChain(SwapChain &&other) = default; + + /** + * @return The swapchain linked with the #SwapChain class + * @note The reference to our Swapchain variable is needed for the recreation step + */ + [[nodiscard]] + const vk::SwapchainKHR& getSwapchain() const; + + /** + * gets the current surface object + * @return current surface + */ + [[nodiscard]] + vk::SurfaceKHR getSurface(); + /** + * gets the current surface format + * @return gets the surface format + */ + [[nodiscard]] + vk::SurfaceFormatKHR getSurfaceFormat(); + + /** + * creates a swap chain object out of the given window and the given context + * @param window a wrapper that represents a glfw window + * @param context of the application + * @return returns an object of swapChain + */ + static SwapChain create(const Window &window, const Context &context, const vk::SurfaceKHR surface); + + /** + * Destructor of SwapChain + */ + virtual ~SwapChain(); + + /** + * @return number of images in swapchain + */ + uint32_t getImageCount(); + }; + +} diff --git a/include/vkcv/SyncResources.hpp b/include/vkcv/SyncResources.hpp new file mode 100644 index 0000000000000000000000000000000000000000..4456594722a30f73128a864714bf4690b2902525 --- /dev/null +++ b/include/vkcv/SyncResources.hpp @@ -0,0 +1,13 @@ +#pragma once +#include <vulkan/vulkan.hpp> + +namespace vkcv { + struct SyncResources { + vk::Semaphore renderFinished; + vk::Fence swapchainImageAcquired; + vk::Fence presentFinished; + }; + + SyncResources createDefaultSyncResources(const vk::Device& device); + void destroySyncResources(const vk::Device& device, const SyncResources& resources); +} \ No newline at end of file diff --git a/include/vkcv/Window.hpp b/include/vkcv/Window.hpp index 080e55350ba82ae58da8afcc8758da3fda77f19f..ef5e35cc10a9983440d3d33f7d8dd93a6aef5199 100644 --- a/include/vkcv/Window.hpp +++ b/include/vkcv/Window.hpp @@ -4,19 +4,17 @@ * @file src/vkcv/Window.hpp * @brief Window class to handle a basic rendering surface and input */ - -#define GLFW_INCLUDE_VULKAN #include <GLFW/glfw3.h> #define NOMINMAX #include <algorithm> namespace vkcv { - class Window final { private: GLFWwindow *m_window; + /** * * @param GLFWwindow of the class @@ -32,8 +30,7 @@ namespace vkcv { * @param[in] resizable resize ability of the window (optional) * @return Window class */ - static Window create(const char *windowTitle, int width = -1, int height = -1, bool resizable = false); - + static Window create( const char *windowTitle, int width = -1, int height = -1, bool resizable = false); /** * checks if the window is still open, or the close event was called * This function should be changed/removed later on @@ -54,20 +51,6 @@ namespace vkcv { [[nodiscard]] GLFWwindow *getWindow() const; - /** - * gets the current window width - * @return int with window width - */ - [[nodiscard]] - int getWidth() const; - - /** - * gets the current window height - * @return int with window height - */ - [[nodiscard]] - int getHeight() const; - /** * Copy-operator of #Window is deleted! * @@ -84,10 +67,25 @@ namespace vkcv { */ Window &operator=(Window &&other) = default; + /** + * gets the window width + * @param window glfwWindow + * @return int with window width + */ + [[nodiscard]] + int getWidth() const; + + /** + * gets the window height + * @param window glfwWindow + * @return int with window height + */ + [[nodiscard]] + int getHeight() const; + /** * Destructor of #Window, terminates GLFW */ virtual ~Window(); - }; } \ No newline at end of file diff --git a/projects/first_triangle/CMakeLists.txt b/projects/first_triangle/CMakeLists.txt index 40e016a71cd3e20690f3ace9ec0260aa37449a0c..b7a2b52fcde1ec4481bb450da233aa319709c829 100644 --- a/projects/first_triangle/CMakeLists.txt +++ b/projects/first_triangle/CMakeLists.txt @@ -5,6 +5,9 @@ project(first_triangle) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/shaders/vert.spv DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/shaders) +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/shaders/frag.spv DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/shaders) + # adding source files to the project add_executable(first_triangle src/main.cpp) diff --git a/projects/first_triangle/shaders/compile.bat b/projects/first_triangle/shaders/compile.bat new file mode 100644 index 0000000000000000000000000000000000000000..b4521235c40fe5fb163bab874560c2f219b7517f --- /dev/null +++ b/projects/first_triangle/shaders/compile.bat @@ -0,0 +1,3 @@ +%VULKAN_SDK%\Bin32\glslc.exe shader.vert -o vert.spv +%VULKAN_SDK%\Bin32\glslc.exe shader.frag -o frag.spv +pause \ No newline at end of file diff --git a/projects/first_triangle/shaders/frag.spv b/projects/first_triangle/shaders/frag.spv new file mode 100644 index 0000000000000000000000000000000000000000..cb13e606fc0041e24ff6a63c0ec7dcca466732aa Binary files /dev/null and b/projects/first_triangle/shaders/frag.spv differ diff --git a/projects/first_triangle/shaders/shader.frag b/projects/first_triangle/shaders/shader.frag new file mode 100644 index 0000000000000000000000000000000000000000..d26446a73020111695aa2c86166205796dfa5e44 --- /dev/null +++ b/projects/first_triangle/shaders/shader.frag @@ -0,0 +1,9 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable + +layout(location = 0) in vec3 fragColor; +layout(location = 0) out vec4 outColor; + +void main() { + outColor = vec4(fragColor, 1.0); +} \ No newline at end of file diff --git a/projects/first_triangle/shaders/shader.vert b/projects/first_triangle/shaders/shader.vert new file mode 100644 index 0000000000000000000000000000000000000000..1d278d5f41f803bb657a303dcc95ffcd2a92fd6e --- /dev/null +++ b/projects/first_triangle/shaders/shader.vert @@ -0,0 +1,21 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable + +layout(location = 0) out vec3 fragColor; + +void main() { + vec3 positions[3] = { + vec3(-0.5, 0.5, 0), + vec3( 0.5, 0.5, 0), + vec3(0, -0.5, 0) + }; + + vec3 colors[3] = { + vec3(1, 0, 0), + vec3(0, 1, 0), + vec3(0, 0, 1) + }; + + gl_Position = vec4(positions[gl_VertexIndex], 1.0); + fragColor = colors[gl_VertexIndex]; +} \ No newline at end of file diff --git a/projects/first_triangle/shaders/vert.spv b/projects/first_triangle/shaders/vert.spv new file mode 100644 index 0000000000000000000000000000000000000000..bd1e0e682c52e6e38a5f5aba4eeaf8e73a70d741 Binary files /dev/null and b/projects/first_triangle/shaders/vert.spv differ diff --git a/projects/first_triangle/src/main.cpp b/projects/first_triangle/src/main.cpp index 0c981a25dc52db4363d2bc7835df3c3c9d7c49fa..fb9d764c6f130c3436f9b76c903e39bcd68d3ac6 100644 --- a/projects/first_triangle/src/main.cpp +++ b/projects/first_triangle/src/main.cpp @@ -1,20 +1,27 @@ #include <iostream> #include <vkcv/Core.hpp> #include <vkcv/Window.hpp> +#include <vkcv/ShaderProgram.hpp> int main(int argc, const char** argv) { const char* applicationName = "First Triangle"; - vkcv::Window window = vkcv::Window::create( - applicationName, - 800, - 600, + + const int windowWidth = 800; + const int windowHeight = 600; + vkcv::Window window = vkcv::Window::create( + applicationName, + windowWidth, + windowHeight, false - ); + ); + vkcv::Core core = vkcv::Core::create( + window, applicationName, VK_MAKE_VERSION(0, 0, 1), - 20, - {vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eTransfer} + {vk::QueueFlagBits::eTransfer,vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute}, + {}, + {"VK_KHR_swapchain"} ); const auto &context = core.getContext(); @@ -33,6 +40,36 @@ int main(int argc, const char** argv) { default: std::cout << "Unknown GPU vendor?! Either you're on an exotic system or your driver is broken..." << std::endl; } + // an example attachment for passes that output to the window + const vkcv::AttachmentDescription present_color_attachment( + vkcv::AttachmentLayout::UNDEFINED, + vkcv::AttachmentLayout::COLOR_ATTACHMENT, + vkcv::AttachmentLayout::PRESENTATION, + vkcv::AttachmentOperation::STORE, + vkcv::AttachmentOperation::CLEAR, + core.getSwapchainImageFormat()); + + vkcv::PassConfig trianglePassDefinition({present_color_attachment}); + vkcv::PassHandle trianglePass = core.createPass(trianglePassDefinition); + + if (trianglePass.id == 0) + { + std::cout << "Error. Could not create renderpass. Exiting." << std::endl; + return EXIT_FAILURE; + } + + vkcv::ShaderProgram triangleShaderProgram{}; + triangleShaderProgram.addShader(vkcv::ShaderStage::VERTEX, std::filesystem::path("shaders/vert.spv")); + triangleShaderProgram.addShader(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("shaders/frag.spv")); + + const vkcv::PipelineConfig trianglePipelineDefinition(triangleShaderProgram, windowWidth, windowHeight, trianglePass); + vkcv::PipelineHandle trianglePipeline = core.createGraphicsPipeline(trianglePipelineDefinition); + if (trianglePipeline.id == 0) + { + std::cout << "Error. Could not create graphics pipeline. Exiting." << std::endl; + return EXIT_FAILURE; + } + /* * BufferHandle triangleVertices = core.createBuffer(vertices); * BufferHandle triangleIndices = core.createBuffer(indices); @@ -51,11 +88,9 @@ int main(int argc, const char** argv) { while (window.isWindowOpen()) { - // core.beginFrame(); or something like that - // core.execute(trianglePass, trianglePipeline, triangleModel); - // core.endFrame(); or something like that - - // TBD: synchronization + core.beginFrame(); + core.renderTriangle(trianglePass, trianglePipeline, windowWidth, windowHeight); + core.endFrame(); window.pollEvents(); } diff --git a/src/vkcv/CommandResources.cpp b/src/vkcv/CommandResources.cpp new file mode 100644 index 0000000000000000000000000000000000000000..451ec4f27b3bc68e6a787bb79d1dc12f59a086aa --- /dev/null +++ b/src/vkcv/CommandResources.cpp @@ -0,0 +1,23 @@ +#include "vkcv/CommandResources.hpp" + +namespace vkcv { + CommandResources createDefaultCommandResources(const vk::Device& device, const int graphicFamilyIndex) { + CommandResources resources; + vk::CommandPoolCreateFlags flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer; + vk::CommandPoolCreateInfo poolCreateInfo(flags, graphicFamilyIndex); + resources.commandPool = device.createCommandPool(poolCreateInfo, nullptr, {}); + + const int commandPoolCount = 1; + vk::CommandBufferAllocateInfo allocateInfo(resources.commandPool, vk::CommandBufferLevel::ePrimary, commandPoolCount); + const std::vector<vk::CommandBuffer> createdBuffers = device.allocateCommandBuffers(allocateInfo, {}); + assert(createdBuffers.size() == 1); + resources.commandBuffer = createdBuffers[0]; + + return resources; + } + + void destroyCommandResources(const vk::Device& device, const CommandResources& resources) { + device.freeCommandBuffers(resources.commandPool, resources.commandBuffer, {}); + device.destroyCommandPool(resources.commandPool, {}); + } +} \ No newline at end of file diff --git a/src/vkcv/Core.cpp b/src/vkcv/Core.cpp index 0c32f4e42d1a232a9b66d42e6a16f0c1eda06bbb..7e6d87ca71e294d4f7df9b6373a002e704c68325 100644 --- a/src/vkcv/Core.cpp +++ b/src/vkcv/Core.cpp @@ -1,13 +1,19 @@ /** - * @authors Sebastian Gaida - * @file src/vkcv/CoreManager.cpp + * @authors Artur Wasmut + * @file src/vkcv/Core.cpp * @brief Handling of global states regarding dependencies */ #include "vkcv/Core.hpp" +#include "PassManager.hpp" +#include "PipelineManager.hpp" +#include "Surface.hpp" +#include "ImageLayoutTransitions.hpp" +#include "Framebuffer.hpp" namespace vkcv { + /** * @brief The physical device is evaluated by three categories: * discrete GPU vs. integrated GPU, amount of queues and its abilities, and VRAM.physicalDevice. @@ -76,53 +82,6 @@ namespace vkcv return phyDevice; } - - /** - * @brief Creates a candidate list of queues that all meet the desired flags and then creates the maximum possible number - * of queues. If the number of desired queues is not sufficient, the remaining queues are created from the next - * candidate from the list. - * @param physicalDevice The physical device - * @param queueCount The amount of queues to be created - * @param qPriorities - * @param queueFlags The abilities which have to be supported by any created queue - * @return - */ - std::vector<vk::DeviceQueueCreateInfo> getQueueCreateInfos(vk::PhysicalDevice& physicalDevice, - uint32_t queueCount, - std::vector<float> &qPriorities, - std::vector<vk::QueueFlagBits>& queueFlags) - { - std::vector<vk::DeviceQueueCreateInfo> queueCreateInfos; - std::vector<vk::QueueFamilyProperties> qFamilyProperties = physicalDevice.getQueueFamilyProperties(); - std::vector<vk::QueueFamilyProperties> qFamilyCandidates; - - // search for queue families which support the desired queue flag bits - for (auto& qFamily : qFamilyProperties) { - bool supported = true; - for (auto qFlag : queueFlags) { - supported = supported && (static_cast<uint32_t>(qFlag & qFamily.queueFlags) != 0); - } - if (supported) { - qFamilyCandidates.push_back(qFamily); - } - } - - uint32_t create = queueCount; - for (uint32_t i = 0; i < qFamilyCandidates.size() && create > 0; i++) { - const uint32_t maxCreatableQueues = std::min(create, qFamilyCandidates[i].queueCount); - vk::DeviceQueueCreateInfo qCreateInfo( - vk::DeviceQueueCreateFlags(), - i, - maxCreatableQueues, - qPriorities.data() - ); - queueCreateInfos.push_back(qCreateInfo); - create -= maxCreatableQueues; - } - - return queueCreateInfos; - } - /** * @brief With the help of the reference "supported" all elements in "check" checked, * if they are supported by the physical device. @@ -146,14 +105,26 @@ namespace vkcv return true; } - Core Core::create(const char *applicationName, + + std::vector<const char*> getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + std::vector<const char*> extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + +#ifndef NDEBUG + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); +#endif + + return extensions; + } + + Core Core::create(const Window &window, + const char *applicationName, uint32_t applicationVersion, - uint32_t queueCount, std::vector<vk::QueueFlagBits> queueFlags, std::vector<const char *> instanceExtensions, std::vector<const char *> deviceExtensions) { - // check for layer support const std::vector<vk::LayerProperties>& layerProperties = vk::enumerateInstanceLayerProperties(); @@ -190,9 +161,9 @@ namespace vkcv throw std::runtime_error("The requested instance extensions are not supported!"); } -#ifndef NDEBUG - instanceExtensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); -#endif + // for GLFW: get all required extensions + std::vector<const char*> requiredExtensions = getRequiredExtensions(); + instanceExtensions.insert(instanceExtensions.end(), requiredExtensions.begin(), requiredExtensions.end()); const vk::ApplicationInfo applicationInfo( applicationName, @@ -231,35 +202,75 @@ namespace vkcv throw std::runtime_error("The requested device extensions are not supported by the physical device!"); } - //vector to define the queue priorities - std::vector<float> qPriorities; - qPriorities.resize(queueCount, 1.f); // all queues have the same priorities + const vk::SurfaceKHR surface = createSurface(window.getWindow(), instance, physicalDevice); + std::vector<vk::DeviceQueueCreateInfo> qCreateInfos; // create required queues - std::vector<vk::DeviceQueueCreateInfo> qCreateInfos = getQueueCreateInfos(physicalDevice, queueCount, qPriorities,queueFlags); - - vk::DeviceCreateInfo deviceCreateInfo( - vk::DeviceCreateFlags(), - qCreateInfos.size(), - qCreateInfos.data(), - 0, - nullptr, - deviceExtensions.size(), - deviceExtensions.data(), - nullptr // Should our device use some features??? If yes: TODO - ); + std::vector<float> qPriorities; + qPriorities.resize(queueFlags.size(), 1.f); + std::vector<std::pair<int, int>> queuePairsGraphics, queuePairsCompute, queuePairsTransfer; + QueueManager::queueCreateInfosQueueHandles(physicalDevice, qPriorities, queueFlags, qCreateInfos, queuePairsGraphics, queuePairsCompute, queuePairsTransfer); + + vk::DeviceCreateInfo deviceCreateInfo( + vk::DeviceCreateFlags(), + qCreateInfos.size(), + qCreateInfos.data(), + 0, + nullptr, + deviceExtensions.size(), + deviceExtensions.data(), + nullptr // Should our device use some features??? If yes: TODO + ); #ifndef NDEBUG deviceCreateInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size()); deviceCreateInfo.ppEnabledLayerNames = validationLayers.data(); #endif + // Ablauf + // qCreateInfos erstellen --> braucht das Device + // device erstellen + // jetzt koennen wir mit dem device die queues erstellen vk::Device device = physicalDevice.createDevice(deviceCreateInfo); - // TODO: implement device.getQueue() to access the queues, if needed - Context context(instance, physicalDevice, device); - return Core(std::move(context)); + QueueManager queueManager = QueueManager::create(device, queuePairsGraphics, queuePairsCompute, queuePairsTransfer); + + Context context (instance, physicalDevice, device); + + SwapChain swapChain = SwapChain::create(window, context, surface); + + std::vector<vk::Image> swapChainImages = device.getSwapchainImagesKHR(swapChain.getSwapchain()); + std::vector<vk::ImageView> imageViews; + imageViews.reserve( swapChainImages.size() ); + //here can be swizzled with vk::ComponentSwizzle if needed + vk::ComponentMapping componentMapping( + vk::ComponentSwizzle::eR, + vk::ComponentSwizzle::eG, + vk::ComponentSwizzle::eB, + vk::ComponentSwizzle::eA ); + + vk::ImageSubresourceRange subResourceRange( vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 ); + + for ( auto image : swapChainImages ) + { + vk::ImageViewCreateInfo imageViewCreateInfo( + vk::ImageViewCreateFlags(), + image, + vk::ImageViewType::e2D, + swapChain.getSurfaceFormat().format, + componentMapping, + subResourceRange + ); + + imageViews.push_back( device.createImageView( imageViewCreateInfo )); + } + + const int graphicQueueFamilyIndex = queuePairsGraphics[0].first; + const auto defaultCommandResources = createDefaultCommandResources(context.getDevice(), graphicQueueFamilyIndex); + const auto defaultSyncResources = createDefaultSyncResources(context.getDevice()); + + return Core(std::move(context) , window, swapChain, imageViews, defaultCommandResources, defaultSyncResources, queueManager); } const Context &Core::getContext() const @@ -267,7 +278,129 @@ namespace vkcv return m_Context; } - Core::Core(Context &&context) noexcept : - m_Context(std::move(context)) - {} + Core::Core(Context &&context, const Window &window , SwapChain swapChain, std::vector<vk::ImageView> imageViews, + const CommandResources& commandResources, const SyncResources& syncResources, const QueueManager& queueManager) noexcept : + m_Context(std::move(context)), + m_window(window), + m_swapchain(swapChain), + m_swapchainImageViews(imageViews), + m_PassManager{std::make_unique<PassManager>(m_Context.m_Device)}, + m_PipelineManager{std::make_unique<PipelineManager>(m_Context.m_Device)}, + m_CommandResources(commandResources), + m_SyncResources(syncResources), + m_QueueManager(queueManager) + {} + + Core::~Core() noexcept { + m_Context.getDevice().waitIdle(); + for (auto image : m_swapchainImageViews) { + m_Context.m_Device.destroyImageView(image); + } + + destroyCommandResources(m_Context.getDevice(), m_CommandResources); + destroySyncResources(m_Context.getDevice(), m_SyncResources); + destroyTemporaryFramebuffers(); + + m_Context.m_Device.destroySwapchainKHR(m_swapchain.getSwapchain()); + m_Context.m_Instance.destroySurfaceKHR(m_swapchain.getSurface()); + } + + PipelineHandle Core::createGraphicsPipeline(const PipelineConfig &config) + { + const vk::RenderPass &pass = m_PassManager->getVkPass(config.m_PassHandle); + return m_PipelineManager->createPipeline(config, pass); + } + + + PassHandle Core::createPass(const PassConfig &config) + { + return m_PassManager->createPass(config); + } + + uint32_t Core::acquireSwapchainImage() { + uint32_t index; + m_Context.getDevice().acquireNextImageKHR(m_swapchain.getSwapchain(), 0, nullptr, + m_SyncResources.swapchainImageAcquired, &index, {}); + const uint64_t timeoutPeriodNs = 1000; // TODO: think if is adequate + const auto& result = m_Context.getDevice().waitForFences(m_SyncResources.swapchainImageAcquired, true, timeoutPeriodNs); + m_Context.getDevice().resetFences(m_SyncResources.swapchainImageAcquired); + + if (result == vk::Result::eTimeout) { + index = std::numeric_limits<uint32_t>::max(); + } + + return index; + } + + void Core::destroyTemporaryFramebuffers() { + for (const vk::Framebuffer f : m_TemporaryFramebuffers) { + m_Context.getDevice().destroyFramebuffer(f); + } + m_TemporaryFramebuffers.clear(); + } + + void Core::beginFrame() { + m_currentSwapchainImageIndex = acquireSwapchainImage(); + + if (m_currentSwapchainImageIndex == std::numeric_limits<uint32_t>::max()) { + std::cerr << "Drop frame!" << std::endl; + return; + } + + m_Context.getDevice().waitIdle(); // FIMXE: this is a sin against graphics programming, but its getting late - Alex + destroyTemporaryFramebuffers(); + const vk::CommandBufferUsageFlags beginFlags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit; + const vk::CommandBufferBeginInfo beginInfos(beginFlags); + m_CommandResources.commandBuffer.begin(beginInfos); + } + + void Core::renderTriangle(const PassHandle renderpassHandle, const PipelineHandle pipelineHandle, + const int width, const int height) { + if (m_currentSwapchainImageIndex == std::numeric_limits<uint32_t>::max()) { + return; + } + + const vk::RenderPass renderpass = m_PassManager->getVkPass(renderpassHandle); + const std::array<float, 4> clearColor = { 0.f, 0.f, 0.f, 1.f }; + const vk::ClearValue clearValues(clearColor); + const vk::Rect2D renderArea(vk::Offset2D(0, 0), vk::Extent2D(width, height)); + const vk::ImageView imageView = m_swapchainImageViews[m_currentSwapchainImageIndex]; + const vk::Framebuffer framebuffer = createFramebuffer(m_Context.getDevice(), renderpass, width, height, imageView); + m_TemporaryFramebuffers.push_back(framebuffer); + const vk::RenderPassBeginInfo beginInfo(renderpass, framebuffer, renderArea, 1, &clearValues); + const vk::SubpassContents subpassContents = {}; + m_CommandResources.commandBuffer.beginRenderPass(beginInfo, subpassContents, {}); + + const vk::Pipeline pipeline = m_PipelineManager->getVkPipeline(pipelineHandle); + m_CommandResources.commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline, {}); + m_CommandResources.commandBuffer.draw(3, 1, 0, 0, {}); + m_CommandResources.commandBuffer.endRenderPass(); + } + + void Core::endFrame() { + if (m_currentSwapchainImageIndex == std::numeric_limits<uint32_t>::max()) { + return; + } + + const auto swapchainImages = m_Context.getDevice().getSwapchainImagesKHR(m_swapchain.getSwapchain()); + const vk::Image presentImage = swapchainImages[m_currentSwapchainImageIndex]; + + m_CommandResources.commandBuffer.end(); + + const vk::SubmitInfo submitInfo(0, nullptr, 0, 1, &(m_CommandResources.commandBuffer), 1, &m_SyncResources.renderFinished); + m_QueueManager.getGraphicsQueues()[0].submit(submitInfo); + + vk::Result presentResult; + const vk::SwapchainKHR& swapchain = m_swapchain.getSwapchain(); + const vk::PresentInfoKHR presentInfo(1, &m_SyncResources.renderFinished, 1, &swapchain, + &m_currentSwapchainImageIndex, &presentResult); + m_QueueManager.getPresentQueue().presentKHR(presentInfo); + if (presentResult != vk::Result::eSuccess) { + std::cout << "Error: swapchain present failed" << std::endl; + } + } + + vk::Format Core::getSwapchainImageFormat() { + return m_swapchain.getSurfaceFormat().format; + } } diff --git a/src/vkcv/Framebuffer.cpp b/src/vkcv/Framebuffer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3b3e8000668e460d476b211984e9e12249f066c0 --- /dev/null +++ b/src/vkcv/Framebuffer.cpp @@ -0,0 +1,11 @@ +#include "Framebuffer.hpp" + +namespace vkcv { + vk::Framebuffer createFramebuffer(const vk::Device device, const vk::RenderPass renderpass, + const int width, const int height, const vk::ImageView imageView) { + const vk::FramebufferCreateFlags flags = {}; + const uint32_t attachmentCount = 1; // TODO: proper value + const vk::FramebufferCreateInfo createInfo(flags, renderpass, attachmentCount, &imageView, width, height, 1); + return device.createFramebuffer(createInfo, nullptr, {}); + } +} \ No newline at end of file diff --git a/src/vkcv/Framebuffer.hpp b/src/vkcv/Framebuffer.hpp new file mode 100644 index 0000000000000000000000000000000000000000..7d5d718adbde0c3f8eb8d97c539fb73f7771987f --- /dev/null +++ b/src/vkcv/Framebuffer.hpp @@ -0,0 +1,7 @@ +#pragma once +#include <vulkan/vulkan.hpp> + +namespace vkcv{ + vk::Framebuffer createFramebuffer(const vk::Device device, const vk::RenderPass renderpass, + const int width, const int height, const vk::ImageView imageView); +} \ No newline at end of file diff --git a/src/vkcv/ImageLayoutTransitions.cpp b/src/vkcv/ImageLayoutTransitions.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0b08819489c41c5cde3ceddbb0629a5d2ae3cd30 --- /dev/null +++ b/src/vkcv/ImageLayoutTransitions.cpp @@ -0,0 +1,24 @@ +#include "ImageLayoutTransitions.hpp" + +namespace vkcv { + void transitionImageLayoutImmediate(const vk::CommandBuffer cmdBuffer, const vk::Image image, + const vk::ImageLayout oldLayout, const vk::ImageLayout newLayout) { + + // TODO: proper src and dst masks + const vk::PipelineStageFlags srcStageMask = vk::PipelineStageFlagBits::eAllCommands; + const vk::PipelineStageFlags dstStageMask = vk::PipelineStageFlagBits::eAllCommands; + const vk::DependencyFlags dependecyFlags = {}; + + // TODO: proper src and dst masks + const vk::AccessFlags srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite; + const vk::AccessFlags dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite; + + // TODO: proper aspect flags + const vk::ImageAspectFlags aspectFlags = vk::ImageAspectFlagBits::eColor; + + const vk::ImageSubresourceRange subresourceRange(aspectFlags, 0, 1, 0, 1); + vk::ImageMemoryBarrier imageBarrier(srcAccessMask, dstAccessMask, oldLayout, newLayout, 0, 0, image, subresourceRange); + + cmdBuffer.pipelineBarrier(srcStageMask, dstStageMask, dependecyFlags, 0, nullptr, 0, nullptr, 1, &imageBarrier, {}); + } +} \ No newline at end of file diff --git a/src/vkcv/ImageLayoutTransitions.hpp b/src/vkcv/ImageLayoutTransitions.hpp new file mode 100644 index 0000000000000000000000000000000000000000..3dbfbdf6690a0683b30a96f400e7e4b6ec25c379 --- /dev/null +++ b/src/vkcv/ImageLayoutTransitions.hpp @@ -0,0 +1,7 @@ +#pragma once +#include <vulkan/vulkan.hpp> + +namespace vkcv { + void transitionImageLayoutImmediate(const vk::CommandBuffer cmdBuffer, const vk::Image image, + const vk::ImageLayout oldLayout, const vk::ImageLayout newLayout); +} \ No newline at end of file diff --git a/src/vkcv/PassConfig.cpp b/src/vkcv/PassConfig.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ef07d3ee8d6170ae893cd055eefcc971cd1b87a3 --- /dev/null +++ b/src/vkcv/PassConfig.cpp @@ -0,0 +1,25 @@ +#include "vkcv/PassConfig.hpp" + +#include <utility> + +namespace vkcv +{ + AttachmentDescription::AttachmentDescription( + AttachmentLayout initial, + AttachmentLayout in_pass, + AttachmentLayout final, + AttachmentOperation store_op, + AttachmentOperation load_op, + vk::Format format) noexcept : + layout_initial{initial}, + layout_in_pass{in_pass}, + layout_final{final}, + store_operation{store_op}, + load_operation{load_op}, + format(format) + {}; + + PassConfig::PassConfig(std::vector<AttachmentDescription> attachments) noexcept : + attachments{std::move(attachments)} + {} +} \ No newline at end of file diff --git a/src/vkcv/PassManager.cpp b/src/vkcv/PassManager.cpp new file mode 100644 index 0000000000000000000000000000000000000000..820bf3ff8a84edb9d070c22c60327cf7cb661ee7 --- /dev/null +++ b/src/vkcv/PassManager.cpp @@ -0,0 +1,135 @@ +#include "PassManager.hpp" + +namespace vkcv +{ + static vk::ImageLayout getVkLayoutFromAttachLayout(AttachmentLayout layout) + { + switch(layout) + { + case AttachmentLayout::GENERAL: + return vk::ImageLayout::eGeneral; + case AttachmentLayout::COLOR_ATTACHMENT: + return vk::ImageLayout::eColorAttachmentOptimal; + case AttachmentLayout::SHADER_READ_ONLY: + return vk::ImageLayout::eShaderReadOnlyOptimal; + case AttachmentLayout::DEPTH_STENCIL_ATTACHMENT: + return vk::ImageLayout::eDepthStencilAttachmentOptimal; + case AttachmentLayout::DEPTH_STENCIL_READ_ONLY: + return vk::ImageLayout::eDepthStencilReadOnlyOptimal; + case AttachmentLayout::PRESENTATION: + return vk::ImageLayout::ePresentSrcKHR; + default: + return vk::ImageLayout::eUndefined; + } + } + + static vk::AttachmentStoreOp getVkStoreOpFromAttachOp(AttachmentOperation op) + { + switch(op) + { + case AttachmentOperation::STORE: + return vk::AttachmentStoreOp::eStore; + default: + return vk::AttachmentStoreOp::eDontCare; + } + } + + static vk::AttachmentLoadOp getVKLoadOpFromAttachOp(AttachmentOperation op) + { + switch(op) + { + case AttachmentOperation::LOAD: + return vk::AttachmentLoadOp::eLoad; + case AttachmentOperation::CLEAR: + return vk::AttachmentLoadOp::eClear; + default: + return vk::AttachmentLoadOp::eDontCare; + } + } + + PassManager::PassManager(vk::Device device) noexcept : + m_Device{device}, + m_RenderPasses{}, + m_NextPassId{1} + {} + + PassManager::~PassManager() noexcept + { + for(const auto &pass : m_RenderPasses) + m_Device.destroy(pass); + + m_RenderPasses.clear(); + m_NextPassId = 1; + } + + PassHandle PassManager::createPass(const PassConfig &config) + { + // description of all {color, input, depth/stencil} attachments of the render pass + std::vector<vk::AttachmentDescription> attachmentDescriptions{}; + + // individual references to color attachments (of a subpass) + std::vector<vk::AttachmentReference> colorAttachmentReferences{}; + // individual reference to depth attachment (of a subpass) + vk::AttachmentReference depthAttachmentReference{}; + vk::AttachmentReference *pDepthAttachment = nullptr; //stays nullptr if no depth attachment used + + for (uint32_t i = 0; i < config.attachments.size(); i++) + { + // TODO: Renderpass struct should hold proper format information + vk::Format format = config.attachments[i].format; + + if (config.attachments[i].layout_in_pass == AttachmentLayout::DEPTH_STENCIL_ATTACHMENT) + { + depthAttachmentReference.attachment = i; + depthAttachmentReference.layout = getVkLayoutFromAttachLayout(config.attachments[i].layout_in_pass); + pDepthAttachment = &depthAttachmentReference; + } + else + { + vk::AttachmentReference attachmentRef(i, getVkLayoutFromAttachLayout(config.attachments[i].layout_in_pass)); + colorAttachmentReferences.push_back(attachmentRef); + } + + vk::AttachmentDescription attachmentDesc({}, + format, + vk::SampleCountFlagBits::e1, + getVKLoadOpFromAttachOp(config.attachments[i].load_operation), + getVkStoreOpFromAttachOp(config.attachments[i].store_operation), + vk::AttachmentLoadOp::eDontCare, + vk::AttachmentStoreOp::eDontCare, + getVkLayoutFromAttachLayout(config.attachments[i].layout_initial), + getVkLayoutFromAttachLayout(config.attachments[i].layout_final)); + attachmentDescriptions.push_back(attachmentDesc); + } + vk::SubpassDescription subpassDescription({}, + vk::PipelineBindPoint::eGraphics, + 0, + {}, + static_cast<uint32_t>(colorAttachmentReferences.size()), + colorAttachmentReferences.data(), + {}, + pDepthAttachment, + 0, + {}); + + vk::RenderPassCreateInfo passInfo({}, + static_cast<uint32_t>(attachmentDescriptions.size()), + attachmentDescriptions.data(), + 1, + &subpassDescription, + 0, + {}); + + vk::RenderPass vkObject{nullptr}; + if(m_Device.createRenderPass(&passInfo, nullptr, &vkObject) != vk::Result::eSuccess) + return PassHandle{0}; + + m_RenderPasses.push_back(vkObject); + return PassHandle{m_NextPassId++}; + } + + vk::RenderPass PassManager::getVkPass(const PassHandle &handle) const + { + return m_RenderPasses[handle.id - 1]; + } +} diff --git a/src/vkcv/PassManager.hpp b/src/vkcv/PassManager.hpp new file mode 100644 index 0000000000000000000000000000000000000000..b6be2cb13d8d24bdb9759f8878917f99e31afbec --- /dev/null +++ b/src/vkcv/PassManager.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include <vulkan/vulkan.hpp> +#include <vector> +#include "vkcv/Handles.hpp" +#include "vkcv/PassConfig.hpp" + +namespace vkcv +{ + class PassManager + { + private: + vk::Device m_Device; + std::vector<vk::RenderPass> m_RenderPasses; + uint64_t m_NextPassId; + public: + PassManager() = delete; // no default ctor + explicit PassManager(vk::Device device) noexcept; // ctor + ~PassManager() noexcept; // dtor + + PassManager(const PassManager &other) = delete; // copy-ctor + PassManager(PassManager &&other) = delete; // move-ctor; + + PassManager & operator=(const PassManager &other) = delete; // copy-assign op + PassManager & operator=(PassManager &&other) = delete; // move-assign op + + PassHandle createPass(const PassConfig &config); + + [[nodiscard]] + vk::RenderPass getVkPass(const PassHandle &handle) const; + }; +} diff --git a/src/vkcv/PipelineConfig.cpp b/src/vkcv/PipelineConfig.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c2b1415f10188f082d8eca576c2143e82f99e7fa --- /dev/null +++ b/src/vkcv/PipelineConfig.cpp @@ -0,0 +1,17 @@ +/** + * @authors Mara Vogt, Mark Mints + * @file src/vkcv/Pipeline.cpp + * @brief Pipeline class to handle shader stages + */ + +#include "vkcv/PipelineConfig.hpp" + +namespace vkcv { + + PipelineConfig::PipelineConfig(const ShaderProgram& shaderProgram, uint32_t width, uint32_t height, PassHandle &passHandle): + m_ShaderProgram(shaderProgram), + m_Height(height), + m_Width(width), + m_PassHandle(passHandle) + {} +} diff --git a/src/vkcv/PipelineManager.cpp b/src/vkcv/PipelineManager.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e6d6beea463f17c86d958c9080f34eb61e9076c2 --- /dev/null +++ b/src/vkcv/PipelineManager.cpp @@ -0,0 +1,206 @@ +#include "PipelineManager.hpp" + +namespace vkcv +{ + + PipelineManager::PipelineManager(vk::Device device) noexcept : + m_Device{device}, + m_Pipelines{}, + m_PipelineLayouts{}, + m_NextPipelineId{1} + {} + + PipelineManager::~PipelineManager() noexcept + { + for(const auto &pipeline: m_Pipelines) + m_Device.destroy(pipeline); + + for(const auto &layout : m_PipelineLayouts) + m_Device.destroy(layout); + + m_Pipelines.clear(); + m_PipelineLayouts.clear(); + m_NextPipelineId = 1; + } + + PipelineHandle PipelineManager::createPipeline(const PipelineConfig &config, const vk::RenderPass &pass) + { + const bool existsVertexShader = config.m_ShaderProgram.existsShader(ShaderStage::VERTEX); + const bool existsFragmentShader = config.m_ShaderProgram.existsShader(ShaderStage::FRAGMENT); + if (!(existsVertexShader && existsFragmentShader)) + { + std::cout << "Core::createGraphicsPipeline requires vertex and fragment shader code" << std::endl; + return PipelineHandle{0}; + } + + // vertex shader stage + std::vector<char> vertexCode = config.m_ShaderProgram.getShader(ShaderStage::VERTEX).shaderCode; + vk::ShaderModuleCreateInfo vertexModuleInfo({}, vertexCode.size(), reinterpret_cast<uint32_t*>(vertexCode.data())); + vk::ShaderModule vertexModule{}; + if (m_Device.createShaderModule(&vertexModuleInfo, nullptr, &vertexModule) != vk::Result::eSuccess) + return PipelineHandle{0}; + + vk::PipelineShaderStageCreateInfo pipelineVertexShaderStageInfo( + {}, + vk::ShaderStageFlagBits::eVertex, + vertexModule, + "main", + nullptr + ); + + // fragment shader stage + std::vector<char> fragCode = config.m_ShaderProgram.getShader(ShaderStage::FRAGMENT).shaderCode; + vk::ShaderModuleCreateInfo fragmentModuleInfo({}, fragCode.size(), reinterpret_cast<uint32_t*>(fragCode.data())); + vk::ShaderModule fragmentModule{}; + if (m_Device.createShaderModule(&fragmentModuleInfo, nullptr, &fragmentModule) != vk::Result::eSuccess) + { + m_Device.destroy(vertexModule); + return PipelineHandle{0}; + } + + vk::PipelineShaderStageCreateInfo pipelineFragmentShaderStageInfo( + {}, + vk::ShaderStageFlagBits::eFragment, + fragmentModule, + "main", + nullptr + ); + + // vertex input state + vk::VertexInputBindingDescription vertexInputBindingDescription(0, 12, vk::VertexInputRate::eVertex); + vk::VertexInputAttributeDescription vertexInputAttributeDescription(0, 0, vk::Format::eR32G32B32Sfloat, 0); + + vk::PipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo( + {}, // no vertex input until vertex buffer is implemented + 0, // 1, + nullptr, // &vertexInputBindingDescription, + 0, // 1, + nullptr // &vertexInputAttributeDescription + ); + + // input assembly state + vk::PipelineInputAssemblyStateCreateInfo pipelineInputAssemblyStateCreateInfo( + {}, + vk::PrimitiveTopology::eTriangleList, + false + ); + + // viewport state + vk::Viewport viewport(0.f, 0.f, static_cast<float>(config.m_Width), static_cast<float>(config.m_Height), 0.f, 1.f); + vk::Rect2D scissor({ 0,0 }, { config.m_Width, config.m_Height }); + vk::PipelineViewportStateCreateInfo pipelineViewportStateCreateInfo({}, 1, &viewport, 1, &scissor); + + // rasterization state + vk::PipelineRasterizationStateCreateInfo pipelineRasterizationStateCreateInfo( + {}, + false, + false, + vk::PolygonMode::eFill, + vk::CullModeFlagBits::eNone, + vk::FrontFace::eCounterClockwise, + false, + 0.f, + 0.f, + 0.f, + 1.f + ); + + // multisample state + vk::PipelineMultisampleStateCreateInfo pipelineMultisampleStateCreateInfo( + {}, + vk::SampleCountFlagBits::e1, + false, + 0.f, + nullptr, + false, + false + ); + + // color blend state + vk::ColorComponentFlags colorWriteMask(VK_COLOR_COMPONENT_R_BIT | + VK_COLOR_COMPONENT_G_BIT | + VK_COLOR_COMPONENT_B_BIT | + VK_COLOR_COMPONENT_A_BIT); + vk::PipelineColorBlendAttachmentState colorBlendAttachmentState( + false, + vk::BlendFactor::eOne, + vk::BlendFactor::eOne, + vk::BlendOp::eAdd, + vk::BlendFactor::eOne, + vk::BlendFactor::eOne, + vk::BlendOp::eAdd, + colorWriteMask + ); + vk::PipelineColorBlendStateCreateInfo pipelineColorBlendStateCreateInfo( + {}, + false, + vk::LogicOp::eClear, + 1, //TODO: hardcoded to one + &colorBlendAttachmentState, + { 1.f,1.f,1.f,1.f } + ); + + // pipeline layout + vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo( + {}, + 0, + {}, + 0, + {} + ); + vk::PipelineLayout vkPipelineLayout{}; + if (m_Device.createPipelineLayout(&pipelineLayoutCreateInfo, nullptr, &vkPipelineLayout) != vk::Result::eSuccess) + { + m_Device.destroy(vertexModule); + m_Device.destroy(fragmentModule); + return PipelineHandle{0}; + } + + // graphics pipeline create + std::vector<vk::PipelineShaderStageCreateInfo> shaderStages = { pipelineVertexShaderStageInfo, pipelineFragmentShaderStageInfo }; + vk::GraphicsPipelineCreateInfo graphicsPipelineCreateInfo( + {}, + static_cast<uint32_t>(shaderStages.size()), + shaderStages.data(), + &pipelineVertexInputStateCreateInfo, + &pipelineInputAssemblyStateCreateInfo, + nullptr, + &pipelineViewportStateCreateInfo, + &pipelineRasterizationStateCreateInfo, + &pipelineMultisampleStateCreateInfo, + nullptr, + &pipelineColorBlendStateCreateInfo, + nullptr, + vkPipelineLayout, + pass, + 0, + {}, + 0 + ); + + vk::Pipeline vkPipeline{}; + if (m_Device.createGraphicsPipelines(nullptr, 1, &graphicsPipelineCreateInfo, nullptr, &vkPipeline) != vk::Result::eSuccess) + { + m_Device.destroy(vertexModule); + m_Device.destroy(fragmentModule); + return PipelineHandle{0}; + } + + m_Device.destroy(vertexModule); + m_Device.destroy(fragmentModule); + + m_Pipelines.push_back(vkPipeline); + m_PipelineLayouts.push_back(vkPipelineLayout); + return PipelineHandle{m_NextPipelineId++}; + } + + vk::Pipeline PipelineManager::getVkPipeline(const PipelineHandle &handle) const + { + return m_Pipelines.at(handle.id -1); + } + + vk::PipelineLayout PipelineManager::getVkPipelineLayout(const PipelineHandle &handle) const + { + return m_PipelineLayouts.at(handle.id - 1); + } +} \ No newline at end of file diff --git a/src/vkcv/PipelineManager.hpp b/src/vkcv/PipelineManager.hpp new file mode 100644 index 0000000000000000000000000000000000000000..b5c0948efa13a4021f424cc576f1403a1ec26ebe --- /dev/null +++ b/src/vkcv/PipelineManager.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include <vulkan/vulkan.hpp> +#include <vector> +#include "vkcv/Handles.hpp" +#include "vkcv/PipelineConfig.hpp" + +namespace vkcv +{ + class PipelineManager + { + private: + vk::Device m_Device; + std::vector<vk::Pipeline> m_Pipelines; + std::vector<vk::PipelineLayout> m_PipelineLayouts; + uint64_t m_NextPipelineId; + public: + PipelineManager() = delete; // no default ctor + explicit PipelineManager(vk::Device device) noexcept; // ctor + ~PipelineManager() noexcept; // dtor + + PipelineManager(const PipelineManager &other) = delete; // copy-ctor + PipelineManager(PipelineManager &&other) = delete; // move-ctor; + + PipelineManager & operator=(const PipelineManager &other) = delete; // copy-assign op + PipelineManager & operator=(PipelineManager &&other) = delete; // move-assign op + + PipelineHandle createPipeline(const PipelineConfig &config, const vk::RenderPass &pass); + + [[nodiscard]] + vk::Pipeline getVkPipeline(const PipelineHandle &handle) const; + [[nodiscard]] + vk::PipelineLayout getVkPipelineLayout(const PipelineHandle &handle) const; + }; +} diff --git a/src/vkcv/QueueManager.cpp b/src/vkcv/QueueManager.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c6c9dda42e1fdfacdde8e7174307cbbcd38ff24d --- /dev/null +++ b/src/vkcv/QueueManager.cpp @@ -0,0 +1,189 @@ +#include "vkcv/QueueManager.hpp" +#include <unordered_set> + +namespace vkcv { + + /** + * Given the @p physicalDevice and the @p queuePriorities, the @p queueCreateInfos are computed. First, the requested + * queues are sorted by priority depending on the availability of queues in the queue families of the given + * @p physicalDevice. Then check, if all requested queues are creatable. If so, the @p queueCreateInfos will be computed. + * Furthermore, lists of index pairs (queueFamilyIndex, queueIndex) for later referencing of the separate queues will + * be computed. + * @param[in] physicalDevice The physical device + * @param[in] queuePriorities The queue priorities used for the computation of @p queueCreateInfos + * @param[in] queueFlags The queue flags requesting the queues + * @param[in,out] queueCreateInfos The queue create info structures to be created + * @param[in,out] queuePairsGraphics The list of index pairs (queueFamilyIndex, queueIndex) of queues of type + * vk::QueueFlagBits::eGraphics + * @param[in,out] queuePairsCompute The list of index pairs (queueFamilyIndex, queueIndex) of queues of type + * vk::QueueFlagBits::eCompute + * @param[in,out] queuePairsTransfer The list of index pairs (queueFamilyIndex, queueIndex) of queues of type + * vk::QueueFlagBits::eTransfer + * @throws std::runtime_error If the requested queues from @p queueFlags are not creatable due to insufficient availability. + */ + void QueueManager::queueCreateInfosQueueHandles(vk::PhysicalDevice &physicalDevice, + std::vector<float> &queuePriorities, + std::vector<vk::QueueFlagBits> &queueFlags, + std::vector<vk::DeviceQueueCreateInfo> &queueCreateInfos, + std::vector<std::pair<int, int>> &queuePairsGraphics, + std::vector<std::pair<int, int>> &queuePairsCompute, + std::vector<std::pair<int, int>> &queuePairsTransfer) + { + queueCreateInfos = {}; + queuePairsGraphics = {}; + queuePairsCompute = {}; + queuePairsTransfer = {}; + std::vector<vk::QueueFamilyProperties> qFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + //check priorities of flags -> the lower prioCount the higher the priority + std::vector<int> prios; + for(auto flag: queueFlags) { + int prioCount = 0; + for (int i = 0; i < qFamilyProperties.size(); i++) { + prioCount += (static_cast<uint32_t>(flag & qFamilyProperties[i].queueFlags) != 0) * qFamilyProperties[i].queueCount; + } + prios.push_back(prioCount); + } + //resort flags with heighest priority before allocating the queues + std::vector<vk::QueueFlagBits> newFlags; + for(int i = 0; i < prios.size(); i++) { + auto minElem = std::min_element(prios.begin(), prios.end()); + int index = minElem - prios.begin(); + newFlags.push_back(queueFlags[index]); + prios[index] = std::numeric_limits<int>::max(); + } + + // create requested queues and check if more requested queues are supported + // herefore: create vector that updates available queues in each queue family + // structure: [qFamily_0, ..., qFamily_n] where + // - qFamily_i = [GraphicsCount, ComputeCount, TransferCount], 0 <= i <= n + std::vector<std::vector<int>> queueFamilyStatus, initialQueueFamilyStatus; + + for (auto qFamily : qFamilyProperties) { + int graphicsCount = int(static_cast<uint32_t>(qFamily.queueFlags & vk::QueueFlagBits::eGraphics) != 0) * qFamily.queueCount; + int computeCount = int(static_cast<uint32_t>(qFamily.queueFlags & vk::QueueFlagBits::eCompute) != 0) * qFamily.queueCount; + int transferCount = int(static_cast<uint32_t>(qFamily.queueFlags & vk::QueueFlagBits::eTransfer) != 0) * qFamily.queueCount; + queueFamilyStatus.push_back({graphicsCount, computeCount, transferCount}); + } + + initialQueueFamilyStatus = queueFamilyStatus; + // check if every queue with the specified queue flag can be created + // this automatically checks for queue flag support! + for (auto qFlag : newFlags) { + bool found; + switch (qFlag) { + case vk::QueueFlagBits::eGraphics: + found = false; + for (int i = 0; i < queueFamilyStatus.size() && !found; i++) { + if (queueFamilyStatus[i][0] > 0) { + queuePairsGraphics.push_back(std::pair(i, initialQueueFamilyStatus[i][0] - queueFamilyStatus[i][0])); + queueFamilyStatus[i][0]--; + queueFamilyStatus[i][1]--; + queueFamilyStatus[i][2]--; + found = true; + } + } + if (!found) { + throw std::runtime_error("Too many graphics queues were requested than being available!"); + } + break; + case vk::QueueFlagBits::eCompute: + found = false; + for (int i = 0; i < queueFamilyStatus.size() && !found; i++) { + if (queueFamilyStatus[i][1] > 0) { + queuePairsCompute.push_back(std::pair(i, initialQueueFamilyStatus[i][1] - queueFamilyStatus[i][1])); + queueFamilyStatus[i][0]--; + queueFamilyStatus[i][1]--; + queueFamilyStatus[i][2]--; + found = true; + } + } + if (!found) { + throw std::runtime_error("Too many compute queues were requested than being available!"); + } + break; + case vk::QueueFlagBits::eTransfer: + found = false; + for (int i = 0; i < queueFamilyStatus.size() && !found; i++) { + if (queueFamilyStatus[i][2] > 0) { + queuePairsTransfer.push_back(std::pair(i, initialQueueFamilyStatus[i][2] - queueFamilyStatus[i][2])); + queueFamilyStatus[i][0]--; + queueFamilyStatus[i][1]--; + queueFamilyStatus[i][2]--; + found = true; + } + } + if (!found) { + throw std::runtime_error("Too many transfer queues were requested than being available!"); + } + break; + default: + throw std::runtime_error("Invalid input for queue flag bits. Valid inputs are 'vk::QueueFlagBits::eGraphics', 'vk::QueueFlagBits::eCompute' and 'vk::QueueFlagBits::eTransfer'."); + } + } + + // create all requested queues + for (int i = 0; i < qFamilyProperties.size(); i++) { + uint32_t create = std::abs(initialQueueFamilyStatus[i][0] - queueFamilyStatus[i][0]); + if (create > 0) { + vk::DeviceQueueCreateInfo qCreateInfo( + vk::DeviceQueueCreateFlags(), + i, + create, + queuePriorities.data() + ); + queueCreateInfos.push_back(qCreateInfo); + } + } + } + + /** + * Computes the queue handles from @p queuePairs + * @param device The device + * @param queuePairs The queuePairs that were created separately for each queue type (e.g., vk::QueueFlagBits::eGraphics) + * @return An array of queue handles based on the @p queuePairs + */ + std::vector<vk::Queue> getQueueHandles(const vk::Device device, const std::vector<std::pair<int, int>> queuePairs) { + std::vector<vk::Queue> queueHandles; + for (auto q : queuePairs) { + int queueFamilyIndex = q.first; // the queueIndex of the queue family + int queueIndex = q.second; // the queueIndex within a queue family + queueHandles.push_back(device.getQueue(queueFamilyIndex, queueIndex)); + } + return queueHandles; + } + + + QueueManager QueueManager::create(vk::Device device, + std::vector<std::pair<int, int>> &queuePairsGraphics, + std::vector<std::pair<int, int>> &queuePairsCompute, + std::vector<std::pair<int, int>> &queuePairsTransfer) { + + std::vector<vk::Queue> graphicsQueues = getQueueHandles(device, queuePairsGraphics); + std::vector<vk::Queue> computeQueues = getQueueHandles(device, queuePairsCompute ); + std::vector<vk::Queue> transferQueues = getQueueHandles(device, queuePairsTransfer); + + return QueueManager( graphicsQueues, computeQueues, transferQueues, graphicsQueues[0]); + } + + QueueManager::QueueManager(std::vector<vk::Queue> graphicsQueues, std::vector<vk::Queue> computeQueues, std::vector<vk::Queue> transferQueues, vk::Queue presentQueue) + : m_graphicsQueues(graphicsQueues), m_computeQueues(computeQueues), m_transferQueues(transferQueues), m_presentQueue(presentQueue) + {} + + const vk::Queue &QueueManager::getPresentQueue() const { + return m_presentQueue; + } + + const std::vector<vk::Queue> &QueueManager::getGraphicsQueues() const { + return m_graphicsQueues; + } + + const std::vector<vk::Queue> &QueueManager::getComputeQueues() const { + return m_computeQueues; + } + + const std::vector<vk::Queue> &QueueManager::getTransferQueues() const { + return m_transferQueues; + } + +} \ No newline at end of file diff --git a/src/vkcv/ShaderProgram.cpp b/src/vkcv/ShaderProgram.cpp new file mode 100644 index 0000000000000000000000000000000000000000..87ccdefbfec0b4891d3152d30aa6c9f6c8c0d5ea --- /dev/null +++ b/src/vkcv/ShaderProgram.cpp @@ -0,0 +1,62 @@ +/** + * @authors Simeon Hermann, Leonie Franken + * @file src/vkcv/ShaderProgram.cpp + * @brief ShaderProgram class to handle and prepare the shader stages for a graphics pipeline + */ + +#include "vkcv/ShaderProgram.hpp" + +namespace vkcv { + /** + * Reads the file of a given shader code. + * Only used within the class. + * @param[in] relative path to the shader code + * @return vector of chars as a buffer for the code + */ + std::vector<char> readShaderCode(const std::filesystem::path &shaderPath) + { + std::ifstream file(shaderPath.string(), std::ios::ate | std::ios::binary); + if (!file.is_open()) { + std::cout << "The file could not be opened." << std::endl; + return std::vector<char>{}; + } + size_t fileSize = (size_t)file.tellg(); + std::vector<char> buffer(fileSize); + file.seekg(0); + file.read(buffer.data(), fileSize); + return buffer; + } + + ShaderProgram::ShaderProgram() noexcept : + m_Shaders{} + {} + + bool ShaderProgram::addShader(ShaderStage shaderStage, const std::filesystem::path &shaderPath) + { + if(m_Shaders.find(shaderStage) != m_Shaders.end()) + std::cout << "Found existing shader stage. Overwriting." << std::endl; + + const std::vector<char> shaderCode = readShaderCode(shaderPath); + if (shaderCode.empty()) + return false; + else + { + Shader shader{shaderCode, shaderStage}; + m_Shaders.insert(std::make_pair(shaderStage, shader)); + return true; + } + } + + const Shader &ShaderProgram::getShader(ShaderStage shaderStage) const + { + return m_Shaders.at(shaderStage); + } + + bool ShaderProgram::existsShader(ShaderStage shaderStage) const + { + if(m_Shaders.find(shaderStage) == m_Shaders.end()) + return false; + else + return true; + } +} diff --git a/src/vkcv/Surface.cpp b/src/vkcv/Surface.cpp new file mode 100644 index 0000000000000000000000000000000000000000..29b6c646dc212cba2cc31f32dca5c4fcc023cd03 --- /dev/null +++ b/src/vkcv/Surface.cpp @@ -0,0 +1,27 @@ +#include "Surface.hpp" + +#define GLFW_INCLUDE_VULKAN +#include <GLFW/glfw3.h> + +namespace vkcv { + /** + * creates surface and checks availability + * @param window current window for the surface + * @param instance Vulkan-Instance + * @param physicalDevice Vulkan-PhysicalDevice + * @return created surface + */ + vk::SurfaceKHR createSurface(GLFWwindow* window, const vk::Instance& instance, const vk::PhysicalDevice& physicalDevice) { + //create surface + VkSurfaceKHR surface; + if (glfwCreateWindowSurface(VkInstance(instance), window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create a window surface!"); + } + vk::Bool32 surfaceSupport = false; + if (physicalDevice.getSurfaceSupportKHR(0, vk::SurfaceKHR(surface), &surfaceSupport) != vk::Result::eSuccess && surfaceSupport != true) { + throw std::runtime_error("surface is not supported by the device!"); + } + + return vk::SurfaceKHR(surface); + } +} diff --git a/src/vkcv/Surface.hpp b/src/vkcv/Surface.hpp new file mode 100644 index 0000000000000000000000000000000000000000..74aafeba821334767ac5e13cd33e1d9674e12f5b --- /dev/null +++ b/src/vkcv/Surface.hpp @@ -0,0 +1,8 @@ +#pragma once +#include <vulkan/vulkan.hpp> + +struct GLFWwindow; + +namespace vkcv { + vk::SurfaceKHR createSurface(GLFWwindow* window, const vk::Instance& instance, const vk::PhysicalDevice& physicalDevice); +} \ No newline at end of file diff --git a/src/vkcv/SwapChain.cpp b/src/vkcv/SwapChain.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3483ae37e718453a99d56d31e025433acb7f4422 --- /dev/null +++ b/src/vkcv/SwapChain.cpp @@ -0,0 +1,171 @@ +#include <vkcv/SwapChain.hpp> + +namespace vkcv { + + SwapChain::SwapChain(vk::SurfaceKHR surface, vk::SwapchainKHR swapchain, vk::SurfaceFormatKHR format, uint32_t imageCount) + : m_surface(surface), m_swapchain(swapchain), m_format( format), m_ImageCount(imageCount) + {} + + const vk::SwapchainKHR& SwapChain::getSwapchain() const { + return m_swapchain; + } + + /** + * gets surface of the swapchain + * @return current surface + */ + vk::SurfaceKHR SwapChain::getSurface() { + return m_surface; + } + + /** + * gets the surface of the swapchain + * @return chosen format + */ + vk::SurfaceFormatKHR SwapChain::getSurfaceFormat(){ + return m_format; + } + + /** + * chooses Extent and clapms values to the available + * @param physicalDevice Vulkan-PhysicalDevice + * @param surface of the swapchain + * @param window of the current application + * @return chosen Extent for the surface + */ + vk::Extent2D chooseSwapExtent(vk::PhysicalDevice physicalDevice, vk::SurfaceKHR surface, const Window &window){ + vk::SurfaceCapabilitiesKHR surfaceCapabilities; + if(physicalDevice.getSurfaceCapabilitiesKHR(surface,&surfaceCapabilities) != vk::Result::eSuccess){ + throw std::runtime_error("cannot get surface capabilities. There is an issue with the surface."); + } + + VkExtent2D extent2D = { + static_cast<uint32_t>(window.getWidth()), + static_cast<uint32_t>(window.getHeight()) + }; + extent2D.width = std::max(surfaceCapabilities.minImageExtent.width, std::min(surfaceCapabilities.maxImageExtent.width, extent2D.width)); + extent2D.height = std::max(surfaceCapabilities.minImageExtent.height, std::min(surfaceCapabilities.maxImageExtent.height, extent2D.height)); + + if (extent2D.width > surfaceCapabilities.maxImageExtent.width || + extent2D.width < surfaceCapabilities.minImageExtent.width || + extent2D.height > surfaceCapabilities.maxImageExtent.height || + extent2D.height < surfaceCapabilities.minImageExtent.height) { + std::printf("Surface size not matching. Resizing to allowed value."); + } + return extent2D; + } + + /** + * chooses Surface Format for the current surface + * @param physicalDevice Vulkan-PhysicalDevice + * @param surface of the swapchain + * @return available Format + */ + vk::SurfaceFormatKHR chooseSwapSurfaceFormat(vk::PhysicalDevice physicalDevice, vk::SurfaceKHR surface) { + uint32_t formatCount; + physicalDevice.getSurfaceFormatsKHR(surface, &formatCount, nullptr); + std::vector<vk::SurfaceFormatKHR> availableFormats(formatCount); + if (physicalDevice.getSurfaceFormatsKHR(surface, &formatCount, &availableFormats[0]) != vk::Result::eSuccess) { + throw std::runtime_error("Failed to get surface formats"); + } + + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == vk::Format::eB8G8R8A8Unorm && availableFormat.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear) { + return availableFormat; + } + } + return availableFormats[0]; + } + + /** + * returns vk::PresentModeKHR::eMailbox if available or vk::PresentModeKHR::eFifo otherwise + * @param physicalDevice Vulkan-PhysicalDevice + * @param surface of the swapchain + * @return available PresentationMode + */ + vk::PresentModeKHR choosePresentMode(vk::PhysicalDevice physicalDevice, vk::SurfaceKHR surface) { + uint32_t modeCount; + physicalDevice.getSurfacePresentModesKHR( surface, &modeCount, nullptr ); + std::vector<vk::PresentModeKHR> availablePresentModes(modeCount); + if (physicalDevice.getSurfacePresentModesKHR(surface, &modeCount, &availablePresentModes[0]) != vk::Result::eSuccess) { + throw std::runtime_error("Failed to get presentation modes"); + } + + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == vk::PresentModeKHR::eMailbox) { + return availablePresentMode; + } + } + // The FIFO present mode is guaranteed by the spec to be supported + return vk::PresentModeKHR::eFifo; + } + + /** + * returns the minImageCount +1 for at least doublebuffering, if it's greater than maxImageCount return maxImageCount + * @param physicalDevice Vulkan-PhysicalDevice + * @param surface of the swapchain + * @return available ImageCount + */ + uint32_t chooseImageCount(vk::PhysicalDevice physicalDevice, vk::SurfaceKHR surface) { + vk::SurfaceCapabilitiesKHR surfaceCapabilities; + if(physicalDevice.getSurfaceCapabilitiesKHR(surface, &surfaceCapabilities) != vk::Result::eSuccess){ + throw std::runtime_error("cannot get surface capabilities. There is an issue with the surface."); + } + + uint32_t imageCount = surfaceCapabilities.minImageCount + 1; // minImageCount should always be at least 2; set to 3 for triple buffering + // check if requested image count is supported + if (surfaceCapabilities.maxImageCount > 0 && imageCount > surfaceCapabilities.maxImageCount) { + imageCount = surfaceCapabilities.maxImageCount; + } + + return imageCount; + } + /** + * creates and returns a swapchain with default specs + * @param window of the current application + * @param context that keeps instance, physicalDevice and a device. + * @return swapchain + */ + SwapChain SwapChain::create(const Window &window, const Context &context, const vk::SurfaceKHR surface) { + const vk::Instance& instance = context.getInstance(); + const vk::PhysicalDevice& physicalDevice = context.getPhysicalDevice(); + const vk::Device& device = context.getDevice(); + + vk::Extent2D extent2D = chooseSwapExtent(physicalDevice, surface, window); + vk::SurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(physicalDevice, surface); + vk::PresentModeKHR presentMode = choosePresentMode(physicalDevice, surface); + uint32_t imageCount = chooseImageCount(physicalDevice, surface); + + vk::SwapchainCreateInfoKHR swapchainCreateInfo( + vk::SwapchainCreateFlagsKHR(), //flags + surface, // surface + imageCount, // minImageCount TODO: how many do we need for our application?? "must be less than or equal to the value returned in maxImageCount" -> 3 for Triple Buffering, else 2 for Double Buffering (should be the standard) + surfaceFormat.format, // imageFormat + surfaceFormat.colorSpace, // imageColorSpace + extent2D, // imageExtent + 1, // imageArrayLayers TODO: should we only allow non-stereoscopic applications? yes -> 1, no -> ? "must be greater than 0, less or equal to maxImageArrayLayers" + vk::ImageUsageFlagBits::eColorAttachment, // imageUsage TODO: what attachments? only color? depth? + vk::SharingMode::eExclusive, // imageSharingMode TODO: which sharing mode? "VK_SHARING_MODE_EXCLUSIV access exclusive to a single queue family, better performance", "VK_SHARING_MODE_CONCURRENT access from multiple queues" + 0, // queueFamilyIndexCount, the number of queue families having access to the image(s) of the swapchain when imageSharingMode is VK_SHARING_MODE_CONCURRENT + nullptr, // pQueueFamilyIndices, the pointer to an array of queue family indices having access to the images(s) of the swapchain when imageSharingMode is VK_SHARING_MODE_CONCURRENT + vk::SurfaceTransformFlagBitsKHR::eIdentity, // preTransform, transformations applied onto the image before display + vk::CompositeAlphaFlagBitsKHR::eOpaque, // compositeAlpha, TODO: how to handle transparent pixels? do we need transparency? If no -> opaque + presentMode, // presentMode + true, // clipped + nullptr // oldSwapchain + ); + + vk::SwapchainKHR swapchain = device.createSwapchainKHR(swapchainCreateInfo); + + return SwapChain(surface, swapchain, surfaceFormat, imageCount); + } + + + SwapChain::~SwapChain() { + // needs to be destroyed by creator + } + + uint32_t SwapChain::getImageCount() { + return m_ImageCount; + } +} diff --git a/src/vkcv/SyncResources.cpp b/src/vkcv/SyncResources.cpp new file mode 100644 index 0000000000000000000000000000000000000000..10d582a20501e1cc8d0bb9a98aa95b09d699c22c --- /dev/null +++ b/src/vkcv/SyncResources.cpp @@ -0,0 +1,24 @@ +#include "vkcv/SyncResources.hpp" + +namespace vkcv { + SyncResources createDefaultSyncResources(const vk::Device& device) { + SyncResources resources; + + const vk::SemaphoreCreateFlags semaphoreFlags = vk::SemaphoreCreateFlagBits(); + const vk::SemaphoreCreateInfo semaphoreInfo(semaphoreFlags); + resources.renderFinished = device.createSemaphore(semaphoreInfo, nullptr, {}); + + const vk::FenceCreateFlags fenceFlags = vk::FenceCreateFlagBits(); + vk::FenceCreateInfo fenceInfo(fenceFlags); + resources.presentFinished = device.createFence(fenceInfo, nullptr, {}); + resources.swapchainImageAcquired = device.createFence(fenceInfo, nullptr, {}); + + return resources; + } + + void destroySyncResources(const vk::Device& device, const SyncResources& resources) { + device.destroySemaphore(resources.renderFinished); + device.destroyFence(resources.presentFinished); + device.destroyFence(resources.swapchainImageAcquired); + } +} \ No newline at end of file diff --git a/src/vkcv/Window.cpp b/src/vkcv/Window.cpp index 8814a5abcf7977386490d9783d5b121b7f986651..ce05d67cb216215c625b468acdb85be2fc2a8b2e 100644 --- a/src/vkcv/Window.cpp +++ b/src/vkcv/Window.cpp @@ -6,6 +6,7 @@ #include "vkcv/Window.hpp" + namespace vkcv { static uint32_t s_WindowCount = 0; @@ -18,14 +19,15 @@ namespace vkcv { glfwDestroyWindow(m_window); s_WindowCount--; - if(s_WindowCount == 0) + if(s_WindowCount == 0) { glfwTerminate(); + } } - Window Window::create(const char *windowTitle, int width, int height, bool resizable) { - if(s_WindowCount == 0) + Window Window::create( const char *windowTitle, int width, int height, bool resizable) { + if(s_WindowCount == 0) { glfwInit(); - + } s_WindowCount++; width = std::max(width, 1); @@ -35,9 +37,8 @@ namespace vkcv { glfwWindowHint(GLFW_RESIZABLE, resizable ? GLFW_TRUE : GLFW_FALSE); GLFWwindow *window; window = glfwCreateWindow(width, height, windowTitle, nullptr, nullptr); - return Window(window); - + return Window(window); } bool Window::isWindowOpen() const { @@ -48,10 +49,6 @@ namespace vkcv { glfwPollEvents(); } - GLFWwindow *Window::getWindow() const { - return m_window; - } - int Window::getWidth() const { int width; glfwGetWindowSize(m_window, &width, nullptr); @@ -63,4 +60,8 @@ namespace vkcv { glfwGetWindowSize(m_window, nullptr, &height); return height; } -} \ No newline at end of file + + GLFWwindow *Window::getWindow() const { + return m_window; + } +}