1/*
2 * Copyright © 2012-2017 Intel Corporation
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 * DEALINGS IN THE SOFTWARE.
22 */
23
24/**
25 * \file performance_query.c
26 * Core Mesa support for the INTEL_performance_query extension.
27 */
28
29#include <stdbool.h>
30#include "glheader.h"
31#include "context.h"
32#include "enums.h"
33#include "hash.h"
34#include "macros.h"
35#include "mtypes.h"
36#include "performance_query.h"
37#include "util/ralloc.h"
38
39void
40_mesa_init_performance_queries(struct gl_context *ctx)
41{
42   ctx->PerfQuery.Objects = _mesa_NewHashTable();
43}
44
45static void
46free_performance_query(GLuint key, void *data, void *user)
47{
48   struct gl_perf_query_object *m = data;
49   struct gl_context *ctx = user;
50
51   ctx->Driver.DeletePerfQuery(ctx, m);
52}
53
54void
55_mesa_free_performance_queries(struct gl_context *ctx)
56{
57   _mesa_HashDeleteAll(ctx->PerfQuery.Objects,
58                       free_performance_query, ctx);
59   _mesa_DeleteHashTable(ctx->PerfQuery.Objects);
60}
61
62static inline struct gl_perf_query_object *
63lookup_object(struct gl_context *ctx, GLuint id)
64{
65   return _mesa_HashLookup(ctx->PerfQuery.Objects, id);
66}
67
68static GLuint
69init_performance_query_info(struct gl_context *ctx)
70{
71   if (ctx->Driver.InitPerfQueryInfo)
72      return ctx->Driver.InitPerfQueryInfo(ctx);
73   else
74      return 0;
75}
76
77/* For INTEL_performance_query, query id 0 is reserved to be invalid. */
78static inline unsigned
79queryid_to_index(GLuint queryid)
80{
81   return queryid - 1;
82}
83
84static inline GLuint
85index_to_queryid(unsigned index)
86{
87   return index + 1;
88}
89
90static inline bool
91queryid_valid(const struct gl_context *ctx, unsigned numQueries, GLuint queryid)
92{
93   /* The GL_INTEL_performance_query spec says:
94    *
95    *  "Performance counter ids values start with 1. Performance counter id 0
96    *  is reserved as an invalid counter."
97    */
98   return queryid != 0 && queryid_to_index(queryid) < numQueries;
99}
100
101static inline GLuint
102counterid_to_index(GLuint counterid)
103{
104   return counterid - 1;
105}
106
107static void
108output_clipped_string(GLchar *stringRet,
109                      GLuint stringMaxLen,
110                      const char *string)
111{
112   if (!stringRet)
113      return;
114
115   strncpy(stringRet, string ? string : "", stringMaxLen);
116
117   /* No specification given about whether returned strings needs
118    * to be zero-terminated. Zero-terminate the string always as we
119    * don't otherwise communicate the length of the returned
120    * string.
121    */
122   if (stringMaxLen > 0)
123      stringRet[stringMaxLen - 1] = '\0';
124}
125
126/*****************************************************************************/
127
128extern void GLAPIENTRY
129_mesa_GetFirstPerfQueryIdINTEL(GLuint *queryId)
130{
131   GET_CURRENT_CONTEXT(ctx);
132
133   unsigned numQueries;
134
135   /* The GL_INTEL_performance_query spec says:
136    *
137    *    "If queryId pointer is equal to 0, INVALID_VALUE error is generated."
138    */
139   if (!queryId) {
140      _mesa_error(ctx, GL_INVALID_VALUE,
141                  "glGetFirstPerfQueryIdINTEL(queryId == NULL)");
142      return;
143   }
144
145   numQueries = init_performance_query_info(ctx);
146
147   /* The GL_INTEL_performance_query spec says:
148    *
149    *    "If the given hardware platform doesn't support any performance
150    *    queries, then the value of 0 is returned and INVALID_OPERATION error
151    *    is raised."
152    */
153   if (numQueries == 0) {
154      *queryId = 0;
155      _mesa_error(ctx, GL_INVALID_OPERATION,
156                  "glGetFirstPerfQueryIdINTEL(no queries supported)");
157      return;
158   }
159
160   *queryId = index_to_queryid(0);
161}
162
163extern void GLAPIENTRY
164_mesa_GetNextPerfQueryIdINTEL(GLuint queryId, GLuint *nextQueryId)
165{
166   GET_CURRENT_CONTEXT(ctx);
167
168   unsigned numQueries;
169
170   /* The GL_INTEL_performance_query spec says:
171    *
172    *    "The result is passed in location pointed by nextQueryId. If query
173    *    identified by queryId is the last query available the value of 0 is
174    *    returned. If the specified performance query identifier is invalid
175    *    then INVALID_VALUE error is generated. If nextQueryId pointer is
176    *    equal to 0, an INVALID_VALUE error is generated.  Whenever error is
177    *    generated, the value of 0 is returned."
178    */
179
180   if (!nextQueryId) {
181      _mesa_error(ctx, GL_INVALID_VALUE,
182                  "glGetNextPerfQueryIdINTEL(nextQueryId == NULL)");
183      return;
184   }
185
186   numQueries = init_performance_query_info(ctx);
187
188   if (!queryid_valid(ctx, numQueries, queryId)) {
189      _mesa_error(ctx, GL_INVALID_VALUE,
190                  "glGetNextPerfQueryIdINTEL(invalid query)");
191      return;
192   }
193
194   if (queryid_valid(ctx, numQueries, ++queryId))
195      *nextQueryId = queryId;
196   else
197      *nextQueryId = 0;
198}
199
200extern void GLAPIENTRY
201_mesa_GetPerfQueryIdByNameINTEL(char *queryName, GLuint *queryId)
202{
203   GET_CURRENT_CONTEXT(ctx);
204
205   unsigned numQueries;
206   unsigned i;
207
208   /* The GL_INTEL_performance_query spec says:
209    *
210    *    "If queryName does not reference a valid query name, an INVALID_VALUE
211    *    error is generated."
212    */
213   if (!queryName) {
214      _mesa_error(ctx, GL_INVALID_VALUE,
215                  "glGetPerfQueryIdByNameINTEL(queryName == NULL)");
216      return;
217   }
218
219   /* The specification does not state that this produces an error but
220    * to be consistent with glGetFirstPerfQueryIdINTEL we generate an
221    * INVALID_VALUE error
222    */
223   if (!queryId) {
224      _mesa_error(ctx, GL_INVALID_VALUE,
225                  "glGetPerfQueryIdByNameINTEL(queryId == NULL)");
226      return;
227   }
228
229   numQueries = init_performance_query_info(ctx);
230
231   for (i = 0; i < numQueries; ++i) {
232      const GLchar *name;
233      GLuint ignore;
234
235      ctx->Driver.GetPerfQueryInfo(ctx, i, &name, &ignore, &ignore, &ignore);
236
237      if (strcmp(name, queryName) == 0) {
238         *queryId = index_to_queryid(i);
239         return;
240      }
241   }
242
243   _mesa_error(ctx, GL_INVALID_VALUE,
244               "glGetPerfQueryIdByNameINTEL(invalid query name)");
245}
246
247extern void GLAPIENTRY
248_mesa_GetPerfQueryInfoINTEL(GLuint queryId,
249                            GLuint nameLength, GLchar *name,
250                            GLuint *dataSize,
251                            GLuint *numCounters,
252                            GLuint *numActive,
253                            GLuint *capsMask)
254{
255   GET_CURRENT_CONTEXT(ctx);
256
257   unsigned numQueries = init_performance_query_info(ctx);
258   unsigned queryIndex = queryid_to_index(queryId);
259   const char *queryName;
260   GLuint queryDataSize;
261   GLuint queryNumCounters;
262   GLuint queryNumActive;
263
264   if (!queryid_valid(ctx, numQueries, queryId)) {
265      /* The GL_INTEL_performance_query spec says:
266       *
267       *    "If queryId does not reference a valid query type, an
268       *    INVALID_VALUE error is generated."
269       */
270      _mesa_error(ctx, GL_INVALID_VALUE,
271                  "glGetPerfQueryInfoINTEL(invalid query)");
272      return;
273   }
274
275   ctx->Driver.GetPerfQueryInfo(ctx, queryIndex,
276                                &queryName,
277                                &queryDataSize,
278                                &queryNumCounters,
279                                &queryNumActive);
280
281   output_clipped_string(name, nameLength, queryName);
282
283   if (dataSize)
284      *dataSize = queryDataSize;
285
286   if (numCounters)
287      *numCounters = queryNumCounters;
288
289   /* The GL_INTEL_performance_query spec says:
290    *
291    *    "-- the actual number of already created query instances in
292    *    maxInstances location"
293    *
294    * 1) Typo in the specification, should be noActiveInstances.
295    * 2) Another typo in the specification, maxInstances parameter is not listed
296    *    in the declaration of this function in the list of new functions.
297    */
298   if (numActive)
299      *numActive = queryNumActive;
300
301   /* Assume for now that all queries are per-context */
302   if (capsMask)
303      *capsMask = GL_PERFQUERY_SINGLE_CONTEXT_INTEL;
304}
305
306extern void GLAPIENTRY
307_mesa_GetPerfCounterInfoINTEL(GLuint queryId, GLuint counterId,
308                              GLuint nameLength, GLchar *name,
309                              GLuint descLength, GLchar *desc,
310                              GLuint *offset,
311                              GLuint *dataSize,
312                              GLuint *typeEnum,
313                              GLuint *dataTypeEnum,
314                              GLuint64 *rawCounterMaxValue)
315{
316   GET_CURRENT_CONTEXT(ctx);
317
318   unsigned numQueries = init_performance_query_info(ctx);
319   unsigned queryIndex = queryid_to_index(queryId);
320   const char *queryName;
321   GLuint queryDataSize;
322   GLuint queryNumCounters;
323   GLuint queryNumActive;
324   unsigned counterIndex;
325   const char *counterName;
326   const char *counterDesc;
327   GLuint counterOffset;
328   GLuint counterDataSize;
329   GLuint counterTypeEnum;
330   GLuint counterDataTypeEnum;
331   GLuint64 counterRawMax;
332
333   if (!queryid_valid(ctx, numQueries, queryId)) {
334      /* The GL_INTEL_performance_query spec says:
335       *
336       *    "If the pair of queryId and counterId does not reference a valid
337       *    counter, an INVALID_VALUE error is generated."
338       */
339      _mesa_error(ctx, GL_INVALID_VALUE,
340                  "glGetPerfCounterInfoINTEL(invalid queryId)");
341      return;
342   }
343
344   ctx->Driver.GetPerfQueryInfo(ctx, queryIndex,
345                                &queryName,
346                                &queryDataSize,
347                                &queryNumCounters,
348                                &queryNumActive);
349
350   counterIndex = counterid_to_index(counterId);
351
352   if (counterIndex >= queryNumCounters) {
353      _mesa_error(ctx, GL_INVALID_VALUE,
354                  "glGetPerfCounterInfoINTEL(invalid counterId)");
355      return;
356   }
357
358   ctx->Driver.GetPerfCounterInfo(ctx, queryIndex, counterIndex,
359                                  &counterName,
360                                  &counterDesc,
361                                  &counterOffset,
362                                  &counterDataSize,
363                                  &counterTypeEnum,
364                                  &counterDataTypeEnum,
365                                  &counterRawMax);
366
367   output_clipped_string(name, nameLength, counterName);
368   output_clipped_string(desc, descLength, counterDesc);
369
370   if (offset)
371      *offset = counterOffset;
372
373   if (dataSize)
374      *dataSize = counterDataSize;
375
376   if (typeEnum)
377      *typeEnum = counterTypeEnum;
378
379   if (dataTypeEnum)
380      *dataTypeEnum = counterDataTypeEnum;
381
382   if (rawCounterMaxValue)
383      *rawCounterMaxValue = counterRawMax;
384
385   if (rawCounterMaxValue) {
386      /* The GL_INTEL_performance_query spec says:
387       *
388       *    "for some raw counters for which the maximal value is
389       *    deterministic, the maximal value of the counter in 1 second is
390       *    returned in the location pointed by rawCounterMaxValue, otherwise,
391       *    the location is written with the value of 0."
392       *
393       *    Since it's very useful to be able to report a maximum value for
394       *    more that just counters using the _COUNTER_RAW_INTEL or
395       *    _COUNTER_DURATION_RAW_INTEL enums (e.g. for a _THROUGHPUT tools
396       *    want to be able to visualize the absolute throughput with respect
397       *    to the theoretical maximum that's possible) and there doesn't seem
398       *    to be any reason not to allow _THROUGHPUT counters to also be
399       *    considerer "raw" here, we always leave it up to the backend to
400       *    decide when it's appropriate to report a maximum counter value or 0
401       *    if not.
402       */
403      *rawCounterMaxValue = counterRawMax;
404   }
405}
406
407extern void GLAPIENTRY
408_mesa_CreatePerfQueryINTEL(GLuint queryId, GLuint *queryHandle)
409{
410   GET_CURRENT_CONTEXT(ctx);
411
412   unsigned numQueries = init_performance_query_info(ctx);
413   GLuint id;
414   struct gl_perf_query_object *obj;
415
416   /* The GL_INTEL_performance_query spec says:
417    *
418    *    "If queryId does not reference a valid query type, an INVALID_VALUE
419    *    error is generated."
420    */
421   if (!queryid_valid(ctx, numQueries, queryId)) {
422      _mesa_error(ctx, GL_INVALID_VALUE,
423                  "glCreatePerfQueryINTEL(invalid queryId)");
424      return;
425   }
426
427   /* This is not specified in the extension, but is the only sane thing to
428    * do.
429    */
430   if (queryHandle == NULL) {
431      _mesa_error(ctx, GL_INVALID_VALUE,
432                  "glCreatePerfQueryINTEL(queryHandle == NULL)");
433      return;
434   }
435
436   id = _mesa_HashFindFreeKeyBlock(ctx->PerfQuery.Objects, 1);
437   if (!id) {
438      /* The GL_INTEL_performance_query spec says:
439       *
440       *    "If the query instance cannot be created due to exceeding the
441       *    number of allowed instances or driver fails query creation due to
442       *    an insufficient memory reason, an OUT_OF_MEMORY error is
443       *    generated, and the location pointed by queryHandle returns NULL."
444       */
445      _mesa_error_no_memory(__func__);
446      return;
447   }
448
449   obj = ctx->Driver.NewPerfQueryObject(ctx, queryid_to_index(queryId));
450   if (obj == NULL) {
451      _mesa_error_no_memory(__func__);
452      return;
453   }
454
455   obj->Id = id;
456   obj->Active = false;
457   obj->Ready = false;
458
459   _mesa_HashInsert(ctx->PerfQuery.Objects, id, obj);
460   *queryHandle = id;
461}
462
463extern void GLAPIENTRY
464_mesa_DeletePerfQueryINTEL(GLuint queryHandle)
465{
466   GET_CURRENT_CONTEXT(ctx);
467
468   struct gl_perf_query_object *obj = lookup_object(ctx, queryHandle);
469
470   /* The GL_INTEL_performance_query spec says:
471    *
472    *    "If a query handle doesn't reference a previously created performance
473    *    query instance, an INVALID_VALUE error is generated."
474    */
475   if (obj == NULL) {
476      _mesa_error(ctx, GL_INVALID_VALUE,
477                  "glDeletePerfQueryINTEL(invalid queryHandle)");
478      return;
479   }
480
481   /* To avoid complications in the backend we never ask the backend to
482    * delete an active query or a query object while we are still
483    * waiting for data.
484    */
485
486   if (obj->Active)
487      _mesa_EndPerfQueryINTEL(queryHandle);
488
489   if (obj->Used && !obj->Ready) {
490      ctx->Driver.WaitPerfQuery(ctx, obj);
491      obj->Ready = true;
492   }
493
494   _mesa_HashRemove(ctx->PerfQuery.Objects, queryHandle);
495   ctx->Driver.DeletePerfQuery(ctx, obj);
496}
497
498extern void GLAPIENTRY
499_mesa_BeginPerfQueryINTEL(GLuint queryHandle)
500{
501   GET_CURRENT_CONTEXT(ctx);
502
503   struct gl_perf_query_object *obj = lookup_object(ctx, queryHandle);
504
505   /* The GL_INTEL_performance_query spec says:
506    *
507    *    "If a query handle doesn't reference a previously created performance
508    *    query instance, an INVALID_VALUE error is generated."
509    */
510   if (obj == NULL) {
511      _mesa_error(ctx, GL_INVALID_VALUE,
512                  "glBeginPerfQueryINTEL(invalid queryHandle)");
513      return;
514   }
515
516   /* The GL_INTEL_performance_query spec says:
517    *
518    *    "Note that some query types, they cannot be collected in the same
519    *    time. Therefore calls of BeginPerfQueryINTEL() cannot be nested if
520    *    they refer to queries of such different types. In such case
521    *    INVALID_OPERATION error is generated."
522    *
523    * We also generate an INVALID_OPERATION error if the driver can't begin
524    * a query for its own reasons, and for nesting the same query.
525    */
526   if (obj->Active) {
527      _mesa_error(ctx, GL_INVALID_OPERATION,
528                  "glBeginPerfQueryINTEL(already active)");
529      return;
530   }
531
532   /* To avoid complications in the backend we never ask the backend to
533    * reuse a query object and begin a new query while we are still
534    * waiting for data on that object.
535    */
536   if (obj->Used && !obj->Ready) {
537      ctx->Driver.WaitPerfQuery(ctx, obj);
538      obj->Ready = true;
539   }
540
541   if (ctx->Driver.BeginPerfQuery(ctx, obj)) {
542      obj->Used = true;
543      obj->Active = true;
544      obj->Ready = false;
545   } else {
546      _mesa_error(ctx, GL_INVALID_OPERATION,
547                  "glBeginPerfQueryINTEL(driver unable to begin query)");
548   }
549}
550
551extern void GLAPIENTRY
552_mesa_EndPerfQueryINTEL(GLuint queryHandle)
553{
554   GET_CURRENT_CONTEXT(ctx);
555
556   struct gl_perf_query_object *obj = lookup_object(ctx, queryHandle);
557
558   /* Not explicitly covered in the spec, but for consistency... */
559   if (obj == NULL) {
560      _mesa_error(ctx, GL_INVALID_VALUE,
561                  "glEndPerfQueryINTEL(invalid queryHandle)");
562      return;
563   }
564
565   /* The GL_INTEL_performance_query spec says:
566    *
567    *    "If a performance query is not currently started, an
568    *    INVALID_OPERATION error will be generated."
569    */
570
571   if (!obj->Active) {
572      _mesa_error(ctx, GL_INVALID_OPERATION,
573                  "glEndPerfQueryINTEL(not active)");
574      return;
575   }
576
577   ctx->Driver.EndPerfQuery(ctx, obj);
578
579   obj->Active = false;
580   obj->Ready = false;
581}
582
583extern void GLAPIENTRY
584_mesa_GetPerfQueryDataINTEL(GLuint queryHandle, GLuint flags,
585                            GLsizei dataSize, void *data, GLuint *bytesWritten)
586{
587   GET_CURRENT_CONTEXT(ctx);
588
589   struct gl_perf_query_object *obj = lookup_object(ctx, queryHandle);
590
591   /* Not explicitly covered in the spec, but for consistency... */
592   if (obj == NULL) {
593      _mesa_error(ctx, GL_INVALID_VALUE,
594                  "glEndPerfQueryINTEL(invalid queryHandle)");
595      return;
596   }
597
598   /* The GL_INTEL_performance_query spec says:
599    *
600    *    "If bytesWritten or data pointers are NULL then an INVALID_VALUE
601    *    error is generated."
602    */
603   if (!bytesWritten || !data) {
604      _mesa_error(ctx, GL_INVALID_VALUE,
605                  "glGetPerfQueryDataINTEL(bytesWritten or data is NULL)");
606      return;
607   }
608
609   /* Just for good measure in case a lazy application is only
610    * checking this and not checking for errors...
611    */
612   *bytesWritten = 0;
613
614   /* Not explicitly covered in the spec but to be consistent with
615    * EndPerfQuery which validates that an application only ends an
616    * active query we also validate that an application doesn't try
617    * and get the data for a still active query...
618    */
619   if (obj->Active) {
620      _mesa_error(ctx, GL_INVALID_OPERATION,
621                  "glGetPerfQueryDataINTEL(query still active)");
622      return;
623   }
624
625   obj->Ready = ctx->Driver.IsPerfQueryReady(ctx, obj);
626
627   if (!obj->Ready) {
628      if (flags == GL_PERFQUERY_FLUSH_INTEL) {
629         ctx->Driver.Flush(ctx);
630      } else if (flags == GL_PERFQUERY_WAIT_INTEL) {
631         ctx->Driver.WaitPerfQuery(ctx, obj);
632         obj->Ready = true;
633      }
634   }
635
636   if (obj->Ready)
637      ctx->Driver.GetPerfQueryData(ctx, obj, dataSize, data, bytesWritten);
638}
639