diff options
Diffstat (limited to 'vcl/skia/win')
-rw-r--r-- | vcl/skia/win/gdiimpl.cxx | 441 |
1 files changed, 441 insertions, 0 deletions
diff --git a/vcl/skia/win/gdiimpl.cxx b/vcl/skia/win/gdiimpl.cxx new file mode 100644 index 000000000..9d01d7257 --- /dev/null +++ b/vcl/skia/win/gdiimpl.cxx @@ -0,0 +1,441 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <sal/config.h> + +#include <skia/win/gdiimpl.hxx> + +#include <win/saldata.hxx> +#include <vcl/skia/SkiaHelper.hxx> +#include <skia/utils.hxx> +#include <skia/zone.hxx> +#include <skia/win/font.hxx> +#include <comphelper/scopeguard.hxx> +#include <comphelper/windowserrorstring.hxx> +#include <sal/log.hxx> + +#include <SkCanvas.h> +#include <SkPaint.h> +#include <SkPixelRef.h> +#include <SkTypeface_win.h> +#include <SkFont.h> +#include <SkFontMgr.h> +#include <tools/sk_app/win/WindowContextFactory_win.h> +#include <tools/sk_app/WindowContext.h> + +#include <windows.h> + +using namespace SkiaHelper; + +WinSkiaSalGraphicsImpl::WinSkiaSalGraphicsImpl(WinSalGraphics& rGraphics, + SalGeometryProvider* mpProvider) + : SkiaSalGraphicsImpl(rGraphics, mpProvider) + , mWinParent(rGraphics) +{ +} + +void WinSkiaSalGraphicsImpl::createWindowSurfaceInternal(bool forceRaster) +{ + assert(!mWindowContext); + assert(!mSurface); + SkiaZone zone; + sk_app::DisplayParams displayParams; + assert(GetWidth() > 0 && GetHeight() > 0); + displayParams.fSurfaceProps = *surfaceProps(); + switch (forceRaster ? RenderRaster : renderMethodToUse()) + { + case RenderRaster: + mWindowContext = sk_app::window_context_factory::MakeRasterForWin(mWinParent.gethWnd(), + displayParams); + if (mWindowContext) + mSurface = mWindowContext->getBackbufferSurface(); + break; + case RenderVulkan: + mWindowContext = sk_app::window_context_factory::MakeVulkanForWin(mWinParent.gethWnd(), + displayParams); + // See flushSurfaceToWindowContext(). + if (mWindowContext) + mSurface = createSkSurface(GetWidth(), GetHeight()); + break; + case RenderMetal: + abort(); + break; + } +} + +void WinSkiaSalGraphicsImpl::freeResources() {} + +void WinSkiaSalGraphicsImpl::Flush() { performFlush(); } + +bool WinSkiaSalGraphicsImpl::TryRenderCachedNativeControl(ControlCacheKey const& rControlCacheKey, + int nX, int nY) +{ + static bool gbCacheEnabled = !getenv("SAL_WITHOUT_WIDGET_CACHE"); + if (!gbCacheEnabled) + return false; + + auto& controlsCache = SkiaControlsCache::get(); + SkiaControlCacheType::const_iterator iterator = controlsCache.find(rControlCacheKey); + if (iterator == controlsCache.end()) + return false; + + preDraw(); + SAL_INFO("vcl.skia.trace", "tryrendercachednativecontrol(" + << this << "): " + << SkIRect::MakeXYWH(nX, nY, iterator->second->width(), + iterator->second->height())); + addUpdateRegion( + SkRect::MakeXYWH(nX, nY, iterator->second->width(), iterator->second->height())); + mSurface->getCanvas()->drawImage(iterator->second, nX, nY); + postDraw(); + return true; +} + +bool WinSkiaSalGraphicsImpl::RenderAndCacheNativeControl(CompatibleDC& rWhite, CompatibleDC& rBlack, + int nX, int nY, + ControlCacheKey& aControlCacheKey) +{ + assert(dynamic_cast<SkiaCompatibleDC*>(&rWhite)); + assert(dynamic_cast<SkiaCompatibleDC*>(&rBlack)); + + sk_sp<SkImage> image = static_cast<SkiaCompatibleDC&>(rBlack).getAsImageDiff( + static_cast<SkiaCompatibleDC&>(rWhite)); + preDraw(); + SAL_INFO("vcl.skia.trace", + "renderandcachednativecontrol(" + << this << "): " << SkIRect::MakeXYWH(nX, nY, image->width(), image->height())); + addUpdateRegion(SkRect::MakeXYWH(nX, nY, image->width(), image->height())); + mSurface->getCanvas()->drawImage(image, nX, nY); + postDraw(); + + if (!aControlCacheKey.canCacheControl()) + return true; + SkiaControlCachePair pair(aControlCacheKey, std::move(image)); + SkiaControlsCache::get().insert(std::move(pair)); + return true; +} + +sk_sp<SkTypeface> WinSkiaSalGraphicsImpl::createDirectWriteTypeface(HDC hdc, HFONT hfont) try +{ + using sal::systools::ThrowIfFailed; + if (!dwriteDone) + { + ThrowIfFailed(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), + reinterpret_cast<IUnknown**>(&dwriteFactory)), + SAL_WHERE); + ThrowIfFailed(dwriteFactory->GetGdiInterop(&dwriteGdiInterop), SAL_WHERE); + dwriteFontMgr = SkFontMgr_New_DirectWrite(dwriteFactory.get()); + dwriteDone = true; + } + if (!dwriteFontMgr) + return nullptr; + + // tdf#137122: We need to get the exact same font as HFONT refers to, + // since VCL core computes things like glyph ids based on that, and getting + // a different font could lead to mismatches (e.g. if there's a slightly + // different version of the same font installed system-wide). + // For that CreateFromFaceFromHdc() is necessary. The simpler + // CreateFontFromLOGFONT() seems to search for the best matching font, + // which may not be the exact font. + sal::systools::COMReference<IDWriteFontFace> fontFace; + { + comphelper::ScopeGuard g( + [ hdc, oldFont(SelectFont(hdc, hfont)) ] { SelectFont(hdc, oldFont); }); + ThrowIfFailed(dwriteGdiInterop->CreateFontFaceFromHdc(hdc, &fontFace), SAL_WHERE); + } + + sal::systools::COMReference<IDWriteFontCollection> collection; + ThrowIfFailed(dwriteFactory->GetSystemFontCollection(&collection), SAL_WHERE); + sal::systools::COMReference<IDWriteFont> font; + // As said above, this fails for our fonts. + if (FAILED(collection->GetFontFromFontFace(fontFace.get(), &font))) + { + // If not found in system collection, try our private font collection. + // If that's not possible we'll fall back to Skia's GDI-based font rendering. + if (!dwritePrivateCollection + || FAILED(dwritePrivateCollection->GetFontFromFontFace(fontFace.get(), &font))) + { + // Our private fonts are installed using AddFontResourceExW( FR_PRIVATE ) + // and that does not make them available to the DWrite system font + // collection. For such cases attempt to update a collection of + // private fonts with this newly used font. + + sal::systools::COMReference<IDWriteFactory3> dwriteFactory3; + ThrowIfFailed(dwriteFactory->QueryInterface(&dwriteFactory3), SAL_WHERE); + + if (!dwriteFontSetBuilder) + ThrowIfFailed(dwriteFactory3->CreateFontSetBuilder(&dwriteFontSetBuilder), + SAL_WHERE); + + UINT32 numberOfFiles; + ThrowIfFailed(fontFace->GetFiles(&numberOfFiles, nullptr), SAL_WHERE); + if (numberOfFiles != 1) + return nullptr; + + sal::systools::COMReference<IDWriteFontFile> fontFile; + ThrowIfFailed(fontFace->GetFiles(&numberOfFiles, &fontFile), SAL_WHERE); + + BOOL isSupported; + DWRITE_FONT_FILE_TYPE fileType; + UINT32 numberOfFonts; + ThrowIfFailed(fontFile->Analyze(&isSupported, &fileType, nullptr, &numberOfFonts), + SAL_WHERE); + if (!isSupported) + return nullptr; + + // For each font within the font file, get a font face reference and add to the builder. + for (UINT32 fontIndex = 0; fontIndex < numberOfFonts; ++fontIndex) + { + sal::systools::COMReference<IDWriteFontFaceReference> fontFaceReference; + if (FAILED(dwriteFactory3->CreateFontFaceReference(fontFile.get(), fontIndex, + DWRITE_FONT_SIMULATIONS_NONE, + &fontFaceReference))) + continue; + + // Leave it to DirectWrite to read properties directly out of the font files + dwriteFontSetBuilder->AddFontFaceReference(fontFaceReference.get()); + } + + sal::systools::COMReference<IDWriteFontSet> fontSet; + ThrowIfFailed(dwriteFontSetBuilder->CreateFontSet(&fontSet), SAL_WHERE); + ThrowIfFailed(dwriteFactory3->CreateFontCollectionFromFontSet(fontSet.get(), + &dwritePrivateCollection), + SAL_WHERE); + ThrowIfFailed(dwritePrivateCollection->GetFontFromFontFace(fontFace.get(), &font), + SAL_WHERE); + } + } + sal::systools::COMReference<IDWriteFontFamily> fontFamily; + ThrowIfFailed(font->GetFontFamily(&fontFamily), SAL_WHERE); + return sk_sp<SkTypeface>( + SkCreateTypefaceDirectWrite(dwriteFontMgr, fontFace.get(), font.get(), fontFamily.get())); +} +catch (const sal::systools::ComError& e) +{ + SAL_DETAIL_LOG_STREAM(SAL_DETAIL_ENABLE_LOG_INFO, ::SAL_DETAIL_LOG_LEVEL_INFO, "vcl.skia", + e.what(), + "HRESULT 0x" << OUString::number(e.GetHresult(), 16) << ": " + << WindowsErrorStringFromHRESULT(e.GetHresult())); + return nullptr; +} + +bool WinSkiaSalGraphicsImpl::DrawTextLayout(const GenericSalLayout& rLayout) +{ + assert(dynamic_cast<const SkiaWinFontInstance*>(&rLayout.GetFont())); + const SkiaWinFontInstance* pWinFont + = static_cast<const SkiaWinFontInstance*>(&rLayout.GetFont()); + const HFONT hLayoutFont = pWinFont->GetHFONT(); + double hScale = pWinFont->getHScale(); + LOGFONTW logFont; + if (GetObjectW(hLayoutFont, sizeof(logFont), &logFont) == 0) + { + assert(false); + return false; + } + sk_sp<SkTypeface> typeface = pWinFont->GetSkiaTypeface(); + if (!typeface) + { + typeface = createDirectWriteTypeface(mWinParent.getHDC(), hLayoutFont); + bool dwrite = true; + if (!typeface) // fall back to GDI text rendering + { + // If lfWidth is kept, then with hScale != 1 characters get too wide, presumably + // because the horizontal scaling gets applied twice if GDI is used for drawing (tdf#141715). + // Using lfWidth /= hScale gives slightly incorrect sizes, for a reason I don't understand. + // LOGFONT docs say that 0 means GDI will find out the right value on its own somehow, + // and it apparently works. + logFont.lfWidth = 0; + // Reset LOGFONT orientation, the proper orientation is applied by drawGenericLayout(), + // and keeping this would make it get applied once more when doing the actual GDI drawing. + // Resetting it here does not seem to cause any problem. + logFont.lfOrientation = 0; + logFont.lfEscapement = 0; + typeface.reset(SkCreateTypefaceFromLOGFONT(logFont)); + dwrite = false; + if (!typeface) + return false; + } + // Cache the typeface. + const_cast<SkiaWinFontInstance*>(pWinFont)->SetSkiaTypeface(typeface, dwrite); + } + + SkFont font(typeface); + + bool bSubpixelPositioning = mWinParent.getTextRenderModeForResolutionIndependentLayoutEnabled(); + SkFont::Edging ePreferredAliasing + = bSubpixelPositioning ? SkFont::Edging::kSubpixelAntiAlias : fontEdging; + if (bSubpixelPositioning) + { + // note that SkFont defaults to a BaselineSnap of true, so I think really only + // subpixel in text direction + font.setSubpixel(true); + } + font.setEdging(logFont.lfQuality == NONANTIALIASED_QUALITY ? SkFont::Edging::kAlias + : ePreferredAliasing); + + const vcl::font::FontSelectPattern& rFSD = pWinFont->GetFontSelectPattern(); + int nHeight = rFSD.mnHeight; + int nWidth = rFSD.mnWidth ? rFSD.mnWidth : nHeight; + if (nWidth == 0 || nHeight == 0) + return false; + font.setSize(nHeight); + font.setScaleX(hScale); + + // Unlike with Freetype-based font handling, use height even in vertical mode, + // additionally multiply it by horizontal scale to get the proper + // size and then scale the width back, otherwise the height would + // not be correct. I don't know why this is inconsistent. + SkFont verticalFont(font); + verticalFont.setSize(nHeight * hScale); + verticalFont.setScaleX(1.0 / hScale); + + assert(dynamic_cast<SkiaSalGraphicsImpl*>(mWinParent.GetImpl())); + SkiaSalGraphicsImpl* impl = static_cast<SkiaSalGraphicsImpl*>(mWinParent.GetImpl()); + COLORREF color = ::GetTextColor(mWinParent.getHDC()); + Color salColor(GetRValue(color), GetGValue(color), GetBValue(color)); + impl->drawGenericLayout(rLayout, salColor, font, verticalFont); + return true; +} + +SkFont::Edging WinSkiaSalGraphicsImpl::fontEdging; + +void WinSkiaSalGraphicsImpl::initFontInfo() +{ + // Skia needs to be explicitly told what kind of antialiasing should be used, + // get it from system settings. This does not actually matter for the text + // rendering itself, since Skia has been patched to simply use the setting + // from the LOGFONT, which gets set by VCL's ImplGetLogFontFromFontSelect() + // and that one normally uses DEFAULT_QUALITY, so Windows will select + // the appropriate AA setting. But Skia internally chooses the format to which + // the glyphs will be rendered based on this setting (subpixel AA requires colors, + // others do not). + fontEdging = SkFont::Edging::kAlias; + SkPixelGeometry pixelGeometry = kUnknown_SkPixelGeometry; + BOOL set; + if (SystemParametersInfo(SPI_GETFONTSMOOTHING, 0, &set, 0) && set) + { + UINT set2; + if (SystemParametersInfo(SPI_GETFONTSMOOTHINGTYPE, 0, &set2, 0) + && set2 == FE_FONTSMOOTHINGCLEARTYPE) + { + fontEdging = SkFont::Edging::kSubpixelAntiAlias; + if (SystemParametersInfo(SPI_GETFONTSMOOTHINGORIENTATION, 0, &set2, 0) + && set2 == FE_FONTSMOOTHINGORIENTATIONBGR) + // No idea how to tell if it's horizontal or vertical. + pixelGeometry = kBGR_H_SkPixelGeometry; + else + pixelGeometry = kRGB_H_SkPixelGeometry; // default + } + else + fontEdging = SkFont::Edging::kAntiAlias; + } + setPixelGeometry(pixelGeometry); +} + +void WinSkiaSalGraphicsImpl::ClearDevFontCache() +{ + dwriteFontMgr.reset(); + dwriteFontSetBuilder.clear(); + dwritePrivateCollection.clear(); + dwriteFactory.clear(); + dwriteGdiInterop.clear(); + dwriteDone = false; + initFontInfo(); // get font info again, just in case +} + +SkiaCompatibleDC::SkiaCompatibleDC(SalGraphics& rGraphics, int x, int y, int width, int height) + : CompatibleDC(rGraphics, x, y, width, height, false) +{ +} + +sk_sp<SkImage> SkiaCompatibleDC::getAsImageDiff(const SkiaCompatibleDC& white) const +{ + SkiaZone zone; + assert(maRects.mnSrcWidth == white.maRects.mnSrcWidth + || maRects.mnSrcHeight == white.maRects.mnSrcHeight); + SkBitmap tmpBitmap; + if (!tmpBitmap.tryAllocPixels(SkImageInfo::Make(maRects.mnSrcWidth, maRects.mnSrcHeight, + kBGRA_8888_SkColorType, kPremul_SkAlphaType), + maRects.mnSrcWidth * 4)) + abort(); + // Native widgets are drawn twice on black/white background to synthetize alpha + // (commit c6b66646870cb2bffaa73565affcf80bf74e0b5c). The problem is that + // most widgets when drawn on transparent background are drawn properly (and the result + // is in premultiplied alpha format), some such as "Edit" (used by ControlType::Editbox) + // keep the alpha channel as transparent. Therefore the alpha is actually computed + // from the difference in the premultiplied red channels when drawn one black and on white. + // Alpha is computed as "alpha = 1.0 - abs(black.red - white.red)". + // I doubt this can be done using Skia, so do it manually here. Fortunately + // the bitmaps should be fairly small and are cached. + uint32_t* dest = tmpBitmap.getAddr32(0, 0); + assert(dest == tmpBitmap.getPixels()); + const sal_uInt32* src = mpData; + const sal_uInt32* whiteSrc = white.mpData; + uint32_t* end = dest + tmpBitmap.width() * tmpBitmap.height(); + while (dest < end) + { + uint32_t alpha = 255 - abs(int(*src & 0xff) - int(*whiteSrc & 0xff)); + *dest = (*src & 0x00ffffff) | (alpha << 24); + ++dest; + ++src; + ++whiteSrc; + } + tmpBitmap.notifyPixelsChanged(); + tmpBitmap.setImmutable(); + sk_sp<SkSurface> surface = createSkSurface(tmpBitmap.width(), tmpBitmap.height()); + SkPaint paint; + paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha + SkCanvas* canvas = surface->getCanvas(); + canvas->save(); + // The data we got is upside-down. + SkMatrix matrix; + matrix.preTranslate(0, tmpBitmap.height()); + matrix.setConcat(matrix, SkMatrix::Scale(1, -1)); + canvas->concat(matrix); + canvas->drawImage(tmpBitmap.asImage(), 0, 0, SkSamplingOptions(), &paint); + canvas->restore(); + return makeCheckedImageSnapshot(surface); +} + +SkiaControlsCache::SkiaControlsCache() + : cache(200) +{ +} + +SkiaControlCacheType& SkiaControlsCache::get() +{ + SalData* data = GetSalData(); + if (!data->m_pSkiaControlsCache) + data->m_pSkiaControlsCache.reset(new SkiaControlsCache); + return data->m_pSkiaControlsCache->cache; +} + +namespace +{ +std::unique_ptr<sk_app::WindowContext> createVulkanWindowContext(bool /*temporary*/) +{ + SkiaZone zone; + sk_app::DisplayParams displayParams; + return sk_app::window_context_factory::MakeVulkanForWin(nullptr, displayParams); +} +} + +void WinSkiaSalGraphicsImpl::prepareSkia() +{ + initFontInfo(); + SkiaHelper::prepareSkia(createVulkanWindowContext); +} + +void WinSkiaSalGraphicsImpl::ClearNativeControlCache() +{ + SalData* data = GetSalData(); + data->m_pSkiaControlsCache.reset(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |