mesa: Implement INTEL_performance_query.
authorPetri Latvala <petri.latvala@intel.com>
Wed, 23 Apr 2014 08:08:53 +0000 (11:08 +0300)
committerIan Romanick <ian.d.romanick@intel.com>
Fri, 2 May 2014 17:07:04 +0000 (10:07 -0700)
Using the existing driver hooks made for AMD_performance_monitor, implement
INTEL_performance_query functions.

v2: Whitespace changes.
v3: Whitespace changes, add a _mesa_warning()

Signed-off-by: Petri Latvala <petri.latvala@intel.com>
Reviewed-by: Ian Romanick <ian.d.romanick@intel.com>
src/mesa/main/performance_monitor.c

index 597f633f6f9c42d1f847d13a3e2581e87c7622fd..21b9423e003274d380de3968fd76b84fc75ae98b 100644 (file)
@@ -137,6 +137,46 @@ get_counter(const struct gl_perf_monitor_group *group_obj, GLuint id)
    return &group_obj->Counters[id];
 }
 
+/* For INTEL_performance_query, query id 0 is reserved to be invalid. We use
+ * index to Groups array + 1 as the query id. Same applies to counter id.
+ */
+static inline GLuint
+queryid_to_index(GLuint queryid)
+{
+   return queryid - 1;
+}
+
+static inline GLuint
+index_to_queryid(GLuint index)
+{
+   return index + 1;
+}
+
+static inline bool
+queryid_valid(const struct gl_context *ctx, GLuint queryid)
+{
+   return get_group(ctx, queryid_to_index(queryid)) != NULL;
+}
+
+static inline GLuint
+counterid_to_index(GLuint counterid)
+{
+   return counterid - 1;
+}
+
+static inline GLuint
+index_to_counterid(GLuint index)
+{
+   return index + 1;
+}
+
+static inline bool
+counterid_valid(const struct gl_perf_monitor_group *group_obj,
+                GLuint counterid)
+{
+   return get_counter(group_obj, counterid_to_index(counterid)) != NULL;
+}
+
 /*****************************************************************************/
 
 void GLAPIENTRY
@@ -644,6 +684,7 @@ extern void GLAPIENTRY
 _mesa_GetFirstPerfQueryIdINTEL(GLuint *queryId)
 {
    GET_CURRENT_CONTEXT(ctx);
+   unsigned numGroups;
 
    /* The GL_INTEL_performance_query spec says:
     *
@@ -655,16 +696,22 @@ _mesa_GetFirstPerfQueryIdINTEL(GLuint *queryId)
       return;
    }
 
+   numGroups = ctx->PerfMonitor.NumGroups;
+
    /* The GL_INTEL_performance_query spec says:
     *
     *    "If the given hardware platform doesn't support any performance
     *    queries, then the value of 0 is returned and INVALID_OPERATION error
     *    is raised."
     */
+   if (numGroups == 0) {
+      *queryId = 0;
+      _mesa_error(ctx, GL_INVALID_OPERATION,
+                  "glGetFirstPerfQueryIdINTEL(no queries supported)");
+      return;
+   }
 
-   *queryId = 0;
-   _mesa_error(ctx, GL_INVALID_OPERATION,
-               "glGetFirstPerfQueryIdINTEL(no queries supported)");
+   *queryId = index_to_queryid(0);
 }
 
 extern void GLAPIENTRY
@@ -674,40 +721,66 @@ _mesa_GetNextPerfQueryIdINTEL(GLuint queryId, GLuint *nextQueryId)
 
    /* The GL_INTEL_performance_query spec says:
     *
-    *    "If nextQueryId pointer is equal to 0, an INVALID_VALUE error is
-    *    generated."
+    *    "The result is passed in location pointed by nextQueryId. If query
+    *    identified by queryId is the last query available the value of 0 is
+    *    returned. If the specified performance query identifier is invalid
+    *    then INVALID_VALUE error is generated. If nextQueryId pointer is
+    *    equal to 0, an INVALID_VALUE error is generated.  Whenever error is
+    *    generated, the value of 0 is returned."
     */
+
    if (!nextQueryId) {
       _mesa_error(ctx, GL_INVALID_VALUE,
                   "glGetNextPerfQueryIdINTEL(nextQueryId == NULL)");
       return;
    }
 
-   /* The GL_INTEL_performance_query spec says:
-    *
-    *    "If the specified performance query identifier is invalid then
-    *    INVALID_VALUE error is generated. Whenever error is generated, the
-    *    value of 0 is returned."
-    *
-    * No queries are supported, so all queries are invalid.
-    */
-   *nextQueryId = 0;
-   _mesa_error(ctx, GL_INVALID_VALUE,
-               "glGetNextPerfQueryIdINTEL(invalid query)");
+   if (!queryid_valid(ctx, queryId)) {
+      *nextQueryId = 0;
+      _mesa_error(ctx, GL_INVALID_VALUE,
+                  "glGetNextPerfQueryIdINTEL(invalid query)");
+      return;
+   }
+
+   ++queryId;
+
+   if (!queryid_valid(ctx, queryId)) {
+      *nextQueryId = 0;
+   } else {
+      *nextQueryId = queryId;
+   }
 }
 
 extern void GLAPIENTRY
 _mesa_GetPerfQueryIdByNameINTEL(char *queryName, GLuint *queryId)
 {
    GET_CURRENT_CONTEXT(ctx);
+   unsigned i;
 
    /* The GL_INTEL_performance_query spec says:
     *
     *    "If queryName does not reference a valid query name, an INVALID_VALUE
     *    error is generated."
-    *
-    * No queries are supported, so all query names are invalid.
     */
+   if (!queryName) {
+      _mesa_error(ctx, GL_INVALID_VALUE,
+                  "glGetPerfQueryIdByNameINTEL(queryName == NULL)");
+      return;
+   }
+
+   /* The specification does not state that this produces an error. */
+   if (!queryId) {
+      _mesa_warning(ctx, "glGetPerfQueryIdByNameINTEL(queryId == NULL)");
+      return;
+   }
+
+   for (i = 0; i < ctx->PerfMonitor.NumGroups; ++i) {
+      const struct gl_perf_monitor_group *group_obj = get_group(ctx, i);
+      if (strcmp(group_obj->Name, queryName) == 0) {
+         *queryId = index_to_queryid(i);
+         return;
+      }
+   }
 
    _mesa_error(ctx, GL_INVALID_VALUE,
                "glGetPerfQueryIdByNameINTEL(invalid query name)");
@@ -721,17 +794,69 @@ _mesa_GetPerfQueryInfoINTEL(GLuint queryId,
                             GLuint *capsMask)
 {
    GET_CURRENT_CONTEXT(ctx);
+   unsigned i;
+
+   const struct gl_perf_monitor_group *group_obj =
+      get_group(ctx, queryid_to_index(queryId));
+
+   if (group_obj == NULL) {
+      /* The GL_INTEL_performance_query spec says:
+       *
+       *    "If queryId does not reference a valid query type, an
+       *    INVALID_VALUE error is generated."
+       */
+      _mesa_error(ctx, GL_INVALID_VALUE,
+                  "glGetPerfQueryInfoINTEL(invalid query)");
+      return;
+   }
+
+   if (queryName) {
+      strncpy(queryName, group_obj->Name, queryNameLength);
+
+      /* No specification given about whether the string needs to be
+       * zero-terminated. Zero-terminate the string always as we don't
+       * otherwise communicate the length of the returned string.
+       */
+      if (queryNameLength > 0) {
+         queryName[queryNameLength - 1] = '\0';
+      }
+   }
+
+   if (dataSize) {
+      unsigned size = 0;
+      for (i = 0; i < group_obj->NumCounters; ++i) {
+         /* What we get from the driver is group id (uint32_t) + counter id
+          * (uint32_t) + value.
+          */
+         size += 2 * sizeof(uint32_t) + _mesa_perf_monitor_counter_size(&group_obj->Counters[i]);
+      }
+      *dataSize = size;
+   }
+
+   if (noCounters) {
+      *noCounters = group_obj->NumCounters;
+   }
 
    /* The GL_INTEL_performance_query spec says:
     *
-    *    "If queryId does not reference a valid query type, an INVALID_VALUE
-    *    error is generated."
+    *    "-- the actual number of already created query instances in
+    *    maxInstances location"
     *
-    * No queries are supported, so all queries are invalid.
+    * 1) Typo in the specification, should be noActiveInstances.
+    * 2) Another typo in the specification, maxInstances parameter is not listed
+    *    in the declaration of this function in the list of new functions.
     */
+   if (noActiveInstances) {
+      *noActiveInstances = _mesa_HashNumEntries(ctx->PerfMonitor.Monitors);
+   }
 
-   _mesa_error(ctx, GL_INVALID_VALUE,
-               "glGetPerfQueryInfoINTEL(invalid query)");
+   if (capsMask) {
+      /* TODO: This information not yet available in the monitor structs. For
+       * now, we hardcode SINGLE_CONTEXT, since that's what the implementation
+       * currently tries very hard to do.
+       */
+      *capsMask = GL_PERFQUERY_SINGLE_CONTEXT_INTEL;
+   }
 }
 
 extern void GLAPIENTRY
@@ -743,73 +868,281 @@ _mesa_GetPerfCounterInfoINTEL(GLuint queryId, GLuint counterId,
 {
    GET_CURRENT_CONTEXT(ctx);
 
+   const struct gl_perf_monitor_group *group_obj;
+   const struct gl_perf_monitor_counter *counter_obj;
+   unsigned counterIndex;
+   unsigned i;
+
+   group_obj = get_group(ctx, queryid_to_index(queryId));
+
    /* The GL_INTEL_performance_query spec says:
     *
     *    "If the pair of queryId and counterId does not reference a valid
     *    counter, an INVALID_VALUE error is generated."
-    *
-    * No queries are supported, so all queries are invalid.
     */
+   if (group_obj == NULL) {
+      _mesa_error(ctx, GL_INVALID_VALUE,
+                  "glGetPerfCounterInfoINTEL(invalid queryId)");
+      return;
+   }
 
-   _mesa_error(ctx, GL_INVALID_VALUE,
-               "glGetPerfCounterInfoINTEL(invalid counterId)");
+   counterIndex = counterid_to_index(counterId);
+   counter_obj = get_counter(group_obj, counterIndex);
+
+   if (counter_obj == NULL) {
+      _mesa_error(ctx, GL_INVALID_VALUE,
+                  "glGetPerfCounterInfoINTEL(invalid counterId)");
+      return;
+   }
+
+   if (counterName) {
+      strncpy(counterName, counter_obj->Name, counterNameLength);
+
+      /* No specification given about whether the string needs to be
+       * zero-terminated. Zero-terminate the string always as we don't
+       * otherwise communicate the length of the returned string.
+       */
+      if (counterNameLength > 0) {
+         counterName[counterNameLength - 1] = '\0';
+      }
+   }
+
+   if (counterDesc) {
+      /* TODO: No separate description text at the moment. We pass the name
+       * again for the moment.
+       */
+      strncpy(counterDesc, counter_obj->Name, counterDescLength);
+
+      /* No specification given about whether the string needs to be
+       * zero-terminated. Zero-terminate the string always as we don't
+       * otherwise communicate the length of the returned string.
+       */
+      if (counterDescLength > 0) {
+         counterDesc[counterDescLength - 1] = '\0';
+      }
+   }
+
+   if (counterOffset) {
+      unsigned offset = 0;
+      for (i = 0; i < counterIndex; ++i) {
+         /* What we get from the driver is group id (uint32_t) + counter id
+          * (uint32_t) + value.
+          */
+         offset += 2 * sizeof(uint32_t) + _mesa_perf_monitor_counter_size(&group_obj->Counters[i]);
+      }
+      *counterOffset = 2 * sizeof(uint32_t) + offset;
+   }
+
+   if (counterDataSize) {
+      *counterDataSize = _mesa_perf_monitor_counter_size(counter_obj);
+   }
+
+   if (counterTypeEnum) {
+      /* TODO: Different counter types (semantic type, not data type) not
+       * supported as of yet.
+       */
+      *counterTypeEnum = GL_PERFQUERY_COUNTER_RAW_INTEL;
+   }
+
+   if (counterDataTypeEnum) {
+      switch (counter_obj->Type) {
+      case GL_FLOAT:
+      case GL_PERCENTAGE_AMD:
+         *counterDataTypeEnum = GL_PERFQUERY_COUNTER_DATA_FLOAT_INTEL;
+         break;
+      case GL_UNSIGNED_INT:
+         *counterDataTypeEnum = GL_PERFQUERY_COUNTER_DATA_UINT32_INTEL;
+         break;
+      case GL_UNSIGNED_INT64_AMD:
+         *counterDataTypeEnum = GL_PERFQUERY_COUNTER_DATA_UINT64_INTEL;
+         break;
+      default:
+         assert(!"Should not get here: invalid counter type");
+         return;
+      }
+   }
+
+   if (rawCounterMaxValue) {
+      /* This value is (implicitly) specified to be used only with
+       * GL_PERFQUERY_COUNTER_RAW_INTEL counters. When semantic types for
+       * counters are added, that needs to be checked.
+       */
+
+      /* The GL_INTEL_performance_query spec says:
+       *
+       *    "for some raw counters for which the maximal value is
+       *    deterministic, the maximal value of the counter in 1 second is
+       *    returned in the location pointed by rawCounterMaxValue, otherwise,
+       *    the location is written with the value of 0."
+       *
+       * The maximum value reported by the driver at the moment is not with
+       * these semantics, so write 0 always to be safe.
+       */
+      *rawCounterMaxValue = 0;
+   }
 }
 
 extern void GLAPIENTRY
 _mesa_CreatePerfQueryINTEL(GLuint queryId, GLuint *queryHandle)
 {
    GET_CURRENT_CONTEXT(ctx);
+   GLuint first;
+   GLuint group;
+   const struct gl_perf_monitor_group *group_obj;
+   struct gl_perf_monitor_object *m;
+   unsigned i;
+
+   /* This is not specified in the extension, but is the only sane thing to
+    * do.
+    */
+   if (queryHandle == NULL) {
+      _mesa_error(ctx, GL_INVALID_VALUE,
+                  "glCreatePerfQueryINTEL(queryHandle == NULL)");
+      return;
+   }
+
+   group = queryid_to_index(queryId);
+   group_obj = get_group(ctx, group);
 
    /* The GL_INTEL_performance_query spec says:
     *
     *    "If queryId does not reference a valid query type, an INVALID_VALUE
     *    error is generated."
-    *
-    * No queries are supported, so all queries are invalid.
     */
+   if (group_obj == NULL) {
+      _mesa_error(ctx, GL_INVALID_VALUE,
+                  "glCreatePerfQueryINTEL(invalid queryId)");
+      return;
+   }
 
-   _mesa_error(ctx, GL_INVALID_VALUE,
-               "glCreatePerfQueryINTEL(invalid queryId)");
+   /* The query object created here is the counterpart of a `monitor' in
+    * AMD_performance_monitor. This call is equivalent to calling
+    * GenPerfMonitorsAMD and SelectPerfMonitorCountersAMD with a list of all
+    * counters in a group.
+    */
+
+   /* We keep the monitor ids contiguous */
+   first = _mesa_HashFindFreeKeyBlock(ctx->PerfMonitor.Monitors, 1);
+   if (!first) {
+      /* The GL_INTEL_performance_query spec says:
+       *
+       *    "If the query instance cannot be created due to exceeding the
+       *    number of allowed instances or driver fails query creation due to
+       *    an insufficient memory reason, an OUT_OF_MEMORY error is
+       *    generated, and the location pointed by queryHandle returns NULL."
+      */
+      _mesa_error(ctx, GL_OUT_OF_MEMORY, "glCreatePerfQueryINTEL");
+      return;
+   }
+
+   m = new_performance_monitor(ctx, first);
+   _mesa_HashInsert(ctx->PerfMonitor.Monitors, first, m);
+   *queryHandle = first;
+
+   ctx->Driver.ResetPerfMonitor(ctx, m);
+
+   for (i = 0; i < group_obj->NumCounters; ++i) {
+      ++m->ActiveGroups[group];
+      /* Counters are a continuous range of integers, 0 to NumCounters (excl),
+       * so i is the counter value to use here.
+       */
+      BITSET_SET(m->ActiveCounters[group], i);
+   }
 }
 
 extern void GLAPIENTRY
 _mesa_DeletePerfQueryINTEL(GLuint queryHandle)
 {
    GET_CURRENT_CONTEXT(ctx);
+   struct gl_perf_monitor_object *m;
+
+   /* The queryHandle is the counterpart to AMD_performance_monitor's monitor
+    * id.
+    */
+   m = lookup_monitor(ctx, queryHandle);
 
    /* The GL_INTEL_performance_query spec says:
     *
     *    "If a query handle doesn't reference a previously created performance
     *    query instance, an INVALID_VALUE error is generated."
-    *
-    * No queries are supported, so all queries are invalid.
     */
+   if (m == NULL) {
+      _mesa_error(ctx, GL_INVALID_VALUE,
+                  "glDeletePerfQueryINTEL(invalid queryHandle)");
+      return;
+   }
 
-   _mesa_error(ctx, GL_INVALID_VALUE,
-               "glDeletePerfQueryINTEL(invalid queryHandle)");
+   /* Let the driver stop the monitor if it's active. */
+   if (m->Active) {
+      ctx->Driver.ResetPerfMonitor(ctx, m);
+      m->Ended = false;
+   }
+
+   _mesa_HashRemove(ctx->PerfMonitor.Monitors, queryHandle);
+   ralloc_free(m->ActiveGroups);
+   ralloc_free(m->ActiveCounters);
+   ctx->Driver.DeletePerfMonitor(ctx, m);
 }
 
 extern void GLAPIENTRY
 _mesa_BeginPerfQueryINTEL(GLuint queryHandle)
 {
    GET_CURRENT_CONTEXT(ctx);
+   struct gl_perf_monitor_object *m;
+
+   /* The queryHandle is the counterpart to AMD_performance_monitor's monitor
+    * id.
+    */
+
+   m = lookup_monitor(ctx, queryHandle);
 
    /* The GL_INTEL_performance_query spec says:
     *
     *    "If a query handle doesn't reference a previously created performance
     *    query instance, an INVALID_VALUE error is generated."
+    */
+   if (m == NULL) {
+      _mesa_error(ctx, GL_INVALID_VALUE,
+                  "glBeginPerfQueryINTEL(invalid queryHandle)");
+      return;
+   }
+
+   /* The GL_INTEL_performance_query spec says:
     *
-    * No queries are supported, so all queries are invalid.
+    *    "Note that some query types, they cannot be collected in the same
+    *    time. Therefore calls of BeginPerfQueryINTEL() cannot be nested if
+    *    they refer to queries of such different types. In such case
+    *    INVALID_OPERATION error is generated."
+    *
+    * We also generate an INVALID_OPERATION error if the driver can't begin
+    * monitoring for its own reasons, and for nesting the same query.
     */
+   if (m->Active) {
+      _mesa_error(ctx, GL_INVALID_OPERATION,
+                  "glBeginPerfQueryINTEL(already active)");
+      return;
+   }
 
-   _mesa_error(ctx, GL_INVALID_VALUE,
-               "glBeginPerfQueryINTEL(invalid queryHandle)");
+   if (ctx->Driver.BeginPerfMonitor(ctx, m)) {
+      m->Active = true;
+      m->Ended = false;
+   } else {
+      _mesa_error(ctx, GL_INVALID_OPERATION,
+                  "glBeginPerfQueryINTEL(driver unable to begin monitoring)");
+   }
 }
 
 extern void GLAPIENTRY
 _mesa_EndPerfQueryINTEL(GLuint queryHandle)
 {
    GET_CURRENT_CONTEXT(ctx);
+   struct gl_perf_monitor_object *m;
+
+   /* The queryHandle is the counterpart to AMD_performance_monitor's monitor
+    * id.
+    */
+
+   m = lookup_monitor(ctx, queryHandle);
 
    /* The GL_INTEL_performance_query spec says:
     *
@@ -818,13 +1151,24 @@ _mesa_EndPerfQueryINTEL(GLuint queryHandle)
     *
     * The specification doesn't state that an invalid handle would be an
     * INVALID_VALUE error. Regardless, query for such a handle will not be
-    * started, so we generate an INVALID_OPERATION in that case.
-    *
-    * No queries are supported, so all handles are invalid.
+    * started, so we generate an INVALID_OPERATION in that case too.
     */
+   if (m == NULL) {
+      _mesa_error(ctx, GL_INVALID_OPERATION,
+                  "glEndPerfQueryINTEL(invalid queryHandle)");
+      return;
+   }
 
-   _mesa_error(ctx, GL_INVALID_OPERATION,
-               "glEndPerfQueryINTEL(query not started)");
+   if (!m->Active) {
+      _mesa_error(ctx, GL_INVALID_OPERATION,
+                  "glEndPerfQueryINTEL(not active)");
+      return;
+   }
+
+   ctx->Driver.EndPerfMonitor(ctx, m);
+
+   m->Active = false;
+   m->Ended = true;
 }
 
 extern void GLAPIENTRY
@@ -832,6 +1176,8 @@ _mesa_GetPerfQueryDataINTEL(GLuint queryHandle, GLuint flags,
                             GLsizei dataSize, void *data, GLuint *bytesWritten)
 {
    GET_CURRENT_CONTEXT(ctx);
+   struct gl_perf_monitor_object *m;
+   bool result_available;
 
    /* The GL_INTEL_performance_query spec says:
     *
@@ -844,6 +1190,12 @@ _mesa_GetPerfQueryDataINTEL(GLuint queryHandle, GLuint flags,
       return;
    }
 
+   /* The queryHandle is the counterpart to AMD_performance_monitor's monitor
+    * id.
+    */
+
+   m = lookup_monitor(ctx, queryHandle);
+
    /* The specification doesn't state that an invalid handle generates an
     * error. We could interpret that to mean the case should be handled as
     * "measurement not ready for this query", but what should be done if
@@ -851,10 +1203,53 @@ _mesa_GetPerfQueryDataINTEL(GLuint queryHandle, GLuint flags,
     *
     * To resolve this, we just generate an INVALID_VALUE from an invalid query
     * handle.
+    */
+   if (m == NULL) {
+      _mesa_error(ctx, GL_INVALID_VALUE,
+                  "glGetPerfQueryDataINTEL(invalid queryHandle)");
+      return;
+   }
+
+   /* We need at least enough room for a single value. */
+   if (dataSize < sizeof(GLuint)) {
+      *bytesWritten = 0;
+      return;
+   }
+
+   /* The GL_INTEL_performance_query spec says:
     *
-    * No queries are supported, so all handles are invalid.
+    *    "The call may end without returning any data if they are not ready
+    *    for reading as the measurement session is still pending (the
+    *    EndPerfQueryINTEL() command processing is not finished by
+    *    hardware). In this case location pointed by the bytesWritten
+    *    parameter will be set to 0."
+    *
+    * If EndPerfQueryINTEL() is not called at all, we follow this.
     */
+   if (!m->Ended) {
+      *bytesWritten = 0;
+      return;
+   }
 
-   _mesa_error(ctx, GL_INVALID_VALUE,
-               "glGetPerfQueryDataINTEL(invalid queryHandle)");
+   result_available = ctx->Driver.IsPerfMonitorResultAvailable(ctx, m);
+
+   if (!result_available) {
+      if (flags == GL_PERFQUERY_FLUSH_INTEL) {
+         ctx->Driver.Flush(ctx);
+      } else if (flags == GL_PERFQUERY_WAIT_INTEL) {
+         /* Assume Finish() is both enough and not too much to wait for
+          * results. If results are still not available after Finish(), the
+          * later code automatically bails out with 0 for bytesWritten.
+          */
+         ctx->Driver.Finish(ctx);
+         result_available =
+            ctx->Driver.IsPerfMonitorResultAvailable(ctx, m);
+      }
+   }
+
+   if (result_available) {
+      ctx->Driver.GetPerfMonitorResult(ctx, m, dataSize, data, (GLint*)bytesWritten);
+   } else {
+      *bytesWritten = 0;
+   }
 }