From 263b6da52883a1c4fcabc9c30a94fbe5451a2b5a Mon Sep 17 00:00:00 2001 From: Caleb Burke Date: Fri, 14 Aug 2026 06:09:53 +0000 Subject: [PATCH] copying code over from alphane engine --- CMakeLists.txt | 42 +++ README.md | 20 ++ engine/CMakeLists.txt | 18 ++ engine/include/device.hpp | 134 ++++++++ engine/include/types.hpp | 21 ++ engine/include/util/delegate.hpp | 111 +++++++ engine/include/util/logger.hpp | 140 +++++++++ engine/include/window.hpp | 51 ++++ engine/src/device.cpp | 505 +++++++++++++++++++++++++++++++ engine/src/window.cpp | 64 ++++ testbed/CMakeLists.txt | 27 ++ testbed/main.cpp | 11 + third_party/CMakeLists.txt | 59 ++++ 13 files changed, 1203 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 engine/CMakeLists.txt create mode 100644 engine/include/device.hpp create mode 100644 engine/include/types.hpp create mode 100644 engine/include/util/delegate.hpp create mode 100644 engine/include/util/logger.hpp create mode 100644 engine/include/window.hpp create mode 100644 engine/src/device.cpp create mode 100644 engine/src/window.cpp create mode 100644 testbed/CMakeLists.txt create mode 100644 testbed/main.cpp create mode 100644 third_party/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..0c4fc3a --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,42 @@ +cmake_minimum_required(VERSION 3.22.2) + +set(NAME sophia) + +project(${NAME} VERSION 0.0.0) + +enable_language(CXX) +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +if(NOT DEFINED CMAKE_BUILD_TYPE OR CMAKE_BUILD_TYPE STREQUAL "") + set(CMAKE_BUILD_TYPE Debug CACHE STRING "Build type" FORCE) +endif() + +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + message(STATUS "Debug build: enabling debug logging") +else() + add_compile_definitions(NDEBUG) +endif() + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +find_package(Git QUIET) +if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git") + option(GIT_SUBMODULE "Check submodules during build" ON) + if(GIT_SUBMODULE) + message(STATUS "Submodule update") + execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + RESULT_VARIABLE GIT_SUBMOD_RESULT) + if(NOT GIT_SUBMOD_RESULT EQUAL "0") + message(FATAL_ERROR "git submodule update --init failed with ${GIT_SUBMOD_RESULT}, checkout submodules") + endif() + endif() +endif() + +add_subdirectory(third_party) +add_subdirectory(engine) +add_subdirectory(testbed) \ No newline at end of file diff --git a/README.md b/README.md index e69de29..0a58655 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,20 @@ +# sophia + +## Getting Started + +COMING SOON + +## Developing on the engine + +### Dependencies + +- [cmake](https://cmake.org/) +- [Vulkan SDK](https://vulkan.lunarg.com/) - With validation layers + +### Build instructions + +```sh +cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug +cd build +make -j +``` \ No newline at end of file diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt new file mode 100644 index 0000000..bd3fd63 --- /dev/null +++ b/engine/CMakeLists.txt @@ -0,0 +1,18 @@ +set(ENGINE_NAME ${PROJECT_NAME}) + +add_library(${ENGINE_NAME} STATIC) + +file(GLOB_RECURSE SOURCES src/*.cpp) +file(GLOB_RECURSE HEADERS include/*.hpp) + +target_sources(${ENGINE_NAME} PRIVATE + ${SOURCES} + # ${HEADERS} +) + +target_include_directories(${ENGINE_NAME} PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}/src" + "${CMAKE_CURRENT_SOURCE_DIR}/include" + "${CMAKE_SOURCE_DIR}/third_party/imgui" + "${CMAKE_SOURCE_DIR}/third_party/imgui/backends" +) diff --git a/engine/include/device.hpp b/engine/include/device.hpp new file mode 100644 index 0000000..ed49da5 --- /dev/null +++ b/engine/include/device.hpp @@ -0,0 +1,134 @@ +#pragma once + +#include + +#include +#include +#include +#include + +#include "types.hpp" +#include "window.hpp" + +#define HEP_VULKAN_API_VERSION VK_API_VERSION_1_3 + +namespace hep { + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapchainSupportDetails { + std::vector formats; + std::vector presentModes; + vk::SurfaceCapabilitiesKHR capabilities; +}; + +class Device { + public: + Device(const Device&) = delete; + Device& operator=(const Device&) = delete; + Device(Device&&) = delete; + Device& operator=(Device&&) = delete; + + Device(Window& window); + ~Device(); + + const vk::Device* get() const { + assert(this->device); + return &this->device.get(); + } + + void waitIdle() { this->device->waitIdle(); } + + vk::CommandPool getCommandPool() const { return this->commandPool; } + vk::SurfaceKHR getSurface() const { return surface; } + vk::Queue getGraphicsQueue() const { return graphicsQueue; } + vk::Queue getPresentQueue() const { return presentQueue; } + + QueueFamilyIndices getQueueIndices() { + return findQueueFamilies(this->physicalDevice); + } + + SwapchainSupportDetails getSwapchainSupport() { + return querySwapchainSupport(this->physicalDevice); + } + + u32 findMemoryType(u32 typeFilter, vk::MemoryPropertyFlags properties); + + vk::Format findSupportedFormat(const std::vector& candidates, + vk::ImageTiling tiling, + vk::FormatFeatureFlags features); + + void createBuffer(vk::DeviceSize size, + vk::BufferUsageFlags usage, + vk::MemoryPropertyFlags properties, + vk::Buffer& buffer, + vk::DeviceMemory& bufferMemory); + + vk::CommandBuffer beginSingleTimeCommands(); + + void endSingleTimeCommands(vk::CommandBuffer commandBuffer); + + void copyBuffer(vk::Buffer sourceBuffer, + vk::Buffer destinationBuffer, + vk::DeviceSize size); + + void createImageWithInfo(const vk::ImageCreateInfo& imageInfo, + vk::MemoryPropertyFlags properties, + vk::Image& image, + vk::DeviceMemory& imageMemory); + + void populateImGuiInitInfo(ImGui_ImplVulkan_InitInfo& initInfo); + + vk::PhysicalDeviceProperties properties; + + private: + void setupDebugMessenger(); + + bool checkValidationLayerSupport(); + std::vector getRequiredExtensions(); + void createVulkanInstance(); + + QueueFamilyIndices findQueueFamilies(vk::PhysicalDevice device); + bool checkDeviceExtensionSupport(const vk::PhysicalDevice& device); + bool isPhysicalDeviceSuitable(const vk::PhysicalDevice& device); + void pickPhysicalDevice(); + + SwapchainSupportDetails querySwapchainSupport(vk::PhysicalDevice device); + + void createLogicalDevice(); + void createCommandPool(); + + vk::UniqueInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + + Window& window; + vk::SurfaceKHR surface; + + vk::PhysicalDevice physicalDevice = VK_NULL_HANDLE; + vk::UniqueDevice device; + + vk::Queue graphicsQueue; + vk::Queue presentQueue; + + vk::CommandPool commandPool; + +#ifdef NDEBUG + const bool enableValidationLayers = false; + const std::vector enabledLayers; +#else + const bool enableValidationLayers = true; + const std::vector enabledLayers = { + "VK_LAYER_KHRONOS_validation"}; +#endif + const std::vector enabledExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME}; +}; + +} // namespace hep diff --git a/engine/include/types.hpp b/engine/include/types.hpp new file mode 100644 index 0000000..0f1ec60 --- /dev/null +++ b/engine/include/types.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include + +namespace hep +{ + + using u8 = uint8_t; + using u16 = uint16_t; + using u32 = uint32_t; + using u64 = uint64_t; + + using s8 = int8_t; + using s16 = int16_t; + using s32 = int32_t; + using s64 = int64_t; + + using f32 = float; + using f64 = double; + +} // namespace hep diff --git a/engine/include/util/delegate.hpp b/engine/include/util/delegate.hpp new file mode 100644 index 0000000..e59b15d --- /dev/null +++ b/engine/include/util/delegate.hpp @@ -0,0 +1,111 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "util/logger.hpp" + +#define DEFAULT_DELEGATE_FUNCTOR_ALLOC 5 + +namespace hep { + +class expired_weak_object : public std::runtime_error { + public: + explicit expired_weak_object(const std::string& message) + : std::runtime_error(message) {} +}; + +/** + * Creates a weak function that safely calls a method if the + * object is alive. + * + * @tparam O Class owning the method + * @tparam A Variadic template parameters for the method arguments + * + * @param method Pointer to the static method to be called + * @param obj Shared pointer to the object instance + * @returns std::function that calls method on obj if alive, or throws if + * expired + * @throws std::invalid_argument If obj is null. + * @throws expired_weak_object If the object has expired when called + * + * @note Could investigate removing throwing exceptions, and instead return a + * bool if the weak pointer is alive. This may improve performance + */ +template +std::function createWeakFunction(void (O::*method)(A...), + std::shared_ptr obj) { + if (!obj) { throw std::invalid_argument("method and obj must not be null"); } + + std::weak_ptr weakObj = obj; + + return std::function([method = std::move(method), + weakObj = std::move(weakObj)](A... args) { + if (auto lockedObj = weakObj.lock()) { + (lockedObj.get()->*method)(std::forward(args)...); + } else { + throw expired_weak_object("Attempting to call function of a dead class"); + } + }); +} + +template +class Delegate { + public: + using Functor = std::function; + + Delegate() { this->functors.reserve(DEFAULT_DELEGATE_FUNCTOR_ALLOC); } + + void add(const Functor& func) { functors.push_back(func); } + + template + void addMethod(void (O::*memberFn)(A...), std::shared_ptr obj) { + functors.push_back(createWeakFunction(memberFn, obj)); + } + + void clear() { this->functors.clear(); } + + void invoke(A... args) { + if (this->functors.empty()) { + log::warning("invoking empty delegate"); + return; + } + + std::vector deadFunctorIndexs; + + for (size_t i = 0; i < functors.size(); i++) { + if (this->functors[i]) { + try { + this->functors[i](std::forward(args)...); + } catch (const expired_weak_object& e) { + log::error("delegate invoke error: ", e.what()); + deadFunctorIndexs.push_back(i); + } + } + } + + for (auto it = deadFunctorIndexs.rbegin(); it != deadFunctorIndexs.rend(); + ++it) { + functors.erase(functors.begin() + *it); + } + } + + void operator=(Functor c) { + clear(); + + if (c != nullptr) { add(c); } + } + + void operator+=(Functor c) { add(c); } + + void operator()() { invoke(); } + + private: + std::vector functors; +}; + +} // namespace hep diff --git a/engine/include/util/logger.hpp b/engine/include/util/logger.hpp new file mode 100644 index 0000000..94ad350 --- /dev/null +++ b/engine/include/util/logger.hpp @@ -0,0 +1,140 @@ +/** + * Lots of work still needs to be done in here. + * + * Usage: + * using namespace hep; + * log::fatal("fatal message") + * log::info("Infomation message") + * + * TODO Impl some form of log error handling + */ + +#pragma once + +#include +#include + +#include "types.hpp" + +namespace hep +{ + namespace log + { + + enum class LEVEL + { + NONE, + FATAL, + ERROR, + WARNING, + INFO, + VERBOSE, + DEBUGGING, + TRACE, + }; + +// Helper print functions +#define LOG_PRINT_HELPER(MSG) std::cout << MSG +#define LOG_PREFIX_FATAL LOG_PRINT_HELPER("\033[1;91m[FATAL]\033[0m ") +#define LOG_PREFIX_ERROR LOG_PRINT_HELPER("\033[1;31m[ERROR]\033[0m ") +#define LOG_PREFIX_WARNING LOG_PRINT_HELPER("\033[1;33m[WARNING]\033[0m ") +#define LOG_PREFIX_INFO LOG_PRINT_HELPER("\033[32m[INFO]\033[0m ") +#define LOG_PREFIX_VERBOSE LOG_PRINT_HELPER("\033[36m[VERBOSE]\033[0m ") +#define LOG_PREFIX_DEBUG LOG_PRINT_HELPER("\033[1;34m[DEBUG]\033[0m ") +#define LOG_PREFIX_TRACE LOG_PRINT_HELPER("\033[1;90m[TRACE]\033[0m ") + + template + void log(LEVEL level = LEVEL::NONE, Args... args) + { +#ifndef NDEBUG + switch (level) + { + case LEVEL::FATAL: + LOG_PREFIX_FATAL; + break; + case LEVEL::ERROR: + LOG_PREFIX_ERROR; + break; + case LEVEL::WARNING: + LOG_PREFIX_WARNING; + break; + case LEVEL::INFO: + LOG_PREFIX_INFO; + break; + case LEVEL::VERBOSE: + LOG_PREFIX_VERBOSE; + break; + // case LEVEL::DEBUGGING: + // LOG_PREFIX_DEBUG; + // break; + case LEVEL::TRACE: + LOG_PREFIX_TRACE; + break; + default: + std::cout << "\033[1;90m[LOG]\033[0m "; + break; + } + + ((std::cout << ' ' << std::forward(args)), ...); + std::cout << std::endl; +#else + (void)level; + (void)std::initializer_list{((void)args, 0)...}; +#endif // !NDEBUG + } + + template + void fatal(Args... args) + { + log(LEVEL::FATAL, args...); + } + + template + void error(Args... args) + { + log(LEVEL::ERROR, args...); + } + + template + void warning(Args... args) + { + log(LEVEL::WARNING, args...); + } + + template + void info(Args... args) + { + log(LEVEL::INFO, args...); + } + + template + void verbose(Args... args) + { + log(LEVEL::VERBOSE, args...); + } + + template + void debug(const char *file, u16 line, Args... args) + { +#ifndef NDEBUG + LOG_PREFIX_DEBUG; + std::cout << file << "::" << line; + ((std::cout << ' ' << std::forward(args)), ...); + std::cout << std::endl; +#else + (void)file; + (void)line; + (void)std::initializer_list{((void)args, 0)...}; +#endif // !NDEBUG + } +// trick +#define debug(...) debug(__FILE__, __LINE__, __VA_ARGS__) + + template + void trace(Args... args) + { + log(LEVEL::TRACE, args...); + } + + } // namespace log +} // namespace hep diff --git a/engine/include/window.hpp b/engine/include/window.hpp new file mode 100644 index 0000000..5833121 --- /dev/null +++ b/engine/include/window.hpp @@ -0,0 +1,51 @@ +#pragma once + +#define GLFW_INCLUDE_VULKAN +#include + +#include +#include + +#include "event.hpp" +#include "types.hpp" + +namespace hep { + +class Window { + public: + Window(const Window&) = delete; + Window& operator=(const Window&) = delete; + + Window(int width, int height, const std::string& name = "hephaestus"); + ~Window(); + + bool shouldClose() { return glfwWindowShouldClose(window); } + + int getWidth() const { return this->width; } + int getHeight() const { return this->height; } + + vk::Extent2D getExtent() { + return {static_cast(width), static_cast(height)}; + } + void createSurface(const vk::Instance& instance, vk::SurfaceKHR& surface); + bool wasResized() const { return this->resized; }; + void resetResizedFlag() { this->resized = false; } + + GLFWwindow* getGLFWwindow() { return this->window; } + + private: + static void resizeCallback(GLFWwindow* window, int width, int height); + static void keyEventCallback(GLFWwindow* window, + int key, + int scancode, + int action, + int mods); + void initialize(); + + int width, height; + bool resized = false; + std::string name; + GLFWwindow* window; +}; + +} // namespace hep diff --git a/engine/src/device.cpp b/engine/src/device.cpp new file mode 100644 index 0000000..1ddc6e5 --- /dev/null +++ b/engine/src/device.cpp @@ -0,0 +1,505 @@ +#include "device.hpp" + +#include + +#include + +#include "util/logger.hpp" + +namespace hep { + +static VKAPI_ATTR VkBool32 VKAPI_CALL +debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT m_severity, + VkDebugUtilsMessageTypeFlagsEXT m_type, + const VkDebugUtilsMessengerCallbackDataEXT* pCallback_data, + void* pUser_data) { + (void)m_type; + (void)pCallback_data; + (void)pUser_data; + + if (m_severity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) { + log::error(pCallback_data->pMessage); + return VK_SUCCESS; + } else if (m_severity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) { + log::warning(pCallback_data->pMessage); + } else { + log::info(pCallback_data->pMessage); + } + + return VK_FALSE; +} + +VkResult createDebugUtilsMessengerEXT( + VkInstance instance, + const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkDebugUtilsMessengerEXT* pCallback) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr( + instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pCallback); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void destroyDebugUtilsMessengerEXT(VkInstance instance, + VkDebugUtilsMessengerEXT callback, + const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr( + instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { func(instance, callback, pAllocator); } +} + +Device::Device(Window& window) : window{window} { + createVulkanInstance(); + setupDebugMessenger(); + this->window.createSurface(*instance, surface); + pickPhysicalDevice(); + createLogicalDevice(); + createCommandPool(); +} + +Device::~Device() { + if (this->enableValidationLayers) { + destroyDebugUtilsMessengerEXT(this->instance.get(), this->debugMessenger, + nullptr); + log::trace("destroyed Vulkan Debugger"); + } + + this->device->destroyCommandPool(commandPool); + log::trace("destroyed vk::CommandPool"); + + this->instance->destroySurfaceKHR(surface); + log::trace("destroyed vk::SurfaceKHR"); +} + +u32 Device::findMemoryType(u32 typeFilter, vk::MemoryPropertyFlags properties) { + vk::PhysicalDeviceMemoryProperties memoryProperties = + this->physicalDevice.getMemoryProperties(); + + for (u32 i = 0; i < memoryProperties.memoryTypeCount; i++) { + if ((typeFilter & (1 << i)) && + (memoryProperties.memoryTypes[i].propertyFlags & properties) == + properties) { + return i; + } + } + + log::fatal("failed to find suitable memory type"); + throw std::runtime_error("failed to find suitable memory type"); +} + +vk::Format Device::findSupportedFormat( + const std::vector& candidates, + vk::ImageTiling tiling, + vk::FormatFeatureFlags features) { + for (vk::Format format : candidates) { + vk::FormatProperties properties = + physicalDevice.getFormatProperties(format); + + if ((tiling == vk::ImageTiling::eLinear && + (properties.linearTilingFeatures & features) == features) || + (tiling == vk::ImageTiling::eOptimal && + (properties.optimalTilingFeatures & features) == features)) { + return format; + } + } + + log::fatal("failed to find supported format"); + throw std::runtime_error("failed to find supported format"); +} + +void Device::createBuffer(vk::DeviceSize size, + vk::BufferUsageFlags usage, + vk::MemoryPropertyFlags properties, + vk::Buffer& buffer, + vk::DeviceMemory& bufferMemory) { + vk::BufferCreateInfo bufferInfo = {}; + bufferInfo.size = size; + bufferInfo.usage = usage; + bufferInfo.sharingMode = vk::SharingMode::eExclusive; + + try { + buffer = this->device->createBuffer(bufferInfo); + } catch (const vk::SystemError& error) { + log::fatal("failed to create vertex buffer"); + throw std::runtime_error("failed to create vertex buffer"); + } + + vk::MemoryRequirements memoryRequirements = + this->device->getBufferMemoryRequirements(buffer); + + vk::MemoryAllocateInfo allocInfo = {}; + allocInfo.allocationSize = memoryRequirements.size; + allocInfo.memoryTypeIndex = + findMemoryType(memoryRequirements.memoryTypeBits, properties); + + try { + bufferMemory = this->device->allocateMemory(allocInfo); + } catch (const vk::SystemError& error) { + log::fatal("failed to allocate vertex buffer memory"); + throw std::runtime_error("failed to allocate vertex buffer memory"); + } + + this->device->bindBufferMemory(buffer, bufferMemory, 0); +} + +vk::CommandBuffer Device::beginSingleTimeCommands() { + vk::CommandBufferAllocateInfo allocInfo{}; + allocInfo.level = vk::CommandBufferLevel::ePrimary; + allocInfo.commandPool = this->commandPool; + allocInfo.commandBufferCount = 1; + + vk::CommandBuffer commandBuffer; + + try { + vk::Result result = + this->device->allocateCommandBuffers(&allocInfo, &commandBuffer); + if (result != vk::Result::eSuccess) { throw vk::SystemError(result); } + } catch (const vk::SystemError& error) { + log::error("failed to allocate single time commandBuffer. Error: ", + error.what()); + throw std::runtime_error("failed to allocate single time commandBuffer"); + } + + vk::CommandBufferBeginInfo beginInfo{}; + beginInfo.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit; + + try { + vk::Result result = commandBuffer.begin(&beginInfo); + if (result != vk::Result::eSuccess) { throw vk::SystemError(result); } + } catch (const vk::SystemError& error) { + log::error("failed to begin single time commandBuffer. Error: ", + error.what()); + throw std::runtime_error("failed to begin single time commandBuffer"); + } + + return commandBuffer; +} + +void Device::endSingleTimeCommands(vk::CommandBuffer commandBuffer) { + commandBuffer.end(); + + vk::SubmitInfo submitInfo = {}; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + this->graphicsQueue.submit(submitInfo, nullptr); + this->graphicsQueue.waitIdle(); + + this->device->freeCommandBuffers(commandPool, commandBuffer); +} + +void Device::copyBuffer(vk::Buffer sourceBuffer, + vk::Buffer destinationBuffer, + vk::DeviceSize size) { + vk::CommandBuffer commandBuffer = beginSingleTimeCommands(); + + vk::BufferCopy copyRegion = {}; + copyRegion.size = size; + + commandBuffer.copyBuffer(sourceBuffer, destinationBuffer, copyRegion); + + endSingleTimeCommands(commandBuffer); +} + +void Device::createImageWithInfo(const vk::ImageCreateInfo& imageInfo, + vk::MemoryPropertyFlags properties, + vk::Image& image, + vk::DeviceMemory& imageMemory) { + try { + vk::Result result = this->device->createImage(&imageInfo, nullptr, &image); + if (result != vk::Result::eSuccess) { throw vk::SystemError(result); } + } catch (const vk::SystemError& error) { + log::fatal("failed to create image. Error: ", error.what()); + throw std::runtime_error("failed to create image"); + } + + vk::MemoryRequirements memoryRequirements = + this->device->getImageMemoryRequirements(image); + + vk::MemoryAllocateInfo allocateInfo{}; + allocateInfo.allocationSize = memoryRequirements.size; + allocateInfo.memoryTypeIndex = + findMemoryType(memoryRequirements.memoryTypeBits, properties); + + try { + vk::Result result = + this->device->allocateMemory(&allocateInfo, nullptr, &imageMemory); + if (result != vk::Result::eSuccess) { throw vk::SystemError(result); } + } catch (const vk::SystemError& error) { + log::fatal("failed to allocate image memory. Error: ", error.what()); + throw std::runtime_error("failed to allocate image memory"); + } + + try { + this->device->bindImageMemory(image, imageMemory, 0); + } catch (const vk::SystemError& error) { + log::fatal("failed to bind image memory. Error: ", error.what()); + throw std::runtime_error("failed to bind image memory"); + } +} + +void Device::populateImGuiInitInfo(ImGui_ImplVulkan_InitInfo& initInfo) { + initInfo.Instance = this->instance.get(); + initInfo.ApiVersion = HEP_VULKAN_API_VERSION; + initInfo.PhysicalDevice = this->physicalDevice; + initInfo.Device = this->device.get(); + + QueueFamilyIndices indices = findQueueFamilies(this->physicalDevice); + initInfo.QueueFamily = indices.graphicsFamily.value(); + initInfo.Queue = getGraphicsQueue(); +} + +void Device::setupDebugMessenger() { + if (!this->enableValidationLayers) return; + + auto createInfo = vk::DebugUtilsMessengerCreateInfoEXT( + vk::DebugUtilsMessengerCreateFlagsEXT(), + vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | + vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | + vk::DebugUtilsMessageSeverityFlagBitsEXT::eError, + vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | + vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation | + vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance, + debugCallback, nullptr); + + if (createDebugUtilsMessengerEXT( + *instance, + reinterpret_cast( + &createInfo), + nullptr, &this->debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug callback!"); + } +} + +bool Device::checkValidationLayerSupport() { + auto availableLayers = vk::enumerateInstanceLayerProperties(); + for (const char* layerName : enabledLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { return false; } + } + + return true; +} + +std::vector Device::getRequiredExtensions() { + u32 glfw_extension_count = 0; + const char** glfw_extensions; + glfw_extensions = glfwGetRequiredInstanceExtensions(&glfw_extension_count); + + std::vector extensions(glfw_extensions, + glfw_extensions + glfw_extension_count); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + +#ifndef NDEBUG + u32 available_extension_count = 0; + vk::Result result = vk::enumerateInstanceExtensionProperties( + nullptr, &available_extension_count, nullptr); + + if (result != vk::Result::eSuccess) { throw std::exception(); } + + std::vector available_extensions( + available_extension_count); + + result = vk::enumerateInstanceExtensionProperties( + nullptr, &available_extension_count, available_extensions.data()); + + if (result != vk::Result::eSuccess) { throw std::exception(); } + + log::verbose("Number of available extensions: ", available_extension_count); + + log::verbose("Available extensions:"); + for (const auto& e : available_extensions) { + std::cout << '\t' << e.extensionName << '\n'; + } + std::cout << "Required extensions:\n"; + for (const auto& e : extensions) { std::cout << "\t" << e << '\n'; } +#endif + + return extensions; +} + +void Device::createVulkanInstance() { + if (this->enableValidationLayers && !checkValidationLayerSupport()) { + log::fatal("Validation layers requested but not available."); + throw std::exception(); + } + + vk::ApplicationInfo app_info = vk::ApplicationInfo( + "Hephaestus", VK_MAKE_VERSION(1, 0, 0), "Hephaestus Vulkan Engine", + VK_MAKE_VERSION(1, 0, 0), VK_API_VERSION_1_3); + + auto extensions = getRequiredExtensions(); + + auto createInfo = vk::InstanceCreateInfo( + vk::InstanceCreateFlags(), &app_info, 0, nullptr, + static_cast(extensions.size()), extensions.data()); + + createInfo.enabledLayerCount = static_cast(enabledLayers.size()); + createInfo.ppEnabledLayerNames = enabledLayers.data(); + + try { + this->instance = vk::createInstanceUnique(createInfo, nullptr); + } catch (const vk::SystemError& err) { + throw std::runtime_error("Failed to create instance!"); + } +} + +QueueFamilyIndices Device::findQueueFamilies(vk::PhysicalDevice device) { + QueueFamilyIndices indices; + auto queueFamilies = device.getQueueFamilyProperties(); + int i = 0; + + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueCount > 0 && + queueFamily.queueFlags & vk::QueueFlagBits::eGraphics) { + indices.graphicsFamily = i; + } + + if (queueFamily.queueCount > 0 && device.getSurfaceSupportKHR(i, surface)) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { break; } + i++; + } + + return indices; +} + +bool Device::checkDeviceExtensionSupport(const vk::PhysicalDevice& device) { + std::set requiredExtensions(this->enabledExtensions.begin(), + this->enabledExtensions.end()); + + for (const auto& extension : device.enumerateDeviceExtensionProperties()) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); +} + +bool Device::isPhysicalDeviceSuitable(const vk::PhysicalDevice& device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapchainAdequate = false; + if (extensionsSupported) { + SwapchainSupportDetails swapchainSupport = querySwapchainSupport(device); + swapchainAdequate = !swapchainSupport.formats.empty() && + !swapchainSupport.presentModes.empty(); + } + return indices.isComplete() && extensionsSupported && swapchainAdequate; +} + +void Device::pickPhysicalDevice() { + // Get all physical devies + std::vector physicalDevices = + instance->enumeratePhysicalDevices(); + + if (physicalDevices.empty()) { + log::fatal("Failed to find any physical devices."); + throw std::exception(); + } + log::verbose("Physical Devices count: ", physicalDevices.size()); + + // Select first physical device that is suitable + for (const auto& device : physicalDevices) { + if (isPhysicalDeviceSuitable(device)) { + this->physicalDevice = device; + break; + } + } + + if (this->physicalDevice == VK_NULL_HANDLE) { + log::fatal("Failed to find a suitable physical device"); + throw std::exception(); + } + + this->properties = this->physicalDevice.getProperties(); + log::verbose("Physical Device: ", this->properties.deviceName); +} + +void Device::createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(this->physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), + indices.presentFamily.value()}; + + float queuePriority = 1.0f; + + for (uint32_t queueFamily : uniqueQueueFamilies) { + queueCreateInfos.push_back( + {vk::DeviceQueueCreateFlags(), queueFamily, 1, &queuePriority}); + } + + auto deviceFeatures = vk::PhysicalDeviceFeatures(); + auto createInfo = vk::DeviceCreateInfo( + vk::DeviceCreateFlags(), static_cast(queueCreateInfos.size()), + queueCreateInfos.data()); + createInfo.pEnabledFeatures = &deviceFeatures; + + createInfo.enabledExtensionCount = + static_cast(this->enabledExtensions.size()); + createInfo.ppEnabledExtensionNames = this->enabledExtensions.data(); + + if (this->enableValidationLayers) { + createInfo.enabledLayerCount = + static_cast(this->enabledLayers.size()); + createInfo.ppEnabledLayerNames = this->enabledLayers.data(); + } + + try { + this->device = this->physicalDevice.createDeviceUnique(createInfo); + log::trace("created vk::Device."); + } catch (const vk::SystemError& err) { + log::fatal("failed to create logical device."); + throw std::exception(); + } + + this->graphicsQueue = device->getQueue(indices.graphicsFamily.value(), 0); + this->presentQueue = device->getQueue(indices.presentFamily.value(), 0); +} + +SwapchainSupportDetails Device::querySwapchainSupport( + vk::PhysicalDevice device) { + SwapchainSupportDetails details; + details.capabilities = device.getSurfaceCapabilitiesKHR(surface); + details.formats = device.getSurfaceFormatsKHR(surface); + details.presentModes = device.getSurfacePresentModesKHR(surface); + + return details; +} + +void Device::createCommandPool() { + QueueFamilyIndices queueFamilyIndices = + findQueueFamilies(this->physicalDevice); + + vk::CommandPoolCreateInfo createInfo = {}; + createInfo.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer; + createInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + try { + this->commandPool = this->device->createCommandPool(createInfo); + log::trace("created vk::CommandPool"); + } catch (const vk::SystemError& err) { + log::fatal("failed to create vk::CommandPool"); + throw std::runtime_error("failed to create vk::CommandPool"); + } +} + +} // namespace hep diff --git a/engine/src/window.cpp b/engine/src/window.cpp new file mode 100644 index 0000000..3b23f81 --- /dev/null +++ b/engine/src/window.cpp @@ -0,0 +1,64 @@ +#include "window.hpp" + +#include "key_event.hpp" +#include "util/logger.hpp" + +namespace hep { + +Window::Window(int width, int height, const std::string& name) + : width{width}, height{height}, name{name} { + initialize(); +} + +Window::~Window() { + glfwDestroyWindow(window); + glfwTerminate(); +} + +void Window::createSurface(const vk::Instance& instance, + vk::SurfaceKHR& surface) { + VkSurfaceKHR rawSurface; + if (glfwCreateWindowSurface(static_cast(instance), window, + nullptr, &rawSurface) != VK_SUCCESS) { + log::fatal("Failed to create vk::SurfaceKHR."); + throw std::exception(); + } + + surface = vk::SurfaceKHR(rawSurface); + log::trace("Created vk::SurfaceKHR."); +} + +void Window::keyEventCallback(GLFWwindow* window, + int key, + int scancode, + int action, + int mods) { + if (action == GLFW_PRESS) { + KeyPressedEvent event{static_cast(key)}; + EventSystem::get().dispatch(event); + } else if (action == GLFW_RELEASE) { + KeyReleasedEvent event{static_cast(key)}; + EventSystem::get().dispatch(event); + } +} + +void Window::resizeCallback(GLFWwindow* window, int width, int height) { + auto newWindow = reinterpret_cast(glfwGetWindowUserPointer(window)); + newWindow->resized = true; + newWindow->width = width; + newWindow->height = height; +} + +void Window::initialize() { + glfwInit(); + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + + this->window = + glfwCreateWindow(width, height, name.c_str(), nullptr, nullptr); + glfwSetWindowUserPointer(this->window, this); + glfwSetFramebufferSizeCallback(this->window, resizeCallback); + glfwSetKeyCallback(this->window, keyEventCallback); +} + +} // namespace hep diff --git a/testbed/CMakeLists.txt b/testbed/CMakeLists.txt new file mode 100644 index 0000000..6671802 --- /dev/null +++ b/testbed/CMakeLists.txt @@ -0,0 +1,27 @@ +set(NAME testbed) +set(ENGINE_NAME ${PROJECT_NAME}) + +file(GLOB_RECURSE SOURCES *.cpp src/*.cpp) + +add_library(deps INTERFACE) + +target_link_libraries(deps INTERFACE + ${ENGINE_NAME} + vulkan + shaderc + glfw + glm + imgui +) + +target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17) +set_property(TARGET ${PROJECT_NAME} PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/build") + +add_executable(testbed main.cpp) + +target_link_libraries(testbed PRIVATE deps) + +target_include_directories(testbed PRIVATE + "${CMAKE_SOURCE_DIR}/engine/include" + "${CMAKE_SOURCE_DIR}/external/imgui" +) diff --git a/testbed/main.cpp b/testbed/main.cpp new file mode 100644 index 0000000..5c943ed --- /dev/null +++ b/testbed/main.cpp @@ -0,0 +1,11 @@ +#include +#include + +int main(int argc, const char **argv) +{ + (void)argc; + (void)argv; + std::cout << __FILE__ << "::" << __LINE__ << '\n'; + + return EXIT_SUCCESS; +} diff --git a/third_party/CMakeLists.txt b/third_party/CMakeLists.txt new file mode 100644 index 0000000..b4325c0 --- /dev/null +++ b/third_party/CMakeLists.txt @@ -0,0 +1,59 @@ +# 1. Vulkan - shaderc, spirv-reflect +add_library(vulkan INTERFACE) + +find_package(Vulkan REQUIRED COMPONENTS shaderc_combined) + +# Adding SPIRV-Reflect to vulkan interface +target_sources(vulkan INTERFACE + ${Vulkan_INCLUDE_DIRS}/SPIRV-Reflect/spirv_reflect.c +) + +target_include_directories(vulkan INTERFACE + ${Vulkan_INCLUDE_DIRS}/SPIRV-Reflect +) + +target_link_libraries(vulkan INTERFACE + Vulkan::Vulkan + Vulkan::shaderc_combined +) + +# 3. GLFW (static) +option(GLFW_BUILD_EXAMPLES "Build the GLFW example programs" OFF) +option(GLFW_BUILD_TESTS "Build the GLFW test programs" OFF) +option(GLFW_BUILD_DOCS "Build the GLFW documentation" OFF) +option(GLFW_INSTALL "Generate installation target" OFF) +option(GLFW_DOCUMENT_INTERNALS "Include internals in documentation" OFF) +set(GLFW_VULKAN_STATIC ON CACHE BOOL "Link Vulkan statically with GLFW" FORCE) +add_subdirectory(glfw) + +# 4. GLM (static) +add_library(glm INTERFACE) +target_include_directories(glm INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/glm) + +# 5. IMGUI (static) +set(IMGUI_DIR ${CMAKE_CURRENT_SOURCE_DIR}/imgui) + +add_library(imgui STATIC + ${IMGUI_DIR}/imgui.cpp + ${IMGUI_DIR}/imgui_draw.cpp + ${IMGUI_DIR}/imgui_demo.cpp # remove this + ${IMGUI_DIR}/imgui_tables.cpp + ${IMGUI_DIR}/imgui_widgets.cpp + ${IMGUI_DIR}/backends/imgui_impl_glfw.cpp + ${IMGUI_DIR}/backends/imgui_impl_vulkan.cpp +) + +target_include_directories(imgui PUBLIC + ${IMGUI_DIR} + ${IMGUI_DIR}/backends + ${Vulkan_INCLUDE_DIRS} +) + +target_link_libraries(imgui PRIVATE + glfw + vulkan +) + +if(UNIX AND NOT APPLE) + target_compile_options(imgui PRIVATE -fPIC) +endif()