Hi Joshua, Sergey pointed me at the EvaluationContext concept yesterday. I tend to see this as a "memory manager", to say it in my own words: It handles the creation and destruction of data buffers, while the actual depsgraph provides lifetime information (when is a data buffer needed, perhaps scheduling can even be optimized for memory use).
I would perhaps keep the types of data and operations separate: rather than have a matching context for each operation, have a dedicated set of data types (transform, mesh, pose, ...) that are used by various operations. That also leaves the possibility for operations to use multiple instances of data, e.g. a modifier combining several meshes. A operation-based context type might dictate data structure unnecessarily. The subgraph concept still seems a bit fuzzy to me. I would suggest to start by defining what features we want to see implemented, rather then presenting the implementation first and then explaining what it can be used for. - I mentioned evaluation for different purposes in the original mail (like render vs simulation). Some of these situations On Fri, Sep 18, 2015 at 2:53 AM, Joshua Leung <[email protected]> wrote: > Hi Lukas, > > Many of the points you've raised here are indeed things I've thought about > at some stage, and mostly agree with. > > Here's the scheme I was originally thinking of (but which we didn't/haven't > implemented yet - for timing/project management reasons, to get an initial > version working first). Warning: LONG TEXT AHEAD! > > Regards, > Joshua > > ----------------------------------------------------------- > > > == 1) Data Ready Nodes == > Similar to your data nodes, we have some explicit nodes in the graph which > signal when certain geometry blobs are "ready to use". Render engines and > the viewport drawing code could register handlers (perhaps as graph nodes, > or otherwise as plain callbacks) which get called when these "ready to use" > nodes are reached. The idea is that as soon as some data becomes available, > downstream users can pull that geometry and start working with it. > > This push-based scheme is partly motivated by the need to have some way to > keep memory usage down in geometry-heavy scenes, and particularly for the > duplicators/particle instancing cases. By having a way of allowing render > engines to take each piece of geometry as it becomes available, we can > minimise the amount of geometry that needs to be concurrently stored as > both Blender-side data and render-converted data. That is, instead of > having to convert the entire scene database to tesselated geometry that the > render engine then takes and converts into it's own internal format (i.e. > we now have 2x the data), we only have the Blender-tesselated stuff around > for as long as it takes for the render engine to convert it into whatever > format it needs. > > > == 2) Registering Interest + Pruning Eval Paths == > Prior to evaluating the depsgraph, whoever is requesting data to be > evaluated via the depsgraph must register their interest for that data. > This can be done in the form of registering callbacks for the relevant data > nodes (as mentioned above). An example of this are the viewport requesting > geometry and/or material colours for visible objects. > > We then "filter" the graph (or do some other processing) by only taking the > paths which lead to these data nodes are included in the graph to execute. > For example, if we're only interested in the transforms for two specific > bones (e.g. for plotting their motion paths), we only need to deal with the > subgraph with paths between any "dirty" nodes and the outputs required. > > (In the original depsgraph prototypes I was doing, this stuff was basically > the whole "querying and filtering" API, which was intended to take the > original "full" depsgraph you have in the scene, and extract out the set of > nodes + relations needed for some specific evaluation problem - like > iteratively evaluating a rig to solve the nasty > posespace-to-transformchannels inverse problem. The filtered graph would > then just contain a reduced set of nodes + relations, and could be set off > to work in a background thread on a separate copy of the data; since it > doesn't contain unnecessary stuff, we save time trying to prevent > unnecessary nodes from evaluating.) > > Where does this registration of interest occur? Well, one thing that we've > learned is that the depsgraph nodes themselves shouldn't be used to store > data state stuff like this; so that means... > > > == 3) Evaluation Contexts => Data Stores == > Disclaimer: IIRC, this seems to be one of the parts where Sergey and I have > slightly different ideas about what should happen. It is also one of the > more invasive/work intensive paths forward. > > If you've had a look at the evaluation callbacks recently, you'll have > noticed that we have a thing called "EvaluationContext" (or a similar name) > which gets passed around. It was introduced as a way to help the various > update processes distinguish between viewport drawing, preview rendering, > and proper rendering. Currently, it also gets passed out to all > OperationDepsNode callbacks as the first arg, laying the foundations for > the inevitable (since it will be needed in some form for those). > > The idea is to extend the usage of this thing, and make it a full-blown > "data store". All data access/management during evaluation passes through > it: > A) Every time some evaluation process needs some piece of data, it asks > the EvaluationContext to give it to them. If necessary, we can distinguish > between requests for "reading" results, and for "writing" results. --> > Sergey's "Copy on Write" stuff could fit in here seamlessly, as we'd be > effectively mediating data access. > > B) By making evaluation operations work on the data provided by the > EvaluationContext instead of the DNA copies directly, we solve many of the > concurrency/instancing problems we have now, as we can simply use a > separate evaluation context for each use case (e.g. one for render, one for > viewport, one for baking). Of course, we now have to make a special > exception (or maybe not? it could work via standard interest registration) > here so that viewport evaluation results get flushed back down to the DNA, > so that all existing tools continue to work the way they have. > > C) Every time you evaluate the depsgraph, you pass it an Evaluation > Context object. When creating the evaluation context, you specify what the > evaluation is for (e.g. render, viewport, baking, background-calculations, > etc.), register interest in results to get out of the evaluation (e.g. > geometry for objects 1,2,3 and the main character's rig), as well as any > callbacks to execute when certain data becomes available (e.g. > mesh/curve/surface geometry, materials, etc.). In response, the evaluation > context will create the relevant DataContexts in preparation/anticipation > for their availability once evaluation has completed. > > So, what are these "DataContexts"? What is stored in the EvaluationContext, > and what about intermediate results? > * To support the granular operations we're now performing, we need > somewhere to store intermediate results used between operations. Usually, > these intermediate results require a bunch of "related products". We > encapsulate each clump of such things as a "DataContext". > > * Examples of "DataContexts" are ParametersContext, TransformContext, > GeometryContext, PoseContext, etc. Basically, for each Component type/node > in the depsgraph, we have a corresponding DataContext. > > For example, the TransformContext would store the current 4x4 matrix > ("x-form" to use the Dreamworks terminology), along with the constraint > stack eval stuff - bConstraintOb, etc. > > Another example is the GeometryContext, which would store the DerivedMesh, > DispList, and/or Path data. > > * One special class of DataContexts are the TimeSource contexts. These > store the current frame + subframe => time float/double value. Again, each > TimeSource context corresponds to one TimeSource node. Therefore, the > operation on a TimeSourceDepsNode is that it either sets/updates the time > stored in the timesource (for the primary), or that it computes the time it > stores (e.g. for the secondary ones used for doing time offset animation, > etc.) > > * DataContexts usually get created/initialised during the "init" > operations for each Component. The "ready" or "done" operation for each > component therefore triggers the "ready to use" callbacks that may have > been registered against that data. Those callbacks will then receive a > pointer to the relevant DataContext containing the data they requested to > be evaluated. > > * If necessary, more than one data context may be created per node (i.e. > to handle intermediate results that need to be used by two different > forking evaluation paths). The exact details of how that would work would > require a bit more thinking... > > * All DataContexts are stored in the EvaluationContext, and are retried > by using some kind of "data access key". This would probably be similar to > the things we use now for finding nodes in the depsgraph, but will probably > need additional info to handle separate instances of duplis or other > dynamically generated stuff. > > > == 4) Subgraphs and Evaluation Contexts == > The general idea is that each subgraph will get its own evaluation context. > A subgraph is typically something like a background set, or maybe a group > (which doesn't interact with the rest of the world). > > It is also possible to just have subgraphs evaluated in the same evaluation > context, just that the access keys would need extra qualifications to > ensure that we're accessing the copies of the data for the subgraphs and > not the main graph. > > > > On Fri, Sep 18, 2015 at 2:18 AM, Lukas Tönne <[email protected]> > wrote: > > > I want to argue for adding a new concept to the new depsgraph design, > which > > i tentatively call "Data Nodes". > > > > The new dependency graph currently supports most of the old depsgraph's > > functionality and has fixed some issues relating to pose animation, but > has > > not yet started to actually add finer detailed operations or actual new > > functionality. In fact, some important parts are still missing (e.g. > > pruning of hidden object evaluation), and some features have been ported > > from the old implementation in the same awkward fashion (layers, > uber-eval > > nodes). > > > > Note for clarity: I will use "dependency graph" to mean the NEW > dependency > > graph, and prefix it with "old" when referring to the old implementation. > > > > A logical expectation would be to handle modifier evaluations and later > > nodes through scheduled operations in the depsgraph. This would mean > that a > > node graph (aka node "tree") is translated into a set of interdependent > > operations, which are scheduled for threaded execution and can run in > > parallel where they don't depend on each other. There are provisions in > the > > depsgraph for individual modifier operations, which are currently a > simple > > chain - but these are just stubs and do not yet create actual data. > > > > A typical case with nodes is that a lot of the nodes in a graph are not > > actually used for the final result. Users often create whole branches of > > nodes that are either muted or not connected to some output node, i.e. > they > > should not be evaluated. The current definition of "dependency" makes it > > difficult to implement pruning in a consistent way. One might consider to > > actually rebuild the whole dependency graph every time the set of active > > nodes is changed and completely leave away unused nodes. Information on > > visibility and such would not need to be stored in nodes at all, any node > > in the graph is also used. > > > > However, the depsgraph is supposed to be a permanent description of the > > scene relations that is not rebuilt very often. More importantly: the > > depsgraph schedules operations for a whole range of situations, like > > - property changes > > - layer and object visibility changes > > - time changes > > - incremental simulation steps > > - render database updates > > > > All of these situations can have different "used" data (an object may be > > invisible during renders, simulation steps can apply only to rigid > bodies, > > etc.). Building a specialized depsgraph for each of them is not very > > desirable. > > > > The set of necessary operations to be scheduled is not just defined by > > "what has changed", but also by "what is needed/visible". Note how both > of > > these labels are written in terms of _data_ rather than operations. In > the > > dependency graph all data is the result of a single operation, so we can > > just as well use "operation" nodes to represent a piece of data that this > > operation writes to, like object transformations, a DerivedMesh, render > > results. The trouble with the strict operation nodes in the depsgraph is > > that no explicit "end points" for data exist, which tie data to its final > > purpose (like the derivedFinal mesh in objects). In consequence, > > backtracking used-ness of operations based on final visibility is not > > possible without fully rebuilding the depsgraph. > > > > Beside lacking such "data nodes", the current way of forward-propagating > > ("flushing") evaluation tags through the dependency nodes also needs to > be > > augmented by a backward-propagated "used node" set. Currently, a node is > > always scheduled if any of its parent nodes is scheduled (i.e. some input > > data has changed), but if a child node isn't actually used the parents > will > > still be scheduled. This wasn't a problem in the old depsgraph, because > of > > the coarse nodes which could store a coherent visibility flag for each > > individual node. With the finer resolution in the new depsgraph and the > > expected differentiation in evaluation cases the problem becomes more > > apparent. > > > > Finally, the addition of explicit data nodes could solve a big design > > problem with generated data in Blender: All the current depsgraph > > operations use _implicit_ references to data in the DNA for storing > runtime > > results (mostly overriding obmat and the derivedFinal mesh result in > > Object). Data nodes could help manage transient runtime data. The > operation > > scheduling state can be used to manage the lifetime of such data > > efficiently, to avoid overhead from keeping unnecessary buffers > allocated. > > Multiple variants of objects (branching operations) are possible if data > is > > not glued to DNA instances. > > _______________________________________________ > > Bf-committers mailing list > > [email protected] > > http://lists.blender.org/mailman/listinfo/bf-committers > > > _______________________________________________ > Bf-committers mailing list > [email protected] > http://lists.blender.org/mailman/listinfo/bf-committers > _______________________________________________ Bf-committers mailing list [email protected] http://lists.blender.org/mailman/listinfo/bf-committers
