Loading...
Searching...
No Matches
VulkanVideoDevice.hpp
1#pragma once
2#include <score/gfx/Vulkan.hpp>
3
4#if QT_HAS_VULKAN && QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
5
6#include <QtGui/private/qrhivulkan_p.h>
7#include <qvulkanfunctions.h>
8#include <vulkan/vulkan.h>
9
10#if __has_include(<vulkan/vulkan_win32.h>)
11#include <vulkan/vulkan.h>
12#ifdef Q_OS_WIN
13#include <vulkan/vulkan_win32.h>
14#endif
15#endif
16
17#include <QCoreApplication>
18#include <QDebug>
19
20#include <chrono>
21#include <cstring>
22#include <mutex>
23#include <string>
24#include <vector>
25
26namespace score::gfx
27{
29inline std::vector<const char*> qtQuickVulkanDeviceExtensions()
30{
31 return {
32 VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME,
33#if defined(_WIN32) && defined(VK_KHR_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME)
34 VK_KHR_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME,
35#endif
36 VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME,
37 VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME,
38 };
39}
40
42inline PFN_vkVoidFunction vulkanDeviceCommand(QRhi& rhi, const char* name)
43{
44 if(rhi.backend() != QRhi::Vulkan)
45 return nullptr;
46 auto* nh = static_cast<const QRhiVulkanNativeHandles*>(rhi.nativeHandles());
47 if(!nh || !nh->inst || !nh->dev)
48 return nullptr;
49 return nh->inst->functions()->vkGetDeviceProcAddr(nh->dev, name);
50}
51}
52
53#ifdef VK_KHR_video_decode_queue
54
55namespace score::gfx
56{
57
58inline double sharedVulkanDeviceTraceClockMs()
59{
60 return std::chrono::duration<double, std::milli>(
61 std::chrono::steady_clock::now().time_since_epoch())
62 .count();
63}
64
68inline bool sharedVulkanDeviceCacheDisabled()
69{
70 static const bool off
71 = qEnvironmentVariableIsSet("SCORE_GFX_NO_VKDEVICE_CACHE");
72 return off;
73}
74
76inline void destroySharedVulkanDevice(QVulkanInstance* inst, VkDevice dev)
77{
78 if(!inst || dev == VK_NULL_HANDLE)
79 return;
80 auto fn = reinterpret_cast<PFN_vkDestroyDevice>(
81 inst->getInstanceProcAddr("vkDestroyDevice"));
82 if(!fn)
83 return;
84
85 const double t0 = sharedVulkanDeviceTraceClockMs();
86 fn(dev, nullptr);
87}
88
97struct SharedVulkanDevice
98{
99 VkPhysicalDevice physDev{VK_NULL_HANDLE};
100 VkDevice dev{VK_NULL_HANDLE};
101 VkQueue gfxQueue{VK_NULL_HANDLE};
102 uint32_t gfxQueueFamilyIdx{0};
103 bool hasVideoDecodeQueue{false};
108 bool timelineSemaphores{false};
109 uint32_t videoDecodeQueueFamilyIdx{0};
110
111 // Persistent storage for extension name strings (FFmpeg needs const char*)
112 std::vector<std::string> enabledExtensions;
113
114 // Queue family info for FFmpeg's AVVulkanDeviceContext
115 struct QueueFamilyInfo
116 {
117 uint32_t idx;
118 uint32_t count;
119 VkQueueFlags flags;
120 };
121 std::vector<QueueFamilyInfo> queueFamilies;
122
123 void destroy() { destroy(staticVulkanInstance(false)); }
124
125 void destroy(QVulkanInstance* inst)
126 {
127 destroySharedVulkanDevice(inst, dev);
128 dev = VK_NULL_HANDLE;
129 }
130
131 explicit operator bool() const { return dev != VK_NULL_HANDLE; }
132};
133
141inline std::vector<const char*> sharedVulkanDeviceExtensions()
142{
143 auto exts = qtQuickVulkanDeviceExtensions();
144 exts.insert(exts.end(), {
145 // Qt QRhi requirements
146 VK_KHR_SWAPCHAIN_EXTENSION_NAME,
147#ifdef VK_KHR_MAINTENANCE_1_EXTENSION_NAME
148 VK_KHR_MAINTENANCE_1_EXTENSION_NAME,
149#endif
150#ifdef VK_KHR_MAINTENANCE_2_EXTENSION_NAME
151 VK_KHR_MAINTENANCE_2_EXTENSION_NAME,
152#endif
153#ifdef VK_KHR_MAINTENANCE_3_EXTENSION_NAME
154 VK_KHR_MAINTENANCE_3_EXTENSION_NAME,
155#endif
156#ifdef VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME
157 VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME,
158#endif
159#ifdef VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME
160 VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME,
161#endif
162#ifdef VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME
163 VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME,
164#endif
165#ifdef VK_KHR_MULTIVIEW_EXTENSION_NAME
166 VK_KHR_MULTIVIEW_EXTENSION_NAME,
167#endif
168#ifdef VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME
169 VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME,
170#endif
171#ifdef VK_KHR_SPIRV_1_4_EXTENSION_NAME
172 VK_KHR_SPIRV_1_4_EXTENSION_NAME,
173#endif
174#ifdef VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME
175 VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME,
176#endif
177 // External memory (DMA-BUF, CUDA interop)
178 VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME,
179#ifdef VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME
180 VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME,
181#endif
182#ifdef VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME
183 VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME,
184#endif
185#ifdef VK_KHR_EXTERNAL_SEMAPHORE_WIN32_EXTENSION_NAME
186 VK_KHR_EXTERNAL_SEMAPHORE_WIN32_EXTENSION_NAME,
187#endif
188#ifdef VK_EXT_EXTERNAL_MEMORY_DMA_BUF_EXTENSION_NAME
189 VK_EXT_EXTERNAL_MEMORY_DMA_BUF_EXTENSION_NAME,
190#endif
191#ifdef VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME
192 VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME,
193#endif
194#ifdef VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME
195 VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME,
196#endif
197 // Vulkan Video decode
198 VK_KHR_VIDEO_QUEUE_EXTENSION_NAME,
199 VK_KHR_VIDEO_DECODE_QUEUE_EXTENSION_NAME,
200#ifdef VK_KHR_VIDEO_DECODE_H264_EXTENSION_NAME
201 VK_KHR_VIDEO_DECODE_H264_EXTENSION_NAME,
202#endif
203#ifdef VK_KHR_VIDEO_DECODE_H265_EXTENSION_NAME
204 VK_KHR_VIDEO_DECODE_H265_EXTENSION_NAME,
205#endif
206#ifdef VK_KHR_VIDEO_DECODE_AV1_EXTENSION_NAME
207 VK_KHR_VIDEO_DECODE_AV1_EXTENSION_NAME,
208#endif
209 // Synchronization
210 VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME,
211 VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME,
212 // YCbCr
213#ifdef VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME
214 VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME,
215#endif
216#ifdef VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME
217 VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME,
218#endif
219#ifdef VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME
220 VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME,
221#endif
222#ifdef VK_KHR_FORMAT_FEATURE_FLAGS_2_EXTENSION_NAME
223 VK_KHR_FORMAT_FEATURE_FLAGS_2_EXTENSION_NAME,
224#endif
225#ifdef VK_KHR_COPY_COMMANDS_2_EXTENSION_NAME
226 VK_KHR_COPY_COMMANDS_2_EXTENSION_NAME,
227#endif
228#ifdef VK_EXT_HOST_QUERY_RESET_EXTENSION_NAME
229 VK_EXT_HOST_QUERY_RESET_EXTENSION_NAME,
230#endif
231#ifdef VK_KHR_VIDEO_MAINTENANCE_1_EXTENSION_NAME
232 VK_KHR_VIDEO_MAINTENANCE_1_EXTENSION_NAME,
233#endif
234#ifdef VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME
235 VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME,
236#endif
237#ifdef VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME
238 VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME,
239#endif
240 });
241 return exts;
242}
243
252inline VkPhysicalDevice resolveSharedVulkanPhysicalDevice(
253 QVulkanInstance* inst, VkPhysicalDevice preferredPhysDev = VK_NULL_HANDLE)
254{
255 if(!inst)
256 return VK_NULL_HANDLE;
257 if(preferredPhysDev != VK_NULL_HANDLE)
258 return preferredPhysDev;
259
260 auto* funcs = inst->functions();
261 if(!funcs)
262 return VK_NULL_HANDLE;
263
264 uint32_t devCount = 0;
265 funcs->vkEnumeratePhysicalDevices(inst->vkInstance(), &devCount, nullptr);
266 if(devCount == 0)
267 return VK_NULL_HANDLE;
268
269 std::vector<VkPhysicalDevice> physDevs(devCount);
270 funcs->vkEnumeratePhysicalDevices(inst->vkInstance(), &devCount, physDevs.data());
271
272 bool ok = false;
273 const int idx = qEnvironmentVariableIntValue("QT_VK_PHYSICAL_DEVICE_INDEX", &ok);
274 if(ok && idx >= 0 && uint32_t(idx) < devCount)
275 return physDevs[uint32_t(idx)];
276 return physDevs[0];
277}
278
292inline SharedVulkanDevice createSharedVulkanDevice(
293 QVulkanInstance* inst, VkPhysicalDevice preferredPhysDev = VK_NULL_HANDLE)
294{
295 SharedVulkanDevice result;
296 if(!inst)
297 return result;
298
299 auto* funcs = inst->functions();
300 if(!funcs)
301 return result;
302
303 // --- Pick physical device (prefer discrete GPU) ---
304
305 // Use the caller-specified physical device (matching QRhi's GPU),
306 // else honour QT_VK_PHYSICAL_DEVICE_INDEX (the same env QRhi's own
307 // device selection respects — critical on multi-GPU boxes where CUDA
308 // interop pins the workload to one specific GPU), else the first one.
309 result.physDev = resolveSharedVulkanPhysicalDevice(inst, preferredPhysDev);
310 if(result.physDev == VK_NULL_HANDLE)
311 return result;
312
313 uint32_t qfCount = 0;
314 funcs->vkGetPhysicalDeviceQueueFamilyProperties(
315 result.physDev, &qfCount, nullptr);
316 if(qfCount == 0)
317 return result;
318
319 std::vector<VkQueueFamilyProperties> qfProps(qfCount);
320 funcs->vkGetPhysicalDeviceQueueFamilyProperties(
321 result.physDev, &qfCount, qfProps.data());
322
323 bool foundGfx = false;
324 for(uint32_t i = 0; i < qfCount; i++)
325 {
326 if(!foundGfx && (qfProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT))
327 {
328 result.gfxQueueFamilyIdx = i;
329 foundGfx = true;
330 }
331 if(qfProps[i].queueFlags & VK_QUEUE_VIDEO_DECODE_BIT_KHR)
332 {
333 result.hasVideoDecodeQueue = true;
334 result.videoDecodeQueueFamilyIdx = i;
335 }
336 }
337
338 if(!foundGfx || !result.hasVideoDecodeQueue)
339 return {};
340
341 // --- Enumerate available device extensions ---
342
343 uint32_t extCount = 0;
344 funcs->vkEnumerateDeviceExtensionProperties(
345 result.physDev, nullptr, &extCount, nullptr);
346 std::vector<VkExtensionProperties> avail(extCount);
347 funcs->vkEnumerateDeviceExtensionProperties(
348 result.physDev, nullptr, &extCount, avail.data());
349
350 auto hasExt = [&](const char* name) {
351 for(auto& e : avail)
352 if(std::strcmp(e.extensionName, name) == 0)
353 return true;
354 return false;
355 };
356
357 // Only enable extensions that are actually available from the curated list.
358 // Use string literal pointers directly — DO NOT copy into std::string
359 // then take c_str(), as vector<string> reallocation invalidates all
360 // previous c_str() pointers (caused vkCreateDevice to get garbage names).
361 auto wantedExtensions = sharedVulkanDeviceExtensions();
362 std::vector<const char*> extPtrs;
363 for(auto* ext : wantedExtensions)
364 {
365 if(hasExt(ext))
366 {
367 result.enabledExtensions.push_back(ext);
368 extPtrs.push_back(ext); // ext is a string literal, always valid
369 }
370 }
371
372 // --- Query and enable ALL supported features ---
373
374 auto vkGetPhysicalDeviceFeatures2Fn
375 = reinterpret_cast<PFN_vkGetPhysicalDeviceFeatures2>(
376 inst->getInstanceProcAddr("vkGetPhysicalDeviceFeatures2"));
377 if(!vkGetPhysicalDeviceFeatures2Fn)
378 return {};
379
380 // Build feature chain: Vulkan 1.1 → 1.2 → 1.3
381 VkPhysicalDeviceVulkan13Features vk13{};
382 vk13.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES;
383
384 VkPhysicalDeviceVulkan12Features vk12{};
385 vk12.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES;
386 vk12.pNext = &vk13;
387
388 VkPhysicalDeviceVulkan11Features vk11{};
389 vk11.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES;
390 vk11.pNext = &vk12;
391
392 VkPhysicalDeviceFeatures2 features2{};
393 features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
394 features2.pNext = &vk11;
395
396 // Query fills all fields with what the device supports
397 vkGetPhysicalDeviceFeatures2Fn(result.physDev, &features2);
398 result.timelineSemaphores = vk12.timelineSemaphore == VK_TRUE;
399
400 // Do not enable the robustness features: they force bounds-checked GPU
401 // memory access on every operation, a documented slow path on several
402 // drivers, and nothing in score or the interop paths relies on them.
403 features2.features.robustBufferAccess = VK_FALSE;
404 vk13.robustImageAccess = VK_FALSE;
405
406 // --- Create queue infos (1 queue per family) ---
407
408 std::vector<VkDeviceQueueCreateInfo> queueInfos;
409 float priority = 1.0f;
410 for(uint32_t i = 0; i < qfCount; i++)
411 {
412 VkDeviceQueueCreateInfo qi{};
413 qi.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
414 qi.queueFamilyIndex = i;
415 qi.queueCount = 1;
416 qi.pQueuePriorities = &priority;
417 queueInfos.push_back(qi);
418
419 result.queueFamilies.push_back(
420 {i, 1, qfProps[i].queueFlags});
421 }
422
423 // --- Create VkDevice ---
424
425 VkDeviceCreateInfo devInfo{};
426 devInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
427 devInfo.pNext = &features2; // Features via pNext, not pEnabledFeatures
428 devInfo.queueCreateInfoCount = static_cast<uint32_t>(queueInfos.size());
429 devInfo.pQueueCreateInfos = queueInfos.data();
430 devInfo.enabledExtensionCount = static_cast<uint32_t>(extPtrs.size());
431 devInfo.ppEnabledExtensionNames = extPtrs.data();
432
433 auto vkCreateDeviceFn = reinterpret_cast<PFN_vkCreateDevice>(
434 inst->getInstanceProcAddr("vkCreateDevice"));
435 if(!vkCreateDeviceFn)
436 return {};
437
438 const double t0 = sharedVulkanDeviceTraceClockMs();
439 VkResult vkResult
440 = vkCreateDeviceFn(result.physDev, &devInfo, nullptr, &result.dev);
441 if(vkResult != VK_SUCCESS)
442 {
443 qDebug() << "createSharedVulkanDevice: vkCreateDevice failed:" << vkResult;
444 result.dev = VK_NULL_HANDLE;
445 return {};
446 }
447
448 // --- Get graphics queue ---
449
450 auto vkGetDeviceQueueFn = reinterpret_cast<PFN_vkGetDeviceQueue>(
451 inst->getInstanceProcAddr("vkGetDeviceQueue"));
452 if(vkGetDeviceQueueFn)
453 vkGetDeviceQueueFn(
454 result.dev, result.gfxQueueFamilyIdx, 0, &result.gfxQueue);
455
456 return result;
457}
458
476class SharedVulkanDeviceCache;
477inline SharedVulkanDeviceCache& sharedVulkanDeviceCache();
478
479class SharedVulkanDeviceCache
480{
481public:
486 SharedVulkanDevice
487 acquire(QVulkanInstance* inst, VkPhysicalDevice preferredPhysDev = VK_NULL_HANDLE)
488 {
489 if(!inst)
490 return {};
491
492 const VkPhysicalDevice physDev
493 = resolveSharedVulkanPhysicalDevice(inst, preferredPhysDev);
494 if(physDev == VK_NULL_HANDLE)
495 return {};
496
497 std::lock_guard lock{m_mutex};
498 for(auto& e : m_entries)
499 {
500 if(e.inst == inst && e.device.physDev == physDev)
501 {
502 e.refcount++;
503 return e.device;
504 }
505 }
506
507 SharedVulkanDevice dev = createSharedVulkanDevice(inst, physDev);
508 if(!dev)
509 {
510 return {};
511 }
512
513 m_created++;
514 m_entries.push_back(Entry{.inst = inst, .device = dev, .refcount = 1});
515 // Per creation rather than once per process: Qt runs each post routine at
516 // most once, and a process that boots a second QCoreApplication (the tests
517 // do) would otherwise leave its second device undestroyed.
518 qAddPostRoutine([] { sharedVulkanDeviceCache().shutdown(); });
519 return dev;
520 }
521
523 void release(VkDevice dev)
524 {
525 if(dev == VK_NULL_HANDLE)
526 return;
527 std::lock_guard lock{m_mutex};
528 for(auto& e : m_entries)
529 {
530 if(e.device.dev == dev)
531 {
532 if(e.refcount > 0)
533 e.refcount--;
534 return;
535 }
536 }
537 }
538
542 void shutdown()
543 {
544 std::lock_guard lock{m_mutex};
545 std::vector<Entry> stillUsed;
546 for(auto& e : m_entries)
547 {
548 if(e.refcount != 0)
549 {
550 // Destroying under a live QRhi would be a use-after-free. Keep the
551 // entry so a later acquire hands back the device its holders are
552 // already using rather than making a second one.
553 qWarning() << "SharedVulkanDeviceCache: device still referenced at "
554 "shutdown, refcount ="
555 << e.refcount;
556 stillUsed.push_back(e);
557 continue;
558 }
559 e.device.destroy(e.inst);
560 }
561 m_entries = std::move(stillUsed);
562 m_created = int(m_entries.size());
563 }
564
569 int createdDeviceCount() const
570 {
571 std::lock_guard lock{m_mutex};
572 return m_created;
573 }
574
575private:
576 struct Entry
577 {
578 QVulkanInstance* inst{};
579 SharedVulkanDevice device;
580 int refcount{};
581 };
582
583 mutable std::mutex m_mutex;
584 std::vector<Entry> m_entries;
585 int m_created{};
586};
587
588inline SharedVulkanDeviceCache& sharedVulkanDeviceCache()
589{
590 static SharedVulkanDeviceCache cache;
591 return cache;
592}
593
594} // namespace score::gfx
595
596#endif // VK_KHR_video_decode_queue
597#endif // QT_HAS_VULKAN
Graphics rendering pipeline for ossia score.
Definition Filter/PreviewWidget.hpp:11
STL namespace.