code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Parts of this implementation were based on:
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferQueueProducer.cpp

#include "common/assert.h"
#include "common/logging/log.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/kernel/k_readable_event.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/nvnflinger/buffer_queue_core.h"
#include "core/hle/service/nvnflinger/buffer_queue_producer.h"
#include "core/hle/service/nvnflinger/consumer_listener.h"
#include "core/hle/service/nvnflinger/parcel.h"
#include "core/hle/service/nvnflinger/ui/graphic_buffer.h"
#include "core/hle/service/nvnflinger/window.h"

namespace Service::android {

BufferQueueProducer::BufferQueueProducer(Service::KernelHelpers::ServiceContext& service_context_,
                                         std::shared_ptr<BufferQueueCore> buffer_queue_core_,
                                         Service::Nvidia::NvCore::NvMap& nvmap_)
    : service_context{service_context_}, core{std::move(buffer_queue_core_)}, slots(core->slots),
      nvmap(nvmap_) {
    buffer_wait_event = service_context.CreateEvent("BufferQueue:WaitEvent");
}

BufferQueueProducer::~BufferQueueProducer() {
    service_context.CloseEvent(buffer_wait_event);
}

Status BufferQueueProducer::RequestBuffer(s32 slot, std::shared_ptr<GraphicBuffer>* buf) {
    LOG_DEBUG(Service_Nvnflinger, "slot {}", slot);

    std::scoped_lock lock{core->mutex};

    if (core->is_abandoned) {
        LOG_ERROR(Service_Nvnflinger, "BufferQueue has been abandoned");
        return Status::NoInit;
    }
    if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
        LOG_ERROR(Service_Nvnflinger, "slot index {} out of range [0, {})", slot,
                  BufferQueueDefs::NUM_BUFFER_SLOTS);
        return Status::BadValue;
    } else if (slots[slot].buffer_state != BufferState::Dequeued) {
        LOG_ERROR(Service_Nvnflinger, "slot {} is not owned by the producer (state = {})", slot,
                  slots[slot].buffer_state);
        return Status::BadValue;
    }

    slots[slot].request_buffer_called = true;
    *buf = slots[slot].graphic_buffer;

    return Status::NoError;
}

Status BufferQueueProducer::SetBufferCount(s32 buffer_count) {
    LOG_DEBUG(Service_Nvnflinger, "count = {}", buffer_count);

    std::shared_ptr<IConsumerListener> listener;
    {
        std::scoped_lock lock{core->mutex};
        core->WaitWhileAllocatingLocked();

        if (core->is_abandoned) {
            LOG_ERROR(Service_Nvnflinger, "BufferQueue has been abandoned");
            return Status::NoInit;
        }

        if (buffer_count > BufferQueueDefs::NUM_BUFFER_SLOTS) {
            LOG_ERROR(Service_Nvnflinger, "buffer_count {} too large (max {})", buffer_count,
                      BufferQueueDefs::NUM_BUFFER_SLOTS);
            return Status::BadValue;
        }

        // There must be no dequeued buffers when changing the buffer count.
        for (s32 s{}; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
            if (slots[s].buffer_state == BufferState::Dequeued) {
                LOG_ERROR(Service_Nvnflinger, "buffer owned by producer");
                return Status::BadValue;
            }
        }

        if (buffer_count == 0) {
            core->override_max_buffer_count = 0;
            core->SignalDequeueCondition();
            return Status::NoError;
        }

        const s32 min_buffer_slots = core->GetMinMaxBufferCountLocked(false);
        if (buffer_count < min_buffer_slots) {
            LOG_ERROR(Service_Nvnflinger, "requested buffer count {} is less than minimum {}",
                      buffer_count, min_buffer_slots);
            return Status::BadValue;
        }

        // Here we are guaranteed that the producer doesn't have any dequeued buffers and will
        // release all of its buffer references.
        if (core->GetPreallocatedBufferCountLocked() <= 0) {
            core->FreeAllBuffersLocked();
        }

        core->override_max_buffer_count = buffer_count;
        core->SignalDequeueCondition();
        buffer_wait_event->Signal();
        listener = core->consumer_listener;
    }

    // Call back without lock held
    if (listener != nullptr) {
        listener->OnBuffersReleased();
    }

    return Status::NoError;
}

Status BufferQueueProducer::WaitForFreeSlotThenRelock(bool async, s32* found, Status* return_flags,
                                                      std::unique_lock<std::mutex>& lk) const {
    bool try_again = true;

    while (try_again) {
        if (core->is_abandoned) {
            LOG_ERROR(Service_Nvnflinger, "BufferQueue has been abandoned");
            return Status::NoInit;
        }

        const s32 max_buffer_count = core->GetMaxBufferCountLocked(async);
        if (async && core->override_max_buffer_count) {
            if (core->override_max_buffer_count < max_buffer_count) {
                *found = BufferQueueCore::INVALID_BUFFER_SLOT;
                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 && slots[s].buffer_state == BufferState::Free &&
                !slots[s].is_preallocated) {
                core->FreeBufferLocked(s);
                *return_flags |= Status::ReleaseAllBuffers;
            }
        }

        // Look for a free buffer to give to the client
        *found = BufferQueueCore::INVALID_BUFFER_SLOT;
        s32 dequeued_count{};
        s32 acquired_count{};
        for (s32 s{}; s < max_buffer_count; ++s) {
            switch (slots[s].buffer_state) {
            case BufferState::Dequeued:
                ++dequeued_count;
                break;
            case BufferState::Acquired:
                ++acquired_count;
                break;
            case BufferState::Free:
                // We return the oldest of the free buffers to avoid stalling the producer if
                // possible, since the consumer may still have pending reads of in-flight buffers
                if (*found == BufferQueueCore::INVALID_BUFFER_SLOT ||
                    slots[s].frame_number < slots[*found].frame_number) {
                    *found = s;
                }
                break;
            default:
                break;
            }
        }

        // Producers are not allowed to dequeue more than one buffer if they did not set a buffer
        // count
        if (!core->override_max_buffer_count && dequeued_count) {
            LOG_ERROR(Service_Nvnflinger,
                      "can't dequeue multiple buffers without setting the buffer count");
            return Status::InvalidOperation;
        }

        // See whether a buffer has been queued since the last SetBufferCount so we know whether to
        // perform the min undequeued buffers check below
        if (core->buffer_has_been_queued) {
            // Make sure the producer is not trying to dequeue more buffers than allowed
            const s32 new_undequeued_count = max_buffer_count - (dequeued_count + 1);
            const s32 min_undequeued_count = core->GetMinUndequeuedBufferCountLocked(async);
            if (new_undequeued_count < min_undequeued_count) {
                LOG_ERROR(Service_Nvnflinger,
                          "min undequeued buffer count({}) exceeded (dequeued={} undequeued={})",
                          min_undequeued_count, dequeued_count, new_undequeued_count);
                return Status::InvalidOperation;
            }
        }

        // If we disconnect and reconnect quickly, we can be in a state where our slots are empty
        // but we have many buffers in the queue. This can cause us to run out of memory if we
        // outrun the consumer. Wait here if it looks like we have too many buffers queued up.
        const bool too_many_buffers = core->queue.size() > static_cast<size_t>(max_buffer_count);
        if (too_many_buffers) {
            LOG_ERROR(Service_Nvnflinger, "queue size is {}, waiting", core->queue.size());
        }

        // If no buffer is found, or if the queue has too many buffers outstanding, wait for a
        // buffer to be acquired or released, or for the max buffer count to change.
        try_again = (*found == BufferQueueCore::INVALID_BUFFER_SLOT) || too_many_buffers;
        if (try_again) {
            // Return an error if we're in non-blocking mode (producer and consumer are controlled
            // by the application).
            if (core->dequeue_buffer_cannot_block &&
                (acquired_count <= core->max_acquired_buffer_count)) {
                return Status::WouldBlock;
            }

            if (!core->WaitForDequeueCondition(lk)) {
                // We are no longer running
                return Status::NoError;
            }
        }
    }

    return Status::NoError;
}

Status BufferQueueProducer::DequeueBuffer(s32* out_slot, Fence* out_fence, bool async, u32 width,
                                          u32 height, PixelFormat format, u32 usage) {
    LOG_DEBUG(Service_Nvnflinger, "async={} w={} h={} format={}, usage={}",
              async ? "true" : "false", width, height, format, usage);

    if ((width != 0 && height == 0) || (width == 0 && height != 0)) {
        LOG_ERROR(Service_Nvnflinger, "invalid size: w={} h={}", width, height);
        return Status::BadValue;
    }

    Status return_flags = Status::NoError;
    bool attached_by_consumer = false;
    {
        std::unique_lock lock{core->mutex};
        core->WaitWhileAllocatingLocked();

        if (format == PixelFormat::NoFormat) {
            format = core->default_buffer_format;
        }

        // Enable the usage bits the consumer requested
        usage |= core->consumer_usage_bit;

        s32 found{};
        Status status = WaitForFreeSlotThenRelock(async, &found, &return_flags, lock);
        if (status != Status::NoError) {
            return status;
        }

        // This should not happen
        if (found == BufferQueueCore::INVALID_BUFFER_SLOT) {
            LOG_ERROR(Service_Nvnflinger, "no available buffer slots");
            return Status::Busy;
        }

        *out_slot = found;

        attached_by_consumer = slots[found].attached_by_consumer;

        const bool use_default_size = !width && !height;
        if (use_default_size) {
            width = core->default_width;
            height = core->default_height;
        }

        slots[found].buffer_state = BufferState::Dequeued;

        const std::shared_ptr<GraphicBuffer>& buffer(slots[found].graphic_buffer);
        if ((buffer == nullptr) || (buffer->Width() != width) || (buffer->Height() != height) ||
            (buffer->Format() != format) || ((buffer->Usage() & usage) != usage)) {
            slots[found].acquire_called = false;
            slots[found].graphic_buffer = nullptr;
            slots[found].request_buffer_called = false;
            slots[found].fence = Fence::NoFence();

            return_flags |= Status::BufferNeedsReallocation;
        }

        *out_fence = slots[found].fence;
        slots[found].fence = Fence::NoFence();
    }

    if ((return_flags & Status::BufferNeedsReallocation) != Status::None) {
        LOG_DEBUG(Service_Nvnflinger, "allocating a new buffer for slot {}", *out_slot);

        auto graphic_buffer = std::make_shared<GraphicBuffer>(width, height, format, usage);
        if (graphic_buffer == nullptr) {
            LOG_ERROR(Service_Nvnflinger, "creating GraphicBuffer failed");
            return Status::NoMemory;
        }

        {
            std::scoped_lock lock{core->mutex};

            if (core->is_abandoned) {
                LOG_ERROR(Service_Nvnflinger, "BufferQueue has been abandoned");
                return Status::NoInit;
            }

            slots[*out_slot].frame_number = UINT32_MAX;
            slots[*out_slot].graphic_buffer = graphic_buffer;
        }
    }

    if (attached_by_consumer) {
        return_flags |= Status::BufferNeedsReallocation;
    }

    LOG_DEBUG(Service_Nvnflinger, "returning slot={} frame={}, flags={}", *out_slot,
              slots[*out_slot].frame_number, return_flags);

    return return_flags;
}

Status BufferQueueProducer::DetachBuffer(s32 slot) {
    LOG_DEBUG(Service_Nvnflinger, "slot {}", slot);

    std::scoped_lock lock{core->mutex};

    if (core->is_abandoned) {
        LOG_ERROR(Service_Nvnflinger, "BufferQueue has been abandoned");
        return Status::NoInit;
    }

    if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
        LOG_ERROR(Service_Nvnflinger, "slot {} out of range [0, {})", slot,
                  BufferQueueDefs::NUM_BUFFER_SLOTS);
        return Status::BadValue;
    } else if (slots[slot].buffer_state != BufferState::Dequeued) {
        LOG_ERROR(Service_Nvnflinger, "slot {} is not owned by the producer (state = {})", slot,
                  slots[slot].buffer_state);
        return Status::BadValue;
    } else if (!slots[slot].request_buffer_called) {
        LOG_ERROR(Service_Nvnflinger, "buffer in slot {} has not been requested", slot);
        return Status::BadValue;
    }

    core->FreeBufferLocked(slot);
    core->SignalDequeueCondition();

    return Status::NoError;
}

Status BufferQueueProducer::DetachNextBuffer(std::shared_ptr<GraphicBuffer>* out_buffer,
                                             Fence* out_fence) {
    if (out_buffer == nullptr) {
        LOG_ERROR(Service_Nvnflinger, "out_buffer must not be nullptr");
        return Status::BadValue;
    } else if (out_fence == nullptr) {
        LOG_ERROR(Service_Nvnflinger, "out_fence must not be nullptr");
        return Status::BadValue;
    }

    std::scoped_lock lock{core->mutex};
    core->WaitWhileAllocatingLocked();

    if (core->is_abandoned) {
        LOG_ERROR(Service_Nvnflinger, "BufferQueue has been abandoned");
        return Status::NoInit;
    }

    // Find the oldest valid slot
    int found = BufferQueueCore::INVALID_BUFFER_SLOT;
    for (int s = 0; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
        if (slots[s].buffer_state == BufferState::Free && slots[s].graphic_buffer != nullptr) {
            if (found == BufferQueueCore::INVALID_BUFFER_SLOT ||
                slots[s].frame_number < slots[found].frame_number) {
                found = s;
            }
        }
    }

    if (found == BufferQueueCore::INVALID_BUFFER_SLOT) {
        return Status::NoMemory;
    }

    LOG_DEBUG(Service_Nvnflinger, "Detached slot {}", found);

    *out_buffer = slots[found].graphic_buffer;
    *out_fence = slots[found].fence;

    core->FreeBufferLocked(found);

    return Status::NoError;
}

Status BufferQueueProducer::AttachBuffer(s32* out_slot,
                                         const std::shared_ptr<GraphicBuffer>& buffer) {
    if (out_slot == nullptr) {
        LOG_ERROR(Service_Nvnflinger, "out_slot must not be nullptr");
        return Status::BadValue;
    } else if (buffer == nullptr) {
        LOG_ERROR(Service_Nvnflinger, "Cannot attach nullptr buffer");
        return Status::BadValue;
    }

    std::unique_lock lock{core->mutex};
    core->WaitWhileAllocatingLocked();

    Status return_flags = Status::NoError;
    s32 found{};

    const auto status = WaitForFreeSlotThenRelock(false, &found, &return_flags, lock);
    if (status != Status::NoError) {
        return status;
    }

    // This should not happen
    if (found == BufferQueueCore::INVALID_BUFFER_SLOT) {
        LOG_ERROR(Service_Nvnflinger, "No available buffer slots");
        return Status::Busy;
    }

    *out_slot = found;

    LOG_DEBUG(Service_Nvnflinger, "Returning slot {} flags={}", *out_slot, return_flags);

    slots[*out_slot].graphic_buffer = buffer;
    slots[*out_slot].buffer_state = BufferState::Dequeued;
    slots[*out_slot].fence = Fence::NoFence();
    slots[*out_slot].request_buffer_called = true;

    return return_flags;
}

Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input,
                                        QueueBufferOutput* output) {
    s64 timestamp{};
    bool is_auto_timestamp{};
    Common::Rectangle<s32> crop;
    NativeWindowScalingMode scaling_mode{};
    NativeWindowTransform transform;
    u32 sticky_transform_{};
    bool async{};
    s32 swap_interval{};
    Fence fence{};

    input.Deflate(&timestamp, &is_auto_timestamp, &crop, &scaling_mode, &transform,
                  &sticky_transform_, &async, &swap_interval, &fence);

    switch (scaling_mode) {
    case NativeWindowScalingMode::Freeze:
    case NativeWindowScalingMode::ScaleToWindow:
    case NativeWindowScalingMode::ScaleCrop:
    case NativeWindowScalingMode::NoScaleCrop:
    case NativeWindowScalingMode::PreserveAspectRatio:
        break;
    default:
        LOG_ERROR(Service_Nvnflinger, "unknown scaling mode {}", scaling_mode);
        return Status::BadValue;
    }

    std::shared_ptr<IConsumerListener> frame_available_listener;
    std::shared_ptr<IConsumerListener> frame_replaced_listener;
    s32 callback_ticket{};
    BufferItem item;

    {
        std::scoped_lock lock{core->mutex};

        if (core->is_abandoned) {
            LOG_ERROR(Service_Nvnflinger, "BufferQueue has been abandoned");
            return Status::NoInit;
        }

        const s32 max_buffer_count = core->GetMaxBufferCountLocked(async);
        if (async && core->override_max_buffer_count) {
            if (core->override_max_buffer_count < max_buffer_count) {
                LOG_ERROR(Service_Nvnflinger, "async mode is invalid with "
                                              "buffer count override");
                return Status::BadValue;
            }
        }

        if (slot < 0 || slot >= max_buffer_count) {
            LOG_ERROR(Service_Nvnflinger, "slot index {} out of range [0, {})", slot,
                      max_buffer_count);
            return Status::BadValue;
        } else if (slots[slot].buffer_state != BufferState::Dequeued) {
            LOG_ERROR(Service_Nvnflinger,
                      "slot {} is not owned by the producer "
                      "(state = {})",
                      slot, slots[slot].buffer_state);
            return Status::BadValue;
        } else if (!slots[slot].request_buffer_called) {
            LOG_ERROR(Service_Nvnflinger,
                      "slot {} was queued without requesting "
                      "a buffer",
                      slot);
            return Status::BadValue;
        }

        LOG_DEBUG(Service_Nvnflinger,
                  "slot={} frame={} time={} crop=[{},{},{},{}] transform={} scale={}", slot,
                  core->frame_counter + 1, timestamp, crop.Left(), crop.Top(), crop.Right(),
                  crop.Bottom(), transform, scaling_mode);

        const std::shared_ptr<GraphicBuffer>& graphic_buffer(slots[slot].graphic_buffer);
        Common::Rectangle<s32> buffer_rect(graphic_buffer->Width(), graphic_buffer->Height());
        Common::Rectangle<s32> cropped_rect;
        [[maybe_unused]] const bool unused = crop.Intersect(buffer_rect, &cropped_rect);

        if (cropped_rect != crop) {
            LOG_ERROR(Service_Nvnflinger, "crop rect is not contained within the buffer in slot {}",
                      slot);
            return Status::BadValue;
        }

        slots[slot].fence = fence;
        slots[slot].buffer_state = BufferState::Queued;
        ++core->frame_counter;
        slots[slot].frame_number = core->frame_counter;

        item.acquire_called = slots[slot].acquire_called;
        item.graphic_buffer = slots[slot].graphic_buffer;
        item.crop = crop;
        item.transform = transform & ~NativeWindowTransform::InverseDisplay;
        item.transform_to_display_inverse =
            (transform & NativeWindowTransform::InverseDisplay) != NativeWindowTransform::None;
        item.scaling_mode = static_cast<u32>(scaling_mode);
        item.timestamp = timestamp;
        item.is_auto_timestamp = is_auto_timestamp;
        item.frame_number = core->frame_counter;
        item.slot = slot;
        item.fence = fence;
        item.is_droppable = core->dequeue_buffer_cannot_block || async;
        item.swap_interval = swap_interval;

        sticky_transform = sticky_transform_;

        if (core->queue.empty()) {
            // When the queue is empty, we can simply queue this buffer
            core->queue.push_back(item);
            frame_available_listener = core->consumer_listener;
        } else {
            // When the queue is not empty, we need to look at the front buffer
            // state to see if we need to replace it
            auto front(core->queue.begin());

            if (front->is_droppable) {
                // If the front queued buffer is still being tracked, we first
                // mark it as freed
                if (core->StillTracking(*front)) {
                    slots[front->slot].buffer_state = BufferState::Free;
                    // Reset the frame number of the freed buffer so that it is the first in line to
                    // be dequeued again
                    slots[front->slot].frame_number = 0;
                }
                // Overwrite the droppable buffer with the incoming one
                *front = item;
                frame_replaced_listener = core->consumer_listener;
            } else {
                core->queue.push_back(item);
                frame_available_listener = core->consumer_listener;
            }
        }

        core->buffer_has_been_queued = true;
        core->SignalDequeueCondition();
        output->Inflate(core->default_width, core->default_height, core->transform_hint,
                        static_cast<u32>(core->queue.size()));

        // Take a ticket for the callback functions
        callback_ticket = next_callback_ticket++;
    }

    // Don't send the GraphicBuffer through the callback, and don't send the slot number, since the
    // consumer shouldn't need it
    item.graphic_buffer.reset();
    item.slot = BufferItem::INVALID_BUFFER_SLOT;

    // Call back without the main BufferQueue lock held, but with the callback lock held so we can
    // ensure that callbacks occur in order
    {
        std::scoped_lock lock{callback_mutex};
        while (callback_ticket != current_callback_ticket) {
            callback_condition.wait(callback_mutex);
        }

        if (frame_available_listener != nullptr) {
            frame_available_listener->OnFrameAvailable(item);
        } else if (frame_replaced_listener != nullptr) {
            frame_replaced_listener->OnFrameReplaced(item);
        }

        ++current_callback_ticket;
        callback_condition.notify_all();
    }

    return Status::NoError;
}

void BufferQueueProducer::CancelBuffer(s32 slot, const Fence& fence) {
    LOG_DEBUG(Service_Nvnflinger, "slot {}", slot);

    std::scoped_lock lock{core->mutex};

    if (core->is_abandoned) {
        LOG_ERROR(Service_Nvnflinger, "BufferQueue has been abandoned");
        return;
    }

    if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
        LOG_ERROR(Service_Nvnflinger, "slot index {} out of range [0, {})", slot,
                  BufferQueueDefs::NUM_BUFFER_SLOTS);
        return;
    } else if (slots[slot].buffer_state != BufferState::Dequeued) {
        LOG_ERROR(Service_Nvnflinger, "slot {} is not owned by the producer (state = {})", slot,
                  slots[slot].buffer_state);
        return;
    }

    slots[slot].buffer_state = BufferState::Free;
    slots[slot].frame_number = 0;
    slots[slot].fence = fence;

    core->SignalDequeueCondition();
    buffer_wait_event->Signal();
}

Status BufferQueueProducer::Query(NativeWindow what, s32* out_value) {
    std::scoped_lock lock{core->mutex};

    if (out_value == nullptr) {
        LOG_ERROR(Service_Nvnflinger, "outValue was nullptr");
        return Status::BadValue;
    }

    if (core->is_abandoned) {
        LOG_ERROR(Service_Nvnflinger, "BufferQueue has been abandoned");
        return Status::NoInit;
    }

    u32 value{};
    switch (what) {
    case NativeWindow::Width:
        value = core->default_width;
        break;
    case NativeWindow::Height:
        value = core->default_height;
        break;
    case NativeWindow::Format:
        value = static_cast<u32>(core->default_buffer_format);
        break;
    case NativeWindow::MinUndequeedBuffers:
        value = core->GetMinUndequeuedBufferCountLocked(false);
        break;
    case NativeWindow::StickyTransform:
        value = sticky_transform;
        break;
    case NativeWindow::ConsumerRunningBehind:
        value = (core->queue.size() > 1);
        break;
    case NativeWindow::ConsumerUsageBits:
        value = core->consumer_usage_bit;
        break;
    default:
        ASSERT(false);
        return Status::BadValue;
    }

    LOG_DEBUG(Service_Nvnflinger, "what = {}, value = {}", what, value);

    *out_value = static_cast<s32>(value);

    return Status::NoError;
}

Status BufferQueueProducer::Connect(const std::shared_ptr<IProducerListener>& listener,
                                    NativeWindowApi api, bool producer_controlled_by_app,
                                    QueueBufferOutput* output) {
    std::scoped_lock lock{core->mutex};

    LOG_DEBUG(Service_Nvnflinger, "api = {} producer_controlled_by_app = {}", api,
              producer_controlled_by_app);

    if (core->is_abandoned) {
        LOG_ERROR(Service_Nvnflinger, "BufferQueue has been abandoned");
        return Status::NoInit;
    }

    if (core->consumer_listener == nullptr) {
        LOG_ERROR(Service_Nvnflinger, "BufferQueue has no consumer");
        return Status::NoInit;
    }

    if (output == nullptr) {
        LOG_ERROR(Service_Nvnflinger, "output was nullptr");
        return Status::BadValue;
    }

    if (core->connected_api != NativeWindowApi::NoConnectedApi) {
        LOG_ERROR(Service_Nvnflinger, "already connected (cur = {} req = {})", core->connected_api,
                  api);
        return Status::BadValue;
    }

    Status status = Status::NoError;
    switch (api) {
    case NativeWindowApi::Egl:
    case NativeWindowApi::Cpu:
    case NativeWindowApi::Media:
    case NativeWindowApi::Camera:
        core->connected_api = api;
        output->Inflate(core->default_width, core->default_height, core->transform_hint,
                        static_cast<u32>(core->queue.size()));
        core->connected_producer_listener = listener;
        break;
    default:
        LOG_ERROR(Service_Nvnflinger, "unknown api = {}", api);
        status = Status::BadValue;
        break;
    }

    core->buffer_has_been_queued = false;
    core->dequeue_buffer_cannot_block =
        core->consumer_controlled_by_app && producer_controlled_by_app;

    return status;
}

Status BufferQueueProducer::Disconnect(NativeWindowApi api) {
    LOG_DEBUG(Service_Nvnflinger, "api = {}", api);

    Status status = Status::NoError;
    std::shared_ptr<IConsumerListener> listener;

    {
        std::scoped_lock lock{core->mutex};

        core->WaitWhileAllocatingLocked();

        if (core->is_abandoned) {
            // Disconnecting after the surface has been abandoned is a no-op.
            return Status::NoError;
        }

        switch (api) {
        case NativeWindowApi::Egl:
        case NativeWindowApi::Cpu:
        case NativeWindowApi::Media:
        case NativeWindowApi::Camera:
            if (core->connected_api == api) {
                core->queue.clear();
                core->FreeAllBuffersLocked();
                core->connected_producer_listener = nullptr;
                core->connected_api = NativeWindowApi::NoConnectedApi;
                core->SignalDequeueCondition();
                buffer_wait_event->Signal();
                listener = core->consumer_listener;
            } else {
                LOG_ERROR(Service_Nvnflinger, "still connected to another api (cur = {} req = {})",
                          core->connected_api, api);
                status = Status::BadValue;
            }
            break;
        default:
            LOG_ERROR(Service_Nvnflinger, "unknown api = {}", api);
            status = Status::BadValue;
            break;
        }
    }

    // Call back without lock held
    if (listener != nullptr) {
        listener->OnBuffersReleased();
    }

    return status;
}

Status BufferQueueProducer::SetPreallocatedBuffer(s32 slot,
                                                  const std::shared_ptr<NvGraphicBuffer>& buffer) {
    LOG_DEBUG(Service_Nvnflinger, "slot {}", slot);

    if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
        return Status::BadValue;
    }

    std::scoped_lock lock{core->mutex};

    slots[slot] = {};
    slots[slot].fence = Fence::NoFence();
    slots[slot].graphic_buffer = std::make_shared<GraphicBuffer>(nvmap, buffer);
    slots[slot].frame_number = 0;

    // Most games preallocate a buffer and pass a valid buffer here. However, it is possible for
    // this to be called with an empty buffer, Naruto Ultimate Ninja Storm is a game that does this.
    if (buffer) {
        slots[slot].is_preallocated = true;

        core->override_max_buffer_count = core->GetPreallocatedBufferCountLocked();
        core->default_width = buffer->Width();
        core->default_height = buffer->Height();
        core->default_buffer_format = buffer->Format();
    }

    core->SignalDequeueCondition();
    buffer_wait_event->Signal();

    return Status::NoError;
}

void BufferQueueProducer::Transact(u32 code, std::span<const u8> parcel_data,
                                   std::span<u8> parcel_reply, u32 flags) {
    // Values used by BnGraphicBufferProducer onTransact
    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,
    };

    Status status{Status::NoError};
    InputParcel parcel_in{parcel_data};
    OutputParcel parcel_out{};

    switch (static_cast<TransactionId>(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<NvGraphicBuffer>();

        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.WriteFlattenedObject(&fence);
        break;
    }
    case TransactionId::RequestBuffer: {
        const auto slot = parcel_in.Read<s32>();

        std::shared_ptr<GraphicBuffer> buf;

        status = RequestBuffer(slot, &buf);

        parcel_out.WriteFlattenedObject<NvGraphicBuffer>(buf.get());
        break;
    }
    case TransactionId::QueueBuffer: {
        const auto slot = parcel_in.Read<s32>();

        QueueBufferInput input{parcel_in};
        QueueBufferOutput output;

        status = QueueBuffer(slot, input, &output);

        parcel_out.Write(output);
        break;
    }
    case TransactionId::Query: {
        const auto what = parcel_in.Read<NativeWindow>();

        s32 value{};

        status = Query(what, &value);

        parcel_out.Write(value);
        break;
    }
    case TransactionId::CancelBuffer: {
        const auto slot = parcel_in.Read<s32>();
        const auto fence = parcel_in.ReadFlattened<Fence>();

        CancelBuffer(slot, fence);
        break;
    }
    case TransactionId::Disconnect: {
        const auto api = parcel_in.Read<NativeWindowApi>();

        status = Disconnect(api);
        break;
    }
    case TransactionId::DetachBuffer: {
        const auto slot = parcel_in.Read<s32>();

        status = DetachBuffer(slot);
        break;
    }
    case TransactionId::SetBufferCount: {
        const auto buffer_count = parcel_in.Read<s32>();

        status = SetBufferCount(buffer_count);
        break;
    }
    case TransactionId::GetBufferHistory:
        LOG_WARNING(Service_Nvnflinger, "(STUBBED) called, transaction=GetBufferHistory");
        break;
    default:
        ASSERT_MSG(false, "Unimplemented TransactionId {}", code);
        break;
    }

    parcel_out.Write(status);

    const auto serialized = parcel_out.Serialize();
    std::memcpy(parcel_reply.data(), serialized.data(),
                std::min(parcel_reply.size(), serialized.size()));
}

Kernel::KReadableEvent* BufferQueueProducer::GetNativeHandle(u32 type_id) {
    return &buffer_wait_event->GetReadableEvent();
}

} // namespace Service::android


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Parts of this implementation were based on:
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferQueueProducer.h

#pragma once

#include <condition_variable>
#include <memory>
#include <mutex>

#include "common/common_funcs.h"
#include "core/hle/service/nvdrv/nvdata.h"
#include "core/hle/service/nvnflinger/binder.h"
#include "core/hle/service/nvnflinger/buffer_queue_defs.h"
#include "core/hle/service/nvnflinger/buffer_slot.h"
#include "core/hle/service/nvnflinger/graphic_buffer_producer.h"
#include "core/hle/service/nvnflinger/pixel_format.h"
#include "core/hle/service/nvnflinger/status.h"
#include "core/hle/service/nvnflinger/window.h"

namespace Kernel {
class KernelCore;
class KEvent;
class KReadableEvent;
} // namespace Kernel

namespace Service::KernelHelpers {
class ServiceContext;
} // namespace Service::KernelHelpers

namespace Service::Nvidia::NvCore {
class NvMap;
} // namespace Service::Nvidia::NvCore

namespace Service::android {

class BufferQueueCore;
class IProducerListener;
struct NvGraphicBuffer;

class BufferQueueProducer final : public IBinder {
public:
    explicit BufferQueueProducer(Service::KernelHelpers::ServiceContext& service_context_,
                                 std::shared_ptr<BufferQueueCore> buffer_queue_core_,
                                 Service::Nvidia::NvCore::NvMap& nvmap_);
    ~BufferQueueProducer() override;

    void Transact(u32 code, std::span<const u8> parcel_data, std::span<u8> parcel_reply,
                  u32 flags) override;

    Kernel::KReadableEvent* GetNativeHandle(u32 type_id) 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<NvGraphicBuffer>& buffer);

private:
    BufferQueueProducer(const BufferQueueProducer&) = delete;

    Status WaitForFreeSlotThenRelock(bool async, s32* found, Status* return_flags,
                                     std::unique_lock<std::mutex>& lk) const;

    Kernel::KEvent* buffer_wait_event{};
    Service::KernelHelpers::ServiceContext& service_context;

    std::shared_ptr<BufferQueueCore> core;
    BufferQueueDefs::SlotsType& slots;
    u32 sticky_transform{};
    std::mutex callback_mutex;
    s32 next_callback_ticket{};
    s32 current_callback_ticket{};
    std::condition_variable_any callback_condition;

    Service::Nvidia::NvCore::NvMap& nvmap;
};

} // namespace Service::android

repo_name: litucks/torzu

license: mit


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Parts of this implementation were based on:
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferSlot.h

#pragma once

#include <memory>

#include "common/common_types.h"
#include "core/hle/service/nvnflinger/ui/fence.h"

namespace Service::android {

class GraphicBuffer;

enum class BufferState : u32 {
    Free = 0,
    Dequeued = 1,
    Queued = 2,
    Acquired = 3,
};

struct BufferSlot final {
    constexpr BufferSlot() = default;

    std::shared_ptr<GraphicBuffer> graphic_buffer;
    BufferState buffer_state{BufferState::Free};
    bool request_buffer_called{};
    u64 frame_number{};
    Fence fence;
    bool acquire_called{};
    bool needs_cleanup_on_release{};
    bool attached_by_consumer{};
    bool is_preallocated{};
};

} // namespace Service::android


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later

#pragma once

#include "common/common_funcs.h"
#include "common/common_types.h"

namespace Service::android {

enum class 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,
};
DECLARE_ENUM_FLAG_OPERATORS(BufferTransformFlags);

} // namespace Service::android


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2010 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Parts of this implementation were based on:
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/ConsumerBase.cpp

#include "common/assert.h"
#include "common/logging/log.h"
#include "core/hle/service/nvnflinger/buffer_item.h"
#include "core/hle/service/nvnflinger/buffer_queue_consumer.h"
#include "core/hle/service/nvnflinger/buffer_queue_core.h"
#include "core/hle/service/nvnflinger/consumer_base.h"
#include "core/hle/service/nvnflinger/ui/graphic_buffer.h"

namespace Service::android {

ConsumerBase::ConsumerBase(std::shared_ptr<BufferQueueConsumer> consumer_)
    : consumer{std::move(consumer_)} {}

ConsumerBase::~ConsumerBase() {
    std::scoped_lock lock{mutex};

    ASSERT_MSG(is_abandoned, "consumer is not abandoned!");
}

void ConsumerBase::Connect(bool controlled_by_app) {
    consumer->Connect(shared_from_this(), controlled_by_app);
}

void ConsumerBase::Abandon() {
    LOG_DEBUG(Service_Nvnflinger, "called");

    std::scoped_lock lock{mutex};

    if (!is_abandoned) {
        this->AbandonLocked();
        is_abandoned = true;
    }
}

void ConsumerBase::AbandonLocked() {
    for (int i = 0; i < BufferQueueDefs::NUM_BUFFER_SLOTS; i++) {
        this->FreeBufferLocked(i);
    }
    // disconnect from the BufferQueue
    consumer->Disconnect();
    consumer = nullptr;
}

void ConsumerBase::FreeBufferLocked(s32 slot_index) {
    LOG_DEBUG(Service_Nvnflinger, "slot_index={}", slot_index);

    slots[slot_index].graphic_buffer = nullptr;
    slots[slot_index].fence = Fence::NoFence();
    slots[slot_index].frame_number = 0;
}

void ConsumerBase::OnFrameAvailable(const BufferItem& item) {
    LOG_DEBUG(Service_Nvnflinger, "called");
}

void ConsumerBase::OnFrameReplaced(const BufferItem& item) {
    LOG_DEBUG(Service_Nvnflinger, "called");
}

void ConsumerBase::OnBuffersReleased() {
    std::scoped_lock lock{mutex};

    LOG_DEBUG(Service_Nvnflinger, "called");

    if (is_abandoned) {
        // Nothing to do if we're already abandoned.
        return;
    }

    u64 mask = 0;
    consumer->GetReleasedBuffers(&mask);
    for (int i = 0; i < BufferQueueDefs::NUM_BUFFER_SLOTS; i++) {
        if (mask & (1ULL << i)) {
            FreeBufferLocked(i);
        }
    }
}

void ConsumerBase::OnSidebandStreamChanged() {}

Status ConsumerBase::AcquireBufferLocked(BufferItem* item, std::chrono::nanoseconds present_when) {
    Status err = consumer->AcquireBuffer(item, present_when);
    if (err != Status::NoError) {
        return err;
    }

    if (item->graphic_buffer != nullptr) {
        slots[item->slot].graphic_buffer = item->graphic_buffer;
    }

    slots[item->slot].frame_number = item->frame_number;
    slots[item->slot].fence = item->fence;

    LOG_DEBUG(Service_Nvnflinger, "slot={}", item->slot);

    return Status::NoError;
}

Status ConsumerBase::AddReleaseFenceLocked(s32 slot,
                                           const std::shared_ptr<GraphicBuffer>& graphic_buffer,
                                           const Fence& fence) {
    LOG_DEBUG(Service_Nvnflinger, "slot={}", slot);

    // If consumer no longer tracks this graphic_buffer, we can safely
    // drop this fence, as it will never be received by the producer.

    if (!StillTracking(slot, graphic_buffer)) {
        return Status::NoError;
    }

    slots[slot].fence = fence;

    return Status::NoError;
}

Status ConsumerBase::ReleaseBufferLocked(s32 slot,
                                         const std::shared_ptr<GraphicBuffer>& graphic_buffer) {
    // If consumer no longer tracks this graphic_buffer (we received a new
    // buffer on the same slot), the buffer producer is definitely no longer
    // tracking it.

    if (!StillTracking(slot, graphic_buffer)) {
        return Status::NoError;
    }

    LOG_DEBUG(Service_Nvnflinger, "slot={}", slot);
    Status err = consumer->ReleaseBuffer(slot, slots[slot].frame_number, slots[slot].fence);
    if (err == Status::StaleBufferSlot) {
        FreeBufferLocked(slot);
    }

    slots[slot].fence = Fence::NoFence();

    return err;
}

bool ConsumerBase::StillTracking(s32 slot,
                                 const std::shared_ptr<GraphicBuffer>& graphic_buffer) const {
    if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
        return false;
    }

    return (slots[slot].graphic_buffer != nullptr &&
            slots[slot].graphic_buffer->Handle() == graphic_buffer->Handle());
}

} // namespace Service::android


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2010 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Parts of this implementation were based on:
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/ConsumerBase.h

#pragma once

#include <array>
#include <chrono>
#include <memory>
#include <mutex>

#include "common/common_types.h"
#include "core/hle/service/nvnflinger/buffer_queue_defs.h"
#include "core/hle/service/nvnflinger/consumer_listener.h"
#include "core/hle/service/nvnflinger/status.h"

namespace Service::android {

class BufferItem;
class BufferQueueConsumer;

class ConsumerBase : public IConsumerListener, public std::enable_shared_from_this<ConsumerBase> {
public:
    void Connect(bool controlled_by_app);
    void Abandon();

protected:
    explicit ConsumerBase(std::shared_ptr<BufferQueueConsumer> consumer_);
    ~ConsumerBase() override;

    void OnFrameAvailable(const BufferItem& item) override;
    void OnFrameReplaced(const BufferItem& item) override;
    void OnBuffersReleased() override;
    void OnSidebandStreamChanged() override;

    void AbandonLocked();
    void FreeBufferLocked(s32 slot_index);
    Status AcquireBufferLocked(BufferItem* item, std::chrono::nanoseconds present_when);
    Status ReleaseBufferLocked(s32 slot, const std::shared_ptr<GraphicBuffer>& graphic_buffer);
    bool StillTracking(s32 slot, const std::shared_ptr<GraphicBuffer>& graphic_buffer) const;
    Status AddReleaseFenceLocked(s32 slot, const std::shared_ptr<GraphicBuffer>& graphic_buffer,
                                 const Fence& fence);

    struct Slot final {
        std::shared_ptr<GraphicBuffer> graphic_buffer;
        Fence fence;
        u64 frame_number{};
    };

protected:
    std::array<Slot, BufferQueueDefs::NUM_BUFFER_SLOTS> slots;

    bool is_abandoned{};

    std::shared_ptr<BufferQueueConsumer> consumer;

    mutable std::mutex mutex;
};

} // namespace Service::android


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Parts of this implementation were based on:
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/IConsumerListener.h

#pragma once

namespace Service::android {

class BufferItem;

/// ConsumerListener is the interface through which the BufferQueue notifies the consumer of events
/// that the consumer may wish to react to.
class IConsumerListener {
public:
    IConsumerListener() = default;
    virtual ~IConsumerListener() = default;

    virtual void OnFrameAvailable(const BufferItem& item) = 0;
    virtual void OnFrameReplaced(const BufferItem& item) = 0;
    virtual void OnBuffersReleased() = 0;
    virtual void OnSidebandStreamChanged() = 0;
};

}; // namespace Service::android


code:
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "core/hle/service/nvnflinger/buffer_item_consumer.h"
#include "core/hle/service/nvnflinger/hwc_layer.h"

namespace Service::Nvnflinger {

struct Layer {
    explicit Layer(std::shared_ptr<android::BufferItemConsumer> buffer_item_consumer_,
                   s32 consumer_id_)
        : buffer_item_consumer(std::move(buffer_item_consumer_)), consumer_id(consumer_id_),
          blending(LayerBlending::None), visible(true) {}
    ~Layer() {
        buffer_item_consumer->Abandon();
    }

    std::shared_ptr<android::BufferItemConsumer> buffer_item_consumer;
    s32 consumer_id;
    LayerBlending blending;
    bool visible;
};

struct LayerStack {
    std::vector<std::shared_ptr<Layer>> layers;

    std::shared_ptr<Layer> FindLayer(s32 consumer_id) {
        for (auto& layer : layers) {
            if (layer->consumer_id == consumer_id) {
                return layer;
            }
        }

        return nullptr;
    }

    bool HasLayers() {
        return !layers.empty();
    }
};

struct Display {
    explicit Display(u64 id_) {
        id = id_;
    }

    u64 id;
    LayerStack stack;
};

} // namespace Service::Nvnflinger


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2010 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Parts of this implementation were based on:
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/IGraphicBufferProducer.cpp

#include "core/hle/service/nvnflinger/graphic_buffer_producer.h"
#include "core/hle/service/nvnflinger/parcel.h"

namespace Service::android {

QueueBufferInput::QueueBufferInput(InputParcel& parcel) {
    parcel.ReadFlattened(*this);
}

QueueBufferOutput::QueueBufferOutput() = default;

} // namespace Service::android


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2010 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Parts of this implementation were based on:
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/IGraphicBufferProducer.h

#pragma once

#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/math_util.h"
#include "core/hle/service/nvnflinger/ui/fence.h"
#include "core/hle/service/nvnflinger/window.h"

namespace Service::android {

class InputParcel;

#pragma pack(push, 1)
struct QueueBufferInput final {
    explicit QueueBufferInput(InputParcel& parcel);
    explicit QueueBufferInput() = default;

    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;
    }

    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


code:
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later

#include <boost/container/small_vector.hpp>

#include "common/microprofile.h"
#include "core/hle/service/nvdrv/devices/nvdisp_disp0.h"
#include "core/hle/service/nvnflinger/buffer_item.h"
#include "core/hle/service/nvnflinger/buffer_item_consumer.h"
#include "core/hle/service/nvnflinger/hardware_composer.h"
#include "core/hle/service/nvnflinger/hwc_layer.h"
#include "core/hle/service/nvnflinger/ui/graphic_buffer.h"

namespace Service::Nvnflinger {

namespace {

s32 NormalizeSwapInterval(f32* out_speed_scale, s32 swap_interval) {
    if (swap_interval <= 0) {
        // As an extension, treat nonpositive swap interval as speed multiplier.
        if (out_speed_scale) {
            *out_speed_scale = 2.f * static_cast<f32>(1 - swap_interval);
        }

        swap_interval = 1;
    }

    if (swap_interval >= 5) {
        // As an extension, treat high swap interval as precise speed control.
        if (out_speed_scale) {
            *out_speed_scale = static_cast<f32>(swap_interval) / 100.f;
        }

        swap_interval = 1;
    }

    return swap_interval;
}

} // namespace

HardwareComposer::HardwareComposer() = default;
HardwareComposer::~HardwareComposer() = default;

u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, Display& display,
                                    Nvidia::Devices::nvdisp_disp0& nvdisp) {
    boost::container::small_vector<HwcLayer, 2> composition_stack;

    // Set default speed limit to 100%.
    *out_speed_scale = 1.0f;

    // Determine the number of vsync periods to wait before composing again.
    std::optional<s32> swap_interval{};
    bool has_acquired_buffer{};

    // Acquire all necessary framebuffers.
    for (auto& layer : display.stack.layers) {
        auto consumer_id = layer->consumer_id;

        // Try to fetch the framebuffer (either new or stale).
        const auto result = this->CacheFramebufferLocked(*layer, consumer_id);

        // If we failed, skip this layer.
        if (result == CacheStatus::NoBufferAvailable) {
            continue;
        }

        // If we acquired a new buffer, we need to present.
        if (result == CacheStatus::BufferAcquired) {
            has_acquired_buffer = true;
        }

        const auto& buffer = m_framebuffers[consumer_id];
        const auto& item = buffer.item;
        const auto& igbp_buffer = *item.graphic_buffer;

        // TODO: get proper Z-index from layer
        if (layer->visible) {
            composition_stack.emplace_back(HwcLayer{
                .buffer_handle = igbp_buffer.BufferId(),
                .offset = igbp_buffer.Offset(),
                .format = igbp_buffer.ExternalFormat(),
                .width = igbp_buffer.Width(),
                .height = igbp_buffer.Height(),
                .stride = igbp_buffer.Stride(),
                .z_index = 0,
                .blending = layer->blending,
                .transform = static_cast<android::BufferTransformFlags>(item.transform),
                .crop_rect = item.crop,
                .acquire_fence = item.fence,
            });
        }

        // We need to compose again either before this frame is supposed to
        // be released, or exactly on the vsync period it should be released.
        const s32 item_swap_interval = NormalizeSwapInterval(out_speed_scale, item.swap_interval);

        // TODO: handle cases where swap intervals are relatively prime. So far,
        // only swap intervals of 0, 1 and 2 have been observed, but if 3 were
        // to be introduced, this would cause an issue.
        if (swap_interval) {
            swap_interval = std::min(*swap_interval, item_swap_interval);
        } else {
            swap_interval = item_swap_interval;
        }
    }

    // If any new buffers were acquired, we can present.
    if (has_acquired_buffer) {
        // Sort by Z-index.
        std::stable_sort(composition_stack.begin(), composition_stack.end(),
                         [&](auto& l, auto& r) { return l.z_index < r.z_index; });

        // Composite.
        nvdisp.Composite(composition_stack);
    }

    // Render MicroProfile.
    MicroProfileFlip();

    // Advance by at least one frame.
    const u32 frame_advance = swap_interval.value_or(1);
    m_frame_number += frame_advance;

    // Release any necessary framebuffers.
    for (auto& [layer_id, framebuffer] : m_framebuffers) {
        if (framebuffer.release_frame_number > m_frame_number) {
            // Not yet ready to release this framebuffer.
            continue;
        }

        if (!framebuffer.is_acquired) {
            // Already released.
            continue;
        }

        if (const auto layer = display.stack.FindLayer(layer_id); layer != nullptr) {
            // TODO: support release fence
            // This is needed to prevent screen tearing
            layer->buffer_item_consumer->ReleaseBuffer(framebuffer.item, android::Fence::NoFence());
            framebuffer.is_acquired = false;
        }
    }

    return frame_advance;
}

void HardwareComposer::RemoveLayerLocked(Display& display, ConsumerId consumer_id) {
    // Check if we are tracking a slot with this consumer_id.
    const auto it = m_framebuffers.find(consumer_id);
    if (it == m_framebuffers.end()) {
        return;
    }

    // Try to release the buffer item.
    const auto layer = display.stack.FindLayer(consumer_id);
    if (layer && it->second.is_acquired) {
        layer->buffer_item_consumer->ReleaseBuffer(it->second.item, android::Fence::NoFence());
    }

    // Erase the slot.
    m_framebuffers.erase(it);
}

bool HardwareComposer::TryAcquireFramebufferLocked(Layer& layer, Framebuffer& framebuffer) {
    // Attempt the update.
    const auto status = layer.buffer_item_consumer->AcquireBuffer(&framebuffer.item, {}, false);
    if (status != android::Status::NoError) {
        return false;
    }

    // We succeeded, so set the new release frame info.
    framebuffer.release_frame_number =
        NormalizeSwapInterval(nullptr, framebuffer.item.swap_interval);
    framebuffer.is_acquired = true;

    return true;
}

HardwareComposer::CacheStatus HardwareComposer::CacheFramebufferLocked(Layer& layer,
                                                                       ConsumerId consumer_id) {
    // Check if this framebuffer is already present.
    const auto it = m_framebuffers.find(consumer_id);
    if (it != m_framebuffers.end()) {
        // If it's currently still acquired, we are done.
        if (it->second.is_acquired) {
            return CacheStatus::CachedBufferReused;
        }

        // Try to acquire a new item.
        if (this->TryAcquireFramebufferLocked(layer, it->second)) {
            // We got a new item.
            return CacheStatus::BufferAcquired;
        } else {
            // We didn't acquire a new item, but we can reuse the slot.
            return CacheStatus::CachedBufferReused;
        }
    }

    // Framebuffer is not present, so try to create it.
    Framebuffer framebuffer{};

    if (this->TryAcquireFramebufferLocked(layer, framebuffer)) {
        // Move the buffer item into a new slot.
        m_framebuffers.emplace(consumer_id, std::move(framebuffer));

        // We succeeded.
        return CacheStatus::BufferAcquired;
    }

    // We couldn't acquire the buffer item, so don't create a slot.
    return CacheStatus::NoBufferAvailable;
}

} // namespace Service::Nvnflinger


code:
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later

#pragma once

#include <boost/container/flat_map.hpp>

#include "core/hle/service/nvnflinger/buffer_item.h"
#include "core/hle/service/nvnflinger/display.h"

namespace Service::Nvidia::Devices {
class nvdisp_disp0;
}

namespace Service::Nvnflinger {

using ConsumerId = s32;

class HardwareComposer {
public:
    explicit HardwareComposer();
    ~HardwareComposer();

    u32 ComposeLocked(f32* out_speed_scale, Display& display,
                      Nvidia::Devices::nvdisp_disp0& nvdisp);
    void RemoveLayerLocked(Display& display, ConsumerId consumer_id);

private:
    u64 m_frame_number{0};

private:
    using ReleaseFrameNumber = u64;

    struct Framebuffer {
        android::BufferItem item{};
        ReleaseFrameNumber release_frame_number{};
        bool is_acquired{false};
    };

    enum class CacheStatus : u32 {
        NoBufferAvailable,
        BufferAcquired,
        CachedBufferReused,
    };

    boost::container::flat_map<ConsumerId, Framebuffer> m_framebuffers{};

private:
    bool TryAcquireFramebufferLocked(Layer& layer, Framebuffer& framebuffer);
    CacheStatus CacheFramebufferLocked(Layer& layer, ConsumerId consumer_id);
};

} // namespace Service::Nvnflinger


code:
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/nvnflinger/binder.h"
#include "core/hle/service/nvnflinger/hos_binder_driver.h"
#include "core/hle/service/nvnflinger/hos_binder_driver_server.h"

namespace Service::Nvnflinger {

IHOSBinderDriver::IHOSBinderDriver(Core::System& system_,
                                   std::shared_ptr<HosBinderDriverServer> server,
                                   std::shared_ptr<SurfaceFlinger> surface_flinger)
    : ServiceFramework{system_, "IHOSBinderDriver"}, m_server(server),
      m_surface_flinger(surface_flinger) {
    static const FunctionInfo functions[] = {
        {0, C<&IHOSBinderDriver::TransactParcel>, "TransactParcel"},
        {1, C<&IHOSBinderDriver::AdjustRefcount>, "AdjustRefcount"},
        {2, C<&IHOSBinderDriver::GetNativeHandle>, "GetNativeHandle"},
        {3, C<&IHOSBinderDriver::TransactParcelAuto>, "TransactParcelAuto"},
    };
    RegisterHandlers(functions);
}

IHOSBinderDriver::~IHOSBinderDriver() = default;

Result IHOSBinderDriver::TransactParcel(s32 binder_id, u32 transaction_id,
                                        InBuffer<BufferAttr_HipcMapAlias> parcel_data,
                                        OutBuffer<BufferAttr_HipcMapAlias> parcel_reply,
                                        u32 flags) {
    LOG_DEBUG(Service_VI, "called. id={} transaction={}, flags={}", binder_id, transaction_id,
              flags);

    const auto binder = m_server->TryGetBinder(binder_id);
    R_SUCCEED_IF(binder == nullptr);

    binder->Transact(transaction_id, parcel_data, parcel_reply, flags);

    R_SUCCEED();
}

Result IHOSBinderDriver::AdjustRefcount(s32 binder_id, s32 addval, s32 type) {
    LOG_WARNING(Service_VI, "(STUBBED) called id={}, addval={}, type={}", binder_id, addval, type);
    R_SUCCEED();
}

Result IHOSBinderDriver::GetNativeHandle(s32 binder_id, u32 type_id,
                                         OutCopyHandle<Kernel::KReadableEvent> out_handle) {
    LOG_WARNING(Service_VI, "(STUBBED) called id={}, type_id={}", binder_id, type_id);

    const auto binder = m_server->TryGetBinder(binder_id);
    R_UNLESS(binder != nullptr, ResultUnknown);

    *out_handle = binder->GetNativeHandle(type_id);

    R_SUCCEED();
}

Result IHOSBinderDriver::TransactParcelAuto(s32 binder_id, u32 transaction_id,
                                            InBuffer<BufferAttr_HipcAutoSelect> parcel_data,
                                            OutBuffer<BufferAttr_HipcAutoSelect> parcel_reply,
                                            u32 flags) {
    R_RETURN(this->TransactParcel(binder_id, transaction_id, parcel_data, parcel_reply, flags));
}

} // namespace Service::Nvnflinger


code:
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "core/hle/service/cmif_types.h"
#include "core/hle/service/service.h"

namespace Kernel {
class KReadableEvent;
}

namespace Service::Nvnflinger {

class HosBinderDriverServer;
class SurfaceFlinger;

class IHOSBinderDriver final : public ServiceFramework<IHOSBinderDriver> {
public:
    explicit IHOSBinderDriver(Core::System& system_, std::shared_ptr<HosBinderDriverServer> server,
                              std::shared_ptr<SurfaceFlinger> surface_flinger);
    ~IHOSBinderDriver() override;

    std::shared_ptr<SurfaceFlinger> GetSurfaceFlinger() {
        return m_surface_flinger;
    }

    std::shared_ptr<HosBinderDriverServer> GetServer() {
        return m_server;
    }

private:
    Result TransactParcel(s32 binder_id, u32 transaction_id,
                          InBuffer<BufferAttr_HipcMapAlias> parcel_data,
                          OutBuffer<BufferAttr_HipcMapAlias> parcel_reply, u32 flags);
    Result AdjustRefcount(s32 binder_id, s32 addval, s32 type);
    Result GetNativeHandle(s32 binder_id, u32 type_id,
                           OutCopyHandle<Kernel::KReadableEvent> out_handle);
    Result TransactParcelAuto(s32 binder_id, u32 transaction_id,
                              InBuffer<BufferAttr_HipcAutoSelect> parcel_data,
                              OutBuffer<BufferAttr_HipcAutoSelect> parcel_reply, u32 flags);

private:
    const std::shared_ptr<HosBinderDriverServer> m_server;
    const std::shared_ptr<SurfaceFlinger> m_surface_flinger;
};

} // namespace Service::Nvnflinger


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later

#include <mutex>

#include "common/common_types.h"
#include "core/hle/service/nvnflinger/hos_binder_driver_server.h"

namespace Service::Nvnflinger {

HosBinderDriverServer::HosBinderDriverServer() = default;
HosBinderDriverServer::~HosBinderDriverServer() = default;

s32 HosBinderDriverServer::RegisterBinder(std::shared_ptr<android::IBinder>&& binder) {
    std::scoped_lock lk{lock};

    last_id++;

    binders[last_id] = std::move(binder);

    return last_id;
}

void HosBinderDriverServer::UnregisterBinder(s32 binder_id) {
    std::scoped_lock lk{lock};

    binders.erase(binder_id);
}

std::shared_ptr<android::IBinder> HosBinderDriverServer::TryGetBinder(s32 id) const {
    std::scoped_lock lk{lock};

    if (auto search = binders.find(id); search != binders.end()) {
        return search->second;
    }

    return {};
}

} // namespace Service::Nvnflinger


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later

#pragma once

#include <memory>
#include <mutex>
#include <unordered_map>

#include "common/common_types.h"
#include "core/hle/service/nvnflinger/binder.h"

namespace Core {
class System;
}

namespace Service::Nvnflinger {

class HosBinderDriverServer final {
public:
    explicit HosBinderDriverServer();
    ~HosBinderDriverServer();

    s32 RegisterBinder(std::shared_ptr<android::IBinder>&& binder);
    void UnregisterBinder(s32 binder_id);

    std::shared_ptr<android::IBinder> TryGetBinder(s32 id) const;

private:
    std::unordered_map<s32, std::shared_ptr<android::IBinder>> binders;
    mutable std::mutex lock;
    s32 last_id{};
};

} // namespace Service::Nvnflinger


code:
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later

#pragma once

#include "common/math_util.h"
#include "core/hle/service/nvdrv/nvdata.h"
#include "core/hle/service/nvnflinger/buffer_transform_flags.h"
#include "core/hle/service/nvnflinger/pixel_format.h"
#include "core/hle/service/nvnflinger/ui/fence.h"

namespace Service::Nvnflinger {

// hwc_layer_t::blending values
enum class LayerBlending : u32 {
    // No blending
    None = 0x100,

    // ONE / ONE_MINUS_SRC_ALPHA
    Premultiplied = 0x105,

    // SRC_ALPHA / ONE_MINUS_SRC_ALPHA
    Coverage = 0x405,
};

struct HwcLayer {
    u32 buffer_handle;
    u32 offset;
    android::PixelFormat format;
    u32 width;
    u32 height;
    u32 stride;
    s32 z_index;
    LayerBlending blending;
    android::BufferTransformFlags transform;
    Common::Rectangle<int> crop_rect;
    android::Fence acquire_fence;
};

} // namespace Service::Nvnflinger


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later

#include "core/core.h"
#include "core/hle/service/nvnflinger/hos_binder_driver.h"
#include "core/hle/service/nvnflinger/hos_binder_driver_server.h"
#include "core/hle/service/nvnflinger/nvnflinger.h"
#include "core/hle/service/nvnflinger/surface_flinger.h"
#include "core/hle/service/server_manager.h"
#include "core/hle/service/sm/sm.h"

namespace Service::Nvnflinger {

void LoopProcess(Core::System& system) {
    const auto binder_server = std::make_shared<HosBinderDriverServer>();
    const auto surface_flinger = std::make_shared<SurfaceFlinger>(system, *binder_server);

    auto server_manager = std::make_unique<ServerManager>(system);
    server_manager->RegisterNamedService(
        "dispdrv", std::make_shared<IHOSBinderDriver>(system, binder_server, surface_flinger));
    ServerManager::RunServer(std::move(server_manager));
}

} // namespace Service::Nvnflinger


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later

#pragma once

namespace Core {
class System;
}

namespace Service::Nvnflinger {

void LoopProcess(Core::System& system);

} // namespace Service::Nvnflinger


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later

#pragma once

#include <memory>
#include <span>
#include <vector>

#include <boost/container/small_vector.hpp>

#include "common/alignment.h"
#include "common/assert.h"
#include "common/common_types.h"

namespace Service::android {

struct ParcelHeader {
    u32 data_size;
    u32 data_offset;
    u32 objects_size;
    u32 objects_offset;
};
static_assert(sizeof(ParcelHeader) == 16, "ParcelHeader has wrong size");

class InputParcel final {
public:
    explicit InputParcel(std::span<const u8> in_data) : read_buffer(std::move(in_data)) {
        DeserializeHeader();
        [[maybe_unused]] const std::u16string token = ReadInterfaceToken();
    }

    template <typename T>
    void Read(T& val) {
        static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable.");
        ASSERT(read_index + sizeof(T) <= read_buffer.size());

        std::memcpy(&val, read_buffer.data() + read_index, sizeof(T));
        read_index += sizeof(T);
        read_index = Common::AlignUp(read_index, 4);
    }

    template <typename T>
    T Read() {
        T val;
        Read(val);
        return val;
    }

    template <typename T>
    void ReadFlattened(T& val) {
        const auto flattened_size = Read<s64>();
        ASSERT(sizeof(T) == flattened_size);
        Read(val);
    }

    template <typename T>
    T ReadFlattened() {
        T val;
        ReadFlattened(val);
        return val;
    }

    template <typename T>
    T ReadUnaligned() {
        static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable.");
        ASSERT(read_index + sizeof(T) <= read_buffer.size());

        T val;
        std::memcpy(&val, read_buffer.data() + read_index, sizeof(T));
        read_index += sizeof(T);
        return val;
    }

    template <typename T>
    const std::shared_ptr<T> ReadObject() {
        static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable.");

        const auto is_valid{Read<bool>()};

        if (is_valid) {
            auto result = std::make_shared<T>();
            ReadFlattened(*result);
            return result;
        }

        return {};
    }

    std::u16string ReadInterfaceToken() {
        [[maybe_unused]] const u32 unknown = Read<u32>();
        const u32 length = Read<u32>();

        std::u16string token;
        token.reserve(length + 1);

        for (u32 ch = 0; ch < length + 1; ++ch) {
            token.push_back(ReadUnaligned<u16>());
        }

        read_index = Common::AlignUp(read_index, 4);

        return token;
    }

    void DeserializeHeader() {
        ASSERT(read_buffer.size() > sizeof(ParcelHeader));

        ParcelHeader header{};
        std::memcpy(&header, read_buffer.data(), sizeof(ParcelHeader));

        read_index = header.data_offset;
    }

private:
    std::span<const u8> read_buffer;
    std::size_t read_index = 0;
};

class OutputParcel final {
public:
    OutputParcel() = default;

    template <typename T>
    void Write(const T& val) {
        this->WriteImpl(val, m_data_buffer);
    }

    template <typename T>
    void WriteFlattenedObject(const T* ptr) {
        if (!ptr) {
            this->Write<u32>(0);
            return;
        }

        this->Write<u32>(1);
        this->Write<s64>(sizeof(T));
        this->Write(*ptr);
    }

    template <typename T>
    void WriteFlattenedObject(const std::shared_ptr<T> ptr) {
        this->WriteFlattenedObject(ptr.get());
    }

    template <typename T>
    void WriteInterface(const T& val) {
        this->WriteImpl(val, m_data_buffer);
        this->WriteImpl(0U, m_object_buffer);
    }

    std::span<u8> Serialize() {
        m_output_buffer.resize(sizeof(ParcelHeader) + m_data_buffer.size() +
                               m_object_buffer.size());

        ParcelHeader header{};
        header.data_size = static_cast<u32>(m_data_buffer.size());
        header.data_offset = sizeof(ParcelHeader);
        header.objects_size = static_cast<u32>(m_object_buffer.size());
        header.objects_offset = header.data_offset + header.data_size;

        std::memcpy(m_output_buffer.data(), &header, sizeof(ParcelHeader));
        std::ranges::copy(m_data_buffer, m_output_buffer.data() + header.data_offset);
        std::ranges::copy(m_object_buffer, m_output_buffer.data() + header.objects_offset);

        return m_output_buffer;
    }

private:
    template <typename T, size_t BufferSize>
        requires(std::is_trivially_copyable_v<T>)
    void WriteImpl(const T& val, boost::container::small_vector<u8, BufferSize>& buffer) {
        const size_t aligned_size = Common::AlignUp(sizeof(T), 4);
        const size_t old_size = buffer.size();
        buffer.resize(old_size + aligned_size);

        std::memcpy(buffer.data() + old_size, &val, sizeof(T));
    }

private:
    boost::container::small_vector<u8, 0x1B0> m_data_buffer;
    boost::container::small_vector<u8, 0x40> m_object_buffer;
    boost::container::small_vector<u8, 0x200> m_output_buffer;
};

} // namespace Service::android


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later

#pragma once

#include "common/common_types.h"

namespace Service::android {

enum class PixelFormat : u32 {
    NoFormat = 0,
    Rgba8888 = 1,
    Rgbx8888 = 2,
    Rgb888 = 3,
    Rgb565 = 4,
    Bgra8888 = 5,
    Rgba5551 = 6,
    Rgba4444 = 7,
};

} // namespace Service::android


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Parts of this implementation were based on:
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/IProducerListener.h

#pragma once

namespace Service::android {

class IProducerListener {
public:
    virtual ~IProducerListener() = default;
    virtual void OnBufferReleased() = 0;
};

} // namespace Service::android


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later

#pragma once

#include "common/common_funcs.h"
#include "common/common_types.h"

namespace Service::android {

enum class Status : s32 {
    None = 0,
    NoError = 0,
    StaleBufferSlot = 1,
    NoBufferAvailable = 2,
    PresentLater = 3,
    WouldBlock = -11,
    NoMemory = -12,
    Busy = -16,
    NoInit = -19,
    BadValue = -22,
    InvalidOperation = -38,
    BufferNeedsReallocation = 1,
    ReleaseAllBuffers = 2,
};
DECLARE_ENUM_FLAG_OPERATORS(Status);

} // namespace Service::android


code:
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "core/core.h"
#include "core/hle/service/nvdrv/devices/nvdisp_disp0.h"
#include "core/hle/service/nvdrv/nvdrv_interface.h"
#include "core/hle/service/nvnflinger/display.h"
#include "core/hle/service/nvnflinger/hos_binder_driver_server.h"
#include "core/hle/service/nvnflinger/surface_flinger.h"
#include "core/hle/service/sm/sm.h"

#include "core/hle/service/nvnflinger/buffer_queue_consumer.h"
#include "core/hle/service/nvnflinger/buffer_queue_core.h"
#include "core/hle/service/nvnflinger/buffer_queue_producer.h"

namespace Service::Nvnflinger {

SurfaceFlinger::SurfaceFlinger(Core::System& system, HosBinderDriverServer& server)
    : m_system(system), m_server(server), m_context(m_system, "SurfaceFlinger") {
    nvdrv = m_system.ServiceManager().GetService<Nvidia::NVDRV>("nvdrv:s", true)->GetModule();
    disp_fd = nvdrv->Open("/dev/nvdisp_disp0", {});
}

SurfaceFlinger::~SurfaceFlinger() {
    nvdrv->Close(disp_fd);
}

void SurfaceFlinger::AddDisplay(u64 display_id) {
    m_displays.emplace_back(display_id);
}

void SurfaceFlinger::RemoveDisplay(u64 display_id) {
    std::erase_if(m_displays, [&](auto& display) { return display.id == display_id; });
}

bool SurfaceFlinger::ComposeDisplay(s32* out_swap_interval, f32* out_compose_speed_scale,
                                    u64 display_id) {
    auto* const display = this->FindDisplay(display_id);
    if (!display || !display->stack.HasLayers()) {
        return false;
    }

    *out_swap_interval =
        m_composer.ComposeLocked(out_compose_speed_scale, *display,
                                 *nvdrv->GetDevice<Nvidia::Devices::nvdisp_disp0>(disp_fd));
    return true;
}

void SurfaceFlinger::CreateLayer(s32 consumer_binder_id) {
    auto binder = std::static_pointer_cast<android::BufferQueueConsumer>(
        m_server.TryGetBinder(consumer_binder_id));
    if (!binder) {
        return;
    }

    auto buffer_item_consumer = std::make_shared<android::BufferItemConsumer>(std::move(binder));
    buffer_item_consumer->Connect(false);

    m_layers.layers.emplace_back(
        std::make_shared<Layer>(std::move(buffer_item_consumer), consumer_binder_id));
}

void SurfaceFlinger::DestroyLayer(s32 consumer_binder_id) {
    std::erase_if(m_layers.layers,
                  [&](auto& layer) { return layer->consumer_id == consumer_binder_id; });
}

void SurfaceFlinger::AddLayerToDisplayStack(u64 display_id, s32 consumer_binder_id) {
    auto* const display = this->FindDisplay(display_id);
    auto layer = this->FindLayer(consumer_binder_id);

    if (!display || !layer) {
        return;
    }

    display->stack.layers.emplace_back(std::move(layer));
}

void SurfaceFlinger::RemoveLayerFromDisplayStack(u64 display_id, s32 consumer_binder_id) {
    auto* const display = this->FindDisplay(display_id);
    if (!display) {
        return;
    }

    m_composer.RemoveLayerLocked(*display, consumer_binder_id);
    std::erase_if(display->stack.layers,
                  [&](auto& layer) { return layer->consumer_id == consumer_binder_id; });
}

void SurfaceFlinger::SetLayerVisibility(s32 consumer_binder_id, bool visible) {
    if (const auto layer = this->FindLayer(consumer_binder_id); layer != nullptr) {
        layer->visible = visible;
        return;
    }
}

void SurfaceFlinger::SetLayerBlending(s32 consumer_binder_id, LayerBlending blending) {
    if (const auto layer = this->FindLayer(consumer_binder_id); layer != nullptr) {
        layer->blending = blending;
        return;
    }
}

Display* SurfaceFlinger::FindDisplay(u64 display_id) {
    for (auto& display : m_displays) {
        if (display.id == display_id) {
            return &display;
        }
    }

    return nullptr;
}

std::shared_ptr<Layer> SurfaceFlinger::FindLayer(s32 consumer_binder_id) {
    for (auto& layer : m_layers.layers) {
        if (layer->consumer_id == consumer_binder_id) {
            return layer;
        }
    }

    return nullptr;
}

void SurfaceFlinger::CreateBufferQueue(s32* out_consumer_binder_id, s32* out_producer_binder_id) {
    auto& nvmap = nvdrv->GetContainer().GetNvMapFile();
    auto core = std::make_shared<android::BufferQueueCore>();
    auto producer = std::make_shared<android::BufferQueueProducer>(m_context, core, nvmap);
    auto consumer = std::make_shared<android::BufferQueueConsumer>(core);

    *out_consumer_binder_id = m_server.RegisterBinder(std::move(consumer));
    *out_producer_binder_id = m_server.RegisterBinder(std::move(producer));
}

void SurfaceFlinger::DestroyBufferQueue(s32 consumer_binder_id, s32 producer_binder_id) {
    m_server.UnregisterBinder(producer_binder_id);
    m_server.UnregisterBinder(consumer_binder_id);
}

} // namespace Service::Nvnflinger


code:
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <vector>

#include "common/common_types.h"
#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/nvnflinger/hardware_composer.h"

namespace Core {
class System;
}

namespace Service::Nvidia {
class Module;
}

// TODO: ISurfaceComposer
// TODO: ISurfaceComposerClient

namespace Service::Nvnflinger {

struct Display;
class HosBinderDriverServer;
enum class LayerBlending : u32;
struct Layer;

class SurfaceFlinger {
public:
    explicit SurfaceFlinger(Core::System& system, HosBinderDriverServer& server);
    ~SurfaceFlinger();

    void AddDisplay(u64 display_id);
    void RemoveDisplay(u64 display_id);
    bool ComposeDisplay(s32* out_swap_interval, f32* out_compose_speed_scale, u64 display_id);

    void CreateLayer(s32 consumer_binder_id);
    void DestroyLayer(s32 consumer_binder_id);

    void AddLayerToDisplayStack(u64 display_id, s32 consumer_binder_id);
    void RemoveLayerFromDisplayStack(u64 display_id, s32 consumer_binder_id);

    void SetLayerVisibility(s32 consumer_binder_id, bool visible);
    void SetLayerBlending(s32 consumer_binder_id, LayerBlending blending);

private:
    Display* FindDisplay(u64 display_id);
    std::shared_ptr<Layer> FindLayer(s32 consumer_binder_id);

public:
    // TODO: these don't belong here
    void CreateBufferQueue(s32* out_consumer_binder_id, s32* out_producer_binder_id);
    void DestroyBufferQueue(s32 consumer_binder_id, s32 producer_binder_id);

private:
    Core::System& m_system;
    HosBinderDriverServer& m_server;
    KernelHelpers::ServiceContext m_context;

    std::vector<Display> m_displays;
    LayerStack m_layers;
    std::shared_ptr<Nvidia::Module> nvdrv;
    s32 disp_fd;
    HardwareComposer m_composer;
};

} // namespace Service::Nvnflinger


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2012 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Parts of this implementation were based on:
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/ui/Fence.h

#pragma once

#include <array>

#include "common/common_types.h"
#include "core/hle/service/nvdrv/nvdata.h"

namespace Service::android {

class Fence {
public:
    constexpr Fence() = default;

    static constexpr Fence NoFence() {
        Fence fence;
        fence.fences[0].id = -1;
        fence.fences[1].id = -1;
        fence.fences[2].id = -1;
        fence.fences[3].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


code:
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later

#include "core/hle/service/nvdrv/core/nvmap.h"
#include "core/hle/service/nvnflinger/ui/graphic_buffer.h"

namespace Service::android {

static NvGraphicBuffer GetBuffer(std::shared_ptr<NvGraphicBuffer>& buffer) {
    if (buffer) {
        return *buffer;
    } else {
        return {};
    }
}

GraphicBuffer::GraphicBuffer(u32 width_, u32 height_, PixelFormat format_, u32 usage_)
    : NvGraphicBuffer(width_, height_, format_, usage_), m_nvmap(nullptr) {}

GraphicBuffer::GraphicBuffer(Service::Nvidia::NvCore::NvMap& nvmap,
                             std::shared_ptr<NvGraphicBuffer> buffer)
    : NvGraphicBuffer(GetBuffer(buffer)), m_nvmap(std::addressof(nvmap)) {
    if (this->BufferId() > 0) {
        m_nvmap->DuplicateHandle(this->BufferId(), true);
        m_nvmap->PinHandle(this->BufferId(), false);
    }
}

GraphicBuffer::~GraphicBuffer() {
    if (m_nvmap != nullptr && this->BufferId() > 0) {
        m_nvmap->UnpinHandle(this->BufferId());
        m_nvmap->FreeHandle(this->BufferId(), true);
    }
}

} // namespace Service::android


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2007 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Parts of this implementation were based on:
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/ui/GraphicBuffer.h

#pragma once

#include <memory>

#include "common/common_funcs.h"
#include "common/common_types.h"
#include "core/hle/service/nvnflinger/pixel_format.h"

namespace Service::Nvidia::NvCore {
class NvMap;
} // namespace Service::Nvidia::NvCore

namespace Service::android {

struct NvGraphicBuffer {
    constexpr NvGraphicBuffer() = default;

    constexpr NvGraphicBuffer(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;
    }

    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(NvGraphicBuffer) == 0x16C, "NvGraphicBuffer has wrong size");

class GraphicBuffer final : public NvGraphicBuffer {
public:
    explicit GraphicBuffer(u32 width, u32 height, PixelFormat format, u32 usage);
    explicit GraphicBuffer(Service::Nvidia::NvCore::NvMap& nvmap,
                           std::shared_ptr<NvGraphicBuffer> buffer);
    ~GraphicBuffer();

private:
    Service::Nvidia::NvCore::NvMap* m_nvmap{};
};

} // namespace Service::android


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later

#pragma once

#include "common/common_funcs.h"
#include "common/common_types.h"

namespace Service::android {

/// Attributes queryable with Query
enum class NativeWindow : s32 {
    Width = 0,
    Height = 1,
    Format = 2,
    MinUndequeedBuffers = 3,
    QueuesToWindowComposer = 4,
    ConcreteType = 5,
    DefaultWidth = 6,
    DefaultHeight = 7,
    TransformHint = 8,
    ConsumerRunningBehind = 9,
    ConsumerUsageBits = 10,
    StickyTransform = 11,
    DefaultDataSpace = 12,
    BufferAge = 13,
};

/// Parameter for Connect/Disconnect
enum class NativeWindowApi : s32 {
    NoConnectedApi = 0,
    Egl = 1,
    Cpu = 2,
    Media = 3,
    Camera = 4,
};

/// Scaling mode parameter for QueueBuffer
enum class NativeWindowScalingMode : s32 {
    Freeze = 0,
    ScaleToWindow = 1,
    ScaleCrop = 2,
    NoScaleCrop = 3,
    PreserveAspectRatio = 4,
};

/// Transform parameter for QueueBuffer
enum class NativeWindowTransform : u32 {
    None = 0x0,
    InverseDisplay = 0x08,
};
DECLARE_ENUM_FLAG_OPERATORS(NativeWindowTransform);

} // namespace Service::android


code:
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/olsc/daemon_controller.h"

namespace Service::OLSC {

IDaemonController::IDaemonController(Core::System& system_)
    : ServiceFramework{system_, "IDaemonController"} {
    // clang-format off
    static const FunctionInfo functions[] = {
        {0, D<&IDaemonController::GetAutoTransferEnabledForAccountAndApplication>, "GetAutoTransferEnabledForAccountAndApplication"},
        {1, nullptr, "SetAutoTransferEnabledForAccountAndApplication"},
        {2, nullptr, "GetGlobalUploadEnabledForAccount"},
        {3, nullptr, "SetGlobalUploadEnabledForAccount"},
        {4, nullptr, "TouchAccount"},
        {5, nullptr, "GetGlobalDownloadEnabledForAccount"},
        {6, nullptr, "SetGlobalDownloadEnabledForAccount"},
        {10, nullptr, "GetForbiddenSaveDataIndication"},
        {11, nullptr, "GetStopperObject"},
        {12, nullptr, "GetState"},
    };
    // clang-format on

    RegisterHandlers(functions);
}

IDaemonController::~IDaemonController() = default;

Result IDaemonController::GetAutoTransferEnabledForAccountAndApplication(Out<bool> out_is_enabled,
                                                                         Common::UUID user_id,
                                                                         u64 application_id) {
    LOG_WARNING(Service_OLSC, "(STUBBED) called, user_id={} application_id={:016X}",
                user_id.FormattedString(), application_id);
    *out_is_enabled = false;
    R_SUCCEED();
}

} // namespace Service::OLSC


code:
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "common/uuid.h"
#include "core/hle/service/cmif_types.h"
#include "core/hle/service/service.h"

namespace Service::OLSC {

class IDaemonController final : public ServiceFramework<IDaemonController> {
public:
    explicit IDaemonController(Core::System& system_);
    ~IDaemonController() override;

private:
    Result GetAutoTransferEnabledForAccountAndApplication(Out<bool> out_is_enabled,
                                                          Common::UUID user_id, u64 application_id);
};

} // namespace Service::OLSC


code:
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/olsc/native_handle_holder.h"

namespace Service::OLSC {

INativeHandleHolder::INativeHandleHolder(Core::System& system_)
    : ServiceFramework{system_, "INativeHandleHolder"} {
    // clang-format off
    static const FunctionInfo functions[] = {
        {0, D<&INativeHandleHolder::GetNativeHandle>, "GetNativeHandle"},
    };
    // clang-format on

    RegisterHandlers(functions);
}

INativeHandleHolder::~INativeHandleHolder() = default;

Result INativeHandleHolder::GetNativeHandle(OutCopyHandle<Kernel::KReadableEvent> out_event) {
    LOG_WARNING(Service_OLSC, "(STUBBED) called");
    *out_event = nullptr;
    R_SUCCEED();
}

} // namespace Service::OLSC


code:
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "core/hle/service/cmif_types.h"
#include "core/hle/service/service.h"

namespace Kernel {
class KReadableEvent;
}

namespace Service::OLSC {

class INativeHandleHolder final : public ServiceFramework<INativeHandleHolder> {
public:
    explicit INativeHandleHolder(Core::System& system_);
    ~INativeHandleHolder() override;

private:
    Result GetNativeHandle(OutCopyHandle<Kernel::KReadableEvent> out_event);
};

} // namespace Service::OLSC


code:
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "core/hle/service/olsc/olsc.h"
#include "core/hle/service/olsc/olsc_service_for_application.h"
#include "core/hle/service/olsc/olsc_service_for_system_service.h"
#include "core/hle/service/server_manager.h"
#include "core/hle/service/service.h"

namespace Service::OLSC {

void LoopProcess(Core::System& system) {
    auto server_manager = std::make_unique<ServerManager>(system);

    const auto OlscFactoryForApplication = [&] {
        return std::make_shared<IOlscServiceForApplication>(system);
    };

    const auto OlscFactoryForSystemService = [&] {
        return std::make_shared<IOlscServiceForSystemService>(system);
    };

    server_manager->RegisterNamedService("olsc:u", OlscFactoryForApplication);
    server_manager->RegisterNamedService("olsc:s", OlscFactoryForSystemService);

    ServerManager::RunServer(std::move(server_manager));
}

} // namespace Service::OLSC


code:
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

namespace Core {
class System;
}

namespace Service::OLSC {

void LoopProcess(Core::System& system);

} // namespace Service::OLSC


code:
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/olsc/olsc_service_for_application.h"

namespace Service::OLSC {

IOlscServiceForApplication::IOlscServiceForApplication(Core::System& system_)
    : ServiceFramework{system_, "olsc:u"} {
    // clang-format off
        static const FunctionInfo functions[] = {
            {0, D<&IOlscServiceForApplication::Initialize>, "Initialize"},
            {10, nullptr, "VerifySaveDataBackupLicenseAsync"},
            {13, D<&IOlscServiceForApplication::GetSaveDataBackupSetting>, "GetSaveDataBackupSetting"},
            {14, D<&IOlscServiceForApplication::SetSaveDataBackupSettingEnabled>, "SetSaveDataBackupSettingEnabled"},
            {15, nullptr, "SetCustomData"},
            {16, nullptr, "DeleteSaveDataBackupSetting"},
            {18, nullptr, "GetSaveDataBackupInfoCache"},
            {19, nullptr, "UpdateSaveDataBackupInfoCacheAsync"},
            {22, nullptr, "DeleteSaveDataBackupAsync"},
            {25, nullptr, "ListDownloadableSaveDataBackupInfoAsync"},
            {26, nullptr, "DownloadSaveDataBackupAsync"},
            {27, nullptr, "UploadSaveDataBackupAsync"},
            {9010, nullptr, "VerifySaveDataBackupLicenseAsyncForDebug"},
            {9013, nullptr, "GetSaveDataBackupSettingForDebug"},
            {9014, nullptr, "SetSaveDataBackupSettingEnabledForDebug"},
            {9015, nullptr, "SetCustomDataForDebug"},
            {9016, nullptr, "DeleteSaveDataBackupSettingForDebug"},
            {9018, nullptr, "GetSaveDataBackupInfoCacheForDebug"},
            {9019, nullptr, "UpdateSaveDataBackupInfoCacheAsyncForDebug"},
            {9022, nullptr, "DeleteSaveDataBackupAsyncForDebug"},
            {9025, nullptr, "ListDownloadableSaveDataBackupInfoAsyncForDebug"},
            {9026, nullptr, "DownloadSaveDataBackupAsyncForDebug"},
        };
    // clang-format on

    RegisterHandlers(functions);
}

IOlscServiceForApplication::~IOlscServiceForApplication() = default;

Result IOlscServiceForApplication::Initialize(ClientProcessId process_id) {
    LOG_WARNING(Service_OLSC, "(STUBBED) called");
    initialized = true;
    R_SUCCEED();
}

Result IOlscServiceForApplication::GetSaveDataBackupSetting(Out<u8> out_save_data_backup_setting) {
    LOG_WARNING(Service_OLSC, "(STUBBED) called");
    // backup_setting is set to 0 since real value is unknown
    *out_save_data_backup_setting = 0;
    R_SUCCEED();
}

Result IOlscServiceForApplication::SetSaveDataBackupSettingEnabled(bool enabled,
                                                                   NS::Uid account_id) {
    LOG_WARNING(Service_OLSC, "(STUBBED) called, enabled={}, account_id={}", enabled,
                account_id.uuid.FormattedString());
    R_SUCCEED();
}

} // namespace Service::OLSC


code:
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "core/hle/service/cmif_types.h"
#include "core/hle/service/ns/ns_types.h"
#include "core/hle/service/service.h"

namespace Service::OLSC {

class IOlscServiceForApplication final : public ServiceFramework<IOlscServiceForApplication> {
public:
    explicit IOlscServiceForApplication(Core::System& system_);
    ~IOlscServiceForApplication() override;

private:
    Result Initialize(ClientProcessId process_id);
    Result GetSaveDataBackupSetting(Out<u8> out_save_data_backup_setting);
    Result SetSaveDataBackupSettingEnabled(bool enabled, NS::Uid account_id);

    bool initialized{};
};

} // namespace Service::OLSC