28#include <unordered_map>
36VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE
42std::optional<uint32_t>
43find_compute_queue_family_index(
const vk::raii::PhysicalDevice& dev) {
44 const auto queue_families = dev.getQueueFamilyProperties();
45 for (
size_t i = 0; i < queue_families.size(); ++i) {
46 if (queue_families[i].queueFlags & vk::QueueFlagBits::eCompute) {
47 return static_cast<uint32_t
>(i);
53std::string format_physical_devices(
const vk::raii::PhysicalDevices& devices) {
54 std::string result =
"Available Physical Devices:\n";
55 for (
size_t idx = 0; idx < devices.size(); ++idx) {
56 const auto props = devices[idx].getProperties();
58 std::format(
" [{}] {}\n", idx, std::string(props.deviceName));
68 static bool initialized = []() {
return volkInitialize() == VK_SUCCESS; }();
70 throw std::runtime_error(
"Failed to initialize volk");
76 using ContextPtr = std::unique_ptr<VulkanContext, NoDestroy>;
78 static std::mutex context_mutex;
79 static std::unordered_map<int, ContextPtr> contexts;
86 std::lock_guard<std::mutex> lock(context_mutex);
87 auto it = contexts.find(key);
88 if (it != contexts.end()) {
92 auto created = std::make_unique<VulkanContext>(key);
96 contexts.emplace(key, ContextPtr(created.release()));
101 : device_id(device_id), instance(nullptr) {
105 volkLoadInstance(*instance);
107 pickPhysicalDevice();
118void VulkanContext::createInstance() {
119 vk::ApplicationInfo app_info(
"Vapoursynth-llvmexpr", 1,
"No Engine", 1,
122 std::vector<const char*> layers;
125 std::vector<vk::LayerProperties> available_layers =
126 context.enumerateInstanceLayerProperties();
127 auto has_layer = [&](
const char* name) {
128 return std::ranges::any_of(available_layers, [&](
const auto& layer) {
129 return strcmp(layer.layerName, name) == 0;
132 if (has_layer(
"VK_LAYER_KHRONOS_validation")) {
133 layers.push_back(
"VK_LAYER_KHRONOS_validation");
137 std::vector<const char*> extensions;
138 vk::InstanceCreateFlags flags;
141 std::vector<vk::ExtensionProperties> instance_extensions =
142 context.enumerateInstanceExtensionProperties();
144 auto has_extension = [&](
const char* name) {
145 return std::ranges::any_of(instance_extensions, [&](
const auto& ext) {
146 return strcmp(ext.extensionName, name) == 0;
150 if (has_extension(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME)) {
151 extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
152 flags |= vk::InstanceCreateFlagBits::eEnumeratePortabilityKHR;
154 if (has_extension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME)) {
155 extensions.push_back(
156 VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
159 vk::InstanceCreateInfo create_info(flags, &app_info, layers, extensions);
162 instance = vk::raii::Instance(context, create_info);
163 }
catch (
const std::exception& e) {
164 throw std::runtime_error(
165 std::string(
"Failed to create Vulkan instance: ") + e.what());
169void VulkanContext::pickPhysicalDevice() {
170 vk::raii::PhysicalDevices devices(instance);
171 if (devices.empty()) {
172 throw std::runtime_error(
"Failed to find GPUs with Vulkan support!");
176 std::cerr << format_physical_devices(devices);
179 auto select_device = [&](
const vk::raii::PhysicalDevice& dev) ->
bool {
180 const auto compute_queue = find_compute_queue_family_index(dev);
181 if (!compute_queue.has_value()) {
184 physical_device = dev;
185 queue_family_index = *compute_queue;
187 const auto props = dev.getProperties();
188 std::cout <<
"Selected Device: " << props.deviceName <<
'\n';
193 if (device_id >= 0) {
194 if (
static_cast<size_t>(device_id) >= devices.size()) {
195 throw std::runtime_error(
196 std::format(
"Invalid device_id: {}\n{}", device_id,
197 format_physical_devices(devices)));
200 const auto& dev = devices[
static_cast<size_t>(device_id)];
201 if (!select_device(dev)) {
202 throw std::runtime_error(
203 std::format(
"Selected device_id {} has no compute queue\n{}",
204 device_id, format_physical_devices(devices)));
209 if (std::ranges::any_of(devices, select_device)) {
213 throw std::runtime_error(std::format(
214 "Failed to find a suitable GPU with Compute capabilities!\n{}",
215 format_physical_devices(devices)));
218void VulkanContext::createDevice() {
219 float queue_priority = 1.0F;
220 vk::DeviceQueueCreateInfo queue_create_info({}, queue_family_index, 1,
223 std::vector<const char*> device_extensions;
225 std::vector<vk::ExtensionProperties> available_extensions =
226 physical_device.enumerateDeviceExtensionProperties();
227 const bool has_portability_subset = std::ranges::any_of(
228 available_extensions, [](
const vk::ExtensionProperties& ext) {
229 return strcmp(ext.extensionName,
"VK_KHR_portability_subset") == 0;
231 if (has_portability_subset) {
232 device_extensions.push_back(
"VK_KHR_portability_subset");
235 vk::DeviceCreateInfo create_info(
236 {}, 1, &queue_create_info,
238 static_cast<uint32_t
>(device_extensions.size()),
239 device_extensions.empty() ?
nullptr : device_extensions.data());
241 device = vk::raii::Device(physical_device, create_info);
244 volkLoadDevice(*device);
246 compute_queue = device.getQueue(queue_family_index, 0);
250 const vk::Fence& fence) {
251 std::lock_guard<std::mutex> lock(queue_mutex);
252 compute_queue.submit(submit_info, fence);
259 std::lock_guard<std::mutex> lock(queue_mutex);
static VulkanContext & getInstance()
void submit(const vk::SubmitInfo &submit_info, const vk::Fence &fence)
VulkanContext(int device_id=-1)