diff --git a/src/lib/time/quantiser.cpp b/src/lib/time/quantiser.cpp index 8b8aee695..6ee841200 100644 --- a/src/lib/time/quantiser.cpp +++ b/src/lib/time/quantiser.cpp @@ -96,7 +96,7 @@ namespace time { /** convenience shortcut: \em materialise a raw time value * based on this grid or time axis, but returning a raw time value. - * Implemented as combination of the #gridAlign and #timeOf operations, + * Implemented as combination of the #gridPoint and #timeOf operations, * i.e. we quantise into this scale, but transform the result back onto * the global raw time value scale. * @warning this operation incurs information loss. Values may be rounded diff --git a/src/lib/time/time.cpp b/src/lib/time/time.cpp index 2a8a4c7b4..303bc1ad5 100644 --- a/src/lib/time/time.cpp +++ b/src/lib/time/time.cpp @@ -399,7 +399,7 @@ namespace { // implementation: basic frame quantisation.... const int64_t microScale {lib::time::TimeValue::SCALE}; // protect against numeric overflow - if (abs(time) < limit_num && microScale < limit_den) + if (abs(time) < limit_num and microScale < limit_den) { // safe to calculate "time * framerate" time -= origin; @@ -425,7 +425,7 @@ lumiera_quantise_frames (gavl_time_t time, gavl_time_t origin, gavl_time_t grid) int64_t lumiera_quantise_frames_fps (gavl_time_t time, gavl_time_t origin, uint framerate) { - return calculate_quantisation (time,origin,framerate); + return calculate_quantisation (time, origin, framerate); } gavl_time_t diff --git a/src/steam/play/timings.cpp b/src/steam/play/timings.cpp index 5ac4d5209..12c280ed4 100644 --- a/src/steam/play/timings.cpp +++ b/src/steam/play/timings.cpp @@ -31,7 +31,7 @@ #include "steam/play/timings.hpp" -#include "vault/engine/engine-config.h" +#include "vault/engine/engine-config.hpp" #include "lib/time/formats.hpp" #include "lib/time/timequant.hpp" @@ -66,7 +66,7 @@ namespace play { * in any way to the current session. * @remarks this ctor is intended rather for testing purposes! * Usually, when creating a play/render process, - * the actual timings \em are related to the timeline + * the actual timings _are related to the timeline_ * and the latency/speed requirements of the output. */ Timings::Timings (FrameRate fps) @@ -115,13 +115,6 @@ namespace play { } - Offset - Timings::getFrameOffsetAt (TimeValue refPoint) const - { - return 1 * getFrameDurationAt (refPoint); /////////////////TODO implement a speed factor here - } - - Duration Timings::getFrameDurationAt (TimeValue refPoint) const { @@ -183,9 +176,9 @@ namespace play { FrameCnt - Timings::establishNextPlanningChunkStart(FrameCnt currentAnchorFrame) const + Timings::establishNextPlanningChunkStart(FrameCnt anchorFrame) const { - TimeVar breakingPoint = grid_->timeOf(currentAnchorFrame); + TimeVar breakingPoint = grid_->timeOf(anchorFrame); breakingPoint += getPlanningChunkDuration(); FrameCnt nextFrame = grid_->gridPoint (breakingPoint); diff --git a/src/steam/play/timings.hpp b/src/steam/play/timings.hpp index 0b7b3c176..7c11e6e9e 100644 --- a/src/steam/play/timings.hpp +++ b/src/steam/play/timings.hpp @@ -114,7 +114,6 @@ namespace play { Time getOrigin() const; Time getFrameStartAt (FrameCnt frameNr) const; - Offset getFrameOffsetAt (TimeValue refPoint) const; Duration getFrameDurationAt (TimeValue refPoint) const; Duration getFrameDurationAt (FrameCnt refFrameNr) const; @@ -141,8 +140,8 @@ namespace play { * which is signalled by `playbackUrgency == TIMEBOUND` * @return wall clock time to expect delivery of data * corresponding to a frame specified relative - * to \link #getOrigin time axis origin \endlink - * @note for other playback urgencies \c Time::NEVER + * to [time axis origin](\ref #getOrigin) + * @note for other playback urgencies `Time::NEVER` is returned * * @warning not clear as of 1/13 if it is even possible to have such a function * on the Timings record. @@ -161,16 +160,16 @@ namespace play { /** establish the time point to anchor the next planning chunk, * in accordance with #getPlanningChunkDuration - * @param currentAnchorFrame frame number where the current planning started + * @param anchorFrame frame number where the current planning chunk started * @return number of the first frame, which is located strictly more than * the planning chunk duration into the future * @remarks this value is used by the frame dispatcher to create a * follow-up planning job */ - FrameCnt establishNextPlanningChunkStart(FrameCnt currentAnchorFrame) const; + FrameCnt establishNextPlanningChunkStart(FrameCnt anchorFrame) const; /** reasonable guess of the current engine working delay. * Frame calculation deadlines will be readjusted by that value, - * to be able to deliver in time with sufficient probability. */ + * to be able to deliver in time with sufficient likeliness. */ Duration currentEngineLatency() const; diff --git a/src/vault/engine/engine-config.cpp b/src/vault/engine/engine-config.cpp index eb3209638..6c825c60b 100644 --- a/src/vault/engine/engine-config.cpp +++ b/src/vault/engine/engine-config.cpp @@ -26,7 +26,7 @@ */ -#include "vault/engine/engine-config.h" +#include "vault/engine/engine-config.hpp" #include "lib/time/timevalue.hpp" #include @@ -83,23 +83,3 @@ namespace engine { } }} // namespace vault::engine - -namespace { - using vault::engine::EngineConfig; - - // any C adapter functions go here... - -} - - - - -extern "C" { /* ==== implementation C interface for engine configuration ======= */ - -gavl_time_t -lumiera_engine_get_latency () -{ - return _raw (EngineConfig::get().currentEngineLatency()); -} - -}//extern "C" diff --git a/src/vault/engine/engine-config.h b/src/vault/engine/engine-config.hpp similarity index 91% rename from src/vault/engine/engine-config.h rename to src/vault/engine/engine-config.hpp index f910abbf9..ad15dec77 100644 --- a/src/vault/engine/engine-config.h +++ b/src/vault/engine/engine-config.hpp @@ -21,7 +21,7 @@ */ -/** @file engine-config.h +/** @file engine-config.hpp ** access point to configuration of engine parameters */ @@ -31,11 +31,6 @@ -#include "lib/time.h" - - -#ifdef __cplusplus /* ============== C++ Interface ================= */ - #include "lib/time/timevalue.hpp" #include "lib/depend.hpp" @@ -63,6 +58,8 @@ namespace engine { * is no locking and all values are hard coded. It is conceivable to implement * the \em access in a lock-free manner (by loosening any guarantee regarding * the actual time point when a changed setting becomes visible) + * + * @deprecated 5/23 singleton access looks questionable; should be part of RenderEnvironmentClosure rather */ class EngineConfig { @@ -101,21 +98,4 @@ namespace engine { }; }} // namespace vault::engine - - - - - -extern "C" { -#endif /* =========================== CL Interface ===================== */ - - -/** guess of the current effective engine calculation delay */ -gavl_time_t lumiera_engine_get_latency (); - - - -#ifdef __cplusplus -} -#endif #endif/*VAULT_ENGINE_ENGINE_CONFIG_H*/ diff --git a/tests/core/steam/engine/timings-test.cpp b/tests/core/steam/engine/timings-test.cpp index f19e711b4..fc76da47c 100644 --- a/tests/core/steam/engine/timings-test.cpp +++ b/tests/core/steam/engine/timings-test.cpp @@ -98,12 +98,10 @@ namespace test { /***************************************************************//** - * @test document and verify the engine::Dispatcher interface, used - * to translate a CalcStream into individual node jobs. - * This test covers the definition of the interface itself, - * together with the supporting types and the default - * implementation of the basic operations. - * It creates and uses a mock Dispatcher implementation. + * @test document and verify frame timing calculations, which are + * used in the Player / engine::Dispatcher, to translate a CalcStream + * into individual node jobs. + * @see TimingConstraints_test */ class Timings_test : public Test { diff --git a/wiki/renderengine.html b/wiki/renderengine.html index ec7296323..260c869ed 100644 --- a/wiki/renderengine.html +++ b/wiki/renderengine.html @@ -7035,14 +7035,21 @@ Later on we expect a distinct __query subsystem__ to emerge, presumably embeddin &rarr; QuantiserImpl -
-
//Invoke and control the time based execution of  [[render jobs|RenderJob]]//
+
+
//Invoke and control the dependency and time based execution of  [[render jobs|RenderJob]]//
 The Scheduler acts as the central hub in the implementation of the RenderEngine and coordinates the //processing resources// of the application. Regarding architecture, the Scheduler is located in the Vault-Layer and //running// the Scheduler is equivalent to activating the »Vault Subsystem«. An EngineFaçade acts as entrance point, providing high-level render services to other parts of the application: [[render jobs|RenderJob]] can be activated under various timing and dependency constraints. Internally, the implementation is segregated into two layers
 ;Layer-2: Control
 :maintains a network of interconnected [[activities|RenderActivity]], tracks dependencies and observes timing constraints
 ;Layer-1: Invocation
 :operates a low-level priority scheduling mechanism for time-bound execution of [[activities|RenderActivity]]
-:coordinates a ThreadPool and dispatches the execution of individual jobs into apropriate worker threads.
+:coordinates a ThreadPool and dispatches the execution of individual jobs into appropriate worker threads.
+
+!Event based vs. time based operational control
+Time bound delivery of media data is an important aspect of editing and playback -- yet other concerns are of similar importance: the ability to make optimum use of scarce resources and to complete extended processing in reasonable time, while retaining some overall responsiveness of the system. And, especially for the //final render,// it is tantamount to produce 100% correct results without any glitches -- a goal that can not be reconciled with the demand for perfect timing. These considerations alone are sufficient to indicate, that strict adherence to a pre-established calculation plan is not enough to build a viable render engine. As far as media processing is concerned, the limited I/O bandwidth is the most precious resource -- reading and writing data from persistent storage requires a lot of time, with generally unpredictable timings, necessitating asynchronous processing.
+
+This leads to the observation that every render or playback process has to deal with rather incompatible processing patterns and trends: for one, processing has to start as soon as the event of an completed I/O-operation is published, yet on the other hand, limited computational resources must be distributed and prioritised such as to deliver the completed data as close as possible to a pre-established timing deadline, under the constraint of limited in-memory buffer capacity. The //control structure// of an render engine is thus not only a time based computation plan -- first and foremost it should be conceived as an asynchronous messaging system, with the ability however to prioritise some messages based on urgency or approaching deadlines.
+
+The time-based ordering and prioritisation of [[render activities|RenderActivity]] is thus used as a //generic medium and agent// to support and implement complex interwoven computational tasks. On the layer-1 mentioned above, a combination of a lock-free dispatch queue is used, feeding into a single threaded priority queue organised by temporal deadlines. Most render activities are lightweight and fast updates of some state flags, while some activities are extremely long running -- and those are shifted into worker threads based on priority.
 
diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index cb0f6046b..b1906f64a 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -68533,7 +68533,9 @@ - + + + @@ -68601,6 +68603,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
  • + die Aufschlüsselung nach Zeiten enthält überall Puffer, die insgesamt zu einer Zeitverschwendung führen +
  • +
  • + normalerweise wird das Verletzen einer Deadline als Fehler interpretiert +
  • +
+ +
+
+
+
+
@@ -69517,6 +69562,18 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + + + @@ -73055,6 +73112,22 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + + + + + + + @@ -73165,7 +73238,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -73225,6 +73298,23 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + +

+ im Job wäre die nominelle Zeit gegeben +

+ +
+ +
@@ -73770,8 +73860,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -74256,6 +74346,315 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + + + + + + + + + + + + + +

+ Zeitsteuerung funktioniert am Besten bei eigentlich entkoppelten Aktivitäten, welche ohne weitere Prüfungen und Synchronisationen starten und enden können +

+ +
+ +
+ + + + + + +

+ Event ⟶ push +

+

+ Zeit ⟵ pull +

+ +
+
+ + + + + + +

+ weil außer dem zeitgetriggerten Start... +

+
    +
  • + noch zusätzlich etwas Externes geprüft werden muß, was Varianz einführt +
  • +
  • + deshalb zwischen Prüfung und eigentlicher Aktion ein künstlicher Sicherheitspuffer liegen muß +
  • +
  • + außerdem noch eine Kommunikation stattfinden muß, schlimmstenfalls über Thread-Grenzen hinweg +
  • +
+ +
+
+
+
+ + + + + + + + +

+ ...nämlich beim finalen Rendern! Lumiera ist darauf ausgelegt, und grade nicht als Aufführungs-System konzipiert. Und auf der anderen Seite, wenn es um zeitgebundene Wiedergabe geht, fordern wir ohnehin, den Aufwand so sehr zu reduzieren (Proxyies), daß das System weit von Vollauslastung entfernt läuft +

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

+ Denn stabiler Playback erfordert zwingend zusätzliche Leistungspuffer (sofern auch nur ein kleiner Teil der Pipeline zufällig streut). Daher steht und fällt die Möglichkeit für flüssigen Playback damit, den Aufwand garantiert erheblich unter das Limit zu drücken. Unter diesen Umständen würde meist auch Batching plus Vorlauf mit Datenpuffer genügen +

+ +
+
+ + + + + + +

+ ...mehr noch, da letzten Endes die amortisierte Lade-Zeit pro Frame deutlich kleiner sein muß als die Framerate, brauchen wir ehr einen ausreichenden Vorlauf und einfache Datenpuffer — zumal flüssiger Playback ohnehin nur mit kleinen Frames funktioniert, denn die I/O-Bandbreite ist bei Weitem nicht so angewachsen, wie die Rechenleistung +

+ +
+
+ + + + + + +

+ In diesem Fall verschlechtert Zeitsteuerung eigentlich die benötigte Gesamtzeit; die optimale Lösung wäre hier, jeweils parallel mehrere I/O-Pipelines, und dann einige Worker nach best serve-Verteilung laufen zu lassen +

+ +
+
+
+ + + + + + +

+ praktisch alle Video-Verarbeitung involviert I/O in irgendeiner Form, und muß deshalb auf Events reagieren, dann aber einen nachfolgenden festen Arbeitsaufwand gleichmäßig disponieren +

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

+ die wirklich recht komplexe Steuerung ist einzig und allein dadurch gerechtfertigt, daß so lange laufende und schwer vorhersehbare Sequenzen überlappend verschachtelt werden können, was bei einem strikten Pipelining oder rein Event-getriebener Verarbeitung nicht möglich wäre; letztere Ansätze funktionieren nur gut bei sehr kleinen Arbeitspaketen oder bei extremer horizontaler Skalierung +

+ +
+ +
+ + + + + + +

+ das heißt, die Priority-Queue ist das Entscheidende; das Ordnungsmaß darf gar nicht primär als ein Zeitplan verstanden werden, sondern als ein Maß an aktueller Dringlichkeit +

+ +
+ +
+ + + + + + +

+ Bei einem reinen Pipelining kann es zur Stau und Lastspitzen kommen, und genau dann auch zu Lock-contention. Wir sollten also die zur Integration der widersprüchlichen Anforderungen notwendigen Pufferzeiten als eine zufällige Streuung deuten, die Lastspitzen durch Verteilung entzerrt; sofern nicht in kurzer Zeit Aktionen an mehrere Threads zugleich  weitergegeben werden müssen, treten auch weniger Behinderungen durch Synchronisation auf +

+ +
+ +
+ + + + + + +

+ Die Implementierung sollte sich von den exakten Zeiten lösen — besser sollte stets mit reichlich Puffer gearbeitet werden, dafür aber auch eine Activity in einer gewissen Toleranzzone um den definierten Zeitpunkt herum gestartet werden können. Dafür sollte im Gegenzug die Menge der jeweils aus der Queue entommenen Activities dynamisch geregelt werden, und stets nur so viel wie gut möglich getan werden. Die concurrency-Struktur sollte zum Leitmaß erhoben werden; idealerweise sollte stets nur ein Thread abnehmen, und auch das nur so lange,  bis er wieder auf eine größere Aufgabe geschickt wird. +

+ +
+ +
+ + + + + + +

+ Keinesfalls sollte a priori eine bestimmte Thread-Struktur festgelegt werden! Im Besonderen auch nicht die Annahme, daß es einen »Scheduler-Thread« gibt, welcher Aufgaben verteilt. Denn letztlich hängen alle Leistungs-Eigenschaften an der Cache-Locality, und diese läßt sich nur empirisch optimieren, denn es ist klar, daß unser Daten-Durchsatz die Cache-Kapazität insgesamt übersteigt — inwiefern sich eine einzelne CPU für bestimmte Aufgaben überhaupt „warm laufen“ kann, muß sich in der Praxis zeigen; gut möglich, daß I/O-Limitierungen jedweden Cache-Efekt überdecken. Grundsätzlich sollte die Möglichkeit vorgesehen werden, daß jeder Thread die Queue bedienen kann — sowohl eingangsseitig alsauch ausgangsseitig. Natürlich darf es stets nur ein Thread sein, der die Queue bedient; andere untätige Threads legen sich schlafen. +

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

+ zwei Queues +

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

+ reguliert über ein konventionelles Lock, oder (besser) über ein Atomic-Token mit CaS +

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

+ zu den Steuerungs-Aufgaben gehört gleichermaßen +

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

+ Activities werden in/aus der Notification-Queue verschoben +

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

+ Die Koordination von Memory-Management ist ein gefährlicher Verzögerungs-Faktor (weil sie zu einem globalen flush der Cache-Hierarchie führen kann). +

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