/* demo-uring.cpp - async file IO with io_uring Copyright (C) 2026, Hermann Vosseler This program is free software; you can redistribute it and/or modify it   under the terms of the GNU GPL version 2+ See the LICENSE file for details. * ************************************************************************/ #include "commons.hpp" #include "egl-display.hpp" #include "gtk-uring-app.hpp" #include #include #include #include #include #include using std::unique_ptr; using std::make_unique; using std::chrono::operator ""ms; //-----------------hard-coded-config-- const size_t RING_SIZ = 5; const string RAW_VIDEO_INPUT_FILE = "demovideo.raw"; const uint VIDEO_WIDTH = 320; const uint VIDEO_HEIGHT = 240; const size_t START_FRAME = 0; const size_t MAX_LOOP_FRAME = 30 * 25; auto calcSrcFrame = [](size_t runningNr){ return START_FRAME + runningNr % MAX_LOOP_FRAME; }; //-----------------hard-coded-config-- class UringVideoPlayer : NonCopyable { using VideoDisplay = EglDisplay; using Buffer = VideoDisplay::Buffer; enum BuffState { FREE, READ, SHOW }; using AtomicBuffState = std::atomic; using BufferAlloc = unique_ptr; using BuffStateAlloc = unique_ptr; /** * Connector to the VideoDisplay for dispatching video data. * @note inherits from Glib::Dispatcher -> signal frame readiness * @remark the callback functions allow the VideoDisplay to interact * with the buffer data and state in a threadsafe manner */ class BufferDispatcher : public VideoDisplay::PixelFeed { Buffer* retrieveFrame() override; void completedFrame() override; UringVideoPlayer& player_; public: BufferDispatcher (UringVideoPlayer& player) : player_{player} { } }; size_t nextRead_{0}; size_t nextShow_{0}; BufferAlloc buffer_{}; BuffStateAlloc buffState_{}; std::atomic_bool playing_{false}; BufferDispatcher dispatchFrame_; VideoDisplay videoDisplayer_; std::thread proactorThread_; public: UringVideoPlayer (Gtk::Window& appWindow) : dispatchFrame_{*this} , videoDisplayer_{appWindow, dispatchFrame_} , proactorThread_{[this]{ playVideo(); }} { } void terminate(); private: void playVideo(); void addNextReadSubmissions (io_uring&, int inputFD); }; /** * Logic of the »Proactor Thread«: * - maintain the working buffers * - open the raw video file to retrieve data * - establish an IO_URING connection for reading * - periodic processing to fill and hand-off buffers */ void UringVideoPlayer::playVideo() { /* ===== initial setup ===== */ int inputFD = open (RAW_VIDEO_INPUT_FILE.c_str(), O_RDONLY); if (inputFD < 0) __FAIL ("open file \""+RAW_VIDEO_INPUT_FILE+"\""); // Allocate several data buffers (used as ring) buffer_ = make_unique (RING_SIZ); buffState_ = make_unique (RING_SIZ); // initialise uRing Kernel interface io_uring uRing; io_uring_queue_init (RING_SIZ+1, &uRing, /*flags*/ 0 ); // Note: one additional »slot« for the timer event(s) // Setup periodic frame beat, firing infinitely const auto TICK_TAG = UINT64_MAX; // special marker for timer events (different from possible frame numbers) auto timespec = asKernelTimespec (40ms); // 40ms ≈ 25fps io_uring_sqe* timerSubmission = io_uring_get_sqe (&uRing); io_uring_prep_timeout (timerSubmission, ×pec, /*no count limit*/ 0, IORING_TIMEOUT_MULTISHOT); io_uring_sqe_set_data64(timerSubmission, TICK_TAG); addNextReadSubmissions (uRing, inputFD); io_uring_submit (&uRing); /* ===== proactor-loop ===== */ close (inputFD); } /** * Instruct the next read operations, * using all free buffer slots ahead. */ void UringVideoPlayer::addNextReadSubmissions (io_uring& uRing, int inputFD) { assert (0 < inputFD); const size_t BUFFSIZ = sizeof(Buffer); while (buffState_[nextRead_ % RING_SIZ] == FREE) { size_t srcOffset = calcSrcFrame (nextRead_); io_uring_sqe* readSubmission = io_uring_get_sqe (&uRing); io_uring_prep_read (readSubmission ,inputFD ,& buffer_[nextRead_ % RING_SIZ] ,BUFFSIZ ,BUFFSIZ * srcOffset ); io_uring_sqe_set_data64 (readSubmission, nextRead_); ++nextRead_; } } UringVideoPlayer::Buffer* UringVideoPlayer::BufferDispatcher::retrieveFrame() { ///////OOO threadsafe hand-over of current frame data } void UringVideoPlayer::BufferDispatcher::completedFrame() { ///////OOO mark end of buffer access } void UringVideoPlayer::terminate() { playing_.store (false, std::memory_order_release); proactorThread_.join(); videoDisplayer_.cleanUp(); } unique_ptr launchPlayer (Gtk::Window& appWindow) { appWindow.resize (VIDEO_WIDTH, VIDEO_HEIGHT); return std::make_unique (appWindow); } int main (int, const char*[]) { return GtkUringApp{"io-uring-demo"} .onClick (launchPlayer) .onClose ([](auto& player){ player.terminate();}) .run(); }