create swapchain

This commit is contained in:
2026-05-24 23:00:39 -05:00
parent 8b798fcaa7
commit 4e9d69ba49
7 changed files with 179 additions and 19 deletions

View File

@@ -5,8 +5,9 @@
Device::Device(vk::raii::Instance* instance, Window* window): instance_(instance), window_(window) { Device::Device(vk::raii::Instance* instance, Window* window): instance_(instance), window_(window) {
std::cout << "[" << __FUNCTION__ << ": " << __LINE__ << "] Device constructor" << std::endl; selectPhysicalDevice();
createSurface(); createSurface();
createLogicalDevice();
} }
@@ -164,6 +165,7 @@ bool Device::createLogicalDevice() {
graphicsQueue_ = vk::raii::Queue(logicalDevice_, queueIndex, 0); graphicsQueue_ = vk::raii::Queue(logicalDevice_, queueIndex, 0);
if(logicalDevice_ != nullptr) { if(logicalDevice_ != nullptr) {
std::cout << "[" << __FUNCTION__ << ": " << __LINE__ << "] Info: Created logcal device" << std::endl;
return true; return true;
} else { } else {
std::cout << "[" << __FUNCTION__ << ": " << __LINE__ << "] Error: could not create a valid logical device." << std::endl; 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_); (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);
} }

View File

@@ -12,20 +12,27 @@ class Device {
Device(vk::raii::Instance* instance, Window* window); Device(vk::raii::Instance* instance, Window* window);
~Device(); ~Device();
// assigns a capable gpu vkdevice to physicalDevice // helper to get the surface extent from the window
bool selectPhysicalDevice(); bool getExtent(int32_t* width, int32_t* height);
vk::raii::SurfaceKHR* getSurface() { return &surface_; }
// initializes the logical device vk::raii::PhysicalDevice physicalDevice() { return physicalDevice_; }
bool createLogicalDevice(); vk::raii::Device* logicalDevice() { return &logicalDevice_; }
private: private:
// gives a device a score to attempt to select the most capable device // gives a device a score to attempt to select the most capable device
uint32_t evaluatePhysicalDevice(vk::raii::PhysicalDevice& device); uint32_t evaluatePhysicalDevice(vk::raii::PhysicalDevice& device);
// internal create surface // creates the surface
void createSurface(); void createSurface();
// assigns a capable gpu vkdevice to physicalDevice
bool selectPhysicalDevice();
// initializes the logical device
bool createLogicalDevice();
// vulkan objects // vulkan objects
vk::raii::Instance* instance_ = nullptr; vk::raii::Instance* instance_ = nullptr;
vk::raii::PhysicalDevice physicalDevice_ = nullptr; vk::raii::PhysicalDevice physicalDevice_ = nullptr;

View File

@@ -4,6 +4,7 @@
#include <iostream> #include <iostream>
#include "Device.hpp" #include "Device.hpp"
#include "Swapchain.hpp"
Engine::Engine(Window* window): window_(window) { Engine::Engine(Window* window): window_(window) {
@@ -23,14 +24,10 @@ void Engine::init() {
// device selection and setup // device selection and setup
Device device(&instance_, window_); Device device(&instance_, window_);
(void)device.selectPhysicalDevice();
(void)device.createLogicalDevice();
// next steps: // render pipeline
// queue creation Swapchain swapchain(&device);
// vulkan memory allocator // Pipeline pipeline(&device, &swapchain);
// create vulkan surface
// attach surface to window
} }
@@ -132,7 +129,7 @@ std::vector<const char*> Engine::getRequiredInstanceExtensions() {
// get extensions that our windowing library requires // get extensions that our windowing library requires
uint32_t sdlExtensionsCount = 0; 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 // what in the world is this kind of pointer btw
std::vector<const char*> requiredExtensions(sdlExtensions, sdlExtensions + static_cast<size_t>(sdlExtensionsCount)); std::vector<const char*> requiredExtensions(sdlExtensions, sdlExtensions + static_cast<size_t>(sdlExtensionsCount));

View File

@@ -1,6 +1,120 @@
#include "Swapchain.hpp" #include "Swapchain.hpp"
Swapchain::Swapchain() { #include <iostream>
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<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);
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<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;
}
}

View File

@@ -1,14 +1,32 @@
#pragma once #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 // 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 { class Swapchain {
public: public:
Swapchain(); Swapchain(Device* device);
~Swapchain() = default; ~Swapchain() = default;
std::vector<vk::Image> getImages() { return vkSwapchain_.getImages(); }
private: private:
vk::raii::SwapchainKHR vkSwapchain_ = nullptr;
std::vector<vk::Image> swapchainImages_;
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();
}; };

View File

@@ -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); .hwnd = (HWND)SDL_GetPointerProperty(SDL_GetWindowProperties(sdlWindow_), "SDL.window.win32.hwnd", nullptr);
} }
surface = instance->createWin32SurfaceKHR(createInfo); surface = instance->createWin32SurfaceKHR(createInfo);
#else #else // linux (this technically works on windows too but vulkan gives us an explicit method for WIN32)
// this is so unbelievably ugly im so sorry
// its just sdl3 uses the c vulkan api and the app uses the c++ api // its just sdl3 uses the c vulkan api and the app uses the c++ api
VkSurfaceKHR cSurface; VkSurfaceKHR cSurface;
(void)SDL_Vulkan_CreateSurface(sdlWindow_, static_cast<VkInstance>(**instance), nullptr, &cSurface); (void)SDL_Vulkan_CreateSurface(sdlWindow_, static_cast<VkInstance>(**instance), nullptr, &cSurface);
*surface = vk::raii::SurfaceKHR(*instance, cSurface); *surface = vk::raii::SurfaceKHR(*instance, cSurface);
#endif // __WIN32 #endif // __WIN32
std::cout << "[" << __FUNCTION__ << ": " << __LINE__ << "] attempted to createSurface" << std::endl;
if(surface != nullptr) { if(surface != nullptr) {
return true; return true;
} else { } else {
@@ -74,3 +72,8 @@ bool Window::createSurface(vk::raii::Instance* instance, vk::raii::SurfaceKHR* s
return false; return false;
} }
} }
bool Window::getExtent(int32_t* width, int32_t* height) {
SDL_GetWindowSizeInPixels(sdlWindow_, width, height);
return true;
}

View File

@@ -19,6 +19,7 @@ public:
bool rendering() { return rendering_; } bool rendering() { return rendering_; }
bool open() { return open_; } bool open() { return open_; }
bool createSurface(vk::raii::Instance* instance, vk::raii::SurfaceKHR* surface); bool createSurface(vk::raii::Instance* instance, vk::raii::SurfaceKHR* surface);
bool getExtent(int32_t* width, int32_t* height);
private: private: