blob: 3d20a0e4e44ad14fefa8458b8372b02963479806 [file] [log] [blame]
// Copyright 2018 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 SRC_MEDIA_PLAYBACK_MEDIAPLAYER_GRAPH_PAYLOADS_PAYLOAD_MANAGER_H_
#define SRC_MEDIA_PLAYBACK_MEDIAPLAYER_GRAPH_PAYLOADS_PAYLOAD_MANAGER_H_
#include <fuchsia/sysmem/cpp/fidl.h>
#include <lib/fit/thread_checker.h>
#include <lib/syslog/cpp/macros.h>
#include <mutex>
#include "src/lib/fxl/synchronization/thread_annotations.h"
#include "src/media/playback/mediaplayer/graph/payloads/local_memory_payload_allocator.h"
#include "src/media/playback/mediaplayer/graph/payloads/payload_allocator.h"
#include "src/media/playback/mediaplayer/graph/payloads/payload_config.h"
#include "src/media/playback/mediaplayer/graph/payloads/vmo_payload_allocator.h"
#include "src/media/playback/mediaplayer/graph/service_provider.h"
namespace media_player {
// DESIGN
//
// |PayloadManager| manages payload allocation for a connection. Its
// responsibilities are:
//
// 1) Assemble the right configuration of allocators based on the
// |PayloadConfig|s from the output and input.
// 2) Initialize the allocators prior to use by the output and input.
// 3) Expose the right capabilities to the output and input.
// 4) Arrange for payload copying when needed.
//
// |PayloadConfig| is described in detail in payload_config.h.
//
// The term 'connector' is used to refer to either the output or input.
//
// The allocator configuration may include zero, one or two allocators, and
// and there are two kinds of allocators, |LocalMemoryPayloadAllocator| and
// |VmoPayloadAllocator|. We associate a particular allocator with either the
// output or the input, though in some cases, both parties can access the same
// VMO allocator. In such cases, the allocator is associated with:
// 1) the connector supplying VMOs to the allocator, if there is one, otherwise
// 2) the connector that needs VMO access, if only one does, otherwise
// 3) the input.
// Associating the allocator with the input in the last case is arbitrary, in
// some respects, but it simplifies the code that deals with the input's
// requirement to perform allocations itself. See the |allocate_callback|
// parameter of |ApplyInputConfiguration|.
//
// When copying is performed, payloads produced by the output are copied to
// memory allocated from the input's allocator.
//
// In most cases, the correct allocator configuration can be established when
// both the output and the input have supplied their |PayloadConfig|s. There
// are other cases in which incompatibility is detected when VMOs are provided
// by the input or output, in which case the allocator configuration must be
// changed to have two allocators.
// Manages payload allocation for a connection, selecting and implementing the
// correct allocation strategy based on the constraints expressed by the
// output and input.
//
// Methods may be called on any thread unless otherwise noted in the method comments.
class PayloadManager {
public:
PayloadManager();
~PayloadManager() = default;
// Function type used by clients who want to implement buffer allocation
// themselves.
//
// uint64_t size - size in bytes of the buffer
// const PayloadVmos& vmos - the VMO collection from which to allocate
// (result) - A |PayloadBuffer| whose size is >= to the
// requested size, or nullptr if the allocation
// failed.
//
// The allocator callback is called on an arbitrary thread.
//
// The supplied VMOs are the same ones available on the node via
// |Node::UseOutputVmos| or |Node::UseInputVmos|. They're passed to the
// callback, because the callback may not call back into the node.
using AllocateCallback = fit::function<fbl::RefPtr<PayloadBuffer>(uint64_t, const PayloadVmos&)>;
// Dumps this |PayloadManager|'s state to |os|.
void Dump(std::ostream& os) const;
// Register callbacks to call when the connection is ready.
void RegisterReadyCallbacks(fit::closure output, fit::closure input);
// Register callbacks to call when the sysmem tokens have been replaced. These are only called
// when old tokens are being replaced. The first token for a node is available immediately after
// the node configures the connector.
void RegisterNewSysmemTokenCallbacks(fit::closure output, fit::closure input);
// Applies the output configuration supplied in |config|.
//
// This method must be called on the main graph thread.
void ApplyOutputConfiguration(const PayloadConfig& config,
ServiceProvider* service_provider = nullptr);
// Applies the input configuration supplied in |config|.
//
// |allocate_callback| may be supplied for VMO modes only. It allows the node
// to perform the actual allocations against the VMOs. The allocator callback
// will never be asked to allocate from VMOs provided by the output.
// |allocate_callback| is called on an arbitrary thread, and may not reenter
// this |PayloadManager|.
//
// This method must be called on the main graph thread.
void ApplyInputConfiguration(const PayloadConfig& config, AllocateCallback allocate_callback,
ServiceProvider* service_provider = nullptr);
// Indicates whether the connection manager is ready for allocator access.
bool ready() const;
// Allocates and returns a |PayloadBuffer| for the output with the specified
// size. Returns nullptr if the allocation fails.
fbl::RefPtr<PayloadBuffer> AllocatePayloadBufferForOutput(uint64_t size) const;
// Gets the |PayloadVmos| interface for the input. This method should only be
// called if this |PayloadManager| is ready and the input mode is |kUsesVmos|
// or |kProvidesVmos|.
PayloadVmos& input_vmos() const;
// Gets the |PayloadVmoProvision| interface for the input. This method should
// only be called if this |PayloadManager| is ready and the input mode is
// |kProvidesVmos|.
PayloadVmoProvision& input_external_vmos() const;
// Takes the |BufferCollectionTokenPtr| for the input. This method should only be called if this
// |PayloadManager| is ready and the input mode is |kUsesSysmemVmos|.
fuchsia::sysmem::BufferCollectionTokenPtr TakeInputSysmemToken();
// Gets the |PayloadVmos| interface for the output. This method should only be
// called if this |PayloadManager| is ready and the output mode is |kUsesVmos|
// or |kProvidesVmos|.
PayloadVmos& output_vmos() const;
// Gets the |PayloadVmoProvision| interface for the output. This method should
// only be called if this |PayloadManager| is ready and the output mode is
// |kProvidesVmos|.
PayloadVmoProvision& output_external_vmos() const;
// Takes the |BufferCollectionTokenPtr| for the output. This method should only be called if this
// |PayloadManager| is ready and the output mode is |kUsesSysmemVmos|.
fuchsia::sysmem::BufferCollectionTokenPtr TakeOutputSysmemToken();
// Indicates whether copying is required and maybe provides a copy destination
// payload buffer. This method returns true if and only if copying is required
// for this connection. If copying is required and |size| is non-zero, this
// method will attempt to allocate a payload buffer into which |size| bytes
// of payload may be copied. If this method returns true, |size| is non-zero
// and |*payload_buffer_out| is nullptr after the method returns, this
// indicates that payload memory for this purpose is exhausted.
//
// |payload_buffer_out| may only be nullptr if |size| is also zero.
bool MaybeAllocatePayloadBufferForCopy(uint64_t size,
fbl::RefPtr<PayloadBuffer>* payload_buffer_out) const;
// Signals that the output and input are disconnected.
void OnDisconnect();
// TEST ONLY.
// Returns a pointer to the |VmoPayloadAllocator| used to satisfy calls to |input_vmos| or
// |input_external_vmos|, if there is one, otherwise nullptr.
VmoPayloadAllocator* input_vmo_payload_allocator_for_testing() const {
std::lock_guard<std::mutex> locker(mutex_);
return input_vmo_payload_allocator_locked();
}
// TEST ONLY.
// Returns a pointer to the |VmoPayloadAllocator| used to satisfy calls to |output_vmos| or
// |output_external_vmos|, if there is one, otherwise nullptr.
VmoPayloadAllocator* output_vmo_payload_allocator_for_testing() const {
std::lock_guard<std::mutex> locker(mutex_);
return output_vmo_payload_allocator_locked();
}
// TEST ONLY.
// Returns a pointer to the |LocalMemoryPayloadAllocator| used to allocate memory for the output,
// if there is one, otherwise nullptr.
LocalMemoryPayloadAllocator* output_local_memory_payload_allocator_for_testing() const {
std::lock_guard<std::mutex> locker(mutex_);
return output_.local_memory_allocator_.get();
}
// TEST ONLY.
// Indicates whether this |PayloadManager| must copy payloads.
bool must_copy_for_testing() const {
std::lock_guard<std::mutex> locker(mutex_);
return copy_;
}
private:
// State relating to output or input.
struct Connector {
// Ensure that this |Connector| has no allocators.
void EnsureNoAllocator();
// Ensure that this |Connector| has a local memory allocator.
void EnsureLocalMemoryAllocator();
// Ensure that this |Connector| has a VMO allocator with no VMOs.
void EnsureEmptyVmoAllocator(VmoAllocation vmo_allocation);
// Ensures this |Connector| has a VMO allocator prepared to allocate from externally-provided
// VMOs. If |vmo_allocation| is not provided, the |VmoAllocation| value from |config_| is used.
void EnsureExternalVmoAllocator(VmoAllocation vmo_allocation = VmoAllocation::kNotApplicable);
// Ensures this |Connector| has a VMO allocator provisioned with VMOs as specified in |config|.
//
// This method is used in three cases:
// 1) The allocator is associated with only the output, in which case |config| is the config for
// that output.
// 2) The allocator is associated with only the input, in which case |config| is the augmented
// config for that input (|AugmentedInputConfig()|).
// 3) The allocator is shared between the output and input, in which case |config| is the
// merged configuration of the output and the input (|CombinedConfig()|).
void EnsureProvisionedVmoAllocator(const PayloadConfig& config);
// Ensures this |Connector| has a VMO allocator prepared to allocate from sysmem-provided VMOs.
//
// |local_config| is used to determine the buffer constraints that are sent to the sysmem buffer
// collection. Those constraints concern the needs of the *local* end of the connection. The
// end of the connection that uses sysmem itself supplies its constraints directly to sysmem.
// For example, if the upstream node uses sysmem and the downstream node wants to access
// payloads locally, the upstream node (associated with the output) will be providing its own
// constraints to sysmem directly, and |local_config| should reflect the constraints of the
// downstream node (associated with the input). In this case, |local_config| should be
// |input_.config_|, event though it's the output side that is using sysmem.
//
// |vmo_allocation| indicates how payload will be allocated from VMOs locally. In many cases,
// no such allocation will occur, in which case the default is appropriate. If allocation does
// occur, it must meet the constraints of the connector using sysmem.
//
// Sometime after this method is called, the owner's |DecrementReadyDeferrals| is called.
// The owning |PayloadManager| should increment |ready_deferrals_| before calling this method.
void EnsureProvisionedSysmemVmoAllocator(
const PayloadConfig& local_config, fuchsia::sysmem::Allocator* sysmem_allocator,
VmoAllocation vmo_allocation = VmoAllocation::kNotApplicable);
// Return a |PayloadAllocator| implemented by this connector, if there is
// one, nullptr otherwise.
PayloadAllocator* payload_allocator() const;
PayloadManager* owner_;
PayloadConfig config_;
fbl::RefPtr<LocalMemoryPayloadAllocator> local_memory_allocator_;
fbl::RefPtr<VmoPayloadAllocator> vmo_allocator_;
// |sysmem_token_for_node_| is the one provided to the node for its use.
fuchsia::sysmem::BufferCollectionTokenPtr sysmem_token_for_node_;
// |sysmem_token_for_mate_or_provisioning_| either becomes |sysmem_token_for_node_| for the
// other connector or is used to provision |vmo_allocator_| with buffers.
fuchsia::sysmem::BufferCollectionTokenPtr sysmem_token_for_mate_or_provisioning_;
fuchsia::sysmem::BufferCollectionPtr sysmem_collection_;
// Incremented when sysmem_token_for_node_ is set (not cleared).
uint32_t sysmem_token_generation_ = 0;
};
void DumpInternal(std::ostream& os) const FXL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Indicates whether the connection manager is ready for allocator access.
bool ready_locked() const FXL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
VmoPayloadAllocator* input_vmo_payload_allocator_locked() const
FXL_EXCLUSIVE_LOCKS_REQUIRED(mutex_) {
FX_DCHECK(ready_locked());
return input_.vmo_allocator_ ? input_.vmo_allocator_.get() : output_.vmo_allocator_.get();
}
// TEST ONLY.
VmoPayloadAllocator* output_vmo_payload_allocator_locked() const
FXL_EXCLUSIVE_LOCKS_REQUIRED(mutex_) {
FX_DCHECK(ready_locked());
return output_.vmo_allocator_ ? output_.vmo_allocator_.get() : input_.vmo_allocator_.get();
}
// Ensures that the connector has a pair of buffer collection tokens.
void EnsureBufferCollectionTokens(Connector* connector) FXL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Share |from|'s buffer collection with |to| creating a duplicate token |dup|.
void ShareBufferCollection(Connector* from, Connector* to,
fidl::InterfaceRequest<fuchsia::sysmem::BufferCollectionToken> dup)
FXL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Decrements |ready_deferrals_| and signals readiness if this |PayloadManager| is ready.
//
// This method must be called on the main graph thread.
void DecrementReadyDeferrals() FXL_LOCKS_EXCLUDED(mutex_);
// Ensures that |sysmem_allocator_| is populated.
//
// This method must be called on the main graph thread.
void EnsureSysmemAllocator() FXL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Updates the allocators based in the current configs.
//
// This method must be called on the main graph thread.
void UpdateAllocators() FXL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Determines whether the output and input configuration are compatible.
// The |mode_| values are not examined and are assumed to be compatible.
// When |kProvidesVmos| mode is used, incompatibility may not be detected
// until VMOs are supplied.
bool ConfigsAreCompatible() const FXL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Determines whether the output and input configuration modes are compatible.
// This method is only used for a DCHECK in |ConfigsAreCompatible|.
bool ConfigModesAreCompatible() const FXL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Returns a |VmoAllocation| value that satisfies both output and input,
// either |kSingleVmo| or |kVmoPerBuffer|. The output and input must have
// compatible |config_.vmo_allocation_| values.
VmoAllocation CombinedVmoAllocation() const FXL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Returns a |PayloadConfig| that combines both output an input payload configs. The output and
// input must have compatible |config_.vmo_allocation_| values.
PayloadConfig CombinedConfig() const FXL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Returns the input's |PayloadConfig| with the |max_payload_size_| value set to the max of those
// values for input and output.
PayloadConfig AugmentedInputConfig() const FXL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Returns the output's |PayloadConfig| with the |max_payload_count_| value set to the zero and
// |map_flags_| set to |ZX_VM_PERM_WRITE|.
PayloadConfig CopyToOutputConfig() const FXL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Allocates and returns a |PayloadBuffer| using the allocator callback.
// Returns nullptr if the allocation fails.
fbl::RefPtr<PayloadBuffer> AllocateUsingAllocateCallback(uint64_t size) const
FXL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
FIT_DECLARE_THREAD_CHECKER(thread_checker_);
mutable std::mutex mutex_;
Connector output_ FXL_GUARDED_BY(mutex_);
Connector input_ FXL_GUARDED_BY(mutex_);
// Optionally provided by the input to perform allocations against the input
// VMOS.
AllocateCallback allocate_callback_ FXL_GUARDED_BY(mutex_);
// Indicates whether copying must occur. If this field is true, the input
// will have an allocator.
bool copy_ FXL_GUARDED_BY(mutex_) = false;
ServiceProvider* service_provider_ FXL_GUARDED_BY(mutex_) = nullptr;
// Accessed only on the main graph thread.
fuchsia::sysmem::AllocatorPtr sysmem_allocator_ FXL_GUARDED_BY(mutex_);
// Async callbacks for readiness. Accessed only on the main graph thread.
fit::closure ready_callback_for_output_;
fit::closure ready_callback_for_input_;
// Async callbacks for when sysmem tokens have been replaced. Accessed only on the main graph
// thread.
fit::closure new_sysmem_token_callback_for_output_;
fit::closure new_sysmem_token_callback_for_input_;
// Count of reasons to defer readiness. This |PayloadManager| is ready when this value reaches
// zero and neither config mode is |kNotConfigured|. |ApplyOutputConfiguration| and
// |ApplyInputConfiguration| both increment this value on entry and decrement it on exit.
// Operations that defer readiness (e.g. |EnsureProvisionedSysmemVmoAllocator|) increment this
// value during |Apply*Configuration| and decrement it when complete.
uint32_t ready_deferrals_ FXL_GUARDED_BY(mutex_) = 0;
};
} // namespace media_player
#endif // SRC_MEDIA_PLAYBACK_MEDIAPLAYER_GRAPH_PAYLOADS_PAYLOAD_MANAGER_H_