From b7bfbc3fcdb7ec76a65bd13f8111a532a72263cd Mon Sep 17 00:00:00 2001 From: Ryan Houdek Date: Fri, 8 May 2026 13:39:51 -0700 Subject: [PATCH] Windows: Setup `tu_override_uncached_as_cache_coherent` inside of dlls To not have this environment variable accidently be enabled on arm64 native Wine games, we need to set it from inside of FEX. Requires the FEX dlls to set them directly rather than launch scripts. --- Source/Windows/ARM64EC/Module.cpp | 3 ++ Source/Windows/Common/CMakeLists.txt | 1 + .../Common/EnvironmentVariablesHandling.cpp | 32 +++++++++++++++++++ .../Common/EnvironmentVariablesHandling.h | 7 ++++ Source/Windows/WOW64/Module.cpp | 3 ++ Source/Windows/include/wine/unixlib.h | 2 ++ 6 files changed, 48 insertions(+) create mode 100644 Source/Windows/Common/EnvironmentVariablesHandling.cpp create mode 100644 Source/Windows/Common/EnvironmentVariablesHandling.h diff --git a/Source/Windows/ARM64EC/Module.cpp b/Source/Windows/ARM64EC/Module.cpp index a112a28a6..241da1038 100644 --- a/Source/Windows/ARM64EC/Module.cpp +++ b/Source/Windows/ARM64EC/Module.cpp @@ -28,6 +28,7 @@ $end_info$ #include #include "Windows/Common/Allocator.h" +#include "Windows/Common/EnvironmentVariablesHandling.h" #include "Common/CallRetStack.h" #include "Common/JITGuardPage.h" #include "Common/Config.h" @@ -595,6 +596,8 @@ NTSTATUS ProcessInit() { const bool IsWine = !!GetProcAddress(NtDll, "wine_get_version"); OvercommitTracker.emplace(IsWine); + FEX::Windows::SetupEnvironmentVariableValues(NtDll); + FEX::Windows::Allocator::SetupHooks(NtDll); { diff --git a/Source/Windows/Common/CMakeLists.txt b/Source/Windows/Common/CMakeLists.txt index d7e2da9c7..3a0a71c07 100644 --- a/Source/Windows/Common/CMakeLists.txt +++ b/Source/Windows/Common/CMakeLists.txt @@ -9,6 +9,7 @@ target_include_directories(CommonWindowsRuntime PRIVATE "${CMAKE_SOURCE_DIR}/Sou add_library(CommonWindows STATIC Allocator.cpp CPUFeatures.cpp + EnvironmentVariablesHandling.cpp SHMStats.cpp InvalidationTracker.cpp ImageTracker.cpp diff --git a/Source/Windows/Common/EnvironmentVariablesHandling.cpp b/Source/Windows/Common/EnvironmentVariablesHandling.cpp new file mode 100644 index 000000000..9d8a0483b --- /dev/null +++ b/Source/Windows/Common/EnvironmentVariablesHandling.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +#include +#include +#include +#include + +namespace FEX::Windows { +void SetupEnvironmentVariableValues(HMODULE NtDll) { + auto Sym = GetProcAddress(NtDll, "__wine_set_unix_env"); + + // On an ARM system we are expecting uncached memory to have marked overhead due to atomic memory usage. + // If `tu_override_uncached_as_cache_coherent` isn't already controlled by the user, then set it here. + // This lets the `turnip` mesa driver allocate uncached memory types as cached coherent instead if the hardware supports it. + // + // The only downside for this approach is that the GPU /may/ need to snoop CPU cachelines which could add some additional load + // on to the GPU side, but is unlikely to matter due to x86 emulation using excessive load-acquire/store-release semantic memory operations. + // + // Other UMA ARM platforms will run in to the same problem, with their own workarounds: + // - NVIDIA Tegra/Spark: Their proprietary driver always maps uncached as cached-coherent. + // - Windows Snapdragon: Same as Tegra + // - Asahi,Mali,PowerVR,Exynos,etc: Unknown behaviour + // + // This `__wine_set_unix_env` path may also not be long-term viable. + // Make sure to communicate with the Wine/Proton people if this interface is expected to change! + // A /possible/ workaround for Proton would be to set the environment variable in the launch script. + // - That would have the downside that arm64 native Windows applications would get workaround set, which isn't strictly necessary. + if (Sym && getenv("tu_override_uncached_as_cache_coherent") == nullptr) { + auto WineSetEnv = reinterpret_cast(Sym); + WineSetEnv("tu_override_uncached_as_cache_coherent", "true"); + } +} +} // namespace FEX::Windows diff --git a/Source/Windows/Common/EnvironmentVariablesHandling.h b/Source/Windows/Common/EnvironmentVariablesHandling.h new file mode 100644 index 000000000..bb7aeecfc --- /dev/null +++ b/Source/Windows/Common/EnvironmentVariablesHandling.h @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT +#pragma once +#include + +namespace FEX::Windows { +void SetupEnvironmentVariableValues(HMODULE NtDll); +} diff --git a/Source/Windows/WOW64/Module.cpp b/Source/Windows/WOW64/Module.cpp index e4772ffea..d5ba1853e 100644 --- a/Source/Windows/WOW64/Module.cpp +++ b/Source/Windows/WOW64/Module.cpp @@ -29,6 +29,7 @@ $end_info$ #include #include "Windows/Common/Allocator.h" +#include "Windows/Common/EnvironmentVariablesHandling.h" #include "Common/CallRetStack.h" #include "Common/JITGuardPage.h" #include "Common/Config.h" @@ -563,6 +564,8 @@ void BTCpuProcessInit() { WineUnixCall = *reinterpret_cast(Sym); } + FEX::Windows::SetupEnvironmentVariableValues(NtDll); + // wow64.dll will only initialise the cross-process queue if this is set GetTLS().Wow64Info().CpuFlags = WOW64_CPUFLAGS_SOFTWARE; diff --git a/Source/Windows/include/wine/unixlib.h b/Source/Windows/include/wine/unixlib.h index 1b8f39929..fd77405b6 100644 --- a/Source/Windows/include/wine/unixlib.h +++ b/Source/Windows/include/wine/unixlib.h @@ -11,6 +11,8 @@ extern "C" { typedef UINT64 unixlib_handle_t; +extern NTSTATUS(WINAPI* __wine_set_unix_env)(const char*, const char*); + extern NTSTATUS(WINAPI* __wine_unix_call_dispatcher)(unixlib_handle_t, unsigned int, void*); static inline NTSTATUS __wine_unix_call(unixlib_handle_t handle, unsigned int code, void* args) {