c++ tratamiento Controlador de gráficos Nvidia que causa un notable tartamudeo de cuadros




tipos de tartamudez (3)

Gracias por la respuesta de subGlitch primero, basada en esa propuesta, solo hago una más segura, lo que le permitiría almacenar en caché y cambiar la optimización de subprocesos y luego restaurarla.

El código es como abajo:

#include <stdlib.h>
#include <stdio.h>
#include <nvapi.h>
#include <NvApiDriverSettings.h>

enum NvThreadOptimization {
    NV_THREAD_OPTIMIZATION_AUTO         = 0,
    NV_THREAD_OPTIMIZATION_ENABLE       = 1,
    NV_THREAD_OPTIMIZATION_DISABLE      = 2,
    NV_THREAD_OPTIMIZATION_NO_SUPPORT   = 3
};

bool NvAPI_OK_Verify(NvAPI_Status status)
{
    if (status == NVAPI_OK)
        return true;

    NvAPI_ShortString szDesc = {0};
    NvAPI_GetErrorMessage(status, szDesc);

    char szResult[255];
    sprintf(szResult, "NVAPI error: %s\n\0", szDesc);
    printf(szResult);

    return false;
}

NvThreadOptimization GetNVidiaThreadOptimization()
{
    NvAPI_Status status;
    NvDRSSessionHandle hSession;
    NvThreadOptimization threadOptimization = NV_THREAD_OPTIMIZATION_NO_SUPPORT;

    status = NvAPI_Initialize();
    if(!NvAPI_OK_Verify(status))
        return threadOptimization;

    status = NvAPI_DRS_CreateSession(&hSession);
    if(!NvAPI_OK_Verify(status))
        return threadOptimization;

    status = NvAPI_DRS_LoadSettings(hSession);
    if(!NvAPI_OK_Verify(status))
    {
        NvAPI_DRS_DestroySession(hSession);
        return threadOptimization;;
    }


    NvDRSProfileHandle hProfile;
    status = NvAPI_DRS_GetBaseProfile(hSession, &hProfile);
    if(!NvAPI_OK_Verify(status))
    {
        NvAPI_DRS_DestroySession(hSession);
        return threadOptimization;;
    }

    NVDRS_SETTING originalSetting;
    originalSetting.version = NVDRS_SETTING_VER;
    status = NvAPI_DRS_GetSetting(hSession, hProfile, OGL_THREAD_CONTROL_ID, &originalSetting);
    if(NvAPI_OK_Verify(status))
    {
        threadOptimization = (NvThreadOptimization)originalSetting.u32CurrentValue;
    }

    NvAPI_DRS_DestroySession(hSession);

    return threadOptimization;
}

void SetNVidiaThreadOptimization(NvThreadOptimization threadedOptimization)
{
    NvAPI_Status status;
    NvDRSSessionHandle hSession;

    if(threadedOptimization == NV_THREAD_OPTIMIZATION_NO_SUPPORT)
        return;

    status = NvAPI_Initialize();
    if(!NvAPI_OK_Verify(status))
        return;

    status = NvAPI_DRS_CreateSession(&hSession);
    if(!NvAPI_OK_Verify(status))
        return;

    status = NvAPI_DRS_LoadSettings(hSession);
    if(!NvAPI_OK_Verify(status))
    {
        NvAPI_DRS_DestroySession(hSession);
        return;
    }

    NvDRSProfileHandle hProfile;
    status = NvAPI_DRS_GetBaseProfile(hSession, &hProfile);
    if(!NvAPI_OK_Verify(status))
    {
        NvAPI_DRS_DestroySession(hSession);
        return;
    }

    NVDRS_SETTING setting;
    setting.version                 = NVDRS_SETTING_VER;
    setting.settingId               = OGL_THREAD_CONTROL_ID;
    setting.settingType             = NVDRS_DWORD_TYPE;
    setting.u32CurrentValue         = (EValues_OGL_THREAD_CONTROL)threadedOptimization;

    status = NvAPI_DRS_SetSetting(hSession, hProfile, &setting);
    if(!NvAPI_OK_Verify(status))
    {
        NvAPI_DRS_DestroySession(hSession);
        return;
    }

    status = NvAPI_DRS_SaveSettings(hSession);
    NvAPI_OK_Verify(status);

    NvAPI_DRS_DestroySession(hSession);
}

De acuerdo con las dos interfaces (Obtener / Establecer) de arriba, puede guardar la configuración original y restaurarla cuando salga la aplicación. Eso significa que su configuración para deshabilitar la optimización de subprocesos solo afectará su propia aplicación.

static NvThreadOptimization s_OriginalNVidiaThreadOptimization = NV_THREAD_OPTIMIZATION_NO_SUPPORT;

// Set
s_OriginalNVidiaThreadOptimization =  GetNVidiaThreadOptimization();
if(    s_OriginalNVidiaThreadOptimization != NV_THREAD_OPTIMIZATION_NO_SUPPORT
    && s_OriginalNVidiaThreadOptimization != NV_THREAD_OPTIMIZATION_DISABLE)
{
    SetNVidiaThreadOptimization(NV_THREAD_OPTIMIZATION_DISABLE);
}

//Restore
if(    s_OriginalNVidiaThreadOptimization != NV_THREAD_OPTIMIZATION_NO_SUPPORT
    && s_OriginalNVidiaThreadOptimization != NV_THREAD_OPTIMIZATION_DISABLE)
{
    SetNVidiaThreadOptimization(s_OriginalNVidiaThreadOptimization);
};

Ok, he estado investigando este problema por unos días, así que permítame repasar lo que hasta ahora, lo que me lleva a creer que esto podría ser un problema con el controlador de NVidia y no con mi código.

Básicamente, mi juego comienza a tartamudear después de correr unos segundos (los marcos aleatorios toman 70 ms en lugar de 16 ms, en un patrón regular). Esto SOLAMENTE sucede si se habilita una configuración llamada "Optimización de subprocesos" en el panel de control de Nvidia (los controladores más recientes, Windows 10). Desafortunadamente, esta configuración está habilitada de forma predeterminada y prefiero no tener que hacer que la gente modifique sus configuraciones para obtener una experiencia agradable.

  • El juego no tiene un uso intensivo de CPU o GPU (2ms por fotograma sin vsync activado). No está llamando a ninguna función de openGL que necesite sincronizar datos, y no está transmitiendo ningún búfer ni leyendo datos de la GPU ni nada. Sobre el renderizador más simple posible.

  • El problema siempre estuvo ahí, solo comenzó a notarse cuando agregué fmod para audio. fmod no es la causa de esto (más adelante en la publicación)

  • Intentar solucionar el problema con NVidia Nsight hizo que el problema desapareciera. "Comenzar a recopilar datos" al instante hace que desaparezca el tartamudeo. No hay dados aquí.

  • En el Profiler, se pasa mucho tiempo de CPU en "nvoglv32.dll". Este proceso solo se genera si la optimización de subprocesos está activada. Sospecho que es un problema de sincronización entonces, así que depuro con Visual Studio Concurrency Viewer.

  • A-HA!

  • Al investigar estos bloques de tiempo de CPU en el subproceso de nvidia, la primera función nombrada que puedo obtener en su pila de llamadas es " CreateToolhelp32Snapshot " seguida de una gran cantidad de tiempo dedicado a Thread32Next . Noté Thread32Next en el generador de perfiles cuando miraba los tiempos de CPU antes, así que parece que estoy en el camino correcto.

  • Entonces, ¿parece que periódicamente el controlador nvidia está tomando una instantánea de todo el proceso por alguna razón? ¿Cuál podría ser la razón, por qué está haciendo esto y cómo lo detengo?

  • Además, esto explica por qué el problema comenzó a notarse una vez que lo agregué a fmod, porque su información de agarre para todos los subprocesos de procesos y fmod genera muchos subprocesos.

  • ¿Alguna ayuda? ¿Es esto solo un error en el controlador de nvidia o hay algo que pueda hacer para solucionarlo y otras personas le dicen a la gente que deshabilite la "Optimización" roscada?

edición 1: El mismo problema ocurre con los controladores nvidia actuales en mi computadora portátil también. Entonces no estoy loco

edición 2: el mismo problema ocurre en la versión 362 (versión principal anterior) del controlador de nvidia


Odio decir lo obvio, pero siento que hay que decirlo.

La optimización en subprocesos es notoria por causar tartamudeo en muchos juegos, incluso en aquellos que aprovechan el multihilo. A menos que su aplicación funcione bien con la configuración de optimización de subprocesos, la única respuesta lógica es decirle a sus usuarios que la deshabiliten. Si los usuarios son tercos y no quieren hacer eso, es su culpa.

El único error en la memoria reciente en el que puedo pensar es que las versiones anteriores del controlador nvidia causaron que las aplicaciones con la optimización de subprocesos ejecutándose en Wine se bloquearan, pero eso no está relacionado con el problema de tartamudeo que describe.


... ¿o hay algo que pueda hacer para solucionarlo si otras personas le dicen que deshabilite la "Optimización" de hilos?

Sí.

Puede crear un "Perfil de aplicación" personalizado para su juego usando NVAPI y deshabilitar la configuración de "Optimización de NVAPI " en él.

Hay un archivo .PDF en el sitio de NVIDIA con algunos ejemplos de ayuda y códigos relacionados con el uso de NVAPI.

Para ver y administrar todos sus perfiles NVIDIA, recomiendo usar NVIDIA Inspector. Es más conveniente que el panel de control predeterminado de NVIDIA.

Además, aquí está mi ejemplo de código que crea "Perfil de aplicación" con "Optimización de hilos" desactivada:

#include <stdlib.h>
#include <stdio.h>

#include <nvapi.h>
#include <NvApiDriverSettings.h>


const wchar_t*  profileName             = L"Your Profile Name";
const wchar_t*  appName                 = L"YourGame.exe";
const wchar_t*  appFriendlyName         = L"Your Game Casual Name";
const bool      threadedOptimization    = false;


void CheckError(NvAPI_Status status)
{
    if (status == NVAPI_OK)
        return;

    NvAPI_ShortString szDesc = {0};
    NvAPI_GetErrorMessage(status, szDesc);
    printf("NVAPI error: %s\n", szDesc);
    exit(-1);
}


void SetNVUstring(NvAPI_UnicodeString& nvStr, const wchar_t* wcStr)
{
    for (int i = 0; i < NVAPI_UNICODE_STRING_MAX; i++)
        nvStr[i] = 0;

    int i = 0;
    while (wcStr[i] != 0)
    {
        nvStr[i] = wcStr[i];
        i++;
    }
}


int main(int argc, char* argv[])
{
    NvAPI_Status status;
    NvDRSSessionHandle hSession;

    status = NvAPI_Initialize();
    CheckError(status);

    status = NvAPI_DRS_CreateSession(&hSession);
    CheckError(status);

    status = NvAPI_DRS_LoadSettings(hSession);
    CheckError(status);


    // Fill Profile Info
    NVDRS_PROFILE profileInfo;
    profileInfo.version             = NVDRS_PROFILE_VER;
    profileInfo.isPredefined        = 0;
    SetNVUstring(profileInfo.profileName, profileName);

    // Create Profile
    NvDRSProfileHandle hProfile;
    status = NvAPI_DRS_CreateProfile(hSession, &profileInfo, &hProfile);
    CheckError(status);


    // Fill Application Info
    NVDRS_APPLICATION app;
    app.version                     = NVDRS_APPLICATION_VER_V1;
    app.isPredefined                = 0;
    SetNVUstring(app.appName, appName);
    SetNVUstring(app.userFriendlyName, appFriendlyName);
    SetNVUstring(app.launcher, L"");
    SetNVUstring(app.fileInFolder, L"");

    // Create Application
    status = NvAPI_DRS_CreateApplication(hSession, hProfile, &app);
    CheckError(status);


    // Fill Setting Info
    NVDRS_SETTING setting;
    setting.version                 = NVDRS_SETTING_VER;
    setting.settingId               = OGL_THREAD_CONTROL_ID;
    setting.settingType             = NVDRS_DWORD_TYPE;
    setting.settingLocation         = NVDRS_CURRENT_PROFILE_LOCATION;
    setting.isCurrentPredefined     = 0;
    setting.isPredefinedValid       = 0;
    setting.u32CurrentValue         = threadedOptimization ? OGL_THREAD_CONTROL_ENABLE : OGL_THREAD_CONTROL_DISABLE;
    setting.u32PredefinedValue      = threadedOptimization ? OGL_THREAD_CONTROL_ENABLE : OGL_THREAD_CONTROL_DISABLE;

    // Set Setting
    status = NvAPI_DRS_SetSetting(hSession, hProfile, &setting);
    CheckError(status);


    // Apply (or save) our changes to the system
    status = NvAPI_DRS_SaveSettings(hSession);
    CheckError(status);


    printf("Success.\n");

    NvAPI_DRS_DestroySession(hSession);

    return 0;
}