1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * Some of this code is based on Skia source code, covered by the following
10 * license notice (see readlicense_oo for the full license):
12 * Copyright 2016 Google Inc.
14 * Use of this source code is governed by a BSD-style license that can be
15 * found in the LICENSE file.
19 #include <sal/config.h>
21 #include <skia/osx/gdiimpl.hxx>
23 #include <skia/utils.hxx>
24 #include <skia/zone.hxx>
26 //#include <tools/window/mac/WindowContextFactory_mac.h>
28 #include <quartz/CoreTextFont.hxx>
29 #include <quartz/SystemFontList.hxx>
30 #include <osx/salnativewidgets.h>
31 #include <skia/quartz/cgutils.h>
32 #include <tools/window/mac/MacWindowInfo.h>
33 #include <tools/window/mac/GaneshMetalWindowContext_mac.h>
38 #include <SkFontMgr_mac_ct.h>
39 #include <SkTypeface_mac.h>
41 using namespace SkiaHelper
;
45 struct SnapshotImageData
52 static void SnapshotImageDataCallback(void* pInfo
, const void*, size_t)
55 delete static_cast<SnapshotImageData
*>(pInfo
);
58 AquaSkiaSalGraphicsImpl::AquaSkiaSalGraphicsImpl(AquaSalGraphics
& rParent
,
59 AquaSharedAttributes
& rShared
)
60 : SkiaSalGraphicsImpl(rParent
, rShared
.mpFrame
)
61 , AquaGraphicsBackendBase(rShared
, this)
65 AquaSkiaSalGraphicsImpl::~AquaSkiaSalGraphicsImpl()
67 DeInit(); // mac code doesn't call DeInit()
70 void AquaSkiaSalGraphicsImpl::createWindowSurfaceInternal(bool forceRaster
)
72 assert(!mWindowContext
);
75 skwindow::DisplayParams displayParams
;
76 displayParams
.fColorType
= kN32_SkColorType
;
77 skwindow::MacWindowInfo macWindow
;
78 macWindow
.fMainView
= mrShared
.mpFrame
->mpNSView
;
79 mScaling
= getWindowScaling();
80 RenderMethod renderMethod
= forceRaster
? RenderRaster
: renderMethodToUse();
84 // RasterWindowContext_mac uses OpenGL internally, which we don't want,
85 // so use our own surface and do blitting to the screen ourselves.
86 mSurface
= createSkSurface(GetWidth() * mScaling
, GetHeight() * mScaling
);
89 mWindowContext
= skwindow::MakeGaneshMetalForMac(macWindow
, displayParams
);
90 // Like with other GPU contexts, create a proxy offscreen surface (see
91 // flushSurfaceToWindowContext()). Here it's additionally needed because
92 // it appears that Metal surfaces cannot be read from, which would break things
95 mSurface
= createSkSurface(GetWidth() * mScaling
, GetHeight() * mScaling
);
103 int AquaSkiaSalGraphicsImpl::getWindowScaling() const
105 // The system function returns float, but only integer multiples realistically make sense.
106 return sal::aqua::getWindowScaling();
109 void AquaSkiaSalGraphicsImpl::Flush() { performFlush(); }
111 void AquaSkiaSalGraphicsImpl::Flush(const tools::Rectangle
&) { performFlush(); }
113 void AquaSkiaSalGraphicsImpl::WindowBackingPropertiesChanged() { windowBackingPropertiesChanged(); }
115 void AquaSkiaSalGraphicsImpl::flushSurfaceToWindowContext()
119 // tdf159175 mark dirty area in NSWindow for redrawing
120 // This will cause -[SalFrameView drawRect:] to be called. That,
121 // in turn, will draw a CGImageRef of the surface fetched from
122 // AquaSkiaSalGraphicsImpl::createCGImageFromRasterSurface().
123 mrShared
.refreshRect(mDirtyRect
.x(), mDirtyRect
.y(), mDirtyRect
.width(),
124 mDirtyRect
.height());
128 SkiaSalGraphicsImpl::flushSurfaceToWindowContext();
132 // For Raster we use our own screen blitting (see above).
133 CGImageRef
AquaSkiaSalGraphicsImpl::createCGImageFromRasterSurface(const NSRect
& rDirtyRect
,
134 CGPoint
& rImageOrigin
,
137 if (isGPU() || !mSurface
)
140 // Based on AquaGraphicsBackend::drawBitmap().
141 if (!mrShared
.checkContext())
144 NSRect aIntegralRect
= NSIntegralRect(rDirtyRect
);
145 if (NSIsEmptyRect(aIntegralRect
))
148 // Do not use sub-rect, it creates copies of the data.
149 SnapshotImageData
* pInfo
= new SnapshotImageData
;
150 pInfo
->image
= makeCheckedImageSnapshot(mSurface
);
151 if (!pInfo
->image
->peekPixels(&pInfo
->pixmap
))
154 SkIRect aDirtyRect
= SkIRect::MakeXYWH(
155 aIntegralRect
.origin
.x
* mScaling
, aIntegralRect
.origin
.y
* mScaling
,
156 aIntegralRect
.size
.width
* mScaling
, aIntegralRect
.size
.height
* mScaling
);
157 if (mrShared
.isFlipped())
158 aDirtyRect
= SkIRect::MakeXYWH(
159 aDirtyRect
.x(), pInfo
->pixmap
.bounds().height() - aDirtyRect
.y() - aDirtyRect
.height(),
160 aDirtyRect
.width(), aDirtyRect
.height());
161 if (!aDirtyRect
.intersect(pInfo
->pixmap
.bounds()))
167 // If window scaling, then aDirtyRect is in scaled VCL coordinates and mSurface has
168 // screen size (=points,HiDPI).
169 // This creates the bitmap context from the cropped part, writable_addr32() will get
170 // the first pixel of aDirtyRect.topLeft(), and using pixmap.rowBytes() ensures the following
171 // pixel lines will be read from correct positions.
172 if (pInfo
->pixmap
.bounds() != aDirtyRect
173 && pInfo
->pixmap
.bounds().bottom() == aDirtyRect
.bottom())
175 // HACK for tdf#145843: If aDirtyRect includes the last line but not the first pixel of it,
176 // then the rowBytes() trick would lead to the CG* functions thinking that even pixels after
177 // the pixmap data belong to the area (since the shifted x()+rowBytes() points there) and
178 // at least on Intel Mac they would actually read those data, even though I see no good reason
179 // to do that, as that's beyond the x()+width() for the last line. That could be handled
180 // by creating a subset SkImage (which as is said above copies data), or set the x coordinate
181 // to 0, which will then make rowBytes() match the actual data.
182 aDirtyRect
.fLeft
= 0;
183 // Related tdf#156630 pixmaps can be wider than the dirty rectangle
184 // This seems to most commonly occur when SAL_FORCE_HIDPI_SCALING=1
185 // and the native window scale is 2.
186 assert(aDirtyRect
.width() <= pInfo
->pixmap
.bounds().width());
189 // tdf#145843 Do not use CGBitmapContextCreate() to create a bitmap context
190 // As described in the comment in the above code, CGBitmapContextCreate()
191 // and CGBitmapContextCreateWithData() will try to access pixels up to
192 // aDirtyRect.x() + pixmap.bounds.width() for each row. When reading the
193 // last line in the SkPixmap, the buffer allocated for the SkPixmap ends at
194 // aDirtyRect.x() + aDirtyRect.width() and aDirtyRect.width() is clamped to
195 // pixmap.bounds.width() - aDirtyRect.x().
196 // This behavior looks like an optimization within CGBitmapContextCreate()
197 // to draw with a single memcpy() so fix this bug by chaining the
198 // CGDataProvider(), CGImageCreate(), and CGImageCreateWithImageInRect()
199 // functions to create the screen image.
200 CGDataProviderRef dataProvider
201 = CGDataProviderCreateWithData(pInfo
, pInfo
->pixmap
.writable_addr32(0, 0),
202 pInfo
->pixmap
.computeByteSize(), SnapshotImageDataCallback
);
206 SAL_WARN("vcl.skia", "flushSurfaceToScreenGC(): Failed to allocate data provider");
211 = CGImageCreate(pInfo
->pixmap
.bounds().width(), pInfo
->pixmap
.bounds().height(), 8,
212 8 * pInfo
->image
->imageInfo().bytesPerPixel(), pInfo
->pixmap
.rowBytes(),
213 GetSalData()->mxRGBSpace
,
214 SkiaToCGBitmapType(pInfo
->image
->colorType(), pInfo
->image
->alphaType()),
215 dataProvider
, nullptr, false, kCGRenderingIntentDefault
);
218 CGDataProviderRelease(dataProvider
);
219 SAL_WARN("vcl.skia", "flushSurfaceToScreenGC(): Failed to allocate full image");
223 CGImageRef screenImage
= CGImageCreateWithImageInRect(
225 CGRectMake(aDirtyRect
.x(), aDirtyRect
.y(), aDirtyRect
.width(), aDirtyRect
.height()));
228 CGImageRelease(fullImage
);
229 CGDataProviderRelease(dataProvider
);
230 SAL_WARN("vcl.skia", "createCGImageFromRasterSurface(): Failed to allocate screen image");
234 rImageOrigin
= CGPointMake(aDirtyRect
.x(), aDirtyRect
.y());
235 rImageFlipped
= mrShared
.isFlipped();
237 CGImageRelease(fullImage
);
238 CGDataProviderRelease(dataProvider
);
243 bool AquaSkiaSalGraphicsImpl::drawNativeControl(ControlType nType
, ControlPart nPart
,
244 const tools::Rectangle
& rControlRegion
,
245 ControlState nState
, const ImplControlValue
& aValue
)
247 // tdf#157613 make sure surface is not a nullptr
252 // rControlRegion is not the whole area that the control should be painted to (e.g. highlight
253 // around focused lineedit is outside of it). Since we draw to a temporary bitmap, we need to find out
255 // Related tdf#163945 Reduce the size of the temporary bitmap
256 // Previously, the temporary bitmap was set to the control region
257 // expanded by 50 * mScaling (e.g. both width and height were
258 // increased by 200 pixels when running on a Retina display). This
259 // caused temporary bitmaps to be up to several times larger than
260 // needed. Also, drawing NSBox objects to a CGBitmapContext is
261 // noticeably slow so filling all that unneeded temporary bitmap
262 // area can slow down performance when a large number of NSBox
263 // objects like the status bar are redrawn in quick succession.
264 // Using getNativeControlRegion() isn't perfect, but it does try to
265 // account for the focus ring as well as the minimum width and/or
266 // height of the native control so union the two regions set by
267 // getNativeControlRegion() and add double the focus ring width on
268 // each side just to be safe. In most cases, this should ensure
269 // that the temporary bitmap is large enough to draw the entire
270 // native control and a focus ring.
271 tools::Rectangle
boundingRegion(rControlRegion
);
272 tools::Rectangle rNativeBoundingRegion
;
273 tools::Rectangle rNativeContentRegion
;
274 AquaSalGraphics
* pGraphics
= mrShared
.mpFrame
->mpGraphics
;
276 && pGraphics
->getNativeControlRegion(nType
, nPart
, rControlRegion
, nState
, aValue
,
277 OUString(), rNativeBoundingRegion
,
278 rNativeContentRegion
))
280 boundingRegion
.Union(rNativeBoundingRegion
);
281 boundingRegion
.Union(rNativeContentRegion
);
283 boundingRegion
.expand(2 * FOCUS_RING_WIDTH
* mScaling
);
285 // Do a scaled bitmap in HiDPI in order not to lose precision.
286 const tools::Long width
= boundingRegion
.GetWidth() * mScaling
;
287 const tools::Long height
= boundingRegion
.GetHeight() * mScaling
;
288 const size_t bytes
= width
* height
* 4;
289 // Let Skia own the CGBitmapContext's buffer so that an SkImage
290 // can be created without Skia making a copy of the buffer
291 sk_sp
<SkData
> data
= SkData::MakeZeroInitialized(bytes
);
292 CGContextRef context
= CGBitmapContextCreate(
293 data
->writable_data(), width
, height
, 8, width
* 4, GetSalData()->mxRGBSpace
,
294 SkiaToCGBitmapType(mSurface
->imageInfo().colorType(), kPremul_SkAlphaType
));
297 SAL_WARN("vcl.skia", "drawNativeControl(): Failed to allocate bitmap context");
300 // Setup context state for drawing (performDrawNativeControl() e.g. fills background in some cases).
301 CGContextSetFillColorSpace(context
, GetSalData()->mxRGBSpace
);
302 CGContextSetStrokeColorSpace(context
, GetSalData()->mxRGBSpace
);
305 RGBAColor
lineColor(*moLineColor
);
306 CGContextSetRGBStrokeColor(context
, lineColor
.GetRed(), lineColor
.GetGreen(),
307 lineColor
.GetBlue(), lineColor
.GetAlpha());
311 RGBAColor
fillColor(*moFillColor
);
312 CGContextSetRGBFillColor(context
, fillColor
.GetRed(), fillColor
.GetGreen(),
313 fillColor
.GetBlue(), fillColor
.GetAlpha());
315 // Adjust for our drawn-to coordinates in the bitmap.
316 tools::Rectangle
movedRegion(Point(rControlRegion
.getX() - boundingRegion
.getX(),
317 rControlRegion
.getY() - boundingRegion
.getY()),
318 rControlRegion
.GetSize());
319 // Flip drawing upside down.
320 CGContextTranslateCTM(context
, 0, height
);
321 CGContextScaleCTM(context
, 1, -1);
322 // And possibly scale the native drawing.
323 CGContextScaleCTM(context
, mScaling
, mScaling
);
324 bool bOK
= performDrawNativeControl(nType
, nPart
, movedRegion
, nState
, aValue
, context
,
326 CGContextRelease(context
);
330 SAL_INFO("vcl.skia.trace", "drawnativecontrol(" << this << "): " << rControlRegion
<< ":"
331 << int(nType
) << "/" << int(nPart
));
332 tools::Rectangle updateRect
= boundingRegion
;
333 // For background update only part that is not clipped, the same
334 // as in AquaGraphicsBackend::drawNativeControl().
335 if (nType
== ControlType::WindowBackground
)
336 updateRect
.Intersection(mClipRegion
.GetBoundRect());
337 addUpdateRegion(SkRect::MakeXYWH(updateRect
.getX(), updateRect
.getY(),
338 updateRect
.GetWidth(), updateRect
.GetHeight()));
339 SkRect drawRect
= SkRect::MakeXYWH(boundingRegion
.getX(), boundingRegion
.getY(),
340 boundingRegion
.GetWidth(), boundingRegion
.GetHeight());
341 sk_sp
<SkImage
> image
= SkImages::RasterFromData(
342 SkImageInfo::Make(width
, height
, mSurface
->imageInfo().colorType(),
343 kPremul_SkAlphaType
),
346 && drawRect
.width() * mScaling
== image
->width()); // no scaling should be needed
347 getDrawCanvas()->drawImageRect(image
, drawRect
, SkSamplingOptions());
348 // Related: tdf#156881 flush the canvas after drawing the pixel buffer
349 if (auto dContext
= GrAsDirectContext(getDrawCanvas()->recordingContext()))
350 dContext
->flushAndSubmit();
351 ++pendingOperationsToFlush
; // tdf#136369
357 void AquaSkiaSalGraphicsImpl::drawTextLayout(const GenericSalLayout
& rLayout
)
359 const bool bSubpixelPositioning
= rLayout
.GetSubpixelPositioning();
360 const CoreTextFont
& rFont
= *static_cast<const CoreTextFont
*>(&rLayout
.GetFont());
361 const vcl::font::FontSelectPattern
& rFontSelect
= rFont
.GetFontSelectPattern();
362 int nHeight
= rFontSelect
.mnHeight
;
363 int nWidth
= rFontSelect
.mnWidth
? rFontSelect
.mnWidth
: nHeight
;
364 if (nWidth
== 0 || nHeight
== 0)
366 SAL_WARN("vcl.skia", "DrawTextLayout(): rFontSelect.mnHeight is zero!?");
372 std::unique_ptr
<SystemFontList
> fontList
= GetCoretextFontList();
373 if (fontList
== nullptr)
375 SAL_WARN("vcl.skia", "DrawTextLayout(): No coretext font list");
376 fontManager
= SkFontMgr_New_CoreText(nullptr);
380 fontManager
= SkFontMgr_New_CoreText(fontList
->fontCollection());
384 sk_sp
<SkTypeface
> typeface
= SkMakeTypefaceFromCTFont(rFont
.GetCTFont());
385 SkFont
font(typeface
);
386 font
.setSize(nHeight
);
387 // font.setScaleX(rFont.mfFontStretch); TODO
388 if (rFont
.NeedsArtificialBold())
389 font
.setEmbolden(true);
391 SkFont::Edging ePreferredAliasing
392 = bSubpixelPositioning
? SkFont::Edging::kSubpixelAntiAlias
: SkFont::Edging::kAntiAlias
;
393 if (bSubpixelPositioning
)
395 // note that SkFont defaults to a BaselineSnap of true, so I think really only
396 // subpixel in text direction
397 font
.setSubpixel(true);
399 font
.setEdging(mrShared
.mbNonAntialiasedText
? SkFont::Edging::kAlias
: ePreferredAliasing
);
401 // Vertical font, use width as "height".
402 SkFont
verticalFont(font
);
403 verticalFont
.setSize(nHeight
);
404 // verticalFont.setSize(nWidth); TODO
405 // verticalFont.setScaleX(1.0 * nHeight / nWidth);
407 drawGenericLayout(rLayout
, mrShared
.maTextColor
, font
, verticalFont
);
412 std::unique_ptr
<skwindow::WindowContext
> createMetalWindowContext(bool /*temporary*/)
414 skwindow::DisplayParams displayParams
;
415 skwindow::MacWindowInfo macWindow
;
416 macWindow
.fMainView
= nullptr;
417 return skwindow::MakeGaneshMetalForMac(macWindow
, displayParams
);
421 void AquaSkiaSalGraphicsImpl::prepareSkia() { SkiaHelper::prepareSkia(createMetalWindowContext
); }
423 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */