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
|
# GPU system files - full backend
|
||||||
set(REXGRAPHICS_SYSTEM_SOURCES
|
set(REXGRAPHICS_SYSTEM_SOURCES
|
||||||
graphics_system.cpp
|
graphics_system.cpp
|
||||||
|
gpu_clock_telemetry.cpp
|
||||||
|
nvidia_app_profile.cpp
|
||||||
command_processor.cpp
|
command_processor.cpp
|
||||||
trace_writer.cpp
|
trace_writer.cpp
|
||||||
pipeline/texture/extent.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/cvar.h>
|
||||||
#include <rex/graphics/command_processor.h>
|
#include <rex/graphics/command_processor.h>
|
||||||
#include <rex/graphics/flags.h>
|
#include <rex/graphics/flags.h>
|
||||||
|
#include <rex/graphics/nvidia_app_profile.h>
|
||||||
#include <rex/kernel/xboxkrnl/video.h>
|
#include <rex/kernel/xboxkrnl/video.h>
|
||||||
#include <rex/logging.h>
|
#include <rex/logging.h>
|
||||||
#include <rex/stream.h>
|
#include <rex/stream.h>
|
||||||
|
|
@ -148,6 +149,10 @@ X_STATUS GraphicsSystem::SetupPresentation(ui::WindowedAppContext* app_context)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!provider_) {
|
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);
|
CreateProvider(true);
|
||||||
if (!provider_) {
|
if (!provider_) {
|
||||||
REXGPU_ERROR("Unable to create graphics 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