From 0e02f1900f124337a872dabf22dc3f8f2dc2c682 Mon Sep 17 00:00:00 2001 From: PixelyIon Date: Sun, 4 Jul 2021 09:39:53 +0530 Subject: [PATCH] Implement additional IGraphicBufferProducer transactions The following GraphicBufferProducer transactions were implemented: * `SetBufferCount` * `DetachBuffer` * `DetachNextBuffer` * `AttachBuffer` It should be noted that `preallocatedBufferCount` (previously `hasBufferCount`) and `activeSlotCount` were adapted accordingly with how they were effectively the same value as all active buffers were preallocated prior but now there can be a non-preallocated active slot. Additionally, a bug has been fixed where `SetPreallocatedBuffer` has the graphic buffer as an optional argument whereas it was treated as a mandatory argument prior and could lead to a SEGFAULT if an application were to not pass in a buffer. --- .../hosbinder/GraphicBufferProducer.cpp | 377 +++++++++++++----- .../hosbinder/GraphicBufferProducer.h | 35 +- 2 files changed, 319 insertions(+), 93 deletions(-) diff --git a/app/src/main/cpp/skyline/services/hosbinder/GraphicBufferProducer.cpp b/app/src/main/cpp/skyline/services/hosbinder/GraphicBufferProducer.cpp index c9710c13..478ee166 100644 --- a/app/src/main/cpp/skyline/services/hosbinder/GraphicBufferProducer.cpp +++ b/app/src/main/cpp/skyline/services/hosbinder/GraphicBufferProducer.cpp @@ -23,7 +23,7 @@ namespace skyline::service::hosbinder { } AndroidStatus GraphicBufferProducer::RequestBuffer(i32 slot, GraphicBuffer *&buffer) { - std::lock_guard guard(mutex); + std::scoped_lock lock(mutex); if (slot < 0 || slot >= queue.size()) [[unlikely]] { state.logger->Warn("#{} was out of range", slot); return AndroidStatus::BadValue; @@ -37,6 +37,45 @@ namespace skyline::service::hosbinder { return AndroidStatus::Ok; } + AndroidStatus GraphicBufferProducer::SetBufferCount(i32 count) { + std::scoped_lock lock(mutex); + if (count >= MaxSlotCount) [[unlikely]] { + state.logger->Warn("Setting buffer count too high: {} (Max: {})", count, MaxSlotCount); + return AndroidStatus::BadValue; + } + + for (auto it{queue.begin()}; it != queue.end(); it++) { + if (it->state == BufferState::Dequeued) { + state.logger->Warn("Cannot set buffer count as #{} is dequeued", std::distance(queue.begin(), it)); + return AndroidStatus::BadValue; + } + } + + if (!count) { + activeSlotCount = 0; + bufferEvent->Signal(); + return AndroidStatus::Ok; + } + + // We don't check minBufferSlots here since it's effectively hardcoded to 0 on HOS (See NativeWindowQuery::MinUndequeuedBuffers) + + // HOS only resets all the buffers if there's no preallocated buffers, it simply sets the active buffer count otherwise + if (preallocatedBufferCount == 0) { + for (auto &slot : queue) { + slot.state = BufferState::Free; + slot.frameNumber = std::numeric_limits::max(); + slot.graphicBuffer = nullptr; + } + } else if (preallocatedBufferCount < count) { + state.logger->Warn("Setting the active slot count ({}) higher than the amount of slots with preallocated buffers ({})", count, preallocatedBufferCount); + } + + activeSlotCount = count; + bufferEvent->Signal(); + + return AndroidStatus::Ok; + } + AndroidStatus GraphicBufferProducer::DequeueBuffer(bool async, u32 width, u32 height, AndroidPixelFormat format, u32 usage, i32 &slot, std::optional &fence) { if ((width && !height) || (!width && height)) { state.logger->Warn("Dimensions {}x{} should be uniformly zero or non-zero", width, height); @@ -46,13 +85,16 @@ namespace skyline::service::hosbinder { constexpr i32 InvalidGraphicBufferSlot{-1}; //!< https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferQueueCore.h;l=61 slot = InvalidGraphicBufferSlot; - std::lock_guard guard(mutex); + std::scoped_lock lock(mutex); + // We don't need a loop here since the consumer is blocking and instantly frees all buffers + // If a valid slot is not found on the first iteration then it would be stuck in an infloop + // As a result of this, we simply warn and return InvalidOperation to the guest auto buffer{queue.end()}; size_t dequeuedSlotCount{}; - for (auto it{queue.begin()}; it != queue.end(); it++) { + for (auto it{queue.begin()}; it != std::min(queue.begin() + activeSlotCount, queue.end()); it++) { // We want to select the oldest slot that's free to use as we'd want all slots to be used // If we go linearly then we have a higher preference for selecting the former slots and being out of order - if (it->state == BufferState::Free && it->texture) { + if (it->state == BufferState::Free) { if (buffer == queue.end() || it->frameNumber < buffer->frameNumber) buffer = it; } else if (it->state == BufferState::Dequeued) { @@ -100,6 +142,104 @@ namespace skyline::service::hosbinder { return AndroidStatus::Ok; } + AndroidStatus GraphicBufferProducer::DetachBuffer(i32 slot) { + std::scoped_lock lock(mutex); + if (slot < 0 || slot >= queue.size()) [[unlikely]] { + state.logger->Warn("#{} was out of range", slot); + return AndroidStatus::BadValue; + } + + auto &bufferSlot{queue[slot]}; + if (bufferSlot.state != BufferState::Dequeued) [[unlikely]] { + state.logger->Warn("#{} was '{}' instead of being dequeued", slot, ToString(bufferSlot.state)); + return AndroidStatus::BadValue; + } else if (!bufferSlot.wasBufferRequested) [[unlikely]] { + state.logger->Warn("#{} was detached prior to being requested", slot); + return AndroidStatus::BadValue; + } + + bufferSlot.state = BufferState::Free; + bufferSlot.frameNumber = std::numeric_limits::max(); + bufferSlot.graphicBuffer = nullptr; + + bufferEvent->Signal(); + + state.logger->Debug("#{}", slot); + return AndroidStatus::Ok; + } + + AndroidStatus GraphicBufferProducer::DetachNextBuffer(std::optional &graphicBuffer, std::optional &fence) { + std::scoped_lock lock(mutex); + auto bufferSlot{queue.end()}; + for (auto it{queue.begin()}; it != queue.end(); it++) { + if (it->state == BufferState::Free && it->graphicBuffer) { + if (bufferSlot == queue.end() || it->frameNumber < bufferSlot->frameNumber) + bufferSlot = it; + } + } + + if (bufferSlot == queue.end()) + return AndroidStatus::NoMemory; + + bufferSlot->state = BufferState::Free; + bufferSlot->frameNumber = std::numeric_limits::max(); + graphicBuffer = *std::exchange(bufferSlot->graphicBuffer, nullptr); + fence = AndroidFence{}; + + bufferEvent->Signal(); + + state.logger->Debug("#{}", std::distance(queue.begin(), bufferSlot)); + return AndroidStatus::Ok; + } + + AndroidStatus GraphicBufferProducer::AttachBuffer(i32 &slot, const GraphicBuffer &graphicBuffer) { + std::scoped_lock lock(mutex); + auto bufferSlot{queue.end()}; + for (auto it{queue.begin()}; it != queue.end(); it++) { + if (it->state == BufferState::Free) { + if (bufferSlot == queue.end() || it->frameNumber < bufferSlot->frameNumber) + bufferSlot = it; + } + } + + if (bufferSlot == queue.end()) { + state.logger->Warn("Could not find any free slots to attach the graphic buffer to"); + return AndroidStatus::NoMemory; + } + + if (graphicBuffer.magic != GraphicBuffer::Magic) + throw exception("Unexpected GraphicBuffer magic: 0x{} (Expected: 0x{})", graphicBuffer.magic, GraphicBuffer::Magic); + else if (graphicBuffer.intCount != sizeof(NvGraphicHandle) / sizeof(u32)) + throw exception("Unexpected GraphicBuffer native_handle integer count: 0x{} (Expected: 0x{})", graphicBuffer.intCount, sizeof(NvGraphicHandle)); + + auto &handle{graphicBuffer.graphicHandle}; + if (handle.magic != NvGraphicHandle::Magic) + throw exception("Unexpected NvGraphicHandle magic: {}", handle.surfaceCount); + else if (handle.surfaceCount < 1) + throw exception("At least one surface is required in a buffer: {}", handle.surfaceCount); + else if (handle.surfaceCount > 1) + throw exception("Multi-planar surfaces are not supported: {}", handle.surfaceCount); + + auto &surface{graphicBuffer.graphicHandle.surfaces.at(0)}; + if (surface.scanFormat != NvDisplayScanFormat::Progressive) + throw exception("Non-Progressive surfaces are not supported: {}", ToString(surface.scanFormat)); + else if (surface.layout == NvSurfaceLayout::Tiled) + throw exception("Legacy 16Bx16 tiled surfaces are not supported"); + + bufferSlot->state = BufferState::Dequeued; + bufferSlot->wasBufferRequested = true; + bufferSlot->isPreallocated = false; + bufferSlot->graphicBuffer = std::make_unique(graphicBuffer); + + slot = std::distance(queue.begin(), bufferSlot); + + preallocatedBufferCount = std::count_if(queue.begin(), queue.end(), [](const BufferSlot &slot) { return slot.graphicBuffer && slot.isPreallocated; }); + activeSlotCount = std::count_if(queue.begin(), queue.end(), [](const BufferSlot &slot) { return static_cast(slot.graphicBuffer); }); + + state.logger->Debug("#{} - Dimensions: {}x{} [Stride: {}], Format: {}, Layout: {}, {}: {}, Usage: 0x{:X}, NvMap {}: {}, Buffer Start/End: 0x{:X} -> 0x{:X}", slot, surface.width, surface.height, handle.stride, ToString(graphicBuffer.format), ToString(surface.layout), surface.layout == NvSurfaceLayout::Blocklinear ? "Block Height" : "Pitch", surface.layout == NvSurfaceLayout::Blocklinear ? 1U << surface.blockHeightLog2 : surface.pitch, graphicBuffer.usage, surface.nvmapHandle ? "Handle" : "ID", surface.nvmapHandle ? surface.nvmapHandle : handle.nvmapId, surface.offset, surface.offset + surface.size); + return AndroidStatus::Ok; + } + AndroidStatus GraphicBufferProducer::QueueBuffer(i32 slot, i64 timestamp, bool isAutoTimestamp, AndroidRect crop, NativeWindowScalingMode scalingMode, NativeWindowTransform transform, NativeWindowTransform stickyTransform, bool async, u32 swapInterval, const AndroidFence &fence, u32 &width, u32 &height, NativeWindowTransform &transformHint, u32 &pendingBufferCount) { switch (scalingMode) { case NativeWindowScalingMode::Freeze: @@ -113,7 +253,7 @@ namespace skyline::service::hosbinder { return AndroidStatus::BadValue; } - std::lock_guard guard(mutex); + std::scoped_lock lock(mutex); if (slot < 0 || slot >= queue.size()) [[unlikely]] { state.logger->Warn("#{} was out of range", slot); return AndroidStatus::BadValue; @@ -134,6 +274,80 @@ namespace skyline::service::hosbinder { return AndroidStatus::BadValue; } + if (!buffer.texture) [[unlikely]] { + // We lazily create a texture if one isn't present at queue time, this allows us to look up the texture in the texture cache + // If we deterministically know that the texture is written by the CPU then we can allocate a CPU-shared host texture for fast uploads + gpu::texture::Format format; + switch (graphicBuffer.format) { + case AndroidPixelFormat::RGBA8888: + case AndroidPixelFormat::RGBX8888: + format = gpu::format::RGBA8888Unorm; + break; + + case AndroidPixelFormat::RGB565: + format = gpu::format::RGB565Unorm; + break; + + default: + throw exception("Unknown format in buffer: '{}' ({})", ToString(graphicBuffer.format), static_cast(graphicBuffer.format)); + } + + auto &handle{graphicBuffer.graphicHandle}; + if (handle.magic != NvGraphicHandle::Magic) + throw exception("Unexpected NvGraphicHandle magic: {}", handle.surfaceCount); + else if (handle.surfaceCount < 1) + throw exception("At least one surface is required in a buffer: {}", handle.surfaceCount); + else if (handle.surfaceCount > 1) + throw exception("Multi-planar surfaces are not supported: {}", handle.surfaceCount); + + auto &surface{graphicBuffer.graphicHandle.surfaces.at(0)}; + if (surface.scanFormat != NvDisplayScanFormat::Progressive) + throw exception("Non-Progressive surfaces are not supported: {}", ToString(surface.scanFormat)); + + std::shared_ptr nvBuffer{}; + { + auto driver{nvdrv::driver.lock()}; + auto nvmap{driver->nvMap.lock()}; + if (surface.nvmapHandle) { + nvBuffer = nvmap->GetObject(surface.nvmapHandle); + } else { + std::shared_lock nvmapLock(nvmap->mapMutex); + for (const auto &object : nvmap->maps) { + if (object->id == handle.nvmapId) { + nvBuffer = object; + break; + } + } + if (!nvBuffer) + throw exception("A QueueBuffer request has an invalid NvMap Handle ({}) and ID ({})", surface.nvmapHandle, handle.nvmapId); + } + } + + if (surface.size > (nvBuffer->size - surface.offset)) + throw exception("Surface doesn't fit into NvMap mapping of size 0x{:X} when mapped at 0x{:X} -> 0x{:X}", nvBuffer->size, surface.offset, surface.offset + surface.size); + + gpu::texture::TileMode tileMode; + gpu::texture::TileConfig tileConfig{}; + if (surface.layout == NvSurfaceLayout::Blocklinear) { + tileMode = gpu::texture::TileMode::Block; + tileConfig = { + .surfaceWidth = static_cast(surface.width), + .blockHeight = static_cast(1U << surface.blockHeightLog2), + .blockDepth = 1, + }; + } else if (surface.layout == NvSurfaceLayout::Pitch) { + tileMode = gpu::texture::TileMode::Pitch; + tileConfig = { + .pitch = surface.pitch, + }; + } else if (surface.layout == NvSurfaceLayout::Tiled) { + throw exception("Legacy 16Bx16 tiled surfaces are not supported"); + } + + auto guestTexture{std::make_shared(state, nvBuffer->ptr + surface.offset, gpu::texture::Dimensions(surface.width, surface.height), format, tileMode, tileConfig)}; + buffer.texture = guestTexture->CreateTexture({}, vk::ImageTiling::eLinear); + } + switch (transform) { case NativeWindowTransform::Identity: case NativeWindowTransform::MirrorHorizontal: @@ -179,7 +393,7 @@ namespace skyline::service::hosbinder { } void GraphicBufferProducer::CancelBuffer(i32 slot, const AndroidFence &fence) { - std::lock_guard guard(mutex); + std::scoped_lock lock(mutex); if (slot < 0 || slot >= queue.size()) [[unlikely]] { state.logger->Warn("#{} was out of range", slot); return; @@ -201,7 +415,7 @@ namespace skyline::service::hosbinder { } AndroidStatus GraphicBufferProducer::Query(NativeWindowQuery query, u32 &out) { - std::lock_guard guard(mutex); + std::scoped_lock lock(mutex); switch (query) { case NativeWindowQuery::Width: out = defaultWidth; @@ -250,7 +464,7 @@ namespace skyline::service::hosbinder { } AndroidStatus GraphicBufferProducer::Connect(NativeWindowApi api, bool producerControlledByApp, u32 &width, u32 &height, NativeWindowTransform &transformHint, u32 &pendingBufferCount) { - std::lock_guard guard(mutex); + std::scoped_lock lock(mutex); if (connectedApi != NativeWindowApi::None) [[unlikely]] { state.logger->Warn("Already connected to API '{}' while connection to '{}' is requested", ToString(connectedApi), ToString(api)); return AndroidStatus::BadValue; @@ -279,7 +493,7 @@ namespace skyline::service::hosbinder { } AndroidStatus GraphicBufferProducer::Disconnect(NativeWindowApi api) { - std::lock_guard guard(mutex); + std::scoped_lock lock(mutex); switch (api) { case NativeWindowApi::EGL: @@ -309,99 +523,51 @@ namespace skyline::service::hosbinder { return AndroidStatus::Ok; } - AndroidStatus GraphicBufferProducer::SetPreallocatedBuffer(i32 slot, const GraphicBuffer &graphicBuffer) { - std::lock_guard guard(mutex); + AndroidStatus GraphicBufferProducer::SetPreallocatedBuffer(i32 slot, const GraphicBuffer *graphicBuffer) { + std::scoped_lock lock(mutex); if (slot < 0 || slot >= MaxSlotCount) [[unlikely]] { state.logger->Warn("#{} was out of range", slot); return AndroidStatus::BadValue; } - if (graphicBuffer.magic != GraphicBuffer::Magic) - throw exception("Unexpected GraphicBuffer magic: 0x{} (Expected: 0x{})", graphicBuffer.magic, GraphicBuffer::Magic); - else if (graphicBuffer.intCount != sizeof(NvGraphicHandle) / sizeof(u32)) - throw exception("Unexpected GraphicBuffer native_handle integer count: 0x{} (Expected: 0x{})", graphicBuffer.intCount, sizeof(NvGraphicHandle)); - - gpu::texture::Format format; - switch (graphicBuffer.format) { - case AndroidPixelFormat::RGBA8888: - case AndroidPixelFormat::RGBX8888: - format = gpu::format::RGBA8888Unorm; - break; - - case AndroidPixelFormat::RGB565: - format = gpu::format::RGB565Unorm; - break; - - default: - throw exception("Unknown format in buffer: '{}' ({})", ToString(graphicBuffer.format), static_cast(graphicBuffer.format)); - } - - auto &handle{graphicBuffer.graphicHandle}; - if (handle.magic != NvGraphicHandle::Magic) - throw exception("Unexpected NvGraphicHandle magic: {}", handle.surfaceCount); - else if (handle.surfaceCount < 1) - throw exception("At least one surface is required in a buffer: {}", handle.surfaceCount); - else if (handle.surfaceCount > 1) - throw exception("Multi-planar surfaces are not supported: {}", handle.surfaceCount); - - auto &surface{graphicBuffer.graphicHandle.surfaces.at(0)}; - if (surface.scanFormat != NvDisplayScanFormat::Progressive) - throw exception("Non-Progressive surfaces are not supported: {}", ToString(surface.scanFormat)); - - std::shared_ptr nvBuffer{}; - { - auto driver{nvdrv::driver.lock()}; - auto nvmap{driver->nvMap.lock()}; - if (surface.nvmapHandle) { - nvBuffer = nvmap->GetObject(surface.nvmapHandle); - } else { - std::shared_lock nvmapLock(nvmap->mapMutex); - for (const auto &object : nvmap->maps) { - if (object->id == handle.nvmapId) { - nvBuffer = object; - break; - } - } - if (!nvBuffer) - throw exception("A QueueBuffer request has an invalid NvMap Handle ({}) and ID ({})", surface.nvmapHandle, handle.nvmapId); - } - } - - if (surface.size > (nvBuffer->size - surface.offset)) - throw exception("Surface doesn't fit into NvMap mapping of size 0x{:X} when mapped at 0x{:X} -> 0x{:X}", nvBuffer->size, surface.offset, surface.offset + surface.size); - - gpu::texture::TileMode tileMode; - gpu::texture::TileConfig tileConfig{}; - if (surface.layout == NvSurfaceLayout::Blocklinear) { - tileMode = gpu::texture::TileMode::Block; - tileConfig = { - .surfaceWidth = static_cast(surface.width), - .blockHeight = static_cast(1U << surface.blockHeightLog2), - .blockDepth = 1, - }; - } else if (surface.layout == NvSurfaceLayout::Pitch) { - tileMode = gpu::texture::TileMode::Pitch; - tileConfig = { - .pitch = surface.pitch, - }; - } else if (surface.layout == NvSurfaceLayout::Tiled) { - throw exception("Legacy 16Bx16 tiled surfaces are not supported"); - } - - auto texture{std::make_shared(state, nvBuffer->ptr + surface.offset, gpu::texture::Dimensions(surface.width, surface.height), format, tileMode, tileConfig)}; - auto &buffer{queue[slot]}; buffer.state = BufferState::Free; buffer.frameNumber = 0; buffer.wasBufferRequested = false; - buffer.graphicBuffer = std::make_unique(graphicBuffer); - buffer.texture = texture->CreateTexture({}, vk::ImageTiling::eLinear); + buffer.isPreallocated = static_cast(graphicBuffer); + buffer.graphicBuffer = graphicBuffer ? std::make_unique(*graphicBuffer) : nullptr; + buffer.texture = {}; - activeSlotCount = hasBufferCount = std::count_if(queue.begin(), queue.end(), [](const BufferSlot &slot) { return static_cast(slot.graphicBuffer); }); + if (graphicBuffer) { + if (graphicBuffer->magic != GraphicBuffer::Magic) + throw exception("Unexpected GraphicBuffer magic: 0x{} (Expected: 0x{})", graphicBuffer->magic, GraphicBuffer::Magic); + else if (graphicBuffer->intCount != sizeof(NvGraphicHandle) / sizeof(u32)) + throw exception("Unexpected GraphicBuffer native_handle integer count: 0x{} (Expected: 0x{})", graphicBuffer->intCount, sizeof(NvGraphicHandle)); + + auto &handle{graphicBuffer->graphicHandle}; + if (handle.magic != NvGraphicHandle::Magic) + throw exception("Unexpected NvGraphicHandle magic: {}", handle.surfaceCount); + else if (handle.surfaceCount < 1) + throw exception("At least one surface is required in a buffer: {}", handle.surfaceCount); + else if (handle.surfaceCount > 1) + throw exception("Multi-planar surfaces are not supported: {}", handle.surfaceCount); + + auto &surface{graphicBuffer->graphicHandle.surfaces.at(0)}; + if (surface.scanFormat != NvDisplayScanFormat::Progressive) + throw exception("Non-Progressive surfaces are not supported: {}", ToString(surface.scanFormat)); + else if (surface.layout == NvSurfaceLayout::Tiled) + throw exception("Legacy 16Bx16 tiled surfaces are not supported"); + + state.logger->Debug("#{} - Dimensions: {}x{} [Stride: {}], Format: {}, Layout: {}, {}: {}, Usage: 0x{:X}, NvMap {}: {}, Buffer Start/End: 0x{:X} -> 0x{:X}", slot, surface.width, surface.height, handle.stride, ToString(graphicBuffer->format), ToString(surface.layout), surface.layout == NvSurfaceLayout::Blocklinear ? "Block Height" : "Pitch", surface.layout == NvSurfaceLayout::Blocklinear ? 1U << surface.blockHeightLog2 : surface.pitch, graphicBuffer->usage, surface.nvmapHandle ? "Handle" : "ID", surface.nvmapHandle ? surface.nvmapHandle : handle.nvmapId, surface.offset, surface.offset + surface.size); + } else { + state.logger->Debug("#{} - No GraphicBuffer", slot); + } + + preallocatedBufferCount = std::count_if(queue.begin(), queue.end(), [](const BufferSlot &slot) { return slot.graphicBuffer && slot.isPreallocated; }); + activeSlotCount = std::count_if(queue.begin(), queue.end(), [](const BufferSlot &slot) { return static_cast(slot.graphicBuffer); }); bufferEvent->Signal(); - state.logger->Debug("#{} - Dimensions: {}x{} [Stride: {}], Format: {}, Layout: {}, {}: {}, Usage: 0x{:X}, NvMap {}: {}, Buffer Start/End: 0x{:X} -> 0x{:X}", slot, surface.width, surface.height, handle.stride, ToString(graphicBuffer.format), ToString(surface.layout), surface.layout == NvSurfaceLayout::Blocklinear ? "Block Height" : "Pitch", surface.layout == NvSurfaceLayout::Blocklinear ? 1U << surface.blockHeightLog2 : surface.pitch, graphicBuffer.usage, surface.nvmapHandle ? "Handle" : "ID", surface.nvmapHandle ? surface.nvmapHandle : handle.nvmapId, surface.offset, surface.offset + surface.size); return AndroidStatus::Ok; } @@ -415,6 +581,12 @@ namespace skyline::service::hosbinder { break; } + case TransactionCode::SetBufferCount: { + auto result{SetBufferCount(in.Pop())}; + out.Push(result); + break; + } + case TransactionCode::DequeueBuffer: { i32 slot{}; std::optional fence{}; @@ -425,6 +597,30 @@ namespace skyline::service::hosbinder { break; } + case TransactionCode::DetachBuffer: { + auto result{DetachBuffer(in.Pop())}; + out.Push(result); + break; + } + + case TransactionCode::DetachNextBuffer: { + std::optional graphicBuffer{}; + std::optional fence{}; + auto result{DetachNextBuffer(graphicBuffer, fence)}; + out.PushOptionalFlattenable(graphicBuffer); + out.PushOptionalFlattenable(fence); + out.Push(result); + break; + } + + case TransactionCode::AttachBuffer: { + i32 slotOut{}; + auto result{AttachBuffer(slotOut, in.Pop())}; + out.Push(slotOut); + out.Push(result); + break; + } + case TransactionCode::QueueBuffer: { u32 width{}, height{}, pendingBufferCount{}; NativeWindowTransform transformHint{}; @@ -481,7 +677,8 @@ namespace skyline::service::hosbinder { } case TransactionCode::SetPreallocatedBuffer: { - SetPreallocatedBuffer(in.Pop(), *in.PopOptionalFlattenable()); + auto result{SetPreallocatedBuffer(in.Pop(), in.PopOptionalFlattenable())}; + out.Push(result); break; } diff --git a/app/src/main/cpp/skyline/services/hosbinder/GraphicBufferProducer.h b/app/src/main/cpp/skyline/services/hosbinder/GraphicBufferProducer.h index 06fa143b..7bcfa6a9 100644 --- a/app/src/main/cpp/skyline/services/hosbinder/GraphicBufferProducer.h +++ b/app/src/main/cpp/skyline/services/hosbinder/GraphicBufferProducer.h @@ -39,6 +39,7 @@ namespace skyline::service::hosbinder { BufferState state{BufferState::Free}; u64 frameNumber{}; //!< The amount of frames that have been queued using this slot bool wasBufferRequested{}; //!< If GraphicBufferProducer::RequestBuffer has been called with this buffer + bool isPreallocated{}; //!< If this slot's graphic buffer has been preallocated or attached std::shared_ptr texture{}; std::unique_ptr graphicBuffer{}; }; @@ -58,8 +59,8 @@ namespace skyline::service::hosbinder { std::mutex mutex; //!< Synchronizes access to the buffer queue constexpr static u8 MaxSlotCount{16}; //!< The maximum amount of buffer slots that a buffer queue can hold, Android supports 64 but they go unused for applications like games so we've lowered this to 16 (https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferQueueDefs.h;l=29) std::array queue; - u8 activeSlotCount{2}; //!< The amount of slots in the queue that can be used - u8 hasBufferCount{}; //!< The amount of slots with buffers attached in the queue + u8 activeSlotCount{}; //!< The amount of slots in the queue that can be dequeued + u8 preallocatedBufferCount{}; //!< The amount of slots with buffers attached in the queue u32 defaultWidth{1}; //!< The assumed width of a buffer if none is supplied in DequeueBuffer u32 defaultHeight{1}; //!< The assumed height of a buffer if none is supplied in DequeueBuffer AndroidPixelFormat defaultFormat{AndroidPixelFormat::RGBA8888}; //!< The assumed format of a buffer if none is supplied in DequeueBuffer @@ -78,6 +79,13 @@ namespace skyline::service::hosbinder { */ AndroidStatus RequestBuffer(i32 slot, GraphicBuffer *&buffer); + /** + * @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/IGraphicBufferProducer.h;l=82-102 + * @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferQueueProducer.h;l=42-57 + * @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferQueueProducer.cpp;l=75-132 + */ + AndroidStatus SetBufferCount(i32 count); + /** * @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/IGraphicBufferProducer.h;l=104-170 * @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferQueueProducer.h;l=59-97 @@ -85,6 +93,27 @@ namespace skyline::service::hosbinder { */ AndroidStatus DequeueBuffer(bool async, u32 width, u32 height, AndroidPixelFormat format, u32 usage, i32 &slot, std::optional &fence); + /** + * @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/IGraphicBufferProducer.h;l=172-186 + * @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferQueueProducer.h;l=99-100 + * @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferQueueProducer.cpp;l=390-419 + */ + AndroidStatus DetachBuffer(i32 slot); + + /** + * @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/IGraphicBufferProducer.h;l=188-207 + * @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferQueueProducer.h;l=102-104 + * @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferQueueProducer.cpp;l=421-464 + */ + AndroidStatus DetachNextBuffer(std::optional &graphicBuffer, std::optional &fence); + + /** + * @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/IGraphicBufferProducer.h;l=209-234 + * @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferQueueProducer.h;l=106-107 + * @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferQueueProducer.cpp;l=466-510 + */ + AndroidStatus AttachBuffer(i32& slot, const GraphicBuffer &graphicBuffer); + /** * @note Nintendo has added an additional field for swap interval which sets the swap interval of the compositor * @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/IGraphicBufferProducer.h;l=236-349 @@ -125,7 +154,7 @@ namespace skyline::service::hosbinder { * @brief Similar to AttachBuffer but the slot is explicitly specified and the producer defaults are set based off it * @note This is an HOS-specific addition to GraphicBufferProducer, it exists so that all allocation of buffers is handled by the client to avoid any shared/transfer memory from the client to loan memory for the buffers which would be quite complicated */ - AndroidStatus SetPreallocatedBuffer(i32 slot, const GraphicBuffer &graphicBuffer); + AndroidStatus SetPreallocatedBuffer(i32 slot, const GraphicBuffer *graphicBuffer); public: std::shared_ptr bufferEvent; //!< Signalled every time a buffer in the queue is freed