nvnflinger: fix name

This commit is contained in:
Liam
2023-02-19 15:05:34 -05:00
parent 65be230fdd
commit 809148e1a5
54 changed files with 443 additions and 444 deletions

View File

@@ -0,0 +1,45 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Parts of this implementation were based 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 KReadableEvent;
} // namespace Kernel
namespace Service {
class HLERequestContext;
}
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 ~IBinder() = default;
virtual void Transact(HLERequestContext& ctx, android::TransactionId code, u32 flags) = 0;
virtual Kernel::KReadableEvent& GetNativeHandle() = 0;
};
} // namespace Service::android

View File

@@ -0,0 +1,46 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Parts of this implementation were based 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/nvnflinger/ui/fence.h"
#include "core/hle/service/nvnflinger/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

View File

@@ -0,0 +1,59 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2012 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Parts of this implementation were based 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/nvnflinger/buffer_item.h"
#include "core/hle/service/nvnflinger/buffer_item_consumer.h"
#include "core/hle/service/nvnflinger/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::scoped_lock lock{mutex};
if (const auto status = AcquireBufferLocked(item, present_when); status != Status::NoError) {
if (status != Status::NoBufferAvailable) {
LOG_ERROR(Service_Nvnflinger, "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, const Fence& release_fence) {
std::scoped_lock lock{mutex};
if (const auto status = AddReleaseFenceLocked(item.buf, item.graphic_buffer, release_fence);
status != Status::NoError) {
LOG_ERROR(Service_Nvnflinger, "Failed to add fence: {}", status);
}
if (const auto status = ReleaseBufferLocked(item.buf, item.graphic_buffer);
status != Status::NoError) {
LOG_WARNING(Service_Nvnflinger, "Failed to release buffer: {}", status);
return status;
}
return Status::NoError;
}
} // namespace Service::android

View File

@@ -0,0 +1,28 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2012 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Parts of this implementation were based 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/nvnflinger/consumer_base.h"
#include "core/hle/service/nvnflinger/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, const Fence& release_fence);
};
} // namespace Service::android

View File

@@ -0,0 +1,213 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Parts of this implementation were based 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/nvdrv/core/nvmap.h"
#include "core/hle/service/nvnflinger/buffer_item.h"
#include "core/hle/service/nvnflinger/buffer_queue_consumer.h"
#include "core/hle/service/nvnflinger/buffer_queue_core.h"
#include "core/hle/service/nvnflinger/producer_listener.h"
#include "core/hle/service/nvnflinger/ui/graphic_buffer.h"
namespace Service::android {
BufferQueueConsumer::BufferQueueConsumer(std::shared_ptr<BufferQueueCore> core_,
Service::Nvidia::NvCore::NvMap& nvmap_)
: core{std::move(core_)}, slots{core->slots}, nvmap(nvmap_) {}
BufferQueueConsumer::~BufferQueueConsumer() = default;
Status BufferQueueConsumer::AcquireBuffer(BufferItem* out_buffer,
std::chrono::nanoseconds expected_present) {
std::scoped_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_Nvnflinger, "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 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_Nvnflinger, "nodrop desire={} expect={}", desired_present,
expected_present.count());
break;
}
LOG_DEBUG(Service_Nvnflinger, "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->queue.erase(front);
front = core->queue.begin();
}
// See if the front buffer is ready to be acquired.
const auto desired_present = front->timestamp;
if (desired_present > expected_present.count() &&
desired_present < expected_present.count() + MAX_REASONABLE_NSEC) {
LOG_DEBUG(Service_Nvnflinger, "defer desire={} expect={}", desired_present,
expected_present.count());
return Status::PresentLater;
}
LOG_DEBUG(Service_Nvnflinger, "accept desire={} expect={}", desired_present,
expected_present.count());
}
const auto slot = front->slot;
*out_buffer = *front;
LOG_DEBUG(Service_Nvnflinger, "acquiring slot={}", slot);
// 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();
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_Nvnflinger, "slot {} out of range", slot);
return Status::BadValue;
}
std::shared_ptr<IProducerListener> listener;
{
std::scoped_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_Nvnflinger, "buffer slot {} pending release is currently queued",
slot);
return Status::BadValue;
}
++current;
}
slots[slot].buffer_state = BufferState::Free;
nvmap.FreeHandle(slots[slot].graphic_buffer->BufferId(), true);
listener = core->connected_producer_listener;
LOG_DEBUG(Service_Nvnflinger, "releasing slot {}", slot);
core->SignalDequeueCondition();
}
// 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_Nvnflinger, "consumer_listener may not be nullptr");
return Status::BadValue;
}
LOG_DEBUG(Service_Nvnflinger, "controlled_by_app={}", controlled_by_app);
std::scoped_lock lock{core->mutex};
if (core->is_abandoned) {
LOG_ERROR(Service_Nvnflinger, "BufferQueue has been abandoned");
return Status::NoInit;
}
core->consumer_listener = std::move(consumer_listener);
core->consumer_controlled_by_app = controlled_by_app;
return Status::NoError;
}
Status BufferQueueConsumer::GetReleasedBuffers(u64* out_slot_mask) {
if (out_slot_mask == nullptr) {
LOG_ERROR(Service_Nvnflinger, "out_slot_mask may not be nullptr");
return Status::BadValue;
}
std::scoped_lock lock{core->mutex};
if (core->is_abandoned) {
LOG_ERROR(Service_Nvnflinger, "BufferQueue has been abandoned");
return Status::NoInit;
}
u64 mask = 0;
for (int s = 0; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
if (!slots[s].acquire_called) {
mask |= (1ULL << s);
}
}
// Remove from the mask queued buffers for which acquire has been called, since the consumer
// will not receive their buffer addresses and so must retain their cached information
auto current(core->queue.begin());
while (current != core->queue.end()) {
if (current->acquire_called) {
mask &= ~(1ULL << current->slot);
}
++current;
}
LOG_DEBUG(Service_Nvnflinger, "returning mask {}", mask);
*out_slot_mask = mask;
return Status::NoError;
}
} // namespace Service::android

View File

@@ -0,0 +1,43 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Parts of this implementation were based 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/nvnflinger/buffer_queue_defs.h"
#include "core/hle/service/nvnflinger/status.h"
namespace Service::Nvidia::NvCore {
class NvMap;
} // namespace Service::Nvidia::NvCore
namespace Service::android {
class BufferItem;
class BufferQueueCore;
class IConsumerListener;
class BufferQueueConsumer final {
public:
explicit BufferQueueConsumer(std::shared_ptr<BufferQueueCore> core_,
Service::Nvidia::NvCore::NvMap& nvmap_);
~BufferQueueConsumer();
Status AcquireBuffer(BufferItem* out_buffer, std::chrono::nanoseconds expected_present);
Status ReleaseBuffer(s32 slot, u64 frame_number, const Fence& release_fence);
Status Connect(std::shared_ptr<IConsumerListener> consumer_listener, bool controlled_by_app);
Status GetReleasedBuffers(u64* out_slot_mask);
private:
std::shared_ptr<BufferQueueCore> core;
BufferQueueDefs::SlotsType& slots;
Service::Nvidia::NvCore::NvMap& nvmap;
};
} // namespace Service::android

View File

@@ -0,0 +1,115 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Parts of this implementation were based 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/nvnflinger/buffer_queue_core.h"
namespace Service::android {
BufferQueueCore::BufferQueueCore() = default;
BufferQueueCore::~BufferQueueCore() = default;
void BufferQueueCore::NotifyShutdown() {
std::scoped_lock lock{mutex};
is_shutting_down = true;
SignalDequeueCondition();
}
void BufferQueueCore::SignalDequeueCondition() {
dequeue_possible.store(true);
dequeue_condition.notify_all();
}
bool BufferQueueCore::WaitForDequeueCondition(std::unique_lock<std::mutex>& lk) {
if (is_shutting_down) {
return false;
}
dequeue_condition.wait(lk, [&] { return dequeue_possible.load(); });
dequeue_possible.store(false);
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_Nvnflinger, "slot {}", slot);
slots[slot].graphic_buffer.reset();
slots[slot].buffer_state = BufferState::Free;
slots[slot].frame_number = UINT32_MAX;
slots[slot].acquire_called = false;
slots[slot].fence = Fence::NoFence();
}
void BufferQueueCore::FreeAllBuffersLocked() {
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) {
is_allocating_condition.wait(mutex);
}
}
} // namespace Service::android

View File

@@ -0,0 +1,80 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Parts of this implementation were based 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/nvnflinger/buffer_item.h"
#include "core/hle/service/nvnflinger/buffer_queue_defs.h"
#include "core/hle/service/nvnflinger/pixel_format.h"
#include "core/hle/service/nvnflinger/status.h"
#include "core/hle/service/nvnflinger/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(std::unique_lock<std::mutex>& lk);
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:
mutable std::mutex mutex;
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;
s32 override_max_buffer_count{};
std::condition_variable dequeue_condition;
std::atomic<bool> dequeue_possible{};
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_any is_allocating_condition;
bool is_shutting_down{};
};
} // namespace Service::android

View File

@@ -0,0 +1,21 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Parts of this implementation were based 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/nvnflinger/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

View File

@@ -0,0 +1,933 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Parts of this implementation were based 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/k_event.h"
#include "core/hle/kernel/k_readable_event.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/service/hle_ipc.h"
#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/nvdrv/core/nvmap.h"
#include "core/hle/service/nvnflinger/buffer_queue_core.h"
#include "core/hle/service/nvnflinger/buffer_queue_producer.h"
#include "core/hle/service/nvnflinger/consumer_listener.h"
#include "core/hle/service/nvnflinger/parcel.h"
#include "core/hle/service/nvnflinger/ui/graphic_buffer.h"
#include "core/hle/service/nvnflinger/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::Nvidia::NvCore::NvMap& nvmap_)
: service_context{service_context_}, core{std::move(buffer_queue_core_)}, slots(core->slots),
nvmap(nvmap_) {
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_Nvnflinger, "slot {}", slot);
std::scoped_lock lock{core->mutex};
if (core->is_abandoned) {
LOG_ERROR(Service_Nvnflinger, "BufferQueue has been abandoned");
return Status::NoInit;
}
if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
LOG_ERROR(Service_Nvnflinger, "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_Nvnflinger, "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_Nvnflinger, "count = {}", buffer_count);
std::shared_ptr<IConsumerListener> listener;
{
std::scoped_lock lock{core->mutex};
core->WaitWhileAllocatingLocked();
if (core->is_abandoned) {
LOG_ERROR(Service_Nvnflinger, "BufferQueue has been abandoned");
return Status::NoInit;
}
if (buffer_count > BufferQueueDefs::NUM_BUFFER_SLOTS) {
LOG_ERROR(Service_Nvnflinger, "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_Nvnflinger, "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_Nvnflinger, "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->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* return_flags,
std::unique_lock<std::mutex>& lk) const {
bool try_again = true;
while (try_again) {
if (core->is_abandoned) {
LOG_ERROR(Service_Nvnflinger, "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_Nvnflinger, "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);
*return_flags |= Status::ReleaseAllBuffers;
}
}
// Look for a free buffer to give to the client
*found = BufferQueueCore::INVALID_BUFFER_SLOT;
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;
case BufferState::Free:
// We return the oldest of the free buffers to avoid stalling the producer if
// possible, since the consumer may still have pending reads of in-flight buffers
if (*found == BufferQueueCore::INVALID_BUFFER_SLOT ||
slots[s].frame_number < slots[*found].frame_number) {
*found = s;
}
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_Nvnflinger,
"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_Nvnflinger,
"min undequeued buffer count({}) exceeded (dequeued={} undequeued={})",
min_undequeued_count, dequeued_count, new_undequeued_count);
return Status::InvalidOperation;
}
}
// 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_Nvnflinger, "queue size is {}, waiting", core->queue.size());
}
// 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(lk)) {
// 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_Nvnflinger, "async={} w={} h={} format={}, usage={}",
async ? "true" : "false", width, height, format, usage);
if ((width != 0 && height == 0) || (width == 0 && height != 0)) {
LOG_ERROR(Service_Nvnflinger, "invalid size: w={} h={}", width, height);
return Status::BadValue;
}
Status return_flags = Status::NoError;
bool attached_by_consumer = false;
{
std::unique_lock lock{core->mutex};
core->WaitWhileAllocatingLocked();
if (format == PixelFormat::NoFormat) {
format = core->default_buffer_format;
}
// Enable the usage bits the consumer requested
usage |= core->consumer_usage_bit;
s32 found{};
Status status = WaitForFreeSlotThenRelock(async, &found, &return_flags, lock);
if (status != Status::NoError) {
return status;
}
// This should not happen
if (found == BufferQueueCore::INVALID_BUFFER_SLOT) {
LOG_ERROR(Service_Nvnflinger, "no available buffer slots");
return Status::Busy;
}
*out_slot = found;
attached_by_consumer = slots[found].attached_by_consumer;
const bool use_default_size = !width && !height;
if (use_default_size) {
width = core->default_width;
height = core->default_height;
}
slots[found].buffer_state = BufferState::Dequeued;
const std::shared_ptr<GraphicBuffer>& buffer(slots[found].graphic_buffer);
if ((buffer == nullptr) || (buffer->Width() != width) || (buffer->Height() != height) ||
(buffer->Format() != format) || ((buffer->Usage() & usage) != usage)) {
slots[found].acquire_called = false;
slots[found].graphic_buffer = nullptr;
slots[found].request_buffer_called = false;
slots[found].fence = Fence::NoFence();
return_flags |= Status::BufferNeedsReallocation;
}
*out_fence = slots[found].fence;
slots[found].fence = Fence::NoFence();
}
if ((return_flags & Status::BufferNeedsReallocation) != Status::None) {
LOG_DEBUG(Service_Nvnflinger, "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_Nvnflinger, "creating GraphicBuffer failed");
return Status::NoMemory;
}
{
std::scoped_lock lock{core->mutex};
if (core->is_abandoned) {
LOG_ERROR(Service_Nvnflinger, "BufferQueue has been abandoned");
return Status::NoInit;
}
slots[*out_slot].frame_number = UINT32_MAX;
slots[*out_slot].graphic_buffer = graphic_buffer;
}
}
if (attached_by_consumer) {
return_flags |= Status::BufferNeedsReallocation;
}
LOG_DEBUG(Service_Nvnflinger, "returning slot={} frame={}, flags={}", *out_slot,
slots[*out_slot].frame_number, return_flags);
return return_flags;
}
Status BufferQueueProducer::DetachBuffer(s32 slot) {
LOG_DEBUG(Service_Nvnflinger, "slot {}", slot);
std::scoped_lock lock{core->mutex};
if (core->is_abandoned) {
LOG_ERROR(Service_Nvnflinger, "BufferQueue has been abandoned");
return Status::NoInit;
}
if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
LOG_ERROR(Service_Nvnflinger, "slot {} out of range [0, {})", slot,
BufferQueueDefs::NUM_BUFFER_SLOTS);
return Status::BadValue;
} else if (slots[slot].buffer_state != BufferState::Dequeued) {
LOG_ERROR(Service_Nvnflinger, "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_Nvnflinger, "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_Nvnflinger, "out_buffer must not be nullptr");
return Status::BadValue;
} else if (out_fence == nullptr) {
LOG_ERROR(Service_Nvnflinger, "out_fence must not be nullptr");
return Status::BadValue;
}
std::scoped_lock lock{core->mutex};
core->WaitWhileAllocatingLocked();
if (core->is_abandoned) {
LOG_ERROR(Service_Nvnflinger, "BufferQueue has been abandoned");
return Status::NoInit;
}
// Find the oldest valid slot
int found = BufferQueueCore::INVALID_BUFFER_SLOT;
for (int s = 0; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
if (slots[s].buffer_state == BufferState::Free && slots[s].graphic_buffer != nullptr) {
if (found == BufferQueueCore::INVALID_BUFFER_SLOT ||
slots[s].frame_number < slots[found].frame_number) {
found = s;
}
}
}
if (found == BufferQueueCore::INVALID_BUFFER_SLOT) {
return Status::NoMemory;
}
LOG_DEBUG(Service_Nvnflinger, "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_Nvnflinger, "out_slot must not be nullptr");
return Status::BadValue;
} else if (buffer == nullptr) {
LOG_ERROR(Service_Nvnflinger, "Cannot attach nullptr buffer");
return Status::BadValue;
}
std::unique_lock lock{core->mutex};
core->WaitWhileAllocatingLocked();
Status return_flags = Status::NoError;
s32 found{};
const auto status = WaitForFreeSlotThenRelock(false, &found, &return_flags, lock);
if (status != Status::NoError) {
return status;
}
// This should not happen
if (found == BufferQueueCore::INVALID_BUFFER_SLOT) {
LOG_ERROR(Service_Nvnflinger, "No available buffer slots");
return Status::Busy;
}
*out_slot = found;
LOG_DEBUG(Service_Nvnflinger, "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_Nvnflinger, "unknown scaling mode {}", scaling_mode);
return Status::BadValue;
}
std::shared_ptr<IConsumerListener> frame_available_listener;
std::shared_ptr<IConsumerListener> frame_replaced_listener;
s32 callback_ticket{};
BufferItem item;
{
std::scoped_lock lock{core->mutex};
if (core->is_abandoned) {
LOG_ERROR(Service_Nvnflinger, "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_Nvnflinger, "async mode is invalid with "
"buffer count override");
return Status::BadValue;
}
}
if (slot < 0 || slot >= max_buffer_count) {
LOG_ERROR(Service_Nvnflinger, "slot index {} out of range [0, {})", slot,
max_buffer_count);
return Status::BadValue;
} else if (slots[slot].buffer_state != BufferState::Dequeued) {
LOG_ERROR(Service_Nvnflinger,
"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_Nvnflinger,
"slot {} was queued without requesting "
"a buffer",
slot);
return Status::BadValue;
}
LOG_DEBUG(Service_Nvnflinger,
"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_Nvnflinger, "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;
nvmap.DuplicateHandle(item.graphic_buffer->BufferId(), true);
sticky_transform = sticky_transform_;
if (core->queue.empty()) {
// When the queue is empty, we can simply queue this buffer
core->queue.push_back(item);
frame_available_listener = 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;
// Reset the frame number of the freed buffer so that it is the first in line to
// be dequeued again
slots[front->slot].frame_number = 0;
}
// Overwrite the droppable buffer with the incoming one
*front = item;
frame_replaced_listener = core->consumer_listener;
} else {
core->queue.push_back(item);
frame_available_listener = 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::scoped_lock lock{callback_mutex};
while (callback_ticket != current_callback_ticket) {
callback_condition.wait(callback_mutex);
}
if (frame_available_listener != nullptr) {
frame_available_listener->OnFrameAvailable(item);
} else if (frame_replaced_listener != nullptr) {
frame_replaced_listener->OnFrameReplaced(item);
}
++current_callback_ticket;
callback_condition.notify_all();
}
return Status::NoError;
}
void BufferQueueProducer::CancelBuffer(s32 slot, const Fence& fence) {
LOG_DEBUG(Service_Nvnflinger, "slot {}", slot);
std::scoped_lock lock{core->mutex};
if (core->is_abandoned) {
LOG_ERROR(Service_Nvnflinger, "BufferQueue has been abandoned");
return;
}
if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
LOG_ERROR(Service_Nvnflinger, "slot index {} out of range [0, {})", slot,
BufferQueueDefs::NUM_BUFFER_SLOTS);
return;
} else if (slots[slot].buffer_state != BufferState::Dequeued) {
LOG_ERROR(Service_Nvnflinger, "slot {} is not owned by the producer (state = {})", slot,
slots[slot].buffer_state);
return;
}
slots[slot].buffer_state = BufferState::Free;
slots[slot].frame_number = 0;
slots[slot].fence = fence;
core->SignalDequeueCondition();
buffer_wait_event->Signal();
}
Status BufferQueueProducer::Query(NativeWindow what, s32* out_value) {
std::scoped_lock lock{core->mutex};
if (out_value == nullptr) {
LOG_ERROR(Service_Nvnflinger, "outValue was nullptr");
return Status::BadValue;
}
if (core->is_abandoned) {
LOG_ERROR(Service_Nvnflinger, "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;
default:
ASSERT(false);
return Status::BadValue;
}
LOG_DEBUG(Service_Nvnflinger, "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) {
std::scoped_lock lock{core->mutex};
LOG_DEBUG(Service_Nvnflinger, "api = {} producer_controlled_by_app = {}", api,
producer_controlled_by_app);
if (core->is_abandoned) {
LOG_ERROR(Service_Nvnflinger, "BufferQueue has been abandoned");
return Status::NoInit;
}
if (core->consumer_listener == nullptr) {
LOG_ERROR(Service_Nvnflinger, "BufferQueue has no consumer");
return Status::NoInit;
}
if (output == nullptr) {
LOG_ERROR(Service_Nvnflinger, "output was nullptr");
return Status::BadValue;
}
if (core->connected_api != NativeWindowApi::NoConnectedApi) {
LOG_ERROR(Service_Nvnflinger, "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_Nvnflinger, "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;
return status;
}
Status BufferQueueProducer::Disconnect(NativeWindowApi api) {
LOG_DEBUG(Service_Nvnflinger, "api = {}", api);
Status status = Status::NoError;
std::shared_ptr<IConsumerListener> listener;
{
std::scoped_lock lock{core->mutex};
core->WaitWhileAllocatingLocked();
if (core->is_abandoned) {
// Disconnecting after the surface has been abandoned is a no-op.
return Status::NoError;
}
// HACK: We are not Android. Remove handle for items in queue, and clear queue.
// Allows synchronous destruction of nvmap handles.
for (auto& item : core->queue) {
nvmap.FreeHandle(item.graphic_buffer->BufferId(), true);
}
core->queue.clear();
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->Signal();
listener = core->consumer_listener;
} else {
LOG_ERROR(Service_Nvnflinger, "still connected to another api (cur = {} req = {})",
core->connected_api, api);
status = Status::BadValue;
}
break;
default:
LOG_ERROR(Service_Nvnflinger, "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_Nvnflinger, "slot {}", slot);
if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
return Status::BadValue;
}
std::scoped_lock lock{core->mutex};
slots[slot] = {};
slots[slot].graphic_buffer = buffer;
slots[slot].frame_number = 0;
// 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->Signal();
return Status::NoError;
}
void BufferQueueProducer::Transact(HLERequestContext& ctx, TransactionId code, u32 flags) {
Status status{Status::NoError};
InputParcel parcel_in{ctx.ReadBuffer()};
OutputParcel 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_Nvnflinger, "(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

View File

@@ -0,0 +1,90 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Parts of this implementation were based 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/nvnflinger/binder.h"
#include "core/hle/service/nvnflinger/buffer_queue_defs.h"
#include "core/hle/service/nvnflinger/buffer_slot.h"
#include "core/hle/service/nvnflinger/graphic_buffer_producer.h"
#include "core/hle/service/nvnflinger/pixel_format.h"
#include "core/hle/service/nvnflinger/status.h"
#include "core/hle/service/nvnflinger/window.h"
namespace Kernel {
class KernelCore;
class KEvent;
class KReadableEvent;
} // namespace Kernel
namespace Service::KernelHelpers {
class ServiceContext;
} // namespace Service::KernelHelpers
namespace Service::Nvidia::NvCore {
class NvMap;
} // namespace Service::Nvidia::NvCore
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_,
Service::Nvidia::NvCore::NvMap& nvmap_);
~BufferQueueProducer();
void Transact(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* return_flags,
std::unique_lock<std::mutex>& lk) 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_any callback_condition;
Service::Nvidia::NvCore::NvMap& nvmap;
};
} // namespace Service::android

View File

@@ -0,0 +1,38 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Parts of this implementation were based 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/nvnflinger/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 attached_by_consumer{};
bool is_preallocated{};
};
} // namespace Service::android

View File

@@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#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

View File

@@ -0,0 +1,133 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2010 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Parts of this implementation were based 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/nvnflinger/buffer_item.h"
#include "core/hle/service/nvnflinger/buffer_queue_consumer.h"
#include "core/hle/service/nvnflinger/buffer_queue_core.h"
#include "core/hle/service/nvnflinger/consumer_base.h"
#include "core/hle/service/nvnflinger/ui/graphic_buffer.h"
namespace Service::android {
ConsumerBase::ConsumerBase(std::unique_ptr<BufferQueueConsumer> consumer_)
: consumer{std::move(consumer_)} {}
ConsumerBase::~ConsumerBase() {
std::scoped_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_Nvnflinger, "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) {
LOG_DEBUG(Service_Nvnflinger, "called");
}
void ConsumerBase::OnFrameReplaced(const BufferItem& item) {
LOG_DEBUG(Service_Nvnflinger, "called");
}
void ConsumerBase::OnBuffersReleased() {
std::scoped_lock lock{mutex};
LOG_DEBUG(Service_Nvnflinger, "called");
if (is_abandoned) {
// Nothing to do if we're already abandoned.
return;
}
u64 mask = 0;
consumer->GetReleasedBuffers(&mask);
for (int i = 0; i < BufferQueueDefs::NUM_BUFFER_SLOTS; i++) {
if (mask & (1ULL << i)) {
FreeBufferLocked(i);
}
}
}
void ConsumerBase::OnSidebandStreamChanged() {}
Status ConsumerBase::AcquireBufferLocked(BufferItem* item, std::chrono::nanoseconds present_when) {
Status err = consumer->AcquireBuffer(item, present_when);
if (err != Status::NoError) {
return err;
}
if (item->graphic_buffer != nullptr) {
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_Nvnflinger, "slot={}", item->slot);
return Status::NoError;
}
Status ConsumerBase::AddReleaseFenceLocked(s32 slot,
const std::shared_ptr<GraphicBuffer>& graphic_buffer,
const Fence& fence) {
LOG_DEBUG(Service_Nvnflinger, "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_Nvnflinger, "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

View File

@@ -0,0 +1,60 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2010 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Parts of this implementation were based 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/nvnflinger/buffer_queue_defs.h"
#include "core/hle/service/nvnflinger/consumer_listener.h"
#include "core/hle/service/nvnflinger/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_);
~ConsumerBase() override;
void OnFrameAvailable(const BufferItem& item) override;
void OnFrameReplaced(const BufferItem& item) override;
void OnBuffersReleased() override;
void OnSidebandStreamChanged() override;
void FreeBufferLocked(s32 slot_index);
Status AcquireBufferLocked(BufferItem* item, std::chrono::nanoseconds present_when);
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

View File

@@ -0,0 +1,26 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Parts of this implementation were based 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

View File

@@ -0,0 +1,18 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2010 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Parts of this implementation were based on:
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/IGraphicBufferProducer.cpp
#include "core/hle/service/nvnflinger/graphic_buffer_producer.h"
#include "core/hle/service/nvnflinger/parcel.h"
namespace Service::android {
QueueBufferInput::QueueBufferInput(InputParcel& parcel) {
parcel.ReadFlattened(*this);
}
QueueBufferOutput::QueueBufferOutput() = default;
} // namespace Service::android

View File

@@ -0,0 +1,76 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2010 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Parts of this implementation were based 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/nvnflinger/ui/fence.h"
#include "core/hle/service/nvnflinger/window.h"
namespace Service::android {
class InputParcel;
#pragma pack(push, 1)
struct QueueBufferInput final {
explicit QueueBufferInput(InputParcel& 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

View File

@@ -0,0 +1,36 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <mutex>
#include "common/common_types.h"
#include "core/hle/service/nvnflinger/hos_binder_driver_server.h"
namespace Service::Nvnflinger {
HosBinderDriverServer::HosBinderDriverServer(Core::System& system_)
: service_context(system_, "HosBinderDriverServer") {}
HosBinderDriverServer::~HosBinderDriverServer() {}
u64 HosBinderDriverServer::RegisterProducer(std::unique_ptr<android::IBinder>&& binder) {
std::scoped_lock lk{lock};
last_id++;
producers[last_id] = std::move(binder);
return last_id;
}
android::IBinder* HosBinderDriverServer::TryGetProducer(u64 id) {
std::scoped_lock lk{lock};
if (auto search = producers.find(id); search != producers.end()) {
return search->second.get();
}
return {};
}
} // namespace Service::Nvnflinger

View File

@@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#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/nvnflinger/binder.h"
namespace Core {
class System;
}
namespace Service::Nvnflinger {
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::Nvnflinger

View File

@@ -0,0 +1,335 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include <optional>
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/microprofile.h"
#include "common/scope_exit.h"
#include "common/settings.h"
#include "common/thread.h"
#include "core/core.h"
#include "core/core_timing.h"
#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/nvnflinger/buffer_item_consumer.h"
#include "core/hle/service/nvnflinger/buffer_queue_core.h"
#include "core/hle/service/nvnflinger/hos_binder_driver_server.h"
#include "core/hle/service/nvnflinger/nvnflinger.h"
#include "core/hle/service/nvnflinger/ui/graphic_buffer.h"
#include "core/hle/service/vi/display/vi_display.h"
#include "core/hle/service/vi/layer/vi_layer.h"
#include "core/hle/service/vi/vi_results.h"
#include "video_core/gpu.h"
#include "video_core/host1x/host1x.h"
#include "video_core/host1x/syncpoint_manager.h"
namespace Service::Nvnflinger {
constexpr auto frame_ns = std::chrono::nanoseconds{1000000000 / 60};
void Nvnflinger::SplitVSync(std::stop_token stop_token) {
system.RegisterHostThread();
std::string name = "VSyncThread";
MicroProfileOnThreadCreate(name.c_str());
// Cleanup
SCOPE_EXIT({ MicroProfileOnThreadExit(); });
Common::SetCurrentThreadName(name.c_str());
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
while (!stop_token.stop_requested()) {
vsync_signal.wait(false);
vsync_signal.store(false);
guard->lock();
Compose();
guard->unlock();
}
}
Nvnflinger::Nvnflinger(Core::System& system_, HosBinderDriverServer& hos_binder_driver_server_)
: system(system_), service_context(system_, "nvnflinger"),
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
multi_composition_event = Core::Timing::CreateEvent(
"ScreenComposition",
[this](std::uintptr_t, s64 time,
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
vsync_signal.store(true);
vsync_signal.notify_all();
return std::chrono::nanoseconds(GetNextTicks());
});
single_composition_event = Core::Timing::CreateEvent(
"ScreenComposition",
[this](std::uintptr_t, s64 time,
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
const auto lock_guard = Lock();
Compose();
return std::chrono::nanoseconds(GetNextTicks());
});
if (system.IsMulticore()) {
system.CoreTiming().ScheduleLoopingEvent(frame_ns, frame_ns, multi_composition_event);
vsync_thread = std::jthread([this](std::stop_token token) { SplitVSync(token); });
} else {
system.CoreTiming().ScheduleLoopingEvent(frame_ns, frame_ns, single_composition_event);
}
}
Nvnflinger::~Nvnflinger() {
if (system.IsMulticore()) {
system.CoreTiming().UnscheduleEvent(multi_composition_event, {});
vsync_thread.request_stop();
vsync_signal.store(true);
vsync_signal.notify_all();
} else {
system.CoreTiming().UnscheduleEvent(single_composition_event, {});
}
ShutdownLayers();
if (nvdrv) {
nvdrv->Close(disp_fd);
}
}
void Nvnflinger::ShutdownLayers() {
for (auto& display : displays) {
for (size_t layer = 0; layer < display.GetNumLayers(); ++layer) {
display.GetLayer(layer).Core().NotifyShutdown();
}
}
}
void Nvnflinger::SetNVDrvInstance(std::shared_ptr<Nvidia::Module> instance) {
nvdrv = std::move(instance);
disp_fd = nvdrv->Open("/dev/nvdisp_disp0");
}
std::optional<u64> Nvnflinger::OpenDisplay(std::string_view name) {
const auto lock_guard = Lock();
LOG_DEBUG(Service_Nvnflinger, "Opening \"{}\" display", name);
const auto itr =
std::find_if(displays.begin(), displays.end(),
[&](const VI::Display& display) { return display.GetName() == name; });
if (itr == displays.end()) {
return std::nullopt;
}
return itr->GetID();
}
bool Nvnflinger::CloseDisplay(u64 display_id) {
const auto lock_guard = Lock();
auto* const display = FindDisplay(display_id);
if (display == nullptr) {
return false;
}
display->Reset();
return true;
}
std::optional<u64> Nvnflinger::CreateLayer(u64 display_id) {
const auto lock_guard = Lock();
auto* const display = FindDisplay(display_id);
if (display == nullptr) {
return std::nullopt;
}
const u64 layer_id = next_layer_id++;
CreateLayerAtId(*display, layer_id);
return layer_id;
}
void Nvnflinger::CreateLayerAtId(VI::Display& display, u64 layer_id) {
const auto buffer_id = next_buffer_queue_id++;
display.CreateLayer(layer_id, buffer_id, nvdrv->container);
}
void Nvnflinger::CloseLayer(u64 layer_id) {
const auto lock_guard = Lock();
for (auto& display : displays) {
display.CloseLayer(layer_id);
}
}
std::optional<u32> Nvnflinger::FindBufferQueueId(u64 display_id, u64 layer_id) {
const auto lock_guard = Lock();
const auto* const layer = FindOrCreateLayer(display_id, layer_id);
if (layer == nullptr) {
return std::nullopt;
}
return layer->GetBinderId();
}
ResultVal<Kernel::KReadableEvent*> Nvnflinger::FindVsyncEvent(u64 display_id) {
const auto lock_guard = Lock();
auto* const display = FindDisplay(display_id);
if (display == nullptr) {
return VI::ResultNotFound;
}
return display->GetVSyncEvent();
}
VI::Display* Nvnflinger::FindDisplay(u64 display_id) {
const auto itr =
std::find_if(displays.begin(), displays.end(),
[&](const VI::Display& display) { return display.GetID() == display_id; });
if (itr == displays.end()) {
return nullptr;
}
return &*itr;
}
const VI::Display* Nvnflinger::FindDisplay(u64 display_id) const {
const auto itr =
std::find_if(displays.begin(), displays.end(),
[&](const VI::Display& display) { return display.GetID() == display_id; });
if (itr == displays.end()) {
return nullptr;
}
return &*itr;
}
VI::Layer* Nvnflinger::FindLayer(u64 display_id, u64 layer_id) {
auto* const display = FindDisplay(display_id);
if (display == nullptr) {
return nullptr;
}
return display->FindLayer(layer_id);
}
const VI::Layer* Nvnflinger::FindLayer(u64 display_id, u64 layer_id) const {
const auto* const display = FindDisplay(display_id);
if (display == nullptr) {
return nullptr;
}
return display->FindLayer(layer_id);
}
VI::Layer* Nvnflinger::FindOrCreateLayer(u64 display_id, u64 layer_id) {
auto* const display = FindDisplay(display_id);
if (display == nullptr) {
return nullptr;
}
auto* layer = display->FindLayer(layer_id);
if (layer == nullptr) {
LOG_DEBUG(Service_Nvnflinger, "Layer at id {} not found. Trying to create it.", layer_id);
CreateLayerAtId(*display, layer_id);
return display->FindLayer(layer_id);
}
return layer;
}
void Nvnflinger::Compose() {
for (auto& display : displays) {
// Trigger vsync for this display at the end of drawing
SCOPE_EXIT({ display.SignalVSyncEvent(); });
// Don't do anything for displays without layers.
if (!display.HasLayers())
continue;
// TODO(Subv): Support more than 1 layer.
VI::Layer& layer = display.GetLayer(0);
android::BufferItem buffer{};
const auto status = layer.GetConsumer().AcquireBuffer(&buffer, {}, false);
if (status != android::Status::NoError) {
continue;
}
const auto& igbp_buffer = *buffer.graphic_buffer;
if (!system.IsPoweredOn()) {
return; // We are likely shutting down
}
// Now send the buffer to the GPU for drawing.
// TODO(Subv): Support more than just disp0. The display device selection is probably based
// on which display we're drawing (Default, Internal, External, etc)
auto nvdisp = nvdrv->GetDevice<Nvidia::Devices::nvdisp_disp0>(disp_fd);
ASSERT(nvdisp);
guard->unlock();
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,
buffer.fence.fences, buffer.fence.num_fences);
MicroProfileFlip();
guard->lock();
swap_interval = buffer.swap_interval;
layer.GetConsumer().ReleaseBuffer(buffer, android::Fence::NoFence());
}
}
s64 Nvnflinger::GetNextTicks() const {
const auto& settings = Settings::values;
auto speed_scale = 1.f;
if (settings.use_multi_core.GetValue()) {
if (settings.use_speed_limit.GetValue()) {
// Scales the speed based on speed_limit setting on MC. SC is handled by
// SpeedLimiter::DoSpeedLimiting.
speed_scale = 100.f / settings.speed_limit.GetValue();
} else {
// Run at unlocked framerate.
speed_scale = 0.01f;
}
}
// As an extension, treat nonpositive swap interval as framerate multiplier.
const f32 effective_fps = swap_interval <= 0 ? 120.f * static_cast<f32>(1 - swap_interval)
: 60.f / static_cast<f32>(swap_interval);
return static_cast<s64>(speed_scale * (1000000000.f / effective_fps));
}
} // namespace Service::Nvnflinger

View File

@@ -0,0 +1,155 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <list>
#include <memory>
#include <mutex>
#include <optional>
#include <thread>
#include <vector>
#include "common/common_types.h"
#include "common/polyfill_thread.h"
#include "core/hle/result.h"
#include "core/hle/service/kernel_helpers.h"
namespace Common {
class Event;
} // namespace Common
namespace Core::Timing {
class CoreTiming;
struct EventType;
} // namespace Core::Timing
namespace Kernel {
class KReadableEvent;
} // namespace Kernel
namespace Service::Nvidia {
class Module;
} // namespace Service::Nvidia
namespace Service::VI {
class Display;
class Layer;
} // namespace Service::VI
namespace Service::android {
class BufferQueueCore;
class BufferQueueProducer;
} // namespace Service::android
namespace Service::Nvnflinger {
class Nvnflinger final {
public:
explicit Nvnflinger(Core::System& system_, HosBinderDriverServer& hos_binder_driver_server_);
~Nvnflinger();
void ShutdownLayers();
/// Sets the NVDrv module instance to use to send buffers to the GPU.
void SetNVDrvInstance(std::shared_ptr<Nvidia::Module> instance);
/// Opens the specified display and returns the ID.
///
/// If an invalid display name is provided, then an empty optional is returned.
[[nodiscard]] std::optional<u64> OpenDisplay(std::string_view name);
/// Closes the specified display by its ID.
///
/// Returns false if an invalid display ID is provided.
[[nodiscard]] bool CloseDisplay(u64 display_id);
/// Creates a layer on the specified display and returns the layer ID.
///
/// If an invalid display ID is specified, then an empty optional is returned.
[[nodiscard]] std::optional<u64> CreateLayer(u64 display_id);
/// Closes a layer on all displays for the given layer ID.
void CloseLayer(u64 layer_id);
/// Finds the buffer queue ID of the specified layer in the specified display.
///
/// If an invalid display ID or layer ID is provided, then an empty optional is returned.
[[nodiscard]] std::optional<u32> FindBufferQueueId(u64 display_id, u64 layer_id);
/// Gets the vsync event for the specified display.
///
/// If an invalid display ID is provided, then VI::ResultNotFound is returned.
/// If the vsync event has already been retrieved, then VI::ResultPermissionDenied is returned.
[[nodiscard]] ResultVal<Kernel::KReadableEvent*> FindVsyncEvent(u64 display_id);
/// Performs a composition request to the emulated nvidia GPU and triggers the vsync events when
/// finished.
void Compose();
[[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};
}
/// Finds the display identified by the specified ID.
[[nodiscard]] VI::Display* FindDisplay(u64 display_id);
/// Finds the display identified by the specified ID.
[[nodiscard]] const VI::Display* FindDisplay(u64 display_id) const;
/// Finds the layer identified by the specified ID in the desired display.
[[nodiscard]] VI::Layer* FindLayer(u64 display_id, u64 layer_id);
/// Finds the layer identified by the specified ID in the desired display.
[[nodiscard]] const VI::Layer* FindLayer(u64 display_id, u64 layer_id) const;
/// Finds the layer identified by the specified ID in the desired display,
/// or creates the layer if it is not found.
/// To be used when the system expects the specified ID to already exist.
[[nodiscard]] VI::Layer* FindOrCreateLayer(u64 display_id, u64 layer_id);
/// Creates a layer with the specified layer ID in the desired display.
void CreateLayerAtId(VI::Display& display, u64 layer_id);
void SplitVSync(std::stop_token stop_token);
std::shared_ptr<Nvidia::Module> nvdrv;
s32 disp_fd;
std::list<VI::Display> displays;
/// Id to use for the next layer that is created, this counter is shared among all displays.
u64 next_layer_id = 1;
/// Id to use for the next buffer queue that is created, this counter is shared among all
/// layers.
u32 next_buffer_queue_id = 1;
s32 swap_interval = 1;
/// Event that handles screen composition.
std::shared_ptr<Core::Timing::EventType> multi_composition_event;
std::shared_ptr<Core::Timing::EventType> single_composition_event;
std::shared_ptr<std::mutex> guard;
Core::System& system;
std::atomic<bool> vsync_signal;
std::jthread vsync_thread;
KernelHelpers::ServiceContext service_context;
HosBinderDriverServer& hos_binder_driver_server;
};
} // namespace Service::Nvnflinger

View File

@@ -0,0 +1,177 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <memory>
#include <span>
#include <vector>
#include "common/alignment.h"
#include "common/assert.h"
#include "common/common_types.h"
namespace Service::android {
struct ParcelHeader {
u32 data_size;
u32 data_offset;
u32 objects_size;
u32 objects_offset;
};
static_assert(sizeof(ParcelHeader) == 16, "ParcelHeader has wrong size");
class InputParcel final {
public:
explicit InputParcel(std::span<const u8> in_data) : read_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) <= read_buffer.size());
std::memcpy(&val, read_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) <= read_buffer.size());
T val;
std::memcpy(&val, read_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;
}
void DeserializeHeader() {
ASSERT(read_buffer.size() > sizeof(ParcelHeader));
ParcelHeader header{};
std::memcpy(&header, read_buffer.data(), sizeof(ParcelHeader));
read_index = header.data_offset;
}
private:
std::span<const u8> read_buffer;
std::size_t read_index = 0;
};
class OutputParcel final {
public:
static constexpr std::size_t DefaultBufferSize = 0x40;
OutputParcel() : buffer(DefaultBufferSize) {}
template <typename T>
explicit OutputParcel(const T& out_data) : buffer(DefaultBufferSize) {
Write(out_data);
}
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());
}
std::vector<u8> Serialize() const {
ParcelHeader header{};
header.data_size = static_cast<u32>(write_index - sizeof(ParcelHeader));
header.data_offset = sizeof(ParcelHeader);
header.objects_size = 4;
header.objects_offset = static_cast<u32>(sizeof(ParcelHeader) + header.data_size);
std::memcpy(buffer.data(), &header, sizeof(ParcelHeader));
return buffer;
}
private:
mutable std::vector<u8> buffer;
std::size_t write_index = sizeof(ParcelHeader);
};
} // namespace Service::android

View File

@@ -0,0 +1,21 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#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

View File

@@ -0,0 +1,17 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Parts of this implementation were based 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 ~IProducerListener() = default;
virtual void OnBufferReleased() = 0;
};
} // namespace Service::android

View File

@@ -0,0 +1,28 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#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

View File

@@ -0,0 +1,32 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2012 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Parts of this implementation were based 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

View File

@@ -0,0 +1,100 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2007 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Parts of this implementation were based 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/nvnflinger/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

View File

@@ -0,0 +1,53 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#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