MXVK Vulkan Framework 0.24.0
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
74 bool VK_Capture::readRgba(cv::Mat &rgba, bool flipY) {
75#ifdef MXVK_CUDA
76 cv::Mat cpuFallbackFrame;
77
78 try {
79 if (initializeCuda()) {
80 if (cudaMappedInput) {
81 if (!cap.read(mappedFrame) || mappedFrame.empty()) {
82 return false;
83 }
84 cpuFallbackFrame = host_mem_mat_header(mappedFrame);
85 const cv::cuda::GpuMat mappedInput = host_mem_gpu_header(mappedFrame);
86 cv::cuda::cvtColor(mappedInput, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
87 } else {
88 if (!cap.read(frame) || frame.empty()) {
89 return false;
90 }
91 cpuFallbackFrame = frame;
92 gpuFrame.upload(frame, cuda_stream);
93 cv::cuda::cvtColor(gpuFrame, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
94 }
95
96 if (flipY) {
97 cv::cuda::flip(gpuRgba, gpuVulkanRgba, 0, cuda_stream);
98 } else {
99 gpuVulkanRgba = gpuRgba;
100 }
101
102 if (!cudaPipelineLogged) {
103 std::cout << std::format(
104 "mxvk_cv: CUDA RGBA path active: capture -> GpuMat -> cv::cuda::cvtColor(BGR to RGBA) -> {} -> download\n",
105 flipY ? "cv::cuda::flip(Y)" : "no Y flip");
106 cudaPipelineLogged = true;
107 }
108
109 pinnedRgba.create(gpuVulkanRgba.size(), gpuVulkanRgba.type());
110 gpuVulkanRgba.download(pinnedRgba, cuda_stream);
111 cuda_stream.waitForCompletion();
112 rgba = host_mem_mat_header(pinnedRgba);
113 return true;
114 }
115 } catch (const cv::Exception &e) {
116 std::cout << std::format("mxvk_cv: CUDA RGBA path failed; falling back to CPU path: {}\n", e.what());
117 cudaAvailable = false;
118 if (cpuFallbackFrame.empty()) {
119 return false;
120 }
121 cv::cvtColor(cpuFallbackFrame, rgba, cv::COLOR_BGR2RGBA);
122 if (flipY) {
123 cv::flip(rgba, rgba, 0);
124 }
125 return true;
126 }
127#endif
128
129 if (!cap.read(frame) || frame.empty()) {
130 return false;
131 }
132 cv::cvtColor(frame, rgba, cv::COLOR_BGR2RGBA);
133 if (flipY) {
134 cv::flip(rgba, rgba, 0);
135 }
136 return true;
137 }
138
139#ifdef MXVK_CUDA
140 bool VK_Capture::initializeCuda() {
141 if (cudaChecked) {
142 return cudaAvailable;
143 }
144
145 cudaChecked = true;
146 std::cout << "mxvk_cv: CUDA init: MXVK_CUDA compile definition is enabled\n";
147 try {
148 const int deviceCount = cv::cuda::getCudaEnabledDeviceCount();
149 std::cout << std::format("mxvk_cv: CUDA init: OpenCV reports {} CUDA-enabled device(s)\n", deviceCount);
150 if (deviceCount <= 0) {
151 std::cout << "mxvk_cv: CUDA path unavailable: no CUDA-enabled OpenCV device\n";
152 return false;
153 }
154
155 cv::cuda::setDevice(0);
156 const cv::cuda::DeviceInfo deviceInfo(0);
157 cudaMappedInput = deviceInfo.canMapHostMemory();
158 if (const char *flipEnv = std::getenv("MXVK_CUDA_FLIP_Y")) {
159 cudaFlipYForVulkan = std::string(flipEnv) != "0";
160 }
161 cudaAvailable = true;
162 std::cout << std::format("mxvk_cv: CUDA init: selected device 0: {}\n", deviceInfo.name());
163 std::cout << std::format("mxvk_cv: CUDA init: compute capability {}.{}\n",
164 deviceInfo.majorVersion(), deviceInfo.minorVersion());
165 std::cout << std::format("mxvk_cv: CUDA init: unified addressing={}, host memory mapping={}\n",
166 deviceInfo.unifiedAddressing() ? "true" : "false",
167 cudaMappedInput ? "true" : "false");
168 std::cout << std::format("mxvk_cv: CUDA init: capture input mode={}\n",
169 cudaMappedInput ? "mapped HostMem::SHARED GpuMat header" : "cv::Mat upload to GpuMat");
170 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",
171 cudaFlipYForVulkan ? "enabled" : "disabled");
172 std::cout << std::format("mxvk_cv: CUDA path enabled on {} ({})\n",
173 deviceInfo.name(),
174 cudaMappedInput ? "mapped zero-copy input" : "pinned async input upload");
175 std::cout << "mxvk_cv: CUDA pipeline: capture -> GpuMat -> cv::cuda::cvtColor(BGR to RGBA) -> direct Vulkan texture when available\n";
176 } catch (const cv::Exception &e) {
177 std::cout << std::format("mxvk_cv: CUDA path unavailable: {}\n", e.what());
178 cudaAvailable = false;
179 }
180
181 return cudaAvailable;
182 }
183
184 bool VK_Capture::readCuda() {
185 cv::Mat cpuFallbackFrame;
186
187 try {
188 if (cudaMappedInput) {
189 if (!cap.read(mappedFrame)) {
190 return false;
191 }
192 if (mappedFrame.empty()) {
193 return false;
194 }
195
196 cpuFallbackFrame = host_mem_mat_header(mappedFrame);
197 const cv::cuda::GpuMat mappedInput = host_mem_gpu_header(mappedFrame);
198 cv::cuda::cvtColor(mappedInput, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
199 } else {
200 if (!cap.read(frame)) {
201 return false;
202 }
203 if (frame.empty()) {
204 return false;
205 }
206
207 cpuFallbackFrame = frame;
208 gpuFrame.upload(frame, cuda_stream);
209 cv::cuda::cvtColor(gpuFrame, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
210 }
211
212 if (cudaFlipYForVulkan) {
213 cv::cuda::flip(gpuRgba, gpuVulkanRgba, 0, cuda_stream);
214 } else {
215 gpuVulkanRgba = gpuRgba;
216 }
217
218 if (sprite && sprite->updateTextureCuda(gpuVulkanRgba, cuda_stream)) {
219 if (!cudaPipelineLogged) {
220 std::cout << std::format(
221 "mxvk_cv: CUDA interop active: RGBA GpuMat -> {} -> Vulkan external-memory image\n",
222 cudaFlipYForVulkan ? "cv::cuda::flip(Y)" : "no Y flip");
223 cudaPipelineLogged = true;
224 }
225 return true;
226 }
227
228 if (!cudaPipelineLogged) {
229 std::cout << "mxvk_cv: CUDA interop unavailable for this sprite; using pinned CUDA download -> Vulkan staging upload\n";
230 cudaPipelineLogged = true;
231 }
232 pinnedRgba.create(gpuVulkanRgba.size(), gpuVulkanRgba.type());
233 gpuVulkanRgba.download(pinnedRgba, cuda_stream);
234 cuda_stream.waitForCompletion();
235
236 pinnedRgbaMat = host_mem_mat_header(pinnedRgba);
237 sprite->updateTexture(pinnedRgbaMat.ptr(), pinnedRgbaMat.cols, pinnedRgbaMat.rows,
238 static_cast<int>(pinnedRgbaMat.step));
239 return true;
240 } catch (const cv::Exception &e) {
241 std::cout << std::format("mxvk_cv: CUDA frame path failed; falling back to CPU path: {}\n", e.what());
242 cudaAvailable = false;
243 }
244
245 if (cpuFallbackFrame.empty()) {
246 return false;
247 }
248
249 cv::Mat rgba;
250 cv::cvtColor(cpuFallbackFrame, rgba, cv::COLOR_BGR2RGBA);
251 sprite->updateTexture(rgba.ptr(), rgba.cols, rgba.rows, static_cast<int>(rgba.step));
252 return true;
253 }
254
255 bool VK_Capture::readGpuRgba(cv::cuda::GpuMat &rgba, bool flipY) {
256 if (!initializeCuda()) {
257 return false;
258 }
259
260 try {
261 if (cudaMappedInput) {
262 if (!cap.read(mappedFrame) || mappedFrame.empty()) {
263 return false;
264 }
265 const cv::cuda::GpuMat mappedInput = host_mem_gpu_header(mappedFrame);
266 cv::cuda::cvtColor(mappedInput, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
267 } else {
268 if (!cap.read(frame) || frame.empty()) {
269 return false;
270 }
271 gpuFrame.upload(frame, cuda_stream);
272 cv::cuda::cvtColor(gpuFrame, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
273 }
274
275 if (flipY) {
276 cv::cuda::flip(gpuRgba, gpuVulkanRgba, 0, cuda_stream);
277 } else {
278 gpuVulkanRgba = gpuRgba;
279 }
280
281 if (!cudaPipelineLogged) {
282 std::cout << std::format(
283 "mxvk_cv: CUDA GpuMat path active: capture -> GpuMat -> cv::cuda::cvtColor(BGR to RGBA) -> {} -> resident GpuMat for caller\n",
284 flipY ? "cv::cuda::flip(Y)" : "no Y flip");
285 cudaPipelineLogged = true;
286 }
287
288 rgba = gpuVulkanRgba;
289 return true;
290 } catch (const cv::Exception &e) {
291 std::cout << std::format("mxvk_cv: CUDA GpuMat path failed: {}\n", e.what());
292 cudaAvailable = false;
293 return false;
294 }
295 }
296#endif
297
299#ifdef MXVK_CUDA
300 if (initializeCuda()) {
301 cv::Mat cpuFallbackFrame;
302
303 try {
304 if (cudaMappedInput) {
305 if (!cap.read(mappedFrame) || mappedFrame.empty()) {
306 return false;
307 }
308 cpuFallbackFrame = host_mem_mat_header(mappedFrame);
309 const cv::cuda::GpuMat mappedInput = host_mem_gpu_header(mappedFrame);
310 cv::cuda::cvtColor(mappedInput, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
311 } else {
312 if (!cap.read(frame) || frame.empty()) {
313 return false;
314 }
315 cpuFallbackFrame = frame;
316 gpuFrame.upload(frame, cuda_stream);
317 cv::cuda::cvtColor(gpuFrame, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
318 }
319
320 if (flipY) {
321 cv::cuda::flip(gpuRgba, gpuVulkanRgba, 0, cuda_stream);
322 } else {
323 gpuVulkanRgba = gpuRgba;
324 }
325
326 if (model.updatePrimaryTextureCuda(gpuVulkanRgba, cuda_stream)) {
327 if (!cudaPipelineLogged) {
328 std::cout << std::format(
329 "mxvk_cv: CUDA interop active for model texture: RGBA GpuMat -> {} -> Vulkan external-memory image array (no download)\n",
330 flipY ? "cv::cuda::flip(Y)" : "no Y flip");
331 cudaPipelineLogged = true;
332 }
333 return true;
334 }
335
336 if (!cudaPipelineLogged) {
337 std::cout << "mxvk_cv: CUDA interop unavailable for model texture; using pinned CUDA download -> Vulkan staging upload\n";
338 cudaPipelineLogged = true;
339 }
340 pinnedRgba.create(gpuVulkanRgba.size(), gpuVulkanRgba.type());
341 gpuVulkanRgba.download(pinnedRgba, cuda_stream);
342 cuda_stream.waitForCompletion();
343
344 pinnedRgbaMat = host_mem_mat_header(pinnedRgba);
345 return model.updatePrimaryTexture(pinnedRgbaMat.ptr(), pinnedRgbaMat.cols, pinnedRgbaMat.rows,
346 static_cast<int>(pinnedRgbaMat.step));
347 } catch (const cv::Exception &e) {
348 std::cout << std::format("mxvk_cv: CUDA model-texture path failed; falling back to CPU path: {}\n", e.what());
349 cudaAvailable = false;
350 if (cpuFallbackFrame.empty()) {
351 return false;
352 }
353 cv::Mat rgba;
354 cv::cvtColor(cpuFallbackFrame, rgba, cv::COLOR_BGR2RGBA);
355 if (flipY) {
356 cv::flip(rgba, rgba, 0);
357 }
358 return model.updatePrimaryTexture(rgba.ptr(), rgba.cols, rgba.rows, static_cast<int>(rgba.step));
359 }
360 }
361#endif
362 cv::Mat rgba;
363 if (!readRgba(rgba, flipY)) {
364 return false;
365 }
366 return model.updatePrimaryTexture(rgba.ptr(), rgba.cols, rgba.rows, static_cast<int>(rgba.step));
367 }
368
370#ifdef MXVK_CUDA
371 if (initializeCuda()) {
372 return readToSprite(targetSprite, cudaFlipYForVulkan);
373 }
374#endif
375 return readToSprite(targetSprite, false);
376 }
377
378 bool VK_Capture::readToSprite(VK_Sprite &targetSprite, bool flipY) {
379#ifdef MXVK_CUDA
380 if (initializeCuda()) {
381 cv::Mat cpuFallbackFrame;
382
383 try {
384 if (cudaMappedInput) {
385 if (!cap.read(mappedFrame) || mappedFrame.empty()) {
386 return false;
387 }
388 cpuFallbackFrame = host_mem_mat_header(mappedFrame);
389 const cv::cuda::GpuMat mappedInput = host_mem_gpu_header(mappedFrame);
390 cv::cuda::cvtColor(mappedInput, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
391 } else {
392 if (!cap.read(frame) || frame.empty()) {
393 return false;
394 }
395 cpuFallbackFrame = frame;
396 gpuFrame.upload(frame, cuda_stream);
397 cv::cuda::cvtColor(gpuFrame, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
398 }
399
400 if (flipY) {
401 cv::cuda::flip(gpuRgba, gpuVulkanRgba, 0, cuda_stream);
402 } else {
403 gpuVulkanRgba = gpuRgba;
404 }
405
406 if (targetSprite.updateTextureCuda(gpuVulkanRgba, cuda_stream)) {
407 if (!cudaPipelineLogged) {
408 std::cout << std::format(
409 "mxvk_cv: CUDA interop active for external sprite: RGBA GpuMat -> {} -> Vulkan external-memory image\n",
410 flipY ? "cv::cuda::flip(Y)" : "no Y flip");
411 cudaPipelineLogged = true;
412 }
413 return true;
414 }
415
416 if (!cudaPipelineLogged) {
417 std::cout << "mxvk_cv: CUDA interop unavailable for external sprite; using pinned CUDA download -> Vulkan staging upload\n";
418 cudaPipelineLogged = true;
419 }
420 pinnedRgba.create(gpuVulkanRgba.size(), gpuVulkanRgba.type());
421 gpuVulkanRgba.download(pinnedRgba, cuda_stream);
422 cuda_stream.waitForCompletion();
423
424 pinnedRgbaMat = host_mem_mat_header(pinnedRgba);
425 targetSprite.updateTexture(pinnedRgbaMat.ptr(), pinnedRgbaMat.cols, pinnedRgbaMat.rows,
426 static_cast<int>(pinnedRgbaMat.step));
427 return true;
428 } catch (const cv::Exception &e) {
429 std::cout << std::format("mxvk_cv: CUDA external-sprite path failed; falling back to CPU path: {}\n", e.what());
430 cudaAvailable = false;
431 if (cpuFallbackFrame.empty()) {
432 return false;
433 }
434 cv::Mat rgba;
435 cv::cvtColor(cpuFallbackFrame, rgba, cv::COLOR_BGR2RGBA);
436 if (flipY) {
437 cv::flip(rgba, rgba, 0);
438 }
439 targetSprite.updateTexture(rgba.ptr(), rgba.cols, rgba.rows, static_cast<int>(rgba.step));
440 return true;
441 }
442 }
443#endif
444 if (!cap.read(frame) || frame.empty()) {
445 return false;
446 }
447 cv::Mat rgba;
448 cv::cvtColor(frame, rgba, cv::COLOR_BGR2RGBA);
449 if (flipY) {
450 cv::flip(rgba, rgba, 0);
451 }
452 targetSprite.updateTexture(rgba.ptr(), rgba.cols, rgba.rows, static_cast<int>(rgba.step));
453 return true;
454 }
455
457#ifdef MXVK_CUDA
458 if (initializeCuda()) {
459 return readCuda();
460 }
461#endif
462 if (!cap.read(frame)) {
463 return false;
464 }
465 cv::Mat rgba;
466 cv::cvtColor(frame, rgba, cv::COLOR_BGR2RGBA);
467 sprite->updateTexture(rgba.ptr(), rgba.cols, rgba.rows, static_cast<int>(rgba.step));
468 return true;
469 }
470
471 void VK_Capture::draw(int x, int y, int width, int height) {
472 if (sprite)
473 sprite->drawSpriteRect(x, y, width, height);
474 }
475
476 void VK_Capture::draw(int x, int y) {
477 if (sprite)
478 sprite->drawSprite(x, y);
479 }
480
481 void VK_Capture::set(unsigned int option, double value) {
482 cap.set(option, value);
483 }
484
485 double VK_Capture::get(unsigned int option) {
486 return cap.get(option);
487 }
488
489} // 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:298
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
void close()
Close the capture device.
Definition mxvk_cv.cpp:37
double get(unsigned int option)
Query a VideoCapture property.
Definition mxvk_cv.cpp:485
bool readToSprite(VK_Sprite &targetSprite)
Definition mxvk_cv.cpp:369
bool readRgba(cv::Mat &rgba, bool flipY=false)
Definition mxvk_cv.cpp:74
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:471
bool read()
Capture and upload the next video frame.
Definition mxvk_cv.cpp:456
void set(unsigned int option, double value)
Set a VideoCapture property.
Definition mxvk_cv.cpp:481
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:30