Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
5b7c7fb
gfx: add the OffsetAllocator submodule
jcelerier Jul 12, 2026
b824245
gfx: rework the render pipeline to be scene-aware and incrementally r…
jcelerier Jul 12, 2026
f7bdb1d
gfx: add the scene preprocessor and scene filter nodes
jcelerier Jul 12, 2026
5ebd4e7
avnd: make the CPU and GPU node lifecycles scene-aware
jcelerier Jul 12, 2026
8b2c463
js: rework the GPU node lifecycle with deterministic teardown
jcelerier Jul 12, 2026
e9f311a
threedim: adapt ModelDisplay to the new pipeline API
jcelerier Jul 12, 2026
38cd38d
gfx: prefer EGL on xcb and force a desktop OpenGL context
jcelerier Jul 12, 2026
998c6d0
core: tolerate a panel delegate factory that declines to create a widget
jcelerier Jul 12, 2026
e4dafbc
core: fix object teardown order in the minimal applications
jcelerier Jul 12, 2026
6b0b345
gfx: do not return a Vulkan instance when creation failed
jcelerier Jul 12, 2026
3e79986
packagemanager: allow skipping the first-run library download in sani…
jcelerier Jul 12, 2026
4a144f2
gfx: add the NVIDIA GPUDirect-for-Video bridge
jcelerier Jul 12, 2026
c835069
gfx: add a video pixel format vocabulary and libav bridge
jcelerier Jul 12, 2026
5675251
gfx: add high-bit-depth, planar and packed GPU video encoders
jcelerier Jul 12, 2026
aefe2d2
gfx: add the GPU texture-interop layer
jcelerier Jul 12, 2026
a00b969
gfx: add wire-format and hardware video decoders
jcelerier Jul 12, 2026
2499013
gfx: add direct video output and capture graph nodes
jcelerier Jul 12, 2026
26c1f5e
gfx: drive rendering from an external genlock clock
jcelerier Jul 12, 2026
c9e44d2
gfx: add PipeWire video input and output devices
jcelerier Jul 12, 2026
72b9b76
gfx: use the new encoders and dma-buf paths in the existing video bac…
jcelerier Jul 12, 2026
ef992e4
gfx: add GPU encoder and PipeWire round-trip test harnesses
jcelerier Jul 12, 2026
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
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,6 @@
[submodule "3rdparty/opengametools"]
path = 3rdparty/opengametools
url = https://github.com/jpaver/opengametools
[submodule "3rdparty/OffsetAllocator"]
path = 3rdparty/OffsetAllocator
url = https://github.com/sebbbi/OffsetAllocator
1 change: 1 addition & 0 deletions 3rdparty/OffsetAllocator
Submodule OffsetAllocator added at 3610a7
7 changes: 7 additions & 0 deletions src/app/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,9 @@ static void setup_x11(int argc, char** argv)

helper_dylibs.run_under_x11 = true;
helper_dylibs.xwayland = wayland;

// EGL is the only way to get zero-copy with dma-buf import
qputenv("QT_XCB_GL_INTEGRATION", "xcb_egl");
}
}
};
Expand Down Expand Up @@ -499,6 +502,7 @@ static void setup_opengl(bool& enable_opengl_ui)

#ifndef QT_NO_OPENGL
#if (defined(__arm__) || defined(__aarch64__)) && !defined(_WIN32) && !defined(__APPLE__)
// Raspberry Pi & such
QSurfaceFormat fmt = QSurfaceFormat::defaultFormat();
fmt.setRenderableType(QSurfaceFormat::OpenGLES);
fmt.setSwapInterval(1);
Expand All @@ -514,6 +518,7 @@ static void setup_opengl(bool& enable_opengl_ui)
fmt.setDefaultFormat(fmt);
#else
{
// Desktop GL
std::vector<std::pair<int, int>> versions_to_test
= {{4, 6}, {4, 5}, {4, 4}, {4, 3}, {4, 2}, {4, 1}, {4, 0},
{3, 3}, {3, 2}, {3, 1}, {3, 0}, {2, 1}, {2, 0}};
Expand Down Expand Up @@ -552,7 +557,9 @@ static void setup_opengl(bool& enable_opengl_ui)

QSurfaceFormat fmt = QSurfaceFormat::defaultFormat();
fmt.setProfile(QSurfaceFormat::CoreProfile);
fmt.setRenderableType(QSurfaceFormat::OpenGL);
fmt.setSwapInterval(1);
fmt.setStencilBufferSize(8);
bool ok = false;
for(auto [maj, min] : versions_to_test)
{
Expand Down
8 changes: 8 additions & 0 deletions src/lib/core/application/ApplicationInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,14 @@ void GUIApplicationInterface::registerPlugin(Plugin_QtInterface& p)
for(auto& panel_fac : panels_ifaces)
{
auto p = static_cast<score::PanelDelegateFactory*>(panel_fac)->make(context);
// A factory may decline (missing hardware, headless environment); a
// null panel in the list crashes every later panels() iteration.
if(!p)
{
qWarning() << "loadPluginData: panel factory made no panel:"
<< typeid(*panel_fac).name();
continue;
}
p->setModel(std::nullopt); // TODO why not current document
components.panels.push_back(std::move(p));
presenter->view()->setupPanel(components.panels.back().get());
Expand Down
12 changes: 12 additions & 0 deletions src/lib/core/application/ApplicationRegistrar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
#include <score/plugins/application/GUIApplicationPlugin.hpp>
#include <score/plugins/panel/PanelDelegateFactory.hpp>

#include <QDebug>

#include <typeinfo>

#include <core/presenter/Presenter.hpp>
#include <core/settings/Settings.hpp>
#include <core/view/Window.hpp>
Expand Down Expand Up @@ -98,6 +102,14 @@ SCORE_LIB_BASE_EXPORT
void GUIApplicationRegistrar::registerPanel(PanelDelegateFactory& factory)
{
auto panel = factory.make(m_context);
// A factory may decline (e.g. missing hardware / headless environment);
// a null panel in the list would crash every panels() iteration later.
if(!panel)
{
qWarning() << "registerPanel: factory made no panel:"
<< typeid(factory).name();
return;
}
panel->setModel(std::nullopt);

m_components.panels.push_back(std::move(panel));
Expand Down
18 changes: 16 additions & 2 deletions src/lib/core/application/MinimalApplication.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,17 @@ class MinimalApplication final
~MinimalApplication() override
{
this->setParent(nullptr);
// Drain the event queue BEFORE deleting the presenter: deferred slots
// queued during plugin load (e.g. Scenario::SearchWidget's deferred init
// → findDeviceExplorerWidgetInstance) read the presenter-owned application
// context, so dispatching them after delete m_presenter is a use-after-free.
QApplication::processEvents();
delete m_presenter;

QApplication::processEvents();
// The settings models are QObjects owned by value members of this class;
// members destruct after this body, i.e. after the QApplication is gone,
// which Qt >= 6.11 does not survive. Take them down while the app lives.
m_settings.teardownModels();
delete m_app;
}

Expand Down Expand Up @@ -107,9 +115,15 @@ class MinimalGUIApplication final
~MinimalGUIApplication() override
{
this->setParent(nullptr);
// Drain queued slots while m_presenter is still alive (see
// ~MinimalApplication): deferred plugin-load slots read the presenter-owned
// context — dispatching them after delete m_presenter is a use-after-free.
QApplication::processEvents();
delete m_presenter;

QApplication::processEvents();
// See ~MinimalApplication: QObject settings models must not outlive the
// QApplication.
m_settings.teardownModels();
delete m_app;
}

Expand Down
6 changes: 6 additions & 0 deletions src/lib/core/settings/Settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,18 @@ namespace score
Settings::Settings() { }

Settings::~Settings()
{
teardownModels();
}

void Settings::teardownModels() noexcept
{
for(auto& ptr : m_settings)
{
auto p = ptr.release();
delete p; //p->deleteLater();
}
m_settings.clear();
}

void Settings::teardownView()
Expand Down
7 changes: 7 additions & 0 deletions src/lib/core/settings/Settings.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ class SCORE_LIB_BASE_EXPORT Settings final
void setupView();
void teardownView();

/** Destroy the registered settings models. The models are QObjects: they
* must go before the QApplication does. Owners that also own the
* QApplication (the Minimal*Application test scaffolding) call this
* explicitly before deleting the app; the destructor calls it too
* (idempotent) for the normal case where the app outlives us. */
void teardownModels() noexcept;

Settings(const Settings&) = delete;
Settings(Settings&&) = delete;
Settings& operator=(const Settings&) = delete;
Expand Down
6 changes: 6 additions & 0 deletions src/lib/score/gfx/Vulkan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ QVulkanInstance* staticVulkanInstance(bool create)
}
});

// Re-check: on the very first call, create() may just have failed inside
// call_once — returning the half-initialized instance would send callers
// (Graph's API-fallback check, QRhi::create) straight into a crash.
if(g_staticVulkanInstanceInvalid)
return nullptr;

return g_staticVulkanInstance;
}
}
Expand Down
21 changes: 18 additions & 3 deletions src/plugins/score-lib-process/Process/Drop/ProcessDropHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include <ossia/detail/algorithms.hpp>

#include <QDebug>
#include <QFile>
#include <QFileInfo>
#include <QSet>
Expand All @@ -19,8 +20,22 @@ void ProcessDropHandler::getCustomDrops(
std::vector<ProcessDropHandler::ProcessDrop>& drops, const QMimeData& mime,
const score::DocumentContext& ctx) const noexcept
{
// Check for special mime handling code
return dropCustom(drops, mime, ctx);
// dropCustom is no longer noexcept (some overrides invoke parsers that
// can throw on malformed input — see ProcessDropHandler.hpp). Catch
// here so a throwing handler never escapes through the noexcept
// public API and tears down the editor.
try
{
dropCustom(drops, mime, ctx);
}
catch(const std::exception& e)
{
qWarning() << "ProcessDropHandler::dropCustom threw:" << e.what();
}
catch(...)
{
qWarning() << "ProcessDropHandler::dropCustom threw an unknown exception";
}
}

void ProcessDropHandler::getMimeDrops(
Expand Down Expand Up @@ -61,7 +76,7 @@ QSet<QString> ProcessDropHandler::fileExtensions() const noexcept

void ProcessDropHandler::dropCustom(
std::vector<ProcessDropHandler::ProcessDrop>&, const QMimeData& data,
const score::DocumentContext& ctx) const noexcept
const score::DocumentContext& ctx) const
{
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class SCORE_LIB_PROCESS_EXPORT ProcessDropHandler : public score::InterfaceBase
protected:
virtual void dropCustom(
std::vector<ProcessDrop>& drops, const QMimeData& mime,
const score::DocumentContext& ctx) const noexcept;
const score::DocumentContext& ctx) const;

virtual void dropPath(
std::vector<ProcessDrop>& drops, const score::FilePath& path,
Expand Down
30 changes: 29 additions & 1 deletion src/plugins/score-plugin-avnd/Crousti/Concepts.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,35 @@ make_control_in(avnd::field_index<N>, Id<Process::Port>&& id, QObject* parent)
auto [Mx, My, Mz] = c.max;
auto [ix, iy, iz] = c.init;
return new Process::XYZSpinboxes{{mx, my, mz}, {Mx, My, Mz}, {ix, iy, iz},
qname, id, parent};
false, qname, id, parent};
}
}
else if constexpr(widg.widget == avnd::widget_type::xyzw_spinbox)
{
static constexpr auto c = avnd::get_range<T>();
if constexpr(requires {
c.min == 0.f;
c.max == 0.f;
c.init == 0.f;
})
{
return new Process::XYZSpinboxes{
{c.min, c.min, c.min},
{c.max, c.max, c.max},
{c.init, c.init, c.init},
false,
qname,
id,
parent};
}
else
{
auto [mx, my, mz, mw] = c.min;
auto [Mx, My, Mz, Mw] = c.max;
auto [ix, iy, iz, iw] = c.init;
// FIXME we don't have a good 4-way widget
return new Process::XYZSpinboxes{{mx, my, mz}, {Mx, My, Mz}, {ix, iy, iz},
false, qname, id, parent};
}
}
else if constexpr(widg.widget == avnd::widget_type::color)
Expand Down
Loading
Loading