AMD_performance_monitor

Name

AMD_performance_monitor

Name Strings

GL_AMD_performance_monitor

Contributors

Dan Ginsburg
Aaftab Munshi
Dave Oldcorn
Maurice Ribble
Jonathan Zarge

Contact

Dan Ginsburg (dan.ginsburg 'at' amd.com)

Status

???

Version

Last Modified Date: 11/29/2007

Number

OpenGL Extension #360
OpenGL ES Extension #50

Dependencies

None

Overview

This extension enables the capture and reporting of performance monitors.
Performance monitors contain groups of counters which hold arbitrary counted 
data.  Typically, the counters hold information on performance-related
counters in the underlying hardware.  The extension is general enough to
allow the implementation to choose which counters to expose and pick the
data type and range of the counters.  The extension also allows counting to 
start and end on arbitrary boundaries during rendering.

Issues

1.  Should this be an EGL or OpenGL/OpenGL ES extension?

    Decision - Make this an OpenGL/OpenGL ES extension
    
    Reason - We would like to expose this extension in both OpenGL and 
    OpenGL ES which makes EGL an unsuitable choice.  Further, support for 
    EGL is not a requirement and there are platforms that support OpenGL ES 
    but not EGL, making it difficult to make this an EGL extension.
    
2.  Should the API support multipassing?

    Decision - No.
    
    Reason - Multipassing should really be left to the application to do.  
    This makes the API unnecessarily complicated.  A major issue is that 
    depending on which counters are to be sampled, the # of passes and which 
    counters get selected in each pass can be difficult to determine.  It is 
    much easier to give a list of counters categorized by groups with 
    specific information on the number of counters that can be selected from 
    each group.

3.  Should we define a 64-bit data type for UNSIGNED_INT64_AMD?

    Decision - No.

    Reason - While counters can be returned as 64-bit unsigned integers, the
    data is passed back to the application inside of a void*.  Therefore,
    there is no need in this extension to define a 64-bit data type (e.g.,
    GLuint64).  It will be up the application to declare a native 64-bit
    unsigned integer and cast the returned data to that type.

New Procedures and Functions

void GetPerfMonitorGroupsAMD(int *numGroups, sizei groupsSize, 
                             uint *groups)

void GetPerfMonitorCountersAMD(uint group, int *numCounters, 
                               int *maxActiveCounters, sizei countersSize, 
                               uint *counters)

void GetPerfMonitorGroupStringAMD(uint group, sizei bufSize, sizei *length, 
                                  char *groupString)

void GetPerfMonitorCounterStringAMD(uint group, uint counter, sizei bufSize,
                                    sizei *length, char *counterString)
 
void GetPerfMonitorCounterInfoAMD(uint group, uint counter, 
                                  enum pname, void *data)

void GenPerfMonitorsAMD(sizei n, uint *monitors)

void DeletePerfMonitorsAMD(sizei n, uint *monitors)

void SelectPerfMonitorCountersAMD(uint monitor, boolean enable, 
                                  uint group, int numCounters, 
                                  uint *counterList)

void BeginPerfMonitorAMD(uint monitor)
    
void EndPerfMonitorAMD(uint monitor)

void GetPerfMonitorCounterDataAMD(uint monitor, enum pname, sizei dataSize, 
                                  uint *data, int *bytesWritten)

New Tokens

Accepted by the <pame> parameter of GetPerfMonitorCounterInfoAMD

    COUNTER_TYPE_AMD                           0x8BC0
    COUNTER_RANGE_AMD                          0x8BC1
    
Returned as a valid value in <data> parameter of
GetPerfMonitorCounterInfoAMD if <pname> = COUNTER_TYPE_AMD
    
    UNSIGNED_INT                               0x1405
    FLOAT                                      0x1406
    UNSIGNED_INT64_AMD                         0x8BC2
    PERCENTAGE_AMD                             0x8BC3
    
Accepted by the <pname> parameter of GetPerfMonitorCounterDataAMD
    
    PERFMON_RESULT_AVAILABLE_AMD               0x8BC4
    PERFMON_RESULT_SIZE_AMD                    0x8BC5
    PERFMON_RESULT_AMD                         0x8BC6

Addition to the GL specification

Add a new section called Performance Monitoring

A performance monitor consists of a number of hardware and software counters
that can be sampled by the GPU and reported back to the application.
Performance counters are organized as a single hierarchy where counters are
categorized into groups.  Each group has a list of counters that belong to
the counter and can be sampled, and a maximum number of counters that can be 
sampled.

The command

    void GetPerfMonitorGroupsAMD(int *numGroups, sizei groupsSize, 
                                 uint *groups);
    
returns the number of available groups in <numGroups>, if <numGroups> is
not NULL.  If <groupsSize> is not 0 and <groups> is not NULL, then the list 
of available groups is returned.  The number of entries that will be 
returned in <groups> is determined by <groupsSize>.  If <groupsSize> is 0, 
no information is copied.  Each group is identified by a unique unsigned int 
identifier.

The command

    void GetPerfMonitorCountersAMD(uint group, int *numCounters, 
                                   int *maxActiveCounters, 
                                   sizei countersSize, 
                                   uint *counters);
    
returns the following information.  For each group, it returns the number of 
available counters in <numCounters>, the max number of counters that can be
active at any time in <maxActiveCounters>, and the list of counters in 
<counters>.  The number of entries that can be returned in <counters> is
determined by <countersSize>.  If <countersSize> is 0, no information is
copied. Each counter in a group is identified by a unique unsigned int
identifier.  If <group> does not reference a valid group ID, an 
INVALID_VALUE error is generated.


The command

    void GetPerfMonitorGroupStringAMD(uint group, sizei bufSize, 
                                      sizei *length, char *groupString)

    
returns the string that describes the group name identified by <group> in 
<groupString>.  The actual number of characters written to <groupString>,
excluding the null terminator, is returned in <length>.  If <length> is 
NULL, then no length is returned.  The maximum number of characters that
may be written into <groupString>, including the null terminator, is 
specified by <bufSize>.  If <bufSize> is 0 and <groupString> is NULL, the 
number of characters that would be required to hold the group string,
excluding the null terminator, is returned in <length>.  If <group> 
does not reference a valid group ID, an INVALID_VALUE error is generated.


The command

    void GetPerfMonitorCounterStringAMD(uint group, uint counter, 
                                        sizei bufSize, sizei *length, 
                                        char *counterString);


returns the string that describes the counter name identified by <group> 
and <counter> in <counterString>.  The actual number of characters written 
to <counterString>, excluding the null terminator, is returned in <length>.  
If <length> is NULL, then no length is returned.  The maximum number of 
characters that may be written into <counterString>, including the null 
terminator, is specified by <bufSize>.  If <bufSize> is 0 and 
<counterString> is NULL, the number of characters that would be required to 
hold the counter string, excluding the null terminator, is returned in 
<length>.  If <group> does not reference a valid group ID, or <counter> 
does not reference a valid counter within the group ID, an INVALID_VALUE 
error is generated.
   
The command

    void GetPerfMonitorCounterInfoAMD(uint group, uint counter, 
                                      enum pname, void *data);
    
returns the following information about a counter.  For a <counter> 
belonging to <group>, we can query the counter type and counter range.  If 
<pname> is COUNTER_TYPE_AMD, then <data> returns the type.  Valid type
values returned are UNSIGNED_INT, UNSIGNED_INT64_AMD, PERCENTAGE_AMD, FLOAT.
If type value returned is PERCENTAGE_AMD, then this describes a float
value that is in the range [0.0 .. 100.0].  If <pname> is COUNTER_RANGE_AMD,
<data> returns two values representing a minimum and a maximum. The 
counter's type is used to determine the format in which the range values 
are returned.  If <group> does not reference a valid group ID, or <counter> 
does not reference a valid counter within the group ID, an INVALID_VALUE 
error is generated.


The command

    void GenPerfMonitorsAMD(sizei n, uint *monitors)
    
returns a list of monitors.  These monitors can then be used to select 
groups/counters to be sampled, to start multiple monitoring sessions and to 
return counter information sampled by the GPU.  At creation time, the 
performance monitor object has all counters disabled.  The value of the
PERFMON_RESULT_AVAILABLE_AMD, PERFMON_RESULT_AMD, and 
PERFMON_RESULT_SIZE_AMD queries will all initially be 0.

The command

    void DeletePerfMonitorsAMD(sizei n, uint *monitors)
    
is used to delete the list of monitors created by a previous call to 
GenPerfMonitors.  If a monitor ID in the list <monitors> does not 
reference a previously generated performance monitor, an INVALID_VALUE
error is generated.

The command 

    void SelectPerfMonitorCountersAMD(uint monitor, boolean enable, 
                                      uint group, int numCounters, 
                                      uint *counterList);
    
is used to enable or disable a list of counters from a group to be monitored 
as identified by <monitor>.  The <enable> argument determines whether the
counters should be enabled or disabled.  <group> specifies the group
ID under which counters will be enabled or disabled.  The <numCounters>
argument gives the number of counters to be selected from the list 
<counterList>.  If <monitor> is not a valid monitor created by 
GenPerfMonitorsAMD, then INVALID_VALUE error will be generated.  If <group>
is not a valid group, the INVALID_VALUE error will be generated.  If 
<numCounters> is less than 0, an INVALID_VALUE error will be generated. 

When SelectPerfMonitorCountersAMD is called on a monitor, any outstanding 
results for that monitor become invalidated and the result queries 
PERFMON_RESULT_SIZE_AMD and PERFMON_RESULT_AVAILABLE_AMD are reset to 0.

The command

    void BeginPerfMonitorAMD(uint monitor);
    
is used to start a monitor session.  Note that BeginPerfMonitor calls cannot 
be nested.  In addition, it is quite possible that given the list of groups 
and counters/group enabled for a monitor, it may not be able to sample the 
necessary counters and so the monitor session will fail.  In such a case,
an INVALID_OPERATION error will be generated.

While BeginPerfMonitorAMD does mark the beginning of performance counter
collection, the counters do not begin collecting immediately.  Rather, the
counters begin collection when BeginPerfMonitorAMD is processed by
the hardware.  That is, the API is asynchronous, and performance counter
collection does not begin until the graphics hardware processes the
BeginPerfMonitorAMD command.  

The command

    void EndPerfMonitorAMD(uint monitor);
    
ends a monitor session started by BeginPerfMonitorAMD.  If a performance 
monitor is not currently started, an INVALID_OPERATION error will be 
generated.

Note that there is an implied overhead to collecting performance counters
that may or may not distort performance depending on the implementation.  
For example, some counters may require a pipeline flush thereby causing a
change in the performance of the application.  Further, the frequency at 
which an application samples may distort the accuracy of counters which are 
variant (e.g., non-deterministic based on the input).  While the effects 
of sampling frequency are implementation dependent, general guidance can
be given that sampling at a high frequency may distort both performance
of the application and the accuracy of variant counters.

The command

    void GetPerfMonitorCounterDataAMD(uint monitor, enum pname, 
                                      sizei dataSize, 
                                      uint *data, sizei *bytesWritten);
    
is used to return counter values that have been sampled for a monitor
session.  If <pname> is PERFMON_RESULT_AVAILABLE_AMD, then <data> will
indicate whether the result is available or not.  If <pname> is 
PERFMON_RESULT_SIZE_AMD, <data> will contain actual size of all counter 
results being sampled.  If <pname> is PERFMON_RESULT_AMD, <data> will
contain results.  For each counter of a group that was selected to be 
sampled, the information is returned as group ID, followed by counter ID, 
followed by counter value.  The size of counter value returned will depend 
on the counter value type.  The argument <dataSize> specifies the number of
bytes available in the <data> buffer for writing.  If <bytesWritten> is not 
NULL, it gives the number of bytes written into the <data> buffer.  It is an 
INVALID_OPERATION error for <data> to be NULL.  If <pname> is 
PERFMON_RESULT_AMD and <dataSize> is less than the number of bytes required 
to store the results as reported by a PERFMON_RESULT_SIZE_AMD query, then 
results will be written only up to the number of bytes specified by 
<dataSize>.

If no BeginPerfMonitorAMD/EndPerfMonitorAMD has been issued for a monitor,
then the result of querying for PERFMON_RESULT_AVAILABLE and 
PERFMON_RESULT_SIZE will be 0.  When SelectPerfMonitorCountersAMD is called
on a monitor, the results stored for the monitor become invalidated and
the value of PERFMON_RESULT_AVAILABLE and PERFMON_RESULT_SIZE queries should
behave as if no BeginPerfMonitorAMD/EndPerfMonitorAMD has been issued for
the monitor.

Errors

INVALID_OPERATION error will be generated if BeginPerfMonitorAMD is unable
to begin monitoring with the currently selected counters.  

INVALID_OPERATION error will be generated if BeginPerfMonitorAMD is called
when a performance monitor is already active.

INVALID_OPERATION error will be generated if EndPerfMonitorAMD is called
when a performance monitor is not currently started.

INVALID_VALUE error will be generated if the <group> parameter to 
GetPerfMonitorCountersAMD, GetPerfMonitorCounterStringAMD,
GetPerfMonitorCounterStringAMD, GetPerfMonitorCounterInfoAMD, or
SelectPerfMonitorCountersAMD does not reference a valid group ID.

INVALID_VALUE error will be generated if the <counter> parameter to
GetPerfMonitorCounterInfoAMD does not reference a valid counter ID
in the group specified by <group>.

INVALID_VALUE error will be generated if any of the monitor IDs
in the <monitors> parameter to DeletePerfMonitorsAMD do not reference
a valid generated monitor ID.
   
INVALID_VALUE error will be generated if the <monitor> parameter to
SelectPerfMonitorCountersAMD does not reference a monitor created by
GenPerfMonitorsAMD.

INVALID_VALUE error will be generated if the <numCounters> parameter to
SelectPerfMonitorCountersAMD is less than 0.

New State

Sample Usage

typedef struct 
{
        GLuint       *counterList;
        int         numCounters;
        int         maxActiveCounters;
} CounterInfo;

void
getGroupAndCounterList(GLuint **groupsList, int *numGroups, 
                       CounterInfo **counterInfo)
{
    GLint          n;
    GLuint        *groups;
    CounterInfo   *counters;

    glGetPerfMonitorGroupsAMD(&n, 0, NULL);
    groups = (GLuint*) malloc(n * sizeof(GLuint));
    glGetPerfMonitorGroupsAMD(NULL, n, groups);
    *numGroups = n;

    *groupsList = groups;
    counters = (CounterInfo*) malloc(sizeof(CounterInfo) * n);
    for (int i = 0 ; i < n; i++ )
    {
        glGetPerfMonitorCountersAMD(groups[i], &counters[i].numCounters,
                                 &counters[i].maxActiveCounters, 0, NULL);

        counters[i].counterList = (GLuint*)malloc(counters[i].numCounters * 
                                                  sizeof(int));

        glGetPerfMonitorCountersAMD(groups[i], NULL, NULL,
                                    counters[i].numCounters, 
                                    counters[i].counterList);
    }

    *counterInfo = counters;
}

static int  countersInitialized = 0;
    
int
getCounterByName(char *groupName, char *counterName, GLuint *groupID, 
                 GLuint *counterID)
{
    int          numGroups;
    GLuint       *groups;
    CounterInfo  *counters;
    int          i = 0;

    if (!countersInitialized)
    {
        getGroupAndCounterList(&groups, &numGroups, &counters);
        countersInitialized = 1;
    }

    for ( i = 0; i < numGroups; i++ )
    {
       char curGroupName[256];
       glGetPerfMonitorGroupStringAMD(groups[i], 256, NULL, curGroupName);
       if (strcmp(groupName, curGroupName) == 0)
       {
           *groupID = groups[i];
           break;
       }
    }

    if ( i == numGroups )
        return -1;           // error - could not find the group name

    for ( int j = 0; j < counters[i].numCounters; j++ )
    {
        char curCounterName[256];
        
        glGetPerfMonitorCounterStringAMD(groups[i],
                                         counters[i].counterList[j], 
                                         256, NULL, curCounterName);
        if (strcmp(counterName, curCounterName) == 0)
        {
            *counterID = counters[i].counterList[j];
            return 0;
        }
    }

    return -1;           // error - could not find the counter name
}

void
drawFrameWithCounters(void)
{
    GLuint group[2];
    GLuint counter[2];
    GLuint monitor;
    GLuint *counterData;

    // Get group/counter IDs by name.  Note that normally the
    // counter and group names need to be queried for because
    // each implementation of this extension on different hardware
    // could define different names and groups.  This is just provided
    // to demonstrate the API.
    getCounterByName("HW", "Hardware Busy", &group[0],
                     &counter[0]);
    getCounterByName("API", "Draw Calls", &group[1], 
                     &counter[1]);
            
    // create perf monitor ID
    glGenPerfMonitorsAMD(1, &monitor);

    // enable the counters
    glSelectPerfMonitorCountersAMD(monitor, GL_TRUE, group[0], 1,
                                   &counter[0]);
    glSelectPerfMonitorCountersAMD(monitor, GL_TRUE, group[1], 1, 
                                   &counter[1]);

    glBeginPerfMonitorAMD(monitor);

    // RENDER FRAME HERE
    // ...
    
    glEndPerfMonitorAMD(monitor);

    // read the counters
    GLint resultSize;
    glGetPerfMonitorCounterDataAMD(monitor, GL_PERFMON_RESULT_SIZE_AMD, 
                                   sizeof(GLint), &resultSize, NULL);

    counterData = (GLuint*) malloc(resultSize);

    GLsizei bytesWritten;
    glGetPerfMonitorCounterDataAMD(monitor, GL_PERFMON_RESULT_AMD,  
                                   resultSize, counterData, &bytesWritten);

    // display or log counter info
    GLsizei wordCount = 0;

    while ( (4 * wordCount) < bytesWritten )
    {
        GLuint groupId = counterData[wordCount];
        GLuint counterId = counterData[wordCount + 1];

        // Determine the counter type
        GLuint counterType;
        glGetPerfMonitorCounterInfoAMD(groupId, counterId, 
                                       GL_COUNTER_TYPE_AMD, &counterType);
 
        if ( counterType == GL_UNSIGNED_INT64_AMD )
        {
            unsigned __int64 counterResult = 
                       *(unsigned __int64*)(&counterData[wordCount + 2]);

            // Print counter result

            wordCount += 4;
        }
        else if ( counterType == GL_FLOAT )
        {
            float counterResult = *(float*)(&counterData[wordCount + 2]);

            // Print counter result

            wordCount += 3;
        } 
        // else if ( ... ) check for other counter types 
        //   (GL_UNSIGNED_INT and GL_PERCENTAGE_AMD)
    }
}

Revision History 11/29/2007 - dginsburg + Clarified the default state of a performance monitor object on creation

11/09/2007 - dginsbur
   + Clarify what happens if SelectPerfMonitorCountersAMD is called on
     a monitor with outstanding query results.
   + Rename counterSize to countersSize
   + Remove some ';' typos

06/13/2007 - dginsbur
   + Add language on the asynchronous nature of the API and 
     counter accuracy/performance distortion.
   + Add myself as the contact
   + Remove INVALID_OPERATION error when countersList is NULL
   + Clarify 64-bit issue
   + Make PERCENTAGE_AMD counters float rather than uint
   + Clarify accuracy distortion on variant counters only
   + Tweak to overview language

06/09/2007 - dginsbur
   + Fill in errors section and make many more errors explicit
   + Fix the example code so it compiles

06/08/2007 - dginsbur
   + Modified GetPerfMonitorGroupString and GetPerfMonitorCounterString to
     be more client/server friendly.  
   + Modified example.
   + Renamed parameters/variables to follow GL conventions.
   + Modified several 'int' param types to 'sizei'
   + Modifid counters type from 'int' to 'uint'
   + Renamed argument 'cb' and 'cbret'
   + Better documented GetPerfMonitorCounterData 
   + Add AMD adornment in many places that were missing it
 
06/07/2007 - dginsbur
   + Cleanup formatting, remove tabs, make fit in proper page width
   + Add FLOAT and UNSIGNED_INT to list of COUNTER_TYPEs
   + Fix some bugs in the example code
   + Rewrite introduction
   + Clarified Issue 1 reasoning
   + Added Issue 3 regarding use of 64-bit data types
   + Added revision history

03/21/2007 - Initial version written.  Written by amunshi.