diff --git a/CMakeLists.txt b/CMakeLists.txt index cfe411c374e3d794ce3c1ee9cea162aed86e7cc0..fa60a82591425ec6c9a44828df76fe97c3dcca4f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,11 @@ cmake_minimum_required(VERSION 3.16) project(vkcv) +# settings c++ standard for the framework set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) +# checking build type and setting up a variable if (CMAKE_BUILD_TYPE) string(TOLOWER "${CMAKE_BUILD_TYPE}" _vkcv_build_type) set(vkcv_build_${_vkcv_build_type} 1) @@ -11,15 +13,19 @@ endif() message("-- Language: [ C++ " ${CMAKE_CXX_STANDARD} " ]") +# setting up different paths set(vkcv_config ${PROJECT_SOURCE_DIR}/config) set(vkcv_config_ext ${vkcv_config}/ext) set(vkcv_lib lib) set(vkcv_source ${PROJECT_SOURCE_DIR}/src) +set(vkcv_include ${PROJECT_SOURCE_DIR}/include) +# initializes compiler flags with defaults set(vkcv_flags ${CMAKE_CXX_FLAGS}) +# enabling warnings in the debug build if (vkcv_build_debug) if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") set(vkcv_flags ${vkcv_flags} " -Weverything") @@ -30,19 +36,21 @@ if (vkcv_build_debug) endif() endif() +# add source files for compilation include(${vkcv_config}/Sources.cmake) + +# configure everything to use the required dependencies include(${vkcv_config}/Libraries.cmake) +# set the compiler flags for the framework set(CMAKE_CXX_FLAGS ${vkcv_flags}) message("-- Flags: [ ${CMAKE_CXX_FLAGS} ]") +# set the compile definitions aka preprocessor variables add_compile_definitions(${vkcv_definitions}) -if (vkcv_directories) - include_directories(${vkcv_directories}) -endif() - +# create VkCV framework as static library using all source files add_library(vkcv STATIC ${vkcv_sources}) if(MSVC) @@ -53,9 +61,17 @@ if(MSVC) source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${vkcv_sources}) endif() +# add include directories from dependencies as system includes target_include_directories(vkcv SYSTEM BEFORE PRIVATE ${vkcv_includes}) + +# add the own include directory for public headers +target_include_directories(vkcv BEFORE PUBLIC ${vkcv_include}) + +# link the framework using all required libraries target_link_libraries(vkcv ${vkcv_libraries}) +# add sub-projects/examples as targets add_subdirectory(projects) +# add doxygen as target if installed include(${vkcv_config}/ext/Doxygen.cmake) diff --git a/Doxyfile b/Doxyfile index 071110099373bf500cc4a94fd6be5183336c7865..10040ece70cd320944009390bcedf9c4e59e417a 100644 --- a/Doxyfile +++ b/Doxyfile @@ -864,7 +864,8 @@ WARN_LOGFILE = # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. -INPUT = src +INPUT = src \ + include # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses diff --git a/config/Libraries.cmake b/config/Libraries.cmake index 3b4c75ead3998641e3868ade305f281b3ce1bd93..ef199df6ed4d59acb87d6b0d6b4d2261081c82d4 100644 --- a/config/Libraries.cmake +++ b/config/Libraries.cmake @@ -1,5 +1,5 @@ -set(vkcv_config_lib ${vkcv_config}/lib) +set(vkcv_config_lib ${vkcv_config}/lib) set(vkcv_lib_path ${PROJECT_SOURCE_DIR}/${vkcv_lib}) if(NOT WIN32) @@ -10,21 +10,27 @@ if(NOT WIN32) list(APPEND vkcv_flags -fopenmp) endif() +# some formatted printing set(vkcv_config_msg " - Library: ") +# load dependencies via separate cmake file include(${vkcv_config_lib}/GLFW.cmake) # glfw-x11 / glfw-wayland # libglfw3-dev include(${vkcv_config_lib}/Vulkan.cmake) # vulkan-intel / vulkan-radeon / nvidia # libvulkan-dev +# cleanup of compiler flags if (vkcv_flags) list(REMOVE_DUPLICATES vkcv_flags) endif() +# cleanup of include directories from dependencies if (vkcv_includes) list(REMOVE_DUPLICATES vkcv_includes) endif () +# fix dependencies for different Linux distros (looking at you Ubuntu) include(${vkcv_config_ext}/CheckLibraries.cmake) +# cleanup of compiler definitions aka preprocessor variables if (vkcv_definitions) list(REMOVE_DUPLICATES vkcv_definitions) endif () diff --git a/config/Sources.cmake b/config/Sources.cmake index 9a9f55747194ab9972254d5bf57ac11735607b06..e390e997071d86f5dcd1f4744a1f8bfd348d0c99 100644 --- a/config/Sources.cmake +++ b/config/Sources.cmake @@ -1,9 +1,12 @@ +# adding all source files and header files of the framework: set(vkcv_sources - ${vkcv_source}/vkcv/Context.hpp - ${vkcv_source}/vkcv/Context.cpp - ${vkcv_source}/vkcv/Window.hpp + ${vkcv_include}/vkcv/Core.hpp + ${vkcv_source}/vkcv/Core.cpp + + ${vkcv_include}/vkcv/Handles.hpp + ${vkcv_source}/vkcv/Handles.cpp + + ${vkcv_include}/vkcv/Window.hpp ${vkcv_source}/vkcv/Window.cpp - ${vkcv_source}/vkcv/CoreManager.hpp - ${vkcv_source}/vkcv/CoreManager.cpp ) diff --git a/config/ext/CheckLibraries.cmake b/config/ext/CheckLibraries.cmake index 97950d1123f126cc555f376b86c90ed1acb2628e..3da1c06ebb529895bef1d8198c1a063173982394 100644 --- a/config/ext/CheckLibraries.cmake +++ b/config/ext/CheckLibraries.cmake @@ -1,6 +1,8 @@ +# cleanup of dependency libraries list(REMOVE_DUPLICATES vkcv_libraries) +# fixing paths of libraries to work on most Linux distros foreach (a_lib IN LISTS vkcv_libraries) if (NOT EXISTS "${a_lib}") string(REGEX MATCH ^/usr/lib/x86_64-linux-gnu/.*$ vkcv_usr_lib_u_match ${a_lib}) diff --git a/include/vkcv/Core.hpp b/include/vkcv/Core.hpp new file mode 100644 index 0000000000000000000000000000000000000000..a347380ece98514049bdac3b7f7d8fee008d15bb --- /dev/null +++ b/include/vkcv/Core.hpp @@ -0,0 +1,129 @@ +#pragma once +/** + * @file src/vkcv/Core.hpp + * @brief Handling of global states regarding dependencies + */ + +#include <vulkan/vulkan.hpp> +#include "vkcv/Handles.hpp" + +namespace vkcv +{ + // TODO: + class Buffer; + class Renderpass; + class Pipeline; + + class Core final + { + private: + class Context + { + public: + /** + * Constructor of #Context requires an @p instance, a @p physicalDevice and a @p device. + * + * @param instance Vulkan-Instance + * @param physicalDevice Vulkan-PhysicalDevice + * @param device Vulkan-Device + */ + Context(vk::Instance instance, vk::PhysicalDevice physicalDevice, vk::Device device) noexcept; + // explicit destruction of default constructor + Context() = delete; + // is never called directly + ~Context() noexcept; + + Context(const Context &other) = delete; // copy-ctor + Context(Context &&other) noexcept; // move-ctor + + Context & operator=(const Context &other) = delete; // copy assignment + Context & operator=(Context &&other) noexcept; // move assignment + + const vk::Instance &getInstance() const; + const vk::PhysicalDevice &getPhysicalDevice() const; + const vk::Device &getDevice() const; + + private: + vk::Instance m_Instance; + vk::PhysicalDevice m_PhysicalDevice; + vk::Device m_Device; + } m_Context; + + /** + * Constructor of #Core requires an @p context. + * + * @param context encapsulates various Vulkan objects + */ + explicit Core(Context &&context) noexcept; + // explicit destruction of default constructor + Core() = delete; + + public: + /** + * Destructor of #Core destroys the Vulkan objects contained in the core's context. + */ + ~Core() noexcept = default; + + /** + * Copy-constructor of #Core is deleted! + * + * @param other Other instance of #Context + */ + Core(const Core& other) = delete; + + /** + * Move-constructor of #Core uses default behavior! + * + * @param other Other instance of #Context + */ + Core(Core &&other) = delete; // move-ctor + + /** + * Copy assignment operator of #Core is deleted! + * + * @param other Other instance of #Context + * @return Reference to itself + */ + Core & operator=(const Core &other) = delete; + + /** + * Move assignment operator of #Core uses default behavior! + * + * @param other Other instance of #Context + * @return Reference to itself + */ + Core & operator=(Core &&other) = delete; + + [[nodiscard]] + const Context &getContext() const; + + /** + * Creates a #Core with given @p applicationName and @p applicationVersion for your application. + * + * It is also possible to require a specific amount of queues, ask for specific queue-flags or + * extensions. This function will take care of the required arguments as best as possible. + * + * To pass a valid version for your application, you should use #VK_MAKE_VERSION(). + * + * @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, + uint32_t applicationVersion, + uint32_t queueCount, + std::vector<vk::QueueFlagBits> queueFlags = {}, + std::vector<const char*> instanceExtensions = {}, + std::vector<const char*> deviceExtensions = {}); + + // TODO: + BufferHandle createBuffer(const Buffer &buf); + PassHandle createRenderPass(const Renderpass &pass) ; + PipelineHandle createPipeline(const Pipeline &pipeline); + + }; +} diff --git a/include/vkcv/Handles.hpp b/include/vkcv/Handles.hpp new file mode 100644 index 0000000000000000000000000000000000000000..4ec2bc058409e9119695700b2b727be9426c2bcd --- /dev/null +++ b/include/vkcv/Handles.hpp @@ -0,0 +1,16 @@ +#pragma once +/** + * @authors Artur Wasmut + * @file src/vkcv/Handles.cpp + * @brief Central header file for all possible handles that the framework will hand out. + */ + +#include <cstdint> + +namespace vkcv +{ + // Handle returned for any buffer created with the core/context objects + struct BufferHandle {uint64_t id;}; + struct PassHandle {uint64_t id;}; + struct PipelineHandle {uint64_t id;}; +} diff --git a/src/vkcv/Window.hpp b/include/vkcv/Window.hpp similarity index 98% rename from src/vkcv/Window.hpp rename to include/vkcv/Window.hpp index 62440d90143d5c4cd9a31fbdf46819fdeda2d101..080e55350ba82ae58da8afcc8758da3fda77f19f 100644 --- a/src/vkcv/Window.hpp +++ b/include/vkcv/Window.hpp @@ -8,6 +8,9 @@ #define GLFW_INCLUDE_VULKAN #include <GLFW/glfw3.h> +#define NOMINMAX +#include <algorithm> + namespace vkcv { class Window final { diff --git a/projects/CMakeLists.txt b/projects/CMakeLists.txt index 27305956cac6670331096364a6658a90efcc831b..f7687bca4ba7cb05835c8afda1396ad319a035c7 100644 --- a/projects/CMakeLists.txt +++ b/projects/CMakeLists.txt @@ -1,2 +1,3 @@ +# Add new projects/examples here: add_subdirectory(first_triangle) diff --git a/projects/first_triangle/CMakeLists.txt b/projects/first_triangle/CMakeLists.txt index c5a9046881dbab737b357b8a3cd875a9c6b284ae..40e016a71cd3e20690f3ace9ec0260aa37449a0c 100644 --- a/projects/first_triangle/CMakeLists.txt +++ b/projects/first_triangle/CMakeLists.txt @@ -1,10 +1,15 @@ cmake_minimum_required(VERSION 3.16) project(first_triangle) +# setting c++ standard for the project set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) +# adding source files to the project add_executable(first_triangle src/main.cpp) -target_include_directories(first_triangle SYSTEM BEFORE PRIVATE ${vkcv_source} ${vkcv_includes}) +# including headers of dependencies and the VkCV framework +target_include_directories(first_triangle SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes}) + +# linking with libraries from all dependencies and the VkCV framework target_link_libraries(first_triangle vkcv ${vkcv_libraries}) diff --git a/projects/first_triangle/src/main.cpp b/projects/first_triangle/src/main.cpp index cc592f468e0c3e95d64a1558404873c4ca19f8b9..0c981a25dc52db4363d2bc7835df3c3c9d7c49fa 100644 --- a/projects/first_triangle/src/main.cpp +++ b/projects/first_triangle/src/main.cpp @@ -1,5 +1,5 @@ #include <iostream> -#include <vkcv/Context.hpp> +#include <vkcv/Core.hpp> #include <vkcv/Window.hpp> int main(int argc, const char** argv) { @@ -10,13 +10,14 @@ int main(int argc, const char** argv) { 600, false ); - vkcv::Context context = vkcv::Context::create( + vkcv::Core core = vkcv::Core::create( applicationName, VK_MAKE_VERSION(0, 0, 1), 20, {vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eTransfer} ); + const auto &context = core.getContext(); const vk::Instance& instance = context.getInstance(); const vk::PhysicalDevice& physicalDevice = context.getPhysicalDevice(); const vk::Device& device = context.getDevice(); @@ -32,7 +33,30 @@ 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; } - while (window.isWindowOpen()) { + /* + * BufferHandle triangleVertices = core.createBuffer(vertices); + * BufferHandle triangleIndices = core.createBuffer(indices); + * + * // triangle Model creation goes here + * + * + * // attachment creation goes here + * PassHandle trianglePass = core.CreatePass(presentationPass); + * + * // shader creation goes here + * // material creation goes here + * + * PipelineHandle trianglePipeline = core.CreatePipeline(trianglePipeline); + */ + + while (window.isWindowOpen()) + { + // core.beginFrame(); or something like that + // core.execute(trianglePass, trianglePipeline, triangleModel); + // core.endFrame(); or something like that + + // TBD: synchronization + window.pollEvents(); } return 0; diff --git a/src/vkcv/Context.cpp b/src/vkcv/Context.cpp deleted file mode 100644 index 3d5dfca627a064a49a57a015b23c4c7d919a2f07..0000000000000000000000000000000000000000 --- a/src/vkcv/Context.cpp +++ /dev/null @@ -1,256 +0,0 @@ -/** - * @authors Tobias Frisch, Vanessa Karolek, Katharina Krämer, Sebastian Gaida - * @file src/vkcv/Context.cpp - * @brief Context class to handle instance, physical-device and device - */ - -#include "Context.hpp" -#include "CoreManager.hpp" - -namespace vkcv { - - Context::Context(vk::Instance instance, vk::PhysicalDevice physicalDevice, vk::Device device) - : m_instance(instance), m_physicalDevice(physicalDevice), m_device(device) - {} - - Context::~Context() { - m_device.destroy(); - m_instance.destroy(); - vkcv::terminateGLFW(); - } - - Context Context::create(const char* applicationName, uint32_t applicationVersion, uint32_t queueCount, std::vector<vk::QueueFlagBits> queueFlags, std::vector<const char*> instanceExtensions, std::vector<const char*> deviceExtensions) { - vkcv::initGLFW(); - - // check for layer support - - const std::vector<vk::LayerProperties>& layerProperties = vk::enumerateInstanceLayerProperties(); - - std::vector<const char*> supportedLayers; - supportedLayers.reserve(layerProperties.size()); - - for (auto& elem : layerProperties) { - supportedLayers.push_back(elem.layerName); - } - -// if in debug mode, check if validation layers are supported. Enable them if supported -#ifndef NDEBUG - std::vector<const char*> validationLayers = { - "VK_LAYER_KHRONOS_validation" - }; - - if (!Context::checkSupport(supportedLayers, validationLayers)) { - throw std::runtime_error("Validation layers requested but not available!"); - } -#endif - - // check for extension support - std::vector<vk::ExtensionProperties> instanceExtensionProperties = vk::enumerateInstanceExtensionProperties(); - - std::vector<const char*> supportedExtensions; - supportedExtensions.reserve(instanceExtensionProperties.size()); - - for (auto& elem : instanceExtensionProperties) { - supportedExtensions.push_back(elem.extensionName); - } - - if (!checkSupport(supportedExtensions, instanceExtensions)) { - throw std::runtime_error("The requested instance extensions are not supported!"); - } - - // for GLFW: get all required extensions - std::vector<const char*> requiredExtensions = Context::getRequiredExtensions(); - instanceExtensions.insert(instanceExtensions.end(), requiredExtensions.begin(), requiredExtensions.end()); - - const vk::ApplicationInfo applicationInfo( - applicationName, - applicationVersion, - "vkCV", - VK_MAKE_VERSION(0, 0, 1), - VK_HEADER_VERSION_COMPLETE - ); - - vk::InstanceCreateInfo instanceCreateInfo( - vk::InstanceCreateFlags(), - &applicationInfo, - 0, - nullptr, - static_cast<uint32_t>(instanceExtensions.size()), - instanceExtensions.data() - ); - -#ifndef NDEBUG - instanceCreateInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size()); - instanceCreateInfo.ppEnabledLayerNames = validationLayers.data(); -#endif - - vk::Instance instance = vk::createInstance(instanceCreateInfo); - - std::vector<vk::PhysicalDevice> physicalDevices = instance.enumeratePhysicalDevices(); - vk::PhysicalDevice physicalDevice = pickPhysicalDevice(instance); - - // check for physical device extension support - std::vector<vk::ExtensionProperties> deviceExtensionProperties = physicalDevice.enumerateDeviceExtensionProperties(); - supportedExtensions.clear(); - for (auto& elem : deviceExtensionProperties) { - supportedExtensions.push_back(elem.extensionName); - } - if (!checkSupport(supportedExtensions, deviceExtensions)) { - 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 - - // 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 - ); - -#ifndef NDEBUG - deviceCreateInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size()); - deviceCreateInfo.ppEnabledLayerNames = validationLayers.data(); -#endif - - - vk::Device device = physicalDevice.createDevice(deviceCreateInfo); - // TODO: implement device.getQueue() to access the queues, if needed - - return Context(instance, physicalDevice, device); - } - - const vk::Instance& Context::getInstance() const { - return m_instance; - } - - const vk::PhysicalDevice& Context::getPhysicalDevice() const { - return m_physicalDevice; - } - - const vk::Device& Context::getDevice() const { - return m_device; - } - - vk::PhysicalDevice Context::pickPhysicalDevice(vk::Instance& instance) { - vk::PhysicalDevice phyDevice; - std::vector<vk::PhysicalDevice> devices = instance.enumeratePhysicalDevices(); - - if (devices.empty()) { - throw std::runtime_error("failed to find GPUs with Vulkan support!"); - } - - int max_score = -1; - for (const auto& device : devices) { - int score = deviceScore(device); - if (score > max_score) { - max_score = score; - phyDevice = device; - } - } - - if (max_score == -1) { - throw std::runtime_error("failed to find a suitable GPU!"); - } - - return phyDevice; - } - - int Context::deviceScore(const vk::PhysicalDevice& physicalDevice) { - int score = 0; - vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); - std::vector<vk::QueueFamilyProperties> qFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // for every queue family compute queue flag bits and the amount of queues - for (const auto& qFamily : qFamilyProperties) { - uint32_t qCount = qFamily.queueCount; - uint32_t bitCount = (static_cast<uint32_t>(qFamily.queueFlags & vk::QueueFlagBits::eCompute) != 0) - + (static_cast<uint32_t>(qFamily.queueFlags & vk::QueueFlagBits::eGraphics) != 0) - + (static_cast<uint32_t>(qFamily.queueFlags & vk::QueueFlagBits::eTransfer) != 0) - + (static_cast<uint32_t>(qFamily.queueFlags & vk::QueueFlagBits::eSparseBinding) != 0); - score += static_cast<int>(qCount * bitCount); - } - - // compute the VRAM of the physical device - vk::PhysicalDeviceMemoryProperties memoryProperties = physicalDevice.getMemoryProperties(); - int vram = static_cast<int>(memoryProperties.memoryHeaps[0].size / 1E9); - score *= vram; - - if (properties.deviceType == vk::PhysicalDeviceType::eDiscreteGpu) { - score *= 2; - } - else if (properties.deviceType != vk::PhysicalDeviceType::eIntegratedGpu) { - score = -1; - } - - return score; - } - - std::vector<vk::DeviceQueueCreateInfo> Context::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 int maxCreatableQueues = std::min(create, qFamilyCandidates[i].queueCount); - vk::DeviceQueueCreateInfo qCreateInfo( - vk::DeviceQueueCreateFlags(), - i, - maxCreatableQueues, - qPriorities.data() - ); - queueCreateInfos.push_back(qCreateInfo); - create -= maxCreatableQueues; - } - - return queueCreateInfos; - } - - bool Context::checkSupport(std::vector<const char*>& supported, std::vector<const char*>& check) { - for (auto checkElem : check) { - bool found = false; - for (auto supportedElem : supported) { - if (strcmp(supportedElem, checkElem) == 0) { - found = true; - break; - } - } - if (!found) - return false; - } - return true; - } - - std::vector<const char*> Context::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; - } -} diff --git a/src/vkcv/Context.hpp b/src/vkcv/Context.hpp deleted file mode 100644 index 1d11f82743ada5d1493df48ece54e558c234df47..0000000000000000000000000000000000000000 --- a/src/vkcv/Context.hpp +++ /dev/null @@ -1,149 +0,0 @@ -#pragma once -/** - * @authors Tobias Frisch, Vanessa Karolek, Katharina Krämer, Sebastian Gaida - * @file src/vkcv/Context.hpp - * @brief Context class to handle instance, physical-device and device - */ - -#include <vulkan/vulkan.hpp> - -namespace vkcv { - - class Context final { - private: - vk::Instance m_instance; - vk::PhysicalDevice m_physicalDevice; - vk::Device m_device; - - /** - * Constructor of #Context requires an @p instance, a @p physicalDevice and a @p device. - * - * @param instance Vulkan-Instance - * @param physicalDevice Vulkan-PhysicalDevice - * @param device Vulkan-Device - */ - Context(vk::Instance instance, vk::PhysicalDevice physicalDevice, vk::Device device); - - public: - /** - * Copy-constructor of #Context is deleted! - * - * @param other Other instance of #Context - */ - Context(const Context &other) = delete; - - /** - * Move-constructor of #Context uses default behavior! - * - * @param other Other instance of #Context - */ - Context(Context &&other) = default; - - /** - * Get the Vulkan handle for the instance. - * - * @return Vulkan-Instance - */ - [[nodiscard]] - const vk::Instance& getInstance() const; - - /** - * Get the Vulkan handle for the physical-device. - * - * @return Vulkan-PhysicalDevice - */ - [[nodiscard]] - const vk::PhysicalDevice& getPhysicalDevice() const; - - /** - * Get the Vulkan handle for the device. - * - * @return Vulkan-Device - */ - [[nodiscard]] - const vk::Device& getDevice() const; - - /** - * Destructor of #Context - */ - virtual ~Context(); - - /** - * Copy-operator of #Context is deleted! - * - * @param other Other instance of #Context - * @return Reference to itself - */ - Context& operator=(const Context &other) = delete; - - /** - * Move-operator of #Context uses default behavior! - * - * @param other Other instance of #Context - * @return Reference to itself - */ - Context& operator=(Context &&other) = default; - - /** - * Creates a #Context with given @p applicationName and @p applicationVersion for your application. - * - * It is also possible to require a specific amount of queues, ask for specific queue-flags or - * extensions. This function will take care of the required arguments as best as possible. - * - * To pass a valid version for your application, you should use #VK_MAKE_VERSION(). - * - * @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 Context create(const char* applicationName, uint32_t applicationVersion, uint32_t queueCount = 1, std::vector<vk::QueueFlagBits> queueFlags = {}, std::vector<const char*> instanceExtensions = {}, std::vector<const char*> deviceExtensions = {}); - - /** - * @brief With the help of the reference "supported" all elements in "check" checked, - * if they are supported by the physical device. - * @param supported The reference that can be used to check "check" - * @param check The elements to be checked - * @return True, if all elements in "check" are supported - */ - static bool checkSupport(std::vector<const char*> &supported, std::vector<const char*> &check); - - /** - * @brief Gets all extensions required, i.e. GLFW and advanced debug extensions. - * @return The required extensions - */ - static std::vector<const char*> getRequiredExtensions(); - - /** - * @brief All existing physical devices will be evaluated by deviceScore. - * @param instance The instance - * @return The optimal physical device - * @see Context.deviceScore - */ - static vk::PhysicalDevice pickPhysicalDevice(vk::Instance& instance); - - /** - * @brief The physical device is evaluated by three categories: - * discrete GPU vs. integrated GPU, amount of queues and its abilities, and VRAM.physicalDevice. - * @param physicalDevice The physical device - * @return Device score as integer - */ - static int deviceScore(const vk::PhysicalDevice &physicalDevice); - - /** - * @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 - */ - static std::vector<vk::DeviceQueueCreateInfo> getQueueCreateInfos(vk::PhysicalDevice& physicalDevice, uint32_t queueCount, std::vector<float>& qPriorities, std::vector<vk::QueueFlagBits> &queueFlags); - }; - -} diff --git a/src/vkcv/Core.cpp b/src/vkcv/Core.cpp new file mode 100644 index 0000000000000000000000000000000000000000..962f55606d492501a95bc3e31c736e1869f61d53 --- /dev/null +++ b/src/vkcv/Core.cpp @@ -0,0 +1,325 @@ +/** + * @authors Sebastian Gaida + * @file src/vkcv/CoreManager.cpp + * @brief Handling of global states regarding dependencies + */ + +#include "vkcv/Core.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. + * @param physicalDevice The physical device + * @return Device score as integer + */ + int deviceScore(const vk::PhysicalDevice& physicalDevice) + { + int score = 0; + vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); + std::vector<vk::QueueFamilyProperties> qFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // for every queue family compute queue flag bits and the amount of queues + for (const auto& qFamily : qFamilyProperties) { + uint32_t qCount = qFamily.queueCount; + uint32_t bitCount = (static_cast<uint32_t>(qFamily.queueFlags & vk::QueueFlagBits::eCompute) != 0) + + (static_cast<uint32_t>(qFamily.queueFlags & vk::QueueFlagBits::eGraphics) != 0) + + (static_cast<uint32_t>(qFamily.queueFlags & vk::QueueFlagBits::eTransfer) != 0) + + (static_cast<uint32_t>(qFamily.queueFlags & vk::QueueFlagBits::eSparseBinding) != 0); + score += static_cast<int>(qCount * bitCount); + } + + // compute the VRAM of the physical device + vk::PhysicalDeviceMemoryProperties memoryProperties = physicalDevice.getMemoryProperties(); + auto vram = static_cast<int>(memoryProperties.memoryHeaps[0].size / static_cast<uint32_t>(1E9)); + score *= vram; + + if (properties.deviceType == vk::PhysicalDeviceType::eDiscreteGpu) { + score *= 2; + } + else if (properties.deviceType != vk::PhysicalDeviceType::eIntegratedGpu) { + score = -1; + } + + return score; + } + + /** + * @brief All existing physical devices will be evaluated by deviceScore. + * @param instance The instance + * @return The optimal physical device + * @see Context.deviceScore + */ + vk::PhysicalDevice pickPhysicalDevice(vk::Instance& instance) + { + vk::PhysicalDevice phyDevice; + std::vector<vk::PhysicalDevice> devices = instance.enumeratePhysicalDevices(); + + if (devices.empty()) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + int max_score = -1; + for (const auto& device : devices) { + int score = deviceScore(device); + if (score > max_score) { + max_score = score; + phyDevice = device; + } + } + + if (max_score == -1) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + + 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. + * @param supported The reference that can be used to check "check" + * @param check The elements to be checked + * @return True, if all elements in "check" are supported + */ + bool checkSupport(std::vector<const char*>& supported, std::vector<const char*>& check) + { + for (auto checkElem : check) { + bool found = false; + for (auto supportedElem : supported) { + if (strcmp(supportedElem, checkElem) == 0) { + found = true; + break; + } + } + if (!found) + return false; + } + return true; + } + + Core Core::create(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(); + + std::vector<const char*> supportedLayers; + supportedLayers.reserve(layerProperties.size()); + + for (auto& elem : layerProperties) { + supportedLayers.push_back(elem.layerName); + } + +// if in debug mode, check if validation layers are supported. Enable them if supported +#ifndef NDEBUG + std::vector<const char*> validationLayers = { + "VK_LAYER_KHRONOS_validation" + }; + + if (!checkSupport(supportedLayers, validationLayers)) { + throw std::runtime_error("Validation layers requested but not available!"); + } +#endif + + // check for extension support + std::vector<vk::ExtensionProperties> instanceExtensionProperties = vk::enumerateInstanceExtensionProperties(); + + std::vector<const char*> supportedExtensions; + supportedExtensions.reserve(instanceExtensionProperties.size()); + + for (auto& elem : instanceExtensionProperties) { + supportedExtensions.push_back(elem.extensionName); + } + + if (!checkSupport(supportedExtensions, instanceExtensions)) { + throw std::runtime_error("The requested instance extensions are not supported!"); + } + +#ifndef NDEBUG + instanceExtensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); +#endif + + const vk::ApplicationInfo applicationInfo( + applicationName, + applicationVersion, + "vkCV", + VK_MAKE_VERSION(0, 0, 1), + VK_HEADER_VERSION_COMPLETE + ); + + vk::InstanceCreateInfo instanceCreateInfo( + vk::InstanceCreateFlags(), + &applicationInfo, + 0, + nullptr, + static_cast<uint32_t>(instanceExtensions.size()), + instanceExtensions.data() + ); + +#ifndef NDEBUG + instanceCreateInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size()); + instanceCreateInfo.ppEnabledLayerNames = validationLayers.data(); +#endif + + vk::Instance instance = vk::createInstance(instanceCreateInfo); + + std::vector<vk::PhysicalDevice> physicalDevices = instance.enumeratePhysicalDevices(); + vk::PhysicalDevice physicalDevice = pickPhysicalDevice(instance); + + // check for physical device extension support + std::vector<vk::ExtensionProperties> deviceExtensionProperties = physicalDevice.enumerateDeviceExtensionProperties(); + supportedExtensions.clear(); + for (auto& elem : deviceExtensionProperties) { + supportedExtensions.push_back(elem.extensionName); + } + if (!checkSupport(supportedExtensions, deviceExtensions)) { + 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 + + // 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 + ); + +#ifndef NDEBUG + deviceCreateInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size()); + deviceCreateInfo.ppEnabledLayerNames = validationLayers.data(); +#endif + + + 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)); + } + + const Core::Context &Core::getContext() const + { + return m_Context; + } + + Core::Core(Core::Context &&context) noexcept : + m_Context(std::move(context)) + {} + + Core::Context::Context(vk::Instance instance, + vk::PhysicalDevice physicalDevice, + vk::Device device) noexcept : + m_Instance{instance}, + m_PhysicalDevice{physicalDevice}, + m_Device{device} + {} + + Core::Context::~Context() noexcept + { + m_Device.destroy(); + m_Instance.destroy(); + } + + const vk::Instance &Core::Context::getInstance() const + { + return m_Instance; + } + + const vk::PhysicalDevice &Core::Context::getPhysicalDevice() const + { + return m_PhysicalDevice; + } + + const vk::Device &Core::Context::getDevice() const + { + return m_Device; + } + + Core::Context::Context(Core::Context &&other) noexcept: + m_Instance(other.m_Instance), + m_PhysicalDevice(other.m_PhysicalDevice), + m_Device(other.m_Device) + { + other.m_Instance = nullptr; + other.m_PhysicalDevice = nullptr; + other.m_Device = nullptr; + } + + Core::Context &Core::Context::operator=(Core::Context &&other) noexcept + { + m_Instance = other.m_Instance; + m_PhysicalDevice = other.m_PhysicalDevice; + m_Device = other.m_Device; + + other.m_Instance = nullptr; + other.m_PhysicalDevice = nullptr; + other.m_Device = nullptr; + + return *this; + } +} diff --git a/src/vkcv/CoreManager.cpp b/src/vkcv/CoreManager.cpp deleted file mode 100644 index 471075e3b48eb6aba8251d2924c00462f506fbc3..0000000000000000000000000000000000000000 --- a/src/vkcv/CoreManager.cpp +++ /dev/null @@ -1,31 +0,0 @@ -/** - * @authors Sebastian Gaida - * @file src/vkcv/CoreManager.cpp - * @brief Handling of global states regarding dependencies - */ - -#include "CoreManager.hpp" - -namespace vkcv { - - int glfwCounter = 0; - - void initGLFW() { - - if (glfwCounter == 0) { - int glfwSuccess = glfwInit(); - - if (glfwSuccess == GLFW_FALSE) { - throw std::runtime_error("Could not initialize GLFW"); - } - } - glfwCounter++; - } - - void terminateGLFW() { - if (glfwCounter == 1) { - glfwTerminate(); - } - glfwCounter--; - } -} diff --git a/src/vkcv/CoreManager.hpp b/src/vkcv/CoreManager.hpp deleted file mode 100644 index a4104ae4a8dca0b01825f023bcb3ec2a2808c876..0000000000000000000000000000000000000000 --- a/src/vkcv/CoreManager.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once -/** - * @authors Sebastian Gaida - * @file src/vkcv/CoreManager.hpp - * @brief Handling of global states regarding dependencies - */ - -#include <GLFW/glfw3.h> -#include <stdexcept> - -namespace vkcv { - - /** - * initializes glfw once and increases the counter - */ - void initGLFW(); - - /** - * terminates glfw once, if it was initialized or decreases the counter - */ - void terminateGLFW(); -} diff --git a/src/vkcv/Handles.cpp b/src/vkcv/Handles.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1337a132ae0b8721bd2776e24212f73dfb94ae46 --- /dev/null +++ b/src/vkcv/Handles.cpp @@ -0,0 +1 @@ +#include "vkcv/Handles.hpp" diff --git a/src/vkcv/Window.cpp b/src/vkcv/Window.cpp index 7d8b86e08f25fe7741db1cfa777f00054aa8cee0..8814a5abcf7977386490d9783d5b121b7f986651 100644 --- a/src/vkcv/Window.cpp +++ b/src/vkcv/Window.cpp @@ -4,22 +4,30 @@ * @brief Window class to handle a basic rendering surface and input */ -#include "Window.hpp" -#include "CoreManager.hpp" +#include "vkcv/Window.hpp" namespace vkcv { + static uint32_t s_WindowCount = 0; + Window::Window(GLFWwindow *window) : m_window(window) { } Window::~Window() { glfwDestroyWindow(m_window); - vkcv::terminateGLFW(); + s_WindowCount--; + + if(s_WindowCount == 0) + glfwTerminate(); } Window Window::create(const char *windowTitle, int width, int height, bool resizable) { - vkcv::initGLFW(); + if(s_WindowCount == 0) + glfwInit(); + + s_WindowCount++; + width = std::max(width, 1); height = std::max(height, 1); @@ -28,6 +36,8 @@ namespace vkcv { GLFWwindow *window; window = glfwCreateWindow(width, height, windowTitle, nullptr, nullptr); return Window(window); + + } bool Window::isWindowOpen() const {