Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.16.0)

project(JlQML)

set(JlQML_VERSION 0.8.0)
set(JlQML_VERSION 0.9.0)
message(STATUS "Project version: v${JlQML_VERSION}")

set(CMAKE_MACOSX_RPATH 1)
Expand Down
7 changes: 5 additions & 2 deletions application_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ void ApplicationManager::exec()
{
app->exit(status);
});
ForeignThreadManager::instance().clear(QThread::currentThread());
const int status = app->exec();
if (status != 0)
{
Expand All @@ -112,7 +111,10 @@ void ApplicationManager::add_import_path(std::string path)

ApplicationManager::ApplicationManager()
{
qputenv("QSG_RENDER_LOOP", QProcessEnvironment::systemEnvironment().value("QSG_RENDER_LOOP").toLocal8Bit());
if(QProcessEnvironment::systemEnvironment().contains("QSG_RENDER_LOOP"))
{
qputenv("QSG_RENDER_LOOP", QProcessEnvironment::systemEnvironment().value("QSG_RENDER_LOOP").toLocal8Bit());
}

qInstallMessageHandler(julia_message_output);

Expand Down Expand Up @@ -154,6 +156,7 @@ void ApplicationManager::set_engine(QQmlEngine* e)
{
e->addImportPath(QString::fromStdString(path));
}
ForeignThreadManager::instance().clear(QThread::currentThread());
}

void ApplicationManager::process_events()
Expand Down
64 changes: 64 additions & 0 deletions foreign_thread_manager.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#include <QQuickWindow>

#include "jlcxx/jlcxx.hpp"

#include "foreign_thread_manager.hpp"
Expand Down Expand Up @@ -34,6 +36,68 @@ void ForeignThreadManager::clear(QThread* main_thread)
m_mutex.unlock();
}

// This was defined after Julia 1.10
#ifndef JL_GC_STATE_UNSAFE
#define JL_GC_STATE_UNSAFE 0
#endif

void _gc_safe_enter(int& state)
{
jl_ptls_t ptls = jl_current_task->ptls;
state = jl_gc_safe_enter(ptls);
#if (JULIA_VERSION_MAJOR * 100 + JULIA_VERSION_MINOR) > 110
ptls->engine_nqueued++;
#endif
}

void _gc_safe_leave(int state)
{
jl_ptls_t ptls = jl_current_task->ptls;
jl_gc_safe_leave(ptls, state);
#if (JULIA_VERSION_MAJOR * 100 + JULIA_VERSION_MINOR) > 110
ptls->engine_nqueued--;
#endif
}

void ForeignThreadManager::add_window(QQuickItem* item)
{
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
{
if (w == nullptr)
{
return;
}
item->connect(w, &QQuickWindow::sceneGraphInitialized, [] ()
{
// Adopt the render thread when it is first initialized
ForeignThreadManager::instance().add_thread(QThread::currentThread());
});
item->connect(w, &QQuickWindow::afterAnimating, [&m_main_gc_state] ()
{
// afterAnimating is the last signal sent before locking the main loop, so indicate
// that the main loop is in a safe state for the Julia GC to run
_gc_safe_enter(m_main_gc_state);
});
item->connect(w, &QQuickWindow::beforeRendering, w, [&m_main_gc_state] ()
{
// beforeRendering is the first signal sent after unlocking the main thread, so restore
// the Julia GC state here. Queued connection so this is executed on the main thread
_gc_safe_leave(m_main_gc_state);
}, Qt::QueuedConnection);
item->connect(w, &QQuickWindow::afterFrameEnd, w, [&m_render_gc_state] ()
{
// After the rendering is done, the render thread may block for event handling, so we mark it as in a GC safe state
// to prevent deadlock
_gc_safe_enter(m_render_gc_state);
}, Qt::DirectConnection);
item->connect(w, &QQuickWindow::beforeFrameBegin, w, [&m_render_gc_state] ()
{
// Reset GC state when the render thread might execute Julia code again
_gc_safe_leave(m_render_gc_state);
}, Qt::DirectConnection);
});
}

ForeignThreadManager::ForeignThreadManager()
{
}
2 changes: 2 additions & 0 deletions foreign_thread_manager.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include <QSet>
#include <QMutex>
#include <QQuickItem>
#include <QThread>

class ForeignThreadManager
Expand All @@ -9,6 +10,7 @@ class ForeignThreadManager

void add_thread(QThread* t);
void clear(QThread* main_thread);
void add_window(QQuickItem* item);

private:
ForeignThreadManager();
Expand Down
6 changes: 3 additions & 3 deletions julia_canvas.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,18 @@ namespace qmlwrap

JuliaCanvas::JuliaCanvas(QQuickItem *parent) : QQuickPaintedItem(parent)
{
ForeignThreadManager::instance().add_window(this);
}

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

// call julia painter
m_callback(jlcxx::make_julia_array(draw_buffer, iwidth*iheight), iwidth, iheight);
m_callback(draw_buffer, iwidth, iheight);

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

void JuliaCanvas::setPaintFunction(jlcxx::SafeCFunction f)
{
m_callback = jlcxx::make_function_pointer<void(jlcxx::ArrayRef<uint>, int, int)>(f);
m_callback = jlcxx::make_function_pointer<void(unsigned int*, int, int)>(f);
}

} // namespace qmlwrap
2 changes: 1 addition & 1 deletion julia_canvas.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class JuliaCanvas : public QQuickPaintedItem
Q_PROPERTY(jlcxx::SafeCFunction paintFunction READ paintFunction WRITE setPaintFunction)

public:
typedef void (*callback_t)(jlcxx::ArrayRef<unsigned int>, int, int);
typedef void (*callback_t)(unsigned int*, int, int);
JuliaCanvas(QQuickItem *parent = 0);
void paint(QPainter *painter);
void setPaintFunction(jlcxx::SafeCFunction f);
Expand Down
4 changes: 0 additions & 4 deletions julia_painteditem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@ namespace qmlwrap

JuliaPaintedItem::JuliaPaintedItem(QQuickItem *parent) : QQuickPaintedItem(parent)
{
if(qgetenv("QSG_RENDER_LOOP") != "basic")
{
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");
}
}

void JuliaPaintedItem::paint(QPainter* painter)
Expand Down
8 changes: 4 additions & 4 deletions opengl_viewport.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#include <QOpenGLFramebufferObject>
#include <QQuickWindow>
#include <QQuickOpenGLUtils>
#include <QSGNode>
#include <QSGSimpleTextureNode>

#include "foreign_thread_manager.hpp"
#include "opengl_viewport.hpp"

namespace qmlwrap
Expand All @@ -26,6 +28,7 @@ class OpenGLViewport::JuliaRenderer : public QQuickFramebufferObject::Renderer
m_vp->render();
m_vp->post_render();
m_vp->window()->endExternalCommands();
QQuickOpenGLUtils::resetOpenGLState();
}

void synchronize(QQuickFramebufferObject *item)
Expand Down Expand Up @@ -59,12 +62,9 @@ OpenGLViewport::OpenGLViewport(QQuickItem *parent, RenderFunction* render_func)
{
qFatal("OpenGL rendering required for OpenGLViewport or MakieViewport. Add the line\nQML.setGraphicsApi(QML.OpenGL)\nbefore loading the QML program.");
}
if(qgetenv("QSG_RENDER_LOOP") != "basic")
{
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");
}
QObject::connect(this, &OpenGLViewport::renderFunctionChanged, this, &OpenGLViewport::update);
setMirrorVertically(true);
ForeignThreadManager::instance().add_window(this);
}

void OpenGLViewport::render()
Expand Down
4 changes: 4 additions & 0 deletions test/test_module_load.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ int main()
throw std::runtime_error("Error opening jlqml lib " + libpath);
}
void* regfunc = nullptr;
#if (JULIA_VERSION_MAJOR * 100 + JULIA_VERSION_MINOR) >= 114
if(!jl_dlsym(lib, "define_julia_module", &regfunc, false, false))
#else
if(!jl_dlsym(lib, "define_julia_module", &regfunc, false))
#endif
{
throw std::runtime_error("Error finding entrypoint define_julia_module");
};
Expand Down
2 changes: 1 addition & 1 deletion wrap_qml.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -738,7 +738,7 @@ JLCXX_MODULE define_julia_module(jlcxx::Module& qml_module)
qml_module.method("init_qquickview", []() { return qmlwrap::ApplicationManager::instance().init_qquickview(); });
qml_module.method("cleanup", []() { qmlwrap::ApplicationManager::instance().cleanup(); });
qml_module.method("qmlcontext", []() { return qmlwrap::ApplicationManager::instance().root_context(); });
qml_module.method("exec", []() { qmlwrap::ApplicationManager::instance().exec(); });
qml_module.method("app_exec", []() { qmlwrap::ApplicationManager::instance().exec(); });
qml_module.method("process_events", qmlwrap::ApplicationManager::process_events);
qml_module.method("add_import_path", [](std::string path) { qmlwrap::ApplicationManager::instance().add_import_path(path); });

Expand Down