Главная Статьи Ссылки Скачать Скриншоты Юмор Почитать Tools Проекты Обо мне Гостевая Форум |
Поскольку OpenGL построен исходя из модели клиент-сервер, то возвращение управления из очередной команды OpenGL (например, glDraw*) еще не говорит ни о чем, кроме того, что Ваша команды была добавлена в командный буфер (вполне возможно она еще даже не начала выполняться на GPU). Поэтому большинство команд являются асинхронными, т.е. они просто добавляют команды в буфер команд и немедленно возвращают управление клиенту.
Поэтому возникает необходимость в некотором асинхронном механизме получения информации от сервера, который бы не обязательно блокировал клиентскую часть приложения в ожидании пока запрос дойдет до GPU, будет готов ответ и этот ответ будет получен клиентской частью. Подобная функциональность предоставляется через так называемые асинхронные запросы (asynchroneous queries), позволяющие отправлять запросы на GPU и отслеживать их состояние.
В OpenGL существует специальный механизм асинхронных запросов (впервые появившиеся в расширении ARB_occlusion_query) позволяющий отправлять на GPU определенные типы запросов, отслеживать их состояние и получать требуемую информацию. В каждый момент времени можно узнать готов ли результат данного запроса (без ожидания) и получить результат запроса (ожидая готовности, если этот результат еще не готов).
На данный момент (версия OpenGL 4.1) поддерживаются следующие типы запросов:
Каждому асинхронному запросу соответствует свой объект OpenGL, который идентифицируется при помощи беззнакового целого числа, не равного нулю. Для выделения массива таких чисел и их освобождения служат следующие функции:
void glGenQueries ( GLsizei n, GLuint * ids );
void glDeleteQueries ( GLsizei n, const GLuint * ids );
Узнать, является ли заданное целое число idидентификатором какого-либо асинхронного запроса, можно при помощи функции glIsQuery:
GLboolean glIsQuery ( GLuint id );
Простейшим (и чаще всего используемым) способом создания запроса с заданным идентификатором по группе команд OpenGL является помещение этих команд внутри блока glBeginQuery/glEndQuery:
void glBeginQuery ( GLenum target, GLuint id );
void glEndQuery ( GLenum target );
В этих запросах параметр id является идентификатором запроса, а параметр target задает тип запроса и принимает одно из следующих значений - GL_SAMPLES_PASSED, GL_ANY_SAMPLES_PASSED, GL_TIME_ELAPSED, GL_PRIMITIVES_GENERATED и GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN.
Также существует возможность создания запроса с индексом (на данный момент это применяется только для transform feedback'а с несколькими вершинными потоками, индекс отвечает за выбор вершинного потока). Для создания индексированных запросов служат команды glBeginQueryIndexed и glEndQueryIndexed.
void glBeginQueryIndexed ( GLenum target, GLuint index, GLuint id );
void glEndQueryIndexed ( GLenum target, GLuint index );
При этом обычный вызов glBeginQuery ( target, id ) эквивалентен вызову glBeginQueryIndexed ( target, 0, id ), а вызов glEndQuery ( target ) - эквивалентен вызову glEndQueryIndexed ( target, 0 ).
При помощи функции glQueryiv можно получать информацию об асинхронных запросах:
void glGetQueryiv ( GLenum target, GLenum pname, int * params );
Запрос с параметром pname равным GL_CURRENT_QUERY позволяет узнать идентификатор активного в данный момент запроса типа target. Запрос с pname равным GL_QUERY_COUNTER_BITS позволяет узнать количество бит счетчика, используемого для хранения результатов запросов данного типа (это значение может зависеть от реализации).
Следующие функции позволяют получить информацию о конкретном запросе по его идентификатору.
void glGetQueryObjectiv ( GLuint id, GLenum pname, int * params );
void glGetQueryObjectuiv ( GLuint id, GLenum pname, GLuint * params );
void glGetQueryObjecti64v ( GLuint id, GLenum pname, GLint64 * params );
void glGetQueryObjectui64v ( GLuint id, GLenum pname, GLuint64 * params );
Параметр id задает идентификатор существующего запроса, а параметр pname определяет какое именно значение, связанное с данным запросом следует вернуть.
Следующий пример кода проверяет, готов ли результат данного запроса без блокировки приложения, и в случае готовности получает значение, соответствующее этому запросу.
GLint ready;
glGetQueryObjectiv ( id, GL_QUERY_RESULT_READY, &ready );
if ( ready )
{
// результат готов можно сразу же читать
GLuint result;
glGetQueryObjectiv ( id, GL_QUERY_RESULT, &result );
// результат получен, можно использовать
}
Для того, чтобы получить результат запроса используется значение параметра pname равное GL_QUERY_RESULT. При этом если результат запроса еще не готов, то происходит блокировка приложения и явное ожидание готовности результата.
Значение однажды полученного идентификатора можно переиспользовать, в том числе и для других типов запросов. Обратите внимание, что поддержка 64-битовых результатов обеспечивается расширением ARB_timer_query.
Оба эти расширения предназначены для отслеживания фрагментов, полученных в результате выполнения команд из соответствующего блока glBeginQuery*/glEndQuery*, успешно прошедших тест глубины. Расширение ARB_occlusion_query вводит типа запроса GL_SAMPLES_PASSED, а расширение ARB_occlusion_query2 - тип запроса GL_ANY_SAMPLES_PASSED.
Первый тип запроса считает сколько именно фрагментов успешно прошло тест глубины (считаются только те фрагменты, которые получены в результате выполнения команд, заключенных внутри соответствующего блока glBeginQuery*/glEndQuery*). Значением данного запроса является именно точное количество фрагментов, прошедших тест глубины (подробнее об этом запросе смотрите в статье о расширении ARB_occlusion_query.
Запрос с типом GL_ANY_SAMPLES_PASSED позволяет узнать прошел хотя бы один фрагмент тест глубины, т.е. данный запрос является упрощенной (и, как правило, более быстрой) версией запроса GL_SAMPLES_PASSED. Значением данного запроса является GL_FALSE или GL_TRUE.
Расширение ARB_occlusion_query вошло в OpenGL 3.3 core.
Данное расширение вводит еще один асинхронного запроса - GL_TIME_ELAPSED. Запросы данного типа позволяют узнать время, затраченное GPU на выполнение команд из соответствующего блока glBeginQuery*/glEndQuery*. Результатом запроса данного типа является затраченное время в наносекундах (наносекунда - 10-9 секунды).
Обратите внимание, что не всегда 32-битового счетчика хватает для представления необходимого времени, поэтому данное расширение вводит функции, позволяющие получать 64-битовое значение запроса - glGetQueryObject*64v.
Следующий фрагмент кода демонстрирует применение данного запроса для получения времени, затраченного на выполнение заданного блока кода.
GLint query; // query id
GLuint64 elapsedTime; // затраченное время в наносекундах
glGenQueries ( 1, &query );
// . . .
// Начинаем запрос
glBeginQuery ( GL_ELAPSED_TIME, query );
// Команды, время выполнения которых нас интересует
glEndQuery ( GL_ELAPSED_TIME );
// . . .
// получаем затраченное время (ожидая готовности результата)
glGetQueryObjectui64v ( query, GL_QUERY_RESULT, &elapsedTime );
Также расширение ARB_timer_query вводит еще один тип запроса - GL_TIMESTAMP. Для работы с этим типом запроса служит специальная функция glQueryCounter:
void glQueryCounter ( GLuint id, GLenum target );
В качестве параметра target этой функции может выступать только константа GL_TIMESTAMP, а параметр id, как и ранее, является идентификатором асинхронного запроса.
Данная функция позволяет отправить запрос к внутреннему таймеру GPU и получить его время (в наносекундах). При этом сама команда просто добавляет запрос на получение времени от таймера в поток команд и когда все команды, предшествующие glQueryCounter будут полностью выполнены, то текущее значение внутреннего таймера GPU будет записано в соответствующий объект-запрос и станет доступным.
Обратите внимание, что вызовы glQueryCounter можно использовать в том числе и внутри блока glBeginQuery*/glEndQuery*. Еще одним важным моментом является то, что GPU может использовать разные таймеры для запросов GL_TIMESTAMP и GL_TIME_ELAPSED.
За счет использования вызова glQueryCounter можно переписать предыдущий пример с определением времени выполнения заданного фрагмента кода следующим образом:
GLint startQuery, endQuery; // query id
GLuint64 startTime, endTime; // время начала выполнения фрагмента кода и время окончания выполнения
GLuint64 elapsedTime; // затраченное время в наносекундах
glGenQueries ( 1, &startQuery );
glGenQueries ( 1, &endQuery );
// . . .
// Начинаем запрос
glQueryCounter ( queryStart, GL_TIMESTAMP );
// Команды, время выполнения которых нас интересует
glQueryCounter ( queryEnd, GL_TIMESTAMP );
// . . .
// получаем затраченное время (ожидая готовности результата)
glGetQueryObjectui64v ( query, GL_QUERY_RESULT, &startTime );
glGetQueryObjectui64v ( query, GL_QUERY_RESULT, &endTime );
elapsedTime = endTime - startTime;
Обратите внимание, что важной особенностью данных запросов является их асинхронность, т.е. можно не ожидая просто узнать готов ли результат. В случае, если результат еще не готов, то можно продолжить работу с теми частями функциональности, которые не требуют результата данного запроса.
Обратите также внимание на расширение ARB_sync также позволяющее асинхронно проверять выполнение кода.
Расширение ARB_timer_query вошло в OpenGL 3.3 core.
Для удобства работы можно завернуть асинхронный запрос в класс, аналогичный приведенному ниже.
#ifndef __GL_QUERY__
#define __GL_QUERY__
#include "libExt.h"
class Query
{
GLuint id;
GLenum target;
public:
Query ();
~Query ();
void beginQuery ( GLenum theTarget )
{
target = theTarget;
glBeginQuery ( target, id );
}
void endQuery ()
{
glEndQuery ( target );
}
void beginQueryIndexed ( GLenum theTarget, int index )
{
target = theTarget;
glBeginQuery ( target, id );
}
void endQueryIndexed ( int index )
{
glEndQueryIndexed ( target, index );
}
void timestamp ()
{
target = GL_TIMESTAMP;
glQueryCounter ( target, id );
}
bool isReady () const; // result is ready ?
GLuint getResult () const;
GLuint64 getResult64 () const;
static GLuint activeQuery ( GLenum theTarget );
static int counterBits ( GLenum theTarget );
};
#endif