Fix #143653: Add use of Quality of Service (QoS) API on Windows
This PR adds code for setting the Quality of Service (QoS) level of the process on Windows. This can, e.g., make sure that on hybrid systems P-cores are utilized even when the app window is out of focus. In the following cases, it is adjusted from the default behavior: - In wm_jobs.cc the QoS level is raised while a priority job is running. - The command line option `--qos [high|eco|default]` can be used to change the QoS level of the process. - By default, the QoS level is raised for the EEVEE performance tests, as they check viewport rendering performance and would otherwise be reliant on never going out of focus to not get a downgraded QoS level. By default, they are created with an out of focus window at the time of landing this PR. This PR makes sure that they actually measure the animation replay performance attainable during real-world use. Pull Request: https://projects.blender.org/blender/blender/pulls/144224
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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);
|
||||
/**
|
||||
|
||||
@@ -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<ReportList>(__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. */
|
||||
|
||||
@@ -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[] =
|
||||
"<level>\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",
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user