avoid Xlib cairo surfaces for small virtual devices (bsc#1183308)

The (private :( ) document contains a large number of small shapes
for which VclProcessor2D::RenderTransparencePrimitive2D() gets
called, which results in GetBitmapEx() on the VirtualDevice.
And Cairo normally needs to do a roundtrip to the XServer to fetch
the content, which makes the rendering pretty slow. Forcing
image-based surface for small sizes of VirtualDevice actually has
better performance for the document, and the lack of possible
HW acceleration generally shouldn't matter for a surface this small.

Additionally drawinglayer's VirtualDevice reusing tries to reuse
even a large one when a small one is needed, so a hack is needed
to avoid that in this case, I couldn't find a better way.

Change-Id: I0f58659ab88165a6bd915f100fc3b1d4802a0fa9
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/128309
Tested-by: Jenkins
Reviewed-by: Luboš Luňák <l.lunak@collabora.com>
This commit is contained in:
Luboš Luňák 2022-01-11 19:08:50 +01:00
parent bbabe9c8dd
commit cf9be3417b
4 changed files with 62 additions and 21 deletions

View file

@ -63,6 +63,8 @@ private:
// virtualdevice because that isn't safe to do at least for Gtk2
std::map<VclPtr<VirtualDevice>, VclPtr<OutputDevice>> maDeviceTemplates;
static bool isSizeSuitable(const VclPtr<VirtualDevice>& device, const Size& size);
public:
VDevBuffer();
virtual ~VDevBuffer() override;
@ -98,6 +100,28 @@ VDevBuffer::~VDevBuffer()
}
}
bool VDevBuffer::isSizeSuitable(const VclPtr<VirtualDevice>& device, const Size& rSizePixel)
{
if (device->GetOutputWidthPixel() >= rSizePixel.getWidth()
&& device->GetOutputHeightPixel() >= rSizePixel.getHeight())
{
#if defined(UNX)
// HACK: See the small size handling in SvpSalVirtualDevice::CreateSurface().
// Make sure to not reuse a larger device when a small one should be preferred.
if (device->GetRenderBackendName() == "svp")
{
if (rSizePixel.getWidth() <= 32 && rSizePixel.getHeight() <= 32
&& (device->GetOutputWidthPixel() > 32 || device->GetOutputHeightPixel() > 32))
{
return false;
}
}
#endif
return true;
}
return false;
}
VclPtr<VirtualDevice> VDevBuffer::alloc(OutputDevice& rOutDev, const Size& rSizePixel,
bool bTransparent)
{
@ -124,9 +148,7 @@ VclPtr<VirtualDevice> VDevBuffer::alloc(OutputDevice& rOutDev, const Size& rSize
if (bOkay)
{
// found is valid
const bool bCandidateOkay(
a->buf->GetOutputWidthPixel() >= rSizePixel.getWidth()
&& a->buf->GetOutputHeightPixel() >= rSizePixel.getHeight());
const bool bCandidateOkay = isSizeSuitable(a->buf, rSizePixel);
if (bCandidateOkay)
{
@ -151,16 +173,14 @@ VclPtr<VirtualDevice> VDevBuffer::alloc(OutputDevice& rOutDev, const Size& rSize
{
// found is invalid, use candidate
aFound = a;
bOkay = aFound->buf->GetOutputWidthPixel() >= rSizePixel.getWidth()
&& aFound->buf->GetOutputHeightPixel() >= rSizePixel.getHeight();
bOkay = isSizeSuitable(aFound->buf, rSizePixel);
}
}
else
{
// none yet, use candidate
aFound = a;
bOkay = aFound->buf->GetOutputWidthPixel() >= rSizePixel.getWidth()
&& aFound->buf->GetOutputHeightPixel() >= rSizePixel.getHeight();
bOkay = isSizeSuitable(aFound->buf, rSizePixel);
}
}
}

View file

@ -295,6 +295,9 @@ public:
const AllSettings& GetSettings() const { return *mxSettings; }
SystemGraphicsData GetSystemGfxData() const;
OUString GetRenderBackendName() const;
// Used by the canvas module. Despite the name it does not always return true if Cairo is supported.
bool SupportsCairo() const;
/// Create Surface from given cairo surface
cairo::SurfaceSharedPtr CreateSurface(const cairo::CairoSurfaceSharedPtr& rSurface) const;

View file

@ -76,29 +76,38 @@ void SvpSalVirtualDevice::CreateSurface(tools::Long nNewDX, tools::Long nNewDY,
cairo_surface_destroy(m_pSurface);
}
double fXScale, fYScale;
if (comphelper::LibreOfficeKit::isActive())
{
// Force scaling of the painting
fXScale = fYScale = comphelper::LibreOfficeKit::getDPIScale();
}
else
{
dl_cairo_surface_get_device_scale(m_pRefSurface, &fXScale, &fYScale);
nNewDX *= fXScale;
nNewDY *= fYScale;
}
if (pBuffer)
{
double fXScale, fYScale;
if (comphelper::LibreOfficeKit::isActive())
{
// Force scaling of the painting
fXScale = fYScale = comphelper::LibreOfficeKit::getDPIScale();
}
else
{
dl_cairo_surface_get_device_scale(m_pRefSurface, &fXScale, &fYScale);
nNewDX *= fXScale;
nNewDY *= fYScale;
}
m_pSurface = cairo_image_surface_create_for_data(pBuffer, CAIRO_FORMAT_ARGB32,
nNewDX, nNewDY, cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, nNewDX));
dl_cairo_surface_set_device_scale(m_pSurface, fXScale, fYScale);
}
else if(nNewDX <= 32 && nNewDY <= 32)
{
// Force image-based surface if small. Small VirtualDevice instances are often used for small
// temporary bitmaps that will eventually have GetBitmap() called on them, which would cause
// X Server roundtrip with Xlib-based surface, which may be way more costly than doing the drawing
// in software (which should be fairly cheap for small surfaces anyway).
m_pSurface = cairo_surface_create_similar_image(m_pRefSurface, CAIRO_FORMAT_ARGB32, nNewDX, nNewDY);
dl_cairo_surface_set_device_scale(m_pSurface, fXScale, fYScale);
}
else
{
m_pSurface = cairo_surface_create_similar(m_pRefSurface, CAIRO_CONTENT_COLOR_ALPHA, nNewDX, nNewDY);
// Device scale is inherited in this case.
}
}

View file

@ -231,6 +231,15 @@ SystemGraphicsData OutputDevice::GetSystemGfxData() const
return mpGraphics->GetGraphicsData();
}
OUString OutputDevice::GetRenderBackendName() const
{
if (!mpGraphics && !AcquireGraphics())
return {};
assert(mpGraphics);
return mpGraphics->getRenderBackendName();
}
#if ENABLE_CAIRO_CANVAS
bool OutputDevice::SupportsCairo() const