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_FORCE_MAX_CLOCK("force_max_clock"),
|
||||||
RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders"),
|
RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders"),
|
||||||
RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"),
|
RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"),
|
||||||
|
ENABLE_BUFFER_HISTORY("enable_buffer_history"),
|
||||||
SYNC_MEMORY_OPERATIONS("sync_memory_operations"),
|
SYNC_MEMORY_OPERATIONS("sync_memory_operations"),
|
||||||
BUFFER_REORDER_DISABLE("disable_buffer_reorder"),
|
BUFFER_REORDER_DISABLE("disable_buffer_reorder"),
|
||||||
RENDERER_DEBUG("debug"),
|
RENDERER_DEBUG("debug"),
|
||||||
|
|||||||
@@ -745,6 +745,13 @@ abstract class SettingsItem(
|
|||||||
descriptionId = R.string.renderer_reactive_flushing_description
|
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(
|
put(
|
||||||
SwitchSetting(
|
SwitchSetting(
|
||||||
BooleanSetting.SYNC_MEMORY_OPERATIONS,
|
BooleanSetting.SYNC_MEMORY_OPERATIONS,
|
||||||
|
|||||||
@@ -275,6 +275,7 @@ class SettingsFragmentPresenter(
|
|||||||
add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key)
|
add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key)
|
||||||
add(BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key)
|
add(BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key)
|
||||||
add(BooleanSetting.RENDERER_REACTIVE_FLUSHING.key)
|
add(BooleanSetting.RENDERER_REACTIVE_FLUSHING.key)
|
||||||
|
add(BooleanSetting.ENABLE_BUFFER_HISTORY.key)
|
||||||
|
|
||||||
add(HeaderSetting(R.string.hacks))
|
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_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">Use reactive flushing</string>
|
||||||
<string name="renderer_reactive_flushing_description">Improves rendering accuracy in some games at the cost of performance.</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>
|
<string name="hacks">Hacks</string>
|
||||||
|
|||||||
@@ -481,6 +481,14 @@ struct Values {
|
|||||||
SwitchableSetting<bool> barrier_feedback_loops{linkage, true, "barrier_feedback_loops",
|
SwitchableSetting<bool> barrier_feedback_loops{linkage, true, "barrier_feedback_loops",
|
||||||
Category::RendererAdvanced};
|
Category::RendererAdvanced};
|
||||||
|
|
||||||
|
SwitchableSetting<bool> enable_buffer_history{linkage,
|
||||||
|
false,
|
||||||
|
"enable_buffer_history",
|
||||||
|
Category::RendererAdvanced,
|
||||||
|
Specialization::Default,
|
||||||
|
true,
|
||||||
|
true};
|
||||||
|
|
||||||
// Renderer Hacks //
|
// Renderer Hacks //
|
||||||
SwitchableSetting<GpuOverclock> fast_gpu_time{linkage,
|
SwitchableSetting<GpuOverclock> fast_gpu_time{linkage,
|
||||||
GpuOverclock::Medium,
|
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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
@@ -99,11 +99,6 @@ Status BufferQueueConsumer::AcquireBuffer(BufferItem* out_buffer,
|
|||||||
slots[slot].acquire_called = true;
|
slots[slot].acquire_called = true;
|
||||||
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;
|
||||||
|
|
||||||
// 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
|
// 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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
@@ -17,6 +17,39 @@ BufferQueueCore::BufferQueueCore() = default;
|
|||||||
|
|
||||||
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() {
|
void BufferQueueCore::SignalDequeueCondition() {
|
||||||
dequeue_possible.store(true);
|
dequeue_possible.store(true);
|
||||||
dequeue_condition.notify_all();
|
dequeue_condition.notify_all();
|
||||||
@@ -30,7 +63,6 @@ bool BufferQueueCore::WaitForDequeueCondition(std::unique_lock<std::mutex>& lk)
|
|||||||
}
|
}
|
||||||
|
|
||||||
s32 BufferQueueCore::GetMinUndequeuedBufferCountLocked(bool async) const {
|
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) {
|
if (!use_async_buffer) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -55,8 +87,6 @@ s32 BufferQueueCore::GetMaxBufferCountLocked(bool async) const {
|
|||||||
return override_max_buffer_count;
|
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) {
|
for (s32 slot = max_buffer_count; slot < BufferQueueDefs::NUM_BUFFER_SLOTS; ++slot) {
|
||||||
const auto state = slots[slot].buffer_state;
|
const auto state = slots[slot].buffer_state;
|
||||||
if (state == BufferState::Queued || state == BufferState::Dequeued) {
|
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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
@@ -10,20 +10,31 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
|
#include <deque>
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#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"
|
||||||
|
|
||||||
namespace Service::android {
|
namespace Service::android {
|
||||||
|
|
||||||
|
struct BufferHistoryInfo {
|
||||||
|
u64 frame_number{};
|
||||||
|
s64 queue_time{};
|
||||||
|
s64 presentation_time{};
|
||||||
|
BufferState state{};
|
||||||
|
};
|
||||||
|
|
||||||
class IConsumerListener;
|
class IConsumerListener;
|
||||||
class IProducerListener;
|
class IProducerListener;
|
||||||
|
|
||||||
@@ -33,10 +44,14 @@ 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);
|
||||||
|
void UpdateHistory(u64 frame_number, BufferState state);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void SignalDequeueCondition();
|
void SignalDequeueCondition();
|
||||||
bool WaitForDequeueCondition(std::unique_lock<std::mutex>& lk);
|
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
|
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::unordered_map<u64, BufferHistoryInfo> buffer_history_map{};
|
||||||
|
mutable std::mutex buffer_history_mutex{};
|
||||||
|
std::deque<u64> buffer_history_order;
|
||||||
|
|
||||||
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;
|
||||||
|
|||||||
@@ -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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
#include "common/settings.h"
|
||||||
#include "core/hle/kernel/k_event.h"
|
#include "core/hle/kernel/k_event.h"
|
||||||
#include "core/hle/kernel/k_readable_event.h"
|
#include "core/hle/kernel/k_readable_event.h"
|
||||||
#include "core/hle/kernel/kernel.h"
|
#include "core/hle/kernel/kernel.h"
|
||||||
@@ -26,7 +27,7 @@ BufferQueueProducer::BufferQueueProducer(Service::KernelHelpers::ServiceContext&
|
|||||||
std::shared_ptr<BufferQueueCore> buffer_queue_core_,
|
std::shared_ptr<BufferQueueCore> buffer_queue_core_,
|
||||||
Service::Nvidia::NvCore::NvMap& nvmap_)
|
Service::Nvidia::NvCore::NvMap& nvmap_)
|
||||||
: service_context{service_context_}, core{std::move(buffer_queue_core_)}, slots(core->slots),
|
: 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");
|
buffer_wait_event = service_context.CreateEvent("BufferQueue:WaitEvent");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -428,8 +429,7 @@ Status BufferQueueProducer::AttachBuffer(s32* out_slot,
|
|||||||
return return_flags;
|
return return_flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input,
|
Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input, QueueBufferOutput* output) {
|
||||||
QueueBufferOutput* output) {
|
|
||||||
s64 timestamp{};
|
s64 timestamp{};
|
||||||
bool is_auto_timestamp{};
|
bool is_auto_timestamp{};
|
||||||
Common::Rectangle<s32> crop;
|
Common::Rectangle<s32> crop;
|
||||||
@@ -440,8 +440,7 @@ Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input,
|
|||||||
s32 swap_interval{};
|
s32 swap_interval{};
|
||||||
Fence fence{};
|
Fence fence{};
|
||||||
|
|
||||||
input.Deflate(×tamp, &is_auto_timestamp, &crop, &scaling_mode, &transform,
|
input.Deflate(×tamp, &is_auto_timestamp, &crop, &scaling_mode, &transform, &sticky_transform_, &async, &swap_interval, &fence);
|
||||||
&sticky_transform_, &async, &swap_interval, &fence);
|
|
||||||
|
|
||||||
switch (scaling_mode) {
|
switch (scaling_mode) {
|
||||||
case NativeWindowScalingMode::Freeze:
|
case NativeWindowScalingMode::Freeze:
|
||||||
@@ -455,10 +454,9 @@ Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input,
|
|||||||
return Status::BadValue;
|
return Status::BadValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<IConsumerListener> frame_available_listener;
|
|
||||||
std::shared_ptr<IConsumerListener> frame_replaced_listener;
|
|
||||||
s32 callback_ticket{};
|
|
||||||
BufferItem item;
|
BufferItem item;
|
||||||
|
std::shared_ptr<IConsumerListener> listener_available;
|
||||||
|
std::shared_ptr<IConsumerListener> listener_replaced;
|
||||||
|
|
||||||
{
|
{
|
||||||
std::scoped_lock lock{core->mutex};
|
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);
|
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) {
|
if (slot < 0 || slot >= max_buffer_count) {
|
||||||
LOG_ERROR(Service_Nvnflinger, "slot index {} out of range [0, {})", slot,
|
LOG_ERROR(Service_Nvnflinger, "slot {} out of range [0, {})", slot, max_buffer_count);
|
||||||
max_buffer_count);
|
|
||||||
return Status::BadValue;
|
return Status::BadValue;
|
||||||
} else if (slots[slot].buffer_state != BufferState::Dequeued) {
|
}
|
||||||
LOG_ERROR(Service_Nvnflinger,
|
if (slots[slot].buffer_state != BufferState::Dequeued) {
|
||||||
"slot {} is not owned by the producer "
|
LOG_ERROR(Service_Nvnflinger, "slot {} is not owned by producer", slot);
|
||||||
"(state = {})",
|
|
||||||
slot, slots[slot].buffer_state);
|
|
||||||
return Status::BadValue;
|
return Status::BadValue;
|
||||||
} else if (!slots[slot].request_buffer_called) {
|
}
|
||||||
LOG_ERROR(Service_Nvnflinger,
|
if (!slots[slot].request_buffer_called) {
|
||||||
"slot {} was queued without requesting "
|
LOG_ERROR(Service_Nvnflinger, "slot {} was queued without request", slot);
|
||||||
"a buffer",
|
|
||||||
slot);
|
|
||||||
return Status::BadValue;
|
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;
|
++core->frame_counter;
|
||||||
|
slots[slot].buffer_state = BufferState::Queued;
|
||||||
slots[slot].frame_number = core->frame_counter;
|
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.graphic_buffer = slots[slot].graphic_buffer;
|
||||||
item.crop = crop;
|
item.frame_number = core->frame_counter;
|
||||||
item.transform = transform & ~NativeWindowTransform::InverseDisplay;
|
|
||||||
item.transform_to_display_inverse =
|
|
||||||
(transform & NativeWindowTransform::InverseDisplay) != NativeWindowTransform::None;
|
|
||||||
item.scaling_mode = static_cast<u32>(scaling_mode);
|
|
||||||
item.timestamp = timestamp;
|
item.timestamp = timestamp;
|
||||||
item.is_auto_timestamp = is_auto_timestamp;
|
item.is_auto_timestamp = is_auto_timestamp;
|
||||||
item.frame_number = core->frame_counter;
|
item.crop = crop;
|
||||||
item.slot = slot;
|
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.fence = fence;
|
||||||
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;
|
||||||
|
item.acquire_called = slots[slot].acquire_called;
|
||||||
|
|
||||||
sticky_transform = sticky_transform_;
|
sticky_transform = sticky_transform_;
|
||||||
|
|
||||||
if (core->queue.empty()) {
|
if (core->queue.empty()) {
|
||||||
// When the queue is empty, we can simply queue this buffer
|
|
||||||
core->queue.push_back(item);
|
core->queue.push_back(item);
|
||||||
frame_available_listener = core->consumer_listener;
|
listener_available = core->consumer_listener;
|
||||||
} else {
|
} else {
|
||||||
// When the queue is not empty, we need to look at the front buffer
|
auto front = core->queue.begin();
|
||||||
// state to see if we need to replace it
|
if (front->is_droppable && core->StillTracking(*front)) {
|
||||||
auto front(core->queue.begin());
|
|
||||||
|
|
||||||
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;
|
slots[front->slot].buffer_state = BufferState::Free;
|
||||||
// Reset the frame number of the freed buffer so that it is the first in line to
|
if (Settings::values.enable_buffer_history.GetValue()) {
|
||||||
// be dequeued again
|
core->UpdateHistory(front->frame_number, BufferState::Free);
|
||||||
|
}
|
||||||
slots[front->slot].frame_number = 0;
|
slots[front->slot].frame_number = 0;
|
||||||
}
|
}
|
||||||
// Overwrite the droppable buffer with the incoming one
|
|
||||||
|
if (front->is_droppable) {
|
||||||
*front = item;
|
*front = item;
|
||||||
frame_replaced_listener = core->consumer_listener;
|
listener_replaced = core->consumer_listener;
|
||||||
} else {
|
} else {
|
||||||
core->queue.push_back(item);
|
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->buffer_has_been_queued = true;
|
||||||
core->SignalDequeueCondition();
|
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
|
output->Inflate(core->default_width, core->default_height, core->transform_hint, static_cast<u32>(core->queue.size()));
|
||||||
callback_ticket = next_callback_ticket++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.graphic_buffer.reset();
|
||||||
item.slot = BufferItem::INVALID_BUFFER_SLOT;
|
item.slot = BufferItem::INVALID_BUFFER_SLOT;
|
||||||
|
|
||||||
// Call back without the main BufferQueue lock held, but with the callback lock held so we can
|
if (listener_available) {
|
||||||
// ensure that callbacks occur in order
|
listener_available->OnFrameAvailable(item);
|
||||||
{
|
} else if (listener_replaced) {
|
||||||
std::scoped_lock lock{callback_mutex};
|
listener_replaced->OnFrameReplaced(item);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Status::NoError;
|
return Status::NoError;
|
||||||
@@ -810,6 +763,10 @@ Status BufferQueueProducer::SetPreallocatedBuffer(s32 slot,
|
|||||||
return Status::NoError;
|
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,
|
void BufferQueueProducer::Transact(u32 code, std::span<const u8> parcel_data,
|
||||||
std::span<u8> parcel_reply, u32 flags) {
|
std::span<u8> parcel_reply, u32 flags) {
|
||||||
// Values used by BnGraphicBufferProducer onTransact
|
// Values used by BnGraphicBufferProducer onTransact
|
||||||
@@ -929,9 +886,42 @@ void BufferQueueProducer::Transact(u32 code, std::span<const u8> parcel_data,
|
|||||||
status = SetBufferCount(buffer_count);
|
status = SetBufferCount(buffer_count);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case TransactionId::GetBufferHistory:
|
case TransactionId::GetBufferHistory: {
|
||||||
LOG_DEBUG(Service_Nvnflinger, "(STUBBED) called, transaction=GetBufferHistory");
|
if (!Settings::values.enable_buffer_history.GetValue()) {
|
||||||
|
LOG_DEBUG(Service_Nvnflinger, "(STUBBED) called");
|
||||||
break;
|
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:
|
default:
|
||||||
ASSERT_MSG(false, "Unimplemented TransactionId {}", code);
|
ASSERT_MSG(false, "Unimplemented TransactionId {}", code);
|
||||||
break;
|
break;
|
||||||
@@ -944,8 +934,4 @@ void BufferQueueProducer::Transact(u32 code, std::span<const u8> parcel_data,
|
|||||||
(std::min)(parcel_reply.size(), serialized.size()));
|
(std::min)(parcel_reply.size(), serialized.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Kernel::KReadableEvent* BufferQueueProducer::GetNativeHandle(u32 type_id) {
|
|
||||||
return &buffer_wait_event->GetReadableEvent();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Service::android
|
} // 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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
#include "common/common_funcs.h"
|
#include "common/common_funcs.h"
|
||||||
|
#include "common/wall_clock.h"
|
||||||
#include "core/hle/service/nvdrv/nvdata.h"
|
#include "core/hle/service/nvdrv/nvdata.h"
|
||||||
#include "core/hle/service/nvnflinger/binder.h"
|
#include "core/hle/service/nvnflinger/binder.h"
|
||||||
#include "core/hle/service/nvnflinger/buffer_queue_defs.h"
|
#include "core/hle/service/nvnflinger/buffer_queue_defs.h"
|
||||||
@@ -88,6 +89,7 @@ private:
|
|||||||
s32 next_callback_ticket{};
|
s32 next_callback_ticket{};
|
||||||
s32 current_callback_ticket{};
|
s32 current_callback_ticket{};
|
||||||
std::condition_variable_any callback_condition;
|
std::condition_variable_any callback_condition;
|
||||||
|
std::unique_ptr<Common::WallClock> clock;
|
||||||
|
|
||||||
Service::Nvidia::NvCore::NvMap& nvmap;
|
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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
@@ -37,6 +37,7 @@ struct BufferSlot final {
|
|||||||
bool needs_cleanup_on_release{};
|
bool needs_cleanup_on_release{};
|
||||||
bool attached_by_consumer{};
|
bool attached_by_consumer{};
|
||||||
bool is_preallocated{};
|
bool is_preallocated{};
|
||||||
|
s64 queue_time{}, presentation_time{};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Service::android
|
} // namespace Service::android
|
||||||
|
|||||||
@@ -329,6 +329,10 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
|
|||||||
barrier_feedback_loops,
|
barrier_feedback_loops,
|
||||||
tr("Barrier feedback loops"),
|
tr("Barrier feedback loops"),
|
||||||
tr("Improves rendering of transparency effects in specific games."));
|
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,
|
INSERT(Settings,
|
||||||
fix_bloom_effects,
|
fix_bloom_effects,
|
||||||
tr("Fix bloom effects"),
|
tr("Fix bloom effects"),
|
||||||
|
|||||||
Reference in New Issue
Block a user