diff --git a/include/vulkan/basalt_physical_device.h b/include/vulkan/basalt_physical_device.h new file mode 100644 index 0000000..064131e --- /dev/null +++ b/include/vulkan/basalt_physical_device.h @@ -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& allowed_formats); + VkPresentModeKHR get_present_mode(const std::initializer_list& allowed_present_modes); + VkExtent2D get_framebuffer_extent(basalt::Window& window); + + VkSurfaceCapabilitiesKHR surface_capabilities; + basalt::darray surface_formats; + basalt::darray 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&& enabled_extensions, VkSurfaceKHR surface); + + operator VkPhysicalDevice() noexcept; + + basalt::darray 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& 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 device_rankings; + basalt::darray devices; + basalt::darray props; + basalt::darray features; + basalt::darray support_details; + basalt::darray enabled_extensions; + basalt::Context& ctx; + VkSurfaceKHR surface; + bool using_swapchain = false; + }; +} \ No newline at end of file diff --git a/src/vulkan/basalt_physical_device.cpp b/src/vulkan/basalt_physical_device.cpp new file mode 100644 index 0000000..843f653 --- /dev/null +++ b/src/vulkan/basalt_physical_device.cpp @@ -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 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 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 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& extensions, const float pv, const float nv) +{ + u32 num_supported_extensions = 0; + darray 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 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&& 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& 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& 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; +} +