graphics: NVIDIA prefer-max-performance profile + GPU clock telemetry

This commit is contained in:
mchughalex 2026-07-17 04:03:46 +01:00
parent 198f138300
commit 49641d2ade
6 changed files with 491 additions and 0 deletions

View 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

View 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

View file

@ -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

View 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

View file

@ -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");

View 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