VapourSynth-llvmexpr
Loading...
Searching...
No Matches
VulkanContext.cpp
Go to the documentation of this file.
1
19
20#include "VulkanContext.hpp"
21#include <algorithm>
22#include <cstring>
23#include <format>
24#include <memory>
25#include <mutex>
26#include <optional>
27#include <string>
28#include <unordered_map>
29#ifndef NDEBUG
30#include <iostream>
31#endif
32#include <vector>
33#include <volk.h>
34
35// NOLINTNEXTLINE
36VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE
37
38namespace vkexpr {
39
40namespace {
41
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);
48 }
49 }
50 return std::nullopt;
51}
52
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();
57 result +=
58 std::format(" [{}] {}\n", idx, std::string(props.deviceName));
59 }
60 return result;
61}
62
63} // namespace
64
66
68 static bool initialized = []() { return volkInitialize() == VK_SUCCESS; }();
69 if (!initialized) {
70 throw std::runtime_error("Failed to initialize volk");
71 }
72
73 struct NoDestroy {
74 void operator()(VulkanContext* /*ptr*/) const {}
75 };
76 using ContextPtr = std::unique_ptr<VulkanContext, NoDestroy>;
77
78 static std::mutex context_mutex;
79 static std::unordered_map<int, ContextPtr> contexts;
80
81 int key = device_id;
82 if (key < 0) {
83 key = -1;
84 }
85
86 std::lock_guard<std::mutex> lock(context_mutex);
87 auto it = contexts.find(key);
88 if (it != contexts.end()) {
89 return *it->second;
90 }
91
92 auto created = std::make_unique<VulkanContext>(key);
93 VulkanContext& ref = *created;
94
95 // Leaky Singleton
96 contexts.emplace(key, ContextPtr(created.release()));
97 return ref;
98}
99
101 : device_id(device_id), instance(nullptr) {
102
103 createInstance();
104 // Re-load volk with the instance
105 volkLoadInstance(*instance);
106
107 pickPhysicalDevice();
108 createDevice();
109}
110
112 try {
113 waitIdle();
114 } catch (...) {
115 }
116}
117
118void VulkanContext::createInstance() {
119 vk::ApplicationInfo app_info("Vapoursynth-llvmexpr", 1, "No Engine", 1,
120 VK_API_VERSION_1_3);
121
122 std::vector<const char*> layers;
123// Enable validation layers in debug if needed
124#ifndef NDEBUG
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;
130 });
131 };
132 if (has_layer("VK_LAYER_KHRONOS_validation")) {
133 layers.push_back("VK_LAYER_KHRONOS_validation");
134 }
135#endif
136
137 std::vector<const char*> extensions;
138 vk::InstanceCreateFlags flags;
139
140 // Check available instance extensions
141 std::vector<vk::ExtensionProperties> instance_extensions =
142 context.enumerateInstanceExtensionProperties();
143
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;
147 });
148 };
149
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;
153 }
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);
157 }
158
159 vk::InstanceCreateInfo create_info(flags, &app_info, layers, extensions);
160
161 try {
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());
166 }
167}
168
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!");
173 }
174
175#ifndef NDEBUG
176 std::cerr << format_physical_devices(devices);
177#endif
178
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()) {
182 return false;
183 }
184 physical_device = dev;
185 queue_family_index = *compute_queue;
186#ifndef NDEBUG
187 const auto props = dev.getProperties();
188 std::cout << "Selected Device: " << props.deviceName << '\n';
189#endif
190 return true;
191 };
192
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)));
198 }
199
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)));
205 }
206 return;
207 }
208
209 if (std::ranges::any_of(devices, select_device)) {
210 return;
211 }
212
213 throw std::runtime_error(std::format(
214 "Failed to find a suitable GPU with Compute capabilities!\n{}",
215 format_physical_devices(devices)));
216}
217
218void VulkanContext::createDevice() {
219 float queue_priority = 1.0F;
220 vk::DeviceQueueCreateInfo queue_create_info({}, queue_family_index, 1,
221 &queue_priority);
222
223 std::vector<const char*> device_extensions;
224
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;
230 });
231 if (has_portability_subset) {
232 device_extensions.push_back("VK_KHR_portability_subset");
233 }
234
235 vk::DeviceCreateInfo create_info(
236 {}, 1, &queue_create_info,
237 0, nullptr, // layers deprecated
238 static_cast<uint32_t>(device_extensions.size()),
239 device_extensions.empty() ? nullptr : device_extensions.data());
240
241 device = vk::raii::Device(physical_device, create_info);
242
243 // Load device specific pointers
244 volkLoadDevice(*device);
245
246 compute_queue = device.getQueue(queue_family_index, 0);
247}
248
249void VulkanContext::submit(const vk::SubmitInfo& submit_info,
250 const vk::Fence& fence) {
251 std::lock_guard<std::mutex> lock(queue_mutex);
252 compute_queue.submit(submit_info, fence);
253}
254
256 if (!*device) {
257 return;
258 }
259 std::lock_guard<std::mutex> lock(queue_mutex);
260 device.waitIdle();
261}
262
263} // namespace vkexpr
static VulkanContext & getInstance()
void submit(const vk::SubmitInfo &submit_info, const vk::Fence &fence)
VulkanContext(int device_id=-1)