diff --git a/FrOSCon26/FrOSCon.26.mm b/FrOSCon26/FrOSCon.26.mm index 43cf312..e4f728f 100644 --- a/FrOSCon26/FrOSCon.26.mm +++ b/FrOSCon26/FrOSCon.26.mm @@ -369,16 +369,13 @@ - - - +

IO_URING kann errno nicht sinnvoll direkt verwenden (errno ist eine globale variable)

- -
+
@@ -843,11 +840,12 @@ - + + - + @@ -894,8 +892,8 @@ - - + + @@ -944,13 +942,24 @@ - - - - + + + + + + + + + + + + + - + + + @@ -1411,9 +1420,7 @@ - - - +
void onTick()
   {
@@ -1434,8 +1441,7 @@
       
     
- -
+ @@ -1863,7 +1869,8 @@
- + + @@ -1951,7 +1958,9 @@ - + + + @@ -1962,6 +1971,7 @@ + @@ -1975,9 +1985,7 @@ - - - +

...es war nicht so, daß Claude diese „Idee gehabt“ hätte — denn ich hatte die Konversation ja so geführt, daß Claude mir meine Konzept-Ansätze validiert und verfeinert. Dann habe ich das Prinzip formuliert, daß für Async-IO irgendwo eine State-Machine sein muß. Claude hat das aufgegriffen, validiert und verschärft; und damit war offensichtlich geworden, daß man diese State-Flag als Atomic auslegen kann. Das habe ich in dem Moment genauso »offensichtlich« gesehen, und Claude hat es ebenso formuliert. @@ -1997,8 +2005,7 @@ ⟹ das wirft ein Licht darauf, wie dieses scheinbar intelligente »Generieren« funktioniert, auf Basis einer industriellen Daten-Technik, die Plausibilitäten integriert und interpoliert.

- -
+
@@ -2006,27 +2013,24 @@ - - + + - + - - - +

hier muß auch die Backpressure beachtet und realisiert werden

- -
+ @@ -2035,102 +2039,82 @@ - - - +

Sofern er aus dem GUI-Thread alloziert wird, also bevor der Proactor-Thread startet, ist die Position für beide Threads sichtbar (und unveränderlich)

- -
+ - - - +

Diese wäre dann ebenfalls teil der cross-Trade-Synchronisation (ebenfalls bewirkt durch release-Semantik auf dem State-Atomic)

- -
+
- - - +

...nur als lokale Variable, die er jeweils hochzählt, wenn er einen Trigger bekommt (Nachteil: duplizierter State, dessen Konsistenz sich nur implizit ergibt und nicht offensichtlich ist)

- -
+
- - - +

Das wäre dann ein Pointer (const void *); seine Sichtbarkeit ergibt sich auch hier aus dem atomic-State

- -
+ - - - +

...da ich immer in Zuständigkeiten denke, ist es mir unsympatisch, einen Satz gemeinsamer Zustands-Variablen zu verwenden

- -
+
- + + - - - +

...das gilt für ein Prod-System, in dem der Speicher framentiert ist, und man vermutlich Buffer-Pools hat

- -
+
- - - +

Für die Demo ist das die offensichtliche Lösung: man bekommt unique-ownership und Subscript-Operator einfach vom std::unique_ptr. Wenn man den notwendigen Speicher nicht bekommt, dann läuft die Demo eh nicht, also wozu die Umstände?

- -
+
@@ -2165,16 +2149,13 @@ - - - +

heißt: hier gibt es keine Möglichkeit, ein Auge zuzudrücken

- -
+
@@ -2188,16 +2169,13 @@
- - - +

Eine Operation zum Zugriff und eine Operation um den abgeschlossenen Zugriff zu markieren; zudem gibt es auch noch den Dispatcher.

- -
+
@@ -2215,16 +2193,13 @@ - - - +

Mehrere Argumente dafür: man könnte die low-level-Flags setzen (wie O_CLOEXEX, O_NOFOLLOW, oder gar O_DIRECT). Das Öffnen ist atomic,  d.h. das File kann nicht mehr gelöscht/verschoben werden beform man das Filehandle hat (was bei einem getrennten STAT und Path-Check durchaus möglich ist)

- -
+
@@ -2236,9 +2211,7 @@ - - - +

Begründung @@ -2261,8 +2234,7 @@ - - + @@ -2270,8 +2242,8 @@ - - + + @@ -2300,23 +2272,18 @@ - - - +

verwende hier den zweiten Cursor: nextShow_

- -
+
- - - +

...und zwar via Glib::Dispatcher ⟶ GUI-Thread. @@ -2325,21 +2292,17 @@ Der zugehörige Handler-Code läuft dann bei nächster Gelegenheit in der GUI-Event-Loop, und holt sich per Callback (Interface PixelFeed) die Daten; in diesem Callback muß die Read-Barrier realisiert sein, damit der GUI-Thread alle vor diesem Punkt erfolgten Datenänderungen auch tatsächlich sieht

- -
+
- - - +

Beachte: das darf erst hier passieren, weil wir hier den nächst niedersten erwarteten Frame anschauen; da wir immer etwas im Voraus lesen, werden wir auch ein end-of-File schon einige Ticks früher markieren, müssen aber die Schleife noch solange weiterlaufen lassen, bis die letzten erfolgreich gelesenen Frames in die Anzeige übertragen wurden

- -
+
@@ -2350,34 +2313,29 @@ - - - +

brauche doch einen 3.Cursor: currShow_

- -
+
- + + - - - +

also brauche ich die cross-Thread-Barriere erst hier

- -
+
@@ -2409,48 +2367,75 @@ - - - +

also keine explizite Steuerung

- -
+
- - - +

ist jeweils nur möglich wenn der dafür vorgesehene Buffer bereits frei geworden ist

- -
+
- - - +

Ist nur möglich wenn diese Daten für den erwartungsgemäß nächsten Frame bereits da sind, und der unmittelbar vorausgegangene Frame schon komplett vom Video-Displayer übernommen wurde

+
+
+
+
+
+
+ + + + + + + + + + + + + +

+ im State SHOW pasiert jetzt gar nicht mehr das Display, sondern es bedeutet nun, daß der Frame weitergegeben werden muß +

+
+ +
+ + + + +

+ suche einen 4-Buchstaben-Namen, da bin ich Ästhet. PUSH wäre auch denkbar, natürlich wäre PROPAGATE viel korrekter ;-) +

+ +
-
+ +
diff --git a/FrOSCon26/examples/demo-uring.cpp b/FrOSCon26/examples/demo-uring.cpp index 54bf4ea..483e4d7 100644 --- a/FrOSCon26/examples/demo-uring.cpp +++ b/FrOSCon26/examples/demo-uring.cpp @@ -40,13 +40,32 @@ auto calcSrcFrame = [](size_t runningNr){ return START_FRAME + runningNr % MAX_L //-----------------hard-coded-config-- +/** + * Read video frames from a raw video file and pass them to OpenGL display. + * Using a »Proactor« thread to coordinate reading via IO_URING. + * Allocates a set of frame-sized buffers (using #RING_SIZ buffers) + * and uses them cyclically, first adding new tasks to read a frame-block + * from the file, receiving completed tasks to hand-over the next frame + * driven by a frame-beat timer, that is also based on IO_URING. + * Each buffer »slot« is associated with a #BuffState flag, implemented + * as `std::atomic` to also serve for cross-thread visibility. + * @note UringVideoPlayer must be constructed in the GTK UI thread, + * yet will immediately launch the »Proactor« thread from the ctor. + * @remarks + * - the source video and layout is hard wired + * - playback will either be looped (if the loop fits into the source video) + * - otherwise, when encountering end-of-file, or when the GTK window is closed, + * the »Proactor« thread will terminate. + * - frames will be submitted to display at the regular timer beats, + * whenever they are ready; frames delayed by IO will slip to the next beat. + */ class UringVideoPlayer : NonCopyable { using VideoDisplay = EglDisplay; using Buffer = VideoDisplay::Buffer; - enum BuffState { FREE=0, READ, SHOW, WAIT, STOP }; + enum BuffState { FREE, READ, FEED, SHOW, STOP }; using AtomicBuffState = std::atomic; using BufferAlloc = unique_ptr; @@ -65,10 +84,15 @@ class UringVideoPlayer void completedFrame() override; UringVideoPlayer& player_; + public: BufferDispatcher (UringVideoPlayer& player) : player_{player} { } + + private: + Buffer& getCurrentFrame(); + AtomicBuffState& getCurrentFrameState(); }; size_t nextRead_{0}; @@ -108,37 +132,37 @@ class UringVideoPlayer void UringVideoPlayer::playVideo() { - /* ===== initial setup ===== */ + /* =============== 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); + 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) + 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); // 25fps ≙ 40ms per frame - io_uring_sqe* timerSubmission = io_uring_get_sqe (&uRing); + const auto TICK_TAG = UINT64_MAX; // special marker for timer events (differs from possible frame numbers) + auto timespec = asKernelTimespec (40ms); // 25fps ≙ 40ms per frame + 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 ===== */ + /* =============== proactor-loop =============== */ playing_ = true; while (playing_.load (std::memory_order_acquire)) { io_uring_cqe* completion; // blocking wait for the next completion entry int res = io_uring_wait_cqe (&uRing, &completion); - if (0 > res) + if (res < 0) __FAIL ("retrieval of io_uring completion failed"); uint64_t tag = completion->user_data; @@ -167,7 +191,7 @@ UringVideoPlayer::addNextReadSubmissions (io_uring& uRing, int inputFD) assert (0 < inputFD); const size_t BUFFSIZ = sizeof(Buffer); - while (buffState_[nextRead_ % RING_SIZ] == FREE) + while (FREE == buffState_[nextRead_ % RING_SIZ]) { size_t srcOffset = calcSrcFrame (nextRead_); io_uring_sqe* readSubmission = io_uring_get_sqe (&uRing); @@ -191,12 +215,12 @@ UringVideoPlayer::handleCompletedRead (io_uring_cqe& completion) __FAIL ("async read of frame from video file failed"); size_t frameNr = completion.user_data; // Note: completion was marked with the frameNr as »user data« - auto& state = buffState_[frameNr % RING_SIZ]; // Can thus pick buffer slot based on frame number, modulus ring size + auto& state = buffState_[frameNr % RING_SIZ]; // Can thus pick buffer slot based on frame number, cycling through the ring if (completion.res != sizeof(Buffer)) state.store(STOP, std::memory_order_relaxed); // Insufficient read, possibly end-of-file => signal stop of playback (to ourselves) else - state.store(SHOW, std::memory_order_relaxed); // Success; frame data is in the buffer => mark ready for use at the next tick event + state.store(FEED, std::memory_order_relaxed); // Success; frame data is in the buffer => mark ready for use at the next tick event } @@ -215,12 +239,12 @@ UringVideoPlayer::maybeDisplayFrame() auto prevState = buffState_[currShow_ % RING_SIZ].load (std::memory_order_relaxed); auto nextState = buffState_[nextShow_ % RING_SIZ].load (std::memory_order_relaxed); - if (nextState == SHOW and prevState == FREE) + if (nextState == FEED and prevState == FREE) { // can hand over this next frame to the VideoDisplay in the GUI-thread currShow_ = nextShow_; ++nextShow_; - // mark state to wait for display to complete; this also creates a write barrier - buffState_[currShow_ % RING_SIZ].store (WAIT, std::memory_order_release); + // mark state to wait for display completion; this also creates a write barrier + buffState_[currShow_ % RING_SIZ].store (SHOW, std::memory_order_release); // Notify GUI thread via Glib::Dispatcher dispatchFrame_(); } @@ -239,9 +263,9 @@ UringVideoPlayer::Buffer* UringVideoPlayer::BufferDispatcher::retrieveFrame() { // retrieve buffer state from proactor-thread; this also creates a read barrier - auto frameState = player_.buffState_[player_.currShow_ % RING_SIZ].load (std::memory_order_acquire); - if (frameState == WAIT) - return & player_.buffer_[player_.currShow_ % RING_SIZ]; + auto frameState = getCurrentFrameState().load (std::memory_order_acquire); + if (frameState == SHOW) + return & getCurrentFrame(); else return nullptr; // (can happen only in boundary cases) @@ -252,7 +276,22 @@ void UringVideoPlayer::BufferDispatcher::completedFrame() { // mark buffer as display complete for the proactor-thread; this also creates a write barrier - player_.buffState_[player_.currShow_ % RING_SIZ].store (FREE, std::memory_order_release); + getCurrentFrameState().store (FREE, std::memory_order_release); +} + + +UringVideoPlayer::Buffer& +UringVideoPlayer::BufferDispatcher::getCurrentFrame() +{ + auto currFrame = player_.currShow_; + return player_.buffer_[currFrame % RING_SIZ]; +} + +UringVideoPlayer::AtomicBuffState& +UringVideoPlayer::BufferDispatcher::getCurrentFrameState() +{ + auto currFrame = player_.currShow_; + return player_.buffState_[currFrame % RING_SIZ]; } diff --git a/FrOSCon26/examples/egl-display.hpp b/FrOSCon26/examples/egl-display.hpp index a64a151..435fcfe 100644 --- a/FrOSCon26/examples/egl-display.hpp +++ b/FrOSCon26/examples/egl-display.hpp @@ -336,7 +336,7 @@ void EglDisplay::displayFrame() { - auto currFrame = pixelFeed_.retrieveFrame(); + auto* currFrame = pixelFeed_.retrieveFrame(); if (not currFrame) return; // pick up current buffer with RGB data