[core/nvnflinger] Rewrite GetBufferHistory (#528)

This rewrite should improve performance with the buffer history by changing the complexity level to O(1).
Replace std::vector with std::array to ensure that elements are allocated on the stack rather than on the free store.
Avoid expensive resizing at runtime.
Adjust buffer states at the right locations.
Tightly pack the BufferHistoryInfo struct to ensure that it only occupies 28 bytes.

Reviewed-on: #528
Co-authored-by: SDK-Chan <sdkchan@eden-emu.dev>
Co-committed-by: SDK-Chan <sdkchan@eden-emu.dev>
This commit is contained in:
SDK-Chan 2025-09-16 19:41:52 +02:00 committed by crueter
parent 6699361b7e
commit 3ca0bde0e9
Signed by: crueter
GPG key ID: 425ACD2D4830EBC6
6 changed files with 94 additions and 52 deletions

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project // SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
@ -97,18 +100,18 @@ Status BufferQueueConsumer::AcquireBuffer(BufferItem* out_buffer,
slots[slot].needs_cleanup_on_release = false; slots[slot].needs_cleanup_on_release = false;
slots[slot].buffer_state = BufferState::Acquired; slots[slot].buffer_state = BufferState::Acquired;
// Mark tracked buffer history records as acquired
for (auto& buffer_history_record : core->buffer_history) {
if (buffer_history_record.frame_number == core->frame_counter) {
buffer_history_record.state = BufferState::Acquired;
break;
}
}
// TODO: for now, avoid resetting the fence, so that when we next return this // TODO: for now, avoid resetting the fence, so that when we next return this
// slot to the producer, it will wait for the fence to pass. We should fix this // slot to the producer, it will wait for the fence to pass. We should fix this
// by properly waiting for the fence in the BufferItemConsumer. // by properly waiting for the fence in the BufferItemConsumer.
// slots[slot].fence = Fence::NoFence(); // slots[slot].fence = Fence::NoFence();
const auto target_frame_number = slots[slot].frame_number;
for (size_t i = 0; i < core->history.size(); i++) {
if (core->history[i].frame_number == target_frame_number) {
core->history[i].state = BufferState::Acquired;
break;
}
}
} }
// If the buffer has previously been acquired by the consumer, set graphic_buffer to nullptr to // If the buffer has previously been acquired by the consumer, set graphic_buffer to nullptr to

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project // SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
@ -10,12 +13,19 @@
namespace Service::android { namespace Service::android {
BufferQueueCore::BufferQueueCore() { BufferQueueCore::BufferQueueCore() = default;
history.resize(8);
};
BufferQueueCore::~BufferQueueCore() = default; BufferQueueCore::~BufferQueueCore() = default;
void BufferQueueCore::PushHistory(u64 frame_number, s64 queue_time, s64 presentation_time, BufferState state) {
buffer_history_pos = (buffer_history_pos + 1) % BUFFER_HISTORY_SIZE;
buffer_history[buffer_history_pos] = BufferHistoryInfo{
.frame_number = frame_number,
.queue_time = queue_time,
.presentation_time = presentation_time,
.state = state,
};
}
void BufferQueueCore::SignalDequeueCondition() { void BufferQueueCore::SignalDequeueCondition() {
dequeue_possible.store(true); dequeue_possible.store(true);
dequeue_condition.notify_all(); dequeue_condition.notify_all();
@ -47,7 +57,7 @@ s32 BufferQueueCore::GetMinMaxBufferCountLocked(bool async) const {
s32 BufferQueueCore::GetMaxBufferCountLocked(bool async) const { s32 BufferQueueCore::GetMaxBufferCountLocked(bool async) const {
const auto min_buffer_count = GetMinMaxBufferCountLocked(async); const auto min_buffer_count = GetMinMaxBufferCountLocked(async);
auto max_buffer_count = (std::max)(default_max_buffer_count, min_buffer_count); auto max_buffer_count = std::max(default_max_buffer_count, min_buffer_count);
if (override_max_buffer_count != 0) { if (override_max_buffer_count != 0) {
ASSERT(override_max_buffer_count >= min_buffer_count); ASSERT(override_max_buffer_count >= min_buffer_count);

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project // SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
@ -15,6 +18,7 @@
#include "core/hle/service/nvnflinger/buffer_item.h" #include "core/hle/service/nvnflinger/buffer_item.h"
#include "core/hle/service/nvnflinger/buffer_queue_defs.h" #include "core/hle/service/nvnflinger/buffer_queue_defs.h"
#include "core/hle/service/nvnflinger/buffer_slot.h"
#include "core/hle/service/nvnflinger/pixel_format.h" #include "core/hle/service/nvnflinger/pixel_format.h"
#include "core/hle/service/nvnflinger/status.h" #include "core/hle/service/nvnflinger/status.h"
#include "core/hle/service/nvnflinger/window.h" #include "core/hle/service/nvnflinger/window.h"
@ -23,22 +27,19 @@ namespace Service::android {
#ifdef _MSC_VER #ifdef _MSC_VER
#pragma pack(push, 1) #pragma pack(push, 1)
struct BufferHistoryInfo {
#elif defined(__GNUC__) || defined(__clang__)
struct __attribute__((packed)) BufferHistoryInfo {
#endif #endif
struct BufferInfo {
u64 frame_number; u64 frame_number;
s64 queue_time; s64 queue_time;
s64 presentation_time{}; s64 presentation_time;
BufferState state{BufferState::Free}; BufferState state;
} };
#if defined(__GNUC__) || defined(__clang__)
__attribute__((packed))
#endif
;
#ifdef _MSC_VER #ifdef _MSC_VER
#pragma pack(pop) #pragma pack(pop)
#endif #endif
static_assert(sizeof(BufferInfo) == 0x1C, static_assert(sizeof(BufferHistoryInfo) == 0x1C, "BufferHistoryInfo must be 28 bytes");
"BufferInfo is an invalid size");
class IConsumerListener; class IConsumerListener;
class IProducerListener; class IProducerListener;
@ -49,10 +50,13 @@ class BufferQueueCore final {
public: public:
static constexpr s32 INVALID_BUFFER_SLOT = BufferItem::INVALID_BUFFER_SLOT; static constexpr s32 INVALID_BUFFER_SLOT = BufferItem::INVALID_BUFFER_SLOT;
static constexpr u32 BUFFER_HISTORY_SIZE = 8;
BufferQueueCore(); BufferQueueCore();
~BufferQueueCore(); ~BufferQueueCore();
void PushHistory(u64 frame_number, s64 queue_time, s64 presentation_time, BufferState state);
private: private:
void SignalDequeueCondition(); void SignalDequeueCondition();
bool WaitForDequeueCondition(std::unique_lock<std::mutex>& lk); bool WaitForDequeueCondition(std::unique_lock<std::mutex>& lk);
@ -88,11 +92,11 @@ private:
const s32 max_acquired_buffer_count{}; // This is always zero on HOS const s32 max_acquired_buffer_count{}; // This is always zero on HOS
bool buffer_has_been_queued{}; bool buffer_has_been_queued{};
u64 frame_counter{}; u64 frame_counter{};
std::array<BufferHistoryInfo, BUFFER_HISTORY_SIZE> buffer_history{};
u32 buffer_history_pos{BUFFER_HISTORY_SIZE-1};
u32 transform_hint{}; u32 transform_hint{};
bool is_allocating{}; bool is_allocating{};
mutable std::condition_variable_any is_allocating_condition; mutable std::condition_variable_any is_allocating_condition;
std::vector<BufferInfo> history{8};
}; };
} // namespace Service::android } // namespace Service::android

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project // SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
@ -530,11 +533,6 @@ Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input,
item.is_droppable = core->dequeue_buffer_cannot_block || async; item.is_droppable = core->dequeue_buffer_cannot_block || async;
item.swap_interval = swap_interval; item.swap_interval = swap_interval;
position = (position + 1) % 8;
core->history[position] = {.frame_number = core->frame_counter,
.queue_time = slots[slot].queue_time,
.state = BufferState::Queued};
sticky_transform = sticky_transform_; sticky_transform = sticky_transform_;
if (core->queue.empty()) { if (core->queue.empty()) {
@ -551,6 +549,15 @@ Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input,
// mark it as freed // mark it as freed
if (core->StillTracking(*front)) { if (core->StillTracking(*front)) {
slots[front->slot].buffer_state = BufferState::Free; slots[front->slot].buffer_state = BufferState::Free;
// Mark tracked buffer history records as free
for (auto& buffer_history_record : core->buffer_history) {
if (buffer_history_record.frame_number == front->frame_number) {
buffer_history_record.state = BufferState::Free;
break;
}
}
// Reset the frame number of the freed buffer so that it is the first in line to // Reset the frame number of the freed buffer so that it is the first in line to
// be dequeued again // be dequeued again
slots[front->slot].frame_number = 0; slots[front->slot].frame_number = 0;
@ -564,6 +571,7 @@ Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input,
} }
} }
core->PushHistory(core->frame_counter, slots[slot].queue_time, slots[slot].presentation_time, BufferState::Queued);
core->buffer_has_been_queued = true; core->buffer_has_been_queued = true;
core->SignalDequeueCondition(); core->SignalDequeueCondition();
output->Inflate(core->default_width, core->default_height, core->transform_hint, output->Inflate(core->default_width, core->default_height, core->transform_hint,
@ -938,33 +946,46 @@ void BufferQueueProducer::Transact(u32 code, std::span<const u8> parcel_data,
break; break;
} }
case TransactionId::GetBufferHistory: { case TransactionId::GetBufferHistory: {
LOG_WARNING(Service_Nvnflinger, "called, transaction=GetBufferHistory"); LOG_DEBUG(Service_Nvnflinger, "called, transaction=GetBufferHistory");
std::scoped_lock lock{core->mutex}; const s32 request = parcel_in.Read<s32>();
if (request <= 0) {
auto buffer_history_count = (std::min)(parcel_in.Read<s32>(), (s32)core->history.size());
if (buffer_history_count <= 0) {
parcel_out.Write(Status::BadValue); parcel_out.Write(Status::BadValue);
parcel_out.Write<s32>(0); parcel_out.Write<s32>(0);
status = Status::None;
break; break;
} }
auto info = new BufferInfo[buffer_history_count]; constexpr u32 history_max = BufferQueueCore::BUFFER_HISTORY_SIZE;
auto pos = position; std::array<BufferHistoryInfo, history_max> buffer_history_snapshot{};
for (int i = 0; i < buffer_history_count; i++) { s32 valid_index{};
info[i] = core->history[(pos - i) % core->history.size()]; {
LOG_WARNING(Service_Nvnflinger, "frame_number={}, state={}", std::scoped_lock lk(core->mutex);
core->history[(pos - i) % core->history.size()].frame_number,
(u32)core->history[(pos - i) % core->history.size()].state); const u32 current_history_pos = core->buffer_history_pos;
pos--; u32 index_reversed{};
for (u32 i = 0; i < history_max; ++i) {
// Wrap values backwards e.g. 7, 6, 5, etc. in the range of 0-7
index_reversed = (current_history_pos + history_max - i) % history_max;
const auto& current_history_buffer = core->buffer_history[index_reversed];
// Here we use the frame number as a terminator.
// Because a buffer without frame_number is not considered complete
if (current_history_buffer.frame_number == 0) {
break;
} }
buffer_history_snapshot[valid_index] = current_history_buffer;
++valid_index;
}
}
const s32 limit = std::min(request, valid_index);
parcel_out.Write(Status::NoError); parcel_out.Write(Status::NoError);
parcel_out.Write(buffer_history_count); parcel_out.Write<s32>(limit);
parcel_out.WriteFlattenedObject<BufferInfo>(info); for (s32 i = 0; i < limit; ++i) {
status = Status::None; parcel_out.Write(buffer_history_snapshot[i]);
}
break; break;
} }
default: default:
@ -972,9 +993,7 @@ void BufferQueueProducer::Transact(u32 code, std::span<const u8> parcel_data,
break; break;
} }
if (status != Status::None) {
parcel_out.Write(status); parcel_out.Write(status);
}
const auto serialized = parcel_out.Serialize(); const auto serialized = parcel_out.Serialize();
std::memcpy(parcel_reply.data(), serialized.data(), std::memcpy(parcel_reply.data(), serialized.data(),

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project // SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project // SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
@ -15,7 +18,7 @@ namespace Service::android {
class GraphicBuffer; class GraphicBuffer;
enum class BufferState : s32 { enum class BufferState : u32 {
Free = 0, Free = 0,
Dequeued = 1, Dequeued = 1,
Queued = 2, Queued = 2,