First commit of Vk-Bootstrap

Library contains barebones implementations for simplified
	Instance creation
	Physical Device selection
	Device creation
	Swapchain Creation/Recreation

Much of the repo is WIP, and there is little to no testing.
Also, while a single header file is desired for the future, currently
it is structured more like a normal project with .cpp files
This commit is contained in:
Charles Giessen 2020-01-30 01:15:10 -07:00
commit d9ca075c86
13 changed files with 1853 additions and 0 deletions

50
.clang-format Normal file
View File

@ -0,0 +1,50 @@
BasedOnStyle: LLVM
AccessModifierOffset: 0
AlignEscapedNewlinesLeft: false
AlignTrailingComments: true
AlignAfterOpenBracket: DontAlign
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortFunctionsOnASingleLine: true
AllowShortIfStatementsOnASingleLine: true
AllowShortLoopsOnASingleLine: false
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: false
BinPackArguments: false
BinPackParameters: false
BreakBeforeBinaryOperators: false
BreakBeforeBraces: Allman
BreakBeforeTernaryOperators: false
BreakConstructorInitializersBeforeComma: false
ColumnLimit: 100
CommentPragmas: ''
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 0
ContinuationIndentWidth: 4
Cpp11BracedListStyle: false
DerivePointerBinding: false
IndentCaseLabels: true
IndentFunctionDeclarationAfterType: false
IndentWidth: 4
Language: Cpp
MaxEmptyLinesToKeep: 4
NamespaceIndentation: None
PenaltyBreakBeforeFirstCallParameter: 100
PenaltyBreakComment: 100
PenaltyBreakFirstLessLess: 0
PenaltyBreakString: 100
PenaltyExcessCharacter: 1
PenaltyReturnTypeOnItsOwnLine: 20
PointerBindsToType: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: Always
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: false
SpacesInParentheses: false
Standard: Auto
TabWidth: 4
UseTab: ForIndentation
SortIncludes: false

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*.vscode
*.vs

29
CMakeLists.txt Normal file
View File

@ -0,0 +1,29 @@
cmake_minimum_required(VERSION 3.10 FATAL_ERROR)
project(VulkanBootstrap)
find_package(Vulkan REQUIRED)
find_package(glfw3)
add_library(vk-bootstrap
src/VkBootstrap.h
src/Instance.cpp
src/Device.cpp
src/Swapchain.cpp)
target_include_directories(vk-bootstrap PUBLIC src)
target_include_directories(vk-bootstrap PRIVATE ${Vulkan_INCLUDE_DIRS})
target_link_libraries(vk-bootstrap PRIVATE ${Vulkan_LIBRARIES})
target_compile_features(vk-bootstrap PUBLIC cxx_std_11)
target_compile_options(vk-bootstrap PUBLIC -fsanitize=address)
target_link_options(vk-bootstrap PUBLIC -fsanitize=address)
add_executable(vk-bootstrap-test tests/run_tests.cpp)
target_link_libraries(vk-bootstrap-test vk-bootstrap)
target_link_libraries(vk-bootstrap-test glfw)

49
README.md Normal file
View File

@ -0,0 +1,49 @@
# Vk-Bootstrap
A Vulkan Utility library meant to jump start a Vulkan Application
This library simplifies the tedious process of:
* Instance Creation
* Physical Device Selection
* Device Creation
* Getting Queues
* Swapchain Creation
## Example
```cpp
vkbs::InstanceBuilder builder;
builder.setup_validation_layers()
.set_app_name ("example")
.set_default_debug_messenger ();
auto inst_ret = builder.build ();
vkbs::Instance inst;
if (inst_ret.has_value()) {
// successfully created instance
inst = inst_ret.value();
}
vkbs::PhysicalDeviceSelector;
selector.set_instance(inst)
.set_surface (/* from user created window*/)
.set_minimum_version (1, 0)
.require_dedicated_transfer_queue();
auto phys_ret = selector.select ();
vkbs::PhysicalDevice phys;
if (phys_ret.has_value()) {
// successfully selected a sufficient physical device
phys = phys_ret.value();
}
vkbs::DeviceBuilder device_builder;
device_builder.set_physical_device (phys_dev);
auto dev_ret = device_builder.build ();
if(dev_ret.has_value()){
// successfully created a vulkan device
}
```
## Building

513
src/Device.cpp Normal file
View File

@ -0,0 +1,513 @@
#include "Device.h"
#include <set>
namespace vkbs
{
namespace detail
{
Expected<SurfaceSupportDetails, VkResult> query_surface_support_details (
VkPhysicalDevice phys_device, VkSurfaceKHR surface)
{
if (surface == VK_NULL_HANDLE)
return Error<VkResult>{ VK_ERROR_INITIALIZATION_FAILED, "surface handle was null" };
VkSurfaceCapabilitiesKHR capabilities;
VkResult res = vkGetPhysicalDeviceSurfaceCapabilitiesKHR (phys_device, surface, &capabilities);
if (res != VK_SUCCESS)
{
// error
/* possible errors
VK_ERROR_OUT_OF_HOST_MEMORY
VK_ERROR_OUT_OF_DEVICE_MEMORY
VK_ERROR_SURFACE_LOST_KHR
*/
}
auto formats = detail::get_vector<VkSurfaceFormatKHR> (
vkGetPhysicalDeviceSurfaceFormatsKHR, phys_device, surface);
if (!formats.has_value ())
return detail::Error<VkResult>{ formats.error ().error_code, "Couldn't get surface formats" };
auto present_modes = detail::get_vector<VkPresentModeKHR> (
vkGetPhysicalDeviceSurfacePresentModesKHR, phys_device, surface);
if (!present_modes.has_value ())
return detail::Error<VkResult>{ formats.error ().error_code, "Couldn't get surface present modes" };
return SurfaceSupportDetails{ capabilities, formats.value (), present_modes.value () };
}
// Given a list of formats, return a format supported by the hardware, else return VK_FORMAT_UNDEFINED
VkFormat find_supported_format (
VkPhysicalDevice physical_device, const std::vector<VkFormat>& candidates, VkImageTiling tiling, VkFormatFeatureFlags features)
{
for (VkFormat format : candidates)
{
VkFormatProperties props;
vkGetPhysicalDeviceFormatProperties (physical_device, format, &props);
if (tiling == VK_IMAGE_TILING_LINEAR && (props.linearTilingFeatures & features) == features)
{
return format;
}
else if (tiling == VK_IMAGE_TILING_OPTIMAL && (props.optimalTilingFeatures & features) == features)
{
return format;
}
}
return VK_FORMAT_UNDEFINED;
}
bool check_device_extension_support (VkPhysicalDevice device, std::vector<std::string> extensions)
{
auto available_extensions =
detail::get_vector<VkExtensionProperties> (vkEnumerateDeviceExtensionProperties, device, nullptr);
if (!available_extensions.has_value ()) return false; // maybe handle error
bool all_available = true;
for (const auto& extension : available_extensions.value ())
{
bool found = false;
for (auto& req_ext : extensions)
{
if (req_ext == extension.extensionName) found = true;
break;
}
if (!found) all_available = false;
}
return all_available;
}
detail::QueueFamilies find_queue_families (VkPhysicalDevice phys_device, VkSurfaceKHR surface)
{
auto queue_families = detail::get_vector_noerror<VkQueueFamilyProperties> (
vkGetPhysicalDeviceQueueFamilyProperties, phys_device);
QueueFamilies families;
int dedicated_compute = -1;
int dedicated_transfer = -1;
for (int i = 0; i < queue_families.size (); i++)
{
auto& queueFlags = queue_families[i].queueFlags;
if (queueFlags & VK_QUEUE_GRAPHICS_BIT) families.graphics = i;
if (queueFlags & VK_QUEUE_COMPUTE_BIT) families.compute = i;
if (queueFlags & VK_QUEUE_TRANSFER_BIT) families.transfer = i;
if (queueFlags & VK_QUEUE_SPARSE_BINDING_BIT) families.sparse = i;
// compute that isn't graphics
if (queueFlags & VK_QUEUE_COMPUTE_BIT && ((queueFlags & VK_QUEUE_GRAPHICS_BIT) == 0))
dedicated_compute = i;
// transfer that isn't computer or graphics
if (queueFlags & VK_QUEUE_TRANSFER_BIT && ((queueFlags & VK_QUEUE_COMPUTE_BIT) == 0) &&
((queueFlags & VK_QUEUE_GRAPHICS_BIT) == 0))
dedicated_transfer = i;
VkBool32 presentSupport = false;
if (surface != VK_NULL_HANDLE)
{
VkResult res = vkGetPhysicalDeviceSurfaceSupportKHR (phys_device, i, surface, &presentSupport);
}
if (presentSupport == true) families.present = i;
}
if (dedicated_compute != -1) families.compute = dedicated_compute;
if (dedicated_transfer != -1) families.transfer = dedicated_transfer;
// compute and transfer always supported on the graphics family
if (families.compute != -1 && queue_families[families.graphics].queueFlags & VK_QUEUE_COMPUTE_BIT)
families.compute = families.graphics;
if (families.transfer != -1 && queue_families[families.graphics].queueFlags & VK_QUEUE_TRANSFER_BIT)
families.transfer = families.graphics;
families.count_graphics = queue_families[families.graphics].queueCount;
families.count_transfer = queue_families[families.transfer].queueCount;
families.count_compute = queue_families[families.compute].queueCount;
if (families.sparse != -1) families.count_sparse = queue_families[families.sparse].queueCount;
return families;
}
bool supports_features (VkPhysicalDeviceFeatures supported, VkPhysicalDeviceFeatures requested)
{
// clang-format off
if (requested.robustBufferAccess && ! supported.robustBufferAccess) return false;
if (requested.fullDrawIndexUint32 && ! supported.fullDrawIndexUint32) return false;
if (requested.imageCubeArray && ! supported.imageCubeArray) return false;
if (requested.independentBlend && ! supported.independentBlend) return false;
if (requested.geometryShader && ! supported.geometryShader) return false;
if (requested.tessellationShader && ! supported.tessellationShader) return false;
if (requested.sampleRateShading && ! supported.sampleRateShading) return false;
if (requested.dualSrcBlend && ! supported.dualSrcBlend) return false;
if (requested.logicOp && ! supported.logicOp) return false;
if (requested.multiDrawIndirect && ! supported.multiDrawIndirect) return false;
if (requested.drawIndirectFirstInstance && ! supported.drawIndirectFirstInstance) return false;
if (requested.depthClamp && ! supported.depthClamp) return false;
if (requested.depthBiasClamp && ! supported.depthBiasClamp) return false;
if (requested.fillModeNonSolid && ! supported.fillModeNonSolid) return false;
if (requested.depthBounds && ! supported.depthBounds) return false;
if (requested.wideLines && ! supported.wideLines) return false;
if (requested.largePoints && ! supported.largePoints) return false;
if (requested.alphaToOne && ! supported.alphaToOne) return false;
if (requested.multiViewport && ! supported.multiViewport) return false;
if (requested.samplerAnisotropy && ! supported.samplerAnisotropy) return false;
if (requested.textureCompressionETC2 && ! supported.textureCompressionETC2) return false;
if (requested.textureCompressionASTC_LDR && ! supported.textureCompressionASTC_LDR) return false;
if (requested.textureCompressionBC && ! supported.textureCompressionBC) return false;
if (requested.occlusionQueryPrecise && ! supported.occlusionQueryPrecise) return false;
if (requested.pipelineStatisticsQuery && ! supported.pipelineStatisticsQuery) return false;
if (requested.vertexPipelineStoresAndAtomics && !supported.vertexPipelineStoresAndAtomics) return false;
if (requested.fragmentStoresAndAtomics && !supported.fragmentStoresAndAtomics) return false;
if (requested.shaderTessellationAndGeometryPointSize && !supported.shaderTessellationAndGeometryPointSize) return false;
if (requested.shaderImageGatherExtended && !supported.shaderImageGatherExtended) return false;
if (requested.shaderStorageImageExtendedFormats && !supported.shaderStorageImageExtendedFormats) return false;
if (requested.shaderStorageImageMultisample && !supported.shaderStorageImageMultisample) return false;
if (requested.shaderStorageImageReadWithoutFormat && !supported.shaderStorageImageReadWithoutFormat) return false;
if (requested.shaderStorageImageWriteWithoutFormat && !supported.shaderStorageImageWriteWithoutFormat) return false;
if (requested.shaderUniformBufferArrayDynamicIndexing && !supported.shaderUniformBufferArrayDynamicIndexing) return false;
if (requested.shaderSampledImageArrayDynamicIndexing && !supported.shaderSampledImageArrayDynamicIndexing) return false;
if (requested.shaderStorageBufferArrayDynamicIndexing && !supported.shaderStorageBufferArrayDynamicIndexing) return false;
if (requested.shaderStorageImageArrayDynamicIndexing && !supported.shaderStorageImageArrayDynamicIndexing) return false;
if (requested.shaderClipDistance && !supported.shaderClipDistance) return false;
if (requested.shaderCullDistance && !supported.shaderCullDistance) return false;
if (requested.shaderFloat64 && !supported.shaderFloat64) return false;
if (requested.shaderInt64 && !supported.shaderInt64) return false;
if (requested.shaderInt16 && !supported.shaderInt16) return false;
if (requested.shaderResourceResidency && !supported.shaderResourceResidency) return false;
if (requested.shaderResourceMinLod && !supported.shaderResourceMinLod) return false;
if (requested.sparseBinding && !supported.sparseBinding) return false;
if (requested.sparseResidencyBuffer && !supported.sparseResidencyBuffer) return false;
if (requested.sparseResidencyImage2D && !supported.sparseResidencyImage2D) return false;
if (requested.sparseResidencyImage3D && !supported.sparseResidencyImage3D) return false;
if (requested.sparseResidency2Samples && !supported.sparseResidency2Samples) return false;
if (requested.sparseResidency4Samples && !supported.sparseResidency4Samples) return false;
if (requested.sparseResidency8Samples && !supported.sparseResidency8Samples) return false;
if (requested.sparseResidency16Samples && !supported.sparseResidency16Samples) return false;
if (requested.sparseResidencyAliased && !supported.sparseResidencyAliased) return false;
if (requested.variableMultisampleRate && !supported.variableMultisampleRate) return false;
if (requested.inheritedQueries && !supported.inheritedQueries) return false;
// clang-format on
return true;
}
void populate_physical_device_details (PhysicalDevice phys_device)
{
vkGetPhysicalDeviceFeatures (phys_device.phys_device, &phys_device.physical_device_features);
vkGetPhysicalDeviceProperties (phys_device.phys_device, &phys_device.physical_device_properties);
vkGetPhysicalDeviceMemoryProperties (phys_device.phys_device, &phys_device.memory_properties);
}
} // namespace detail
PhysicalDeviceSelector::Suitable PhysicalDeviceSelector::is_device_suitable (VkPhysicalDevice phys_device)
{
Suitable suitable = Suitable::yes;
detail::QueueFamilies indices = detail::find_queue_families (phys_device, info.surface);
if (criteria.require_dedicated_compute_queue && indices.graphics != indices.compute)
suitable = Suitable::no;
if (criteria.require_dedicated_transfer_queue && indices.graphics != indices.transfer)
suitable = Suitable::no;
if (criteria.require_present && indices.present == -1) suitable = Suitable::no;
bool required_extensions_supported =
detail::check_device_extension_support (phys_device, criteria.required_extensions);
if (!required_extensions_supported) suitable = Suitable::no;
bool desired_extensions_supported =
detail::check_device_extension_support (phys_device, criteria.desired_extensions);
if (!desired_extensions_supported) suitable = Suitable::partial;
bool swapChainAdequate = false;
if (!info.headless)
{
auto swapChainSupport_ret = detail::query_surface_support_details (phys_device, info.surface);
if (swapChainSupport_ret.has_value ())
{
auto swapchain_support = swapChainSupport_ret.value ();
swapChainAdequate =
!swapchain_support.formats.empty () && !swapchain_support.present_modes.empty ();
}
}
if (criteria.require_present && !swapChainAdequate) suitable = Suitable::no;
VkPhysicalDeviceMemoryProperties mem_properties;
vkGetPhysicalDeviceMemoryProperties (phys_device, &mem_properties);
bool has_required_memory = false;
bool has_preferred_memory = false;
for (int i = 0; i < mem_properties.memoryHeapCount; i++)
{
if (mem_properties.memoryHeaps[i].flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)
{
if (mem_properties.memoryHeaps[i].size > criteria.required_mem_size)
{
has_required_memory = true;
}
if (mem_properties.memoryHeaps[i].size > criteria.desired_mem_size)
{
has_preferred_memory = true;
}
}
}
if (!has_required_memory) suitable = Suitable::no;
if (!has_preferred_memory) suitable = Suitable::partial;
VkPhysicalDeviceProperties device_properties;
vkGetPhysicalDeviceProperties (phys_device, &device_properties);
if ((criteria.prefer_discrete && device_properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU) ||
(criteria.prefer_integrated && device_properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU))
{
if (criteria.allow_fallback)
suitable = Suitable::partial;
else
suitable = Suitable::no;
}
if (criteria.required_version < device_properties.apiVersion) suitable = Suitable::no;
if (criteria.desired_version < device_properties.apiVersion) suitable = Suitable::partial;
VkPhysicalDeviceFeatures supported_features{};
vkGetPhysicalDeviceFeatures (phys_device, &supported_features);
bool all_features_supported = detail::supports_features (supported_features, criteria.required_features);
return suitable;
}
detail::Expected<PhysicalDevice, VkResult> PhysicalDeviceSelector::select ()
{
auto physical_devices = detail::get_vector<VkPhysicalDevice> (vkEnumeratePhysicalDevices, info.instance);
if (!physical_devices.has_value ())
{
return detail::Error<VkResult>{ physical_devices.error ().error_code, "Failed to find physical devices" };
}
PhysicalDevice physical_device;
for (const auto& device : physical_devices.value ())
{
auto suitable = is_device_suitable (device);
if (suitable == Suitable::yes)
{
physical_device.phys_device = device;
break;
}
else if (suitable == Suitable::partial)
{
physical_device.phys_device = device;
}
}
if (physical_device.phys_device == VK_NULL_HANDLE)
{
return detail::Error<VkResult>{ VK_ERROR_INITIALIZATION_FAILED, "Failed to find a suitable GPU!" };
}
detail::populate_physical_device_details (physical_device);
physical_device.physical_device_features = criteria.required_features;
physical_device.queue_family_properties =
detail::find_queue_families (physical_device.phys_device, info.surface);
return physical_device;
}
PhysicalDeviceSelector& PhysicalDeviceSelector::set_instance (Instance instance)
{
info.instance = instance.instance;
info.headless = instance.headless;
criteria.require_present = !instance.headless;
return *this;
}
PhysicalDeviceSelector& PhysicalDeviceSelector::set_surface (VkSurfaceKHR surface)
{
info.surface = surface;
info.headless = false;
return *this;
}
PhysicalDeviceSelector& PhysicalDeviceSelector::prefer_discrete (bool prefer_discrete)
{
criteria.prefer_discrete = prefer_discrete;
return *this;
}
PhysicalDeviceSelector& PhysicalDeviceSelector::prefer_integrated (bool prefer_integrated)
{
criteria.prefer_integrated = prefer_integrated;
return *this;
}
PhysicalDeviceSelector& PhysicalDeviceSelector::allow_fallback (bool fallback)
{
criteria.allow_fallback = fallback;
return *this;
}
PhysicalDeviceSelector& PhysicalDeviceSelector::require_present (bool require)
{
criteria.require_present = require;
return *this;
}
PhysicalDeviceSelector& PhysicalDeviceSelector::require_dedicated_transfer_queue ()
{
criteria.require_dedicated_transfer_queue = true;
return *this;
}
PhysicalDeviceSelector& PhysicalDeviceSelector::require_dedicated_compute_queue ()
{
criteria.require_dedicated_compute_queue = true;
return *this;
}
PhysicalDeviceSelector& PhysicalDeviceSelector::required_device_memory_size (VkDeviceSize size)
{
criteria.required_mem_size = size;
return *this;
}
PhysicalDeviceSelector& PhysicalDeviceSelector::desired_device_memory_size (VkDeviceSize size)
{
criteria.desired_mem_size = size;
return *this;
}
PhysicalDeviceSelector& PhysicalDeviceSelector::add_required_extension (std::string extension)
{
criteria.required_extensions.push_back (extension);
return *this;
}
PhysicalDeviceSelector& PhysicalDeviceSelector::add_desired_extension (std::string extension)
{
criteria.desired_extensions.push_back (extension);
return *this;
}
PhysicalDeviceSelector& PhysicalDeviceSelector::set_minimum_version (uint32_t major, uint32_t minor)
{
criteria.required_version = VK_MAKE_VERSION (major, minor, 0);
return *this;
}
PhysicalDeviceSelector& PhysicalDeviceSelector::set_desired_version (uint32_t major, uint32_t minor)
{
criteria.desired_version = VK_MAKE_VERSION (major, minor, 0);
return *this;
}
PhysicalDeviceSelector& PhysicalDeviceSelector::set_required_features (VkPhysicalDeviceFeatures features)
{
criteria.required_features = features;
return *this;
}
// ---- Device ---- //
void destroy_device (Device device) { vkDestroyDevice (device.device, device.allocator); }
struct QueueFamily
{
int32_t family;
uint32_t count;
};
detail::Expected<Device, VkResult> DeviceBuilder::build ()
{
auto& queue_properties = info.physical_device.queue_family_properties;
std::vector<QueueFamily> families;
families.push_back ({ queue_properties.graphics, queue_properties.count_graphics });
if (queue_properties.compute != -1 && queue_properties.compute != queue_properties.graphics)
families.push_back ({ queue_properties.compute, queue_properties.count_compute });
if (queue_properties.transfer != -1 && queue_properties.transfer != queue_properties.graphics)
families.push_back ({ queue_properties.transfer, queue_properties.count_transfer });
if (queue_properties.sparse != -1)
families.push_back ({ queue_properties.sparse, queue_properties.count_sparse });
std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;
float queuePriority = 1.0f;
for (auto& queue : families)
{
VkDeviceQueueCreateInfo queue_create_info = {};
queue_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queue_create_info.queueFamilyIndex = static_cast<uint32_t> (queue.family);
queue_create_info.queueCount = queue.count;
queue_create_info.pQueuePriorities = &queuePriority;
queueCreateInfos.push_back (queue_create_info);
}
std::vector<const char*> extensions;
for (auto& ext : info.extensions)
extensions.push_back (ext.c_str ());
if (info.physical_device.surface != VK_NULL_HANDLE)
extensions.push_back ({ VK_KHR_SWAPCHAIN_EXTENSION_NAME });
VkDeviceCreateInfo device_create_info = {};
device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
device_create_info.pNext = info.pNext_chain;
device_create_info.flags = info.flags;
device_create_info.queueCreateInfoCount = static_cast<uint32_t> (queueCreateInfos.size ());
device_create_info.pQueueCreateInfos = queueCreateInfos.data ();
device_create_info.enabledExtensionCount = static_cast<uint32_t> (extensions.size ());
device_create_info.ppEnabledExtensionNames = extensions.data ();
device_create_info.pEnabledFeatures = &info.physical_device.physical_device_features;
Device device;
VkResult res =
vkCreateDevice (info.physical_device.phys_device, &device_create_info, nullptr, &device.device);
if (res != VK_SUCCESS)
{
return detail::Error<VkResult>{ res, "Couldn't create device" };
}
return device;
}
DeviceBuilder& DeviceBuilder::set_physical_device (PhysicalDevice const& phys_device)
{
info.physical_device = phys_device;
return *this;
}
template <typename T> DeviceBuilder& DeviceBuilder::add_pNext (T* structure)
{
if (info.pNext_chain == nullptr)
info.pNext_chain = (VkBaseOutStructure*)structure;
else
detail::pNext_append (info.pNext_chain, structure);
return *this;
}
// ---- Queue ---- //
namespace detail
{
VkQueue get_queue (Device const& device, uint32_t family, uint32_t index)
{
VkQueue queue;
vkGetDeviceQueue (device.device, family, index, &queue);
return queue;
}
} // namespace detail
detail::Expected<VkQueue, VkResult> get_queue_present (Device const& device)
{
return detail::get_queue (device, device.physical_device.queue_family_properties.present, 0);
}
detail::Expected<VkQueue, VkResult> get_queue_graphics (Device const& device, uint32_t index)
{
if (index >= device.physical_device.queue_family_properties.count_graphics)
return detail::Error<VkResult>{ VK_ERROR_INITIALIZATION_FAILED,
"requested graphics queue index is out of bounds" };
return detail::get_queue (device, device.physical_device.queue_family_properties.graphics, index);
}
detail::Expected<VkQueue, VkResult> get_queue_compute (Device const& device, uint32_t index)
{
if (index >= device.physical_device.queue_family_properties.count_compute)
return detail::Error<VkResult>{ VK_ERROR_INITIALIZATION_FAILED,
"requested compute queue index is out of bounds" };
return detail::get_queue (device, device.physical_device.queue_family_properties.compute, index);
}
detail::Expected<VkQueue, VkResult> get_queue_transfer (Device const& device, uint32_t index)
{
if (index >= device.physical_device.queue_family_properties.count_transfer)
return detail::Error<VkResult>{ VK_ERROR_INITIALIZATION_FAILED,
"requested transfer queue index is out of bounds" };
return detail::get_queue (device, device.physical_device.queue_family_properties.transfer, index);
}
detail::Expected<VkQueue, VkResult> get_queue_sparse (Device const& device, uint32_t index)
{
if (index >= device.physical_device.queue_family_properties.count_sparse)
return detail::Error<VkResult>{ VK_ERROR_INITIALIZATION_FAILED,
"requested sparse queue index is out of bounds" };
return detail::get_queue (device, device.physical_device.queue_family_properties.sparse, index);
}
} // namespace vkbs

180
src/Device.h Normal file
View File

@ -0,0 +1,180 @@
#pragma once
#include "Util.h"
#include "Instance.h"
namespace vkbs
{
namespace detail
{
struct SurfaceSupportDetails
{
VkSurfaceCapabilitiesKHR capabilities;
std::vector<VkSurfaceFormatKHR> formats;
std::vector<VkPresentModeKHR> present_modes;
};
Expected<SurfaceSupportDetails, VkResult> query_surface_support_details (
VkPhysicalDevice phys_device, VkSurfaceKHR surface);
struct QueueFamilies
{
int graphics = -1;
int present = -1;
int transfer = -1;
int compute = -1;
int sparse = -1;
uint32_t count_graphics = 0;
uint32_t count_transfer = 0;
uint32_t count_compute = 0;
uint32_t count_sparse = 0;
};
VkFormat find_supported_format (VkPhysicalDevice physical_device,
const std::vector<VkFormat>& candidates,
VkImageTiling tiling,
VkFormatFeatureFlags features);
bool check_device_extension_support (VkPhysicalDevice device, std::vector<std::string> extensions);
detail::QueueFamilies find_queue_families (VkPhysicalDevice physDevice, VkSurfaceKHR windowSurface);
bool supports_features (VkPhysicalDeviceFeatures supported, VkPhysicalDeviceFeatures requested);
} // namespace detail
// ---- Physical Device ---- //
struct PhysicalDevice
{
VkPhysicalDevice phys_device = VK_NULL_HANDLE;
VkSurfaceKHR surface = VK_NULL_HANDLE;
VkPhysicalDeviceProperties physical_device_properties{};
VkPhysicalDeviceFeatures physical_device_features{};
VkPhysicalDeviceMemoryProperties memory_properties{};
detail::QueueFamilies queue_family_properties;
};
namespace detail
{
void populate_physical_device_details (PhysicalDevice physical_device);
} // namespace detail
struct PhysicalDeviceSelector
{
public:
detail::Expected<PhysicalDevice, VkResult> select ();
PhysicalDeviceSelector& set_instance (Instance instance);
PhysicalDeviceSelector& set_surface (VkSurfaceKHR instance);
PhysicalDeviceSelector& prefer_discrete (bool prefer_discrete = true);
PhysicalDeviceSelector& prefer_integrated (bool prefer_integrated = true);
PhysicalDeviceSelector& allow_fallback (bool fallback = true);
PhysicalDeviceSelector& require_present (bool require = true);
PhysicalDeviceSelector& require_dedicated_transfer_queue ();
PhysicalDeviceSelector& require_dedicated_compute_queue ();
PhysicalDeviceSelector& required_device_memory_size (VkDeviceSize size);
PhysicalDeviceSelector& desired_device_memory_size (VkDeviceSize size);
PhysicalDeviceSelector& add_required_extension (std::string extension);
PhysicalDeviceSelector& add_desired_extension (std::string extension);
PhysicalDeviceSelector& set_desired_version (uint32_t major, uint32_t minor);
PhysicalDeviceSelector& set_minimum_version (uint32_t major = 1, uint32_t minor = 0);
PhysicalDeviceSelector& set_required_features (VkPhysicalDeviceFeatures features);
private:
struct PhysicalDeviceInfo
{
VkInstance instance = VK_NULL_HANDLE;
VkSurfaceKHR surface = VK_NULL_HANDLE;
bool headless = false;
} info;
struct SelectionCriteria
{
bool prefer_discrete = true;
bool prefer_integrated = false;
bool allow_fallback = true;
bool require_present = true;
bool require_dedicated_transfer_queue = false;
bool require_dedicated_compute_queue = false;
VkDeviceSize required_mem_size = 0;
VkDeviceSize desired_mem_size = 0;
std::vector<std::string> required_extensions;
std::vector<std::string> desired_extensions;
uint32_t required_version = VK_MAKE_VERSION (1, 0, 0);
uint32_t desired_version = VK_MAKE_VERSION (1, 0, 0);
VkPhysicalDeviceFeatures required_features{};
} criteria;
enum class Suitable
{
yes,
partial,
no
};
Suitable is_device_suitable (VkPhysicalDevice phys_device);
};
// ---- Device ---- //
struct Device
{
VkDevice device = VK_NULL_HANDLE;
VkAllocationCallbacks* allocator = VK_NULL_HANDLE;
PhysicalDevice physical_device;
VkSurfaceKHR surface = VK_NULL_HANDLE;
};
void destroy_device (Device device);
class DeviceBuilder
{
public:
detail::Expected<Device, VkResult> build ();
DeviceBuilder& set_physical_device (PhysicalDevice const& phys_device);
template <typename T> DeviceBuilder& add_pNext (T* structure);
private:
struct DeviceInfo
{
VkDeviceCreateFlags flags;
VkBaseOutStructure* pNext_chain = nullptr;
VkAllocationCallbacks* allocator = VK_NULL_HANDLE;
PhysicalDevice physical_device;
std::vector<std::string> extensions;
} info;
};
// ---- Queue ---- //
namespace detail
{
VkQueue get_queue (Device const& device, uint32_t family, uint32_t index = 0);
}
detail::Expected<VkQueue, VkResult> get_queue_present (Device const& device);
detail::Expected<VkQueue, VkResult> get_queue_graphics (Device const& device, uint32_t index = 0);
detail::Expected<VkQueue, VkResult> get_queue_compute (Device const& device, uint32_t index = 0);
detail::Expected<VkQueue, VkResult> get_queue_transfer (Device const& device, uint32_t index = 0);
detail::Expected<VkQueue, VkResult> get_queue_sparse (Device const& device, uint32_t index = 0);
} // namespace vkbs

349
src/Instance.cpp Normal file
View File

@ -0,0 +1,349 @@
#include "Instance.h"
#include <stdio.h>
#include <string.h>
namespace vkbs
{
const char* DebugMessageSeverity (VkDebugUtilsMessageSeverityFlagBitsEXT s)
{
switch (s)
{
case VkDebugUtilsMessageSeverityFlagBitsEXT::VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT:
return "VERBOSE";
case VkDebugUtilsMessageSeverityFlagBitsEXT::VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT:
return "ERROR";
case VkDebugUtilsMessageSeverityFlagBitsEXT::VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT:
return "WARNING";
case VkDebugUtilsMessageSeverityFlagBitsEXT::VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT:
return "INFO";
default:
return "UNKNOWN";
}
}
const char* DebugMessageType (VkDebugUtilsMessageTypeFlagsEXT s)
{
switch (s)
{
case VkDebugUtilsMessageTypeFlagBitsEXT::VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT:
return "General";
case VkDebugUtilsMessageTypeFlagBitsEXT::VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT:
return "Validation";
case VkDebugUtilsMessageTypeFlagBitsEXT::VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT:
return "Performance";
default:
return "Unknown";
}
}
namespace detail
{
VkResult create_debug_utils_messenger (VkInstance instance,
PFN_vkDebugUtilsMessengerCallbackEXT debug_callback,
VkDebugUtilsMessageSeverityFlagsEXT severity,
VkDebugUtilsMessageTypeFlagsEXT type,
const VkAllocationCallbacks* pAllocator,
VkDebugUtilsMessengerEXT* pDebugMessenger)
{
if (debug_callback == nullptr) debug_callback = default_debug_callback;
VkDebugUtilsMessengerCreateInfoEXT messengerCreateInfo = {};
messengerCreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
messengerCreateInfo.messageSeverity = severity;
messengerCreateInfo.messageType = type;
messengerCreateInfo.pfnUserCallback = debug_callback;
auto vkCreateDebugUtilsMessengerEXT_func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr (
instance, "vkCreateDebugUtilsMessengerEXT");
if (vkCreateDebugUtilsMessengerEXT_func != nullptr)
{
return vkCreateDebugUtilsMessengerEXT_func (instance, &messengerCreateInfo, pAllocator, pDebugMessenger);
}
else
{
return VK_ERROR_EXTENSION_NOT_PRESENT;
}
}
void destroy_debug_utils_messenger (
VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator)
{
auto vkDestroyDebugUtilsMessengerEXT_func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr (
instance, "vkDestroyDebugUtilsMessengerEXT");
if (vkDestroyDebugUtilsMessengerEXT_func != nullptr)
{
vkDestroyDebugUtilsMessengerEXT_func (instance, debugMessenger, pAllocator);
}
}
VkBool32 default_debug_callback (VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
VkDebugUtilsMessageTypeFlagsEXT messageType,
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
void* pUserData)
{
auto ms = DebugMessageSeverity (messageSeverity);
auto mt = DebugMessageType (messageType);
printf ("[%s: %s]\n%s\n", ms, mt, pCallbackData->pMessage);
return VK_FALSE;
}
bool check_layers_supported (std::vector<const char*> layer_names)
{
auto available_layers = detail::get_vector<VkLayerProperties> (vkEnumerateInstanceLayerProperties);
if (!available_layers.has_value ()) return false; // maybe report error?
bool all_found = true;
for (const auto& layer_name : layer_names)
{
bool found = false;
for (const auto& layer_properties : available_layers.value ())
{
if (strcmp (layer_name, layer_properties.layerName) == 0)
{
found = true;
break;
}
}
if (!found) all_found = false;
}
return all_found;
}
} // namespace detail
void destroy_instance (Instance instance)
{
if (instance.debug_messenger != nullptr)
detail::destroy_debug_utils_messenger (instance.instance, instance.debug_messenger, instance.allocator);
if (instance.instance != VK_NULL_HANDLE)
vkDestroyInstance (instance.instance, instance.allocator);
}
detail::Expected<Instance, VkResult> InstanceBuilder::build ()
{
VkApplicationInfo app_info = {};
app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
app_info.pNext = nullptr;
app_info.pApplicationName = info.app_name.c_str ();
app_info.applicationVersion = info.application_version;
app_info.pEngineName = info.engine_name.c_str ();
app_info.engineVersion = info.engine_version;
app_info.apiVersion = info.api_version;
std::vector<const char*> extensions;
for (auto& ext : info.extensions)
extensions.push_back (ext.c_str ());
if (info.debug_callback != nullptr)
{
extensions.push_back (VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
}
if (!info.headless_context)
{
extensions.push_back (VK_KHR_SURFACE_EXTENSION_NAME);
#if defined(_WIN32)
extentions.push_back (VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
extentions.push_back (VK_KHR_ANDROID_SURFACE_EXTENSION_NAME);
#elif defined(_DIRECT2DISPLAY)
extentions.push_back (VK_KHR_DISPLAY_EXTENSION_NAME);
#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
extentions.push_back (VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME);
#elif defined(VK_USE_PLATFORM_XCB_KHR)
extentions.push_back (VK_KHR_XCB_SURFACE_EXTENSION_NAME);
#elif defined(VK_USE_PLATFORM_X11_HKR)
extentions.push_back (VK_KHR_X11_SURFACE_EXTENSION_NAME);
#elif defined(VK_USE_PLATFORM_IOS_MVK)
extentions.push_back (VK_MVK_IOS_SURFACE_EXTENSION_NAME);
#elif defined(VK_USE_PLATFORM_MACOS_MVK)
extentions.push_back (VK_MVK_MACOS_SURFACE_EXTENSION_NAME);
#endif
}
std::vector<const char*> layers;
for (auto& layer : info.layers)
layers.push_back (layer.c_str ());
if (info.enable_validation_layers)
{
layers.push_back ("VK_LAYER_KHRONOS_validation");
}
bool all_layers_supported = detail::check_layers_supported (layers);
if (!all_layers_supported)
{
return detail::Error<VkResult>{ VK_ERROR_LAYER_NOT_PRESENT, "Not all layers supported!" };
}
VkBaseOutStructure pNext_chain;
pNext_chain.pNext = (VkBaseOutStructure*)info.pNext;
if (info.use_debug_messenger)
{
VkDebugUtilsMessengerCreateInfoEXT messengerCreateInfo = {};
messengerCreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
messengerCreateInfo.messageSeverity = info.debug_message_severity;
messengerCreateInfo.messageType = info.debug_message_type;
messengerCreateInfo.pfnUserCallback = info.debug_callback;
detail::pNext_append (&pNext_chain, &messengerCreateInfo);
}
if (info.enabled_validation_features.size () != 0 || info.disabled_validation_features.size ())
{
VkValidationFeaturesEXT features{};
features.sType = VK_STRUCTURE_TYPE_VALIDATION_FEATURES_EXT;
features.pNext = nullptr;
features.enabledValidationFeatureCount = info.enabled_validation_features.size ();
features.pEnabledValidationFeatures = info.enabled_validation_features.data ();
features.disabledValidationFeatureCount = info.disabled_validation_features.size ();
features.pDisabledValidationFeatures = info.disabled_validation_features.data ();
detail::pNext_append (&pNext_chain, &features);
}
if (info.disabled_validation_checks.size () != 0)
{
VkValidationFlagsEXT checks{};
checks.sType = VK_STRUCTURE_TYPE_VALIDATION_FLAGS_EXT;
checks.pNext = nullptr;
checks.disabledValidationCheckCount = info.disabled_validation_checks.size ();
checks.pDisabledValidationChecks = info.disabled_validation_checks.data ();
detail::pNext_append (&pNext_chain, &checks);
}
VkInstanceCreateInfo instance_create_info = {};
instance_create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
instance_create_info.pNext = pNext_chain.pNext;
instance_create_info.flags = info.flags;
instance_create_info.pApplicationInfo = &app_info;
instance_create_info.enabledExtensionCount = static_cast<uint32_t> (extensions.size ());
instance_create_info.ppEnabledExtensionNames = extensions.data ();
instance_create_info.enabledLayerCount = static_cast<uint32_t> (layers.size ());
instance_create_info.ppEnabledLayerNames = layers.data ();
Instance instance;
VkResult res = vkCreateInstance (&instance_create_info, info.allocator, &instance.instance);
if (res != VK_SUCCESS) return detail::Error<VkResult>{ res, "Failed to create instance" };
res = detail::create_debug_utils_messenger (instance.instance,
info.debug_callback,
info.debug_message_severity,
info.debug_message_type,
info.allocator,
&instance.debug_messenger);
if (res != VK_SUCCESS)
return detail::Error<VkResult>{ res, "Failed to create setup debug callback" };
if (info.headless_context)
{
instance.headless = true;
}
return instance;
}
InstanceBuilder& InstanceBuilder::set_app_name (std::string app_name)
{
info.app_name = app_name;
return *this;
}
InstanceBuilder& InstanceBuilder::set_engine_name (std::string engine_name)
{
info.engine_name = engine_name;
return *this;
}
InstanceBuilder& InstanceBuilder::set_app_version (uint32_t major, uint32_t minor, uint32_t patch)
{
info.application_version = VK_MAKE_VERSION (major, minor, patch);
return *this;
}
InstanceBuilder& InstanceBuilder::set_engine_version (uint32_t major, uint32_t minor, uint32_t patch)
{
info.engine_version = VK_MAKE_VERSION (major, minor, patch);
return *this;
}
InstanceBuilder& InstanceBuilder::set_api_version (uint32_t major, uint32_t minor, uint32_t patch)
{
info.api_version = VK_MAKE_VERSION (major, minor, patch);
return *this;
}
InstanceBuilder& InstanceBuilder::add_layer (std::string layer_name)
{
info.layers.push_back (layer_name);
return *this;
}
InstanceBuilder& InstanceBuilder::add_extension (std::string extension_name)
{
info.extensions.push_back (extension_name);
return *this;
}
InstanceBuilder& InstanceBuilder::setup_validation_layers (bool enable_validation)
{
info.enable_validation_layers = enable_validation;
return *this;
}
InstanceBuilder& InstanceBuilder::set_default_debug_messenger ()
{
info.use_debug_messenger = true;
info.debug_callback = detail::default_debug_callback;
return *this;
}
InstanceBuilder& InstanceBuilder::set_debug_callback (PFN_vkDebugUtilsMessengerCallbackEXT callback)
{
info.use_debug_messenger = true;
info.debug_callback = callback;
return *this;
}
InstanceBuilder& InstanceBuilder::set_headless (bool headless)
{
info.headless_context = headless;
return *this;
}
InstanceBuilder& InstanceBuilder::set_debug_messenger_severity (VkDebugUtilsMessageSeverityFlagsEXT severity)
{
info.debug_message_severity = severity;
return *this;
}
InstanceBuilder& InstanceBuilder::add_debug_messenger_severity (VkDebugUtilsMessageSeverityFlagsEXT severity)
{
info.debug_message_severity = info.debug_message_severity | severity;
return *this;
}
InstanceBuilder& InstanceBuilder::set_debug_messenger_type (VkDebugUtilsMessageTypeFlagsEXT type)
{
info.debug_message_type = type;
return *this;
}
InstanceBuilder& InstanceBuilder::add_debug_messenger_type (VkDebugUtilsMessageTypeFlagsEXT type)
{
info.debug_message_type = info.debug_message_type | type;
return *this;
}
InstanceBuilder& InstanceBuilder::add_validation_disable (VkValidationCheckEXT check)
{
info.disabled_validation_checks.push_back (check);
return *this;
}
InstanceBuilder& InstanceBuilder::add_validation_feature_enable (VkValidationFeatureEnableEXT enable)
{
info.enabled_validation_features.push_back (enable);
return *this;
}
InstanceBuilder& InstanceBuilder::add_validation_feature_disable (VkValidationFeatureDisableEXT disable)
{
info.disabled_validation_features.push_back (disable);
return *this;
}
InstanceBuilder& InstanceBuilder::set_allocator_callback (VkAllocationCallbacks* allocator)
{
info.allocator = allocator;
return *this;
}
} // namespace vkbs

113
src/Instance.h Normal file
View File

@ -0,0 +1,113 @@
#pragma once
#include "Util.h"
namespace vkbs
{
const char* DebugMessageSeverity (VkDebugUtilsMessageSeverityFlagBitsEXT s);
const char* DebugMessageType (VkDebugUtilsMessageTypeFlagsEXT s);
namespace detail
{
VkResult create_debug_utils_messenger (VkInstance instance,
PFN_vkDebugUtilsMessengerCallbackEXT debug_callback,
VkDebugUtilsMessageSeverityFlagsEXT severity,
VkDebugUtilsMessageTypeFlagsEXT type,
const VkAllocationCallbacks* pAllocator,
VkDebugUtilsMessengerEXT* pDebugMessenger);
void destroy_debug_utils_messenger (
VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator);
static VKAPI_ATTR VkBool32 VKAPI_CALL default_debug_callback (VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
VkDebugUtilsMessageTypeFlagsEXT messageType,
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
void* pUserData);
bool check_layers_supported (std::vector<const char*> layer_names);
} // namespace detail
struct Instance
{
VkInstance instance = VK_NULL_HANDLE;
VkAllocationCallbacks* allocator = VK_NULL_HANDLE;
VkDebugUtilsMessengerEXT debug_messenger = VK_NULL_HANDLE;
bool headless = false;
bool validation_enabled = false;
bool debug_callback_enabled = false;
};
void destroy_instance (Instance instance); // release instance resources
class InstanceBuilder
{
public:
detail::Expected<Instance, VkResult> build (); // use builder pattern
InstanceBuilder& set_app_name (std::string app_name);
InstanceBuilder& set_engine_name (std::string engine_name);
InstanceBuilder& set_app_version (uint32_t major, uint32_t minor, uint32_t patch);
InstanceBuilder& set_engine_version (uint32_t major, uint32_t minor, uint32_t patch);
InstanceBuilder& set_api_version (uint32_t major, uint32_t minor, uint32_t patch);
InstanceBuilder& add_layer (std::string app_name);
InstanceBuilder& add_extension (std::string app_name);
InstanceBuilder& setup_validation_layers (bool enable_validation = true);
InstanceBuilder& set_headless (bool headless = false);
InstanceBuilder& set_default_debug_messenger ();
InstanceBuilder& set_debug_callback (PFN_vkDebugUtilsMessengerCallbackEXT callback);
InstanceBuilder& set_debug_messenger_severity (VkDebugUtilsMessageSeverityFlagsEXT severity);
InstanceBuilder& add_debug_messenger_severity (VkDebugUtilsMessageSeverityFlagsEXT severity);
InstanceBuilder& set_debug_messenger_type (VkDebugUtilsMessageTypeFlagsEXT type);
InstanceBuilder& add_debug_messenger_type (VkDebugUtilsMessageTypeFlagsEXT type);
InstanceBuilder& add_validation_disable (VkValidationCheckEXT check);
InstanceBuilder& add_validation_feature_enable (VkValidationFeatureEnableEXT enable);
InstanceBuilder& add_validation_feature_disable (VkValidationFeatureDisableEXT disable);
InstanceBuilder& set_allocator_callback (VkAllocationCallbacks* allocator);
private:
struct InstanceInfo
{
// VkApplicationInfo
std::string app_name;
std::string engine_name;
uint32_t application_version = 0;
uint32_t engine_version = 0;
uint32_t api_version = VK_MAKE_VERSION (1, 0, 0);
// VkInstanceCreateInfo
std::vector<std::string> layers;
std::vector<std::string> extensions;
VkInstanceCreateFlags flags = 0;
void* pNext = nullptr;
VkAllocationCallbacks* allocator = nullptr;
// debug callback
PFN_vkDebugUtilsMessengerCallbackEXT debug_callback = nullptr;
VkDebugUtilsMessageSeverityFlagsEXT debug_message_severity =
VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
VkDebugUtilsMessageTypeFlagsEXT debug_message_type =
VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
// validation features
std::vector<VkValidationCheckEXT> disabled_validation_checks;
std::vector<VkValidationFeatureEnableEXT> enabled_validation_features;
std::vector<VkValidationFeatureDisableEXT> disabled_validation_features;
// booleans
bool ignore_non_critical_issues = true;
bool enable_validation_layers = false;
bool use_debug_messenger = false;
bool headless_context = false;
} info;
};
} // namespace vkbs

154
src/Swapchain.cpp Normal file
View File

@ -0,0 +1,154 @@
#include "Swapchain.h"
namespace vkbs
{
namespace detail
{
VkSurfaceFormatKHR choose_swapchain_surface_format (std::vector<VkSurfaceFormatKHR> const& availableFormats)
{
for (const auto& availableFormat : availableFormats)
{
if (availableFormat.format == VK_FORMAT_B8G8R8A8_UNORM && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)
{
return availableFormat;
}
}
return availableFormats[0];
}
VkPresentModeKHR choose_swap_present_mode (std::vector<VkPresentModeKHR> const& availablePresentModes)
{
for (const auto& availablePresentMode : availablePresentModes)
{
if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR)
{
return availablePresentMode;
}
}
return VK_PRESENT_MODE_FIFO_KHR;
}
VkExtent2D choose_swap_extent (
VkSurfaceCapabilitiesKHR const& capabilities, uint32_t desired_width, uint32_t desired_height)
{
if (capabilities.currentExtent.width != UINT32_MAX)
{
return capabilities.currentExtent;
}
else
{
const int WIDTH = 800;
const int HEIGHT = 600;
VkExtent2D actualExtent = { WIDTH, HEIGHT };
actualExtent.width = std::max (capabilities.minImageExtent.width,
std::min (capabilities.maxImageExtent.width, actualExtent.width));
actualExtent.height = std::max (capabilities.minImageExtent.height,
std::min (capabilities.maxImageExtent.height, actualExtent.height));
return actualExtent;
}
}
} // namespace detail
detail::Expected<Swapchain, VkResult> SwapchainBuilder::build ()
{
auto surface_support =
detail::query_surface_support_details (info.physical_device.phys_device, info.surface);
if (!surface_support.has_value ())
return detail::Error<VkResult>{ surface_support.error ().error_code, "can't get surface support" };
VkSurfaceFormatKHR surfaceFormat =
detail::choose_swapchain_surface_format (surface_support.value ().formats);
VkPresentModeKHR presentMode = detail::choose_swap_present_mode (surface_support.value ().present_modes);
VkExtent2D extent = detail::choose_swap_extent (surface_support.value ().capabilities);
uint32_t imageCount = surface_support.value ().capabilities.minImageCount + 1;
if (surface_support.value ().capabilities.maxImageCount > 0 &&
imageCount > surface_support.value ().capabilities.maxImageCount)
{
imageCount = surface_support.value ().capabilities.maxImageCount;
}
VkSwapchainCreateInfoKHR swapchain_create_info = {};
swapchain_create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
swapchain_create_info.surface = info.surface;
swapchain_create_info.minImageCount = imageCount;
swapchain_create_info.imageFormat = surfaceFormat.format;
swapchain_create_info.imageColorSpace = surfaceFormat.colorSpace;
swapchain_create_info.imageExtent = extent;
swapchain_create_info.imageArrayLayers = 1;
swapchain_create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
detail::QueueFamilies indices =
detail::find_queue_families (info.physical_device.phys_device, info.surface);
uint32_t queueFamilyIndices[] = { static_cast<uint32_t> (indices.graphics),
static_cast<uint32_t> (indices.present) };
if (indices.graphics != indices.present)
{
swapchain_create_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
swapchain_create_info.queueFamilyIndexCount = 2;
swapchain_create_info.pQueueFamilyIndices = queueFamilyIndices;
}
else
{
swapchain_create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
}
swapchain_create_info.preTransform = surface_support.value ().capabilities.currentTransform;
swapchain_create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
swapchain_create_info.presentMode = presentMode;
swapchain_create_info.clipped = VK_TRUE;
swapchain_create_info.oldSwapchain = info.old_swapchain;
Swapchain swapchain;
VkResult res = vkCreateSwapchainKHR (info.device, &swapchain_create_info, nullptr, &swapchain.swapchain);
if (res != VK_SUCCESS)
{
return detail::Error<VkResult>{ res, "Failed to create swapchain" };
}
auto swapchain_images =
detail::get_vector<VkImage> (vkGetSwapchainImagesKHR, info.device, swapchain.swapchain);
swapchain.image_format = surfaceFormat.format;
swapchain.extent = extent;
return swapchain;
}
detail::Expected<Swapchain, VkResult> SwapchainBuilder::recreate (Swapchain const& swapchain)
{
info.old_swapchain = swapchain.swapchain;
return build ();
}
void SwapchainBuilder::destroy (Swapchain const& swapchain)
{
vkDestroySwapchainKHR (swapchain.device, swapchain.swapchain, swapchain.allocator);
}
SwapchainBuilder& SwapchainBuilder::set_desired_format (VkFormat format)
{
info.desired_format = format;
return *this;
}
SwapchainBuilder& SwapchainBuilder::set_fallback_format (VkFormat format)
{
info.fallback_format = format;
return *this;
}
SwapchainBuilder& SwapchainBuilder::set_desired_present_mode (VkPresentModeKHR present_mode)
{
info.desired_present_mode = present_mode;
return *this;
}
SwapchainBuilder& SwapchainBuilder::set_fallback_present_mode (VkPresentModeKHR present_mode)
{
info.fallback_present_mode = present_mode;
return *this;
}
} // namespace vkbs

58
src/Swapchain.h Normal file
View File

@ -0,0 +1,58 @@
#pragma once
#include "Util.h"
#include "Instance.h"
#include "Device.h"
namespace vkbs
{
namespace detail
{
VkSurfaceFormatKHR choose_swapchain_surface_format (std::vector<VkSurfaceFormatKHR> const& availableFormats);
VkPresentModeKHR choose_swap_present_mode (std::vector<VkPresentModeKHR> const& availablePresentModes);
VkExtent2D choose_swap_extent (VkSurfaceCapabilitiesKHR const& capabilities);
} // namespace detail
struct Swapchain
{
VkDevice device = VK_NULL_HANDLE;
VkSwapchainKHR swapchain = VK_NULL_HANDLE;
VkAllocationCallbacks* allocator = VK_NULL_HANDLE;
std::vector<VkImage> images;
VkFormat image_format = VK_FORMAT_UNDEFINED;
VkExtent2D extent = { 0, 0 };
};
class SwapchainBuilder
{
public:
detail::Expected<Swapchain, VkResult> build ();
detail::Expected<Swapchain, VkResult> recreate (Swapchain const& swapchain);
void destroy (Swapchain const& swapchain);
SwapchainBuilder& set_device (Device const& device);
SwapchainBuilder& set_desired_format (VkFormat format);
SwapchainBuilder& set_fallback_format (VkFormat format);
SwapchainBuilder& set_desired_present_mode (VkPresentModeKHR present_mode);
SwapchainBuilder& set_fallback_present_mode (VkPresentModeKHR present_mode);
private:
struct SwapchainInfo
{
VkDevice device = VK_NULL_HANDLE;
PhysicalDevice physical_device;
VkSurfaceKHR surface = VK_NULL_HANDLE;
VkSwapchainKHR old_swapchain = VK_NULL_HANDLE;
VkFormat desired_format = VK_FORMAT_R8G8B8A8_UNORM;
VkFormat fallback_format = VK_FORMAT_R8G8B8A8_UNORM;
VkPresentModeKHR desired_present_mode = VK_PRESENT_MODE_FIFO_KHR;
VkPresentModeKHR fallback_present_mode = VK_PRESENT_MODE_FIFO_KHR;
std::vector<VkPresentModeKHR> acceptable_present_modes;
uint32_t desired_width = 256;
uint32_t desired_height = 256;
} info;
};
} // namespace vkbs

170
src/Util.h Normal file
View File

@ -0,0 +1,170 @@
#pragma once
#include <cassert>
#include <array>
#include <string>
#include <vector>
#include <vulkan/vulkan.h>
namespace vkbs
{
namespace detail
{
template <typename T> struct Error
{
T error_code;
const char* msg;
};
template <typename E, typename U> class Expected
{
public:
Expected (const E& expect) : m_expect{ expect }, m_init{ true } {}
Expected (E&& expect) : m_expect{ std::move (expect) }, m_init{ true } {}
Expected (const Error<U>& error) : m_error{ error }, m_init{ false } {}
Expected (Error<U>&& error) : m_error{ std::move (error) }, m_init{ false } {}
~Expected () { destroy (); }
Expected (Expected const& expected) : m_init (expected.m_init)
{
if (m_init)
new (&m_expect) E{ expected.m_expect };
else
new (&m_error) Error<U>{ expected.m_error };
}
Expected (Expected&& expected) : m_init (expected.m_init)
{
if (m_init)
new (&m_expect) E{ std::move (expected.m_expect) };
else
new (&m_error) Error<U>{ std::move (expected.m_error) };
expected.destroy ();
}
Expected& operator= (const E& expect)
{
destroy ();
m_init = true;
new (&m_expect) E{ expect };
return *this;
}
Expected& operator= (E&& expect)
{
destroy ();
m_init = true;
new (&m_expect) E{ std::move (expect) };
return *this;
}
Expected& operator= (const Error<U>& error)
{
destroy ();
m_init = false;
new (&m_error) Error<U>{ error };
return *this;
}
Expected& operator= (Error<U>&& error)
{
destroy ();
m_init = false;
new (&m_error) Error<U>{ std::move (error) };
return *this;
}
// clang-format off
const E* operator-> () const { assert (m_init); return &m_expect; }
E* operator-> () { assert (m_init); return &m_expect; }
const E& operator* () const& { assert (m_init); return m_expect; }
E& operator* () & { assert (m_init); return m_expect; }
E&& operator* () && { assert (m_init); return std::move (m_expect); }
const E& value () const& { assert (m_init); return m_expect; }
E& value () & { assert (m_init); return m_expect; }
const E&& value () const&& { assert (m_init); return std::move (m_expect); }
E&& value () && { assert (m_init); return std::move (m_expect); }
const Error<U>& error () const& { assert (!m_init); return m_error; }
Error<U>& error () & { assert (!m_init); return m_error; }
const Error<U>&& error () const&& { assert (!m_init); return std::move (m_error); }
Error<U>&& error () && { assert (!m_init); return move (m_error); }
// clang-format on
bool has_value () const { return m_init; }
explicit operator bool () const { return m_init; }
private:
void destroy ()
{
if (m_init)
m_expect.~E ();
else
m_error.~Error<U> ();
}
union
{
E m_expect;
Error<U> m_error;
};
bool m_init;
};
/* TODO implement operator == and operator != as friend or global */
// Helper for robustly executing the two-call pattern
template <typename T, typename F, typename... Ts>
auto get_vector_init (F&& f, T init, Ts&&... ts) -> Expected<std::vector<T>, VkResult>
{
uint32_t count = 0;
std::vector<T> results;
VkResult err;
do
{
err = f (ts..., &count, nullptr);
if (err)
{
return Error<VkResult>{ err, "" };
};
results.resize (count, init);
err = f (ts..., &count, results.data ());
} while (err == VK_INCOMPLETE);
if (err)
{
return Error<VkResult>{ err, "" };
};
return results;
}
template <typename T, typename F, typename... Ts>
auto get_vector (F&& f, Ts&&... ts) -> Expected<std::vector<T>, VkResult>
{
return get_vector_init (f, T (), ts...);
}
template <typename T, typename F, typename... Ts>
auto get_vector_noerror (F&& f, T init, Ts&&... ts) -> std::vector<T>
{
uint32_t count = 0;
std::vector<T> results;
f (ts..., &count, nullptr);
results.resize (count, init);
f (ts..., &count, results.data ());
return results;
}
template <typename T, typename F, typename... Ts>
auto get_vector_noerror (F&& f, Ts&&... ts) -> std::vector<T>
{
return get_vector_noerror (f, T (), ts...);
}
template <typename T> void pNext_append (VkBaseOutStructure* chain, T* structure)
{
if (chain == nullptr) return;
while (chain->pNext != nullptr)
{
chain = chain->pNext;
}
chain->pNext = (VkBaseOutStructure*)structure;
}
} // namespace detail
} // namespace vkbs

5
src/VkBootstrap.h Normal file
View File

@ -0,0 +1,5 @@
#pragma once
#include "Instance.h"
#include "Device.h"
#include "Swapchain.h"

181
tests/run_tests.cpp Normal file
View File

@ -0,0 +1,181 @@
#include <stdio.h>
#include "VkBootstrap.h"
#define GLFW_INCLUDE_VULKAN
#include "GLFW/glfw3.h"
GLFWwindow* create_window_glfw ()
{
glfwInit ();
glfwWindowHint (GLFW_CLIENT_API, GLFW_NO_API);
return glfwCreateWindow (640, 480, "Window Title", NULL, NULL);
}
void destroy_window_glfw (GLFWwindow* window)
{
glfwDestroyWindow (window);
glfwTerminate ();
}
VkSurfaceKHR create_surface_glfw (VkInstance instance, GLFWwindow* window)
{
VkSurfaceKHR surface = nullptr;
VkResult err = glfwCreateWindowSurface (instance, window, NULL, &surface);
if (err)
{
surface = nullptr;
}
return surface;
}
int test_happy_path ()
{
auto window = create_window_glfw ();
vkbs::InstanceBuilder instance_builder;
auto instance_ret = instance_builder.set_default_debug_messenger ().build ();
if (!instance_ret) return -1; // couldn't make instance
vkbs::Instance instance = instance_ret.value ();
auto surface = create_surface_glfw (instance.instance, window);
vkbs::PhysicalDeviceSelector phys_device_selector;
auto phys_device_ret = phys_device_selector.set_instance (instance).set_surface (surface).select ();
if (!phys_device_ret) return -2; // couldn't select physical device
vkbs::PhysicalDevice physical_device = phys_device_ret.value ();
vkbs::DeviceBuilder device_builder;
auto device_ret = device_builder.set_physical_device (physical_device).build ();
if (!device_ret) return -3; // couldn't create device
vkbs::Device device = device_ret.value ();
// possible swapchain creation...
vkbs::destroy_device (device);
vkbs::destroy_instance (instance);
destroy_window_glfw (window);
return 0;
}
int test_instance_basic ()
{
vkbs::InstanceBuilder builder;
auto instance_ret =
builder.setup_validation_layers ()
.set_app_name ("test")
.set_debug_callback ([] (VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
VkDebugUtilsMessageTypeFlagsEXT messageType,
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
void* pUserData) -> VkBool32 {
auto ms = vkbs::DebugMessageSeverity (messageSeverity);
auto mt = vkbs::DebugMessageType (messageType);
printf ("[%s: %s](user defined)\n%s\n", ms, mt, pCallbackData->pMessage);
return VK_FALSE;
})
.set_api_version (1, 2, 111)
.build ();
if (!instance_ret.has_value ())
{
return 1;
}
return 0;
}
int test_instance_headless ()
{
vkbs::InstanceBuilder builder;
auto instance_ret = builder.setup_validation_layers ()
.set_headless ()
.set_app_version (4, 5, 6)
.set_app_name ("headless")
.set_engine_name ("nick")
.set_api_version (1, 0, 34)
.set_default_debug_messenger ()
.build ();
if (!instance_ret.has_value ())
{
return 1;
}
return 0;
}
int test_physical_device_selection ()
{
vkbs::InstanceBuilder instance_builder;
auto instance_ret = instance_builder.set_default_debug_messenger ().build ();
auto instance = instance_ret.value ();
auto window = create_window_glfw ();
auto surface = create_surface_glfw (instance.instance, window);
vkbs::PhysicalDeviceSelector selector;
auto phys_dev_ret = selector.set_instance (instance)
.set_surface (surface)
.add_desired_extension (VK_KHR_MULTIVIEW_EXTENSION_NAME)
.add_required_extension (VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME)
.set_minimum_version (1, 0)
.set_desired_version (1, 1)
.select ();
if (!phys_dev_ret.has_value ())
{
return -1;
}
vkbs::destroy_instance (instance);
destroy_window_glfw (window);
return 0;
}
int test_device_creation ()
{
vkbs::InstanceBuilder instance_builder;
auto instance_ret = instance_builder.set_default_debug_messenger ().build ();
auto instance = instance_ret.value ();
auto window = create_window_glfw ();
auto surface = create_surface_glfw (instance.instance, window);
vkbs::PhysicalDeviceSelector selector;
auto phys_dev_ret = selector.set_instance (instance).set_surface (surface).select ();
auto phys_dev = phys_dev_ret.value ();
vkbs::DeviceBuilder device_builder;
auto dev_ret = device_builder.set_physical_device (phys_dev).build ();
if (!dev_ret.has_value ())
{
printf ("%s\n", dev_ret.error ().msg);
return -1;
}
vkbs::destroy_device (dev_ret.value ());
vkbs::destroy_instance (instance);
destroy_window_glfw (window);
return 0;
}
int main ()
{
printf ("happy path\n");
test_happy_path ();
printf ("\nbasic instance\n");
{
auto ret = test_instance_basic ();
if (ret != 0) printf ("test_failed\n");
}
printf ("\nheadless instance\n");
{
auto ret = test_instance_headless ();
if (ret != 0) printf ("test_failed\n");
}
printf ("\nphysical device selection\n");
{
auto ret = test_physical_device_selection ();
if (ret != 0) printf ("test_failed\n");
}
printf ("\ndevice creation\n");
{
auto ret = test_device_creation ();
if (ret != 0) printf ("test_failed\n");
}
}