MXVK Vulkan Framework 0.33.1
C++20 Vulkan rendering framework for practical 2D and 3D application development with SDL3.
Loading...
Searching...
No Matches
mxvk_cv.cpp
Go to the documentation of this file.
1/**
2 * @file mxvk_cv.cpp
3 * @brief Implementation of mxvk::VK_Capture (Vulkan + OpenCV variant).
4 */
5#include "mxvk/mxvk_cv.hpp"
8#include <cstdlib>
9#include <filesystem>
10#include <iostream>
11
12namespace mxvk {
13
14 bool VK_Capture::open(const std::string &filename) {
15 const bool ok = cap.open(filename);
16 if (ok)
17 std::cout << std::format("mxvk_cv: Opened file: {}\n", filename);
18 else
19 std::cout << std::format("mxvk_cv: Failed to open file: {}\n", filename);
20 return ok;
21 }
22
23 bool VK_Capture::open(int id, int mode) {
24#ifdef __linux__
25 if (mode == 0)
26 mode = cv::CAP_V4L2;
27#endif
28 if (cap.open(id, mode)) {
29 cap.set(cv::CAP_PROP_FOURCC, cv::VideoWriter::fourcc('M', 'J', 'P', 'G'));
30 std::cout << std::format("mxvk_cv: Opened device: {}\n", id);
31 return true;
32 }
33 std::cout << std::format("mxvk_cv: Failed to open device: {}\n", id);
34 return false;
35 }
36
38 cap.release();
39 std::cout << "mxvk_cv: Capture closed\n";
40 }
41
43 sprite.reset();
44 }
45
46 bool VK_Capture::createImage(VkDevice device, VkPhysicalDevice physDev, VkQueue gQueue,
47 VkCommandPool cmdPool, size_t width, size_t height,
48 const std::string &vert, const std::string &frag) {
49 sprite = std::make_unique<VK_Sprite>(device, physDev, gQueue, cmdPool);
50 sprite->createEmptySprite(static_cast<int>(width), static_cast<int>(height), vert, frag);
51 sprite->enableExtendedUBO();
52 sprite->rebuildPipeline();
53 std::cout << std::format("mxvk_cv: Sprite created: {}x{}\n", width, height);
54 return true;
55 }
56
57 bool VK_Capture::reload(size_t width, size_t height, const std::string &vert, const std::string &frag) {
58 if (sprite) {
59 sprite->createEmptySprite(static_cast<int>(width), static_cast<int>(height), vert, frag);
60 sprite->enableExtendedUBO();
61 std::cout << std::format("mxvk_cv: Shader reloaded: vert={} frag={}\n",
62 std::filesystem::path(vert).filename().string(),
63 std::filesystem::path(frag).filename().string());
64 return true;
65 }
66 std::cout << "mxvk_cv: Reload failed: no sprite\n";
67 return false;
68 }
69
70 bool VK_Capture::read(cv::Mat &frame) {
71 return cap.read(frame);
72 }
73
75 return cap.grab();
76 }
77
78 bool VK_Capture::readRgba(cv::Mat &rgba, bool flipY) {
79#ifdef MXVK_CUDA
80 cv::Mat cpuFallbackFrame;
81
82 try {
83 if (initializeCuda()) {
84 if (cudaMappedInput) {
85 if (!cap.read(mappedFrame) || mappedFrame.empty()) {
86 return false;
87 }
88 cpuFallbackFrame = host_mem_mat_header(mappedFrame);
89 const cv::cuda::GpuMat mappedInput = host_mem_gpu_header(mappedFrame);
90 cv::cuda::cvtColor(mappedInput, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
91 } else {
92 if (!cap.read(frame) || frame.empty()) {
93 return false;
94 }
95 cpuFallbackFrame = frame;
96 gpuFrame.upload(frame, cuda_stream);
97 cv::cuda::cvtColor(gpuFrame, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
98 }
99
100 if (flipY) {
101 cv::cuda::flip(gpuRgba, gpuVulkanRgba, 0, cuda_stream);
102 } else {
103 gpuVulkanRgba = gpuRgba;
104 }
105
106 if (!cudaPipelineLogged) {
107 std::cout << std::format(
108 "mxvk_cv: CUDA RGBA path active: capture -> GpuMat -> cv::cuda::cvtColor(BGR to RGBA) -> {} -> download\n",
109 flipY ? "cv::cuda::flip(Y)" : "no Y flip");
110 cudaPipelineLogged = true;
111 }
112
113 pinnedRgba.create(gpuVulkanRgba.size(), gpuVulkanRgba.type());
114 gpuVulkanRgba.download(pinnedRgba, cuda_stream);
115 cuda_stream.waitForCompletion();
116 rgba = host_mem_mat_header(pinnedRgba);
117 return true;
118 }
119 } catch (const cv::Exception &e) {
120 std::cout << std::format("mxvk_cv: CUDA RGBA path failed; falling back to CPU path: {}\n", e.what());
121 cudaAvailable = false;
122 if (cpuFallbackFrame.empty()) {
123 return false;
124 }
125 cv::cvtColor(cpuFallbackFrame, rgba, cv::COLOR_BGR2RGBA);
126 if (flipY) {
127 cv::flip(rgba, rgba, 0);
128 }
129 return true;
130 }
131#endif
132
133 if (!cap.read(frame) || frame.empty()) {
134 return false;
135 }
136 cv::cvtColor(frame, rgba, cv::COLOR_BGR2RGBA);
137 if (flipY) {
138 cv::flip(rgba, rgba, 0);
139 }
140 return true;
141 }
142
143#ifdef MXVK_CUDA
144 bool VK_Capture::initializeCuda() {
145 if (cudaChecked) {
146 return cudaAvailable;
147 }
148
149 cudaChecked = true;
150 std::cout << "mxvk_cv: CUDA init: MXVK_CUDA compile definition is enabled\n";
151 try {
152 const int deviceCount = cv::cuda::getCudaEnabledDeviceCount();
153 std::cout << std::format("mxvk_cv: CUDA init: OpenCV reports {} CUDA-enabled device(s)\n", deviceCount);
154 if (deviceCount <= 0) {
155 std::cout << "mxvk_cv: CUDA path unavailable: no CUDA-enabled OpenCV device\n";
156 return false;
157 }
158
159 cv::cuda::setDevice(0);
160 const cv::cuda::DeviceInfo deviceInfo(0);
161 cudaMappedInput = deviceInfo.canMapHostMemory();
162 if (const char *flipEnv = std::getenv("MXVK_CUDA_FLIP_Y")) {
163 cudaFlipYForVulkan = std::string(flipEnv) != "0";
164 }
165 cudaAvailable = true;
166 std::cout << std::format("mxvk_cv: CUDA init: selected device 0: {}\n", deviceInfo.name());
167 std::cout << std::format("mxvk_cv: CUDA init: compute capability {}.{}\n",
168 deviceInfo.majorVersion(), deviceInfo.minorVersion());
169 std::cout << std::format("mxvk_cv: CUDA init: unified addressing={}, host memory mapping={}\n",
170 deviceInfo.unifiedAddressing() ? "true" : "false",
171 cudaMappedInput ? "true" : "false");
172 std::cout << std::format("mxvk_cv: CUDA init: capture input mode={}\n",
173 cudaMappedInput ? "mapped HostMem::SHARED GpuMat header" : "cv::Mat upload to GpuMat");
174 std::cout << std::format("mxvk_cv: CUDA init: Vulkan upload Y flip={} (default on for CUDA external-memory textures; override with MXVK_CUDA_FLIP_Y=0 or 1)\n",
175 cudaFlipYForVulkan ? "enabled" : "disabled");
176 std::cout << std::format("mxvk_cv: CUDA path enabled on {} ({})\n",
177 deviceInfo.name(),
178 cudaMappedInput ? "mapped zero-copy input" : "pinned async input upload");
179 std::cout << "mxvk_cv: CUDA pipeline: capture -> GpuMat -> cv::cuda::cvtColor(BGR to RGBA) -> direct Vulkan texture when available\n";
180 } catch (const cv::Exception &e) {
181 std::cout << std::format("mxvk_cv: CUDA path unavailable: {}\n", e.what());
182 cudaAvailable = false;
183 }
184
185 return cudaAvailable;
186 }
187
188 bool VK_Capture::readCuda() {
189 cv::Mat cpuFallbackFrame;
190
191 try {
192 if (cudaMappedInput) {
193 if (!cap.read(mappedFrame)) {
194 return false;
195 }
196 if (mappedFrame.empty()) {
197 return false;
198 }
199
200 cpuFallbackFrame = host_mem_mat_header(mappedFrame);
201 const cv::cuda::GpuMat mappedInput = host_mem_gpu_header(mappedFrame);
202 cv::cuda::cvtColor(mappedInput, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
203 } else {
204 if (!cap.read(frame)) {
205 return false;
206 }
207 if (frame.empty()) {
208 return false;
209 }
210
211 cpuFallbackFrame = frame;
212 gpuFrame.upload(frame, cuda_stream);
213 cv::cuda::cvtColor(gpuFrame, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
214 }
215
216 if (cudaFlipYForVulkan) {
217 cv::cuda::flip(gpuRgba, gpuVulkanRgba, 0, cuda_stream);
218 } else {
219 gpuVulkanRgba = gpuRgba;
220 }
221
222 if (sprite && sprite->updateTextureCuda(gpuVulkanRgba, cuda_stream)) {
223 if (!cudaPipelineLogged) {
224 std::cout << std::format(
225 "mxvk_cv: CUDA interop active: RGBA GpuMat -> {} -> Vulkan external-memory image\n",
226 cudaFlipYForVulkan ? "cv::cuda::flip(Y)" : "no Y flip");
227 cudaPipelineLogged = true;
228 }
229 return true;
230 }
231
232 if (!cudaPipelineLogged) {
233 std::cout << "mxvk_cv: CUDA interop unavailable for this sprite; using pinned CUDA download -> Vulkan staging upload\n";
234 cudaPipelineLogged = true;
235 }
236 pinnedRgba.create(gpuVulkanRgba.size(), gpuVulkanRgba.type());
237 gpuVulkanRgba.download(pinnedRgba, cuda_stream);
238 cuda_stream.waitForCompletion();
239
240 pinnedRgbaMat = host_mem_mat_header(pinnedRgba);
241 sprite->updateTexture(pinnedRgbaMat.ptr(), pinnedRgbaMat.cols, pinnedRgbaMat.rows,
242 static_cast<int>(pinnedRgbaMat.step));
243 return true;
244 } catch (const cv::Exception &e) {
245 std::cout << std::format("mxvk_cv: CUDA frame path failed; falling back to CPU path: {}\n", e.what());
246 cudaAvailable = false;
247 }
248
249 if (cpuFallbackFrame.empty()) {
250 return false;
251 }
252
253 cv::Mat rgba;
254 cv::cvtColor(cpuFallbackFrame, rgba, cv::COLOR_BGR2RGBA);
255 sprite->updateTexture(rgba.ptr(), rgba.cols, rgba.rows, static_cast<int>(rgba.step));
256 return true;
257 }
258
259 bool VK_Capture::readGpuRgba(cv::cuda::GpuMat &rgba, bool flipY) {
260 if (!initializeCuda()) {
261 return false;
262 }
263
264 try {
265 if (cudaMappedInput) {
266 if (!cap.read(mappedFrame) || mappedFrame.empty()) {
267 return false;
268 }
269 const cv::cuda::GpuMat mappedInput = host_mem_gpu_header(mappedFrame);
270 cv::cuda::cvtColor(mappedInput, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
271 } else {
272 if (!cap.read(frame) || frame.empty()) {
273 return false;
274 }
275 gpuFrame.upload(frame, cuda_stream);
276 cv::cuda::cvtColor(gpuFrame, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
277 }
278
279 if (flipY) {
280 cv::cuda::flip(gpuRgba, gpuVulkanRgba, 0, cuda_stream);
281 } else {
282 gpuVulkanRgba = gpuRgba;
283 }
284
285 if (!cudaPipelineLogged) {
286 std::cout << std::format(
287 "mxvk_cv: CUDA GpuMat path active: capture -> GpuMat -> cv::cuda::cvtColor(BGR to RGBA) -> {} -> resident GpuMat for caller\n",
288 flipY ? "cv::cuda::flip(Y)" : "no Y flip");
289 cudaPipelineLogged = true;
290 }
291
292 rgba = gpuVulkanRgba;
293 return true;
294 } catch (const cv::Exception &e) {
295 std::cout << std::format("mxvk_cv: CUDA GpuMat path failed: {}\n", e.what());
296 cudaAvailable = false;
297 return false;
298 }
299 }
300#endif
301
303#ifdef MXVK_CUDA
304 if (initializeCuda()) {
305 cv::Mat cpuFallbackFrame;
306
307 try {
308 if (cudaMappedInput) {
309 if (!cap.read(mappedFrame) || mappedFrame.empty()) {
310 return false;
311 }
312 cpuFallbackFrame = host_mem_mat_header(mappedFrame);
313 const cv::cuda::GpuMat mappedInput = host_mem_gpu_header(mappedFrame);
314 cv::cuda::cvtColor(mappedInput, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
315 } else {
316 if (!cap.read(frame) || frame.empty()) {
317 return false;
318 }
319 cpuFallbackFrame = frame;
320 gpuFrame.upload(frame, cuda_stream);
321 cv::cuda::cvtColor(gpuFrame, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
322 }
323
324 if (flipY) {
325 cv::cuda::flip(gpuRgba, gpuVulkanRgba, 0, cuda_stream);
326 } else {
327 gpuVulkanRgba = gpuRgba;
328 }
329
330 if (model.updatePrimaryTextureCuda(gpuVulkanRgba, cuda_stream)) {
331 if (!cudaPipelineLogged) {
332 std::cout << std::format(
333 "mxvk_cv: CUDA interop active for model texture: RGBA GpuMat -> {} -> Vulkan external-memory image array (no download)\n",
334 flipY ? "cv::cuda::flip(Y)" : "no Y flip");
335 cudaPipelineLogged = true;
336 }
337 return true;
338 }
339
340 if (!cudaPipelineLogged) {
341 std::cout << "mxvk_cv: CUDA interop unavailable for model texture; using pinned CUDA download -> Vulkan staging upload\n";
342 cudaPipelineLogged = true;
343 }
344 pinnedRgba.create(gpuVulkanRgba.size(), gpuVulkanRgba.type());
345 gpuVulkanRgba.download(pinnedRgba, cuda_stream);
346 cuda_stream.waitForCompletion();
347
348 pinnedRgbaMat = host_mem_mat_header(pinnedRgba);
349 return model.updatePrimaryTexture(pinnedRgbaMat.ptr(), pinnedRgbaMat.cols, pinnedRgbaMat.rows,
350 static_cast<int>(pinnedRgbaMat.step));
351 } catch (const cv::Exception &e) {
352 std::cout << std::format("mxvk_cv: CUDA model-texture path failed; falling back to CPU path: {}\n", e.what());
353 cudaAvailable = false;
354 if (cpuFallbackFrame.empty()) {
355 return false;
356 }
357 cv::Mat rgba;
358 cv::cvtColor(cpuFallbackFrame, rgba, cv::COLOR_BGR2RGBA);
359 if (flipY) {
360 cv::flip(rgba, rgba, 0);
361 }
362 return model.updatePrimaryTexture(rgba.ptr(), rgba.cols, rgba.rows, static_cast<int>(rgba.step));
363 }
364 }
365#endif
366 cv::Mat rgba;
367 if (!readRgba(rgba, flipY)) {
368 return false;
369 }
370 return model.updatePrimaryTexture(rgba.ptr(), rgba.cols, rgba.rows, static_cast<int>(rgba.step));
371 }
372
374#ifdef MXVK_CUDA
375 if (initializeCuda()) {
376 return readToSprite(targetSprite, cudaFlipYForVulkan);
377 }
378#endif
379 return readToSprite(targetSprite, false);
380 }
381
382 bool VK_Capture::readToSprite(VK_Sprite &targetSprite, bool flipY) {
383#ifdef MXVK_CUDA
384 if (initializeCuda()) {
385 cv::Mat cpuFallbackFrame;
386
387 try {
388 if (cudaMappedInput) {
389 if (!cap.read(mappedFrame) || mappedFrame.empty()) {
390 return false;
391 }
392 cpuFallbackFrame = host_mem_mat_header(mappedFrame);
393 const cv::cuda::GpuMat mappedInput = host_mem_gpu_header(mappedFrame);
394 cv::cuda::cvtColor(mappedInput, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
395 } else {
396 if (!cap.read(frame) || frame.empty()) {
397 return false;
398 }
399 cpuFallbackFrame = frame;
400 gpuFrame.upload(frame, cuda_stream);
401 cv::cuda::cvtColor(gpuFrame, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
402 }
403
404 if (flipY) {
405 cv::cuda::flip(gpuRgba, gpuVulkanRgba, 0, cuda_stream);
406 } else {
407 gpuVulkanRgba = gpuRgba;
408 }
409
410 if (targetSprite.updateTextureCuda(gpuVulkanRgba, cuda_stream)) {
411 if (!cudaPipelineLogged) {
412 std::cout << std::format(
413 "mxvk_cv: CUDA interop active for external sprite: RGBA GpuMat -> {} -> Vulkan external-memory image\n",
414 flipY ? "cv::cuda::flip(Y)" : "no Y flip");
415 cudaPipelineLogged = true;
416 }
417 return true;
418 }
419
420 if (!cudaPipelineLogged) {
421 std::cout << "mxvk_cv: CUDA interop unavailable for external sprite; using pinned CUDA download -> Vulkan staging upload\n";
422 cudaPipelineLogged = true;
423 }
424 pinnedRgba.create(gpuVulkanRgba.size(), gpuVulkanRgba.type());
425 gpuVulkanRgba.download(pinnedRgba, cuda_stream);
426 cuda_stream.waitForCompletion();
427
428 pinnedRgbaMat = host_mem_mat_header(pinnedRgba);
429 targetSprite.updateTexture(pinnedRgbaMat.ptr(), pinnedRgbaMat.cols, pinnedRgbaMat.rows,
430 static_cast<int>(pinnedRgbaMat.step));
431 return true;
432 } catch (const cv::Exception &e) {
433 std::cout << std::format("mxvk_cv: CUDA external-sprite path failed; falling back to CPU path: {}\n", e.what());
434 cudaAvailable = false;
435 if (cpuFallbackFrame.empty()) {
436 return false;
437 }
438 cv::Mat rgba;
439 cv::cvtColor(cpuFallbackFrame, rgba, cv::COLOR_BGR2RGBA);
440 if (flipY) {
441 cv::flip(rgba, rgba, 0);
442 }
443 targetSprite.updateTexture(rgba.ptr(), rgba.cols, rgba.rows, static_cast<int>(rgba.step));
444 return true;
445 }
446 }
447#endif
448 if (!cap.read(frame) || frame.empty()) {
449 return false;
450 }
451 cv::Mat rgba;
452 cv::cvtColor(frame, rgba, cv::COLOR_BGR2RGBA);
453 if (flipY) {
454 cv::flip(rgba, rgba, 0);
455 }
456 targetSprite.updateTexture(rgba.ptr(), rgba.cols, rgba.rows, static_cast<int>(rgba.step));
457 return true;
458 }
459
461#ifdef MXVK_CUDA
462 if (initializeCuda()) {
463 return readCuda();
464 }
465#endif
466 if (!cap.read(frame)) {
467 return false;
468 }
469 cv::Mat rgba;
470 cv::cvtColor(frame, rgba, cv::COLOR_BGR2RGBA);
471 sprite->updateTexture(rgba.ptr(), rgba.cols, rgba.rows, static_cast<int>(rgba.step));
472 return true;
473 }
474
475 void VK_Capture::draw(int x, int y, int width, int height) {
476 if (sprite)
477 sprite->drawSpriteRect(x, y, width, height);
478 }
479
480 void VK_Capture::draw(int x, int y) {
481 if (sprite)
482 sprite->drawSprite(x, y);
483 }
484
485 void VK_Capture::set(unsigned int option, double value) {
486 cap.set(option, value);
487 }
488
489 double VK_Capture::get(unsigned int option) {
490 return cap.get(option);
491 }
492
493} // namespace mxvk
Convenience wrapper that owns mesh, textures, descriptors, and pipeline state.
bool updatePrimaryTexture(const void *pixels, int width, int height, int pitch=0)
Upload raw RGBA pixels into the primary model texture.
bool readToModelTexture(VKAbstractModel &model, bool flipY=false)
Definition mxvk_cv.cpp:302
bool open(const std::string &filename)
Open a video file.
Definition mxvk_cv.cpp:14
bool reload(size_t width, size_t height, const std::string &vert, const std::string &frag)
Replace the vertex/fragment shaders.
Definition mxvk_cv.cpp:57
void resetSprite()
Destroy the current sprite and its Vulkan resources.
Definition mxvk_cv.cpp:42
bool grab()
Decode and discard the next capture frame without conversion.
Definition mxvk_cv.cpp:74
void close()
Close the capture device.
Definition mxvk_cv.cpp:37
double get(unsigned int option)
Query a VideoCapture property.
Definition mxvk_cv.cpp:489
bool readToSprite(VK_Sprite &targetSprite)
Definition mxvk_cv.cpp:373
bool readRgba(cv::Mat &rgba, bool flipY=false)
Definition mxvk_cv.cpp:78
bool createImage(VkDevice device, VkPhysicalDevice physDev, VkQueue gQueue, VkCommandPool cmdPool, size_t width, size_t height, const std::string &vert, const std::string &frag)
Allocate the VKSprite and upload the initial texture.
Definition mxvk_cv.cpp:46
void draw(int x, int y, int width, int height)
Draw the current frame at a specified position and size.
Definition mxvk_cv.cpp:475
bool read()
Capture and upload the next video frame.
Definition mxvk_cv.cpp:460
void set(unsigned int option, double value)
Set a VideoCapture property.
Definition mxvk_cv.cpp:485
void updateTexture(SDL_Surface *surface)
Replace the sprite texture from an SDL_Surface.
High-level model wrapper integrated with MXVK dynamic rendering.
OpenCV video-capture integration for the Vulkan backend.
Small compatibility wrappers around OpenCV CUDA APIs.
Utilities for loading and saving PNG images.
Definition mxvk.hpp:31