office-gobmx/vcl/skia
Patrick Luby 0807fe3628 Improve rendering speed for native controls when using Skia/Metal
While debugging tdf#163945, Xcode's Instruments application uncovered
the following performance bottlenecks when using Skia/Metal:

1. Very slow rendering an NSBox:
   Many system colors have the NSColorTypeCatalog color type. For
   some unkown reason, setting the NSBox's fill color to a color set
   to NSColorTypeCatalog causes drawing to take at least twice as
   long as when the fill color is set to NSColorTypeComponentBased.
   So, only draw with a fill color set to NSColorTypeComponentBased.

2. Excessively large offscreen buffers when drawing native controls:
   The temporary bitmap was set to the control region expanded by
   50 * mScaling (e.g. both width and height were increased by 200
   pixels when running on a Retina display). This caused temporary
   bitmaps to be up to several times larger than needed. Also,
   drawing NSBox objects to a CGBitmapContext is noticeably slow
   so filling all that unneeded temporary bitmap area can slow down
   performance when a large number of NSBox objects like the status
   bar are redrawn in quick succession.
   Using getNativeControlRegion() isn't perfect, but it does try to
   account for the focus ring as well as the minimum width and/or
   height of the native control so union the two regions set by
   getNativeControlRegion() and add double the focus ring width on
   each side just to be safe. In most cases, this should ensure
   that the temporary bitmap is large enough to draw the entire
   native control and a focus ring.

3. Unncessary copying of bitmap buffer when drawing native controls:
   Let Skia own the CGBitmapContext's buffer so that an SkImage
   can be created without Skia making a copy of the buffer.

Change-Id: Ibd3abb4b9d7045c47268319772fe97a5c4dba3c6
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/177225
Tested-by: Jenkins
Reviewed-by: Patrick Luby <guibomacdev@gmail.com>
2024-11-25 19:30:21 +01:00
..
osx Improve rendering speed for native controls when using Skia/Metal 2024-11-25 19:30:21 +01:00
quartz
win SalGraphics does not need virtual freeResources() 2024-11-18 20:51:39 +01:00
x11 Simplify SalGraphicsImpl::Init 2024-11-19 16:55:43 +01:00
gdiimpl.cxx tdf#157312 and tdf#163945 Lower Skia flush timer priority on macOS 2024-11-24 14:45:24 +01:00
README
salbmp.cxx
skia_denylist_vulkan.xml
SkiaHelper.cxx
zone.cxx

This is code for using the Skia library as a drawing library in VCL backends.
See external/skia for info on the library itself.

Environment variables:
======================

See README.vars in the toplevel vcl/ directory. Note that many backends do not
use Skia. E.g. on Linux it is necessary to also use SAL_USE_VCLPLUGIN=gen .

There are also GUI options for controlling whether Skia is enabled.

Skia drawing methods:
=====================

Skia supports several methods to draw:
- Raster - CPU-based drawing (here primarily used for fallback when Vulkan isn't available or for debugging)
- Vulkan - Vulkan-based GPU drawing, this is the default
- Metal - MACOSX GPU drawing, this is the Mac default

There are more (OpenGL, Metal on Mac, etc.), but (as of now) they are not supported by VCL.

Logging:
========

Run LO with 'SAL_LOG=+INFO.vcl.skia' to get log information about Skia including
tracing each drawing operation. If you want log information without drawing operations,
use 'SAL_LOG=+INFO.vcl.skia-INFO.vcl.skia.trace'.

Debugging:
==========

Both SkiaSalBitmap and SkiaSalGraphicsImpl have a dump() method that writes a PNG
with the current contents. There is also SkiaHelper::dump() for dumping contents
of SkBitmap, SkImage and SkSurface. You can use these in a debugger too, for example
'p SkiaHelper::dump(image, "/tmp/a.png")'.

If there is a drawing problem, you can use something like the following piece of code
to dump an image after each relevant operation (or do it in postDraw() if you don't
know which operation is responsible). You can then find the relevant image
and match it with the responsible operation (run LO with 'SAL_LOG=+INFO.vcl.skia').

    static int cnt = 0;
    ++cnt;
    char buf[100];
    sprintf(buf,"/tmp/a%05d.png", cnt);
    SAL_DEBUG("CNT:" << cnt);
    if(cnt > 4000) // skip some initial drawing operations
        dump(buf);


Testing:
========

Currently unittests always use the 'headless' VCL backend. Use something like the following
to run VCL unittests with Skia (and possibly skip slowcheck):

SAL_SKIA=raster SAL_ENABLESKIA=1 SAL_USE_VCLPLUGIN=gen make vcl.build vcl.unitcheck vcl.slowcheck

You can also use 'visualbackendtest' to visually check some operations. Use something like:

SAL_SKIA=raster SAL_ENABLESKIA=1 SAL_USE_VCLPLUGIN=gen [srcdir]/bin/run visualbackendtest


Thread safety:
==============

SolarMutex must be held for most operations (asserted in SkiaSalGraphicsImpl::preDraw() and
in SkiaZone constructor). The reason for this is that this restriction does not appear to be
a problem, so there's no need to verify thread safety of the code (including the Skia library).
There's probably no fundamental reason why the code couldn't be made thread-safe.


GrDirectContext sharing:
========================

We use Skia's sk_app::WindowContext class for creating surfaces for windows, that class
takes care of the internals. But of offscreen drawing, we need an instance of class
GrDirectContext. There is sk_app::WindowContext::getGrDirectContext(), but each instance creates
its own GrDirectContext, and apparently it does not work to mix them. Which means that
for offscreen drawing we would need to know which window (and only that window)
the contents will be eventually painted to, which is not possible (it may not even
be known at the time).

To solve this problem we patch sk_app::WindowContext to create just one GrDirectContext object
and share it between instances. Additionally, using sk_app::WindowContext::SharedGrDirectContext
it is possible to share it also for offscreen drawing, including keeping proper reference
count.