diff --git a/src/video_core/dma_pusher.cpp b/src/video_core/dma_pusher.cpp index 3844a8e2f9..a34696bf75 100644 --- a/src/video_core/dma_pusher.cpp +++ b/src/video_core/dma_pusher.cpp @@ -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::GpuGuestMemoryheaders(memory_manager, dma_state.dma_get, header.size, &command_headers); ProcessCommands(headers); } else { diff --git a/src/video_core/engines/draw_guard.h b/src/video_core/engines/draw_guard.h new file mode 100644 index 0000000000..c071885353 --- /dev/null +++ b/src/video_core/engines/draw_guard.h @@ -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 +#include + +#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(draw_state.index_buffer.count) + : static_cast(draw_state.vertex_buffer.count); + const u64 first = draw_indexed ? static_cast(draw_state.index_buffer.first) + : static_cast(draw_state.vertex_buffer.first); + const u64 base_index = static_cast(draw_state.base_index); + const u64 base_instance = static_cast(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::max() - count); + index_end = index_end_overflow ? std::numeric_limits::max() : (first + count); + + const u64 format_size = static_cast(draw_state.index_buffer.FormatSizeInBytes()); + span_overflow = format_size != 0 && + index_end > (std::numeric_limits::max() / format_size); + span_bytes = span_overflow ? std::numeric_limits::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(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::max() / stride); + const size_t draw_tail = + draw_tail_overflow ? std::numeric_limits::max() + : ((max_draw_count - 1) * stride); + const bool needed_overflow = + draw_tail_overflow || + draw_tail > (std::numeric_limits::max() - command_size); + const size_t needed_size = + needed_overflow ? std::numeric_limits::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(max_draw_count) ^ + (static_cast(buffer_size) << 9) ^ + (static_cast(stride) << 17); + + const auto should_emit_draw_guard_log = [](u64 sig) { + static std::atomic 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 \ No newline at end of file diff --git a/src/video_core/engines/draw_manager.cpp b/src/video_core/engines/draw_manager.cpp index 971025cb55..8ca7fab1a8 100644 --- a/src/video_core/engines/draw_manager.cpp +++ b/src/video_core/engines/draw_manager.cpp @@ -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();