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);
76 cv::Mat cpuFallbackFrame;
79 if (initializeCuda()) {
80 if (cudaMappedInput) {
81 if (!cap.read(mappedFrame) || mappedFrame.empty()) {
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);
88 if (!cap.read(frame) || frame.empty()) {
91 cpuFallbackFrame = frame;
92 gpuFrame.upload(frame, cuda_stream);
93 cv::cuda::cvtColor(gpuFrame, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
97 cv::cuda::flip(gpuRgba, gpuVulkanRgba, 0, cuda_stream);
99 gpuVulkanRgba = gpuRgba;
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;
109 pinnedRgba.create(gpuVulkanRgba.size(), gpuVulkanRgba.type());
110 gpuVulkanRgba.download(pinnedRgba, cuda_stream);
111 cuda_stream.waitForCompletion();
112 rgba = host_mem_mat_header(pinnedRgba);
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()) {
121 cv::cvtColor(cpuFallbackFrame, rgba, cv::COLOR_BGR2RGBA);
123 cv::flip(rgba, rgba, 0);
129 if (!cap.read(frame) || frame.empty()) {
132 cv::cvtColor(frame, rgba, cv::COLOR_BGR2RGBA);
134 cv::flip(rgba, rgba, 0);
140 bool VK_Capture::initializeCuda() {
142 return cudaAvailable;
146 std::cout <<
"mxvk_cv: CUDA init: MXVK_CUDA compile definition is enabled\n";
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";
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";
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",
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;
181 return cudaAvailable;
184 bool VK_Capture::readCuda() {
185 cv::Mat cpuFallbackFrame;
188 if (cudaMappedInput) {
189 if (!cap.read(mappedFrame)) {
192 if (mappedFrame.empty()) {
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);
200 if (!cap.read(frame)) {
207 cpuFallbackFrame = frame;
208 gpuFrame.upload(frame, cuda_stream);
209 cv::cuda::cvtColor(gpuFrame, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
212 if (cudaFlipYForVulkan) {
213 cv::cuda::flip(gpuRgba, gpuVulkanRgba, 0, cuda_stream);
215 gpuVulkanRgba = gpuRgba;
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;
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;
232 pinnedRgba.create(gpuVulkanRgba.size(), gpuVulkanRgba.type());
233 gpuVulkanRgba.download(pinnedRgba, cuda_stream);
234 cuda_stream.waitForCompletion();
236 pinnedRgbaMat = host_mem_mat_header(pinnedRgba);
237 sprite->updateTexture(pinnedRgbaMat.ptr(), pinnedRgbaMat.cols, pinnedRgbaMat.rows,
238 static_cast<int>(pinnedRgbaMat.step));
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;
245 if (cpuFallbackFrame.empty()) {
250 cv::cvtColor(cpuFallbackFrame, rgba, cv::COLOR_BGR2RGBA);
251 sprite->updateTexture(rgba.ptr(), rgba.cols, rgba.rows,
static_cast<int>(rgba.step));
255 bool VK_Capture::readGpuRgba(cv::cuda::GpuMat &rgba,
bool flipY) {
256 if (!initializeCuda()) {
261 if (cudaMappedInput) {
262 if (!cap.read(mappedFrame) || mappedFrame.empty()) {
265 const cv::cuda::GpuMat mappedInput = host_mem_gpu_header(mappedFrame);
266 cv::cuda::cvtColor(mappedInput, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
268 if (!cap.read(frame) || frame.empty()) {
271 gpuFrame.upload(frame, cuda_stream);
272 cv::cuda::cvtColor(gpuFrame, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
276 cv::cuda::flip(gpuRgba, gpuVulkanRgba, 0, cuda_stream);
278 gpuVulkanRgba = gpuRgba;
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;
288 rgba = gpuVulkanRgba;
290 }
catch (
const cv::Exception &e) {
291 std::cout << std::format(
"mxvk_cv: CUDA GpuMat path failed: {}\n", e.what());
292 cudaAvailable =
false;
300 if (initializeCuda()) {
301 cv::Mat cpuFallbackFrame;
304 if (cudaMappedInput) {
305 if (!cap.read(mappedFrame) || mappedFrame.empty()) {
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);
312 if (!cap.read(frame) || frame.empty()) {
315 cpuFallbackFrame = frame;
316 gpuFrame.upload(frame, cuda_stream);
317 cv::cuda::cvtColor(gpuFrame, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
321 cv::cuda::flip(gpuRgba, gpuVulkanRgba, 0, cuda_stream);
323 gpuVulkanRgba = gpuRgba;
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;
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;
340 pinnedRgba.create(gpuVulkanRgba.size(), gpuVulkanRgba.type());
341 gpuVulkanRgba.download(pinnedRgba, cuda_stream);
342 cuda_stream.waitForCompletion();
344 pinnedRgbaMat = host_mem_mat_header(pinnedRgba);
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()) {
354 cv::cvtColor(cpuFallbackFrame, rgba, cv::COLOR_BGR2RGBA);
356 cv::flip(rgba, rgba, 0);
358 return model.
updatePrimaryTexture(rgba.ptr(), rgba.cols, rgba.rows,
static_cast<int>(rgba.step));
366 return model.
updatePrimaryTexture(rgba.ptr(), rgba.cols, rgba.rows,
static_cast<int>(rgba.step));
371 if (initializeCuda()) {
380 if (initializeCuda()) {
381 cv::Mat cpuFallbackFrame;
384 if (cudaMappedInput) {
385 if (!cap.read(mappedFrame) || mappedFrame.empty()) {
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);
392 if (!cap.read(frame) || frame.empty()) {
395 cpuFallbackFrame = frame;
396 gpuFrame.upload(frame, cuda_stream);
397 cv::cuda::cvtColor(gpuFrame, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
401 cv::cuda::flip(gpuRgba, gpuVulkanRgba, 0, cuda_stream);
403 gpuVulkanRgba = gpuRgba;
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;
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;
420 pinnedRgba.create(gpuVulkanRgba.size(), gpuVulkanRgba.type());
421 gpuVulkanRgba.download(pinnedRgba, cuda_stream);
422 cuda_stream.waitForCompletion();
424 pinnedRgbaMat = host_mem_mat_header(pinnedRgba);
425 targetSprite.
updateTexture(pinnedRgbaMat.ptr(), pinnedRgbaMat.cols, pinnedRgbaMat.rows,
426 static_cast<int>(pinnedRgbaMat.step));
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()) {
435 cv::cvtColor(cpuFallbackFrame, rgba, cv::COLOR_BGR2RGBA);
437 cv::flip(rgba, rgba, 0);
439 targetSprite.
updateTexture(rgba.ptr(), rgba.cols, rgba.rows,
static_cast<int>(rgba.step));
444 if (!cap.read(frame) || frame.empty()) {
448 cv::cvtColor(frame, rgba, cv::COLOR_BGR2RGBA);
450 cv::flip(rgba, rgba, 0);
452 targetSprite.
updateTexture(rgba.ptr(), rgba.cols, rgba.rows,
static_cast<int>(rgba.step));
458 if (initializeCuda()) {
462 if (!cap.read(frame)) {
466 cv::cvtColor(frame, rgba, cv::COLOR_BGR2RGBA);
467 sprite->updateTexture(rgba.ptr(), rgba.cols, rgba.rows,
static_cast<int>(rgba.step));
473 sprite->drawSpriteRect(x, y, width, height);
478 sprite->drawSprite(x, y);
482 cap.set(option, value);
486 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.
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.