From fb2f0b0e2d4277244772fd0881999c3449747a69 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 5 Jan 2025 02:48:07 +0100 Subject: [PATCH] Invocation: build and invoke a chain of Render Nodes This is a first! Now we can really invoke a tree of Nodes, as demonstrated with this simple test. --- src/steam/engine/node-builder.hpp | 2 +- tests/core/steam/engine/node-builder-test.cpp | 52 +- wiki/renderengine.html | 22 +- wiki/thinkPad.ichthyo.mm | 463 +++++++++++++----- 4 files changed, 405 insertions(+), 134 deletions(-) diff --git a/src/steam/engine/node-builder.hpp b/src/steam/engine/node-builder.hpp index 87b3f8ccb..656307adf 100644 --- a/src/steam/engine/node-builder.hpp +++ b/src/steam/engine/node-builder.hpp @@ -372,7 +372,7 @@ namespace engine { /** connect the next input slot to either existing or new lead-node" */ PortBuilder&& - conectLead (ProcNode& leadNode) + connectLead (ProcNode& leadNode) { return connectLeadPort (leadNode, this->defaultPort_); } diff --git a/tests/core/steam/engine/node-builder-test.cpp b/tests/core/steam/engine/node-builder-test.cpp index ae43e54ad..d101c221c 100644 --- a/tests/core/steam/engine/node-builder-test.cpp +++ b/tests/core/steam/engine/node-builder-test.cpp @@ -26,8 +26,11 @@ #include "lib/symbol.hpp" //#include "lib/util.hpp" +#include + using lib::Symbol; using std::string; +using std::array; using lib::time::Time; using lib::time::QuTime; using lib::time::FrameNr; @@ -119,7 +122,7 @@ namespace test { ProcNode node{prepareNode("Test") .preparePort() - .invoke("fun()", procFun) + .invoke ("fun()", procFun) .setParam (LIFE_AND_UNIVERSE_4EVER) .completePort() .build()}; @@ -144,7 +147,7 @@ namespace test { ProcNode node{prepareNode("Test") .preparePort() - .invoke("fun()", procFun) + .invoke ("fun()", procFun) .attachAutomation (autoFun) .completePort() .build()}; @@ -162,13 +165,52 @@ namespace test { } - /** @test TODO build a chain with two connected Nodes - * @todo WIP 12/24 define ⟶ implement + /** @test build a chain with three connected Nodes + * - have two source nodes, which accept a parameter + * - but configure them differently: one gets a constant, + * while the other draws a random number + * - the third node takes two input buffers and and one output; + * it retrieves the input values, and sums them together + * - use the »simplified 1:1 wiring«, which connects consecutively + * each input slot to the next given node on the same port number; + * here we only use port#0 on all three nodes. + * @todo 12/24 ✔ define ⟶ ✔ implement */ void build_connectedNodes() { - UNIMPLEMENTED ("build two linked nodes"); + using SrcBuffs = array; + auto detailFun = [](uint param, uint* out) { *out = 1 + param; }; + auto joinerFun = [](SrcBuffs src, uint* out){ *out = *src[0] + *src[1]; }; + + int peek{0}; + auto randParam = [&](TurnoutSystem&){ return peek = rani(100); }; + + + ProcNode n1{prepareNode("Src1") + .preparePort() + .invoke ("fix-val()", detailFun) + .setParam (LIFE_AND_UNIVERSE_4EVER) + .completePort() + .build()}; + + ProcNode n2{prepareNode("Src2") + .preparePort() + .invoke ("ran-val()", detailFun) + .attachParamFun (randParam) + .completePort() + .build()}; + + ProcNode n3{prepareNode("Join") + .preparePort() + .invoke ("add()", joinerFun) + .connectLead(n1) + .connectLead(n2) + .completePort() + .build()}; + + uint res = invokeRenderNode(n3); + CHECK (res == peek+1 + LIFE_AND_UNIVERSE_4EVER+1 ); } diff --git a/wiki/renderengine.html b/wiki/renderengine.html index 7ea70c18d..87e3a571c 100644 --- a/wiki/renderengine.html +++ b/wiki/renderengine.html @@ -4774,9 +4774,8 @@ Moreover, the design of coordinate matching and resolving incurs a structure sim In the most general case the render network may be just a DAG (not just a tree). Especially, multiple exit points may lead down to the same node, and following each of this possible paths the node may be at a different depth on each. This rules out a simple counter starting from the exit level, leaving us with the possibility of either employing a rather convoluted addressing scheme or using arbitrary ID numbers.{{red{...which is what we do for now}}} -
-
{{red{⚠ In-depth rework underway as of 12/2024...}}}
-The [[Render Nodes|ProcNode]] are wired to form a "Directed Acyclic Graph" ([[DAG|https://en.wikipedia.org/wiki/Directed_acyclic_graph]]); each node knows its predecessor(s), but not its successor(s).  The RenderProcess is organized according to the ''pull principle''. This implies that there is no central entity to „activate and apply“ nodes consecutively. Rather, the ExitNode is prompted to produce results -- and since the nodes are interconnected in accordance to their required prerequisite, the calculation plan works itself out recursively. However, some prerequisite resources must be provided before any calculation can start. Notably, loading source media data is an I/O-intensive task and can not be precisely timed. The actual calculation is broken down thus into atomic chunks of work, resulting in a 2-phase invocation scheme for generating data:
+
+
The [[Render Nodes|ProcNode]] are wired to form a "Directed Acyclic Graph" ([[DAG|https://en.wikipedia.org/wiki/Directed_acyclic_graph]]); each node knows its predecessor(s), but not its successor(s).  The RenderProcess is organized according to the ''pull principle''. This implies that there is no central entity to „activate and apply“ nodes consecutively. Rather, the ExitNode is prompted to produce results -- and since the nodes are interconnected in accordance to their required prerequisite, the calculation plan works itself out recursively. However, some prerequisite resources must be provided before any calculation can start. Notably, loading source media data is an I/O-intensive task and can not be precisely timed. The actual calculation is broken down thus into atomic chunks of work, resulting in a 2-phase invocation scheme for generating data:
 ;planning phase
 :when data for a given part of the timeline shall be produced, the engine has to work out what ExitNode to activate and what further prerequisites must be fulfilled
 :# the planning is initiated by issuing an "get me output" request, finally resulting in a JobTicket
@@ -4799,10 +4798,6 @@ The [[Render Nodes|ProcNode]] are wired to form a "Directed Acyclic Graph&q
 :# now everything is ready for the //»weft« phase:// the external [[processing-functor|NodeProcFunctor]] is triggered
 :# finally, in the //»fix« phase//, input buffers can be released and output buffers can be //committed//
 :# when the {{{weft()}}} call returns, "parent" state originating the pull-activation holds onto the result buffer containing the calculated output data.
-^^┅┅┅┅┅┅the following text is ''superseded''┅┅┅┅┅┅┅┅┅^^
-{{red{WIP as of 9/11  -- many details here are still to be worked out and might change as we go}}}
-
-{{red{Update  8/13  -- work on this part of the code base has stalled, but now the plain is to get back to this topic when coding down from the Player to the Engine interface and from there to the NodeInvocation. The design as outlined above was mostly coded in 2011, but never really tested or finished; you can expect some reworkings and simplifications, but basically this design looks OK}}}
 
 some points to note:
 * when a node is "inplace-capable", input and output buffer may actually point to the same location
@@ -4814,11 +4809,20 @@ some points to note:
 → more fine grained [[implementation details|RenderImplDetails]]
 
-
+
//The actual processing units within a Render Node.//
 At a conceptual level, a »Node« represents a distinct processing functionality. But when it comes down to actually invoking the processing code, the operation is typically exposed in several flavours or configuration variations typically related to data format. E.g. a sound filtering node can be able to process stereo sound, but also deliver the filtering on the left or right channel in isolation. Or video image processing can be provided to work on various image resolutions, each requiring a different buffer layout. Any such processing variants are exposed as »''ports''« of the node.
 
-This seemingly redundant configuration is based on //fundamental reasoning:// The Lumiera Render Engine performs //pre-arranged primitive operations,// which are -- to the extent this is even possible -- deprived of active decision logic. Instead of letting the invocation „work out“ some technical details like buffer-sizes on-the-fly, there is a pre-canned set of ports, each with a viable pre-configuration, so that data elements can be passed ''without any further checks'' and adaptation steps. In this respect, the [[»Render Node Network«|LowLevelModel]] is similar to //assembly code:// It proceeds into processing of low-level data right away and takes compatibility of all data types, buffer sizes and invoked functors for granted
+This seemingly redundant configuration is based on //fundamental reasoning:// The Lumiera Render Engine performs //pre-arranged primitive operations,// which are -- to the extent this is even possible -- deprived of active decision logic. Instead of letting the invocation „work out“ some technical details like buffer-sizes on-the-fly, there is a pre-canned set of ports, each with a viable pre-configuration, so that data elements can be passed ''without any further checks'' and adaptation steps. In this respect, the [[»Render Node Network«|LowLevelModel]] is similar to //assembly code:// It proceeds into processing of low-level data right away and takes compatibility of all data types, buffer sizes and invoked functors for granted + +!Turnout, ~TurnoutSystem and Weaving Pattern +The implementation of the {{{Port}}} interface is called a ''Turnout''. +For operation, it exposes a simple API function {{{Turnout::weave(TurnoutSystem&)}}} — which acts as indirection or //virtual entrance point// into the complexly interwoven activities necessary to invoke a specific render operation, passing various input- and output-buffers and parameters. All these activities are organised into a common scheme of five steps, called a [[»Weaving Pattern«|NodeWeavingPattern]]. It is implemented as ''C++ template'' and will thus adapt itself in a flexible way to the actual buffer- and data-types and cardinalities. The actual instantiation of these templates must be driven from within a [[Adapter-Plug-in for a Media-Library|MediaLibPlugin]] (e.g. ~FFMpeg) -- because only there the actual types and layout requirements can be known. This setup leads to a specific twist for the Builder: at a given point, it must //delegate the control-flow// into the ~Media-Lib Plug-in, while passing a Node-Builder object. The Builder knows by means of the Asset-ID which Plug-in to address, and the specific Binding in the Plug-in knows how to implement an abstract »Asset« by invocation of specific processing functions within the Library. + +The actual invocation of a Render Node processing chain is accomplished by an interplay of Turnout and TurnoutSystem, where the former represents the fixed, preconfigured operation scheme, while the latter, the {{{TurnoutSystem}}} instance is created anew on the stack for each invocation, and embodies the flexible control structure to guide through the sequence of Turnout invocations. + +__🛈 Remark__: The name »Turnout« plays upon the overlay of several metaphors, notably the [[Railroad Turnout|https://en.wikipedia.org/wiki/Railroad_turnout]]. +A »Turnout System« may thus imply either a system for generating and collecting turnout, or the complex interwoven system of tracks and switches found in large railway stations.
The calculations for rendering and playback are designed with a base case in mind: calculating a linear sequence of frames consecutive in time.
diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm
index 855954cfc..6fa08eb15 100644
--- a/wiki/thinkPad.ichthyo.mm
+++ b/wiki/thinkPad.ichthyo.mm
@@ -82324,8 +82324,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + @@ -86932,10 +86932,10 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - - + + + + @@ -87040,8 +87040,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + @@ -87064,7 +87064,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -87098,10 +87098,10 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - - + + @@ -87196,8 +87196,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + + @@ -87356,18 +87357,18 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + - - - - - + + + + + @@ -87377,9 +87378,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - + + + @@ -87391,10 +87392,10 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - + @@ -87403,12 +87404,12 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - + @@ -87435,9 +87436,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - + @@ -88237,19 +88238,33 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - + + + + + + + +
- - - - + + + + + + + + + - - + + + + + + @@ -88257,7 +88272,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -88308,6 +88323,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+
@@ -89065,10 +89081,10 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - + @@ -89083,6 +89099,23 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + + + + + + + + @@ -89236,8 +89269,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + +
@@ -91788,9 +91821,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - + + + @@ -93383,7 +93416,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -93921,8 +93954,18 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + + + + + + + + + + + @@ -94418,7 +94461,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -94463,7 +94506,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -94496,7 +94539,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -94553,13 +94596,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - + + + - + @@ -96334,7 +96377,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + @@ -96349,24 +96393,40 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - - + + - + + + + + +

+ Ja .... aber (YAGNI) +

+ + +
- + + + + + + + @@ -96374,7 +96434,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + @@ -96429,7 +96490,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -96511,6 +96572,39 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + +

+ Nachtrag: aus anderen Gründen habe ich Bulk-Allokation ermöglicht +

+ +
+ + + + + + +

+ Aber es führt kein Weg daran vorbei; das hier erstmals bemerkte Problem, daß Port non-copyable ist/sein sollte, führt tatsächlich dazu, daß man keine einfache dynamische Speicherbelegung bekommt. Genauere Analyse zeigt aber, daß das grundsätzlich nicht möglich ist (und auch nicht wünschenswert, da wir eine low-level-Struktur bauen und auch gute Cache-Kohärenz wollen) +

+ +
+
+ + + + +

+ Nun habe ich den Builder so umgebaut, daß alle Port-Konstruktoren als Lambda verzögert aufgesammelt werden; man kann dann am Ende alle Allokationen auf einmal »abwerfen«, so daß sie wunderbar kompakt liegen +

+ + +
+
+
@@ -96530,7 +96624,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -96552,7 +96646,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -96636,12 +96730,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + - + @@ -96850,7 +96945,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -96885,9 +96980,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - + + + @@ -97040,7 +97135,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -97061,7 +97156,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -97213,7 +97308,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -97378,9 +97473,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - + + + @@ -97393,7 +97488,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -97422,7 +97517,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -97501,7 +97596,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -97521,8 +97616,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + @@ -97539,6 +97634,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + @@ -97546,10 +97643,11 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + - + @@ -97625,12 +97723,15 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + - + + - + @@ -98050,13 +98151,13 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + - + @@ -98069,23 +98170,38 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
- - - + + + + + + + + + + + + + + - - + + + + + - + + @@ -98136,9 +98252,10 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + + @@ -98264,19 +98381,36 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + + + + + + + + + + + + + + + + + + - + @@ -98318,11 +98452,25 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)

- + + + + + +

+ Da der Prototyp hier direkt durchmaschiert, und man trotzdem damit erst mal jede erdenkliche Medienberechnung per CPU erschlagen könnte (dank der Flexibilität, die ich mir in die Parameter-Signatur der Processing-function eingebaut habe), besteht vorerst wohl doch kein so starker Bedarf nach weiterer Flexibilität. Immerhin, der Extension-Point ist da, wie ich durch die Implementierung des »Param Agent Scheme« demonstrieren konnte. +

+

+ Potentiell problematisch ist, daß die Verdrahtung im NodeBuilder / PortBuilder extrem technisch anspruchsvoll ist (man reicht mehrere Template-Parameter durch, hat cross-Builder, ein up-Slicing und einen deduction-Guide, der dann »nebenbei« auch noch eine funktionale Datenstruktur befüllt. Nicht daß ich das aus Spaß so gemacht hätte.... +

+ + +
- + + @@ -98332,6 +98480,11 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) + + + + + @@ -98345,6 +98498,41 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
+ + + + + + + + + + + + + + + + + + + + + + + +

+ Daß das in solchem Maß erfolgreich wird, hätte ich nicht erwartet. Zugegeben: ich hab's erst vor mir hergeschoben, und als ich dann den Umbau tatsächlich durchgezogen habe, war das eine der brutalsten Aktionen, die ich jemals gemacht habe. Also genau das Richtige für Weihnachten (!). +

+

+ Und — oh Wunder — mir geht die Phantasie aus. Ich kann mir im Moment keine Berechnungs-Aufgabe ausdenken, die man nicht mit diesem Standard Weaving-Pattern + geschickter Verwendung von Parametern lösen kann. Also wird das vielleicht erst etwas für viel später — Hardware-Accelleration und so.... +

+ +
+ +
+
+
@@ -98354,11 +98542,11 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - + + - + @@ -98406,22 +98594,40 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - - + + + - + + + - - + + - - + + + + + + + + + + + + + + + + + - - + + + @@ -99322,8 +99528,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - + + @@ -99331,9 +99537,9 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - - + + + @@ -101786,6 +101992,10 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) + + + + @@ -141105,8 +141315,23 @@ std::cout << tmpl.render({"what", "World"}) << s + + + + + + + + + + + + + + +