mirror of
https://git.eden-emu.dev/eden-emu/eden
synced 2026-10-31 01:04:49 +01:00
[dma_pusher,draw_manager] segment dirty (0x6D) and bogus draws (0xE3B) guards
This commit is contained in:
parent
27cadb24f3
commit
db0bff68ac
3 changed files with 202 additions and 6 deletions
|
|
@ -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
|
||||
|
|
@ -78,12 +78,19 @@ bool DmaPusher::Step() {
|
|||
synced = false;
|
||||
}
|
||||
|
||||
if (header.size > 0 && dma_state.method >= MacroRegistersStart && subchannels[dma_state.subchannel]) {
|
||||
subchannels[dma_state.subchannel]->current_dirty = memory_manager.IsMemoryDirty(dma_state.dma_get, header.size * sizeof(u32));
|
||||
}
|
||||
|
||||
if (header.size > 0) {
|
||||
if (Settings::IsDMALevelDefault() ? (Settings::IsGPULevelMedium() || Settings::IsGPULevelHigh()) : Settings::IsDMALevelSafe()) {
|
||||
|
||||
if (dma_state.method >= MacroRegistersStart && subchannels[dma_state.subchannel]) {
|
||||
subchannels[dma_state.subchannel]->current_dirty = memory_manager.IsMemoryDirty(dma_state.dma_get, header.size * sizeof(u32));
|
||||
}
|
||||
|
||||
const bool safe_by_settings =
|
||||
Settings::IsDMALevelDefault() ? !Settings::IsGPULevelLow() : Settings::IsDMALevelSafe();
|
||||
|
||||
const bool segment_dirty = !safe_by_settings &&
|
||||
memory_manager.IsMemoryDirty(dma_state.dma_get, sizeof(u32), VideoCommon::CacheType::BufferCache);
|
||||
|
||||
if (safe_by_settings || segment_dirty) {
|
||||
Tegra::Memory::GpuGuestMemory<Tegra::CommandHeader, Tegra::Memory::GuestMemoryFlags::SafeRead>headers(memory_manager, dma_state.dma_get, header.size, &command_headers);
|
||||
ProcessCommands(headers);
|
||||
} else {
|
||||
|
|
|
|||
183
src/video_core/engines/draw_guard.h
Normal file
183
src/video_core/engines/draw_guard.h
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/*
|
||||
Discards invalid segments to avoid huge buffer bombs,
|
||||
caused in UE games (like ender magnolia)
|
||||
|
||||
Usage:
|
||||
#include "video_core/engines/draw_guard.h"
|
||||
|
||||
//in DrawManager::ProcessDraw (after UpdateTopology)
|
||||
if (ShouldDiscardCorruptedDraw(draw_state, nullptr, draw_indexed, instance_count)) {
|
||||
return;
|
||||
}
|
||||
|
||||
//in DrawManager::ProcessDrawIndirect (after UpdateTopology)
|
||||
if (ShouldDiscardCorruptedDraw(draw_state, &indirect_state, indirect_state.is_indexed, 1)) {
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <limits>
|
||||
|
||||
#include "common/logging.h"
|
||||
#include "video_core/engines/draw_manager.h"
|
||||
|
||||
namespace Tegra::Engines {
|
||||
|
||||
[[nodiscard]] inline bool ShouldDiscardCorruptedDraw(const DrawManager::State& draw_state,
|
||||
const DrawManager::IndirectParams* indirect_state,
|
||||
bool draw_indexed, u32 instance_count) {
|
||||
constexpr u32 draw_count_limit = 1u << 19;
|
||||
constexpr u32 instance_count_limit = 1u << 17;
|
||||
constexpr u64 draw_span_limit_bytes = 1u << 27;
|
||||
constexpr size_t indirect_draw_count_limit = 1u << 18;
|
||||
constexpr size_t indirect_buffer_limit = 1u << 24;
|
||||
constexpr u32 draw_guard_log_limit = 4096;
|
||||
|
||||
const u64 count = draw_indexed ? static_cast<u64>(draw_state.index_buffer.count)
|
||||
: static_cast<u64>(draw_state.vertex_buffer.count);
|
||||
const u64 first = draw_indexed ? static_cast<u64>(draw_state.index_buffer.first)
|
||||
: static_cast<u64>(draw_state.vertex_buffer.first);
|
||||
const u64 base_index = static_cast<u64>(draw_state.base_index);
|
||||
const u64 base_instance = static_cast<u64>(draw_state.base_instance);
|
||||
|
||||
bool index_end_overflow = false;
|
||||
bool span_overflow = false;
|
||||
bool bounds_invalid = false;
|
||||
bool span_exceeds_available = false;
|
||||
|
||||
u64 span_bytes = 0;
|
||||
u64 available = 0;
|
||||
u64 index_end = first;
|
||||
|
||||
if (draw_indexed) {
|
||||
index_end_overflow = first > (std::numeric_limits<u64>::max() - count);
|
||||
index_end = index_end_overflow ? std::numeric_limits<u64>::max() : (first + count);
|
||||
|
||||
const u64 format_size = static_cast<u64>(draw_state.index_buffer.FormatSizeInBytes());
|
||||
span_overflow = format_size != 0 &&
|
||||
index_end > (std::numeric_limits<u64>::max() / format_size);
|
||||
span_bytes = span_overflow ? std::numeric_limits<u64>::max() : (index_end * format_size);
|
||||
|
||||
const GPUVAddr start = draw_state.index_buffer.StartAddress();
|
||||
const GPUVAddr end = draw_state.index_buffer.EndAddress();
|
||||
bounds_invalid = end < start;
|
||||
if (!bounds_invalid) {
|
||||
available = (end - start) + 1;
|
||||
span_exceeds_available = span_bytes > available;
|
||||
}
|
||||
}
|
||||
|
||||
const bool validate_count = (indirect_state == nullptr) || draw_indexed;
|
||||
const bool count_exceeded = validate_count && count > draw_count_limit;
|
||||
const bool instance_exceeded =
|
||||
(indirect_state == nullptr) && (static_cast<u64>(instance_count) > instance_count_limit);
|
||||
const bool span_exceeded = draw_indexed && span_bytes > draw_span_limit_bytes;
|
||||
|
||||
size_t max_draw_count = 0;
|
||||
size_t buffer_size = 0;
|
||||
size_t stride = 0;
|
||||
bool indirect_count_exceeded = false;
|
||||
bool indirect_buffer_exceeded = false;
|
||||
bool indirect_stride_exceeded = false;
|
||||
bool indirect_shape_invalid = false;
|
||||
|
||||
if (indirect_state != nullptr) {
|
||||
max_draw_count = indirect_state->max_draw_counts;
|
||||
buffer_size = indirect_state->buffer_size;
|
||||
stride = indirect_state->stride;
|
||||
|
||||
indirect_count_exceeded = max_draw_count > indirect_draw_count_limit;
|
||||
indirect_buffer_exceeded = buffer_size > indirect_buffer_limit;
|
||||
indirect_stride_exceeded = stride > draw_span_limit_bytes;
|
||||
|
||||
if (!indirect_state->is_byte_count && max_draw_count > 1) {
|
||||
if (stride == 0) {
|
||||
indirect_shape_invalid = true;
|
||||
} else {
|
||||
const size_t command_size =
|
||||
indirect_state->is_indexed ? (5 * sizeof(u32)) : (4 * sizeof(u32));
|
||||
const bool draw_tail_overflow =
|
||||
(max_draw_count - 1) > (std::numeric_limits<size_t>::max() / stride);
|
||||
const size_t draw_tail =
|
||||
draw_tail_overflow ? std::numeric_limits<size_t>::max()
|
||||
: ((max_draw_count - 1) * stride);
|
||||
const bool needed_overflow =
|
||||
draw_tail_overflow ||
|
||||
draw_tail > (std::numeric_limits<size_t>::max() - command_size);
|
||||
const size_t needed_size =
|
||||
needed_overflow ? std::numeric_limits<size_t>::max()
|
||||
: (draw_tail + command_size);
|
||||
indirect_shape_invalid = needed_overflow || needed_size > buffer_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bool discard = count_exceeded || instance_exceeded || span_exceeded ||
|
||||
index_end_overflow || span_overflow || bounds_invalid ||
|
||||
span_exceeds_available || indirect_count_exceeded ||
|
||||
indirect_buffer_exceeded || indirect_stride_exceeded ||
|
||||
indirect_shape_invalid;
|
||||
if (!discard) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const u64 signature = (draw_indexed ? (1ull << 63) : 0ull) ^
|
||||
(indirect_state != nullptr ? (1ull << 62) : 0ull) ^
|
||||
(count << 32) ^ first ^ (base_index << 7) ^ (base_instance << 13) ^
|
||||
(span_bytes << 1) ^ static_cast<u64>(max_draw_count) ^
|
||||
(static_cast<u64>(buffer_size) << 9) ^
|
||||
(static_cast<u64>(stride) << 17);
|
||||
|
||||
const auto should_emit_draw_guard_log = [](u64 sig) {
|
||||
static std::atomic<u32> s_log_count{0};
|
||||
static u64 s_last_signature = 0;
|
||||
static u32 s_repeat_count = 0;
|
||||
|
||||
bool emit = false;
|
||||
if (sig != s_last_signature) {
|
||||
s_last_signature = sig;
|
||||
s_repeat_count = 0;
|
||||
emit = true;
|
||||
} else {
|
||||
++s_repeat_count;
|
||||
// Exponential backoff: 1, 2, 4, 8, ...
|
||||
emit = (s_repeat_count & (s_repeat_count - 1)) == 0;
|
||||
}
|
||||
|
||||
if (!emit) {
|
||||
return false;
|
||||
}
|
||||
return s_log_count.fetch_add(1, std::memory_order_relaxed) < draw_guard_log_limit;
|
||||
};
|
||||
|
||||
if (should_emit_draw_guard_log(signature)) {
|
||||
LOG_WARNING(
|
||||
HW_GPU,
|
||||
"DrawManager: blocked {} draw path={} count={} limit={} first={:#x} "
|
||||
"base_index={:#x} base_instance={:#x} span_bytes={} available={} "
|
||||
"flags(count_exceeded={} instance_exceeded={} span_exceeded={} "
|
||||
"index_end_overflow={} span_overflow={} bounds_invalid={} span_exceeds_available={} "
|
||||
"indirect_count_exceeded={} indirect_buffer_exceeded={} "
|
||||
"indirect_stride_exceeded={} indirect_shape_invalid={}) "
|
||||
"offset_limit={} max_draw_count={} buffer_size={} "
|
||||
"indirect_limits(count={} buffer={})",
|
||||
draw_indexed ? "indexed" : "vertex",
|
||||
indirect_state != nullptr ? "indirect" : "direct",
|
||||
count, draw_count_limit, first, base_index, base_instance, span_bytes, available,
|
||||
count_exceeded ? 1 : 0, instance_exceeded ? 1 : 0, span_exceeded ? 1 : 0,
|
||||
index_end_overflow ? 1 : 0, span_overflow ? 1 : 0, bounds_invalid ? 1 : 0,
|
||||
span_exceeds_available ? 1 : 0, indirect_count_exceeded ? 1 : 0,
|
||||
indirect_buffer_exceeded ? 1 : 0, indirect_stride_exceeded ? 1 : 0,
|
||||
indirect_shape_invalid ? 1 : 0, draw_span_limit_bytes, max_draw_count, buffer_size,
|
||||
indirect_draw_count_limit, indirect_buffer_limit);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Tegra::Engines
|
||||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
|
@ -5,6 +8,7 @@
|
|||
#include "video_core/dirty_flags.h"
|
||||
#include "video_core/engines/draw_manager.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
#include "video_core/engines/draw_guard.h"
|
||||
|
||||
namespace Tegra::Engines {
|
||||
DrawManager::DrawManager(Maxwell3D* maxwell3d_) : maxwell3d(maxwell3d_) {}
|
||||
|
|
@ -265,6 +269,7 @@ void DrawManager::ProcessDraw(bool draw_indexed, u32 instance_count) {
|
|||
draw_indexed ? draw_state.index_buffer.count : draw_state.vertex_buffer.count);
|
||||
|
||||
UpdateTopology();
|
||||
if (ShouldDiscardCorruptedDraw(draw_state, nullptr, draw_indexed, instance_count)) return;
|
||||
|
||||
if (maxwell3d->ShouldExecute()) {
|
||||
maxwell3d->rasterizer->Draw(draw_indexed, instance_count);
|
||||
|
|
@ -279,6 +284,7 @@ void DrawManager::ProcessDrawIndirect() {
|
|||
indirect_state.buffer_size, indirect_state.max_draw_counts);
|
||||
|
||||
UpdateTopology();
|
||||
if (ShouldDiscardCorruptedDraw(draw_state, &indirect_state, indirect_state.is_indexed, 1)) return;
|
||||
|
||||
if (maxwell3d->ShouldExecute()) {
|
||||
maxwell3d->rasterizer->DrawIndirect();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue