diff --git a/source/blender/blenlib/BLI_winstuff.h b/source/blender/blenlib/BLI_winstuff.h index 0b3388d2b6b..bcf8659c22f 100644 --- a/source/blender/blenlib/BLI_winstuff.h +++ b/source/blender/blenlib/BLI_winstuff.h @@ -88,6 +88,12 @@ bool BLI_windows_update_pinned_launcher(const char *launcher_path); bool BLI_windows_get_directx_driver_version(const wchar_t *deviceSubString, long long *r_driverVersion); +/* Checks the version of the Windows build in the format "major.minor.build". + * Example: 10.0.22000 corresponds to Windows 11 21H2. */ +bool BLI_windows_is_build_version_greater_or_equal(DWORD majorVersion, + DWORD minorVersion, + DWORD buildNumber); + /** * Set the `root_dir` to the default root directory on MS-Windows, * The string is guaranteed to be set with a length of 3 & null terminated, @@ -113,3 +119,31 @@ bool BLI_windows_execute_self(const char *parameters, const bool wait, const bool elevated, const bool silent); + +/** Quality of Service (QoS) modes as defined in the Windows documentation at: + * https://learn.microsoft.com/en-us/windows/win32/procthread/quality-of-service */ +enum class QoSMode { + /** Default mode uses heuristics described in Windows docs. */ + DEFAULT = 0, + /** HighQoS mode for performance critical scenarios. */ + HIGH = 1, + /** EcoQoS mode for preserving energy. */ + ECO = 2 +}; + +/** QoS precedence (to make sure command line args overwrite what is set by jobs). + * Higher values have more precedence. */ +enum class QoSPrecedence { + /** QoS mode requested set via the job system. */ + JOB = 0, + /** QoS mode requested set via a command line argument. */ + CMDLINE_ARG = 1, +}; + +/** + * Sets the Quality of Service (QoS) mode of the process. + * + * \param qos_mode: The QoS mode to use for the process. + * \param qos_precedence: The precedence of the caller (higher wins). + */ +void BLI_windows_process_set_qos(QoSMode qos_mode, QoSPrecedence qos_precedence); diff --git a/source/blender/blenlib/intern/winstuff.cc b/source/blender/blenlib/intern/winstuff.cc index 7e419fda9dc..5ffe28963ea 100644 --- a/source/blender/blenlib/intern/winstuff.cc +++ b/source/blender/blenlib/intern/winstuff.cc @@ -516,6 +516,80 @@ bool BLI_windows_get_directx_driver_version(const wchar_t *deviceSubString, return false; } +bool BLI_windows_is_build_version_greater_or_equal(DWORD majorVersion, + DWORD minorVersion, + DWORD buildNumber) +{ + HMODULE hMod = ::GetModuleHandleW(L"ntdll.dll"); + if (hMod == 0) { + return false; + } + + typedef NTSTATUS(WINAPI * RtlGetVersionPtr)(PRTL_OSVERSIONINFOW); + RtlGetVersionPtr rtl_get_version = (RtlGetVersionPtr)::GetProcAddress(hMod, "RtlGetVersion"); + if (rtl_get_version == nullptr) { + fprintf(stderr, "BLI_windows_is_build_version_greater_or_equal: RtlGetVersion not found."); + return false; + } + + RTL_OSVERSIONINFOW osVersioninfo{}; + osVersioninfo.dwOSVersionInfoSize = sizeof(RTL_OSVERSIONINFOW); + if (rtl_get_version(&osVersioninfo) != 0) { + fprintf(stderr, "BLI_windows_is_build_version_greater_or_equal: RtlGetVersion failed."); + return false; + } + if (majorVersion != osVersioninfo.dwMajorVersion) { + return osVersioninfo.dwMajorVersion > majorVersion; + } + if (minorVersion != osVersioninfo.dwMinorVersion) { + return osVersioninfo.dwMajorVersion > minorVersion; + } + return osVersioninfo.dwBuildNumber >= buildNumber; +} + +void BLI_windows_process_set_qos(QoSMode qos_mode, QoSPrecedence qos_precedence) +{ + static QoSPrecedence qos_precedence_last = QoSPrecedence::JOB; + if (int(qos_precedence) < int(qos_precedence_last)) { + return; + } + + /* Only supported on Windows build >= 10.0.22000, i.e., Windows 11 21H2: + * https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/ne-processthreadsapi-process_information_class + */ + if (!BLI_windows_is_build_version_greater_or_equal(10, 0, 22000)) { + return; + } + + PROCESS_POWER_THROTTLING_STATE processPowerThrottlingState{}; + processPowerThrottlingState.Version = PROCESS_POWER_THROTTLING_CURRENT_VERSION; + switch (qos_mode) { + case QoSMode::DEFAULT: + processPowerThrottlingState.ControlMask = 0; + processPowerThrottlingState.StateMask = 0; + break; + case QoSMode::HIGH: + processPowerThrottlingState.ControlMask = PROCESS_POWER_THROTTLING_EXECUTION_SPEED; + processPowerThrottlingState.StateMask = 0; + break; + case QoSMode::ECO: + processPowerThrottlingState.ControlMask = PROCESS_POWER_THROTTLING_EXECUTION_SPEED; + processPowerThrottlingState.StateMask = PROCESS_POWER_THROTTLING_EXECUTION_SPEED; + break; + } + HANDLE hProcess = GetCurrentProcess(); + if (!SetProcessInformation(hProcess, + ProcessPowerThrottling, + &processPowerThrottlingState, + sizeof(PROCESS_POWER_THROTTLING_STATE))) + { + fprintf( + stderr, "BLI_windows_set_process_qos: SetProcessInformation failed: %d\n", GetLastError()); + return; + } + qos_precedence_last = qos_precedence; +} + #else /* intentionally empty for UNIX */ diff --git a/source/blender/windowmanager/WM_api.hh b/source/blender/windowmanager/WM_api.hh index 178457aa316..c79e9e8bce4 100644 --- a/source/blender/windowmanager/WM_api.hh +++ b/source/blender/windowmanager/WM_api.hh @@ -1850,6 +1850,7 @@ void WM_jobs_callbacks_ex(wmJob *wm_job, * * If the new \a wm_job is flagged with #WM_JOB_PRIORITY, it will request other blocking jobs to * stop (using #WM_jobs_stop(), so this doesn't take immediate effect) rather than finish its work. + * Additionally, it will hint the operating system to use performance cores on hybrid CPUs. */ void WM_jobs_start(wmWindowManager *wm, wmJob *wm_job); /** diff --git a/source/blender/windowmanager/intern/wm_jobs.cc b/source/blender/windowmanager/intern/wm_jobs.cc index 30ffd4ca4f9..c9588f42be6 100644 --- a/source/blender/windowmanager/intern/wm_jobs.cc +++ b/source/blender/windowmanager/intern/wm_jobs.cc @@ -14,12 +14,17 @@ #include "MEM_guardedalloc.h" +#include "BLI_build_config.h" #include "BLI_listbase.h" #include "BLI_string.h" #include "BLI_threads.h" #include "BLI_time.h" #include "BLI_utildefines.h" +#if OS_WINDOWS +# include "BLI_winstuff.h" +#endif + #include "BKE_global.hh" #include "BKE_report.hh" @@ -155,6 +160,20 @@ static void wm_job_main_thread_yield(wmJob *wm_job) BLI_ticket_mutex_lock(wm_job->main_thread_mutex); } +static void wm_jobs_update_qos(const wmWindowManager *wm) +{ + /* A QoS API is currently only available for Windows. */ +#if OS_WINDOWS + LISTBASE_FOREACH (wmJob *, wm_job, &wm->jobs) { + if (wm_job->flag & WM_JOB_PRIORITY) { + BLI_windows_process_set_qos(QoSMode::HIGH, QoSPrecedence::JOB); + return; + } + } + + BLI_windows_process_set_qos(QoSMode::DEFAULT, QoSPrecedence::JOB); +#endif +} /** * Finds if type or owner, compare for it, otherwise any matching job. */ @@ -212,6 +231,8 @@ wmJob *WM_jobs_get(wmWindowManager *wm, wm_job->worker_status.reports = MEM_callocN(__func__); BKE_reports_init(wm_job->worker_status.reports, RPT_STORE | RPT_PRINT); BKE_report_print_level_set(wm_job->worker_status.reports, RPT_WARNING); + + wm_jobs_update_qos(wm); } /* Else: a running job, be careful. */ @@ -538,6 +559,8 @@ static void wm_job_free(wmWindowManager *wm, wmJob *wm_job) BKE_reports_free(wm_job->worker_status.reports); MEM_delete(wm_job->worker_status.reports); MEM_freeN(wm_job); + + wm_jobs_update_qos(wm); } /* Stop job, end thread, free data completely. */ diff --git a/source/creator/creator_args.cc b/source/creator/creator_args.cc index d08641a7652..e3968a387d3 100644 --- a/source/creator/creator_args.cc +++ b/source/creator/creator_args.cc @@ -818,6 +818,7 @@ static void print_help(bArgs *ba, bool all) BLI_args_print_arg_doc(ba, "--register-allusers"); BLI_args_print_arg_doc(ba, "--unregister"); BLI_args_print_arg_doc(ba, "--unregister-allusers"); + BLI_args_print_arg_doc(ba, "--qos"); BLI_args_print_arg_doc(ba, "--version"); @@ -1980,6 +1981,42 @@ static int arg_handle_unregister_extension_all(int argc, const char **argv, void return 0; } +static const char arg_handle_qos_set_doc[] = + "\n" + "\tSet the Quality of Service (QoS) mode for hybrid CPU architectures (Windows only).\n" + "\n" + "\tdefault: Uses the default behavior of the OS.\n" + "\thigh: Always makes use of performance cores.\n" + "\teco: Schedules Blender threads exclusively to efficiency cores."; +static int arg_handle_qos_set(int argc, const char **argv, void * /*data*/) +{ + const char *arg_id = "--qos"; + if (argc > 1) { +# ifdef _WIN32 + QoSMode qos_mode; + if (STRCASEEQ(argv[1], "default")) { + qos_mode = QoSMode::DEFAULT; + } + else if (STRCASEEQ(argv[1], "high")) { + qos_mode = QoSMode::HIGH; + } + else if (STRCASEEQ(argv[1], "eco")) { + qos_mode = QoSMode::ECO; + } + else { + fprintf(stderr, "\nError: Invalid QoS level '%s %s'.\n", arg_id, argv[1]); + return 1; + } + BLI_windows_process_set_qos(qos_mode, QoSPrecedence::CMDLINE_ARG); +# else + fprintf(stderr, "\nError: '%s' is Windows only.\n", arg_id); +# endif + return 1; + } + fprintf(stderr, "\nError: '%s' no args given.\n", arg_id); + return 0; +} + static const char arg_handle_audio_disable_doc[] = "\n\t" "Force sound system to None."; @@ -2835,6 +2872,8 @@ void main_args_setup(bContext *C, bArgs *ba, bool all) /* Command implies background mode (defers execution). */ BLI_args_add(ba, "-c", "--command", CB(arg_handle_command_set), C); + BLI_args_add(ba, nullptr, "--qos", CB(arg_handle_qos_set), nullptr); + BLI_args_add(ba, nullptr, "--disable-depsgraph-on-file-load", diff --git a/tests/performance/api/environment.py b/tests/performance/api/environment.py index cfde4050f08..568c8a092bf 100644 --- a/tests/performance/api/environment.py +++ b/tests/performance/api/environment.py @@ -244,6 +244,10 @@ class TestEnvironment: def call_blender(self, args: list[str], foreground=False) -> list[str]: # Execute Blender command with arguments. common_args = ['--factory-startup', '-noaudio', '--enable-autoexec', '--python-exit-code', '1'] + if sys.platform == 'win32': + # Set HighQoS level on Windows to avoid reduced performance when the window is out of focus. + # See: https://learn.microsoft.com/en-us/windows/win32/procthread/quality-of-service + common_args += ['--qos', 'high'] if foreground: common_args += ['--no-window-focus', '--window-geometry', '0', '0', '1024', '768', '--gpu-vsync', 'off'] else: