Files
test2/source/blender/blenlib/intern/winstuff.cc
Christoph Neuhauser 1e523e2f5d 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
2025-09-01 11:19:17 +02:00

598 lines
18 KiB
C++

/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bli
* WIN32-POSIX compatibility layer, MS-Windows-specific functions.
*/
#ifdef WIN32
# include <conio.h>
# include <shlwapi.h>
# include <stdio.h>
# include <stdlib.h>
# define COBJMACROS /* Remove this when converting to C++ */
# include <dxgi.h>
# include "MEM_guardedalloc.h"
# define WIN32_SKIP_HKEY_PROTECTION /* Need to use HKEY. */
# include "BLI_fileops.h"
# include "BLI_path_utils.hh"
# include "BLI_string.h"
# include "BLI_utildefines.h"
# include "BLI_winstuff.h"
# include "utf_winfunc.hh"
# include "utfconv.hh"
/* FILE_MAXDIR + FILE_MAXFILE */
int BLI_windows_get_executable_dir(char r_dirpath[/*FILE_MAXDIR*/])
{
char filepath[FILE_MAX];
char dir[FILE_MAX];
int a;
/* Change to UTF support. */
GetModuleFileName(nullptr, filepath, sizeof(filepath));
BLI_path_split_dir_part(filepath, dir, sizeof(dir)); /* shouldn't be relative */
a = strlen(dir);
if (dir[a - 1] == '\\') {
dir[a - 1] = 0;
}
BLI_strncpy(r_dirpath, dir, FILE_MAXDIR);
return 1;
}
bool BLI_windows_is_store_install(void)
{
char install_dir[FILE_MAXDIR];
BLI_windows_get_executable_dir(install_dir);
return (BLI_strcasestr(install_dir, "\\WindowsApps\\") != nullptr);
}
static void registry_error(HKEY root, const char *message)
{
if (root) {
RegCloseKey(root);
}
fprintf(stderr, "%s\n", message);
}
static bool open_registry_hive(bool all_users, HKEY *r_root)
{
if (RegOpenKeyEx(all_users ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER,
"Software\\Classes",
0,
KEY_ALL_ACCESS,
r_root) != ERROR_SUCCESS)
{
registry_error(*r_root, "Unable to open the registry with the required permissions");
return false;
}
return true;
}
static bool register_blender_prog_id(const char *prog_id,
const char *executable,
const char *friendly_name,
bool all_users)
{
LONG lresult;
HKEY root = 0;
HKEY hkey_progid = 0;
char buffer[256];
DWORD dwd = 0;
if (!open_registry_hive(all_users, &root)) {
return false;
}
lresult = RegCreateKeyEx(root,
prog_id,
0,
nullptr,
REG_OPTION_NON_VOLATILE,
KEY_ALL_ACCESS,
nullptr,
&hkey_progid,
&dwd);
if (lresult == ERROR_SUCCESS) {
lresult = RegSetValueEx(
hkey_progid, nullptr, 0, REG_SZ, (BYTE *)friendly_name, strlen(friendly_name) + 1);
}
if (lresult == ERROR_SUCCESS) {
lresult = RegSetValueEx(
hkey_progid, "AppUserModelId", 0, REG_SZ, (BYTE *)prog_id, strlen(prog_id) + 1);
}
if (lresult != ERROR_SUCCESS) {
registry_error(root, "Unable to register Blender App Id");
return false;
}
SNPRINTF(buffer, "%s\\shell\\open", prog_id);
lresult = RegCreateKeyEx(root,
buffer,
0,
nullptr,
REG_OPTION_NON_VOLATILE,
KEY_ALL_ACCESS,
nullptr,
&hkey_progid,
&dwd);
lresult = RegSetValueEx(
hkey_progid, "FriendlyAppName", 0, REG_SZ, (BYTE *)friendly_name, strlen(friendly_name) + 1);
SNPRINTF(buffer, "%s\\shell\\open\\command", prog_id);
lresult = RegCreateKeyEx(root,
buffer,
0,
nullptr,
REG_OPTION_NON_VOLATILE,
KEY_ALL_ACCESS,
nullptr,
&hkey_progid,
&dwd);
if (lresult == ERROR_SUCCESS) {
SNPRINTF(buffer, "\"%s\" \"%%1\"", executable);
lresult = RegSetValueEx(hkey_progid, nullptr, 0, REG_SZ, (BYTE *)buffer, strlen(buffer) + 1);
RegCloseKey(hkey_progid);
}
if (lresult != ERROR_SUCCESS) {
registry_error(root, "Unable to register Blender App Id");
return false;
}
SNPRINTF(buffer, "%s\\DefaultIcon", prog_id);
lresult = RegCreateKeyEx(root,
buffer,
0,
nullptr,
REG_OPTION_NON_VOLATILE,
KEY_ALL_ACCESS,
nullptr,
&hkey_progid,
&dwd);
if (lresult == ERROR_SUCCESS) {
SNPRINTF(buffer, "\"%s\", 1", executable);
lresult = RegSetValueEx(hkey_progid, nullptr, 0, REG_SZ, (BYTE *)buffer, strlen(buffer) + 1);
RegCloseKey(hkey_progid);
}
if (lresult != ERROR_SUCCESS) {
registry_error(root, "Unable to register Blender App Id");
return false;
}
return true;
}
bool BLI_windows_register_blend_extension(const bool all_users)
{
if (BLI_windows_is_store_install()) {
fprintf(stderr, "Registration not possible from Microsoft Store installation.");
return false;
}
HKEY root = 0;
char blender_path[MAX_PATH];
char *blender_app;
HKEY hkey = 0;
LONG lresult;
DWORD dwd = 0;
const char *prog_id = BLENDER_WIN_APPID;
const char *friendly_name = BLENDER_WIN_APPID_FRIENDLY_NAME;
GetModuleFileName(0, blender_path, sizeof(blender_path));
/* Prevent overflow when we add -launcher to the executable name. */
if (strlen(blender_path) > (sizeof(blender_path) - 10)) {
return false;
}
/* Replace the actual app name with the wrapper. */
blender_app = strstr(blender_path, "blender.exe");
if (!blender_app) {
return false;
}
strcpy(blender_app, "blender-launcher.exe");
if (!open_registry_hive(all_users, &root)) {
return false;
}
if (!register_blender_prog_id(prog_id, blender_path, friendly_name, all_users)) {
registry_error(root, "Unable to register Blend document type");
return false;
}
lresult = RegCreateKeyEx(
root, ".blend", 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, nullptr, &hkey, &dwd);
if (lresult == ERROR_SUCCESS) {
/* Set this instance the default. */
lresult = RegSetValueEx(hkey, nullptr, 0, REG_SZ, (BYTE *)prog_id, strlen(prog_id) + 1);
if (lresult != ERROR_SUCCESS) {
registry_error(root, "Unable to register Blend document type");
RegCloseKey(hkey);
return false;
}
RegCloseKey(hkey);
lresult = RegCreateKeyEx(root,
".blend\\OpenWithProgids",
0,
nullptr,
REG_OPTION_NON_VOLATILE,
KEY_ALL_ACCESS,
nullptr,
&hkey,
&dwd);
if (lresult != ERROR_SUCCESS) {
registry_error(root, "Unable to register Blend document type");
RegCloseKey(hkey);
return false;
}
lresult = RegSetValueEx(hkey, prog_id, 0, REG_NONE, nullptr, 0);
RegCloseKey(hkey);
}
if (lresult != ERROR_SUCCESS) {
registry_error(root, "Unable to register Blend document type");
return false;
}
if (!BLI_windows_update_pinned_launcher(blender_path)) {
fprintf(stderr, "Update of pinned launcher failed.");
return false;
}
# ifdef WITH_BLENDER_THUMBNAILER
{
char reg_cmd[MAX_PATH * 2];
char install_dir[FILE_MAXDIR];
char system_dir[FILE_MAXDIR];
BLI_windows_get_executable_dir(install_dir);
GetSystemDirectory(system_dir, sizeof(system_dir));
const char *thumbnail_handler = "BlendThumb.dll";
SNPRINTF(reg_cmd, "%s\\regsvr32 /s \"%s\\%s\"", system_dir, install_dir, thumbnail_handler);
system(reg_cmd);
}
# endif
RegCloseKey(root);
char message[256];
SNPRINTF(message,
"Blend file extension registered for %s.",
all_users ? "all users" : "the current user");
printf("%s\n", message);
return true;
}
bool BLI_windows_unregister_blend_extension(const bool all_users)
{
if (BLI_windows_is_store_install()) {
fprintf(stderr, "Unregistration not possible from Microsoft Store installation.");
return false;
}
HKEY root = 0;
HKEY hkey = 0;
LONG lresult;
if (!open_registry_hive(all_users, &root)) {
return false;
}
/* Don't stop on failure. We want to allow unregister after unregister. */
RegDeleteTree(root, BLENDER_WIN_APPID);
lresult = RegOpenKeyEx(root, ".blend", 0, KEY_ALL_ACCESS, &hkey);
if (lresult == ERROR_SUCCESS) {
char buffer[256] = {0};
DWORD size = sizeof(buffer);
lresult = RegGetValueA(hkey, nullptr, nullptr, RRF_RT_REG_SZ, nullptr, &buffer, &size);
if (lresult == ERROR_SUCCESS && STREQ(buffer, BLENDER_WIN_APPID)) {
RegSetValueEx(hkey, nullptr, 0, REG_SZ, 0, 0);
}
}
# ifdef WITH_BLENDER_THUMBNAILER
{
char reg_cmd[MAX_PATH * 2];
char install_dir[FILE_MAXDIR];
char system_dir[FILE_MAXDIR];
BLI_windows_get_executable_dir(install_dir);
GetSystemDirectory(system_dir, sizeof(system_dir));
const char *thumbnail_handler = "BlendThumb.dll";
SNPRINTF(reg_cmd, "%s\\regsvr32 /u /s \"%s\\%s\"", system_dir, install_dir, thumbnail_handler);
system(reg_cmd);
}
# endif
lresult = RegOpenKeyEx(hkey, "OpenWithProgids", 0, KEY_ALL_ACCESS, &hkey);
if (lresult == ERROR_SUCCESS) {
RegDeleteValue(hkey, BLENDER_WIN_APPID);
}
RegCloseKey(root);
char message[256];
SNPRINTF(message,
"Blend file extension unregistered for %s.",
all_users ? "all users" : "the current user");
printf("%s\n", message);
return true;
}
/**
* Check the registry to see if there is an operation association to a file
* extension. Extension *should almost always contain a dot like `.txt`,
* but this does allow querying non - extensions *like "Directory", "Drive",
* "AllProtocols", etc - anything in Classes with a "shell" branch.
*/
static bool BLI_windows_file_operation_is_registered(const char *extension, const char *operation)
{
HKEY hKey;
HRESULT hr = AssocQueryKey(ASSOCF_INIT_IGNOREUNKNOWN,
ASSOCKEY_SHELLEXECCLASS,
(LPCTSTR)extension,
(LPCTSTR)operation,
&hKey);
if (SUCCEEDED(hr)) {
RegCloseKey(hKey);
return true;
}
return false;
}
bool BLI_windows_external_operation_supported(const char *filepath, const char *operation)
{
if (STREQ(operation, "open") || STREQ(operation, "properties")) {
return true;
}
if (BLI_is_dir(filepath)) {
return BLI_windows_file_operation_is_registered("Directory", operation);
}
const char *extension = BLI_path_extension(filepath);
return BLI_windows_file_operation_is_registered(extension, operation);
}
bool BLI_windows_external_operation_execute(const char *filepath, const char *operation)
{
WCHAR wpath[FILE_MAX];
if (conv_utf_8_to_16(filepath, wpath, ARRAY_SIZE(wpath)) != 0) {
return false;
}
WCHAR woperation[FILE_MAX];
if (conv_utf_8_to_16(operation, woperation, ARRAY_SIZE(woperation)) != 0) {
return false;
}
SHELLEXECUTEINFOW shellinfo = {0};
shellinfo.cbSize = sizeof(SHELLEXECUTEINFO);
shellinfo.fMask = SEE_MASK_INVOKEIDLIST;
shellinfo.lpVerb = woperation;
shellinfo.lpFile = wpath;
shellinfo.nShow = SW_SHOW;
return ShellExecuteExW(&shellinfo);
}
bool BLI_windows_execute_self(const char *parameters,
const bool wait,
const bool elevated,
const bool silent)
{
char blender_path[MAX_PATH];
GetModuleFileName(0, blender_path, MAX_PATH);
SHELLEXECUTEINFOA shellinfo = {0};
shellinfo.cbSize = sizeof(SHELLEXECUTEINFO);
shellinfo.fMask = wait ? SEE_MASK_NOCLOSEPROCESS : SEE_MASK_DEFAULT;
shellinfo.hwnd = nullptr;
shellinfo.lpVerb = elevated ? "runas" : nullptr;
shellinfo.lpFile = blender_path;
shellinfo.lpParameters = parameters;
shellinfo.lpDirectory = nullptr;
shellinfo.nShow = silent ? SW_HIDE : SW_SHOW;
shellinfo.hInstApp = nullptr;
shellinfo.hProcess = 0;
DWORD exitCode = 0;
if (!ShellExecuteExA(&shellinfo)) {
return false;
}
if (!wait) {
return true;
}
if (shellinfo.hProcess != 0) {
WaitForSingleObject(shellinfo.hProcess, INFINITE);
GetExitCodeProcess(shellinfo.hProcess, &exitCode);
CloseHandle(shellinfo.hProcess);
return (exitCode == 0);
}
return false;
}
void BLI_windows_get_default_root_dir(char root[4])
{
char str[MAX_PATH + 1];
/* the default drive to resolve a directory without a specified drive
* should be the Windows installation drive, since this was what the OS
* assumes. */
if (GetWindowsDirectory(str, MAX_PATH + 1)) {
root[0] = str[0];
root[1] = ':';
root[2] = '\\';
root[3] = '\0';
}
else {
/* if GetWindowsDirectory fails, something has probably gone wrong,
* we are trying the blender install dir though */
if (GetModuleFileName(nullptr, str, MAX_PATH + 1)) {
printf(
"Error! Could not get the Windows Directory - "
"Defaulting to Blender installation Dir!\n");
root[0] = str[0];
root[1] = ':';
root[2] = '\\';
root[3] = '\0';
}
else {
DWORD tmp;
int i;
int rc = 0;
/* now something has gone really wrong - still trying our best guess */
printf(
"Error! Could not get the Windows Directory - "
"Defaulting to first valid drive! Path might be invalid!\n");
tmp = GetLogicalDrives();
for (i = 2; i < 26; i++) {
if ((tmp >> i) & 1) {
root[0] = 'a' + i;
root[1] = ':';
root[2] = '\\';
root[3] = '\0';
if (GetFileAttributes(root) != 0xFFFFFFFF) {
rc = i;
break;
}
}
}
if (0 == rc) {
printf("ERROR in 'BLI_windows_get_default_root_dir': cannot find a valid drive!\n");
root[0] = 'C';
root[1] = ':';
root[2] = '\\';
root[3] = '\0';
}
}
}
}
bool BLI_windows_get_directx_driver_version(const wchar_t *deviceSubString,
long long *r_driverVersion)
{
IDXGIFactory *pFactory = nullptr;
IDXGIAdapter *pAdapter = nullptr;
if (CreateDXGIFactory(__uuidof(IDXGIFactory), (void **)&pFactory) == S_OK) {
for (UINT i = 0; pFactory->EnumAdapters(i, &pAdapter) != DXGI_ERROR_NOT_FOUND; ++i) {
LARGE_INTEGER version;
if (pAdapter->CheckInterfaceSupport(__uuidof(IDXGIDevice), &version) == S_OK) {
DXGI_ADAPTER_DESC desc;
if (pAdapter->GetDesc(&desc) == S_OK) {
if (wcsstr(desc.Description, deviceSubString)) {
*r_driverVersion = version.QuadPart;
pAdapter->Release();
pFactory->Release();
return true;
}
}
}
pAdapter->Release();
}
pFactory->Release();
}
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 */
#endif