From b9d2eab695cebe90cbf56690c0e3b09ce485d1c8 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 26 Sep 2024 15:01:32 +0200 Subject: [PATCH] Fix pdlua multi-layer rendering, fix pdlua issue with multi-window --- Libraries/pd-lua | 2 +- Source/Canvas.cpp | 8 ++++ Source/Objects/GraphOnParent.h | 11 +++++ Source/Objects/LuaObject.h | 84 +++++++++++++--------------------- Source/Objects/ObjectBase.h | 3 ++ 5 files changed, 56 insertions(+), 52 deletions(-) diff --git a/Libraries/pd-lua b/Libraries/pd-lua index 046ac53f1..e3a5acd02 160000 --- a/Libraries/pd-lua +++ b/Libraries/pd-lua @@ -1 +1 @@ -Subproject commit 046ac53f1f157ec129ab71c76efce1f6a342974d +Subproject commit e3a5acd0231773b420630872a4f61713fb5b839e diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index 765a3dc61..eeeb88785 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -268,6 +268,14 @@ void Canvas::parentHierarchyChanged() bool Canvas::updateFramebuffers(NVGcontext* nvg, Rectangle invalidRegion) { + for(auto& object : objects) + { + if(object->gui) + { + object->gui->updateFramebuffers(); + } + } + auto pixelScale = getRenderScale(); auto zoom = getValue(zoomScale); diff --git a/Source/Objects/GraphOnParent.h b/Source/Objects/GraphOnParent.h index 80ac2f609..c27152fec 100644 --- a/Source/Objects/GraphOnParent.h +++ b/Source/Objects/GraphOnParent.h @@ -309,6 +309,17 @@ class GraphOnParent final : public ObjectBase { drawTicksForGraph(nvg, graph.get(), this); } } + + void updateFramebuffers() override + { + if(canvas) + { + for(auto& object : canvas->objects) + { + if(object->gui) object->gui->updateFramebuffers(); + } + } + } static void drawTicksForGraph(NVGcontext* nvg, t_glist* x, ObjectBase* parent) { diff --git a/Source/Objects/LuaObject.h b/Source/Objects/LuaObject.h index e86aea175..c0379b287 100644 --- a/Source/Objects/LuaObject.h +++ b/Source/Objects/LuaObject.h @@ -19,7 +19,7 @@ void pdlua_gfx_repaint(t_pdlua* o, int firsttime); } class LuaObject final : public ObjectBase - , public Timer { +{ Colour currentColour; @@ -30,7 +30,6 @@ class LuaObject final : public ObjectBase std::unique_ptr saveDialog; std::map framebuffers; - int activeLayer = -1; struct LuaGuiMessage { t_symbol* symbol; @@ -66,8 +65,8 @@ class LuaObject final : public ObjectBase } }; - std::vector guiCommandBuffer; - moodycamel::ReaderWriterQueue guiMessageQueue; + std::map> guiCommandBuffer; + std::map> guiMessageQueue; static inline std::map> allDrawTargets = std::map>(); @@ -81,7 +80,6 @@ class LuaObject final : public ObjectBase } parentHierarchyChanged(); - startTimerHz(60); } ~LuaObject() @@ -204,7 +202,7 @@ class LuaObject final : public ObjectBase sendRepaintMessage(); } - void handleGuiMessage(t_symbol* sym, int argc, t_atom* argv) + void handleGuiMessage(int layer, t_symbol* sym, int argc, t_atom* argv) { NVGcontext* nvg = cnv->editor->nvgSurface.getRawContext(); if (!nvg) @@ -214,18 +212,16 @@ class LuaObject final : public ObjectBase // First check functions that don't need an active graphics context, of modify the active graphics context switch (hashsym) { case hash("lua_start_paint"): { - if (getLocalBounds().isEmpty() || !argc) + if (getLocalBounds().isEmpty()) break; auto scale = getValue(zoomScale) * 2.0f; // Multiply by 2 for hi-dpi screens int imageWidth = std::ceil(getWidth() * scale); int imageHeight = std::ceil(getHeight() * scale); - int layer = atom_getfloat(argv); if (!imageWidth || !imageHeight) return; - activeLayer = layer; framebuffers[layer].bind(nvg, imageWidth, imageHeight); - + nvgViewport(0, 0, imageWidth, imageHeight); nvgClear(nvg); nvgBeginFrame(nvg, getWidth(), getHeight(), scale); @@ -233,13 +229,8 @@ class LuaObject final : public ObjectBase return; } case hash("lua_end_paint"): { - if(!argc) return; - int layer = atom_getfloat(argv); - if (!framebuffers[layer].isValid()) return; - - activeLayer = -1; - + auto scale = getValue(zoomScale) * 2.0f; // Multiply by 2 for hi-dpi screens nvgGlobalScissor(nvg, 0, 0, getWidth() * scale, getHeight() * scale); nvgEndFrame(nvg); @@ -261,10 +252,7 @@ class LuaObject final : public ObjectBase return; } } - - if (activeLayer < 0 || !framebuffers[activeLayer].isValid()) - return; // If there is no active framebuffer at this point, return - + switch (hashsym) { case hash("lua_set_color"): { if (argc == 1) { @@ -475,31 +463,28 @@ class LuaObject final : public ObjectBase } } - void timerCallback() override + // We need to update the framebuffer in a place where the current graphics context is active (for multi-window support, thanks to Alex for figuring that out) + // but we also need to be outside of calls to beginFrame/endFrame + // So we have this separate callback function that occurs after activating the GPU context, but before starting the frame + void updateFramebuffers() override { LuaGuiMessage guiMessage; - while (guiMessageQueue.try_dequeue(guiMessage)) { - guiCommandBuffer.push_back(guiMessage); - } - - auto* startMesage = pd->generateSymbol("lua_start_paint"); - auto* endMessage = pd->generateSymbol("lua_end_paint"); - - // TODO: this can be optimised more - int numLayers = framebuffers.size() + 2; - for(int layer = 1; layer < numLayers; layer++) { + for(auto& [layer, layerQueue] : guiMessageQueue) { + while (layerQueue.try_dequeue(guiMessage)) { + guiCommandBuffer[layer].push_back(guiMessage); + } + + auto* startMesage = pd->generateSymbol("lua_start_paint"); + auto* endMessage = pd->generateSymbol("lua_end_paint"); + int startIdx = -1, endIdx = -1; - int currentLayer = -1; bool updateScene = false; - for (int i = guiCommandBuffer.size() - 1; i >= 0; i--) { - if (guiCommandBuffer[i].symbol == startMesage) { - currentLayer = atom_getfloat(&guiCommandBuffer[i].data[0]); - if(currentLayer != layer) continue; + for (int i = guiCommandBuffer[layer].size() - 1; i >= 0; i--) { + if (guiCommandBuffer[layer][i].symbol == startMesage) startIdx = i; - } - if (guiCommandBuffer[i].symbol == endMessage) { + if (guiCommandBuffer[layer][i].symbol == endMessage) endIdx = i + 1; - } + if (startIdx != -1 && endIdx != -1) { updateScene = true; break; @@ -509,26 +494,23 @@ class LuaObject final : public ObjectBase if (updateScene) { if (endIdx > startIdx) { for (int i = startIdx; i < endIdx; i++) { - handleGuiMessage(guiCommandBuffer[i].symbol, guiCommandBuffer[i].size, guiCommandBuffer[i].data.data()); + handleGuiMessage(layer, guiCommandBuffer[layer][i].symbol, guiCommandBuffer[layer][i].size, guiCommandBuffer[layer][i].data.data()); } } + guiCommandBuffer[layer].erase(guiCommandBuffer[layer].begin(), guiCommandBuffer[layer].begin() + endIdx); + } + + if (isSelected != object->isSelected() || !framebuffers[layer].isValid()) { + isSelected = object->isSelected(); + sendRepaintMessage(); } - //guiCommandBuffer.erase(guiCommandBuffer.begin() + startIdx, guiCommandBuffer.begin() + endIdx); - } - - guiCommandBuffer.clear(); - - auto needsFramebufferUpdate = framebuffers.size() == 0 || !framebuffers[0].isValid(); - if (isSelected != object->isSelected() || needsFramebufferUpdate) { - isSelected = object->isSelected(); - sendRepaintMessage(); } } - static void drawCallback(void* target, t_symbol* sym, int argc, t_atom* argv) + static void drawCallback(void* target, int layer, t_symbol* sym, int argc, t_atom* argv) { for (auto* object : allDrawTargets[static_cast(target)]) { - object->guiMessageQueue.enqueue({ sym, argc, argv }); + object->guiMessageQueue[layer].enqueue({ sym, argc, argv }); } } diff --git a/Source/Objects/ObjectBase.h b/Source/Objects/ObjectBase.h index 72950b391..5d94746d0 100644 --- a/Source/Objects/ObjectBase.h +++ b/Source/Objects/ObjectBase.h @@ -219,6 +219,9 @@ class ObjectBase : public Component // Flag to make object visible or hidden inside a GraphOnParent virtual bool hideInGraph(); + + // Override function if you need to update framebuffers outside of the render loop (but with the correct active context) + virtual void updateFramebuffers() {}; // Most objects ignore mouseclicks when locked // Objects can override this to do custom locking behaviour