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
39 void
_mesa_init_performance_queries(struct gl_context * ctx)40 _mesa_init_performance_queries(struct gl_context *ctx)
41 {
42 ctx->PerfQuery.Objects = _mesa_NewHashTable();
43 }
44
45 static void
free_performance_query(GLuint key,void * data,void * user)46 free_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
54 void
_mesa_free_performance_queries(struct gl_context * ctx)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
62 static inline struct gl_perf_query_object *
lookup_object(struct gl_context * ctx,GLuint id)63 lookup_object(struct gl_context *ctx, GLuint id)
64 {
65 return _mesa_HashLookup(ctx->PerfQuery.Objects, id);
66 }
67
68 static GLuint
init_performance_query_info(struct gl_context * ctx)69 init_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. */
78 static inline unsigned
queryid_to_index(GLuint queryid)79 queryid_to_index(GLuint queryid)
80 {
81 return queryid - 1;
82 }
83
84 static inline GLuint
index_to_queryid(unsigned index)85 index_to_queryid(unsigned index)
86 {
87 return index + 1;
88 }
89
90 static inline bool
queryid_valid(const struct gl_context * ctx,unsigned numQueries,GLuint queryid)91 queryid_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
101 static inline GLuint
counterid_to_index(GLuint counterid)102 counterid_to_index(GLuint counterid)
103 {
104 return counterid - 1;
105 }
106
107 static void
output_clipped_string(GLchar * stringRet,GLuint stringMaxLen,const char * string)108 output_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
128 extern void GLAPIENTRY
_mesa_GetFirstPerfQueryIdINTEL(GLuint * queryId)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
163 extern void GLAPIENTRY
_mesa_GetNextPerfQueryIdINTEL(GLuint queryId,GLuint * nextQueryId)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
200 extern void GLAPIENTRY
_mesa_GetPerfQueryIdByNameINTEL(char * queryName,GLuint * queryId)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
247 extern void GLAPIENTRY
_mesa_GetPerfQueryInfoINTEL(GLuint queryId,GLuint nameLength,GLchar * name,GLuint * dataSize,GLuint * numCounters,GLuint * numActive,GLuint * capsMask)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
306 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)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
407 extern void GLAPIENTRY
_mesa_CreatePerfQueryINTEL(GLuint queryId,GLuint * queryHandle)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
463 extern void GLAPIENTRY
_mesa_DeletePerfQueryINTEL(GLuint queryHandle)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
498 extern void GLAPIENTRY
_mesa_BeginPerfQueryINTEL(GLuint queryHandle)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
551 extern void GLAPIENTRY
_mesa_EndPerfQueryINTEL(GLuint queryHandle)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
583 extern void GLAPIENTRY
_mesa_GetPerfQueryDataINTEL(GLuint queryHandle,GLuint flags,GLsizei dataSize,void * data,GLuint * bytesWritten)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