File: node.h

package info (click to toggle)
olive-editor 20200620-2
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 40,228 kB
  • sloc: cpp: 51,932; sh: 56; makefile: 7; xml: 7
file content (533 lines) | stat: -rw-r--r-- 16,599 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
/***

  Olive - Non-Linear Video Editor
  Copyright (C) 2019 Olive Team

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.

***/

#ifndef NODE_H
#define NODE_H

#include <QCryptographicHash>
#include <QObject>
#include <QPainter>
#include <QPointF>
#include <QXmlStreamWriter>

#include "codec/frame.h"
#include "codec/samplebuffer.h"
#include "common/rational.h"
#include "common/xmlutils.h"
#include "node/input.h"
#include "node/inputarray.h"
#include "node/output.h"
#include "node/value.h"
#include "render/audioparams.h"
#include "render/shaderinfo.h"

OLIVE_NAMESPACE_ENTER

/**
 * @brief A single processing unit that can be connected with others to create intricate processing systems
 *
 * A cornerstone of "visual programming", a node is a single "function" that takes input and returns an output that can
 * be connected to other nodes. Inputs can be either user-set or retrieved from the output of another node. By joining
 * several nodes together, intricate, highly customizable, and infinitely extensible systems can be made for processing
 * data. It can also all be exposed to the user without forcing them to write code or compile anything.
 *
 * A major example in Olive is the entire rendering workflow. To render a frame, Olive will work through a node graph
 * that can be infinitely customized by the user to create images.
 *
 * This is a simple base class designed to contain all the functionality for this kind of processing connective unit.
 * It is an abstract class intended to be subclassed to create nodes with actual functionality.
 */
class Node : public QObject
{
  Q_OBJECT
public:
  enum CategoryID {
    kCategoryUnknown = -1,

    kCategoryInput,
    kCategoryOutput,
    kCategoryGenerator,
    kCategoryMath,
    kCategoryFilter,
    kCategoryColor,
    kCategoryGeneral,
    kCategoryTimeline,
    kCategoryChannels,
    kCategoryTransition,

    kCategoryCount
  };

  Node();

  virtual ~Node() override;

  /**
   * @brief Creates a clone of the Node
   *
   * By default, the clone will NOT have the values and connections of the original node. The caller is responsible for
   * copying that data with functions like CopyInputs() as copies may be done for different reasons.
   */
  virtual Node* copy() const = 0;

  /**
   * @brief Clear current node variables and replace them with
   */
  void Load(QXmlStreamReader* reader, XMLNodeData &xml_node_data, const QAtomicInt *cancelled);

  /**
   * @brief Save this node into a text/XML format
   */
  void Save(QXmlStreamWriter* writer, const QString& custom_name = QString()) const;

  /**
   * @brief Return the name of the node
   *
   * This is the node's name shown to the user. This must be overridden by subclasses, and preferably run through the
   * translator.
   */
  virtual QString Name() const = 0;

  /**
   * @brief Returns a shortened name of this node if applicable
   *
   * Defaults to returning Name() but can be overridden.
   */
  virtual QString ShortName() const;

  /**
   * @brief Return the unique identifier of the node
   *
   * This is used in save files and any other times a specific node must be picked out at runtime. This must be an ID
   * completely unique to this node, and preferably in bundle identifier format (e.g. "org.company.Name"). This string
   * should NOT be translated.
   */
  virtual QString id() const = 0;

  /**
   * @brief Return the category this node is in (optional for subclassing, but recommended)
   *
   * In any organized node menus, show the node in this category. If this node should be in a subfolder of a subfolder,
   * use a "/" to separate categories (e.g. "Distort/Noise"). The string should not start with a "/" as this will be
   * interpreted as an empty string category. This value should be run through a translator as its largely user
   * oriented.
   */
  virtual QList<CategoryID> Category() const = 0;

  /**
   * @brief Return a description of this node's purpose (optional for subclassing, but recommended)
   *
   * A short (1-2 sentence) description of what this node should do to help the user understand its purpose. This should
   * be run through a translator.
   */
  virtual QString Description() const;

  /**
   * @brief Function called to retranslate parameter names (should be overridden in derivatives)
   */
  virtual void Retranslate();

  /**
   * @brief Return a list of NodeParams
   */
  const QList<NodeParam*>& parameters() const;

  /**
   * @brief Return the index of a parameter
   * @return Parameter index or -1 if this parameter is not part of this Node
   */
  int IndexOfParameter(NodeParam* param) const;

  /**
   * @brief Return a list of all Nodes that this Node's inputs are connected to (does not include this Node)
   */
  QList<Node*> GetDependencies() const;

  /**
   * @brief Returns a list of Nodes that this Node is dependent on, provided no other Nodes are dependent on them
   * outside of this hierarchy.
   *
   * Similar to GetDependencies(), but excludes any Nodes that are used outside the dependency graph of this Node.
   */
  QList<Node*> GetExclusiveDependencies() const;

  /**
   * @brief Retrieve immediate dependencies (only nodes that are directly connected to the inputs of this one)
   */
  QList<Node*> GetImmediateDependencies() const;

  /**
   * @brief Generate hardware accelerated code for this Node
   */
  virtual ShaderCode GetShaderCode(const QString& shader_id) const;

  /**
   * @brief If Value() pushes a ShaderJob, this is the function that will process them.
   */
  virtual void ProcessSamples(NodeValueDatabase &values, const SampleBufferPtr input, SampleBufferPtr output, int index) const;

  /**
   * @brief If Value() pushes a GenerateJob, override this function for the image to create
   *
   * @param frame
   *
   * The destination buffer. It will already be allocated and ready for writing to.
   */
  virtual void GenerateFrame(FramePtr frame, const GenerateJob &job) const;

  /**
   * @brief Returns the input with the specified ID (or nullptr if it doesn't exist)
   */
  NodeInput* GetInputWithID(const QString& id) const;

  /**
   * @brief Returns the output with the specified ID (or nullptr if it doesn't exist)
   */
  NodeOutput* GetOutputWithID(const QString& id) const;

  /**
   * @brief Returns whether this Node outputs to `n`
   *
   * @param n
   *
   * The node instance to check.
   *
   * @param recursively
   *
   * Whether to keep traversing down outputs to find this node (TRUE) or stick to immediate outputs
   * (FALSE).
   */
  bool OutputsTo(Node* n, bool recursively) const;

  /**
   * @brief Same as OutputsTo(Node*), but for a node ID rather than a specific instance.
   */
  bool OutputsTo(const QString& id, bool recursively) const;

  /**
   * @brief Same as OutputsTo(Node*), but for a specific node input rather than just a node.
   */
  bool OutputsTo(NodeInput* input, bool recursively) const;

  /**
   * @brief Returns whether this node ever receives an input from a particular node instance
   */
  bool InputsFrom(Node* n, bool recursively) const;

  /**
   * @brief Returns whether this node ever receives an input from a node with a particular ID
   */
  bool InputsFrom(const QString& id, bool recursively) const;

  /**
   * @brief Determines how many paths go from this node out to another node
   */
  int GetRoutesTo(Node* n) const;

  /**
   * @brief Return whether this Node has input parameters
   */
  bool HasInputs() const;

  /**
   * @brief Return whether this Node has output parameters
   */
  bool HasOutputs() const;

  /**
   * @brief Return whether this Node has input parameters and at least one of them is connected
   */
  bool HasConnectedInputs() const;

  /**
   * @brief Return whether this Node has output parameters and at least one of them is connected
   */
  bool HasConnectedOutputs() const;

  /**
   * @brief Severs all input and output connections
   */
  void DisconnectAll();

  /**
   * @brief Get the human-readable name for any category
   */
  static QString GetCategoryName(const CategoryID &c);

  /**
   * @brief Transforms time from this node through the connections it takes to get to the specified node
   */
  QList<TimeRange> TransformTimeTo(const TimeRange& time, Node* target, NodeParam::Type direction);

  template<class T>
  /**
   * @brief Find a node of a certain type that this Node outputs to
   */
  T* FindOutputNode();

  /**
   * @brief Convert a pointer to a value that can be sent between NodeParams
   */
  static QVariant PtrToValue(void* ptr);

  template<class T>
  /**
   * @brief Convert a NodeParam value to a pointer of any kind
   */
  static T* ValueToPtr(const QVariant& ptr);

  /**
   * @brief Signal all dependent Nodes that anything cached between start_range and end_range is now invalid and
   *        requires re-rendering
   *
   * Override this if your Node subclass keeps a cache, but call this base function at the end of the subclass function.
   * Default behavior is to relay this signal to all connected outputs, which will need to be done as to not break
   * the DAG. Even if the time needs to be transformed somehow (e.g. converting media time to sequence time), you can
   * call this function with transformed time and relay the signal that way.
   */
  virtual void InvalidateCache(const TimeRange& range, NodeInput* from, NodeInput* source);

  /**
   * @brief Limits cache invalidation temporarily
   *
   * If you intend to do a number of operations in quick succession, you can optimize it by running
   * this function with EndOperation().
   */
  virtual void BeginOperation();

  /**
   * @brief Stops limiting cache invalidation and flushes changes
   */
  virtual void EndOperation();

  /**
   * @brief Adjusts time that should be sent to nodes connected to certain inputs.
   *
   * If this node modifies the `time` (i.e. a clip converting sequence time to media time), this function should be
   * overridden to do so. Also make sure to override OutputTimeAdjustment() to provide the inverse function.
   */
  virtual TimeRange InputTimeAdjustment(NodeInput* input, const TimeRange& input_time) const;

  /**
   * @brief The inverse of InputTimeAdjustment()
   */
  virtual TimeRange OutputTimeAdjustment(NodeInput* input, const TimeRange& input_time) const;

  /**
   * @brief Copies inputs from from Node to another including connections
   *
   * Nodes must be of the same types (i.e. have the same ID)
   */
  static void CopyInputs(Node* source, Node* destination, bool include_connections = true);

  /**
   * @brief Return whether this Node can be deleted or not
   */
  bool CanBeDeleted() const;

  /**
   * @brief Set whether this Node can be deleted in the UI or not
   */
  void SetCanBeDeleted(bool s);

  /**
   * @brief Returns whether this Node is a "Block" type or not
   *
   * You shouldn't ever need to override this since all derivatives of Block will automatically have this set to true.
   * It's just a more convenient way of checking than dynamic_casting.
   */
  virtual bool IsBlock() const;

  /**
   * @brief Returns whether this Node is a "Track" type or not
   *
   * You shouldn't ever need to override this since all derivatives of Track will automatically have this set to true.
   * It's just a more convenient way of checking than dynamic_casting.
   */
  virtual bool IsTrack() const;

  /**
   * @brief The main processing function
   *
   * The node's main purpose is to take values from inputs to set values in outputs. For whatever subclass node you
   * create, this is where the code for that goes.
   *
   * Note that as a video editor, the node graph has to work across time. Depending on the purpose of your node, it may
   * output different values depending on the time, and even if not, it will likely be receiving different input
   * depending on the time. Most of the difficult work here is handled by NodeInput::get_value() which you should pass
   * the `time` parameter to. It will return its value (at that time, if it's keyframed), or pass the time to a
   * corresponding output if it's connected to one. If your node doesn't directly deal with time, the default behavior
   * of the NodeParam objects will handle everything related to it automatically.
   */
  virtual NodeValueTable Value(NodeValueDatabase& value) const;

  /**
   * @brief Return whether a parameter with ID `id` has already been added to this Node
   */
  bool HasParamWithID(const QString& id) const;

  NodeOutput* output() const;

  const QPointF& GetPosition() const;

  void SetPosition(const QPointF& pos);

  static QString ReadFileAsString(const QString& filename);

  QList<NodeInput*> GetInputsIncludingArrays() const;

  QList<NodeOutput*> GetOutputs() const;

  virtual bool HasGizmos() const;

  virtual void DrawGizmos(NodeValueDatabase& db, QPainter* p, const QVector2D &scale, const QSize& viewport) const;

  virtual bool GizmoPress(NodeValueDatabase& db, const QPointF& p, const QVector2D &scale, const QSize& viewport);
  virtual void GizmoMove(const QPointF& p, const QVector2D &scale, const rational &time);
  virtual void GizmoRelease();

  const QString& GetLabel() const;
  void SetLabel(const QString& s);

  virtual void Hash(QCryptographicHash& hash, const rational &time) const;

protected:
  void AddInput(NodeInput* input);

  void ClearCachedValuesInParameters(const rational& start_range, const rational& end_range);

  void SendInvalidateCache(const TimeRange &range, NodeInput *source);

  virtual void LoadInternal(QXmlStreamReader* reader, XMLNodeData& xml_node_data);

  virtual void SaveInternal(QXmlStreamWriter* writer) const;

  virtual QList<NodeInput*> GetInputsToHash() const;

protected slots:
  void InputChanged(const OLIVE_NAMESPACE::TimeRange &range);

  void InputConnectionChanged(NodeEdgePtr edge);

signals:
  /**
   * @brief Signal emitted when a node is connected to another node (creating an "edge")
   *
   * @param edge
   *
   * The edge that was added
   */
  void EdgeAdded(NodeEdgePtr edge);

  /**
   * @brief Signal emitted when a node is disconnected from another node (removing an "edge")
   *
   * @param edge
   *
   * The edge that was removed
   */
  void EdgeRemoved(NodeEdgePtr edge);

  /**
   * @brief Signal emitted whenever the position is set through SetPosition()
   */
  void PositionChanged(const QPointF& pos);

  /**
   * @brief Signal emitted when SetLabel() is called
   */
  void LabelChanged(const QString& s);

private:
  /**
   * @brief Add a parameter to this node
   *
   * The Node takes ownership of this parameter.
   *
   * This can be either an output or an input at any time. Parameters will always appear in the order they're added.
   */
  void AddParameter(NodeParam* param);

  bool HasParamOfType(NodeParam::Type type, bool must_be_connected) const;

  void ConnectInput(NodeInput* input);

  void DisconnectInput(NodeInput* input);

  QList<Node *> GetDependenciesInternal(bool traverse, bool exclusive_only) const;

  QList<NodeParam *> params_;

  /**
   * @brief Internal variable for whether this Node can be deleted or not
   */
  bool can_be_deleted_;

  /**
   * @brief Primary node output
   */
  NodeOutput* output_;

  /**
   * @brief UI position for NodeViews
   */
  QPointF position_;

  /**
   * @brief Custom user label for node
   */
  QString label_;

};

template<class T>
T* Node::ValueToPtr(const QVariant &ptr)
{
  return reinterpret_cast<T*>(ptr.value<quintptr>());
}

template<class T>
Node* FindOutputNodeInternal(Node* n) {
  foreach (NodeEdgePtr edge, n->output()->edges()) {
    Node* connected = edge->input()->parentNode();
    T* cast_test = dynamic_cast<T*>(connected);

    if (cast_test) {
      return cast_test;
    } else {
      Node* drill_test = FindOutputNodeInternal<T>(connected);
      if (drill_test) {
        return drill_test;
      }
    }
  }

  return nullptr;
}

template<class T>
T* Node::FindOutputNode()
{
  return static_cast<T*>(FindOutputNodeInternal<T>(this));
}

OLIVE_NAMESPACE_EXIT

#endif // NODE_H