- PVSM.RU - https://www.pvsm.ru -
Недавний релиз Qt 5.4 [1], помимо прочего, предоставил в распоряжение разработчиков один, на мой взгляд, очень любопытный инструмент. А именно, разработчики Qt сделали QQuickRenderControl [2] частью публичного API. Занятность данного класса заключается в том, что теперь появилась возможность использовать Qml в связке с любым другим фреймворком, если он предоставляет возможность получить (или задать) указатель на используемый OpenGL контекст.
С другой стороны, в процессе работы над одним из своих проектов, я столкнулся с необходимостью отрисовывать QML сцену на CALayer (Mac OS X) [3], без малейшей возможности получить доступ к родительскому окну. Недельный поиск возможных вариантов решения проблемы показал, что самым адекватным решением будет как раз использование QQuickRenderControl из Qt 5.4, благодаря удачному совпадению, получившего статус релиза одновременно с возникновением вышеупомянутой задачи.
Изначально я предположил что задача плевая, и будет решена в течении пары вечеров, но как же я сильно заблуждался — задача заняла порядка полумесяца на исследования, и еще пол месяца на реализацию (которая все еще далека от идеала).
1) Реализовать настройку QQuickWindow для рендеринга в FBO и управления этим процессом через QQuickRenderControl;
2) Реализовать загрузку Qml и присоединение результата к QQuickWindow;
3) Реализовать передачу событий мыши и клавиатуры;
4) Отрисовать FBO (ради чего все и затевалось);
В данной статье я позволю себе остановится только на пункте 1), остальные пункты в последющих частях (если вы сочтете это интересным).
Отправной точкой является OpenGL контекст в котором в конечном итоге и будет отрисовываться FBO. Но поскольку, с большой долей вероятности, работать необходимо с контекстом изначально не имеющим никакого отношения к Qt, то необходимо провести конвертацию контекста из формата операционной системы в экземпляр QOpenGLContext. Для этого необходимо использовать метод QOpenGLContext::setNativeHandle [6].
Пример использования на основе NSOpenGLContext:
NSOpenGLContext* nativeContext = [super openGLContextForPixelFormat: pixelFormat];
QOpenGLContext* extContext = new QOpenGLContext;
extContext->setNativeHandle( QVariant::fromValue( QCocoaNativeContext( nativeContext ) ) );
extContext->create();
Список доступных Native Context лучше смотреть непосредственно в заголовочных файлах Qt ( includeQtPlatformHeaders ), т.к. документация в этой части сильно не полна.
Далее можно использовать этот контекст (но при этом необходимо внимательно следить чтоб изменения состояния этого контекста не входили в конфликт с манипуляциями владельца), а можно сделать 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 предварительно создается QQuickRenderControl и передается в конструктор:
QQuickRenderControl* renderControl = new QQuickRenderControl();
QQuickWindow* quickWindow = new QQuickWindow( renderControl );
quickWindow->setGeometry( 0, 0, 640, 480 );
Кроме того важно задать размер окна, для дальнейшего успешного создания FBO.
Перед вызовом 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. Второй важной особенностью является то что настойчиво не рекомендуется [7] отсуществлять рендеринг непосредственно в обработчиках упомянутых выше сигналов. Поэтому используется таймер с небольшим интервалом. Ну и последней токостью является то, что, в случае использования 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 [8], FboQuickWindow.cpp [8]
Коментарии, вопросы, здоровая критика в комментариях приветствуется.
Продолжение следует...
Автор: RSATom
Источник [9]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/opengl/79098
Ссылки в тексте:
[1] релиз Qt 5.4: http://habrahabr.ru/post/245521/
[2] QQuickRenderControl: http://doc-snapshot.qt-project.org/qt5-5.4/qquickrendercontrol.html
[3] CALayer (Mac OS X): https://developer.apple.com/library/mac/documentation/GraphicsImaging/Reference/CALayer_class/index.html
[4] QQuickWindow: http://doc.qt.io/qt-5/qquickwindow.html
[5] QOpenGLFramebufferObject (далее FBO): http://doc.qt.io/qt-5/qopenglframebufferobject.html
[6] QOpenGLContext::setNativeHandle: http://doc.qt.io/qt-5/qopenglcontext.html#setNativeHandle
[7] настойчиво не рекомендуется: http://doc-snapshot.qt-project.org/qt5-5.4/qquickrendercontrol.html#renderRequested
[8] FboQuickWindow.h: https://github.com/RSATom/QuickLayer/blob/master/FboQuickWindow.h
[9] Источник: http://habrahabr.ru/post/247477/
Нажмите здесь для печати.