| Name | |
| ANGLE_timer_query | |
| Name Strings | |
| GL_ANGLE_timer_query | |
| Contributors | |
| Contributors to ARB_occlusion_query | |
| Contributors to EXT_timer_query | |
| Contributors to ARB_timer_query | |
| Ben Vanik, Google Inc. | |
| Daniel Koch, TransGaming Inc. | |
| Contact | |
| Ben Vanik, Google Inc. (benvanik 'at' google 'dot' com) | |
| Status | |
| Draft | |
| Version | |
| Last Modified Date: Apr 28, 2011 | |
| Author Revision: 1 | |
| Number | |
| OpenGL ES Extension #?? | |
| Dependencies | |
| OpenGL ES 2.0 is required. | |
| The extension is written against the OpenGL ES 2.0 specification. | |
| Overview | |
| Applications can benefit from accurate timing information in a number of | |
| different ways. During application development, timing information can | |
| help identify application or driver bottlenecks. At run time, | |
| applications can use timing information to dynamically adjust the amount | |
| of detail in a scene to achieve constant frame rates. OpenGL | |
| implementations have historically provided little to no useful timing | |
| information. Applications can get some idea of timing by reading timers | |
| on the CPU, but these timers are not synchronized with the graphics | |
| rendering pipeline. Reading a CPU timer does not guarantee the completion | |
| of a potentially large amount of graphics work accumulated before the | |
| timer is read, and will thus produce wildly inaccurate results. | |
| glFinish() can be used to determine when previous rendering commands have | |
| been completed, but will idle the graphics pipeline and adversely affect | |
| application performance. | |
| This extension provides a query mechanism that can be used to determine | |
| the amount of time it takes to fully complete a set of GL commands, and | |
| without stalling the rendering pipeline. It uses the query object | |
| mechanisms first introduced in the occlusion query extension, which allow | |
| time intervals to be polled asynchronously by the application. | |
| IP Status | |
| No known IP claims. | |
| New Procedures and Functions | |
| void GenQueriesANGLE(sizei n, uint *ids); | |
| void DeleteQueriesANGLE(sizei n, const uint *ids); | |
| boolean IsQueryANGLE(uint id); | |
| void BeginQueryANGLE(enum target, uint id); | |
| void EndQueryANGLE(enum target); | |
| void QueryCounterANGLE(uint id, enum target); | |
| void GetQueryivANGLE(enum target, enum pname, int *params); | |
| void GetQueryObjectivANGLE(uint id, enum pname, int *params); | |
| void GetQueryObjectuivANGLE(uint id, enum pname, uint *params); | |
| void GetQueryObjecti64vANGLE(uint id, enum pname, int64 *params); | |
| void GetQueryObjectui64vANGLE(uint id, enum pname, uint64 *params); | |
| New Tokens | |
| Accepted by the <pname> parameter of GetQueryivANGLE: | |
| QUERY_COUNTER_BITS_ANGLE 0x8864 | |
| CURRENT_QUERY_ANGLE 0x8865 | |
| Accepted by the <pname> parameter of GetQueryObjectivANGLE, | |
| GetQueryObjectuivANGLE, GetQueryObjecti64vANGLE, and | |
| GetQueryObjectui64vANGLE: | |
| QUERY_RESULT_ANGLE 0x8866 | |
| QUERY_RESULT_AVAILABLE_ANGLE 0x8867 | |
| Accepted by the <target> parameter of BeginQueryANGLE, EndQueryANGLE, and | |
| GetQueryivANGLE: | |
| TIME_ELAPSED_ANGLE 0x88BF | |
| Accepted by the <target> parameter of GetQueryivANGLE and | |
| QueryCounterANGLE: | |
| TIMESTAMP_ANGLE 0x8E28 | |
| Additions to Chapter 2 of the OpenGL ES 2.0 Specification (OpenGL ES Operation) | |
| (Modify table 2.1, Correspondence of command suffix letters to GL argument) | |
| Add two new types: | |
| Letter Corresponding GL Type | |
| ------ --------------------- | |
| i64 int64ANGLE | |
| ui64 uint64ANGLE | |
| (Modify table 2.2, GL data types) Add two new types: | |
| GL Type Minimum Bit Width Description | |
| ------- ----------------- ----------------------------- | |
| int64ANGLE 64 Signed 2's complement integer | |
| uint64ANGLE 64 Unsigned binary integer | |
| Additions to Chapter 5 of the OpenGL ES 2.0 Specification (Special Functions) | |
| Add a new section 5.3 "Timer Queries": | |
| "5.3 Timer Queries | |
| Timer queries use query objects to track the amount of time needed to | |
| fully complete a set of GL commands, or to determine the current time | |
| of the GL. | |
| Timer queries are associated with query objects. The command | |
| void GenQueriesANGLE(sizei n, uint *ids); | |
| returns <n> previously unused query object names in <ids>. These | |
| names are marked as used, but no object is associated with them until | |
| the first time they are used by BeginQueryANGLE. Query objects contain | |
| one piece of state, an integer result value. This result value is | |
| initialized to zero when the object is created. Any positive integer | |
| except for zero (which is reserved for the GL) is a valid query | |
| object name. | |
| Query objects are deleted by calling | |
| void DeleteQueriesANGLE(sizei n, const uint *ids); | |
| <ids> contains <n> names of query objects to be deleted. After a | |
| query object is deleted, its name is again unused. Unused names in | |
| <ids> are silently ignored. | |
| If an active query object is deleted its name immediately becomes unused, | |
| but the underlying object is not deleted until it is no longer active. | |
| A timer query can be started and finished by calling | |
| void BeginQueryANGLE(enum target, uint id); | |
| void EndQueryANGLE(enum target); | |
| where <target> is TIME_ELAPSED_ANGLE. If BeginQueryANGLE is called | |
| with an unused <id>, that name is marked as used and associated with | |
| a new query object. | |
| If BeginQueryANGLE is called with an <id> of zero, if the active query | |
| object name for <target> is non-zero, if <id> is the name of an existing | |
| query object whose type does not match <target>, or if <id> is the active | |
| query object name for any query type, the error INVALID_OPERATION is | |
| generated. If EndQueryANGLE is called while no query with the same target | |
| is in progress, an INVALID_OPERATION error is generated. | |
| When BeginQueryANGLE and EndQueryANGLE are called with a <target> of | |
| TIME_ELAPSED_ANGLE, the GL prepares to start and stop the timer used for | |
| timer queries. The timer is started or stopped when the effects from all | |
| previous commands on the GL client and server state and the framebuffer | |
| have been fully realized. The BeginQueryANGLE and EndQueryANGLE commands | |
| may return before the timer is actually started or stopped. When the timer | |
| query timer is finally stopped, the elapsed time (in nanoseconds) is | |
| written to the corresponding query object as the query result value, and | |
| the query result for that object is marked as available. | |
| If the elapsed time overflows the number of bits, <n>, available to hold | |
| elapsed time, its value becomes undefined. It is recommended, but not | |
| required, that implementations handle this overflow case by saturating at | |
| 2^n - 1. | |
| The necessary state is a single bit indicating whether an timer | |
| query is active, the identifier of the currently active timer | |
| query, and a counter keeping track of the time that has passed. | |
| When the command | |
| void QueryCounterANGLE(uint id, enum target); | |
| is called with <target> TIMESTAMP_ANGLE, the GL records the current time | |
| into the corresponding query object. The time is recorded after all | |
| previous commands on the GL client and server state and the framebuffer | |
| have been fully realized. When the time is recorded, the query result for | |
| that object is marked available. QueryCounterANGLE timer queries can be | |
| used within a BeginQueryANGLE / EndQueryANGLE block where the <target> is | |
| TIME_ELAPSED_ANGLE and it does not affect the result of that query object. | |
| The error INVALID_OPERATION is generated if the <id> is already in use | |
| within a BeginQueryANGLE/EndQueryANGLE block." | |
| Additions to Chapter 6 of the OpenGL ES 2.0 Specification (State and State | |
| Requests) | |
| Add a new section 6.1.9 "Timer Queries": | |
| "The command | |
| boolean IsQueryANGLE(uint id); | |
| returns TRUE if <id> is the name of a query object. If <id> is zero, | |
| or if <id> is a non-zero value that is not the name of a query | |
| object, IsQueryANGLE returns FALSE. | |
| Information about a query target can be queried with the command | |
| void GetQueryivANGLE(enum target, enum pname, int *params); | |
| <target> identifies the query target and can be TIME_ELAPSED_ANGLE or | |
| TIMESTAMP_ANGLE for timer queries. | |
| If <pname> is CURRENT_QUERY_ANGLE, the name of the currently active query | |
| for <target>, or zero if no query is active, will be placed in <params>. | |
| If <pname> is QUERY_COUNTER_BITS_ANGLE, the implementation-dependent number | |
| of bits used to hold the query result for <target> will be placed in | |
| <params>. The number of query counter bits may be zero, in which case | |
| the counter contains no useful information. | |
| For timer queries (TIME_ELAPSED_ANGLE and TIMESTAMP_ANGLE), if the number | |
| of bits is non-zero, the minimum number of bits allowed is 30 which | |
| will allow at least 1 second of timing. | |
| The state of a query object can be queried with the commands | |
| void GetQueryObjectivANGLE(uint id, enum pname, int *params); | |
| void GetQueryObjectuivANGLE(uint id, enum pname, uint *params); | |
| void GetQueryObjecti64vANGLE(uint id, enum pname, int64 *params); | |
| void GetQueryObjectui64vANGLE(uint id, enum pname, uint64 *params); | |
| If <id> is not the name of a query object, or if the query object | |
| named by <id> is currently active, then an INVALID_OPERATION error is | |
| generated. | |
| If <pname> is QUERY_RESULT_ANGLE, then the query object's result | |
| value is returned as a single integer in <params>. If the value is so | |
| large in magnitude that it cannot be represented with the requested type, | |
| then the nearest value representable using the requested type is | |
| returned. If the number of query counter bits for target is zero, then | |
| the result is returned as a single integer with the value zero. | |
| There may be an indeterminate delay before the above query returns. If | |
| <pname> is QUERY_RESULT_AVAILABLE_ANGLE, FALSE is returned if such a delay | |
| would be required; otherwise TRUE is returned. It must always be true | |
| that if any query object returns a result available of TRUE, all queries | |
| of the same type issued prior to that query must also return TRUE. | |
| Querying the state for a given timer query forces that timer query to | |
| complete within a finite amount of time. | |
| If multiple queries are issued on the same target and id prior to | |
| calling GetQueryObject[u]i[64]vANGLE, the result returned will always be | |
| from the last query issued. The results from any queries before the | |
| last one will be lost if the results are not retrieved before starting | |
| a new query on the same <target> and <id>." | |
| Errors | |
| The error INVALID_VALUE is generated if GenQueriesANGLE is called where | |
| <n> is negative. | |
| The error INVALID_VALUE is generated if DeleteQueriesANGLE is called | |
| where <n> is negative. | |
| The error INVALID_OPERATION is generated if BeginQueryANGLE is called | |
| when a query of the given <target> is already active. | |
| The error INVALID_OPERATION is generated if EndQueryANGLE is called | |
| when a query of the given <target> is not active. | |
| The error INVALID_OPERATION is generated if BeginQueryANGLE is called | |
| where <id> is zero. | |
| The error INVALID_OPERATION is generated if BeginQueryANGLE is called | |
| where <id> is the name of a query currently in progress. | |
| The error INVALID_OPERATION is generated if BeginQueryANGLE is called | |
| where <id> is the name of an existing query object whose type does not | |
| match <target>. | |
| The error INVALID_ENUM is generated if BeginQueryANGLE or EndQueryANGLE | |
| is called where <target> is not TIME_ELAPSED_ANGLE. | |
| The error INVALID_ENUM is generated if GetQueryivANGLE is called where | |
| <target> is not TIME_ELAPSED_ANGLE or TIMESTAMP_ANGLE. | |
| The error INVALID_ENUM is generated if GetQueryivANGLE is called where | |
| <pname> is not QUERY_COUNTER_BITS_ANGLE or CURRENT_QUERY_ANGLE. | |
| The error INVALID_ENUM is generated if QueryCounterANGLE is called where | |
| <target> is not TIMESTAMP_ANGLE. | |
| The error INVALID_OPERATION is generated if QueryCounterANGLE is called | |
| on a query object that is already in use inside a | |
| BeginQueryANGLE/EndQueryANGLE. | |
| The error INVALID_OPERATION is generated if GetQueryObjectivANGLE, | |
| GetQueryObjectuivANGLE, GetQueryObjecti64vANGLE, or | |
| GetQueryObjectui64vANGLE is called where <id> is not the name of a query | |
| object. | |
| The error INVALID_OPERATION is generated if GetQueryObjectivANGLE, | |
| GetQueryObjectuivANGLE, GetQueryObjecti64vANGLE, or | |
| GetQueryObjectui64vANGLE is called where <id> is the name of a currently | |
| active query object. | |
| The error INVALID_ENUM is generated if GetQueryObjectivANGLE, | |
| GetQueryObjectuivANGLE, GetQueryObjecti64vANGLE, or | |
| GetQueryObjectui64vANGLE is called where <pname> is not | |
| QUERY_RESULT_ANGLE or QUERY_RESULT_AVAILABLE_ANGLE. | |
| New State | |
| (Add a new table 6.xx, "Query Operations") | |
| Get Value Type Get Command Initial Value Description Sec | |
| --------- ---- ----------- ------------- ----------- ------ | |
| - B - FALSE query active 5.3 | |
| CURRENT_QUERY_ANGLE Z+ GetQueryivANGLE 0 active query ID 5.3 | |
| QUERY_RESULT_ANGLE Z+ GetQueryObjectuivANGLE, 0 samples-passed count 5.3 | |
| GetQueryObjectui64vANGLE | |
| QUERY_RESULT_AVAILABLE_ANGLE B GetQueryObjectivANGLE FALSE query result available 5.3 | |
| New Implementation Dependent State | |
| (Add the following entry to table 6.18): | |
| Get Value Type Get Command Minimum Value Description Sec | |
| -------------------------- ---- ----------- ------------- ---------------- ------ | |
| QUERY_COUNTER_BITS_ANGLE Z+ GetQueryivANGLE see 6.1.9 Number of bits in 6.1.9 | |
| query counter | |
| Examples | |
| (1) Here is some rough sample code that demonstrates the intended usage | |
| of this extension. | |
| GLint queries[N]; | |
| GLint available = 0; | |
| // timer queries can contain more than 32 bits of data, so always | |
| // query them using the 64 bit types to avoid overflow | |
| GLuint64ANGLE timeElapsed = 0; | |
| // Create a query object. | |
| glGenQueriesANGLE(N, queries); | |
| // Start query 1 | |
| glBeginQueryANGLE(GL_TIME_ELAPSED_ANGLE, queries[0]); | |
| // Draw object 1 | |
| .... | |
| // End query 1 | |
| glEndQueryANGLE(GL_TIME_ELAPSED_ANGLE); | |
| ... | |
| // Start query N | |
| glBeginQueryANGLE(GL_TIME_ELAPSED_ANGLE, queries[N-1]); | |
| // Draw object N | |
| .... | |
| // End query N | |
| glEndQueryANGLE(GL_TIME_ELAPSED_ANGLE); | |
| // Wait for all results to become available | |
| while (!available) { | |
| glGetQueryObjectivANGLE(queries[N-1], GL_QUERY_RESULT_AVAILABLE_ANGLE, &available); | |
| } | |
| for (i = 0; i < N; i++) { | |
| // See how much time the rendering of object i took in nanoseconds. | |
| glGetQueryObjectui64vANGLE(queries[i], GL_QUERY_RESULT_ANGLE, &timeElapsed); | |
| // Do something useful with the time. Note that care should be | |
| // taken to use all significant bits of the result, not just the | |
| // least significant 32 bits. | |
| AdjustObjectLODBasedOnDrawTime(i, timeElapsed); | |
| } | |
| This example is sub-optimal in that it stalls at the end of every | |
| frame to wait for query results. Ideally, the collection of results | |
| would be delayed one frame to minimize the amount of time spent | |
| waiting for the GPU to finish rendering. | |
| (2) This example is basically the same as the example above but uses | |
| QueryCounter instead. | |
| GLint queries[N+1]; | |
| GLint available = 0; | |
| // timer queries can contain more than 32 bits of data, so always | |
| // query them using the 64 bit types to avoid overflow | |
| GLuint64ANGLE timeStart, timeEnd, timeElapsed = 0; | |
| // Create a query object. | |
| glGenQueriesANGLE(N+1, queries); | |
| // Query current timestamp 1 | |
| glQueryCounterANGLE(queries[0], GL_TIMESTAMP_ANGLE); | |
| // Draw object 1 | |
| .... | |
| // Query current timestamp N | |
| glQueryCounterANGLE(queries[N-1], GL_TIMESTAMP_ANGLE); | |
| // Draw object N | |
| .... | |
| // Query current timestamp N+1 | |
| glQueryCounterANGLE(queries[N], GL_TIMESTAMP_ANGLE); | |
| // Wait for all results to become available | |
| while (!available) { | |
| glGetQueryObjectivANGLE(queries[N], GL_QUERY_RESULT_AVAILABLE_ANGLE, &available); | |
| } | |
| for (i = 0; i < N; i++) { | |
| // See how much time the rendering of object i took in nanoseconds. | |
| glGetQueryObjectui64vANGLE(queries[i], GL_QUERY_RESULT_ANGLE, &timeStart); | |
| glGetQueryObjectui64vANGLE(queries[i+1], GL_QUERY_RESULT_ANGLE, &timeEnd); | |
| timeElapsed = timeEnd - timeStart; | |
| // Do something useful with the time. Note that care should be | |
| // taken to use all significant bits of the result, not just the | |
| // least significant 32 bits. | |
| AdjustObjectLODBasedOnDrawTime(i, timeElapsed); | |
| } | |
| Issues from EXT_timer_query | |
| (1) What time interval is being measured? | |
| RESOLVED: The timer starts when all commands prior to BeginQuery() have | |
| been fully executed. At that point, everything that should be drawn by | |
| those commands has been written to the framebuffer. The timer stops | |
| when all commands prior to EndQuery() have been fully executed. | |
| (2) What unit of time will time intervals be returned in? | |
| RESOLVED: Nanoseconds (10^-9 seconds). This unit of measurement allows | |
| for reasonably accurate timing of even small blocks of rendering | |
| commands. The granularity of the timer is implementation-dependent. A | |
| 32-bit query counter can express intervals of up to approximately 4 | |
| seconds. | |
| (3) What should be the minimum number of counter bits for timer queries? | |
| RESOLVED: 30 bits, which will allow timing sections that take up to 1 | |
| second to render. | |
| (4) How are counter results of more than 32 bits returned? | |
| RESOLVED: Via two new datatypes, int64ANGLE and uint64ANGLE, and their | |
| corresponding GetQueryObject entry points. These types hold integer | |
| values and have a minimum bit width of 64. | |
| (5) Should the extension measure total time elapsed between the full | |
| completion of the BeginQuery and EndQuery commands, or just time | |
| spent in the graphics library? | |
| RESOLVED: This extension will measure the total time elapsed between | |
| the full completion of these commands. Future extensions may implement | |
| a query to determine time elapsed at different stages of the graphics | |
| pipeline. | |
| (6) If multiple query types are supported, can multiple query types be | |
| active simultaneously? | |
| RESOLVED: Yes; an application may perform a timer query and another | |
| type of query simultaneously. An application can not perform multiple | |
| timer queries or multiple queries of other types simultaneously. An | |
| application also can not use the same query object for another query | |
| and a timer query simultaneously. | |
| (7) Do query objects have a query type permanently associated with them? | |
| RESOLVED: No. A single query object can be used to perform different | |
| types of queries, but not at the same time. | |
| Having a fixed type for each query object simplifies some aspects of the | |
| implementation -- not having to deal with queries with different result | |
| sizes, for example. It would also mean that BeginQuery() with a query | |
| object of the "wrong" type would result in an INVALID_OPERATION error. | |
| UPDATE: This resolution was relevant for EXT_timer_query and OpenGL 2.0. | |
| Since EXT_transform_feedback has since been incorporated into the core, | |
| the resolution is that BeginQuery will generate error INVALID_OPERATION | |
| if <id> represents a query object of a different type. | |
| (8) How predictable/repeatable are the results returned by the timer | |
| query? | |
| RESOLVED: In general, the amount of time needed to render the same | |
| primitives should be fairly constant. But there may be many other | |
| system issues (e.g., context switching on the CPU and GPU, virtual | |
| memory page faults, memory cache behavior on the CPU and GPU) that can | |
| cause times to vary wildly. | |
| Note that modern GPUs are generally highly pipelined, and may be | |
| processing different primitives in different pipeline stages | |
| simultaneously. In this extension, the timers start and stop when the | |
| BeginQuery/EndQuery commands reach the bottom of the rendering pipeline. | |
| What that means is that by the time the timer starts, the GL driver on | |
| the CPU may have started work on GL commands issued after BeginQuery, | |
| and the higher pipeline stages (e.g., vertex transformation) may have | |
| started as well. | |
| (9) What should the new 64 bit integer type be called? | |
| RESOLVED: The new types will be called GLint64ANGLE/GLuint64ANGLE. The new | |
| command suffixes will be i64 and ui64. These names clearly convey the | |
| minimum size of the types. These types are similar to the C99 standard | |
| type int_least64_t, but we use names similar to the C99 optional type | |
| int64_t for simplicity. | |
| Issues from ARB_timer_query | |
| (10) What about tile-based implementations? The effects of a command are | |
| not complete until the frame is completely rendered. Timing recorded | |
| before the frame is complete may not be what developers expect. Also | |
| the amount of time needed to render the same primitives is not | |
| consistent, which conflicts with issue (8) above. The time depends on | |
| how early or late in the scene it is placed. | |
| RESOLVED: The current language supports tile-based rendering okay as it | |
| is written. Developers are warned that using timers on tile-based | |
| implementation may not produce results they expect since rendering is not | |
| done in a linear order. Timing results are calculated when the frame is | |
| completed and may depend on how early or late in the scene it is placed. | |
| (11) Can the GL implementation use different clocks to implement the | |
| TIME_ELAPSED and TIMESTAMP queries? | |
| RESOLVED: Yes, the implemenation can use different internal clocks to | |
| implement TIME_ELAPSED and TIMESTAMP. If different clocks are | |
| used it is possible there is a slight discrepancy when comparing queries | |
| made from TIME_ELAPSED and TIMESTAMP; they may have slight | |
| differences when both are used to measure the same sequence. However, this | |
| is unlikely to affect real applications since comparing the two queries is | |
| not expected to be useful. | |
| Issues | |
| (12) What should we call this extension? | |
| RESOLVED: ANGLE_timer_query | |
| (13) Why is this done as a separate extension instead of just supporting | |
| ARB_timer_query? | |
| ARB_timer_query is written against OpenGL 3.2, which includes a lot of | |
| the required support for dealing with query objects. None of these | |
| functions or tokens exist in OpenGL ES, and as such have to be added in | |
| this specification. | |
| (14) How does this extension differ from ARB_timer_query? | |
| This extension contains most ARB_timer_query behavior unchanged as well | |
| as a subset of the query support required to use it from the core | |
| OpenGL 3.2 spec. It omits the glGetInteger(TIMESTAMP) functionality used to | |
| query the current time on the GPU, but the behavior for all remaining | |
| functionality taken from ARB_timer_query is the same. | |
| (15) Are query objects shareable between multiple contexts? | |
| RESOLVED: No. Query objects are lightweight and we normally share | |
| large data across contexts. Also, being able to share query objects | |
| across contexts is not particularly useful. In order to do the async | |
| query across contexts, a query on one context would have to be finished | |
| before the other context could query it. | |
| Revision History | |
| Revision 1, 2011/04/28 | |
| - copied from revision 9 of ARB_timer_query and revision 7 of | |
| ARB_occlusion_query | |
| - removed language that was clearly not relevant to ES2 | |
| - rebased changes against the OpenGL ES 2.0 specification |