Browse Source
Merge pull request #8050 from bunnei/nvflinger-rewrite
Merge pull request #8050 from bunnei/nvflinger-rewrite
Rewrite of the NVFlinger implementationnce_cpp
committed by
GitHub
62 changed files with 3609 additions and 1112 deletions
-
4README.md
-
937license.txt
-
1src/common/logging/filter.cpp
-
1src/common/logging/types.h
-
50src/common/math_util.h
-
29src/core/CMakeLists.txt
-
10src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
-
7src/core/hle/service/nvdrv/devices/nvdisp_disp0.h
-
7src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
-
10src/core/hle/service/nvdrv/devices/nvhost_gpu.h
-
10src/core/hle/service/nvdrv/nvdata.h
-
3src/core/hle/service/nvdrv/nvdrv.h
-
42src/core/hle/service/nvflinger/binder.h
-
46src/core/hle/service/nvflinger/buffer_item.h
-
59src/core/hle/service/nvflinger/buffer_item_consumer.cpp
-
28src/core/hle/service/nvflinger/buffer_item_consumer.h
-
225src/core/hle/service/nvflinger/buffer_queue_consumer.cpp
-
37src/core/hle/service/nvflinger/buffer_queue_consumer.h
-
134src/core/hle/service/nvflinger/buffer_queue_core.cpp
-
99src/core/hle/service/nvflinger/buffer_queue_core.h
-
21src/core/hle/service/nvflinger/buffer_queue_defs.h
-
937src/core/hle/service/nvflinger/buffer_queue_producer.cpp
-
83src/core/hle/service/nvflinger/buffer_queue_producer.h
-
39src/core/hle/service/nvflinger/buffer_slot.h
-
25src/core/hle/service/nvflinger/buffer_transform_flags.h
-
130src/core/hle/service/nvflinger/consumer_base.cpp
-
61src/core/hle/service/nvflinger/consumer_base.h
-
26src/core/hle/service/nvflinger/consumer_listener.h
-
20src/core/hle/service/nvflinger/graphic_buffer_producer.cpp
-
76src/core/hle/service/nvflinger/graphic_buffer_producer.h
-
36src/core/hle/service/nvflinger/hos_binder_driver_server.cpp
-
37src/core/hle/service/nvflinger/hos_binder_driver_server.h
-
81src/core/hle/service/nvflinger/nvflinger.cpp
-
26src/core/hle/service/nvflinger/nvflinger.h
-
172src/core/hle/service/nvflinger/parcel.h
-
21src/core/hle/service/nvflinger/pixel_format.h
-
16src/core/hle/service/nvflinger/producer_listener.h
-
28src/core/hle/service/nvflinger/status.h
-
32src/core/hle/service/nvflinger/ui/fence.h
-
100src/core/hle/service/nvflinger/ui/graphic_buffer.h
-
53src/core/hle/service/nvflinger/window.h
-
6src/core/hle/service/service.cpp
-
4src/core/hle/service/service.h
-
50src/core/hle/service/vi/display/vi_display.cpp
-
29src/core/hle/service/vi/display/vi_display.h
-
6src/core/hle/service/vi/layer/vi_layer.cpp
-
57src/core/hle/service/vi/layer/vi_layer.h
-
690src/core/hle/service/vi/vi.cpp
-
10src/core/hle/service/vi/vi.h
-
9src/core/hle/service/vi/vi_m.cpp
-
7src/core/hle/service/vi/vi_m.h
-
9src/core/hle/service/vi/vi_s.cpp
-
7src/core/hle/service/vi/vi_s.h
-
9src/core/hle/service/vi/vi_u.cpp
-
7src/core/hle/service/vi/vi_u.h
-
29src/video_core/framebuffer_config.h
-
1src/video_core/gpu.h
-
8src/video_core/renderer_opengl/renderer_opengl.cpp
-
4src/video_core/renderer_opengl/renderer_opengl.h
-
10src/video_core/renderer_vulkan/vk_blit_screen.cpp
-
8src/video_core/surface.cpp
-
2src/video_core/surface.h
@ -0,0 +1,42 @@ |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
// Copyright 2021 yuzu Emulator Project |
|||
// Copyright 2014 The Android Open Source Project |
|||
// Parts of this implementation were base on: |
|||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/binder/IBinder.h |
|||
|
|||
#pragma once |
|||
|
|||
#include "common/common_types.h" |
|||
|
|||
namespace Kernel { |
|||
class HLERequestContext; |
|||
class KReadableEvent; |
|||
} // namespace Kernel |
|||
|
|||
namespace Service::android { |
|||
|
|||
enum class TransactionId { |
|||
RequestBuffer = 1, |
|||
SetBufferCount = 2, |
|||
DequeueBuffer = 3, |
|||
DetachBuffer = 4, |
|||
DetachNextBuffer = 5, |
|||
AttachBuffer = 6, |
|||
QueueBuffer = 7, |
|||
CancelBuffer = 8, |
|||
Query = 9, |
|||
Connect = 10, |
|||
Disconnect = 11, |
|||
AllocateBuffers = 13, |
|||
SetPreallocatedBuffer = 14, |
|||
GetBufferHistory = 17, |
|||
}; |
|||
|
|||
class IBinder { |
|||
public: |
|||
virtual void Transact(Kernel::HLERequestContext& ctx, android::TransactionId code, |
|||
u32 flags) = 0; |
|||
virtual Kernel::KReadableEvent& GetNativeHandle() = 0; |
|||
}; |
|||
|
|||
} // namespace Service::android |
|||
@ -0,0 +1,46 @@ |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
// Copyright 2021 yuzu Emulator Project |
|||
// Copyright 2014 The Android Open Source Project |
|||
// Parts of this implementation were base on: |
|||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferItem.h |
|||
|
|||
#pragma once |
|||
|
|||
#include <memory> |
|||
|
|||
#include "common/common_types.h" |
|||
#include "common/math_util.h" |
|||
#include "core/hle/service/nvflinger/ui/fence.h" |
|||
#include "core/hle/service/nvflinger/window.h" |
|||
|
|||
namespace Service::android { |
|||
|
|||
class GraphicBuffer; |
|||
|
|||
class BufferItem final { |
|||
public: |
|||
constexpr BufferItem() = default; |
|||
|
|||
std::shared_ptr<GraphicBuffer> graphic_buffer; |
|||
Fence fence; |
|||
Common::Rectangle<s32> crop; |
|||
NativeWindowTransform transform{}; |
|||
u32 scaling_mode{}; |
|||
s64 timestamp{}; |
|||
bool is_auto_timestamp{}; |
|||
u64 frame_number{}; |
|||
|
|||
// The default value for buf, used to indicate this doesn't correspond to a slot. |
|||
static constexpr s32 INVALID_BUFFER_SLOT = -1; |
|||
union { |
|||
s32 slot{INVALID_BUFFER_SLOT}; |
|||
s32 buf; |
|||
}; |
|||
|
|||
bool is_droppable{}; |
|||
bool acquire_called{}; |
|||
bool transform_to_display_inverse{}; |
|||
s32 swap_interval{}; |
|||
}; |
|||
|
|||
} // namespace Service::android |
|||
@ -0,0 +1,59 @@ |
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|||
// Copyright 2021 yuzu Emulator Project
|
|||
// Copyright 2012 The Android Open Source Project
|
|||
// Parts of this implementation were base on:
|
|||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferItemConsumer.cpp
|
|||
|
|||
#include "common/assert.h"
|
|||
#include "common/logging/log.h"
|
|||
#include "core/hle/service/nvflinger/buffer_item.h"
|
|||
#include "core/hle/service/nvflinger/buffer_item_consumer.h"
|
|||
#include "core/hle/service/nvflinger/buffer_queue_consumer.h"
|
|||
|
|||
namespace Service::android { |
|||
|
|||
BufferItemConsumer::BufferItemConsumer(std::unique_ptr<BufferQueueConsumer> consumer_) |
|||
: ConsumerBase{std::move(consumer_)} {} |
|||
|
|||
Status BufferItemConsumer::AcquireBuffer(BufferItem* item, std::chrono::nanoseconds present_when, |
|||
bool wait_for_fence) { |
|||
if (!item) { |
|||
return Status::BadValue; |
|||
} |
|||
|
|||
std::unique_lock lock(mutex); |
|||
|
|||
if (const auto status = AcquireBufferLocked(item, present_when); status != Status::NoError) { |
|||
if (status != Status::NoBufferAvailable) { |
|||
LOG_ERROR(Service_NVFlinger, "Failed to acquire buffer: {}", status); |
|||
} |
|||
return status; |
|||
} |
|||
|
|||
if (wait_for_fence) { |
|||
UNIMPLEMENTED(); |
|||
} |
|||
|
|||
item->graphic_buffer = slots[item->slot].graphic_buffer; |
|||
|
|||
return Status::NoError; |
|||
} |
|||
|
|||
Status BufferItemConsumer::ReleaseBuffer(const BufferItem& item, Fence& release_fence) { |
|||
std::unique_lock lock(mutex); |
|||
|
|||
if (const auto status = AddReleaseFenceLocked(item.buf, item.graphic_buffer, release_fence); |
|||
status != Status::NoError) { |
|||
LOG_ERROR(Service_NVFlinger, "Failed to add fence: {}", status); |
|||
} |
|||
|
|||
if (const auto status = ReleaseBufferLocked(item.buf, item.graphic_buffer); |
|||
status != Status::NoError) { |
|||
LOG_WARNING(Service_NVFlinger, "Failed to release buffer: {}", status); |
|||
return status; |
|||
} |
|||
|
|||
return Status::NoError; |
|||
} |
|||
|
|||
} // namespace Service::android
|
|||
@ -0,0 +1,28 @@ |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
// Copyright 2021 yuzu Emulator Project |
|||
// Copyright 2012 The Android Open Source Project |
|||
// Parts of this implementation were base on: |
|||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferItemConsumer.h |
|||
|
|||
#pragma once |
|||
|
|||
#include <chrono> |
|||
#include <memory> |
|||
|
|||
#include "common/common_types.h" |
|||
#include "core/hle/service/nvflinger/consumer_base.h" |
|||
#include "core/hle/service/nvflinger/status.h" |
|||
|
|||
namespace Service::android { |
|||
|
|||
class BufferItem; |
|||
|
|||
class BufferItemConsumer final : public ConsumerBase { |
|||
public: |
|||
explicit BufferItemConsumer(std::unique_ptr<BufferQueueConsumer> consumer); |
|||
Status AcquireBuffer(BufferItem* item, std::chrono::nanoseconds present_when, |
|||
bool wait_for_fence = true); |
|||
Status ReleaseBuffer(const BufferItem& item, Fence& release_fence); |
|||
}; |
|||
|
|||
} // namespace Service::android |
|||
@ -0,0 +1,225 @@ |
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|||
// Copyright 2021 yuzu Emulator Project
|
|||
// Copyright 2014 The Android Open Source Project
|
|||
// Parts of this implementation were base on:
|
|||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferQueueConsumer.cpp
|
|||
|
|||
#include "common/logging/log.h"
|
|||
#include "core/hle/service/nvflinger/buffer_item.h"
|
|||
#include "core/hle/service/nvflinger/buffer_queue_consumer.h"
|
|||
#include "core/hle/service/nvflinger/buffer_queue_core.h"
|
|||
#include "core/hle/service/nvflinger/producer_listener.h"
|
|||
|
|||
namespace Service::android { |
|||
|
|||
BufferQueueConsumer::BufferQueueConsumer(std::shared_ptr<BufferQueueCore> core_) |
|||
: core{std::move(core_)}, slots{core->slots} {} |
|||
|
|||
BufferQueueConsumer::~BufferQueueConsumer() = default; |
|||
|
|||
Status BufferQueueConsumer::AcquireBuffer(BufferItem* out_buffer, |
|||
std::chrono::nanoseconds expected_present, |
|||
u64 max_frame_number) { |
|||
s32 num_dropped_buffers{}; |
|||
|
|||
std::shared_ptr<IProducerListener> listener; |
|||
{ |
|||
std::unique_lock lock(core->mutex); |
|||
|
|||
// Check that the consumer doesn't currently have the maximum number of buffers acquired.
|
|||
const s32 num_acquired_buffers{ |
|||
static_cast<s32>(std::count_if(slots.begin(), slots.end(), [](const auto& slot) { |
|||
return slot.buffer_state == BufferState::Acquired; |
|||
}))}; |
|||
|
|||
if (num_acquired_buffers >= core->max_acquired_buffer_count + 1) { |
|||
LOG_ERROR(Service_NVFlinger, "max acquired buffer count reached: {} (max {})", |
|||
num_acquired_buffers, core->max_acquired_buffer_count); |
|||
return Status::InvalidOperation; |
|||
} |
|||
|
|||
// Check if the queue is empty.
|
|||
if (core->queue.empty()) { |
|||
return Status::NoBufferAvailable; |
|||
} |
|||
|
|||
auto front(core->queue.begin()); |
|||
|
|||
// If expected_present is specified, we may not want to return a buffer yet.
|
|||
if (expected_present.count() != 0) { |
|||
constexpr auto MAX_REASONABLE_NSEC = 1000000000LL; // 1 second
|
|||
|
|||
// The expected_present argument indicates when the buffer is expected to be
|
|||
// presented on-screen.
|
|||
while (core->queue.size() > 1 && !core->queue[0].is_auto_timestamp) { |
|||
const auto& buffer_item{core->queue[1]}; |
|||
|
|||
// If dropping entry[0] would leave us with a buffer that the consumer is not yet
|
|||
// ready for, don't drop it.
|
|||
if (max_frame_number && buffer_item.frame_number > max_frame_number) { |
|||
break; |
|||
} |
|||
|
|||
// If entry[1] is timely, drop entry[0] (and repeat).
|
|||
const auto desired_present = buffer_item.timestamp; |
|||
if (desired_present < expected_present.count() - MAX_REASONABLE_NSEC || |
|||
desired_present > expected_present.count()) { |
|||
// This buffer is set to display in the near future, or desired_present is
|
|||
// garbage.
|
|||
LOG_DEBUG(Service_NVFlinger, "nodrop desire={} expect={}", desired_present, |
|||
expected_present.count()); |
|||
break; |
|||
} |
|||
|
|||
LOG_DEBUG(Service_NVFlinger, "drop desire={} expect={} size={}", desired_present, |
|||
expected_present.count(), core->queue.size()); |
|||
|
|||
if (core->StillTracking(*front)) { |
|||
// Front buffer is still in mSlots, so mark the slot as free
|
|||
slots[front->slot].buffer_state = BufferState::Free; |
|||
core->free_buffers.push_back(front->slot); |
|||
listener = core->connected_producer_listener; |
|||
++num_dropped_buffers; |
|||
} |
|||
|
|||
core->queue.erase(front); |
|||
front = core->queue.begin(); |
|||
} |
|||
|
|||
// See if the front buffer is ready to be acquired.
|
|||
const auto desired_present = front->timestamp; |
|||
const auto buffer_is_due = |
|||
desired_present <= expected_present.count() || |
|||
desired_present > expected_present.count() + MAX_REASONABLE_NSEC; |
|||
const auto consumer_is_ready = |
|||
max_frame_number > 0 ? front->frame_number <= max_frame_number : true; |
|||
|
|||
if (!buffer_is_due || !consumer_is_ready) { |
|||
LOG_DEBUG(Service_NVFlinger, "defer desire={} expect={}", desired_present, |
|||
expected_present.count()); |
|||
return Status::PresentLater; |
|||
} |
|||
|
|||
LOG_DEBUG(Service_NVFlinger, "accept desire={} expect={}", desired_present, |
|||
expected_present.count()); |
|||
} |
|||
|
|||
const auto slot = front->slot; |
|||
*out_buffer = *front; |
|||
|
|||
LOG_DEBUG(Service_NVFlinger, "acquiring slot={}", slot); |
|||
|
|||
// If the front buffer is still being tracked, update its slot state
|
|||
if (core->StillTracking(*front)) { |
|||
slots[slot].acquire_called = true; |
|||
slots[slot].needs_cleanup_on_release = false; |
|||
slots[slot].buffer_state = BufferState::Acquired; |
|||
slots[slot].fence = Fence::NoFence(); |
|||
} |
|||
|
|||
// If the buffer has previously been acquired by the consumer, set graphic_buffer to nullptr
|
|||
// to avoid unnecessarily remapping this buffer on the consumer side.
|
|||
if (out_buffer->acquire_called) { |
|||
out_buffer->graphic_buffer = nullptr; |
|||
} |
|||
|
|||
core->queue.erase(front); |
|||
|
|||
// We might have freed a slot while dropping old buffers, or the producer may be blocked
|
|||
// waiting for the number of buffers in the queue to decrease.
|
|||
core->SignalDequeueCondition(); |
|||
} |
|||
|
|||
if (listener != nullptr) { |
|||
for (s32 i = 0; i < num_dropped_buffers; ++i) { |
|||
listener->OnBufferReleased(); |
|||
} |
|||
} |
|||
|
|||
return Status::NoError; |
|||
} |
|||
|
|||
Status BufferQueueConsumer::ReleaseBuffer(s32 slot, u64 frame_number, const Fence& release_fence) { |
|||
if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) { |
|||
LOG_ERROR(Service_NVFlinger, "slot {} out of range", slot); |
|||
return Status::BadValue; |
|||
} |
|||
|
|||
std::shared_ptr<IProducerListener> listener; |
|||
{ |
|||
std::unique_lock lock(core->mutex); |
|||
|
|||
// If the frame number has changed because the buffer has been reallocated, we can ignore
|
|||
// this ReleaseBuffer for the old buffer.
|
|||
if (frame_number != slots[slot].frame_number) { |
|||
return Status::StaleBufferSlot; |
|||
} |
|||
|
|||
// Make sure this buffer hasn't been queued while acquired by the consumer.
|
|||
auto current(core->queue.begin()); |
|||
while (current != core->queue.end()) { |
|||
if (current->slot == slot) { |
|||
LOG_ERROR(Service_NVFlinger, "buffer slot {} pending release is currently queued", |
|||
slot); |
|||
return Status::BadValue; |
|||
} |
|||
++current; |
|||
} |
|||
|
|||
if (slots[slot].buffer_state == BufferState::Acquired) { |
|||
slots[slot].fence = release_fence; |
|||
slots[slot].buffer_state = BufferState::Free; |
|||
|
|||
core->free_buffers.push_back(slot); |
|||
|
|||
listener = core->connected_producer_listener; |
|||
|
|||
LOG_DEBUG(Service_NVFlinger, "releasing slot {}", slot); |
|||
} else if (slots[slot].needs_cleanup_on_release) { |
|||
LOG_DEBUG(Service_NVFlinger, "releasing a stale buffer slot {} (state = {})", slot, |
|||
slots[slot].buffer_state); |
|||
|
|||
slots[slot].needs_cleanup_on_release = false; |
|||
|
|||
return Status::StaleBufferSlot; |
|||
} else { |
|||
LOG_ERROR(Service_NVFlinger, "attempted to release buffer slot {} but its state was {}", |
|||
slot, slots[slot].buffer_state); |
|||
|
|||
return Status::BadValue; |
|||
} |
|||
|
|||
core->dequeue_condition.notify_all(); |
|||
} |
|||
|
|||
// Call back without lock held
|
|||
if (listener != nullptr) { |
|||
listener->OnBufferReleased(); |
|||
} |
|||
|
|||
return Status::NoError; |
|||
} |
|||
|
|||
Status BufferQueueConsumer::Connect(std::shared_ptr<IConsumerListener> consumer_listener, |
|||
bool controlled_by_app) { |
|||
if (consumer_listener == nullptr) { |
|||
LOG_ERROR(Service_NVFlinger, "consumer_listener may not be nullptr"); |
|||
return Status::BadValue; |
|||
} |
|||
|
|||
LOG_DEBUG(Service_NVFlinger, "controlled_by_app={}", controlled_by_app); |
|||
|
|||
BufferQueueCore::AutoLock lock(core); |
|||
|
|||
if (core->is_abandoned) { |
|||
LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned"); |
|||
return Status::NoInit; |
|||
} |
|||
|
|||
core->consumer_listener = consumer_listener; |
|||
core->consumer_controlled_by_app = controlled_by_app; |
|||
|
|||
return Status::NoError; |
|||
} |
|||
|
|||
} // namespace Service::android
|
|||
@ -0,0 +1,37 @@ |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
// Copyright 2021 yuzu Emulator Project |
|||
// Copyright 2014 The Android Open Source Project |
|||
// Parts of this implementation were base on: |
|||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferQueueConsumer.h |
|||
|
|||
#pragma once |
|||
|
|||
#include <chrono> |
|||
#include <memory> |
|||
|
|||
#include "common/common_types.h" |
|||
#include "core/hle/service/nvflinger/buffer_queue_defs.h" |
|||
#include "core/hle/service/nvflinger/status.h" |
|||
|
|||
namespace Service::android { |
|||
|
|||
class BufferItem; |
|||
class BufferQueueCore; |
|||
class IConsumerListener; |
|||
|
|||
class BufferQueueConsumer final { |
|||
public: |
|||
explicit BufferQueueConsumer(std::shared_ptr<BufferQueueCore> core_); |
|||
~BufferQueueConsumer(); |
|||
|
|||
Status AcquireBuffer(BufferItem* out_buffer, std::chrono::nanoseconds expected_present, |
|||
u64 max_frame_number = 0); |
|||
Status ReleaseBuffer(s32 slot, u64 frame_number, const Fence& release_fence); |
|||
Status Connect(std::shared_ptr<IConsumerListener> consumer_listener, bool controlled_by_app); |
|||
|
|||
private: |
|||
std::shared_ptr<BufferQueueCore> core; |
|||
BufferQueueDefs::SlotsType& slots; |
|||
}; |
|||
|
|||
} // namespace Service::android |
|||
@ -0,0 +1,134 @@ |
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|||
// Copyright 2021 yuzu Emulator Project
|
|||
// Copyright 2014 The Android Open Source Project
|
|||
// Parts of this implementation were base on:
|
|||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferQueueCore.cpp
|
|||
|
|||
#include "common/assert.h"
|
|||
|
|||
#include "core/hle/service/nvflinger/buffer_queue_core.h"
|
|||
|
|||
namespace Service::android { |
|||
|
|||
BufferQueueCore::BufferQueueCore() : lock{mutex, std::defer_lock} { |
|||
for (s32 slot = 0; slot < BufferQueueDefs::NUM_BUFFER_SLOTS; ++slot) { |
|||
free_slots.insert(slot); |
|||
} |
|||
} |
|||
|
|||
BufferQueueCore::~BufferQueueCore() = default; |
|||
|
|||
void BufferQueueCore::NotifyShutdown() { |
|||
std::unique_lock lk(mutex); |
|||
|
|||
is_shutting_down = true; |
|||
|
|||
SignalDequeueCondition(); |
|||
} |
|||
|
|||
void BufferQueueCore::SignalDequeueCondition() { |
|||
dequeue_condition.notify_all(); |
|||
} |
|||
|
|||
bool BufferQueueCore::WaitForDequeueCondition() { |
|||
if (is_shutting_down) { |
|||
return false; |
|||
} |
|||
|
|||
dequeue_condition.wait(lock); |
|||
|
|||
return true; |
|||
} |
|||
|
|||
s32 BufferQueueCore::GetMinUndequeuedBufferCountLocked(bool async) const { |
|||
// If DequeueBuffer is allowed to error out, we don't have to add an extra buffer.
|
|||
if (!use_async_buffer) { |
|||
return max_acquired_buffer_count; |
|||
} |
|||
|
|||
if (dequeue_buffer_cannot_block || async) { |
|||
return max_acquired_buffer_count + 1; |
|||
} |
|||
|
|||
return max_acquired_buffer_count; |
|||
} |
|||
|
|||
s32 BufferQueueCore::GetMinMaxBufferCountLocked(bool async) const { |
|||
return GetMinUndequeuedBufferCountLocked(async) + 1; |
|||
} |
|||
|
|||
s32 BufferQueueCore::GetMaxBufferCountLocked(bool async) const { |
|||
const auto min_buffer_count = GetMinMaxBufferCountLocked(async); |
|||
auto max_buffer_count = std::max(default_max_buffer_count, min_buffer_count); |
|||
|
|||
if (override_max_buffer_count != 0) { |
|||
ASSERT(override_max_buffer_count >= min_buffer_count); |
|||
max_buffer_count = override_max_buffer_count; |
|||
} |
|||
|
|||
// Any buffers that are dequeued by the producer or sitting in the queue waiting to be consumed
|
|||
// need to have their slots preserved.
|
|||
for (s32 slot = max_buffer_count; slot < BufferQueueDefs::NUM_BUFFER_SLOTS; ++slot) { |
|||
const auto state = slots[slot].buffer_state; |
|||
if (state == BufferState::Queued || state == BufferState::Dequeued) { |
|||
max_buffer_count = slot + 1; |
|||
} |
|||
} |
|||
|
|||
return max_buffer_count; |
|||
} |
|||
|
|||
s32 BufferQueueCore::GetPreallocatedBufferCountLocked() const { |
|||
return static_cast<s32>(std::count_if(slots.begin(), slots.end(), |
|||
[](const auto& slot) { return slot.is_preallocated; })); |
|||
} |
|||
|
|||
void BufferQueueCore::FreeBufferLocked(s32 slot) { |
|||
LOG_DEBUG(Service_NVFlinger, "slot {}", slot); |
|||
|
|||
const auto had_buffer = slots[slot].graphic_buffer != nullptr; |
|||
|
|||
slots[slot].graphic_buffer.reset(); |
|||
|
|||
if (slots[slot].buffer_state == BufferState::Acquired) { |
|||
slots[slot].needs_cleanup_on_release = true; |
|||
} |
|||
|
|||
if (slots[slot].buffer_state != BufferState::Free) { |
|||
free_slots.insert(slot); |
|||
} else if (had_buffer) { |
|||
// If the slot was FREE, but we had a buffer, we need to move this slot from the free
|
|||
// buffers list to the the free slots list.
|
|||
free_buffers.remove(slot); |
|||
free_slots.insert(slot); |
|||
} |
|||
|
|||
slots[slot].buffer_state = BufferState::Free; |
|||
slots[slot].acquire_called = false; |
|||
slots[slot].frame_number = 0; |
|||
slots[slot].fence = Fence::NoFence(); |
|||
} |
|||
|
|||
void BufferQueueCore::FreeAllBuffersLocked() { |
|||
queue.clear(); |
|||
buffer_has_been_queued = false; |
|||
|
|||
for (s32 slot = 0; slot < BufferQueueDefs::NUM_BUFFER_SLOTS; ++slot) { |
|||
FreeBufferLocked(slot); |
|||
} |
|||
} |
|||
|
|||
bool BufferQueueCore::StillTracking(const BufferItem& item) const { |
|||
const BufferSlot& slot = slots[item.slot]; |
|||
|
|||
return (slot.graphic_buffer != nullptr) && (item.graphic_buffer == slot.graphic_buffer); |
|||
} |
|||
|
|||
void BufferQueueCore::WaitWhileAllocatingLocked() const { |
|||
while (is_allocating) { |
|||
std::unique_lock lk(mutex); |
|||
is_allocating_condition.wait(lk); |
|||
} |
|||
} |
|||
|
|||
} // namespace Service::android
|
|||
@ -0,0 +1,99 @@ |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
// Copyright 2021 yuzu Emulator Project |
|||
// Copyright 2014 The Android Open Source Project |
|||
// Parts of this implementation were base on: |
|||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferQueueCore.h |
|||
|
|||
#pragma once |
|||
|
|||
#include <condition_variable> |
|||
#include <list> |
|||
#include <memory> |
|||
#include <mutex> |
|||
#include <set> |
|||
#include <vector> |
|||
|
|||
#include "core/hle/service/nvflinger/buffer_item.h" |
|||
#include "core/hle/service/nvflinger/buffer_queue_defs.h" |
|||
#include "core/hle/service/nvflinger/pixel_format.h" |
|||
#include "core/hle/service/nvflinger/status.h" |
|||
#include "core/hle/service/nvflinger/window.h" |
|||
|
|||
namespace Service::android { |
|||
|
|||
class IConsumerListener; |
|||
class IProducerListener; |
|||
|
|||
class BufferQueueCore final { |
|||
friend class BufferQueueProducer; |
|||
friend class BufferQueueConsumer; |
|||
|
|||
public: |
|||
static constexpr s32 INVALID_BUFFER_SLOT = BufferItem::INVALID_BUFFER_SLOT; |
|||
|
|||
BufferQueueCore(); |
|||
~BufferQueueCore(); |
|||
|
|||
void NotifyShutdown(); |
|||
|
|||
private: |
|||
void SignalDequeueCondition(); |
|||
bool WaitForDequeueCondition(); |
|||
|
|||
s32 GetMinUndequeuedBufferCountLocked(bool async) const; |
|||
s32 GetMinMaxBufferCountLocked(bool async) const; |
|||
s32 GetMaxBufferCountLocked(bool async) const; |
|||
s32 GetPreallocatedBufferCountLocked() const; |
|||
void FreeBufferLocked(s32 slot); |
|||
void FreeAllBuffersLocked(); |
|||
bool StillTracking(const BufferItem& item) const; |
|||
void WaitWhileAllocatingLocked() const; |
|||
|
|||
private: |
|||
class AutoLock final { |
|||
public: |
|||
AutoLock(std::shared_ptr<BufferQueueCore>& core_) : core{core_} { |
|||
core->lock.lock(); |
|||
} |
|||
|
|||
~AutoLock() { |
|||
core->lock.unlock(); |
|||
} |
|||
|
|||
private: |
|||
std::shared_ptr<BufferQueueCore>& core; |
|||
}; |
|||
|
|||
private: |
|||
mutable std::mutex mutex; |
|||
mutable std::unique_lock<std::mutex> lock; |
|||
bool is_abandoned{}; |
|||
bool consumer_controlled_by_app{}; |
|||
std::shared_ptr<IConsumerListener> consumer_listener; |
|||
u32 consumer_usage_bit{}; |
|||
NativeWindowApi connected_api{NativeWindowApi::NoConnectedApi}; |
|||
std::shared_ptr<IProducerListener> connected_producer_listener; |
|||
BufferQueueDefs::SlotsType slots{}; |
|||
std::vector<BufferItem> queue; |
|||
std::set<s32> free_slots; |
|||
std::list<s32> free_buffers; |
|||
s32 override_max_buffer_count{}; |
|||
mutable std::condition_variable dequeue_condition; |
|||
const bool use_async_buffer{}; // This is always disabled on HOS |
|||
bool dequeue_buffer_cannot_block{}; |
|||
PixelFormat default_buffer_format{PixelFormat::Rgba8888}; |
|||
u32 default_width{1}; |
|||
u32 default_height{1}; |
|||
s32 default_max_buffer_count{2}; |
|||
const s32 max_acquired_buffer_count{}; // This is always zero on HOS |
|||
bool buffer_has_been_queued{}; |
|||
u64 frame_counter{}; |
|||
u32 transform_hint{}; |
|||
bool is_allocating{}; |
|||
mutable std::condition_variable is_allocating_condition; |
|||
bool allow_allocation{true}; |
|||
u64 buffer_age{}; |
|||
bool is_shutting_down{}; |
|||
}; |
|||
|
|||
} // namespace Service::android |
|||
@ -0,0 +1,21 @@ |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
// Copyright 2021 yuzu Emulator Project |
|||
// Copyright 2014 The Android Open Source Project |
|||
// Parts of this implementation were base on: |
|||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferQueueDefs.h |
|||
|
|||
#pragma once |
|||
|
|||
#include <array> |
|||
|
|||
#include "common/common_types.h" |
|||
#include "core/hle/service/nvflinger/buffer_slot.h" |
|||
|
|||
namespace Service::android::BufferQueueDefs { |
|||
|
|||
// BufferQueue will keep track of at most this value of buffers. |
|||
constexpr s32 NUM_BUFFER_SLOTS = 64; |
|||
|
|||
using SlotsType = std::array<BufferSlot, NUM_BUFFER_SLOTS>; |
|||
|
|||
} // namespace Service::android::BufferQueueDefs |
|||
@ -0,0 +1,937 @@ |
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|||
// Copyright 2021 yuzu Emulator Project
|
|||
// Copyright 2014 The Android Open Source Project
|
|||
// Parts of this implementation were base on:
|
|||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferQueueProducer.cpp
|
|||
|
|||
#include "common/assert.h"
|
|||
#include "common/logging/log.h"
|
|||
#include "common/settings.h"
|
|||
#include "core/core.h"
|
|||
#include "core/hle/kernel/hle_ipc.h"
|
|||
#include "core/hle/kernel/k_event.h"
|
|||
#include "core/hle/kernel/k_readable_event.h"
|
|||
#include "core/hle/kernel/k_writable_event.h"
|
|||
#include "core/hle/kernel/kernel.h"
|
|||
#include "core/hle/service/kernel_helpers.h"
|
|||
#include "core/hle/service/nvdrv/nvdrv.h"
|
|||
#include "core/hle/service/nvflinger/buffer_queue_core.h"
|
|||
#include "core/hle/service/nvflinger/buffer_queue_producer.h"
|
|||
#include "core/hle/service/nvflinger/consumer_listener.h"
|
|||
#include "core/hle/service/nvflinger/parcel.h"
|
|||
#include "core/hle/service/nvflinger/ui/graphic_buffer.h"
|
|||
#include "core/hle/service/nvflinger/window.h"
|
|||
#include "core/hle/service/vi/vi.h"
|
|||
|
|||
namespace Service::android { |
|||
|
|||
BufferQueueProducer::BufferQueueProducer(Service::KernelHelpers::ServiceContext& service_context_, |
|||
std::shared_ptr<BufferQueueCore> buffer_queue_core_) |
|||
: service_context{service_context_}, core{std::move(buffer_queue_core_)}, slots(core->slots) { |
|||
buffer_wait_event = service_context.CreateEvent("BufferQueue:WaitEvent"); |
|||
} |
|||
|
|||
BufferQueueProducer::~BufferQueueProducer() { |
|||
service_context.CloseEvent(buffer_wait_event); |
|||
} |
|||
|
|||
Status BufferQueueProducer::RequestBuffer(s32 slot, std::shared_ptr<GraphicBuffer>* buf) { |
|||
LOG_DEBUG(Service_NVFlinger, "slot {}", slot); |
|||
|
|||
BufferQueueCore::AutoLock lock(core); |
|||
|
|||
if (core->is_abandoned) { |
|||
LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned"); |
|||
return Status::NoInit; |
|||
} |
|||
if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) { |
|||
LOG_ERROR(Service_NVFlinger, "slot index {} out of range [0, {})", slot, |
|||
BufferQueueDefs::NUM_BUFFER_SLOTS); |
|||
return Status::BadValue; |
|||
} else if (slots[slot].buffer_state != BufferState::Dequeued) { |
|||
LOG_ERROR(Service_NVFlinger, "slot {} is not owned by the producer (state = {})", slot, |
|||
slots[slot].buffer_state); |
|||
return Status::BadValue; |
|||
} |
|||
|
|||
slots[slot].request_buffer_called = true; |
|||
*buf = slots[slot].graphic_buffer; |
|||
|
|||
return Status::NoError; |
|||
} |
|||
|
|||
Status BufferQueueProducer::SetBufferCount(s32 buffer_count) { |
|||
LOG_DEBUG(Service_NVFlinger, "count = {}", buffer_count); |
|||
std::shared_ptr<IConsumerListener> listener; |
|||
|
|||
{ |
|||
BufferQueueCore::AutoLock lock(core); |
|||
core->WaitWhileAllocatingLocked(); |
|||
if (core->is_abandoned) { |
|||
LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned"); |
|||
return Status::NoInit; |
|||
} |
|||
|
|||
if (buffer_count > BufferQueueDefs::NUM_BUFFER_SLOTS) { |
|||
LOG_ERROR(Service_NVFlinger, "buffer_count {} too large (max {})", buffer_count, |
|||
BufferQueueDefs::NUM_BUFFER_SLOTS); |
|||
return Status::BadValue; |
|||
} |
|||
|
|||
// There must be no dequeued buffers when changing the buffer count.
|
|||
for (s32 s{}; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) { |
|||
if (slots[s].buffer_state == BufferState::Dequeued) { |
|||
LOG_ERROR(Service_NVFlinger, "buffer owned by producer"); |
|||
return Status::BadValue; |
|||
} |
|||
} |
|||
|
|||
if (buffer_count == 0) { |
|||
core->override_max_buffer_count = 0; |
|||
core->SignalDequeueCondition(); |
|||
return Status::NoError; |
|||
} |
|||
|
|||
const s32 min_buffer_slots = core->GetMinMaxBufferCountLocked(false); |
|||
if (buffer_count < min_buffer_slots) { |
|||
LOG_ERROR(Service_NVFlinger, "requested buffer count {} is less than minimum {}", |
|||
buffer_count, min_buffer_slots); |
|||
return Status::BadValue; |
|||
} |
|||
|
|||
// Here we are guaranteed that the producer doesn't have any dequeued buffers and will
|
|||
// release all of its buffer references.
|
|||
if (core->GetPreallocatedBufferCountLocked() <= 0) { |
|||
core->FreeAllBuffersLocked(); |
|||
} |
|||
|
|||
core->override_max_buffer_count = buffer_count; |
|||
core->SignalDequeueCondition(); |
|||
buffer_wait_event->GetWritableEvent().Signal(); |
|||
listener = core->consumer_listener; |
|||
} |
|||
|
|||
// Call back without lock held
|
|||
if (listener != nullptr) { |
|||
listener->OnBuffersReleased(); |
|||
} |
|||
|
|||
return Status::NoError; |
|||
} |
|||
|
|||
Status BufferQueueProducer::WaitForFreeSlotThenRelock(bool async, s32* found, |
|||
Status* returnFlags) const { |
|||
bool try_again = true; |
|||
|
|||
while (try_again) { |
|||
if (core->is_abandoned) { |
|||
LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned"); |
|||
return Status::NoInit; |
|||
} |
|||
|
|||
const s32 max_buffer_count = core->GetMaxBufferCountLocked(async); |
|||
if (async && core->override_max_buffer_count) { |
|||
if (core->override_max_buffer_count < max_buffer_count) { |
|||
LOG_ERROR(Service_NVFlinger, "async mode is invalid with buffer count override"); |
|||
return Status::BadValue; |
|||
} |
|||
} |
|||
|
|||
// Free up any buffers that are in slots beyond the max buffer count
|
|||
for (s32 s = max_buffer_count; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) { |
|||
ASSERT(slots[s].buffer_state == BufferState::Free); |
|||
if (slots[s].graphic_buffer != nullptr) { |
|||
core->FreeBufferLocked(s); |
|||
*returnFlags |= Status::ReleaseAllBuffers; |
|||
} |
|||
} |
|||
|
|||
s32 dequeued_count{}; |
|||
s32 acquired_count{}; |
|||
for (s32 s{}; s < max_buffer_count; ++s) { |
|||
switch (slots[s].buffer_state) { |
|||
case BufferState::Dequeued: |
|||
++dequeued_count; |
|||
break; |
|||
case BufferState::Acquired: |
|||
++acquired_count; |
|||
break; |
|||
default: |
|||
break; |
|||
} |
|||
} |
|||
|
|||
// Producers are not allowed to dequeue more than one buffer if they did not set a buffer
|
|||
// count
|
|||
if (!core->override_max_buffer_count && dequeued_count) { |
|||
LOG_ERROR(Service_NVFlinger, |
|||
"can't dequeue multiple buffers without setting the buffer count"); |
|||
return Status::InvalidOperation; |
|||
} |
|||
|
|||
// See whether a buffer has been queued since the last SetBufferCount so we know whether to
|
|||
// perform the min undequeued buffers check below
|
|||
if (core->buffer_has_been_queued) { |
|||
// Make sure the producer is not trying to dequeue more buffers than allowed
|
|||
const s32 new_undequeued_count = max_buffer_count - (dequeued_count + 1); |
|||
const s32 min_undequeued_count = core->GetMinUndequeuedBufferCountLocked(async); |
|||
if (new_undequeued_count < min_undequeued_count) { |
|||
LOG_ERROR(Service_NVFlinger, |
|||
"min undequeued buffer count({}) exceeded (dequeued={} undequeued={})", |
|||
min_undequeued_count, dequeued_count, new_undequeued_count); |
|||
return Status::InvalidOperation; |
|||
} |
|||
} |
|||
|
|||
*found = BufferQueueCore::INVALID_BUFFER_SLOT; |
|||
|
|||
// If we disconnect and reconnect quickly, we can be in a state where our slots are empty
|
|||
// but we have many buffers in the queue. This can cause us to run out of memory if we
|
|||
// outrun the consumer. Wait here if it looks like we have too many buffers queued up.
|
|||
const bool too_many_buffers = core->queue.size() > static_cast<size_t>(max_buffer_count); |
|||
if (too_many_buffers) { |
|||
LOG_ERROR(Service_NVFlinger, "queue size is {}, waiting", core->queue.size()); |
|||
} else { |
|||
if (!core->free_buffers.empty()) { |
|||
auto slot = core->free_buffers.begin(); |
|||
*found = *slot; |
|||
core->free_buffers.erase(slot); |
|||
} else if (core->allow_allocation && !core->free_slots.empty()) { |
|||
auto slot = core->free_slots.begin(); |
|||
// Only return free slots up to the max buffer count
|
|||
if (*slot < max_buffer_count) { |
|||
*found = *slot; |
|||
core->free_slots.erase(slot); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// If no buffer is found, or if the queue has too many buffers outstanding, wait for a
|
|||
// buffer to be acquired or released, or for the max buffer count to change.
|
|||
try_again = (*found == BufferQueueCore::INVALID_BUFFER_SLOT) || too_many_buffers; |
|||
if (try_again) { |
|||
// Return an error if we're in non-blocking mode (producer and consumer are controlled
|
|||
// by the application).
|
|||
if (core->dequeue_buffer_cannot_block && |
|||
(acquired_count <= core->max_acquired_buffer_count)) { |
|||
return Status::WouldBlock; |
|||
} |
|||
|
|||
if (!core->WaitForDequeueCondition()) { |
|||
// We are no longer running
|
|||
return Status::NoError; |
|||
} |
|||
} |
|||
} |
|||
|
|||
return Status::NoError; |
|||
} |
|||
|
|||
Status BufferQueueProducer::DequeueBuffer(s32* out_slot, Fence* out_fence, bool async, u32 width, |
|||
u32 height, PixelFormat format, u32 usage) { |
|||
LOG_DEBUG(Service_NVFlinger, "async={} w={} h={} format={}, usage={}", async ? "true" : "false", |
|||
width, height, format, usage); |
|||
|
|||
if ((width != 0 && height == 0) || (width == 0 && height != 0)) { |
|||
LOG_ERROR(Service_NVFlinger, "invalid size: w={} h={}", width, height); |
|||
return Status::BadValue; |
|||
} |
|||
|
|||
Status return_flags = Status::NoError; |
|||
bool attached_by_consumer = false; |
|||
{ |
|||
BufferQueueCore::AutoLock lock(core); |
|||
core->WaitWhileAllocatingLocked(); |
|||
if (format == PixelFormat::NoFormat) { |
|||
format = core->default_buffer_format; |
|||
} |
|||
|
|||
// Enable the usage bits the consumer requested
|
|||
usage |= core->consumer_usage_bit; |
|||
const bool use_default_size = !width && !height; |
|||
if (use_default_size) { |
|||
width = core->default_width; |
|||
height = core->default_height; |
|||
} |
|||
|
|||
s32 found = BufferItem::INVALID_BUFFER_SLOT; |
|||
while (found == BufferItem::INVALID_BUFFER_SLOT) { |
|||
Status status = WaitForFreeSlotThenRelock(async, &found, &return_flags); |
|||
if (status != Status::NoError) { |
|||
return status; |
|||
} |
|||
|
|||
// This should not happen
|
|||
if (found == BufferQueueCore::INVALID_BUFFER_SLOT) { |
|||
LOG_DEBUG(Service_NVFlinger, "no available buffer slots"); |
|||
return Status::Busy; |
|||
} |
|||
|
|||
const std::shared_ptr<GraphicBuffer>& buffer(slots[found].graphic_buffer); |
|||
|
|||
// If we are not allowed to allocate new buffers, WaitForFreeSlotThenRelock must have
|
|||
// returned a slot containing a buffer. If this buffer would require reallocation to
|
|||
// meet the requested attributes, we free it and attempt to get another one.
|
|||
if (!core->allow_allocation) { |
|||
if (buffer->NeedsReallocation(width, height, format, usage)) { |
|||
core->FreeBufferLocked(found); |
|||
found = BufferItem::INVALID_BUFFER_SLOT; |
|||
continue; |
|||
} |
|||
} |
|||
} |
|||
|
|||
*out_slot = found; |
|||
attached_by_consumer = slots[found].attached_by_consumer; |
|||
slots[found].buffer_state = BufferState::Dequeued; |
|||
|
|||
const std::shared_ptr<GraphicBuffer>& buffer(slots[found].graphic_buffer); |
|||
|
|||
if ((buffer == nullptr) || buffer->NeedsReallocation(width, height, format, usage)) { |
|||
slots[found].acquire_called = false; |
|||
slots[found].graphic_buffer = nullptr; |
|||
slots[found].request_buffer_called = false; |
|||
slots[found].fence = Fence::NoFence(); |
|||
core->buffer_age = 0; |
|||
return_flags |= Status::BufferNeedsReallocation; |
|||
} else { |
|||
// We add 1 because that will be the frame number when this buffer
|
|||
// is queued
|
|||
core->buffer_age = core->frame_counter + 1 - slots[found].frame_number; |
|||
} |
|||
|
|||
LOG_DEBUG(Service_NVFlinger, "setting buffer age to {}", core->buffer_age); |
|||
|
|||
*out_fence = slots[found].fence; |
|||
|
|||
slots[found].fence = Fence::NoFence(); |
|||
} |
|||
|
|||
if ((return_flags & Status::BufferNeedsReallocation) != Status::None) { |
|||
LOG_DEBUG(Service_NVFlinger, "allocating a new buffer for slot {}", *out_slot); |
|||
|
|||
auto graphic_buffer = std::make_shared<GraphicBuffer>(width, height, format, usage); |
|||
if (graphic_buffer == nullptr) { |
|||
LOG_ERROR(Service_NVFlinger, "creating GraphicBuffer failed"); |
|||
return Status::NoMemory; |
|||
} |
|||
|
|||
{ |
|||
BufferQueueCore::AutoLock lock(core); |
|||
if (core->is_abandoned) { |
|||
LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned"); |
|||
return Status::NoInit; |
|||
} |
|||
|
|||
slots[*out_slot].graphic_buffer = graphic_buffer; |
|||
} |
|||
} |
|||
|
|||
if (attached_by_consumer) { |
|||
return_flags |= Status::BufferNeedsReallocation; |
|||
} |
|||
|
|||
LOG_DEBUG(Service_NVFlinger, "returning slot={} frame={}, flags={}", *out_slot, |
|||
slots[*out_slot].frame_number, return_flags); |
|||
return return_flags; |
|||
} |
|||
|
|||
Status BufferQueueProducer::DetachBuffer(s32 slot) { |
|||
LOG_DEBUG(Service_NVFlinger, "slot {}", slot); |
|||
|
|||
BufferQueueCore::AutoLock lock(core); |
|||
if (core->is_abandoned) { |
|||
LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned"); |
|||
return Status::NoInit; |
|||
} |
|||
|
|||
if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) { |
|||
LOG_ERROR(Service_NVFlinger, "slot {} out of range [0, {})", slot, |
|||
BufferQueueDefs::NUM_BUFFER_SLOTS); |
|||
return Status::BadValue; |
|||
} else if (slots[slot].buffer_state != BufferState::Dequeued) { |
|||
LOG_ERROR(Service_NVFlinger, "slot {} is not owned by the producer (state = {})", slot, |
|||
slots[slot].buffer_state); |
|||
return Status::BadValue; |
|||
} else if (!slots[slot].request_buffer_called) { |
|||
LOG_ERROR(Service_NVFlinger, "buffer in slot {} has not been requested", slot); |
|||
return Status::BadValue; |
|||
} |
|||
|
|||
core->FreeBufferLocked(slot); |
|||
core->SignalDequeueCondition(); |
|||
|
|||
return Status::NoError; |
|||
} |
|||
|
|||
Status BufferQueueProducer::DetachNextBuffer(std::shared_ptr<GraphicBuffer>* out_buffer, |
|||
Fence* out_fence) { |
|||
if (out_buffer == nullptr) { |
|||
LOG_ERROR(Service_NVFlinger, "out_buffer must not be nullptr"); |
|||
return Status::BadValue; |
|||
} else if (out_fence == nullptr) { |
|||
LOG_ERROR(Service_NVFlinger, "out_fence must not be nullptr"); |
|||
return Status::BadValue; |
|||
} |
|||
|
|||
BufferQueueCore::AutoLock lock(core); |
|||
|
|||
core->WaitWhileAllocatingLocked(); |
|||
|
|||
if (core->is_abandoned) { |
|||
LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned"); |
|||
return Status::NoInit; |
|||
} |
|||
if (core->free_buffers.empty()) { |
|||
return Status::NoMemory; |
|||
} |
|||
|
|||
const s32 found = core->free_buffers.front(); |
|||
core->free_buffers.remove(found); |
|||
|
|||
LOG_DEBUG(Service_NVFlinger, "Detached slot {}", found); |
|||
|
|||
*out_buffer = slots[found].graphic_buffer; |
|||
*out_fence = slots[found].fence; |
|||
|
|||
core->FreeBufferLocked(found); |
|||
|
|||
return Status::NoError; |
|||
} |
|||
|
|||
Status BufferQueueProducer::AttachBuffer(s32* out_slot, |
|||
const std::shared_ptr<GraphicBuffer>& buffer) { |
|||
if (out_slot == nullptr) { |
|||
LOG_ERROR(Service_NVFlinger, "out_slot must not be nullptr"); |
|||
return Status::BadValue; |
|||
} else if (buffer == nullptr) { |
|||
LOG_ERROR(Service_NVFlinger, "Cannot attach nullptr buffer"); |
|||
return Status::BadValue; |
|||
} |
|||
|
|||
BufferQueueCore::AutoLock lock(core); |
|||
core->WaitWhileAllocatingLocked(); |
|||
|
|||
Status return_flags = Status::NoError; |
|||
s32 found{}; |
|||
|
|||
const auto status = WaitForFreeSlotThenRelock(false, &found, &return_flags); |
|||
if (status != Status::NoError) { |
|||
return status; |
|||
} |
|||
|
|||
if (found == BufferQueueCore::INVALID_BUFFER_SLOT) { |
|||
LOG_ERROR(Service_NVFlinger, "No available buffer slots"); |
|||
return Status::Busy; |
|||
} |
|||
|
|||
*out_slot = found; |
|||
|
|||
LOG_DEBUG(Service_NVFlinger, "Returning slot {} flags={}", *out_slot, return_flags); |
|||
|
|||
slots[*out_slot].graphic_buffer = buffer; |
|||
slots[*out_slot].buffer_state = BufferState::Dequeued; |
|||
slots[*out_slot].fence = Fence::NoFence(); |
|||
slots[*out_slot].request_buffer_called = true; |
|||
|
|||
return return_flags; |
|||
} |
|||
|
|||
Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input, |
|||
QueueBufferOutput* output) { |
|||
s64 timestamp{}; |
|||
bool is_auto_timestamp{}; |
|||
Common::Rectangle<s32> crop; |
|||
NativeWindowScalingMode scaling_mode{}; |
|||
NativeWindowTransform transform; |
|||
u32 sticky_transform_{}; |
|||
bool async{}; |
|||
s32 swap_interval{}; |
|||
Fence fence{}; |
|||
|
|||
input.Deflate(×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_NVFlinger, "unknown scaling mode {}", scaling_mode); |
|||
return Status::BadValue; |
|||
} |
|||
|
|||
std::shared_ptr<IConsumerListener> frameAvailableListener; |
|||
std::shared_ptr<IConsumerListener> frameReplacedListener; |
|||
s32 callback_ticket{}; |
|||
BufferItem item; |
|||
|
|||
{ |
|||
BufferQueueCore::AutoLock lock(core); |
|||
|
|||
if (core->is_abandoned) { |
|||
LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned"); |
|||
return Status::NoInit; |
|||
} |
|||
|
|||
const s32 max_buffer_count = core->GetMaxBufferCountLocked(async); |
|||
if (async && core->override_max_buffer_count) { |
|||
if (core->override_max_buffer_count < max_buffer_count) { |
|||
LOG_ERROR(Service_NVFlinger, "async mode is invalid with " |
|||
"buffer count override"); |
|||
return Status::BadValue; |
|||
} |
|||
} |
|||
|
|||
if (slot < 0 || slot >= max_buffer_count) { |
|||
LOG_ERROR(Service_NVFlinger, "slot index {} out of range [0, {})", slot, |
|||
max_buffer_count); |
|||
return Status::BadValue; |
|||
} else if (slots[slot].buffer_state != BufferState::Dequeued) { |
|||
LOG_ERROR(Service_NVFlinger, |
|||
"slot {} is not owned by the producer " |
|||
"(state = {})", |
|||
slot, slots[slot].buffer_state); |
|||
return Status::BadValue; |
|||
} else if (!slots[slot].request_buffer_called) { |
|||
LOG_ERROR(Service_NVFlinger, |
|||
"slot {} was queued without requesting " |
|||
"a buffer", |
|||
slot); |
|||
return Status::BadValue; |
|||
} |
|||
|
|||
LOG_DEBUG(Service_NVFlinger, |
|||
"slot={} frame={} time={} crop=[{},{},{},{}] transform={} scale={}", slot, |
|||
core->frame_counter + 1, timestamp, crop.Left(), crop.Top(), crop.Right(), |
|||
crop.Bottom(), transform, scaling_mode); |
|||
|
|||
const std::shared_ptr<GraphicBuffer>& graphic_buffer(slots[slot].graphic_buffer); |
|||
Common::Rectangle<s32> buffer_rect(graphic_buffer->Width(), graphic_buffer->Height()); |
|||
Common::Rectangle<s32> cropped_rect; |
|||
[[maybe_unused]] const bool unused = crop.Intersect(buffer_rect, &cropped_rect); |
|||
|
|||
if (cropped_rect != crop) { |
|||
LOG_ERROR(Service_NVFlinger, "crop rect is not contained within the buffer in slot {}", |
|||
slot); |
|||
return Status::BadValue; |
|||
} |
|||
|
|||
slots[slot].fence = fence; |
|||
slots[slot].buffer_state = BufferState::Queued; |
|||
++core->frame_counter; |
|||
slots[slot].frame_number = core->frame_counter; |
|||
|
|||
item.acquire_called = slots[slot].acquire_called; |
|||
item.graphic_buffer = slots[slot].graphic_buffer; |
|||
item.crop = crop; |
|||
item.transform = transform & ~NativeWindowTransform::InverseDisplay; |
|||
item.transform_to_display_inverse = |
|||
(transform & NativeWindowTransform::InverseDisplay) != NativeWindowTransform::None; |
|||
item.scaling_mode = static_cast<u32>(scaling_mode); |
|||
item.timestamp = timestamp; |
|||
item.is_auto_timestamp = is_auto_timestamp; |
|||
item.frame_number = core->frame_counter; |
|||
item.slot = slot; |
|||
item.fence = fence; |
|||
item.is_droppable = core->dequeue_buffer_cannot_block || async; |
|||
item.swap_interval = swap_interval; |
|||
sticky_transform = sticky_transform_; |
|||
|
|||
if (core->queue.empty()) { |
|||
// When the queue is empty, we can simply queue this buffer
|
|||
core->queue.push_back(item); |
|||
frameAvailableListener = core->consumer_listener; |
|||
} else { |
|||
// When the queue is not empty, we need to look at the front buffer
|
|||
// state to see if we need to replace it
|
|||
auto front(core->queue.begin()); |
|||
|
|||
if (front->is_droppable) { |
|||
// If the front queued buffer is still being tracked, we first
|
|||
// mark it as freed
|
|||
if (core->StillTracking(*front)) { |
|||
slots[front->slot].buffer_state = BufferState::Free; |
|||
core->free_buffers.push_front(front->slot); |
|||
} |
|||
// Overwrite the droppable buffer with the incoming one
|
|||
*front = item; |
|||
frameReplacedListener = core->consumer_listener; |
|||
} else { |
|||
core->queue.push_back(item); |
|||
frameAvailableListener = core->consumer_listener; |
|||
} |
|||
} |
|||
|
|||
core->buffer_has_been_queued = true; |
|||
core->SignalDequeueCondition(); |
|||
output->Inflate(core->default_width, core->default_height, core->transform_hint, |
|||
static_cast<u32>(core->queue.size())); |
|||
|
|||
// Take a ticket for the callback functions
|
|||
callback_ticket = next_callback_ticket++; |
|||
} |
|||
|
|||
// Don't send the GraphicBuffer through the callback, and don't send the slot number, since the
|
|||
// consumer shouldn't need it
|
|||
item.graphic_buffer.reset(); |
|||
item.slot = BufferItem::INVALID_BUFFER_SLOT; |
|||
|
|||
// Call back without the main BufferQueue lock held, but with the callback lock held so we can
|
|||
// ensure that callbacks occur in order
|
|||
{ |
|||
std::unique_lock lock(callback_mutex); |
|||
while (callback_ticket != current_callback_ticket) { |
|||
std::unique_lock<std::mutex> lk(callback_mutex); |
|||
callback_condition.wait(lk); |
|||
} |
|||
|
|||
if (frameAvailableListener != nullptr) { |
|||
frameAvailableListener->OnFrameAvailable(item); |
|||
} else if (frameReplacedListener != nullptr) { |
|||
frameReplacedListener->OnFrameReplaced(item); |
|||
} |
|||
|
|||
++current_callback_ticket; |
|||
callback_condition.notify_all(); |
|||
} |
|||
|
|||
return Status::NoError; |
|||
} |
|||
|
|||
void BufferQueueProducer::CancelBuffer(s32 slot, const Fence& fence) { |
|||
LOG_DEBUG(Service_NVFlinger, "slot {}", slot); |
|||
|
|||
BufferQueueCore::AutoLock lock(core); |
|||
|
|||
if (core->is_abandoned) { |
|||
LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned"); |
|||
return; |
|||
} |
|||
|
|||
if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) { |
|||
LOG_ERROR(Service_NVFlinger, "slot index {} out of range [0, {})", slot, |
|||
BufferQueueDefs::NUM_BUFFER_SLOTS); |
|||
return; |
|||
} else if (slots[slot].buffer_state != BufferState::Dequeued) { |
|||
LOG_ERROR(Service_NVFlinger, "slot {} is not owned by the producer (state = {})", slot, |
|||
slots[slot].buffer_state); |
|||
return; |
|||
} |
|||
|
|||
core->free_buffers.push_front(slot); |
|||
slots[slot].buffer_state = BufferState::Free; |
|||
slots[slot].fence = fence; |
|||
|
|||
core->SignalDequeueCondition(); |
|||
buffer_wait_event->GetWritableEvent().Signal(); |
|||
} |
|||
|
|||
Status BufferQueueProducer::Query(NativeWindow what, s32* out_value) { |
|||
BufferQueueCore::AutoLock lock(core); |
|||
|
|||
if (out_value == nullptr) { |
|||
LOG_ERROR(Service_NVFlinger, "outValue was nullptr"); |
|||
return Status::BadValue; |
|||
} |
|||
|
|||
if (core->is_abandoned) { |
|||
LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned"); |
|||
return Status::NoInit; |
|||
} |
|||
|
|||
u32 value{}; |
|||
switch (what) { |
|||
case NativeWindow::Width: |
|||
value = core->default_width; |
|||
break; |
|||
case NativeWindow::Height: |
|||
value = core->default_height; |
|||
break; |
|||
case NativeWindow::Format: |
|||
value = static_cast<u32>(core->default_buffer_format); |
|||
break; |
|||
case NativeWindow::MinUndequeedBuffers: |
|||
value = core->GetMinUndequeuedBufferCountLocked(false); |
|||
break; |
|||
case NativeWindow::StickyTransform: |
|||
value = sticky_transform; |
|||
break; |
|||
case NativeWindow::ConsumerRunningBehind: |
|||
value = (core->queue.size() > 1); |
|||
break; |
|||
case NativeWindow::ConsumerUsageBits: |
|||
value = core->consumer_usage_bit; |
|||
break; |
|||
case NativeWindow::BufferAge: |
|||
if (core->buffer_age > INT32_MAX) { |
|||
value = 0; |
|||
} else { |
|||
value = static_cast<u32>(core->buffer_age); |
|||
} |
|||
break; |
|||
default: |
|||
UNREACHABLE(); |
|||
return Status::BadValue; |
|||
} |
|||
|
|||
LOG_DEBUG(Service_NVFlinger, "what = {}, value = {}", what, value); |
|||
|
|||
*out_value = static_cast<s32>(value); |
|||
|
|||
return Status::NoError; |
|||
} |
|||
|
|||
Status BufferQueueProducer::Connect(const std::shared_ptr<IProducerListener>& listener, |
|||
NativeWindowApi api, bool producer_controlled_by_app, |
|||
QueueBufferOutput* output) { |
|||
BufferQueueCore::AutoLock lock(core); |
|||
|
|||
LOG_DEBUG(Service_NVFlinger, "api = {} producer_controlled_by_app = {}", api, |
|||
producer_controlled_by_app); |
|||
|
|||
if (core->is_abandoned) { |
|||
LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned"); |
|||
return Status::NoInit; |
|||
} |
|||
|
|||
if (core->consumer_listener == nullptr) { |
|||
LOG_ERROR(Service_NVFlinger, "BufferQueue has no consumer"); |
|||
return Status::NoInit; |
|||
} |
|||
|
|||
if (output == nullptr) { |
|||
LOG_ERROR(Service_NVFlinger, "output was nullptr"); |
|||
return Status::BadValue; |
|||
} |
|||
|
|||
if (core->connected_api != NativeWindowApi::NoConnectedApi) { |
|||
LOG_ERROR(Service_NVFlinger, "already connected (cur = {} req = {})", core->connected_api, |
|||
api); |
|||
return Status::BadValue; |
|||
} |
|||
|
|||
Status status = Status::NoError; |
|||
switch (api) { |
|||
case NativeWindowApi::Egl: |
|||
case NativeWindowApi::Cpu: |
|||
case NativeWindowApi::Media: |
|||
case NativeWindowApi::Camera: |
|||
core->connected_api = api; |
|||
output->Inflate(core->default_width, core->default_height, core->transform_hint, |
|||
static_cast<u32>(core->queue.size())); |
|||
core->connected_producer_listener = listener; |
|||
break; |
|||
default: |
|||
LOG_ERROR(Service_NVFlinger, "unknown api = {}", api); |
|||
status = Status::BadValue; |
|||
break; |
|||
} |
|||
|
|||
core->buffer_has_been_queued = false; |
|||
core->dequeue_buffer_cannot_block = |
|||
core->consumer_controlled_by_app && producer_controlled_by_app; |
|||
core->allow_allocation = true; |
|||
|
|||
return status; |
|||
} |
|||
|
|||
Status BufferQueueProducer::Disconnect(NativeWindowApi api) { |
|||
LOG_DEBUG(Service_NVFlinger, "api = {}", api); |
|||
|
|||
Status status = Status::NoError; |
|||
std::shared_ptr<IConsumerListener> listener; |
|||
|
|||
{ |
|||
BufferQueueCore::AutoLock lock(core); |
|||
|
|||
core->WaitWhileAllocatingLocked(); |
|||
|
|||
if (core->is_abandoned) { |
|||
// Disconnecting after the surface has been abandoned is a no-op.
|
|||
return Status::NoError; |
|||
} |
|||
|
|||
switch (api) { |
|||
case NativeWindowApi::Egl: |
|||
case NativeWindowApi::Cpu: |
|||
case NativeWindowApi::Media: |
|||
case NativeWindowApi::Camera: |
|||
if (core->connected_api == api) { |
|||
core->FreeAllBuffersLocked(); |
|||
core->connected_producer_listener = nullptr; |
|||
core->connected_api = NativeWindowApi::NoConnectedApi; |
|||
core->SignalDequeueCondition(); |
|||
buffer_wait_event->GetWritableEvent().Signal(); |
|||
listener = core->consumer_listener; |
|||
} else if (core->connected_api != NativeWindowApi::NoConnectedApi) { |
|||
LOG_ERROR(Service_NVFlinger, "still connected to another api (cur = {} req = {})", |
|||
core->connected_api, api); |
|||
status = Status::BadValue; |
|||
} |
|||
break; |
|||
default: |
|||
LOG_ERROR(Service_NVFlinger, "unknown api = {}", api); |
|||
status = Status::BadValue; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
// Call back without lock held
|
|||
if (listener != nullptr) { |
|||
listener->OnBuffersReleased(); |
|||
} |
|||
|
|||
return status; |
|||
} |
|||
|
|||
Status BufferQueueProducer::SetPreallocatedBuffer(s32 slot, |
|||
const std::shared_ptr<GraphicBuffer>& buffer) { |
|||
LOG_DEBUG(Service_NVFlinger, "slot {}", slot); |
|||
|
|||
if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) { |
|||
return Status::BadValue; |
|||
} |
|||
|
|||
BufferQueueCore::AutoLock lock(core); |
|||
|
|||
slots[slot] = {}; |
|||
slots[slot].graphic_buffer = buffer; |
|||
|
|||
// Most games preallocate a buffer and pass a valid buffer here. However, it is possible for
|
|||
// this to be called with an empty buffer, Naruto Ultimate Ninja Storm is a game that does this.
|
|||
if (buffer) { |
|||
slots[slot].is_preallocated = true; |
|||
|
|||
core->override_max_buffer_count = core->GetPreallocatedBufferCountLocked(); |
|||
core->default_width = buffer->Width(); |
|||
core->default_height = buffer->Height(); |
|||
core->default_buffer_format = buffer->Format(); |
|||
} |
|||
|
|||
core->SignalDequeueCondition(); |
|||
buffer_wait_event->GetWritableEvent().Signal(); |
|||
|
|||
return Status::NoError; |
|||
} |
|||
|
|||
void BufferQueueProducer::Transact(Kernel::HLERequestContext& ctx, TransactionId code, u32 flags) { |
|||
Status status{Status::NoError}; |
|||
Parcel parcel_in{ctx.ReadBuffer()}; |
|||
Parcel parcel_out{}; |
|||
|
|||
switch (code) { |
|||
case TransactionId::Connect: { |
|||
const auto enable_listener = parcel_in.Read<bool>(); |
|||
const auto api = parcel_in.Read<NativeWindowApi>(); |
|||
const auto producer_controlled_by_app = parcel_in.Read<bool>(); |
|||
|
|||
UNIMPLEMENTED_IF_MSG(enable_listener, "Listener is unimplemented!"); |
|||
|
|||
std::shared_ptr<IProducerListener> listener; |
|||
QueueBufferOutput output{}; |
|||
|
|||
status = Connect(listener, api, producer_controlled_by_app, &output); |
|||
|
|||
parcel_out.Write(output); |
|||
break; |
|||
} |
|||
case TransactionId::SetPreallocatedBuffer: { |
|||
const auto slot = parcel_in.Read<s32>(); |
|||
const auto buffer = parcel_in.ReadObject<GraphicBuffer>(); |
|||
|
|||
status = SetPreallocatedBuffer(slot, buffer); |
|||
break; |
|||
} |
|||
case TransactionId::DequeueBuffer: { |
|||
const auto is_async = parcel_in.Read<bool>(); |
|||
const auto width = parcel_in.Read<u32>(); |
|||
const auto height = parcel_in.Read<u32>(); |
|||
const auto pixel_format = parcel_in.Read<PixelFormat>(); |
|||
const auto usage = parcel_in.Read<u32>(); |
|||
|
|||
s32 slot{}; |
|||
Fence fence{}; |
|||
|
|||
status = DequeueBuffer(&slot, &fence, is_async, width, height, pixel_format, usage); |
|||
|
|||
parcel_out.Write(slot); |
|||
parcel_out.WriteObject(&fence); |
|||
break; |
|||
} |
|||
case TransactionId::RequestBuffer: { |
|||
const auto slot = parcel_in.Read<s32>(); |
|||
|
|||
std::shared_ptr<GraphicBuffer> buf; |
|||
|
|||
status = RequestBuffer(slot, &buf); |
|||
|
|||
parcel_out.WriteObject(buf); |
|||
break; |
|||
} |
|||
case TransactionId::QueueBuffer: { |
|||
const auto slot = parcel_in.Read<s32>(); |
|||
|
|||
QueueBufferInput input{parcel_in}; |
|||
QueueBufferOutput output; |
|||
|
|||
status = QueueBuffer(slot, input, &output); |
|||
|
|||
parcel_out.Write(output); |
|||
break; |
|||
} |
|||
case TransactionId::Query: { |
|||
const auto what = parcel_in.Read<NativeWindow>(); |
|||
|
|||
s32 value{}; |
|||
|
|||
status = Query(what, &value); |
|||
|
|||
parcel_out.Write(value); |
|||
break; |
|||
} |
|||
case TransactionId::CancelBuffer: { |
|||
const auto slot = parcel_in.Read<s32>(); |
|||
const auto fence = parcel_in.ReadFlattened<Fence>(); |
|||
|
|||
CancelBuffer(slot, fence); |
|||
break; |
|||
} |
|||
case TransactionId::Disconnect: { |
|||
const auto api = parcel_in.Read<NativeWindowApi>(); |
|||
|
|||
status = Disconnect(api); |
|||
break; |
|||
} |
|||
case TransactionId::DetachBuffer: { |
|||
const auto slot = parcel_in.Read<s32>(); |
|||
|
|||
status = DetachBuffer(slot); |
|||
break; |
|||
} |
|||
case TransactionId::SetBufferCount: { |
|||
const auto buffer_count = parcel_in.Read<s32>(); |
|||
|
|||
status = SetBufferCount(buffer_count); |
|||
break; |
|||
} |
|||
case TransactionId::GetBufferHistory: |
|||
LOG_WARNING(Service_NVFlinger, "(STUBBED) called, transaction=GetBufferHistory"); |
|||
break; |
|||
default: |
|||
ASSERT_MSG(false, "Unimplemented TransactionId {}", code); |
|||
break; |
|||
} |
|||
|
|||
parcel_out.Write(status); |
|||
|
|||
ctx.WriteBuffer(parcel_out.Serialize()); |
|||
} |
|||
|
|||
Kernel::KReadableEvent& BufferQueueProducer::GetNativeHandle() { |
|||
return buffer_wait_event->GetReadableEvent(); |
|||
} |
|||
|
|||
} // namespace Service::android
|
|||
@ -0,0 +1,83 @@ |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
// Copyright 2021 yuzu Emulator Project |
|||
// Copyright 2014 The Android Open Source Project |
|||
// Parts of this implementation were base on: |
|||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferQueueProducer.h |
|||
|
|||
#pragma once |
|||
|
|||
#include <condition_variable> |
|||
#include <memory> |
|||
#include <mutex> |
|||
|
|||
#include "common/common_funcs.h" |
|||
#include "core/hle/service/nvdrv/nvdata.h" |
|||
#include "core/hle/service/nvflinger/binder.h" |
|||
#include "core/hle/service/nvflinger/buffer_queue_defs.h" |
|||
#include "core/hle/service/nvflinger/buffer_slot.h" |
|||
#include "core/hle/service/nvflinger/graphic_buffer_producer.h" |
|||
#include "core/hle/service/nvflinger/pixel_format.h" |
|||
#include "core/hle/service/nvflinger/status.h" |
|||
#include "core/hle/service/nvflinger/window.h" |
|||
|
|||
namespace Kernel { |
|||
class KernelCore; |
|||
class KEvent; |
|||
class KReadableEvent; |
|||
class KWritableEvent; |
|||
} // namespace Kernel |
|||
|
|||
namespace Service::KernelHelpers { |
|||
class ServiceContext; |
|||
} // namespace Service::KernelHelpers |
|||
|
|||
namespace Service::android { |
|||
|
|||
class BufferQueueCore; |
|||
class IProducerListener; |
|||
|
|||
class BufferQueueProducer final : public IBinder { |
|||
public: |
|||
explicit BufferQueueProducer(Service::KernelHelpers::ServiceContext& service_context_, |
|||
std::shared_ptr<BufferQueueCore> buffer_queue_core_); |
|||
~BufferQueueProducer(); |
|||
|
|||
void Transact(Kernel::HLERequestContext& ctx, android::TransactionId code, u32 flags) override; |
|||
|
|||
Kernel::KReadableEvent& GetNativeHandle() override; |
|||
|
|||
public: |
|||
Status RequestBuffer(s32 slot, std::shared_ptr<GraphicBuffer>* buf); |
|||
Status SetBufferCount(s32 buffer_count); |
|||
Status DequeueBuffer(s32* out_slot, android::Fence* out_fence, bool async, u32 width, |
|||
u32 height, PixelFormat format, u32 usage); |
|||
Status DetachBuffer(s32 slot); |
|||
Status DetachNextBuffer(std::shared_ptr<GraphicBuffer>* out_buffer, Fence* out_fence); |
|||
Status AttachBuffer(s32* outSlot, const std::shared_ptr<GraphicBuffer>& buffer); |
|||
Status QueueBuffer(s32 slot, const QueueBufferInput& input, QueueBufferOutput* output); |
|||
void CancelBuffer(s32 slot, const Fence& fence); |
|||
Status Query(NativeWindow what, s32* out_value); |
|||
Status Connect(const std::shared_ptr<IProducerListener>& listener, NativeWindowApi api, |
|||
bool producer_controlled_by_app, QueueBufferOutput* output); |
|||
|
|||
Status Disconnect(NativeWindowApi api); |
|||
Status SetPreallocatedBuffer(s32 slot, const std::shared_ptr<GraphicBuffer>& buffer); |
|||
|
|||
private: |
|||
BufferQueueProducer(const BufferQueueProducer&) = delete; |
|||
|
|||
Status WaitForFreeSlotThenRelock(bool async, s32* found, Status* returnFlags) const; |
|||
|
|||
Kernel::KEvent* buffer_wait_event{}; |
|||
Service::KernelHelpers::ServiceContext& service_context; |
|||
|
|||
std::shared_ptr<BufferQueueCore> core; |
|||
BufferQueueDefs::SlotsType& slots; |
|||
u32 sticky_transform{}; |
|||
std::mutex callback_mutex; |
|||
s32 next_callback_ticket{}; |
|||
s32 current_callback_ticket{}; |
|||
std::condition_variable callback_condition; |
|||
}; |
|||
|
|||
} // namespace Service::android |
|||
@ -0,0 +1,39 @@ |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
// Copyright 2021 yuzu Emulator Project |
|||
// Copyright 2014 The Android Open Source Project |
|||
// Parts of this implementation were base on: |
|||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferSlot.h |
|||
|
|||
#pragma once |
|||
|
|||
#include <memory> |
|||
|
|||
#include "common/common_types.h" |
|||
#include "core/hle/service/nvflinger/ui/fence.h" |
|||
|
|||
namespace Service::android { |
|||
|
|||
class GraphicBuffer; |
|||
|
|||
enum class BufferState : u32 { |
|||
Free = 0, |
|||
Dequeued = 1, |
|||
Queued = 2, |
|||
Acquired = 3, |
|||
}; |
|||
|
|||
struct BufferSlot final { |
|||
constexpr BufferSlot() = default; |
|||
|
|||
std::shared_ptr<GraphicBuffer> graphic_buffer; |
|||
BufferState buffer_state{BufferState::Free}; |
|||
bool request_buffer_called{}; |
|||
u64 frame_number{}; |
|||
Fence fence; |
|||
bool acquire_called{}; |
|||
bool needs_cleanup_on_release{}; |
|||
bool attached_by_consumer{}; |
|||
bool is_preallocated{}; |
|||
}; |
|||
|
|||
} // namespace Service::android |
|||
@ -0,0 +1,25 @@ |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
// Copyright 2021 yuzu Emulator Project |
|||
|
|||
#pragma once |
|||
|
|||
#include "common/common_types.h" |
|||
|
|||
namespace Service::android { |
|||
|
|||
enum class BufferTransformFlags : u32 { |
|||
/// No transform flags are set |
|||
Unset = 0x00, |
|||
/// Flip source image horizontally (around the vertical axis) |
|||
FlipH = 0x01, |
|||
/// Flip source image vertically (around the horizontal axis) |
|||
FlipV = 0x02, |
|||
/// Rotate source image 90 degrees clockwise |
|||
Rotate90 = 0x04, |
|||
/// Rotate source image 180 degrees |
|||
Rotate180 = 0x03, |
|||
/// Rotate source image 270 degrees clockwise |
|||
Rotate270 = 0x07, |
|||
}; |
|||
|
|||
} // namespace Service::android |
|||
@ -0,0 +1,130 @@ |
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|||
// Copyright 2021 yuzu Emulator Project
|
|||
// Copyright 2010 The Android Open Source Project
|
|||
// Parts of this implementation were base on:
|
|||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/ConsumerBase.cpp
|
|||
|
|||
#include "common/assert.h"
|
|||
#include "common/logging/log.h"
|
|||
#include "core/hle/service/nvflinger/buffer_item.h"
|
|||
#include "core/hle/service/nvflinger/buffer_queue_consumer.h"
|
|||
#include "core/hle/service/nvflinger/buffer_queue_core.h"
|
|||
#include "core/hle/service/nvflinger/consumer_base.h"
|
|||
#include "core/hle/service/nvflinger/ui/graphic_buffer.h"
|
|||
|
|||
namespace Service::android { |
|||
|
|||
ConsumerBase::ConsumerBase(std::unique_ptr<BufferQueueConsumer> consumer_) |
|||
: consumer{std::move(consumer_)} {} |
|||
|
|||
ConsumerBase::~ConsumerBase() { |
|||
std::unique_lock lock(mutex); |
|||
|
|||
ASSERT_MSG(is_abandoned, "consumer is not abandoned!"); |
|||
} |
|||
|
|||
void ConsumerBase::Connect(bool controlled_by_app) { |
|||
consumer->Connect(shared_from_this(), controlled_by_app); |
|||
} |
|||
|
|||
void ConsumerBase::FreeBufferLocked(s32 slot_index) { |
|||
LOG_DEBUG(Service_NVFlinger, "slot_index={}", slot_index); |
|||
|
|||
slots[slot_index].graphic_buffer = nullptr; |
|||
slots[slot_index].fence = Fence::NoFence(); |
|||
slots[slot_index].frame_number = 0; |
|||
} |
|||
|
|||
void ConsumerBase::OnFrameAvailable(const BufferItem& item) { |
|||
std::unique_lock lock(mutex); |
|||
LOG_DEBUG(Service_NVFlinger, "called"); |
|||
} |
|||
|
|||
void ConsumerBase::OnFrameReplaced(const BufferItem& item) { |
|||
std::unique_lock lock(mutex); |
|||
LOG_DEBUG(Service_NVFlinger, "called"); |
|||
} |
|||
|
|||
void ConsumerBase::OnBuffersReleased() { |
|||
std::unique_lock lock(mutex); |
|||
LOG_DEBUG(Service_NVFlinger, "called"); |
|||
} |
|||
|
|||
void ConsumerBase::OnSidebandStreamChanged() {} |
|||
|
|||
Status ConsumerBase::AcquireBufferLocked(BufferItem* item, std::chrono::nanoseconds present_when, |
|||
u64 max_frame_number) { |
|||
if (is_abandoned) { |
|||
LOG_ERROR(Service_NVFlinger, "consumer is abandoned!"); |
|||
return Status::NoInit; |
|||
} |
|||
|
|||
Status err = consumer->AcquireBuffer(item, present_when, max_frame_number); |
|||
if (err != Status::NoError) { |
|||
return err; |
|||
} |
|||
|
|||
if (item->graphic_buffer != nullptr) { |
|||
if (slots[item->slot].graphic_buffer != nullptr) { |
|||
FreeBufferLocked(item->slot); |
|||
} |
|||
slots[item->slot].graphic_buffer = item->graphic_buffer; |
|||
} |
|||
|
|||
slots[item->slot].frame_number = item->frame_number; |
|||
slots[item->slot].fence = item->fence; |
|||
|
|||
LOG_DEBUG(Service_NVFlinger, "slot={}", item->slot); |
|||
|
|||
return Status::NoError; |
|||
} |
|||
|
|||
Status ConsumerBase::AddReleaseFenceLocked(s32 slot, |
|||
const std::shared_ptr<GraphicBuffer> graphic_buffer, |
|||
const Fence& fence) { |
|||
LOG_DEBUG(Service_NVFlinger, "slot={}", slot); |
|||
|
|||
// If consumer no longer tracks this graphic_buffer, we can safely
|
|||
// drop this fence, as it will never be received by the producer.
|
|||
|
|||
if (!StillTracking(slot, graphic_buffer)) { |
|||
return Status::NoError; |
|||
} |
|||
|
|||
slots[slot].fence = fence; |
|||
|
|||
return Status::NoError; |
|||
} |
|||
|
|||
Status ConsumerBase::ReleaseBufferLocked(s32 slot, |
|||
const std::shared_ptr<GraphicBuffer> graphic_buffer) { |
|||
// If consumer no longer tracks this graphic_buffer (we received a new
|
|||
// buffer on the same slot), the buffer producer is definitely no longer
|
|||
// tracking it.
|
|||
|
|||
if (!StillTracking(slot, graphic_buffer)) { |
|||
return Status::NoError; |
|||
} |
|||
|
|||
LOG_DEBUG(Service_NVFlinger, "slot={}", slot); |
|||
Status err = consumer->ReleaseBuffer(slot, slots[slot].frame_number, slots[slot].fence); |
|||
if (err == Status::StaleBufferSlot) { |
|||
FreeBufferLocked(slot); |
|||
} |
|||
|
|||
slots[slot].fence = Fence::NoFence(); |
|||
|
|||
return err; |
|||
} |
|||
|
|||
bool ConsumerBase::StillTracking(s32 slot, |
|||
const std::shared_ptr<GraphicBuffer> graphic_buffer) const { |
|||
if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) { |
|||
return false; |
|||
} |
|||
|
|||
return (slots[slot].graphic_buffer != nullptr && |
|||
slots[slot].graphic_buffer->Handle() == graphic_buffer->Handle()); |
|||
} |
|||
|
|||
} // namespace Service::android
|
|||
@ -0,0 +1,61 @@ |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
// Copyright 2021 yuzu Emulator Project |
|||
// Copyright 2010 The Android Open Source Project |
|||
// Parts of this implementation were base on: |
|||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/ConsumerBase.h |
|||
|
|||
#pragma once |
|||
|
|||
#include <array> |
|||
#include <chrono> |
|||
#include <memory> |
|||
#include <mutex> |
|||
|
|||
#include "common/common_types.h" |
|||
#include "core/hle/service/nvflinger/buffer_queue_defs.h" |
|||
#include "core/hle/service/nvflinger/consumer_listener.h" |
|||
#include "core/hle/service/nvflinger/status.h" |
|||
|
|||
namespace Service::android { |
|||
|
|||
class BufferItem; |
|||
class BufferQueueConsumer; |
|||
|
|||
class ConsumerBase : public IConsumerListener, public std::enable_shared_from_this<ConsumerBase> { |
|||
public: |
|||
void Connect(bool controlled_by_app); |
|||
|
|||
protected: |
|||
explicit ConsumerBase(std::unique_ptr<BufferQueueConsumer> consumer_); |
|||
virtual ~ConsumerBase(); |
|||
|
|||
virtual void OnFrameAvailable(const BufferItem& item) override; |
|||
virtual void OnFrameReplaced(const BufferItem& item) override; |
|||
virtual void OnBuffersReleased() override; |
|||
virtual void OnSidebandStreamChanged() override; |
|||
|
|||
void FreeBufferLocked(s32 slot_index); |
|||
Status AcquireBufferLocked(BufferItem* item, std::chrono::nanoseconds present_when, |
|||
u64 max_frame_number = 0); |
|||
Status ReleaseBufferLocked(s32 slot, const std::shared_ptr<GraphicBuffer> graphic_buffer); |
|||
bool StillTracking(s32 slot, const std::shared_ptr<GraphicBuffer> graphic_buffer) const; |
|||
Status AddReleaseFenceLocked(s32 slot, const std::shared_ptr<GraphicBuffer> graphic_buffer, |
|||
const Fence& fence); |
|||
|
|||
struct Slot final { |
|||
std::shared_ptr<GraphicBuffer> graphic_buffer; |
|||
Fence fence; |
|||
u64 frame_number{}; |
|||
}; |
|||
|
|||
protected: |
|||
std::array<Slot, BufferQueueDefs::NUM_BUFFER_SLOTS> slots; |
|||
|
|||
bool is_abandoned{}; |
|||
|
|||
std::unique_ptr<BufferQueueConsumer> consumer; |
|||
|
|||
mutable std::mutex mutex; |
|||
}; |
|||
|
|||
} // namespace Service::android |
|||
@ -0,0 +1,26 @@ |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
// Copyright 2021 yuzu Emulator Project |
|||
// Copyright 2014 The Android Open Source Project |
|||
// Parts of this implementation were base on: |
|||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/IConsumerListener.h |
|||
|
|||
#pragma once |
|||
|
|||
namespace Service::android { |
|||
|
|||
class BufferItem; |
|||
|
|||
/// ConsumerListener is the interface through which the BufferQueue notifies the consumer of events |
|||
/// that the consumer may wish to react to. |
|||
class IConsumerListener { |
|||
public: |
|||
IConsumerListener() = default; |
|||
virtual ~IConsumerListener() = default; |
|||
|
|||
virtual void OnFrameAvailable(const BufferItem& item) = 0; |
|||
virtual void OnFrameReplaced(const BufferItem& item) = 0; |
|||
virtual void OnBuffersReleased() = 0; |
|||
virtual void OnSidebandStreamChanged() = 0; |
|||
}; |
|||
|
|||
}; // namespace Service::android |
|||
@ -0,0 +1,20 @@ |
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|||
// Copyright 2021 yuzu Emulator Project
|
|||
// Copyright 2010 The Android Open Source Project
|
|||
// Parts of this implementation were base on:
|
|||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/IGraphicBufferProducer.cpp
|
|||
|
|||
#pragma once
|
|||
|
|||
#include "core/hle/service/nvflinger/graphic_buffer_producer.h"
|
|||
#include "core/hle/service/nvflinger/parcel.h"
|
|||
|
|||
namespace Service::android { |
|||
|
|||
QueueBufferInput::QueueBufferInput(Parcel& parcel) { |
|||
parcel.ReadFlattened(*this); |
|||
} |
|||
|
|||
QueueBufferOutput::QueueBufferOutput() = default; |
|||
|
|||
} // namespace Service::android
|
|||
@ -0,0 +1,76 @@ |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
// Copyright 2021 yuzu Emulator Project |
|||
// Copyright 2010 The Android Open Source Project |
|||
// Parts of this implementation were base on: |
|||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/IGraphicBufferProducer.h |
|||
|
|||
#pragma once |
|||
|
|||
#include "common/common_funcs.h" |
|||
#include "common/common_types.h" |
|||
#include "common/math_util.h" |
|||
#include "core/hle/service/nvflinger/ui/fence.h" |
|||
#include "core/hle/service/nvflinger/window.h" |
|||
|
|||
namespace Service::android { |
|||
|
|||
class Parcel; |
|||
|
|||
#pragma pack(push, 1) |
|||
struct QueueBufferInput final { |
|||
explicit QueueBufferInput(Parcel& parcel); |
|||
|
|||
void Deflate(s64* timestamp_, bool* is_auto_timestamp_, Common::Rectangle<s32>* crop_, |
|||
NativeWindowScalingMode* scaling_mode_, NativeWindowTransform* transform_, |
|||
u32* sticky_transform_, bool* async_, s32* swap_interval_, Fence* fence_) const { |
|||
*timestamp_ = timestamp; |
|||
*is_auto_timestamp_ = static_cast<bool>(is_auto_timestamp); |
|||
*crop_ = crop; |
|||
*scaling_mode_ = scaling_mode; |
|||
*transform_ = transform; |
|||
*sticky_transform_ = sticky_transform; |
|||
*async_ = static_cast<bool>(async); |
|||
*swap_interval_ = swap_interval; |
|||
*fence_ = fence; |
|||
} |
|||
|
|||
private: |
|||
s64 timestamp{}; |
|||
s32 is_auto_timestamp{}; |
|||
Common::Rectangle<s32> crop{}; |
|||
NativeWindowScalingMode scaling_mode{}; |
|||
NativeWindowTransform transform{}; |
|||
u32 sticky_transform{}; |
|||
s32 async{}; |
|||
s32 swap_interval{}; |
|||
Fence fence{}; |
|||
}; |
|||
#pragma pack(pop) |
|||
static_assert(sizeof(QueueBufferInput) == 84, "QueueBufferInput has wrong size"); |
|||
|
|||
struct QueueBufferOutput final { |
|||
QueueBufferOutput(); |
|||
|
|||
void Deflate(u32* width_, u32* height_, u32* transform_hint_, u32* num_pending_buffers_) const { |
|||
*width_ = width; |
|||
*height_ = height; |
|||
*transform_hint_ = transform_hint; |
|||
*num_pending_buffers_ = num_pending_buffers; |
|||
} |
|||
|
|||
void Inflate(u32 width_, u32 height_, u32 transform_hint_, u32 num_pending_buffers_) { |
|||
width = width_; |
|||
height = height_; |
|||
transform_hint = transform_hint_; |
|||
num_pending_buffers = num_pending_buffers_; |
|||
} |
|||
|
|||
private: |
|||
u32 width{}; |
|||
u32 height{}; |
|||
u32 transform_hint{}; |
|||
u32 num_pending_buffers{}; |
|||
}; |
|||
static_assert(sizeof(QueueBufferOutput) == 16, "QueueBufferOutput has wrong size"); |
|||
|
|||
} // namespace Service::android |
|||
@ -0,0 +1,36 @@ |
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|||
// Copyright 2021 yuzu Emulator Project
|
|||
|
|||
#include <mutex>
|
|||
|
|||
#include "common/common_types.h"
|
|||
#include "core/hle/service/nvflinger/hos_binder_driver_server.h"
|
|||
|
|||
namespace Service::NVFlinger { |
|||
|
|||
HosBinderDriverServer::HosBinderDriverServer(Core::System& system_) |
|||
: service_context(system_, "HosBinderDriverServer") {} |
|||
|
|||
HosBinderDriverServer::~HosBinderDriverServer() {} |
|||
|
|||
u64 HosBinderDriverServer::RegisterProducer(std::unique_ptr<android::IBinder>&& binder) { |
|||
std::lock_guard lk{lock}; |
|||
|
|||
last_id++; |
|||
|
|||
producers[last_id] = std::move(binder); |
|||
|
|||
return last_id; |
|||
} |
|||
|
|||
android::IBinder* HosBinderDriverServer::TryGetProducer(u64 id) { |
|||
std::lock_guard lk{lock}; |
|||
|
|||
if (auto search = producers.find(id); search != producers.end()) { |
|||
return search->second.get(); |
|||
} |
|||
|
|||
return {}; |
|||
} |
|||
|
|||
} // namespace Service::NVFlinger
|
|||
@ -0,0 +1,37 @@ |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
// Copyright 2021 yuzu Emulator Project |
|||
|
|||
#pragma once |
|||
|
|||
#include <memory> |
|||
#include <mutex> |
|||
#include <unordered_map> |
|||
|
|||
#include "common/common_types.h" |
|||
#include "core/hle/service/kernel_helpers.h" |
|||
#include "core/hle/service/nvflinger/binder.h" |
|||
|
|||
namespace Core { |
|||
class System; |
|||
} |
|||
|
|||
namespace Service::NVFlinger { |
|||
|
|||
class HosBinderDriverServer final { |
|||
public: |
|||
explicit HosBinderDriverServer(Core::System& system_); |
|||
~HosBinderDriverServer(); |
|||
|
|||
u64 RegisterProducer(std::unique_ptr<android::IBinder>&& binder); |
|||
|
|||
android::IBinder* TryGetProducer(u64 id); |
|||
|
|||
private: |
|||
KernelHelpers::ServiceContext service_context; |
|||
|
|||
std::unordered_map<u64, std::unique_ptr<android::IBinder>> producers; |
|||
std::mutex lock; |
|||
u64 last_id{}; |
|||
}; |
|||
|
|||
} // namespace Service::NVFlinger |
|||
@ -0,0 +1,172 @@ |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
// Copyright 2021 yuzu Emulator Project |
|||
|
|||
#pragma once |
|||
|
|||
#include <memory> |
|||
#include <vector> |
|||
|
|||
#include "common/alignment.h" |
|||
#include "common/assert.h" |
|||
#include "common/common_types.h" |
|||
|
|||
namespace Service::android { |
|||
|
|||
class Parcel final { |
|||
public: |
|||
static constexpr std::size_t DefaultBufferSize = 0x40; |
|||
|
|||
Parcel() : buffer(DefaultBufferSize) {} |
|||
|
|||
template <typename T> |
|||
explicit Parcel(const T& out_data) : buffer(DefaultBufferSize) { |
|||
Write(out_data); |
|||
} |
|||
|
|||
explicit Parcel(std::vector<u8> in_data) : buffer(std::move(in_data)) { |
|||
DeserializeHeader(); |
|||
[[maybe_unused]] const std::u16string token = ReadInterfaceToken(); |
|||
} |
|||
|
|||
template <typename T> |
|||
void Read(T& val) { |
|||
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable."); |
|||
ASSERT(read_index + sizeof(T) <= buffer.size()); |
|||
|
|||
std::memcpy(&val, buffer.data() + read_index, sizeof(T)); |
|||
read_index += sizeof(T); |
|||
read_index = Common::AlignUp(read_index, 4); |
|||
} |
|||
|
|||
template <typename T> |
|||
T Read() { |
|||
T val; |
|||
Read(val); |
|||
return val; |
|||
} |
|||
|
|||
template <typename T> |
|||
void ReadFlattened(T& val) { |
|||
const auto flattened_size = Read<s64>(); |
|||
ASSERT(sizeof(T) == flattened_size); |
|||
Read(val); |
|||
} |
|||
|
|||
template <typename T> |
|||
T ReadFlattened() { |
|||
T val; |
|||
ReadFlattened(val); |
|||
return val; |
|||
} |
|||
|
|||
template <typename T> |
|||
T ReadUnaligned() { |
|||
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable."); |
|||
ASSERT(read_index + sizeof(T) <= buffer.size()); |
|||
|
|||
T val; |
|||
std::memcpy(&val, buffer.data() + read_index, sizeof(T)); |
|||
read_index += sizeof(T); |
|||
return val; |
|||
} |
|||
|
|||
template <typename T> |
|||
const std::shared_ptr<T> ReadObject() { |
|||
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable."); |
|||
|
|||
const auto is_valid{Read<bool>()}; |
|||
|
|||
if (is_valid) { |
|||
auto result = std::make_shared<T>(); |
|||
ReadFlattened(*result); |
|||
return result; |
|||
} |
|||
|
|||
return {}; |
|||
} |
|||
|
|||
std::u16string ReadInterfaceToken() { |
|||
[[maybe_unused]] const u32 unknown = Read<u32>(); |
|||
const u32 length = Read<u32>(); |
|||
|
|||
std::u16string token; |
|||
token.reserve(length + 1); |
|||
|
|||
for (u32 ch = 0; ch < length + 1; ++ch) { |
|||
token.push_back(ReadUnaligned<u16>()); |
|||
} |
|||
|
|||
read_index = Common::AlignUp(read_index, 4); |
|||
|
|||
return token; |
|||
} |
|||
|
|||
template <typename T> |
|||
void Write(const T& val) { |
|||
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable."); |
|||
|
|||
if (buffer.size() < write_index + sizeof(T)) { |
|||
buffer.resize(buffer.size() + sizeof(T) + DefaultBufferSize); |
|||
} |
|||
|
|||
std::memcpy(buffer.data() + write_index, &val, sizeof(T)); |
|||
write_index += sizeof(T); |
|||
write_index = Common::AlignUp(write_index, 4); |
|||
} |
|||
|
|||
template <typename T> |
|||
void WriteObject(const T* ptr) { |
|||
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable."); |
|||
|
|||
if (!ptr) { |
|||
Write<u32>(0); |
|||
return; |
|||
} |
|||
|
|||
Write<u32>(1); |
|||
Write<s64>(sizeof(T)); |
|||
Write(*ptr); |
|||
} |
|||
|
|||
template <typename T> |
|||
void WriteObject(const std::shared_ptr<T> ptr) { |
|||
WriteObject(ptr.get()); |
|||
} |
|||
|
|||
void DeserializeHeader() { |
|||
ASSERT(buffer.size() > sizeof(Header)); |
|||
|
|||
Header header{}; |
|||
std::memcpy(&header, buffer.data(), sizeof(Header)); |
|||
|
|||
read_index = header.data_offset; |
|||
} |
|||
|
|||
std::vector<u8> Serialize() const { |
|||
ASSERT(read_index == 0); |
|||
|
|||
Header header{}; |
|||
header.data_size = static_cast<u32>(write_index - sizeof(Header)); |
|||
header.data_offset = sizeof(Header); |
|||
header.objects_size = 4; |
|||
header.objects_offset = static_cast<u32>(sizeof(Header) + header.data_size); |
|||
std::memcpy(buffer.data(), &header, sizeof(Header)); |
|||
|
|||
return buffer; |
|||
} |
|||
|
|||
private: |
|||
struct Header { |
|||
u32 data_size; |
|||
u32 data_offset; |
|||
u32 objects_size; |
|||
u32 objects_offset; |
|||
}; |
|||
static_assert(sizeof(Header) == 16, "ParcelHeader has wrong size"); |
|||
|
|||
mutable std::vector<u8> buffer; |
|||
std::size_t read_index = 0; |
|||
std::size_t write_index = sizeof(Header); |
|||
}; |
|||
|
|||
} // namespace Service::android |
|||
@ -0,0 +1,21 @@ |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
// Copyright 2021 yuzu Emulator Project |
|||
|
|||
#pragma once |
|||
|
|||
#include "common/common_types.h" |
|||
|
|||
namespace Service::android { |
|||
|
|||
enum class PixelFormat : u32 { |
|||
NoFormat = 0, |
|||
Rgba8888 = 1, |
|||
Rgbx8888 = 2, |
|||
Rgb888 = 3, |
|||
Rgb565 = 4, |
|||
Bgra8888 = 5, |
|||
Rgba5551 = 6, |
|||
Rgba4444 = 7, |
|||
}; |
|||
|
|||
} // namespace Service::android |
|||
@ -0,0 +1,16 @@ |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
// Copyright 2021 yuzu Emulator Project |
|||
// Copyright 2014 The Android Open Source Project |
|||
// Parts of this implementation were base on: |
|||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/IProducerListener.h |
|||
|
|||
#pragma once |
|||
|
|||
namespace Service::android { |
|||
|
|||
class IProducerListener { |
|||
public: |
|||
virtual void OnBufferReleased() = 0; |
|||
}; |
|||
|
|||
} // namespace Service::android |
|||
@ -0,0 +1,28 @@ |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
// Copyright 2021 yuzu Emulator Project |
|||
|
|||
#pragma once |
|||
|
|||
#include "common/common_funcs.h" |
|||
#include "common/common_types.h" |
|||
|
|||
namespace Service::android { |
|||
|
|||
enum class Status : s32 { |
|||
None = 0, |
|||
NoError = 0, |
|||
StaleBufferSlot = 1, |
|||
NoBufferAvailable = 2, |
|||
PresentLater = 3, |
|||
WouldBlock = -11, |
|||
NoMemory = -12, |
|||
Busy = -16, |
|||
NoInit = -19, |
|||
BadValue = -22, |
|||
InvalidOperation = -37, |
|||
BufferNeedsReallocation = 1, |
|||
ReleaseAllBuffers = 2, |
|||
}; |
|||
DECLARE_ENUM_FLAG_OPERATORS(Status); |
|||
|
|||
} // namespace Service::android |
|||
@ -0,0 +1,32 @@ |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
// Copyright 2021 yuzu Emulator Project |
|||
// Copyright 2012 The Android Open Source Project |
|||
// Parts of this implementation were base on: |
|||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/ui/Fence.h |
|||
|
|||
#pragma once |
|||
|
|||
#include <array> |
|||
|
|||
#include "common/common_types.h" |
|||
#include "core/hle/service/nvdrv/nvdata.h" |
|||
|
|||
namespace Service::android { |
|||
|
|||
class Fence { |
|||
public: |
|||
constexpr Fence() = default; |
|||
|
|||
static constexpr Fence NoFence() { |
|||
Fence fence; |
|||
fence.fences[0].id = -1; |
|||
return fence; |
|||
} |
|||
|
|||
public: |
|||
u32 num_fences{}; |
|||
std::array<Service::Nvidia::NvFence, 4> fences{}; |
|||
}; |
|||
static_assert(sizeof(Fence) == 36, "Fence has wrong size"); |
|||
|
|||
} // namespace Service::android |
|||
@ -0,0 +1,100 @@ |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
// Copyright 2021 yuzu Emulator Project |
|||
// Copyright 2007 The Android Open Source Project |
|||
// Parts of this implementation were base on: |
|||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/ui/GraphicBuffer.h |
|||
|
|||
#pragma once |
|||
|
|||
#include "common/common_funcs.h" |
|||
#include "common/common_types.h" |
|||
#include "core/hle/service/nvflinger/pixel_format.h" |
|||
|
|||
namespace Service::android { |
|||
|
|||
class GraphicBuffer final { |
|||
public: |
|||
constexpr GraphicBuffer() = default; |
|||
|
|||
constexpr GraphicBuffer(u32 width_, u32 height_, PixelFormat format_, u32 usage_) |
|||
: width{static_cast<s32>(width_)}, height{static_cast<s32>(height_)}, format{format_}, |
|||
usage{static_cast<s32>(usage_)} {} |
|||
|
|||
constexpr u32 Width() const { |
|||
return static_cast<u32>(width); |
|||
} |
|||
|
|||
constexpr u32 Height() const { |
|||
return static_cast<u32>(height); |
|||
} |
|||
|
|||
constexpr u32 Stride() const { |
|||
return static_cast<u32>(stride); |
|||
} |
|||
|
|||
constexpr u32 Usage() const { |
|||
return static_cast<u32>(usage); |
|||
} |
|||
|
|||
constexpr PixelFormat Format() const { |
|||
return format; |
|||
} |
|||
|
|||
constexpr u32 BufferId() const { |
|||
return buffer_id; |
|||
} |
|||
|
|||
constexpr PixelFormat ExternalFormat() const { |
|||
return external_format; |
|||
} |
|||
|
|||
constexpr u32 Handle() const { |
|||
return handle; |
|||
} |
|||
|
|||
constexpr u32 Offset() const { |
|||
return offset; |
|||
} |
|||
|
|||
constexpr bool NeedsReallocation(u32 width_, u32 height_, PixelFormat format_, |
|||
u32 usage_) const { |
|||
if (static_cast<s32>(width_) != width) { |
|||
return true; |
|||
} |
|||
|
|||
if (static_cast<s32>(height_) != height) { |
|||
return true; |
|||
} |
|||
|
|||
if (format_ != format) { |
|||
return true; |
|||
} |
|||
|
|||
if ((static_cast<u32>(usage) & usage_) != usage_) { |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
private: |
|||
u32 magic{}; |
|||
s32 width{}; |
|||
s32 height{}; |
|||
s32 stride{}; |
|||
PixelFormat format{}; |
|||
s32 usage{}; |
|||
INSERT_PADDING_WORDS(1); |
|||
u32 index{}; |
|||
INSERT_PADDING_WORDS(3); |
|||
u32 buffer_id{}; |
|||
INSERT_PADDING_WORDS(6); |
|||
PixelFormat external_format{}; |
|||
INSERT_PADDING_WORDS(10); |
|||
u32 handle{}; |
|||
u32 offset{}; |
|||
INSERT_PADDING_WORDS(60); |
|||
}; |
|||
static_assert(sizeof(GraphicBuffer) == 0x16C, "GraphicBuffer has wrong size"); |
|||
|
|||
} // namespace Service::android |
|||
@ -0,0 +1,53 @@ |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
// Copyright 2021 yuzu Emulator Project |
|||
|
|||
#pragma once |
|||
|
|||
#include "common/common_funcs.h" |
|||
#include "common/common_types.h" |
|||
|
|||
namespace Service::android { |
|||
|
|||
/// Attributes queryable with Query |
|||
enum class NativeWindow : s32 { |
|||
Width = 0, |
|||
Height = 1, |
|||
Format = 2, |
|||
MinUndequeedBuffers = 3, |
|||
QueuesToWindowComposer = 4, |
|||
ConcreteType = 5, |
|||
DefaultWidth = 6, |
|||
DefaultHeight = 7, |
|||
TransformHint = 8, |
|||
ConsumerRunningBehind = 9, |
|||
ConsumerUsageBits = 10, |
|||
StickyTransform = 11, |
|||
DefaultDataSpace = 12, |
|||
BufferAge = 13, |
|||
}; |
|||
|
|||
/// Parameter for Connect/Disconnect |
|||
enum class NativeWindowApi : s32 { |
|||
NoConnectedApi = 0, |
|||
Egl = 1, |
|||
Cpu = 2, |
|||
Media = 3, |
|||
Camera = 4, |
|||
}; |
|||
|
|||
/// Scaling mode parameter for QueueBuffer |
|||
enum class NativeWindowScalingMode : s32 { |
|||
Freeze = 0, |
|||
ScaleToWindow = 1, |
|||
ScaleCrop = 2, |
|||
NoScaleCrop = 3, |
|||
}; |
|||
|
|||
/// Transform parameter for QueueBuffer |
|||
enum class NativeWindowTransform : u32 { |
|||
None = 0x0, |
|||
InverseDisplay = 0x08, |
|||
}; |
|||
DECLARE_ENUM_FLAG_OPERATORS(NativeWindowTransform); |
|||
|
|||
} // namespace Service::android |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue