blob: e5d22e1242c4e0fb9b6223388ff808f4651b8889 [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 LIB_MEDIA_TRANSPORT_SHARED_BUFFER_SET_ALLOCATOR_H_
#define LIB_MEDIA_TRANSPORT_SHARED_BUFFER_SET_ALLOCATOR_H_
#include <map>
#include <memory>
#include <mutex>
#include <queue>
#include <vector>
#include "lib/fxl/logging.h"
#include "lib/fxl/synchronization/thread_annotations.h"
#include "lib/media/transport/fifo_allocator.h"
#include "lib/media/transport/shared_buffer_set.h"
namespace media {
// SharedBufferSetAllocator enhances SharedBufferSet by adding allocation
// semantics (allocate and release). SharedBufferSetAllocator is thread-safe.
//
// DESIGN:
//
// SharedBufferSetAllocator implements two different strategies depending on
// the size of the allocations requested. In most media applications, only one
// of the two strategies will be used for any given instance, but both
// strategies can be used at the same time.
//
// The 'sliced' strategy is used for smaller allocations (smaller than
// kWholeRegionMinimumSize). In this strategy, there is one 'active' shared
// buffer from which allocations are made. If the active buffer proves
// insufficient, a larger one is created, and that one becomes active. Buffers
// that were previously active are deleted once all of their allocations have
// been released.
//
// The size chosen for the initial active buffer is a function of the first
// allocation request. Specifically, it's the size of the allocation request
// multipled by kSlicedBufferInitialSizeMultiplier. When an existing active
// buffer is too small and a new one needs to be created, the new size is either
// kSlicedBufferGrowMultiplier times the old size or
// kSlicedBufferInitialSizeMultiplier times the current request size, whichever
// is larger.
//
// The 'whole' strategy is used for larger allocations (at least as large as
// kWholeRegionMinimumSize). In this strategy, each allocation uses a whole
// shared buffer. A list of free whole buffers is maintained so buffers are
// reused. When an allocation occurs, free whole buffers that are too small
// are deleted. If none of the free whole buffers is large enough, a new
// buffer of the required size is allocated.
//
// This design is intended to do a reasonable job of serving the real-world
// needs of the fidl packet transport. The 'whole' strategy is intended for the
// very large packets containing uncompressed video. Typically, these are of
// consistent size for a given connection, and packet flow control is tuned to
// keep their number small and consistent (typically one or two per connection).
//
// The 'sliced' strategy is intended for packets containing anything but
// uncompressed video. Such packets are rarely bigger than a few kilobytes, and
// their size and number per connection may be more varied.
//
// Both strategies, relative to their intended uses, are designed to be
// reasonably memory-efficient and to reasonably minimize the number of
// buffers and the rate at which buffers are created and deleted. Another
// consideration is the complexity of this code. Additional logic may be added
// later if the additional complexity is justified by improved efficiency.
//
// This code can be tuned by tweaking the various constants:
//
// kWholeRegionMinimumSize: This value should be tuned such that uncompressed
// video packets fall above this value and other packets fall below. So far,
// observations bear out the assumption that packet sizes are clustered well
// away from this threshold (much higher for uncompressed video packets, much
// lower for other packets).
//
// kSlicedBufferInitialSizeMultiplier: This value should be tuned to maximize
// the sliced buffer utilization while minimizing the chances that a new buffer
// will need to be allocated.
//
// kSlicedBufferGrowMultiplier: This value should be tuned to maximimize the
// utilization of replacement buffers while minimizing the chances that a new
// buffer will need to be allocated yet again.
//
// The intended use for this class is media packet prodcuer implementations
// (such as MediaPacketProducerBase and its subclasses). Producers need to
// inform consumers of changes to the set of buffers used for the connection.
// The producer accomplishes this by calling PollForBufferUpdate and sending
// the relevant AddPayloadBuffer and RemovePayloadBuffer messages. The queue
// of updates should be drained before the producer sends a SupplyPacket
// message to assure that the buffer referenced by SupplyPacket is known to the
// consumer.
//
// MediaPacketConsumer implementations such as MediaPacketConsumerBase and its
// subclasses should used SharedBufferSet rather than SharedBufferSetAllocator,
// because they only need the buffer mapping and offset translation
// functionality it provides, not the ability to allocate and release regions.
//
class SharedBufferSetAllocator : public SharedBufferSet {
public:
// Constructs a SharedBufferSetAllocator. |local_map_flags| specifies flags
// used to map vmos for local access. |remote_rights| specifies the rights
// applies to vmos sent to the remote party via buffer updates.
SharedBufferSetAllocator(uint32_t local_map_flags, zx_rights_t remote_rights);
~SharedBufferSetAllocator() override;
// Resets the object to its initial state.
void Reset() override;
// Configures the allocator to use a single buffer of the specified size for
// all allocations. |AllocateRegion| fails if the requested allocation cannot
// be accommodated using the single VMO. This method should be called before
// any allocation attempts. Returns true unless the VMO could not be
// allocated.
bool SetFixedBufferSize(uint64_t size);
// Allocates a region, returning a pointer. If the requested region could not
// be allocated, returns nullptr.
void* AllocateRegion(uint64_t size);
// Releases a region of the buffer previously allocated.
void ReleaseRegion(void* ptr);
// Returns the first queued buffer update, if there is one. Return true if
// there is one, false if not. If this method returns true, |*buffer_id_out|
// and |*handle_out| are updated. If |*handle_out| is valid, the update is
// a buffer add, a buffer remove if not.
bool PollForBufferUpdate(uint32_t* buffer_id_out, zx::vmo* handle_out);
private:
struct Buffer {
Buffer();
Buffer(Buffer&& other) {
size_ = other.size_;
allocator_ = std::move(other.allocator_);
}
~Buffer();
bool whole() { return !static_cast<bool>(allocator_); }
uint64_t size_;
std::unique_ptr<FifoAllocator> allocator_;
};
struct BufferUpdate {
BufferUpdate(uint32_t buffer_id, zx::vmo vmo);
BufferUpdate(uint32_t buffer_id);
~BufferUpdate();
uint32_t buffer_id_;
zx::vmo vmo_;
};
static constexpr uint64_t kWholeRegionMinimumSize = 64 * 1024;
static constexpr uint64_t kSlicedBufferInitialSizeMultiplier = 3;
static constexpr uint64_t kSlicedBufferGrowMultiplier = 2;
static constexpr uint32_t kNullBufferId =
std::numeric_limits<uint32_t>::max();
// Allocates a region using the whole region strategy.
Locator AllocateWholeRegion(uint64_t size)
FXL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Releases a region using the whole region strategy.
void ReleaseWholeRegion(const Locator& locator)
FXL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Allocates a region using the sliced region strategy.
Locator AllocateSlicedRegion(uint64_t size)
FXL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Releases a region using the sliced region strategy.
void ReleaseSlicedRegion(const Locator& locator)
FXL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Creates a new buffer and notifies the caller that this has occurred.
uint32_t CreateBuffer(bool whole, uint64_t size)
FXL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Deletes a buffer and notifies the caller that this has occurred.
void DeleteBuffer(uint32_t id) FXL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Deletes the buffer if it's fully released.
void MaybeDeleteSlicedBuffer(uint32_t id)
FXL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
zx_rights_t remote_rights_;
mutable std::mutex mutex_;
bool use_fixed_buffer_ FXL_GUARDED_BY(mutex_) = false;
std::vector<Buffer> buffers_ FXL_GUARDED_BY(mutex_);
std::multimap<uint64_t, uint32_t> free_whole_buffer_ids_by_size_
FXL_GUARDED_BY(mutex_);
uint32_t active_sliced_buffer_id_ FXL_GUARDED_BY(mutex_) = kNullBufferId;
std::queue<BufferUpdate> buffer_updates_ FXL_GUARDED_BY(mutex_);
};
} // namespace media
#endif // LIB_MEDIA_TRANSPORT_SHARED_BUFFER_SET_ALLOCATOR_H_