Menu

#1560 Device Context cache for Scintilla

Undefined
open
nobody
Scintilla (5)
Patch
18 hours ago
2025-10-12
No

Device Context cache for Scintilla allows to significantly speed-up window refreshing and reduce CPU load in case of "external" paint requests, such as those resulting from AUI layout changes.
The cache works in a very simple way: in ScintillaWX::DoPaint() the output from Editor::Paint() is passed trough the DC cache (wxMemoryDC). From this moment, all paint events are serviced by simply blitting the requested rectangle from the cache, completely bypassing Scintilla' rendering stack. The content of of the cache is invalidated by "normal" editor events - such as keystrokes or scroll events.
To keep memory consumption at a reasonable level, caches are allocated only for active (visible) editors, and this is achieved in EditorManager::OnPageChanged(), which calls EditorBase::DropCaches()

Since I've tested this solution only on GTK3, it is enabled by default only for WXGTK3 or if CB_ENABLE_SCINTILLA_DC_CACHE is defined - for testing on other platforms.

Motivation:
I've observed (on GTK3) that every time the mouse starts and stops moving, CPU load jumps to over 60% (a 3.6GHz CPU).
It turned out that this is caused by scintilla - rendering area under scrollbars during fade-in/fade-out animations.
The DC cache reduced CPU load by ~85% - from 60% to 8% (on average) and AUI layout swiching is now much faster.

1 Attachments

Discussion

  • ollydbg

    ollydbg - 2025-10-13

    Good work, I will test it under Windows.

     
  • ollydbg

    ollydbg - 2025-10-13
    -------------- Build: scintilla in Code::Blocks wx3.3.x (64 bit) (compiler: GNU GCC Compiler)---------------
    
    [ 14.3%] g++.exe -Wall -m64 -g -pipe -mthreads -fmessage-length=0 -fexceptions -DHAVE_W32API_H -D__WXMSW__ -DWXUSINGDLL -DcbDEBUG -DNOPCH -DwxUSE_UNICODE -D_WIN64 -D__WX__ -DWINVER=0x0501 -DLINK_LEXERS -DSCI_LEXER -DWXMAKINGDLL_SCI -std=gnu++11 -iquote.objs33_64\include -I.objs33_64\include -I. -IF:\code\wxWidgets-3.3.1\include -IF:\code\wxWidgets-3.3.1\lib\gcc_dll\mswu -Isdk\wxscintilla\include -Iinclude\tinyxml -Isdk\wxscintilla\src\scintilla\include -Isdk\wxscintilla\src\scintilla\src -Isdk\wxscintilla\src\scintilla\lexlib -c F:\code\codeblocks-src\codeblocks_sfmirror\src\sdk\wxscintilla\src\PlatWX.cpp -o .objs33_64\sdk\wxscintilla\src\PlatWX.o
    [ 28.6%] g++.exe -Wall -m64 -g -pipe -mthreads -fmessage-length=0 -fexceptions -DHAVE_W32API_H -D__WXMSW__ -DWXUSINGDLL -DcbDEBUG -DNOPCH -DwxUSE_UNICODE -D_WIN64 -D__WX__ -DWINVER=0x0501 -DLINK_LEXERS -DSCI_LEXER -DWXMAKINGDLL_SCI -std=gnu++11 -iquote.objs33_64\include -I.objs33_64\include -I. -IF:\code\wxWidgets-3.3.1\include -IF:\code\wxWidgets-3.3.1\lib\gcc_dll\mswu -Isdk\wxscintilla\include -Iinclude\tinyxml -Isdk\wxscintilla\src\scintilla\include -Isdk\wxscintilla\src\scintilla\src -Isdk\wxscintilla\src\scintilla\lexlib -c F:\code\codeblocks-src\codeblocks_sfmirror\src\sdk\wxscintilla\src\scintilla\src\Editor.cxx -o .objs33_64\sdk\wxscintilla\src\scintilla\src\Editor.o
    In file included from F:\code\codeblocks-src\codeblocks_sfmirror\src\sdk\wxscintilla\src\scintilla\src\Editor.cxx:54:
    F:\code\codeblocks-src\codeblocks_sfmirror\src\sdk\wxscintilla\src\scintilla\src\Editor.h:11:10: fatal error: devcontextcache.h: No such file or directory
       11 | #include "devcontextcache.h"
          |          ^~~~~~~~~~~~~~~~~~~
    compilation terminated.
    Process terminated with status 1 (0 minute(s), 3 second(s))
    
    In file included from F:\code\codeblocks-src\codeblocks_sfmirror\src\sdk\wxscintilla\src\ScintillaWX.h:68,
                     from F:\code\codeblocks-src\codeblocks_sfmirror\src\sdk\wxscintilla\src\PlatWX.cpp:70:
    sdk\wxscintilla\src\scintilla\src/Editor.h:11:10: fatal error: devcontextcache.h: No such file or directory
       11 | #include "devcontextcache.h"
          |          ^~~~~~~~~~~~~~~~~~~
    compilation terminated.
    Process terminated with status 1 (0 minute(s), 6 second(s))
    2 error(s), 0 warning(s) (0 minute(s), 6 second(s))
    

    I'm not sure, but it looks like the new devcontextcache.h file should be put in some other place?

     
  • ollydbg

    ollydbg - 2025-10-13

    I put the devcontextcache.h file under the folder: codeblocks_sfmirror\src\sdk\wxscintilla\include, and the cpp file is put in the folder: codeblocks_sfmirror\src\sdk\wxscintilla\src. So, the build continues.

     
  • Tomasz Pawlak

    Tomasz Pawlak - 2025-10-13

    Hi,
    I think the location is correct - it allows to use the cache for other widgets too (in the future). The problem is most likely with what goes to pre-compiled header or with include paths passed to GCC under MSW - I'll try to figure this out.

    Meanwhile, I've discovered that the cache can't be used when window size is reduced and line wrapping is enabled (I never use this option). So here's a trivial fix: Editor::ChangeSize() must call SCI_DC_CACHE_INVALIDATE if wrapping needs to be updated (otherwise size changes can be cached)
    Updated version 2 of the patch attached.

    EDIT:
    @ollydbg:
    from src/sdk/makefile.am:

    AM_CPPFLAGS = $(WX_CXXFLAGS) \
                $(CB_GLIB2_CFLAGS) \
                $(CB_SQUIRREL_CFLAGS) \
                -I$(top_srcdir)/src/include \ <<< THIS
                -I$(top_srcdir)/src/sdk/wxscintilla/include \
                ...
    

    The compilation error suggest that for some reason GCC does not get the "-I$(top_srcdir)/src/include" option - but it is shared for all platforms - so it should work.

     

    Last edit: Tomasz Pawlak 2025-10-13
  • ollydbg

    ollydbg - 2025-10-14

    Hi, thanks for the update.

    From my point of view, I think your added two files devcontextcache.h and devcontextcache.cpp should be put in the SDK level,

    Option 1: the header file should be put here: src/include
    and the cpp file should be put here: src/sdk

    Option 2: if it is only for the wxScintilla control, it should be put inside the folder: src/sdk/wxscintilla/src(for both cpp and header files).

    When I build C::B yesterday with your patch, I'm using the option 2, and I'm using the src/CodeBlocks_wx33_64.workspace under Windows.

     
  • ollydbg

    ollydbg - 2025-10-14

    Another thing I see is that it looks like the file: src/sdk/wxscintilla/src/scintilla/src/Editor.cxx is a more general file, which means every portion of the scintilla should use this file, and the platform dependent file is under the folder src/sdk/wxscintilla/src, not inside the core editor control folder src/sdk/wxscintilla/src/scintilla.

    My question is: can you code change be only inside the platform dependent files?

    Thanks.

     
  • Tomasz Pawlak

    Tomasz Pawlak - 2025-10-14

    Hi,
    For the patch to work, it really doesn't matter which location is chosen - both options will work if compiler will get correct include paths.

    Regarding Editor.cxx: there are 10 occurrences of SCI_DC_CACHE_INVALIDATE macro, which expands to void if the DC cache is not enabled.

    However, while working on this patch I've changed Editor::Redraw():

    void Editor::Redraw() {
        //Platform::DebugPrintf("Redraw all\n");
        PRectangle rcClient = GetClientRectangle();
        wMain.InvalidateRectangle(rcClient);
        if (wMargin.GetID())
            wMargin.InvalidateAll();
    //  wMain.InvalidateAll();
    }
    

    to

    void Editor::Redraw() {
        //Platform::DebugPrintf("Redraw all\n");
    //  PRectangle rcClient = GetClientRectangle();
    //  wMain.InvalidateRectangle(rcClient);
        if (wMargin.GetID())
            wMargin.InvalidateAll();
        wMain.InvalidateAll();
    }
    

    I've changed this, because it hurts my eyes when I see a code which does completely nothing except wasting CPU cycles. Probably I should change it back or add a comment before creating the patch ;)

    Do You wish to have a patch without Editor::Redraw() modification?

     
  • ollydbg

    ollydbg - 7 days ago

    Here are my comments about the patch.

    From my point of view, I think you should keep the scintilla's core code unchanged. So, here is my suggest:

    I see you have made some changes to the file:
    src/sdk/wxscintilla/src/scintilla/src/Editor.cxx

    for example:

    @@ -466,6 +474,7 @@ void Editor::RedrawRect(PRectangle rc) {
            rc.right = rcClient.right;
    
        if ((rc.bottom > rc.top) && (rc.right > rc.left)) {
    +       SCI_DC_CACHE_INVALIDATE;
            wMain.InvalidateRectangle(rc);
        }
     }
    

    But I see the function

    wMain.InvalidateRectangle(rc);

    will go to the function inside the file: sdk\wxscintilla\src\PlatWX.cpp

    void Window::InvalidateRectangle(PRectangle rc) {
        wxRect r = wxRectFromPRectangle(rc);
        GETWIN(wid)->Refresh(false, &r);
    }
    

    So, why not put the code inside the PlatWX.cpp?

    By this method, I think all the code changes can be restricted in the platform code, that is the code

    PlatWX.cpp
    PlatWX.h
    ScintillaWX.cpp
    ScintillaWX.h
    wxscintilla.cpp
    

    Any ideas?

    Thanks.

     
  • Tomasz Pawlak

    Tomasz Pawlak - 5 days ago

    Hi,

    The problem is that Window::InvalidateRectangle() operates on wxWindow base class, so it can't access Editor methods/members. -> crash.

    Meanwhile, I've found and fixed 2 BUGs in DC cache implementation:
    - Disassembly dialog did not invalidated the cache (no editor events) - so it was not updated.
    - Incorrect handling of "abandoned" paints -> brace and occurrences highlighting did not always updated the editor.

    Moreover, I've changed the cache logic by introducing SCI_DC_CACHE_TRIM_ARENA - it's now possible to use cached repaints for almost 100% of "manual" resize events.

    Version 3 of the patch attached.

     
  • ollydbg

    ollydbg - 5 days ago

    The problem is that Window::InvalidateRectangle() operates on wxWindow base class, so it can't access Editor methods/members. -> crash.

    I'm not fully understand this sentence.

    I see in your patch:

    @@ -106,6 +106,10 @@
     Editor::Editor() {
        ctrlID = 0;
    
    +#ifdef SCI_DC_CACHE_ENABLED
    +   DCcache = new devctx_cache(wxDefaultSize);
    +#endif
    +
        stylesValid = false;
        technology = SC_TECHNOLOGY_DEFAULT;
        scaleRGBAImage = 100.0f;
    

    I'm not sure why the DCcache variable should be put inside the Editor class, can this be put in the derived wxWidgets related class?

    And what does the crash here mean?

     
  • ollydbg

    ollydbg - 5 days ago

    All what I expect is the those scintilla core classes should not have any wxWidgets related code, that is, it should be more general code for all platforms, and the derived classes are for any platforms, such as the wxWidgets platforms.

     
  • Tomasz Pawlak

    Tomasz Pawlak - 4 days ago

    Hi,
    During early testing of this patch I've tried to up-cast 'wid' in Window::InvalidateRectangle() to wxScintilla, but that caused random crashes - so I've assumed that there must some other object derived from wxWindow which also uses the generic Window class.
    Today I've confirmed that this is not the case - and the crashes were most likely a result of broken incremental build.

    So, here's v4 of the patch, which accordingly to Your requirements does not touch the core Editor class - please check if this version is OK for You.

     

    Last edit: Tomasz Pawlak 4 days ago
  • ollydbg

    ollydbg - 24 hours ago

    Thanks for your work, I will test your v4 patch under my Windows system as soon as possible.

     
  • ollydbg

    ollydbg - 18 hours ago

    This is the patch file I used to test the Windows (wx 3.3) build of C::B. I have enabled the __WXWIN__ option.

    The result C::B looks OK, I don't see much difference(cpu usage) when i scroll the editor window.

     

Log in to post a comment.

Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.