...

суббота, 10 января 2015 г.

QQuickRenderControl, или как подружить QML с чужим OpenGL контекстом. Часть I

Недавний релиз Qt 5.4, помимо прочего, предоставил в распоряжение разработчиков один, на мой взгляд, очень любопытный инструмент. А именно, разработчики Qt сделали QQuickRenderControl частью публичного API. Занятность данного класса заключается в том, что теперь появилась возможность использовать Qml в связке с любым другим фреймворком, если он предоставляет возможность получить (или задать) указатель на используемый OpenGL контекст.

С другой стороны, в процессе работы над одним из своих проектов, я столкнулся с необходимостью отрисовывать QML сцену на CALayer (Mac OS X), без малейшей возможности получить доступ к родительскому окну. Недельный поиск возможных вариантов решения проблемы показал, что самым адекватным решением будет как раз использование QQuickRenderControl из Qt 5.4, благодаря удачному совпадению, получившего статус релиза одновременно с возникновением вышеупомянутой задачи.

Изначально я предположил что задача плевая, и будет решена в течении пары вечеров, но как же я сильно заблуждался — задача заняла порядка полумесяца на исследования, и еще пол месяца на реализацию (которая все еще далека от идеала).



Несколько тезисов





  • QQuickRenderControl это всего навсего дополнительный интерфейс к реализации QQuickWindow для получения нотификаций об изменении QML сцены, а так же передачи команд в обратном направлении (т.е. фактически «костыль»);

  • Результат рендеринга будет получен в виде QOpenGLFramebufferObject (далее FBO), который в дальнейшем может быть использован в качестве текстуры;

  • Работать придется непосредственно с QuickWindow, соответственно сервис по загрузке QML предоставляемый QQuickView будет недоступен, и придется его реализовывать самостоятельно;

  • Поскольку никакого окна на самом деле не создается, возникает необходимость искуственно передавать события мыши и клавиатуры в QQuickWindow. Так же необходимо вручную управлять размером окна;

  • Пример использования QQuickRenderControl я сумел найти только один, в Qt 5.4 (Examples\Qt-5.4\quick\rendercontrol) — собственно по нему и проходили все разбирательства;


Что же нужно сделать для решения исходной задачи?




1) Реализовать настройку QQuickWindow для рендеринга в FBO и управления этим процессом через QQuickRenderControl;

2) Реализовать загрузку Qml и присоединение результата к QQuickWindow;

3) Реализовать передачу событий мыши и клавиатуры;

4) Отрисовать FBO (ради чего все и затевалось);

В данной статье я позволю себе остановится только на пункте 1), остальные пункты в последющих частях (если вы сочтете это интересным).


Настраиваем QQuickWindow




Внешний QOpenGLContext



Отправной точкой является OpenGL контекст в котором в конечном итоге и будет отрисовываться FBO. Но поскольку, с большой долей вероятности, работать необходимо с контекстом изначально не имеющим никакого отношения к Qt, то необходимо провести конвертацию контекста из формата операционной системы в экземпляр QOpenGLContext. Для этого необходимо использовать метод QOpenGLContext::​setNativeHandle.

Пример использования на основе NSOpenGLContext:

NSOpenGLContext* nativeContext = [super openGLContextForPixelFormat: pixelFormat];

QOpenGLContext* extContext = new QOpenGLContext;
extContext->setNativeHandle( QVariant::fromValue( QCocoaNativeContext( nativeContext ) ) );
extContext->create();




Список доступных Native Context лучше смотреть непосредственно в заголовочных файлах Qt ( include\QtPlatformHeaders ), т.к. документация в этой части сильно не полна.

Далее можно использовать этот контекст (но при этом необходимо внимательно следить чтоб изменения состояния этого контекста не входили в конфликт с манипуляциями владельца), а можно сделать shared контекст:



QSurfaceFormat format;
format.setDepthBufferSize( 16 );
format.setStencilBufferSize( 8 );

context = new QOpenGLContext;
context->setFormat( format );
context->setShareContext( extContext );
context->create();


Важным ньюансом для использования OpenGL контекста с QML является наличие в нем настроенных Depth Buffer и Stencil Buffer, поэтому если у вас нет возможности влиять на параметры исходного контекста, нужно использовать shared контекст с установленными «Depth Buffer Size» и «Stencil Buffer Size».


Создание QQuickWindow



При создании QQuickWindow предварительно создается QQuickRenderControl и передается в конструктор:

QQuickRenderControl* renderControl = new QQuickRenderControl();
QQuickWindow* quickWindow = new QQuickWindow( renderControl );
quickWindow->setGeometry( 0, 0, 640, 480 );




Кроме того важно задать размер окна, для дальнейшего успешного создания FBO.
Инициализация QQuickRenderControl и QOpenGLFramebufferObject



Перед вызовом QQuickRenderControl::initialize важно сделать контекст текущим, т.к. в процессе вызова будет сгенерирован сигнал sceneGraphInitialized, а это хорошая точка для создания FBO (который, в свою очередь, требует выставленного текущего контекста).

QOpenGLFramebufferObject* fbo = nullptr;
connect( quickWindow, &QQuickWindow::sceneGraphInitialized,
[&] () {
fbo = new QOpenGLFramebufferObject( quickWindow->size(), QOpenGLFramebufferObject::CombinedDepthStencil );
quickWindow->setRenderTarget( fbo );
}
);

offscreenSurface = new QOffscreenSurface();
offscreenSurface->setFormat( context->format() );
offscreenSurface->create();

context->makeCurrent( offscreenSurface );
renderControl->initialize( context );
context->doneCurrent();


Рендеринг



Рендеринг необходимо осуществлять как реакцию на сигналы QQuickRenderControl::renderRequested и QQuickRenderControl::sceneChanged. Разница в этих двух случаях заключается в том что во втором случае необходимо дополнительно вызывать QQuickRenderControl::polishItems и QQuickRenderControl::sync. Второй важной особенностью является то что настойчиво не рекомендуется отсуществлять рендеринг непосредственно в обработчиках упомянутых выше сигналов. Поэтому используется таймер с небольшим интервалом. Ну и последней токостью является то, что, в случае использования shared OpenGL контекста, после рендеринга, требуется вызывать glFlush — в противном случае первичный контекст не видит изменений в FBO.

bool* needSyncAndPolish = new bool;
*needSyncAndPolish = true;

QTimer* renderTimer = new QTimer;
renderTimer->setSingleShot( true );
renderTimer->setInterval( 5 );
connect( renderTimer, &QTimer::timeout,
[&] () {
if( context->makeCurrent( offscreenSurface ) ) {
if( *needPolishAndSync ) {
*needPolishAndSync = false;
renderControl->polishItems();
renderControl->sync();
}
renderControl->render();
quickWindow->resetOpenGLState();
context->functions()->glFlush();
context->doneCurrent();
}
);

connect( renderControl, &QQuickRenderControl::renderRequested,
[&] () {
if( !renderTimer->isActive() )
renderTimer->start();
}
);

connect( renderControl, &QQuickRenderControl::sceneChanged,
[&] () {
*needPolishAndSync = true;
if( !renderTimer->isActive() )
renderTimer->start();
}
);


Ну вот в общем то и все, первая часть задачи выполнена.


Класс реализующий вышеприведенную концепцию доступен на GitHub: FboQuickWindow.h, FboQuickWindow.cpp

Коментарии, вопросы, здоровая критика в комментариях приветствуется.


Продолжение следует...


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Комментариев нет:

Отправить комментарий