desktop/inc/lib/init.hxx | 3 + desktop/source/lib/init.cxx | 12 +++++- include/sfx2/viewsh.hxx | 2 + sfx2/source/view/viewsh.cxx | 5 ++ sw/inc/view.hxx | 1 sw/inc/viewsh.hxx | 1 sw/qa/core/txtnode/txtnode.cxx | 1 sw/qa/extras/tiledrendering/tiledrendering.cxx | 9 ++++ sw/source/core/inc/viewimp.hxx | 5 ++ sw/source/core/view/viewimp.cxx | 15 +++++++ sw/source/core/view/viewsh.cxx | 49 +++++++++++++++++++++++++ sw/source/uibase/uiview/view.cxx | 7 +++ 12 files changed, 109 insertions(+), 1 deletion(-)
New commits: commit cb60edbc2c0725f0309a5b934f41e999a1fe0c5f Author: Luboš Luňák <[email protected]> AuthorDate: Tue Oct 12 14:24:44 2021 +0200 Commit: Luboš Luňák <[email protected]> CommitDate: Fri Oct 15 13:40:30 2021 +0200 delay, collect and compress LOK invalidations for Writer views Due the to way views are updated on any document change, invalidations are at least O(n^2), and since LOK may use a number of views and for each view the entire document is considered to be the view area, this can lead to a huge number of invalidations that are mostly the same repeated rectangles. Change-Id: I63682958d2fc388344641dcd19fa1d2b39054b51 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/123617 Tested-by: Jenkins Reviewed-by: Luboš Luňák <[email protected]> diff --git a/desktop/inc/lib/init.hxx b/desktop/inc/lib/init.hxx index b31b52eabaa0..9f15377ba0ec 100644 --- a/desktop/inc/lib/init.hxx +++ b/desktop/inc/lib/init.hxx @@ -98,6 +98,8 @@ namespace desktop { void addViewStates(int viewId); void removeViewStates(int viewId); + void setViewId( int viewId ) { m_viewId = viewId; } + // SfxLockCallbackInterface virtual void libreOfficeKitViewCallback(int nType, const char* pPayload) override; virtual void libreOfficeKitViewCallback(int nType, const char* pPayload, int nViewId) override; @@ -177,6 +179,7 @@ namespace desktop { std::map<int, std::string> m_states; std::unordered_map<int, std::unordered_map<int, std::string>> m_viewStates; LibreOfficeKitDocument* m_pDocument; + int m_viewId = -1; // view id of the associated SfxViewShell LibreOfficeKitCallback m_pCallback; void *m_pData; int m_nDisableCallbacks; diff --git a/desktop/source/lib/init.cxx b/desktop/source/lib/init.cxx index 521fe2a18be9..535cf9808d4d 100644 --- a/desktop/source/lib/init.cxx +++ b/desktop/source/lib/init.cxx @@ -2039,6 +2039,14 @@ void CallbackFlushHandler::Invoke() if (!m_pCallback) return; + // Get any pending invalidate tile events. This will call our callbacks, + // so it must be done before taking the mutex. + if(SfxViewShell* viewShell = SfxViewShell::GetFirst( false, + [this](const SfxViewShell* shell) { return shell->GetViewShellId().get() == m_viewId; } )) + { + viewShell->flushPendingLOKInvalidateTiles(); + } + std::scoped_lock<std::mutex> lock(m_mutex); SAL_INFO("lok", "Flushing " << m_queue1.size() << " elements."); @@ -3423,7 +3431,7 @@ static void doc_paintPartTile(LibreOfficeKitDocument* pThis, { if (pViewShell->getPart() == nPart) { - nViewId = static_cast<sal_Int32>(pViewShell->GetViewShellId()); + nViewId = pViewShell->GetViewShellId().get(); doc_setView(pThis, nViewId); break; } @@ -3556,6 +3564,7 @@ static void doc_registerCallback(LibreOfficeKitDocument* pThis, if (SfxViewShell* pViewShell = SfxViewShell::Current()) { + pDocument->mpCallbackFlushHandlers[nView]->setViewId(pViewShell->GetViewShellId().get()); pViewShell->setLibreOfficeKitViewCallback(pDocument->mpCallbackFlushHandlers[nView].get()); } } @@ -3564,6 +3573,7 @@ static void doc_registerCallback(LibreOfficeKitDocument* pThis, if (SfxViewShell* pViewShell = SfxViewShell::Current()) { pViewShell->setLibreOfficeKitViewCallback(nullptr); + pDocument->mpCallbackFlushHandlers[nView]->setViewId(-1); } } } diff --git a/include/sfx2/viewsh.hxx b/include/sfx2/viewsh.hxx index 44201db40e41..c972d67c7f13 100644 --- a/include/sfx2/viewsh.hxx +++ b/include/sfx2/viewsh.hxx @@ -340,6 +340,8 @@ public: virtual void libreOfficeKitViewCallback(int nType, const char* pPayload) const override; virtual void libreOfficeKitViewCallback(int nType, const char* pPayload, int nViewId) const override; virtual void libreOfficeKitViewInvalidateTilesCallback(const tools::Rectangle* pRect, int nPart) const override; + // Performs any pending calls to libreOfficeKitViewInvalidateTilesCallback() as necessary. + virtual void flushPendingLOKInvalidateTiles(); /// Set if we are doing tiled searching. void setTiledSearching(bool bTiledSearching); diff --git a/sfx2/source/view/viewsh.cxx b/sfx2/source/view/viewsh.cxx index 9f5ff0e1ff85..4b12dae1c0ee 100644 --- a/sfx2/source/view/viewsh.cxx +++ b/sfx2/source/view/viewsh.cxx @@ -1527,6 +1527,11 @@ void SfxViewShell::afterCallbackRegistered() { } +void SfxViewShell::flushPendingLOKInvalidateTiles() +{ + // SfxViewShell itself does not delay any tile invalidations. +} + vcl::Window* SfxViewShell::GetEditWindowForActiveOLEObj() const { vcl::Window* pEditWin = nullptr; diff --git a/sw/inc/view.hxx b/sw/inc/view.hxx index a69e0c2e1483..7a5423732a6e 100644 --- a/sw/inc/view.hxx +++ b/sw/inc/view.hxx @@ -692,6 +692,7 @@ public: void SetOldDrwCat(const OUString& sStr); virtual tools::Rectangle getLOKVisibleArea() const override; + virtual void flushPendingLOKInvalidateTiles() override; }; inline tools::Long SwView::GetXScroll() const diff --git a/sw/inc/viewsh.hxx b/sw/inc/viewsh.hxx index 6c0b40302a72..620412dfc227 100644 --- a/sw/inc/viewsh.hxx +++ b/sw/inc/viewsh.hxx @@ -211,6 +211,7 @@ public: bool AddPaintRect( const SwRect &rRect ); void InvalidateWindows( const SwRect &rRect ); + void FlushPendingLOKInvalidateTiles(); /// Invalidates complete Layout (ApplyViewOption). void Reformat(); diff --git a/sw/qa/core/txtnode/txtnode.cxx b/sw/qa/core/txtnode/txtnode.cxx index 43d8b2104b9d..49472c25cecd 100644 --- a/sw/qa/core/txtnode/txtnode.cxx +++ b/sw/qa/core/txtnode/txtnode.cxx @@ -125,6 +125,7 @@ CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testTitleFieldInvalidate) // When typing to the document: pWrtShell->Insert("x"); + pWrtShell->GetSfxViewShell()->flushPendingLOKInvalidateTiles(); // Then make sure that only the text frame at the cursor is invalidated: pDoc->getIDocumentStatistics().GetUpdatedDocStat(/*bCompleteAsync=*/true, /*bFields=*/false); diff --git a/sw/qa/extras/tiledrendering/tiledrendering.cxx b/sw/qa/extras/tiledrendering/tiledrendering.cxx index a96cac16c652..8f637753d1d4 100644 --- a/sw/qa/extras/tiledrendering/tiledrendering.cxx +++ b/sw/qa/extras/tiledrendering/tiledrendering.cxx @@ -438,6 +438,7 @@ void SwTiledRenderingTest::testRegisterCallback() pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(this); // Insert a character at the beginning of the document. pWrtShell->Insert("x"); + pWrtShell->GetSfxViewShell()->flushPendingLOKInvalidateTiles(); // Check that the top left 256x256px tile would be invalidated. CPPUNIT_ASSERT(!m_aInvalidation.IsEmpty()); @@ -854,6 +855,11 @@ namespace { mpViewShell->setLibreOfficeKitViewCallback(nullptr); } + void flushPendingLOKInvalidateTiles() + { + mpViewShell->flushPendingLOKInvalidateTiles(); + } + virtual void libreOfficeKitViewCallback(int nType, const char* pPayload, int /*nViewId*/) override { libreOfficeKitViewCallback(nType, pPayload); // the view id is also included in payload @@ -1037,6 +1043,8 @@ void SwTiledRenderingTest::testMissingInvalidation() pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::DELETE); pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::DELETE); Scheduler::ProcessEventsToIdle(); + aView1.flushPendingLOKInvalidateTiles(); + aView2.flushPendingLOKInvalidateTiles(); CPPUNIT_ASSERT(aView1.m_bTilesInvalidated); CPPUNIT_ASSERT(aView2.m_bTilesInvalidated); } @@ -1715,6 +1723,7 @@ void SwTiledRenderingTest::testCommentEndTextEdit() pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_RETURN); pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_RETURN); Scheduler::ProcessEventsToIdle(); + aView1.flushPendingLOKInvalidateTiles(); CPPUNIT_ASSERT(aView1.m_bTilesInvalidated); } diff --git a/sw/source/core/inc/viewimp.hxx b/sw/source/core/inc/viewimp.hxx index 06e8968d9483..fdd0ed330592 100644 --- a/sw/source/core/inc/viewimp.hxx +++ b/sw/source/core/inc/viewimp.hxx @@ -66,6 +66,8 @@ class SwViewShellImp SwPageFrame *m_pFirstVisiblePage; // Always points to the first visible Page std::unique_ptr<SwRegionRects> m_pPaintRegion; // Collector of Paintrects from the LayAction + std::vector<SwRect> m_pendingLOKInvalidations; + SwLayAction *m_pLayAction; // Is set if an Action object exists // Is registered by the SwLayAction ctor and deregistered by the dtor SwLayIdle *m_pIdleAct; // The same as SwLayAction for SwLayIdle @@ -151,6 +153,9 @@ public: const SwRegionRects* GetPaintRegion() { return m_pPaintRegion.get(); } void DeletePaintRegion() { m_pPaintRegion.reset(); } + void AddPendingLOKInvalidation( const SwRect& rRect ); + std::vector<SwRect> TakePendingLOKInvalidations(); + /// New Interface for StarView Drawing bool HasDrawView() const { return nullptr != m_pDrawView; } SwDrawView* GetDrawView() { return m_pDrawView.get(); } diff --git a/sw/source/core/view/viewimp.cxx b/sw/source/core/view/viewimp.cxx index abb2ef6b174a..f1b2c2531897 100644 --- a/sw/source/core/view/viewimp.cxx +++ b/sw/source/core/view/viewimp.cxx @@ -158,6 +158,21 @@ bool SwViewShellImp::AddPaintRect( const SwRect &rRect ) return false; } +void SwViewShellImp::AddPendingLOKInvalidation( const SwRect& rRect ) +{ + // These are often repeated, so check first for duplicates. + std::vector<SwRect>& l = m_pendingLOKInvalidations; + if( std::find( l.begin(), l.end(), rRect ) == l.end()) + l.push_back( rRect ); +} + +std::vector<SwRect> SwViewShellImp::TakePendingLOKInvalidations() +{ + std::vector<SwRect> ret; + std::swap(ret, m_pendingLOKInvalidations); + return ret; +} + void SwViewShellImp::CheckWaitCursor() { if ( m_pLayAction ) diff --git a/sw/source/core/view/viewsh.cxx b/sw/source/core/view/viewsh.cxx index c1adf1af8337..f97a61c2387a 100644 --- a/sw/source/core/view/viewsh.cxx +++ b/sw/source/core/view/viewsh.cxx @@ -551,6 +551,24 @@ void SwViewShell::InvalidateWindows( const SwRect &rRect ) if ( Imp()->IsCalcLayoutProgress() ) return; + if(comphelper::LibreOfficeKit::isActive()) + { + // First collect all invalidations and perform them only later, + // otherwise the number of Invalidate() calls would be at least + // O(n^2) if not worse. The problem is that if any change in a document + // is made, SwEditShell::EndAllAction() is called, which calls EndAction() + // for every view. And every view does it own handling of paint rectangles, + // and then calls InvalidateWindows() based on that. On then this code + // would call Invalidate() for all views for each rectangle. + // So collect the rectangles, avoid duplicates (which there usually will + // be many because of the repetitions), FlushPendingLOKInvalidateTiles() + // will collect all rectangles from all related views, compress them + // and only with those relatively few rectangle it'd call Invalidate() + // for all views. + Imp()->AddPendingLOKInvalidation(rRect); + return; + } + for(SwViewShell& rSh : GetRingContainer()) { if ( rSh.GetWin() ) @@ -565,6 +583,37 @@ void SwViewShell::InvalidateWindows( const SwRect &rRect ) } } +void SwViewShell::FlushPendingLOKInvalidateTiles() +{ + assert(comphelper::LibreOfficeKit::isActive()); + SwRegionRects rects; + for(SwViewShell& rSh : GetRingContainer()) + { + std::vector<SwRect> tmpRects = rSh.Imp()->TakePendingLOKInvalidations(); + rects.insert( rects.end(), tmpRects.begin(), tmpRects.end()); + } + rects.Compress( SwRegionRects::CompressFuzzy ); + if(rects.empty()) + return; + // This is basically the loop from SwViewShell::InvalidateWindows(). + for(SwViewShell& rSh : GetRingContainer()) + { + if ( rSh.GetWin() ) + { + if ( rSh.IsPreview() ) + { + for( const SwRect& rect : rects ) + ::RepaintPagePreview( &rSh, rect ); + } + else + { + for( const SwRect& rect : rects ) + rSh.GetWin()->Invalidate( rect.SVRect() ); + } + } + } +} + const SwRect& SwViewShell::VisArea() const { // when using the tiled rendering, consider the entire document as our diff --git a/sw/source/uibase/uiview/view.cxx b/sw/source/uibase/uiview/view.cxx index a8d230f33793..9a4b83a28566 100644 --- a/sw/source/uibase/uiview/view.cxx +++ b/sw/source/uibase/uiview/view.cxx @@ -1905,6 +1905,13 @@ tools::Rectangle SwView::getLOKVisibleArea() const return tools::Rectangle(); } +void SwView::flushPendingLOKInvalidateTiles() +{ + SwWrtShell* pSh = GetWrtShellPtr(); + assert(pSh); + pSh->FlushPendingLOKInvalidateTiles(); +} + OUString SwView::GetDataSourceName() const { uno::Reference<lang::XMultiServiceFactory> xFactory(GetDocShell()->GetModel(), uno::UNO_QUERY);
