| 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 |