SlimDX y DirectX 11 - AMD GPUPerfAPI
Código fuente y ejecutable de la solución (para que GPA funcione, la aplicación debe ejecutarse como administrador)
Advertencia
Colgué un par de veces la PC mientras probaba algunos counters (muchos a la vez) con Fraps al mismo tiempo :P
GPUPerfAPI
Biblioteca de AMD para analizar performance en placas de video AMD/ATI. Overview y user guide (bastante completa).
GPAWrapper.cs
Importé todas las funciones que figuran en GPUPerfAPI.h y agregué una función para leer los datos de una sesión y formatearlos según su tipo y usage.
namespace Framework { [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void GPA_Logging_Delegate(GPA_Logging_Type logType, string mensaje); /// /// Status enumerations /// public enum GPA_Status { GPA_STATUS_OK = 0, GPA_STATUS_ERROR_NULL_POINTER, GPA_STATUS_ERROR_COUNTERS_NOT_OPEN, GPA_STATUS_ERROR_COUNTERS_ALREADY_OPEN, GPA_STATUS_ERROR_INDEX_OUT_OF_RANGE, GPA_STATUS_ERROR_NOT_FOUND, GPA_STATUS_ERROR_ALREADY_ENABLED, GPA_STATUS_ERROR_NO_COUNTERS_ENABLED, GPA_STATUS_ERROR_NOT_ENABLED, GPA_STATUS_ERROR_SAMPLING_NOT_STARTED, GPA_STATUS_ERROR_SAMPLING_ALREADY_STARTED, GPA_STATUS_ERROR_SAMPLING_NOT_ENDED, GPA_STATUS_ERROR_NOT_ENOUGH_PASSES, GPA_STATUS_ERROR_PASS_NOT_ENDED, GPA_STATUS_ERROR_PASS_NOT_STARTED, GPA_STATUS_ERROR_PASS_ALREADY_STARTED, GPA_STATUS_ERROR_SAMPLE_NOT_STARTED, GPA_STATUS_ERROR_SAMPLE_ALREADY_STARTED, GPA_STATUS_ERROR_SAMPLE_NOT_ENDED, GPA_STATUS_ERROR_CANNOT_CHANGE_COUNTERS_WHEN_SAMPLING, GPA_STATUS_ERROR_SESSION_NOT_FOUND, GPA_STATUS_ERROR_SAMPLE_NOT_FOUND, GPA_STATUS_ERROR_SAMPLE_NOT_FOUND_IN_ALL_PASSES, GPA_STATUS_ERROR_COUNTER_NOT_OF_SPECIFIED_TYPE, GPA_STATUS_ERROR_READING_COUNTER_RESULT, GPA_STATUS_ERROR_VARIABLE_NUMBER_OF_SAMPLES_IN_PASSES, GPA_STATUS_ERROR_FAILED, GPA_STATUS_ERROR_HARDWARE_NOT_SUPPORTED, } /// /// Value type definitions /// public enum GPA_Type { GPA_TYPE_FLOAT32, // Result will be a 32-bit float GPA_TYPE_FLOAT64, // Result will be a 64-bit float GPA_TYPE_UINT32, // Result will be a 32-bit unsigned int GPA_TYPE_UINT64, // Result will be a 64-bit unsigned int GPA_TYPE_INT32, // Result will be a 32-bit int GPA_TYPE_INT64, // Result will be a 64-bit int GPA_TYPE__LAST // Marker indicating last element } /// /// Result usage type definitions /// public enum GPA_Usage_Type { GPA_USAGE_TYPE_RATIO, // Result is a ratio of two different values or types GPA_USAGE_TYPE_PERCENTAGE, // Result is a percentage, typically within [0,100] range, but may be higher for certain counters GPA_USAGE_TYPE_CYCLES, // Result is in clock cycles GPA_USAGE_TYPE_MILLISECONDS, // Result is in milliseconds GPA_USAGE_TYPE_BYTES, // Result is in bytes GPA_USAGE_TYPE_ITEMS, // Result is a count of items or objects (ie, vertices, triangles, threads, pixels, texels, etc) GPA_USAGE_TYPE_KILOBYTES, // Result is in kilobytes GPA_USAGE_TYPE__LAST // Marker indicating last element } /// /// Logging type definitions /// public enum GPA_Logging_Type { GPA_LOGGING_NONE = 0, GPA_LOGGING_ERROR = 1, GPA_LOGGING_MESSAGE = 2, GPA_LOGGING_ERROR_AND_MESSAGE = 3, GPA_LOGGING_TRACE = 4, GPA_LOGGING_ERROR_AND_TRACE = 5, GPA_LOGGING_MESSAGE_AND_TRACE = 6, GPA_LOGGING_ERROR_MESSAGE_AND_TRACE = 7, GPA_LOGGING_ALL = 0xFF } /// /// See http://developer.amd.com/tools/GPUPerfAPI/assets/GPUPerfAPI-UserGuide.pdf /// public static class GPAWrapper { [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)] public static extern GPA_Status GPA_Initialize(); [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)] public static extern GPA_Status GPA_Destroy(); [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)] public static extern GPA_Status GPA_OpenContext(IntPtr pointerToDevice); [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)] public static extern GPA_Status GPA_CloseContext(); [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)] public static extern GPA_Status GPA_SelectContext(IntPtr pointerToDevice); [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)] public static extern GPA_Status GPA_GetNumCounters(out uint counterIndicesList); [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)] public static extern GPA_Status GPA_GetCounterName(uint counterIndex, out string counterName); [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)] public static extern GPA_Status GPA_GetCounterIndex(string counterName, out uint counterIndex); [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)] public static extern GPA_Status GPA_GetCounterDescription(uint counterIndex, out string counterDescription); [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)] public static extern GPA_Status GPA_GetCounterDataType(uint counterIndex, out GPA_Type counterDataType); [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)] public static extern GPA_Status GPA_GetDataTypeAsStr(GPA_Type counterDataType, out string counterDataTypeStr); [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)] public static extern GPA_Status GPA_GetCounterUsageType(uint counterIndex, out GPA_Usage_Type counterUsageType); [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)] public static extern GPA_Status GPA_GetUsageTypeAsStr(GPA_Usage_Type counterUsageType, out string counterUsageTypeStr); [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)] public static extern GPA_Status GPA_EnableCounter(uint counterIndex); [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)] public static extern GPA_Status GPA_EnableCounterStr(string counterName); [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)] public static extern GPA_Status GPA_EnableAllCounters(); [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)] public static extern GPA_Status GPA_GetEnabledCount(out uint enabledCount); [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)] public static extern GPA_Status GPA_GetEnabledIndex(uint enabledIndex, out uint counterIndex); [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)] public static extern GPA_Status GPA_IsCounterEnabled(uint counterIndex); [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)] public static extern GPA_Status GPA_DisableCounter(uint counterIndex); [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)] public static extern GPA_Status GPA_DisableCounterStr(string counterName); [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)] public static extern GPA_Status GPA_DisableAllCounters(); [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)] public static extern GPA_Status GPA_GetPassCount(out uint passCount); [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)] public static extern GPA_Status GPA_BeginSession(out uint sessionID); [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)] public static extern GPA_Status GPA_EndSession(); [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)] public static extern GPA_Status GPA_BeginPass(); [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)] public static extern GPA_Status GPA_EndPass(); [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)] public static extern GPA_Status GPA_BeginSample(uint sampleID); [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)] public static extern GPA_Status GPA_EndSample(); [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)] public static extern GPA_Status GPA_IsSessionReady(out bool isReady, uint sessionID); [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)] public static extern GPA_Status GPA_IsSampleReady(out bool isReady, uint sessionID, uint sampleID); [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)] public static extern GPA_Status GPA_GetSampleUInt32(uint sessionID, uint sampleID, uint counterIndex, out uint sampledValue); [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)] public static extern GPA_Status GPA_GetSampleUInt64(uint sessionID, uint sampleID, uint counterIndex, out ulong sampledValue); [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)] public static extern GPA_Status GPA_GetSampleFloat32(uint sessionID, uint sampleID, uint counterIndex, out float sampledValue); [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)] public static extern GPA_Status GPA_GetSampleFloat64(uint sessionID, uint sampleID, uint counterIndex, out double sampledValue); [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)] public static extern GPA_Status GPA_GetSampleCount(uint sessionID, out uint samplesCount); [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)] public static extern string GPA_GetStatusAsStr(GPA_Status gpaStatus); [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)] public static extern GPA_Status GPA_RegisterLoggingCallback(GPA_Logging_Type gpaLoggingType, GPA_Logging_Delegate loggingFunction); /// /// Queries the last samples taken in a session /// ///Session ID ///Array of counter indices ///Array with the data type associated with each counter index of counters ///Array with the usage type associated with each counter index of counters /// An array of string formatted values public static string[] SamplesToString(uint sessionID, uint[] counters, GPA_Type[] types, GPA_Usage_Type[] usages) { string[] outputValues = new string[counters.Length]; uint sampleIndex; GPAWrapper.GPA_GetSampleCount(sessionID, out sampleIndex); sampleIndex--; for (int j = 0; j < counters.Length; j++) { switch (types[j]) { case GPA_Type.GPA_TYPE_FLOAT32: float floatValue; GPAWrapper.GPA_GetSampleFloat32(sessionID, sampleIndex, counters[j], out floatValue); outputValues[j] = string.Format("{0:0.###}", floatValue); break; case GPA_Type.GPA_TYPE_FLOAT64: double doubleValue; GPAWrapper.GPA_GetSampleFloat64(sessionID, sampleIndex, counters[j], out doubleValue); outputValues[j] = string.Format("{0:0.###}", doubleValue); break; case GPA_Type.GPA_TYPE_UINT32: uint uintValue; GPAWrapper.GPA_GetSampleUInt32(sessionID, sampleIndex, counters[j], out uintValue); outputValues[j] = uintValue.ToString(); break; case GPA_Type.GPA_TYPE_UINT64: ulong uint64Value; GPAWrapper.GPA_GetSampleUInt64(sessionID, sampleIndex, counters[j], out uint64Value); outputValues[j] = uint64Value.ToString(); break; } switch (usages[j]) { case GPA_Usage_Type.GPA_USAGE_TYPE_BYTES: outputValues[j] += " bytes"; break; case GPA_Usage_Type.GPA_USAGE_TYPE_CYCLES: outputValues[j] += " cycles"; break; case GPA_Usage_Type.GPA_USAGE_TYPE_ITEMS: outputValues[j] += " items"; break; case GPA_Usage_Type.GPA_USAGE_TYPE_KILOBYTES: outputValues[j] += " Kbytes"; break; case GPA_Usage_Type.GPA_USAGE_TYPE_MILLISECONDS: outputValues[j] += " ms"; break; case GPA_Usage_Type.GPA_USAGE_TYPE_PERCENTAGE: outputValues[j] += " %"; break; case GPA_Usage_Type.GPA_USAGE_TYPE_RATIO: outputValues[j] += " ratio"; break; } } return outputValues; } } }
GPA brinda la posibilidad de setear una callback function a la cual la biblioteca invoca con los distintos errores y mensajes de logueo. Para mi ejemplo, hice una función que graba en GPALog.txt sólo los errores y advertencias.
Cómo usarlo
Habilitar GPA en el checkbox Enable GPUPerfAPI y agregar los counters que uno quiera medir en el datagrid que está debajo.
Para otros proyectos, sólo hace falta copiar GPAWrapper.cs y llamar a la biblioteca como uno quiera (por ejemplo, leyendo la data después de terminada la aplicación para no disminuir la performance).
Limitaciones
Desde el punto de vista de funcionalidad importada, ninguna.
Por otro lado, algunos counters requieren de más de una pasada de exactamente el mismo frame para poder obtener sus resultados, por lo que si el usuario selecciona un conjunto de counters que requiere de más de una pasada para samplear los datos, se desactiva GPA y se muestra una advertencia.










