Invocation: create a test setup with a complex Render Node tree
In NodeLink_test, a processing pipeline was established that works on `TestFrame` and is quite complex; here I use a slightly simplified variant (featuring only a single port) to perform the same kind of computations. However, node memory allocations are managed by an `AllocationCluster`
This commit is contained in:
parent
70bef00324
commit
2ddb921e6e
3 changed files with 213 additions and 4 deletions
|
|
@ -39,7 +39,7 @@ using lib::Symbol;
|
|||
using util::isnil;
|
||||
using util::isSameObject;
|
||||
using test::TestFrame;
|
||||
using test::testRand;
|
||||
using test::testRand;
|
||||
namespace ont = test::ont;
|
||||
|
||||
namespace steam {
|
||||
|
|
@ -362,7 +362,7 @@ namespace test {
|
|||
|
||||
|
||||
// Prepare a precomputed parameter for the complete tree
|
||||
auto selectFrameNo = [&](TurnoutSystem& tuSys){ return quantSecs(tuSys.getNomTime()); };
|
||||
auto selectFrameNo = [&](TurnoutSystem& turSys){ return quantSecs(turSys.getNomTime()); };
|
||||
auto paramSpec = buildParamSpec()
|
||||
.addSlot (selectFrameNo);
|
||||
|
||||
|
|
@ -373,8 +373,8 @@ namespace test {
|
|||
auto stepMixer = [] (FrameCnt id)-> ont::Factr { return util::limited (0, + id, 50) / 50.0; };
|
||||
|
||||
// note: binds the accessor for the precomputed FrameNo-parameter
|
||||
auto autoFilter = [=](TurnoutSystem& tuSys){ return stepFilter (tuSys.get (accFrameNo)); };
|
||||
auto autoMixer = [=](TurnoutSystem& tuSys){ return stepMixer (tuSys.get (accFrameNo)); };
|
||||
auto autoFilter = [=](TurnoutSystem& turSys){ return stepFilter (turSys.get (accFrameNo)); };
|
||||
auto autoMixer = [=](TurnoutSystem& turSys){ return stepMixer (turSys.get (accFrameNo)); };
|
||||
|
||||
|
||||
// A Node with two (source) ports
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@
|
|||
|
||||
|
||||
#include "test/run.hpp"
|
||||
#include "lib/allocation-cluster.hpp"
|
||||
#include "test/test-rand-ontology.hpp"
|
||||
#include "vault/mem/buffer-provider-setup.hpp"
|
||||
#include "vault/mem/engine-buffer-metadata.hpp"
|
||||
#include "vault/mem/engine-buffer-manager.hpp"
|
||||
|
|
@ -24,8 +26,12 @@
|
|||
#include "vault/mem/local-buffer-store.hpp"
|
||||
#include "steam/engine/engine-facilities.hpp"
|
||||
#include "steam/engine/node-builder.hpp"
|
||||
#include "steam/asset/meta/time-grid.hpp"
|
||||
#include "lib/time/timequant.hpp"
|
||||
#include "lib/time/timecode.hpp"
|
||||
#include "lib/depend-inject.hpp"
|
||||
#include "lib/thread.hpp"
|
||||
//#include "lib/symbol.hpp"
|
||||
//#include "lib/util.hpp"
|
||||
#include "test/diagnostic-output.hpp"///////////////TODO
|
||||
|
||||
|
|
@ -36,6 +42,19 @@ using std::this_thread::yield;
|
|||
using std::this_thread::sleep_for;
|
||||
using std::chrono_literals::operator ""us;
|
||||
|
||||
using lib::time::Time;
|
||||
using lib::time::QuTime;
|
||||
using lib::time::FrameNr;
|
||||
using lib::time::FrameCnt;
|
||||
using lib::time::FrameRate;
|
||||
using lib::time::PGrid;
|
||||
using steam::asset::meta::TimeGrid;
|
||||
//using lib::Symbol;
|
||||
//using util::isnil;
|
||||
//using util::isSameObject;
|
||||
using test::TestFrame;
|
||||
using test::testRand;
|
||||
namespace ont = test::ont;
|
||||
|
||||
namespace steam {
|
||||
namespace engine{
|
||||
|
|
@ -99,6 +118,7 @@ namespace test {
|
|||
seedRand();
|
||||
|
||||
invokeSimpleNode();
|
||||
invokeNodeTree();
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -170,6 +190,176 @@ namespace test {
|
|||
// now there should be no memory leaks.......
|
||||
CHECK (0 == watch(*mockEngine.globalPool).bytesLeased());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/** @test perform a RenderInvocation to pull from a complex node tree into an output sink.
|
||||
* - use a test helper class to build a node topology similar to NodeLink_test
|
||||
* - the ProcNodes and all backing structures are managed by an AllocationCluster
|
||||
*/
|
||||
void
|
||||
invokeNodeTree()
|
||||
{
|
||||
TestEngineCtx mockEngine;
|
||||
RenderPipeline pipeline;
|
||||
|
||||
Thread testWorker{[&] /* === perform a render invocation === */
|
||||
{
|
||||
BuffHandle buff = EngineCtx::access().mem.lockBufferFor<TestFrame>();
|
||||
Time nomTime{Time::ZERO};
|
||||
ProcessKey key{0};
|
||||
uint port{0};
|
||||
|
||||
TestFrame& result = buff.accessAs<TestFrame>();
|
||||
CHECK (result.isPristine());
|
||||
SHOW_EXPR(result.getChecksum())
|
||||
|
||||
// Trigger Node invocation...
|
||||
buff = pipeline.exitNode.pull (port, buff, nomTime, key);
|
||||
|
||||
CHECK (not result.isPristine());
|
||||
CHECK (result.isValid());
|
||||
SHOW_EXPR(result.getChecksum())
|
||||
buff.release();
|
||||
}};
|
||||
|
||||
while (testWorker)
|
||||
yield(); // wait for worker to finish
|
||||
sleep_for (500us);
|
||||
|
||||
mockEngine.globalPool->processPendingRequests();
|
||||
// now there should be no memory leaks.......
|
||||
CHECK (0 == watch(*mockEngine.globalPool).bytesLeased());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper context to build and manage a processing chain of Render Nodes.
|
||||
* In the real Render Engine, these processing chains are generated by the Builder,
|
||||
* and organised into »Segments« of the timeline, where each segment features a
|
||||
* distinct node connectivity (i.e. typically for one clip).
|
||||
* For this test, a single top-level _exit node_ is exposed.
|
||||
* @note this helper context is constructed in _RAII style_,
|
||||
* and the embedded AllocationCluster unwinds automatically.
|
||||
* @see \ref NodeLink_test for a detailed breakdown of a similar topology
|
||||
*/
|
||||
struct RenderPipeline
|
||||
: util::NonCopyable
|
||||
{
|
||||
static constexpr ont::Flavr SRC_A = 10; ///< »chain-A« arbitrary source frame marker
|
||||
static constexpr ont::Flavr SRC_B = 20; ///< similar for »chain-B«
|
||||
|
||||
ProcNode& exitNode{buildTree()};
|
||||
|
||||
HashVal
|
||||
expectedChecksum (Time nomTime)
|
||||
{
|
||||
UNIMPLEMENTED ("recompute");
|
||||
}
|
||||
|
||||
private:
|
||||
/** custom allocator for this node tree */
|
||||
lib::AllocationCluster alloc_;
|
||||
|
||||
PGrid secondsGrid_{TimeGrid::build (FrameRate{1})};
|
||||
|
||||
FrameCnt quantSecs (Time time) { return secondsGrid_->gridPoint (time); };
|
||||
|
||||
ont::Param stepFilter(FrameCnt id) { return util::limited (10, -10 + id, 50); };
|
||||
ont::Factr stepMixer (FrameCnt id) { return util::limited (0, + id, 50) / 50.0; };
|
||||
|
||||
/** @internal wire the processing node topology.
|
||||
* This fake "render pipeline" is based on hash chaining computations,
|
||||
* operating on TestFrame (with reproducible random data content).
|
||||
* Two processing chains are built, each producing test frames;
|
||||
* the first chain even adds a "filter". Some "parameters" are added
|
||||
* to change the random data in a deterministic way. Finally, both chains
|
||||
* are "mixed" together into a single result TestFrame. Furthermore, the
|
||||
* parameter automation is based on a precomputed _frame number_ parameter,
|
||||
* that is derived from the _absolute nominal time_ by quantisation into a
|
||||
* time grid with a stepping of 1 second (-> `FrameRate{1}`). This precomputed
|
||||
* frame number parameter is stored in a parameter data block on the call stack,
|
||||
* prior to recursively evaluating the node chain attached below.
|
||||
* @note the generated Render Nodes are placed into the AllocationCluster,
|
||||
* together with all the further storage used by those nodes internally.
|
||||
* The real Render Engine uses an AllocationCluster per Segment of the timeline.
|
||||
*/
|
||||
ProcNode&
|
||||
buildTree()
|
||||
{
|
||||
// Prepare a precomputed parameter for the complete tree
|
||||
auto selectFrameNo = [&](TurnoutSystem& turSys){ return quantSecs (turSys.getNomTime()); };
|
||||
auto paramSpec = buildParamSpec()
|
||||
.addSlot (selectFrameNo);
|
||||
auto accFrameNo = paramSpec.makeAccessor<0>();
|
||||
|
||||
// Automation functions using the accessor for the precomputed FrameNo-parameter
|
||||
auto autoFilter = [&](TurnoutSystem& turSys){ return stepFilter (turSys.get (accFrameNo)); };
|
||||
auto autoMixer = [&](TurnoutSystem& turSys){ return stepMixer (turSys.get (accFrameNo)); };
|
||||
|
||||
// use processing functions from the »TestRand Ontology«
|
||||
auto testGen = testRand().setupGenerator();
|
||||
auto testMan = testRand().setupManipulator();
|
||||
auto testMix = testRand().setupCombinator();
|
||||
|
||||
// A source node to produce random test frames
|
||||
auto& n1s = alloc_.create<ProcNode>(
|
||||
prepareNode("srcA")
|
||||
.preparePort()
|
||||
.invoke(testGen.procID(), testGen.makeFun()) // params(frameNo, flavour)
|
||||
.closeParam<1>(SRC_A) // --> fix the flavour ≔ SRC_A
|
||||
.retrieveParam(accFrameNo)
|
||||
.completePort()
|
||||
.build());
|
||||
|
||||
// A node to »filter« the data in chain-A
|
||||
auto& n1f = alloc_.create<ProcNode>(
|
||||
prepareNode("filterA")
|
||||
.preparePort()
|
||||
.invoke(testMan.procID(), testMan.makeFun())
|
||||
.attachParamFun(autoFilter) // filter-param <-- autoFilter(frameNo)
|
||||
.connectLead(n1s)
|
||||
.completePort()
|
||||
.build());
|
||||
|
||||
|
||||
// A secondary source Node for the »chain-B«
|
||||
auto& n2s = alloc_.create<ProcNode>(
|
||||
prepareNode("srcB")
|
||||
.preparePort()
|
||||
.invoke(testGen.procID(), testGen.makeFun()) // params(frameNo, flavour)
|
||||
.closeParam<1>(SRC_B) // --> fix the flavour ≔ SRC_B
|
||||
.retrieveParam(accFrameNo)
|
||||
.completePort()
|
||||
.build());
|
||||
|
||||
|
||||
// A mixing Node to combine both chains
|
||||
auto& mix = alloc_.create<ProcNode>(
|
||||
prepareNode("mix")
|
||||
.preparePort()
|
||||
.invoke(testMix.procID(), testMix.makeFun())
|
||||
.attachParamFun(autoMixer) // mixer-param <-- autoMixer(frameNo)
|
||||
.connectLead(n1f)
|
||||
.connectLead(n2s)
|
||||
.completePort()
|
||||
.build());
|
||||
|
||||
|
||||
// Place a »Param-Agent«-Node on top to pre-compute the FrameNo
|
||||
auto& parNode = alloc_.create<ProcNode>(
|
||||
prepareNode("Param")
|
||||
.preparePort()
|
||||
.computeParam(paramSpec)
|
||||
.delegateLead(mix)
|
||||
.completePort()
|
||||
.build());
|
||||
|
||||
// here the »Param-Agent« also acts as the Exit-Node
|
||||
return parNode;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -116727,6 +116727,25 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
|
|||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1742172669058" ID="ID_1246173489" MODIFIED="1742172669058" TEXT="Job ruft 2-Node-Chain auf (incl Buffer-Management)">
|
||||
<linktarget COLOR="#01c3fe" DESTINATION="ID_1246173489" ENDARROW="Default" ENDINCLINATION="-1287;-72;" ID="Arrow_ID_1281025448" SOURCE="ID_1063052148" STARTARROW="None" STARTINCLINATION="-777;45;"/>
|
||||
<icon BUILTIN="idea"/>
|
||||
<node CREATED="1782408860168" ID="ID_1200447326" MODIFIED="1782408872611" TEXT="Bau des Node-Tree in eine Hilfsklasse auslagern">
|
||||
<node CREATED="1782408874426" ID="ID_1194116935" MODIFIED="1782408890796" TEXT="das ist weitgehend redundant mit NodeLink_test"/>
|
||||
<node CREATED="1782408892650" ID="ID_59723084" MODIFIED="1782408910377" TEXT="Erzeugen der Nodes sollte in einem Scope erfolgen"/>
|
||||
<node CREATED="1782408911567" ID="ID_1141231248" MODIFIED="1782408930823" TEXT="brauche schließlich aber nur eine Referenz auf die Exit-Node"/>
|
||||
<node CREATED="1782408938470" ID="ID_1128249575" MODIFIED="1782408948801" TEXT="⟹ Allocation-Cluster verwenden (RAII)"/>
|
||||
<node CREATED="1782408961611" ID="ID_223879187" MODIFIED="1782409005657" TEXT="Hilfsfunktion: Verifikation">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
sollte exakt die gleiche Kalkulation direkt wiederholen (genauso wie in NodeLink_test) und die Checksumme berechnen...
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1782263948069" ID="ID_1944543405" MODIFIED="1782264541446" TEXT="Methode: »Prototyp-Integration«">
|
||||
<icon BUILTIN="yes"/>
|
||||
|
|
|
|||
Loading…
Reference in a new issue