10#include <opencv2/videoio.hpp>
13#ifndef opencv_example_ASSET_DIR
14#define opencv_example_ASSET_DIR "."
17#ifndef opencv_example_SHADER_DIR
18#define opencv_example_SHADER_DIR "."
23 std::string current_path =
".";
24 std::string shader_path;
25 std::string input_filename;
28 bool using_file =
false;
31 int fallback_width = 1280;
32 int fallback_height = 720;
33 double current_fps = 0.0;
34 uint32_t fps_frame_count = 0;
35 std::chrono::steady_clock::time_point fps_sample_time{std::chrono::steady_clock::now()};
36 std::string fps_text =
"FPS: --";
38 [[nodiscard]]
bool openCaptureSource() {
40 return capture.
open(input_filename);
42 return capture.
open(camera_index);
45 [[nodiscard]]
double configureCameraFps() {
46 static constexpr std::array<double, 3> fpsChoices = {60.0, 30.0, 24.0};
48 for (
const double requestedFps : fpsChoices) {
49 capture.
set(cv::CAP_PROP_FPS, requestedFps);
50 const double reportedFps = capture.
get(cv::CAP_PROP_FPS);
51 if (reportedFps > 0.0 && reportedFps + 0.5 >= requestedFps) {
56 capture.
set(cv::CAP_PROP_FPS, fpsChoices.back());
57 const double reportedFps = capture.
get(cv::CAP_PROP_FPS);
58 return (reportedFps > 0.0) ? reportedFps : fpsChoices.back();
61 [[nodiscard]]
bool createOrRefreshCameraSprite() {
62 int frame_width =
static_cast<int>(capture.
get(cv::CAP_PROP_FRAME_WIDTH));
63 int frame_height =
static_cast<int>(capture.
get(cv::CAP_PROP_FRAME_HEIGHT));
64 if (frame_width <= 0 || frame_height <= 0) {
65 frame_width = fallback_width;
66 frame_height = fallback_height;
69 const std::string vertex_shader = shader_path +
"/vertex.vert.spv";
70 const std::string fragment_shader = shader_path +
"/fragment.frag.spv";
72 if (camera_sprite ==
nullptr) {
73 camera_sprite =
createSprite(frame_width, frame_height, vertex_shader, fragment_shader);
74 return camera_sprite !=
nullptr;
77 camera_sprite->
createEmptySprite(frame_width, frame_height, vertex_shader, fragment_shader);
81 [[nodiscard]]
bool uploadFrameToSprite(cv::Mat &frame) {
82 if (camera_sprite ==
nullptr || frame.empty()) {
87 if (frame.channels() == 4) {
88 cv::cvtColor(frame, rgba, cv::COLOR_BGRA2RGBA);
89 }
else if (frame.channels() == 3) {
90 cv::cvtColor(frame, rgba, cv::COLOR_BGR2RGBA);
91 }
else if (frame.channels() == 1) {
92 cv::cvtColor(frame, rgba, cv::COLOR_GRAY2RGBA);
97 camera_sprite->
updateTexture(rgba.ptr(), rgba.cols, rgba.rows,
static_cast<int>(rgba.step));
101 void initializeCameraRendering() {
106 if (!createOrRefreshCameraSprite()) {
107 throw mxvk::Exception(
"opencv_example: failed to create capture sprite");
110 if (camera_sprite !=
nullptr && !capture.
readToSprite(*camera_sprite)) {
111 std::cerr <<
"opencv_example: failed to upload initial camera frame\n";
115 void updateFpsOverlay() {
117 const auto now = std::chrono::steady_clock::now();
118 const double elapsed = std::chrono::duration<double>(now - fps_sample_time).count();
119 if (elapsed >= 0.25) {
120 current_fps =
static_cast<double>(fps_frame_count) / elapsed;
122 fps_sample_time = now;
123 fps_text = std::format(
"FPS: {:.1f}", current_fps);
126 printText(fps_text, 15, 15, SDL_Color{255, 255, 255, 255});
133 setFont(current_path +
"/data/font.ttf", 20);
136 using_file = !input_filename.empty();
137 if (!openCaptureSource()) {
139 throw mxvk::Exception(std::format(
"opencv_example: failed to open video file '{}'", input_filename));
141 throw mxvk::Exception(std::format(
"opencv_example: failed to open camera index {}", camera_index));
143 fallback_width = args.
width;
144 fallback_height = args.
height;
146 capture.set(cv::CAP_PROP_FRAME_WIDTH, fallback_width);
147 capture.set(cv::CAP_PROP_FRAME_HEIGHT, fallback_height);
148 fallback_width = capture.get(cv::CAP_PROP_FRAME_WIDTH);
149 fallback_height = capture.get(cv::CAP_PROP_FRAME_HEIGHT);
150 fallback_height = args.
height;
151 fps = configureCameraFps();
153 fps = capture.get(cv::CAP_PROP_FPS);
155 std::cout <<
"mxvk_cv: Capture opened at: " << fallback_width <<
"x" << fallback_height <<
" @ " << fps <<
" fps\n";
156 initializeCameraRendering();
164 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) {
170 initializeCameraRendering();
174 if (camera_sprite ==
nullptr || !capture.readToSprite(*camera_sprite)) {
177 if (openCaptureSource()) {
178 if (camera_sprite !=
nullptr && !capture.readToSprite(*camera_sprite)) {
179 std::cerr <<
"opencv_example: failed to upload restarted stream frame\n";
183 fps = configureCameraFps();
188 int target_w = fallback_width;
189 int target_h = fallback_height;
196 camera_sprite->drawSpriteRect(0, 0, target_w, target_h);
203int main(
int argc,
char **argv) {
209 std::cerr << std::format(
"mxvk: Exception: {}\n", e.
text());
212 std::cerr << std::format(
"mxvk: Argument Exception: {}\n", e.
text());
Lightweight, header-only, template command-line argument parser.
Arguments proc_args(int &argc, char **argv)
Parse standard libmx2 command-line options from main()'s argv.
Exception thrown by Argz::proc() on unrecognised or malformed options.
ExampleWindow(const Arguments &args, const std::string &text)
void onSwapchainRecreated() override
Called after swapchain and render resources are recreated.
void event(SDL_Event &e) override
Handle one SDL event.
void proc() override
Execute one processing/update step.
~ExampleWindow() override
Vulkan OpenCV video capture source.
bool open(const std::string &filename)
Open a video file.
double get(unsigned int option)
Query a VideoCapture property.
bool readToSprite(VK_Sprite &targetSprite)
void set(unsigned int option, double value)
Set a VideoCapture property.
void updateTexture(SDL_Surface *surface)
Replace the sprite texture from an SDL_Surface.
void createEmptySprite(int width, int height, const std::string &vertexShaderPath="", const std::string &fragmentShaderPath="")
Create a blank (un-initialised) sprite texture.
Main Vulkan window wrapper for MXVK.
void loop()
Run the main event/render loop.
VK_Sprite * createSprite(const std::string &pngPath, const std::string &vertexShaderPath="", const std::string &fragmentShaderPath="")
Create a sprite from a PNG file and register it with this window.
VkExtent2D swapchain_extent
void createDevice()
Create final device resources.
void exit()
Request loop termination.
VkCommandPool command_pool
VK_Window()=default
Construct an empty window object.
void setFont(const std::string &fontPath, int fontSize=24)
Set the active text-render font.
void printText(const std::string &text, int x, int y, const SDL_Color &col)
Queue a text string for rendering during the current frame.
#define opencv_example_ASSET_DIR
OpenCV video-capture integration for the Vulkan backend.
Utilities for loading and saving PNG images.
Plain data structure returned by proc_args() with all common libmx2 CLI options.
std::string shaderPath
Optional SPV shader folder path (-S / --shader-path).
int camera_index
Optional camera index.
int height
Viewport height in pixels (default: 720).
std::string filename
Optional input filename (--filename).
std::string path
Asset root; proc_args() defaults it to the executable directory.
int width
Viewport width in pixels (default: 1280).