Commit dcbff92d authored by Laszlo Agocs's avatar Laszlo Agocs
Browse files

Finish the main structure of multiwindow_threaded

Clears the buffers at least.
parent 3abf8361
......@@ -56,7 +56,10 @@
#include <QVBoxLayout>
#include <QThread>
#include <QMutex>
#include <QWaitCondition>
#include <QQueue>
#include <QEvent>
#include <QCommandLineParser>
#include <QWindow>
#include <QPlatformSurfaceEvent>
......@@ -115,6 +118,51 @@ static QString graphicsApiName()
QVulkanInstance *instance = nullptr;
#endif
// Window (main thread) emit signals -> Renderer::send* (main thread) -> event queue (add on main, process on render thread) -> Renderer::renderEvent (render thread)
// event queue is taken from the Qt Quick scenegraph as-is
// all this below is conceptually the same as the QSG threaded render loop
class RenderThreadEventQueue : public QQueue<QEvent *>
{
public:
RenderThreadEventQueue()
: waiting(false)
{
}
void addEvent(QEvent *e) {
mutex.lock();
enqueue(e);
if (waiting)
condition.wakeOne();
mutex.unlock();
}
QEvent *takeEvent(bool wait) {
mutex.lock();
if (isEmpty() && wait) {
waiting = true;
condition.wait(&mutex);
waiting = false;
}
QEvent *e = dequeue();
mutex.unlock();
return e;
}
bool hasMoreEvents() {
mutex.lock();
bool has = !isEmpty();
mutex.unlock();
return has;
}
private:
QMutex mutex;
QWaitCondition condition;
bool waiting;
};
struct Renderer;
struct Thread : public QThread
......@@ -129,15 +177,66 @@ struct Thread : public QThread
Renderer *renderer;
bool active;
RenderThreadEventQueue eventQueue;
bool sleeping = false;
bool stopEventProcessing = false;
bool pendingRender = false;
bool pendingRenderIsNewExpose = false;
// mutex and cond used to allow the main thread waiting until something completes on the render thread
QMutex mutex;
QWaitCondition cond;
};
class RenderThreadEvent : public QEvent
{
public:
RenderThreadEvent(QEvent::Type type) : QEvent(type) { }
};
class InitEvent : public RenderThreadEvent
{
public:
static const QEvent::Type TYPE = QEvent::Type(QEvent::User + 1);
InitEvent() : RenderThreadEvent(TYPE)
{ }
};
class RenderEvent : public RenderThreadEvent
{
public:
static const QEvent::Type TYPE = QEvent::Type(QEvent::User + 2);
RenderEvent(bool newlyExposed_) : RenderThreadEvent(TYPE), newlyExposed(newlyExposed_)
{ }
bool newlyExposed;
};
class SurfaceCleanupEvent : public RenderThreadEvent
{
public:
static const QEvent::Type TYPE = QEvent::Type(QEvent::User + 3);
SurfaceCleanupEvent() : RenderThreadEvent(TYPE)
{ }
};
class CloseEvent : public RenderThreadEvent
{
public:
static const QEvent::Type TYPE = QEvent::Type(QEvent::User + 4);
CloseEvent() : RenderThreadEvent(TYPE)
{ }
};
struct Renderer
{
// ctor and dtor and send* are called main thread, rest on the render thread
Renderer(QWindow *w);
~Renderer();
void sendInit();
void sendRender(bool newlyExposed);
void sendSurfaceGoingAway();
QWindow *window;
Thread *thread;
QRhi *r = nullptr;
......@@ -148,12 +247,43 @@ struct Renderer
void createRhi();
void destroyRhi();
void renderEvent(QEvent *e);
void init();
void releaseSwapChain();
void releaseResources();
void render(bool newlyExposed);
QVector<QRhiResource *> m_releasePool;
bool m_hasSwapChain = false;
QRhiSwapChain *m_sc = nullptr;
QRhiRenderBuffer *m_ds = nullptr;
QRhiRenderPassDescriptor *m_rp = nullptr;
};
void Thread::run()
{
while (active) {
qDebug() << QThread::currentThread();
if (pendingRender) {
pendingRender = false;
renderer->render(pendingRenderIsNewExpose);
}
while (eventQueue.hasMoreEvents()) {
QEvent *e = eventQueue.takeEvent(false);
renderer->renderEvent(e);
delete e;
}
if (active && !pendingRender) {
sleeping = true;
stopEventProcessing = false;
while (!stopEventProcessing) {
QEvent *e = eventQueue.takeEvent(true);
renderer->renderEvent(e);
delete e;
}
sleeping = false;
}
}
#ifndef QT_NO_OPENGL
......@@ -164,10 +294,9 @@ void Thread::run()
Renderer::Renderer(QWindow *w)
: window(w)
{
{ // main thread
thread = new Thread(this);
// ctor and dtor are called main thread, rest on the render thread
#ifndef QT_NO_OPENGL
if (graphicsApi == OpenGL) {
context = new QOpenGLContext;
......@@ -184,8 +313,8 @@ Renderer::Renderer(QWindow *w)
}
Renderer::~Renderer()
{
thread->active = false;
{ // main thread
thread->eventQueue.addEvent(new CloseEvent);
thread->wait();
delete thread;
......@@ -197,6 +326,9 @@ Renderer::~Renderer()
void Renderer::createRhi()
{
if (r)
return;
#ifndef QT_NO_OPENGL
if (graphicsApi == OpenGL) {
QRhiGles2InitParams params;
......@@ -237,17 +369,164 @@ void Renderer::createRhi()
void Renderer::destroyRhi()
{
delete r;
r = nullptr;
}
void Renderer::renderEvent(QEvent *e)
{
Q_ASSERT(QThread::currentThread() == thread);
if (thread->sleeping)
thread->stopEventProcessing = true;
switch (e->type()) {
case InitEvent::TYPE:
qDebug() << "renderer" << this << "for window" << window << "is initializing";
createRhi();
init();
break;
case RenderEvent::TYPE:
thread->pendingRender = true;
thread->pendingRenderIsNewExpose = static_cast<RenderEvent *>(e)->newlyExposed;
break;
case SurfaceCleanupEvent::TYPE: // when the QWindow is closed, before QPlatformWindow goes away
thread->mutex.lock();
qDebug() << "renderer" << this << "for window" << window << "is destroying swapchain";
releaseSwapChain();
thread->cond.wakeOne();
thread->mutex.unlock();
break;
case CloseEvent::TYPE: // when destroying the window+renderer (NB not the same as hitting X on the window, that's just QWindow close)
qDebug() << "renderer" << this << "for window" << window << "is shutting down";
thread->active = false;
thread->stopEventProcessing = true;
releaseResources();
destroyRhi();
break;
default:
break;
}
}
void Renderer::init()
{
m_sc = r->newSwapChain();
m_ds = r->newRenderBuffer(QRhiRenderBuffer::DepthStencil,
QSize(), // no need to set the size yet
1,
QRhiRenderBuffer::ToBeUsedWithSwapChainOnly);
m_releasePool << m_ds;
m_sc->setWindow(window);
m_sc->setDepthStencil(m_ds);
m_rp = m_sc->newCompatibleRenderPassDescriptor();
m_releasePool << m_rp;
m_sc->setRenderPassDescriptor(m_rp);
}
void Renderer::releaseSwapChain()
{
if (m_hasSwapChain) {
m_hasSwapChain = false;
m_sc->release();
}
}
void Renderer::releaseResources()
{
for (QRhiResource *res : m_releasePool)
res->releaseAndDestroy();
m_releasePool.clear();
if (m_sc) {
m_sc->releaseAndDestroy();
m_sc = nullptr;
}
}
void Renderer::render(bool newlyExposed)
{
auto buildOrResizeSwapChain = [this] {
qDebug() << "renderer" << this << "build or resize swapchain for window" << window;
const QSize outputSize = m_sc->surfacePixelSize();
qDebug() << " size is" << outputSize;
m_ds->setPixelSize(outputSize);
m_ds->build();
m_hasSwapChain = m_sc->buildOrResize();
};
if (newlyExposed || m_sc->currentPixelSize() != m_sc->surfacePixelSize())
buildOrResizeSwapChain();
if (!m_hasSwapChain)
return;
QRhi::FrameOpResult result = r->beginFrame(m_sc);
if (result == QRhi::FrameOpSwapChainOutOfDate) {
buildOrResizeSwapChain();
if (!m_hasSwapChain)
return;
result = r->beginFrame(m_sc);
}
if (result != QRhi::FrameOpSuccess)
return;
QRhiCommandBuffer *cb = m_sc->currentFrameCommandBuffer();
cb->beginPass(m_sc->currentFrameRenderTarget(), { 0.4f, 0.7f, 0.0f, 1.0f }, { 1.0f, 0 });
cb->endPass();
r->endFrame(m_sc);
}
void Renderer::sendInit()
{ // main thread
InitEvent *e = new InitEvent;
thread->eventQueue.addEvent(e);
}
void Renderer::sendRender(bool newlyExposed)
{ // main thread
RenderEvent *e = new RenderEvent(newlyExposed);
thread->eventQueue.addEvent(e);
}
void Renderer::sendSurfaceGoingAway()
{ // main thread
SurfaceCleanupEvent *e = new SurfaceCleanupEvent;
// cannot let this thread to proceed with tearing down the native window
// before the render thread completes the swapchain release
thread->mutex.lock();
thread->eventQueue.addEvent(e);
thread->cond.wait(&thread->mutex);
thread->mutex.unlock();
}
class Window : public QWindow
{
Q_OBJECT
public:
Window(const QString &title, const QColor &bgColor, int axis);
~Window();
void exposeEvent(QExposeEvent *) override;
bool event(QEvent *) override;
signals:
void initRequested();
void renderRequested(bool newlyExposed);
void surfaceGoingAway();
protected:
QColor m_bgColor;
int m_rotationAxis = 0;
bool m_running = false;
bool m_notExposed = true;
};
Window::Window(const QString &title, const QColor &bgColor, int axis)
......@@ -282,6 +561,48 @@ Window::~Window()
{
}
void Window::exposeEvent(QExposeEvent *)
{
// initialize and start rendering when the window becomes usable for graphics purposes
if (isExposed() && !m_running) {
m_running = true;
m_notExposed = false;
emit initRequested();
emit renderRequested(true);
}
// stop pushing frames when not exposed (on some platforms this is essential, optional on others)
if (!isExposed() && m_running)
m_notExposed = true;
// continue when exposed again
if (isExposed() && m_running && m_notExposed) {
m_notExposed = false;
emit renderRequested(true);
}
}
bool Window::event(QEvent *e)
{
switch (e->type()) {
case QEvent::UpdateRequest:
if (!m_notExposed)
emit renderRequested(false);
break;
case QEvent::PlatformSurface:
// this is the proper time to tear down the swapchain (while the native window and surface are still around)
if (static_cast<QPlatformSurfaceEvent *>(e)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed)
emit surfaceGoingAway();
break;
default:
break;
}
return QWindow::event(e);
}
struct WindowAndRenderer
{
QWindow *window;
......@@ -294,8 +615,19 @@ void createWindow()
{
static QColor colors[] = { Qt::red, Qt::green, Qt::blue, Qt::yellow, Qt::cyan, Qt::gray };
const int n = windows.count();
QWindow *w = new Window(QString::asprintf("Window #%d", n), colors[n % 6], n % 3);
windows.append({ w, new Renderer(w) });
Window *w = new Window(QString::asprintf("Window #%d", n), colors[n % 6], n % 3);
Renderer *renderer = new Renderer(w);
QObject::connect(w, &Window::initRequested, w, [renderer] {
renderer->sendInit();
});
QObject::connect(w, &Window::renderRequested, w, [w, renderer](bool newlyExposed) {
renderer->sendRender(newlyExposed);
w->requestUpdate();
});
QObject::connect(w, &Window::surfaceGoingAway, w, [renderer] {
renderer->sendSurfaceGoingAway();
});
windows.append({ w, renderer });
w->show();
}
......@@ -378,9 +710,11 @@ int main(int argc, char **argv)
QVBoxLayout *layout = new QVBoxLayout(&w);
QPlainTextEdit *info = new QPlainTextEdit(
QLatin1String("This application tests rendering on a separate thread per window, with dedicated QRhi instances still sharing the same graphics device. "
QLatin1String("This application tests rendering on a separate thread per window, with dedicated QRhi instances. " // ### still sharing the same graphics device where applicable
"No resources are shared across windows here. (so no synchronization mess) "
"Note that this is only safe with D3D/DXGI if the main (gui) thread is not blocked when issuing the Present."
"\n\nNote that this is only safe with D3D/DXGI if the main (gui) thread is not blocked when issuing the Present."
"\n\nThis is the same concept as the Qt Quick Scenegraph's threaded render loop. This should allow rendering to the different windows "
"without unintentionally throttling each other's threads."
"\n\nUsing API: ") + graphicsApiName());
info->setReadOnly(true);
layout->addWidget(info);
......@@ -417,3 +751,5 @@ int main(int argc, char **argv)
return result;
}
#include "multiwindow_threaded.moc"
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment