This is a simple quick implementation to set up a Vulkan Window using SDL or GLFW
This was tested on Linux Mint 20, although I'm sure the code will work on Windows with few changes. If you discover any issues in windows, please do send merge requests.
VKW handles a number of tedious features which are usually required when creating vulkan applications. The features provided are listed below:
- Provides easy ways to enable extensions.
- Creates the Vulkan window/swapchain
- Creates a default render pass and optional depth image
- Handles acquiring of new frame images
- Handles resizing the window and rebuilding the swapchain images
To use this library you can add the repo as a git submodule
# add the submodule
add_subdirectories( third_party/vkw)
# Link to the vkw target
target_link_libraries( myapplication vkw::vkw)
Follow the example outline in example.cpp
to get started.
cd vkw
mkdir build && cd build
# run the conan install to get the SDL dependency
# this is only needed for the examples
conan install .. -s compiler.libcxx=libstdc++11
# Run cmake and point the cmake prefix path to the location
# where Qt 5.15 is located. This is only needed
# for buliding the examples.
cmake .. -DCMAKE_PREFIX_PATH=/path/to/Qt5.15
cmake --build .
cd vkw
mkdir build && cd build
# run the conan install to get the SDL dependency
# this is only needed for the examples
conan install ../conanfile_win.txt
# Run cmake and point the cmake prefix path to the location
# where Qt 5.15 is located. This is only needed
# for buliding the examples.
cmake .. -DCMAKE_MODULE_PATH=$PWD -DCMAKE_PREFIX_PATH=/path/to/Qt5.15
cmake --build .
VKW provides two classes to set up Vulkan windows: SDL and GLFW. Both classes are very similar to set up and use.
The following two sections show the code to set up a vulkan window using both libraries. As you can see, the code for each of them is very similar.
#include <iostream>
#include <vkw/VKWVulkanWindow.h>
#include <vkw/Adapters/SDLVulkanWindowAdapter.h>
// Need this in at least one cpp file
#include <vkw/VKWVulkanWindow.inl>
int main()
{
// This needs to be called first to initialize SDL
SDL_Init(SDL_INIT_EVERYTHING);
// create a default window and initialize all vulkan
// objects.
auto window = new vkw::VKWVulkanWindow();
auto sdl_window = new vkw::SDLVulkanWindowAdapter();
// 1. create the window and set the adapater
sdl_window->createWindow("Title", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1024,768);
window->setWindowAdapater(sdl_window);
// 2. Create the Instance
vkw::VKWVulkanWindow::InstanceInitilizationInfo2 instanceInfo;
instanceInfo.debugCallback = &VulkanReportFunc;
instanceInfo.vulkanVersion = VK_MAKE_VERSION(1, 0, 0);
instanceInfo.enabledExtensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
window->createVulkanInstance(instanceInfo);
// 3. Create the surface
vkw::VKWVulkanWindow::SurfaceInitilizationInfo2 surfaceInfo;
surfaceInfo.depthFormat = VK_FORMAT_D32_SFLOAT_S8_UINT;
surfaceInfo.presentMode = VK_PRESENT_MODE_FIFO_KHR;
surfaceInfo.additionalImageCount = 1;// how many additional swapchain images should we create ( total = min_images + additionalImageCount
window->createVulkanSurface(surfaceInfo);
// 4. Create the device
// and add additional extensions that we want to enable
window->createVulkanPhysicalDevice();
vkw::VKWVulkanWindow::DeviceInitilizationInfo2 deviceInfo;
deviceInfo.deviceExtensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
deviceInfo.deviceExtensions.push_back(VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME);
// enable a new extended feature
VkPhysicalDeviceVertexInputDynamicStateFeaturesEXT dynamicVertexState = {};
dynamicVertexState.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_INPUT_DYNAMIC_STATE_FEATURES_EXT;
dynamicVertexState.vertexInputDynamicState = true;
deviceInfo.enabledFeatures12.pNext = &dynamicVertexState;
window->createVulkanDevice(deviceInfo);
bool running=true;
while(running)
{
SDL_Event event;
bool resize=false;
while (SDL_PollEvent(&event))
{
if (event.type == SDL_QUIT)
{
running = false;
}
else if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED
/*&& event.window.windowID == SDL_GetWindowID( window->getSDLWindow()) */ )
{
resize=true;
}
}
if( resize )
{
// If the window has changed size. we need to rebuild the swapchain
// and any other textures (depth texture)
window->rebuildSwapchain();
}
// Get the next available frame.
// the Frame struct is simply a POD containing
// all the information that you need to record a command buffer
auto frame = window->acquireNextFrame();
frame.beginCommandBuffer();
frame.clearColor = {{1.f,0.f,0.f,0.f}};
frame.beginRenderPass( frame.commandBuffer );
// record to frame.commandbuffer
frame.endRenderPass(frame.commandBuffer);
frame.endCommandBuffer();
window->submitFrame(frame);
// Present the frame after you have recorded
// the command buffer;
window->presentFrame(frame);
window->waitForPresent();
}
// delete the window to destroy all objects
// that were created.
window->destroy();
delete window;
SDL_Quit();
return 0;
}
#include <iostream>
#include <vkw/VKWVulkanWindow.h>
#include <vkw/Adapters/GLFWVulkanWindowAdapter.h>
// Need this in at least one cpp file
#include <vkw/VKWVulkanWindow.inl>
int main()
{
// This needs to be called first to initialize SDL
glfwInit();
// create a default window and initialize all vulkan
// objects.
auto window = new vkw::VKWVulkanWindow();
auto glfw_window = new vkw::GLFWVulkanWindowAdapter();
// 1. create the window and set the adapater
glfw_window->createWindow("Title", 1024,768);
window->setWindowAdapater(glfw_window);
// 2. Create the Instance
vkw::VKWVulkanWindow::InstanceInitilizationInfo2 instanceInfo;
instanceInfo.debugCallback = &VulkanReportFunc;
instanceInfo.vulkanVersion = VK_MAKE_VERSION(1, 0, 0);
instanceInfo.enabledExtensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
window->createVulkanInstance(instanceInfo);
// 3. Create the surface
vkw::VKWVulkanWindow::SurfaceInitilizationInfo2 surfaceInfo;
surfaceInfo.depthFormat = VkFormat::VK_FORMAT_D32_SFLOAT_S8_UINT;
surfaceInfo.presentMode = VK_PRESENT_MODE_FIFO_KHR;
surfaceInfo.additionalImageCount = 1;// how many additional swapchain images should we create ( total = min_images + additionalImageCount
window->createVulkanSurface(surfaceInfo);
window->createVulkanPhysicalDevice();
// 4. Create the device
// and add additional extensions that we want to enable
vkw::VKWVulkanWindow::DeviceInitilizationInfo2 deviceInfo;
deviceInfo.deviceExtensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
deviceInfo.deviceExtensions.push_back(VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME);
// enable a new extended feature
VkPhysicalDeviceVertexInputDynamicStateFeaturesEXT dynamicVertexState = {};
dynamicVertexState.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_INPUT_DYNAMIC_STATE_FEATURES_EXT;
dynamicVertexState.vertexInputDynamicState = true;
deviceInfo.enabledFeatures12.pNext = &dynamicVertexState;
window->createVulkanDevice(deviceInfo);
bool running = true;
while (!glfwWindowShouldClose(glfw_window->m_window) )
{
glfwPollEvents();
bool resize = glfw_window->requiresResize();
glfw_window->clearRequireResize();
if( resize )
{
// If the window has changed size. we need to rebuild the swapchain
// and any other textures (depth texture)
window->rebuildSwapchain();
}
// Get the next available frame.
// the Frame struct is simply a POD containing
// all the information that you need to record a command buffer
auto frame = window->acquireNextFrame();
frame.beginCommandBuffer();
frame.clearColor = {{1.f,0.f,0.f,0.f}};
frame.beginRenderPass( frame.commandBuffer );
// record to frame.commandbuffer
frame.endRenderPass(frame.commandBuffer);
frame.endCommandBuffer();
window->submitFrame(frame);
// Present the frame after you have recorded
// the command buffer;
window->presentFrame(frame);
window->waitForPresent();
}
// delete the window to destroy all objects
// that were created.
window->destroy();
delete window;
glfwTerminate();
return 0;
}
The application widget is a virtual class which allows you to write your vulkan rendering code. And then use one of the window manager widgets (GLFW,SDL/Qt) provide the main rendering loop.
This was modeled after how Qt handles vulkan rendering. See example_widget.cpp
for more info.
To create your vulkan application, you have to inherit from the vkw::Application
class and provide overloads for 5 functions:
#include <vkw/VulkanApplication.h>
#include <iostream>
class MyApplication : public vkw::Application
{
// Application interface
public:
// Called once during start up. Use this to
// initialize any resources you might want to use
void initResources() override
{
// The following can be used here
// getDevice();
// getPhysicalDevice();
// getInstance();
std::cout << "initResources() " << std::endl;
}
// Called once during shutdown. Use this to release any
// vulkan resources you have created in initResources()
void releaseResources() override
{
// The following can be used here
// getDevice();
// getPhysicalDevice();
// getInstance();
std::cout << "releaseResources() " << std::endl;
}
// Called when the swapchain changes size. Use this to
// allocate any resources that may depend on the swapchain images
// such as descriptor sets, etc.
void initSwapChainResources() override
{
// The following can be used here
// swapchainImageCount();
// swapchainImage( index );
// colorFormat();
// depthStencilFormat();
// swapchainImageSize();
// swapchainImageView();
std::cout << "initSwapchainResources() " << std::endl;
}
// called when the swapchain changes size. This is called
// before initSwapChainResources(). Use this to
// release any resources that depend on the swapchain images .
void releaseSwapChainResources() override
{
std::cout << "releaseSwapChainResources() " << std::endl;
}
// The main render function.
void render( vkw::Frame &frame) override
{
assert( frame.depthImage != VK_NULL_HANDLE);
frame.clearColor.float32[0] = 0.0f;
//frame.clearColor.float32[1] = 1.0f;
//frame.clearColor.float32[2] = 1.0f;
//frame.clearColor.float32[3] = 1.0f;
frame.beginRenderPass( frame.commandBuffer );
frame.endRenderPass(frame.commandBuffer);
// request the next frame
// so that this function will be called again
requestNextFrame();
}
};
The widgets are are wrappers around each of the window handling codes. You can simply choose which window manager you want to use and it will handle the render loops.
See example_widget.cpp
to see how GLFW/SDL can be used.
The different window manager is set using a compile-time constant.
See example_widget_qt.cpp
to see how to use the application in a Qt application.