From a63c271f922bbf45b06f569c3123812fae2379ec Mon Sep 17 00:00:00 2001 From: Preston McGee <106772059+Blitblank@users.noreply.github.com> Date: Sat, 16 May 2026 12:48:39 -0700 Subject: [PATCH] Feature/VkExtensions+VkLayers (#2) --- CMakeLists.txt | 3 + CONTRIBUTING.md | 14 ++++ README.md | 22 ++++++- src/Engine.cpp | 169 ++++++++++++++++++++++++++++++++++++++++++------ src/Engine.hpp | 28 +++++++- 5 files changed, 213 insertions(+), 23 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c8802c5..9e7c3d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,6 +56,9 @@ target_link_options(maiden_core PRIVATE --coverage) target_link_options(maiden PRIVATE --coverage) target_link_options(maiden_test PRIVATE --coverage) +# vulkan necessity +target_compile_definitions(maiden_core PRIVATE VULKAN_HPP_NO_STRUCT_CONSTRUCTORS) + target_link_libraries(maiden PRIVATE maiden_core SDL3::SDL3 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ecf742c..c782eff 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,6 +19,20 @@ Basic outline where I pretty much copy the guide: - ... Boring optimization stuff, like mipmaps, multithreading, multisampling - it has a section on raytracing :3 + Goals: + - loading 3d models + - applying textures + - simple diffuse lighting + - vertex animations + - 3d camera and scene + - transparency + +Future ambitions: + - entity component system + - shadows + - raytracing + - rigidbody simulation + ## Supporting App Infrastructure Although not crucial to the core rendering functions of the app, features separate from the rendering engine are convenient to have for testing and usability. Components below are less urgent but should be simpler to develop and implement. diff --git a/README.md b/README.md index 3515f4b..98f0210 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,13 @@ The maiden project is a GPU accelerated 3D rendering engine built with C++ based ### Clone Repository ```bash -$ git clone https://git.vxbard.net/homeburger/maiden.git +# ssh recommended for contribution +$ git clone git@github.com:Blitblank/maiden.git +# http if you don't like ssh: +$ git clone https://github.com/Blitblank/maiden.git # If there's any necessary submodules then: -$ git clone --recurse-submodules https://git.vxbard.net/homeburger/maiden.git +$ git clone --recurse-submodules git@github.com:Blitblank/maiden.git # If you have already cloned the repository and you need its submodules: $ git submodule update --init --recursive @@ -70,7 +73,20 @@ $ cd build $ gcovr -r .. --filter "../src" ``` -### app troubleshooting here +## App Troubleshooting +Basically these are some tricky situations that I encountered when trying to execute this app throughout this development phase. If you are running on WSL Ubuntu 26.04 like me, then you mightr run into these too, hopefully my steps help fix. +note: I am running an x86_64 system with an Nvidia GPU so some things may be slightly different if your system doesn't match. + +### [WARN: COPY MODE] +This seems like a WSL specific error and causes real issues with relaying graphics from linux to windows. I fixed this by installing new mesa drivers as reccommended by https://github.com/microsoft/wslg/discussions/312: +```bash +$ sudo add-apt-repository ppa:kisak/kisak-mesa +$ sudo apt-get update && sudo apt upgrade +``` +Note: this resulted in the following erre "WARNING: dzn is not a conformant Vulkan implementation, testing use only." Running `$ vkcube` showed that this indeed was just a warning. + +(for those curious, dzn is a compaitibility layer between DirectX12 and Vulkan for that WSL conformity) + ## Development Roadmap ### lots of todo here diff --git a/src/Engine.cpp b/src/Engine.cpp index 003fe2e..35afc4f 100644 --- a/src/Engine.cpp +++ b/src/Engine.cpp @@ -1,7 +1,6 @@ #include "Engine.hpp" -#include "vulkan/vulkan.h" #include Engine::Engine(Window* window): window_(window) { @@ -13,24 +12,7 @@ Engine::Engine(Window* window): window_(window) { void Engine::init() { - VkApplicationInfo appInfo { - .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, - .pApplicationName = "maiden", - .apiVersion = VK_API_VERSION_1_4 - }; - - uint32_t instanceExtensionsCount = 0; - char const* const* instanceExtensions{ SDL_Vulkan_GetInstanceExtensions(&instanceExtensionsCount) }; - - VkInstanceCreateInfo instanceCI { - .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, - .pApplicationInfo = &appInfo, - .enabledExtensionCount = instanceExtensionsCount, - .ppEnabledExtensionNames = instanceExtensions, - }; - - VkInstance instance; - if(vkCreateInstance(&instanceCI, nullptr, &instance) == VK_SUCCESS) { + if(createInstance()) { // TODO: need some kind of logger service std::cout << "[" << __FUNCTION__ << ": " << __LINE__ << "] Vulkan instance successfully created." << std::endl; } else { @@ -49,3 +31,152 @@ void Engine::init() { void Engine::draw() { } + +bool Engine::createInstance() { + + uint32_t errorCount = 0; + + // create the appInfo filled with information about our app + constexpr vk::ApplicationInfo appInfo { // using the c++ api instead of the c api + .pApplicationName = "maiden", + .applicationVersion = VK_MAKE_VERSION(1, 0, 0), + .pEngineName = "null", + .engineVersion = VK_MAKE_VERSION(1, 0, 0), + .apiVersion = VK_API_VERSION_1_4 // this one is most important + }; + + std::vector requiredInstanceExtensions = getRequiredInstanceExtensions(); + + // get all available extensions + auto extensionProperties = context_.enumerateInstanceExtensionProperties(); + + // print if we feel like it + std::cout << "Available Vulkan Extensions: " << std::endl; + for(const auto& extensionProperty : extensionProperties) { + std::cout << "\t" << extensionProperty.extensionName << std::endl; + } // this would be a logger.debug(...) + + // check that all required extensions are available + for(uint32_t i = 0; i < requiredInstanceExtensions.size(); i++) { // for each extension that we require + bool found = false; + for(const auto& extensionProperty : extensionProperties) { // see if it matches any extensions that are provided + if(strcmp(extensionProperty.extensionName, requiredInstanceExtensions[i]) == 0) { + found = true; + break; + } + } + if(!found) { + std::cout << "[" << __FUNCTION__ << ": " << __LINE__ << "] Required SDL3 extension not supported: " << requiredInstanceExtensions[i] << std::endl; + errorCount++; + } else { + // in case you're curious + //std::cout << "[" << __FUNCTION__ << ": " << __LINE__ << "] SDL3 extension located: " << requiredInstanceExtensions[i] << std::endl; + } + } + + // get required validation layers as specified by our app + std::vector requiredValidationLayers; + if(enableValidationLayers) requiredValidationLayers.assign(validationLayers.begin(), validationLayers.end()); + + // get available validation layers + auto validationLayerProperties = context_.enumerateInstanceLayerProperties(); + + // again print if we feel like it + std::cout << "Available Vulkan Validation Layers: " << std::endl; + for(const auto& validationLayer : validationLayerProperties) { + std::cout << "\t" << validationLayer.layerName << std::endl; + } + + // check that all required validation layers are avilable + for(int i = 0; i < requiredValidationLayers.size(); i++) { + bool found = false; + for(const auto& validationLayer : validationLayerProperties) { + if(strcmp(requiredValidationLayers[i], validationLayer.layerName) == 0) { + found = true; + break; + } + } + if(!found) { + errorCount++; + std::cout << "[" << __FUNCTION__ << ": " << __LINE__ << "] Required validation layer not supported: " << requiredValidationLayers[i] << std::endl; + } else { // in case you're curious + //std::cout << "[" << __FUNCTION__ << ": " << __LINE__ << "] VkValidation layer located: " << requiredValidationLayers[i] << std::endl; + } + } + + // if any we had errors then we must exit + if(errorCount != 0) { + std::cout << "[" << __FUNCTION__ << ": " << __LINE__ << "] Unable to create Vulkan instance. Error count: " << errorCount << std::endl; + return false; + } + + vk::InstanceCreateInfo instanceCreateInfo { + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredValidationLayers.size()), + .ppEnabledLayerNames = requiredValidationLayers.data(), + .enabledExtensionCount = static_cast(requiredInstanceExtensions.size()), + .ppEnabledExtensionNames = requiredInstanceExtensions.data() + }; + + instance_ = vk::raii::Instance(context_, instanceCreateInfo); + return (instance_ != nullptr); +} + +std::vector Engine::getRequiredInstanceExtensions() { + + // get extensions that our windowing library requires + uint32_t sdlExtensionsCount = 0; + const char* const* sdlExtensions{ SDL_Vulkan_GetInstanceExtensions(&sdlExtensionsCount) }; + // what in the world is this kind of pointer btw + + std::vector requiredExtensions(sdlExtensions, sdlExtensions + static_cast(sdlExtensionsCount)); + + // manually add an extension for handling validation layers + if(enableValidationLayers) { + requiredExtensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return requiredExtensions; +} + +bool Engine::initDebugMessenger() { + + if(!enableValidationLayers) return false; + + // masks for which debug messages we want to see + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{.messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger_ = instance_.createDebugUtilsMessengerEXT( debugUtilsMessengerCreateInfoEXT ); + // we could get rid of this and just pass all the control to the logger + // like: "treat all messages of severity vk::eVerbose as our own DebugVerbosity::Info" + + return (debugMessenger_ != nullptr); +} + +VKAPI_ATTR vk::Bool32 VKAPI_CALL Engine::debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, + vk::DebugUtilsMessageTypeFlagsEXT type, + const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, + void* pUserData) { + + // this will eventually go through our logger + std::cout << "[ Validation Layer ] [Type: " << to_string(type) << "] " << pCallbackData->pMessage << std::endl; + /* + vk severity types: + vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose + vk::DebugUtilsMessageSeverityFlagBitsEXT::eInfo + vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning + vk::DebugUtilsMessageSeverityFlagBitsEXT::eError + + vk message types: + vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral + vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation + vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance + */ + + // returns whether or not we should abort, we'll always say no + return vk::False; +} + diff --git a/src/Engine.hpp b/src/Engine.hpp index cb017e1..9963109 100644 --- a/src/Engine.hpp +++ b/src/Engine.hpp @@ -1,6 +1,9 @@ #pragma once +// resource allocation is initializion my beloved +#include + #include "Window.hpp" class Engine { @@ -10,6 +13,7 @@ public: Engine(Window* window); ~Engine() = default; + // initializes and sets up the vulkan instance. outside of constructor to allow control of order the order of initialization void init(); // draw is called every render iteration in that while loop @@ -17,8 +21,30 @@ public: private: - // might get rid of this + // returns a list of the required extensions needed by our engine + std::vector getRequiredInstanceExtensions(); + + // helper for organizing the creation of the vulkan instance. returns success or failure + bool createInstance(); + + // helper for attaching callbacks to the instance + bool initDebugMessenger(); + + // callback function for the vulkan debug extension: routes debug messages from validation layers to our app + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, + vk::DebugUtilsMessageTypeFlagsEXT type, + const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, + void* pUserData); Window* window_; + // Vulkan specific instance members + vk::raii::Context context_; + vk::raii::Instance instance_ = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger_ = nullptr; + + // members for control over validation layers + static constexpr bool enableValidationLayers = true; // TODO: only true in debug mode + const std::vector validationLayers = { "VK_LAYER_KHRONOS_validation" }; + };