/* * Mesa 3-D graphics library * * Copyright (C) 1999-2007 Brian Paul All Rights Reserved. * * 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 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. */ #include "bufferobj.h" #include "glheader.h" #include "context.h" #include "enums.h" #include "hash.h" #include "queryobj.h" #include "mtypes.h" #include "util/u_memory.h" /** * Allocate a new query object. This is a fallback routine called via * ctx->Driver.NewQueryObject(). * \param ctx - rendering context * \param id - the new object's ID * \return pointer to new query_object object or NULL if out of memory. */ static struct gl_query_object * _mesa_new_query_object(struct gl_context *ctx, GLuint id) { struct gl_query_object *q = CALLOC_STRUCT(gl_query_object); (void) ctx; if (q) { q->Id = id; q->Result = 0; q->Active = GL_FALSE; /* This is to satisfy the language of the specification: "In the initial * state of a query object, the result is available" (OpenGL 3.1 § * 2.13). */ q->Ready = GL_TRUE; /* OpenGL 3.1 § 2.13 says about GenQueries, "These names are marked as * used, but no object is associated with them until the first time they * are used by BeginQuery." Since our implementation actually does * allocate an object at this point, use a flag to indicate that this * object has not yet been bound so should not be considered a query. */ q->EverBound = GL_FALSE; } return q; } /** * Begin a query. Software driver fallback. * Called via ctx->Driver.BeginQuery(). */ static void _mesa_begin_query(struct gl_context *ctx, struct gl_query_object *q) { ctx->NewState |= _NEW_DEPTH; /* for swrast */ } /** * End a query. Software driver fallback. * Called via ctx->Driver.EndQuery(). */ static void _mesa_end_query(struct gl_context *ctx, struct gl_query_object *q) { ctx->NewState |= _NEW_DEPTH; /* for swrast */ q->Ready = GL_TRUE; } /** * Wait for query to complete. Software driver fallback. * Called via ctx->Driver.WaitQuery(). */ static void _mesa_wait_query(struct gl_context *ctx, struct gl_query_object *q) { /* For software drivers, _mesa_end_query() should have completed the query. * For real hardware, implement a proper WaitQuery() driver function, * which may require issuing a flush. */ assert(q->Ready); } /** * Check if a query results are ready. Software driver fallback. * Called via ctx->Driver.CheckQuery(). */ static void _mesa_check_query(struct gl_context *ctx, struct gl_query_object *q) { /* No-op for sw rendering. * HW drivers may need to flush at this time. */ } /** * Delete a query object. Called via ctx->Driver.DeleteQuery(), if not * overwritten by driver. In the latter case, called from the driver * after all driver-specific clean-up has been done. * Not removed from hash table here. * * \param ctx GL context to wich query object belongs. * \param q query object due to be deleted. */ void _mesa_delete_query(struct gl_context *ctx, struct gl_query_object *q) { free(q->Label); free(q); } void _mesa_init_query_object_functions(struct dd_function_table *driver) { driver->NewQueryObject = _mesa_new_query_object; driver->DeleteQuery = _mesa_delete_query; driver->BeginQuery = _mesa_begin_query; driver->EndQuery = _mesa_end_query; driver->WaitQuery = _mesa_wait_query; driver->CheckQuery = _mesa_check_query; } static struct gl_query_object ** get_pipe_stats_binding_point(struct gl_context *ctx, GLenum target) { const int which = target - GL_VERTICES_SUBMITTED; assert(which < MAX_PIPELINE_STATISTICS); if (!_mesa_has_ARB_pipeline_statistics_query(ctx)) return NULL; return &ctx->Query.pipeline_stats[which]; } /** * Return pointer to the query object binding point for the given target and * index. * \return NULL if invalid target, else the address of binding point */ static struct gl_query_object ** get_query_binding_point(struct gl_context *ctx, GLenum target, GLuint index) { switch (target) { case GL_SAMPLES_PASSED: if (_mesa_has_ARB_occlusion_query(ctx) || _mesa_has_ARB_occlusion_query2(ctx)) return &ctx->Query.CurrentOcclusionObject; else return NULL; case GL_ANY_SAMPLES_PASSED: if (_mesa_has_ARB_occlusion_query2(ctx) || _mesa_has_EXT_occlusion_query_boolean(ctx)) return &ctx->Query.CurrentOcclusionObject; else return NULL; case GL_ANY_SAMPLES_PASSED_CONSERVATIVE: if (_mesa_has_ARB_ES3_compatibility(ctx) || _mesa_has_EXT_occlusion_query_boolean(ctx)) return &ctx->Query.CurrentOcclusionObject; else return NULL; case GL_TIME_ELAPSED: if (_mesa_has_EXT_timer_query(ctx) || _mesa_has_EXT_disjoint_timer_query(ctx)) return &ctx->Query.CurrentTimerObject; else return NULL; case GL_PRIMITIVES_GENERATED: if (_mesa_has_EXT_transform_feedback(ctx) || _mesa_has_EXT_tessellation_shader(ctx) || _mesa_has_OES_geometry_shader(ctx)) return &ctx->Query.PrimitivesGenerated[index]; else return NULL; case GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN: if (_mesa_has_EXT_transform_feedback(ctx) || _mesa_is_gles3(ctx)) return &ctx->Query.PrimitivesWritten[index]; else return NULL; case GL_TRANSFORM_FEEDBACK_STREAM_OVERFLOW: if (_mesa_has_ARB_transform_feedback_overflow_query(ctx)) return &ctx->Query.TransformFeedbackOverflow[index]; else return NULL; case GL_TRANSFORM_FEEDBACK_OVERFLOW: if (_mesa_has_ARB_transform_feedback_overflow_query(ctx)) return &ctx->Query.TransformFeedbackOverflowAny; else return NULL; case GL_VERTICES_SUBMITTED: case GL_PRIMITIVES_SUBMITTED: case GL_VERTEX_SHADER_INVOCATIONS: case GL_FRAGMENT_SHADER_INVOCATIONS: case GL_CLIPPING_INPUT_PRIMITIVES: case GL_CLIPPING_OUTPUT_PRIMITIVES: return get_pipe_stats_binding_point(ctx, target); case GL_GEOMETRY_SHADER_INVOCATIONS: /* GL_GEOMETRY_SHADER_INVOCATIONS is defined in a non-sequential order */ target = GL_VERTICES_SUBMITTED + MAX_PIPELINE_STATISTICS - 1; /* fallthrough */ case GL_GEOMETRY_SHADER_PRIMITIVES_EMITTED: if (_mesa_has_geometry_shaders(ctx)) return get_pipe_stats_binding_point(ctx, target); else return NULL; case GL_TESS_CONTROL_SHADER_PATCHES: case GL_TESS_EVALUATION_SHADER_INVOCATIONS: if (_mesa_has_tessellation(ctx)) return get_pipe_stats_binding_point(ctx, target); else return NULL; case GL_COMPUTE_SHADER_INVOCATIONS: if (_mesa_has_compute_shaders(ctx)) return get_pipe_stats_binding_point(ctx, target); else return NULL; default: return NULL; } } /** * Create $n query objects and store them in *ids. Make them of type $target * if dsa is set. Called from _mesa_GenQueries() and _mesa_CreateQueries(). */ static void create_queries(struct gl_context *ctx, GLenum target, GLsizei n, GLuint *ids, bool dsa) { const char *func = dsa ? "glGenQueries" : "glCreateQueries"; if (MESA_VERBOSE & VERBOSE_API) _mesa_debug(ctx, "%s(%d)\n", func, n); if (n < 0) { _mesa_error(ctx, GL_INVALID_VALUE, "%s(n < 0)", func); return; } if (_mesa_HashFindFreeKeys(ctx->Query.QueryObjects, ids, n)) { GLsizei i; for (i = 0; i < n; i++) { struct gl_query_object *q = ctx->Driver.NewQueryObject(ctx, ids[i]); if (!q) { _mesa_error(ctx, GL_OUT_OF_MEMORY, "%s", func); return; } else if (dsa) { /* Do the equivalent of binding the buffer with a target */ q->Target = target; q->EverBound = GL_TRUE; } _mesa_HashInsertLocked(ctx->Query.QueryObjects, ids[i], q, true); } } } void GLAPIENTRY _mesa_GenQueries(GLsizei n, GLuint *ids) { GET_CURRENT_CONTEXT(ctx); create_queries(ctx, 0, n, ids, false); } void GLAPIENTRY _mesa_CreateQueries(GLenum target, GLsizei n, GLuint *ids) { GET_CURRENT_CONTEXT(ctx); switch (target) { case GL_SAMPLES_PASSED: case GL_ANY_SAMPLES_PASSED: case GL_ANY_SAMPLES_PASSED_CONSERVATIVE: case GL_TIME_ELAPSED: case GL_TIMESTAMP: case GL_PRIMITIVES_GENERATED: case GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN: case GL_TRANSFORM_FEEDBACK_STREAM_OVERFLOW: case GL_TRANSFORM_FEEDBACK_OVERFLOW: break; default: _mesa_error(ctx, GL_INVALID_ENUM, "glCreateQueries(invalid target = %s)", _mesa_enum_to_string(target)); return; } create_queries(ctx, target, n, ids, true); } void GLAPIENTRY _mesa_DeleteQueries(GLsizei n, const GLuint *ids) { GLint i; GET_CURRENT_CONTEXT(ctx); FLUSH_VERTICES(ctx, 0); if (MESA_VERBOSE & VERBOSE_API) _mesa_debug(ctx, "glDeleteQueries(%d)\n", n); if (n < 0) { _mesa_error(ctx, GL_INVALID_VALUE, "glDeleteQueriesARB(n < 0)"); return; } for (i = 0; i < n; i++) { if (ids[i] > 0) { struct gl_query_object *q = _mesa_lookup_query_object(ctx, ids[i]); if (q) { if (q->Active) { struct gl_query_object **bindpt; bindpt = get_query_binding_point(ctx, q->Target, q->Stream); assert(bindpt); /* Should be non-null for active q. */ if (bindpt) { *bindpt = NULL; } q->Active = GL_FALSE; ctx->Driver.EndQuery(ctx, q); } _mesa_HashRemoveLocked(ctx->Query.QueryObjects, ids[i]); ctx->Driver.DeleteQuery(ctx, q); } } } } GLboolean GLAPIENTRY _mesa_IsQuery(GLuint id) { struct gl_query_object *q; GET_CURRENT_CONTEXT(ctx); ASSERT_OUTSIDE_BEGIN_END_WITH_RETVAL(ctx, GL_FALSE); if (MESA_VERBOSE & VERBOSE_API) _mesa_debug(ctx, "glIsQuery(%u)\n", id); if (id == 0) return GL_FALSE; q = _mesa_lookup_query_object(ctx, id); if (q == NULL) return GL_FALSE; return q->EverBound; } static GLboolean query_error_check_index(struct gl_context *ctx, GLenum target, GLuint index) { switch (target) { case GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN: case GL_PRIMITIVES_GENERATED: case GL_TRANSFORM_FEEDBACK_STREAM_OVERFLOW: if (index >= ctx->Const.MaxVertexStreams) { _mesa_error(ctx, GL_INVALID_VALUE, "glBeginQueryIndexed(index>=MaxVertexStreams)"); return GL_FALSE; } break; default: if (index > 0) { _mesa_error(ctx, GL_INVALID_VALUE, "glBeginQueryIndexed(index>0)"); return GL_FALSE; } } return GL_TRUE; } void GLAPIENTRY _mesa_BeginQueryIndexed(GLenum target, GLuint index, GLuint id) { struct gl_query_object *q, **bindpt; GET_CURRENT_CONTEXT(ctx); if (MESA_VERBOSE & VERBOSE_API) _mesa_debug(ctx, "glBeginQueryIndexed(%s, %u, %u)\n", _mesa_enum_to_string(target), index, id); if (!query_error_check_index(ctx, target, index)) return; FLUSH_VERTICES(ctx, 0); bindpt = get_query_binding_point(ctx, target, index); if (!bindpt) { _mesa_error(ctx, GL_INVALID_ENUM, "glBeginQuery{Indexed}(target)"); return; } /* From the GL_ARB_occlusion_query spec: * * "If BeginQueryARB is called while another query is already in * progress with the same target, an INVALID_OPERATION error is * generated." */ if (*bindpt) { _mesa_error(ctx, GL_INVALID_OPERATION, "glBeginQuery{Indexed}(target=%s is active)", _mesa_enum_to_string(target)); return; } if (id == 0) { _mesa_error(ctx, GL_INVALID_OPERATION, "glBeginQuery{Indexed}(id==0)"); return; } q = _mesa_lookup_query_object(ctx, id); if (!q) { if (ctx->API != API_OPENGL_COMPAT) { _mesa_error(ctx, GL_INVALID_OPERATION, "glBeginQuery{Indexed}(non-gen name)"); return; } else { /* create new object */ q = ctx->Driver.NewQueryObject(ctx, id); if (!q) { _mesa_error(ctx, GL_OUT_OF_MEMORY, "glBeginQuery{Indexed}"); return; } _mesa_HashInsertLocked(ctx->Query.QueryObjects, id, q, false); } } else { /* pre-existing object */ if (q->Active) { _mesa_error(ctx, GL_INVALID_OPERATION, "glBeginQuery{Indexed}(query already active)"); return; } /* Section 2.14 Asynchronous Queries, page 84 of the OpenGL ES 3.0.4 * spec states: * * "BeginQuery generates an INVALID_OPERATION error if any of the * following conditions hold: [...] id is the name of an * existing query object whose type does not match target; [...] * * Similar wording exists in the OpenGL 4.5 spec, section 4.2. QUERY * OBJECTS AND ASYNCHRONOUS QUERIES, page 43. */ if (q->EverBound && q->Target != target) { _mesa_error(ctx, GL_INVALID_OPERATION, "glBeginQuery{Indexed}(target mismatch)"); return; } } /* This possibly changes the target of a buffer allocated by * CreateQueries. Issue 39) in the ARB_direct_state_access extension states * the following: * * "CreateQueries adds a , so strictly speaking the * command isn't needed for BeginQuery/EndQuery, but in the end, this also * isn't a selector, so we decided not to change it." * * Updating the target of the query object should be acceptable, so let's * do that. */ q->Target = target; q->Active = GL_TRUE; q->Result = 0; q->Ready = GL_FALSE; q->EverBound = GL_TRUE; q->Stream = index; /* XXX should probably refcount query objects */ *bindpt = q; ctx->Driver.BeginQuery(ctx, q); } void GLAPIENTRY _mesa_EndQueryIndexed(GLenum target, GLuint index) { struct gl_query_object *q, **bindpt; GET_CURRENT_CONTEXT(ctx); if (MESA_VERBOSE & VERBOSE_API) _mesa_debug(ctx, "glEndQueryIndexed(%s, %u)\n", _mesa_enum_to_string(target), index); if (!query_error_check_index(ctx, target, index)) return; FLUSH_VERTICES(ctx, 0); bindpt = get_query_binding_point(ctx, target, index); if (!bindpt) { _mesa_error(ctx, GL_INVALID_ENUM, "glEndQuery{Indexed}(target)"); return; } /* XXX should probably refcount query objects */ q = *bindpt; /* Check for GL_ANY_SAMPLES_PASSED vs GL_SAMPLES_PASSED. */ if (q && q->Target != target) { _mesa_error(ctx, GL_INVALID_OPERATION, "glEndQuery(target=%s with active query of target %s)", _mesa_enum_to_string(target), _mesa_enum_to_string(q->Target)); return; } *bindpt = NULL; if (!q || !q->Active) { _mesa_error(ctx, GL_INVALID_OPERATION, "glEndQuery{Indexed}(no matching glBeginQuery{Indexed})"); return; } q->Active = GL_FALSE; ctx->Driver.EndQuery(ctx, q); } void GLAPIENTRY _mesa_BeginQuery(GLenum target, GLuint id) { _mesa_BeginQueryIndexed(target, 0, id); } void GLAPIENTRY _mesa_EndQuery(GLenum target) { _mesa_EndQueryIndexed(target, 0); } void GLAPIENTRY _mesa_QueryCounter(GLuint id, GLenum target) { struct gl_query_object *q; GET_CURRENT_CONTEXT(ctx); if (MESA_VERBOSE & VERBOSE_API) _mesa_debug(ctx, "glQueryCounter(%u, %s)\n", id, _mesa_enum_to_string(target)); /* error checking */ if (target != GL_TIMESTAMP) { _mesa_error(ctx, GL_INVALID_ENUM, "glQueryCounter(target)"); return; } if (id == 0) { _mesa_error(ctx, GL_INVALID_OPERATION, "glQueryCounter(id==0)"); return; } q = _mesa_lookup_query_object(ctx, id); if (!q) { /* XXX the Core profile should throw INVALID_OPERATION here */ /* create new object */ q = ctx->Driver.NewQueryObject(ctx, id); if (!q) { _mesa_error(ctx, GL_OUT_OF_MEMORY, "glQueryCounter"); return; } _mesa_HashInsertLocked(ctx->Query.QueryObjects, id, q, false); } else { if (q->Target && q->Target != GL_TIMESTAMP) { _mesa_error(ctx, GL_INVALID_OPERATION, "glQueryCounter(id has an invalid target)"); return; } } if (q->Active) { _mesa_error(ctx, GL_INVALID_OPERATION, "glQueryCounter(id is active)"); return; } /* This possibly changes the target of a buffer allocated by * CreateQueries. Issue 39) in the ARB_direct_state_access extension states * the following: * * "CreateQueries adds a , so strictly speaking the * command isn't needed for BeginQuery/EndQuery, but in the end, this also * isn't a selector, so we decided not to change it." * * Updating the target of the query object should be acceptable, so let's * do that. */ q->Target = target; q->Result = 0; q->Ready = GL_FALSE; q->EverBound = GL_TRUE; if (ctx->Driver.QueryCounter) { ctx->Driver.QueryCounter(ctx, q); } else { /* QueryCounter is implemented using EndQuery without BeginQuery * in drivers. This is actually Direct3D and Gallium convention. */ ctx->Driver.EndQuery(ctx, q); } } void GLAPIENTRY _mesa_GetQueryIndexediv(GLenum target, GLuint index, GLenum pname, GLint *params) { struct gl_query_object *q = NULL, **bindpt = NULL; GET_CURRENT_CONTEXT(ctx); if (MESA_VERBOSE & VERBOSE_API) _mesa_debug(ctx, "glGetQueryIndexediv(%s, %u, %s)\n", _mesa_enum_to_string(target), index, _mesa_enum_to_string(pname)); if (!query_error_check_index(ctx, target, index)) return; /* From the GL_EXT_occlusion_query_boolean spec: * * "The error INVALID_ENUM is generated if GetQueryivEXT is called where * is not CURRENT_QUERY_EXT." * * Same rule is present also in ES 3.2 spec. * * EXT_disjoint_timer_query extends this with GL_QUERY_COUNTER_BITS. */ if (_mesa_is_gles(ctx)) { switch (pname) { case GL_CURRENT_QUERY: break; case GL_QUERY_COUNTER_BITS: if (_mesa_has_EXT_disjoint_timer_query(ctx)) break; /* fallthrough */ default: _mesa_error(ctx, GL_INVALID_ENUM, "glGetQueryivEXT(%s)", _mesa_enum_to_string(pname)); } } if (target == GL_TIMESTAMP) { if (!_mesa_has_ARB_timer_query(ctx) && !_mesa_has_EXT_disjoint_timer_query(ctx)) { _mesa_error(ctx, GL_INVALID_ENUM, "glGetQueryARB(target)"); return; } } else { bindpt = get_query_binding_point(ctx, target, index); if (!bindpt) { _mesa_error(ctx, GL_INVALID_ENUM, "glGetQuery{Indexed}iv(target)"); return; } q = *bindpt; } switch (pname) { case GL_QUERY_COUNTER_BITS: switch (target) { case GL_SAMPLES_PASSED: *params = ctx->Const.QueryCounterBits.SamplesPassed; break; case GL_ANY_SAMPLES_PASSED: case GL_ANY_SAMPLES_PASSED_CONSERVATIVE: /* The minimum value of this is 1 if it's nonzero, and the value * is only ever GL_TRUE or GL_FALSE, so no sense in reporting more * bits. */ *params = 1; break; case GL_TIME_ELAPSED: *params = ctx->Const.QueryCounterBits.TimeElapsed; break; case GL_TIMESTAMP: *params = ctx->Const.QueryCounterBits.Timestamp; break; case GL_PRIMITIVES_GENERATED: *params = ctx->Const.QueryCounterBits.PrimitivesGenerated; break; case GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN: *params = ctx->Const.QueryCounterBits.PrimitivesWritten; break; case GL_TRANSFORM_FEEDBACK_STREAM_OVERFLOW: case GL_TRANSFORM_FEEDBACK_OVERFLOW: /* The minimum value of this is 1 if it's nonzero, and the value * is only ever GL_TRUE or GL_FALSE, so no sense in reporting more * bits. */ *params = 1; break; case GL_VERTICES_SUBMITTED: *params = ctx->Const.QueryCounterBits.VerticesSubmitted; break; case GL_PRIMITIVES_SUBMITTED: *params = ctx->Const.QueryCounterBits.PrimitivesSubmitted; break; case GL_VERTEX_SHADER_INVOCATIONS: *params = ctx->Const.QueryCounterBits.VsInvocations; break; case GL_TESS_CONTROL_SHADER_PATCHES: *params = ctx->Const.QueryCounterBits.TessPatches; break; case GL_TESS_EVALUATION_SHADER_INVOCATIONS: *params = ctx->Const.QueryCounterBits.TessInvocations; break; case GL_GEOMETRY_SHADER_INVOCATIONS: *params = ctx->Const.QueryCounterBits.GsInvocations; break; case GL_GEOMETRY_SHADER_PRIMITIVES_EMITTED: *params = ctx->Const.QueryCounterBits.GsPrimitives; break; case GL_FRAGMENT_SHADER_INVOCATIONS: *params = ctx->Const.QueryCounterBits.FsInvocations; break; case GL_COMPUTE_SHADER_INVOCATIONS: *params = ctx->Const.QueryCounterBits.ComputeInvocations; break; case GL_CLIPPING_INPUT_PRIMITIVES: *params = ctx->Const.QueryCounterBits.ClInPrimitives; break; case GL_CLIPPING_OUTPUT_PRIMITIVES: *params = ctx->Const.QueryCounterBits.ClOutPrimitives; break; default: _mesa_problem(ctx, "Unknown target in glGetQueryIndexediv(target = %s)", _mesa_enum_to_string(target)); *params = 0; break; } break; case GL_CURRENT_QUERY: *params = (q && q->Target == target) ? q->Id : 0; break; default: _mesa_error(ctx, GL_INVALID_ENUM, "glGetQuery{Indexed}iv(pname)"); return; } } void GLAPIENTRY _mesa_GetQueryiv(GLenum target, GLenum pname, GLint *params) { _mesa_GetQueryIndexediv(target, 0, pname, params); } static void get_query_object(struct gl_context *ctx, const char *func, GLuint id, GLenum pname, GLenum ptype, struct gl_buffer_object *buf, intptr_t offset) { struct gl_query_object *q = NULL; uint64_t value; if (MESA_VERBOSE & VERBOSE_API) _mesa_debug(ctx, "%s(%u, %s)\n", func, id, _mesa_enum_to_string(pname)); if (id) q = _mesa_lookup_query_object(ctx, id); if (!q || q->Active || !q->EverBound) { _mesa_error(ctx, GL_INVALID_OPERATION, "%s(id=%d is invalid or active)", func, id); return; } /* From GL_EXT_occlusion_query_boolean spec: * * "Accepted by the parameter of GetQueryObjectivEXT and * GetQueryObjectuivEXT: * * QUERY_RESULT_EXT 0x8866 * QUERY_RESULT_AVAILABLE_EXT 0x8867" * * Same rule is present also in ES 3.2 spec. */ if (_mesa_is_gles(ctx) && (pname != GL_QUERY_RESULT && pname != GL_QUERY_RESULT_AVAILABLE)) { _mesa_error(ctx, GL_INVALID_ENUM, "%s(%s)", func, _mesa_enum_to_string(pname)); return; } if (buf) { bool is_64bit = ptype == GL_INT64_ARB || ptype == GL_UNSIGNED_INT64_ARB; if (!_mesa_has_ARB_query_buffer_object(ctx)) { _mesa_error(ctx, GL_INVALID_OPERATION, "%s(not supported)", func); return; } if (buf->Size < offset + 4 * (is_64bit ? 2 : 1)) { _mesa_error(ctx, GL_INVALID_OPERATION, "%s(out of bounds)", func); return; } if (offset < 0) { _mesa_error(ctx, GL_INVALID_VALUE, "%s(offset is negative)", func); return; } switch (pname) { case GL_QUERY_RESULT: case GL_QUERY_RESULT_NO_WAIT: case GL_QUERY_RESULT_AVAILABLE: case GL_QUERY_TARGET: ctx->Driver.StoreQueryResult(ctx, q, buf, offset, pname, ptype); return; } /* fall through to get error below */ } switch (pname) { case GL_QUERY_RESULT: if (!q->Ready) ctx->Driver.WaitQuery(ctx, q); value = q->Result; break; case GL_QUERY_RESULT_NO_WAIT: if (!_mesa_has_ARB_query_buffer_object(ctx)) goto invalid_enum; ctx->Driver.CheckQuery(ctx, q); if (!q->Ready) return; value = q->Result; break; case GL_QUERY_RESULT_AVAILABLE: if (!q->Ready) ctx->Driver.CheckQuery(ctx, q); value = q->Ready; break; case GL_QUERY_TARGET: value = q->Target; break; default: invalid_enum: _mesa_error(ctx, GL_INVALID_ENUM, "%s(pname=%s)", func, _mesa_enum_to_string(pname)); return; } switch (ptype) { case GL_INT: { GLint *param = (GLint *)offset; if (value > 0x7fffffff) *param = 0x7fffffff; else *param = value; break; } case GL_UNSIGNED_INT: { GLuint *param = (GLuint *)offset; if (value > 0xffffffff) *param = 0xffffffff; else *param = value; break; } case GL_INT64_ARB: case GL_UNSIGNED_INT64_ARB: { GLuint64EXT *param = (GLuint64EXT *)offset; *param = value; break; } default: unreachable("unexpected ptype"); } } void GLAPIENTRY _mesa_GetQueryObjectiv(GLuint id, GLenum pname, GLint *params) { GET_CURRENT_CONTEXT(ctx); get_query_object(ctx, "glGetQueryObjectiv", id, pname, GL_INT, ctx->QueryBuffer, (intptr_t)params); } void GLAPIENTRY _mesa_GetQueryObjectuiv(GLuint id, GLenum pname, GLuint *params) { GET_CURRENT_CONTEXT(ctx); get_query_object(ctx, "glGetQueryObjectuiv", id, pname, GL_UNSIGNED_INT, ctx->QueryBuffer, (intptr_t)params); } /** * New with GL_EXT_timer_query */ void GLAPIENTRY _mesa_GetQueryObjecti64v(GLuint id, GLenum pname, GLint64EXT *params) { GET_CURRENT_CONTEXT(ctx); get_query_object(ctx, "glGetQueryObjecti64v", id, pname, GL_INT64_ARB, ctx->QueryBuffer, (intptr_t)params); } /** * New with GL_EXT_timer_query */ void GLAPIENTRY _mesa_GetQueryObjectui64v(GLuint id, GLenum pname, GLuint64EXT *params) { GET_CURRENT_CONTEXT(ctx); get_query_object(ctx, "glGetQueryObjectui64v", id, pname, GL_UNSIGNED_INT64_ARB, ctx->QueryBuffer, (intptr_t)params); } /** * New with GL_ARB_query_buffer_object */ void GLAPIENTRY _mesa_GetQueryBufferObjectiv(GLuint id, GLuint buffer, GLenum pname, GLintptr offset) { struct gl_buffer_object *buf; GET_CURRENT_CONTEXT(ctx); buf = _mesa_lookup_bufferobj_err(ctx, buffer, "glGetQueryBufferObjectiv"); if (!buf) return; get_query_object(ctx, "glGetQueryBufferObjectiv", id, pname, GL_INT, buf, offset); } void GLAPIENTRY _mesa_GetQueryBufferObjectuiv(GLuint id, GLuint buffer, GLenum pname, GLintptr offset) { struct gl_buffer_object *buf; GET_CURRENT_CONTEXT(ctx); buf = _mesa_lookup_bufferobj_err(ctx, buffer, "glGetQueryBufferObjectuiv"); if (!buf) return; get_query_object(ctx, "glGetQueryBufferObjectuiv", id, pname, GL_UNSIGNED_INT, buf, offset); } void GLAPIENTRY _mesa_GetQueryBufferObjecti64v(GLuint id, GLuint buffer, GLenum pname, GLintptr offset) { struct gl_buffer_object *buf; GET_CURRENT_CONTEXT(ctx); buf = _mesa_lookup_bufferobj_err(ctx, buffer, "glGetQueryBufferObjecti64v"); if (!buf) return; get_query_object(ctx, "glGetQueryBufferObjecti64v", id, pname, GL_INT64_ARB, buf, offset); } void GLAPIENTRY _mesa_GetQueryBufferObjectui64v(GLuint id, GLuint buffer, GLenum pname, GLintptr offset) { struct gl_buffer_object *buf; GET_CURRENT_CONTEXT(ctx); buf = _mesa_lookup_bufferobj_err(ctx, buffer, "glGetQueryBufferObjectui64v"); if (!buf) return; get_query_object(ctx, "glGetQueryBufferObjectui64v", id, pname, GL_UNSIGNED_INT64_ARB, buf, offset); } /** * Allocate/init the context state related to query objects. */ void _mesa_init_queryobj(struct gl_context *ctx) { ctx->Query.QueryObjects = _mesa_NewHashTable(); ctx->Query.CurrentOcclusionObject = NULL; ctx->Const.QueryCounterBits.SamplesPassed = 64; ctx->Const.QueryCounterBits.TimeElapsed = 64; ctx->Const.QueryCounterBits.Timestamp = 64; ctx->Const.QueryCounterBits.PrimitivesGenerated = 64; ctx->Const.QueryCounterBits.PrimitivesWritten = 64; ctx->Const.QueryCounterBits.VerticesSubmitted = 64; ctx->Const.QueryCounterBits.PrimitivesSubmitted = 64; ctx->Const.QueryCounterBits.VsInvocations = 64; ctx->Const.QueryCounterBits.TessPatches = 64; ctx->Const.QueryCounterBits.TessInvocations = 64; ctx->Const.QueryCounterBits.GsInvocations = 64; ctx->Const.QueryCounterBits.GsPrimitives = 64; ctx->Const.QueryCounterBits.FsInvocations = 64; ctx->Const.QueryCounterBits.ComputeInvocations = 64; ctx->Const.QueryCounterBits.ClInPrimitives = 64; ctx->Const.QueryCounterBits.ClOutPrimitives = 64; } /** * Callback for deleting a query object. Called by _mesa_HashDeleteAll(). */ static void delete_queryobj_cb(void *data, void *userData) { struct gl_query_object *q= (struct gl_query_object *) data; struct gl_context *ctx = (struct gl_context *)userData; ctx->Driver.DeleteQuery(ctx, q); } /** * Free the context state related to query objects. */ void _mesa_free_queryobj_data(struct gl_context *ctx) { _mesa_HashDeleteAll(ctx->Query.QueryObjects, delete_queryobj_cb, ctx); _mesa_DeleteHashTable(ctx->Query.QueryObjects); }