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