Compare commits

...
Sign in to create a new pull request.
Author SHA1 Message Date
CamilleLaVey
751dd8546e Smol change 2026-08-03 18:38:53 -04:00
CamilleLaVey
67a54e7357 Some adjustments on overlapped images not recoverted, relaxed due dynamic resolution 2026-08-03 17:29:31 -04:00
CamilleLaVey
2215455617 Another pair of fixes on nvgpu 2026-08-02 03:44:54 -04:00
CamilleLaVey
bbb75f8dd4 Some adjustments between buffer history and ZBC Table 2026-08-02 03:07:06 -04:00
CamilleLaVey
b8f384f3bd Adjustments on the memory reclamations 2 2026-08-02 01:04:19 -04:00
CamilleLaVey
4b209db82c Request maximum map counts 2026-08-01 23:20:41 -04:00
CamilleLaVey
c232624b8b Extend UMA to async path 2026-08-01 22:37:22 -04:00
CamilleLaVey
a686b50333 3rd step on UMA implementation 2026-08-01 22:04:31 -04:00
CamilleLaVey
1507074346 Refactor Android CPU affinity/threading 2026-08-01 20:49:41 -04:00
CamilleLaVey
332d0b6bad Revert "debug uma impl" 2026-08-01 19:16:20 -04:00
CamilleLaVey
e2984caedc Revert "MEOW" 2026-08-01 19:09:27 -04:00
CamilleLaVey
50763528a2 MEOW 2026-08-01 03:41:13 -04:00
CamilleLaVey
4b3608b58d debug uma impl 2026-08-01 03:07:57 -04:00
CamilleLaVey
b76d04e47d ASTC adjustments 2026-08-01 02:41:09 -04:00
CamilleLaVey
71afaff6b3 Another try 2026-08-01 00:03:29 -04:00
CamilleLaVey
dcbfa3c2d3 Meow gidolio 2.0 2026-07-31 23:14:58 -04:00
CamilleLaVey
70ff43f2f8 Meow gidolio 2026-07-31 23:10:31 -04:00
CamilleLaVey
76faac9252 shader quad control impl 2026-07-31 23:06:39 -04:00
CamilleLaVey
6346371725 Some memory corrections 2026-07-31 22:52:42 -04:00
CamilleLaVey
4162f86a11 debug overlap images 2 2026-07-31 21:33:49 -04:00
CamilleLaVey
86224e49ef Test on depth bias 2026-07-31 20:46:07 -04:00
CamilleLaVey
6665a89dd2 Fix build 2026-07-31 19:55:28 -04:00
CamilleLaVey
515afa2959 Initial Implementation AHB 2026-07-31 19:46:04 -04:00
CamilleLaVey
8c8c7c6162 Adjustments on UMA 2026-07-31 19:30:31 -04:00
CamilleLaVey
fb65aa3c33 2nd step on the bindless implementation 2026-07-31 18:35:17 -04:00
CamilleLaVey
a24ad03ff3 Android toggle for uma 2026-07-31 18:10:45 -04:00
CamilleLaVey
4884a0c002 Initial implementation on unified memory access 2026-07-31 18:03:49 -04:00
CamilleLaVey
ab1d99d218 some debugs tools 2026-07-31 17:31:43 -04:00
CamilleLaVey
e6ee576f80 Quick shenanigan 2026-07-31 05:45:27 -04:00
CamilleLaVey
1682b532da relax memory and draws dispatchs 2026-07-31 05:09:14 -04:00
CamilleLaVey
40fe050954 Quick change for cheats 2026-07-31 04:58:17 -04:00
CamilleLaVey
24ff23f2ea Quick change on pipeline barriers 2026-07-31 04:17:38 -04:00
CamilleLaVey
87ebbfb9b1 Coalescing memory entries on MLP 2026-07-31 04:11:40 -04:00
CamilleLaVey
471778c850 WEML toggle 2026-07-31 03:12:22 -04:00
CamilleLaVey
095f27fd34 Another try with 3D BCn 2026-07-31 03:05:52 -04:00
CamilleLaVey
a6c423cafb Smolio jenkins 2026-07-31 02:19:41 -04:00
CamilleLaVey
514507b674 fix build 2026-07-31 02:13:19 -04:00
CamilleLaVey
10935f8d46 playing with isberd 2026-07-31 02:11:28 -04:00
CamilleLaVey
a0dae2d490 Compute shader 3D BCn to VK 2026-07-31 01:41:00 -04:00
CamilleLaVey
5e40e3c9c0 GIDOLIO THE KOREAN MENACE 2026-07-31 00:57:05 -04:00
CamilleLaVey
f61d05f8da HERE WE GO AGAIN. 2026-07-31 00:45:18 -04:00
CamilleLaVey
b04207fdfc Adjustments on the plain data for vertex buffer optimization 2026-07-30 23:54:26 -04:00
CamilleLaVey
91791bd5fe Meow Gidolius Galacticus 2026-07-30 20:36:44 -04:00
CamilleLaVey
9928885a5d DynamicRendering + Pipeline Worker Thread toggles 2026-07-30 20:24:50 -04:00
CamilleLaVey
81fa91b400 Another funny change for pipelines 2026-07-30 20:15:11 -04:00
CamilleLaVey
eb8706e366 Funny experiment for pipelines 2026-07-30 19:53:34 -04:00
CamilleLaVey
b9d55c9448 2nd step for the buffer address device implementation 2026-07-30 19:29:19 -04:00
CamilleLaVey
1188f285b7 Pipeline Creation Feedback impl 2026-07-30 18:57:15 -04:00
CamilleLaVey
200b81b087 Initial step for the buffer address device implementation 2026-07-30 18:28:24 -04:00
CamilleLaVey
742cadde1d Keep conditional rendering inside a single render pass instance
Begin and end were landing in different scopes: the clear and draw
texture paths opened it with no pass bound, the compare helpers reopened
it after their resolve pass had closed one, and the pass teardown could
return before pausing, leaving it active at submit.
2026-07-30 18:13:56 -04:00
CamilleLaVey
c3effdf558 Use per-barrier stage masks on render pass end
Adds a native VkDependencyInfo entry point and splits the end of pass
barrier so color and depth/stencil attachments no longer wait on each
other's stages.
2026-07-30 17:09:21 -04:00
CamilleLaVey
b9428acf12 Cut per-draw descriptor set work
Only query the timeline semaphore when no set looks free, and skip the
update plus rebind when the payload matches the previous draw.
2026-07-30 17:09:21 -04:00
CamilleLaVey
b027dc42ce Restore conditional rendering workaround for Adreno, Mali and Turnip 2026-07-30 16:54:30 -04:00
CamilleLaVey
dc9c99b22d Revert "[TEST] Keep tuning the drain/ memory allocation"
This reverts commit 7f4529b15d.
2026-07-30 16:53:25 -04:00
CamilleLaVey
5346e77c0a Revert "[TEST] Adjust mutable formats usage"
This reverts commit 328a2c1081.
2026-07-30 16:45:08 -04:00
CamilleLaVey
914b78495c Revert "[TEST] Change on storage views per request instead of always granted"
This reverts commit d1eb08c036.
2026-07-30 16:45:03 -04:00
CamilleLaVey
a17b4cbb04 Revert "[TEST] New optimal image layouts access"
This reverts commit 236696b0ff.
2026-07-30 16:44:37 -04:00
CamilleLaVey
a7b5910eaa Revert "[TEST] Adjustments on storage view + general image layouts"
This reverts commit 88fcb7d338.
2026-07-30 16:44:10 -04:00
CamilleLaVey
f98f8b65de Some trivial tests 2026-07-30 15:57:59 -04:00
CamilleLaVey
88fcb7d338 [TEST] Adjustments on storage view + general image layouts 2026-07-30 15:13:04 -04:00
CamilleLaVey
4a4fd056ff enable msaa depth resolve 2026-07-29 20:57:20 -04:00
CamilleLaVey
236696b0ff [TEST] New optimal image layouts access 2026-07-29 20:56:56 -04:00
CamilleLaVey
d1eb08c036 [TEST] Change on storage views per request instead of always granted 2026-07-29 20:49:36 -04:00
CamilleLaVey
84a4149db2 Presync fix again 2026-07-29 19:46:51 -04:00
CamilleLaVey
de9b1071a5 [TEST] VMA budget fix 2026-07-29 19:41:38 -04:00
CamilleLaVey
85049c2c78 [TEST] Presync + CR workaround removal from QCOM 2026-07-29 19:09:22 -04:00
CamilleLaVey
7fcf88cb10 [TEST] DR Adjustment 2 2026-07-29 17:28:09 -04:00
CamilleLaVey
e4f29899d2 [TEST] Adjustment memory reclamation 2 2026-07-29 16:32:54 -04:00
CamilleLaVey
41d2bf4a3e Fix build 2026-07-29 14:52:06 -04:00
CamilleLaVey
328a2c1081 [TEST] Adjust mutable formats usage 2026-07-29 14:41:36 -04:00
CamilleLaVey
6e48eeb09c [TEST] depth stencil resolve implementation + fixes 2026-07-29 14:11:30 -04:00
CamilleLaVey
2cdd7173a9 [TEST] Line/ conservative rasterization adjustment 2026-07-29 13:27:31 -04:00
CamilleLaVey
69342044ef [TEST] Quick corrections 2026-07-29 13:27:31 -04:00
CamilleLaVey
1f41d809b0 [TEST] depth clip enable implementation 2026-07-29 13:27:31 -04:00
CamilleLaVey
250b545d75 [TEST] Debug viewport geometry 2026-07-29 13:27:31 -04:00
CamilleLaVey
5a9322af80 [TEST] Debug depths on viewports 2026-07-29 13:27:31 -04:00
CamilleLaVey
bbee229d07 [TEST] Adjustments on memory reclamations 2026-07-29 13:27:31 -04:00
CamilleLaVey
cfe1c8b16c [TEST] Rework Android CPU affinity/ threading 2 2026-07-29 13:27:31 -04:00
CamilleLaVey
dd5d09e3ae [TEST] Rework Android CPU affinity/ threading 2026-07-29 13:27:25 -04:00
CamilleLaVey
25af655e48 [TEST] Adjustments on TransformFeedback 2026-07-29 13:26:55 -04:00
CamilleLaVey
0cd5dd1e70 [TEST] Adjustments on framepacing submit calls 2026-07-29 13:26:23 -04:00
CamilleLaVey
8c828e78f2 [TEST] Refinements on RAII 2026-07-29 13:26:23 -04:00
CamilleLaVey
b1adbd8e6b [TEST] ASTC fixes 2026-07-29 13:26:23 -04:00
CamilleLaVey
0fb0592a5f [TEST] Miscellaneous changes 2026-07-29 13:26:23 -04:00
CamilleLaVey
b59dda3af6 Lizzie get meowed, please. 2026-07-29 13:26:23 -04:00
CamilleLaVey
aec20586be [TEST] Adjusments on deferred destruction implementation 2026-07-29 13:26:23 -04:00
CamilleLaVey
6918f034e2 [TEST] Overlap image on non implemented path 2026-07-29 13:26:23 -04:00
CamilleLaVey
4b1e2d1d6a [TEST] Debug 3D image copy 2026-07-29 13:26:23 -04:00
CamilleLaVey
9318a508f7 [TEST] Fallback on TICEntry out of bounds 2026-07-29 13:26:23 -04:00
CamilleLaVey
f95a9f3901 Meow 2026-07-29 13:26:23 -04:00
CamilleLaVey
1169533a74 [TEST] Debug TICEntry shader pass 2026-07-29 13:26:23 -04:00
CamilleLaVey
991ff44bd6 [TEST] Adjust ternary on pipeline barriers 2026-07-29 13:26:23 -04:00
CamilleLaVey
624f955821 [TEST] Upload on depth MSAACopy pass 2026-07-29 13:26:23 -04:00
CamilleLaVey
5c0fe5da06 [TEST] Adjustments on descriptors payloads 2026-07-29 13:26:23 -04:00
CamilleLaVey
18630fa739 [TEST] Smolios forever 2026-07-29 13:26:23 -04:00
CamilleLaVey
9752908030 [TEST] Test anti-smolios 2026-07-29 13:26:23 -04:00
CamilleLaVey
a711fd99a6 [TEST] debug MSAA texture missing handling 2026-07-29 13:26:23 -04:00
CamilleLaVey
f6c1404e09 Revert "[TEST] Some miscellaneous changes within buffers queue and RAII deallocations" 2026-07-29 13:26:23 -04:00
CamilleLaVey
77ffefbbbf Revert "[TEST] Another miscellaneous change 2" 2026-07-29 13:26:23 -04:00
CamilleLaVey
bf13e7ce1d [TEST] Another miscellaneous change 2 2026-07-29 13:26:23 -04:00
CamilleLaVey
3cd17ec392 [TEST] Some miscellaneous changes within buffers queue and RAII deallocations 2026-07-29 13:26:22 -04:00
CamilleLaVey
7f4529b15d [TEST] Keep tuning the drain/ memory allocation 2026-07-29 13:26:22 -04:00
CamilleLaVey
f649730cb1 Fix build 2026-07-29 13:26:22 -04:00
CamilleLaVey
40c91abdad Fix license headers 2026-07-29 13:26:22 -04:00
CamilleLaVey
d21bb9a1b7 [TEST] Adjustments on memory collection 2026-07-29 13:26:22 -04:00
CamilleLaVey
94e814ecf1 [TEST] Adjustments on memory collection + removal dead code 2026-07-29 13:26:22 -04:00
CamilleLaVey
b8135a3e47 [TEST] Refactor on memory collections 2026-07-29 13:26:22 -04:00
CamilleLaVey
8d7f992792 Enable DR 2026-07-29 13:22:20 -04:00
CamilleLaVey
f199597158 [TEST] DR Off 2026-07-17 21:03:17 -04:00
CamilleLaVey
9c313fb787 [TEST] Caching for texture + pages on NCE 2026-07-17 20:15:19 -04:00
CamilleLaVey
eec29b83f3 [TEST] Coalesce NCE fault write. 2026-07-17 19:56:16 -04:00
CamilleLaVey
a57041d62f [TEST] Discards on MSAA depth/stencil 2026-07-17 17:57:55 -04:00
CamilleLaVey
4956bc86c3 [TEST] Hunting down recursive mutex 7 2026-07-17 17:44:24 -04:00
CamilleLaVey
e8b1dc7c0b [TEST] Adjustments on CommandPools + ResetQueryPool 2026-07-17 07:52:45 -04:00
CamilleLaVey
e81d170458 [TEST] Remove unnecessary memory upload 2026-07-17 07:28:30 -04:00
CamilleLaVey
7ed7e5e31d [TEST] Remove unnecessary submit 2026-07-17 03:56:02 -04:00
CamilleLaVey
eb32b8766a [TEST] Hunting down recursive mutex 6 2026-07-17 03:13:55 -04:00
CamilleLaVey
a484e6c34b [TEST] Hunting down recursive mutex 5 2026-07-17 03:00:53 -04:00
CamilleLaVey
84490a7d6f [TEST] Hunting down recursive mutex 4 2026-07-17 02:32:14 -04:00
CamilleLaVey
933f79af95 [TEST] Hunting down recursive mutex 3 2026-07-17 02:15:10 -04:00
CamilleLaVey
f532357793 [TEST] Hunting down recursive mutex 2 2026-07-17 01:55:28 -04:00
CamilleLaVey
ab92e5fa52 [TEST] Hunting down recursive mutex 1 2026-07-17 01:13:15 -04:00
CamilleLaVey
791880f9bf [TEST] Adjust records/commandBuffer 2026-07-16 22:50:08 -04:00
CamilleLaVey
e058a15074 [TEST] Wire MSAA resolve to dynamic rendering native resolve 2026-07-16 20:29:18 -04:00
CamilleLaVey
6313800aee [TEST] Check on color components for masks/blending 2026-07-16 20:02:59 -04:00
CamilleLaVey
5428dbbd14 [TEST] Adjustments on blit/clears per blending object 2026-07-16 17:04:32 -04:00
CamilleLaVey
9694216ad7 [TEST] 2nd stage on dynamic rendering implementation 2026-07-16 16:53:22 -04:00
CamilleLaVey
4fbdc133dd [vulkan] Add resume/supend bits on renderpass 2026-07-16 15:50:36 -04:00
CamilleLaVey
f210f16e8c [TEST] Debug on clears made by driver 2026-07-16 15:47:23 -04:00
CamilleLaVey
20bf6bc282 [vulkan] Adjustment on the framebuffer use vs non used attachments 2026-07-16 15:45:49 -04:00
CamilleLaVey
99e95eebb1 [vulkan] Initial dynamic rendering implementation 2026-07-16 15:44:06 -04:00
116 changed files with 6398 additions and 1285 deletions

View file

@ -65,7 +65,7 @@ android {
defaultConfig {
applicationId = "dev.eden.eden_emulator"
minSdk = 24
minSdk = 33
targetSdk = 36
versionName = getGitVersion()
versionCode = autoVersion

View file

@ -218,6 +218,8 @@ object NativeLibrary {
external fun logSettings()
external fun refreshThreadPolicies()
external fun getDebugKnobAt(index: Int): Boolean
/**

View file

@ -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"),

View file

@ -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,

View file

@ -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))

View file

@ -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())

View file

@ -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",

View file

@ -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)));
}

View file

@ -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>

View file

@ -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) {

View file

@ -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;
}

View file

@ -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

View file

@ -18,4 +18,8 @@ struct MemoryInfo {
*/
[[nodiscard]] const MemoryInfo& GetMemInfo();
[[nodiscard]] u64 GetAvailablePhysicalMemory();
[[nodiscard]] u64 GetMaxMapCount();
} // namespace Common

View file

@ -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};

View file

@ -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(&current);
if (sched_getaffinity(getpid(), sizeof(current), &current) != 0) {
return;
}
if (std::memcmp(&current, &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

View file

@ -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

View file

@ -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()) {

View file

@ -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;
}

View file

@ -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;

View file

@ -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