/*** 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 . ***/ #ifndef NODE_H #define NODE_H #include #include #include #include #include #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 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& 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 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 GetExclusiveDependencies() const; /** * @brief Retrieve immediate dependencies (only nodes that are directly connected to the inputs of this one) */ QList 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, bool include_arrays) 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 TransformTimeTo(const TimeRange& time, Node* target, NodeParam::Type direction); /** * @brief Find nodes of a certain type that this Node takes inputs from */ template QList FindInputNodes() const; template /** * @brief Find a node of a certain type that this Node outputs to */ QList FindOutputNode(); /** * @brief Convert a pointer to a value that can be sent between NodeParams */ static QVariant PtrToValue(void* ptr); template /** * @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 GetInputsIncludingArrays() const; QList 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 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); template static void FindInputNodeInternal(const Node* n, QList& list); template static void FindOutputNodeInternal(const Node* n, QList& list); QList GetDependenciesInternal(bool traverse, bool exclusive_only) const; QList 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 void Node::FindInputNodeInternal(const Node* n, QList& list) { QList inputs = n->GetInputsIncludingArrays(); foreach (NodeInput* input, inputs) { if (input->is_connected()) { Node* connected = input->get_connected_node(); T* cast_test = dynamic_cast(connected); if (cast_test) { list.append(cast_test); } FindInputNodeInternal(connected, list); } } } template QList Node::FindInputNodes() const { QList list; FindInputNodeInternal(this, list); return list; } template T* Node::ValueToPtr(const QVariant &ptr) { return reinterpret_cast(ptr.value()); } template void Node::FindOutputNodeInternal(const Node* n, QList& list) { foreach (NodeEdgePtr edge, n->output()->edges()) { Node* connected = edge->input()->parentNode(); T* cast_test = dynamic_cast(connected); if (cast_test) { list.append(cast_test); } FindOutputNodeInternal(connected); } } template QList Node::FindOutputNode() { QList list; FindOutputNodeInternal(this, list); return list; } OLIVE_NAMESPACE_EXIT #endif // NODE_H