diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ede19f..03c136c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,12 @@ cmake_minimum_required(VERSION 3.28) -project(led-matrix-zmq-server) +project(led-matrix-zmq-server + LANGUAGES CXX +) option(BUILD_SERVER "Build the server" ON) option(BUILD_TOOLS "Build tools" ON) +option(BUILD_VIRTUAL "Build virtual server" OFF) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) @@ -59,3 +62,16 @@ if (BUILD_TOOLS) target_link_libraries(led-matrix-zmq-pipe PRIVATE argparse plog zmq) target_compile_features(led-matrix-zmq-pipe PRIVATE ${COMPILE_FEATURES}) endif() + +if (BUILD_VIRTUAL) + find_package(SDL2 REQUIRED) + + add_executable(led-matrix-zmq-virtual src/virtual_main.cpp) + target_link_libraries(led-matrix-zmq-virtual PRIVATE + argparse + plog + SDL2::SDL2 + zmq + ) + target_compile_features(led-matrix-zmq-virtual PRIVATE ${COMPILE_FEATURES}) +endif() diff --git a/src/virtual_main.cpp b/src/virtual_main.cpp new file mode 100644 index 0000000..4d1893d --- /dev/null +++ b/src/virtual_main.cpp @@ -0,0 +1,104 @@ +#include +#include + +#include +#include +#include + +#include "consts.hpp" + +class Options { +public: + std::string frame_endpoint; + int width; + int height; + int scale; + + static Options from_args(int argc, char *argv[]) { + argparse::ArgumentParser parser("led-matrix-zmq-virtual"); + + parser.add_argument("--width").default_value(32).scan<'i', int>(); + parser.add_argument("--height").default_value(32).scan<'i', int>(); + parser.add_argument("--scale").default_value(-1).scan<'i', int>(); + parser.add_argument("--frame-endpoint").default_value(consts::default_frame_endpoint); + + parser.parse_args(argc, argv); + + return Options{ + .frame_endpoint = parser.get("--frame-endpoint"), + .width = parser.get("--width"), + .height = parser.get("--height"), + .scale = parser.get("--scale"), + }; + ; + } +}; +; + +int main(int argc, char *argv[]) { + const auto options = Options::from_args(argc, argv); + + SDL_Init(SDL_INIT_VIDEO); + + auto scale = options.scale; + if (options.scale < 0) { + SDL_Rect bounds; + SDL_GetDisplayBounds(0, &bounds); + + bounds.w *= 0.8; + bounds.h *= 0.8; + + scale = std::min(bounds.w / options.width, bounds.h / options.height); + } + + const auto win_width = options.width * scale; + const auto win_height = options.height * scale; + + auto *window = SDL_CreateWindow("led-matrix-zmq-virtual", SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, win_width, win_height, 0); + auto *renderer = SDL_CreateRenderer(window, -1, 0); + auto *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, + options.width, options.height); + + zmq::context_t zmq_ctx; + zmq::socket_t zmq_sock(zmq_ctx, zmq::socket_type::rep); + zmq_sock.bind(options.frame_endpoint); + + auto running = true; + while (running) { + SDL_Event event; + while (SDL_PollEvent(&event)) { + if (event.type == SDL_QUIT) { + running = false; + } + } + + void *tex_pixels; + int tex_pitch; + SDL_LockTexture(texture, nullptr, &tex_pixels, &tex_pitch); + + zmq::message_t req; + static_cast(zmq_sock.recv(req, zmq::recv_flags::none)); + zmq_sock.send(zmq::message_t(), zmq::send_flags::none); + + if (req.size() != options.width * options.height * consts::pixel_size) { + throw std::runtime_error("Invalid frame size"); + } + + std::memcpy(tex_pixels, req.data(), req.size()); + + SDL_UnlockTexture(texture); + SDL_RenderCopy(renderer, texture, nullptr, nullptr); + SDL_RenderPresent(renderer); + } + + zmq_sock.close(); + zmq_ctx.close(); + + SDL_DestroyTexture(texture); + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); + + return 0; +}