#include "window_multithreaded.h"
#include "cuberenderer.h"
#include <QOpenGLContext>
#include <QOpenGLFunctions>
#include <QOpenGLFramebufferObject>
#include <QOpenGLShaderProgram>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
#include <QOpenGLVertexArrayObject>
#include <QOffscreenSurface>
#include <QQmlEngine>
#include <QQmlComponent>
#include <QQuickItem>
#include <QQuickWindow>
#include <QQuickRenderControl>
#include <QCoreApplication>
static const QEvent::Type INIT = QEvent::Type(QEvent::User + 1);
static const QEvent::Type RENDER = QEvent::Type(QEvent::User + 2);
static const QEvent::Type RESIZE = QEvent::Type(QEvent::User + 3);
static const QEvent::Type STOP = QEvent::Type(QEvent::User + 4);
static const QEvent::Type UPDATE = QEvent::Type(QEvent::User + 5);
QuickRenderer::QuickRenderer()
: m_context(nullptr),
m_surface(nullptr),
m_fbo(nullptr),
m_window(nullptr),
m_quickWindow(nullptr),
m_renderControl(nullptr),
m_cubeRenderer(nullptr),
m_quit(false)
{
}
void QuickRenderer::requestInit()
{
QCoreApplication::postEvent(this, new QEvent(INIT));
}
void QuickRenderer::requestRender()
{
QCoreApplication::postEvent(this, new QEvent(RENDER));
}
void QuickRenderer::requestResize()
{
QCoreApplication::postEvent(this, new QEvent(RESIZE));
}
void QuickRenderer::requestStop()
{
QCoreApplication::postEvent(this, new QEvent(STOP));
}
bool QuickRenderer::event(QEvent *e)
{
QMutexLocker lock(&m_mutex);
switch (int(e->type())) {
case INIT:
init();
return true;
case RENDER:
render(&lock);
return true;
case RESIZE:
if (m_cubeRenderer)
m_cubeRenderer->resize(m_window->width(), m_window->height());
return true;
case STOP:
cleanup();
return true;
default:
return QObject::event(e);
}
}
void QuickRenderer::init()
{
m_context->makeCurrent(m_surface);
m_cubeRenderer = new CubeRenderer(m_surface);
m_cubeRenderer->resize(m_window->width(), m_window->height());
m_renderControl->initialize(m_context);
}
void QuickRenderer::cleanup()
{
m_context->makeCurrent(m_surface);
m_renderControl->invalidate();
delete m_fbo;
m_fbo = nullptr;
delete m_cubeRenderer;
m_cubeRenderer = nullptr;
m_context->doneCurrent();
m_context->moveToThread(QCoreApplication::instance()->thread());
m_cond.wakeOne();
}
void QuickRenderer::ensureFbo()
{
if (m_fbo && m_fbo->size() != m_window->size() * m_window->devicePixelRatio()) {
delete m_fbo;
m_fbo = nullptr;
}
if (!m_fbo) {
m_fbo = new QOpenGLFramebufferObject(m_window->size() * m_window->devicePixelRatio(),
QOpenGLFramebufferObject::CombinedDepthStencil);
m_quickWindow->setRenderTarget(m_fbo);
}
}
void QuickRenderer::render(QMutexLocker *lock)
{
Q_ASSERT(QThread::currentThread() != m_window->thread());
if (!m_context->makeCurrent(m_surface)) {
qWarning("Failed to make context current on render thread");
return;
}
ensureFbo();
m_renderControl->sync();
m_cond.wakeOne();
lock->unlock();
m_renderControl->render();
m_context->functions()->glFlush();
QMutexLocker quitLock(&m_quitMutex);
if (!m_quit)
m_cubeRenderer->render(m_window, m_context, m_fbo->texture());
}
void QuickRenderer::aboutToQuit()
{
QMutexLocker lock(&m_quitMutex);
m_quit = true;
}
class RenderControl : public QQuickRenderControl
{
public:
RenderControl(QWindow *w) : m_window(w) { }
QWindow *renderWindow(QPoint *offset) override;
private:
QWindow *m_window;
};
WindowMultiThreaded::WindowMultiThreaded()
: m_qmlComponent(nullptr),
m_rootItem(nullptr),
m_quickInitialized(false),
m_psrRequested(false)
{
setSurfaceType(QSurface::OpenGLSurface);
QSurfaceFormat format;
format.setDepthBufferSize(16);
format.setStencilBufferSize(8);
setFormat(format);
m_context = new QOpenGLContext;
m_context->setFormat(format);
m_context->create();
m_offscreenSurface = new QOffscreenSurface;
m_offscreenSurface->setFormat(m_context->format());
m_offscreenSurface->create();
m_renderControl = new RenderControl(this);
m_quickWindow = new QQuickWindow(m_renderControl);
m_qmlEngine = new QQmlEngine;
if (!m_qmlEngine->incubationController())
m_qmlEngine->setIncubationController(m_quickWindow->incubationController());
m_quickRenderer = new QuickRenderer;
m_quickRenderer->setContext(m_context);
m_quickRenderer->setSurface(m_offscreenSurface);
m_quickRenderer->setWindow(this);
m_quickRenderer->setQuickWindow(m_quickWindow);
m_quickRenderer->setRenderControl(m_renderControl);
m_quickRendererThread = new QThread;
m_renderControl->prepareThread(m_quickRendererThread);
m_context->moveToThread(m_quickRendererThread);
m_quickRenderer->moveToThread(m_quickRendererThread);
m_quickRendererThread->start();
connect(m_renderControl, &QQuickRenderControl::renderRequested, this, &WindowMultiThreaded::requestUpdate);
connect(m_renderControl, &QQuickRenderControl::sceneChanged, this, &WindowMultiThreaded::requestUpdate);
}
WindowMultiThreaded::~WindowMultiThreaded()
{
m_quickRenderer->mutex()->lock();
m_quickRenderer->requestStop();
m_quickRenderer->cond()->wait(m_quickRenderer->mutex());
m_quickRenderer->mutex()->unlock();
m_quickRendererThread->quit();
m_quickRendererThread->wait();
delete m_renderControl;
delete m_qmlComponent;
delete m_quickWindow;
delete m_qmlEngine;
delete m_offscreenSurface;
delete m_context;
}
void WindowMultiThreaded::requestUpdate()
{
if (m_quickInitialized && !m_psrRequested) {
m_psrRequested = true;
QCoreApplication::postEvent(this, new QEvent(UPDATE));
}
}
bool WindowMultiThreaded::event(QEvent *e)
{
if (e->type() == UPDATE) {
polishSyncAndRender();
m_psrRequested = false;
return true;
} else if (e->type() == QEvent::Close) {
m_quickRenderer->aboutToQuit();
}
return QWindow::event(e);
}
void WindowMultiThreaded::polishSyncAndRender()
{
Q_ASSERT(QThread::currentThread() == thread());
m_renderControl->polishItems();
QMutexLocker lock(m_quickRenderer->mutex());
m_quickRenderer->requestRender();
m_quickRenderer->cond()->wait(m_quickRenderer->mutex());
}
void WindowMultiThreaded::run()
{
disconnect(m_qmlComponent, &QQmlComponent::statusChanged, this, &WindowMultiThreaded::run);
if (m_qmlComponent->isError()) {
const QList<QQmlError> errorList = m_qmlComponent->errors();
for (const QQmlError &error : errorList)
qWarning() << error.url() << error.line() << error;
return;
}
QObject *rootObject = m_qmlComponent->create();
if (m_qmlComponent->isError()) {
const QList<QQmlError> errorList = m_qmlComponent->errors();
for (const QQmlError &error : errorList)
qWarning() << error.url() << error.line() << error;
return;
}
m_rootItem = qobject_cast<QQuickItem *>(rootObject);
if (!m_rootItem) {
qWarning("run: Not a QQuickItem");
delete rootObject;
return;
}
m_rootItem->setParentItem(m_quickWindow->contentItem());
updateSizes();
m_quickInitialized = true;
m_quickRenderer->requestInit();
polishSyncAndRender();
}
void WindowMultiThreaded::updateSizes()
{
m_rootItem->setWidth(width());
m_rootItem->setHeight(height());
m_quickWindow->setGeometry(0, 0, width(), height());
}
void WindowMultiThreaded::startQuick(const QString &filename)
{
m_qmlComponent = new QQmlComponent(m_qmlEngine, QUrl(filename));
if (m_qmlComponent->isLoading())
connect(m_qmlComponent, &QQmlComponent::statusChanged, this, &WindowMultiThreaded::run);
else
run();
}
void WindowMultiThreaded::exposeEvent(QExposeEvent *)
{
if (isExposed()) {
if (!m_quickInitialized)
startQuick(QStringLiteral("qrc:/rendercontrol/demo.qml"));
}
}
void WindowMultiThreaded::resizeEvent(QResizeEvent *)
{
if (m_rootItem) {
updateSizes();
m_quickRenderer->requestResize();
polishSyncAndRender();
}
}
void WindowMultiThreaded::mousePressEvent(QMouseEvent *e)
{
QMouseEvent mappedEvent(e->type(), e->localPos(), e->screenPos(), e->button(), e->buttons(), e->modifiers());
QCoreApplication::sendEvent(m_quickWindow, &mappedEvent);
}
void WindowMultiThreaded::mouseReleaseEvent(QMouseEvent *e)
{
QMouseEvent mappedEvent(e->type(), e->localPos(), e->screenPos(), e->button(), e->buttons(), e->modifiers());
QCoreApplication::sendEvent(m_quickWindow, &mappedEvent);
}