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