From f72bd8c44f9c5c96cc9ea8c9a6d48cb76949163e Mon Sep 17 00:00:00 2001 From: homeburger Date: Sat, 9 May 2026 15:36:53 -0500 Subject: [PATCH 1/4] create SDL window --- CMakeLists.txt | 18 +++++++++++++++++ src/App.cpp | 51 ++++++++++++++++++++++++++++++++++++++---------- src/App.hpp | 14 ++++++++----- src/Window.cpp | 45 ++++++++++++++++++++++++++++++++++++++++++ src/Window.hpp | 34 ++++++++++++++++++++++++++++++++ src/main.cpp | 2 ++ test/TestApp.cpp | 15 ++------------ 7 files changed, 151 insertions(+), 28 deletions(-) create mode 100644 src/Window.cpp create mode 100644 src/Window.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e177d05..6f2a14c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,28 +17,45 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(googletest) +# sdl3 +FetchContent_Declare( + sdl3 + GIT_REPOSITORY https://github.com/libsdl-org/SDL.git + GIT_TAG release-3.4.x +) +FetchContent_MakeAvailable(sdl3) + # add_subdirectory() to nest CMakeLists add_executable(maiden src/App.cpp + src/Window.cpp src/main.cpp # include extra source files here ) add_executable(maiden_test src/App.cpp + src/Window.cpp test/TestApp.cpp ) +# i think the strat is to build all of the core app components into a single library +# and then you link that to the two different executables (real main and test main) target_include_directories(maiden PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src" # add additional include directories here ) +target_link_libraries(maiden PRIVATE + SDL3::SDL3 +) + target_include_directories(maiden_test PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src" ) +# test only stuff down here VVV target_compile_options(maiden_test PRIVATE --coverage) target_link_options(maiden_test PRIVATE --coverage) @@ -46,6 +63,7 @@ target_link_options(maiden_test PRIVATE --coverage) target_link_libraries(maiden_test PRIVATE GTest::gmock GTest::gmock_main + SDL3::SDL3 ) include(CTest) diff --git a/src/App.cpp b/src/App.cpp index 0026493..d6845cc 100644 --- a/src/App.cpp +++ b/src/App.cpp @@ -1,18 +1,49 @@ #include "App.hpp" -void App::run() { - - std::cout << "im an app and im running !!" << std::endl; - - (void)foo(); - - return; +#include +#include +#include +App::App(): window_(new Window()) { + // smart pointers might be a good idea + // since app will be the top level owner we can use unique ptrs for almost everything } -int32_t App::foo() { +int32_t App::run() { - return 12; + SDL_Event event; + while (window_->open()) { + // app loop for as long as the window is open -} + // pass events to the window + while(SDL_PollEvent(&event) != 0) { + window_->handleEvent(event); + } + + // pass vulkan handling to engine + // call engine.render() or something + // engine will have a pointer to window so can handle pushing to the screen + + if(window_->rendering()) { + // engine.draw(); + + // TODO: performance profiling :3 + // static int counter = 0; + // if(counter > 100) { + // std::cout << "out" << std::endl; + // counter = 0; + // } + // counter++; + + } else { + // throttle while the window isn't active + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + } + + // SDL teardown handled in window destructor + + return 0; // maybe want to have an error codes enum for better clarity + +} \ No newline at end of file diff --git a/src/App.hpp b/src/App.hpp index 500db8c..c94bb56 100644 --- a/src/App.hpp +++ b/src/App.hpp @@ -4,16 +4,20 @@ #include #include +#include "Window.hpp" + class App { - public: - App() = default; +public: + App(); ~App() = default; - void run(); + int32_t run(); - private: +private: - int32_t foo(); + Window* window_; + + bool rendering_ = true; }; diff --git a/src/Window.cpp b/src/Window.cpp new file mode 100644 index 0000000..2d12611 --- /dev/null +++ b/src/Window.cpp @@ -0,0 +1,45 @@ + +#include "Window.hpp" + +#include + +Window::Window() { + + (void)init(); + +} + +Window::~Window() { + + SDL_DestroyWindow(sdlWindow_); + SDL_Quit(); + +} + +int Window::init() { + + // TODO: config service for controlling window parameters + sdlWindow_ = SDL_CreateWindow("Ouros: Vulkan", 1280u, 720u, SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE); + + if(sdlWindow_ != nullptr) { + rendering_ = true; + open_ = true; + return 0; + } else { + return -1; + } + +} + +void Window::handleEvent(SDL_Event& event) { + + if(event.type == SDL_EVENT_QUIT ) { + open_ = false; + } + + if(event.type == SDL_EVENT_WINDOW_MINIMIZED) { + rendering_ = false; + } else if(event.type == SDL_EVENT_WINDOW_RESTORED) { + rendering_ = true; + } +} diff --git a/src/Window.hpp b/src/Window.hpp new file mode 100644 index 0000000..a8889e8 --- /dev/null +++ b/src/Window.hpp @@ -0,0 +1,34 @@ + +#pragma once + +#include "SDL3/SDL.h" +#include "SDL3/SDL_vulkan.h" + +// reference: https://wiki.libsdl.org/SDL3/SDL_CreateWindow +class Window { + +public: + + Window(); + ~Window(); + + // for SDL3 event polling, runs once per app iteration in its while(running) loop + void handleEvent(SDL_Event& event); + + bool rendering() { return rendering_; } + bool open() { return open_; } + +private: + + // launches window, runs at app startup + int init(); + + // this window class will eventually hold mouse, keyboard, audio, etc. interfaces, like an SDL3 hub + // app will be able to attach callbacks for mouse and keyboard events + + SDL_Window* sdlWindow_; + + bool rendering_ = false; + bool open_ = false; + +}; diff --git a/src/main.cpp b/src/main.cpp index 80dab38..ff0f4f3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,8 +7,10 @@ int main(int argc, char** argv) { std::cout << "hi mom !" << std::endl; + // create app and run App app; app.run(); + // no freaking way return 0; } diff --git a/test/TestApp.cpp b/test/TestApp.cpp index 65159fa..ae61cb2 100644 --- a/test/TestApp.cpp +++ b/test/TestApp.cpp @@ -2,9 +2,7 @@ #include #include -#define private public -#include -#undef private +#include "App.hpp" class TestApp : public testing::Test { @@ -21,16 +19,7 @@ class TestApp : public testing::Test { TEST_F(TestApp, TestApp_run_nominal) { createUut(); - uut_->run(); - // no expect here + //EXPECT_EQ(uut_->run(), 0l); } - -TEST_F(TestApp, TestApp_foo_nominal) { - - createUut(); - - EXPECT_EQ(uut_->foo(), 12); - -} From fcdb09a1426750c63fa7d7afe52a860ef3918d56 Mon Sep 17 00:00:00 2001 From: homeburger Date: Sat, 9 May 2026 20:22:51 -0500 Subject: [PATCH 2/4] hello vulkan --- CMakeLists.txt | 6 ++++++ README.md | 25 ++++++++++++++++++++++++- src/App.cpp | 2 +- src/App.hpp | 2 ++ src/Engine.cpp | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/Engine.hpp | 24 ++++++++++++++++++++++++ src/main.cpp | 4 ---- 7 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 src/Engine.cpp create mode 100644 src/Engine.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 6f2a14c..e692c27 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,9 +25,12 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(sdl3) +find_package(Vulkan REQUIRED) + # add_subdirectory() to nest CMakeLists add_executable(maiden + src/Engine.cpp src/App.cpp src/Window.cpp src/main.cpp @@ -37,6 +40,7 @@ add_executable(maiden add_executable(maiden_test src/App.cpp src/Window.cpp + src/Engine.cpp test/TestApp.cpp ) # i think the strat is to build all of the core app components into a single library @@ -49,6 +53,7 @@ target_include_directories(maiden PRIVATE target_link_libraries(maiden PRIVATE SDL3::SDL3 + Vulkan::Vulkan ) target_include_directories(maiden_test PRIVATE @@ -64,6 +69,7 @@ target_link_libraries(maiden_test PRIVATE GTest::gmock GTest::gmock_main SDL3::SDL3 + Vulkan::Vulkan ) include(CTest) diff --git a/README.md b/README.md index 27d798c..943b9c0 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,30 @@ $ git clone --recurse-submodules https://git.vxbard.net/homeburger/maiden.git # If you have already cloned the repository and you need its submodules: $ git submodule update --init --recursive ``` -### Build + +### Installation Prequisites +LunarG's Vulkan SDK is used for almost every Vulkan project, including this one. +Installation instructions here are for Ubuntu 24, further instructions can be found here: https://vulkan.lunarg.com/doc/sdk/1.4.341.1/linux/getting_started.html +```bash +$ cd ~/Downloads +$ wget "https://sdk.lunarg.com/sdk/download/1.4.341.1/linux/vulkansdk-linux-x86_64-1.4.341.1.tar.xz" # or the latest version on https://vulkan.lunarg.com/sdk/home +$ mkdir /opt/vulkan-sdk +$ cd /opt/vulkan-sdk +$ tar -xvf ~/Downloads/vulkansdk-linux-x86_64-1.4.341.1.tar.xz +$ sudo apt install libxcb-xinput0 libxcb-xinerama0 libxcb-cursor-dev # vulkan prerequeisites +``` +Then to setup your current session: +```bash +$ source /opt/vulkan-sdk/1.4.341.1/setup-env.sh # or whichever version you have +``` +Note: sourcing only affects your current shell session. Add to your .bashrc script if you wish for it to persist. + +Verify your installation by using the the command: +```bash +$ vulkaninfo +``` + +### Build This app uses CMake and C++20, so a compatible compiler (gcc8, clang9, msvc16) are necessary. Most library prerequisites are handled within CMake. Any others that need to be manually installed will be described below as the project grows. diff --git a/src/App.cpp b/src/App.cpp index d6845cc..1302106 100644 --- a/src/App.cpp +++ b/src/App.cpp @@ -5,7 +5,7 @@ #include #include -App::App(): window_(new Window()) { +App::App(): window_(new Window()), engine_(new Engine(window_)) { // smart pointers might be a good idea // since app will be the top level owner we can use unique ptrs for almost everything } diff --git a/src/App.hpp b/src/App.hpp index c94bb56..3c3ab18 100644 --- a/src/App.hpp +++ b/src/App.hpp @@ -5,6 +5,7 @@ #include #include "Window.hpp" +#include "Engine.hpp" class App { @@ -17,6 +18,7 @@ public: private: Window* window_; + Engine* engine_; bool rendering_ = true; diff --git a/src/Engine.cpp b/src/Engine.cpp new file mode 100644 index 0000000..003fe2e --- /dev/null +++ b/src/Engine.cpp @@ -0,0 +1,51 @@ + +#include "Engine.hpp" + +#include "vulkan/vulkan.h" +#include + +Engine::Engine(Window* window): window_(window) { + + // cleans up this constructor + init(); + +} + +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) { + // TODO: need some kind of logger service + std::cout << "[" << __FUNCTION__ << ": " << __LINE__ << "] Vulkan instance successfully created." << std::endl; + } else { + std::cout << "[" << __FUNCTION__ << ": " << __LINE__ << "] Error creating Vulkan instance." << std::endl; + } + + // next steps: + // device selection and setup + // queue creation + // vulkan memory allocator + // create vulkan surface + // attach surface to window + +} + +void Engine::draw() { + +} diff --git a/src/Engine.hpp b/src/Engine.hpp new file mode 100644 index 0000000..cb017e1 --- /dev/null +++ b/src/Engine.hpp @@ -0,0 +1,24 @@ + +#pragma once + +#include "Window.hpp" + +class Engine { + +public: + + Engine(Window* window); + ~Engine() = default; + + void init(); + + // draw is called every render iteration in that while loop + void draw(); + +private: + + // might get rid of this + + Window* window_; + +}; diff --git a/src/main.cpp b/src/main.cpp index ff0f4f3..e5aba48 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,12 +1,8 @@ -#include - #include "App.hpp" int main(int argc, char** argv) { - std::cout << "hi mom !" << std::endl; - // create app and run App app; app.run(); From f54c889b11184a552922ead507fb07bfb4dbf1ed Mon Sep 17 00:00:00 2001 From: homeburger Date: Sun, 10 May 2026 13:13:57 -0500 Subject: [PATCH 3/4] me rambling about project plans --- CONTRIBUTING.md | 32 ++++++++++++++++++++++++++++++++ README.md | 1 + 2 files changed, 33 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..ecf742c --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,32 @@ + +# Contributing +### Note: not a real contributing guide. +Because this project is so early in development, this information outlines a design framework and tasks that need to be completed. + +## Vulkan Roadmap +From the authors themselves: https://docs.vulkan.org/tutorial/latest/00_Introduction.html + +Basic outline where I pretty much copy the guide: + - Device Setup: GPU+Queues initialization, CPU+Queues initialization, include validation layers for testing + - Window Presentation: Surface initialization, Window attachment, Swapchain creation + - Graphics Pipeline Setup: Shaders -> Render Pass + - Command Buffers: Framebuffers, Draw commands, Presenation. <<-- HelloTriangle is here + - Vertex Buffers: Loading geometry data to gpu (mesh loading happens later) + - Descriptor Sets: Dynamic arbitrary data transfer from CPU -> GPU (the guide introduces 3D here) + - Textures: Loading Images, Sampling textures in shader + - Depth Buffering: additional render pass for determining most-front geometry + - Mesh Loading: guide uses TinyObj to load obj files into vertex & index vectors + - ... Boring optimization stuff, like mipmaps, multithreading, multisampling + - it has a section on raytracing :3 + +## 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. + +### Performance Profiler +In order to properly test different Vulkan practices and engine implementations, performance should be quickly monitorable by the program. Important compute metrics include: CPU utilization, thread count, memory utilization, VRAM utilization, GPU utilization, and most importantly FPS. Supplemental details like geometry count or hardware configuration could be beneficial by providing context. Frame time (and fps) can be easily recorded inside of the engine, though other metrics must be accessed via the operating system's tools perhaps via the POSIX interface (I haven't done too much research). Recording the performance can initially be done through the logging service (detailed further below) but in-window updates with a GUI (oh boy) would be ideal. + +### Configuration Service +A configuration service allows the application to be more flexible by providing the option to tweak parameters without requiring recompilation. Useful configuration parameters could be on window creation, rendering settings (geometry culling, descriptor layout), or vertex loading patterns. There are many options for implementation, like yaml, json, or cfg, with various c++ libraries for support. Classes that require configuration parameters will be injected with a config class from the top-level app and can access whatever it needs through the service. + +### Logging Service +Consolidating logging functionality to a single service makes the code much more organized and easier to maintain. The advantage of centralized logging over std::couts everywhere is that much more information can be added to each logged statement: timestamps, function & line traces, log severity level (debug, info, warning, error), etc. We can also give it control to write to log files instead of just the terminal session so all logged messages can persist. Again, implementation involves injecting the service to components that require it and they can interface through the reference. diff --git a/README.md b/README.md index 943b9c0..3515f4b 100644 --- a/README.md +++ b/README.md @@ -74,3 +74,4 @@ $ gcovr -r .. --filter "../src" ## Development Roadmap ### lots of todo here +See CONTRIBUTING.md for design and more specific development plans. From 6407cb7fdb394daeacf8532dcd9bc590e000c4e0 Mon Sep 17 00:00:00 2001 From: homeburger Date: Sun, 10 May 2026 17:20:21 -0500 Subject: [PATCH 4/4] consolidate some cmake --- CMakeLists.txt | 30 +++++++++++++++++------------- src/Window.cpp | 2 +- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e692c27..c8802c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,43 +29,47 @@ find_package(Vulkan REQUIRED) # add_subdirectory() to nest CMakeLists -add_executable(maiden - src/Engine.cpp +add_library(maiden_core STATIC src/App.cpp src/Window.cpp - src/main.cpp + src/Engine.cpp # include extra source files here ) +add_executable(maiden + src/main.cpp +) + add_executable(maiden_test - src/App.cpp - src/Window.cpp - src/Engine.cpp test/TestApp.cpp ) -# i think the strat is to build all of the core app components into a single library -# and then you link that to the two different executables (real main and test main) -target_include_directories(maiden PRIVATE +target_include_directories(maiden_core PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src" + "${SDL3_SOURCE_DIR}/include" # was having issues with cmake automatically adding these for a lib + "$ENV{VULKAN_SDK}/include" # requires sourcing vulkan sdk setup-env.sh # add additional include directories here ) +target_compile_options(maiden_core PRIVATE --coverage) +target_link_options(maiden_core PRIVATE --coverage) +target_link_options(maiden PRIVATE --coverage) +target_link_options(maiden_test PRIVATE --coverage) + target_link_libraries(maiden PRIVATE + maiden_core SDL3::SDL3 Vulkan::Vulkan ) +# test only stuff down here VVV target_include_directories(maiden_test PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src" ) -# test only stuff down here VVV -target_compile_options(maiden_test PRIVATE --coverage) -target_link_options(maiden_test PRIVATE --coverage) - # TODO: add option for disabling tests (like if target == DEBUG then tests on, otherwise tests off) target_link_libraries(maiden_test PRIVATE + maiden_core GTest::gmock GTest::gmock_main SDL3::SDL3 diff --git a/src/Window.cpp b/src/Window.cpp index 2d12611..2e43b87 100644 --- a/src/Window.cpp +++ b/src/Window.cpp @@ -19,7 +19,7 @@ Window::~Window() { int Window::init() { // TODO: config service for controlling window parameters - sdlWindow_ = SDL_CreateWindow("Ouros: Vulkan", 1280u, 720u, SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE); + sdlWindow_ = SDL_CreateWindow("Maiden", 1280u, 720u, SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE); if(sdlWindow_ != nullptr) { rendering_ = true;