/*
 * Copyright © 2012-2017 Intel Corporation
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

/**
 * \file performance_query.c
 * Core Mesa support for the INTEL_performance_query extension.
 */

#include <stdbool.h>
#include "glheader.h"
#include "context.h"
#include "enums.h"
#include "hash.h"
#include "macros.h"
#include "mtypes.h"
#include "performance_query.h"
#include "util/ralloc.h"

void
_mesa_init_performance_queries(struct gl_context *ctx)
{
   ctx->PerfQuery.Objects = _mesa_NewHashTable();
}

static void
free_performance_query(GLuint key, void *data, void *user)
{
   struct gl_perf_query_object *m = data;
   struct gl_context *ctx = user;

   ctx->Driver.DeletePerfQuery(ctx, m);
}

void
_mesa_free_performance_queries(struct gl_context *ctx)
{
   _mesa_HashDeleteAll(ctx->PerfQuery.Objects,
                       free_performance_query, ctx);
   _mesa_DeleteHashTable(ctx->PerfQuery.Objects);
}

static inline struct gl_perf_query_object *
lookup_object(struct gl_context *ctx, GLuint id)
{
   return _mesa_HashLookup(ctx->PerfQuery.Objects, id);
}

static GLuint
init_performance_query_info(struct gl_context *ctx)
{
   if (ctx->Driver.InitPerfQueryInfo)
      return ctx->Driver.InitPerfQueryInfo(ctx);
   else
      return 0;
}

/* For INTEL_performance_query, query id 0 is reserved to be invalid. */
static inline unsigned
queryid_to_index(GLuint queryid)
{
   return queryid - 1;
}

static inline GLuint
index_to_queryid(unsigned index)
{
   return index + 1;
}

static inline bool
queryid_valid(const struct gl_context *ctx, unsigned numQueries, GLuint queryid)
{
   /* The GL_INTEL_performance_query spec says:
    *
    *  "Performance counter ids values start with 1. Performance counter id 0
    *  is reserved as an invalid counter."
    */
   return queryid != 0 && queryid_to_index(queryid) < numQueries;
}

static inline GLuint
counterid_to_index(GLuint counterid)
{
   return counterid - 1;
}

static void
output_clipped_string(GLchar *stringRet,
                      GLuint stringMaxLen,
                      const char *string)
{
   if (!stringRet)
      return;

   strncpy(stringRet, string ? string : "", stringMaxLen);

   /* No specification given about whether returned strings needs
    * to be zero-terminated. Zero-terminate the string always as we
    * don't otherwise communicate the length of the returned
    * string.
    */
   if (stringMaxLen > 0)
      stringRet[stringMaxLen - 1] = '\0';
}

/*****************************************************************************/

extern void GLAPIENTRY
_mesa_GetFirstPerfQueryIdINTEL(GLuint *queryId)
{
   GET_CURRENT_CONTEXT(ctx);

   unsigned numQueries;

   /* The GL_INTEL_performance_query spec says:
    *
    *    "If queryId pointer is equal to 0, INVALID_VALUE error is generated."
    */
   if (!queryId) {
      _mesa_error(ctx, GL_INVALID_VALUE,
                  "glGetFirstPerfQueryIdINTEL(queryId == NULL)");
      return;
   }

   numQueries = init_performance_query_info(ctx);

   /* 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 (numQueries == 0) {
      *queryId = 0;
      _mesa_error(ctx, GL_INVALID_OPERATION,
                  "glGetFirstPerfQueryIdINTEL(no queries supported)");
      return;
   }

   *queryId = index_to_queryid(0);
}

extern void GLAPIENTRY
_mesa_GetNextPerfQueryIdINTEL(GLuint queryId, GLuint *nextQueryId)
{
   GET_CURRENT_CONTEXT(ctx);

   unsigned numQueries;

   /* The GL_INTEL_performance_query spec says:
    *
    *    "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;
   }

   numQueries = init_performance_query_info(ctx);

   if (!queryid_valid(ctx, numQueries, queryId)) {
      _mesa_error(ctx, GL_INVALID_VALUE,
                  "glGetNextPerfQueryIdINTEL(invalid query)");
      return;
   }

   if (queryid_valid(ctx, numQueries, ++queryId))
      *nextQueryId = queryId;
   else
      *nextQueryId = 0;
}

extern void GLAPIENTRY
_mesa_GetPerfQueryIdByNameINTEL(char *queryName, GLuint *queryId)
{
   GET_CURRENT_CONTEXT(ctx);

   unsigned numQueries;
   unsigned i;

   /* The GL_INTEL_performance_query spec says:
    *
    *    "If queryName does not reference a valid query name, an INVALID_VALUE
    *    error is generated."
    */
   if (!queryName) {
      _mesa_error(ctx, GL_INVALID_VALUE,
                  "glGetPerfQueryIdByNameINTEL(queryName == NULL)");
      return;
   }

   /* The specification does not state that this produces an error but
    * to be consistent with glGetFirstPerfQueryIdINTEL we generate an
    * INVALID_VALUE error
    */
   if (!queryId) {
      _mesa_error(ctx, GL_INVALID_VALUE,
                  "glGetPerfQueryIdByNameINTEL(queryId == NULL)");
      return;
   }

   numQueries = init_performance_query_info(ctx);

   for (i = 0; i < numQueries; ++i) {
      const GLchar *name;
      GLuint ignore;

      ctx->Driver.GetPerfQueryInfo(ctx, i, &name, &ignore, &ignore, &ignore);

      if (strcmp(name, queryName) == 0) {
         *queryId = index_to_queryid(i);
         return;
      }
   }

   _mesa_error(ctx, GL_INVALID_VALUE,
               "glGetPerfQueryIdByNameINTEL(invalid query name)");
}

extern void GLAPIENTRY
_mesa_GetPerfQueryInfoINTEL(GLuint queryId,
                            GLuint nameLength, GLchar *name,
                            GLuint *dataSize,
                            GLuint *numCounters,
                            GLuint *numActive,
                            GLuint *capsMask)
{
   GET_CURRENT_CONTEXT(ctx);

   unsigned numQueries = init_performance_query_info(ctx);
   unsigned queryIndex = queryid_to_index(queryId);
   const char *queryName;
   GLuint queryDataSize;
   GLuint queryNumCounters;
   GLuint queryNumActive;

   if (!queryid_valid(ctx, numQueries, queryId)) {
      /* 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;
   }

   ctx->Driver.GetPerfQueryInfo(ctx, queryIndex,
                                &queryName,
                                &queryDataSize,
                                &queryNumCounters,
                                &queryNumActive);

   output_clipped_string(name, nameLength, queryName);

   if (dataSize)
      *dataSize = queryDataSize;

   if (numCounters)
      *numCounters = queryNumCounters;

   /* The GL_INTEL_performance_query spec says:
    *
    *    "-- the actual number of already created query instances in
    *    maxInstances location"
    *
    * 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 (numActive)
      *numActive = queryNumActive;

   /* Assume for now that all queries are per-context */
   if (capsMask)
      *capsMask = GL_PERFQUERY_SINGLE_CONTEXT_INTEL;
}

extern void GLAPIENTRY
_mesa_GetPerfCounterInfoINTEL(GLuint queryId, GLuint counterId,
                              GLuint nameLength, GLchar *name,
                              GLuint descLength, GLchar *desc,
                              GLuint *offset,
                              GLuint *dataSize,
                              GLuint *typeEnum,
                              GLuint *dataTypeEnum,
                              GLuint64 *rawCounterMaxValue)
{
   GET_CURRENT_CONTEXT(ctx);

   unsigned numQueries = init_performance_query_info(ctx);
   unsigned queryIndex = queryid_to_index(queryId);
   const char *queryName;
   GLuint queryDataSize;
   GLuint queryNumCounters;
   GLuint queryNumActive;
   unsigned counterIndex;
   const char *counterName;
   const char *counterDesc;
   GLuint counterOffset;
   GLuint counterDataSize;
   GLuint counterTypeEnum;
   GLuint counterDataTypeEnum;
   GLuint64 counterRawMax;

   if (!queryid_valid(ctx, numQueries, 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."
       */
      _mesa_error(ctx, GL_INVALID_VALUE,
                  "glGetPerfCounterInfoINTEL(invalid queryId)");
      return;
   }

   ctx->Driver.GetPerfQueryInfo(ctx, queryIndex,
                                &queryName,
                                &queryDataSize,
                                &queryNumCounters,
                                &queryNumActive);

   counterIndex = counterid_to_index(counterId);

   if (counterIndex >= queryNumCounters) {
      _mesa_error(ctx, GL_INVALID_VALUE,
                  "glGetPerfCounterInfoINTEL(invalid counterId)");
      return;
   }

   ctx->Driver.GetPerfCounterInfo(ctx, queryIndex, counterIndex,
                                  &counterName,
                                  &counterDesc,
                                  &counterOffset,
                                  &counterDataSize,
                                  &counterTypeEnum,
                                  &counterDataTypeEnum,
                                  &counterRawMax);

   output_clipped_string(name, nameLength, counterName);
   output_clipped_string(desc, descLength, counterDesc);

   if (offset)
      *offset = counterOffset;

   if (dataSize)
      *dataSize = counterDataSize;

   if (typeEnum)
      *typeEnum = counterTypeEnum;

   if (dataTypeEnum)
      *dataTypeEnum = counterDataTypeEnum;

   if (rawCounterMaxValue)
      *rawCounterMaxValue = counterRawMax;

   if (rawCounterMaxValue) {
      /* 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."
       *
       *    Since it's very useful to be able to report a maximum value for
       *    more that just counters using the _COUNTER_RAW_INTEL or
       *    _COUNTER_DURATION_RAW_INTEL enums (e.g. for a _THROUGHPUT tools
       *    want to be able to visualize the absolute throughput with respect
       *    to the theoretical maximum that's possible) and there doesn't seem
       *    to be any reason not to allow _THROUGHPUT counters to also be
       *    considerer "raw" here, we always leave it up to the backend to
       *    decide when it's appropriate to report a maximum counter value or 0
       *    if not.
       */
      *rawCounterMaxValue = counterRawMax;
   }
}

extern void GLAPIENTRY
_mesa_CreatePerfQueryINTEL(GLuint queryId, GLuint *queryHandle)
{
   GET_CURRENT_CONTEXT(ctx);

   unsigned numQueries = init_performance_query_info(ctx);
   GLuint id;
   struct gl_perf_query_object *obj;

   /* The GL_INTEL_performance_query spec says:
    *
    *    "If queryId does not reference a valid query type, an INVALID_VALUE
    *    error is generated."
    */
   if (!queryid_valid(ctx, numQueries, queryId)) {
      _mesa_error(ctx, GL_INVALID_VALUE,
                  "glCreatePerfQueryINTEL(invalid queryId)");
      return;
   }

   /* 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;
   }

   id = _mesa_HashFindFreeKeyBlock(ctx->PerfQuery.Objects, 1);
   if (!id) {
      /* 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_no_memory(__func__);
      return;
   }

   obj = ctx->Driver.NewPerfQueryObject(ctx, queryid_to_index(queryId));
   if (obj == NULL) {
      _mesa_error_no_memory(__func__);
      return;
   }

   obj->Id = id;
   obj->Active = false;
   obj->Ready = false;

   _mesa_HashInsert(ctx->PerfQuery.Objects, id, obj);
   *queryHandle = id;
}

extern void GLAPIENTRY
_mesa_DeletePerfQueryINTEL(GLuint queryHandle)
{
   GET_CURRENT_CONTEXT(ctx);

   struct gl_perf_query_object *obj = lookup_object(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 (obj == NULL) {
      _mesa_error(ctx, GL_INVALID_VALUE,
                  "glDeletePerfQueryINTEL(invalid queryHandle)");
      return;
   }

   /* To avoid complications in the backend we never ask the backend to
    * delete an active query or a query object while we are still
    * waiting for data.
    */

   if (obj->Active)
      _mesa_EndPerfQueryINTEL(queryHandle);

   if (obj->Used && !obj->Ready) {
      ctx->Driver.WaitPerfQuery(ctx, obj);
      obj->Ready = true;
   }

   _mesa_HashRemove(ctx->PerfQuery.Objects, queryHandle);
   ctx->Driver.DeletePerfQuery(ctx, obj);
}

extern void GLAPIENTRY
_mesa_BeginPerfQueryINTEL(GLuint queryHandle)
{
   GET_CURRENT_CONTEXT(ctx);

   struct gl_perf_query_object *obj = lookup_object(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 (obj == NULL) {
      _mesa_error(ctx, GL_INVALID_VALUE,
                  "glBeginPerfQueryINTEL(invalid queryHandle)");
      return;
   }

   /* The GL_INTEL_performance_query spec says:
    *
    *    "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
    * a query for its own reasons, and for nesting the same query.
    */
   if (obj->Active) {
      _mesa_error(ctx, GL_INVALID_OPERATION,
                  "glBeginPerfQueryINTEL(already active)");
      return;
   }

   /* To avoid complications in the backend we never ask the backend to
    * reuse a query object and begin a new query while we are still
    * waiting for data on that object.
    */
   if (obj->Used && !obj->Ready) {
      ctx->Driver.WaitPerfQuery(ctx, obj);
      obj->Ready = true;
   }

   if (ctx->Driver.BeginPerfQuery(ctx, obj)) {
      obj->Used = true;
      obj->Active = true;
      obj->Ready = false;
   } else {
      _mesa_error(ctx, GL_INVALID_OPERATION,
                  "glBeginPerfQueryINTEL(driver unable to begin query)");
   }
}

extern void GLAPIENTRY
_mesa_EndPerfQueryINTEL(GLuint queryHandle)
{
   GET_CURRENT_CONTEXT(ctx);

   struct gl_perf_query_object *obj = lookup_object(ctx, queryHandle);

   /* Not explicitly covered in the spec, but for consistency... */
   if (obj == NULL) {
      _mesa_error(ctx, GL_INVALID_VALUE,
                  "glEndPerfQueryINTEL(invalid queryHandle)");
      return;
   }

   /* The GL_INTEL_performance_query spec says:
    *
    *    "If a performance query is not currently started, an
    *    INVALID_OPERATION error will be generated."
    */

   if (!obj->Active) {
      _mesa_error(ctx, GL_INVALID_OPERATION,
                  "glEndPerfQueryINTEL(not active)");
      return;
   }

   ctx->Driver.EndPerfQuery(ctx, obj);

   obj->Active = false;
   obj->Ready = false;
}

extern void GLAPIENTRY
_mesa_GetPerfQueryDataINTEL(GLuint queryHandle, GLuint flags,
                            GLsizei dataSize, void *data, GLuint *bytesWritten)
{
   GET_CURRENT_CONTEXT(ctx);

   struct gl_perf_query_object *obj = lookup_object(ctx, queryHandle);

   /* Not explicitly covered in the spec, but for consistency... */
   if (obj == NULL) {
      _mesa_error(ctx, GL_INVALID_VALUE,
                  "glEndPerfQueryINTEL(invalid queryHandle)");
      return;
   }

   /* The GL_INTEL_performance_query spec says:
    *
    *    "If bytesWritten or data pointers are NULL then an INVALID_VALUE
    *    error is generated."
    */
   if (!bytesWritten || !data) {
      _mesa_error(ctx, GL_INVALID_VALUE,
                  "glGetPerfQueryDataINTEL(bytesWritten or data is NULL)");
      return;
   }

   /* Just for good measure in case a lazy application is only
    * checking this and not checking for errors...
    */
   *bytesWritten = 0;

   /* Not explicitly covered in the spec but to be consistent with
    * EndPerfQuery which validates that an application only ends an
    * active query we also validate that an application doesn't try
    * and get the data for a still active query...
    */
   if (obj->Active) {
      _mesa_error(ctx, GL_INVALID_OPERATION,
                  "glGetPerfQueryDataINTEL(query still active)");
      return;
   }

   obj->Ready = ctx->Driver.IsPerfQueryReady(ctx, obj);

   if (!obj->Ready) {
      if (flags == GL_PERFQUERY_FLUSH_INTEL) {
         ctx->Driver.Flush(ctx);
      } else if (flags == GL_PERFQUERY_WAIT_INTEL) {
         ctx->Driver.WaitPerfQuery(ctx, obj);
         obj->Ready = true;
      }
   }

   if (obj->Ready)
      ctx->Driver.GetPerfQueryData(ctx, obj, dataSize, data, bytesWritten);
}
