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