diff options
| -rw-r--r-- | src/core/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | src/core/hle/service/nvflinger/buffer_queue_consumer.cpp | 225 | ||||
| -rw-r--r-- | src/core/hle/service/nvflinger/buffer_queue_consumer.h | 36 | 
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  | 
