summaryrefslogtreecommitdiffstats
path: root/src/core/hle/service/nvflinger
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/hle/service/nvflinger')
-rw-r--r--src/core/hle/service/nvflinger/binder.h42
-rw-r--r--src/core/hle/service/nvflinger/buffer_item.h46
-rw-r--r--src/core/hle/service/nvflinger/buffer_item_consumer.cpp59
-rw-r--r--src/core/hle/service/nvflinger/buffer_item_consumer.h28
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue_consumer.cpp225
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue_consumer.h37
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue_core.cpp134
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue_core.h99
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue_defs.h21
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue_producer.cpp937
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue_producer.h83
-rw-r--r--src/core/hle/service/nvflinger/buffer_slot.h39
-rw-r--r--src/core/hle/service/nvflinger/buffer_transform_flags.h25
-rw-r--r--src/core/hle/service/nvflinger/consumer_base.cpp130
-rw-r--r--src/core/hle/service/nvflinger/consumer_base.h61
-rw-r--r--src/core/hle/service/nvflinger/consumer_listener.h26
-rw-r--r--src/core/hle/service/nvflinger/graphic_buffer_producer.cpp20
-rw-r--r--src/core/hle/service/nvflinger/graphic_buffer_producer.h76
-rw-r--r--src/core/hle/service/nvflinger/hos_binder_driver_server.cpp36
-rw-r--r--src/core/hle/service/nvflinger/hos_binder_driver_server.h37
-rw-r--r--src/core/hle/service/nvflinger/nvflinger.cpp81
-rw-r--r--src/core/hle/service/nvflinger/nvflinger.h26
-rw-r--r--src/core/hle/service/nvflinger/parcel.h172
-rw-r--r--src/core/hle/service/nvflinger/pixel_format.h21
-rw-r--r--src/core/hle/service/nvflinger/producer_listener.h16
-rw-r--r--src/core/hle/service/nvflinger/status.h28
-rw-r--r--src/core/hle/service/nvflinger/ui/fence.h32
-rw-r--r--src/core/hle/service/nvflinger/ui/graphic_buffer.h100
-rw-r--r--src/core/hle/service/nvflinger/window.h53
29 files changed, 2638 insertions, 52 deletions
diff --git a/src/core/hle/service/nvflinger/binder.h b/src/core/hle/service/nvflinger/binder.h
new file mode 100644
index 000000000..7d0d4d819
--- /dev/null
+++ b/src/core/hle/service/nvflinger/binder.h
@@ -0,0 +1,42 @@
+// 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/binder/IBinder.h
+
+#pragma once
+
+#include "common/common_types.h"
+
+namespace Kernel {
+class HLERequestContext;
+class KReadableEvent;
+} // namespace Kernel
+
+namespace Service::android {
+
+enum class TransactionId {
+ RequestBuffer = 1,
+ SetBufferCount = 2,
+ DequeueBuffer = 3,
+ DetachBuffer = 4,
+ DetachNextBuffer = 5,
+ AttachBuffer = 6,
+ QueueBuffer = 7,
+ CancelBuffer = 8,
+ Query = 9,
+ Connect = 10,
+ Disconnect = 11,
+ AllocateBuffers = 13,
+ SetPreallocatedBuffer = 14,
+ GetBufferHistory = 17,
+};
+
+class IBinder {
+public:
+ virtual void Transact(Kernel::HLERequestContext& ctx, android::TransactionId code,
+ u32 flags) = 0;
+ virtual Kernel::KReadableEvent& GetNativeHandle() = 0;
+};
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/buffer_item.h b/src/core/hle/service/nvflinger/buffer_item.h
new file mode 100644
index 000000000..64b82b851
--- /dev/null
+++ b/src/core/hle/service/nvflinger/buffer_item.h
@@ -0,0 +1,46 @@
+// 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/BufferItem.h
+
+#pragma once
+
+#include <memory>
+
+#include "common/common_types.h"
+#include "common/math_util.h"
+#include "core/hle/service/nvflinger/ui/fence.h"
+#include "core/hle/service/nvflinger/window.h"
+
+namespace Service::android {
+
+class GraphicBuffer;
+
+class BufferItem final {
+public:
+ constexpr BufferItem() = default;
+
+ std::shared_ptr<GraphicBuffer> graphic_buffer;
+ Fence fence;
+ Common::Rectangle<s32> crop;
+ NativeWindowTransform transform{};
+ u32 scaling_mode{};
+ s64 timestamp{};
+ bool is_auto_timestamp{};
+ u64 frame_number{};
+
+ // The default value for buf, used to indicate this doesn't correspond to a slot.
+ static constexpr s32 INVALID_BUFFER_SLOT = -1;
+ union {
+ s32 slot{INVALID_BUFFER_SLOT};
+ s32 buf;
+ };
+
+ bool is_droppable{};
+ bool acquire_called{};
+ bool transform_to_display_inverse{};
+ s32 swap_interval{};
+};
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/buffer_item_consumer.cpp b/src/core/hle/service/nvflinger/buffer_item_consumer.cpp
new file mode 100644
index 000000000..7f32c0775
--- /dev/null
+++ b/src/core/hle/service/nvflinger/buffer_item_consumer.cpp
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+// Copyright 2012 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/BufferItemConsumer.cpp
+
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "core/hle/service/nvflinger/buffer_item.h"
+#include "core/hle/service/nvflinger/buffer_item_consumer.h"
+#include "core/hle/service/nvflinger/buffer_queue_consumer.h"
+
+namespace Service::android {
+
+BufferItemConsumer::BufferItemConsumer(std::unique_ptr<BufferQueueConsumer> consumer_)
+ : ConsumerBase{std::move(consumer_)} {}
+
+Status BufferItemConsumer::AcquireBuffer(BufferItem* item, std::chrono::nanoseconds present_when,
+ bool wait_for_fence) {
+ if (!item) {
+ return Status::BadValue;
+ }
+
+ std::unique_lock lock(mutex);
+
+ if (const auto status = AcquireBufferLocked(item, present_when); status != Status::NoError) {
+ if (status != Status::NoBufferAvailable) {
+ LOG_ERROR(Service_NVFlinger, "Failed to acquire buffer: {}", status);
+ }
+ return status;
+ }
+
+ if (wait_for_fence) {
+ UNIMPLEMENTED();
+ }
+
+ item->graphic_buffer = slots[item->slot].graphic_buffer;
+
+ return Status::NoError;
+}
+
+Status BufferItemConsumer::ReleaseBuffer(const BufferItem& item, Fence& release_fence) {
+ std::unique_lock lock(mutex);
+
+ if (const auto status = AddReleaseFenceLocked(item.buf, item.graphic_buffer, release_fence);
+ status != Status::NoError) {
+ LOG_ERROR(Service_NVFlinger, "Failed to add fence: {}", status);
+ }
+
+ if (const auto status = ReleaseBufferLocked(item.buf, item.graphic_buffer);
+ status != Status::NoError) {
+ LOG_WARNING(Service_NVFlinger, "Failed to release buffer: {}", status);
+ return status;
+ }
+
+ return Status::NoError;
+}
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/buffer_item_consumer.h b/src/core/hle/service/nvflinger/buffer_item_consumer.h
new file mode 100644
index 000000000..536db81e2
--- /dev/null
+++ b/src/core/hle/service/nvflinger/buffer_item_consumer.h
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+// Copyright 2012 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/BufferItemConsumer.h
+
+#pragma once
+
+#include <chrono>
+#include <memory>
+
+#include "common/common_types.h"
+#include "core/hle/service/nvflinger/consumer_base.h"
+#include "core/hle/service/nvflinger/status.h"
+
+namespace Service::android {
+
+class BufferItem;
+
+class BufferItemConsumer final : public ConsumerBase {
+public:
+ explicit BufferItemConsumer(std::unique_ptr<BufferQueueConsumer> consumer);
+ Status AcquireBuffer(BufferItem* item, std::chrono::nanoseconds present_when,
+ bool wait_for_fence = true);
+ Status ReleaseBuffer(const BufferItem& item, Fence& release_fence);
+};
+
+} // namespace Service::android
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..677bec932
--- /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 Service::android {
+
+BufferQueueConsumer::BufferQueueConsumer(std::shared_ptr<BufferQueueCore> core_)
+ : core{std::move(core_)}, slots{core->slots} {}
+
+BufferQueueConsumer::~BufferQueueConsumer() = default;
+
+Status BufferQueueConsumer::AcquireBuffer(BufferItem* out_buffer,
+ std::chrono::nanoseconds expected_present,
+ 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.
+ const s32 num_acquired_buffers{
+ static_cast<s32>(std::count_if(slots.begin(), slots.end(), [](const auto& slot) {
+ return slot.buffer_state == BufferState::Acquired;
+ }))};
+
+ 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_present is specified, we may not want to return a buffer yet.
+ if (expected_present.count() != 0) {
+ constexpr auto MAX_REASONABLE_NSEC = 1000000000LL; // 1 second
+
+ // The expected_present 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_present.count() - MAX_REASONABLE_NSEC ||
+ desired_present > expected_present.count()) {
+ // 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_present.count());
+ break;
+ }
+
+ LOG_DEBUG(Service_NVFlinger, "drop desire={} expect={} size={}", desired_present,
+ expected_present.count(), 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_present.count() ||
+ desired_present > expected_present.count() + 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_present.count());
+ return Status::PresentLater;
+ }
+
+ LOG_DEBUG(Service_NVFlinger, "accept desire={} expect={}", desired_present,
+ expected_present.count());
+ }
+
+ 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 Service::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..f22854394
--- /dev/null
+++ b/src/core/hle/service/nvflinger/buffer_queue_consumer.h
@@ -0,0 +1,37 @@
+// 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 <chrono>
+#include <memory>
+
+#include "common/common_types.h"
+#include "core/hle/service/nvflinger/buffer_queue_defs.h"
+#include "core/hle/service/nvflinger/status.h"
+
+namespace Service::android {
+
+class BufferItem;
+class BufferQueueCore;
+class IConsumerListener;
+
+class BufferQueueConsumer final {
+public:
+ explicit BufferQueueConsumer(std::shared_ptr<BufferQueueCore> core_);
+ ~BufferQueueConsumer();
+
+ Status AcquireBuffer(BufferItem* out_buffer, std::chrono::nanoseconds expected_present,
+ 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 Service::android
diff --git a/src/core/hle/service/nvflinger/buffer_queue_core.cpp b/src/core/hle/service/nvflinger/buffer_queue_core.cpp
new file mode 100644
index 000000000..eb93b43ee
--- /dev/null
+++ b/src/core/hle/service/nvflinger/buffer_queue_core.cpp
@@ -0,0 +1,134 @@
+// 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/BufferQueueCore.cpp
+
+#include "common/assert.h"
+
+#include "core/hle/service/nvflinger/buffer_queue_core.h"
+
+namespace Service::android {
+
+BufferQueueCore::BufferQueueCore() : lock{mutex, std::defer_lock} {
+ for (s32 slot = 0; slot < BufferQueueDefs::NUM_BUFFER_SLOTS; ++slot) {
+ free_slots.insert(slot);
+ }
+}
+
+BufferQueueCore::~BufferQueueCore() = default;
+
+void BufferQueueCore::NotifyShutdown() {
+ std::unique_lock lk(mutex);
+
+ is_shutting_down = true;
+
+ SignalDequeueCondition();
+}
+
+void BufferQueueCore::SignalDequeueCondition() {
+ dequeue_condition.notify_all();
+}
+
+bool BufferQueueCore::WaitForDequeueCondition() {
+ if (is_shutting_down) {
+ return false;
+ }
+
+ dequeue_condition.wait(lock);
+
+ return true;
+}
+
+s32 BufferQueueCore::GetMinUndequeuedBufferCountLocked(bool async) const {
+ // If DequeueBuffer is allowed to error out, we don't have to add an extra buffer.
+ if (!use_async_buffer) {
+ return max_acquired_buffer_count;
+ }
+
+ if (dequeue_buffer_cannot_block || async) {
+ return max_acquired_buffer_count + 1;
+ }
+
+ return max_acquired_buffer_count;
+}
+
+s32 BufferQueueCore::GetMinMaxBufferCountLocked(bool async) const {
+ return GetMinUndequeuedBufferCountLocked(async) + 1;
+}
+
+s32 BufferQueueCore::GetMaxBufferCountLocked(bool async) const {
+ const auto min_buffer_count = GetMinMaxBufferCountLocked(async);
+ auto max_buffer_count = std::max(default_max_buffer_count, min_buffer_count);
+
+ if (override_max_buffer_count != 0) {
+ ASSERT(override_max_buffer_count >= min_buffer_count);
+ max_buffer_count = override_max_buffer_count;
+ }
+
+ // Any buffers that are dequeued by the producer or sitting in the queue waiting to be consumed
+ // need to have their slots preserved.
+ for (s32 slot = max_buffer_count; slot < BufferQueueDefs::NUM_BUFFER_SLOTS; ++slot) {
+ const auto state = slots[slot].buffer_state;
+ if (state == BufferState::Queued || state == BufferState::Dequeued) {
+ max_buffer_count = slot + 1;
+ }
+ }
+
+ return max_buffer_count;
+}
+
+s32 BufferQueueCore::GetPreallocatedBufferCountLocked() const {
+ return static_cast<s32>(std::count_if(slots.begin(), slots.end(),
+ [](const auto& slot) { return slot.is_preallocated; }));
+}
+
+void BufferQueueCore::FreeBufferLocked(s32 slot) {
+ LOG_DEBUG(Service_NVFlinger, "slot {}", slot);
+
+ const auto had_buffer = slots[slot].graphic_buffer != nullptr;
+
+ slots[slot].graphic_buffer.reset();
+
+ if (slots[slot].buffer_state == BufferState::Acquired) {
+ slots[slot].needs_cleanup_on_release = true;
+ }
+
+ if (slots[slot].buffer_state != BufferState::Free) {
+ free_slots.insert(slot);
+ } else if (had_buffer) {
+ // If the slot was FREE, but we had a buffer, we need to move this slot from the free
+ // buffers list to the the free slots list.
+ free_buffers.remove(slot);
+ free_slots.insert(slot);
+ }
+
+ slots[slot].buffer_state = BufferState::Free;
+ slots[slot].acquire_called = false;
+ slots[slot].frame_number = 0;
+ slots[slot].fence = Fence::NoFence();
+}
+
+void BufferQueueCore::FreeAllBuffersLocked() {
+ queue.clear();
+ buffer_has_been_queued = false;
+
+ for (s32 slot = 0; slot < BufferQueueDefs::NUM_BUFFER_SLOTS; ++slot) {
+ FreeBufferLocked(slot);
+ }
+}
+
+bool BufferQueueCore::StillTracking(const BufferItem& item) const {
+ const BufferSlot& slot = slots[item.slot];
+
+ return (slot.graphic_buffer != nullptr) && (item.graphic_buffer == slot.graphic_buffer);
+}
+
+void BufferQueueCore::WaitWhileAllocatingLocked() const {
+ while (is_allocating) {
+ std::unique_lock lk(mutex);
+ is_allocating_condition.wait(lk);
+ }
+}
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/buffer_queue_core.h b/src/core/hle/service/nvflinger/buffer_queue_core.h
new file mode 100644
index 000000000..a3cd89f1c
--- /dev/null
+++ b/src/core/hle/service/nvflinger/buffer_queue_core.h
@@ -0,0 +1,99 @@
+// 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/BufferQueueCore.h
+
+#pragma once
+
+#include <condition_variable>
+#include <list>
+#include <memory>
+#include <mutex>
+#include <set>
+#include <vector>
+
+#include "core/hle/service/nvflinger/buffer_item.h"
+#include "core/hle/service/nvflinger/buffer_queue_defs.h"
+#include "core/hle/service/nvflinger/pixel_format.h"
+#include "core/hle/service/nvflinger/status.h"
+#include "core/hle/service/nvflinger/window.h"
+
+namespace Service::android {
+
+class IConsumerListener;
+class IProducerListener;
+
+class BufferQueueCore final {
+ friend class BufferQueueProducer;
+ friend class BufferQueueConsumer;
+
+public:
+ static constexpr s32 INVALID_BUFFER_SLOT = BufferItem::INVALID_BUFFER_SLOT;
+
+ BufferQueueCore();
+ ~BufferQueueCore();
+
+ void NotifyShutdown();
+
+private:
+ void SignalDequeueCondition();
+ bool WaitForDequeueCondition();
+
+ s32 GetMinUndequeuedBufferCountLocked(bool async) const;
+ s32 GetMinMaxBufferCountLocked(bool async) const;
+ s32 GetMaxBufferCountLocked(bool async) const;
+ s32 GetPreallocatedBufferCountLocked() const;
+ void FreeBufferLocked(s32 slot);
+ void FreeAllBuffersLocked();
+ bool StillTracking(const BufferItem& item) const;
+ void WaitWhileAllocatingLocked() const;
+
+private:
+ class AutoLock final {
+ public:
+ AutoLock(std::shared_ptr<BufferQueueCore>& core_) : core{core_} {
+ core->lock.lock();
+ }
+
+ ~AutoLock() {
+ core->lock.unlock();
+ }
+
+ private:
+ std::shared_ptr<BufferQueueCore>& core;
+ };
+
+private:
+ mutable std::mutex mutex;
+ mutable std::unique_lock<std::mutex> lock;
+ bool is_abandoned{};
+ bool consumer_controlled_by_app{};
+ std::shared_ptr<IConsumerListener> consumer_listener;
+ u32 consumer_usage_bit{};
+ NativeWindowApi connected_api{NativeWindowApi::NoConnectedApi};
+ std::shared_ptr<IProducerListener> connected_producer_listener;
+ BufferQueueDefs::SlotsType slots{};
+ std::vector<BufferItem> queue;
+ std::set<s32> free_slots;
+ std::list<s32> free_buffers;
+ s32 override_max_buffer_count{};
+ mutable std::condition_variable dequeue_condition;
+ const bool use_async_buffer{}; // This is always disabled on HOS
+ bool dequeue_buffer_cannot_block{};
+ PixelFormat default_buffer_format{PixelFormat::Rgba8888};
+ u32 default_width{1};
+ u32 default_height{1};
+ s32 default_max_buffer_count{2};
+ const s32 max_acquired_buffer_count{}; // This is always zero on HOS
+ bool buffer_has_been_queued{};
+ u64 frame_counter{};
+ u32 transform_hint{};
+ bool is_allocating{};
+ mutable std::condition_variable is_allocating_condition;
+ bool allow_allocation{true};
+ u64 buffer_age{};
+ bool is_shutting_down{};
+};
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/buffer_queue_defs.h b/src/core/hle/service/nvflinger/buffer_queue_defs.h
new file mode 100644
index 000000000..387d3d36a
--- /dev/null
+++ b/src/core/hle/service/nvflinger/buffer_queue_defs.h
@@ -0,0 +1,21 @@
+// 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/BufferQueueDefs.h
+
+#pragma once
+
+#include <array>
+
+#include "common/common_types.h"
+#include "core/hle/service/nvflinger/buffer_slot.h"
+
+namespace Service::android::BufferQueueDefs {
+
+// BufferQueue will keep track of at most this value of buffers.
+constexpr s32 NUM_BUFFER_SLOTS = 64;
+
+using SlotsType = std::array<BufferSlot, NUM_BUFFER_SLOTS>;
+
+} // namespace Service::android::BufferQueueDefs
diff --git a/src/core/hle/service/nvflinger/buffer_queue_producer.cpp b/src/core/hle/service/nvflinger/buffer_queue_producer.cpp
new file mode 100644
index 000000000..078091904
--- /dev/null
+++ b/src/core/hle/service/nvflinger/buffer_queue_producer.cpp
@@ -0,0 +1,937 @@
+// 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/BufferQueueProducer.cpp
+
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "common/settings.h"
+#include "core/core.h"
+#include "core/hle/kernel/hle_ipc.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_readable_event.h"
+#include "core/hle/kernel/k_writable_event.h"
+#include "core/hle/kernel/kernel.h"
+#include "core/hle/service/kernel_helpers.h"
+#include "core/hle/service/nvdrv/nvdrv.h"
+#include "core/hle/service/nvflinger/buffer_queue_core.h"
+#include "core/hle/service/nvflinger/buffer_queue_producer.h"
+#include "core/hle/service/nvflinger/consumer_listener.h"
+#include "core/hle/service/nvflinger/parcel.h"
+#include "core/hle/service/nvflinger/ui/graphic_buffer.h"
+#include "core/hle/service/nvflinger/window.h"
+#include "core/hle/service/vi/vi.h"
+
+namespace Service::android {
+
+BufferQueueProducer::BufferQueueProducer(Service::KernelHelpers::ServiceContext& service_context_,
+ std::shared_ptr<BufferQueueCore> buffer_queue_core_)
+ : service_context{service_context_}, core{std::move(buffer_queue_core_)}, slots(core->slots) {
+ buffer_wait_event = service_context.CreateEvent("BufferQueue:WaitEvent");
+}
+
+BufferQueueProducer::~BufferQueueProducer() {
+ service_context.CloseEvent(buffer_wait_event);
+}
+
+Status BufferQueueProducer::RequestBuffer(s32 slot, std::shared_ptr<GraphicBuffer>* buf) {
+ LOG_DEBUG(Service_NVFlinger, "slot {}", slot);
+
+ BufferQueueCore::AutoLock lock(core);
+
+ if (core->is_abandoned) {
+ LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned");
+ return Status::NoInit;
+ }
+ if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
+ LOG_ERROR(Service_NVFlinger, "slot index {} out of range [0, {})", slot,
+ BufferQueueDefs::NUM_BUFFER_SLOTS);
+ return Status::BadValue;
+ } else if (slots[slot].buffer_state != BufferState::Dequeued) {
+ LOG_ERROR(Service_NVFlinger, "slot {} is not owned by the producer (state = {})", slot,
+ slots[slot].buffer_state);
+ return Status::BadValue;
+ }
+
+ slots[slot].request_buffer_called = true;
+ *buf = slots[slot].graphic_buffer;
+
+ return Status::NoError;
+}
+
+Status BufferQueueProducer::SetBufferCount(s32 buffer_count) {
+ LOG_DEBUG(Service_NVFlinger, "count = {}", buffer_count);
+ std::shared_ptr<IConsumerListener> listener;
+
+ {
+ BufferQueueCore::AutoLock lock(core);
+ core->WaitWhileAllocatingLocked();
+ if (core->is_abandoned) {
+ LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned");
+ return Status::NoInit;
+ }
+
+ if (buffer_count > BufferQueueDefs::NUM_BUFFER_SLOTS) {
+ LOG_ERROR(Service_NVFlinger, "buffer_count {} too large (max {})", buffer_count,
+ BufferQueueDefs::NUM_BUFFER_SLOTS);
+ return Status::BadValue;
+ }
+
+ // There must be no dequeued buffers when changing the buffer count.
+ for (s32 s{}; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
+ if (slots[s].buffer_state == BufferState::Dequeued) {
+ LOG_ERROR(Service_NVFlinger, "buffer owned by producer");
+ return Status::BadValue;
+ }
+ }
+
+ if (buffer_count == 0) {
+ core->override_max_buffer_count = 0;
+ core->SignalDequeueCondition();
+ return Status::NoError;
+ }
+
+ const s32 min_buffer_slots = core->GetMinMaxBufferCountLocked(false);
+ if (buffer_count < min_buffer_slots) {
+ LOG_ERROR(Service_NVFlinger, "requested buffer count {} is less than minimum {}",
+ buffer_count, min_buffer_slots);
+ return Status::BadValue;
+ }
+
+ // Here we are guaranteed that the producer doesn't have any dequeued buffers and will
+ // release all of its buffer references.
+ if (core->GetPreallocatedBufferCountLocked() <= 0) {
+ core->FreeAllBuffersLocked();
+ }
+
+ core->override_max_buffer_count = buffer_count;
+ core->SignalDequeueCondition();
+ buffer_wait_event->GetWritableEvent().Signal();
+ listener = core->consumer_listener;
+ }
+
+ // Call back without lock held
+ if (listener != nullptr) {
+ listener->OnBuffersReleased();
+ }
+
+ return Status::NoError;
+}
+
+Status BufferQueueProducer::WaitForFreeSlotThenRelock(bool async, s32* found,
+ Status* returnFlags) const {
+ bool try_again = true;
+
+ while (try_again) {
+ if (core->is_abandoned) {
+ LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned");
+ return Status::NoInit;
+ }
+
+ const s32 max_buffer_count = core->GetMaxBufferCountLocked(async);
+ if (async && core->override_max_buffer_count) {
+ if (core->override_max_buffer_count < max_buffer_count) {
+ LOG_ERROR(Service_NVFlinger, "async mode is invalid with buffer count override");
+ return Status::BadValue;
+ }
+ }
+
+ // Free up any buffers that are in slots beyond the max buffer count
+ for (s32 s = max_buffer_count; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
+ ASSERT(slots[s].buffer_state == BufferState::Free);
+ if (slots[s].graphic_buffer != nullptr) {
+ core->FreeBufferLocked(s);
+ *returnFlags |= Status::ReleaseAllBuffers;
+ }
+ }
+
+ s32 dequeued_count{};
+ s32 acquired_count{};
+ for (s32 s{}; s < max_buffer_count; ++s) {
+ switch (slots[s].buffer_state) {
+ case BufferState::Dequeued:
+ ++dequeued_count;
+ break;
+ case BufferState::Acquired:
+ ++acquired_count;
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Producers are not allowed to dequeue more than one buffer if they did not set a buffer
+ // count
+ if (!core->override_max_buffer_count && dequeued_count) {
+ LOG_ERROR(Service_NVFlinger,
+ "can't dequeue multiple buffers without setting the buffer count");
+ return Status::InvalidOperation;
+ }
+
+ // See whether a buffer has been queued since the last SetBufferCount so we know whether to
+ // perform the min undequeued buffers check below
+ if (core->buffer_has_been_queued) {
+ // Make sure the producer is not trying to dequeue more buffers than allowed
+ const s32 new_undequeued_count = max_buffer_count - (dequeued_count + 1);
+ const s32 min_undequeued_count = core->GetMinUndequeuedBufferCountLocked(async);
+ if (new_undequeued_count < min_undequeued_count) {
+ LOG_ERROR(Service_NVFlinger,
+ "min undequeued buffer count({}) exceeded (dequeued={} undequeued={})",
+ min_undequeued_count, dequeued_count, new_undequeued_count);
+ return Status::InvalidOperation;
+ }
+ }
+
+ *found = BufferQueueCore::INVALID_BUFFER_SLOT;
+
+ // If we disconnect and reconnect quickly, we can be in a state where our slots are empty
+ // but we have many buffers in the queue. This can cause us to run out of memory if we
+ // outrun the consumer. Wait here if it looks like we have too many buffers queued up.
+ const bool too_many_buffers = core->queue.size() > static_cast<size_t>(max_buffer_count);
+ if (too_many_buffers) {
+ LOG_ERROR(Service_NVFlinger, "queue size is {}, waiting", core->queue.size());
+ } else {
+ if (!core->free_buffers.empty()) {
+ auto slot = core->free_buffers.begin();
+ *found = *slot;
+ core->free_buffers.erase(slot);
+ } else if (core->allow_allocation && !core->free_slots.empty()) {
+ auto slot = core->free_slots.begin();
+ // Only return free slots up to the max buffer count
+ if (*slot < max_buffer_count) {
+ *found = *slot;
+ core->free_slots.erase(slot);
+ }
+ }
+ }
+
+ // If no buffer is found, or if the queue has too many buffers outstanding, wait for a
+ // buffer to be acquired or released, or for the max buffer count to change.
+ try_again = (*found == BufferQueueCore::INVALID_BUFFER_SLOT) || too_many_buffers;
+ if (try_again) {
+ // Return an error if we're in non-blocking mode (producer and consumer are controlled
+ // by the application).
+ if (core->dequeue_buffer_cannot_block &&
+ (acquired_count <= core->max_acquired_buffer_count)) {
+ return Status::WouldBlock;
+ }
+
+ if (!core->WaitForDequeueCondition()) {
+ // We are no longer running
+ return Status::NoError;
+ }
+ }
+ }
+
+ return Status::NoError;
+}
+
+Status BufferQueueProducer::DequeueBuffer(s32* out_slot, Fence* out_fence, bool async, u32 width,
+ u32 height, PixelFormat format, u32 usage) {
+ LOG_DEBUG(Service_NVFlinger, "async={} w={} h={} format={}, usage={}", async ? "true" : "false",
+ width, height, format, usage);
+
+ if ((width != 0 && height == 0) || (width == 0 && height != 0)) {
+ LOG_ERROR(Service_NVFlinger, "invalid size: w={} h={}", width, height);
+ return Status::BadValue;
+ }
+
+ Status return_flags = Status::NoError;
+ bool attached_by_consumer = false;
+ {
+ BufferQueueCore::AutoLock lock(core);
+ core->WaitWhileAllocatingLocked();
+ if (format == PixelFormat::NoFormat) {
+ format = core->default_buffer_format;
+ }
+
+ // Enable the usage bits the consumer requested
+ usage |= core->consumer_usage_bit;
+ const bool use_default_size = !width && !height;
+ if (use_default_size) {
+ width = core->default_width;
+ height = core->default_height;
+ }
+
+ s32 found = BufferItem::INVALID_BUFFER_SLOT;
+ while (found == BufferItem::INVALID_BUFFER_SLOT) {
+ Status status = WaitForFreeSlotThenRelock(async, &found, &return_flags);
+ if (status != Status::NoError) {
+ return status;
+ }
+
+ // This should not happen
+ if (found == BufferQueueCore::INVALID_BUFFER_SLOT) {
+ LOG_DEBUG(Service_NVFlinger, "no available buffer slots");
+ return Status::Busy;
+ }
+
+ const std::shared_ptr<GraphicBuffer>& buffer(slots[found].graphic_buffer);
+
+ // If we are not allowed to allocate new buffers, WaitForFreeSlotThenRelock must have
+ // returned a slot containing a buffer. If this buffer would require reallocation to
+ // meet the requested attributes, we free it and attempt to get another one.
+ if (!core->allow_allocation) {
+ if (buffer->NeedsReallocation(width, height, format, usage)) {
+ core->FreeBufferLocked(found);
+ found = BufferItem::INVALID_BUFFER_SLOT;
+ continue;
+ }
+ }
+ }
+
+ *out_slot = found;
+ attached_by_consumer = slots[found].attached_by_consumer;
+ slots[found].buffer_state = BufferState::Dequeued;
+
+ const std::shared_ptr<GraphicBuffer>& buffer(slots[found].graphic_buffer);
+
+ if ((buffer == nullptr) || buffer->NeedsReallocation(width, height, format, usage)) {
+ slots[found].acquire_called = false;
+ slots[found].graphic_buffer = nullptr;
+ slots[found].request_buffer_called = false;
+ slots[found].fence = Fence::NoFence();
+ core->buffer_age = 0;
+ return_flags |= Status::BufferNeedsReallocation;
+ } else {
+ // We add 1 because that will be the frame number when this buffer
+ // is queued
+ core->buffer_age = core->frame_counter + 1 - slots[found].frame_number;
+ }
+
+ LOG_DEBUG(Service_NVFlinger, "setting buffer age to {}", core->buffer_age);
+
+ *out_fence = slots[found].fence;
+
+ slots[found].fence = Fence::NoFence();
+ }
+
+ if ((return_flags & Status::BufferNeedsReallocation) != Status::None) {
+ LOG_DEBUG(Service_NVFlinger, "allocating a new buffer for slot {}", *out_slot);
+
+ auto graphic_buffer = std::make_shared<GraphicBuffer>(width, height, format, usage);
+ if (graphic_buffer == nullptr) {
+ LOG_ERROR(Service_NVFlinger, "creating GraphicBuffer failed");
+ return Status::NoMemory;
+ }
+
+ {
+ BufferQueueCore::AutoLock lock(core);
+ if (core->is_abandoned) {
+ LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned");
+ return Status::NoInit;
+ }
+
+ slots[*out_slot].graphic_buffer = graphic_buffer;
+ }
+ }
+
+ if (attached_by_consumer) {
+ return_flags |= Status::BufferNeedsReallocation;
+ }
+
+ LOG_DEBUG(Service_NVFlinger, "returning slot={} frame={}, flags={}", *out_slot,
+ slots[*out_slot].frame_number, return_flags);
+ return return_flags;
+}
+
+Status BufferQueueProducer::DetachBuffer(s32 slot) {
+ LOG_DEBUG(Service_NVFlinger, "slot {}", slot);
+
+ BufferQueueCore::AutoLock lock(core);
+ if (core->is_abandoned) {
+ LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned");
+ return Status::NoInit;
+ }
+
+ if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
+ LOG_ERROR(Service_NVFlinger, "slot {} out of range [0, {})", slot,
+ BufferQueueDefs::NUM_BUFFER_SLOTS);
+ return Status::BadValue;
+ } else if (slots[slot].buffer_state != BufferState::Dequeued) {
+ LOG_ERROR(Service_NVFlinger, "slot {} is not owned by the producer (state = {})", slot,
+ slots[slot].buffer_state);
+ return Status::BadValue;
+ } else if (!slots[slot].request_buffer_called) {
+ LOG_ERROR(Service_NVFlinger, "buffer in slot {} has not been requested", slot);
+ return Status::BadValue;
+ }
+
+ core->FreeBufferLocked(slot);
+ core->SignalDequeueCondition();
+
+ return Status::NoError;
+}
+
+Status BufferQueueProducer::DetachNextBuffer(std::shared_ptr<GraphicBuffer>* out_buffer,
+ Fence* out_fence) {
+ if (out_buffer == nullptr) {
+ LOG_ERROR(Service_NVFlinger, "out_buffer must not be nullptr");
+ return Status::BadValue;
+ } else if (out_fence == nullptr) {
+ LOG_ERROR(Service_NVFlinger, "out_fence must not be nullptr");
+ return Status::BadValue;
+ }
+
+ BufferQueueCore::AutoLock lock(core);
+
+ core->WaitWhileAllocatingLocked();
+
+ if (core->is_abandoned) {
+ LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned");
+ return Status::NoInit;
+ }
+ if (core->free_buffers.empty()) {
+ return Status::NoMemory;
+ }
+
+ const s32 found = core->free_buffers.front();
+ core->free_buffers.remove(found);
+
+ LOG_DEBUG(Service_NVFlinger, "Detached slot {}", found);
+
+ *out_buffer = slots[found].graphic_buffer;
+ *out_fence = slots[found].fence;
+
+ core->FreeBufferLocked(found);
+
+ return Status::NoError;
+}
+
+Status BufferQueueProducer::AttachBuffer(s32* out_slot,
+ const std::shared_ptr<GraphicBuffer>& buffer) {
+ if (out_slot == nullptr) {
+ LOG_ERROR(Service_NVFlinger, "out_slot must not be nullptr");
+ return Status::BadValue;
+ } else if (buffer == nullptr) {
+ LOG_ERROR(Service_NVFlinger, "Cannot attach nullptr buffer");
+ return Status::BadValue;
+ }
+
+ BufferQueueCore::AutoLock lock(core);
+ core->WaitWhileAllocatingLocked();
+
+ Status return_flags = Status::NoError;
+ s32 found{};
+
+ const auto status = WaitForFreeSlotThenRelock(false, &found, &return_flags);
+ if (status != Status::NoError) {
+ return status;
+ }
+
+ if (found == BufferQueueCore::INVALID_BUFFER_SLOT) {
+ LOG_ERROR(Service_NVFlinger, "No available buffer slots");
+ return Status::Busy;
+ }
+
+ *out_slot = found;
+
+ LOG_DEBUG(Service_NVFlinger, "Returning slot {} flags={}", *out_slot, return_flags);
+
+ slots[*out_slot].graphic_buffer = buffer;
+ slots[*out_slot].buffer_state = BufferState::Dequeued;
+ slots[*out_slot].fence = Fence::NoFence();
+ slots[*out_slot].request_buffer_called = true;
+
+ return return_flags;
+}
+
+Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input,
+ QueueBufferOutput* output) {
+ s64 timestamp{};
+ bool is_auto_timestamp{};
+ Common::Rectangle<s32> crop;
+ NativeWindowScalingMode scaling_mode{};
+ NativeWindowTransform transform;
+ u32 sticky_transform_{};
+ bool async{};
+ s32 swap_interval{};
+ Fence fence{};
+
+ input.Deflate(&timestamp, &is_auto_timestamp, &crop, &scaling_mode, &transform,
+ &sticky_transform_, &async, &swap_interval, &fence);
+
+ switch (scaling_mode) {
+ case NativeWindowScalingMode::Freeze:
+ case NativeWindowScalingMode::ScaleToWindow:
+ case NativeWindowScalingMode::ScaleCrop:
+ case NativeWindowScalingMode::NoScaleCrop:
+ break;
+ default:
+ LOG_ERROR(Service_NVFlinger, "unknown scaling mode {}", scaling_mode);
+ return Status::BadValue;
+ }
+
+ std::shared_ptr<IConsumerListener> frameAvailableListener;
+ std::shared_ptr<IConsumerListener> frameReplacedListener;
+ s32 callback_ticket{};
+ BufferItem item;
+
+ {
+ BufferQueueCore::AutoLock lock(core);
+
+ if (core->is_abandoned) {
+ LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned");
+ return Status::NoInit;
+ }
+
+ const s32 max_buffer_count = core->GetMaxBufferCountLocked(async);
+ if (async && core->override_max_buffer_count) {
+ if (core->override_max_buffer_count < max_buffer_count) {
+ LOG_ERROR(Service_NVFlinger, "async mode is invalid with "
+ "buffer count override");
+ return Status::BadValue;
+ }
+ }
+
+ if (slot < 0 || slot >= max_buffer_count) {
+ LOG_ERROR(Service_NVFlinger, "slot index {} out of range [0, {})", slot,
+ max_buffer_count);
+ return Status::BadValue;
+ } else if (slots[slot].buffer_state != BufferState::Dequeued) {
+ LOG_ERROR(Service_NVFlinger,
+ "slot {} is not owned by the producer "
+ "(state = {})",
+ slot, slots[slot].buffer_state);
+ return Status::BadValue;
+ } else if (!slots[slot].request_buffer_called) {
+ LOG_ERROR(Service_NVFlinger,
+ "slot {} was queued without requesting "
+ "a buffer",
+ slot);
+ return Status::BadValue;
+ }
+
+ LOG_DEBUG(Service_NVFlinger,
+ "slot={} frame={} time={} crop=[{},{},{},{}] transform={} scale={}", slot,
+ core->frame_counter + 1, timestamp, crop.Left(), crop.Top(), crop.Right(),
+ crop.Bottom(), transform, scaling_mode);
+
+ const std::shared_ptr<GraphicBuffer>& graphic_buffer(slots[slot].graphic_buffer);
+ Common::Rectangle<s32> buffer_rect(graphic_buffer->Width(), graphic_buffer->Height());
+ Common::Rectangle<s32> cropped_rect;
+ [[maybe_unused]] const bool unused = crop.Intersect(buffer_rect, &cropped_rect);
+
+ if (cropped_rect != crop) {
+ LOG_ERROR(Service_NVFlinger, "crop rect is not contained within the buffer in slot {}",
+ slot);
+ return Status::BadValue;
+ }
+
+ slots[slot].fence = fence;
+ slots[slot].buffer_state = BufferState::Queued;
+ ++core->frame_counter;
+ slots[slot].frame_number = core->frame_counter;
+
+ item.acquire_called = slots[slot].acquire_called;
+ item.graphic_buffer = slots[slot].graphic_buffer;
+ item.crop = crop;
+ item.transform = transform & ~NativeWindowTransform::InverseDisplay;
+ item.transform_to_display_inverse =
+ (transform & NativeWindowTransform::InverseDisplay) != NativeWindowTransform::None;
+ item.scaling_mode = static_cast<u32>(scaling_mode);
+ item.timestamp = timestamp;
+ item.is_auto_timestamp = is_auto_timestamp;
+ item.frame_number = core->frame_counter;
+ item.slot = slot;
+ item.fence = fence;
+ item.is_droppable = core->dequeue_buffer_cannot_block || async;
+ item.swap_interval = swap_interval;
+ sticky_transform = sticky_transform_;
+
+ if (core->queue.empty()) {
+ // When the queue is empty, we can simply queue this buffer
+ core->queue.push_back(item);
+ frameAvailableListener = core->consumer_listener;
+ } else {
+ // When the queue is not empty, we need to look at the front buffer
+ // state to see if we need to replace it
+ auto front(core->queue.begin());
+
+ if (front->is_droppable) {
+ // If the front queued buffer is still being tracked, we first
+ // mark it as freed
+ if (core->StillTracking(*front)) {
+ slots[front->slot].buffer_state = BufferState::Free;
+ core->free_buffers.push_front(front->slot);
+ }
+ // Overwrite the droppable buffer with the incoming one
+ *front = item;
+ frameReplacedListener = core->consumer_listener;
+ } else {
+ core->queue.push_back(item);
+ frameAvailableListener = core->consumer_listener;
+ }
+ }
+
+ core->buffer_has_been_queued = true;
+ core->SignalDequeueCondition();
+ output->Inflate(core->default_width, core->default_height, core->transform_hint,
+ static_cast<u32>(core->queue.size()));
+
+ // Take a ticket for the callback functions
+ callback_ticket = next_callback_ticket++;
+ }
+
+ // Don't send the GraphicBuffer through the callback, and don't send the slot number, since the
+ // consumer shouldn't need it
+ item.graphic_buffer.reset();
+ item.slot = BufferItem::INVALID_BUFFER_SLOT;
+
+ // Call back without the main BufferQueue lock held, but with the callback lock held so we can
+ // ensure that callbacks occur in order
+ {
+ std::unique_lock lock(callback_mutex);
+ while (callback_ticket != current_callback_ticket) {
+ std::unique_lock<std::mutex> lk(callback_mutex);
+ callback_condition.wait(lk);
+ }
+
+ if (frameAvailableListener != nullptr) {
+ frameAvailableListener->OnFrameAvailable(item);
+ } else if (frameReplacedListener != nullptr) {
+ frameReplacedListener->OnFrameReplaced(item);
+ }
+
+ ++current_callback_ticket;
+ callback_condition.notify_all();
+ }
+
+ return Status::NoError;
+}
+
+void BufferQueueProducer::CancelBuffer(s32 slot, const Fence& fence) {
+ LOG_DEBUG(Service_NVFlinger, "slot {}", slot);
+
+ BufferQueueCore::AutoLock lock(core);
+
+ if (core->is_abandoned) {
+ LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned");
+ return;
+ }
+
+ if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
+ LOG_ERROR(Service_NVFlinger, "slot index {} out of range [0, {})", slot,
+ BufferQueueDefs::NUM_BUFFER_SLOTS);
+ return;
+ } else if (slots[slot].buffer_state != BufferState::Dequeued) {
+ LOG_ERROR(Service_NVFlinger, "slot {} is not owned by the producer (state = {})", slot,
+ slots[slot].buffer_state);
+ return;
+ }
+
+ core->free_buffers.push_front(slot);
+ slots[slot].buffer_state = BufferState::Free;
+ slots[slot].fence = fence;
+
+ core->SignalDequeueCondition();
+ buffer_wait_event->GetWritableEvent().Signal();
+}
+
+Status BufferQueueProducer::Query(NativeWindow what, s32* out_value) {
+ BufferQueueCore::AutoLock lock(core);
+
+ if (out_value == nullptr) {
+ LOG_ERROR(Service_NVFlinger, "outValue was nullptr");
+ return Status::BadValue;
+ }
+
+ if (core->is_abandoned) {
+ LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned");
+ return Status::NoInit;
+ }
+
+ u32 value{};
+ switch (what) {
+ case NativeWindow::Width:
+ value = core->default_width;
+ break;
+ case NativeWindow::Height:
+ value = core->default_height;
+ break;
+ case NativeWindow::Format:
+ value = static_cast<u32>(core->default_buffer_format);
+ break;
+ case NativeWindow::MinUndequeedBuffers:
+ value = core->GetMinUndequeuedBufferCountLocked(false);
+ break;
+ case NativeWindow::StickyTransform:
+ value = sticky_transform;
+ break;
+ case NativeWindow::ConsumerRunningBehind:
+ value = (core->queue.size() > 1);
+ break;
+ case NativeWindow::ConsumerUsageBits:
+ value = core->consumer_usage_bit;
+ break;
+ case NativeWindow::BufferAge:
+ if (core->buffer_age > INT32_MAX) {
+ value = 0;
+ } else {
+ value = static_cast<u32>(core->buffer_age);
+ }
+ break;
+ default:
+ UNREACHABLE();
+ return Status::BadValue;
+ }
+
+ LOG_DEBUG(Service_NVFlinger, "what = {}, value = {}", what, value);
+
+ *out_value = static_cast<s32>(value);
+
+ return Status::NoError;
+}
+
+Status BufferQueueProducer::Connect(const std::shared_ptr<IProducerListener>& listener,
+ NativeWindowApi api, bool producer_controlled_by_app,
+ QueueBufferOutput* output) {
+ BufferQueueCore::AutoLock lock(core);
+
+ LOG_DEBUG(Service_NVFlinger, "api = {} producer_controlled_by_app = {}", api,
+ producer_controlled_by_app);
+
+ if (core->is_abandoned) {
+ LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned");
+ return Status::NoInit;
+ }
+
+ if (core->consumer_listener == nullptr) {
+ LOG_ERROR(Service_NVFlinger, "BufferQueue has no consumer");
+ return Status::NoInit;
+ }
+
+ if (output == nullptr) {
+ LOG_ERROR(Service_NVFlinger, "output was nullptr");
+ return Status::BadValue;
+ }
+
+ if (core->connected_api != NativeWindowApi::NoConnectedApi) {
+ LOG_ERROR(Service_NVFlinger, "already connected (cur = {} req = {})", core->connected_api,
+ api);
+ return Status::BadValue;
+ }
+
+ Status status = Status::NoError;
+ switch (api) {
+ case NativeWindowApi::Egl:
+ case NativeWindowApi::Cpu:
+ case NativeWindowApi::Media:
+ case NativeWindowApi::Camera:
+ core->connected_api = api;
+ output->Inflate(core->default_width, core->default_height, core->transform_hint,
+ static_cast<u32>(core->queue.size()));
+ core->connected_producer_listener = listener;
+ break;
+ default:
+ LOG_ERROR(Service_NVFlinger, "unknown api = {}", api);
+ status = Status::BadValue;
+ break;
+ }
+
+ core->buffer_has_been_queued = false;
+ core->dequeue_buffer_cannot_block =
+ core->consumer_controlled_by_app && producer_controlled_by_app;
+ core->allow_allocation = true;
+
+ return status;
+}
+
+Status BufferQueueProducer::Disconnect(NativeWindowApi api) {
+ LOG_DEBUG(Service_NVFlinger, "api = {}", api);
+
+ Status status = Status::NoError;
+ std::shared_ptr<IConsumerListener> listener;
+
+ {
+ BufferQueueCore::AutoLock lock(core);
+
+ core->WaitWhileAllocatingLocked();
+
+ if (core->is_abandoned) {
+ // Disconnecting after the surface has been abandoned is a no-op.
+ return Status::NoError;
+ }
+
+ switch (api) {
+ case NativeWindowApi::Egl:
+ case NativeWindowApi::Cpu:
+ case NativeWindowApi::Media:
+ case NativeWindowApi::Camera:
+ if (core->connected_api == api) {
+ core->FreeAllBuffersLocked();
+ core->connected_producer_listener = nullptr;
+ core->connected_api = NativeWindowApi::NoConnectedApi;
+ core->SignalDequeueCondition();
+ buffer_wait_event->GetWritableEvent().Signal();
+ listener = core->consumer_listener;
+ } else if (core->connected_api != NativeWindowApi::NoConnectedApi) {
+ LOG_ERROR(Service_NVFlinger, "still connected to another api (cur = {} req = {})",
+ core->connected_api, api);
+ status = Status::BadValue;
+ }
+ break;
+ default:
+ LOG_ERROR(Service_NVFlinger, "unknown api = {}", api);
+ status = Status::BadValue;
+ break;
+ }
+ }
+
+ // Call back without lock held
+ if (listener != nullptr) {
+ listener->OnBuffersReleased();
+ }
+
+ return status;
+}
+
+Status BufferQueueProducer::SetPreallocatedBuffer(s32 slot,
+ const std::shared_ptr<GraphicBuffer>& buffer) {
+ LOG_DEBUG(Service_NVFlinger, "slot {}", slot);
+
+ if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
+ return Status::BadValue;
+ }
+
+ BufferQueueCore::AutoLock lock(core);
+
+ slots[slot] = {};
+ slots[slot].graphic_buffer = buffer;
+
+ // Most games preallocate a buffer and pass a valid buffer here. However, it is possible for
+ // this to be called with an empty buffer, Naruto Ultimate Ninja Storm is a game that does this.
+ if (buffer) {
+ slots[slot].is_preallocated = true;
+
+ core->override_max_buffer_count = core->GetPreallocatedBufferCountLocked();
+ core->default_width = buffer->Width();
+ core->default_height = buffer->Height();
+ core->default_buffer_format = buffer->Format();
+ }
+
+ core->SignalDequeueCondition();
+ buffer_wait_event->GetWritableEvent().Signal();
+
+ return Status::NoError;
+}
+
+void BufferQueueProducer::Transact(Kernel::HLERequestContext& ctx, TransactionId code, u32 flags) {
+ Status status{Status::NoError};
+ Parcel parcel_in{ctx.ReadBuffer()};
+ Parcel parcel_out{};
+
+ switch (code) {
+ case TransactionId::Connect: {
+ const auto enable_listener = parcel_in.Read<bool>();
+ const auto api = parcel_in.Read<NativeWindowApi>();
+ const auto producer_controlled_by_app = parcel_in.Read<bool>();
+
+ UNIMPLEMENTED_IF_MSG(enable_listener, "Listener is unimplemented!");
+
+ std::shared_ptr<IProducerListener> listener;
+ QueueBufferOutput output{};
+
+ status = Connect(listener, api, producer_controlled_by_app, &output);
+
+ parcel_out.Write(output);
+ break;
+ }
+ case TransactionId::SetPreallocatedBuffer: {
+ const auto slot = parcel_in.Read<s32>();
+ const auto buffer = parcel_in.ReadObject<GraphicBuffer>();
+
+ status = SetPreallocatedBuffer(slot, buffer);
+ break;
+ }
+ case TransactionId::DequeueBuffer: {
+ const auto is_async = parcel_in.Read<bool>();
+ const auto width = parcel_in.Read<u32>();
+ const auto height = parcel_in.Read<u32>();
+ const auto pixel_format = parcel_in.Read<PixelFormat>();
+ const auto usage = parcel_in.Read<u32>();
+
+ s32 slot{};
+ Fence fence{};
+
+ status = DequeueBuffer(&slot, &fence, is_async, width, height, pixel_format, usage);
+
+ parcel_out.Write(slot);
+ parcel_out.WriteObject(&fence);
+ break;
+ }
+ case TransactionId::RequestBuffer: {
+ const auto slot = parcel_in.Read<s32>();
+
+ std::shared_ptr<GraphicBuffer> buf;
+
+ status = RequestBuffer(slot, &buf);
+
+ parcel_out.WriteObject(buf);
+ break;
+ }
+ case TransactionId::QueueBuffer: {
+ const auto slot = parcel_in.Read<s32>();
+
+ QueueBufferInput input{parcel_in};
+ QueueBufferOutput output;
+
+ status = QueueBuffer(slot, input, &output);
+
+ parcel_out.Write(output);
+ break;
+ }
+ case TransactionId::Query: {
+ const auto what = parcel_in.Read<NativeWindow>();
+
+ s32 value{};
+
+ status = Query(what, &value);
+
+ parcel_out.Write(value);
+ break;
+ }
+ case TransactionId::CancelBuffer: {
+ const auto slot = parcel_in.Read<s32>();
+ const auto fence = parcel_in.ReadFlattened<Fence>();
+
+ CancelBuffer(slot, fence);
+ break;
+ }
+ case TransactionId::Disconnect: {
+ const auto api = parcel_in.Read<NativeWindowApi>();
+
+ status = Disconnect(api);
+ break;
+ }
+ case TransactionId::DetachBuffer: {
+ const auto slot = parcel_in.Read<s32>();
+
+ status = DetachBuffer(slot);
+ break;
+ }
+ case TransactionId::SetBufferCount: {
+ const auto buffer_count = parcel_in.Read<s32>();
+
+ status = SetBufferCount(buffer_count);
+ break;
+ }
+ case TransactionId::GetBufferHistory:
+ LOG_WARNING(Service_NVFlinger, "(STUBBED) called, transaction=GetBufferHistory");
+ break;
+ default:
+ ASSERT_MSG(false, "Unimplemented TransactionId {}", code);
+ break;
+ }
+
+ parcel_out.Write(status);
+
+ ctx.WriteBuffer(parcel_out.Serialize());
+}
+
+Kernel::KReadableEvent& BufferQueueProducer::GetNativeHandle() {
+ return buffer_wait_event->GetReadableEvent();
+}
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/buffer_queue_producer.h b/src/core/hle/service/nvflinger/buffer_queue_producer.h
new file mode 100644
index 000000000..5ddeebe0c
--- /dev/null
+++ b/src/core/hle/service/nvflinger/buffer_queue_producer.h
@@ -0,0 +1,83 @@
+// 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/BufferQueueProducer.h
+
+#pragma once
+
+#include <condition_variable>
+#include <memory>
+#include <mutex>
+
+#include "common/common_funcs.h"
+#include "core/hle/service/nvdrv/nvdata.h"
+#include "core/hle/service/nvflinger/binder.h"
+#include "core/hle/service/nvflinger/buffer_queue_defs.h"
+#include "core/hle/service/nvflinger/buffer_slot.h"
+#include "core/hle/service/nvflinger/graphic_buffer_producer.h"
+#include "core/hle/service/nvflinger/pixel_format.h"
+#include "core/hle/service/nvflinger/status.h"
+#include "core/hle/service/nvflinger/window.h"
+
+namespace Kernel {
+class KernelCore;
+class KEvent;
+class KReadableEvent;
+class KWritableEvent;
+} // namespace Kernel
+
+namespace Service::KernelHelpers {
+class ServiceContext;
+} // namespace Service::KernelHelpers
+
+namespace Service::android {
+
+class BufferQueueCore;
+class IProducerListener;
+
+class BufferQueueProducer final : public IBinder {
+public:
+ explicit BufferQueueProducer(Service::KernelHelpers::ServiceContext& service_context_,
+ std::shared_ptr<BufferQueueCore> buffer_queue_core_);
+ ~BufferQueueProducer();
+
+ void Transact(Kernel::HLERequestContext& ctx, android::TransactionId code, u32 flags) override;
+
+ Kernel::KReadableEvent& GetNativeHandle() override;
+
+public:
+ Status RequestBuffer(s32 slot, std::shared_ptr<GraphicBuffer>* buf);
+ Status SetBufferCount(s32 buffer_count);
+ Status DequeueBuffer(s32* out_slot, android::Fence* out_fence, bool async, u32 width,
+ u32 height, PixelFormat format, u32 usage);
+ Status DetachBuffer(s32 slot);
+ Status DetachNextBuffer(std::shared_ptr<GraphicBuffer>* out_buffer, Fence* out_fence);
+ Status AttachBuffer(s32* outSlot, const std::shared_ptr<GraphicBuffer>& buffer);
+ Status QueueBuffer(s32 slot, const QueueBufferInput& input, QueueBufferOutput* output);
+ void CancelBuffer(s32 slot, const Fence& fence);
+ Status Query(NativeWindow what, s32* out_value);
+ Status Connect(const std::shared_ptr<IProducerListener>& listener, NativeWindowApi api,
+ bool producer_controlled_by_app, QueueBufferOutput* output);
+
+ Status Disconnect(NativeWindowApi api);
+ Status SetPreallocatedBuffer(s32 slot, const std::shared_ptr<GraphicBuffer>& buffer);
+
+private:
+ BufferQueueProducer(const BufferQueueProducer&) = delete;
+
+ Status WaitForFreeSlotThenRelock(bool async, s32* found, Status* returnFlags) const;
+
+ Kernel::KEvent* buffer_wait_event{};
+ Service::KernelHelpers::ServiceContext& service_context;
+
+ std::shared_ptr<BufferQueueCore> core;
+ BufferQueueDefs::SlotsType& slots;
+ u32 sticky_transform{};
+ std::mutex callback_mutex;
+ s32 next_callback_ticket{};
+ s32 current_callback_ticket{};
+ std::condition_variable callback_condition;
+};
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/buffer_slot.h b/src/core/hle/service/nvflinger/buffer_slot.h
new file mode 100644
index 000000000..e3ea58910
--- /dev/null
+++ b/src/core/hle/service/nvflinger/buffer_slot.h
@@ -0,0 +1,39 @@
+// 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/BufferSlot.h
+
+#pragma once
+
+#include <memory>
+
+#include "common/common_types.h"
+#include "core/hle/service/nvflinger/ui/fence.h"
+
+namespace Service::android {
+
+class GraphicBuffer;
+
+enum class BufferState : u32 {
+ Free = 0,
+ Dequeued = 1,
+ Queued = 2,
+ Acquired = 3,
+};
+
+struct BufferSlot final {
+ constexpr BufferSlot() = default;
+
+ std::shared_ptr<GraphicBuffer> graphic_buffer;
+ BufferState buffer_state{BufferState::Free};
+ bool request_buffer_called{};
+ u64 frame_number{};
+ Fence fence;
+ bool acquire_called{};
+ bool needs_cleanup_on_release{};
+ bool attached_by_consumer{};
+ bool is_preallocated{};
+};
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/buffer_transform_flags.h b/src/core/hle/service/nvflinger/buffer_transform_flags.h
new file mode 100644
index 000000000..e8e6300e3
--- /dev/null
+++ b/src/core/hle/service/nvflinger/buffer_transform_flags.h
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+
+#pragma once
+
+#include "common/common_types.h"
+
+namespace Service::android {
+
+enum class BufferTransformFlags : u32 {
+ /// No transform flags are set
+ Unset = 0x00,
+ /// Flip source image horizontally (around the vertical axis)
+ FlipH = 0x01,
+ /// Flip source image vertically (around the horizontal axis)
+ FlipV = 0x02,
+ /// Rotate source image 90 degrees clockwise
+ Rotate90 = 0x04,
+ /// Rotate source image 180 degrees
+ Rotate180 = 0x03,
+ /// Rotate source image 270 degrees clockwise
+ Rotate270 = 0x07,
+};
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/consumer_base.cpp b/src/core/hle/service/nvflinger/consumer_base.cpp
new file mode 100644
index 000000000..3ccbb7fb8
--- /dev/null
+++ b/src/core/hle/service/nvflinger/consumer_base.cpp
@@ -0,0 +1,130 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+// Copyright 2010 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/ConsumerBase.cpp
+
+#include "common/assert.h"
+#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/consumer_base.h"
+#include "core/hle/service/nvflinger/ui/graphic_buffer.h"
+
+namespace Service::android {
+
+ConsumerBase::ConsumerBase(std::unique_ptr<BufferQueueConsumer> consumer_)
+ : consumer{std::move(consumer_)} {}
+
+ConsumerBase::~ConsumerBase() {
+ std::unique_lock lock(mutex);
+
+ ASSERT_MSG(is_abandoned, "consumer is not abandoned!");
+}
+
+void ConsumerBase::Connect(bool controlled_by_app) {
+ consumer->Connect(shared_from_this(), controlled_by_app);
+}
+
+void ConsumerBase::FreeBufferLocked(s32 slot_index) {
+ LOG_DEBUG(Service_NVFlinger, "slot_index={}", slot_index);
+
+ slots[slot_index].graphic_buffer = nullptr;
+ slots[slot_index].fence = Fence::NoFence();
+ slots[slot_index].frame_number = 0;
+}
+
+void ConsumerBase::OnFrameAvailable(const BufferItem& item) {
+ std::unique_lock lock(mutex);
+ LOG_DEBUG(Service_NVFlinger, "called");
+}
+
+void ConsumerBase::OnFrameReplaced(const BufferItem& item) {
+ std::unique_lock lock(mutex);
+ LOG_DEBUG(Service_NVFlinger, "called");
+}
+
+void ConsumerBase::OnBuffersReleased() {
+ std::unique_lock lock(mutex);
+ LOG_DEBUG(Service_NVFlinger, "called");
+}
+
+void ConsumerBase::OnSidebandStreamChanged() {}
+
+Status ConsumerBase::AcquireBufferLocked(BufferItem* item, std::chrono::nanoseconds present_when,
+ u64 max_frame_number) {
+ if (is_abandoned) {
+ LOG_ERROR(Service_NVFlinger, "consumer is abandoned!");
+ return Status::NoInit;
+ }
+
+ Status err = consumer->AcquireBuffer(item, present_when, max_frame_number);
+ if (err != Status::NoError) {
+ return err;
+ }
+
+ if (item->graphic_buffer != nullptr) {
+ if (slots[item->slot].graphic_buffer != nullptr) {
+ FreeBufferLocked(item->slot);
+ }
+ slots[item->slot].graphic_buffer = item->graphic_buffer;
+ }
+
+ slots[item->slot].frame_number = item->frame_number;
+ slots[item->slot].fence = item->fence;
+
+ LOG_DEBUG(Service_NVFlinger, "slot={}", item->slot);
+
+ return Status::NoError;
+}
+
+Status ConsumerBase::AddReleaseFenceLocked(s32 slot,
+ const std::shared_ptr<GraphicBuffer> graphic_buffer,
+ const Fence& fence) {
+ LOG_DEBUG(Service_NVFlinger, "slot={}", slot);
+
+ // If consumer no longer tracks this graphic_buffer, we can safely
+ // drop this fence, as it will never be received by the producer.
+
+ if (!StillTracking(slot, graphic_buffer)) {
+ return Status::NoError;
+ }
+
+ slots[slot].fence = fence;
+
+ return Status::NoError;
+}
+
+Status ConsumerBase::ReleaseBufferLocked(s32 slot,
+ const std::shared_ptr<GraphicBuffer> graphic_buffer) {
+ // If consumer no longer tracks this graphic_buffer (we received a new
+ // buffer on the same slot), the buffer producer is definitely no longer
+ // tracking it.
+
+ if (!StillTracking(slot, graphic_buffer)) {
+ return Status::NoError;
+ }
+
+ LOG_DEBUG(Service_NVFlinger, "slot={}", slot);
+ Status err = consumer->ReleaseBuffer(slot, slots[slot].frame_number, slots[slot].fence);
+ if (err == Status::StaleBufferSlot) {
+ FreeBufferLocked(slot);
+ }
+
+ slots[slot].fence = Fence::NoFence();
+
+ return err;
+}
+
+bool ConsumerBase::StillTracking(s32 slot,
+ const std::shared_ptr<GraphicBuffer> graphic_buffer) const {
+ if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
+ return false;
+ }
+
+ return (slots[slot].graphic_buffer != nullptr &&
+ slots[slot].graphic_buffer->Handle() == graphic_buffer->Handle());
+}
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/consumer_base.h b/src/core/hle/service/nvflinger/consumer_base.h
new file mode 100644
index 000000000..9ab949420
--- /dev/null
+++ b/src/core/hle/service/nvflinger/consumer_base.h
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+// Copyright 2010 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/ConsumerBase.h
+
+#pragma once
+
+#include <array>
+#include <chrono>
+#include <memory>
+#include <mutex>
+
+#include "common/common_types.h"
+#include "core/hle/service/nvflinger/buffer_queue_defs.h"
+#include "core/hle/service/nvflinger/consumer_listener.h"
+#include "core/hle/service/nvflinger/status.h"
+
+namespace Service::android {
+
+class BufferItem;
+class BufferQueueConsumer;
+
+class ConsumerBase : public IConsumerListener, public std::enable_shared_from_this<ConsumerBase> {
+public:
+ void Connect(bool controlled_by_app);
+
+protected:
+ explicit ConsumerBase(std::unique_ptr<BufferQueueConsumer> consumer_);
+ virtual ~ConsumerBase();
+
+ virtual void OnFrameAvailable(const BufferItem& item) override;
+ virtual void OnFrameReplaced(const BufferItem& item) override;
+ virtual void OnBuffersReleased() override;
+ virtual void OnSidebandStreamChanged() override;
+
+ void FreeBufferLocked(s32 slot_index);
+ Status AcquireBufferLocked(BufferItem* item, std::chrono::nanoseconds present_when,
+ u64 max_frame_number = 0);
+ Status ReleaseBufferLocked(s32 slot, const std::shared_ptr<GraphicBuffer> graphic_buffer);
+ bool StillTracking(s32 slot, const std::shared_ptr<GraphicBuffer> graphic_buffer) const;
+ Status AddReleaseFenceLocked(s32 slot, const std::shared_ptr<GraphicBuffer> graphic_buffer,
+ const Fence& fence);
+
+ struct Slot final {
+ std::shared_ptr<GraphicBuffer> graphic_buffer;
+ Fence fence;
+ u64 frame_number{};
+ };
+
+protected:
+ std::array<Slot, BufferQueueDefs::NUM_BUFFER_SLOTS> slots;
+
+ bool is_abandoned{};
+
+ std::unique_ptr<BufferQueueConsumer> consumer;
+
+ mutable std::mutex mutex;
+};
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/consumer_listener.h b/src/core/hle/service/nvflinger/consumer_listener.h
new file mode 100644
index 000000000..b6d1c3e9a
--- /dev/null
+++ b/src/core/hle/service/nvflinger/consumer_listener.h
@@ -0,0 +1,26 @@
+// 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/IConsumerListener.h
+
+#pragma once
+
+namespace Service::android {
+
+class BufferItem;
+
+/// ConsumerListener is the interface through which the BufferQueue notifies the consumer of events
+/// that the consumer may wish to react to.
+class IConsumerListener {
+public:
+ IConsumerListener() = default;
+ virtual ~IConsumerListener() = default;
+
+ virtual void OnFrameAvailable(const BufferItem& item) = 0;
+ virtual void OnFrameReplaced(const BufferItem& item) = 0;
+ virtual void OnBuffersReleased() = 0;
+ virtual void OnSidebandStreamChanged() = 0;
+};
+
+}; // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/graphic_buffer_producer.cpp b/src/core/hle/service/nvflinger/graphic_buffer_producer.cpp
new file mode 100644
index 000000000..d4da98ddb
--- /dev/null
+++ b/src/core/hle/service/nvflinger/graphic_buffer_producer.cpp
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+// Copyright 2010 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/IGraphicBufferProducer.cpp
+
+#pragma once
+
+#include "core/hle/service/nvflinger/graphic_buffer_producer.h"
+#include "core/hle/service/nvflinger/parcel.h"
+
+namespace Service::android {
+
+QueueBufferInput::QueueBufferInput(Parcel& parcel) {
+ parcel.ReadFlattened(*this);
+}
+
+QueueBufferOutput::QueueBufferOutput() = default;
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/graphic_buffer_producer.h b/src/core/hle/service/nvflinger/graphic_buffer_producer.h
new file mode 100644
index 000000000..98d27871c
--- /dev/null
+++ b/src/core/hle/service/nvflinger/graphic_buffer_producer.h
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+// Copyright 2010 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/IGraphicBufferProducer.h
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/math_util.h"
+#include "core/hle/service/nvflinger/ui/fence.h"
+#include "core/hle/service/nvflinger/window.h"
+
+namespace Service::android {
+
+class Parcel;
+
+#pragma pack(push, 1)
+struct QueueBufferInput final {
+ explicit QueueBufferInput(Parcel& parcel);
+
+ void Deflate(s64* timestamp_, bool* is_auto_timestamp_, Common::Rectangle<s32>* crop_,
+ NativeWindowScalingMode* scaling_mode_, NativeWindowTransform* transform_,
+ u32* sticky_transform_, bool* async_, s32* swap_interval_, Fence* fence_) const {
+ *timestamp_ = timestamp;
+ *is_auto_timestamp_ = static_cast<bool>(is_auto_timestamp);
+ *crop_ = crop;
+ *scaling_mode_ = scaling_mode;
+ *transform_ = transform;
+ *sticky_transform_ = sticky_transform;
+ *async_ = static_cast<bool>(async);
+ *swap_interval_ = swap_interval;
+ *fence_ = fence;
+ }
+
+private:
+ s64 timestamp{};
+ s32 is_auto_timestamp{};
+ Common::Rectangle<s32> crop{};
+ NativeWindowScalingMode scaling_mode{};
+ NativeWindowTransform transform{};
+ u32 sticky_transform{};
+ s32 async{};
+ s32 swap_interval{};
+ Fence fence{};
+};
+#pragma pack(pop)
+static_assert(sizeof(QueueBufferInput) == 84, "QueueBufferInput has wrong size");
+
+struct QueueBufferOutput final {
+ QueueBufferOutput();
+
+ void Deflate(u32* width_, u32* height_, u32* transform_hint_, u32* num_pending_buffers_) const {
+ *width_ = width;
+ *height_ = height;
+ *transform_hint_ = transform_hint;
+ *num_pending_buffers_ = num_pending_buffers;
+ }
+
+ void Inflate(u32 width_, u32 height_, u32 transform_hint_, u32 num_pending_buffers_) {
+ width = width_;
+ height = height_;
+ transform_hint = transform_hint_;
+ num_pending_buffers = num_pending_buffers_;
+ }
+
+private:
+ u32 width{};
+ u32 height{};
+ u32 transform_hint{};
+ u32 num_pending_buffers{};
+};
+static_assert(sizeof(QueueBufferOutput) == 16, "QueueBufferOutput has wrong size");
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/hos_binder_driver_server.cpp b/src/core/hle/service/nvflinger/hos_binder_driver_server.cpp
new file mode 100644
index 000000000..0c937d682
--- /dev/null
+++ b/src/core/hle/service/nvflinger/hos_binder_driver_server.cpp
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+
+#include <mutex>
+
+#include "common/common_types.h"
+#include "core/hle/service/nvflinger/hos_binder_driver_server.h"
+
+namespace Service::NVFlinger {
+
+HosBinderDriverServer::HosBinderDriverServer(Core::System& system_)
+ : service_context(system_, "HosBinderDriverServer") {}
+
+HosBinderDriverServer::~HosBinderDriverServer() {}
+
+u64 HosBinderDriverServer::RegisterProducer(std::unique_ptr<android::IBinder>&& binder) {
+ std::lock_guard lk{lock};
+
+ last_id++;
+
+ producers[last_id] = std::move(binder);
+
+ return last_id;
+}
+
+android::IBinder* HosBinderDriverServer::TryGetProducer(u64 id) {
+ std::lock_guard lk{lock};
+
+ if (auto search = producers.find(id); search != producers.end()) {
+ return search->second.get();
+ }
+
+ return {};
+}
+
+} // namespace Service::NVFlinger
diff --git a/src/core/hle/service/nvflinger/hos_binder_driver_server.h b/src/core/hle/service/nvflinger/hos_binder_driver_server.h
new file mode 100644
index 000000000..cbca87fa0
--- /dev/null
+++ b/src/core/hle/service/nvflinger/hos_binder_driver_server.h
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+
+#pragma once
+
+#include <memory>
+#include <mutex>
+#include <unordered_map>
+
+#include "common/common_types.h"
+#include "core/hle/service/kernel_helpers.h"
+#include "core/hle/service/nvflinger/binder.h"
+
+namespace Core {
+class System;
+}
+
+namespace Service::NVFlinger {
+
+class HosBinderDriverServer final {
+public:
+ explicit HosBinderDriverServer(Core::System& system_);
+ ~HosBinderDriverServer();
+
+ u64 RegisterProducer(std::unique_ptr<android::IBinder>&& binder);
+
+ android::IBinder* TryGetProducer(u64 id);
+
+private:
+ KernelHelpers::ServiceContext service_context;
+
+ std::unordered_map<u64, std::unique_ptr<android::IBinder>> producers;
+ std::mutex lock;
+ u64 last_id{};
+};
+
+} // namespace Service::NVFlinger
diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp
index 01e69de30..76ce1fbfd 100644
--- a/src/core/hle/service/nvflinger/nvflinger.cpp
+++ b/src/core/hle/service/nvflinger/nvflinger.cpp
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
#include <algorithm>
#include <optional>
@@ -16,8 +15,11 @@
#include "core/hle/kernel/k_readable_event.h"
#include "core/hle/service/nvdrv/devices/nvdisp_disp0.h"
#include "core/hle/service/nvdrv/nvdrv.h"
-#include "core/hle/service/nvflinger/buffer_queue.h"
+#include "core/hle/service/nvflinger/buffer_item_consumer.h"
+#include "core/hle/service/nvflinger/buffer_queue_core.h"
+#include "core/hle/service/nvflinger/hos_binder_driver_server.h"
#include "core/hle/service/nvflinger/nvflinger.h"
+#include "core/hle/service/nvflinger/ui/graphic_buffer.h"
#include "core/hle/service/vi/display/vi_display.h"
#include "core/hle/service/vi/layer/vi_layer.h"
#include "video_core/gpu.h"
@@ -53,13 +55,14 @@ void NVFlinger::SplitVSync(std::stop_token stop_token) {
}
}
-NVFlinger::NVFlinger(Core::System& system_)
- : system(system_), service_context(system_, "nvflinger") {
- displays.emplace_back(0, "Default", service_context, system);
- displays.emplace_back(1, "External", service_context, system);
- displays.emplace_back(2, "Edid", service_context, system);
- displays.emplace_back(3, "Internal", service_context, system);
- displays.emplace_back(4, "Null", service_context, system);
+NVFlinger::NVFlinger(Core::System& system_, HosBinderDriverServer& hos_binder_driver_server_)
+ : system(system_), service_context(system_, "nvflinger"),
+ hos_binder_driver_server(hos_binder_driver_server_) {
+ displays.emplace_back(0, "Default", hos_binder_driver_server, service_context, system);
+ displays.emplace_back(1, "External", hos_binder_driver_server, service_context, system);
+ displays.emplace_back(2, "Edid", hos_binder_driver_server, service_context, system);
+ displays.emplace_back(3, "Internal", hos_binder_driver_server, service_context, system);
+ displays.emplace_back(4, "Null", hos_binder_driver_server, service_context, system);
guard = std::make_shared<std::mutex>();
// Schedule the screen composition events
@@ -83,12 +86,15 @@ NVFlinger::NVFlinger(Core::System& system_)
}
NVFlinger::~NVFlinger() {
- for (auto& buffer_queue : buffer_queues) {
- buffer_queue->Disconnect();
- }
if (!system.IsMulticore()) {
system.CoreTiming().UnscheduleEvent(composition_event, 0);
}
+
+ for (auto& display : displays) {
+ for (size_t layer = 0; layer < display.GetNumLayers(); ++layer) {
+ display.GetLayer(layer).Core().NotifyShutdown();
+ }
+ }
}
void NVFlinger::SetNVDrvInstance(std::shared_ptr<Nvidia::Module> instance) {
@@ -125,10 +131,8 @@ std::optional<u64> NVFlinger::CreateLayer(u64 display_id) {
}
void NVFlinger::CreateLayerAtId(VI::Display& display, u64 layer_id) {
- const u32 buffer_queue_id = next_buffer_queue_id++;
- buffer_queues.emplace_back(
- std::make_unique<BufferQueue>(system.Kernel(), buffer_queue_id, layer_id, service_context));
- display.CreateLayer(layer_id, *buffer_queues.back());
+ const auto buffer_id = next_buffer_queue_id++;
+ display.CreateLayer(layer_id, buffer_id);
}
void NVFlinger::CloseLayer(u64 layer_id) {
@@ -147,7 +151,7 @@ std::optional<u32> NVFlinger::FindBufferQueueId(u64 display_id, u64 layer_id) {
return std::nullopt;
}
- return layer->GetBufferQueue().GetId();
+ return layer->GetBinderId();
}
Kernel::KReadableEvent* NVFlinger::FindVsyncEvent(u64 display_id) {
@@ -161,18 +165,6 @@ Kernel::KReadableEvent* NVFlinger::FindVsyncEvent(u64 display_id) {
return &display->GetVSyncEvent();
}
-BufferQueue* NVFlinger::FindBufferQueue(u32 id) {
- const auto lock_guard = Lock();
- const auto itr = std::find_if(buffer_queues.begin(), buffer_queues.end(),
- [id](const auto& queue) { return queue->GetId() == id; });
-
- if (itr == buffer_queues.end()) {
- return nullptr;
- }
-
- return itr->get();
-}
-
VI::Display* NVFlinger::FindDisplay(u64 display_id) {
const auto itr =
std::find_if(displays.begin(), displays.end(),
@@ -246,23 +238,22 @@ void NVFlinger::Compose() {
// TODO(Subv): Support more than 1 layer.
VI::Layer& layer = display.GetLayer(0);
- auto& buffer_queue = layer.GetBufferQueue();
- // Search for a queued buffer and acquire it
- auto buffer = buffer_queue.AcquireBuffer();
+ android::BufferItem buffer{};
+ const auto status = layer.GetConsumer().AcquireBuffer(&buffer, {}, false);
- if (!buffer) {
+ if (status != android::Status::NoError) {
continue;
}
- const auto& igbp_buffer = buffer->get().igbp_buffer;
+ const auto& igbp_buffer = *buffer.graphic_buffer;
if (!system.IsPoweredOn()) {
return; // We are likely shutting down
}
auto& gpu = system.GPU();
- const auto& multi_fence = buffer->get().multi_fence;
+ const auto& multi_fence = buffer.fence;
guard->unlock();
for (u32 fence_id = 0; fence_id < multi_fence.num_fences; fence_id++) {
const auto& fence = multi_fence.fences[fence_id];
@@ -278,12 +269,18 @@ void NVFlinger::Compose() {
auto nvdisp = nvdrv->GetDevice<Nvidia::Devices::nvdisp_disp0>("/dev/nvdisp_disp0");
ASSERT(nvdisp);
- nvdisp->flip(igbp_buffer.gpu_buffer_id, igbp_buffer.offset, igbp_buffer.external_format,
- igbp_buffer.width, igbp_buffer.height, igbp_buffer.stride,
- buffer->get().transform, buffer->get().crop_rect);
+ Common::Rectangle<int> crop_rect{
+ static_cast<int>(buffer.crop.Left()), static_cast<int>(buffer.crop.Top()),
+ static_cast<int>(buffer.crop.Right()), static_cast<int>(buffer.crop.Bottom())};
+
+ nvdisp->flip(igbp_buffer.BufferId(), igbp_buffer.Offset(), igbp_buffer.ExternalFormat(),
+ igbp_buffer.Width(), igbp_buffer.Height(), igbp_buffer.Stride(),
+ static_cast<android::BufferTransformFlags>(buffer.transform), crop_rect);
+
+ swap_interval = buffer.swap_interval;
- swap_interval = buffer->get().swap_interval;
- buffer_queue.ReleaseBuffer(buffer->get().slot);
+ auto fence = android::Fence::NoFence();
+ layer.GetConsumer().ReleaseBuffer(buffer, fence);
}
}
diff --git a/src/core/hle/service/nvflinger/nvflinger.h b/src/core/hle/service/nvflinger/nvflinger.h
index 7935cf773..ed160f6f9 100644
--- a/src/core/hle/service/nvflinger/nvflinger.h
+++ b/src/core/hle/service/nvflinger/nvflinger.h
@@ -1,6 +1,5 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
#pragma once
@@ -37,13 +36,16 @@ class Display;
class Layer;
} // namespace Service::VI
-namespace Service::NVFlinger {
+namespace Service::android {
+class BufferQueueCore;
+class BufferQueueProducer;
+} // namespace Service::android
-class BufferQueue;
+namespace Service::NVFlinger {
class NVFlinger final {
public:
- explicit NVFlinger(Core::System& system_);
+ explicit NVFlinger(Core::System& system_, HosBinderDriverServer& hos_binder_driver_server_);
~NVFlinger();
/// Sets the NVDrv module instance to use to send buffers to the GPU.
@@ -72,9 +74,6 @@ public:
/// If an invalid display ID is provided, then nullptr is returned.
[[nodiscard]] Kernel::KReadableEvent* FindVsyncEvent(u64 display_id);
- /// Obtains a buffer queue identified by the ID.
- [[nodiscard]] BufferQueue* FindBufferQueue(u32 id);
-
/// Performs a composition request to the emulated nvidia GPU and triggers the vsync events when
/// finished.
void Compose();
@@ -82,6 +81,12 @@ public:
[[nodiscard]] s64 GetNextTicks() const;
private:
+ struct Layer {
+ std::unique_ptr<android::BufferQueueCore> core;
+ std::unique_ptr<android::BufferQueueProducer> producer;
+ };
+
+private:
[[nodiscard]] std::unique_lock<std::mutex> Lock() const {
return std::unique_lock{*guard};
}
@@ -111,7 +116,6 @@ private:
std::shared_ptr<Nvidia::Module> nvdrv;
std::list<VI::Display> displays;
- std::vector<std::unique_ptr<BufferQueue>> buffer_queues;
/// Id to use for the next layer that is created, this counter is shared among all displays.
u64 next_layer_id = 1;
@@ -131,6 +135,8 @@ private:
std::jthread vsync_thread;
KernelHelpers::ServiceContext service_context;
+
+ HosBinderDriverServer& hos_binder_driver_server;
};
} // namespace Service::NVFlinger
diff --git a/src/core/hle/service/nvflinger/parcel.h b/src/core/hle/service/nvflinger/parcel.h
new file mode 100644
index 000000000..aa36e6479
--- /dev/null
+++ b/src/core/hle/service/nvflinger/parcel.h
@@ -0,0 +1,172 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+
+#pragma once
+
+#include <memory>
+#include <vector>
+
+#include "common/alignment.h"
+#include "common/assert.h"
+#include "common/common_types.h"
+
+namespace Service::android {
+
+class Parcel final {
+public:
+ static constexpr std::size_t DefaultBufferSize = 0x40;
+
+ Parcel() : buffer(DefaultBufferSize) {}
+
+ template <typename T>
+ explicit Parcel(const T& out_data) : buffer(DefaultBufferSize) {
+ Write(out_data);
+ }
+
+ explicit Parcel(std::vector<u8> in_data) : buffer(std::move(in_data)) {
+ DeserializeHeader();
+ [[maybe_unused]] const std::u16string token = ReadInterfaceToken();
+ }
+
+ template <typename T>
+ void Read(T& val) {
+ static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable.");
+ ASSERT(read_index + sizeof(T) <= buffer.size());
+
+ std::memcpy(&val, buffer.data() + read_index, sizeof(T));
+ read_index += sizeof(T);
+ read_index = Common::AlignUp(read_index, 4);
+ }
+
+ template <typename T>
+ T Read() {
+ T val;
+ Read(val);
+ return val;
+ }
+
+ template <typename T>
+ void ReadFlattened(T& val) {
+ const auto flattened_size = Read<s64>();
+ ASSERT(sizeof(T) == flattened_size);
+ Read(val);
+ }
+
+ template <typename T>
+ T ReadFlattened() {
+ T val;
+ ReadFlattened(val);
+ return val;
+ }
+
+ template <typename T>
+ T ReadUnaligned() {
+ static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable.");
+ ASSERT(read_index + sizeof(T) <= buffer.size());
+
+ T val;
+ std::memcpy(&val, buffer.data() + read_index, sizeof(T));
+ read_index += sizeof(T);
+ return val;
+ }
+
+ template <typename T>
+ const std::shared_ptr<T> ReadObject() {
+ static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable.");
+
+ const auto is_valid{Read<bool>()};
+
+ if (is_valid) {
+ auto result = std::make_shared<T>();
+ ReadFlattened(*result);
+ return result;
+ }
+
+ return {};
+ }
+
+ std::u16string ReadInterfaceToken() {
+ [[maybe_unused]] const u32 unknown = Read<u32>();
+ const u32 length = Read<u32>();
+
+ std::u16string token;
+ token.reserve(length + 1);
+
+ for (u32 ch = 0; ch < length + 1; ++ch) {
+ token.push_back(ReadUnaligned<u16>());
+ }
+
+ read_index = Common::AlignUp(read_index, 4);
+
+ return token;
+ }
+
+ template <typename T>
+ void Write(const T& val) {
+ static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable.");
+
+ if (buffer.size() < write_index + sizeof(T)) {
+ buffer.resize(buffer.size() + sizeof(T) + DefaultBufferSize);
+ }
+
+ std::memcpy(buffer.data() + write_index, &val, sizeof(T));
+ write_index += sizeof(T);
+ write_index = Common::AlignUp(write_index, 4);
+ }
+
+ template <typename T>
+ void WriteObject(const T* ptr) {
+ static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable.");
+
+ if (!ptr) {
+ Write<u32>(0);
+ return;
+ }
+
+ Write<u32>(1);
+ Write<s64>(sizeof(T));
+ Write(*ptr);
+ }
+
+ template <typename T>
+ void WriteObject(const std::shared_ptr<T> ptr) {
+ WriteObject(ptr.get());
+ }
+
+ void DeserializeHeader() {
+ ASSERT(buffer.size() > sizeof(Header));
+
+ Header header{};
+ std::memcpy(&header, buffer.data(), sizeof(Header));
+
+ read_index = header.data_offset;
+ }
+
+ std::vector<u8> Serialize() const {
+ ASSERT(read_index == 0);
+
+ Header header{};
+ header.data_size = static_cast<u32>(write_index - sizeof(Header));
+ header.data_offset = sizeof(Header);
+ header.objects_size = 4;
+ header.objects_offset = static_cast<u32>(sizeof(Header) + header.data_size);
+ std::memcpy(buffer.data(), &header, sizeof(Header));
+
+ return buffer;
+ }
+
+private:
+ struct Header {
+ u32 data_size;
+ u32 data_offset;
+ u32 objects_size;
+ u32 objects_offset;
+ };
+ static_assert(sizeof(Header) == 16, "ParcelHeader has wrong size");
+
+ mutable std::vector<u8> buffer;
+ std::size_t read_index = 0;
+ std::size_t write_index = sizeof(Header);
+};
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/pixel_format.h b/src/core/hle/service/nvflinger/pixel_format.h
new file mode 100644
index 000000000..8a77d8bea
--- /dev/null
+++ b/src/core/hle/service/nvflinger/pixel_format.h
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+
+#pragma once
+
+#include "common/common_types.h"
+
+namespace Service::android {
+
+enum class PixelFormat : u32 {
+ NoFormat = 0,
+ Rgba8888 = 1,
+ Rgbx8888 = 2,
+ Rgb888 = 3,
+ Rgb565 = 4,
+ Bgra8888 = 5,
+ Rgba5551 = 6,
+ Rgba4444 = 7,
+};
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/producer_listener.h b/src/core/hle/service/nvflinger/producer_listener.h
new file mode 100644
index 000000000..468e06431
--- /dev/null
+++ b/src/core/hle/service/nvflinger/producer_listener.h
@@ -0,0 +1,16 @@
+// 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/IProducerListener.h
+
+#pragma once
+
+namespace Service::android {
+
+class IProducerListener {
+public:
+ virtual void OnBufferReleased() = 0;
+};
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/status.h b/src/core/hle/service/nvflinger/status.h
new file mode 100644
index 000000000..a003eda89
--- /dev/null
+++ b/src/core/hle/service/nvflinger/status.h
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+
+namespace Service::android {
+
+enum class Status : s32 {
+ None = 0,
+ NoError = 0,
+ StaleBufferSlot = 1,
+ NoBufferAvailable = 2,
+ PresentLater = 3,
+ WouldBlock = -11,
+ NoMemory = -12,
+ Busy = -16,
+ NoInit = -19,
+ BadValue = -22,
+ InvalidOperation = -37,
+ BufferNeedsReallocation = 1,
+ ReleaseAllBuffers = 2,
+};
+DECLARE_ENUM_FLAG_OPERATORS(Status);
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/ui/fence.h b/src/core/hle/service/nvflinger/ui/fence.h
new file mode 100644
index 000000000..4a74e26a3
--- /dev/null
+++ b/src/core/hle/service/nvflinger/ui/fence.h
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+// Copyright 2012 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/ui/Fence.h
+
+#pragma once
+
+#include <array>
+
+#include "common/common_types.h"
+#include "core/hle/service/nvdrv/nvdata.h"
+
+namespace Service::android {
+
+class Fence {
+public:
+ constexpr Fence() = default;
+
+ static constexpr Fence NoFence() {
+ Fence fence;
+ fence.fences[0].id = -1;
+ return fence;
+ }
+
+public:
+ u32 num_fences{};
+ std::array<Service::Nvidia::NvFence, 4> fences{};
+};
+static_assert(sizeof(Fence) == 36, "Fence has wrong size");
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/ui/graphic_buffer.h b/src/core/hle/service/nvflinger/ui/graphic_buffer.h
new file mode 100644
index 000000000..7abbf78ba
--- /dev/null
+++ b/src/core/hle/service/nvflinger/ui/graphic_buffer.h
@@ -0,0 +1,100 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+// Copyright 2007 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/ui/GraphicBuffer.h
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "core/hle/service/nvflinger/pixel_format.h"
+
+namespace Service::android {
+
+class GraphicBuffer final {
+public:
+ constexpr GraphicBuffer() = default;
+
+ constexpr GraphicBuffer(u32 width_, u32 height_, PixelFormat format_, u32 usage_)
+ : width{static_cast<s32>(width_)}, height{static_cast<s32>(height_)}, format{format_},
+ usage{static_cast<s32>(usage_)} {}
+
+ constexpr u32 Width() const {
+ return static_cast<u32>(width);
+ }
+
+ constexpr u32 Height() const {
+ return static_cast<u32>(height);
+ }
+
+ constexpr u32 Stride() const {
+ return static_cast<u32>(stride);
+ }
+
+ constexpr u32 Usage() const {
+ return static_cast<u32>(usage);
+ }
+
+ constexpr PixelFormat Format() const {
+ return format;
+ }
+
+ constexpr u32 BufferId() const {
+ return buffer_id;
+ }
+
+ constexpr PixelFormat ExternalFormat() const {
+ return external_format;
+ }
+
+ constexpr u32 Handle() const {
+ return handle;
+ }
+
+ constexpr u32 Offset() const {
+ return offset;
+ }
+
+ constexpr bool NeedsReallocation(u32 width_, u32 height_, PixelFormat format_,
+ u32 usage_) const {
+ if (static_cast<s32>(width_) != width) {
+ return true;
+ }
+
+ if (static_cast<s32>(height_) != height) {
+ return true;
+ }
+
+ if (format_ != format) {
+ return true;
+ }
+
+ if ((static_cast<u32>(usage) & usage_) != usage_) {
+ return true;
+ }
+
+ return false;
+ }
+
+private:
+ u32 magic{};
+ s32 width{};
+ s32 height{};
+ s32 stride{};
+ PixelFormat format{};
+ s32 usage{};
+ INSERT_PADDING_WORDS(1);
+ u32 index{};
+ INSERT_PADDING_WORDS(3);
+ u32 buffer_id{};
+ INSERT_PADDING_WORDS(6);
+ PixelFormat external_format{};
+ INSERT_PADDING_WORDS(10);
+ u32 handle{};
+ u32 offset{};
+ INSERT_PADDING_WORDS(60);
+};
+static_assert(sizeof(GraphicBuffer) == 0x16C, "GraphicBuffer has wrong size");
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/window.h b/src/core/hle/service/nvflinger/window.h
new file mode 100644
index 000000000..e26f8160e
--- /dev/null
+++ b/src/core/hle/service/nvflinger/window.h
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Copyright 2021 yuzu Emulator Project
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+
+namespace Service::android {
+
+/// Attributes queryable with Query
+enum class NativeWindow : s32 {
+ Width = 0,
+ Height = 1,
+ Format = 2,
+ MinUndequeedBuffers = 3,
+ QueuesToWindowComposer = 4,
+ ConcreteType = 5,
+ DefaultWidth = 6,
+ DefaultHeight = 7,
+ TransformHint = 8,
+ ConsumerRunningBehind = 9,
+ ConsumerUsageBits = 10,
+ StickyTransform = 11,
+ DefaultDataSpace = 12,
+ BufferAge = 13,
+};
+
+/// Parameter for Connect/Disconnect
+enum class NativeWindowApi : s32 {
+ NoConnectedApi = 0,
+ Egl = 1,
+ Cpu = 2,
+ Media = 3,
+ Camera = 4,
+};
+
+/// Scaling mode parameter for QueueBuffer
+enum class NativeWindowScalingMode : s32 {
+ Freeze = 0,
+ ScaleToWindow = 1,
+ ScaleCrop = 2,
+ NoScaleCrop = 3,
+};
+
+/// Transform parameter for QueueBuffer
+enum class NativeWindowTransform : u32 {
+ None = 0x0,
+ InverseDisplay = 0x08,
+};
+DECLARE_ENUM_FLAG_OPERATORS(NativeWindowTransform);
+
+} // namespace Service::android