graphics: NVIDIA prefer-max-performance profile + GPU clock telemetry
This commit is contained in:
parent
198f138300
commit
49641d2ade
6 changed files with 491 additions and 0 deletions
32
include/rex/graphics/gpu_clock_telemetry.h
Normal file
32
include/rex/graphics/gpu_clock_telemetry.h
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* @file graphics/gpu_clock_telemetry.h
|
||||
* @brief Host GPU clock/power-state sampling for performance attribution.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace rex::graphics {
|
||||
|
||||
// Snapshot of the host GPU's clock/power state, read from the vendor's
|
||||
// management library (NVML for NVIDIA). Lets logs distinguish "the workload
|
||||
// grew" from "the same workload executes slower" (all GPU timestamp buckets
|
||||
// inflating together = a core/memory clock drop the driver never recovered
|
||||
// from).
|
||||
struct GpuClockSample {
|
||||
bool valid = false;
|
||||
uint32_t sm_mhz = 0;
|
||||
uint32_t mem_mhz = 0;
|
||||
uint32_t gpu_util_percent = 0;
|
||||
uint32_t mem_util_percent = 0;
|
||||
uint32_t performance_state = 0; // P-state number; 0 = full performance.
|
||||
uint64_t throttle_reasons = 0; // NVML clocks-throttle-reasons bitmask.
|
||||
};
|
||||
|
||||
// Samples the first vendor GPU. Loads the management library on the first
|
||||
// call; returns valid=false forever if it is unavailable (non-NVIDIA host,
|
||||
// library missing). Cheap enough to call a few times per second.
|
||||
GpuClockSample SampleGpuClocks();
|
||||
|
||||
} // namespace rex::graphics
|
||||
21
include/rex/graphics/nvidia_app_profile.h
Normal file
21
include/rex/graphics/nvidia_app_profile.h
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
* @file graphics/nvidia_app_profile.h
|
||||
* @brief NVIDIA driver application-profile setup (power management mode).
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace rex::graphics {
|
||||
|
||||
// Ensures the NVIDIA driver's application profile for the current executable
|
||||
// requests "Prefer maximum performance" power management. Without it the
|
||||
// driver parks the GPU in a low P-state during load-screen lulls and, because
|
||||
// a paced frame loop never pushes utilization over the boost threshold, can
|
||||
// keep it parked through gameplay (the sticky 40-80 fps state; memory clock
|
||||
// halves or worse while all GPU timestamp buckets inflate together).
|
||||
//
|
||||
// Windows + NVIDIA only; a no-op everywhere else. Never fatal - failures are
|
||||
// logged and swallowed. Controlled by the nvidia_prefer_max_performance cvar.
|
||||
void ApplyNvidiaMaxPerformanceProfile();
|
||||
|
||||
} // namespace rex::graphics
|
||||
|
|
@ -17,6 +17,8 @@ set(REXGRAPHICS_CORE_SOURCES
|
|||
# GPU system files - full backend
|
||||
set(REXGRAPHICS_SYSTEM_SOURCES
|
||||
graphics_system.cpp
|
||||
gpu_clock_telemetry.cpp
|
||||
nvidia_app_profile.cpp
|
||||
command_processor.cpp
|
||||
trace_writer.cpp
|
||||
pipeline/texture/extent.cpp
|
||||
|
|
|
|||
133
src/graphics/gpu_clock_telemetry.cpp
Normal file
133
src/graphics/gpu_clock_telemetry.cpp
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
#include <rex/graphics/gpu_clock_telemetry.h>
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include <rex/platform.h>
|
||||
#include <rex/platform/dynlib.h>
|
||||
|
||||
namespace rex::graphics {
|
||||
|
||||
namespace {
|
||||
|
||||
// Minimal NVML surface, resolved dynamically so there is no build-time
|
||||
// dependency. nvml.dll ships with the NVIDIA driver into System32; Linux
|
||||
// driver packages install libnvidia-ml.so.1. Other vendors simply fail the
|
||||
// load and sampling stays disabled.
|
||||
using NvmlReturn = int; // NVML_SUCCESS == 0.
|
||||
using NvmlDevice = void*;
|
||||
|
||||
struct NvmlUtilization {
|
||||
unsigned int gpu;
|
||||
unsigned int memory;
|
||||
};
|
||||
|
||||
// nvmlClockType_t.
|
||||
constexpr int kNvmlClockSm = 1;
|
||||
constexpr int kNvmlClockMem = 2;
|
||||
|
||||
struct NvmlApi {
|
||||
rex::platform::DynamicLibrary library;
|
||||
NvmlReturn (*init)() = nullptr;
|
||||
NvmlReturn (*device_get_handle_by_index)(unsigned int, NvmlDevice*) = nullptr;
|
||||
NvmlReturn (*device_get_clock_info)(NvmlDevice, int, unsigned int*) = nullptr;
|
||||
NvmlReturn (*device_get_utilization_rates)(NvmlDevice, NvmlUtilization*) = nullptr;
|
||||
NvmlReturn (*device_get_performance_state)(NvmlDevice, int*) = nullptr;
|
||||
NvmlReturn (*device_get_current_clocks_throttle_reasons)(NvmlDevice,
|
||||
unsigned long long*) = nullptr;
|
||||
NvmlDevice device = nullptr;
|
||||
bool ready = false;
|
||||
};
|
||||
|
||||
NvmlApi& GetNvml() {
|
||||
static NvmlApi api;
|
||||
static std::once_flag once;
|
||||
std::call_once(once, [] {
|
||||
#if REX_PLATFORM_WIN32
|
||||
bool loaded = api.library.Load("nvml.dll");
|
||||
#elif REX_PLATFORM_LINUX
|
||||
bool loaded = api.library.Load("libnvidia-ml.so.1");
|
||||
#else
|
||||
bool loaded = false;
|
||||
#endif
|
||||
if (!loaded) {
|
||||
return;
|
||||
}
|
||||
api.init = api.library.GetSymbol<decltype(api.init)>("nvmlInit_v2");
|
||||
if (!api.init) {
|
||||
api.init = api.library.GetSymbol<decltype(api.init)>("nvmlInit");
|
||||
}
|
||||
api.device_get_handle_by_index =
|
||||
api.library.GetSymbol<decltype(api.device_get_handle_by_index)>(
|
||||
"nvmlDeviceGetHandleByIndex_v2");
|
||||
if (!api.device_get_handle_by_index) {
|
||||
api.device_get_handle_by_index =
|
||||
api.library.GetSymbol<decltype(api.device_get_handle_by_index)>(
|
||||
"nvmlDeviceGetHandleByIndex");
|
||||
}
|
||||
api.device_get_clock_info =
|
||||
api.library.GetSymbol<decltype(api.device_get_clock_info)>("nvmlDeviceGetClockInfo");
|
||||
api.device_get_utilization_rates =
|
||||
api.library.GetSymbol<decltype(api.device_get_utilization_rates)>(
|
||||
"nvmlDeviceGetUtilizationRates");
|
||||
api.device_get_performance_state =
|
||||
api.library.GetSymbol<decltype(api.device_get_performance_state)>(
|
||||
"nvmlDeviceGetPerformanceState");
|
||||
api.device_get_current_clocks_throttle_reasons =
|
||||
api.library.GetSymbol<decltype(api.device_get_current_clocks_throttle_reasons)>(
|
||||
"nvmlDeviceGetCurrentClocksThrottleReasons");
|
||||
if (!api.init || !api.device_get_handle_by_index || !api.device_get_clock_info) {
|
||||
return;
|
||||
}
|
||||
if (api.init() != 0) {
|
||||
return;
|
||||
}
|
||||
// Device 0: NVML enumerates NVIDIA GPUs only, so on single-GPU and hybrid
|
||||
// systems alike this is the discrete GPU doing the rendering.
|
||||
if (api.device_get_handle_by_index(0, &api.device) != 0 || !api.device) {
|
||||
return;
|
||||
}
|
||||
api.ready = true;
|
||||
});
|
||||
return api;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
GpuClockSample SampleGpuClocks() {
|
||||
GpuClockSample sample;
|
||||
NvmlApi& api = GetNvml();
|
||||
if (!api.ready) {
|
||||
return sample;
|
||||
}
|
||||
unsigned int sm_mhz = 0;
|
||||
unsigned int mem_mhz = 0;
|
||||
if (api.device_get_clock_info(api.device, kNvmlClockSm, &sm_mhz) != 0) {
|
||||
return sample;
|
||||
}
|
||||
api.device_get_clock_info(api.device, kNvmlClockMem, &mem_mhz);
|
||||
sample.sm_mhz = sm_mhz;
|
||||
sample.mem_mhz = mem_mhz;
|
||||
if (api.device_get_utilization_rates) {
|
||||
NvmlUtilization utilization = {};
|
||||
if (api.device_get_utilization_rates(api.device, &utilization) == 0) {
|
||||
sample.gpu_util_percent = utilization.gpu;
|
||||
sample.mem_util_percent = utilization.memory;
|
||||
}
|
||||
}
|
||||
if (api.device_get_performance_state) {
|
||||
int pstate = 0;
|
||||
if (api.device_get_performance_state(api.device, &pstate) == 0 && pstate >= 0) {
|
||||
sample.performance_state = uint32_t(pstate);
|
||||
}
|
||||
}
|
||||
if (api.device_get_current_clocks_throttle_reasons) {
|
||||
unsigned long long reasons = 0;
|
||||
if (api.device_get_current_clocks_throttle_reasons(api.device, &reasons) == 0) {
|
||||
sample.throttle_reasons = reasons;
|
||||
}
|
||||
}
|
||||
sample.valid = true;
|
||||
return sample;
|
||||
}
|
||||
|
||||
} // namespace rex::graphics
|
||||
|
|
@ -24,6 +24,7 @@
|
|||
#include <rex/cvar.h>
|
||||
#include <rex/graphics/command_processor.h>
|
||||
#include <rex/graphics/flags.h>
|
||||
#include <rex/graphics/nvidia_app_profile.h>
|
||||
#include <rex/kernel/xboxkrnl/video.h>
|
||||
#include <rex/logging.h>
|
||||
#include <rex/stream.h>
|
||||
|
|
@ -148,6 +149,10 @@ X_STATUS GraphicsSystem::SetupPresentation(ui::WindowedAppContext* app_context)
|
|||
}
|
||||
|
||||
if (!provider_) {
|
||||
// Before the driver loads: make sure our NVIDIA application profile
|
||||
// requests maximum performance so load-screen lulls cannot park the GPU
|
||||
// in a low P-state for the rest of the session.
|
||||
ApplyNvidiaMaxPerformanceProfile();
|
||||
CreateProvider(true);
|
||||
if (!provider_) {
|
||||
REXGPU_ERROR("Unable to create graphics provider");
|
||||
|
|
|
|||
298
src/graphics/nvidia_app_profile.cpp
Normal file
298
src/graphics/nvidia_app_profile.cpp
Normal file
|
|
@ -0,0 +1,298 @@
|
|||
#include <rex/graphics/nvidia_app_profile.h>
|
||||
|
||||
#include <rex/cvar.h>
|
||||
#include <rex/logging.h>
|
||||
#include <rex/platform.h>
|
||||
|
||||
REXCVAR_DEFINE_BOOL(nvidia_prefer_max_performance, true, "GPU",
|
||||
"Write 'Power management mode = Prefer maximum performance' into the NVIDIA "
|
||||
"driver's application profile for this executable at startup, preventing the "
|
||||
"driver from parking the GPU in a low P-state after load-screen lulls (the "
|
||||
"sticky low-fps state). Setting this to false only skips the write; it does "
|
||||
"not remove an already-written profile");
|
||||
|
||||
#if REX_PLATFORM_WIN32
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <cwctype>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#include <rex/platform/dynlib.h>
|
||||
|
||||
namespace rex::graphics {
|
||||
|
||||
namespace {
|
||||
|
||||
// Minimal NvAPI DRS surface. nvapi64.dll ships with the NVIDIA driver and
|
||||
// exports a single nvapi_QueryInterface; everything else is resolved through
|
||||
// it by the stable public interface ids from the MIT-licensed NVAPI SDK.
|
||||
using NvStatus = int; // NVAPI_OK == 0.
|
||||
using NvDrsSessionHandle = void*;
|
||||
using NvDrsProfileHandle = void*;
|
||||
|
||||
constexpr uint32_t kIdInitialize = 0x0150E828;
|
||||
constexpr uint32_t kIdDrsCreateSession = 0x0694D52E;
|
||||
constexpr uint32_t kIdDrsDestroySession = 0xDAD9CFF8;
|
||||
constexpr uint32_t kIdDrsLoadSettings = 0x375DBD6B;
|
||||
constexpr uint32_t kIdDrsSaveSettings = 0xFCBC7E14;
|
||||
constexpr uint32_t kIdDrsFindProfileByName = 0x7E4A9A0B;
|
||||
constexpr uint32_t kIdDrsCreateProfile = 0xCC176068;
|
||||
constexpr uint32_t kIdDrsFindApplicationByName = 0xEEE566B2;
|
||||
constexpr uint32_t kIdDrsCreateApplication = 0x4347A9DE;
|
||||
constexpr uint32_t kIdDrsGetSetting = 0x73BF8338;
|
||||
constexpr uint32_t kIdDrsSetSetting = 0x577DD202;
|
||||
|
||||
// PREFERRED_PSTATE_ID / PREFERRED_PSTATE_PREFER_MAX from NvApiDriverSettings.h.
|
||||
constexpr uint32_t kPreferredPstateSettingId = 0x1057EB71;
|
||||
constexpr uint32_t kPreferredPstatePreferMax = 1;
|
||||
|
||||
constexpr uint32_t kNvapiUnicodeStringMax = 2048;
|
||||
using NvApiUnicodeString = uint16_t[kNvapiUnicodeStringMax];
|
||||
|
||||
constexpr uint32_t MakeNvapiVersion(size_t struct_size, uint32_t version) {
|
||||
return uint32_t(struct_size) | (version << 16);
|
||||
}
|
||||
|
||||
struct NvdrsApplicationV1 {
|
||||
uint32_t version;
|
||||
uint32_t is_predefined;
|
||||
NvApiUnicodeString app_name;
|
||||
NvApiUnicodeString user_friendly_name;
|
||||
NvApiUnicodeString launcher;
|
||||
};
|
||||
|
||||
struct NvdrsProfileV1 {
|
||||
uint32_t version;
|
||||
NvApiUnicodeString profile_name;
|
||||
uint32_t gpu_support;
|
||||
uint32_t is_predefined;
|
||||
uint32_t num_of_apps;
|
||||
uint32_t num_of_settings;
|
||||
};
|
||||
|
||||
// NVDRS_SETTING_V1: both value unions are the size of the largest member, the
|
||||
// binary setting (4-byte length + 4096 payload).
|
||||
struct NvdrsBinarySetting {
|
||||
uint32_t value_length;
|
||||
uint8_t value_data[4096];
|
||||
};
|
||||
|
||||
struct NvdrsSettingV1 {
|
||||
uint32_t version;
|
||||
NvApiUnicodeString setting_name;
|
||||
uint32_t setting_id;
|
||||
uint32_t setting_type; // NVDRS_SETTING_TYPE; 0 = DWORD.
|
||||
uint32_t setting_location; // NVDRS_SETTING_LOCATION.
|
||||
uint32_t is_current_predefined;
|
||||
uint32_t is_predefined_valid;
|
||||
union {
|
||||
uint32_t u32_predefined_value;
|
||||
NvdrsBinarySetting binary_predefined_value;
|
||||
NvApiUnicodeString wsz_predefined_value;
|
||||
};
|
||||
union {
|
||||
uint32_t u32_current_value;
|
||||
NvdrsBinarySetting binary_current_value;
|
||||
NvApiUnicodeString wsz_current_value;
|
||||
};
|
||||
};
|
||||
|
||||
static_assert(sizeof(NvdrsApplicationV1) == 8 + 3 * 4096, "NVDRS_APPLICATION_V1 layout");
|
||||
static_assert(sizeof(NvdrsProfileV1) == 20 + 4096, "NVDRS_PROFILE_V1 layout");
|
||||
static_assert(sizeof(NvdrsSettingV1) == 24 + 4096 + 2 * 4100, "NVDRS_SETTING_V1 layout");
|
||||
|
||||
void FillNvapiUnicodeString(NvApiUnicodeString& dest, const std::wstring& src) {
|
||||
std::memset(dest, 0, sizeof(NvApiUnicodeString));
|
||||
size_t count = std::min(src.size(), size_t(kNvapiUnicodeStringMax - 1));
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
dest[i] = uint16_t(src[i]);
|
||||
}
|
||||
}
|
||||
|
||||
struct NvApiDrs {
|
||||
rex::platform::DynamicLibrary library;
|
||||
NvStatus (*initialize)() = nullptr;
|
||||
NvStatus (*create_session)(NvDrsSessionHandle*) = nullptr;
|
||||
NvStatus (*destroy_session)(NvDrsSessionHandle) = nullptr;
|
||||
NvStatus (*load_settings)(NvDrsSessionHandle) = nullptr;
|
||||
NvStatus (*save_settings)(NvDrsSessionHandle) = nullptr;
|
||||
NvStatus (*find_profile_by_name)(NvDrsSessionHandle, uint16_t*, NvDrsProfileHandle*) = nullptr;
|
||||
NvStatus (*create_profile)(NvDrsSessionHandle, NvdrsProfileV1*, NvDrsProfileHandle*) = nullptr;
|
||||
NvStatus (*find_application_by_name)(NvDrsSessionHandle, uint16_t*, NvDrsProfileHandle*,
|
||||
NvdrsApplicationV1*) = nullptr;
|
||||
NvStatus (*create_application)(NvDrsSessionHandle, NvDrsProfileHandle,
|
||||
NvdrsApplicationV1*) = nullptr;
|
||||
NvStatus (*get_setting)(NvDrsSessionHandle, NvDrsProfileHandle, uint32_t,
|
||||
NvdrsSettingV1*) = nullptr;
|
||||
NvStatus (*set_setting)(NvDrsSessionHandle, NvDrsProfileHandle, NvdrsSettingV1*) = nullptr;
|
||||
|
||||
bool Load() {
|
||||
if (!library.Load("nvapi64.dll")) {
|
||||
return false;
|
||||
}
|
||||
using QueryInterfaceFn = void* (*)(uint32_t);
|
||||
auto query_interface = library.GetSymbol<QueryInterfaceFn>("nvapi_QueryInterface");
|
||||
if (!query_interface) {
|
||||
return false;
|
||||
}
|
||||
initialize = reinterpret_cast<decltype(initialize)>(query_interface(kIdInitialize));
|
||||
create_session =
|
||||
reinterpret_cast<decltype(create_session)>(query_interface(kIdDrsCreateSession));
|
||||
destroy_session =
|
||||
reinterpret_cast<decltype(destroy_session)>(query_interface(kIdDrsDestroySession));
|
||||
load_settings =
|
||||
reinterpret_cast<decltype(load_settings)>(query_interface(kIdDrsLoadSettings));
|
||||
save_settings =
|
||||
reinterpret_cast<decltype(save_settings)>(query_interface(kIdDrsSaveSettings));
|
||||
find_profile_by_name =
|
||||
reinterpret_cast<decltype(find_profile_by_name)>(query_interface(kIdDrsFindProfileByName));
|
||||
create_profile =
|
||||
reinterpret_cast<decltype(create_profile)>(query_interface(kIdDrsCreateProfile));
|
||||
find_application_by_name = reinterpret_cast<decltype(find_application_by_name)>(
|
||||
query_interface(kIdDrsFindApplicationByName));
|
||||
create_application =
|
||||
reinterpret_cast<decltype(create_application)>(query_interface(kIdDrsCreateApplication));
|
||||
get_setting = reinterpret_cast<decltype(get_setting)>(query_interface(kIdDrsGetSetting));
|
||||
set_setting = reinterpret_cast<decltype(set_setting)>(query_interface(kIdDrsSetSetting));
|
||||
return initialize && create_session && destroy_session && load_settings && save_settings &&
|
||||
find_profile_by_name && create_profile && find_application_by_name &&
|
||||
create_application && get_setting && set_setting;
|
||||
}
|
||||
};
|
||||
|
||||
std::wstring GetExecutableBaseNameLower() {
|
||||
wchar_t module_path[MAX_PATH] = {};
|
||||
if (!GetModuleFileNameW(nullptr, module_path, MAX_PATH)) {
|
||||
return {};
|
||||
}
|
||||
std::wstring path(module_path);
|
||||
size_t separator = path.find_last_of(L"\\/");
|
||||
std::wstring name = separator == std::wstring::npos ? path : path.substr(separator + 1);
|
||||
std::transform(name.begin(), name.end(), name.begin(),
|
||||
[](wchar_t c) { return wchar_t(std::towlower(c)); });
|
||||
return name;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void ApplyNvidiaMaxPerformanceProfile() {
|
||||
if (!REXCVAR_GET(nvidia_prefer_max_performance)) {
|
||||
return;
|
||||
}
|
||||
|
||||
NvApiDrs api;
|
||||
if (!api.Load()) {
|
||||
// Not an NVIDIA system (or a very old driver) - nothing to do.
|
||||
return;
|
||||
}
|
||||
if (api.initialize() != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::wstring app_name = GetExecutableBaseNameLower();
|
||||
if (app_name.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
NvDrsSessionHandle session = nullptr;
|
||||
if (api.create_session(&session) != 0 || !session) {
|
||||
REXLOG_WARN("NVIDIA app profile: failed to create a driver settings session");
|
||||
return;
|
||||
}
|
||||
|
||||
// Everything from here on cleans up through this single exit path.
|
||||
do {
|
||||
NvStatus status = api.load_settings(session);
|
||||
if (status != 0) {
|
||||
REXLOG_WARN("NVIDIA app profile: DRS_LoadSettings failed ({})", status);
|
||||
break;
|
||||
}
|
||||
|
||||
NvApiUnicodeString app_name_nvapi;
|
||||
FillNvapiUnicodeString(app_name_nvapi, app_name);
|
||||
|
||||
// Find the profile that owns this executable, creating one if needed.
|
||||
NvDrsProfileHandle profile = nullptr;
|
||||
auto application = std::make_unique<NvdrsApplicationV1>();
|
||||
application->version = MakeNvapiVersion(sizeof(NvdrsApplicationV1), 1);
|
||||
status = api.find_application_by_name(session, app_name_nvapi, &profile, application.get());
|
||||
if (status != 0 || !profile) {
|
||||
profile = nullptr;
|
||||
// No profile references the executable yet. Reuse a profile previously
|
||||
// created by us if present, otherwise create one.
|
||||
NvApiUnicodeString profile_name_nvapi;
|
||||
FillNvapiUnicodeString(profile_name_nvapi, app_name);
|
||||
if (api.find_profile_by_name(session, profile_name_nvapi, &profile) != 0 || !profile) {
|
||||
profile = nullptr;
|
||||
auto new_profile = std::make_unique<NvdrsProfileV1>();
|
||||
new_profile->version = MakeNvapiVersion(sizeof(NvdrsProfileV1), 1);
|
||||
FillNvapiUnicodeString(new_profile->profile_name, app_name);
|
||||
status = api.create_profile(session, new_profile.get(), &profile);
|
||||
if (status != 0 || !profile) {
|
||||
REXLOG_WARN("NVIDIA app profile: DRS_CreateProfile failed ({})", status);
|
||||
break;
|
||||
}
|
||||
}
|
||||
auto new_application = std::make_unique<NvdrsApplicationV1>();
|
||||
new_application->version = MakeNvapiVersion(sizeof(NvdrsApplicationV1), 1);
|
||||
std::memcpy(new_application->app_name, app_name_nvapi, sizeof(NvApiUnicodeString));
|
||||
status = api.create_application(session, profile, new_application.get());
|
||||
if (status != 0) {
|
||||
REXLOG_WARN("NVIDIA app profile: DRS_CreateApplication failed ({})", status);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Skip the save when the profile already requests maximum performance.
|
||||
auto setting = std::make_unique<NvdrsSettingV1>();
|
||||
setting->version = MakeNvapiVersion(sizeof(NvdrsSettingV1), 1);
|
||||
status = api.get_setting(session, profile, kPreferredPstateSettingId, setting.get());
|
||||
if (status == 0 && setting->u32_current_value == kPreferredPstatePreferMax &&
|
||||
setting->setting_location == 0 /* current profile, not inherited */) {
|
||||
REXLOG_INFO(
|
||||
"NVIDIA app profile: power management already 'Prefer maximum performance' for {}",
|
||||
std::string(app_name.begin(), app_name.end()));
|
||||
break;
|
||||
}
|
||||
|
||||
auto new_setting = std::make_unique<NvdrsSettingV1>();
|
||||
new_setting->version = MakeNvapiVersion(sizeof(NvdrsSettingV1), 1);
|
||||
new_setting->setting_id = kPreferredPstateSettingId;
|
||||
new_setting->setting_type = 0; // NVDRS_DWORD_TYPE.
|
||||
new_setting->u32_current_value = kPreferredPstatePreferMax;
|
||||
status = api.set_setting(session, profile, new_setting.get());
|
||||
if (status != 0) {
|
||||
REXLOG_WARN("NVIDIA app profile: DRS_SetSetting failed ({})", status);
|
||||
break;
|
||||
}
|
||||
status = api.save_settings(session);
|
||||
if (status != 0) {
|
||||
REXLOG_WARN("NVIDIA app profile: DRS_SaveSettings failed ({})", status);
|
||||
break;
|
||||
}
|
||||
REXLOG_INFO(
|
||||
"NVIDIA app profile: set power management to 'Prefer maximum performance' for {} "
|
||||
"(prevents the driver parking the GPU in a low P-state; disable with "
|
||||
"nvidia_prefer_max_performance=false)",
|
||||
std::string(app_name.begin(), app_name.end()));
|
||||
} while (false);
|
||||
|
||||
api.destroy_session(session);
|
||||
}
|
||||
|
||||
} // namespace rex::graphics
|
||||
|
||||
#else // !REX_PLATFORM_WIN32
|
||||
|
||||
namespace rex::graphics {
|
||||
|
||||
void ApplyNvidiaMaxPerformanceProfile() {}
|
||||
|
||||
} // namespace rex::graphics
|
||||
|
||||
#endif // REX_PLATFORM_WIN32
|
||||
Loading…
Add table
Reference in a new issue