Created physical device object

Contains helper functions for picking the most suitable device in a rust/java factory-like way
Has cast operator for underlying vulkan object
This commit is contained in:
2025-07-07 22:48:07 +10:00
parent 175897e7d9
commit 78a5421021
2 changed files with 500 additions and 0 deletions

View File

@@ -0,0 +1,82 @@
#pragma once
#include "basalt_context.h"
namespace basalt
{
struct DeviceQueueFamilyIndicies
{
DeviceQueueFamilyIndicies(VkPhysicalDevice phys, VkSurfaceKHR surface);
DeviceQueueFamilyIndicies(u32 graphics=-1, u32 present=-1, u32 compute=-1, u32 transfer=-1);
u32 graphics = -1;
u32 present = -1;
u32 compute = -1;
u32 transfer = -1;
};
struct SwapchainSupportDetails
{
SwapchainSupportDetails();
SwapchainSupportDetails(VkPhysicalDevice phy, VkSurfaceKHR surface);
~SwapchainSupportDetails();
VkSurfaceFormatKHR get_surface_format(const std::initializer_list<VkSurfaceFormatKHR>& allowed_formats);
VkPresentModeKHR get_present_mode(const std::initializer_list<VkPresentModeKHR>& allowed_present_modes);
VkExtent2D get_framebuffer_extent(basalt::Window& window);
VkSurfaceCapabilitiesKHR surface_capabilities;
basalt::darray<VkSurfaceFormatKHR> surface_formats;
basalt::darray<VkPresentModeKHR> present_modes;
VkSurfaceFormatKHR cached_format = {VK_FORMAT_MAX_ENUM, VK_COLOR_SPACE_MAX_ENUM_KHR};
VkPresentModeKHR cached_present_mode = VK_PRESENT_MODE_MAX_ENUM_KHR;
};
class PhysicalDevice
{
public:
PhysicalDevice(VkPhysicalDevice dev, const DeviceQueueFamilyIndicies& indicies, basalt::darray<const char*>&& enabled_extensions, VkSurfaceKHR surface);
operator VkPhysicalDevice() noexcept;
basalt::darray<const char*> enabled_extensions;
DeviceQueueFamilyIndicies indicies;
VkSurfaceKHR surface;
VkPhysicalDevice phys;
};
class PhysicalDeviceSelector
{
public:
PhysicalDeviceSelector(const PhysicalDeviceSelector& src);
PhysicalDeviceSelector& operator =(const PhysicalDeviceSelector& src) = delete;
PhysicalDeviceSelector(PhysicalDeviceSelector&& other);
PhysicalDeviceSelector& operator =(PhysicalDeviceSelector&& other) = delete;
PhysicalDeviceSelector(basalt::Context& ctx, VkSurfaceKHR surface);
~PhysicalDeviceSelector();
uint32_t num_devices(void);
basalt::PhysicalDevice pick();
basalt::PhysicalDeviceSelector& prefer_types(const VkPhysicalDeviceType type, const float pv = 1.0f, const float nv = INFINITY);
basalt::PhysicalDeviceSelector& prefer_features(const VkPhysicalDeviceFeatures req, const float pv = 1.0f, const float nv = INFINITY);
basalt::PhysicalDeviceSelector& ensure_queues(const VkQueueFlagBits req_queues, const bool require_present, const float pv = 1.0f, const float nv = INFINITY);
basalt::PhysicalDeviceSelector& prefer_extension (const char* extension, const float pv = 1.0f, const float nv=INFINITY);
basalt::PhysicalDeviceSelector& prefer_extensions(const char** extensions, u32 num_extensions, const float pv = 1.0f, const float nv = INFINITY);
basalt::PhysicalDeviceSelector& prefer_extensions(const basalt::darray<const char*>& extensions, const float pv = 1.0f, const float nv = INFINITY);
basalt::PhysicalDeviceSelector& ensure_swapchain(void);
basalt::PhysicalDeviceSelector& prefer_surface_format(VkSurfaceFormatKHR format, const float pv = 1.0f, const float nv = INFINITY);
basalt::PhysicalDeviceSelector& prefer_present_mode(VkPresentModeKHR present_mode, const float pv = 1.0f, const float nv = INFINITY);
basalt::darray<float> device_rankings;
basalt::darray<VkPhysicalDevice> devices;
basalt::darray<VkPhysicalDeviceProperties> props;
basalt::darray<VkPhysicalDeviceFeatures> features;
basalt::darray<SwapchainSupportDetails> support_details;
basalt::darray<const char*> enabled_extensions;
basalt::Context& ctx;
VkSurfaceKHR surface;
bool using_swapchain = false;
};
}

View File

@@ -0,0 +1,418 @@
#include "vulkan/basalt_physical_device.h"
basalt::PhysicalDeviceSelector::PhysicalDeviceSelector(const PhysicalDeviceSelector& src) :
ctx(src.ctx), device_rankings(src.device_rankings), devices(src.devices), props(src.props),
features(src.features), enabled_extensions(src.enabled_extensions), support_details(src.support_details)
{}
basalt::PhysicalDeviceSelector::PhysicalDeviceSelector(PhysicalDeviceSelector&& other) :
ctx(other.ctx), device_rankings(std::move(other.device_rankings)), devices(std::move(other.devices)),
props(std::move(other.props)), features(std::move(other.features)), enabled_extensions(std::move(other.enabled_extensions)),
support_details(std::move(other.support_details))
{}
basalt::PhysicalDeviceSelector::PhysicalDeviceSelector(basalt::Context& ctx, VkSurfaceKHR surface) :
ctx(ctx),
devices(MEMORY_TAG_CLASS_ARRAY | MEMORY_TAG_ZONE_ENGINE | MEMORY_TAG_ALIGN_ANY),
props(MEMORY_TAG_CLASS_ARRAY | MEMORY_TAG_ZONE_ENGINE | MEMORY_TAG_ALIGN_ANY),
features(MEMORY_TAG_CLASS_ARRAY | MEMORY_TAG_ZONE_ENGINE | MEMORY_TAG_ALIGN_ANY),
device_rankings(MEMORY_TAG_CLASS_ARRAY | MEMORY_TAG_ZONE_ENGINE | MEMORY_TAG_ALIGN_ANY),
enabled_extensions(MEMORY_TAG_CLASS_ARRAY | MEMORY_TAG_ZONE_ENGINE | MEMORY_TAG_ALIGN_ANY),
support_details(MEMORY_TAG_CLASS_ARRAY | MEMORY_TAG_ZONE_ENGINE | MEMORY_TAG_ALIGN_ANY),
surface(surface)
{
VkResult err = VK_SUCCESS;
u32 n_devices;
VK_ASSERT(vkEnumeratePhysicalDevices(ctx.inst, &n_devices, VK_NULL_HANDLE), "Failed to enumerate count of physical devices\n\tAt %s:%d\n\tError %s\n");
this->devices.resize(n_devices);
this->features.resize(n_devices);
this->device_rankings.resize(n_devices);
this->props.resize(n_devices);
VK_ASSERT(vkEnumeratePhysicalDevices(ctx.inst, &n_devices, this->devices.m_pdata), "Failed to enumerate count of physical devices\n\tAt %s:%d\n\tError %s\n");
for (u32 i = 0; i < n_devices; ++i)
{
vkGetPhysicalDeviceFeatures(this->devices[i], &this->features[i]);
vkGetPhysicalDeviceProperties(this->devices[i], &this->props[i]);
device_rankings[i] = 0.0f;
}
BTRACE("%s %p created\n", typeid(*this).name(), this);
}
basalt::PhysicalDeviceSelector::~PhysicalDeviceSelector()
{
BTRACE("%s %p destroyed\n", typeid(*this).name(), this);
}
uint32_t basalt::PhysicalDeviceSelector::num_devices(void)
{
u32 cnt = 0;
for (u32 i = 0; i < this->devices.m_nelements; ++i)
cnt += this->device_rankings[i] != -INFINITY;
return cnt;
}
basalt::PhysicalDevice basalt::PhysicalDeviceSelector::pick()
{
VkResult err = VK_SUCCESS;
VkPhysicalDevice best = nullptr;
float score = -INFINITY;
u32 best_idx = -1;
for (u32 i = 0; i < this->devices.m_nelements; ++i)
{
if (this->device_rankings[i] > score)
{
best = this->devices[i];
score = this->device_rankings[i];
best_idx = i;
}
}
BASSERT_FATAL(best != nullptr, "Assertion %s failed at %s:%d\n\tNo device matches all requirements specified\n");
//BDEBUG("Chosen device %s supports the extensions;", this->props[best_idx].deviceName);
//darray<VkExtensionProperties> ext_props(MEMORY_TAG_CLASS_DYNARRAY | MEMORY_TAG_ZONE_ENGINE | MEMORY_TAG_ALIGN_ANY);
//u32 n = -1;
//vkEnumerateDeviceExtensionProperties(best, nullptr, &n, nullptr);
//ext_props.resize(n);
//vkEnumerateDeviceExtensionProperties(best, nullptr, &n, ext_props.begin());
//for (const VkExtensionProperties& ext : ext_props)
//{
// BDEBUG("\t%s\n", ext.extensionName);
//}
return basalt::PhysicalDevice(best, basalt::DeviceQueueFamilyIndicies(best, this->surface), std::move(this->enabled_extensions), this->surface);
}
basalt::PhysicalDeviceSelector& basalt::PhysicalDeviceSelector::prefer_types(const VkPhysicalDeviceType type, const float pv, const float nv)
{
for (u32 i = 0; i < this->devices.m_nelements; ++i)
{
if (this->device_rankings[i] == -INFINITY)
continue;
if (this->props[i].deviceType & type)
this->device_rankings[i] += pv;
else
this->device_rankings[i] -= nv;
}
return *this;
}
basalt::PhysicalDeviceSelector& basalt::PhysicalDeviceSelector::prefer_features(const VkPhysicalDeviceFeatures req, const float pv, const float nv)
{
const VkBool32* u32_req = (VkBool32*)&req;
for (u32 i = 0; i < this->devices.m_nelements; ++i)
{
if (this->device_rankings[i] == -INFINITY)
continue;
const VkBool32* u32_avail = (VkBool32*)&this->features[i];
for (size_t j = 0; j < sizeof(VkPhysicalDeviceProperties) / sizeof(VkBool32); ++j)
{
if (u32_req && u32_req == u32_avail)
this->device_rankings[i] += pv;
else if (u32_req)
this->device_rankings[i] -= nv;
if (this->device_rankings[i] == -INFINITY)
break;
}
}
return *this;
}
basalt::PhysicalDeviceSelector& basalt::PhysicalDeviceSelector::ensure_queues(const VkQueueFlagBits req_queues, const bool require_present, const float pv, const float nv)
{
for (u32 i = 0; i < this->devices.m_nelements; ++i)
{
basalt::DeviceQueueFamilyIndicies indicies(this->devices[i], this->surface);
if ((req_queues & VK_QUEUE_GRAPHICS_BIT) && (indicies.graphics != -1))
this->device_rankings[i] += pv;
else if (req_queues & VK_QUEUE_GRAPHICS_BIT)
this->device_rankings[i] -= nv;
if ((req_queues & VK_QUEUE_TRANSFER_BIT) && (indicies.transfer != -1))
this->device_rankings[i] += pv;
else if (req_queues & VK_QUEUE_TRANSFER_BIT)
this->device_rankings[i] -= nv;
if ((req_queues & VK_QUEUE_COMPUTE_BIT) && (indicies.compute != -1))
this->device_rankings[i] += pv;
else if (req_queues & VK_QUEUE_COMPUTE_BIT)
this->device_rankings[i] -= nv;
if (require_present && (indicies.present != -1))
this->device_rankings[i] += pv;
else if (require_present)
this->device_rankings[i] -= nv;
}
return *this;
}
basalt::PhysicalDeviceSelector& basalt::PhysicalDeviceSelector::prefer_extension(const char* extension, const float pv, const float nv)
{
u32 num_supported_extensions = 0;
darray<VkExtensionProperties> exts(32, MEMORY_TAG_CLASS_DYNARRAY | MEMORY_TAG_ZONE_ENGINE | MEMORY_TAG_ALIGN_ANY);
for (u32 i = 0; i < this->devices.m_nelements; ++i)
{
vkEnumerateDeviceExtensionProperties(this->devices[i], nullptr, &num_supported_extensions, nullptr);
exts.reserve(num_supported_extensions);
vkEnumerateDeviceExtensionProperties(this->devices[i], nullptr, &num_supported_extensions, exts.m_pdata);
exts.m_nelements = num_supported_extensions;
bool found = false;
for (const VkExtensionProperties& prop : exts)
{
if (strcmp(prop.extensionName, extension) == 0)
{
this->device_rankings[i] += pv;
found = true;
break;
}
}
if (!found)
this->device_rankings[i] -= nv;
}
return *this;
}
basalt::PhysicalDeviceSelector& basalt::PhysicalDeviceSelector::prefer_extensions(const char** extensions, u32 num_extensions, const float pv, const float nv)
{
u32 num_supported_extensions = 0;
darray<VkExtensionProperties> exts(32, MEMORY_TAG_CLASS_DYNARRAY | MEMORY_TAG_ZONE_ENGINE | MEMORY_TAG_ALIGN_ANY);
for (u32 i = 0; i < this->devices.m_nelements; ++i)
{
vkEnumerateDeviceExtensionProperties(this->devices[i], nullptr, &num_supported_extensions, nullptr);
exts.reserve(num_supported_extensions);
vkEnumerateDeviceExtensionProperties(this->devices[i], nullptr, &num_supported_extensions, exts.m_pdata);
exts.m_nelements = num_supported_extensions;
for (u32 j = 0; j < num_extensions; ++j)
{
for (const VkExtensionProperties& prop : exts)
{
if (strcmp(prop.extensionName, extensions[j]) == 0)
{
this->device_rankings[i] += pv;
break;
}
}
this->device_rankings[i] -= nv;
}
}
return *this;
}
basalt::PhysicalDeviceSelector& basalt::PhysicalDeviceSelector::prefer_extensions(const basalt::darray<const char*>& extensions, const float pv, const float nv)
{
u32 num_supported_extensions = 0;
darray<VkExtensionProperties> exts(32, MEMORY_TAG_CLASS_DYNARRAY | MEMORY_TAG_ZONE_ENGINE | MEMORY_TAG_ALIGN_ANY);
for (u32 i = 0; i < this->devices.m_nelements; ++i)
{
vkEnumerateDeviceExtensionProperties(this->devices[i], nullptr, &num_supported_extensions, nullptr);
exts.reserve(num_supported_extensions);
vkEnumerateDeviceExtensionProperties(this->devices[i], nullptr, &num_supported_extensions, exts.m_pdata);
exts.m_nelements = num_supported_extensions;
for (const char* extension : extensions)
{
for (const VkExtensionProperties& prop : exts)
{
if (strcmp(prop.extensionName, extension) == 0)
{
this->device_rankings[i] += pv;
break;
}
}
this->device_rankings[i] -= nv;
}
}
return *this;
}
basalt::PhysicalDeviceSelector& basalt::PhysicalDeviceSelector::ensure_swapchain(void)
{
// Require the swapchain extension
this->prefer_extension(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
this->support_details.resize(this->devices.m_nelements);
for (u32 i = 0; i < this->devices.m_nelements; ++i)
{
if (this->device_rankings[i] == -INFINITY)
{
new (this->support_details.m_pdata + i) SwapchainSupportDetails();
continue;
}
// Ensure that the extension has present and surface modes
new (this->support_details.m_pdata + i) SwapchainSupportDetails(this->devices[i], surface);
if (this->support_details[i].present_modes.m_nelements == 0 ||
this->support_details[i].surface_formats.m_nelements == 0)
{
this->device_rankings[i] = -INFINITY;
}
}
using_swapchain = true;
return *this;
}
basalt::PhysicalDeviceSelector& basalt::PhysicalDeviceSelector::prefer_surface_format(VkSurfaceFormatKHR format, const float pv, const float nv)
{
BASSERT_FATAL(this->using_swapchain, "Assertion %s failed at %s:%d\n\t%s is not set up for checking swapchain surface format support\n\tCall ensure_swapchain() before this\n");
for (u32 i = 0; i < this->devices.m_nelements; ++i)
{
if (this->device_rankings[i] == -INFINITY)
continue;
bool found = false;
for (const VkSurfaceFormatKHR& fmt : this->support_details[i].surface_formats)
{
if (fmt.colorSpace == format.colorSpace && fmt.format == format.format)
{
found = true;
break;
}
}
if (found)
this->device_rankings[i] += pv;
else this->device_rankings[i] -= nv;
}
return *this;
}
basalt::PhysicalDeviceSelector& basalt::PhysicalDeviceSelector::prefer_present_mode(VkPresentModeKHR present_mode, const float pv, const float nv)
{
BASSERT_FATAL(this->using_swapchain, "Assertion %s failed at %s:%d\n\t%s is not set up for checking swapchain present mode support\n\tCall ensure_swapchain() before this\n");
for (u32 i = 0; i < this->devices.m_nelements; ++i)
{
if (this->device_rankings[i] == -INFINITY)
continue;
bool found = false;
for (const VkPresentModeKHR& mode : this->support_details[i].present_modes)
{
if (mode == present_mode)
{
found = true;
break;
}
}
if (found)
this->device_rankings[i] += pv;
else this->device_rankings[i] -= nv;
}
return *this;
}
basalt::DeviceQueueFamilyIndicies::DeviceQueueFamilyIndicies(VkPhysicalDevice phys, VkSurfaceKHR surface)
{
VkResult err = VK_SUCCESS;
u32 num_queue_families = 0;
vkGetPhysicalDeviceQueueFamilyProperties(phys, &num_queue_families, VK_NULL_HANDLE);
basalt::darray<VkQueueFamilyProperties> queue_families(num_queue_families,
MEMORY_TAG_CLASS_ARRAY | MEMORY_TAG_ZONE_ENGINE | MEMORY_TAG_ALIGN_ANY);
vkGetPhysicalDeviceQueueFamilyProperties(phys, &num_queue_families, queue_families.m_pdata);
queue_families.m_nelements = num_queue_families;
for (u32 i = 0; i < queue_families.m_nelements; ++i)
{
if (queue_families[i].queueFlags & VK_QUEUE_GRAPHICS_BIT)
this->graphics = i;
if (queue_families[i].queueFlags & VK_QUEUE_TRANSFER_BIT)
this->transfer = i;
if (queue_families[i].queueFlags & VK_QUEUE_COMPUTE_BIT)
this->compute = i;
if (surface != VK_NULL_HANDLE)
{
VkBool32 present_supported = VK_FALSE;
VK_ASSERT(vkGetPhysicalDeviceSurfaceSupportKHR(phys, i, surface, &present_supported), "Failed to determine if physical device queue supports presenting to surface\n\tAt %s:%d\n\tError %s\n");
if (present_supported)
this->present = i;
}
}
}
basalt::DeviceQueueFamilyIndicies::DeviceQueueFamilyIndicies(u32 graphics, u32 present, u32 compute, u32 transfer) :
graphics(graphics), present(present), compute(compute), transfer(transfer)
{}
basalt::PhysicalDevice::PhysicalDevice(VkPhysicalDevice dev, const DeviceQueueFamilyIndicies& indicies, basalt::darray<const char*>&& enabled_extensions, VkSurfaceKHR surface) :
phys(dev), indicies(indicies), enabled_extensions(std::move(enabled_extensions)), surface(surface)
{ }
basalt::PhysicalDevice::operator VkPhysicalDevice() noexcept
{ return this->phys; }
basalt::SwapchainSupportDetails::SwapchainSupportDetails() :
surface_formats(MEMORY_TAG_CLASS_DYNARRAY | MEMORY_TAG_ZONE_UNKNOWN | MEMORY_TAG_ALIGN_ANY),
present_modes(MEMORY_TAG_CLASS_DYNARRAY | MEMORY_TAG_ZONE_UNKNOWN | MEMORY_TAG_ALIGN_ANY)
{}
basalt::SwapchainSupportDetails::SwapchainSupportDetails(VkPhysicalDevice phy, VkSurfaceKHR surface) :
surface_formats(MEMORY_TAG_CLASS_DYNARRAY | MEMORY_TAG_ZONE_ENGINE | MEMORY_TAG_ALIGN_ANY),
present_modes(MEMORY_TAG_CLASS_DYNARRAY | MEMORY_TAG_ZONE_ENGINE | MEMORY_TAG_ALIGN_ANY)
{
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(phy, surface, &this->surface_capabilities);
u32 tmp = 0;
vkGetPhysicalDeviceSurfaceFormatsKHR(phy, surface, &tmp, VK_NULL_HANDLE);
this->surface_formats.resize(tmp);
vkGetPhysicalDeviceSurfaceFormatsKHR(phy, surface, &tmp, this->surface_formats.begin());
vkGetPhysicalDeviceSurfacePresentModesKHR(phy, surface, &tmp, VK_NULL_HANDLE);
this->present_modes.resize(tmp);
vkGetPhysicalDeviceSurfacePresentModesKHR(phy, surface, &tmp, this->present_modes.begin());
BTRACE("Created %s (%p) with present modes at %p, and surface formats at %p\n", typeid(SwapchainSupportDetails).name(), this, this->present_modes.m_pdata, this->surface_formats.m_pdata);
}
basalt::SwapchainSupportDetails::~SwapchainSupportDetails()
{
BTRACE("Destroyed %s (%p) with present modes at %p, and surface formats at %p\n", typeid(SwapchainSupportDetails).name(), this, this->present_modes.m_pdata, this->surface_formats.m_pdata);
}
VkSurfaceFormatKHR basalt::SwapchainSupportDetails::get_surface_format(const std::initializer_list<VkSurfaceFormatKHR>& allowed_formats)
{
if (this->cached_format.colorSpace != VK_COLOR_SPACE_MAX_ENUM_KHR && this->cached_format.format != VK_FORMAT_MAX_ENUM)
return this->cached_format;
for (const VkSurfaceFormatKHR& prefered_fmt : allowed_formats)
{
for (const auto& available_fmt : surface_formats)
{
if (available_fmt.colorSpace == prefered_fmt.colorSpace && available_fmt.format == prefered_fmt.format)
{
this->cached_format = prefered_fmt;
return prefered_fmt;
}
}
}
this->cached_format = surface_formats[0];
return surface_formats[0];
}
VkPresentModeKHR basalt::SwapchainSupportDetails::get_present_mode(const std::initializer_list<VkPresentModeKHR>& allowed_present_modes)
{
if (this->cached_present_mode != VK_PRESENT_MODE_MAX_ENUM_KHR)
return this->cached_present_mode;
for (const VkPresentModeKHR& mode : present_modes)
{
for (const VkPresentModeKHR& pref_mode : allowed_present_modes)
{
if (mode == pref_mode)
{
this->cached_present_mode = pref_mode;
return pref_mode;
}
}
}
this->cached_present_mode = VK_PRESENT_MODE_FIFO_KHR;
return VK_PRESENT_MODE_FIFO_KHR;
}
VkExtent2D basalt::SwapchainSupportDetails::get_framebuffer_extent(basalt::Window& window)
{
if (this->surface_capabilities.currentExtent.width != -1)
return this->surface_capabilities.currentExtent;
i32 width, height;
glfwGetFramebufferSize(window, &width, &height);
VkExtent2D ext = {
.width = (u32)width,
.height = (u32)height
};
BCLAMP_EXTENT(ext, this->surface_capabilities.minImageExtent, this->surface_capabilities.maxImageExtent);
return ext;
}