// 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{};
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);
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");
}
parcel_out.Write(status);
ctx.WriteBuffer(parcel_out.Serialize());
}
Kernel::KReadableEvent& BufferQueueProducer::GetNativeHandle() {
return buffer_wait_event->GetReadableEvent();
}
} // namespace Service::android