diff --git a/src/control/settings/Settings.cpp b/src/control/settings/Settings.cpp index 259cf5db..b4dd41f2 100644 --- a/src/control/settings/Settings.cpp +++ b/src/control/settings/Settings.cpp @@ -139,6 +139,9 @@ void Settings::loadDefault() { this->pageRerenderThreshold = 5.0; this->pdfPageCacheSize = 10; + this->preloadPagesBefore = 3U; + this->preloadPagesAfter = 5U; + this->eagerPageCleanup = true; this->selectionBorderColor = 0xff0000U; // red this->selectionMarkerColor = 0x729fcfU; // light blue @@ -398,6 +401,12 @@ void Settings::parseItem(xmlDocPtr doc, xmlNodePtr cur) { this->pageRerenderThreshold = g_ascii_strtod(reinterpret_cast(value), nullptr); } else if (xmlStrcmp(name, reinterpret_cast("pdfPageCacheSize")) == 0) { this->pdfPageCacheSize = g_ascii_strtoll(reinterpret_cast(value), nullptr, 10); + } else if (xmlStrcmp(name, reinterpret_cast("preloadPagesBefore")) == 0) { + this->preloadPagesBefore = g_ascii_strtoull(reinterpret_cast(value), nullptr, 10); + } else if (xmlStrcmp(name, reinterpret_cast("preloadPagesAfter")) == 0) { + this->preloadPagesAfter = g_ascii_strtoull(reinterpret_cast(value), nullptr, 10); + } else if (xmlStrcmp(name, reinterpret_cast("eagerPageCleanup")) == 0) { + this->eagerPageCleanup = xmlStrcmp(value, reinterpret_cast("true")) == 0; } else if (xmlStrcmp(name, reinterpret_cast("selectionBorderColor")) == 0) { this->selectionBorderColor = Color(g_ascii_strtoull(reinterpret_cast(value), nullptr, 10)); } else if (xmlStrcmp(name, reinterpret_cast("selectionMarkerColor")) == 0) { @@ -843,6 +852,9 @@ void Settings::save() { WRITE_INT_PROP(pdfPageCacheSize); WRITE_COMMENT("The count of rendered PDF pages which will be cached."); + WRITE_UINT_PROP(preloadPagesBefore); + WRITE_UINT_PROP(preloadPagesAfter); + WRITE_BOOL_PROP(eagerPageCleanup); WRITE_COMMENT("Config for new pages"); WRITE_STRING_PROP(pageTemplate); @@ -1624,6 +1636,36 @@ void Settings::setPdfPageCacheSize(int size) { save(); } +auto Settings::getPreloadPagesBefore() const -> unsigned int { return this->preloadPagesBefore; } + +void Settings::setPreloadPagesBefore(unsigned int n) { + if (this->preloadPagesBefore == n) { + return; + } + this->preloadPagesBefore = n; + save(); +} + +auto Settings::getPreloadPagesAfter() const -> unsigned int { return this->preloadPagesAfter; } + +void Settings::setPreloadPagesAfter(unsigned int n) { + if (this->preloadPagesAfter == n) { + return; + } + this->preloadPagesAfter = n; + save(); +} + +auto Settings::isEagerPageCleanup() const -> bool { return this->eagerPageCleanup; } + +void Settings::setEagerPageCleanup(bool b) { + if (this->eagerPageCleanup == b) { + return; + } + this->eagerPageCleanup = b; + save(); +} + auto Settings::getBorderColor() const -> Color { return this->selectionBorderColor; } void Settings::setBorderColor(Color color) { diff --git a/src/control/settings/Settings.h b/src/control/settings/Settings.h index af90659a..21f2ca51 100644 --- a/src/control/settings/Settings.h +++ b/src/control/settings/Settings.h @@ -330,6 +330,15 @@ public: int getPdfPageCacheSize() const; [[maybe_unused]] void setPdfPageCacheSize(int size); + unsigned int getPreloadPagesBefore() const; + void setPreloadPagesBefore(unsigned int n); + + unsigned int getPreloadPagesAfter() const; + void setPreloadPagesAfter(unsigned int n); + + bool isEagerPageCleanup() const; + void setEagerPageCleanup(bool b); + string const& getPageTemplate() const; void setPageTemplate(const string& pageTemplate); @@ -937,4 +946,18 @@ private: * e.g. "en_US" */ std::string preferredLocale; + /** + * The number of pages to pre-load before the current page. + */ + unsigned int preloadPagesBefore{}; + + /** + * The number of pages to pre-load after the current page. + */ + unsigned int preloadPagesAfter{}; + + /** + * Whether to evict from the page buffer cache when scrolling. + */ + bool eagerPageCleanup{}; }; diff --git a/src/gui/XournalView.cpp b/src/gui/XournalView.cpp index d584b738..543ba90d 100644 --- a/src/gui/XournalView.cpp +++ b/src/gui/XournalView.cpp @@ -1,5 +1,6 @@ #include "XournalView.h" +#include #include #include #include @@ -24,6 +25,14 @@ #include "XournalppCursor.h" #include "filesystem.h" +std::pair XournalView::preloadPageBounds(size_t page, size_t maxPage) { + const size_t preloadBefore = this->control->getSettings()->getPreloadPagesBefore(); + const size_t preloadAfter = this->control->getSettings()->getPreloadPagesAfter(); + const size_t lower = page > preloadBefore ? page - preloadBefore : 0; + const size_t upper = std::min(maxPage, page + preloadAfter); + return {lower, upper}; +} + XournalView::XournalView(GtkWidget* parent, Control* control, ScrollHandling* scrollHandling): scrollHandling(scrollHandling), control(control) { this->cache = new PdfCache(control->getSettings()->getPdfPageCacheSize()); @@ -87,40 +96,24 @@ void XournalView::staticLayoutPages(GtkWidget* widget, GtkAllocation* allocation xv->layoutPages(); } -auto XournalView::clearMemoryTimer(XournalView* widget) -> gboolean { - GList* list = nullptr; - - for (auto&& page: widget->viewPages) { - if (page->getLastVisibleTime() > 0) { - list = g_list_insert_sorted(list, page, reinterpret_cast(pageViewIncreasingClockTime)); - } - } - int pixel = 2884560; - int firstPages = 4; - - int i = 0; +auto XournalView::clearMemoryTimer(XournalView* widget) -> gboolean { + widget->cleanupBufferCache(); + return true; +} - for (GList* l = g_list_last(list); l != nullptr; l = l->prev) // older (higher time) to newer (lower time) - { - if (firstPages) { - firstPages--; - } else { - auto* v = static_cast(l->data); +auto XournalView::cleanupBufferCache() -> void { + const auto& [pagesLower, pagesUpper] = this->preloadPageBounds(this->currentPage, this->viewPages.size()); + g_assert(pagesLower <= pagesUpper); - if (pixel <= 0) { - v->deleteViewBuffer(); - } else { - pixel -= v->getBufferPixels(); - } + for (size_t i = 0; i < this->viewPages.size(); i++) { + auto&& page = this->viewPages[i]; + const size_t pageNum = i + 1; + const bool isPreload = pagesLower <= pageNum && pageNum <= pagesUpper; + if (!isPreload && page->getLastVisibleTime() > 0 && page->getBufferPixels() > 0) { + page->deleteViewBuffer(); } - i++; } - - g_list_free(list); - - // call again - return true; } auto XournalView::getCurrentPage() const -> size_t { return currentPage; } @@ -378,6 +371,19 @@ void XournalView::pageSelected(size_t page) { control->updatePageNumbers(currentPage, pdfPage); control->updateBackgroundSizeButton(); + + if (control->getSettings()->isEagerPageCleanup()) { + this->cleanupBufferCache(); + } + + // Load surrounding pages if they are not + const auto& [pagesLower, pagesUpper] = preloadPageBounds(page, this->viewPages.size()); + g_assert(pagesLower <= pagesUpper); + for (size_t i = pagesLower; i < pagesUpper; i++) { + if (this->viewPages[i]->getBufferPixels() == 0) { + this->viewPages[i]->rerenderPage(); + } + } } auto XournalView::getControl() -> Control* { return control; } diff --git a/src/gui/XournalView.h b/src/gui/XournalView.h index 899181cf..e1fd8b3e 100644 --- a/src/gui/XournalView.h +++ b/src/gui/XournalView.h @@ -140,12 +140,14 @@ public: private: void fireZoomChanged(); - void addLoadPageToQue(PageRef page, int priority); + std::pair preloadPageBounds(size_t page, size_t maxPage); Rectangle* getVisibleRect(size_t page); static gboolean clearMemoryTimer(XournalView* widget); + void cleanupBufferCache(); + static void staticLayoutPages(GtkWidget* widget, GtkAllocation* allocation, void* data); private: diff --git a/src/gui/dialog/SettingsDialog.cpp b/src/gui/dialog/SettingsDialog.cpp index 5fbf4e19..27741ead 100644 --- a/src/gui/dialog/SettingsDialog.cpp +++ b/src/gui/dialog/SettingsDialog.cpp @@ -487,6 +487,12 @@ void SettingsDialog::load() { loadCheckbox("cbHidePresentationSidebar", hidePresentationSidebar); loadCheckbox("cbHideMenubarStartup", settings->isMenubarVisible()); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(get("preloadPagesBefore")), + static_cast(settings->getPreloadPagesBefore())); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(get("preloadPagesAfter")), + static_cast(settings->getPreloadPagesAfter())); + loadCheckbox("cbEagerPageCleanup", settings->isEagerPageCleanup()); + enableWithCheckbox("cbAutosave", "boxAutosave"); enableWithCheckbox("cbIgnoreFirstStylusEvents", "spNumIgnoredStylusEvents"); enableWithCheckbox("cbAddVerticalSpace", "spAddVerticalSpace"); @@ -706,6 +712,16 @@ void SettingsDialog::save() { settings->setMenubarVisible(getCheckbox("cbHideMenubarStartup")); + constexpr auto spinAsUint = [&](GtkSpinButton* btn) { + int v = gtk_spin_button_get_value_as_int(btn); + return v < 0 ? 0U : static_cast(v); + }; + unsigned int preloadPagesBefore = spinAsUint(GTK_SPIN_BUTTON(get("preloadPagesBefore"))); + unsigned int preloadPagesAfter = spinAsUint(GTK_SPIN_BUTTON(get("preloadPagesAfter"))); + settings->setPreloadPagesAfter(preloadPagesAfter); + settings->setPreloadPagesBefore(preloadPagesBefore); + settings->setEagerPageCleanup(getCheckbox("cbEagerPageCleanup")); + settings->setDefaultSaveName(gtk_entry_get_text(GTK_ENTRY(get("txtDefaultSaveName")))); // Todo(fabian): use Util::fromGFilename! char* uri = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(get("fcAudioPath"))); diff --git a/ui/settings.glade b/ui/settings.glade index c4339ec1..3f69f832 100644 --- a/ui/settings.glade +++ b/ui/settings.glade @@ -48,6 +48,16 @@ 1 1 + + 99 + 1 + 10 + + + 99 + 1 + 10 + 0.5 4 @@ -3227,6 +3237,106 @@ This also enables touch drawing.</i> 5 + + + True + False + 0.009999999776482582 + + + True + False + 12 + + + + True + False + 10 + + + True + False + start + Pre-load pages before + + + 0 + 0 + + + + + True + False + start + Pre-load pages after + + + 0 + 1 + + + + + preloadPagesBefore + True + True + number + adjustmentPreloadPagesBefore + True + + + 1 + 0 + + + + + preloadPagesAfter + True + True + number + adjustmentPreloadPagesAfter + True + + + 1 + 1 + + + + + Clear cached paged while scrolling + True + True + False + True + + + 0 + 2 + 2 + + + + + + + + + True + False + Performance + + + + + False + True + 6 + +