summaryrefslogtreecommitdiffstats
path: root/src/core
diff options
context:
space:
mode:
Diffstat (limited to 'src/core')
-rw-r--r--src/core/CMakeLists.txt2
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue_consumer.cpp225
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue_consumer.h36
3 files changed, 263 insertions, 0 deletions
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<BufferQueueCore> 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<IProducerListener> 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<IProducerListener> 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<IConsumerListener> 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 <memory>
+
+#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<BufferQueueCore> 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<IConsumerListener> consumer_listener, bool controlled_by_app);
+
+private:
+ std::shared_ptr<BufferQueueCore> core;
+ BufferQueueDefs::SlotsType& slots;
+};
+
+} // namespace android