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";
44 bool VK_Capture::createImage(VkDevice device, VkPhysicalDevice physDev, VkQueue gQueue, VkCommandPool cmdPool,
size_t width,
size_t height,
const std::string &vert,
const std::string &frag) {
45 sprite = std::make_unique<VK_Sprite>(device, physDev, gQueue, cmdPool);
46 sprite->createEmptySprite(
static_cast<int>(width),
static_cast<int>(height), vert, frag);
47 sprite->enableExtendedUBO();
48 sprite->rebuildPipeline();
49 std::cout << std::format(
"mxvk_cv: Sprite created: {}x{}\n", width, height);
53 bool VK_Capture::reload(
size_t width,
size_t height,
const std::string &vert,
const std::string &frag) {
55 sprite->createEmptySprite(
static_cast<int>(width),
static_cast<int>(height), vert, frag);
56 sprite->enableExtendedUBO();
57 std::cout << std::format(
"mxvk_cv: Shader reloaded: vert={} frag={}\n", std::filesystem::path(vert).filename().
string(), std::filesystem::path(frag).filename().
string());
60 std::cout <<
"mxvk_cv: Reload failed: no sprite\n";
70 cv::Mat cpuFallbackFrame;
73 if (initializeCuda()) {
74 if (cudaMappedInput) {
75 if (!cap.read(mappedFrame) || mappedFrame.empty()) {
78 cpuFallbackFrame = host_mem_mat_header(mappedFrame);
79 const cv::cuda::GpuMat mappedInput = host_mem_gpu_header(mappedFrame);
80 cv::cuda::cvtColor(mappedInput, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
82 if (!cap.read(frame) || frame.empty()) {
85 cpuFallbackFrame = frame;
86 gpuFrame.upload(frame, cuda_stream);
87 cv::cuda::cvtColor(gpuFrame, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
91 cv::cuda::flip(gpuRgba, gpuVulkanRgba, 0, cuda_stream);
93 gpuVulkanRgba = gpuRgba;
96 if (!cudaPipelineLogged) {
97 std::cout << std::format(
"mxvk_cv: CUDA RGBA path active: capture -> GpuMat -> cv::cuda::cvtColor(BGR to RGBA) -> {} -> download\n", flipY ?
"cv::cuda::flip(Y)" :
"no Y flip");
98 cudaPipelineLogged =
true;
101 pinnedRgba.create(gpuVulkanRgba.size(), gpuVulkanRgba.type());
102 gpuVulkanRgba.download(pinnedRgba, cuda_stream);
103 cuda_stream.waitForCompletion();
104 rgba = host_mem_mat_header(pinnedRgba);
107 }
catch (
const cv::Exception &e) {
108 std::cout << std::format(
"mxvk_cv: CUDA RGBA path failed; falling back to CPU path: {}\n", e.what());
109 cudaAvailable =
false;
110 if (cpuFallbackFrame.empty()) {
113 cv::cvtColor(cpuFallbackFrame, rgba, cv::COLOR_BGR2RGBA);
115 cv::flip(rgba, rgba, 0);
121 if (!cap.read(frame) || frame.empty()) {
124 cv::cvtColor(frame, rgba, cv::COLOR_BGR2RGBA);
126 cv::flip(rgba, rgba, 0);
132 bool VK_Capture::initializeCuda() {
134 return cudaAvailable;
138 std::cout <<
"mxvk_cv: CUDA init: MXVK_CUDA compile definition is enabled\n";
140 const int deviceCount = cv::cuda::getCudaEnabledDeviceCount();
141 std::cout << std::format(
"mxvk_cv: CUDA init: OpenCV reports {} CUDA-enabled device(s)\n", deviceCount);
142 if (deviceCount <= 0) {
143 std::cout <<
"mxvk_cv: CUDA path unavailable: no CUDA-enabled OpenCV device\n";
147 cv::cuda::setDevice(0);
148 const cv::cuda::DeviceInfo deviceInfo(0);
149 cudaMappedInput = deviceInfo.canMapHostMemory();
150 if (
const char *flipEnv = std::getenv(
"MXVK_CUDA_FLIP_Y")) {
151 cudaFlipYForVulkan = std::string(flipEnv) !=
"0";
153 cudaAvailable =
true;
154 std::cout << std::format(
"mxvk_cv: CUDA init: selected device 0: {}\n", deviceInfo.name());
155 std::cout << std::format(
"mxvk_cv: CUDA init: compute capability {}.{}\n", deviceInfo.majorVersion(), deviceInfo.minorVersion());
156 std::cout << std::format(
"mxvk_cv: CUDA init: unified addressing={}, host memory mapping={}\n", deviceInfo.unifiedAddressing() ?
"true" :
"false", cudaMappedInput ?
"true" :
"false");
157 std::cout << std::format(
"mxvk_cv: CUDA init: capture input mode={}\n", cudaMappedInput ?
"mapped HostMem::SHARED GpuMat header" :
"cv::Mat upload to GpuMat");
158 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", cudaFlipYForVulkan ?
"enabled" :
"disabled");
159 std::cout << std::format(
"mxvk_cv: CUDA path enabled on {} ({})\n", deviceInfo.name(), cudaMappedInput ?
"mapped zero-copy input" :
"pinned async input upload");
160 std::cout <<
"mxvk_cv: CUDA pipeline: capture -> GpuMat -> cv::cuda::cvtColor(BGR to RGBA) -> direct Vulkan texture when available\n";
161 }
catch (
const cv::Exception &e) {
162 std::cout << std::format(
"mxvk_cv: CUDA path unavailable: {}\n", e.what());
163 cudaAvailable =
false;
166 return cudaAvailable;
169 bool VK_Capture::readCuda() {
170 cv::Mat cpuFallbackFrame;
173 if (cudaMappedInput) {
174 if (!cap.read(mappedFrame)) {
177 if (mappedFrame.empty()) {
181 cpuFallbackFrame = host_mem_mat_header(mappedFrame);
182 const cv::cuda::GpuMat mappedInput = host_mem_gpu_header(mappedFrame);
183 cv::cuda::cvtColor(mappedInput, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
185 if (!cap.read(frame)) {
192 cpuFallbackFrame = frame;
193 gpuFrame.upload(frame, cuda_stream);
194 cv::cuda::cvtColor(gpuFrame, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
197 if (cudaFlipYForVulkan) {
198 cv::cuda::flip(gpuRgba, gpuVulkanRgba, 0, cuda_stream);
200 gpuVulkanRgba = gpuRgba;
203 if (sprite && sprite->updateTextureCuda(gpuVulkanRgba, cuda_stream)) {
204 if (!cudaPipelineLogged) {
205 std::cout << std::format(
"mxvk_cv: CUDA interop active: RGBA GpuMat -> {} -> Vulkan external-memory image\n", cudaFlipYForVulkan ?
"cv::cuda::flip(Y)" :
"no Y flip");
206 cudaPipelineLogged =
true;
211 if (!cudaPipelineLogged) {
212 std::cout <<
"mxvk_cv: CUDA interop unavailable for this sprite; using pinned CUDA download -> Vulkan staging upload\n";
213 cudaPipelineLogged =
true;
215 pinnedRgba.create(gpuVulkanRgba.size(), gpuVulkanRgba.type());
216 gpuVulkanRgba.download(pinnedRgba, cuda_stream);
217 cuda_stream.waitForCompletion();
219 pinnedRgbaMat = host_mem_mat_header(pinnedRgba);
220 sprite->updateTexture(pinnedRgbaMat.ptr(), pinnedRgbaMat.cols, pinnedRgbaMat.rows,
static_cast<int>(pinnedRgbaMat.step));
222 }
catch (
const cv::Exception &e) {
223 std::cout << std::format(
"mxvk_cv: CUDA frame path failed; falling back to CPU path: {}\n", e.what());
224 cudaAvailable =
false;
227 if (cpuFallbackFrame.empty()) {
232 cv::cvtColor(cpuFallbackFrame, rgba, cv::COLOR_BGR2RGBA);
233 sprite->updateTexture(rgba.ptr(), rgba.cols, rgba.rows,
static_cast<int>(rgba.step));
237 bool VK_Capture::readGpuRgba(cv::cuda::GpuMat &rgba,
bool flipY) {
238 if (!initializeCuda()) {
243 if (cudaMappedInput) {
244 if (!cap.read(mappedFrame) || mappedFrame.empty()) {
247 const cv::cuda::GpuMat mappedInput = host_mem_gpu_header(mappedFrame);
248 cv::cuda::cvtColor(mappedInput, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
250 if (!cap.read(frame) || frame.empty()) {
253 gpuFrame.upload(frame, cuda_stream);
254 cv::cuda::cvtColor(gpuFrame, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
258 cv::cuda::flip(gpuRgba, gpuVulkanRgba, 0, cuda_stream);
260 gpuVulkanRgba = gpuRgba;
263 if (!cudaPipelineLogged) {
264 std::cout << std::format(
"mxvk_cv: CUDA GpuMat path active: capture -> GpuMat -> cv::cuda::cvtColor(BGR to RGBA) -> {} -> resident GpuMat for caller\n", flipY ?
"cv::cuda::flip(Y)" :
"no Y flip");
265 cudaPipelineLogged =
true;
268 rgba = gpuVulkanRgba;
270 }
catch (
const cv::Exception &e) {
271 std::cout << std::format(
"mxvk_cv: CUDA GpuMat path failed: {}\n", e.what());
272 cudaAvailable =
false;
280 if (initializeCuda()) {
281 cv::Mat cpuFallbackFrame;
284 if (cudaMappedInput) {
285 if (!cap.read(mappedFrame) || mappedFrame.empty()) {
288 cpuFallbackFrame = host_mem_mat_header(mappedFrame);
289 const cv::cuda::GpuMat mappedInput = host_mem_gpu_header(mappedFrame);
290 cv::cuda::cvtColor(mappedInput, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
292 if (!cap.read(frame) || frame.empty()) {
295 cpuFallbackFrame = frame;
296 gpuFrame.upload(frame, cuda_stream);
297 cv::cuda::cvtColor(gpuFrame, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
301 cv::cuda::flip(gpuRgba, gpuVulkanRgba, 0, cuda_stream);
303 gpuVulkanRgba = gpuRgba;
306 if (
model.updatePrimaryTextureCuda(gpuVulkanRgba, cuda_stream)) {
307 if (!cudaPipelineLogged) {
308 std::cout << std::format(
"mxvk_cv: CUDA interop active for model texture: RGBA GpuMat -> {} -> Vulkan external-memory image array (no download)\n", flipY ?
"cv::cuda::flip(Y)" :
"no Y flip");
309 cudaPipelineLogged =
true;
314 if (!cudaPipelineLogged) {
315 std::cout <<
"mxvk_cv: CUDA interop unavailable for model texture; using pinned CUDA download -> Vulkan staging upload\n";
316 cudaPipelineLogged =
true;
318 pinnedRgba.create(gpuVulkanRgba.size(), gpuVulkanRgba.type());
319 gpuVulkanRgba.download(pinnedRgba, cuda_stream);
320 cuda_stream.waitForCompletion();
322 pinnedRgbaMat = host_mem_mat_header(pinnedRgba);
323 return model.updatePrimaryTexture(pinnedRgbaMat.ptr(), pinnedRgbaMat.cols, pinnedRgbaMat.rows,
static_cast<int>(pinnedRgbaMat.step));
324 }
catch (
const cv::Exception &e) {
325 std::cout << std::format(
"mxvk_cv: CUDA model-texture path failed; falling back to CPU path: {}\n", e.what());
326 cudaAvailable =
false;
327 if (cpuFallbackFrame.empty()) {
331 cv::cvtColor(cpuFallbackFrame, rgba, cv::COLOR_BGR2RGBA);
333 cv::flip(rgba, rgba, 0);
335 return model.updatePrimaryTexture(rgba.ptr(), rgba.cols, rgba.rows,
static_cast<int>(rgba.step));
343 return model.updatePrimaryTexture(rgba.ptr(), rgba.cols, rgba.rows,
static_cast<int>(rgba.step));
348 if (initializeCuda()) {
357 if (initializeCuda()) {
358 cv::Mat cpuFallbackFrame;
361 if (cudaMappedInput) {
362 if (!cap.read(mappedFrame) || mappedFrame.empty()) {
365 cpuFallbackFrame = host_mem_mat_header(mappedFrame);
366 const cv::cuda::GpuMat mappedInput = host_mem_gpu_header(mappedFrame);
367 cv::cuda::cvtColor(mappedInput, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
369 if (!cap.read(frame) || frame.empty()) {
372 cpuFallbackFrame = frame;
373 gpuFrame.upload(frame, cuda_stream);
374 cv::cuda::cvtColor(gpuFrame, gpuRgba, cv::COLOR_BGR2RGBA, 0, cuda_stream);
378 cv::cuda::flip(gpuRgba, gpuVulkanRgba, 0, cuda_stream);
380 gpuVulkanRgba = gpuRgba;
383 if (targetSprite.updateTextureCuda(gpuVulkanRgba, cuda_stream)) {
384 if (!cudaPipelineLogged) {
385 std::cout << std::format(
"mxvk_cv: CUDA interop active for external sprite: RGBA GpuMat -> {} -> Vulkan external-memory image\n", flipY ?
"cv::cuda::flip(Y)" :
"no Y flip");
386 cudaPipelineLogged =
true;
391 if (!cudaPipelineLogged) {
392 std::cout <<
"mxvk_cv: CUDA interop unavailable for external sprite; using pinned CUDA download -> Vulkan staging upload\n";
393 cudaPipelineLogged =
true;
395 pinnedRgba.create(gpuVulkanRgba.size(), gpuVulkanRgba.type());
396 gpuVulkanRgba.download(pinnedRgba, cuda_stream);
397 cuda_stream.waitForCompletion();
399 pinnedRgbaMat = host_mem_mat_header(pinnedRgba);
400 targetSprite.
updateTexture(pinnedRgbaMat.ptr(), pinnedRgbaMat.cols, pinnedRgbaMat.rows,
static_cast<int>(pinnedRgbaMat.step));
402 }
catch (
const cv::Exception &e) {
403 std::cout << std::format(
"mxvk_cv: CUDA external-sprite path failed; falling back to CPU path: {}\n", e.what());
404 cudaAvailable =
false;
405 if (cpuFallbackFrame.empty()) {
409 cv::cvtColor(cpuFallbackFrame, rgba, cv::COLOR_BGR2RGBA);
411 cv::flip(rgba, rgba, 0);
413 targetSprite.
updateTexture(rgba.ptr(), rgba.cols, rgba.rows,
static_cast<int>(rgba.step));
418 if (!cap.read(frame) || frame.empty()) {
422 cv::cvtColor(frame, rgba, cv::COLOR_BGR2RGBA);
424 cv::flip(rgba, rgba, 0);
426 targetSprite.
updateTexture(rgba.ptr(), rgba.cols, rgba.rows,
static_cast<int>(rgba.step));
432 if (initializeCuda()) {
436 if (!cap.read(frame)) {
440 cv::cvtColor(frame, rgba, cv::COLOR_BGR2RGBA);
441 sprite->updateTexture(rgba.ptr(), rgba.cols, rgba.rows,
static_cast<int>(rgba.step));
447 sprite->drawSpriteRect(x, y, width, height);
452 sprite->drawSprite(x, y);
Convenience wrapper that owns mesh, textures, descriptors, and pipeline state.
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.