From c90434962f6a03103a5483808350b63f7d7d6411 Mon Sep 17 00:00:00 2001 From: Wiseguy <68165316+Mr-Wiseguy@users.noreply.github.com> Date: Sun, 28 Jul 2024 22:00:39 -0400 Subject: [PATCH] Preload executable into memory on Windows to prevent stutters (#450) --- src/main/main.cpp | 119 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/src/main/main.cpp b/src/main/main.cpp index 99d940b..e3dd529 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -416,8 +416,123 @@ namespace zelda64 { } } +#ifdef _WIN32 + +struct PreloadContext { + HANDLE handle; + HANDLE mapping_handle; + SIZE_T size; + PVOID view; +}; + +bool preload_executable(PreloadContext& context) { + wchar_t module_name[MAX_PATH]; + GetModuleFileNameW(NULL, module_name, MAX_PATH); + + context.handle = CreateFileW(module_name, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (context.handle == INVALID_HANDLE_VALUE) { + fprintf(stderr, "Failed to load executable into memory!"); + context = {}; + return false; + } + + LARGE_INTEGER module_size; + if (!GetFileSizeEx(context.handle, &module_size)) { + fprintf(stderr, "Failed to get size of executable!"); + CloseHandle(context.handle); + context = {}; + return false; + } + + context.size = module_size.QuadPart; + + context.mapping_handle = CreateFileMappingW(context.handle, nullptr, PAGE_READONLY, 0, 0, nullptr); + if (context.mapping_handle == nullptr) { + recompui::message_box("Failed to create file mapping of executable!"); + CloseHandle(context.handle); + context = {}; + return EXIT_FAILURE; + } + + context.view = MapViewOfFile(context.mapping_handle, FILE_MAP_READ, 0, 0, 0); + if (context.view == nullptr) { + fprintf(stderr, "Failed to map view of of executable!"); + CloseHandle(context.mapping_handle); + CloseHandle(context.handle); + context = {}; + return false; + } + + DWORD pid = GetCurrentProcessId(); + HANDLE process_handle = OpenProcess(PROCESS_SET_QUOTA | PROCESS_QUERY_INFORMATION, FALSE, pid); + if (process_handle == nullptr) { + fprintf(stderr, "Failed to open own process!"); + CloseHandle(context.mapping_handle); + CloseHandle(context.handle); + context = {}; + return false; + } + + SIZE_T minimum_set_size, maximum_set_size; + if (!GetProcessWorkingSetSize(process_handle, &minimum_set_size, &maximum_set_size)) { + fprintf(stderr, "Failed to get working set size!"); + CloseHandle(context.mapping_handle); + CloseHandle(context.handle); + context = {}; + return false; + } + + if (!SetProcessWorkingSetSize(process_handle, minimum_set_size + context.size, maximum_set_size + context.size)) { + fprintf(stderr, "Failed to set working set size!"); + CloseHandle(context.mapping_handle); + CloseHandle(context.handle); + context = {}; + return false; + } + + if (VirtualLock(context.view, context.size) == 0) { + fprintf(stderr, "Failed to lock view of executable! (Error: %08lx)\n", GetLastError()); + CloseHandle(context.mapping_handle); + CloseHandle(context.handle); + context = {}; + return false; + } + + return true; +} + +void release_preload(PreloadContext& context) { + VirtualUnlock(context.view, context.size); + CloseHandle(context.mapping_handle); + CloseHandle(context.handle); + context = {}; +} + +#else + +struct PreloadContext { + +}; + +// TODO implement on other platforms +bool preload_executable(PreloadContext& context) { + return false; +} + +void release_preload(PreloadContext& context) { +} + +#endif int main(int argc, char** argv) { + // Map this executable into memory and lock it, which should keep it in physical memory. This ensures + // that there are no stutters from the OS having to load new pages of the executable whenever a new code page is run. + PreloadContext preload_context; + bool preloaded = preload_executable(preload_context); + + if (!preloaded) { + fprintf(stderr, "Failed to preload executable!\n"); + } #ifdef _WIN32 // Set up console output to accept UTF-8 on windows @@ -518,5 +633,9 @@ int main(int argc, char** argv) { NFD_Quit(); + if (preloaded) { + release_preload(preload_context); + } + return EXIT_SUCCESS; }