diff --git a/src/core/hle/service/nvnflinger/buffer_queue_consumer.cpp b/src/core/hle/service/nvnflinger/buffer_queue_consumer.cpp index a9b0f9d2f3..2913d25819 100644 --- a/src/core/hle/service/nvnflinger/buffer_queue_consumer.cpp +++ b/src/core/hle/service/nvnflinger/buffer_queue_consumer.cpp @@ -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 2014 The Android Open Source Project // 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].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 // 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. // 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 diff --git a/src/core/hle/service/nvnflinger/buffer_queue_core.cpp b/src/core/hle/service/nvnflinger/buffer_queue_core.cpp index 27ac930f96..6120d8eae1 100644 --- a/src/core/hle/service/nvnflinger/buffer_queue_core.cpp +++ b/src/core/hle/service/nvnflinger/buffer_queue_core.cpp @@ -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 2014 The Android Open Source Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -10,12 +13,19 @@ namespace Service::android { -BufferQueueCore::BufferQueueCore() { - 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() { dequeue_possible.store(true); dequeue_condition.notify_all(); @@ -47,7 +57,7 @@ s32 BufferQueueCore::GetMinMaxBufferCountLocked(bool async) const { s32 BufferQueueCore::GetMaxBufferCountLocked(bool async) const { 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) { ASSERT(override_max_buffer_count >= min_buffer_count); diff --git a/src/core/hle/service/nvnflinger/buffer_queue_core.h b/src/core/hle/service/nvnflinger/buffer_queue_core.h index 341634352b..ed7d4b4069 100644 --- a/src/core/hle/service/nvnflinger/buffer_queue_core.h +++ b/src/core/hle/service/nvnflinger/buffer_queue_core.h @@ -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 2014 The Android Open Source Project // 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_queue_defs.h" +#include "core/hle/service/nvnflinger/buffer_slot.h" #include "core/hle/service/nvnflinger/pixel_format.h" #include "core/hle/service/nvnflinger/status.h" #include "core/hle/service/nvnflinger/window.h" @@ -23,22 +27,19 @@ namespace Service::android { #ifdef _MSC_VER #pragma pack(push, 1) +struct BufferHistoryInfo { +#elif defined(__GNUC__) || defined(__clang__) +struct __attribute__((packed)) BufferHistoryInfo { #endif -struct BufferInfo { u64 frame_number; s64 queue_time; - s64 presentation_time{}; - BufferState state{BufferState::Free}; -} -#if defined(__GNUC__) || defined(__clang__) -__attribute__((packed)) -#endif -; + s64 presentation_time; + BufferState state; +}; #ifdef _MSC_VER #pragma pack(pop) #endif -static_assert(sizeof(BufferInfo) == 0x1C, - "BufferInfo is an invalid size"); +static_assert(sizeof(BufferHistoryInfo) == 0x1C, "BufferHistoryInfo must be 28 bytes"); class IConsumerListener; class IProducerListener; @@ -49,10 +50,13 @@ class BufferQueueCore final { public: static constexpr s32 INVALID_BUFFER_SLOT = BufferItem::INVALID_BUFFER_SLOT; + static constexpr u32 BUFFER_HISTORY_SIZE = 8; BufferQueueCore(); ~BufferQueueCore(); + void PushHistory(u64 frame_number, s64 queue_time, s64 presentation_time, BufferState state); + private: void SignalDequeueCondition(); bool WaitForDequeueCondition(std::unique_lock& lk); @@ -88,11 +92,11 @@ private: const s32 max_acquired_buffer_count{}; // This is always zero on HOS bool buffer_has_been_queued{}; u64 frame_counter{}; + std::array buffer_history{}; + u32 buffer_history_pos{BUFFER_HISTORY_SIZE-1}; u32 transform_hint{}; bool is_allocating{}; mutable std::condition_variable_any is_allocating_condition; - - std::vector history{8}; }; } // namespace Service::android diff --git a/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp b/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp index f9e1dba965..bc3076d20b 100644 --- a/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp +++ b/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp @@ -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 2014 The Android Open Source Project // 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.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_; if (core->queue.empty()) { @@ -551,6 +549,15 @@ Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input, // mark it as freed if (core->StillTracking(*front)) { 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 // be dequeued again 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->SignalDequeueCondition(); output->Inflate(core->default_width, core->default_height, core->transform_hint, @@ -938,33 +946,46 @@ void BufferQueueProducer::Transact(u32 code, std::span parcel_data, break; } case TransactionId::GetBufferHistory: { - LOG_WARNING(Service_Nvnflinger, "called, transaction=GetBufferHistory"); + LOG_DEBUG(Service_Nvnflinger, "called, transaction=GetBufferHistory"); - std::scoped_lock lock{core->mutex}; - - auto buffer_history_count = (std::min)(parcel_in.Read(), (s32)core->history.size()); - - if (buffer_history_count <= 0) { + const s32 request = parcel_in.Read(); + if (request <= 0) { parcel_out.Write(Status::BadValue); parcel_out.Write(0); - status = Status::None; break; } - auto info = new BufferInfo[buffer_history_count]; - auto pos = position; - for (int i = 0; i < buffer_history_count; i++) { - info[i] = core->history[(pos - i) % core->history.size()]; - LOG_WARNING(Service_Nvnflinger, "frame_number={}, state={}", - core->history[(pos - i) % core->history.size()].frame_number, - (u32)core->history[(pos - i) % core->history.size()].state); - pos--; + constexpr u32 history_max = BufferQueueCore::BUFFER_HISTORY_SIZE; + std::array buffer_history_snapshot{}; + s32 valid_index{}; + { + std::scoped_lock lk(core->mutex); + + const u32 current_history_pos = core->buffer_history_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(buffer_history_count); - parcel_out.WriteFlattenedObject(info); - status = Status::None; + parcel_out.Write(limit); + for (s32 i = 0; i < limit; ++i) { + parcel_out.Write(buffer_history_snapshot[i]); + } + break; } default: @@ -972,9 +993,7 @@ void BufferQueueProducer::Transact(u32 code, std::span parcel_data, break; } - if (status != Status::None) { - parcel_out.Write(status); - } + parcel_out.Write(status); const auto serialized = parcel_out.Serialize(); std::memcpy(parcel_reply.data(), serialized.data(), diff --git a/src/core/hle/service/nvnflinger/buffer_queue_producer.h b/src/core/hle/service/nvnflinger/buffer_queue_producer.h index 28195cd3c5..6610e0853a 100644 --- a/src/core/hle/service/nvnflinger/buffer_queue_producer.h +++ b/src/core/hle/service/nvnflinger/buffer_queue_producer.h @@ -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 2014 The Android Open Source Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/nvnflinger/buffer_slot.h b/src/core/hle/service/nvnflinger/buffer_slot.h index 5b5cbb6fbd..d348b331cb 100644 --- a/src/core/hle/service/nvnflinger/buffer_slot.h +++ b/src/core/hle/service/nvnflinger/buffer_slot.h @@ -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 2014 The Android Open Source Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -15,7 +18,7 @@ namespace Service::android { class GraphicBuffer; -enum class BufferState : s32 { +enum class BufferState : u32 { Free = 0, Dequeued = 1, Queued = 2,