Compare commits
13 Commits
feature/re
...
fabce2805d
| Author | SHA1 | Date | |
|---|---|---|---|
| fabce2805d | |||
| 4e9d69ba49 | |||
| 8b798fcaa7 | |||
| 65d21cd3c4 | |||
|
|
bcd19f1e60 | ||
|
|
a63c271f92 | ||
|
|
836012648b | ||
| 6407cb7fdb | |||
| f54c889b11 | |||
| fcdb09a142 | |||
| f72bd8c44f | |||
| c3afcadc0f | |||
| 434480d156 |
@@ -17,35 +17,69 @@ 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)
|
||||
|
||||
find_package(Vulkan REQUIRED)
|
||||
|
||||
# add_subdirectory() to nest CMakeLists
|
||||
|
||||
add_executable(maiden
|
||||
add_library(maiden_core STATIC
|
||||
src/App.cpp
|
||||
src/main.cpp
|
||||
src/Window.cpp
|
||||
src/Engine.cpp
|
||||
src/Device.cpp
|
||||
src/Pipeline.cpp
|
||||
src/Swapchain.cpp
|
||||
# include extra source files here
|
||||
)
|
||||
|
||||
add_executable(maiden
|
||||
src/main.cpp
|
||||
)
|
||||
|
||||
add_executable(maiden_test
|
||||
src/App.cpp
|
||||
test/TestApp.cpp
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
# vulkan necessity
|
||||
target_compile_definitions(maiden_core PRIVATE VULKAN_HPP_NO_STRUCT_CONSTRUCTORS)
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
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
|
||||
Vulkan::Vulkan
|
||||
)
|
||||
|
||||
include(CTest)
|
||||
|
||||
46
CONTRIBUTING.md
Normal file
46
CONTRIBUTING.md
Normal file
@@ -0,0 +1,46 @@
|
||||
|
||||
# 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
|
||||
|
||||
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.
|
||||
|
||||
### 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.
|
||||
54
README.md
54
README.md
@@ -10,14 +10,40 @@ 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
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
@@ -47,7 +73,29 @@ $ 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)
|
||||
|
||||
### Could not locate a Nvidia GPU
|
||||
```bash
|
||||
$ sudo add-apt-repository ppa:kisak/turtle
|
||||
$ sudo apt update
|
||||
$ sudo apt upgrade
|
||||
```
|
||||
verify with `$ vulkaninfo --summary` to ensure your GPU is shown.
|
||||
|
||||
## Development Roadmap
|
||||
### lots of todo here
|
||||
See CONTRIBUTING.md for design and more specific development plans.
|
||||
|
||||
49
src/App.cpp
49
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 <thread>
|
||||
#include <chrono>
|
||||
#include <SDL3/SDL_events.h>
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
}
|
||||
12
src/App.hpp
12
src/App.hpp
@@ -4,16 +4,22 @@
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
|
||||
#include "Window.hpp"
|
||||
#include "Engine.hpp"
|
||||
|
||||
class App {
|
||||
|
||||
public:
|
||||
App() = default;
|
||||
App();
|
||||
~App() = default;
|
||||
|
||||
void run();
|
||||
int32_t run();
|
||||
|
||||
private:
|
||||
|
||||
int32_t foo();
|
||||
Window* window_;
|
||||
Engine* engine_;
|
||||
|
||||
bool rendering_ = true;
|
||||
|
||||
};
|
||||
|
||||
199
src/Device.cpp
Normal file
199
src/Device.cpp
Normal file
@@ -0,0 +1,199 @@
|
||||
|
||||
#include "Device.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
Device::Device(vk::raii::Instance* instance, Window* window): instance_(instance), window_(window) {
|
||||
|
||||
selectPhysicalDevice();
|
||||
createSurface();
|
||||
createLogicalDevice();
|
||||
|
||||
}
|
||||
|
||||
Device::~Device() {
|
||||
|
||||
}
|
||||
|
||||
bool Device::selectPhysicalDevice() {
|
||||
|
||||
std::vector<vk::raii::PhysicalDevice> physicalDevices = instance_->enumeratePhysicalDevices();
|
||||
|
||||
if(physicalDevices.empty()) {
|
||||
std::cout << "[" << __FUNCTION__ << ": " << __LINE__ << "] Error: no physical devices with Vulkan support found." << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// validate found devices
|
||||
uint32_t maxScore = 0;
|
||||
for(vk::raii::PhysicalDevice& physicalDevice : physicalDevices) {
|
||||
uint32_t capabilityScore = evaluatePhysicalDevice(physicalDevice);
|
||||
if(capabilityScore > maxScore) {
|
||||
maxScore = capabilityScore;
|
||||
physicalDevice_ = physicalDevice;
|
||||
}
|
||||
}
|
||||
if(maxScore = 0) {
|
||||
std::cout << "[" << __FUNCTION__ << ": " << __LINE__ << "] Error: physical devices found, but none capable for this engine." << std::endl;
|
||||
return false;
|
||||
} else {
|
||||
vk::PhysicalDeviceProperties deviceProperties = physicalDevice_.getProperties();
|
||||
std::cout << "[" << __FUNCTION__ << ": " << __LINE__ << "] Physical device selected: " << deviceProperties.deviceName << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
uint32_t Device::evaluatePhysicalDevice(vk::raii::PhysicalDevice& device) {
|
||||
|
||||
vk::PhysicalDeviceProperties deviceProperties = device.getProperties();
|
||||
vk::PhysicalDeviceFeatures deviceFeatures = device.getFeatures();
|
||||
|
||||
std::cout << "[" << __FUNCTION__ << ": " << __LINE__ << "] Physical device found: " << deviceProperties.deviceName << std::endl;
|
||||
|
||||
uint32_t score = 0;
|
||||
|
||||
// TODO: this is very basic and can be improved
|
||||
|
||||
// prefer discrete graphics to integrated graphics
|
||||
if(deviceProperties.deviceType == vk::PhysicalDeviceType::eDiscreteGpu) {
|
||||
score += 2;
|
||||
} else {
|
||||
std::cout << "[" << __FUNCTION__ << ": " << __LINE__ << "] Warning: physical device " << deviceProperties.deviceName << " is not a discrete device!" << std::endl;
|
||||
}
|
||||
|
||||
// prefer devices that support vulkan 1.3
|
||||
if(deviceProperties.apiVersion >= vk::ApiVersion14) {
|
||||
score++;
|
||||
} else {
|
||||
std::cout << "[" << __FUNCTION__ << ": " << __LINE__ << "] Warning: physical device " << deviceProperties.deviceName << " does not support Vulkan 1.3! (" << std::endl;
|
||||
}
|
||||
|
||||
// prefer devices that support graphics queues
|
||||
auto queueFamilies = device.getQueueFamilyProperties();
|
||||
if(std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } )) {
|
||||
score++;
|
||||
} else {
|
||||
std::cout << "[" << __FUNCTION__ << ": " << __LINE__ << "] Warning: physical device " << deviceProperties.deviceName << " does not support graphics queue families!" << std::endl;
|
||||
}
|
||||
|
||||
// prefer devices that support all required extensions
|
||||
auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties();
|
||||
bool found = false;
|
||||
uint32_t missingExtensions = 0;
|
||||
for(auto& requiredExtension : requiredDeviceExtensions_) {
|
||||
for(auto& availableExtension: availableDeviceExtensions) {
|
||||
if(strcmp(availableExtension.extensionName, requiredExtension) == 0) {
|
||||
found = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if(found == false) {
|
||||
missingExtensions++;
|
||||
}
|
||||
}
|
||||
if(missingExtensions == 0) {
|
||||
score++;
|
||||
} else {
|
||||
std::cout << "[" << __FUNCTION__ << ": " << __LINE__ << "] Warning: physical device " << deviceProperties.deviceName << " is missing extensions!" << std::endl;
|
||||
}
|
||||
|
||||
// prefer devices that support all required features
|
||||
auto features = device.template getFeatures2<vk::PhysicalDeviceFeatures2, vk::PhysicalDeviceVulkan13Features, vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT>();
|
||||
if(features.template get<vk::PhysicalDeviceVulkan13Features>().dynamicRendering && features.template get<vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT>().extendedDynamicState) {
|
||||
score++;
|
||||
} else {
|
||||
std::cout << "[" << __FUNCTION__ << ": " << __LINE__ << "] Warning: physical device " << deviceProperties.deviceName << " is missing features!" << std::endl;
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
bool Device::createLogicalDevice() {
|
||||
|
||||
if(surface_ == nullptr) {
|
||||
std::cout << "[" << __FUNCTION__ << ": " << __LINE__ << "] Error: cannot create logical device without a valid presentation surface." << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if(physicalDevice_ == nullptr) {
|
||||
std::cout << "[" << __FUNCTION__ << ": " << __LINE__ << "] Error: cannot create logical device without a valid physical device." << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// specify queue family requirements
|
||||
std::vector<vk::QueueFamilyProperties> queueFamilyProperties = physicalDevice_.getQueueFamilyProperties();
|
||||
int32_t queueIndex = -1;
|
||||
for(uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) {
|
||||
if((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && physicalDevice_.getSurfaceSupportKHR(qfpIndex, *surface_)) {
|
||||
queueIndex = static_cast<int32_t>(qfpIndex);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(queueIndex <= -1) {
|
||||
std::cout << "[" << __FUNCTION__ << ": " << __LINE__ << "] Error: could not locate valid graphics queues." << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
float queuePriority = 0.5f;
|
||||
vk::DeviceQueueCreateInfo deviceQueueCreateInfo {
|
||||
.queueFamilyIndex = static_cast<uint32_t>(queueIndex),
|
||||
.queueCount = 1,
|
||||
.pQueuePriorities = &queuePriority
|
||||
};
|
||||
|
||||
// specify device feature requirements
|
||||
vk::PhysicalDeviceVulkan13Features deviceFeatures = { .dynamicRendering = true };
|
||||
vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT deviceStateFeatures = { . extendedDynamicState = true };
|
||||
vk::StructureChain<vk::PhysicalDeviceFeatures2, vk::PhysicalDeviceVulkan13Features, vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT> featureChain = {
|
||||
{}, // empty for now
|
||||
deviceFeatures,
|
||||
deviceStateFeatures
|
||||
};
|
||||
|
||||
// create logical device
|
||||
vk::DeviceCreateInfo deviceCreateInfo {
|
||||
.pNext = &featureChain.get<vk::PhysicalDeviceFeatures2>(),
|
||||
.queueCreateInfoCount = 1,
|
||||
.pQueueCreateInfos = &deviceQueueCreateInfo,
|
||||
.enabledExtensionCount = static_cast<uint32_t>(requiredDeviceExtensions_.size()),
|
||||
.ppEnabledExtensionNames = requiredDeviceExtensions_.data()
|
||||
};
|
||||
logicalDevice_ = vk::raii::Device(physicalDevice_, deviceCreateInfo);
|
||||
|
||||
// initialize the graphics queue
|
||||
graphicsQueue_ = vk::raii::Queue(logicalDevice_, queueIndex, 0);
|
||||
|
||||
if(logicalDevice_ != nullptr) {
|
||||
std::cout << "[" << __FUNCTION__ << ": " << __LINE__ << "] Info: Created logcal device" << std::endl;
|
||||
return true;
|
||||
} else {
|
||||
std::cout << "[" << __FUNCTION__ << ": " << __LINE__ << "] Error: could not create a valid logical device." << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Device::createSurface() {
|
||||
|
||||
(void)window_->createSurface(instance_, &surface_);
|
||||
|
||||
if(surface_ == nullptr) {
|
||||
std::cout << "[" << __FUNCTION__ << ": " << __LINE__ << "] Error creating surface!" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
if(physicalDevice_ == nullptr) {
|
||||
std::cout << "[" << __FUNCTION__ << ": " << __LINE__ << "] Error: cannot create surface without a physical device!" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
auto surfaceCapabilities = physicalDevice_.getSurfaceCapabilitiesKHR(*surface_);
|
||||
std::vector<vk::SurfaceFormatKHR> availableFormats = physicalDevice_.getSurfaceFormatsKHR(*surface_);
|
||||
std::vector<vk::PresentModeKHR> availablePresentModes = physicalDevice_.getSurfacePresentModesKHR(*surface_);
|
||||
|
||||
}
|
||||
|
||||
bool Device::getExtent(int32_t* width, int32_t* height) {
|
||||
return window_->getExtent(width, height);
|
||||
}
|
||||
49
src/Device.hpp
Normal file
49
src/Device.hpp
Normal file
@@ -0,0 +1,49 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vulkan/vulkan_raii.hpp>
|
||||
|
||||
#include "Window.hpp"
|
||||
|
||||
class Device {
|
||||
|
||||
public:
|
||||
|
||||
Device(vk::raii::Instance* instance, Window* window);
|
||||
~Device();
|
||||
|
||||
// helper to get the surface extent from the window
|
||||
bool getExtent(int32_t* width, int32_t* height);
|
||||
vk::raii::SurfaceKHR* getSurface() { return &surface_; }
|
||||
|
||||
vk::raii::PhysicalDevice physicalDevice() { return physicalDevice_; }
|
||||
vk::raii::Device* logicalDevice() { return &logicalDevice_; }
|
||||
|
||||
private:
|
||||
|
||||
// gives a device a score to attempt to select the most capable device
|
||||
uint32_t evaluatePhysicalDevice(vk::raii::PhysicalDevice& device);
|
||||
|
||||
// creates the surface
|
||||
void createSurface();
|
||||
|
||||
// assigns a capable gpu vkdevice to physicalDevice
|
||||
bool selectPhysicalDevice();
|
||||
|
||||
// initializes the logical device
|
||||
bool createLogicalDevice();
|
||||
|
||||
// vulkan objects
|
||||
vk::raii::Instance* instance_ = nullptr;
|
||||
vk::raii::PhysicalDevice physicalDevice_ = nullptr;
|
||||
vk::raii::Device logicalDevice_ = nullptr;
|
||||
vk::raii::Queue graphicsQueue_ = nullptr;
|
||||
vk::raii::SurfaceKHR surface_ = nullptr;
|
||||
|
||||
// ptrs to other engine objects
|
||||
Window* window_ = nullptr;
|
||||
|
||||
// required extensions for the physical device
|
||||
std::vector<const char*> requiredDeviceExtensions_ = { vk::KHRSwapchainExtensionName };
|
||||
|
||||
};
|
||||
185
src/Engine.cpp
Normal file
185
src/Engine.cpp
Normal file
@@ -0,0 +1,185 @@
|
||||
|
||||
#include "Engine.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "Device.hpp"
|
||||
#include "Swapchain.hpp"
|
||||
|
||||
Engine::Engine(Window* window): window_(window) {
|
||||
|
||||
// cleans up this constructor
|
||||
init();
|
||||
|
||||
}
|
||||
|
||||
void Engine::init() {
|
||||
|
||||
if(createInstance()) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
// device selection and setup
|
||||
Device device(&instance_, window_);
|
||||
|
||||
// render pipeline
|
||||
Swapchain swapchain(&device);
|
||||
// Pipeline pipeline(&device, &swapchain);
|
||||
|
||||
}
|
||||
|
||||
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<const char*> 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<const char*> 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<uint32_t>(requiredValidationLayers.size()),
|
||||
.ppEnabledLayerNames = requiredValidationLayers.data(),
|
||||
.enabledExtensionCount = static_cast<uint32_t>(requiredInstanceExtensions.size()),
|
||||
.ppEnabledExtensionNames = requiredInstanceExtensions.data()
|
||||
};
|
||||
|
||||
instance_ = vk::raii::Instance(context_, instanceCreateInfo);
|
||||
return (instance_ != nullptr);
|
||||
}
|
||||
|
||||
std::vector<const char*> Engine::getRequiredInstanceExtensions() {
|
||||
|
||||
// get extensions that our windowing library requires
|
||||
uint32_t sdlExtensionsCount = 0;
|
||||
const char* const* sdlExtensions{ SDL_Vulkan_GetInstanceExtensions(&sdlExtensionsCount) }; // TODO: get this from window so all sdl3 is encapsulated there
|
||||
// what in the world is this kind of pointer btw
|
||||
|
||||
std::vector<const char*> requiredExtensions(sdlExtensions, sdlExtensions + static_cast<size_t>(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;
|
||||
}
|
||||
|
||||
50
src/Engine.hpp
Normal file
50
src/Engine.hpp
Normal file
@@ -0,0 +1,50 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
// resource allocation is initializion my beloved
|
||||
#include <vulkan/vulkan_raii.hpp>
|
||||
|
||||
#include "Window.hpp"
|
||||
|
||||
class Engine {
|
||||
|
||||
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
|
||||
void draw();
|
||||
|
||||
private:
|
||||
|
||||
// returns a list of the required extensions needed by our engine
|
||||
std::vector<const char*> 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<const char*> validationLayers = { "VK_LAYER_KHRONOS_validation" };
|
||||
|
||||
};
|
||||
6
src/Pipeline.cpp
Normal file
6
src/Pipeline.cpp
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
#include "Pipeline.hpp"
|
||||
|
||||
Pipeline::Pipeline(Device* device) : device_(device) {
|
||||
|
||||
}
|
||||
19
src/Pipeline.hpp
Normal file
19
src/Pipeline.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Device.hpp"
|
||||
|
||||
// the Pipeline lays out the rendering steps for the vulkan engine to follow
|
||||
class Pipeline {
|
||||
|
||||
public:
|
||||
|
||||
Pipeline(Device* device);
|
||||
~Pipeline() = default;
|
||||
|
||||
private:
|
||||
|
||||
Device* device_ = nullptr;
|
||||
|
||||
// will include shaders eventually
|
||||
};
|
||||
142
src/Swapchain.cpp
Normal file
142
src/Swapchain.cpp
Normal file
@@ -0,0 +1,142 @@
|
||||
|
||||
#include "Swapchain.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
Swapchain::Swapchain(Device* device) : device_(device) {
|
||||
|
||||
(void)createSwapchain();
|
||||
(void)createImageViews();
|
||||
|
||||
}
|
||||
|
||||
bool Swapchain::createSwapchain() {
|
||||
|
||||
// get capabilities from the device
|
||||
vk::raii::PhysicalDevice physicalDevice = device_->physicalDevice();
|
||||
vk::raii::Device* logicalDevice = device_->logicalDevice();
|
||||
vk::raii::SurfaceKHR* surface = device_->getSurface();
|
||||
vk::SurfaceCapabilitiesKHR surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(*surface);
|
||||
extent_ = chooseExtent(surfaceCapabilities);
|
||||
uint32_t minImageCount = chooseMinImageCount(surfaceCapabilities);
|
||||
|
||||
std::vector<vk::SurfaceFormatKHR> availableFormats = physicalDevice.getSurfaceFormatsKHR(*surface);
|
||||
surfaceFormat_ = chooseSurfaceFormat(availableFormats);
|
||||
|
||||
std::vector<vk::PresentModeKHR> availablePresentModes = physicalDevice.getSurfacePresentModesKHR(*surface);
|
||||
vk::PresentModeKHR presentMode = choosePresentMode(availablePresentModes);
|
||||
|
||||
// create the swapchain object
|
||||
vk::SwapchainCreateInfoKHR swapchainCreateInfo {
|
||||
.surface = *surface,
|
||||
.minImageCount = minImageCount,
|
||||
.imageFormat = surfaceFormat_.format,
|
||||
.imageColorSpace = surfaceFormat_.colorSpace,
|
||||
.imageExtent = extent_,
|
||||
.imageArrayLayers = 1, // only rendering to a single screen
|
||||
.imageUsage = vk::ImageUsageFlagBits::eColorAttachment, // draw directly to swapchain image (as opposed to an offscreen image)
|
||||
.imageSharingMode = vk::SharingMode::eExclusive, // owned only by a single queue
|
||||
.preTransform = surfaceCapabilities.currentTransform, // dont flip or rotate the image
|
||||
.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque, // use other settings if you want the window to be partly transparent :o
|
||||
.presentMode = presentMode,
|
||||
.clipped = true // discard fragments in the window that aren't visible (obscured by another window or minimized)
|
||||
};
|
||||
// TODO: [after presentation] need to handle swapchain recreation as a result of window changes
|
||||
swapchainCreateInfo.oldSwapchain = nullptr;
|
||||
|
||||
vkSwapchain_ = vk::raii::SwapchainKHR(*logicalDevice, swapchainCreateInfo);
|
||||
images_ = vkSwapchain_.getImages();
|
||||
|
||||
if(vkSwapchain_ == nullptr) {
|
||||
std::cout << "[" << __FUNCTION__ << ": " << __LINE__ << "] Error: could not create the Vulkan Swapchain!" << std::endl;
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
vk::SurfaceFormatKHR Swapchain::chooseSurfaceFormat(std::vector<vk::SurfaceFormatKHR> const& availableFormats) {
|
||||
|
||||
if(availableFormats.empty()) {
|
||||
std::cout << "[" << __FUNCTION__ << ": " << __LINE__ << "] Error: no swap formats available!" << std::endl;
|
||||
return vk::SurfaceFormatKHR { vk::Format::eUndefined, vk::ColorSpaceKHR::eSrgbNonlinear };
|
||||
}
|
||||
|
||||
// check for preferred format vk::Format::eB8G8R8A8Srgb
|
||||
const auto formatIt = std::ranges::find_if(availableFormats, [](const auto& format) {
|
||||
return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear;
|
||||
});
|
||||
|
||||
// just return whatever if the correct one doesn't exist
|
||||
return formatIt != availableFormats.end() ? *formatIt : availableFormats[0];
|
||||
|
||||
}
|
||||
|
||||
vk::PresentModeKHR Swapchain::choosePresentMode(std::vector<vk::PresentModeKHR> const& availablePresentModes) {
|
||||
|
||||
// vk::PresentModeKHR::eImmediate: present rendered fragments directly to the surface (results in frame tearing)
|
||||
// vk::PresentModeKHR::eFifo: first in first out, waits for frame refresh (vsync)
|
||||
// vk::PresentModeKHR::eFifoRelaxed: same as fifo but does not wait for vsync if the last frame was late
|
||||
// vk::PresentModeKHR::eMailbox: same as fifo but continues rendering new frames while waiting for vsync
|
||||
|
||||
if(availablePresentModes.empty()) {
|
||||
// fifo is guaranteed to be available so this is unreachable
|
||||
std::cout << "[" << __FUNCTION__ << ": " << __LINE__ << "] Error: no swap formats available!" << std::endl;
|
||||
return vk::PresentModeKHR::eFifo;
|
||||
}
|
||||
|
||||
// prefer mailbox if it exists; mailbox ensures least latency between displayed frames and cpu commands
|
||||
assert(std::ranges::any_of(availablePresentModes, [](auto presentMode) { return presentMode == vk::PresentModeKHR::eFifo; }));
|
||||
return std::ranges::any_of(availablePresentModes, [](const vk::PresentModeKHR value) {
|
||||
return vk::PresentModeKHR::eMailbox == value; }) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo;
|
||||
|
||||
}
|
||||
|
||||
vk::Extent2D Swapchain::chooseExtent(vk::SurfaceCapabilitiesKHR const& capabilities) {
|
||||
|
||||
if(capabilities.currentExtent.width != UINT32_MAX) {
|
||||
return capabilities.currentExtent;
|
||||
}
|
||||
|
||||
// otherwise we have to get the swap extent via the window
|
||||
int32_t width = 0;
|
||||
int32_t height = 0;
|
||||
(void)device_->getExtent(&width, &height);
|
||||
vk::Extent2D extent = {
|
||||
std::clamp<uint32_t>(width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width),
|
||||
std::clamp<uint32_t>(height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height)
|
||||
};
|
||||
return extent;
|
||||
}
|
||||
|
||||
uint32_t Swapchain::chooseMinImageCount(vk::SurfaceCapabilitiesKHR const& capabilities) {
|
||||
|
||||
// prefer at least 3 images for the swapchain, otherwise default to the max the driver can support
|
||||
uint32_t minImageCount = std::max(3u, capabilities.minImageCount); // TODO: magic numbers ?
|
||||
if((0 < capabilities.maxImageCount) && capabilities.maxImageCount < minImageCount) {
|
||||
return capabilities.maxImageCount;
|
||||
} else {
|
||||
return minImageCount;
|
||||
}
|
||||
}
|
||||
|
||||
bool Swapchain::createImageViews() {
|
||||
|
||||
if(!imageViews_.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
vk::ImageViewCreateInfo imageViewCreateInfo {
|
||||
.viewType = vk::ImageViewType::e2D,
|
||||
.format = surfaceFormat_.format,
|
||||
.components = { vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity }, // default rgba
|
||||
.subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}, // aspect mask, baseMipLevel, levelCount, baseArrayLayer, layerCount
|
||||
};
|
||||
|
||||
for(vk::Image &image : images_) {
|
||||
imageViewCreateInfo.image = image;
|
||||
imageViews_.emplace_back(*(device_->logicalDevice()), imageViewCreateInfo);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
35
src/Swapchain.hpp
Normal file
35
src/Swapchain.hpp
Normal file
@@ -0,0 +1,35 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Device.hpp"
|
||||
|
||||
// the swapchain functions as the layout for framebuffers that get written to by the gpu and read from to present to the screen
|
||||
class Swapchain {
|
||||
|
||||
public:
|
||||
|
||||
Swapchain(Device* device);
|
||||
~Swapchain() = default;
|
||||
|
||||
std::vector<vk::Image> getImages() { return vkSwapchain_.getImages(); }
|
||||
|
||||
private:
|
||||
|
||||
vk::raii::SwapchainKHR vkSwapchain_ = nullptr;
|
||||
std::vector<vk::Image> images_;
|
||||
std::vector<vk::raii::ImageView> imageViews_;
|
||||
|
||||
Device* device_ = nullptr;
|
||||
vk::SurfaceFormatKHR surfaceFormat_;
|
||||
vk::PresentModeKHR presentFormat_;
|
||||
vk::Extent2D extent_;
|
||||
|
||||
vk::SurfaceFormatKHR chooseSurfaceFormat(std::vector<vk::SurfaceFormatKHR> const& availableFormats);
|
||||
vk::PresentModeKHR choosePresentMode(std::vector<vk::PresentModeKHR> const& availablePresentModes);
|
||||
vk::Extent2D chooseExtent(vk::SurfaceCapabilitiesKHR const& capabilities);
|
||||
uint32_t chooseMinImageCount(vk::SurfaceCapabilitiesKHR const& capabilities);
|
||||
|
||||
bool createSwapchain();
|
||||
bool createImageViews();
|
||||
|
||||
};
|
||||
79
src/Window.cpp
Normal file
79
src/Window.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
|
||||
#include "Window.hpp"
|
||||
|
||||
#include <SDL3/SDL_events.h>
|
||||
#include <iostream>
|
||||
|
||||
Window::Window() {
|
||||
|
||||
(void)init();
|
||||
|
||||
}
|
||||
|
||||
Window::~Window() {
|
||||
|
||||
SDL_DestroyWindow(sdlWindow_);
|
||||
SDL_Quit();
|
||||
|
||||
}
|
||||
|
||||
int Window::init() {
|
||||
|
||||
// TODO: config service for controlling window parameters
|
||||
sdlWindow_ = SDL_CreateWindow("Maiden", 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;
|
||||
}
|
||||
}
|
||||
|
||||
bool Window::createSurface(vk::raii::Instance* instance, vk::raii::SurfaceKHR* surface) {
|
||||
|
||||
if(instance == nullptr) {
|
||||
std::cout << "[" << __FUNCTION__ << ": " << __LINE__ << "] Error: cannot create surface with a null Vulkan instance." << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef __WIN32
|
||||
vk::Win32SurfaceCreateInfoKHR createInfo {
|
||||
.hinstance = GetModuleHandle(nullptr),
|
||||
.hwnd = (HWND)SDL_GetPointerProperty(SDL_GetWindowProperties(sdlWindow_), "SDL.window.win32.hwnd", nullptr);
|
||||
}
|
||||
surface = instance->createWin32SurfaceKHR(createInfo);
|
||||
#else // linux (this technically works on windows too but vulkan gives us an explicit method for WIN32)
|
||||
// its just sdl3 uses the c vulkan api and the app uses the c++ api
|
||||
VkSurfaceKHR cSurface;
|
||||
(void)SDL_Vulkan_CreateSurface(sdlWindow_, static_cast<VkInstance>(**instance), nullptr, &cSurface);
|
||||
*surface = vk::raii::SurfaceKHR(*instance, cSurface);
|
||||
#endif // __WIN32
|
||||
|
||||
if(surface != nullptr) {
|
||||
return true;
|
||||
} else {
|
||||
std::cout << "[" << __FUNCTION__ << ": " << __LINE__ << "] Error: unable to create window surface." << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Window::getExtent(int32_t* width, int32_t* height) {
|
||||
SDL_GetWindowSizeInPixels(sdlWindow_, width, height);
|
||||
return true;
|
||||
}
|
||||
37
src/Window.hpp
Normal file
37
src/Window.hpp
Normal file
@@ -0,0 +1,37 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3/SDL_vulkan.h>
|
||||
#include <vulkan/vulkan_raii.hpp>
|
||||
|
||||
// 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_; }
|
||||
bool createSurface(vk::raii::Instance* instance, vk::raii::SurfaceKHR* surface);
|
||||
bool getExtent(int32_t* width, int32_t* height);
|
||||
|
||||
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;
|
||||
|
||||
};
|
||||
@@ -1,14 +1,12 @@
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "App.hpp"
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
#include <gmock/gmock.h>
|
||||
#include <memory>
|
||||
|
||||
#define private public
|
||||
#include <App.hpp>
|
||||
#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
|
||||
|
||||
}
|
||||
|
||||
TEST_F(TestApp, TestApp_foo_nominal) {
|
||||
|
||||
createUut();
|
||||
|
||||
EXPECT_EQ(uut_->foo(), 12);
|
||||
//EXPECT_EQ(uut_->run(), 0l);
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user