From f0b3556ac5da102e955616bb5ab71a03d82970b7 Mon Sep 17 00:00:00 2001 From: icex2 Date: Thu, 13 Feb 2025 14:47:22 +0100 Subject: [PATCH] feat: nvgpu, make test timeout parameter optional 10 seconds is what the nvidia control panel also has for the test timeout. Can still be changed if shorter or longer timeouts are desired, but 10 seconds should be a general fine timeout value to have this optional. --- doc/tools/nvgpu.md | 78 +++ src/main/nvgpu/main.c | 1358 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1436 insertions(+) create mode 100644 doc/tools/nvgpu.md create mode 100644 src/main/nvgpu/main.c diff --git a/doc/tools/nvgpu.md b/doc/tools/nvgpu.md new file mode 100644 index 0000000..c3671ce --- /dev/null +++ b/doc/tools/nvgpu.md @@ -0,0 +1,78 @@ +# nvgpu - Command line tool to configure and tweak the NVIDIA GPU driver + +An open source re-implementation of the “NvDisplayConfigLDJ" tool with additional enhancements. + +The tool is based on NVIDIA's [nvapi](https://github.com/NVIDIA/nvapi) which is an interface to +various driver settings that can also be tweaked from the NVIDIA Control Panel. The goal of this +tool is to provide a streamlined command line interface to configure various settings that can +improve gameplay experience significantly. + +This can be used to tweak your nvidia GPU driver settings to create custom display timings to address +IIDX’s requirement if expecting proper display timings. This can also be used for any legacy IIDX +versions that even expect very specific display timings, e.g. 59.95 or 60.05 hz. + +Furthermore, creating application profiles allows further tweaks to important GPU settings such as +the current performance mode setting. This is crucial to ensure the GPU is not going into any kind of +power saving states which results in non-smooth scrolling during gameplay and micro stuttering that +cannot be measured on application level. + +Simply run the tool without any arguments to get a full synopsis of available commands. + +## Example usage for modern IIDX + +### Custom GPU profile + +The following creates a custom profile to address potential performance concerns such as not 100% smooth scrolling and +micro stuttering. + +* Create a custom profile: `nvgpu profile create launcher` +* Add the launcher application to the profile: `nvgpu profile application-add launcher launcher.exe` + * This will apply to any (IIDX) game running with `launcher.exe` +* Set GPU power state to maximum for the profile: `nvgpu profile gpu-power-state-max launcher` + +### Custom timing + +* Run the game and observe the monitor check screen (requires IIDX 20+) +* Take note of the refresh rate of the monitor that is determined by the game +* Exit the game +* Run `nvgpu display list` to get the display ID of the monitor you want to change use that ID in the following commands +* Run `nvgpu display custom-resolution-test` with your display ID and monitor settings to test the custom configuration + first + * For example, for IIDX 31 which runs in native 1920x1080 with a monitor also having that as its native resolution, + having the monitor id `0x12345678` and the monitor check yielding a value of ~`59.9345`, run + `nvgpu display custom-resolution-test 1920 1080 59.9345` + * Observe if the test is successful and the display doesn't turn blank or displays a glitched image for ~10 seconds +* Run `nvgpu display custom-resolution-set` with the previously tested settings to apply the custom display mode + * For example, `nvgpu display custom-resolution-set 0x12345678 1920 1080 59.9345` + +## Example usage for legacy IIDX + +Legacy IIDX concerns any game prior to IIDX 20 that introduced the monitor check screen. The game engine was expecting +a the display/GPU/driver to perform at specific refresh rate timings in order to provide correct timing and audio +playback for the game. To this date (i.e. IIDX 31), the game engine never re-syncs audio playback during gameplay. +Therefore any flaky and incorrect timing will result in audio desynchronization either early on or throughout a song. + +### Custom GPU profile + +The following creates a custom profile to address potential performance concerns such as not 100% smooth scrolling and +micro stuttering. + +* Create a custom profile: `nvgpu profile create bm2dx` +* Add the launcher application to the profile: `nvgpu profile application-add bm2dx bm2dx.exe` + * This will apply to any (IIDX) game running with an executable named `bm2fx.exe` +* Set GPU power state to maximum for the profile: `nvgpu profile gpu-power-state-max bm2dx` + +### Custom timing + +* Use a modern game and it to observe the monitor check screen (requires IIDX 20+) +* Take note of the refresh rate of the monitor that is determined by the game +* Exit the game +* Run `nvgpu display list` to get the display ID of the monitor you want to change use that ID in the following commands +* Run `nvgpu display custom-resolution-test` with your display ID and monitor settings to test the custom configuration + first + * For example, for IIDX 31 which runs in native 1920x1080 with a monitor also having that as its native resolution, + having the monitor id `0x12345678` and the monitor check yielding a value of ~`59.9345`, run + `nvgpu display custom-resolution-test 1920 1080 59.9345` + * Observe if the test is successful and the display doesn't turn blank or displays a glitched image for ~10 seconds +* Run `nvgpu display custom-resolution-set` with the previously tested settings to apply the custom display mode + * For example, `nvgpu display custom-resolution-set 0x12345678 1920 1080 59.9345` \ No newline at end of file diff --git a/src/main/nvgpu/main.c b/src/main/nvgpu/main.c new file mode 100644 index 0000000..9bcf3d0 --- /dev/null +++ b/src/main/nvgpu/main.c @@ -0,0 +1,1358 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include "nv/api.h" +#include "nv/module.h" + +#include "util/fs.h" +#include "util/mem.h" +#include "util/str.h" + +#define NV_DRS_SETTINGS_FOLDER "C:\\ProgramData\\NVIDIA Corporation\\Drs" + +#define printf_out(fmt, ...) \ + fprintf(stdout, fmt, ##__VA_ARGS__) +#define printf_err(fmt, ...) \ + fprintf(stderr, fmt, ##__VA_ARGS__) +#define printfln_out(fmt, ...) \ + fprintf(stdout, fmt "\n", ##__VA_ARGS__) +#define printfln_err(fmt, ...) \ + fprintf(stderr, fmt "\n", ##__VA_ARGS__) + +#define PRINT_ERR_WITH_NVAPI_MESSAGE(status, fmt, ...) \ + NvAPI_ShortString error_str; \ + nv_api->NvAPI_GetErrorMessage(status, error_str); \ + fprintf(stderr, fmt ", reason: %s\n", ##__VA_ARGS__, error_str); + +static bool _session_destroy(const nv_api_t *nv_api, NvDRSSessionHandle session) +{ + assert(nv_api); + assert(session); + + NvAPI_Status status; + + status = nv_api->NvAPI_DRS_DestroySession(session); + + if (status != NVAPI_OK) { + PRINT_ERR_WITH_NVAPI_MESSAGE(status, "ERROR: Destroying driver settings session"); + return false; + } + + return true; +} + +static bool _session_create_and_settings_load(const nv_api_t *nv_api, NvDRSSessionHandle *session) +{ + assert(nv_api); + assert(session); + + NvAPI_Status status; + + status = nv_api->NvAPI_DRS_CreateSession(session); + + if (status != NVAPI_OK) { + PRINT_ERR_WITH_NVAPI_MESSAGE(status, "ERROR: Creating driver settings session"); + return false; + } + + status = nv_api->NvAPI_DRS_LoadSettings(*session); + + if (status != NVAPI_OK) { + PRINT_ERR_WITH_NVAPI_MESSAGE(status, "ERROR: Loading driver settings"); + + return _session_destroy(nv_api, *session); + } + + return true; +} + +static bool _settings_save_and_session_destroy(const nv_api_t *nv_api, NvDRSSessionHandle session) +{ + assert(nv_api); + assert(session); + + NvAPI_Status status; + + printfln_err("Saving ..."); + + status = nv_api->NvAPI_DRS_SaveSettings(session); + + if (status != NVAPI_OK) { + PRINT_ERR_WITH_NVAPI_MESSAGE(status, "ERROR: Saving driver settings"); + + return _session_destroy(nv_api, session); + } + + return _session_destroy(nv_api, session); +} + +static bool _profile_get( + const nv_api_t *nv_api, + const char *profile_name, + NvDRSSessionHandle session, + NvDRSProfileHandle *profile) +{ + NvAPI_UnicodeString nv_profile_name; + NvAPI_Status status; + wchar_t *profile_name_wstr; + + assert(nv_api); + assert(session); + assert(profile); + + profile_name_wstr = str_widen(profile_name); + wstr_cpy(nv_profile_name, sizeof(nv_profile_name), profile_name_wstr); + + status = nv_api->NvAPI_DRS_FindProfileByName(session, nv_profile_name, profile); + + if (status != NVAPI_OK) { + PRINT_ERR_WITH_NVAPI_MESSAGE(status, "ERROR: Finding driver profile %s", profile_name); + return false; + } + + return true; +} + +static bool _profile_setting_set( + const nv_api_t *nv_api, + NvDRSSessionHandle session, + NvDRSProfileHandle profile, + NVDRS_SETTING *setting) +{ + NvAPI_Status status; + + assert(nv_api); + assert(setting); + + status = nv_api->NvAPI_DRS_SetSetting(session, profile, setting); + + if (status != NVAPI_OK) { + PRINT_ERR_WITH_NVAPI_MESSAGE(status, "Error setting driver settings"); + return false; + } + + return true; +} + +static void _ensure_drs_settings_folder_exists() +{ + // Even on a fresh install, this might not exist which result + // in NVAPI_ACCESS_DENIED errors on many (or even all?) nvapi DRS functions + if (!path_exists(NV_DRS_SETTINGS_FOLDER)) { + printfln_err("NOTE: DRS settings folder to store profiles does not exist, creating..."); + path_mkdir(NV_DRS_SETTINGS_FOLDER); + } +} + +// ------------------------------------------------------------------------------------------------- + +static bool _nv_info(const nv_api_t *nv_api) +{ + NvAPI_ShortString interface_version; + NvAPI_ShortString driver_version; + NvU32 driver_version_number; + NvAPI_Status status; + + printfln_err("Getting interface version..."); + + status = nv_api->NvAPI_GetInterfaceVersionStringEx(interface_version); + + if (status != NVAPI_OK) { + PRINT_ERR_WITH_NVAPI_MESSAGE(status, "ERROR: Getting interface version string"); + return false; + } + + printfln_err("Getting driver and branch version..."); + + status = nv_api->NvAPI_SYS_GetDriverAndBranchVersion(&driver_version_number, driver_version); + + if (status != NVAPI_OK) { + PRINT_ERR_WITH_NVAPI_MESSAGE(status, "ERROR: Getting driver and branch version"); + return false; + } + + printfln_out("Interface version: %s", interface_version); + printfln_out("Driver version: %s", driver_version); + printfln_out("Driver version number: %lu", driver_version_number); + + return true; +} + +static bool _profile_create(const nv_api_t *nv_api, const char *profile_name) +{ + NvDRSSessionHandle session; + NVDRS_PROFILE profile; + wchar_t *profile_name_wstr; + NvAPI_Status status; + NvDRSProfileHandle profile_handle; + + assert(nv_api); + assert(profile_name); + + if (!_session_create_and_settings_load(nv_api, &session)) { + return false; + } + + profile.version = NVDRS_PROFILE_VER; + + profile_name_wstr = str_widen(profile_name); + wstr_cpy(profile.profileName, sizeof(profile.profileName), profile_name_wstr); + free(profile_name_wstr); + + printfln_err("Creating profile %s...", profile_name); + + status = nv_api->NvAPI_DRS_CreateProfile(session, &profile, &profile_handle); + + if (status != NVAPI_OK) { + PRINT_ERR_WITH_NVAPI_MESSAGE(status, "ERROR: Creating driver profile"); + + _session_destroy(nv_api, session); + return false; + } + + return _settings_save_and_session_destroy(nv_api, session); +} + +static bool _profile_delete(const nv_api_t *nv_api, const char *profile_name) +{ + NvDRSSessionHandle session; + NvDRSProfileHandle profile; + NvAPI_Status status; + + assert(nv_api); + assert(profile_name); + + if (!_session_create_and_settings_load(nv_api, &session)) { + return false; + } + + if (!_profile_get(nv_api, profile_name, session, &profile)) { + _session_destroy(nv_api, session); + + return false; + } + + printfln_err("Deleting profile %s...", profile_name); + + status = nv_api->NvAPI_DRS_DeleteProfile(session, profile); + + if (status != NVAPI_OK) { + PRINT_ERR_WITH_NVAPI_MESSAGE(status, "ERROR: Deleting driver profile %s", profile_name); + + _session_destroy(nv_api, session); + + return false; + } + + return _settings_save_and_session_destroy(nv_api, session); +} + +static bool _profile_application_add(const nv_api_t *nv_api, const char *profile_name, const char *application_name) +{ + NvDRSSessionHandle session; + NvDRSProfileHandle profile; + NvAPI_Status status; + NVDRS_APPLICATION application; + wchar_t *application_name_wstr; + + assert(nv_api); + assert(profile_name); + assert(application_name); + + if (!_session_create_and_settings_load(nv_api, &session)) { + return false; + } + + if (!_profile_get(nv_api, profile_name, session, &profile)) { + _session_destroy(nv_api, session); + + return false; + } + + application.version = NVDRS_APPLICATION_VER; + + application_name_wstr = str_widen(application_name); + wstr_cpy(application.appName, sizeof(application.appName), application_name_wstr); + free(application_name_wstr); + + printfln_err("Adding application %s to profile %s...", application_name, profile_name); + + status = nv_api->NvAPI_DRS_CreateApplication(session, profile, &application); + + if (status != NVAPI_OK) { + PRINT_ERR_WITH_NVAPI_MESSAGE(status, "ERROR: Adding application %s to profile %s", application_name, profile_name); + + _session_destroy(nv_api, session); + + return false; + } + + return _settings_save_and_session_destroy(nv_api, session); +} + +static bool _profile_gsync_disable(const nv_api_t *nv_api, const char *profile_name) +{ + NvDRSSessionHandle session; + NvDRSProfileHandle profile; + NVDRS_SETTING setting; + + assert(nv_api); + assert(profile_name); + + if (!_session_create_and_settings_load(nv_api, &session)) { + return false; + } + + if (!_profile_get(nv_api, profile_name, session, &profile)) { + _session_destroy(nv_api, session); + + return false; + } + + setting.version = NVDRS_SETTING_VER; + setting.settingId = VRR_APP_OVERRIDE_ID; + setting.settingType = NVDRS_DWORD_TYPE; + setting.u32PredefinedValue = VRR_APP_OVERRIDE_FIXED_REFRESH; + setting.u32CurrentValue = VRR_APP_OVERRIDE_FIXED_REFRESH; + + printfln_err("Disabling G-SYNC for profile %s...", profile_name); + + if (!_profile_setting_set(nv_api, session, profile, &setting)) { + _session_destroy(nv_api, session); + + return false; + } + + return _settings_save_and_session_destroy(nv_api, session); +} + +static bool _profile_gpu_power_state_max(const nv_api_t *nv_api, const char *profile_name) +{ + NvDRSSessionHandle session; + NvDRSProfileHandle profile; + NVDRS_SETTING setting; + + assert(nv_api); + assert(profile_name); + + if (!_session_create_and_settings_load(nv_api, &session)) { + return false; + } + + if (!_profile_get(nv_api, profile_name, session, &profile)) { + _session_destroy(nv_api, session); + + return false; + } + + setting.version = NVDRS_SETTING_VER; + setting.settingId = PREFERRED_PSTATE_ID; + setting.settingType = NVDRS_DWORD_TYPE; + setting.u32PredefinedValue = PREFERRED_PSTATE_PREFER_MAX; + setting.u32CurrentValue = PREFERRED_PSTATE_PREFER_MAX; + + printfln_err("Setting GPU power state to maximum for profile %s...", profile_name); + + if (!_profile_setting_set(nv_api, session, profile, &setting)) { + _session_destroy(nv_api, session); + + return false; + } + + return _settings_save_and_session_destroy(nv_api, session); +} + +static bool _displays_list(const nv_api_t *nv_api) +{ + NvAPI_Status status; + NvPhysicalGpuHandle gpu_handle[NVAPI_MAX_PHYSICAL_GPUS]; + NvU32 gpu_count; + NvU32 display_id_count; + NV_GPU_DISPLAYIDS *display_ids; + const char *connector_type; + + assert(nv_api); + + status = nv_api->NvAPI_EnumPhysicalGPUs(gpu_handle, &gpu_count); + + if (status != NVAPI_OK) { + PRINT_ERR_WITH_NVAPI_MESSAGE(status, "ERROR: Enumerating physical GPUs"); + return false; + } + + printfln_err("Display ID, Connector Type, Active, Connected, Physically Connected"); + + for (NvU32 i = 0; i < gpu_count; i++) { + // Get display count per GPU + display_id_count = 0; + status = nv_api->NvAPI_GPU_GetConnectedDisplayIds(gpu_handle[i], NULL, &display_id_count, 0); + + if (status != NVAPI_OK) { + PRINT_ERR_WITH_NVAPI_MESSAGE(status, "ERROR: Getting connected display count of GPU"); + return false; + } + + if (display_id_count > 0) { + display_ids = (NV_GPU_DISPLAYIDS*) xmalloc(sizeof(NV_GPU_DISPLAYIDS) * display_id_count); + + for (NvU32 j = 0; j < display_id_count; j++) { + display_ids[j].version = NV_GPU_DISPLAYIDS_VER; + } + + status = nv_api->NvAPI_GPU_GetConnectedDisplayIds(gpu_handle[i], display_ids, &display_id_count, 0); + + if (status != NVAPI_OK) { + free(display_ids); + PRINT_ERR_WITH_NVAPI_MESSAGE(status, "ERROR: Getting connected display ids of GPU"); + return false; + } + + for (NvU32 j = 0; j < display_id_count; j++) { + switch (display_ids[j].connectorType) { + case NV_MONITOR_CONN_TYPE_VGA: + connector_type = "VGA"; + break; + case NV_MONITOR_CONN_TYPE_COMPONENT: + connector_type = "Component"; + break; + case NV_MONITOR_CONN_TYPE_SVIDEO: + connector_type = "S-Video"; + break; + case NV_MONITOR_CONN_TYPE_HDMI: + connector_type = "HDMI"; + break; + case NV_MONITOR_CONN_TYPE_DVI: + connector_type = "DVI"; + break; + case NV_MONITOR_CONN_TYPE_LVDS: + connector_type = "LVDS"; + break; + case NV_MONITOR_CONN_TYPE_DP: + connector_type = "DP"; + break; + case NV_MONITOR_CONN_TYPE_COMPOSITE: + connector_type = "Composite"; + break; + default: + connector_type = "unknown"; + break; + } + + printfln_out("0x%lX, %s, %s, %s, %s", + display_ids[j].displayId, + connector_type, + display_ids[j].isActive ? "active" : "inactive", + display_ids[j].isConnected ? "connected" : "disconnected", + display_ids[j].isPhysicallyConnected ? "connected" : "disconnected"); + } + + free(display_ids); + } + } + + return true; +} + +static bool _display_config_get(const nv_api_t *nv_api, NvU32 display_id) +{ + NvU32 target_info_count; + NV_DISPLAYCONFIG_PATH_INFO display_config; + NvAPI_Status status; + NV_DISPLAYCONFIG_PATH_TARGET_INFO_V2 *target_info; + NV_DISPLAYCONFIG_SOURCE_MODE_INFO_V1 *source_mode_info; + + assert(nv_api); + + status = nv_api->NvAPI_DISP_GetDisplayConfig(&target_info_count, NULL); + + if (status != NVAPI_OK) { + PRINT_ERR_WITH_NVAPI_MESSAGE(status, "ERROR: Getting display config count"); + return false; + } + + memset(&display_config, 0, sizeof(NV_DISPLAYCONFIG_PATH_INFO)); + + display_config.version = NV_DISPLAYCONFIG_PATH_INFO_VER; + display_config.targetInfoCount = target_info_count; + display_config.targetInfo = (NV_DISPLAYCONFIG_PATH_TARGET_INFO_V2*) xmalloc(sizeof(NV_DISPLAYCONFIG_PATH_TARGET_INFO_V2) * target_info_count); + display_config.sourceModeInfo = (NV_DISPLAYCONFIG_SOURCE_MODE_INFO_V1*) xmalloc(sizeof(NV_DISPLAYCONFIG_SOURCE_MODE_INFO_V1) * target_info_count); + + status = nv_api->NvAPI_DISP_GetDisplayConfig(&target_info_count, &display_config); + + if (status != NVAPI_OK) { + PRINT_ERR_WITH_NVAPI_MESSAGE(status, "ERROR: Getting display config"); + + free(display_config.targetInfo); + free(display_config.sourceModeInfo); + + return false; + } + + if (display_id != 0) { + printfln_err("Applying display ID filter: %lX", display_id); + } + + printfln_err("Num total display configs: %ld", display_config.targetInfoCount); + + for (NvU32 i = 0; i < display_config.targetInfoCount; i++) { + target_info = &display_config.targetInfo[i]; + source_mode_info = &display_config.sourceModeInfo[i]; + + if (display_id != 0 && target_info->displayId != display_id) { + continue; + } + + printfln_out("--------------------------------"); + printfln_out("Display ID: %lX", target_info->displayId); + printfln_out("Resolution width: %ld", source_mode_info->resolution.width); + printfln_out("Resolution height: %ld", source_mode_info->resolution.height); + printfln_out("Color depth: %d", source_mode_info->colorFormat); + printfln_out("Position x: %d", source_mode_info->position.x); + printfln_out("Position y: %d", source_mode_info->position.y); + printfln_out("Spanning orientation: %d", source_mode_info->spanningOrientation); + printfln_out("GDI primary: %d", source_mode_info->bGDIPrimary); + printfln_out("SLI focus: %d", source_mode_info->bSLIFocus); + + if (target_info->details) { + switch (target_info->details->rotation) { + case NV_ROTATE_0: + printfln_out("Rotation: 0 degrees"); + break; + case NV_ROTATE_90: + printfln_out("Rotation: 90 degrees"); + break; + case NV_ROTATE_180: + printfln_out("Rotation: 180 degrees"); + break; + case NV_ROTATE_270: + printfln_out("Rotation: 270 degrees"); + break; + case NV_ROTATE_IGNORED: + printfln_out("Rotation: ignored"); + break; + default: + printfln_out("Rotation: unknown (%d)", target_info->details->rotation); + break; + } + + switch (target_info->details->scaling) { + case NV_SCALING_DEFAULT: + printfln_out("Scaling: default"); + break; + case NV_SCALING_GPU_SCALING_TO_CLOSEST: + printfln_out("Scaling: GPU scaling to closest"); + break; + case NV_SCALING_GPU_SCALING_TO_NATIVE: + printfln_out("Scaling: GPU scaling to native"); + break; + case NV_SCALING_GPU_SCANOUT_TO_NATIVE: + printfln_out("Scaling: GPU scanout to native"); + break; + case NV_SCALING_GPU_SCALING_TO_ASPECT_SCANOUT_TO_NATIVE: + printfln_out("Scaling: GPU scaling to aspect scanout to native"); + break; + case NV_SCALING_GPU_SCALING_TO_ASPECT_SCANOUT_TO_CLOSEST: + printfln_out("Scaling: GPU scaling to aspect scanout to closest"); + break; + case NV_SCALING_GPU_SCANOUT_TO_CLOSEST: + printfln_out("Scaling: GPU scanout to closest"); + break; + case NV_SCALING_GPU_INTEGER_ASPECT_SCALING: + printfln_out("Scaling: GPU integer aspect scaling"); + break; + default: + printfln_out("Scaling: unknown (%d)", target_info->details->scaling); + break; + } + + printfln_out("Refresh rate (non interlaced): %ld", target_info->details->refreshRate1K * 1000); + printfln_out("Interlaced: %d", target_info->details->interlaced); + printfln_out("Primary: %d", target_info->details->primary); + printfln_out("Disable virtual mode support: %d", target_info->details->disableVirtualModeSupport); + printfln_out("Is preferred unscaled target: %d", target_info->details->isPreferredUnscaledTarget); + + switch (target_info->details->connector) { + case NVAPI_GPU_CONNECTOR_VGA_15_PIN: + printfln_out("Connector: VGA 15 Pin"); + break; + case NVAPI_GPU_CONNECTOR_TV_COMPOSITE: + printfln_out("Connector: TV Composite"); + break; + case NVAPI_GPU_CONNECTOR_TV_SVIDEO: + printfln_out("Connector: TV S-Video"); + break; + case NVAPI_GPU_CONNECTOR_TV_HDTV_COMPONENT: + printfln_out("Connector: TV HDTV Component"); + break; + case NVAPI_GPU_CONNECTOR_TV_SCART: + printfln_out("Connector: TV SCART"); + break; + case NVAPI_GPU_CONNECTOR_TV_COMPOSITE_SCART_ON_EIAJ4120: + printfln_out("Connector: TV Composite SCART on EIAJ4120"); + break; + case NVAPI_GPU_CONNECTOR_TV_HDTV_EIAJ4120: + printfln_out("Connector: TV HDTV EIAJ4120"); + break; + case NVAPI_GPU_CONNECTOR_PC_POD_HDTV_YPRPB: + printfln_out("Connector: PC POD HDTV YPRPB"); + break; + case NVAPI_GPU_CONNECTOR_PC_POD_SVIDEO: + printfln_out("Connector: PC POD S-Video"); + break; + case NVAPI_GPU_CONNECTOR_PC_POD_COMPOSITE: + printfln_out("Connector: PC POD Composite"); + break; + case NVAPI_GPU_CONNECTOR_DVI_I_TV_SVIDEO: + printfln_out("Connector: DVI-I TV S-Video"); + break; + case NVAPI_GPU_CONNECTOR_DVI_I_TV_COMPOSITE: + printfln_out("Connector: DVI-I TV Composite"); + break; + case NVAPI_GPU_CONNECTOR_DVI_I: + printfln_out("Connector: DVI-I"); + break; + case NVAPI_GPU_CONNECTOR_DVI_D: + printfln_out("Connector: DVI-D"); + break; + case NVAPI_GPU_CONNECTOR_ADC: + printfln_out("Connector: ADC"); + break; + case NVAPI_GPU_CONNECTOR_LFH_DVI_I_1: + printfln_out("Connector: LFH DVI-I 1"); + break; + case NVAPI_GPU_CONNECTOR_LFH_DVI_I_2: + printfln_out("Connector: LFH DVI-I 2"); + break; + case NVAPI_GPU_CONNECTOR_SPWG: + printfln_out("Connector: SPWG"); + break; + case NVAPI_GPU_CONNECTOR_OEM: + printfln_out("Connector: OEM"); + break; + case NVAPI_GPU_CONNECTOR_DISPLAYPORT_EXTERNAL: + printfln_out("Connector: DisplayPort External"); + break; + case NVAPI_GPU_CONNECTOR_DISPLAYPORT_INTERNAL: + printfln_out("Connector: DisplayPort Internal"); + break; + case NVAPI_GPU_CONNECTOR_DISPLAYPORT_MINI_EXT: + printfln_out("Connector: DisplayPort Mini External"); + break; + case NVAPI_GPU_CONNECTOR_HDMI_A: + printfln_out("Connector: HDMI A"); + break; + case NVAPI_GPU_CONNECTOR_HDMI_C_MINI: + printfln_out("Connector: HDMI C Mini"); + break; + case NVAPI_GPU_CONNECTOR_LFH_DISPLAYPORT_1: + printfln_out("Connector: LFH DisplayPort 1"); + break; + case NVAPI_GPU_CONNECTOR_LFH_DISPLAYPORT_2: + printfln_out("Connector: LFH DisplayPort 2"); + break; + case NVAPI_GPU_CONNECTOR_VIRTUAL_WFD: + printfln_out("Connector: Virtual WFD"); + break; + case NVAPI_GPU_CONNECTOR_USB_C: + printfln_out("Connector: USB C"); + break; + default: + printfln_out("Connector: unknown (%d)", target_info->details->connector); + break; + } + + switch (target_info->details->tvFormat) { + case NV_DISPLAY_TV_FORMAT_NONE: + printfln_out("TV format: none"); + break; + case NV_DISPLAY_TV_FORMAT_SD_NTSCM: + printfln_out("TV format: SD NTSC-M"); + break; + case NV_DISPLAY_TV_FORMAT_SD_NTSCJ: + printfln_out("TV format: SD NTSC-J"); + break; + case NV_DISPLAY_TV_FORMAT_SD_PALM: + printfln_out("TV format: SD PAL-M"); + break; + case NV_DISPLAY_TV_FORMAT_SD_PALBDGH: + printfln_out("TV format: SD PAL-BDGH"); + break; + case NV_DISPLAY_TV_FORMAT_SD_PALN: + printfln_out("TV format: SD PAL-N"); + break; + case NV_DISPLAY_TV_FORMAT_SD_PALNC: + printfln_out("TV format: SD PAL-NC"); + break; + case NV_DISPLAY_TV_FORMAT_SD_576i: + printfln_out("TV format: SD 576i"); + break; + case NV_DISPLAY_TV_FORMAT_SD_480i: + printfln_out("TV format: SD 480i"); + break; + case NV_DISPLAY_TV_FORMAT_ED_480p: + printfln_out("TV format: ED 480p"); + break; + case NV_DISPLAY_TV_FORMAT_ED_576p: + printfln_out("TV format: ED 576p"); + break; + case NV_DISPLAY_TV_FORMAT_HD_720p: + printfln_out("TV format: HD 720p"); + break; + case NV_DISPLAY_TV_FORMAT_HD_1080i: + printfln_out("TV format: HD 1080i"); + break; + case NV_DISPLAY_TV_FORMAT_HD_1080p: + printfln_out("TV format: HD 1080p"); + break; + case NV_DISPLAY_TV_FORMAT_HD_720p50: + printfln_out("TV format: HD 720p50"); + break; + case NV_DISPLAY_TV_FORMAT_HD_1080p24: + printfln_out("TV format: HD 1080p24"); + break; + case NV_DISPLAY_TV_FORMAT_HD_1080i50: + printfln_out("TV format: HD 1080i50"); + break; + case NV_DISPLAY_TV_FORMAT_HD_1080p50: + printfln_out("TV format: HD 1080p50"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_4Kp30_3840: + printfln_out("TV format: UHD 4Kp30 3840"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_4Kp25_3840: + printfln_out("TV format: UHD 4Kp25 3840"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_4Kp24_3840: + printfln_out("TV format: UHD 4Kp24 3840"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_4Kp24_SMPTE: + printfln_out("TV format: UHD 4Kp24 SMPTE"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_4Kp50_3840: + printfln_out("TV format: UHD 4Kp50 3840"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_4Kp60_3840: + printfln_out("TV format: UHD 4Kp60 3840"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_4Kp30_4096: + printfln_out("TV format: UHD 4Kp30 4096"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_4Kp25_4096: + printfln_out("TV format: UHD 4Kp25 4096"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_4Kp24_4096: + printfln_out("TV format: UHD 4Kp24 4096"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_4Kp50_4096: + printfln_out("TV format: UHD 4Kp50 4096"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_4Kp60_4096: + printfln_out("TV format: UHD 4Kp60 4096"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_8Kp24_7680: + printfln_out("TV format: UHD 8Kp24 7680"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_8Kp25_7680: + printfln_out("TV format: UHD 8Kp25 7680"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_8Kp30_7680: + printfln_out("TV format: UHD 8Kp30 7680"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_8Kp48_7680: + printfln_out("TV format: UHD 8Kp48 7680"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_8Kp50_7680: + printfln_out("TV format: UHD 8Kp50 7680"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_8Kp60_7680: + printfln_out("TV format: UHD 8Kp60 7680"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_8Kp100_7680: + printfln_out("TV format: UHD 8Kp100 7680"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_8Kp120_7680: + printfln_out("TV format: UHD 8Kp120 7680"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_4Kp48_3840: + printfln_out("TV format: UHD 4Kp48 3840"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_4Kp48_4096: + printfln_out("TV format: UHD 4Kp48 4096"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_4Kp100_3840: + printfln_out("TV format: UHD 4Kp100 3840"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_4Kp100_4096: + printfln_out("TV format: UHD 4Kp100 4096"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_4Kp120_4096: + printfln_out("TV format: UHD 4Kp120 4096"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_4Kp120_3840: + printfln_out("TV format: UHD 4Kp120 3840"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_4Kp100_5120: + printfln_out("TV format: UHD 4Kp100 5120"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_4Kp120_5120: + printfln_out("TV format: UHD 4Kp120 5120"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_4Kp24_5120: + printfln_out("TV format: UHD 4Kp24 5120"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_4Kp25_5120: + printfln_out("TV format: UHD 4Kp25 5120"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_4Kp30_5120: + printfln_out("TV format: UHD 4Kp30 5120"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_4Kp48_5120: + printfln_out("TV format: UHD 4Kp48 5120"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_4Kp50_5120: + printfln_out("TV format: UHD 4Kp50 5120"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_4Kp60_5120: + printfln_out("TV format: UHD 4Kp60 5120"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_10Kp24_10240: + printfln_out("TV format: UHD 10Kp24 10240"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_10Kp25_10240: + printfln_out("TV format: UHD 10Kp25 10240"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_10Kp30_10240: + printfln_out("TV format: UHD 10Kp30 10240"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_10Kp48_10240: + printfln_out("TV format: UHD 10Kp48 10240"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_10Kp50_10240: + printfln_out("TV format: UHD 10Kp50 10240"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_10Kp60_10240: + printfln_out("TV format: UHD 10Kp60 10240"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_10Kp100_10240: + printfln_out("TV format: UHD 10Kp100 10240"); + break; + case NV_DISPLAY_TV_FORMAT_UHD_10Kp120_10240: + printfln_out("TV format: UHD 10Kp120 10240"); + break; + case NV_DISPLAY_TV_FORMAT_SD_OTHER: + printfln_out("TV format: SD Other"); + break; + case NV_DISPLAY_TV_FORMAT_ED_OTHER: + printfln_out("TV format: ED Other"); + break; + case NV_DISPLAY_TV_FORMAT_HD_OTHER: + printfln_out("TV format: HD Other"); + break; + case NV_DISPLAY_TV_FORMAT_ANY: + printfln_out("TV format: Any"); + break; + default: + printfln_out("TV format: unknown (%d)", target_info->details->tvFormat); + break; + } + + switch (target_info->details->timingOverride) { + case NV_TIMING_OVERRIDE_CURRENT: + printfln_out("Timing override: current"); + break; + case NV_TIMING_OVERRIDE_AUTO: + printfln_out("Timing override: auto"); + break; + case NV_TIMING_OVERRIDE_EDID: + printfln_out("Timing override: EDID"); + break; + case NV_TIMING_OVERRIDE_DMT: + printfln_out("Timing override: VESA DMT"); + break; + case NV_TIMING_OVERRIDE_DMT_RB: + printfln_out("Timing override: VESA DMT RB"); + break; + case NV_TIMING_OVERRIDE_CVT: + printfln_out("Timing override: VESA CVT"); + break; + case NV_TIMING_OVERRIDE_CVT_RB: + printfln_out("Timing override: VESA CVT RB"); + break; + case NV_TIMING_OVERRIDE_GTF: + printfln_out("Timing override: VESA GTF"); + break; + case NV_TIMING_OVERRIDE_EIA861: + printfln_out("Timing override: EIA 861x pre-defined timing"); + break; + case NV_TIMING_OVERRIDE_ANALOG_TV: + printfln_out("Timing override: analog SD/HDTV timing"); + break; + case NV_TIMING_OVERRIDE_CUST: + printfln_out("Timing override: NV custom timings"); + break; + case NV_TIMING_OVERRIDE_NV_PREDEFINED: + printfln_out("Timing override: NV pre-defined timing (basically the PsF timings)"); + break; + case NV_TIMING_OVERRIDE_NV_ASPR: + printfln_out("Timing override: NV ASPR"); + break; + case NV_TIMING_OVERRIDE_SDI: + printfln_out("Timing override: SDI"); + break; + case NV_TIMING_OVRRIDE_MAX: + printfln_out("Timing override: max"); + break; + default: + printfln_out("Timing override: unknown (%d)", target_info->details->timingOverride); + break; + } + + printfln_out("Override custom (backend raster) timing"); + + printfln_out("Horizontal visible: %d", target_info->details->timing.HVisible); + printfln_out("Horizontal border: %d", target_info->details->timing.HBorder); + printfln_out("Horizontal front porch: %d", target_info->details->timing.HFrontPorch); + printfln_out("Horizontal sync width: %d", target_info->details->timing.HSyncWidth); + printfln_out("Horizontal total: %d", target_info->details->timing.HTotal); + printfln_out("Horizontal sync polarity: %s", target_info->details->timing.HSyncPol == 1 ? "negative" : "positive"); + + printfln_out("Vertical visible: %d", target_info->details->timing.VVisible); + printfln_out("Vertical border: %d", target_info->details->timing.VBorder); + printfln_out("Vertical front porch: %d", target_info->details->timing.VFrontPorch); + printfln_out("Vertical sync width: %d", target_info->details->timing.VSyncWidth); + printfln_out("Vertical total: %d", target_info->details->timing.VTotal); + printfln_out("Vertical sync polarity: %s", target_info->details->timing.VSyncPol == 1 ? "negative" : "positive"); + + printfln_out("Interlaced: %s", target_info->details->timing.interlaced == 1 ? "interlaced" : "progressive"); + printfln_out("Pixel clock: %lu kHz", target_info->details->timing.pclk * 10); + + printfln_out("Flag: 0x%lX", target_info->details->timing.etc.flag); + printfln_out("Logical refresh rate to present: %d", target_info->details->timing.etc.rr); + printfln_out("Physical vertical refresh rate: %f Hz", target_info->details->timing.etc.rrx1k * 0.001); + printfln_out("Display aspect ratio: %lu:%lu", target_info->details->timing.etc.aspect >> 16, target_info->details->timing.etc.aspect & 0xFFFF); + printfln_out("Bit-wise pixel repetition factor (1 = no pixel repetition, 2 = each pixel repeats twice horizontally, etc.): %d", target_info->details->timing.etc.rep); + printfln_out("Timing status: 0x%lX", target_info->details->timing.etc.status); + printfln_out("Timing name: %s", target_info->details->timing.etc.name); + } + + printfln_out("--------------------------------"); + } + + free(display_config.targetInfo); + free(display_config.sourceModeInfo); + + return true; +} + +static bool _custom_resolution_set( + const nv_api_t *nv_api, + NvU32 display_id, + uint16_t screen_width, + uint16_t screen_height, + float screen_refresh_rate, + uint8_t test_only_timeout_sec) +{ + NV_CUSTOM_DISPLAY custom_display; + NV_TIMING_FLAG flag; + NV_TIMING_INPUT timing; + NvAPI_Status status; + + assert(nv_api); + + memset(&custom_display, 0, sizeof(NV_CUSTOM_DISPLAY)); + memset(&flag, 0, sizeof(NV_TIMING_FLAG)); + memset(&timing, 0, sizeof(NV_TIMING_INPUT)); + + custom_display.version = NV_CUSTOM_DISPLAY_VER; + custom_display.width = screen_width; + custom_display.height = screen_height; + custom_display.depth = 32; + custom_display.colorFormat = NV_FORMAT_A8R8G8B8; + custom_display.srcPartition.x = 0; + custom_display.srcPartition.y = 0; + custom_display.srcPartition.w = 1; + custom_display.srcPartition.h = 1; + custom_display.xRatio = 1; + custom_display.yRatio = 1; + + timing.version = NV_TIMING_INPUT_VER; + timing.height = screen_height; + timing.width = screen_width; + timing.rr = screen_refresh_rate; + timing.flag = flag; + timing.type = NV_TIMING_OVERRIDE_CVT_RB; + + printfln_err("Calculating custom display timing for display ID 0x%lX", display_id); + + status = nv_api->NvAPI_DISP_GetTiming(display_id, &timing, &custom_display.timing); + + if (status != NVAPI_OK) { + PRINT_ERR_WITH_NVAPI_MESSAGE(status, "ERROR: Calculating custom display timing"); + return false; + } + + status = nv_api->NvAPI_DISP_TryCustomDisplay(&display_id, 1, &custom_display); + + if (status != NVAPI_OK) { + PRINT_ERR_WITH_NVAPI_MESSAGE(status, "ERROR: Trying custom display configuration"); + return false; + } + + if (test_only_timeout_sec == 0) { + printfln_err("Persisting custom display configuration"); + + status = nv_api->NvAPI_DISP_SaveCustomDisplay(&display_id, 1, true, true); + + if (status != NVAPI_OK) { + PRINT_ERR_WITH_NVAPI_MESSAGE(status, "ERROR: Saving custom display configuration"); + return false; + } + + printfln_err("Custom display configuration persisted"); + } else { + printfln_err("Trying custom display configuration for %d seconds...", test_only_timeout_sec); + + Sleep(test_only_timeout_sec * 1000); + + printfln_err("Reverting custom display configuration, not persisting"); + + status = nv_api->NvAPI_DISP_RevertCustomDisplayTrial(&display_id, 1); + + if (status != NVAPI_OK) { + PRINT_ERR_WITH_NVAPI_MESSAGE(status, "ERROR: Reverting custom display configuration"); + return false; + } + } + + return true; +} + +// ------------------------------------------------------------------------------------------------- + +static void _print_synopsis() +{ + printfln_err("Usage: nvgpu "); + printfln_err("Commands:"); + printfln_err(" nv"); + printfln_err(" info Print information about the NVAPI module and driver"); + printfln_err(""); + printfln_err(" profile"); + printfln_err(" create Create a new driver profile with the given name"); + printfln_err(" delete Delete an existing driver profile"); + printfln_err(" application-add "); + printfln_err(" Add an application to the driver profile. This will apply the profile to the application when the driver detects a process being launched, e.g. MyApplication.exe"); + printfln_err(" gsync-disable Disable G-SYNC for the driver profile"); + printfln_err(" gpu-power-state-max Set GPU power state to maximum for the driver profile"); + printfln_err(""); + printfln_err(" display"); + printfln_err(" list List all connected displays and their display IDs"); + printfln_err(" config-get [display_id] Get the current display configurations. Optionally, specify a display ID to get the configuration of that display only"); + printfln_err(" custom-resolution-set "); + printfln_err(" Set a custom display mode with the given parameters for the given display ID. The settings are persisted immediately. Ensure you tested these before with the custom-display-test command."); + printfln_err(" custom-resolution-test [--test-timeout-secs n]"); + printfln_err(" Test a custom display mode for a limited amount of time. This will revert the display mode after the given amount of seconds and not persist the changes."); + printfln_err(" test-timeout-secs: Optional. Number of seconds to test the custom display mode for. Default is 10 seconds."); +} + +static bool _cmd_nv_info(const nv_api_t *nv_api) +{ + return _nv_info(nv_api); +} + +static bool _cmd_profile_create(const nv_api_t *nv_api, int argc, char **argv) +{ + const char *profile_name; + + if (argc < 1) { + _print_synopsis(); + printfln_err("ERROR: Insufficient arguments"); + return false; + } + + profile_name = argv[0]; + + return _profile_create(nv_api, profile_name); +} + +static bool _cmd_profile_delete(const nv_api_t *nv_api, int argc, char **argv) +{ + const char *profile_name; + + if (argc < 1) { + _print_synopsis(); + printfln_err("ERROR: Insufficient arguments"); + return false; + } + + profile_name = argv[0]; + + return _profile_delete(nv_api, profile_name); +} + +static bool _cmd_profile_application_add(const nv_api_t *nv_api, int argc, char **argv) +{ + const char *profile_name; + const char *application_name; + + if (argc < 2) { + _print_synopsis(); + printfln_err("ERROR: Insufficient arguments"); + return false; + } + + profile_name = argv[0]; + application_name = argv[1]; + + return _profile_application_add(nv_api, profile_name, application_name); +} + +static bool _cmd_profile_gsync_disable(const nv_api_t *nv_api, int argc, char **argv) +{ + const char *profile_name; + + if (argc < 1) { + _print_synopsis(); + printfln_err("ERROR: Insufficient arguments"); + return false; + } + + profile_name = argv[0]; + + return _profile_gsync_disable(nv_api, profile_name); +} + +static bool _cmd_profile_gpu_power_state_max(const nv_api_t *nv_api, int argc, char **argv) +{ + const char *profile_name; + + if (argc < 1) { + _print_synopsis(); + printfln_err("ERROR: Insufficient arguments"); + return false; + } + + profile_name = argv[0]; + + return _profile_gpu_power_state_max(nv_api, profile_name); +} + +static bool _cmd_display_list(const nv_api_t *nv_api) +{ + return _displays_list(nv_api); +} + +static bool _cmd_display_config_get(const nv_api_t *nv_api, int argc, char **argv) +{ + uint32_t display_id; + + if (argc > 0) { + if (argv[0][0] == '0' && argv[0][1] == 'x') { + display_id = strtoul(argv[0] + 2, NULL, 16); + } else { + display_id = strtoul(argv[0], NULL, 10); + } + } else { + display_id = 0; + } + + return _display_config_get(nv_api, display_id); +} + +static bool _cmd_custom_resolution_set(const nv_api_t *nv_api, int argc, char **argv) +{ + uint32_t display_id; + uint16_t screen_width; + uint16_t screen_height; + float screen_refresh_rate; + + if (argc < 4) { + _print_synopsis(); + printfln_err("ERROR: Insufficient arguments"); + return false; + } + + if (argv[0][0] == '0' && argv[0][1] == 'x') { + display_id = strtoul(argv[0] + 2, NULL, 16); + } else { + display_id = strtoul(argv[0], NULL, 10); + } + + screen_width = atoi(argv[1]); + screen_height = atoi(argv[2]); + screen_refresh_rate = atof(argv[3]); + + printf_err("Setting custom resolution for display ID 0x%X: %dx%d@%f\n", display_id, screen_width, screen_height, screen_refresh_rate); + + return _custom_resolution_set( + nv_api, + display_id, + screen_width, + screen_height, + screen_refresh_rate, + 0); +} + +static bool _cmd_custom_resolution_test(const nv_api_t *nv_api, int argc, char **argv) +{ + uint32_t display_id; + uint16_t screen_width; + uint16_t screen_height; + float screen_refresh_rate; + uint8_t test_only_timeout_sec; + + if (argc < 5) { + _print_synopsis(); + printfln_err("ERROR: Insufficient arguments"); + return false; + } + + if (argv[0][0] == '0' && argv[0][1] == 'x') { + display_id = strtoul(argv[0] + 2, NULL, 16); + } else { + display_id = strtoul(argv[0], NULL, 10); + } + + screen_width = atoi(argv[1]); + screen_height = atoi(argv[2]); + screen_refresh_rate = atof(argv[3]); + + // Sane defaults for optional parameters + test_only_timeout_sec = 10; + + for (int i = 4; i < argc; i++) { + if (!strcmp(argv[i], "--test-timeout-secs")) { + if (i + 1 < argc) { + test_only_timeout_sec = atoi(argv[++i]); + + if (test_only_timeout_sec == 0) { + printfln_err("ERROR: Time out parameter must be greater than 0"); + return false; + } + } else { + printfln_err("ERROR: Missing argument for --test-timeout-secs"); + return false; + } + } + } + + printf_err("Testing custom resolution for display ID %u: %dx%d@%f, timeout %d seconds\n", display_id, screen_width, screen_height, screen_refresh_rate, test_only_timeout_sec); + + return _custom_resolution_set( + nv_api, + display_id, + screen_width, + screen_height, + screen_refresh_rate, + test_only_timeout_sec); +} + +// ------------------------------------------------------------------------------------------------- + +int main(int argc, char **argv) +{ + const char *command; + const char *sub_command; + nv_module_t *nv_module; + nv_api_t nv_api; + NvAPI_Status status; + bool result; + + if (argc < 3) { + _print_synopsis(); + printfln_err("ERROR: Insufficient arguments"); + exit(1); + } + + command = argv[1]; + sub_command = argv[2]; + + printfln_err("Loading NVAPI module..."); + + nv_module_load(&nv_module); + nv_module_api_get(nv_module, &nv_api); + + printfln_err("Initializing NVAPI..."); + + status = nv_api.NvAPI_Initialize(); + + if (status != NVAPI_OK) { + NvAPI_ShortString error_str; + nv_api.NvAPI_GetErrorMessage(status, error_str); + fprintf(stderr, "ERROR: Initializing NVAPI, reason: %s\n", error_str); + + nv_module_free(&nv_module); + exit(1); + } + + printfln_err("Running command: %s %s", command, sub_command); + + if (!strcmp(command, "nv")) { + if (!strcmp(sub_command, "info")) { + result = _cmd_nv_info(&nv_api); + } else { + printfln_err("ERROR: Unknown sub-command: %s", sub_command); + _print_synopsis(); + result = false; + } + } else if (!strcmp(command, "profile")) { + _ensure_drs_settings_folder_exists(); + + if (!strcmp(sub_command, "create")) { + result = _cmd_profile_create(&nv_api, argc - 3, argv + 3); + } else if (!strcmp(sub_command, "delete")) { + result = _cmd_profile_delete(&nv_api, argc - 3, argv + 3); + } else if (!strcmp(sub_command, "application-add")) { + result = _cmd_profile_application_add(&nv_api, argc - 3, argv + 3); + } else if (!strcmp(sub_command, "gsync-disable")) { + result = _cmd_profile_gsync_disable(&nv_api, argc - 3, argv + 3); + } else if (!strcmp(sub_command, "gpu-power-state-max")) { + result = _cmd_profile_gpu_power_state_max(&nv_api, argc - 3, argv + 3); + } else { + printfln_err("ERROR: Unknown sub-command: %s", sub_command); + _print_synopsis(); + result = false; + } + } else if (!strcmp(command, "display")) { + if (!strcmp(sub_command, "list")) { + result = _cmd_display_list(&nv_api); + } else if (!strcmp(sub_command, "config-get")) { + result = _cmd_display_config_get(&nv_api, argc - 3, argv + 3); + } else if (!strcmp(sub_command, "custom-resolution-set")) { + result = _cmd_custom_resolution_set(&nv_api, argc - 3, argv + 3); + } else if (!strcmp(sub_command, "custom-resolution-test")) { + result = _cmd_custom_resolution_test(&nv_api, argc - 3, argv + 3); + } else { + printfln_err("ERROR: Unknown sub-command: %s", sub_command); + _print_synopsis(); + result = false; + } + } else { + printfln_err("ERROR: Unknown command: %s", command); + _print_synopsis(); + result = false; + } + + status = nv_api.NvAPI_Unload(); + + if (status != NVAPI_OK) { + NvAPI_ShortString error_str; + nv_api.NvAPI_GetErrorMessage(status, error_str); + printfln_err("ERROR: Unloading NVAPI, reason: %s", error_str); + } + + nv_module_free(&nv_module); + + if (result) { + printfln_err("Command completed successfully"); + exit(0); + } else { + printfln_err("Command failed"); + exit(1); + } +} \ No newline at end of file