Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1601bcfa authored by Jiwen 'Steve' Cai's avatar Jiwen 'Steve' Cai
Browse files

Harden BufferHubQueueProducerTest

This is a test that covers our implementation of bufferhubqueue-based
IGraphicBufferProducer, which eventually enables VrCore/VrFlinger to
access buffers produced from other Android compontents. Note that the
gtest itself is a (slightly) modified version of
IGraphicBufferProducer_test.cpp.

This also improves BufferHubQueue in the following way to support more
features of android::BufferQueue.
1/ Supports more buffer queue parameters that we query.
2/ BufferHubQueue (the PDX client) now supports default width, height,
and format configuration.
3/ Change default max_dequeue_buffer_count to 1 (which is the same
behavior current android::BufferQueue adopts).
4/ Fill in |QueueBufferOutput| during |enqueueBuffer|.

Bug: 34197998
Bug: 36266201
Test: build and run buffer_hub_queue_producer-test
Change-Id: I9d3e8bb66dbfb66e67ab7b0e5093e49a3f374e9c
parent 62391218
Loading
Loading
Loading
Loading
+0 −20
Original line number Diff line number Diff line
@@ -50,27 +50,7 @@ status_t BufferHubQueueCore::AllocateBuffer(uint32_t width, uint32_t height,
  LOG_ALWAYS_FATAL_IF(buffer_producer == nullptr,
                      "Failed to get buffer producer at slot: %zu", slot);

  // Allocating a new buffer, |buffers_[slot]| should be in initial state.
  LOG_ALWAYS_FATAL_IF(buffers_[slot].mGraphicBuffer != nullptr,
                      "AllocateBuffer: slot %zu is not empty.", slot);

  // Create new GraphicBuffer based on the newly created |buffer_producer|. Here
  // we have to cast |buffer_handle_t| to |native_handle_t|, it's OK because
  // internally, GraphicBuffer is still an |ANativeWindowBuffer| and |handle|
  // is still type of |buffer_handle_t| and bears const property.
  sp<GraphicBuffer> graphic_buffer(new GraphicBuffer(
      buffer_producer->width(), buffer_producer->height(),
      buffer_producer->format(),
      1, /* layer count */
      buffer_producer->usage(),
      buffer_producer->stride(),
      const_cast<native_handle_t*>(buffer_producer->buffer()->handle()),
      false));

  LOG_ALWAYS_FATAL_IF(NO_ERROR != graphic_buffer->initCheck(),
                      "Failed to init GraphicBuffer.");
  buffers_[slot].mBufferProducer = buffer_producer;
  buffers_[slot].mGraphicBuffer = graphic_buffer;

  return NO_ERROR;
}
+216 −40
Original line number Diff line number Diff line
@@ -8,7 +8,7 @@ namespace dvr {

BufferHubQueueProducer::BufferHubQueueProducer(
    const std::shared_ptr<BufferHubQueueCore>& core)
    : core_(core), req_buffer_count_(kInvalidBufferCount) {}
    : core_(core) {}

status_t BufferHubQueueProducer::requestBuffer(int slot,
                                               sp<GraphicBuffer>* buf) {
@@ -16,18 +16,48 @@ status_t BufferHubQueueProducer::requestBuffer(int slot,

  std::unique_lock<std::mutex> lock(core_->mutex_);

  if (slot < 0 || slot >= req_buffer_count_) {
  if (core_->connected_api_ == BufferHubQueueCore::kNoConnectedApi) {
    ALOGE("requestBuffer: BufferHubQueueProducer has no connected producer");
    return NO_INIT;
  }

  if (slot < 0 || slot >= max_buffer_count_) {
    ALOGE("requestBuffer: slot index %d out of range [0, %d)", slot,
          req_buffer_count_);
          max_buffer_count_);
    return BAD_VALUE;
  } else if (!core_->buffers_[slot].mBufferState.isDequeued()) {
    ALOGE("requestBuffer: slot %d is not owned by the producer (state = %s)",
          slot, core_->buffers_[slot].mBufferState.string());
    return BAD_VALUE;
  } else if (core_->buffers_[slot].mGraphicBuffer != nullptr) {
    ALOGE("requestBuffer: slot %d is not empty.", slot);
    return BAD_VALUE;
  } else if (core_->buffers_[slot].mBufferProducer == nullptr) {
    ALOGE("requestBuffer: slot %d is not dequeued.", slot);
    return BAD_VALUE;
  }

  const auto& buffer_producer = core_->buffers_[slot].mBufferProducer;

  // Create new GraphicBuffer based on the newly created |buffer_producer|. Here
  // we have to cast |buffer_handle_t| to |native_handle_t|, it's OK because
  // internally, GraphicBuffer is still an |ANativeWindowBuffer| and |handle|
  // is still type of |buffer_handle_t| and bears const property.
  sp<GraphicBuffer> graphic_buffer(new GraphicBuffer(
      buffer_producer->width(), buffer_producer->height(),
      buffer_producer->format(),
      1, /* layer count */
      buffer_producer->usage(),
      buffer_producer->stride(),
      const_cast<native_handle_t*>(buffer_producer->buffer()->handle()),
      false));

  LOG_ALWAYS_FATAL_IF(NO_ERROR != graphic_buffer->initCheck(),
                      "Failed to init GraphicBuffer.");
  core_->buffers_[slot].mGraphicBuffer = graphic_buffer;
  core_->buffers_[slot].mRequestBufferCalled = true;
  *buf = core_->buffers_[slot].mGraphicBuffer;

  *buf = graphic_buffer;
  return NO_ERROR;
}

@@ -46,30 +76,68 @@ status_t BufferHubQueueProducer::setMaxDequeuedBufferCount(
    return BAD_VALUE;
  }

  req_buffer_count_ = max_dequeued_buffers;
  // The new dequeued_buffers count should not be violated by the number
  // of currently dequeued buffers.
  int dequeued_count = 0;
  for (const auto& buf : core_->buffers_) {
    if (buf.mBufferState.isDequeued()) {
      dequeued_count++;
    }
  }
  if (dequeued_count > max_dequeued_buffers) {
    ALOGE(
        "setMaxDequeuedBufferCount: the requested dequeued_buffers"
        "count (%d) exceeds the current dequeued buffer count (%d)",
        max_dequeued_buffers, dequeued_count);
    return BAD_VALUE;
  }

  max_dequeued_buffer_count_ = max_dequeued_buffers;
  return NO_ERROR;
}

status_t BufferHubQueueProducer::setAsyncMode(bool /* async */) {
  ALOGE("BufferHubQueueProducer::setAsyncMode not implemented.");
  return INVALID_OPERATION;
status_t BufferHubQueueProducer::setAsyncMode(bool async) {
  if (async) {
    // TODO(b/36724099) BufferHubQueue's consumer end always acquires the buffer
    // automatically and behaves differently from IGraphicBufferConsumer. Thus,
    // android::BufferQueue's async mode (a.k.a. allocating an additional buffer
    // to prevent dequeueBuffer from being blocking) technically does not apply
    // here.
    //
    // In Daydream, non-blocking producer side dequeue is guaranteed by careful
    // buffer consumer implementations. In another word, BufferHubQueue based
    // dequeueBuffer should never block whether setAsyncMode(true) is set or
    // not.
    //
    // See: IGraphicBufferProducer::setAsyncMode and
    // BufferQueueProducer::setAsyncMode for more about original implementation.
    ALOGW(
        "BufferHubQueueProducer::setAsyncMode: BufferHubQueue should always be "
        "asynchronous. This call makes no effact.");
    return NO_ERROR;
  }
  return NO_ERROR;
}

status_t BufferHubQueueProducer::dequeueBuffer(int* out_slot,
                                               sp<Fence>* out_fence,
                                               uint32_t width, uint32_t height,
                                               PixelFormat format,
                                               uint32_t usage,
                                               FrameEventHistoryDelta* /* outTimestamps */) {
status_t BufferHubQueueProducer::dequeueBuffer(
    int* out_slot, sp<Fence>* out_fence, uint32_t width, uint32_t height,
    PixelFormat format, uint32_t usage,
    FrameEventHistoryDelta* /* out_timestamps */) {
  ALOGD_IF(TRACE, "dequeueBuffer: w=%u, h=%u, format=%d, usage=%u", width,
           height, format, usage);

  status_t ret;
  std::unique_lock<std::mutex> lock(core_->mutex_);

  if (static_cast<int32_t>(core_->producer_->capacity()) < req_buffer_count_) {
  if (core_->connected_api_ == BufferHubQueueCore::kNoConnectedApi) {
    ALOGE("dequeueBuffer: BufferQueue has no connected producer");
    return NO_INIT;
  }

  if (static_cast<int32_t>(core_->producer_->capacity()) <
      max_dequeued_buffer_count_) {
    // Lazy allocation. When the capacity of |core_->producer_| has not reach
    // |req_buffer_count_|, allocate new buffer.
    // |max_dequeued_buffer_count_|, allocate new buffer.
    // TODO(jwcai) To save memory, the really reasonable thing to do is to go
    // over existing slots and find first existing one to dequeue.
    ret = core_->AllocateBuffer(width, height, format, usage, 1);
@@ -126,8 +194,8 @@ status_t BufferHubQueueProducer::dequeueBuffer(int* out_slot,
  // BufferHubQueue).
  // TODO(jwcai) Clean this up, make mBufferState compatible with BufferHub's
  // model.
  LOG_ALWAYS_FATAL_IF(!core_->buffers_[slot].mBufferState.isFree() &&
                          !core_->buffers_[slot].mBufferState.isQueued(),
  LOG_ALWAYS_FATAL_IF((!core_->buffers_[slot].mBufferState.isFree() &&
                       !core_->buffers_[slot].mBufferState.isQueued()),
                      "dequeueBuffer: slot %zu is not free or queued.", slot);

  core_->buffers_[slot].mBufferState.freeQueued();
@@ -170,22 +238,39 @@ status_t BufferHubQueueProducer::attachBuffer(

status_t BufferHubQueueProducer::queueBuffer(int slot,
                                             const QueueBufferInput& input,
                                             QueueBufferOutput* /* output */) {
                                             QueueBufferOutput* output) {
  ALOGD_IF(TRACE, "queueBuffer: slot %d", slot);

  if (output == nullptr) {
    return BAD_VALUE;
  }

  int64_t timestamp;
  int scaling_mode;
  sp<Fence> fence;
  Rect crop(Rect::EMPTY_RECT);

  // TODO(jwcai) The following attributes are ignored.
  bool is_auto_timestamp;
  android_dataspace data_space;
  Rect crop(Rect::EMPTY_RECT);
  int scaling_mode;
  uint32_t transform;

  input.deflate(&timestamp, &is_auto_timestamp, &data_space, &crop,
                &scaling_mode, &transform, &fence);

  // Check input scaling mode is valid.
  switch (scaling_mode) {
    case NATIVE_WINDOW_SCALING_MODE_FREEZE:
    case NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW:
    case NATIVE_WINDOW_SCALING_MODE_SCALE_CROP:
    case NATIVE_WINDOW_SCALING_MODE_NO_SCALE_CROP:
      break;
    default:
      ALOGE("queueBuffer: unknown scaling mode %d", scaling_mode);
      return BAD_VALUE;
  }

  // Check input fence is valid.
  if (fence == nullptr) {
    ALOGE("queueBuffer: fence is NULL");
    return BAD_VALUE;
@@ -194,25 +279,61 @@ status_t BufferHubQueueProducer::queueBuffer(int slot,
  status_t ret;
  std::unique_lock<std::mutex> lock(core_->mutex_);

  if (slot < 0 || slot >= req_buffer_count_) {
  if (core_->connected_api_ == BufferHubQueueCore::kNoConnectedApi) {
    ALOGE("queueBuffer: BufferQueue has no connected producer");
    return NO_INIT;
  }

  if (slot < 0 || slot >= max_buffer_count_) {
    ALOGE("queueBuffer: slot index %d out of range [0, %d)", slot,
          req_buffer_count_);
          max_buffer_count_);
    return BAD_VALUE;
  } else if (!core_->buffers_[slot].mBufferState.isDequeued()) {
    ALOGE("queueBuffer: slot %d is not owned by the producer (state = %s)",
          slot, core_->buffers_[slot].mBufferState.string());
    return BAD_VALUE;
  } else if ((!core_->buffers_[slot].mRequestBufferCalled ||
              core_->buffers_[slot].mGraphicBuffer == nullptr)) {
    ALOGE(
        "queueBuffer: slot %d is not requested (mRequestBufferCalled=%d, "
        "mGraphicBuffer=%p)",
        slot, core_->buffers_[slot].mRequestBufferCalled,
        core_->buffers_[slot].mGraphicBuffer.get());
    return BAD_VALUE;
  }

  // Post the buffer producer with timestamp in the metadata.
  auto buffer_producer = core_->buffers_[slot].mBufferProducer;
  const auto& buffer_producer = core_->buffers_[slot].mBufferProducer;

  // Check input crop is not out of boundary of current buffer.
  Rect buffer_rect(buffer_producer->width(), buffer_producer->height());
  Rect cropped_rect(Rect::EMPTY_RECT);
  crop.intersect(buffer_rect, &cropped_rect);
  if (cropped_rect != crop) {
    ALOGE("queueBuffer: slot %d has out-of-boundary crop.", slot);
    return BAD_VALUE;
  }

  LocalHandle fence_fd(fence->isValid() ? fence->dup() : -1);

  BufferHubQueueCore::BufferMetadata meta_data = {.timestamp = timestamp};
  buffer_producer->Post(fence_fd, &meta_data, sizeof(meta_data));
  core_->buffers_[slot].mBufferState.queue();

  // TODO(jwcai) check how to fill in output properly.
  output->width = buffer_producer->width();
  output->height = buffer_producer->height();
  output->transformHint = 0; // default value, we don't use it yet.

  // |numPendingBuffers| counts of the number of buffers that has been enqueued
  // by the producer but not yet acquired by the consumer. Due to the nature
  // of BufferHubQueue design, this is hard to trace from the producer's client
  // side, but it's safe to assume it's zero.
  output->numPendingBuffers = 0;

  // Note that we are not setting nextFrameNumber here as it seems to be only
  // used by surface flinger. See more at b/22802885, ag/791760.
  output->nextFrameNumber = 0;

  return NO_ERROR;
}

@@ -222,15 +343,20 @@ status_t BufferHubQueueProducer::cancelBuffer(int slot,

  std::unique_lock<std::mutex> lock(core_->mutex_);

  if (slot < 0 || slot >= req_buffer_count_) {
  if (core_->connected_api_ == BufferHubQueueCore::kNoConnectedApi) {
    ALOGE("cancelBuffer: BufferQueue has no connected producer");
    return NO_INIT;
  }

  if (slot < 0 || slot >= max_buffer_count_) {
    ALOGE("cancelBuffer: slot index %d out of range [0, %d)", slot,
          req_buffer_count_);
          max_buffer_count_);
    return BAD_VALUE;
  } else if (!core_->buffers_[slot].mBufferState.isDequeued()) {
    ALOGE("cancelBuffer: slot %d is not owned by the producer (state = %s)",
          slot, core_->buffers_[slot].mBufferState.string());
    return BAD_VALUE;
  } else if (fence == NULL) {
  } else if (fence == nullptr) {
    ALOGE("cancelBuffer: fence is NULL");
    return BAD_VALUE;
  }
@@ -249,7 +375,7 @@ status_t BufferHubQueueProducer::query(int what, int* out_value) {

  std::unique_lock<std::mutex> lock(core_->mutex_);

  if (out_value == NULL) {
  if (out_value == nullptr) {
    ALOGE("query: out_value was NULL");
    return BAD_VALUE;
  }
@@ -262,15 +388,30 @@ status_t BufferHubQueueProducer::query(int what, int* out_value) {
    case NATIVE_WINDOW_BUFFER_AGE:
      value = 0;
      break;
    // The following queries are currently considered as unsupported.
    // TODO(jwcai) Need to carefully check the whether they should be
    // supported after all.
    case NATIVE_WINDOW_WIDTH:
      value = core_->producer_->default_width();
      break;
    case NATIVE_WINDOW_HEIGHT:
      value = core_->producer_->default_height();
      break;
    case NATIVE_WINDOW_FORMAT:
    case NATIVE_WINDOW_STICKY_TRANSFORM:
      value = core_->producer_->default_format();
      break;
    case NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND:
      // BufferHubQueue is always operating in async mode, thus semantically
      // consumer can never be running behind. See BufferQueueCore.cpp core
      // for more information about the original meaning of this flag.
      value = 0;
      break;
    case NATIVE_WINDOW_CONSUMER_USAGE_BITS:
      // TODO(jwcai) This is currently not implement as we don't need
      // IGraphicBufferConsumer parity.
      value = 0;
      break;
    // The following queries are currently considered as unsupported.
    // TODO(jwcai) Need to carefully check the whether they should be
    // supported after all.
    case NATIVE_WINDOW_STICKY_TRANSFORM:
    case NATIVE_WINDOW_DEFAULT_DATASPACE:
    default:
      return BAD_VALUE;
@@ -282,24 +423,58 @@ status_t BufferHubQueueProducer::query(int what, int* out_value) {
}

status_t BufferHubQueueProducer::connect(
    const sp<IProducerListener>& /* listener */, int /* api */,
    bool /* producer_controlled_by_app */, QueueBufferOutput* /* output */) {
    const sp<IProducerListener>& /* listener */, int api,
    bool /* producer_controlled_by_app */, QueueBufferOutput* output) {
  // Consumer interaction are actually handled by buffer hub, and we need
  // to maintain consumer operations here. Hence |connect| is a NO-OP.
  // to maintain consumer operations here. We only need to perform basic input
  // parameter checks here.
  ALOGD_IF(TRACE, __FUNCTION__);

  if (output == nullptr) {
    return BAD_VALUE;
  }

  std::unique_lock<std::mutex> lock(core_->mutex_);

  if (core_->connected_api_ != BufferHubQueueCore::kNoConnectedApi) {
    return BAD_VALUE;
  }

  switch (api) {
    case NATIVE_WINDOW_API_EGL:
    case NATIVE_WINDOW_API_CPU:
    case NATIVE_WINDOW_API_MEDIA:
    case NATIVE_WINDOW_API_CAMERA:
      core_->connected_api_ = api;
      // TODO(jwcai) Fill output.
      break;
    default:
      ALOGE("BufferHubQueueProducer::connect: unknow API %d", api);
      return BAD_VALUE;
  }

  return NO_ERROR;
}

status_t BufferHubQueueProducer::disconnect(int /* api */, DisconnectMode /* mode */) {
status_t BufferHubQueueProducer::disconnect(int api, DisconnectMode mode) {
  // Consumer interaction are actually handled by buffer hub, and we need
  // to maintain consumer operations here. Hence |disconnect| is a NO-OP.
  // to maintain consumer operations here.  We only need to perform basic input
  // parameter checks here.
  ALOGD_IF(TRACE, __FUNCTION__);

  std::unique_lock<std::mutex> lock(core_->mutex_);

  if (api != core_->connected_api_) {
    return BAD_VALUE;
  }

  core_->connected_api_ = BufferHubQueueCore::kNoConnectedApi;
  return NO_ERROR;
}

status_t BufferHubQueueProducer::setSidebandStream(
    const sp<NativeHandle>& stream) {
  if (stream != NULL) {
  if (stream != nullptr) {
    // TODO(jwcai) Investigate how is is used, maybe use BufferHubBuffer's
    // metadata.
    ALOGE("SidebandStream is not currently supported.");
@@ -314,7 +489,7 @@ void BufferHubQueueProducer::allocateBuffers(uint32_t /* width */,
                                             uint32_t /* usage */) {
  // TODO(jwcai) |allocateBuffers| aims to preallocate up to the maximum number
  // of buffers permitted by the current BufferQueue configuration (aka
  // |req_buffer_count_|).
  // |max_buffer_count_|).
  ALOGE("BufferHubQueueProducer::allocateBuffers not implemented.");
}

@@ -343,6 +518,7 @@ String8 BufferHubQueueProducer::getConsumerName() const {
status_t BufferHubQueueProducer::setSharedBufferMode(
    bool /* shared_buffer_mode */) {
  ALOGE("BufferHubQueueProducer::setSharedBufferMode not implemented.");
  // TODO(b/36373181) Front buffer mode for buffer hub queue as ANativeWindow.
  return INVALID_OPERATION;
}

+21 −0
Original line number Diff line number Diff line
@@ -32,6 +32,15 @@ class BufferHubQueue : public pdx::Client {
  // a new consumer queue client or nullptr on failure.
  std::unique_ptr<ConsumerQueue> CreateConsumerQueue();

  // Return the default buffer width of this buffer queue.
  size_t default_width() const { return default_width_; }

  // Return the default buffer height of this buffer queue.
  size_t default_height() const { return default_height_; }

  // Return the default buffer format of this buffer queue.
  int32_t default_format() const { return default_format_; }

  // Return the number of buffers avaiable for dequeue.
  size_t count() const { return available_buffers_.GetSize(); }

@@ -169,6 +178,18 @@ class BufferHubQueue : public pdx::Client {
    void operator=(BufferInfo&) = delete;
  };

  // Default buffer width that can be set to override the buffer width when a
  // width and height of 0 are specified in AllocateBuffer.
  size_t default_width_{1};

  // Default buffer height that can be set to override the buffer height when a
  // width and height of 0 are specified in AllocateBuffer.
  size_t default_height_{1};

  // Default buffer format that can be set to override the buffer format when it
  // isn't specified in AllocateBuffer.
  int32_t default_format_{PIXEL_FORMAT_RGBA_8888};

  // Buffer queue:
  // |buffers_| tracks all |BufferHubBuffer|s created by this |BufferHubQueue|.
  std::vector<std::shared_ptr<BufferHubBuffer>> buffers_;
+5 −0
Original line number Diff line number Diff line
@@ -17,6 +17,8 @@ class BufferHubQueueCore {
  friend class BufferHubQueueProducer;

 public:
  static constexpr int kNoConnectedApi = -1;

  // Create a BufferHubQueueCore instance by creating a new producer queue.
  static std::shared_ptr<BufferHubQueueCore> Create();

@@ -87,6 +89,9 @@ class BufferHubQueueCore {
  // Mutex for thread safety.
  std::mutex mutex_;

  // Connect client API, should be one of the NATIVE_WINDOW_API_* flags.
  int connected_api_{kNoConnectedApi};

  // |buffers_| stores the buffers that have been dequeued from
  // |dvr::BufferHubQueue|, It is initialized to invalid buffers, and gets
  // filled in with the result of |Dequeue|.
+6 −4
Original line number Diff line number Diff line
@@ -103,13 +103,15 @@ class BufferHubQueueProducer : public IGraphicBufferProducer {
 private:
  using LocalHandle = pdx::LocalHandle;

  static constexpr int kInvalidBufferCount = -1;

  // |core_| holds the actually buffer slots.
  std::shared_ptr<BufferHubQueueCore> core_;

  // |req_buffer_count_| sets the capacity of the underlying buffer queue.
  int32_t req_buffer_count_;
  // |max_buffer_count_| sets the capacity of the underlying buffer queue.
  int32_t max_buffer_count_{BufferHubQueue::kMaxQueueCapacity};

  // |max_dequeued_buffer_count_| set the maximum number of buffers that can
  // be dequeued at the same momment.
  int32_t max_dequeued_buffer_count_{1};
};

}  // namespace dvr
Loading