Graphics preview in parallel
Pavel Sanda <[email protected]> Tue, 23 Jun 2026 15:52:12 +0200
| Newsgroups | gmane.editors.lyx.devel |
|---|---|
| Message-ID | <[email protected]> |
Hi all, it's my long-term pain to wait for lyx loading the graphics when conversion chain is needed for the view. With longer reports with tons of images one has to wait while most of the cores are idling. Years back I created some patch to parallelize the preview chain, but it kept crashing for strange reason. I looked into the issue again now with claude and have decent version which does not crash anymore. 1. The attached code triggers in parallel min(4, #of threads on your machine) conversion chains and thus process the conversions fater. I kept the cap on 4 so we don't overload the machine because of RAM. Might slow initial load times on some machines. I prefer it this way but I could see someone might not like it, objections? 2. I am introducing lfun which would trigger autoload of all previews. I found myself regularly to scroll through the whole document, just that the load process start and I dont have to wait later when working with the rest of document. That's somewhat ridiculous. I do not propose to add this to UI and keep it for power user who can bind it by shortcut. Completely different approach would be just autoload everything on start by default, but I don't like that option - the preview can take on longer documents quite a while and slows down all editation. It's in the second patch, and it does not interfere with the rest of the codebase, so should be less controversial. Currently childern are included, but I am thinking of striping that part. Anyway are there some objections to include this lfun? Pavel -- lyx-devel mailing list [email protected] https://lists.lyx.org/mailman/listinfo/lyx-devel
4_threads_preview.patch
(text/x-diff, 2.2 KB)
diff --git a/src/support/ForkedCalls.cpp b/src/support/ForkedCalls.cpp
index 054290d0dd..539bf301db 100644
--- a/src/support/ForkedCalls.cpp
+++ b/src/support/ForkedCalls.cpp
@@ -443,18 +443,19 @@ typedef pair<string, ForkedCall::sigPtr> Process;
/// in-progress queue
static queue<Process> callQueue_;
-/// flag whether queue is running
-static bool running_ = false;
+/// Max threads consuming the conversion chain for graphics preview
+static int const MAX_RUNNING = 4;
+
+/// Current threads running
+static int running_count_ = 0;
///
-void startCaller();
-///
-void stopCaller();
+void callNext();
///
void callback(pid_t, int);
-/** Add a process to the queue. Processes are forked sequentially
- * only one is running at a time.
+/** Add a process to the queue. Up to MAX_RUNNING processes are
+ * forked in parallel; the remainder wait for a slot to free up.
* Connect to the returned signal and you'll be informed when
* the process has ended.
*/
@@ -463,8 +464,10 @@ ForkedCall::sigPtr add(string const & process)
ForkedCall::sigPtr ptr;
ptr.reset(new ForkedCall::sig);
callQueue_.push(Process(process, ptr));
- if (!running_)
- startCaller();
+ if (running_count_ == 0)
+ LYXERR(Debug::GRAPHICS, "ForkedCallQueue: waking up");
+ while (running_count_ < MAX_RUNNING && !callQueue_.empty())
+ callNext();
return ptr;
}
@@ -477,6 +480,7 @@ void callNext()
callQueue_.pop();
// Bind our chain caller
pro.second->connect(callback);
+ ++running_count_;
ForkedCall call;
//If we fail to fork the process, then emit the signal
//to tell the outside world that it failed.
@@ -487,31 +491,17 @@ void callNext()
void callback(pid_t, int)
{
- if (callQueue_.empty())
- stopCaller();
- else
+ --running_count_;
+ while (running_count_ < MAX_RUNNING && !callQueue_.empty())
callNext();
-}
-
-
-void startCaller()
-{
- LYXERR(Debug::GRAPHICS, "ForkedCallQueue: waking up");
- running_ = true ;
- callNext();
-}
-
-
-void stopCaller()
-{
- running_ = false ;
- LYXERR(Debug::GRAPHICS, "ForkedCallQueue: I'm going to sleep");
+ if (running_count_ == 0 && callQueue_.empty())
+ LYXERR(Debug::GRAPHICS, "ForkedCallQueue: I'm going to sleep");
}
bool running()
{
- return running_;
+ return running_count_ > 0;
}
} // namespace ForkedCallQueue
buffer-load-graphics.patch
(text/x-diff, 4.9 KB)
diff --git a/src/Buffer.cpp b/src/Buffer.cpp
index 8122a05843..1f4e139330 100644
--- a/src/Buffer.cpp
+++ b/src/Buffer.cpp
@@ -70,6 +70,7 @@
#include "xml.h"
#include "insets/InsetBranch.h"
+#include "insets/InsetGraphics.h"
#include "insets/InsetInclude.h"
#include "insets/InsetText.h"
@@ -2888,6 +2889,10 @@ bool Buffer::getStatus(FuncRequest const & cmd, FuncStatus & flag) const
enable = (d->preview_file_).exists() && !(d->preview_file_).isFileEmpty();
break;
+ case LFUN_BUFFER_LOAD_GRAPHICS:
+ enable = true;
+ break;
+
case LFUN_CHANGES_TRACK:
flag.setEnabled(true);
flag.setOnOff(params().track_changes);
@@ -3163,6 +3168,24 @@ void Buffer::dispatch(FuncRequest const & func, DispatchResult & dr)
dr.setMessage(_("Error viewing the output file."));
break;
+ case LFUN_BUFFER_LOAD_GRAPHICS: {
+ ListOfBuffers bufs = getDescendants();
+ bufs.push_front(this);
+ int counted = 0;
+ for (Buffer * b : bufs) {
+ InsetIterator it = begin(b->inset());
+ InsetIterator const itend = end(b->inset());
+ for (; it != itend; ++it) {
+ if (it->lyxCode() == GRAPHICS_CODE) {
+ static_cast<InsetGraphics const &>(*it).preload();
+ ++counted;
+ }
+ }
+ }
+ //dr.setMessage(bformat(_("Queued %1$d image(s) for preview."), counted));
+ break;
+ }
+
case LFUN_CHANGES_TRACK:
if (params().save_transient_properties)
undo().recordUndoBufferParams();
diff --git a/src/FuncCode.h b/src/FuncCode.h
index 0a56683779..5c3259dd68 100644
--- a/src/FuncCode.h
+++ b/src/FuncCode.h
@@ -461,6 +461,7 @@ enum FuncCode
LFUN_SEPARATOR_INSERT, // ef 20140502
LFUN_SERVER_GET_STATISTICS, // brokenclock 20141010
LFUN_BUFFER_VIEW_CACHE, // skostysh 20150401
+ LFUN_BUFFER_LOAD_GRAPHICS, // ps 20260623
// 360
LFUN_BUFFER_MOVE_NEXT, // skostysh 20150408
LFUN_BUFFER_MOVE_PREVIOUS, // skostysh 20150408
diff --git a/src/LyXAction.cpp b/src/LyXAction.cpp
index 0e0826ead4..3da6433db2 100644
--- a/src/LyXAction.cpp
+++ b/src/LyXAction.cpp
@@ -904,6 +904,16 @@ void LyXAction::init()
*/
{ LFUN_BUFFER_VIEW_CACHE, "buffer-view-cache", ReadOnly, Buffer },
+/*!
+ * \var lyx::FuncCode lyx::LFUN_BUFFER_LOAD_GRAPHICS
+ * \li Action: Start preview conversion for every image in the current buffer/childern,
+ instead of waiting for each image to be scrolled into view.
+ * \li Syntax: buffer-load-graphics
+ * \li Origin: ps, 23 Jun 2026
+ * \endvar
+ */
+ { LFUN_BUFFER_LOAD_GRAPHICS, "buffer-load-graphics", ReadOnly, Buffer },
+
/*!
* \var lyx::FuncCode lyx::LFUN_BUFFER_WRITE
* \li Action: Saves the current buffer.
diff --git a/src/insets/InsetGraphics.cpp b/src/insets/InsetGraphics.cpp
index 0ca24e0488..c087533905 100644
--- a/src/insets/InsetGraphics.cpp
+++ b/src/insets/InsetGraphics.cpp
@@ -1157,6 +1157,13 @@ InsetGraphicsParams const & InsetGraphics::params() const
}
+void InsetGraphics::preload() const
+{
+ if (graphic_)
+ graphic_->preload();
+}
+
+
void InsetGraphics::editGraphics(InsetGraphicsParams const & p) const
{
theFormats().edit(buffer(), p.filename,
diff --git a/src/insets/InsetGraphics.h b/src/insets/InsetGraphics.h
index 95289832cb..7f7cb2e81d 100644
--- a/src/insets/InsetGraphics.h
+++ b/src/insets/InsetGraphics.h
@@ -91,6 +91,8 @@ public:
docstring layoutName() const override { return from_ascii("Graphics"); }
/// Get the inset parameters, used by the GUIndependent dialog.
InsetGraphicsParams const & params() const;
+ /// Force preview generation now (skip the wait-for-paint).
+ void preload() const;
///
int topOffset(BufferView const *) const override { return 0; }
diff --git a/src/insets/RenderGraphic.cpp b/src/insets/RenderGraphic.cpp
index 67d44e1bb4..5f88372953 100644
--- a/src/insets/RenderGraphic.cpp
+++ b/src/insets/RenderGraphic.cpp
@@ -51,6 +51,7 @@ RenderBase * RenderGraphic::clone(Inset const * inset) const
return new RenderGraphic(*this, inset);
}
+
void RenderGraphic::reload() const
{
loader_.reload();
@@ -132,6 +133,17 @@ bool readyToDisplay(graphics::Loader const & loader)
} // namespace
+void RenderGraphic::preload() const
+{
+ if (!displayGraphic(params_))
+ return;
+ // ErrorConverting is included so we retry failed conversions.
+ if (loader_.status() == graphics::WaitingToLoad
+ || loader_.status() == graphics::ErrorConverting)
+ loader_.startLoading();
+}
+
+
void RenderGraphic::metrics(MetricsInfo & mi, Dimension & dim) const
{
if (displayGraphic(params_)) {
diff --git a/src/insets/RenderGraphic.h b/src/insets/RenderGraphic.h
index fc631a0c7a..2b175008a3 100644
--- a/src/insets/RenderGraphic.h
+++ b/src/insets/RenderGraphic.h
@@ -35,6 +35,8 @@ public:
/// Refresh the info about which file to display and how to display it.
void update(graphics::Params const & params);
+ /// Trigger conversion, no-op if the image is already loading, loaded.
+ void preload() const;
/// Reloads the image if necessary
void reload() const;