From 91a662431cfe419bd46b6ca4ed298a4ba70d7820 Mon Sep 17 00:00:00 2001 From: JPikachu Date: Sun, 29 Jun 2025 17:14:23 +0000 Subject: [PATCH] [Texture_cache] Better memory handling for devices with lower memory allocations (#233) Means games like Minecraft Dungeons, Sea of Stars, Luigi Mansion 2, Astroneer, Alan Wake, etc are now playable. It also cleans up the recent abi.cpp and bindless texture commits a bit. Everything is in #ifdef ANDROID - The biggest change is CACHING_PAGEBITS = 12. Without that the way the buffercache grows and joins buffers can cause Android to run out of memory (as you end up with just one big buffer that needs to be copied every time it grows) Also patches up ffmpeg issues. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/233 Co-authored-by: JPikachu Co-committed-by: JPikachu --- .../dynarmic/src/dynarmic/backend/x64/abi.cpp | 25 +++---------------- src/core/arm/nce/arm_nce.cpp | 2 +- src/core/hle/service/sockets/sfdnsres.cpp | 14 +++++++++-- .../maxwell/translate/impl/warp_shuffle.cpp | 17 ++++++++++++- src/shader_recompiler/ir_opt/texture_pass.cpp | 20 ++------------- src/video_core/buffer_cache/buffer_cache.h | 10 +++++++- .../buffer_cache/buffer_cache_base.h | 17 ++++++++++++- src/video_core/cdma_pusher.cpp | 3 +++ src/video_core/delayed_destruction_ring.h | 1 - src/video_core/host1x/ffmpeg/ffmpeg.cpp | 19 +++++++++----- src/video_core/host1x/ffmpeg/ffmpeg.h | 1 + .../renderer_vulkan/present/layer.cpp | 1 + src/video_core/texture_cache/texture_cache.h | 3 +++ .../texture_cache/texture_cache_base.h | 11 ++++++++ .../vulkan_common/vulkan_memory_allocator.cpp | 5 +++- 15 files changed, 96 insertions(+), 53 deletions(-) diff --git a/externals/dynarmic/src/dynarmic/backend/x64/abi.cpp b/externals/dynarmic/src/dynarmic/backend/x64/abi.cpp index 7cbe92aaf3..c97d98d9ae 100644 --- a/externals/dynarmic/src/dynarmic/backend/x64/abi.cpp +++ b/externals/dynarmic/src/dynarmic/backend/x64/abi.cpp @@ -116,18 +116,13 @@ void ABI_PopCallerSaveRegistersAndAdjustStack(BlockOfCode& code, const std::size ABI_PopRegistersAndAdjustStack(code, frame_size, ABI_ALL_CALLER_SAVE); } -static consteval size_t ABI_AllCallerSaveSize() noexcept { - return ABI_ALL_CALLER_SAVE.max_size(); -} -static consteval std::array ABI_AllCallerSaveExcept(const std::size_t except) noexcept { - std::array arr; - for(std::size_t i = 0; i < arr.size(); ++i) { - arr[i] = static_cast(i + (i >= except ? 1 : 0)); - } +static std::vector ABI_AllCallerSaveExcept(const std::size_t except) noexcept { + std::vector arr; + std::remove_copy(ABI_ALL_CALLER_SAVE.begin(), ABI_ALL_CALLER_SAVE.end(), std::back_inserter(arr), static_cast(except)); return arr; } -alignas(64) static constinit std::array ABI_CALLER_SAVED_EXCEPT_TABLE[32] = { +alignas(64) static std::vector ABI_CALLER_SAVED_EXCEPT_TABLE[32] = { ABI_AllCallerSaveExcept(0), ABI_AllCallerSaveExcept(1), ABI_AllCallerSaveExcept(2), @@ -163,24 +158,12 @@ alignas(64) static constinit std::array AB }; void ABI_PushCallerSaveRegistersAndAdjustStackExcept(BlockOfCode& code, const HostLoc exception) { -#ifdef _MSC_VER - std::vector regs; - std::remove_copy(ABI_ALL_CALLER_SAVE.begin(), ABI_ALL_CALLER_SAVE.end(), std::back_inserter(regs), exception); - ABI_PushRegistersAndAdjustStack(code, 0, regs); -#else ASSUME(size_t(exception) < 32); ABI_PushRegistersAndAdjustStack(code, 0, ABI_CALLER_SAVED_EXCEPT_TABLE[size_t(exception)]); -#endif } void ABI_PopCallerSaveRegistersAndAdjustStackExcept(BlockOfCode& code, const HostLoc exception) { -#ifdef _MSC_VER - std::vector regs; - std::remove_copy(ABI_ALL_CALLER_SAVE.begin(), ABI_ALL_CALLER_SAVE.end(), std::back_inserter(regs), exception); - ABI_PopRegistersAndAdjustStack(code, 0, regs); -#else ASSUME(size_t(exception) < 32); ABI_PopRegistersAndAdjustStack(code, 0, ABI_CALLER_SAVED_EXCEPT_TABLE[size_t(exception)]); -#endif } } // namespace Dynarmic::Backend::X64 diff --git a/src/core/arm/nce/arm_nce.cpp b/src/core/arm/nce/arm_nce.cpp index 90891e241d..e63cf82cc5 100644 --- a/src/core/arm/nce/arm_nce.cpp +++ b/src/core/arm/nce/arm_nce.cpp @@ -224,7 +224,7 @@ HaltReason ArmNce::RunThread(Kernel::KThread* thread) { if (auto it = post_handlers.find(m_guest_ctx.pc); it != post_handlers.end()) { hr = ReturnToRunCodeByTrampoline(thread_params, &m_guest_ctx, it->second); } else { - hr = ReturnToRunCodeByExceptionLevelChange(m_thread_id, thread_params); + hr = ReturnToRunCodeByExceptionLevelChange(m_thread_id, thread_params); // Android: Use "process handle SIGUSR2 -n true -p true -s false" (and SIGURG) in LLDB when debugging } // Critical section for thread cleanup diff --git a/src/core/hle/service/sockets/sfdnsres.cpp b/src/core/hle/service/sockets/sfdnsres.cpp index c657c4efd8..034a4b013c 100644 --- a/src/core/hle/service/sockets/sfdnsres.cpp +++ b/src/core/hle/service/sockets/sfdnsres.cpp @@ -151,7 +151,12 @@ static std::pair GetHostByNameRequestImpl(HLERequestConte // For now, ignore options, which are in input buffer 1 for GetHostByNameRequestWithOptions. // Prevent resolution of Nintendo servers - if (host.find("srv.nintendo.net") != std::string::npos) { + if (host.find("srv.nintendo.net") != std::string::npos || + host.find("battle.net") != std::string::npos || + host.find("microsoft.com") != std::string::npos || + host.find("mojang.com") != std::string::npos || + host.find("xboxlive.com") != std::string::npos || + host.find("minecraftservices.com") != std::string::npos) { LOG_WARNING(Network, "Resolution of hostname {} requested, returning EAI_AGAIN", host); return {0, GetAddrInfoError::AGAIN}; } @@ -268,7 +273,12 @@ static std::pair GetAddrInfoRequestImpl(HLERequestContext const std::string host = Common::StringFromBuffer(host_buffer); // Prevent resolution of Nintendo servers - if (host.find("srv.nintendo.net") != std::string::npos) { + if (host.find("srv.nintendo.net") != std::string::npos || + host.find("battle.net") != std::string::npos || + host.find("microsoft.com") != std::string::npos || + host.find("mojang.com") != std::string::npos || + host.find("xboxlive.com") != std::string::npos || + host.find("minecraftservices.com") != std::string::npos) { LOG_WARNING(Network, "Resolution of hostname {} requested, returning EAI_AGAIN", host); return {0, GetAddrInfoError::AGAIN}; } diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/warp_shuffle.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/warp_shuffle.cpp index f0436994b2..da2e31c012 100644 --- a/src/shader_recompiler/frontend/maxwell/translate/impl/warp_shuffle.cpp +++ b/src/shader_recompiler/frontend/maxwell/translate/impl/warp_shuffle.cpp @@ -4,6 +4,7 @@ #include "common/bit_field.h" #include "common/common_types.h" #include "shader_recompiler/frontend/maxwell/translate/impl/impl.h" +#include namespace Shader::Maxwell { namespace { @@ -33,6 +34,17 @@ enum class ShuffleMode : u64 { } } +bool IsKONA() { + std::ifstream machineFile("/sys/devices/soc0/machine"); + if (machineFile.is_open()) { + std::string line; + std::getline(machineFile, line); + if (line == "KONA") + return true; + } + return false; +} + void Shuffle(TranslatorVisitor& v, u64 insn, const IR::U32& index, const IR::U32& mask) { union { u64 insn; @@ -44,7 +56,10 @@ void Shuffle(TranslatorVisitor& v, u64 insn, const IR::U32& index, const IR::U32 const IR::U32 result{ShuffleOperation(v.ir, v.X(shfl.src_reg), index, mask, shfl.mode)}; v.ir.SetPred(shfl.pred, v.ir.GetInBoundsFromOp(result)); - v.X(shfl.dest_reg, result); + if (IsKONA()) + v.X(shfl.dest_reg, v.ir.Imm32(0xffffffff)); // This fixes the freeze for Retroid / Snapdragon SD865 + else + v.X(shfl.dest_reg, result); } } // Anonymous namespace diff --git a/src/shader_recompiler/ir_opt/texture_pass.cpp b/src/shader_recompiler/ir_opt/texture_pass.cpp index ef1ef63500..44ffa2b578 100644 --- a/src/shader_recompiler/ir_opt/texture_pass.cpp +++ b/src/shader_recompiler/ir_opt/texture_pass.cpp @@ -327,30 +327,14 @@ std::optional TryGetConstBuffer(const IR::Inst* inst, Environme }; } -// TODO:xbzk: shall be dropped when Track method cover all bindless stuff -static ConstBufferAddr last_valid_addr = ConstBufferAddr{ - .index = 0, - .offset = 0, - .shift_left = 0, - .secondary_index = 0, - .secondary_offset = 0, - .secondary_shift_left = 0, - .dynamic_offset = {}, - .count = 1, - .has_secondary = false, -}; - TextureInst MakeInst(Environment& env, IR::Block* block, IR::Inst& inst) { - ConstBufferAddr addr; + ConstBufferAddr addr{}; if (IsBindless(inst)) { const std::optional track_addr{Track(inst.Arg(0), env)}; - if (!track_addr) { - //throw NotImplementedException("Failed to track bindless texture constant buffer"); - addr = last_valid_addr; // TODO:xbzk: shall be dropped when Track method cover all bindless stuff + LOG_WARNING(Shader, "Failed to track bindless texture constant buffer"); } else { addr = *track_addr; - last_valid_addr = addr; // TODO:xbzk: shall be dropped when Track method cover all bindless stuff } } else { addr = ConstBufferAddr{ diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index af237703d5..c7e287553e 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -50,7 +50,7 @@ BufferCache

::~BufferCache() = default; template void BufferCache

::RunGarbageCollector() { const bool aggressive_gc = total_used_memory >= critical_memory; - const u64 ticks_to_destroy = aggressive_gc ? 60 : 120; + const u64 ticks_to_destroy = aggressive_gc ? 60 : 150; int num_iterations = aggressive_gc ? 64 : 32; const auto clean_up = [this, &num_iterations](BufferId buffer_id) { if (num_iterations == 0) { @@ -1380,6 +1380,9 @@ void BufferCache

::JoinOverlap(BufferId new_buffer_id, BufferId overlap_id, }); new_buffer.MarkUsage(copies[0].dst_offset, copies[0].size); runtime.CopyBuffer(new_buffer, overlap, copies, true); +#ifdef ANDROID + runtime.Finish(); +#endif DeleteBuffer(overlap_id, true); } @@ -1671,7 +1674,12 @@ void BufferCache

::DeleteBuffer(BufferId buffer_id, bool do_not_mark) { } Unregister(buffer_id); + +#ifdef ANDROID + if (!do_not_mark) +#endif delayed_destruction_ring.Push(std::move(slot_buffers[buffer_id])); + slot_buffers.erase(buffer_id); if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) { diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h index 240e9f0150..3a40bbad19 100644 --- a/src/video_core/buffer_cache/buffer_cache_base.h +++ b/src/video_core/buffer_cache/buffer_cache_base.h @@ -156,7 +156,11 @@ template class BufferCache : public VideoCommon::ChannelSetupCaches { // Page size for caching purposes. // This is unrelated to the CPU page size and it can be changed as it seems optimal. +#ifdef ANDROID + static constexpr u32 CACHING_PAGEBITS = 12; +#else static constexpr u32 CACHING_PAGEBITS = 16; +#endif static constexpr u64 CACHING_PAGESIZE = u64{1} << CACHING_PAGEBITS; static constexpr bool IS_OPENGL = P::IS_OPENGL; @@ -170,9 +174,15 @@ class BufferCache : public VideoCommon::ChannelSetupCaches slot_buffers; - DelayedDestructionRing delayed_destruction_ring; +#ifdef ANDROID + static constexpr size_t TICKS_TO_DESTROY = 2; +#else + static constexpr size_t TICKS_TO_DESTROY = 8; +#endif + DelayedDestructionRing delayed_destruction_ring; const Tegra::Engines::DrawManager::IndirectParams* current_draw_indirect{}; diff --git a/src/video_core/cdma_pusher.cpp b/src/video_core/cdma_pusher.cpp index 3bcf1b0664..aa501ae51b 100644 --- a/src/video_core/cdma_pusher.cpp +++ b/src/video_core/cdma_pusher.cpp @@ -20,6 +20,9 @@ CDmaPusher::CDmaPusher(Host1x::Host1x& host1x_, s32 id) : host1x{host1x_}, memory_manager{host1x.GMMU()}, host_processor{std::make_unique(host1x_)}, current_class{ static_cast(id)} { +#ifdef ANDROID + std::this_thread::sleep_for(std::chrono::milliseconds{500}); // HACK: Fix for Astroneer - doesn't always start without this delay. Happens on Windows too (but rarer) +#endif thread = std::jthread([this](std::stop_token stop_token) { ProcessEntries(stop_token); }); } diff --git a/src/video_core/delayed_destruction_ring.h b/src/video_core/delayed_destruction_ring.h index d13ee622b2..49d7842f8e 100644 --- a/src/video_core/delayed_destruction_ring.h +++ b/src/video_core/delayed_destruction_ring.h @@ -22,7 +22,6 @@ public: void Push(T&& object) { elements[index].push_back(std::move(object)); } - private: size_t index = 0; std::array, TICKS_TO_DESTROY> elements; diff --git a/src/video_core/host1x/ffmpeg/ffmpeg.cpp b/src/video_core/host1x/ffmpeg/ffmpeg.cpp index ffde231aee..4bca9ab7b1 100644 --- a/src/video_core/host1x/ffmpeg/ffmpeg.cpp +++ b/src/video_core/host1x/ffmpeg/ffmpeg.cpp @@ -237,16 +237,23 @@ std::shared_ptr DecoderContext::ReceiveFrame() { if (m_codec_context->hw_device_ctx) { // If we have a hardware context, make a separate frame here to receive the // hardware result before sending it to the output. - Frame intermediate_frame; + std::shared_ptr intermediate_frame = std::make_shared(); - if (!receive(intermediate_frame.GetFrame())) { + if (!receive(intermediate_frame->GetFrame())) { return {}; } - m_temp_frame->SetFormat(PreferredGpuFormat); - if (int ret = av_hwframe_transfer_data(m_temp_frame->GetFrame(), intermediate_frame.GetFrame(), 0); ret < 0) { - LOG_ERROR(HW_GPU, "av_hwframe_transfer_data error: {}", AVError(ret)); - return {}; + const auto fmt = intermediate_frame->GetPixelFormat(); + const auto desc = av_pix_fmt_desc_get(fmt); + if (desc && (desc->flags & AV_PIX_FMT_FLAG_HWACCEL)) { + m_temp_frame->SetFormat(PreferredGpuFormat); + if (int ret = av_hwframe_transfer_data(m_temp_frame->GetFrame(), intermediate_frame->GetFrame(), 0); ret < 0) { + LOG_ERROR(HW_GPU, "av_hwframe_transfer_data error: {}", AVError(ret)); + return {}; + } + } + else { + m_temp_frame = std::move(intermediate_frame); } } else { // Otherwise, decode the frame as normal. diff --git a/src/video_core/host1x/ffmpeg/ffmpeg.h b/src/video_core/host1x/ffmpeg/ffmpeg.h index 28f1742b7e..9fe0b1532a 100644 --- a/src/video_core/host1x/ffmpeg/ffmpeg.h +++ b/src/video_core/host1x/ffmpeg/ffmpeg.h @@ -21,6 +21,7 @@ extern "C" { #include #include +#include #include #if defined(__GNUC__) || defined(__clang__) diff --git a/src/video_core/renderer_vulkan/present/layer.cpp b/src/video_core/renderer_vulkan/present/layer.cpp index 4e41afe5b4..984466f687 100644 --- a/src/video_core/renderer_vulkan/present/layer.cpp +++ b/src/video_core/renderer_vulkan/present/layer.cpp @@ -137,6 +137,7 @@ void Layer::CreateStagingBuffer(const Tegra::FramebufferConfig& framebuffer) { .pQueueFamilyIndices = nullptr, }; + buffer.reset(); buffer = memory_allocator.CreateBuffer(ci, MemoryUsage::Upload); } diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index feb1c575eb..3178866128 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -78,6 +78,9 @@ void TextureCache

::RunGarbageCollector() { size_t num_iterations = 0; const auto Configure = [&](bool allow_aggressive) { +#ifdef ANDROID + high_priority_mode = true; +#endif high_priority_mode = total_used_memory >= expected_memory; aggressive_mode = allow_aggressive && total_used_memory >= critical_memory; ticks_to_destroy = aggressive_mode ? 10ULL : high_priority_mode ? 25ULL : 50ULL; diff --git a/src/video_core/texture_cache/texture_cache_base.h b/src/video_core/texture_cache/texture_cache_base.h index da98a634b5..ba38182169 100644 --- a/src/video_core/texture_cache/texture_cache_base.h +++ b/src/video_core/texture_cache/texture_cache_base.h @@ -107,10 +107,17 @@ class TextureCache : public VideoCommon::ChannelSetupCaches::max()}; + #ifdef ANDROID + static constexpr s64 TARGET_THRESHOLD = 3_GiB; + static constexpr s64 DEFAULT_EXPECTED_MEMORY = 1_GiB + 125_MiB; + static constexpr s64 DEFAULT_CRITICAL_MEMORY = 1_GiB + 625_MiB; + static constexpr size_t GC_EMERGENCY_COUNTS = 2; + #else static constexpr s64 TARGET_THRESHOLD = 4_GiB; static constexpr s64 DEFAULT_EXPECTED_MEMORY = 1_GiB + 125_MiB; static constexpr s64 DEFAULT_CRITICAL_MEMORY = 1_GiB + 625_MiB; static constexpr size_t GC_EMERGENCY_COUNTS = 2; + #endif using Runtime = typename P::Runtime; using Image = typename P::Image; @@ -476,7 +483,11 @@ private: }; Common::LeastRecentlyUsedCache lru_cache; + #ifdef ANDROID + static constexpr size_t TICKS_TO_DESTROY = 2; + #else static constexpr size_t TICKS_TO_DESTROY = 8; +#endif DelayedDestructionRing sentenced_images; DelayedDestructionRing sentenced_image_view; DelayedDestructionRing sentenced_framebuffers; diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp index 54331688e3..e35dea90d6 100644 --- a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp +++ b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp @@ -268,7 +268,10 @@ vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo& ci, MemoryUsa VmaAllocation allocation{}; VkMemoryPropertyFlags property_flags{}; - vk::Check(vmaCreateBuffer(allocator, &ci, &alloc_ci, &handle, &allocation, &alloc_info)); + VkResult result = vmaCreateBuffer(allocator, &ci, &alloc_ci, &handle, &allocation, &alloc_info); + if (result == VK_ERROR_OUT_OF_DEVICE_MEMORY) { + LOG_ERROR(Render_Vulkan, "Out of memory creating buffer (size: {})", ci.size); + } vmaGetAllocationMemoryProperties(allocator, allocation, &property_flags); u8* data = reinterpret_cast(alloc_info.pMappedData);