From be97bf3c1b6b09fcfc1828f201f04a87d034b92e Mon Sep 17 00:00:00 2001 From: Maufeat Date: Mon, 21 Jul 2025 07:16:26 +0200 Subject: [PATCH] [nvnflinger] add GetBufferHistory from sudachi (#82) Co-authored-by: Maufeat Co-authored-by: CamilleLaVey Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/82 This commit adds a working implementation of the `GetBufferHistory` transaction in `BufferQueueProducer`, removing the previous stub. Adapted by Jarrod Norwell for Sudachi, this implementation references the behavior in Ryujinx; commit rescued by Maufeat and another Eden teammate from Sudachi's reference, fixed and adapted for Eden usage. It helps improve compatibility with Unreal Engine 4 titles and others that depend on proper surface history tracking for rendering pipelines, especially with regard to lighting, bloom, and alpha transitions. Functionality has been tested for stability and does not introduce regressions, though further validation is recommended. Co-authored-by: Maufeat Co-committed-by: Maufeat --- .../nvnflinger/buffer_queue_consumer.cpp | 8 +++ .../service/nvnflinger/buffer_queue_core.cpp | 4 +- .../service/nvnflinger/buffer_queue_core.h | 21 ++++++++ .../nvnflinger/buffer_queue_producer.cpp | 49 ++++++++++++++++--- .../nvnflinger/buffer_queue_producer.h | 2 + src/core/hle/service/nvnflinger/buffer_slot.h | 3 +- 6 files changed, 79 insertions(+), 8 deletions(-) diff --git a/src/core/hle/service/nvnflinger/buffer_queue_consumer.cpp b/src/core/hle/service/nvnflinger/buffer_queue_consumer.cpp index 3bc23aa976..91ba35aef5 100644 --- a/src/core/hle/service/nvnflinger/buffer_queue_consumer.cpp +++ b/src/core/hle/service/nvnflinger/buffer_queue_consumer.cpp @@ -101,6 +101,14 @@ Status BufferQueueConsumer::AcquireBuffer(BufferItem* out_buffer, // 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 5d8c861fa7..30095b0f73 100644 --- a/src/core/hle/service/nvnflinger/buffer_queue_core.cpp +++ b/src/core/hle/service/nvnflinger/buffer_queue_core.cpp @@ -10,7 +10,9 @@ namespace Service::android { -BufferQueueCore::BufferQueueCore() = default; +BufferQueueCore::BufferQueueCore() { + history.resize(8); +}; BufferQueueCore::~BufferQueueCore() = default; diff --git a/src/core/hle/service/nvnflinger/buffer_queue_core.h b/src/core/hle/service/nvnflinger/buffer_queue_core.h index e513d183bf..341634352b 100644 --- a/src/core/hle/service/nvnflinger/buffer_queue_core.h +++ b/src/core/hle/service/nvnflinger/buffer_queue_core.h @@ -21,6 +21,25 @@ namespace Service::android { +#ifdef _MSC_VER +#pragma pack(push, 1) +#endif +struct BufferInfo { + u64 frame_number; + s64 queue_time; + s64 presentation_time{}; + BufferState state{BufferState::Free}; +} +#if defined(__GNUC__) || defined(__clang__) +__attribute__((packed)) +#endif +; +#ifdef _MSC_VER +#pragma pack(pop) +#endif +static_assert(sizeof(BufferInfo) == 0x1C, + "BufferInfo is an invalid size"); + class IConsumerListener; class IProducerListener; @@ -72,6 +91,8 @@ private: 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 9e5091eebd..4317aee1c4 100644 --- a/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp +++ b/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp @@ -512,6 +512,8 @@ Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input, slots[slot].buffer_state = BufferState::Queued; ++core->frame_counter; slots[slot].frame_number = core->frame_counter; + slots[slot].queue_time = timestamp; + slots[slot].presentation_time = 0; item.acquire_called = slots[slot].acquire_called; item.graphic_buffer = slots[slot].graphic_buffer; @@ -528,6 +530,11 @@ 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()) { @@ -803,6 +810,10 @@ Status BufferQueueProducer::SetPreallocatedBuffer(s32 slot, return Status::NoError; } +Kernel::KReadableEvent* BufferQueueProducer::GetNativeHandle(u32 type_id) { + return &buffer_wait_event->GetReadableEvent(); +} + void BufferQueueProducer::Transact(u32 code, std::span parcel_data, std::span parcel_reply, u32 flags) { // Values used by BnGraphicBufferProducer onTransact @@ -922,23 +933,49 @@ void BufferQueueProducer::Transact(u32 code, std::span parcel_data, status = SetBufferCount(buffer_count); break; } - case TransactionId::GetBufferHistory: - LOG_WARNING(Service_Nvnflinger, "(STUBBED) called, transaction=GetBufferHistory"); + case TransactionId::GetBufferHistory: { + LOG_WARNING(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) { + 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--; + } + + parcel_out.Write(Status::NoError); + parcel_out.Write(buffer_history_count); + parcel_out.WriteFlattenedObject(info); + status = Status::None; break; + } default: ASSERT_MSG(false, "Unimplemented TransactionId {}", code); break; } - parcel_out.Write(status); + if (status != Status::None) { + 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 diff --git a/src/core/hle/service/nvnflinger/buffer_queue_producer.h b/src/core/hle/service/nvnflinger/buffer_queue_producer.h index 048523514c..28195cd3c5 100644 --- a/src/core/hle/service/nvnflinger/buffer_queue_producer.h +++ b/src/core/hle/service/nvnflinger/buffer_queue_producer.h @@ -86,6 +86,8 @@ private: s32 current_callback_ticket{}; std::condition_variable_any callback_condition; + u64 position; + Service::Nvidia::NvCore::NvMap& nvmap; }; diff --git a/src/core/hle/service/nvnflinger/buffer_slot.h b/src/core/hle/service/nvnflinger/buffer_slot.h index 37daca78b1..5b5cbb6fbd 100644 --- a/src/core/hle/service/nvnflinger/buffer_slot.h +++ b/src/core/hle/service/nvnflinger/buffer_slot.h @@ -15,7 +15,7 @@ namespace Service::android { class GraphicBuffer; -enum class BufferState : u32 { +enum class BufferState : s32 { Free = 0, Dequeued = 1, Queued = 2, @@ -34,6 +34,7 @@ struct BufferSlot final { bool needs_cleanup_on_release{}; bool attached_by_consumer{}; bool is_preallocated{}; + s64 queue_time{}, presentation_time{}; }; } // namespace Service::android