3 changed files with 1021 additions and 2 deletions
-
4src/core/CMakeLists.txt
-
936src/core/hle/service/nvflinger/buffer_queue_producer.cpp
-
83src/core/hle/service/nvflinger/buffer_queue_producer.h
@ -0,0 +1,936 @@ |
|||
// 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 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) { |
|||
{ BufferQueueCore::AutoLock lock(core); } |
|||
|
|||
LOG_DEBUG(Service_NVFlinger, "async={} w={} h={} format={}, usage={}", async ? "true" : "false", |
|||
width, height, format, usage); |
|||
|
|||
if ((width && !height) || (!width && height)) { |
|||
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{}; |
|||
Rect 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); |
|||
Rect buffer_rect(graphic_buffer->Width(), graphic_buffer->Height()); |
|||
Rect cropped_rect; |
|||
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; |
|||
} |
|||
|
|||
s32 value{}; |
|||
switch (what) { |
|||
case NativeWindow::Width: |
|||
value = static_cast<s32>(core->default_width); |
|||
break; |
|||
case NativeWindow::Height: |
|||
value = static_cast<s32>(core->default_height); |
|||
break; |
|||
case NativeWindow::Format: |
|||
value = static_cast<s32>(core->default_buffer_format); |
|||
break; |
|||
case NativeWindow::MinUndequeedBuffers: |
|||
value = core->GetMinUndequeuedBufferCountLocked(false); |
|||
break; |
|||
case NativeWindow::StickyTransform: |
|||
value = static_cast<s32>(sticky_transform); |
|||
break; |
|||
case NativeWindow::ConsumerRunningBehind: |
|||
value = (core->queue.size() > 1); |
|||
break; |
|||
case NativeWindow::ConsumerUsageBits: |
|||
value = static_cast<s32>(core->consumer_usage_bit); |
|||
break; |
|||
case NativeWindow::BufferAge: |
|||
if (core->buffer_age > INT32_MAX) { |
|||
value = 0; |
|||
} else { |
|||
value = static_cast<s32>(core->buffer_age); |
|||
} |
|||
break; |
|||
default: |
|||
UNREACHABLE(); |
|||
return Status::BadValue; |
|||
} |
|||
|
|||
LOG_DEBUG(Service_NVFlinger, "what = {}, value = {}", what, value); |
|||
|
|||
*out_value = 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); |
|||
|
|||
UNIMPLEMENTED_IF_MSG(!buffer, "buffer must be valid!"); |
|||
|
|||
if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) { |
|||
return Status::BadValue; |
|||
} |
|||
|
|||
BufferQueueCore::AutoLock lock(core); |
|||
|
|||
slots[slot] = {}; |
|||
slots[slot].is_preallocated = true; |
|||
slots[slot].graphic_buffer = buffer; |
|||
|
|||
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"); |
|||
} |
|||
|
|||
parcel_out.Write(status); |
|||
|
|||
ctx.WriteBuffer(parcel_out.Serialize()); |
|||
} |
|||
|
|||
Kernel::KReadableEvent& BufferQueueProducer::GetNativeHandle() { |
|||
return buffer_wait_event->GetReadableEvent(); |
|||
} |
|||
|
|||
} // namespace 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 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 android |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue