diff --git a/src/Device.cpp b/src/Device.cpp index 834bbc8..fc02ab3 100644 --- a/src/Device.cpp +++ b/src/Device.cpp @@ -5,8 +5,9 @@ Device::Device(vk::raii::Instance* instance, Window* window): instance_(instance), window_(window) { - std::cout << "[" << __FUNCTION__ << ": " << __LINE__ << "] Device constructor" << std::endl; + selectPhysicalDevice(); createSurface(); + createLogicalDevice(); } @@ -164,6 +165,7 @@ bool Device::createLogicalDevice() { 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; @@ -176,4 +178,22 @@ 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 availableFormats = physicalDevice_.getSurfaceFormatsKHR(*surface_); + std::vector availablePresentModes = physicalDevice_.getSurfacePresentModesKHR(*surface_); + +} + +bool Device::getExtent(int32_t* width, int32_t* height) { + return window_->getExtent(width, height); } diff --git a/src/Device.hpp b/src/Device.hpp index 8eb363d..db913a7 100644 --- a/src/Device.hpp +++ b/src/Device.hpp @@ -12,20 +12,27 @@ class Device { Device(vk::raii::Instance* instance, Window* window); ~Device(); - // assigns a capable gpu vkdevice to physicalDevice - bool selectPhysicalDevice(); + // helper to get the surface extent from the window + bool getExtent(int32_t* width, int32_t* height); + vk::raii::SurfaceKHR* getSurface() { return &surface_; } - // initializes the logical device - bool createLogicalDevice(); + 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); - // internal create surface + // 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; diff --git a/src/Engine.cpp b/src/Engine.cpp index c7ee7e9..c53a66f 100644 --- a/src/Engine.cpp +++ b/src/Engine.cpp @@ -4,6 +4,7 @@ #include #include "Device.hpp" +#include "Swapchain.hpp" Engine::Engine(Window* window): window_(window) { @@ -23,14 +24,10 @@ void Engine::init() { // device selection and setup Device device(&instance_, window_); - (void)device.selectPhysicalDevice(); - (void)device.createLogicalDevice(); - // next steps: - // queue creation - // vulkan memory allocator - // create vulkan surface - // attach surface to window + // render pipeline + Swapchain swapchain(&device); + // Pipeline pipeline(&device, &swapchain); } @@ -132,7 +129,7 @@ std::vector Engine::getRequiredInstanceExtensions() { // get extensions that our windowing library requires uint32_t sdlExtensionsCount = 0; - const char* const* sdlExtensions{ SDL_Vulkan_GetInstanceExtensions(&sdlExtensionsCount) }; + 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 requiredExtensions(sdlExtensions, sdlExtensions + static_cast(sdlExtensionsCount)); diff --git a/src/Swapchain.cpp b/src/Swapchain.cpp index 8be4204..e9de203 100644 --- a/src/Swapchain.cpp +++ b/src/Swapchain.cpp @@ -1,6 +1,120 @@ #include "Swapchain.hpp" -Swapchain::Swapchain() { +#include + +Swapchain::Swapchain(Device* device) : device_(device) { + + (void)createSwapchain(); } + +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 availableFormats = physicalDevice.getSurfaceFormatsKHR(*surface); + surfaceFormat_ = chooseSurfaceFormat(availableFormats); + + std::vector 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); + swapchainImages_ = 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 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 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(width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width), + std::clamp(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; + } +} diff --git a/src/Swapchain.hpp b/src/Swapchain.hpp index cba4749..3d3c145 100644 --- a/src/Swapchain.hpp +++ b/src/Swapchain.hpp @@ -1,14 +1,32 @@ #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(); + Swapchain(Device* device); ~Swapchain() = default; + std::vector getImages() { return vkSwapchain_.getImages(); } + private: + vk::raii::SwapchainKHR vkSwapchain_ = nullptr; + std::vector swapchainImages_; + + Device* device_ = nullptr; + vk::SurfaceFormatKHR surfaceFormat_; + vk::PresentModeKHR presentFormat_; + vk::Extent2D extent_; + + vk::SurfaceFormatKHR chooseSurfaceFormat(std::vector const& availableFormats); + vk::PresentModeKHR choosePresentMode(std::vector const& availablePresentModes); + vk::Extent2D chooseExtent(vk::SurfaceCapabilitiesKHR const& capabilities); + uint32_t chooseMinImageCount(vk::SurfaceCapabilitiesKHR const& capabilities); + + bool createSwapchain(); }; \ No newline at end of file diff --git a/src/Window.cpp b/src/Window.cpp index b026b71..9bcb586 100644 --- a/src/Window.cpp +++ b/src/Window.cpp @@ -58,15 +58,13 @@ bool Window::createSurface(vk::raii::Instance* instance, vk::raii::SurfaceKHR* s .hwnd = (HWND)SDL_GetPointerProperty(SDL_GetWindowProperties(sdlWindow_), "SDL.window.win32.hwnd", nullptr); } surface = instance->createWin32SurfaceKHR(createInfo); -#else - // this is so unbelievably ugly im so sorry +#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(**instance), nullptr, &cSurface); *surface = vk::raii::SurfaceKHR(*instance, cSurface); #endif // __WIN32 - std::cout << "[" << __FUNCTION__ << ": " << __LINE__ << "] attempted to createSurface" << std::endl; if(surface != nullptr) { return true; } else { @@ -74,3 +72,8 @@ bool Window::createSurface(vk::raii::Instance* instance, vk::raii::SurfaceKHR* s return false; } } + +bool Window::getExtent(int32_t* width, int32_t* height) { + SDL_GetWindowSizeInPixels(sdlWindow_, width, height); + return true; +} diff --git a/src/Window.hpp b/src/Window.hpp index 1f85a98..ce787ae 100644 --- a/src/Window.hpp +++ b/src/Window.hpp @@ -19,6 +19,7 @@ public: 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: