mirror of
https://git.eden-emu.dev/eden-emu/eden
synced 2026-02-04 02:51:18 +01:00
[nvnflinger] Reimplement GetBufferHistory (#3394)
Reimplements GetBufferHistory, enabling tracking and retrieval of recent buffer states. This can improve rendering performance and stability in some games. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3394 Reviewed-by: CamilleLaVey <camillelavey99@gmail.com> Co-authored-by: MaranBr <maranbr@outlook.com> Co-committed-by: MaranBr <maranbr@outlook.com>
This commit is contained in:
@@ -24,6 +24,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
||||
RENDERER_FORCE_MAX_CLOCK("force_max_clock"),
|
||||
RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders"),
|
||||
RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"),
|
||||
ENABLE_BUFFER_HISTORY("enable_buffer_history"),
|
||||
SYNC_MEMORY_OPERATIONS("sync_memory_operations"),
|
||||
BUFFER_REORDER_DISABLE("disable_buffer_reorder"),
|
||||
RENDERER_DEBUG("debug"),
|
||||
|
||||
@@ -745,6 +745,13 @@ abstract class SettingsItem(
|
||||
descriptionId = R.string.renderer_reactive_flushing_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.ENABLE_BUFFER_HISTORY,
|
||||
titleId = R.string.enable_buffer_history,
|
||||
descriptionId = R.string.enable_buffer_history_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.SYNC_MEMORY_OPERATIONS,
|
||||
|
||||
@@ -275,6 +275,7 @@ class SettingsFragmentPresenter(
|
||||
add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key)
|
||||
add(BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key)
|
||||
add(BooleanSetting.RENDERER_REACTIVE_FLUSHING.key)
|
||||
add(BooleanSetting.ENABLE_BUFFER_HISTORY.key)
|
||||
|
||||
add(HeaderSetting(R.string.hacks))
|
||||
|
||||
|
||||
@@ -493,6 +493,8 @@
|
||||
<string name="renderer_force_max_clock_description">Forces the GPU to run at the maximum possible clocks (thermal constraints will still be applied).</string>
|
||||
<string name="renderer_reactive_flushing">Use reactive flushing</string>
|
||||
<string name="renderer_reactive_flushing_description">Improves rendering accuracy in some games at the cost of performance.</string>
|
||||
<string name="enable_buffer_history">Enable buffer history</string>
|
||||
<string name="enable_buffer_history_description">Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games.</string>
|
||||
|
||||
|
||||
<string name="hacks">Hacks</string>
|
||||
|
||||
@@ -481,6 +481,14 @@ struct Values {
|
||||
SwitchableSetting<bool> barrier_feedback_loops{linkage, true, "barrier_feedback_loops",
|
||||
Category::RendererAdvanced};
|
||||
|
||||
SwitchableSetting<bool> enable_buffer_history{linkage,
|
||||
false,
|
||||
"enable_buffer_history",
|
||||
Category::RendererAdvanced,
|
||||
Specialization::Default,
|
||||
true,
|
||||
true};
|
||||
|
||||
// Renderer Hacks //
|
||||
SwitchableSetting<GpuOverclock> fast_gpu_time{linkage,
|
||||
GpuOverclock::Medium,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
@@ -99,11 +99,6 @@ Status BufferQueueConsumer::AcquireBuffer(BufferItem* out_buffer,
|
||||
slots[slot].acquire_called = true;
|
||||
slots[slot].needs_cleanup_on_release = false;
|
||||
slots[slot].buffer_state = BufferState::Acquired;
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
// If the buffer has previously been acquired by the consumer, set graphic_buffer to nullptr to
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
@@ -17,6 +17,39 @@ BufferQueueCore::BufferQueueCore() = default;
|
||||
|
||||
BufferQueueCore::~BufferQueueCore() = default;
|
||||
|
||||
void BufferQueueCore::PushHistory(u64 frame_number, s64 queue_time, s64 presentation_time, BufferState state) {
|
||||
std::lock_guard lk(buffer_history_mutex);
|
||||
|
||||
auto it = buffer_history_map.find(frame_number);
|
||||
if (it != buffer_history_map.end()) {
|
||||
it->second.state = state;
|
||||
return;
|
||||
}
|
||||
|
||||
buffer_history_map.emplace(frame_number, BufferHistoryInfo{
|
||||
frame_number,
|
||||
queue_time,
|
||||
presentation_time,
|
||||
state
|
||||
});
|
||||
buffer_history_order.push_back(frame_number);
|
||||
|
||||
if (buffer_history_order.size() > BUFFER_HISTORY_SIZE) {
|
||||
u64 oldest_frame = buffer_history_order.front();
|
||||
buffer_history_order.pop_front();
|
||||
buffer_history_map.erase(oldest_frame);
|
||||
}
|
||||
}
|
||||
|
||||
void BufferQueueCore::UpdateHistory(u64 frame_number, BufferState state) {
|
||||
std::lock_guard lk(buffer_history_mutex);
|
||||
|
||||
auto it = buffer_history_map.find(frame_number);
|
||||
if (it != buffer_history_map.end()) {
|
||||
it->second.state = state;
|
||||
}
|
||||
}
|
||||
|
||||
void BufferQueueCore::SignalDequeueCondition() {
|
||||
dequeue_possible.store(true);
|
||||
dequeue_condition.notify_all();
|
||||
@@ -30,7 +63,6 @@ bool BufferQueueCore::WaitForDequeueCondition(std::unique_lock<std::mutex>& lk)
|
||||
}
|
||||
|
||||
s32 BufferQueueCore::GetMinUndequeuedBufferCountLocked(bool async) const {
|
||||
// If DequeueBuffer is allowed to error out, we don't have to add an extra buffer.
|
||||
if (!use_async_buffer) {
|
||||
return 0;
|
||||
}
|
||||
@@ -55,8 +87,6 @@ s32 BufferQueueCore::GetMaxBufferCountLocked(bool async) const {
|
||||
return override_max_buffer_count;
|
||||
}
|
||||
|
||||
// Any buffers that are dequeued by the producer or sitting in the queue waiting to be consumed
|
||||
// need to have their slots preserved.
|
||||
for (s32 slot = max_buffer_count; slot < BufferQueueDefs::NUM_BUFFER_SLOTS; ++slot) {
|
||||
const auto state = slots[slot].buffer_state;
|
||||
if (state == BufferState::Queued || state == BufferState::Dequeued) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
@@ -10,20 +10,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <algorithm>
|
||||
|
||||
#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"
|
||||
|
||||
namespace Service::android {
|
||||
|
||||
struct BufferHistoryInfo {
|
||||
u64 frame_number{};
|
||||
s64 queue_time{};
|
||||
s64 presentation_time{};
|
||||
BufferState state{};
|
||||
};
|
||||
|
||||
class IConsumerListener;
|
||||
class IProducerListener;
|
||||
|
||||
@@ -33,10 +44,14 @@ 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);
|
||||
void UpdateHistory(u64 frame_number, BufferState state);
|
||||
|
||||
private:
|
||||
void SignalDequeueCondition();
|
||||
bool WaitForDequeueCondition(std::unique_lock<std::mutex>& lk);
|
||||
@@ -72,6 +87,11 @@ private:
|
||||
const s32 max_acquired_buffer_count{}; // This is always zero on HOS
|
||||
bool buffer_has_been_queued{};
|
||||
u64 frame_counter{};
|
||||
|
||||
std::unordered_map<u64, BufferHistoryInfo> buffer_history_map{};
|
||||
mutable std::mutex buffer_history_mutex{};
|
||||
std::deque<u64> buffer_history_order;
|
||||
|
||||
u32 transform_hint{};
|
||||
bool is_allocating{};
|
||||
mutable std::condition_variable_any is_allocating_condition;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/hle/kernel/k_event.h"
|
||||
#include "core/hle/kernel/k_readable_event.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
@@ -26,7 +27,7 @@ BufferQueueProducer::BufferQueueProducer(Service::KernelHelpers::ServiceContext&
|
||||
std::shared_ptr<BufferQueueCore> buffer_queue_core_,
|
||||
Service::Nvidia::NvCore::NvMap& nvmap_)
|
||||
: service_context{service_context_}, core{std::move(buffer_queue_core_)}, slots(core->slots),
|
||||
nvmap(nvmap_) {
|
||||
clock{Common::CreateOptimalClock()}, nvmap(nvmap_) {
|
||||
buffer_wait_event = service_context.CreateEvent("BufferQueue:WaitEvent");
|
||||
}
|
||||
|
||||
@@ -428,8 +429,7 @@ Status BufferQueueProducer::AttachBuffer(s32* out_slot,
|
||||
return return_flags;
|
||||
}
|
||||
|
||||
Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input,
|
||||
QueueBufferOutput* output) {
|
||||
Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input, QueueBufferOutput* output) {
|
||||
s64 timestamp{};
|
||||
bool is_auto_timestamp{};
|
||||
Common::Rectangle<s32> crop;
|
||||
@@ -440,8 +440,7 @@ Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input,
|
||||
s32 swap_interval{};
|
||||
Fence fence{};
|
||||
|
||||
input.Deflate(×tamp, &is_auto_timestamp, &crop, &scaling_mode, &transform,
|
||||
&sticky_transform_, &async, &swap_interval, &fence);
|
||||
input.Deflate(×tamp, &is_auto_timestamp, &crop, &scaling_mode, &transform, &sticky_transform_, &async, &swap_interval, &fence);
|
||||
|
||||
switch (scaling_mode) {
|
||||
case NativeWindowScalingMode::Freeze:
|
||||
@@ -455,10 +454,9 @@ Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input,
|
||||
return Status::BadValue;
|
||||
}
|
||||
|
||||
std::shared_ptr<IConsumerListener> frame_available_listener;
|
||||
std::shared_ptr<IConsumerListener> frame_replaced_listener;
|
||||
s32 callback_ticket{};
|
||||
BufferItem item;
|
||||
std::shared_ptr<IConsumerListener> listener_available;
|
||||
std::shared_ptr<IConsumerListener> listener_replaced;
|
||||
|
||||
{
|
||||
std::scoped_lock lock{core->mutex};
|
||||
@@ -469,127 +467,82 @@ Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input,
|
||||
}
|
||||
|
||||
const s32 max_buffer_count = core->GetMaxBufferCountLocked(async);
|
||||
if (async && core->override_max_buffer_count) {
|
||||
if (core->override_max_buffer_count < max_buffer_count) {
|
||||
LOG_ERROR(Service_Nvnflinger, "async mode is invalid with "
|
||||
"buffer count override");
|
||||
return Status::BadValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (slot < 0 || slot >= max_buffer_count) {
|
||||
LOG_ERROR(Service_Nvnflinger, "slot index {} out of range [0, {})", slot,
|
||||
max_buffer_count);
|
||||
LOG_ERROR(Service_Nvnflinger, "slot {} out of range [0, {})", slot, max_buffer_count);
|
||||
return Status::BadValue;
|
||||
} else if (slots[slot].buffer_state != BufferState::Dequeued) {
|
||||
LOG_ERROR(Service_Nvnflinger,
|
||||
"slot {} is not owned by the producer "
|
||||
"(state = {})",
|
||||
slot, slots[slot].buffer_state);
|
||||
}
|
||||
if (slots[slot].buffer_state != BufferState::Dequeued) {
|
||||
LOG_ERROR(Service_Nvnflinger, "slot {} is not owned by producer", slot);
|
||||
return Status::BadValue;
|
||||
} else if (!slots[slot].request_buffer_called) {
|
||||
LOG_ERROR(Service_Nvnflinger,
|
||||
"slot {} was queued without requesting "
|
||||
"a buffer",
|
||||
slot);
|
||||
}
|
||||
if (!slots[slot].request_buffer_called) {
|
||||
LOG_ERROR(Service_Nvnflinger, "slot {} was queued without request", slot);
|
||||
return Status::BadValue;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Service_Nvnflinger,
|
||||
"slot={} frame={} time={} crop=[{},{},{},{}] transform={} scale={}", slot,
|
||||
core->frame_counter + 1, timestamp, crop.Left(), crop.Top(), crop.Right(),
|
||||
crop.Bottom(), transform, scaling_mode);
|
||||
|
||||
const std::shared_ptr<GraphicBuffer>& graphic_buffer(slots[slot].graphic_buffer);
|
||||
Common::Rectangle<s32> buffer_rect(graphic_buffer->Width(), graphic_buffer->Height());
|
||||
Common::Rectangle<s32> cropped_rect;
|
||||
[[maybe_unused]] const bool unused = crop.Intersect(buffer_rect, &cropped_rect);
|
||||
|
||||
if (cropped_rect != crop) {
|
||||
LOG_ERROR(Service_Nvnflinger, "crop rect is not contained within the buffer in slot {}",
|
||||
slot);
|
||||
return Status::BadValue;
|
||||
}
|
||||
|
||||
slots[slot].fence = fence;
|
||||
slots[slot].buffer_state = BufferState::Queued;
|
||||
++core->frame_counter;
|
||||
slots[slot].buffer_state = BufferState::Queued;
|
||||
slots[slot].frame_number = core->frame_counter;
|
||||
slots[slot].queue_time = timestamp;
|
||||
slots[slot].presentation_time = clock->GetTimeNS().count();
|
||||
slots[slot].fence = fence;
|
||||
|
||||
item.acquire_called = slots[slot].acquire_called;
|
||||
item.slot = slot;
|
||||
item.graphic_buffer = slots[slot].graphic_buffer;
|
||||
item.crop = crop;
|
||||
item.transform = transform & ~NativeWindowTransform::InverseDisplay;
|
||||
item.transform_to_display_inverse =
|
||||
(transform & NativeWindowTransform::InverseDisplay) != NativeWindowTransform::None;
|
||||
item.scaling_mode = static_cast<u32>(scaling_mode);
|
||||
item.frame_number = core->frame_counter;
|
||||
item.timestamp = timestamp;
|
||||
item.is_auto_timestamp = is_auto_timestamp;
|
||||
item.frame_number = core->frame_counter;
|
||||
item.slot = slot;
|
||||
item.crop = crop;
|
||||
item.transform = transform & ~NativeWindowTransform::InverseDisplay;
|
||||
item.transform_to_display_inverse = (transform & NativeWindowTransform::InverseDisplay) != NativeWindowTransform::None;
|
||||
item.scaling_mode = static_cast<u32>(scaling_mode);
|
||||
item.fence = fence;
|
||||
item.is_droppable = core->dequeue_buffer_cannot_block || async;
|
||||
item.swap_interval = swap_interval;
|
||||
item.acquire_called = slots[slot].acquire_called;
|
||||
|
||||
sticky_transform = sticky_transform_;
|
||||
|
||||
if (core->queue.empty()) {
|
||||
// When the queue is empty, we can simply queue this buffer
|
||||
core->queue.push_back(item);
|
||||
frame_available_listener = core->consumer_listener;
|
||||
listener_available = core->consumer_listener;
|
||||
} else {
|
||||
// When the queue is not empty, we need to look at the front buffer
|
||||
// state to see if we need to replace it
|
||||
auto front(core->queue.begin());
|
||||
auto front = core->queue.begin();
|
||||
if (front->is_droppable && core->StillTracking(*front)) {
|
||||
slots[front->slot].buffer_state = BufferState::Free;
|
||||
if (Settings::values.enable_buffer_history.GetValue()) {
|
||||
core->UpdateHistory(front->frame_number, BufferState::Free);
|
||||
}
|
||||
slots[front->slot].frame_number = 0;
|
||||
}
|
||||
|
||||
if (front->is_droppable) {
|
||||
// If the front queued buffer is still being tracked, we first
|
||||
// mark it as freed
|
||||
if (core->StillTracking(*front)) {
|
||||
slots[front->slot].buffer_state = BufferState::Free;
|
||||
// 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;
|
||||
}
|
||||
// Overwrite the droppable buffer with the incoming one
|
||||
*front = item;
|
||||
frame_replaced_listener = core->consumer_listener;
|
||||
listener_replaced = core->consumer_listener;
|
||||
} else {
|
||||
core->queue.push_back(item);
|
||||
frame_available_listener = core->consumer_listener;
|
||||
listener_available = core->consumer_listener;
|
||||
}
|
||||
}
|
||||
|
||||
if (Settings::values.enable_buffer_history.GetValue()) {
|
||||
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,
|
||||
static_cast<u32>(core->queue.size()));
|
||||
|
||||
// Take a ticket for the callback functions
|
||||
callback_ticket = next_callback_ticket++;
|
||||
output->Inflate(core->default_width, core->default_height, core->transform_hint, static_cast<u32>(core->queue.size()));
|
||||
}
|
||||
|
||||
// Don't send the GraphicBuffer through the callback, and don't send the slot number, since the
|
||||
// consumer shouldn't need it
|
||||
item.graphic_buffer.reset();
|
||||
item.slot = BufferItem::INVALID_BUFFER_SLOT;
|
||||
|
||||
// Call back without the main BufferQueue lock held, but with the callback lock held so we can
|
||||
// ensure that callbacks occur in order
|
||||
{
|
||||
std::scoped_lock lock{callback_mutex};
|
||||
while (callback_ticket != current_callback_ticket) {
|
||||
callback_condition.wait(callback_mutex);
|
||||
}
|
||||
|
||||
if (frame_available_listener != nullptr) {
|
||||
frame_available_listener->OnFrameAvailable(item);
|
||||
} else if (frame_replaced_listener != nullptr) {
|
||||
frame_replaced_listener->OnFrameReplaced(item);
|
||||
}
|
||||
|
||||
++current_callback_ticket;
|
||||
callback_condition.notify_all();
|
||||
if (listener_available) {
|
||||
listener_available->OnFrameAvailable(item);
|
||||
} else if (listener_replaced) {
|
||||
listener_replaced->OnFrameReplaced(item);
|
||||
}
|
||||
|
||||
return Status::NoError;
|
||||
@@ -810,6 +763,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<const u8> parcel_data,
|
||||
std::span<u8> parcel_reply, u32 flags) {
|
||||
// Values used by BnGraphicBufferProducer onTransact
|
||||
@@ -929,9 +886,42 @@ void BufferQueueProducer::Transact(u32 code, std::span<const u8> parcel_data,
|
||||
status = SetBufferCount(buffer_count);
|
||||
break;
|
||||
}
|
||||
case TransactionId::GetBufferHistory:
|
||||
LOG_DEBUG(Service_Nvnflinger, "(STUBBED) called, transaction=GetBufferHistory");
|
||||
case TransactionId::GetBufferHistory: {
|
||||
if (!Settings::values.enable_buffer_history.GetValue()) {
|
||||
LOG_DEBUG(Service_Nvnflinger, "(STUBBED) called");
|
||||
break;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Service_Nvnflinger, "called, transaction=GetBufferHistory");
|
||||
|
||||
const s32 request = parcel_in.Read<s32>();
|
||||
if (request <= 0) {
|
||||
parcel_out.Write(Status::BadValue);
|
||||
parcel_out.Write<s32>(0);
|
||||
break;
|
||||
}
|
||||
|
||||
std::vector<BufferHistoryInfo> snapshot;
|
||||
|
||||
{
|
||||
std::scoped_lock lk(core->buffer_history_mutex);
|
||||
for (auto& [frame, info] : core->buffer_history_map) {
|
||||
snapshot.push_back(info);
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(snapshot.begin(), snapshot.end(), [](auto& a, auto& b){
|
||||
return a.frame_number > b.frame_number;
|
||||
});
|
||||
|
||||
const s32 limit = std::min(request, (s32)snapshot.size());
|
||||
parcel_out.Write(Status::NoError);
|
||||
parcel_out.Write<s32>(limit);
|
||||
for (s32 i = 0; i < limit; ++i) {
|
||||
parcel_out.Write(snapshot[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ASSERT_MSG(false, "Unimplemented TransactionId {}", code);
|
||||
break;
|
||||
@@ -944,8 +934,4 @@ void BufferQueueProducer::Transact(u32 code, std::span<const u8> parcel_data,
|
||||
(std::min)(parcel_reply.size(), serialized.size()));
|
||||
}
|
||||
|
||||
Kernel::KReadableEvent* BufferQueueProducer::GetNativeHandle(u32 type_id) {
|
||||
return &buffer_wait_event->GetReadableEvent();
|
||||
}
|
||||
|
||||
} // namespace Service::android
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <mutex>
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/wall_clock.h"
|
||||
#include "core/hle/service/nvdrv/nvdata.h"
|
||||
#include "core/hle/service/nvnflinger/binder.h"
|
||||
#include "core/hle/service/nvnflinger/buffer_queue_defs.h"
|
||||
@@ -88,6 +89,7 @@ private:
|
||||
s32 next_callback_ticket{};
|
||||
s32 current_callback_ticket{};
|
||||
std::condition_variable_any callback_condition;
|
||||
std::unique_ptr<Common::WallClock> clock;
|
||||
|
||||
Service::Nvidia::NvCore::NvMap& nvmap;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
@@ -37,6 +37,7 @@ struct BufferSlot final {
|
||||
bool needs_cleanup_on_release{};
|
||||
bool attached_by_consumer{};
|
||||
bool is_preallocated{};
|
||||
s64 queue_time{}, presentation_time{};
|
||||
};
|
||||
|
||||
} // namespace Service::android
|
||||
|
||||
@@ -329,6 +329,10 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
|
||||
barrier_feedback_loops,
|
||||
tr("Barrier feedback loops"),
|
||||
tr("Improves rendering of transparency effects in specific games."));
|
||||
INSERT(Settings,
|
||||
enable_buffer_history,
|
||||
tr("Enable buffer history"),
|
||||
tr("Enables access to previous buffer states.\nThis option may improve rendering quality and performance consistency in some games."));
|
||||
INSERT(Settings,
|
||||
fix_bloom_effects,
|
||||
tr("Fix bloom effects"),
|
||||
|
||||
Reference in New Issue
Block a user