3#if defined(QGC_HAS_GST_DMABUF_GPU_PATH) && defined(QGC_HAS_GST_VULKAN_GPU_PATH)
5#include <QtCore/QLoggingCategory>
6#include <QtCore/QScopeGuard>
7#include <QtGui/QVulkanInstance>
10#include <drm_fourcc.h>
12#include <gst/allocators/gstdmabuf.h>
13#include <gst/video/video.h>
17#include <rhi/qrhi_platform.h>
19#include <vulkan/vulkan.h>
33std::atomic<bool> s_loggedVulkanUnimpl{
false};
37VkFormat vkFormatForDrmFourcc(
int fourcc)
40 case DRM_FORMAT_ABGR8888:
41 case DRM_FORMAT_XBGR8888:
42 return VK_FORMAT_R8G8B8A8_UNORM;
43 case DRM_FORMAT_ARGB8888:
44 case DRM_FORMAT_XRGB8888:
45 return VK_FORMAT_B8G8R8A8_UNORM;
47 return VK_FORMAT_G8_B8R8_2PLANE_420_UNORM;
49 return VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16;
50 case DRM_FORMAT_YUV420:
51 return VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM;
53 return VK_FORMAT_UNDEFINED;
62 PFN_vkCreateImage createImage =
nullptr;
63 PFN_vkDestroyImage destroyImage =
nullptr;
64 PFN_vkAllocateMemory allocateMemory =
nullptr;
65 PFN_vkFreeMemory freeMemory =
nullptr;
66 PFN_vkBindImageMemory bindImageMemory =
nullptr;
67 PFN_vkGetImageMemoryRequirements getImageMemoryRequirements =
nullptr;
68 PFN_vkGetMemoryFdPropertiesKHR getMemoryFdProperties =
nullptr;
69 PFN_vkGetPhysicalDeviceMemoryProperties getPhysicalDeviceMemoryProperties =
nullptr;
71 bool ok() const noexcept
73 return createImage && destroyImage && allocateMemory && freeMemory && bindImageMemory &&
74 getImageMemoryRequirements && getMemoryFdProperties && getPhysicalDeviceMemoryProperties;
78const VulkanImportFns& resolveVulkanImportFns(QVulkanInstance& inst, VkDevice dev)
80 static VulkanImportFns fns;
81 static std::once_flag once;
82 std::call_once(once, [&] {
83 const auto getDeviceProcAddr =
84 reinterpret_cast<PFN_vkGetDeviceProcAddr
>(inst.getInstanceProcAddr(
"vkGetDeviceProcAddr"));
85 const auto dev_fn = [&](
const char* name) -> PFN_vkVoidFunction {
86 return getDeviceProcAddr ? getDeviceProcAddr(dev, name) : nullptr;
88 fns.createImage =
reinterpret_cast<PFN_vkCreateImage
>(dev_fn(
"vkCreateImage"));
89 fns.destroyImage =
reinterpret_cast<PFN_vkDestroyImage
>(dev_fn(
"vkDestroyImage"));
90 fns.allocateMemory =
reinterpret_cast<PFN_vkAllocateMemory
>(dev_fn(
"vkAllocateMemory"));
91 fns.freeMemory =
reinterpret_cast<PFN_vkFreeMemory
>(dev_fn(
"vkFreeMemory"));
92 fns.bindImageMemory =
reinterpret_cast<PFN_vkBindImageMemory
>(dev_fn(
"vkBindImageMemory"));
93 fns.getImageMemoryRequirements =
94 reinterpret_cast<PFN_vkGetImageMemoryRequirements
>(dev_fn(
"vkGetImageMemoryRequirements"));
95 fns.getMemoryFdProperties =
96 reinterpret_cast<PFN_vkGetMemoryFdPropertiesKHR
>(dev_fn(
"vkGetMemoryFdPropertiesKHR"));
97 fns.getPhysicalDeviceMemoryProperties =
reinterpret_cast<PFN_vkGetPhysicalDeviceMemoryProperties
>(
98 inst.getInstanceProcAddr(
"vkGetPhysicalDeviceMemoryProperties"));
103quint64 dmaMemoryBound(GstMemory* memory,
const GstVideoInfo& videoInfo)
noexcept
106 const gsize size = memory ? gst_memory_get_sizes(memory,
nullptr, &maxSize) : 0;
107 return std::max({
static_cast<quint64
>(GST_VIDEO_INFO_SIZE(&videoInfo)),
static_cast<quint64
>(size),
108 static_cast<quint64
>(maxSize)});
111bool validateVulkanPlaneLayouts(
const GstVideoInfo& videoInfo, GstVideoMeta* vmeta,
int planeCount, quint64 memoryBound,
112 VkSubresourceLayout* planeLayouts)
114 const int width = GST_VIDEO_INFO_WIDTH(&videoInfo);
115 const int height = GST_VIDEO_INFO_HEIGHT(&videoInfo);
116 if (width <= 0 || height <= 0) {
117 qCWarning(GstDmaBufVulkanLog) <<
"Vulkan import: invalid DMABuf dimensions" << width <<
"x" << height;
121 const int componentCount = GST_VIDEO_INFO_N_COMPONENTS(&videoInfo);
122 if (componentCount <= 0 || planeCount > componentCount) {
123 qCWarning(GstDmaBufVulkanLog) <<
"Vulkan import: invalid DMABuf plane count" << planeCount <<
"components"
127 if (vmeta &&
static_cast<int>(vmeta->n_planes) < planeCount) {
128 qCWarning(GstDmaBufVulkanLog) <<
"Vulkan import: GstVideoMeta has too few planes" << vmeta->n_planes
129 <<
"expected" << planeCount;
133 constexpr quint64 kMaxVkDeviceSize =
static_cast<quint64
>((std::numeric_limits<VkDeviceSize>::max)());
134 for (
int p = 0; p < planeCount; ++p) {
135 const gsize offset = vmeta ? vmeta->offset[p] : GST_VIDEO_INFO_PLANE_OFFSET(&videoInfo, p);
136 const gint stride = vmeta ? vmeta->stride[p] : GST_VIDEO_INFO_PLANE_STRIDE(&videoInfo, p);
137 const int planeWidth = GST_VIDEO_INFO_COMP_WIDTH(&videoInfo, p);
138 const int planeHeight = GST_VIDEO_INFO_COMP_HEIGHT(&videoInfo, p);
139 const int pixelStride = GST_VIDEO_INFO_COMP_PSTRIDE(&videoInfo, p);
141 if (stride <= 0 || planeWidth <= 0 || planeHeight <= 0 || pixelStride <= 0) {
142 qCWarning(GstDmaBufVulkanLog) <<
"Vulkan import: invalid DMABuf plane layout"
143 <<
"plane" << p <<
"stride" << stride <<
"width" << planeWidth <<
"height"
144 << planeHeight <<
"pixelStride" << pixelStride;
148 const quint64 rowPitch =
static_cast<quint64
>(stride);
149 const quint64 minRowBytes =
static_cast<quint64
>(planeWidth) *
static_cast<quint64
>(pixelStride);
150 if (rowPitch < minRowBytes || offset > kMaxVkDeviceSize || rowPitch > kMaxVkDeviceSize) {
151 qCWarning(GstDmaBufVulkanLog) <<
"Vulkan import: implausible DMABuf plane layout"
152 <<
"plane" << p <<
"offset" <<
static_cast<quint64
>(offset) <<
"stride"
153 << stride <<
"minRowBytes" << minRowBytes;
157 if (rowPitch > std::numeric_limits<quint64>::max() /
static_cast<quint64
>(planeHeight)) {
158 qCWarning(GstDmaBufVulkanLog) <<
"Vulkan import: DMABuf plane size overflow"
159 <<
"plane" << p <<
"stride" << stride <<
"height" << planeHeight;
162 const quint64 planeSize = rowPitch *
static_cast<quint64
>(planeHeight);
163 if (
static_cast<quint64
>(offset) > std::numeric_limits<quint64>::max() - planeSize) {
164 qCWarning(GstDmaBufVulkanLog)
165 <<
"Vulkan import: DMABuf plane end overflow"
166 <<
"plane" << p <<
"offset" <<
static_cast<quint64
>(offset) <<
"planeSize" << planeSize;
169 const quint64 planeEnd =
static_cast<quint64
>(offset) + planeSize;
170 if (memoryBound > 0 && planeEnd > memoryBound) {
171 qCWarning(GstDmaBufVulkanLog) <<
"Vulkan import: DMABuf plane exceeds buffer bounds"
172 <<
"plane" << p <<
"end" << planeEnd <<
"bound" << memoryBound;
176 planeLayouts[p].offset =
static_cast<VkDeviceSize
>(offset);
177 planeLayouts[p].rowPitch =
static_cast<VkDeviceSize
>(rowPitch);
184namespace GstDmaBufVulkan {
186void resetLoggedState() noexcept
188 s_loggedVulkanUnimpl.store(
false, std::memory_order_release);
193QVideoFrameTexturesUPtr GstDmaBufVideoBuffer::importVulkan(QRhi& rhi)
198 const auto* nh =
static_cast<const QRhiVulkanNativeHandles*
>(rhi.nativeHandles());
199 if (!nh || nh->dev == VK_NULL_HANDLE || nh->physDev == VK_NULL_HANDLE || !nh->inst) {
200 QGC_HW_WARN_ONCE(GstDmaBufVulkanLog, s_loggedVulkanUnimpl,
"Vulkan import: native device handles unavailable");
203 const VkDevice dev = nh->dev;
206 const VulkanImportFns& fns = resolveVulkanImportFns(*nh->inst, dev);
209 "Vulkan import: required device functions (VK_EXT_external_memory_dma_buf) unavailable");
212 const auto pfnCreateImage = fns.createImage;
213 const auto pfnDestroyImage = fns.destroyImage;
214 const auto pfnAllocateMemory = fns.allocateMemory;
215 const auto pfnFreeMemory = fns.freeMemory;
216 const auto pfnBindImageMemory = fns.bindImageMemory;
217 const auto pfnGetMemReq = fns.getImageMemoryRequirements;
218 const auto pfnGetFdProps = fns.getMemoryFdProperties;
219 const auto pfnGetPhysMemProps = fns.getPhysicalDeviceMemoryProperties;
221 GstBuffer* buffer = gst_sample_get_buffer(_sample);
222 if (!buffer || gst_buffer_n_memory(buffer) != 1) {
225 "Vulkan import: multi-fd DMABuf not supported — CPU upload");
228 const int fourcc = GstHw::drmFourccForSingleFd(_videoInfo);
229 const VkFormat vkFormat = vkFormatForDrmFourcc(fourcc);
230 if (vkFormat == VK_FORMAT_UNDEFINED) {
232 "Vulkan import: no VkFormat for DMABuf layout — CPU upload");
239 _format.frameSize())) {
243 GstMemory* gstMemory = gst_buffer_peek_memory(buffer, 0);
244 const int fd = gst_dmabuf_memory_get_fd(gstMemory);
250 const int planeCount = std::clamp(
int(GST_VIDEO_INFO_N_PLANES(&_videoInfo)), 1,
GstHw::kMaxPlanes);
251 GstVideoMeta* vmeta = gst_buffer_get_video_meta(buffer);
253 if (!validateVulkanPlaneLayouts(_videoInfo, vmeta, planeCount, dmaMemoryBound(gstMemory, _videoInfo), planeLayouts)) {
257 VkImageDrmFormatModifierExplicitCreateInfoEXT modInfo = {};
258 modInfo.sType = VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_EXPLICIT_CREATE_INFO_EXT;
259 modInfo.drmFormatModifier = _drmModifier;
260 modInfo.drmFormatModifierPlaneCount =
static_cast<uint32_t
>(planeCount);
261 modInfo.pPlaneLayouts = planeLayouts;
263 VkExternalMemoryImageCreateInfo extImg = {};
264 extImg.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO;
265 extImg.pNext = &modInfo;
266 extImg.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT;
268 VkImageCreateInfo imgInfo = {};
269 imgInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
270 imgInfo.pNext = &extImg;
271 imgInfo.imageType = VK_IMAGE_TYPE_2D;
272 imgInfo.format = vkFormat;
273 imgInfo.extent = {
static_cast<uint32_t
>(GST_VIDEO_INFO_WIDTH(&_videoInfo)),
274 static_cast<uint32_t
>(GST_VIDEO_INFO_HEIGHT(&_videoInfo)), 1};
275 imgInfo.mipLevels = 1;
276 imgInfo.arrayLayers = 1;
277 imgInfo.samples = VK_SAMPLE_COUNT_1_BIT;
278 imgInfo.tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT;
279 imgInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT;
280 imgInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
281 imgInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
283 VkImage image = VK_NULL_HANDLE;
284 if (pfnCreateImage(dev, &imgInfo,
nullptr, &image) != VK_SUCCESS || image == VK_NULL_HANDLE) {
285 QGC_HW_WARN_ONCE(GstDmaBufVulkanLog, s_loggedVulkanUnimpl,
"Vulkan import: vkCreateImage failed — CPU upload");
288 const auto destroyImage = qScopeGuard([&] {
289 if (image != VK_NULL_HANDLE)
290 pfnDestroyImage(dev, image,
nullptr);
294 const int dupFd = ::fcntl(fd, F_DUPFD_CLOEXEC, 0);
298 bool fdConsumed =
false;
299 const auto closeFd = qScopeGuard([&] {
300 if (!fdConsumed && dupFd >= 0)
304 VkMemoryFdPropertiesKHR fdProps = {};
305 fdProps.sType = VK_STRUCTURE_TYPE_MEMORY_FD_PROPERTIES_KHR;
306 if (pfnGetFdProps(dev, VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, dupFd, &fdProps) != VK_SUCCESS) {
310 VkMemoryRequirements memReq = {};
311 pfnGetMemReq(dev, image, &memReq);
312 VkPhysicalDeviceMemoryProperties physMem = {};
313 pfnGetPhysMemProps(nh->physDev, &physMem);
314 uint32_t memTypeIndex = UINT32_MAX;
315 const uint32_t allowed = memReq.memoryTypeBits & fdProps.memoryTypeBits;
316 for (uint32_t i = 0; i < physMem.memoryTypeCount; ++i) {
317 if (allowed & (1u << i)) {
322 if (memTypeIndex == UINT32_MAX) {
326 VkImportMemoryFdInfoKHR importInfo = {};
327 importInfo.sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR;
328 importInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT;
329 importInfo.fd = dupFd;
331 VkMemoryDedicatedAllocateInfo dedicated = {};
332 dedicated.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO;
333 dedicated.image = image;
334 dedicated.pNext = &importInfo;
336 VkMemoryAllocateInfo allocInfo = {};
337 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
338 allocInfo.pNext = &dedicated;
339 allocInfo.allocationSize = memReq.size;
340 allocInfo.memoryTypeIndex = memTypeIndex;
344 VkDeviceMemory memory = VK_NULL_HANDLE;
345 if (pfnAllocateMemory(dev, &allocInfo,
nullptr, &memory) != VK_SUCCESS || memory == VK_NULL_HANDLE) {
347 "Vulkan import: vkAllocateMemory failed — CPU upload");
351 const auto freeMemory = qScopeGuard([&] {
352 if (memory != VK_NULL_HANDLE)
353 pfnFreeMemory(dev, memory,
nullptr);
356 if (
const VkResult bindRes = pfnBindImageMemory(dev, image, memory, 0); bindRes != VK_SUCCESS) {
358 "Vulkan import: vkBindImageMemory failed (VkResult=" << bindRes <<
") — CPU upload");
363 std::make_unique<GstVulkanOwnedFrameTextures>(&rhi, _format.frameSize(), _format.pixelFormat(), image);
364 if (!frameTextures->valid()) {
366 "Vulkan import: QRhiTexture::createFrom(VkImage) failed");
370 frameTextures->adoptVulkanResources(dev, image, memory, pfnDestroyImage, pfnFreeMemory);
371 image = VK_NULL_HANDLE;
372 memory = VK_NULL_HANDLE;
373 frameTextures->setSourceSample(takeSample());
374 static std::atomic<bool> s_loggedVulkanOk{
false};
375 if (!s_loggedVulkanOk.exchange(
true, std::memory_order_relaxed)) {
376 qCInfo(GstDmaBufVulkanLog) <<
"First Vulkan DMABuf zero-copy import success: format="
377 << int(_format.pixelFormat());
379 return frameTextures;
#define QGC_HW_WARN_ONCE(LOGCAT, FLAG,...)
Logs once via qCWarning(LOGCAT) the first time FLAG flips true; subsequent trips are silent.
#define QGC_LOGGING_CATEGORY(name, categoryStr)
constexpr int kMaxPlanes
Matches GST_VIDEO_MAX_PLANES (gst-video pins it at 4); single source of truth for every per-platform ...