15 const bool ok = cap.open(filename);
17 std::cout << std::format(
"mxvk_cv: Opened file: {}\n", filename);
19 std::cout << std::format(
"mxvk_cv: Failed to open file: {}\n", filename);
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);
33 std::cout << std::format(
"mxvk_cv: Failed to open device: {}\n",
id);
39 std::cout <<
"mxvk_cv: Capture closed\n";
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);
57 bool VK_Capture::reload(
size_t width,
size_t height,
const std::string &vert,
const std::string &frag) {
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());
66 std::cout <<
"mxvk_cv: Reload failed: no sprite\n";
71 return cap.read(frame);
80 cv::Mat cpuFallbackFrame;
83 if (initializeCuda()) {
84 if (cudaMappedInput) {
85 if (!cap.read(mappedFrame) || mappedFrame.empty()) {
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);
92 if (!cap.read(frame) || frame.empty()) {
95 cpuFallbackFrame = frame;
96 gpuFrame.upload(frame, cuda_stream);
97 cv::cuda::cvtColor(gpuFrame, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
101 cv::cuda::flip(gpuRgba, gpuVulkanRgba, 0, cuda_stream);
103 gpuVulkanRgba = gpuRgba;
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;
113 pinnedRgba.create(gpuVulkanRgba.size(), gpuVulkanRgba.type());
114 gpuVulkanRgba.download(pinnedRgba, cuda_stream);
115 cuda_stream.waitForCompletion();
116 rgba = host_mem_mat_header(pinnedRgba);
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()) {
125 cv::cvtColor(cpuFallbackFrame, rgba, cv::COLOR_BGR2RGBA);
127 cv::flip(rgba, rgba, 0);
133 if (!cap.read(frame) || frame.empty()) {
136 cv::cvtColor(frame, rgba, cv::COLOR_BGR2RGBA);
138 cv::flip(rgba, rgba, 0);
144 bool VK_Capture::initializeCuda() {
146 return cudaAvailable;
150 std::cout <<
"mxvk_cv: CUDA init: MXVK_CUDA compile definition is enabled\n";
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";
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";
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",
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;
185 return cudaAvailable;
188 bool VK_Capture::readCuda() {
189 cv::Mat cpuFallbackFrame;
192 if (cudaMappedInput) {
193 if (!cap.read(mappedFrame)) {
196 if (mappedFrame.empty()) {
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);
204 if (!cap.read(frame)) {
211 cpuFallbackFrame = frame;
212 gpuFrame.upload(frame, cuda_stream);
213 cv::cuda::cvtColor(gpuFrame, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
216 if (cudaFlipYForVulkan) {
217 cv::cuda::flip(gpuRgba, gpuVulkanRgba, 0, cuda_stream);
219 gpuVulkanRgba = gpuRgba;
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;
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;
236 pinnedRgba.create(gpuVulkanRgba.size(), gpuVulkanRgba.type());
237 gpuVulkanRgba.download(pinnedRgba, cuda_stream);
238 cuda_stream.waitForCompletion();
240 pinnedRgbaMat = host_mem_mat_header(pinnedRgba);
241 sprite->updateTexture(pinnedRgbaMat.ptr(), pinnedRgbaMat.cols, pinnedRgbaMat.rows,
242 static_cast<int>(pinnedRgbaMat.step));
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;
249 if (cpuFallbackFrame.empty()) {
254 cv::cvtColor(cpuFallbackFrame, rgba, cv::COLOR_BGR2RGBA);
255 sprite->updateTexture(rgba.ptr(), rgba.cols, rgba.rows,
static_cast<int>(rgba.step));
259 bool VK_Capture::readGpuRgba(cv::cuda::GpuMat &rgba,
bool flipY) {
260 if (!initializeCuda()) {
265 if (cudaMappedInput) {
266 if (!cap.read(mappedFrame) || mappedFrame.empty()) {
269 const cv::cuda::GpuMat mappedInput = host_mem_gpu_header(mappedFrame);
270 cv::cuda::cvtColor(mappedInput, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
272 if (!cap.read(frame) || frame.empty()) {
275 gpuFrame.upload(frame, cuda_stream);
276 cv::cuda::cvtColor(gpuFrame, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
280 cv::cuda::flip(gpuRgba, gpuVulkanRgba, 0, cuda_stream);
282 gpuVulkanRgba = gpuRgba;
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;
292 rgba = gpuVulkanRgba;
294 }
catch (
const cv::Exception &e) {
295 std::cout << std::format(
"mxvk_cv: CUDA GpuMat path failed: {}\n", e.what());
296 cudaAvailable =
false;
304 if (initializeCuda()) {
305 cv::Mat cpuFallbackFrame;
308 if (cudaMappedInput) {
309 if (!cap.read(mappedFrame) || mappedFrame.empty()) {
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);
316 if (!cap.read(frame) || frame.empty()) {
319 cpuFallbackFrame = frame;
320 gpuFrame.upload(frame, cuda_stream);
321 cv::cuda::cvtColor(gpuFrame, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
325 cv::cuda::flip(gpuRgba, gpuVulkanRgba, 0, cuda_stream);
327 gpuVulkanRgba = gpuRgba;
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;
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;
344 pinnedRgba.create(gpuVulkanRgba.size(), gpuVulkanRgba.type());
345 gpuVulkanRgba.download(pinnedRgba, cuda_stream);
346 cuda_stream.waitForCompletion();
348 pinnedRgbaMat = host_mem_mat_header(pinnedRgba);
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()) {
358 cv::cvtColor(cpuFallbackFrame, rgba, cv::COLOR_BGR2RGBA);
360 cv::flip(rgba, rgba, 0);
362 return model.
updatePrimaryTexture(rgba.ptr(), rgba.cols, rgba.rows,
static_cast<int>(rgba.step));
370 return model.
updatePrimaryTexture(rgba.ptr(), rgba.cols, rgba.rows,
static_cast<int>(rgba.step));
375 if (initializeCuda()) {
384 if (initializeCuda()) {
385 cv::Mat cpuFallbackFrame;
388 if (cudaMappedInput) {
389 if (!cap.read(mappedFrame) || mappedFrame.empty()) {
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);
396 if (!cap.read(frame) || frame.empty()) {
399 cpuFallbackFrame = frame;
400 gpuFrame.upload(frame, cuda_stream);
401 cv::cuda::cvtColor(gpuFrame, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
405 cv::cuda::flip(gpuRgba, gpuVulkanRgba, 0, cuda_stream);
407 gpuVulkanRgba = gpuRgba;
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;
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;
424 pinnedRgba.create(gpuVulkanRgba.size(), gpuVulkanRgba.type());
425 gpuVulkanRgba.download(pinnedRgba, cuda_stream);
426 cuda_stream.waitForCompletion();
428 pinnedRgbaMat = host_mem_mat_header(pinnedRgba);
429 targetSprite.
updateTexture(pinnedRgbaMat.ptr(), pinnedRgbaMat.cols, pinnedRgbaMat.rows,
430 static_cast<int>(pinnedRgbaMat.step));
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()) {
439 cv::cvtColor(cpuFallbackFrame, rgba, cv::COLOR_BGR2RGBA);
441 cv::flip(rgba, rgba, 0);
443 targetSprite.
updateTexture(rgba.ptr(), rgba.cols, rgba.rows,
static_cast<int>(rgba.step));
448 if (!cap.read(frame) || frame.empty()) {
452 cv::cvtColor(frame, rgba, cv::COLOR_BGR2RGBA);
454 cv::flip(rgba, rgba, 0);
456 targetSprite.
updateTexture(rgba.ptr(), rgba.cols, rgba.rows,
static_cast<int>(rgba.step));
462 if (initializeCuda()) {
466 if (!cap.read(frame)) {
470 cv::cvtColor(frame, rgba, cv::COLOR_BGR2RGBA);
471 sprite->updateTexture(rgba.ptr(), rgba.cols, rgba.rows,
static_cast<int>(rgba.step));
477 sprite->drawSpriteRect(x, y, width, height);
482 sprite->drawSprite(x, y);
486 cap.set(option, value);
490 return cap.get(option);
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)
bool open(const std::string &filename)
Open a video file.
bool reload(size_t width, size_t height, const std::string &vert, const std::string &frag)
Replace the vertex/fragment shaders.
void resetSprite()
Destroy the current sprite and its Vulkan resources.
bool grab()
Decode and discard the next capture frame without conversion.
void close()
Close the capture device.
double get(unsigned int option)
Query a VideoCapture property.
bool readToSprite(VK_Sprite &targetSprite)
bool readRgba(cv::Mat &rgba, bool flipY=false)
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.
void draw(int x, int y, int width, int height)
Draw the current frame at a specified position and size.
bool read()
Capture and upload the next video frame.
void set(unsigned int option, double value)
Set a VideoCapture property.
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.