Wait on slot to be freed in GraphicBufferProducer::DequeueBuffer

We currently don't wait on a slot to be freed if none are free, this worked prior to async presentation as GBP's slots wouldn't change their state until other commands were called but now slots can be held by the presentation engine. As a result, we now have to wait on the presentation engine to free up slots.

This commit also fixes the behavior of the `async` flag in `DequeueBuffer` as it was treated as a non-blocking flag but isn't supposed to do anything on HOS.
This commit is contained in:
PixelyIon 2022-10-22 17:15:33 +05:30
parent cdb2b85d6c
commit 597a6ff31d
2 changed files with 30 additions and 29 deletions

View File

@ -92,38 +92,36 @@ 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::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 infinite loop
// As a result of this, we simply warn and return InvalidOperation to the guest
std::unique_lock lock{mutex};
auto buffer{queue.end()};
size_t dequeuedSlotCount{};
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) {
if (buffer == queue.end() || it->frameNumber < buffer->frameNumber)
buffer = it;
} else if (it->state == BufferState::Dequeued) {
dequeuedSlotCount++;
freeCondition.wait(lock, [&]() {
size_t dequeuedSlotCount{};
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) {
if (buffer == queue.end() || it->frameNumber < buffer->frameNumber)
buffer = it;
} else if (it->state == BufferState::Dequeued) {
dequeuedSlotCount++;
}
}
}
if (buffer != queue.end()) {
slot = static_cast<i32>(std::distance(queue.begin(), buffer));
} else if (async) {
return AndroidStatus::WouldBlock;
} else if (dequeuedSlotCount == queue.size()) {
Logger::Warn("Client attempting to dequeue more buffers when all buffers are dequeued by the client: {}", dequeuedSlotCount);
if (buffer != queue.end()) {
slot = static_cast<i32>(std::distance(queue.begin(), buffer));
return true;
} else if (dequeuedSlotCount == queue.size()) {
Logger::Warn("Client attempting to dequeue more buffers when all buffers are dequeued by the client: {}", dequeuedSlotCount);
slot = InvalidGraphicBufferSlot;
return true;
}
buffer = queue.end();
return false;
});
if (slot == InvalidGraphicBufferSlot) [[unlikely]]
return AndroidStatus::InvalidOperation;
} else {
size_t index{};
std::string bufferString;
for (auto &bufferSlot : queue)
bufferString += util::Format("\n#{} - State: {}, Has Graphic Buffer: {}, Frame Number: {}", ++index, ToString(bufferSlot.state), bufferSlot.graphicBuffer != nullptr, bufferSlot.frameNumber);
Logger::Warn("Cannot find any free buffers to dequeue:{}", bufferString);
return AndroidStatus::InvalidOperation;
}
width = width ? width : defaultWidth;
height = height ? height : defaultHeight;
@ -386,10 +384,12 @@ namespace skyline::service::hosbinder {
}
state.gpu->presentation.Present(buffer.texture, isAutoTimestamp ? 0 : timestamp, swapInterval, crop, scalingMode, transform, fence, [this, &buffer] {
std::scoped_lock lock(mutex);
std::unique_lock lock{mutex};
buffer.state = BufferState::Free;
bufferEvent->Signal();
freeCondition.notify_all();
});
buffer.state = BufferState::Queued;

View File

@ -61,6 +61,7 @@ namespace skyline::service::hosbinder {
private:
const DeviceState &state;
std::mutex mutex; //!< Synchronizes access to the buffer queue
std::condition_variable freeCondition; //!< Used to wait for a free buffer slot
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<BufferSlot, MaxSlotCount> queue;
u8 activeSlotCount{}; //!< The amount of slots in the queue that can be dequeued