blob: d32d43b841f38f088145b5fb184a89ed42ba6152 [file] [log] [blame]
// Copyright 2021 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_VNEXT_LIB_STREAM_IO_BUFFER_COLLECTION_H_
#define SRC_MEDIA_VNEXT_LIB_STREAM_IO_BUFFER_COLLECTION_H_
#include <fuchsia/media2/cpp/fidl.h>
#include <lib/async/cpp/executor.h>
#include <lib/fpromise/bridge.h>
#include <lib/fpromise/promise.h>
#include <lib/fpromise/scope.h>
#include <lib/fzl/vmo-mapper.h>
#include <lib/sync/completion.h>
#include <lib/zx/vmo.h>
#include <vector>
#include "src/lib/fxl/synchronization/thread_annotations.h"
#include "src/media/vnext/lib/stream_io/payload_buffer.h"
namespace fmlib {
// Base class for |OutputBufferCollection| and |InputBufferCollection|, which are used when payload
// buffers must be mapped into process memory.
class BufferCollection {
public:
virtual ~BufferCollection() = default;
// Returns duplicates of the VMOs managed by this |BufferCollection|. This method is thread-safe.
std::vector<zx::vmo> DuplicateVmos(zx_rights_t rights) const;
protected:
// A payload buffer VMO held by this |BufferCollection|.
struct BufferVmo {
BufferVmo(zx::vmo vmo, zx_vm_option_t map_flags);
// The status returned by the initialization of this |BufferVmo|.
zx_status_t status() const { return status_; }
const zx::vmo& vmo() const { return vmo_; }
zx::vmo& vmo() { return vmo_; }
bool is_valid() const { return status_ == ZX_OK; }
// Returns the address in process virtual memory where this VMO is mapped.
void* data() const { return vmo_mapper_.start(); }
// Returns the size of this VMO in bytes.
size_t size() const { return vmo_mapper_.size(); }
// Returns |data()| offset by |offset| bytes.
void* at_offset(size_t offset) const;
// Determines if this |BufferVmo| is currently allocated.
bool is_allocated() const { return allocated_; }
// Indicates this |BufferVmo| is now allocated.
void Allocate() { allocated_ = true; }
// Indicates this |BufferVmo| is now free.
void Free() { allocated_ = false; }
private:
zx_status_t status_;
zx::vmo vmo_;
fzl::VmoMapper vmo_mapper_;
bool allocated_ = false;
};
// Constructs a |BufferCollection|.
explicit BufferCollection(std::vector<BufferVmo> buffer_vmos)
: buffer_vmos_(std::move(buffer_vmos)) {}
// Calls |GetBuffers| on the buffer provider and returns a promise that completes when the
// provider responds.
[[nodiscard]] static fpromise::promise<std::vector<zx::vmo>, fuchsia::media2::BufferProviderError>
GetBuffers(fuchsia::media2::BufferProvider& provider, zx::eventpair token,
const fuchsia::media2::BufferConstraints& constraints, const std::string& name,
uint64_t id, zx_vm_option_t map_flags);
// Creates a vector of |BufferVmo|s from a vector of |zx::vmo|s. |vmos| must not be empty.
// Returns an empty array if mapping fails for any of |vmos|.
static std::vector<BufferVmo> CreateBufferVmos(std::vector<zx::vmo> vmos,
zx_vm_option_t map_flags);
std::vector<BufferVmo>& buffer_vmos() FXL_EXCLUSIVE_LOCKS_REQUIRED(mutex_) {
return buffer_vmos_;
}
const std::vector<BufferVmo>& buffer_vmos() const FXL_EXCLUSIVE_LOCKS_REQUIRED(mutex_) {
return buffer_vmos_;
}
mutable std::mutex mutex_;
private:
std::vector<BufferVmo> buffer_vmos_ FXL_GUARDED_BY(mutex_);
fpromise::scope scope_;
};
// A |BufferCollection| to be used for outputs. This subclass provides methods for allocating
// payload buffers.
class OutputBufferCollection : public BufferCollection {
public:
// Starts creating an |OutputBufferCollection| and returns a promise that returns the collection
// when the it is ready to use. |provider| must be valid until the operation completes. This
// method is thread-safe, and the returned promise may be run on any thread. |executor| is used to
// run promises when payload buffers are released.
[[nodiscard]] static fpromise::promise<std::unique_ptr<OutputBufferCollection>,
fuchsia::media2::ConnectionError>
Create(async::Executor& executor, fuchsia::media2::BufferProvider& provider, zx::eventpair token,
const fuchsia::media2::BufferConstraints& constraints, const std::string& name,
uint64_t id, zx_vm_option_t map_flags = ZX_VM_PERM_READ | ZX_VM_PERM_WRITE);
~OutputBufferCollection() override;
// Allocates a |PayloadBuffer| of the specified size. Returns an invalid |PayloadBuffer| if the
// buffer collection is currently exhausted. |size| must be greater than zero and less than or
// equal to the buffer size. This method is thread-safe.
// TODO(dalesat): Consider using std::optional here rather than having invalid PayloadBuffers.
PayloadBuffer AllocatePayloadBuffer(size_t size);
// Allocates a |PayloadBuffer| of the specified size when one becomes available, blocking in the
// the mean time. Returns an invalid |PayloadBuffer| if |FailPendingAllocation| is called. |size|
// must be greater than zero and less than or equal to the buffer size. This method is
// thread-safe, but may not be called on thread represented by the executor passed to the
// constructor.
// TODO(dalesat): Consider separating Blocking from WhenAvailable so only one can be used.
PayloadBuffer AllocatePayloadBufferBlocking(size_t size);
// Returns a promise that completes with an allocated |PayloadBuffer| of the specified size when
// one becomes available. Completes with an invalid |PayloadBuffer| if |FailPendingAllocation| is
// called. |size| must be greater than zero and less than or equal to the buffer size. This method
// is thread-safe. This method must not be called when a promise from a previous call is still
// pending.
[[nodiscard]] fpromise::promise<PayloadBuffer> AllocatePayloadBufferWhenAvailable(size_t size);
// Causes any allocation (|AllocatePayloadBufferBlocking| or |AllocatePayloadBufferWhenAvailable|)
// that is currently pending buffer availability to fail. This method is thread-safe.
void FailPendingAllocation();
// Gets a closure that calls |FailPendingAllocation| on this collection as long as this collection
// exists, does nothing thereafter.
fit::closure GetFailPendingAllocationClosure();
private:
// Used for calls to |FailPendingAllocation| by non-owners.
class ClosureContext {
public:
explicit ClosureContext(OutputBufferCollection* collection) : collection_(collection) {
FX_CHECK(collection);
}
~ClosureContext() = default;
// Fails a pending allocation if |collection_| is still set and the collection has a pending
// allocation.
void FailPendingAllocation() {
std::lock_guard<std::mutex> locker(mutex_);
if (collection_) {
collection_->FailPendingAllocation();
}
}
// Clears |collection_|.
void Reset() {
std::lock_guard<std::mutex> locker(mutex_);
collection_ = nullptr;
}
private:
mutable std::mutex mutex_;
OutputBufferCollection* collection_ FXL_GUARDED_BY(mutex_);
};
OutputBufferCollection(async::Executor& executor, std::vector<BufferVmo> buffer_vmos);
PayloadBuffer AllocatePayloadBufferUnsafe(size_t size) FXL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Determines whether a buffer of the specified size is currently available.
bool BufferAvailableUnsafe(size_t size) FXL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
async::Executor& executor_;
size_t free_vmo_guess_ FXL_GUARDED_BY(mutex_) = 0;
fpromise::completer<> when_available_completer_ FXL_GUARDED_BY(mutex_);
size_t when_available_size_ FXL_GUARDED_BY(mutex_);
sync_completion completion_;
std::shared_ptr<ClosureContext> closure_context_ FXL_GUARDED_BY(mutex_);
std::queue<zx::time> arrival_times_;
fpromise::scope scope_;
};
// A |BufferCollection| to be used for inputs. This subclass provides a method for obtaining a
// mapped payload buffer.
class InputBufferCollection : public BufferCollection {
public:
// Starts creating an |InputBufferCollection| and returns a promise that returns the collection
// when the it is ready to use. |provider| must be valid until the operation completes. This
// method is thread-safe, and the returned promise may be run on any thread.
[[nodiscard]] static fpromise::promise<std::unique_ptr<InputBufferCollection>,
fuchsia::media2::ConnectionError>
Create(fuchsia::media2::BufferProvider& provider, zx::eventpair token,
const fuchsia::media2::BufferConstraints& constraints, const std::string& name,
uint64_t id, zx_vm_option_t map_flags = ZX_VM_PERM_READ);
~InputBufferCollection() override = default;
// Gets the |PayloadBuffer| described by |payload_range|. If |payload_range| isn't valid for this
// collection, returns an invalid |PayloadBuffer|. This method is thread-safe.
PayloadBuffer GetPayloadBuffer(const fuchsia::media2::PayloadRange& payload_range);
private:
explicit InputBufferCollection(std::vector<BufferVmo> buffer_vmos)
: BufferCollection(std::move(buffer_vmos)) {}
};
} // namespace fmlib
#endif // SRC_MEDIA_VNEXT_LIB_STREAM_IO_BUFFER_COLLECTION_H_