From 6e7f687df421e3d30b5b08e8e1747e6084e89342 Mon Sep 17 00:00:00 2001 From: bunnei Date: Thu, 11 Nov 2021 18:53:00 -0800 Subject: [PATCH] hle: nvflinger: Add implementation for BufferQueueConsumer class. --- src/core/CMakeLists.txt | 2 + .../nvflinger/buffer_queue_consumer.cpp | 225 ++++++++++++++++++ .../service/nvflinger/buffer_queue_consumer.h | 36 +++ 3 files changed, 263 insertions(+) create mode 100644 src/core/hle/service/nvflinger/buffer_queue_consumer.cpp create mode 100644 src/core/hle/service/nvflinger/buffer_queue_consumer.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 045a08648..c7ce6c1a6 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -541,6 +541,8 @@ add_library(core STATIC hle/service/nvflinger/buffer_item.h hle/service/nvflinger/buffer_item_consumer.cpp hle/service/nvflinger/buffer_item_consumer.h + hle/service/nvflinger/buffer_queue_consumer.cpp + hle/service/nvflinger/buffer_queue_consumer.h hle/service/nvflinger/buffer_queue_defs.h hle/service/nvflinger/buffer_slot.h hle/service/nvflinger/buffer_transform_flags.h diff --git a/src/core/hle/service/nvflinger/buffer_queue_consumer.cpp b/src/core/hle/service/nvflinger/buffer_queue_consumer.cpp new file mode 100644 index 000000000..f0875eca3 --- /dev/null +++ b/src/core/hle/service/nvflinger/buffer_queue_consumer.cpp @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright 2021 yuzu Emulator Project +// Copyright 2014 The Android Open Source Project +// Parts of this implementation were base on: +// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferQueueConsumer.cpp + +#include "common/logging/log.h" +#include "core/hle/service/nvflinger/buffer_item.h" +#include "core/hle/service/nvflinger/buffer_queue_consumer.h" +#include "core/hle/service/nvflinger/buffer_queue_core.h" +#include "core/hle/service/nvflinger/producer_listener.h" + +namespace android { + +BufferQueueConsumer::BufferQueueConsumer(std::shared_ptr core_) + : core{std::move(core_)}, slots{core->slots} {} + +BufferQueueConsumer::~BufferQueueConsumer() = default; + +Status BufferQueueConsumer::AcquireBuffer(BufferItem* out_buffer, s64 expected_presenst_ns, + u64 max_frame_number) { + s32 num_dropped_buffers{}; + + std::shared_ptr listener; + { + std::unique_lock lock(core->mutex); + + // Check that the consumer doesn't currently have the maximum number of buffers acquired. + s32 num_acquired_buffers{}; + for (const auto& slot : slots) { + if (slot.buffer_state == BufferState::Acquired) { + ++num_acquired_buffers; + } + } + + if (num_acquired_buffers >= core->max_acquired_buffer_count + 1) { + LOG_ERROR(Service_NVFlinger, "max acquired buffer count reached: {} (max {})", + num_acquired_buffers, core->max_acquired_buffer_count); + return Status::InvalidOperation; + } + + // Check if the queue is empty. + if (core->queue.empty()) { + return Status::NoBufferAvailable; + } + + auto front(core->queue.begin()); + + // If expected_presenst_ns is specified, we may not want to return a buffer yet. + if (expected_presenst_ns != 0) { + constexpr auto MAX_REASONABLE_NSEC = 1000000000LL; // 1 second + + // The expected_presenst_ns argument indicates when the buffer is expected to be + // presented on-screen. + while (core->queue.size() > 1 && !core->queue[0].is_auto_timestamp) { + const auto& buffer_item{core->queue[1]}; + + // If dropping entry[0] would leave us with a buffer that the consumer is not yet + // ready for, don't drop it. + if (max_frame_number && buffer_item.frame_number > max_frame_number) { + break; + } + + // If entry[1] is timely, drop entry[0] (and repeat). + const auto desired_present = buffer_item.timestamp; + if (desired_present < expected_presenst_ns - MAX_REASONABLE_NSEC || + desired_present > expected_presenst_ns) { + // This buffer is set to display in the near future, or desired_present is + // garbage. + LOG_DEBUG(Service_NVFlinger, "nodrop desire={} expect={}", desired_present, + expected_presenst_ns); + break; + } + + LOG_DEBUG(Service_NVFlinger, "drop desire={} expect={} size={}", desired_present, + expected_presenst_ns, core->queue.size()); + + if (core->StillTracking(&*front)) { + // Front buffer is still in mSlots, so mark the slot as free + slots[front->slot].buffer_state = BufferState::Free; + core->free_buffers.push_back(front->slot); + listener = core->connected_producer_listener; + ++num_dropped_buffers; + } + + core->queue.erase(front); + front = core->queue.begin(); + } + + // See if the front buffer is ready to be acquired. + const auto desired_present = front->timestamp; + const auto buffer_is_due = desired_present <= expected_presenst_ns || + desired_present > expected_presenst_ns + MAX_REASONABLE_NSEC; + const auto consumer_is_ready = + max_frame_number > 0 ? front->frame_number <= max_frame_number : true; + + if (!buffer_is_due || !consumer_is_ready) { + LOG_DEBUG(Service_NVFlinger, "defer desire={} expect={}", desired_present, + expected_presenst_ns); + return Status::PresentLater; + } + + LOG_DEBUG(Service_NVFlinger, "accept desire={} expect={}", desired_present, + expected_presenst_ns); + } + + const auto slot = front->slot; + *out_buffer = *front; + + LOG_DEBUG(Service_NVFlinger, "acquiring slot={}", slot); + + // If the front buffer is still being tracked, update its slot state + if (core->StillTracking(&*front)) { + slots[slot].acquire_called = true; + slots[slot].needs_cleanup_on_release = false; + slots[slot].buffer_state = BufferState::Acquired; + slots[slot].fence = Fence::NoFence(); + } + + // If the buffer has previously been acquired by the consumer, set graphic_buffer to nullptr + // to avoid unnecessarily remapping this buffer on the consumer side. + if (out_buffer->acquire_called) { + out_buffer->graphic_buffer = nullptr; + } + + core->queue.erase(front); + + // We might have freed a slot while dropping old buffers, or the producer may be blocked + // waiting for the number of buffers in the queue to decrease. + core->SignalDequeueCondition(); + } + + if (listener != nullptr) { + for (s32 i = 0; i < num_dropped_buffers; ++i) { + listener->OnBufferReleased(); + } + } + + return Status::NoError; +} + +Status BufferQueueConsumer::ReleaseBuffer(s32 slot, u64 frame_number, const Fence& release_fence) { + if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) { + LOG_ERROR(Service_NVFlinger, "slot {} out of range", slot); + return Status::BadValue; + } + + std::shared_ptr listener; + { + std::unique_lock lock(core->mutex); + + // If the frame number has changed because the buffer has been reallocated, we can ignore + // this ReleaseBuffer for the old buffer. + if (frame_number != slots[slot].frame_number) { + return Status::StaleBufferSlot; + } + + // Make sure this buffer hasn't been queued while acquired by the consumer. + auto current(core->queue.begin()); + while (current != core->queue.end()) { + if (current->slot == slot) { + LOG_ERROR(Service_NVFlinger, "buffer slot {} pending release is currently queued", + slot); + return Status::BadValue; + } + ++current; + } + + if (slots[slot].buffer_state == BufferState::Acquired) { + slots[slot].fence = release_fence; + slots[slot].buffer_state = BufferState::Free; + + core->free_buffers.push_back(slot); + + listener = core->connected_producer_listener; + + LOG_DEBUG(Service_NVFlinger, "releasing slot {}", slot); + } else if (slots[slot].needs_cleanup_on_release) { + LOG_DEBUG(Service_NVFlinger, "releasing a stale buffer slot {} (state = {})", slot, + slots[slot].buffer_state); + + slots[slot].needs_cleanup_on_release = false; + + return Status::StaleBufferSlot; + } else { + LOG_ERROR(Service_NVFlinger, "attempted to release buffer slot {} but its state was {}", + slot, slots[slot].buffer_state); + + return Status::BadValue; + } + + core->dequeue_condition.notify_all(); + } + + // Call back without lock held + if (listener != nullptr) { + listener->OnBufferReleased(); + } + + return Status::NoError; +} + +Status BufferQueueConsumer::Connect(std::shared_ptr consumer_listener, + bool controlled_by_app) { + if (consumer_listener == nullptr) { + LOG_ERROR(Service_NVFlinger, "consumer_listener may not be nullptr"); + return Status::BadValue; + } + + LOG_DEBUG(Service_NVFlinger, "controlled_by_app={}", controlled_by_app); + + BufferQueueCore::AutoLock lock(core); + + if (core->is_abandoned) { + LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned"); + return Status::NoInit; + } + + core->consumer_listener = consumer_listener; + core->consumer_controlled_by_app = controlled_by_app; + + return Status::NoError; +} + +} // namespace android diff --git a/src/core/hle/service/nvflinger/buffer_queue_consumer.h b/src/core/hle/service/nvflinger/buffer_queue_consumer.h new file mode 100644 index 000000000..fbeb8b8d7 --- /dev/null +++ b/src/core/hle/service/nvflinger/buffer_queue_consumer.h @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright 2021 yuzu Emulator Project +// Copyright 2014 The Android Open Source Project +// Parts of this implementation were base on: +// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferQueueConsumer.h + +#pragma once + +#include + +#include "common/common_types.h" +#include "core/hle/service/nvflinger/buffer_queue_defs.h" +#include "core/hle/service/nvflinger/status.h" + +namespace android { + +class BufferItem; +class BufferQueueCore; +class IConsumerListener; + +class BufferQueueConsumer final { +public: + explicit BufferQueueConsumer(std::shared_ptr core_); + ~BufferQueueConsumer(); + + Status AcquireBuffer(BufferItem* out_buffer, s64 expected_presenst_ns, + u64 max_frame_number = 0); + Status ReleaseBuffer(s32 slot, u64 frame_number, const Fence& release_fence); + Status Connect(std::shared_ptr consumer_listener, bool controlled_by_app); + +private: + std::shared_ptr core; + BufferQueueDefs::SlotsType& slots; +}; + +} // namespace android