查询对象和异步查询(Query Objects and Asynchronous Queries)
Query Objects(查询对象)是OpenGL中的一种机制,用于获取有关一系列GL命令处理过程的信息。这些信息可以包括:
- 绘图命令处理的图元数量。
- 写入变换反馈缓冲区的图元数量。
- 在片段处理期间通过深度测试的样本数量。
- 处理命令所需的时间量。
通过使用查询对象,OpenGL应用程序可以动态地获取有关图形渲染过程的各种性能指标和统计信息。这些信息对于性能优化、调试和分析应用程序的渲染流程非常有用。
使用查询对象的一般步骤如下:
- 创建查询对象:通过调用OpenGL的API函数,可以创建一个查询对象,并指定要查询的信息类型。
- 开始查询:在需要获取信息的地方,通过调用OpenGL的API函数,开始一个查询操作。
- 执行一系列GL命令:在开始查询和结束查询之间,执行一系列的GL命令,这些命令可以是绘图命令、变换反馈命令、深度测试命令等。
- 结束查询:在执行完一系列GL命令后,通过调用OpenGL的API函数,结束查询操作。
- 获取查询结果:通过调用OpenGL的API函数,可以获取查询对象的结果,这些结果包含了在查询期间收集的有关图形处理过程的信息,比如处理的图元数量、通过的样本数量等。
使用查询对象可以帮助开发人员更好地了解和优化他们的OpenGL应用程序的性能特征,从而提高图形渲染的效率和质量。
查询类型
OpenGL 支持的查询类型包括:
-
图元生成查询 :目标为
PRIMITIVES_GENERATED
,用于返回通过 OpenGL 处理的图元数量。同时激活的此类查询最多可达MAX_VERTEX_STREAMS
个。 -
变换反馈写入的图元查询 :目标为
TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN
,用于统计被写入一个或多个缓冲对象中的图元数量。同时激活的此类查询同样不得超过MAX_VERTEX_STREAMS
个。 -
变换反馈溢出查询 :目标为
TRANSFORM_FEEDBACK_OVERFLOW
或TRANSFORM_FEEDBACK_STREAM_OVERFLOW
,用于报告一个或多个流是否存在变换反馈溢出情况。 -
遮挡查询 :目标为
SAMPLES_PASSED
、ANY_SAMPLES_PASSED
或ANY_SAMPLES_PASSED_CONSERVATIVE
,用于计数通过深度测试的片段或样本数量,或者设置布尔值以表示是否至少有一个片段或样本通过了深度测试。同时只能激活一个此类查询。 -
时间流逝查询 :目标为
TIME_ELAPSED
,记录完成一系列命令所需的完整时间。同时只能激活一个此类查询。 -
时间戳查询 :目标为
TIMESTAMP
,记录当前 OpenGL 的时间。同时只能激活一个此类查询。 -
提交查询 :目标为
VERTICES_SUBMITTED
和PRIMITIVES_SUBMITTED
分别返回传输到 OpenGL 的顶点数量和图元数量信息。 -
顶点着色器查询 :目标为
VERTEX_SHADER_INVOCATIONS
,返回顶点着色器被调用的次数。 -
曲面细分着色器查询 :目标为
TESS_CONTROL_SHADER_PATCHES
和TESS_EVALUATION_SHADER_INVOCATIONS
,分别返回曲面细分控制着色器处理的补丁数量和曲面细分评估着色器被调用的次数。 -
几何着色器查询 :目标为
GEOMETRY_SHADER_INVOCATIONS
和GEOMETRY_SHADER_PRIMITIVES_EMITTED
,分别返回几何着色器被调用的次数以及它发射的图元数量。 -
图元裁剪查询 :目标为
CLIPPING_INPUT_PRIMITIVES
和CLIPPING_OUTPUT_PRIMITIVES
,分别返回在图元裁剪阶段处理的图元数量和由图元裁剪阶段输出并进一步被光栅化阶段处理的图元数量。 -
片段着色器查询 :目标为
FRAGMENT_SHADER_INVOCATIONS
,返回片段着色器被调用的次数。 -
计算着色器查询 :目标为
COMPUTE_SHADER_INVOCATIONS
,返回计算着色器被调用的次数。
查询对象创建和激活
异步查询的结果不会在集合中的最后一个命令完成后立即由GL返回;在查询结果完全就绪之前,可以处理后续命令。一旦可用,查询结果将存储在相关联的查询对象中。第4.2.3节描述的命令提供了确定查询结果何时可用并返回查询的实际结果的机制。查询对象的名称空间是无符号整数,其中零被GL保留。
void glGenQueries( sizei n, uint *ids )
命令在ids中返回n个先前未使用的查询对象名称。这些名称被标记为已使用,仅用于GenQueries,但在它们首次被BeginQuery、BeginQueryIndexed或QueryCounter使用之前,它们不与任何对象关联。
void glCreateQueries( enum target, sizei n, uint *ids )
glCreateQueries在ids中返回n个先前未使用的查询对象名称,每个名称表示具有指定目标的新查询对象。目标必须是 **查询类型** 中描述的查询对象目标之一。
生成的查询对象的初始状态是结果已标记为可用(查询对象的QUERY_RESULT_AVAILABLE的值为TRUE),并且结果值(QUERY_RESULT的值)为零。
void glDeleteQueries( sizei n, const uint *ids )
ids包含要删除的n个查询对象的名称。删除查询对象后,其名称再次变为未使用。如果删除了活动查询对象,则其名称立即变为未使用,但底层对象直到不再处于活动状态(参见第5.1节)才会被删除。对于GenQueries目的而标记为已使用的ids中的未使用名称将再次标记为未使用。未使用的ids中的名称将被静默忽略,零值也将被静默忽略。
在OpenGL中,除TIMESTAMP类型的计时器查询外,对于支持的每种查询类型,针对每个可能激活的查询,都有一个对应的活动查询对象名称。如果这个活动查询对象名称非零,则表示OpenGL当前正在追踪相关的信息,并且查询结果会被记录到该查询对象内。
反之,如果活动查询对象名称为零,则意味着未追踪此类信息。
创建并激活查询对象可以使用以下命令:
void glBeginQueryIndexed( enum target, uint index, uint id )
target
参数指定了要执行的查询类型,后续章节将详细介绍其有效值。- 如果
id
是一个未使用的查询对象名称,则该名称会被标记为已使用,并与指定目标类型的新的查询对象关联起来。否则,id
必须是现有且同类型查询对象的名称。请注意,通过ANY_SAMPLES_PASSED或ANY_SAMPLES_PASSED_CONSERVATIVE两种目标指定的遮挡查询对象,在未来的查询中可以重用于这两种目标中的任何一种。而由SAMPLES_PASSED目标指定的对象只能重用于该目标。 index
参数是查询的索引,其值必须在0和目标特定的最大值之间。- 无论
id
是否为新创建的查询对象,其状态都将被设置为结果不可用(查询对象的QUERY_RESULT_AVAILABLE值为FALSE),并且结果值(QUERY_RESULT)初始化为零。 - 对于给定的
target
和index
,其对应的活动查询对象名称会被设置为id
。
void glBeginQuery( enum target, uint id )
- 等价于 glBeginQueryIndexed(target, 0, id);
void glEndQueryIndexed( enum target, uint index )
-
标记了由target和index指定的活动查询要跟踪的命令序列的结束。target和index的含义与BeginQueryIndexed中相同。
-
相应的活动查询对象被更新以指示查询结果不可用,并且target和index的活动查询对象名称被重置为零。当在EndQueryIndexed调用时发出的命令完成并且最终查询结果可用时,被调用时活动的查询对象被更新以包含查询结果,并指示查询结果可用。
void glEndQuery( enum target )
- 等价于 glEndQueryIndexed(target, 0);
查询对象包含两部分状态信息:
-
一个单比特位,用于指示查询结果是否可用。
-
一个整数,用于存储查询结果的值。这个表示查询结果所使用的比特位数量(n)是实现依赖的,并且可以按照4.2.3节中描述的方式确定。
查询对象的初始状态取决于它是通过
CreateQueries
还是BeginQueryIndexed
创建的,如上所述。如果查询结果发生溢出(即超过2^n - 1的值),其值将变为未定义。尽管不是必需的,但建议实现能够通过在达到2^n - 1时饱和并停止增加的方式来处理这种溢出情况。
对于每个可能的活动查询目标和索引,都需要维护的状态包括:一个无符号整数,用于保存活动查询对象名称(如果没有活动查询对象,则为零);以及任何保持正在进行的异步查询当前结果所需的状态。同一时间内只允许一种遮挡查询类型处于活动状态,因此遮挡查询所需的必要状态会被共享。
boolean glIsQuery( uint id )
用于检查给定的ID(id)是否为一个查询对象的名称。
void glGetQueryIndexediv( enum target, uint index, enum pname, int *params )
查询有关活动查询对象的信息
-
target
和index
指定了要查询的活动查询,其含义与BeginQueryIndexed
中相同。 -
如果
pname
设置为CURRENT_QUERY
,则当前为target
和index
指定的活动查询对象名称(如果有活动查询的话),将被放置在params
指向的内存中。若目标为TIMESTAMP
,则始终返回0。 -
若
pname
设为QUERY_COUNTER_BITS
,则忽略index
参数,并将在params
中放置目标查询类型的实现依赖的查询结果所使用的比特位数。查询计数器的比特位数量可能为0,这意味着计数器不包含有用信息。
对于不同类型的查询对象,如果对应的比特位数非零,则它们至少应具备以下最低位数要求:
- 基本图元查询(PRIMITIVES_GENERATED和TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN):至少32位。
- 变换反馈溢出查询(TRANSFORM_FEEDBACK_OVERFLOW和TRANSFORM_FEEDBACK_STREAM_OVERFLOW):至少1位。
- 遮挡查询(ANY_SAMPLES_PASSED或ANY_SAMPLES_PASSED_CONSERVATIVE):至少1位。针对SAMPLES_PASSED目标的遮挡查询,至少需要32位。
- 计时器查询(TIME_ELAPSED和TIMESTAMP):至少30位,确保至少能测量一秒的时间。
- 管道统计查询(VERTICES_SUBMITTED、PRIMITIVES_SUBMITTED、VERTEX_SHADER_INVOCATIONS、TESS_CONTROL_SHADER_PATCHES、TESS_EVALUATION_SHADER_INVOCATIONS、GEOMETRY_SHADER_INVOCATIONS、FRAGMENT_SHADER_INVOCATIONS、COMPUTE_SHADER_INVOCATIONS、GEOMETRY_SHADER_PRIMITIVES_EMITTED、CLIPPING_INPUT_PRIMITIVES和CLIPPING_OUTPUT_PRIMITIVES):至少32位。
void glGetQueryiv( enum target, enum pname, int *params )
等价于 glGetQueryIndexediv(target, 0, pname, params);
查询对象的状态可以通过以下命令进行查询
cpp
void GetQueryObjectiv( uint id, enum pname, int *params );
void GetQueryObjectuiv( uint id, enum pname, uint *params );
void GetQueryObjecti64v( uint id, enum pname, int64 *params );
void GetQueryObjectui64v( uint id, enum pname, uint64 *params );
void GetQueryBufferObjectiv( uint id, uint buffer, enum pname, intptr offset );
void GetQueryBufferObjectuiv( uint id, uint buffer, enum pname, intptr offset );
void GetQueryBufferObjecti64v( uint id, uint buffer, enum pname, intptr offset );
void GetQueryBufferObjectui64v( uint id, uint buffer, enum pname, intptr offset );
其中:
id
是一个查询对象的名称。- 对于
GetQueryBufferObject*
函数,buffer
是缓冲区对象的名称,而offset
是缓冲区内写入查询值的偏移量。 - 对于
GetQueryObject*
函数,查询值可以被返回到客户端内存中,也可以写入到缓冲区对象中。如果当前绑定到查询结果缓冲区绑定点(见第6.1节中的 QUERY_RESULT)的是零,则params
被视为指向客户端内存的一个指针,在该位置写入查询值;否则,params
将被视为查询结果缓冲对象内的一个偏移量。
对于查询对象的结果值,可能需要一定时间才能变得可用。如果 pname
设置为 QUERY_RESULT_AVAILABLE
,则在需要等待时函数将返回 FALSE
;否则返回 TRUE
。必须保证,若任何查询对象返回的结果可用性为 TRUE
,那么在此之前所有相同类型的所有查询也必须返回 TRUE
。对任一查询对象反复查询 QUERY_RESULT_AVAILABLE
保证最终会返回 TRUE
。
- 若
pname
为QUERY_TARGET
,则返回查询对象的目标类型作为一个整数值。 - 若
pname
为QUERY_RESULT
,则返回查询对象的结果值作为单个整数。如果结果值的大小过大以至于无法用请求的类型表示,则返回最接近的可表示值。如果目标类型的查询计数器位数为零,则结果将以整数值0的形式返回。查询QUERY_RESULT
会使给定查询对象在有限时间内完成其计算过程。 - 若
pname
为QUERY_RESULT_NO_WAIT
,则仅当结果在执行状态查询时已可用时,才以单个整数形式返回查询对象的结果值。若结果不可用,则不写入查询返回值。
如果在调用上述查询命令之前,使用同一对象名称发起了多个查询操作,则返回的结果和可用性信息始终来自最后发起的那个查询。在开始针对同一目标和ID的新查询之前,如果不先检索先前查询的结果,这些结果将会丢失。
时间查询(Time Queries)
查询对象还可用于追踪完成一组OpenGL命令所需的时间(时间流逝查询),或确定当前的OpenGL时间(计时器查询)。
当使用目标 TIME_ELAPSED
调用 BeginQuery
和 EndQuery
时,OpenGL将准备启动和停止用于时间流逝查询的计时器。计时器会在所有先前命令对OpenGL客户端、服务器状态以及帧缓冲区的影响完全实现后开始或停止。BeginQuery
和 EndQuery
命令可能在计时器实际开始或停止之前返回。
当时间流逝查询的计时器最终停止时,流逝的时间(以纳秒为单位)会被写入到相应的查询对象作为查询结果值,并标记该对象的查询结果为可用。
可以通过以下命令创建计时器查询对象:
cpp
void QueryCounter( uint id, enum target );
其中 target
必须是 TIMESTAMP
。如果 id
是未使用的查询对象名称,则该名称会被标记为已使用并与新的类型为 TIMESTAMP
的查询对象关联。否则,id
必须是现有同类型查询对象的名称。
另外,也可以通过调用 CreateQueries
并将 target
设置为 TIMESTAMP
来创建 TIMESTAMP
类型的查询对象。
当调用 QueryCounter
时,OpenGL会记录下当前时间并将其存入对应的查询对象中。这个时间是在所有先前命令对OpenGL客户端、服务器状态及帧缓冲区的影响完全实现之后记录的。一旦时间被记录,该对象的查询结果就会被标记为可用。计时器查询可以在目标为 TIME_ELAPSED
的 BeginQuery
/ EndQuery
块内部使用,且不会影响该查询对象的结果。
通过调用 GetIntegerv
或 GetInteger64v
并传入符号常量 TIMESTAMP
,可以查询当前的OpenGL时间。这将返回所有先前命令到达OpenGL服务器但不一定执行完毕后的GL时间。通过结合这种同步获取命令和异步时间戳查询对象目标,应用程序可以测量命令从到达OpenGL服务器到在帧缓冲区实现之间的延迟。