blob: 76824608f7ccd6445d418ac3af62b16eea7ec9d2 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef GARNET_BIN_MEDIAPLAYER_GRAPH_NODES_NODE_H_
#define GARNET_BIN_MEDIAPLAYER_GRAPH_NODES_NODE_H_
#include <lib/async/dispatcher.h>
#include <lib/fit/function.h>
#include <deque>
#include <queue>
#include "garnet/bin/mediaplayer/graph/nodes/input.h"
#include "garnet/bin/mediaplayer/graph/nodes/output.h"
#include "garnet/bin/mediaplayer/graph/packet.h"
#include "garnet/bin/mediaplayer/graph/payloads/payload_allocator.h"
#include "garnet/bin/mediaplayer/graph/payloads/payload_config.h"
#include "garnet/bin/mediaplayer/graph/refs.h"
#include "lib/fxl/synchronization/thread_annotations.h"
#include "lib/fxl/synchronization/thread_checker.h"
namespace media_player {
// TODO(dalesat): Ensure that we contractually have all the configuration
// info we need.
// TODO(dalesat): Track payload allocations and complain when usage exceeds
// expectations set by payload configurations.
// TODO(dalesat): Be more precise about the language around the semantics of
// payload configurations.
class Node : public std::enable_shared_from_this<Node> {
public:
using AllocateCallback =
fit::function<fbl::RefPtr<PayloadBuffer>(uint64_t, const PayloadVmos&)>;
Node();
virtual ~Node();
// Returns a diagnostic label for the node.
virtual const char* label() const;
// Generates a report for the node.
virtual void Dump(std::ostream& os) const;
// Configures inputs and outputs for the node. When this method is called,
// the node calls ConfigureInputXxx/ConfigureOutputXxx methods on the stage
// for each input and output the node will support.
//
// This method will be called on the graph's thread.
//
// TODO(dalesat): Support dynamic reconfiguration.
virtual void ConfigureConnectors() = 0;
//////////////////////////////////////////////////////////////////////////////
// Methods relating to inputs (inbound packets from upstream).
//////////////////////////////////////////////////////////////////////////////
// Notifies that the connection for the specified input is ready for
// allocation activity. Note that this method is not called if the connection
// becomes ready as the result of a call to a ConfigureInputXxx method on
// the stage. In that case, the ConfigureInputXxx method returns true to
// indicate the connection is ready.
virtual void OnInputConnectionReady(size_t input_index) {}
// Flushes an input. |hold_frame| indicates whether a video renderer should
// hold and display the newest frame. The callback is used to indicate that
// the flush operation is complete. It may be called synchronously or on an
// arbitrary thread. The default implementation aborts.
//
// Flushing operations proceed downstream from a particular output until a
// sink (node with no outputs) is reached. When an input is flushed on a node
// that has outputs, the node in question can assume that all of its outputs
// will be flushed as well. Outputs may be flushed independently, so the
// converse it not true.
//
// This method will be called on the graph's thread.
virtual void FlushInput(bool hold_frame, size_t input_index,
fit::closure callback) {
FXL_CHECK(false) << "FlushInput not implemented.";
}
// Supplies the node with a packet that arrived on the specified input. This
// method may call |NodeStage::RequestInputPacket|. The default
// implementation aborts.
//
// This method will be called on the graph's thread.
virtual void PutInputPacket(PacketPtr packet, size_t input_index) {
FXL_CHECK(false) << "PutInputPacket not implemented.";
}
// Returns the number of input connections.
size_t input_count() const;
// Returns the indicated input connection.
Input& input(size_t index);
// Returns the number of output connections.
size_t output_count() const;
// Returns the indicated output connection.
Output& output(size_t index);
// Notifies the node that the connection for the indicated input is ready
// for allocation activity.
//
// This method may be called on an arbitrary thread.
void NotifyInputConnectionReady(size_t index);
// Notifies the node that the connection for the indicated output is ready
// for allocation activity.
//
// This method may be called on an arbitrary thread.
void NotifyOutputConnectionReady(size_t index);
// Flushes an input. |hold_frame| indicates whether a video renderer should
// hold and display the newest frame. The callback is used to indicate that
// the flush operation is complete. It must be called on the graph's thread
// and may be called synchronously.
//
// The input in question must be flushed (|Input.Flush|) synchronously with
// this call to eject the queued packet (if there is one) and clear the
// input's need for a packet. The callback is provided in case the node
// has additional flushing business that can't be completed synchronously.
void FlushInputExternal(size_t index, bool hold_frame, fit::closure callback);
// Flushes an output. The callback is used to indicate that the flush
// operation is complete. It must be called on the graph's thread and may be
// called synchronously. The callback is provided in case the node has
// additional flushing business that can't be completed synchronously.
//
// The output in question must not produce any packets after this method is
// called and before the need for a packet is signalled.
void FlushOutputExternal(size_t index, fit::closure callback);
// Shuts down the stage.
void ShutDown();
// Queues the stage for update if it isn't already queued. This method may
// be called on any thread.
void NeedsUpdate();
// Calls |Update| until no more updates are required.
void UpdateUntilDone();
// Acquires the stage, preventing posted tasks from running until the stage
// is released. |callback| is called when the stage is acquired.
void Acquire(fit::closure callback);
// Releases the stage previously acquired via |Acquire|.
void Release();
// Sets an |async_t| for running tasks .
void SetDispatcher(async_dispatcher_t* dispatcher);
//////////////////////////////////////////////////////////////////////////////
// Methods relating to outputs (outbound packets to downstream).
//////////////////////////////////////////////////////////////////////////////
// Notifies that the connection for the specified output is ready for
// allocation activity. Note that this method is not called if the connection
// becomes ready as the result of a call to a ConfigureOutputXxx method on
// the stage. In that case, the ConfigureOutputXxx method returns true to
// indicate the connection is ready.
virtual void OnOutputConnectionReady(size_t output_index) {}
// Flushes an output. The callback is used to indicate that the flush
// operation is complete. It may be called synchronously or on an arbitrary
// thread. The default implementation aborts.
//
// Flushing operations proceed downstream from a particular output until a
// sink (node with no outputs) is reached. When an input is flushed on a node
// that has outputs, the node in question can assume that all of its outputs
// will be flushed as well. Outputs may be flushed independently, so the
// converse it not true.
//
// This method will be called on the graph's thread.
virtual void FlushOutput(size_t output_index, fit::closure callback) {
FXL_CHECK(false) << "FlushOutput not implemented.";
}
// Requests an output packet. The default implementation aborts.
//
// This method will be called on the graph's thread.
virtual void RequestOutputPacket() {
FXL_CHECK(false) << "RequestOutputPacket not implemented.";
}
//////////////////////////////////////////////////////////////////////////////
// Methods relating to inputs (inbound packets from upstream).
//////////////////////////////////////////////////////////////////////////////
// Indicates that the specified input exists but explicityly defers its
// configuration until a later time. This call is provided so the stage is
// informed that the input exists, even though the node doesn't know enough
// at that point to configure the input completely. This allows the input to
// be connected up by whoever is building the graph. The connection won't
// transition to ready state (see |InputConnectionReady|) until the input is
// fully configured.
//
// This method must be called on the main graph thread.
void ConfigureInputDeferred(size_t input_index = 0);
// Configures an input to address payloads as contiguous regions of process
// virtual memory. |max_aggregate_payload_size| sets expectations about how
// much memory will be required for all the payloads that the input will keep
// in memory at one time. This value does not include memory required by
// the connected output or for buffers queued on the connection. Likewise,
// |max_payload_count| sets expectations about how many payloads the input
// will keep in memory at one time. At least one of these two values must be
// non-zero.
//
// Calling this function prohibits the use of |UseInputVmos| or
// |ProvideInputVmos| for the specified input.
//
// Returns true if the connection is ready for allocation activity. Returns
// false if not, in which case |Node::InputConnectionReady| is called
// when the connection becomes ready.
//
// This method may be called on any thread provided the input has been
// configured previously (possibly with |ConfigureInputDeferred|). Otherwise,
// it must be called on the main graph thread.
bool ConfigureInputToUseLocalMemory(uint64_t max_aggregate_payload_size,
uint32_t max_payload_count,
size_t input_index = 0);
// Configures an input to address payloads as contiguous regions in VMOs
// that are created by some other party. |max_aggregate_payload_size| sets
// expectations about how much memory will be required for the payloads that
// the input will keep in memory at one time. This value does not include
// memory required by the connected output or for buffers queued on the
// connection. Likewise, |max_payload_count| sets expectations about how many
// payloads the input will keep in memory at one time. |max_payload_size|
// sets expectations about how large payloads can be. Either or both of
// |max_aggregate_payload_size| and |max_payload_count| must be non-zero.
//
// |vmo_allocation| indicates how the payload buffers must be distributed
// across the VMOs. |physically_contiguous| indicates whether the VMOs
// must be physically contiguous. If and only if |physically_contiguous|
// is true, |bti_handle| provides the handle required for
// |zx_vmo_create_contiguous|.
//
// Calling this function prohibits the use of |ProvideInputVmos| for the
// specified input. |UseInputVmos| is available to determine what VMOs are
// being used.
//
// |allocate_callback| is used when the node wants to perform allocations
// against the VMOs itself rather than allowing the |VmoAllocator| to do
// it. This callback will be called on an arbitrary thread and may not
// call any methods on the stage. The VMOs the allocator callback must
// allocate from will be provided by the payload manager, not by the
// connected output. This guarantee is made so the input doesn't have to
// deal with the arbitrary VMOs provided by the output.
// TODO(dalesat): Be explicit about what the VMOs will actually be like.
//
// Returns true if the connection is ready for allocation activity.
// Returns false if not, in which case |Node::InputConnectionReady|
// is called when the connection becomes ready.
//
// This method may be called on any thread provided the input has been
// configured previously (possibly with |ConfigureInputDeferred|).
// Otherwise, it must be called on the main graph thread.
bool ConfigureInputToUseVmos(
uint64_t max_aggregate_payload_size, uint32_t max_payload_count,
uint64_t max_payload_size, VmoAllocation vmo_allocation,
bool physically_contiguous = false, zx::handle bti_handle = zx::handle(),
AllocateCallback allocate_callback = nullptr, size_t input_index = 0);
// Configures an input to address payloads as contiguous regions in VMOs
// that the input provides. If the VMOs provided by the input are
// inadequate to hold all the payloads that are kept in memory at one time,
// the connection will adapt accordingly by creating a separate allocator
// for the output and doing copies. |vmo_allocation| indicates how the
// payload buffers will be distributed across the VMOs.
// |physically_contiguous| indicates whether the VMOs will be contiguous in
// physical memory.
//
// Calling this function allows the use of |ProvideInputVmos| for the
// specified input.
//
// |allocate_callback| is used when the node wants to perform allocations
// against the VMOs itself rather than allowing the |VmoAllocator| to do it.
// This callback will be called on an arbitrary thread and may not call any
// methods on the stage. The VMOs the allocator callback must allocate from
// will always be the same VMOs provided by the input.
//
// Returns true if the connection is ready for allocation activity. Returns
// false if not, in which case |Node::InputConnectionReady| is called
// when the connection becomes ready.
//
// This method may be called on any thread provided the input has been
// configured previously (possibly with |ConfigureInputDeferred|). Otherwise,
// it must be called on the main graph thread.
bool ConfigureInputToProvideVmos(VmoAllocation vmo_allocation,
bool physically_contiguous = false,
AllocateCallback allocate_callback = nullptr,
size_t input_index = 0);
// Returns true if the specified output is ready for calls to |UseInputVmos|
// or |ProvideInputVmos|.
//
// This method may be called on an arbitrary thread.
bool InputConnectionReady(size_t input_index = 0) const;
// Returns the |PayloadVmos| for the specified input. This method is only
// is only useable if |ConfigureInputToUseVmos| or
// |ConfigureInputToProvideVmos| has been called to configure the specified
// input, and the connection is ready.
//
// This method may be called on an arbitrary thread.
const PayloadVmos& UseInputVmos(size_t input_index = 0) const;
// Returns the |PayloadVmoProvision| for the specified input. This method is
// only useable if |ConfigureInputToProvideVmos| has been called to
// configure the specified input, and the connection is ready.
//
// This method may be called on an arbitrary thread.
PayloadVmoProvision& ProvideInputVmos(size_t input_index = 0);
// Requests an input packet on the specified input. |input_index| must be
// less than the configured input count. This method may be called from
// |Node::PutInputPacket|.
//
// This method may be called on an arbitrary thread.
void RequestInputPacket(size_t input_index = 0);
//////////////////////////////////////////////////////////////////////////////
// Methods relating to outputs (outbound packets to downstream).
//////////////////////////////////////////////////////////////////////////////
// Indicates that the specified output exists but explicityly defers its
// configuration until a later time. This call is provided so the stage is
// informed that the output exists, even though the node doesn't know enough
// at that point to configure the output completely. This allows the output
// to be connected up by whoever is building the graph. The connection won't
// transition to ready state (see |OutputConnectionReady|) until the output
// is fully configured.
//
// This method must be called on the main graph thread.
void ConfigureOutputDeferred(size_t output_index = 0);
// Configures an output to address payloads as contiguous regions of process
// virtual memory allocated by another party. |max_aggregate_payload_size|
// sets expectations about how much memory will be required for the payloads
// the output will keep in memory and for the payloads queued on the
// connection. This value does not include memory required by the connected
// input. Likewise, |max_payload_count| sets expectations about how many
// payloads the output will keep in memory at one time plus the number of
// payloads that may be queued on the connection. |max_payload_size| indicates
// how large a single payload may be. Either or both of
// |max_aggregate_payload_size| and |max_payload_count| must be non-zero.
//
// Calling this function prohibits the use of |UseOutputVmos| or
// |ProvideOutputVmos| for the specified output. |AllocatePayloadBuffer|
// is available for allocating payloads.
//
// Returns true if the connection is ready for allocation activity. Returns
// false if not, in which case |Node::OutputConnectionReady| is called
// when the connection becomes ready.
//
// This method may be called on any thread provided the output has been
// configured previously (possibly with |ConfigureOutputDeferred|). Otherwise,
// it must be called on the main graph thread.
bool ConfigureOutputToUseLocalMemory(uint64_t max_aggregate_payload_size,
uint32_t max_payload_count,
uint64_t max_payload_size,
size_t output_index = 0);
// Configures an output to allocate its own payloads from local memory. It is
// assumed that the output can allocate as much memory as is required.
// TODO(dalesat): Consider committing to handle shortfalls by copying.
//
// Calling this function prohibits the use of |UseOutputVmos|,
// |ProvideOutputVmos| or |AllocatePayloadBuffer| for the specified output.
//
// Returns true if the connection is ready for allocation activity. Returns
// false if not, in which case |Node::OutputConnectionReady| is called
// when the connection becomes ready.
//
// This method may be called on any thread provided the output has been
// configured previously (possibly with |ConfigureOutputDeferred|). Otherwise,
// it must be called on the main graph thread.
bool ConfigureOutputToProvideLocalMemory(size_t output_index = 0);
// Configures an output to address payloads as contiguous regions in VMOs
// that are created by some other party. |max_aggregate_payload_size|
// sets expectations about how much memory will be required for the payloads
// the output will keep in memory and for the payloads queued on the
// connection. This value does not include memory required by the connected
// input. Likewise, |max_payload_count| sets expectations about how many
// payloads the output will keep in memory at one time plus the number of
// payloads that may be queued on the connection. |max_payload_size| indicates
// how large a single payload may be. Either |max_aggregate_payload_size| or
// |max_payload_count| must be non-zero.
//
// |vmo_allocation| indicates how the payload buffers must be distributed
// across the VMOs. |physically_contiguous| indicates whether the VMOs must
// be physically contiguous. If and only if |physically_contiguous| is true,
// |bti_handle| provides the handle required for |zx_vmo_create_contiguous|.
//
// Calling this function prohibits the use of |ProvideOutputVmos| for the
// specified output. |UseOutputVmos| is available to determine what VMOs are
// being used, and |AllocatePayloadBuffer| is available for allocating
// payloads.
//
// Returns true if the connection is ready for allocation activity. Returns
// false if not, in which case |Node::OutputConnectionReady| is called
// when the connection becomes ready.
//
// This method may be called on any thread provided the output has been
// configured previously (possibly with |ConfigureOutputDeferred|). Otherwise,
// it must be called on the main graph thread.
bool ConfigureOutputToUseVmos(uint64_t max_aggregate_payload_size,
uint32_t max_payload_count,
uint64_t max_payload_size,
VmoAllocation vmo_allocation,
bool physically_contiguous = false,
zx::handle bti_handle = zx::handle(),
size_t output_index = 0);
// Configures an output to address payloads as contiguous regions in VMOs
// that the output provides. If the VMOs provided by the output are
// inadequate to hold all the payloads that are kept in memory at one time,
// the connection will adapt accordingly by creating a separate allocator
// for the output and doing copies. |vmo_allocation| indicates how the
// payload buffers will be distributed across the VMOs.
// |physically_contiguous| indicates whether the VMOs will be contiguous in
// physical memory.
//
// Calling this function allows the use of |ProvideOutputVmos| for the
// specified output, and |AllocatePayloadBuffer| is available for allocating
// payloads.
//
// Returns true if the connection is ready for allocation activity. Returns
// false if not, in which case |Node::OutputConnectionReady| is called
// when the connection becomes ready.
//
// This method may be called on any thread provided the output has been
// configured previously (possibly with |ConfigureOutputDeferred|). Otherwise,
// it must be called on the main graph thread.
bool ConfigureOutputToProvideVmos(VmoAllocation vmo_allocation,
bool physically_contiguous = false,
size_t output_index = 0);
// Returns true if the specified input is ready for calls to
// |AllocatePayloadBuffer|, |UseOutputVmos| or |ProvideOutputVmos|.
//
// This method may be called on an arbitrary thread.
bool OutputConnectionReady(size_t output_index = 0) const;
// Allocates a payload buffer for the specified output. This method
// is only useable if a ConfigureOutputFor* method other than
// |ConfigureOutputToProvideLocalMemory| has been called to configure the
// specified output, and the connection is ready.
//
// This method may be called on an arbitrary thread.
fbl::RefPtr<PayloadBuffer> AllocatePayloadBuffer(uint64_t size,
size_t output_index = 0);
// Returns the |PayloadVmos| for the specified output. This method is only
// useable if |ConfigureOutputToUseVmos| or |ConfigureOutputToProvideVmos|
// has been called to configure the specified output, and the connection is
// ready.
//
// This method may be called on an arbitrary thread.
const PayloadVmos& UseOutputVmos(size_t output_index = 0) const;
// Returns the |PayloadVmoProvision| for the specified output. This
// method is only useable if |ConfigureOutputToProvideVmos| has been called
// to configure the specified output, and the connection is ready.
//
// This method may be called on an arbitrary thread.
PayloadVmoProvision& ProvideOutputVmos(size_t output_index = 0);
// Supplies a packet to be sent downstream on the specified output.
//
// This method may be called on an arbitrary thread.
void PutOutputPacket(PacketPtr packet, size_t output_index = 0);
protected:
// Posts a task to run as soon as possible. A task posted with this method is
// run exclusive of any other such tasks.
void PostTask(fit::closure task);
// Updates packet supply and demand.
void Update();
// Post a task that will run even if the stage has been shut down.
void PostShutdownTask(fit::closure task);
private:
// Runs tasks in the task queue. This method is always called from
// |dispatcher_|. A |StageImpl| funnels all task execution through
// |RunTasks|. The lambdas that call |RunTasks| capture a shared pointer to
// the stage, so the stage can't be deleted from the time such a lambda is
// created until it's done executing |RunTasks|. A stage that's no longer
// referenced by the graph will be deleted when all such lambdas have
// completed. |ShutDown| prevents |RunTasks| from actually executing any
// tasks.
void RunTasks();
// Takes a packet from the queue for |output| if that queue isn't empty and
// the output needs a packet. Returns true if and only if the queue is empty
// and the output needs a packet.
bool MaybeTakePacketForOutput(const Output& output, PacketPtr* packet_out);
void DumpInputDetail(std::ostream& os, const Input& input) const;
void DumpOutputDetail(std::ostream& os, const Output& output) const;
void EnsureInput(size_t input_index);
void EnsureOutput(size_t output_index);
// The stage's thread is always the main graph thread.
FXL_DECLARE_THREAD_CHECKER(thread_checker_);
async_dispatcher_t* dispatcher_;
// Used for ensuring the stage is properly updated. This value is zero
// initially, indicating that there's no need to update the stage. When the
// stage needs updating, the counter is incremented. A transition from 0 to
// 1 indicates that the stage should be enqueued. Before the update occurs,
// this value is set to 1. If it's no longer 1 after update completes, it is
// updated again. When an update completes and the counter is still 1, the
// counter is reset to 0.
std::atomic_uint32_t update_counter_;
mutable std::mutex tasks_mutex_;
// Pending tasks. Only |RunTasks| may pop from this queue.
std::queue<fit::closure> tasks_ FXL_GUARDED_BY(tasks_mutex_);
// Set to true to suspend task execution.
bool tasks_suspended_ FXL_GUARDED_BY(tasks_mutex_) = false;
// These fields are modified on the main graph thread only.
std::vector<Input> inputs_;
std::vector<Output> outputs_;
mutable std::mutex packets_per_output_mutex_;
std::vector<std::deque<PacketPtr>> packets_per_output_
FXL_GUARDED_BY(packets_per_output_mutex_);
friend class Graph;
};
} // namespace media_player
#endif // GARNET_BIN_MEDIAPLAYER_GRAPH_NODES_NODE_H_