Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
751dd8546e | ||
|
|
67a54e7357 | ||
|
|
2215455617 | ||
|
|
bbb75f8dd4 | ||
|
|
b8f384f3bd | ||
|
|
4b209db82c | ||
|
|
c232624b8b | ||
|
|
a686b50333 | ||
|
|
1507074346 | ||
|
|
332d0b6bad | ||
|
|
e2984caedc | ||
|
|
50763528a2 | ||
|
|
4b3608b58d | ||
|
|
b76d04e47d | ||
|
|
71afaff6b3 | ||
|
|
dcbfa3c2d3 | ||
|
|
70ff43f2f8 | ||
|
|
76faac9252 | ||
|
|
6346371725 | ||
|
|
4162f86a11 | ||
|
|
86224e49ef | ||
|
|
6665a89dd2 | ||
|
|
515afa2959 | ||
|
|
8c8c7c6162 | ||
|
|
fb65aa3c33 | ||
|
|
a24ad03ff3 | ||
|
|
4884a0c002 | ||
|
|
ab1d99d218 | ||
|
|
e6ee576f80 | ||
|
|
1682b532da | ||
|
|
40fe050954 | ||
|
|
24ff23f2ea | ||
|
|
87ebbfb9b1 | ||
|
|
471778c850 | ||
|
|
095f27fd34 | ||
|
|
a6c423cafb | ||
|
|
514507b674 | ||
|
|
10935f8d46 | ||
|
|
a0dae2d490 | ||
|
|
5e40e3c9c0 | ||
|
|
f61d05f8da | ||
|
|
b04207fdfc | ||
|
|
91791bd5fe | ||
|
|
9928885a5d | ||
|
|
81fa91b400 | ||
|
|
eb8706e366 | ||
|
|
b9d55c9448 | ||
|
|
1188f285b7 | ||
|
|
200b81b087 | ||
|
|
742cadde1d | ||
|
|
c3effdf558 | ||
|
|
b9428acf12 | ||
|
|
b027dc42ce | ||
|
|
dc9c99b22d | ||
|
|
5346e77c0a | ||
|
|
914b78495c | ||
|
|
a17b4cbb04 | ||
|
|
a7b5910eaa | ||
|
|
f98f8b65de | ||
|
|
88fcb7d338 | ||
|
|
4a4fd056ff | ||
|
|
236696b0ff | ||
|
|
d1eb08c036 | ||
|
|
84a4149db2 | ||
|
|
de9b1071a5 | ||
|
|
85049c2c78 | ||
|
|
7fcf88cb10 | ||
|
|
e4f29899d2 | ||
|
|
41d2bf4a3e | ||
|
|
328a2c1081 | ||
|
|
6e48eeb09c | ||
|
|
2cdd7173a9 | ||
|
|
69342044ef | ||
|
|
1f41d809b0 | ||
|
|
250b545d75 | ||
|
|
5a9322af80 | ||
|
|
bbee229d07 | ||
|
|
cfe1c8b16c | ||
|
|
dd5d09e3ae | ||
|
|
25af655e48 | ||
|
|
0cd5dd1e70 | ||
|
|
8c828e78f2 | ||
|
|
b1adbd8e6b | ||
|
|
0fb0592a5f | ||
|
|
b59dda3af6 | ||
|
|
aec20586be | ||
|
|
6918f034e2 | ||
|
|
4b1e2d1d6a | ||
|
|
9318a508f7 | ||
|
|
f95a9f3901 | ||
|
|
1169533a74 | ||
|
|
991ff44bd6 | ||
|
|
624f955821 | ||
|
|
5c0fe5da06 | ||
|
|
18630fa739 | ||
|
|
9752908030 | ||
|
|
a711fd99a6 | ||
|
|
f6c1404e09 | ||
|
|
77ffefbbbf | ||
|
|
bf13e7ce1d | ||
|
|
3cd17ec392 | ||
|
|
7f4529b15d | ||
|
|
f649730cb1 | ||
|
|
40c91abdad | ||
|
|
d21bb9a1b7 | ||
|
|
94e814ecf1 | ||
|
|
b8135a3e47 | ||
|
|
8d7f992792 | ||
|
|
f199597158 | ||
|
|
9c313fb787 | ||
|
|
eec29b83f3 | ||
|
|
a57041d62f | ||
|
|
4956bc86c3 | ||
|
|
e8b1dc7c0b | ||
|
|
e81d170458 | ||
|
|
7ed7e5e31d | ||
|
|
eb32b8766a | ||
|
|
a484e6c34b | ||
|
|
84490a7d6f | ||
|
|
933f79af95 | ||
|
|
f532357793 | ||
|
|
ab92e5fa52 | ||
|
|
791880f9bf | ||
|
|
e058a15074 | ||
|
|
6313800aee | ||
|
|
5428dbbd14 | ||
|
|
9694216ad7 | ||
|
|
4fbdc133dd | ||
|
|
f210f16e8c | ||
|
|
20bf6bc282 | ||
|
|
99e95eebb1 |
116 changed files with 6398 additions and 1285 deletions
|
|
@ -65,7 +65,7 @@ android {
|
|||
|
||||
defaultConfig {
|
||||
applicationId = "dev.eden.eden_emulator"
|
||||
minSdk = 24
|
||||
minSdk = 33
|
||||
targetSdk = 36
|
||||
versionName = getGitVersion()
|
||||
versionCode = autoVersion
|
||||
|
|
|
|||
|
|
@ -218,6 +218,8 @@ object NativeLibrary {
|
|||
|
||||
external fun logSettings()
|
||||
|
||||
external fun refreshThreadPolicies()
|
||||
|
||||
external fun getDebugKnobAt(index: Int): Boolean
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
|||
RENDERER_ASYNCHRONOUS_GPU_EMULATION("use_asynchronous_gpu_emulation"),
|
||||
RENDERER_ASYNC_PRESENTATION("async_presentation"),
|
||||
RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders"),
|
||||
RENDERER_UNIFIED_MEMORY("use_unified_memory"),
|
||||
RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"),
|
||||
ENABLE_BUFFER_HISTORY("enable_buffer_history"),
|
||||
USE_OPTIMIZED_VERTEX_BUFFERS("use_optimized_vertex_buffers"),
|
||||
|
|
@ -36,6 +37,8 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
|||
RENDERER_DEBUG("debug"),
|
||||
RENDERER_PATCH_OLD_QCOM_DRIVERS("patch_old_qcom_drivers"),
|
||||
RENDERER_VERTEX_INPUT_DYNAMIC_STATE("vertex_input_dynamic_state"),
|
||||
RENDERER_DYNAMIC_RENDERING("dynamic_rendering"),
|
||||
RENDERER_WORKGROUP_MEMORY_EXPLICIT_LAYOUT("workgroup_memory_explicit_layout"),
|
||||
RENDERER_SAMPLE_SHADING("sample_shading"),
|
||||
GPU_UNSWIZZLE_ENABLED("gpu_unswizzle_enabled"),
|
||||
PICTURE_IN_PICTURE("picture_in_picture"),
|
||||
|
|
|
|||
|
|
@ -155,6 +155,20 @@ abstract class SettingsItem(
|
|||
descriptionId = R.string.vertex_input_dynamic_state_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.RENDERER_DYNAMIC_RENDERING,
|
||||
titleId = R.string.dynamic_rendering,
|
||||
descriptionId = R.string.dynamic_rendering_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.RENDERER_WORKGROUP_MEMORY_EXPLICIT_LAYOUT,
|
||||
titleId = R.string.workgroup_memory_explicit_layout,
|
||||
descriptionId = R.string.workgroup_memory_explicit_layout_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SliderSetting(
|
||||
IntSetting.RENDERER_SAMPLE_SHADING,
|
||||
|
|
@ -594,7 +608,7 @@ abstract class SettingsItem(
|
|||
IntSetting.ANDROID_PIPELINE_WORKERS,
|
||||
titleId = R.string.pipeline_worker_cores,
|
||||
descriptionId = R.string.pipeline_worker_cores_description,
|
||||
min = 4,
|
||||
min = 2,
|
||||
max = 8,
|
||||
units = "cores"
|
||||
)
|
||||
|
|
@ -685,6 +699,13 @@ abstract class SettingsItem(
|
|||
descriptionId = R.string.renderer_asynchronous_shaders_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.RENDERER_UNIFIED_MEMORY,
|
||||
titleId = R.string.renderer_unified_memory,
|
||||
descriptionId = R.string.renderer_unified_memory_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.FAST_GPU_TIME,
|
||||
|
|
|
|||
|
|
@ -304,6 +304,7 @@ class SettingsFragmentPresenter(
|
|||
add(BooleanSetting.EMULATE_BGR565.key)
|
||||
add(BooleanSetting.RESCALE_HACK.key)
|
||||
add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key)
|
||||
add(BooleanSetting.RENDERER_UNIFIED_MEMORY.key)
|
||||
add(IntSetting.ANDROID_PIPELINE_WORKERS.key)
|
||||
add(BooleanSetting.RENDERER_ASYNCHRONOUS_GPU_EMULATION.key)
|
||||
add(BooleanSetting.RENDERER_ASYNC_PRESENTATION.key)
|
||||
|
|
@ -313,6 +314,8 @@ class SettingsFragmentPresenter(
|
|||
|
||||
add(IntSetting.RENDERER_DYNA_STATE.key)
|
||||
add(BooleanSetting.RENDERER_VERTEX_INPUT_DYNAMIC_STATE.key)
|
||||
add(BooleanSetting.RENDERER_DYNAMIC_RENDERING.key)
|
||||
add(BooleanSetting.RENDERER_WORKGROUP_MEMORY_EXPLICIT_LAYOUT.key)
|
||||
add(IntSetting.RENDERER_SAMPLE_SHADING.key)
|
||||
|
||||
add(HeaderSetting(R.string.display))
|
||||
|
|
|
|||
|
|
@ -1451,6 +1451,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
NativeLibrary.refreshThreadPolicies()
|
||||
val b = _binding ?: return
|
||||
updateStatsPosition(IntSetting.PERF_OVERLAY_POSITION.getInt())
|
||||
updateSocPosition(IntSetting.SOC_OVERLAY_POSITION.getInt())
|
||||
|
|
|
|||
|
|
@ -147,13 +147,6 @@ namespace AndroidSettings {
|
|||
&show_performance_overlay};
|
||||
|
||||
|
||||
Settings::Setting<s32> pipeline_worker_count{linkage, 4, "pipeline_worker_count",
|
||||
Settings::Category::Android,
|
||||
Settings::Specialization::Default,
|
||||
true,
|
||||
true};
|
||||
|
||||
|
||||
Settings::Setting<bool> show_input_overlay{linkage, true, "show_input_overlay",
|
||||
Settings::Category::Overlay};
|
||||
Settings::Setting<bool> overlay_snap_to_grid{linkage, false, "overlay_snap_to_grid",
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ extern "C" {
|
|||
#include "common/scope_exit.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/string_util.h"
|
||||
#include "common/thread.h"
|
||||
#include "frontend_common/play_time_manager.h"
|
||||
#include "core/constants.h"
|
||||
#include "core/core.h"
|
||||
|
|
@ -1182,6 +1183,10 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_logSettings(JNIEnv* env, jobject jobj
|
|||
Settings::LogSettings();
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_refreshThreadPolicies(JNIEnv* env, jobject jobj) {
|
||||
Common::RefreshThreadPolicies();
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_getDebugKnobAt(JNIEnv* env, jobject jobj, jint index) {
|
||||
return static_cast<jboolean>(Settings::getDebugKnobAt(static_cast<u8>(index)));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -524,6 +524,8 @@
|
|||
<string name="rescale_hack_description">Enables a legacy handling for the rescale configuration pass for games by using a quick rescale path</string>
|
||||
<string name="renderer_asynchronous_shaders">Use asynchronous shaders</string>
|
||||
<string name="renderer_asynchronous_shaders_description">Compiles shaders asynchronously. This may reduce stutters but may also introduce glitches.</string>
|
||||
<string name="renderer_unified_memory">Unified memory access (UMA)</string>
|
||||
<string name="renderer_unified_memory_description">Allows GPU write buffer readbacks directly into guest memory, skipping the CPU staging copy.</string>
|
||||
<string name="gpu_unswizzle_settings">GPU Unswizzle Settings</string>
|
||||
<string name="gpu_unswizzle_settings_description">Configure GPU-based texture unswizzling parameters or disable it entirely. Adjust these settings to balance performance and texture loading quality.</string>
|
||||
<string name="gpu_unswizzle_enable">Enable GPU Unswizzle</string>
|
||||
|
|
@ -544,6 +546,10 @@
|
|||
<string name="disabled">Disabled</string>
|
||||
<string name="vertex_input_dynamic_state">Vertex Input Dynamic State</string>
|
||||
<string name="vertex_input_dynamic_state_description">Enabling this feature allows for more flexible vertex input handling, potentially reducing pipeline compilation time in vertex/buffer.</string>
|
||||
<string name="dynamic_rendering">Dynamic Rendering</string>
|
||||
<string name="dynamic_rendering_description">Render without render pass and framebuffer objects. Results vary by driver: some gain performance, others lose it.</string>
|
||||
<string name="workgroup_memory_explicit_layout">Workgroup Memory Explicit Layout</string>
|
||||
<string name="workgroup_memory_explicit_layout_description">Let shaders declare explicit layouts for workgroup memory. Disabled by default: some Qualcomm drivers are unstable with it.</string>
|
||||
<string name="sample_shading_fraction">Sample Shading</string>
|
||||
<string name="sample_shading_fraction_description">Allows the fragment shader to execute per sample in a multi-sampled fragment instead once per fragment. Improves graphics quality at the cost of some performance.</string>
|
||||
|
||||
|
|
|
|||
|
|
@ -51,14 +51,45 @@
|
|||
|
||||
#endif // ^^^ POSIX ^^^
|
||||
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <random>
|
||||
#include <vector>
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/free_region_manager.h"
|
||||
#include "common/host_memory.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/memory_detect.h"
|
||||
#include "common/settings.h"
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <dlfcn.h>
|
||||
#include <android/hardware_buffer.h>
|
||||
|
||||
namespace {
|
||||
|
||||
struct NativeHandle {
|
||||
int version;
|
||||
int numFds;
|
||||
int numInts;
|
||||
int data[1];
|
||||
};
|
||||
|
||||
using PFN_AHardwareBuffer_getNativeHandle = const NativeHandle* (*)(const AHardwareBuffer*);
|
||||
|
||||
PFN_AHardwareBuffer_getNativeHandle ResolveGetNativeHandle() {
|
||||
void* const lib = dlopen("libnativewindow.so", RTLD_NOW);
|
||||
if (lib == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return reinterpret_cast<PFN_AHardwareBuffer_getNativeHandle>(
|
||||
dlsym(lib, "AHardwareBuffer_getNativeHandle"));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
#if defined(__ANDROID__) && __ANDROID_API__ < 30
|
||||
#include <sys/syscall.h>
|
||||
|
|
@ -75,6 +106,12 @@ namespace Common {
|
|||
[[maybe_unused]] constexpr size_t PageAlignment = 0x1000;
|
||||
[[maybe_unused]] constexpr size_t HugePageSize = 0x200000;
|
||||
|
||||
static std::atomic<u64> committed_backing_size{};
|
||||
|
||||
u64 GetCommittedBackingSize() noexcept {
|
||||
return committed_backing_size.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
// Manually imported for MinGW compatibility
|
||||
|
|
@ -123,7 +160,7 @@ static void GetFuncAddress(Common::DynamicLibrary& dll, const char* name, T& pfn
|
|||
|
||||
class HostMemory::Impl {
|
||||
public:
|
||||
explicit Impl(size_t backing_size_, size_t virtual_size_)
|
||||
explicit Impl(size_t backing_size_, size_t virtual_size_, size_t)
|
||||
: backing_size{backing_size_}
|
||||
, virtual_size{virtual_size_}
|
||||
, process{GetCurrentProcess()}
|
||||
|
|
@ -229,6 +266,10 @@ public:
|
|||
UNREACHABLE();
|
||||
}
|
||||
|
||||
bool IsBackingShared() const noexcept {
|
||||
return true;
|
||||
}
|
||||
|
||||
const size_t backing_size; ///< Size of the backing memory in bytes
|
||||
const size_t virtual_size; ///< Size of the virtual address placeholder in bytes
|
||||
|
||||
|
|
@ -501,9 +542,10 @@ static int shm_open_anon(int flags, mode_t mode) {
|
|||
|
||||
class HostMemory::Impl {
|
||||
public:
|
||||
explicit Impl(size_t backing_size_, size_t virtual_size_)
|
||||
explicit Impl(size_t backing_size_, size_t virtual_size_, size_t preferred_offset_)
|
||||
: backing_size{backing_size_}
|
||||
, virtual_size{virtual_size_}
|
||||
, preferred_offset{preferred_offset_}
|
||||
{}
|
||||
|
||||
bool Init() {
|
||||
|
|
@ -543,10 +585,15 @@ public:
|
|||
LOG_WARNING(Common_Memory, "Using private mappings instead of shared ones");
|
||||
backing_base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
|
||||
if (fd > 0) {
|
||||
fd = -1;
|
||||
close(fd);
|
||||
}
|
||||
fd = -1;
|
||||
} else {
|
||||
#ifdef __ANDROID__
|
||||
if (InitAhbBacking()) {
|
||||
return InitVirtual();
|
||||
}
|
||||
#endif
|
||||
backing_base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0));
|
||||
}
|
||||
if (backing_base == MAP_FAILED) {
|
||||
|
|
@ -554,7 +601,10 @@ public:
|
|||
return false;
|
||||
}
|
||||
|
||||
// Virtual memory initialization
|
||||
return InitVirtual();
|
||||
}
|
||||
|
||||
bool InitVirtual() {
|
||||
virtual_base = virtual_map_base = static_cast<u8*>(ChooseVirtualBase(virtual_size));
|
||||
if (virtual_base == MAP_FAILED) {
|
||||
LOG_CRITICAL(HW_Memory, "mmap failed: {}", strerror(errno));
|
||||
|
|
@ -567,6 +617,248 @@ public:
|
|||
return true;
|
||||
}
|
||||
|
||||
#ifdef __ANDROID__
|
||||
static AHardwareBuffer_Desc MakeBlobDesc(size_t len) {
|
||||
return AHardwareBuffer_Desc{
|
||||
.width = static_cast<u32>(len),
|
||||
.height = 1,
|
||||
.layers = 1,
|
||||
.format = AHARDWAREBUFFER_FORMAT_BLOB,
|
||||
.usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN |
|
||||
AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN |
|
||||
AHARDWAREBUFFER_USAGE_GPU_DATA_BUFFER,
|
||||
.stride = 0,
|
||||
.rfu0 = 0,
|
||||
.rfu1 = 0,
|
||||
};
|
||||
}
|
||||
|
||||
static bool ProbeAhbBacking(PFN_AHardwareBuffer_getNativeHandle get_native_handle) {
|
||||
const AHardwareBuffer_Desc desc = MakeBlobDesc(PageAlignment * 2);
|
||||
AHardwareBuffer* buffer{};
|
||||
if (AHardwareBuffer_allocate(&desc, &buffer) != 0 || buffer == nullptr) {
|
||||
LOG_WARNING(HW_Memory, "Hardware buffer probe allocation failed");
|
||||
return false;
|
||||
}
|
||||
const NativeHandle* const handle = get_native_handle(buffer);
|
||||
if (handle == nullptr || handle->numFds < 1) {
|
||||
LOG_WARNING(HW_Memory, "Hardware buffer has no mappable file descriptor");
|
||||
AHardwareBuffer_release(buffer);
|
||||
return false;
|
||||
}
|
||||
const int probe_fd = handle->data[0];
|
||||
bool ok = true;
|
||||
const auto try_map = [&](int prot, off_t offset, const char* what) {
|
||||
if (!ok) {
|
||||
return;
|
||||
}
|
||||
void* const ptr = mmap(nullptr, PageAlignment, prot, MAP_SHARED, probe_fd, offset);
|
||||
if (ptr == MAP_FAILED) {
|
||||
LOG_WARNING(HW_Memory, "Hardware buffer backing rejects {}: {}", what,
|
||||
strerror(errno));
|
||||
ok = false;
|
||||
return;
|
||||
}
|
||||
munmap(ptr, PageAlignment);
|
||||
};
|
||||
try_map(PROT_READ | PROT_WRITE, 0, "shared mappings");
|
||||
try_map(PROT_READ | PROT_WRITE, static_cast<off_t>(PageAlignment), "mappings at an offset");
|
||||
#ifdef ARCHITECTURE_arm64
|
||||
try_map(PROT_READ | PROT_EXEC, 0, "executable mappings");
|
||||
#endif
|
||||
AHardwareBuffer_release(buffer);
|
||||
return ok;
|
||||
}
|
||||
|
||||
size_t ComputeAhbBudget(size_t window_size) const {
|
||||
const u64 total_physical = Common::GetMemInfo().TotalPhysicalMemory;
|
||||
if (total_physical == 0) {
|
||||
LOG_WARNING(HW_Memory, "Host memory size is unknown, not committing hardware buffers");
|
||||
return 0;
|
||||
}
|
||||
constexpr u64 MinimumTotalPhysical = 7ULL << 30;
|
||||
if (total_physical < MinimumTotalPhysical) {
|
||||
LOG_INFO(HW_Memory,
|
||||
"Skipping hardware buffer backing, {} MiB of RAM is below the {} MiB minimum",
|
||||
total_physical >> 20, MinimumTotalPhysical >> 20);
|
||||
return 0;
|
||||
}
|
||||
const u64 max_map_count = Common::GetMaxMapCount();
|
||||
constexpr u64 ReservedMaps = 24576;
|
||||
if (max_map_count == 0 || max_map_count <= ReservedMaps) {
|
||||
LOG_WARNING(HW_Memory,
|
||||
"Skipping hardware buffer backing, vm.max_map_count is unknown or too low");
|
||||
return 0;
|
||||
}
|
||||
u64 budget = total_physical / 6;
|
||||
budget = (std::min)(budget, (max_map_count - ReservedMaps) * PageAlignment);
|
||||
const u64 available = Common::GetAvailablePhysicalMemory();
|
||||
if (available != 0) {
|
||||
constexpr u64 Headroom = 2ULL << 30;
|
||||
budget = (std::min)(budget, available > Headroom ? available - Headroom : 0);
|
||||
}
|
||||
budget = (std::min)(budget, static_cast<u64>(backing_size));
|
||||
budget = Common::AlignDown(budget, window_size);
|
||||
constexpr u64 MinimumBudget = 256ULL << 20;
|
||||
if (budget < MinimumBudget) {
|
||||
LOG_INFO(HW_Memory,
|
||||
"Skipping hardware buffer backing, only {} MiB could be committed on a {} MiB "
|
||||
"system with {} MiB available and vm.max_map_count {}",
|
||||
budget >> 20, total_physical >> 20, available >> 20, max_map_count);
|
||||
return 0;
|
||||
}
|
||||
return static_cast<size_t>(budget);
|
||||
}
|
||||
|
||||
bool InitAhbBacking() {
|
||||
if (!Settings::values.use_unified_memory.GetValue()) {
|
||||
return false;
|
||||
}
|
||||
static const PFN_AHardwareBuffer_getNativeHandle get_native_handle =
|
||||
ResolveGetNativeHandle();
|
||||
if (get_native_handle == nullptr) {
|
||||
LOG_WARNING(HW_Memory, "AHardwareBuffer_getNativeHandle is not available");
|
||||
return false;
|
||||
}
|
||||
constexpr size_t window_size = 64ULL << 20;
|
||||
const AHardwareBuffer_Desc window_desc = MakeBlobDesc(window_size);
|
||||
if (AHardwareBuffer_isSupported(&window_desc) == 0) {
|
||||
LOG_WARNING(HW_Memory, "Allocator rejects {} MiB hardware buffer windows",
|
||||
window_size >> 20);
|
||||
return false;
|
||||
}
|
||||
const size_t budget = ComputeAhbBudget(window_size);
|
||||
if (budget == 0) {
|
||||
return false;
|
||||
}
|
||||
if (!ProbeAhbBacking(get_native_handle)) {
|
||||
return false;
|
||||
}
|
||||
const size_t aligned_backing = Common::AlignDown(backing_size, window_size);
|
||||
const size_t region_size = (std::min)(budget, aligned_backing);
|
||||
const size_t region_base = Common::AlignDown(
|
||||
(std::min)(preferred_offset, aligned_backing - region_size), window_size);
|
||||
const size_t num_windows = region_size / window_size;
|
||||
|
||||
std::vector<AHardwareBuffer*> buffers;
|
||||
std::vector<int> buffer_fds;
|
||||
const auto cleanup = [&] {
|
||||
for (AHardwareBuffer* buffer : buffers) {
|
||||
AHardwareBuffer_release(buffer);
|
||||
}
|
||||
buffers.clear();
|
||||
buffer_fds.clear();
|
||||
};
|
||||
for (size_t i = 0; i < num_windows; ++i) {
|
||||
const AHardwareBuffer_Desc desc = MakeBlobDesc(window_size);
|
||||
AHardwareBuffer* buffer{};
|
||||
if (AHardwareBuffer_allocate(&desc, &buffer) != 0 || buffer == nullptr) {
|
||||
LOG_WARNING(HW_Memory, "Hardware buffer allocation failed for window {} of {}", i,
|
||||
num_windows);
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
buffers.push_back(buffer);
|
||||
const NativeHandle* const handle = get_native_handle(buffer);
|
||||
if (handle == nullptr || handle->numFds < 1) {
|
||||
LOG_WARNING(HW_Memory, "Hardware buffer has no mappable file descriptor");
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
const int buffer_fd = handle->data[0];
|
||||
const off_t buffer_len = lseek(buffer_fd, 0, SEEK_END);
|
||||
if (buffer_len < static_cast<off_t>(window_size)) {
|
||||
LOG_WARNING(HW_Memory, "Hardware buffer descriptor smaller than requested");
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
buffer_fds.push_back(buffer_fd);
|
||||
}
|
||||
u8* const base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_NONE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0));
|
||||
if (base == MAP_FAILED) {
|
||||
LOG_WARNING(HW_Memory, "Failed to reserve backing address space: {}", strerror(errno));
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
const auto map_over_reservation = [&](size_t offset, size_t len, int map_fd,
|
||||
off_t map_offset) {
|
||||
if (len == 0) {
|
||||
return true;
|
||||
}
|
||||
if (mmap(base + offset, len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, map_fd,
|
||||
map_offset) == MAP_FAILED) {
|
||||
LOG_WARNING(HW_Memory, "Backing mmap failed: {}", strerror(errno));
|
||||
munmap(base, backing_size);
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
if (!map_over_reservation(0, region_base, fd, 0)) {
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < num_windows; ++i) {
|
||||
if (!map_over_reservation(region_base + i * window_size, window_size, buffer_fds[i],
|
||||
0)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const size_t tail_offset = region_base + region_size;
|
||||
if (!map_over_reservation(tail_offset, backing_size - tail_offset, fd,
|
||||
static_cast<off_t>(tail_offset))) {
|
||||
return false;
|
||||
}
|
||||
backing_base = base;
|
||||
ahb_windows = std::move(buffers);
|
||||
ahb_fds = std::move(buffer_fds);
|
||||
ahb_window_size = window_size;
|
||||
ahb_base = region_base;
|
||||
ahb_bytes = region_size;
|
||||
committed_backing_size.store(region_size, std::memory_order_relaxed);
|
||||
LOG_INFO(HW_Memory,
|
||||
"Guest memory {:#x}-{:#x} backed by {} hardware buffer windows, {} MiB committed",
|
||||
region_base, region_base + region_size, ahb_windows.size(), region_size >> 20);
|
||||
return true;
|
||||
}
|
||||
|
||||
void MapBackingRange(size_t virtual_offset, size_t host_offset, size_t length, int prot_flags) {
|
||||
while (length > 0) {
|
||||
int map_fd = fd;
|
||||
off_t map_offset = static_cast<off_t>(host_offset);
|
||||
size_t chunk = length;
|
||||
if (host_offset < ahb_base) {
|
||||
chunk = (std::min)(chunk, ahb_base - host_offset);
|
||||
} else if (host_offset < ahb_base + ahb_bytes) {
|
||||
const size_t relative = host_offset - ahb_base;
|
||||
const size_t window = relative / ahb_window_size;
|
||||
const size_t local = relative % ahb_window_size;
|
||||
map_fd = ahb_fds[window];
|
||||
map_offset = static_cast<off_t>(local);
|
||||
chunk = (std::min)(chunk, ahb_window_size - local);
|
||||
}
|
||||
void* const ret = mmap(virtual_base + virtual_offset, chunk, prot_flags,
|
||||
MAP_SHARED | MAP_FIXED, map_fd, map_offset);
|
||||
ASSERT_MSG(ret != MAP_FAILED, "mmap: {}", strerror(errno));
|
||||
virtual_offset += chunk;
|
||||
host_offset += chunk;
|
||||
length -= chunk;
|
||||
}
|
||||
}
|
||||
|
||||
std::span<AHardwareBuffer* const> AhbWindows() const noexcept {
|
||||
return ahb_windows;
|
||||
}
|
||||
|
||||
size_t AhbWindowSize() const noexcept {
|
||||
return ahb_bytes != 0 ? ahb_window_size : 0;
|
||||
}
|
||||
|
||||
size_t AhbBase() const noexcept {
|
||||
return ahb_base;
|
||||
}
|
||||
#endif
|
||||
|
||||
~Impl() {
|
||||
Release();
|
||||
}
|
||||
|
|
@ -587,6 +879,12 @@ public:
|
|||
#ifdef ARCHITECTURE_arm64
|
||||
if (True(perms & MemoryPermission::Execute))
|
||||
prot_flags |= PROT_EXEC;
|
||||
#endif
|
||||
#ifdef __ANDROID__
|
||||
if (ahb_bytes != 0) {
|
||||
MapBackingRange(virtual_offset, host_offset, length, prot_flags);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
int flags = (fd >= 0 ? MAP_SHARED : MAP_PRIVATE) | MAP_FIXED;
|
||||
void* ret = mmap(virtual_base + virtual_offset, length, prot_flags, flags, fd, host_offset);
|
||||
|
|
@ -632,8 +930,18 @@ public:
|
|||
virtual_base = nullptr;
|
||||
}
|
||||
|
||||
bool IsBackingShared() const noexcept {
|
||||
#ifdef __ANDROID__
|
||||
if (ahb_bytes != 0) {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
return fd >= 0;
|
||||
}
|
||||
|
||||
const size_t backing_size; ///< Size of the backing memory in bytes
|
||||
const size_t virtual_size; ///< Size of the virtual address placeholder in bytes
|
||||
const size_t preferred_offset;
|
||||
|
||||
u8* backing_base{reinterpret_cast<u8*>(MAP_FAILED)};
|
||||
u8* virtual_base{reinterpret_cast<u8*>(MAP_FAILED)};
|
||||
|
|
@ -656,6 +964,18 @@ private:
|
|||
int ret = close(fd);
|
||||
ASSERT_MSG(ret == 0, "close failed: {}", strerror(errno));
|
||||
}
|
||||
|
||||
#ifdef __ANDROID__
|
||||
for (AHardwareBuffer* buffer : ahb_windows) {
|
||||
AHardwareBuffer_release(buffer);
|
||||
}
|
||||
ahb_windows.clear();
|
||||
ahb_fds.clear();
|
||||
if (ahb_bytes != 0) {
|
||||
committed_backing_size.store(0, std::memory_order_relaxed);
|
||||
ahb_bytes = 0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void AdjustMap(size_t* virtual_offset, size_t* length) {
|
||||
|
|
@ -681,11 +1001,19 @@ private:
|
|||
|
||||
int fd{-1}; // memfd file descriptor, -1 is the error value of memfd_create
|
||||
FreeRegionManager free_manager{};
|
||||
|
||||
#ifdef __ANDROID__
|
||||
std::vector<AHardwareBuffer*> ahb_windows;
|
||||
std::vector<int> ahb_fds;
|
||||
size_t ahb_window_size{};
|
||||
size_t ahb_base{};
|
||||
size_t ahb_bytes{};
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // ^^^ POSIX ^^^
|
||||
|
||||
HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_)
|
||||
HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_, size_t preferred_offset_)
|
||||
: backing_size(backing_size_)
|
||||
, virtual_size(virtual_size_)
|
||||
{
|
||||
|
|
@ -697,7 +1025,7 @@ HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_)
|
|||
#else
|
||||
// Try to allocate a fastmem arena.
|
||||
// The implementation will fail with std::bad_alloc on errors.
|
||||
impl = std::make_unique<HostMemory::Impl>(AlignUp(backing_size, PageAlignment), AlignUp(virtual_size, PageAlignment) + HugePageSize);
|
||||
impl = std::make_unique<HostMemory::Impl>(AlignUp(backing_size, PageAlignment), AlignUp(virtual_size, PageAlignment) + HugePageSize, preferred_offset_);
|
||||
if (impl->Init()) {
|
||||
backing_base = impl->backing_base;
|
||||
virtual_base = impl->virtual_base;
|
||||
|
|
@ -767,6 +1095,38 @@ void HostMemory::ClearBackingRegion(size_t physical_offset, size_t length, u32 f
|
|||
std::memset(backing_base + physical_offset, fill_value, length);
|
||||
}
|
||||
|
||||
std::span<AHardwareBuffer* const> HostMemory::BackingHardwareBuffers() const noexcept {
|
||||
#ifdef __ANDROID__
|
||||
return impl ? impl->AhbWindows() : std::span<AHardwareBuffer* const>{};
|
||||
#else
|
||||
return {};
|
||||
#endif
|
||||
}
|
||||
|
||||
size_t HostMemory::BackingHardwareBufferWindowSize() const noexcept {
|
||||
#ifdef __ANDROID__
|
||||
return impl ? impl->AhbWindowSize() : 0;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool HostMemory::IsBackingShared() const noexcept {
|
||||
#if defined(__OPENORBIS__) || defined(__managarm__)
|
||||
return false;
|
||||
#else
|
||||
return impl && impl->IsBackingShared();
|
||||
#endif
|
||||
}
|
||||
|
||||
size_t HostMemory::BackingHardwareBufferBase() const noexcept {
|
||||
#ifdef __ANDROID__
|
||||
return impl ? impl->AhbBase() : 0;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
void HostMemory::EnableDirectMappedAddress() {
|
||||
#if !(defined(__OPENORBIS__) || defined(__managarm__))
|
||||
if (impl) {
|
||||
|
|
|
|||
|
|
@ -8,12 +8,17 @@
|
|||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/virtual_buffer.h"
|
||||
|
||||
struct AHardwareBuffer;
|
||||
|
||||
namespace Common {
|
||||
|
||||
[[nodiscard]] u64 GetCommittedBackingSize() noexcept;
|
||||
|
||||
enum class MemoryPermission : u32 {
|
||||
Read = 1 << 0,
|
||||
Write = 1 << 1,
|
||||
|
|
@ -28,7 +33,7 @@ DECLARE_ENUM_FLAG_OPERATORS(MemoryPermission)
|
|||
*/
|
||||
class HostMemory {
|
||||
public:
|
||||
explicit HostMemory(size_t backing_size_, size_t virtual_size_);
|
||||
explicit HostMemory(size_t backing_size_, size_t virtual_size_, size_t preferred_offset_ = 0);
|
||||
~HostMemory();
|
||||
|
||||
/**
|
||||
|
|
@ -62,6 +67,18 @@ public:
|
|||
return backing_base;
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t BackingSize() const noexcept {
|
||||
return backing_size;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::span<AHardwareBuffer* const> BackingHardwareBuffers() const noexcept;
|
||||
|
||||
[[nodiscard]] size_t BackingHardwareBufferWindowSize() const noexcept;
|
||||
|
||||
[[nodiscard]] size_t BackingHardwareBufferBase() const noexcept;
|
||||
|
||||
[[nodiscard]] bool IsBackingShared() const noexcept;
|
||||
|
||||
[[nodiscard]] u8* VirtualBasePointer() noexcept {
|
||||
return virtual_base;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,10 @@
|
|||
#endif
|
||||
#endif
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include "common/memory_detect.h"
|
||||
|
||||
namespace Common {
|
||||
|
|
@ -69,4 +73,55 @@ const MemoryInfo& GetMemInfo() {
|
|||
return mem_info;
|
||||
}
|
||||
|
||||
u64 GetAvailablePhysicalMemory() {
|
||||
#ifdef _WIN32
|
||||
MEMORYSTATUSEX memorystatus;
|
||||
memorystatus.dwLength = sizeof(memorystatus);
|
||||
if (GlobalMemoryStatusEx(&memorystatus)) {
|
||||
return memorystatus.ullAvailPhys;
|
||||
}
|
||||
return 0;
|
||||
#elif defined(__linux__)
|
||||
if (std::FILE* const file = std::fopen("/proc/meminfo", "re")) {
|
||||
char line[256];
|
||||
u64 available = 0;
|
||||
while (std::fgets(line, sizeof(line), file) != nullptr) {
|
||||
if (std::strncmp(line, "MemAvailable:", 13) == 0) {
|
||||
available = std::strtoull(line + 13, nullptr, 10) * 1024ULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
std::fclose(file);
|
||||
if (available != 0) {
|
||||
return available;
|
||||
}
|
||||
}
|
||||
struct sysinfo info;
|
||||
if (sysinfo(&info) == 0) {
|
||||
const u64 unit = info.mem_unit != 0 ? info.mem_unit : 1ULL;
|
||||
return (static_cast<u64>(info.freeram) + static_cast<u64>(info.bufferram)) * unit;
|
||||
}
|
||||
return 0;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
u64 GetMaxMapCount() {
|
||||
#ifdef __linux__
|
||||
if (std::FILE* const file = std::fopen("/proc/sys/vm/max_map_count", "re")) {
|
||||
char line[32];
|
||||
u64 count = 0;
|
||||
if (std::fgets(line, sizeof(line), file) != nullptr) {
|
||||
count = std::strtoull(line, nullptr, 10);
|
||||
}
|
||||
std::fclose(file);
|
||||
return count;
|
||||
}
|
||||
return 0;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
|
|
|||
|
|
@ -18,4 +18,8 @@ struct MemoryInfo {
|
|||
*/
|
||||
[[nodiscard]] const MemoryInfo& GetMemInfo();
|
||||
|
||||
[[nodiscard]] u64 GetAvailablePhysicalMemory();
|
||||
|
||||
[[nodiscard]] u64 GetMaxMapCount();
|
||||
|
||||
} // namespace Common
|
||||
|
|
|
|||
|
|
@ -587,6 +587,9 @@ struct Values {
|
|||
SwitchableSetting<bool> use_asynchronous_shaders{linkage, false, "use_asynchronous_shaders",
|
||||
Category::RendererHacks};
|
||||
|
||||
SwitchableSetting<bool> use_unified_memory{linkage, false, "use_unified_memory",
|
||||
Category::RendererHacks};
|
||||
|
||||
SwitchableSetting<GpuUnswizzleSize> gpu_unswizzle_texture_size{linkage,
|
||||
GpuUnswizzleSize::Large,
|
||||
"gpu_unswizzle_texture_size",
|
||||
|
|
@ -635,6 +638,16 @@ struct Values {
|
|||
#endif
|
||||
"vertex_input_dynamic_state", Category::RendererExtensions};
|
||||
|
||||
SwitchableSetting<bool> dynamic_rendering{linkage, true, "dynamic_rendering",
|
||||
Category::RendererExtensions};
|
||||
|
||||
SwitchableSetting<bool> workgroup_memory_explicit_layout{
|
||||
linkage, false, "workgroup_memory_explicit_layout", Category::RendererExtensions};
|
||||
|
||||
SwitchableSetting<s32, true> pipeline_worker_count{
|
||||
linkage, 2, 2, 8, "pipeline_worker_count", Category::RendererAdvanced,
|
||||
Specialization::Scalar};
|
||||
|
||||
Setting<bool> renderer_debug{linkage, false, "debug", Category::RendererDebug};
|
||||
Setting<bool> renderer_shader_feedback{linkage, false, "shader_feedback",
|
||||
Category::RendererDebug};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
|
||||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
@ -39,6 +40,258 @@
|
|||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <sys/resource.h>
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
constexpr int ANDROID_THREAD_PRIORITY_AUDIO = -16;
|
||||
constexpr int ANDROID_THREAD_PRIORITY_URGENT_DISPLAY = -8;
|
||||
constexpr int ANDROID_THREAD_PRIORITY_DISPLAY = -4;
|
||||
constexpr int ANDROID_THREAD_PRIORITY_DEFAULT = 0;
|
||||
constexpr int ANDROID_THREAD_PRIORITY_BACKGROUND = 10;
|
||||
|
||||
constexpr size_t ANDROID_MINIMUM_PERFORMANCE_CORES = 4;
|
||||
|
||||
enum class CoreGroup {
|
||||
Unrestricted,
|
||||
Performance,
|
||||
Efficiency,
|
||||
};
|
||||
|
||||
struct CoreTopology {
|
||||
cpu_set_t allowed;
|
||||
cpu_set_t performance;
|
||||
cpu_set_t efficiency;
|
||||
bool separated;
|
||||
bool initialized;
|
||||
};
|
||||
|
||||
struct ThreadPolicy {
|
||||
pid_t tid;
|
||||
CoreGroup group;
|
||||
int nice_value;
|
||||
bool has_nice;
|
||||
};
|
||||
|
||||
std::mutex g_topology_mutex;
|
||||
CoreTopology g_topology{};
|
||||
|
||||
std::mutex g_policy_mutex;
|
||||
|
||||
std::vector<ThreadPolicy>& Policies() {
|
||||
static auto* const policies = new std::vector<ThreadPolicy>();
|
||||
return *policies;
|
||||
}
|
||||
|
||||
struct PolicyRegistration {
|
||||
~PolicyRegistration() {
|
||||
const pid_t tid = gettid();
|
||||
std::scoped_lock lock{g_policy_mutex};
|
||||
std::erase_if(Policies(), [tid](const ThreadPolicy& policy) { return policy.tid == tid; });
|
||||
}
|
||||
};
|
||||
|
||||
thread_local PolicyRegistration t_policy_registration;
|
||||
|
||||
int PossibleCpuCount() {
|
||||
std::ifstream file("/sys/devices/system/cpu/possible");
|
||||
std::string list;
|
||||
if (file && std::getline(file, list) && !list.empty()) {
|
||||
int highest = -1;
|
||||
const char* cursor = list.c_str();
|
||||
while (*cursor != '\0') {
|
||||
char* end = nullptr;
|
||||
const long value = std::strtol(cursor, &end, 10);
|
||||
if (end == cursor) {
|
||||
break;
|
||||
}
|
||||
highest = (std::max)(highest, static_cast<int>(value));
|
||||
cursor = end;
|
||||
while (*cursor == '-' || *cursor == ',') {
|
||||
++cursor;
|
||||
}
|
||||
}
|
||||
if (highest >= 0) {
|
||||
return (std::min)(highest + 1, CPU_SETSIZE);
|
||||
}
|
||||
}
|
||||
const long configured = sysconf(_SC_NPROCESSORS_CONF);
|
||||
if (configured > 0) {
|
||||
return static_cast<int>((std::min<long>)(configured, CPU_SETSIZE));
|
||||
}
|
||||
return static_cast<int>((std::min<unsigned>)(std::thread::hardware_concurrency(), CPU_SETSIZE));
|
||||
}
|
||||
|
||||
long ReadCpuScalar(int cpu, const char* node) {
|
||||
long value = 0;
|
||||
std::ifstream file("/sys/devices/system/cpu/cpu" + std::to_string(cpu) + "/" + node);
|
||||
if (!file || !(file >> value) || value <= 0) {
|
||||
return 0;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
std::vector<std::pair<long, int>> CollectCoreWeights(const cpu_set_t& allowed, int total,
|
||||
const char* node, bool require_all) {
|
||||
std::vector<std::pair<long, int>> cores;
|
||||
for (int cpu = 0; cpu < total; ++cpu) {
|
||||
if (!CPU_ISSET(cpu, &allowed)) {
|
||||
continue;
|
||||
}
|
||||
const long weight = ReadCpuScalar(cpu, node);
|
||||
if (weight <= 0) {
|
||||
if (require_all) {
|
||||
return {};
|
||||
}
|
||||
LOG_WARNING(Common, "Could not read {} for CPU {}, treating it as an efficiency core",
|
||||
node, cpu);
|
||||
continue;
|
||||
}
|
||||
cores.emplace_back(weight, cpu);
|
||||
}
|
||||
return cores;
|
||||
}
|
||||
|
||||
void ComputeTopologyLocked() {
|
||||
g_topology.initialized = true;
|
||||
g_topology.separated = false;
|
||||
CPU_ZERO(&g_topology.allowed);
|
||||
CPU_ZERO(&g_topology.performance);
|
||||
CPU_ZERO(&g_topology.efficiency);
|
||||
|
||||
if (sched_getaffinity(getpid(), sizeof(g_topology.allowed), &g_topology.allowed) != 0) {
|
||||
LOG_WARNING(Common, "Could not query process CPU affinity: {}",
|
||||
::Common::GetLastErrorMsg());
|
||||
return;
|
||||
}
|
||||
|
||||
const int total = PossibleCpuCount();
|
||||
auto cores = CollectCoreWeights(g_topology.allowed, total, "cpu_capacity", true);
|
||||
if (cores.empty()) {
|
||||
cores = CollectCoreWeights(g_topology.allowed, total, "cpufreq/cpuinfo_max_freq", false);
|
||||
}
|
||||
if (cores.empty()) {
|
||||
LOG_WARNING(Common, "Could not determine CPU topology, thread placement is disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
std::sort(cores.begin(), cores.end(),
|
||||
[](const auto& lhs, const auto& rhs) { return lhs.first > rhs.first; });
|
||||
|
||||
const size_t allowed_count = static_cast<size_t>(CPU_COUNT(&g_topology.allowed));
|
||||
const size_t maximum =
|
||||
allowed_count > 2 * ANDROID_MINIMUM_PERFORMANCE_CORES
|
||||
? allowed_count - ANDROID_MINIMUM_PERFORMANCE_CORES
|
||||
: ANDROID_MINIMUM_PERFORMANCE_CORES;
|
||||
|
||||
size_t taken = 0;
|
||||
long cluster_weight = cores.front().first;
|
||||
for (const auto& [weight, cpu] : cores) {
|
||||
if (weight != cluster_weight) {
|
||||
if (taken >= ANDROID_MINIMUM_PERFORMANCE_CORES) {
|
||||
break;
|
||||
}
|
||||
cluster_weight = weight;
|
||||
}
|
||||
if (taken >= maximum) {
|
||||
break;
|
||||
}
|
||||
CPU_SET(cpu, &g_topology.performance);
|
||||
++taken;
|
||||
}
|
||||
if (taken == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int cpu = 0; cpu < total; ++cpu) {
|
||||
if (CPU_ISSET(cpu, &g_topology.allowed) && !CPU_ISSET(cpu, &g_topology.performance)) {
|
||||
CPU_SET(cpu, &g_topology.efficiency);
|
||||
}
|
||||
}
|
||||
|
||||
g_topology.separated = CPU_COUNT(&g_topology.efficiency) > 0;
|
||||
LOG_INFO(Common, "CPU topology: {} performance cores, {} efficiency cores, separation {}",
|
||||
CPU_COUNT(&g_topology.performance), CPU_COUNT(&g_topology.efficiency),
|
||||
g_topology.separated ? "enabled" : "unavailable");
|
||||
}
|
||||
|
||||
void EnsureTopologyLocked() {
|
||||
if (!g_topology.initialized) {
|
||||
ComputeTopologyLocked();
|
||||
}
|
||||
}
|
||||
|
||||
void RefreshTopologyLocked() {
|
||||
if (!g_topology.initialized) {
|
||||
ComputeTopologyLocked();
|
||||
return;
|
||||
}
|
||||
cpu_set_t current;
|
||||
CPU_ZERO(¤t);
|
||||
if (sched_getaffinity(getpid(), sizeof(current), ¤t) != 0) {
|
||||
return;
|
||||
}
|
||||
if (std::memcmp(¤t, &g_topology.allowed, sizeof(current)) != 0) {
|
||||
ComputeTopologyLocked();
|
||||
}
|
||||
}
|
||||
|
||||
bool ApplyCoreGroupLocked(pid_t tid, CoreGroup group) {
|
||||
if (!g_topology.separated || group == CoreGroup::Unrestricted) {
|
||||
return false;
|
||||
}
|
||||
const cpu_set_t& mask =
|
||||
group == CoreGroup::Performance ? g_topology.performance : g_topology.efficiency;
|
||||
if (CPU_COUNT(&mask) == 0) {
|
||||
return false;
|
||||
}
|
||||
if (sched_setaffinity(tid, sizeof(mask), &mask) != 0) {
|
||||
LOG_WARNING(Common, "Could not restrict thread {} to its core group: {}", tid,
|
||||
::Common::GetLastErrorMsg());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ThreadPolicy& AcquirePolicyLocked(pid_t tid) {
|
||||
auto& policies = Policies();
|
||||
for (auto& policy : policies) {
|
||||
if (policy.tid == tid) {
|
||||
return policy;
|
||||
}
|
||||
}
|
||||
return policies.emplace_back(ThreadPolicy{tid, CoreGroup::Unrestricted, 0, false});
|
||||
}
|
||||
|
||||
void SetCurrentThreadCoreGroup(CoreGroup group) {
|
||||
const pid_t tid = gettid();
|
||||
{
|
||||
std::scoped_lock lock{g_topology_mutex};
|
||||
EnsureTopologyLocked();
|
||||
ApplyCoreGroupLocked(tid, group);
|
||||
}
|
||||
(void)&t_policy_registration;
|
||||
std::scoped_lock lock{g_policy_mutex};
|
||||
AcquirePolicyLocked(tid).group = group;
|
||||
}
|
||||
|
||||
void RememberCurrentThreadNice(pid_t tid, int nice_value) {
|
||||
(void)&t_policy_registration;
|
||||
std::scoped_lock lock{g_policy_mutex};
|
||||
ThreadPolicy& policy = AcquirePolicyLocked(tid);
|
||||
policy.nice_value = nice_value;
|
||||
policy.has_nice = true;
|
||||
}
|
||||
} // Anonymous namespace
|
||||
#endif
|
||||
|
||||
#include "common/cpu_features.h"
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
#ifdef _MSC_VER
|
||||
|
|
@ -48,7 +301,6 @@
|
|||
#endif
|
||||
#include "common/x64/rdtsc.h"
|
||||
#endif
|
||||
#include "core/core_timing.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
|
|
@ -78,6 +330,24 @@ void SetCurrentThreadPriority(ThreadPriority new_priority) {
|
|||
}
|
||||
}();
|
||||
set_thread_priority(find_thread(NULL), priority);
|
||||
#elif defined(__ANDROID__)
|
||||
const int nice_value = [&]() {
|
||||
switch (new_priority) {
|
||||
case ThreadPriority::Low: return ANDROID_THREAD_PRIORITY_BACKGROUND;
|
||||
case ThreadPriority::Normal: return ANDROID_THREAD_PRIORITY_DEFAULT;
|
||||
case ThreadPriority::High: return ANDROID_THREAD_PRIORITY_DISPLAY;
|
||||
case ThreadPriority::VeryHigh: return ANDROID_THREAD_PRIORITY_URGENT_DISPLAY;
|
||||
case ThreadPriority::Critical: return ANDROID_THREAD_PRIORITY_AUDIO;
|
||||
default: return ANDROID_THREAD_PRIORITY_DEFAULT;
|
||||
}
|
||||
}();
|
||||
const pid_t tid = gettid();
|
||||
if (setpriority(PRIO_PROCESS, static_cast<id_t>(tid), nice_value) != 0) {
|
||||
LOG_WARNING(Common, "Could not set thread nice value to {}: {}", nice_value,
|
||||
GetLastErrorMsg());
|
||||
return;
|
||||
}
|
||||
RememberCurrentThreadNice(tid, nice_value);
|
||||
#else
|
||||
pthread_t this_thread = pthread_self();
|
||||
const auto scheduling_type = SCHED_OTHER;
|
||||
|
|
@ -132,29 +402,31 @@ void SetCurrentThreadName(const char* name) {
|
|||
#endif
|
||||
}
|
||||
|
||||
void PinCurrentThreadToPerformanceCore(size_t core_id) {
|
||||
ASSERT(core_id < 4);
|
||||
// If we set a flag for a CPU that doesn't exist, the thread may not be allowed to
|
||||
// run in ANY processor!
|
||||
auto const total_cores = std::thread::hardware_concurrency();
|
||||
if (core_id < total_cores) {
|
||||
void SetCurrentThreadToPerformanceCores() {
|
||||
#if defined(__ANDROID__)
|
||||
cpu_set_t set;
|
||||
CPU_ZERO(&set);
|
||||
CPU_SET(core_id, &set);
|
||||
sched_setaffinity(pthread_self(), sizeof(set), &set);
|
||||
#elif defined(__linux__) || defined(__FreeBSD__)
|
||||
cpu_set_t set;
|
||||
CPU_ZERO(&set);
|
||||
CPU_SET(core_id, &set);
|
||||
pthread_setaffinity_np(pthread_self(), sizeof(set), &set);
|
||||
#elif defined(_WIN32)
|
||||
DWORD set = 1UL << core_id;
|
||||
SetThreadAffinityMask(GetCurrentThread(), set);
|
||||
#else
|
||||
// No pin functionality implemented
|
||||
SetCurrentThreadCoreGroup(CoreGroup::Performance);
|
||||
#endif
|
||||
}
|
||||
|
||||
void SetCurrentThreadToEfficiencyCores() {
|
||||
#if defined(__ANDROID__)
|
||||
SetCurrentThreadCoreGroup(CoreGroup::Efficiency);
|
||||
#endif
|
||||
}
|
||||
|
||||
void RefreshThreadPolicies() {
|
||||
#if defined(__ANDROID__)
|
||||
std::scoped_lock topology_lock{g_topology_mutex};
|
||||
RefreshTopologyLocked();
|
||||
|
||||
std::scoped_lock policy_lock{g_policy_mutex};
|
||||
for (const auto& policy : Policies()) {
|
||||
if (policy.has_nice) {
|
||||
setpriority(PRIO_PROCESS, static_cast<id_t>(policy.tid), policy.nice_value);
|
||||
}
|
||||
ApplyCoreGroupLocked(policy.tid, policy.group);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
|
|
|
|||
|
|
@ -99,8 +99,16 @@ enum class ThreadPriority : u32 {
|
|||
Critical = 4,
|
||||
};
|
||||
|
||||
enum class ThreadPlacement : u32 {
|
||||
Default = 0,
|
||||
Background = 1,
|
||||
Efficiency = 2,
|
||||
};
|
||||
|
||||
void SetCurrentThreadPriority(ThreadPriority new_priority);
|
||||
void SetCurrentThreadName(const char* name);
|
||||
void PinCurrentThreadToPerformanceCore(size_t core_id);
|
||||
void SetCurrentThreadToPerformanceCores();
|
||||
void SetCurrentThreadToEfficiencyCores();
|
||||
void RefreshThreadPolicies();
|
||||
|
||||
} // namespace Common
|
||||
|
|
|
|||
|
|
@ -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 2020 yuzu Emulator Project
|
||||
|
|
@ -37,10 +37,17 @@ class StatefulThreadWorker {
|
|||
using StateMaker = std::conditional_t<with_state, std::function<StateType()>, DummyCallable>;
|
||||
|
||||
public:
|
||||
explicit StatefulThreadWorker(size_t num_workers, std::string name, StateMaker func = {})
|
||||
explicit StatefulThreadWorker(size_t num_workers, std::string name, StateMaker func = {},
|
||||
ThreadPlacement placement = ThreadPlacement::Default)
|
||||
: workers_queued{num_workers}, thread_name{std::move(name)} {
|
||||
const auto lambda = [this, func](std::stop_token stop_token) {
|
||||
const auto lambda = [this, func, placement](std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName(thread_name.c_str());
|
||||
if (placement != ThreadPlacement::Default) {
|
||||
Common::SetCurrentThreadPriority(ThreadPriority::Low);
|
||||
}
|
||||
if (placement == ThreadPlacement::Efficiency) {
|
||||
Common::SetCurrentThreadToEfficiencyCores();
|
||||
}
|
||||
{
|
||||
[[maybe_unused]] std::conditional_t<with_state, StateType, int> state{func()};
|
||||
while (!stop_token.stop_requested()) {
|
||||
|
|
|
|||
|
|
@ -157,6 +157,8 @@ bool ArmNce::HandleGuestAlignmentFault(GuestContext* guest_ctx, void* raw_info,
|
|||
return HandleFailedGuestFault(guest_ctx, raw_info, raw_context);
|
||||
}
|
||||
|
||||
constexpr size_t NCE_WRITE_FAULT_CLUSTER_PAGES = 4;
|
||||
|
||||
bool ArmNce::HandleGuestAccessFault(GuestContext* guest_ctx, void* raw_info, void* raw_context) {
|
||||
auto* info = static_cast<siginfo_t*>(raw_info);
|
||||
|
||||
|
|
@ -165,7 +167,7 @@ bool ArmNce::HandleGuestAccessFault(GuestContext* guest_ctx, void* raw_info, voi
|
|||
const Common::ProcessAddress addr =
|
||||
(reinterpret_cast<u64>(info->si_addr) & ~Memory::YUZU_PAGEMASK);
|
||||
auto& memory = guest_ctx->parent->m_running_thread->GetOwnerProcess()->GetMemory();
|
||||
if (memory.InvalidateNCE(addr, Memory::YUZU_PAGESIZE)) {
|
||||
if (memory.InvalidateNCE(addr, Memory::YUZU_PAGESIZE * NCE_WRITE_FAULT_CLUSTER_PAGES)) {
|
||||
// We handled the access successfully and are returning to guest code.
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -118,6 +118,7 @@ struct System::Impl {
|
|||
|
||||
is_multicore = Settings::values.use_multi_core.GetValue();
|
||||
extended_memory_layout = Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb;
|
||||
unified_memory = Settings::values.use_unified_memory.GetValue();
|
||||
|
||||
core_timing.SetMulticore(is_multicore);
|
||||
core_timing.Initialize([&system]() { system.RegisterHostThread(); });
|
||||
|
|
@ -145,7 +146,8 @@ struct System::Impl {
|
|||
!device_memory.has_value() ||
|
||||
is_multicore != Settings::values.use_multi_core.GetValue() ||
|
||||
extended_memory_layout != (Settings::values.memory_layout_mode.GetValue() !=
|
||||
Settings::MemoryLayout::Memory_4Gb);
|
||||
Settings::MemoryLayout::Memory_4Gb) ||
|
||||
unified_memory != Settings::values.use_unified_memory.GetValue();
|
||||
|
||||
if (!must_reinitialize) {
|
||||
return;
|
||||
|
|
@ -156,6 +158,7 @@ struct System::Impl {
|
|||
is_multicore = Settings::values.use_multi_core.GetValue();
|
||||
extended_memory_layout =
|
||||
Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb;
|
||||
unified_memory = Settings::values.use_unified_memory.GetValue();
|
||||
|
||||
Initialize(system);
|
||||
}
|
||||
|
|
@ -503,6 +506,7 @@ struct System::Impl {
|
|||
std::atomic_bool is_powered_on{};
|
||||
bool is_multicore : 1 = false;
|
||||
bool extended_memory_layout : 1 = false;
|
||||
bool unified_memory : 1 = false;
|
||||
bool exit_locked : 1 = false;
|
||||
bool exit_requested : 1 = false;
|
||||
bool nvdec_active : 1 = false;
|
||||
|
|
|
|||
|
|
@ -58,7 +58,8 @@ void CoreTiming::Initialize(std::function<void()>&& on_thread_init_) {
|
|||
if (is_multicore) {
|
||||
timer_thread = std::jthread([this](std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName("HostTiming");
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::VeryHigh);
|
||||
Common::SetCurrentThreadToPerformanceCores();
|
||||
on_thread_init();
|
||||
has_started = true;
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue