17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-26 22:54:54 +01:00
Files
maszyna/rendering/vulkan14/vulkan14device.cpp

335 lines
9.7 KiB
C++

/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14device.h"
#include "rendering/vulkan14/vulkan14instance.h"
#include <cstring>
namespace vk14
{
namespace
{
// "1" -> 1; anything which isn't a plain number -> -1
int index_of(std::string const &Text)
{
if (Text.empty() || (Text.size() > 3) || (false == std::all_of(Text.begin(), Text.end(), [](char const Char) { return std::isdigit(static_cast<unsigned char>(Char)) != 0; })))
{
return -1;
}
return std::stoi(Text);
}
std::string lowercase(std::string Text)
{
std::transform(Text.begin(), Text.end(), Text.begin(), [](unsigned char const Char) { return static_cast<char>(std::tolower(Char)); });
return Text;
}
} // namespace
device::~device()
{
destroy();
}
queue_families device::find_queue_families(VkInstance Instance, VkPhysicalDevice Physical, VkSurfaceKHR Surface, std::uint32_t &TimestampBits)
{
queue_families families;
TimestampBits = 0;
std::uint32_t count{0};
vkGetPhysicalDeviceQueueFamilyProperties(Physical, &count, nullptr);
std::vector<VkQueueFamilyProperties> properties(count);
vkGetPhysicalDeviceQueueFamilyProperties(Physical, &count, properties.data());
for (std::uint32_t i = 0; i < count; ++i)
{
if ((properties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) == 0)
{
continue;
}
VkBool32 present{VK_FALSE};
if (Surface != VK_NULL_HANDLE)
{
if (!VK14_VERIFY(vkGetPhysicalDeviceSurfaceSupportKHR(Physical, i, Surface, &present)))
{
present = VK_FALSE;
}
}
else
{
present = (glfwGetPhysicalDevicePresentationSupport(Instance, Physical, i) == GLFW_TRUE ? VK_TRUE : VK_FALSE);
}
if (present == VK_TRUE)
{
families.graphics = i;
TimestampBits = properties[i].timestampValidBits;
break;
}
}
if (families.graphics == queue_families::invalid)
{
return families;
}
families.compute = families.graphics;
families.transfer = families.graphics;
// a compute family without graphics can work alongside the frame
for (std::uint32_t i = 0; i < count; ++i)
{
auto const flags{properties[i].queueFlags};
if (((flags & VK_QUEUE_COMPUTE_BIT) != 0) && ((flags & VK_QUEUE_GRAPHICS_BIT) == 0))
{
families.compute = i;
families.dedicated_compute = true;
break;
}
}
// a family of transfers only is usually the copy engine
for (std::uint32_t i = 0; i < count; ++i)
{
auto const flags{properties[i].queueFlags};
if (((flags & VK_QUEUE_TRANSFER_BIT) != 0) && ((flags & (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT)) == 0))
{
families.transfer = i;
families.dedicated_transfer = true;
break;
}
}
return families;
}
std::uint64_t device::score(capabilities const &Caps)
{
std::uint64_t score{0};
switch (Caps.device_type)
{
case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU:
score = 3;
break;
case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU:
score = 2;
break;
case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU:
score = 1;
break;
default:
break;
}
// then the memory, in mebibytes
return (score << 40) + (Caps.device_local_memory >> 20);
}
bool device::select(VkInstance Instance, VkSurfaceKHR Surface, std::string const &Wanted, bool const Verbose, std::string &Reason)
{
m_physical = VK_NULL_HANDLE;
std::uint32_t count{0};
if (!VK14_VERIFY(vkEnumeratePhysicalDevices(Instance, &count, nullptr)) || (count == 0))
{
Reason = "no vulkan device";
return false;
}
std::vector<VkPhysicalDevice> devices(count);
if (!VK14_VERIFY(vkEnumeratePhysicalDevices(Instance, &count, devices.data())))
{
Reason = "the vulkan devices can't be listed";
return false;
}
auto const wantedindex{index_of(Wanted)};
auto const wantedname{lowercase(Wanted)};
if (Verbose)
{
log_info(std::to_string(count) + " device(s)");
}
VkPhysicalDevice best{VK_NULL_HANDLE};
VkPhysicalDevice chosen{VK_NULL_HANDLE};
std::uint64_t bestscore{0};
bool wantedfound{false};
std::string firstmissing;
for (std::uint32_t i = 0; i < count; ++i)
{
feature_chain queried;
capabilities caps;
query_capabilities(devices[i], queried, caps);
std::uint32_t timestampbits{0};
auto const families{find_queue_families(Instance, devices[i], Surface, timestampbits)};
if (families.graphics == queue_families::invalid)
{
caps.missing.push_back("no queue family both draws and shows images in a window");
}
else if (timestampbits == 0)
{
caps.missing.push_back("the graphics queue has no timestamps (needed for the gpu times of the passes)");
}
auto const label{"device " + std::to_string(i) + " \"" + caps.device_name + "\" (" + device_type_string(caps.device_type) + ", vulkan " + version_string(caps.api_version) + ")"};
auto const iswanted{(false == Wanted.empty()) && ((wantedindex >= 0) ? (wantedindex == static_cast<int>(i)) : (lowercase(caps.device_name).find(wantedname) != std::string::npos))};
wantedfound |= iswanted;
if (false == caps.missing.empty())
{
if (Verbose || iswanted)
{
log_warning(label + " can't be used:");
for (auto const &reason : caps.missing)
{
log_warning(" " + reason);
}
}
if (firstmissing.empty())
{
firstmissing = caps.device_name + ": " + caps.missing.front();
}
continue;
}
if (Verbose)
{
log_info(label + " can be used");
}
if (iswanted && (chosen == VK_NULL_HANDLE))
{
chosen = devices[i];
}
auto const devicescore{score(caps)};
if ((best == VK_NULL_HANDLE) || (devicescore > bestscore))
{
best = devices[i];
bestscore = devicescore;
}
}
if (Verbose && (false == Wanted.empty()) && (chosen == VK_NULL_HANDLE))
{
log_warning("gfx.vulkan.device " + Wanted + (wantedfound ? " can't be used" : " matches no device") + ", picking the device by itself");
}
if (chosen == VK_NULL_HANDLE)
{
chosen = best;
}
if (chosen == VK_NULL_HANDLE)
{
Reason = "no gpu has what the vulkan renderer needs (" + firstmissing + ")";
return false;
}
m_physical = chosen;
query_capabilities(m_physical, m_queried, m_capabilities);
std::uint32_t timestampbits{0};
m_families = find_queue_families(Instance, m_physical, Surface, timestampbits);
return true;
}
bool device::create(instance const &Instance, std::string &Reason)
{
if (m_device != VK_NULL_HANDLE)
{
return true;
}
if (m_physical == VK_NULL_HANDLE)
{
Reason = "no gpu was picked";
return false;
}
feature_chain enabled;
build_enabled_features(m_queried, enabled);
std::vector<std::uint32_t> families{m_families.graphics};
for (auto const family : {m_families.compute, m_families.transfer})
{
if (std::find(families.begin(), families.end(), family) == families.end())
{
families.push_back(family);
}
}
float const priority{1.f};
std::vector<VkDeviceQueueCreateInfo> queues;
for (auto const family : families)
{
VkDeviceQueueCreateInfo info{};
info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
info.queueFamilyIndex = family;
info.queueCount = 1;
info.pQueuePriorities = &priority;
queues.push_back(info);
}
// everything else the renderer uses is in the core of vulkan 1.4
std::vector<char const *> extensions{VK_KHR_SWAPCHAIN_EXTENSION_NAME};
{
// a device of the portability subset (vulkan on metal) has to be told the program knows it
std::uint32_t count{0};
vkEnumerateDeviceExtensionProperties(m_physical, nullptr, &count, nullptr);
std::vector<VkExtensionProperties> available(count);
vkEnumerateDeviceExtensionProperties(m_physical, nullptr, &count, available.data());
m_portability_subset = std::any_of(available.begin(), available.end(), [](VkExtensionProperties const &Entry) { return std::strcmp(Entry.extensionName, "VK_KHR_portability_subset") == 0; });
if (m_portability_subset)
{
extensions.push_back("VK_KHR_portability_subset");
}
// the allocator keeps to the budget of the memory heaps where the driver tells it
m_memory_budget = m_capabilities.ext_memory_budget;
if (m_memory_budget)
{
extensions.push_back(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME);
}
}
VkDeviceCreateInfo create{};
create.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
create.pNext = enabled.head();
create.queueCreateInfoCount = static_cast<std::uint32_t>(queues.size());
create.pQueueCreateInfos = queues.data();
create.enabledExtensionCount = static_cast<std::uint32_t>(extensions.size());
create.ppEnabledExtensionNames = extensions.data();
auto const result{vkCreateDevice(m_physical, &create, nullptr, &m_device)};
if (result != VK_SUCCESS)
{
m_device = VK_NULL_HANDLE;
Reason = std::string("vkCreateDevice failed with ") + result_string(result);
return false;
}
// the functions of the device straight from the driver, without the dispatch of the loader
volkLoadDevice(m_device);
vkGetDeviceQueue(m_device, m_families.graphics, 0, &m_graphics_queue);
vkGetDeviceQueue(m_device, m_families.compute, 0, &m_compute_queue);
vkGetDeviceQueue(m_device, m_families.transfer, 0, &m_transfer_queue);
Instance.set_object_name(m_device, VK_OBJECT_TYPE_DEVICE, reinterpret_cast<std::uint64_t>(m_device), "eu07 device");
Instance.set_object_name(m_device, VK_OBJECT_TYPE_QUEUE, reinterpret_cast<std::uint64_t>(m_graphics_queue), "graphics queue");
log_info("using " + m_capabilities.device_name + ", queue families: graphics " + std::to_string(m_families.graphics) + ", compute " + std::to_string(m_families.compute) + (m_families.dedicated_compute ? " (own)" : " (shared)") + ", transfer " +
std::to_string(m_families.transfer) + (m_families.dedicated_transfer ? " (own)" : " (shared)"));
return true;
}
void device::destroy()
{
if (m_device != VK_NULL_HANDLE)
{
vkDestroyDevice(m_device, nullptr);
m_device = VK_NULL_HANDLE;
}
m_graphics_queue = VK_NULL_HANDLE;
m_compute_queue = VK_NULL_HANDLE;
m_transfer_queue = VK_NULL_HANDLE;
}
} // namespace vk14