mirror of
https://github.com/yuzu-emu/yuzu-android
synced 2025-08-09 11:52:33 -07:00
nvnflinger: fix name
This commit is contained in:
45
src/core/hle/service/nvnflinger/binder.h
Normal file
45
src/core/hle/service/nvnflinger/binder.h
Normal 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
|
46
src/core/hle/service/nvnflinger/buffer_item.h
Normal file
46
src/core/hle/service/nvnflinger/buffer_item.h
Normal 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
|
59
src/core/hle/service/nvnflinger/buffer_item_consumer.cpp
Normal file
59
src/core/hle/service/nvnflinger/buffer_item_consumer.cpp
Normal 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
|
28
src/core/hle/service/nvnflinger/buffer_item_consumer.h
Normal file
28
src/core/hle/service/nvnflinger/buffer_item_consumer.h
Normal 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
|
213
src/core/hle/service/nvnflinger/buffer_queue_consumer.cpp
Normal file
213
src/core/hle/service/nvnflinger/buffer_queue_consumer.cpp
Normal 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
|
43
src/core/hle/service/nvnflinger/buffer_queue_consumer.h
Normal file
43
src/core/hle/service/nvnflinger/buffer_queue_consumer.h
Normal 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
|
115
src/core/hle/service/nvnflinger/buffer_queue_core.cpp
Normal file
115
src/core/hle/service/nvnflinger/buffer_queue_core.cpp
Normal 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
|
80
src/core/hle/service/nvnflinger/buffer_queue_core.h
Normal file
80
src/core/hle/service/nvnflinger/buffer_queue_core.h
Normal 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
|
21
src/core/hle/service/nvnflinger/buffer_queue_defs.h
Normal file
21
src/core/hle/service/nvnflinger/buffer_queue_defs.h
Normal 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
|
933
src/core/hle/service/nvnflinger/buffer_queue_producer.cpp
Normal file
933
src/core/hle/service/nvnflinger/buffer_queue_producer.cpp
Normal 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(×tamp, &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
|
90
src/core/hle/service/nvnflinger/buffer_queue_producer.h
Normal file
90
src/core/hle/service/nvnflinger/buffer_queue_producer.h
Normal 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
|
38
src/core/hle/service/nvnflinger/buffer_slot.h
Normal file
38
src/core/hle/service/nvnflinger/buffer_slot.h
Normal 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
|
25
src/core/hle/service/nvnflinger/buffer_transform_flags.h
Normal file
25
src/core/hle/service/nvnflinger/buffer_transform_flags.h
Normal 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
|
133
src/core/hle/service/nvnflinger/consumer_base.cpp
Normal file
133
src/core/hle/service/nvnflinger/consumer_base.cpp
Normal 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
|
60
src/core/hle/service/nvnflinger/consumer_base.h
Normal file
60
src/core/hle/service/nvnflinger/consumer_base.h
Normal 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
|
26
src/core/hle/service/nvnflinger/consumer_listener.h
Normal file
26
src/core/hle/service/nvnflinger/consumer_listener.h
Normal 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
|
18
src/core/hle/service/nvnflinger/graphic_buffer_producer.cpp
Normal file
18
src/core/hle/service/nvnflinger/graphic_buffer_producer.cpp
Normal 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
|
76
src/core/hle/service/nvnflinger/graphic_buffer_producer.h
Normal file
76
src/core/hle/service/nvnflinger/graphic_buffer_producer.h
Normal 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
|
36
src/core/hle/service/nvnflinger/hos_binder_driver_server.cpp
Normal file
36
src/core/hle/service/nvnflinger/hos_binder_driver_server.cpp
Normal 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
|
37
src/core/hle/service/nvnflinger/hos_binder_driver_server.h
Normal file
37
src/core/hle/service/nvnflinger/hos_binder_driver_server.h
Normal 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
|
335
src/core/hle/service/nvnflinger/nvnflinger.cpp
Normal file
335
src/core/hle/service/nvnflinger/nvnflinger.cpp
Normal 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
|
155
src/core/hle/service/nvnflinger/nvnflinger.h
Normal file
155
src/core/hle/service/nvnflinger/nvnflinger.h
Normal 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
|
177
src/core/hle/service/nvnflinger/parcel.h
Normal file
177
src/core/hle/service/nvnflinger/parcel.h
Normal 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
|
21
src/core/hle/service/nvnflinger/pixel_format.h
Normal file
21
src/core/hle/service/nvnflinger/pixel_format.h
Normal 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
|
17
src/core/hle/service/nvnflinger/producer_listener.h
Normal file
17
src/core/hle/service/nvnflinger/producer_listener.h
Normal 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
|
28
src/core/hle/service/nvnflinger/status.h
Normal file
28
src/core/hle/service/nvnflinger/status.h
Normal 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
|
32
src/core/hle/service/nvnflinger/ui/fence.h
Normal file
32
src/core/hle/service/nvnflinger/ui/fence.h
Normal 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
|
100
src/core/hle/service/nvnflinger/ui/graphic_buffer.h
Normal file
100
src/core/hle/service/nvnflinger/ui/graphic_buffer.h
Normal 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
|
53
src/core/hle/service/nvnflinger/window.h
Normal file
53
src/core/hle/service/nvnflinger/window.h
Normal 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
|
Reference in New Issue
Block a user