diff --git a/.gitmodules b/.gitmodules index bfdf187..9429753 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,6 +3,6 @@ url = https://github.com/ocornut/imgui.git branch = docking -[submodule "include/CL11"] - path = include/CL11 - url = "https://github.com/CLIUtils/CLI11.git" +[submodule "include/CLI11"] + path = include/CLI11 + url = https://github.com/CLIUtils/CLI11.git diff --git a/CMakeLists.txt b/CMakeLists.txt index bfc34ef..158faa5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,6 +31,9 @@ find_program (GLSLANG_VALIDATOR "glslangValidator" HINTS $ENV{VULKAN_SDK}/bin RE set_property (TARGET glslang::validator PROPERTY IMPORTED_LOCATION "${GLSLANG_VALIDATOR}") find_program(SLANGC_EXECUTABLE slangc HINTS $ENV{VULKAN_SDK}/bin REQUIRED) +# CL11 +add_subdirectory(include/CLI11) + # fastgltf because it's not in nixpkgs FetchContent_Declare( @@ -91,7 +94,7 @@ endfunction() # A function to automate executables aditions function ( addBinary BINARY_NAME ) - cmake_parse_arguments ( BINARY "IMGUI;LICENSE" "SOURCE" "SHADERS;SLANG_SHADERS;LIBS;TEXTURES;MODELS" ${ARGN} ) + cmake_parse_arguments ( BINARY "IMGUI;LICENSE;GLOB_INCLUDE" "SOURCE" "SHADERS;SLANG_SHADERS;LIBS;TEXTURES;MODELS" ${ARGN} ) if (DEFINED BINARY_SOURCE) add_executable ( ${BINARY_NAME} src/${BINARY_SOURCE}.cpp ) endif() @@ -118,20 +121,21 @@ function ( addBinary BINARY_NAME ) ) if(BINARY_IMGUI) target_include_directories(${BINARY_NAME} PRIVATE - ${CMAKE_SOURCE_DIR}/src/imgui - ${CMAKE_SOURCE_DIR}/src/imgui/backends + ${CMAKE_SOURCE_DIR}/include/imgui + ${CMAKE_SOURCE_DIR}/include/imgui/backends ) + target_sources(${BINARY_NAME} PRIVATE - ${CMAKE_SOURCE_DIR}/src/imgui/imgui.cpp + ${CMAKE_SOURCE_DIR}/include/imgui/imgui.cpp - ${CMAKE_SOURCE_DIR}/src/imgui/imgui_draw.cpp - ${CMAKE_SOURCE_DIR}/src/imgui/imgui_tables.cpp - ${CMAKE_SOURCE_DIR}/src/imgui/imgui_widgets.cpp - ${CMAKE_SOURCE_DIR}/src/imgui/imgui_demo.cpp - #${CMAKE_SOURCE_DIR}/src/imgui/imgui_memory_editor.h + ${CMAKE_SOURCE_DIR}/include/imgui/imgui_draw.cpp + ${CMAKE_SOURCE_DIR}/include/imgui/imgui_tables.cpp + ${CMAKE_SOURCE_DIR}/include/imgui/imgui_widgets.cpp + ${CMAKE_SOURCE_DIR}/include/imgui/imgui_demo.cpp + #${CMAKE_SOURCE_DIR}/include/imgui/imgui_memory_editor.h - ${CMAKE_SOURCE_DIR}/src/imgui/backends/imgui_impl_glfw.cpp - ${CMAKE_SOURCE_DIR}/src/imgui/backends/imgui_impl_vulkan.cpp + ${CMAKE_SOURCE_DIR}/include/imgui/backends/imgui_impl_glfw.cpp + ${CMAKE_SOURCE_DIR}/include/imgui/backends/imgui_impl_vulkan.cpp ) endif() @@ -219,7 +223,7 @@ addBinary ( SHADERS ${SHADER_SOURCES} SHADERS - LIBS glfw fmt vk-bootstrap vulkan + LIBS glfw fmt vk-bootstrap vulkan CLI11::CLI11 IMGUI LICENSE ) diff --git a/include/CLI11 b/include/CLI11 new file mode 160000 index 0000000..c1cfe00 --- /dev/null +++ b/include/CLI11 @@ -0,0 +1 @@ +Subproject commit c1cfe00d2f3d862aecfe6e69ec810414d5f4c906 diff --git a/src/Common/Logger.h b/src/Common/Logger.h index aed44a9..079b6b8 100644 --- a/src/Common/Logger.h +++ b/src/Common/Logger.h @@ -86,8 +86,12 @@ class Logger { Args&&... args ); // Logs to console and file, in cases of crashes or errors + + // Utils functions void merge(std::string_view source); // Merges a source dumped logs into this + void logSeparator(); + void clear(); private: @@ -301,6 +305,10 @@ void Logger::merge(std::string_view source) { logBuffer += source; } +void Logger::logSeparator() { + logDisk("========================================================================="); +} + void Logger::clear() { logBuffer.clear(); } diff --git a/src/Common/Types.h b/src/Common/Types.h index 1498032..e79ad2c 100644 --- a/src/Common/Types.h +++ b/src/Common/Types.h @@ -100,6 +100,11 @@ struct GPUDrawPushConstants { VkDeviceAddress vertexBuffer; }; +// Engine config +struct EngineConfig { + int gpuIndex; +}; + // "Global" ? (I hope so) logger Logger logger; diff --git a/src/Engine/engine/engine.h b/src/Engine/engine/engine.h index fe07a6c..ab8bbcb 100644 --- a/src/Engine/engine/engine.h +++ b/src/Engine/engine/engine.h @@ -44,6 +44,8 @@ constexpr uint32_t FRAME_OVERLAP = 2; class Engine { public: //========== Class Members ========== + // Config + int _gpuIndex {0}; // Unitialized bool _isInitialized { false }; int _frameNumber { 0 }; VkExtent2D _windowExtent; @@ -128,6 +130,9 @@ class Engine { GPUMeshBuffers rectangle; + void parseConfig( + int gpuIndex = 0 + ); void init(); @@ -141,6 +146,9 @@ class Engine { std::function && function ); + // Public functions (for CLI args) + void getAvailableGPU(); + private: using enum LogModule; using enum LogLevel; @@ -266,6 +274,13 @@ constexpr bool bUseValidationLayers = false; //=== Main Functions === //====================== +void Engine::parseConfig( + int gpuIndex +) { + _gpuIndex = gpuIndex; +} + + void Engine::init() { #ifdef DEBUG std::ifstream infile("LICENSE"); @@ -352,7 +367,7 @@ void Engine::cleanup() { glfwTerminate(); } - _logger.logDisk("========================================================================="); + _logger.logSeparator(); #ifdef DEBUG _logger.logConsole(CLEANUP, EINFO, "Engine cleaned up !"); @@ -616,27 +631,118 @@ void Engine::initVulkan() { .bufferDeviceAddress = true }; - // Use vkBootstrap to select the GPU - vkb::PhysicalDeviceSelector selector { vkbInst }; - vkb::PhysicalDevice physicalDevice = selector - .set_minimum_version(1, 3) - .set_required_features_13(features13) - .set_required_features_12(features12) - .set_surface(_surface) - .select() - .value(); + if(_gpuIndex < 0) { + // No overwrite, use VKB - vkb::DeviceBuilder deviceBuilder { physicalDevice }; - _logger.logAll(ENGINE, EINFO, "Device {}", physicalDevice.name); + // Use vkBootstrap to select the GPU + vkb::PhysicalDeviceSelector selector { vkbInst }; + vkb::PhysicalDevice physicalDevice = selector + .set_minimum_version(1, 3) + .set_required_features_13(features13) + .set_required_features_12(features12) + .set_surface(_surface) + .select() + .value(); - vkb::Device vkbDevice { deviceBuilder.build().value() }; + vkb::DeviceBuilder deviceBuilder { physicalDevice }; - _device = { vkbDevice.device }; - _choosenGPU = { physicalDevice.physical_device }; + _logger.logAll(ENGINE, EINFO, "Device {}", physicalDevice.name); - _graphicsQueue = { vkbDevice.get_queue(vkb::QueueType::graphics).value() }; - _graphicsQueueFamily = { vkbDevice.get_queue_index(vkb::QueueType::graphics).value() }; + vkb::Device vkbDevice { deviceBuilder.build().value() }; + + _device = { vkbDevice.device }; + _choosenGPU = { physicalDevice.physical_device }; + + _graphicsQueue = { vkbDevice.get_queue(vkb::QueueType::graphics).value() }; + _graphicsQueueFamily = { vkbDevice.get_queue_index(vkb::QueueType::graphics).value() }; + + } else { + // Overwrite, select GPU by hand + #ifdef DEBUG + _logger.logAll(ENGINE, EINFO, "Manual GPU selection, choose {}", _gpuIndex); + #endif + + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(_instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("[Engine:Error] No Vulkan GPU found !"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(_instance, &deviceCount, devices.data()); + + if (_gpuIndex >= deviceCount) { + throw std::runtime_error( + "[Engine:Error] GPU index " + + std::to_string(_gpuIndex) + + " out of bounds !(" + + std::to_string(deviceCount) + + " available GPUs)" + ); + } + + _choosenGPU = devices[_gpuIndex]; + + VkPhysicalDeviceProperties props{}; + vkGetPhysicalDeviceProperties(_choosenGPU, &props); + _logger.logAll(ENGINE, EINFO, "Device {}", props.deviceName); + + // Now, find queues by hand + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(_choosenGPU, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(_choosenGPU, &queueFamilyCount, queueFamilies.data()); + + std::optional queueFamily; + for (uint32_t i = 0; i < queueFamilyCount; ++i) { + VkBool32 presentSupport = VK_FALSE; + vkGetPhysicalDeviceSurfaceSupportKHR(_choosenGPU, i, _surface, &presentSupport); + + if ((queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) && presentSupport) { + queueFamily = i; + break; + } + } + + if (!queueFamily.has_value()) { + throw std::runtime_error("[Engine:Error] No graphics queue compatible with the surface on the selected GPU !"); + } + + _graphicsQueueFamily = queueFamily.value(); + + + // 3. Créer le device logique + float queuePriority = 1.0f; + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = _graphicsQueueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + + + features13.pNext = &features12; + + std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME + }; + + VkDeviceCreateInfo deviceCreateInfo{}; + deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + deviceCreateInfo.pNext = &features13; + deviceCreateInfo.queueCreateInfoCount = 1; + deviceCreateInfo.pQueueCreateInfos = &queueCreateInfo; + deviceCreateInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + deviceCreateInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (vkCreateDevice(_choosenGPU, &deviceCreateInfo, nullptr, &_device) != VK_SUCCESS) { + throw std::runtime_error("[Engine:Error] Failed to create Vulkan device !"); + } + + vkGetDeviceQueue(_device, _graphicsQueueFamily, 0, &_graphicsQueue); + } VmaAllocatorCreateInfo allocatorInfo = {}; allocatorInfo.physicalDevice = _choosenGPU; @@ -2067,7 +2173,50 @@ void Engine::resizeRenderImage(uint32_t width, uint32_t height) { #endif } +void Engine::getAvailableGPU() { + // Creates a temporary Vulkan Instance + // Largely copied from initVulkan function + #ifdef DEBUG + _logger.logDisk(ENGINE, EINFO, "Listing available GPUs..."); + #endif + vkb::InstanceBuilder builder; + // Create a minimal instance + auto instRet = builder.set_app_name("Engine") + .request_validation_layers(false) + .use_default_debug_messenger() + .require_api_version(1, 3, 0) + .build(); + vkb::Instance vkbInst = instRet.value(); + + VkInstance instance = vkbInst.instance; + + // Get GPUs + uint32_t deviceCount {0}; + + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + // Juicy vector, to interate upon + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + _logger.logAll(ENGINE, EINFO, "Found {} devices !", deviceCount); + // Now, loop ! + for( + uint32_t i = 0; + i < devices.size(); + ++i + ) { + // Get properties + VkPhysicalDeviceProperties2 deviceProperties{ + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2 + }; + vkGetPhysicalDeviceProperties2( + devices[i], + &deviceProperties + ); + _logger.logAll(ENGINE, EINFO, "GPU {}:\n{}", i, deviceProperties.properties.deviceName); + } +} #endif #endif diff --git a/src/Engine/main.cpp b/src/Engine/main.cpp index d8903dd..52187d0 100644 --- a/src/Engine/main.cpp +++ b/src/Engine/main.cpp @@ -7,18 +7,82 @@ Also, checkout the roadmap #define ENGINE_IMPL #include "engine/engine.h" +#include "CLI/CLI.hpp" + +class ArgHandler { + public: + CLI::App* _run; + // All options + bool _listGPU {false}; + int _gpuIndex {0}; + + void buildArgs() { + // Setup + // Subcommands + _run = _app.add_subcommand("run", "Starts the Engine"); + + // Main options/flags + _app.add_flag("--list-gpu", _listGPU, "Lists available Graphics cards, useful for debugging"); + + // Subcommand options/flags + // Run + _run->add_option("--gpu, -g", _gpuIndex, "Manually select a GPU"); + } + + int dispatch(int argc, char* argv[]) { + CLI11_PARSE(_app, argc, argv); + + return 0; + } + + void printHelp() { + fmt::println("{}", _app.help()); + } + + + private: + CLI::App _app; +}; + int main(int argc, char* argv[]) { + // Classes we need Engine engine; + ArgHandler handler; // Exemple of overwriting a class value //engine._windowExtent = { 67, 67 }; - engine.init(); + handler.buildArgs(); - engine.run(); + if (argc == 1) { + handler.printHelp(); + return 0; + } - engine.cleanup(); + // Checks for -h flag + int result = handler.dispatch(argc, argv); + if(result != 0) return result; + + if (handler._run->parsed() && + handler._run->get_help_ptr()->as()) { + return 0; + } + + if(handler._listGPU) { + engine.getAvailableGPU(); + return 0; + } + + if(handler._run->parsed()){ + engine.parseConfig(handler._gpuIndex); + + engine.init(); + + engine.run(); + + engine.cleanup(); + } return 0; }