Skip to content

Commit 5038909

Browse files
committed
Add support for threaded renderer
1 parent 6bb3af7 commit 5038909

9 files changed

Lines changed: 66 additions & 16 deletions

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.16.0)
22

33
project(JlQML)
44

5-
set(JlQML_VERSION 0.8.0)
5+
set(JlQML_VERSION 0.9.0)
66
message(STATUS "Project version: v${JlQML_VERSION}")
77

88
set(CMAKE_MACOSX_RPATH 1)

application_manager.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ void ApplicationManager::exec()
9494
{
9595
app->exit(status);
9696
});
97-
ForeignThreadManager::instance().clear(QThread::currentThread());
9897
const int status = app->exec();
9998
if (status != 0)
10099
{
@@ -112,7 +111,10 @@ void ApplicationManager::add_import_path(std::string path)
112111

113112
ApplicationManager::ApplicationManager()
114113
{
115-
qputenv("QSG_RENDER_LOOP", QProcessEnvironment::systemEnvironment().value("QSG_RENDER_LOOP").toLocal8Bit());
114+
if(QProcessEnvironment::systemEnvironment().contains("QSG_RENDER_LOOP"))
115+
{
116+
qputenv("QSG_RENDER_LOOP", QProcessEnvironment::systemEnvironment().value("QSG_RENDER_LOOP").toLocal8Bit());
117+
}
116118

117119
qInstallMessageHandler(julia_message_output);
118120

@@ -154,6 +156,7 @@ void ApplicationManager::set_engine(QQmlEngine* e)
154156
{
155157
e->addImportPath(QString::fromStdString(path));
156158
}
159+
ForeignThreadManager::instance().clear(QThread::currentThread());
157160
}
158161

159162
void ApplicationManager::process_events()

foreign_thread_manager.cpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#include <QQuickWindow>
2+
13
#include "jlcxx/jlcxx.hpp"
24

35
#include "foreign_thread_manager.hpp"
@@ -34,6 +36,53 @@ void ForeignThreadManager::clear(QThread* main_thread)
3436
m_mutex.unlock();
3537
}
3638

39+
void ForeignThreadManager::add_window(QQuickItem* item)
40+
{
41+
QObject::connect(item, &QQuickItem::windowChanged, [item, m_main_gc_state = JL_GC_STATE_UNSAFE, m_render_gc_state = JL_GC_STATE_UNSAFE] (QQuickWindow* w) mutable
42+
{
43+
if (w == nullptr)
44+
{
45+
return;
46+
}
47+
item->connect(w, &QQuickWindow::sceneGraphInitialized, [] ()
48+
{
49+
// Adopt the render thread when it is first initialized
50+
ForeignThreadManager::instance().add_thread(QThread::currentThread());
51+
});
52+
item->connect(w, &QQuickWindow::afterAnimating, [&m_main_gc_state] ()
53+
{
54+
// afterAnimating is the last signal sent before locking the main loop, so indicate
55+
// that the main loop is in a safe state for the Julia GC to run
56+
jl_ptls_t ptls = jl_current_task->ptls;
57+
m_main_gc_state = jl_gc_safe_enter(ptls);
58+
ptls->engine_nqueued++;
59+
});
60+
item->connect(w, &QQuickWindow::beforeRendering, w, [&m_main_gc_state] ()
61+
{
62+
// beforeRendering is the first signal sent after unlocking the main thread, so restore
63+
// the Julia GC state here. Queued connection so this is executed on the main thread
64+
jl_ptls_t ptls = jl_current_task->ptls;
65+
jl_gc_safe_leave(ptls, m_main_gc_state);
66+
ptls->engine_nqueued--;
67+
}, Qt::QueuedConnection);
68+
item->connect(w, &QQuickWindow::afterFrameEnd, w, [&m_render_gc_state] ()
69+
{
70+
// After the rendering is done, the render thread may block for event handling, so we mark it as in a GC safe state
71+
// to prevent deadlock
72+
jl_ptls_t ptls = jl_current_task->ptls;
73+
m_render_gc_state = jl_gc_safe_enter(ptls);
74+
ptls->engine_nqueued++;
75+
}, Qt::DirectConnection);
76+
item->connect(w, &QQuickWindow::beforeFrameBegin, w, [&m_render_gc_state] ()
77+
{
78+
// Reset GC state when the render thread might execute Julia code again
79+
jl_ptls_t ptls = jl_current_task->ptls;
80+
jl_gc_safe_leave(ptls, m_render_gc_state);
81+
ptls->engine_nqueued--;
82+
}, Qt::DirectConnection);
83+
});
84+
}
85+
3786
ForeignThreadManager::ForeignThreadManager()
3887
{
3988
}

foreign_thread_manager.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include <QSet>
22
#include <QMutex>
3+
#include <QQuickItem>
34
#include <QThread>
45

56
class ForeignThreadManager
@@ -9,6 +10,7 @@ class ForeignThreadManager
910

1011
void add_thread(QThread* t);
1112
void clear(QThread* main_thread);
13+
void add_window(QQuickItem* item);
1214

1315
private:
1416
ForeignThreadManager();

julia_canvas.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,18 @@ namespace qmlwrap
1111

1212
JuliaCanvas::JuliaCanvas(QQuickItem *parent) : QQuickPaintedItem(parent)
1313
{
14+
ForeignThreadManager::instance().add_window(this);
1415
}
1516

1617
void JuliaCanvas::paint(QPainter *painter)
1718
{
18-
ForeignThreadManager::instance().add_thread(QThread::currentThread());
1919
// allocate buffer for julia callback to draw on
2020
int iwidth = width();
2121
int iheight = height();
2222
unsigned int *draw_buffer = new unsigned int[iwidth * iheight];
2323

2424
// call julia painter
25-
m_callback(jlcxx::make_julia_array(draw_buffer, iwidth*iheight), iwidth, iheight);
25+
m_callback(draw_buffer, iwidth, iheight);
2626

2727
// make QImage
2828
QImage *image = new QImage((uchar*)draw_buffer, width(), height(), QImage::Format_ARGB32);
@@ -37,7 +37,7 @@ void JuliaCanvas::paint(QPainter *painter)
3737

3838
void JuliaCanvas::setPaintFunction(jlcxx::SafeCFunction f)
3939
{
40-
m_callback = jlcxx::make_function_pointer<void(jlcxx::ArrayRef<uint>, int, int)>(f);
40+
m_callback = jlcxx::make_function_pointer<void(unsigned int*, int, int)>(f);
4141
}
4242

4343
} // namespace qmlwrap

julia_canvas.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class JuliaCanvas : public QQuickPaintedItem
1818
Q_PROPERTY(jlcxx::SafeCFunction paintFunction READ paintFunction WRITE setPaintFunction)
1919

2020
public:
21-
typedef void (*callback_t)(jlcxx::ArrayRef<unsigned int>, int, int);
21+
typedef void (*callback_t)(unsigned int*, int, int);
2222
JuliaCanvas(QQuickItem *parent = 0);
2323
void paint(QPainter *painter);
2424
void setPaintFunction(jlcxx::SafeCFunction f);

julia_painteditem.cpp

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,6 @@ namespace qmlwrap
1010

1111
JuliaPaintedItem::JuliaPaintedItem(QQuickItem *parent) : QQuickPaintedItem(parent)
1212
{
13-
if(qgetenv("QSG_RENDER_LOOP") != "basic")
14-
{
15-
qFatal("QSG_RENDER_LOOP must be set to basic to use JuliaPaintedItem. Add the line\nENV[\"QSG_RENDER_LOOP\"] = \"basic\"\nat the top of your Julia program");
16-
}
1713
}
1814

1915
void JuliaPaintedItem::paint(QPainter* painter)

opengl_viewport.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
#include <QOpenGLFramebufferObject>
22
#include <QQuickWindow>
3+
#include <QQuickOpenGLUtils>
34
#include <QSGNode>
45
#include <QSGSimpleTextureNode>
56

7+
#include "foreign_thread_manager.hpp"
68
#include "opengl_viewport.hpp"
79

810
namespace qmlwrap
@@ -26,6 +28,7 @@ class OpenGLViewport::JuliaRenderer : public QQuickFramebufferObject::Renderer
2628
m_vp->render();
2729
m_vp->post_render();
2830
m_vp->window()->endExternalCommands();
31+
QQuickOpenGLUtils::resetOpenGLState();
2932
}
3033

3134
void synchronize(QQuickFramebufferObject *item)
@@ -59,12 +62,9 @@ OpenGLViewport::OpenGLViewport(QQuickItem *parent, RenderFunction* render_func)
5962
{
6063
qFatal("OpenGL rendering required for OpenGLViewport or MakieViewport. Add the line\nQML.setGraphicsApi(QML.OpenGL)\nbefore loading the QML program.");
6164
}
62-
if(qgetenv("QSG_RENDER_LOOP") != "basic")
63-
{
64-
qFatal("QSG_RENDER_LOOP must be set to basic to use OpenGLViewport or MakieViewport. Add the line\nENV[\"QSG_RENDER_LOOP\"] = \"basic\"\nat the top of your Julia program");
65-
}
6665
QObject::connect(this, &OpenGLViewport::renderFunctionChanged, this, &OpenGLViewport::update);
6766
setMirrorVertically(true);
67+
ForeignThreadManager::instance().add_window(this);
6868
}
6969

7070
void OpenGLViewport::render()

wrap_qml.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -738,7 +738,7 @@ JLCXX_MODULE define_julia_module(jlcxx::Module& qml_module)
738738
qml_module.method("init_qquickview", []() { return qmlwrap::ApplicationManager::instance().init_qquickview(); });
739739
qml_module.method("cleanup", []() { qmlwrap::ApplicationManager::instance().cleanup(); });
740740
qml_module.method("qmlcontext", []() { return qmlwrap::ApplicationManager::instance().root_context(); });
741-
qml_module.method("exec", []() { qmlwrap::ApplicationManager::instance().exec(); });
741+
qml_module.method("app_exec", []() { qmlwrap::ApplicationManager::instance().exec(); });
742742
qml_module.method("process_events", qmlwrap::ApplicationManager::process_events);
743743
qml_module.method("add_import_path", [](std::string path) { qmlwrap::ApplicationManager::instance().add_import_path(path); });
744744

0 commit comments

Comments
 (0)