1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the plugins of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qcocoabackingstore.h"
41
42#include "qcocoawindow.h"
43#include "qcocoahelpers.h"
44
45#include <QtCore/qmath.h>
46#include <QtGui/qpainter.h>
47
48#include <QuartzCore/CATransaction.h>
49
50QT_BEGIN_NAMESPACE
51
52QCocoaBackingStore::QCocoaBackingStore(QWindow *window)
53    : QRasterBackingStore(window)
54{
55}
56
57QCFType<CGColorSpaceRef> QCocoaBackingStore::colorSpace() const
58{
59    NSView *view = static_cast<QCocoaWindow *>(window()->handle())->view();
60    return QCFType<CGColorSpaceRef>::constructFromGet(view.window.colorSpace.CGColorSpace);
61}
62
63// ----------------------------------------------------------------------------
64
65QNSWindowBackingStore::QNSWindowBackingStore(QWindow *window)
66    : QCocoaBackingStore(window)
67{
68    // Choose an appropriate window depth based on the requested surface format.
69    // On deep color displays the default bit depth is 16-bit, so unless we need
70    // that level of precision we opt out of it (and the expensive RGB32 -> RGB64
71    // conversions that come with it if our backingstore depth does not match).
72
73    NSWindow *nsWindow = static_cast<QCocoaWindow *>(window->handle())->view().window;
74    auto colorSpaceName = NSColorSpaceFromDepth(nsWindow.depthLimit);
75
76    static const int kDefaultBitDepth = 8;
77    auto surfaceFormat = window->requestedFormat();
78    auto bitsPerSample = qMax(kDefaultBitDepth, qMax(surfaceFormat.redBufferSize(),
79        qMax(surfaceFormat.greenBufferSize(), surfaceFormat.blueBufferSize())));
80
81    // NSBestDepth does not seem to guarantee a window depth deep enough for the
82    // given bits per sample, even if documented as such. For example, requesting
83    // 10 bits per sample will not give us a 16-bit format, even if that's what's
84    // available. Work around this by manually bumping the bit depth.
85    bitsPerSample = !(bitsPerSample & (bitsPerSample - 1))
86        ? bitsPerSample : qNextPowerOfTwo(bitsPerSample);
87
88    auto bestDepth = NSBestDepth(colorSpaceName, bitsPerSample, 0, NO, nullptr);
89
90    // Disable dynamic depth limit, otherwise our depth limit will be overwritten
91    // by AppKit if the window moves to a screen with a different depth. We call
92    // this before setting the depth limit, as the call will reset the depth to 0.
93    [nsWindow setDynamicDepthLimit:NO];
94
95    qCDebug(lcQpaBackingStore) << "Using" << NSBitsPerSampleFromDepth(bestDepth)
96        << "bit window depth for" << nsWindow;
97
98    nsWindow.depthLimit = bestDepth;
99}
100
101QNSWindowBackingStore::~QNSWindowBackingStore()
102{
103}
104
105bool QNSWindowBackingStore::windowHasUnifiedToolbar() const
106{
107    Q_ASSERT(window()->handle());
108    return static_cast<QCocoaWindow *>(window()->handle())->m_drawContentBorderGradient;
109}
110
111QImage::Format QNSWindowBackingStore::format() const
112{
113    if (windowHasUnifiedToolbar())
114        return QImage::Format_ARGB32_Premultiplied;
115
116    return QRasterBackingStore::format();
117}
118
119void QNSWindowBackingStore::resize(const QSize &size, const QRegion &staticContents)
120{
121    qCDebug(lcQpaBackingStore) << "Resize requested to" << size;
122    QRasterBackingStore::resize(size, staticContents);
123
124    // The window shadow rendered by AppKit is based on the shape/content of the
125    // NSWindow surface. Technically any flush of the backingstore can result in
126    // a potentially new shape of the window, and would need a shadow invalidation,
127    // but this is likely too expensive to do at every flush for the few cases where
128    // clients change the shape dynamically. One case where we do know that the shadow
129    // likely needs invalidation, if the window has partially transparent content,
130    // is after a resize, where AppKit's default shadow may be based on the previous
131    // window content.
132    QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window()->handle());
133    if (cocoaWindow->isContentView() && !cocoaWindow->isOpaque())
134        cocoaWindow->m_needsInvalidateShadow = true;
135}
136
137/*!
138    Flushes the given \a region from the specified \a window onto the
139    screen.
140
141    The \a window is the top level window represented by this backingstore,
142    or a non-transient child of that window.
143
144    If the \a window is a child window, the \a region will be in child window
145    coordinates, and the \a offset will be the child window's offset in relation
146    to the backingstore's top level window.
147*/
148void QNSWindowBackingStore::flush(QWindow *window, const QRegion &region, const QPoint &offset)
149{
150    if (m_image.isNull())
151        return;
152
153    // Use local pool so that any stale image references are cleaned up after flushing
154    QMacAutoReleasePool pool;
155
156    const QWindow *topLevelWindow = this->window();
157
158    Q_ASSERT(topLevelWindow->handle() && window->handle());
159    Q_ASSERT(!topLevelWindow->handle()->isForeignWindow() && !window->handle()->isForeignWindow());
160
161    QNSView *topLevelView = qnsview_cast(static_cast<QCocoaWindow *>(topLevelWindow->handle())->view());
162    QNSView *view = qnsview_cast(static_cast<QCocoaWindow *>(window->handle())->view());
163
164    if (lcQpaBackingStore().isDebugEnabled()) {
165        QString targetViewDescription;
166        if (view != topLevelView) {
167            QDebug targetDebug(&targetViewDescription);
168            targetDebug << "onto" << topLevelView << "at" << offset;
169        }
170        qCDebug(lcQpaBackingStore) << "Flushing" << region << "of" << view << qPrintable(targetViewDescription);
171    }
172
173    // Normally a NSView is drawn via drawRect, as part of the display cycle in the
174    // main runloop, via setNeedsDisplay and friends. AppKit will lock focus on each
175    // individual view, starting with the top level and then traversing any subviews,
176    // calling drawRect for each of them. This pull model results in expose events
177    // sent to Qt, which result in drawing to the backingstore and flushing it.
178    // Qt may also decide to paint and flush the backingstore via e.g. timers,
179    // or other events such as mouse events, in which case we're in a push model.
180    // If there is no focused view, it means we're in the latter case, and need
181    // to manually flush the NSWindow after drawing to its graphic context.
182    const bool drawingOutsideOfDisplayCycle = ![NSView focusView];
183
184    // We also need to ensure the flushed view has focus, so that the graphics
185    // context is set up correctly (coordinate system, clipping, etc). Outside
186    // of the normal display cycle there is no focused view, as explained above,
187    // so we have to handle it manually. There's also a corner case inside the
188    // normal display cycle due to way QWidgetRepaintManager composits native child
189    // widgets, where we'll get a flush of a native child during the drawRect of
190    // its parent/ancestor, and the parent/ancestor being the one locked by AppKit.
191    // In this case we also need to lock and unlock focus manually.
192    const bool shouldHandleViewLockManually = [NSView focusView] != view;
193    if (shouldHandleViewLockManually && ![view lockFocusIfCanDraw]) {
194        qWarning() << "failed to lock focus of" << view;
195        return;
196    }
197
198    const qreal devicePixelRatio = m_image.devicePixelRatio();
199
200    // If the flushed window is a content view, and we're filling the drawn area
201    // completely, or it doesn't have a window background we need to preserve,
202    // we can get away with copying instead of blending the backing store.
203    QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window->handle());
204    const NSCompositingOperation compositingOperation = cocoaWindow->isContentView()
205        && (cocoaWindow->isOpaque() || view.window.backgroundColor == NSColor.clearColor)
206            ? NSCompositingOperationCopy : NSCompositingOperationSourceOver;
207
208#ifdef QT_DEBUG
209    static bool debugBackingStoreFlush = [[NSUserDefaults standardUserDefaults]
210        boolForKey:@"QtCocoaDebugBackingStoreFlush"];
211#endif
212
213    // -------------------------------------------------------------------------
214
215    // The current contexts is typically a NSWindowGraphicsContext, but can be
216    // NSBitmapGraphicsContext e.g. when debugging the view hierarchy in Xcode.
217    // If we need to distinguish things here in the future, we can use e.g.
218    // [NSGraphicsContext drawingToScreen], or the attributes of the context.
219    NSGraphicsContext *graphicsContext = [NSGraphicsContext currentContext];
220    Q_ASSERT_X(graphicsContext, "QCocoaBackingStore",
221        "Focusing the view should give us a current graphics context");
222
223    // Tag backingstore image with color space based on the window.
224    // Note: This does not copy the underlying image data.
225    QCFType<CGImageRef> cgImage = CGImageCreateCopyWithColorSpace(
226        QCFType<CGImageRef>(m_image.toCGImage()), colorSpace());
227
228    // Create temporary image to use for blitting, without copying image data
229    NSImage *backingStoreImage = [[[NSImage alloc] initWithCGImage:cgImage size:NSZeroSize] autorelease];
230
231    QRegion clippedRegion = region;
232    for (QWindow *w = window; w; w = w->parent()) {
233        if (!w->mask().isEmpty()) {
234            clippedRegion &= w == window ? w->mask()
235                : w->mask().translated(window->mapFromGlobal(w->mapToGlobal(QPoint(0, 0))));
236        }
237    }
238
239    for (const QRect &viewLocalRect : clippedRegion) {
240        QPoint backingStoreOffset = viewLocalRect.topLeft() + offset;
241        QRect backingStoreRect(backingStoreOffset * devicePixelRatio, viewLocalRect.size() * devicePixelRatio);
242        if (graphicsContext.flipped) // Flip backingStoreRect to match graphics context
243            backingStoreRect.moveTop(m_image.height() - (backingStoreRect.y() + backingStoreRect.height()));
244
245        CGRect viewRect = viewLocalRect.toCGRect();
246
247        [backingStoreImage drawInRect:viewRect fromRect:backingStoreRect.toCGRect()
248            operation:compositingOperation fraction:1.0 respectFlipped:YES hints:nil];
249
250#ifdef QT_DEBUG
251        if (Q_UNLIKELY(debugBackingStoreFlush)) {
252            [[NSColor colorWithCalibratedRed:drand48() green:drand48() blue:drand48() alpha:0.3] set];
253            [NSBezierPath fillRect:viewRect];
254
255            if (drawingOutsideOfDisplayCycle) {
256                [[[NSColor magentaColor] colorWithAlphaComponent:0.5] set];
257                [NSBezierPath strokeLineFromPoint:viewLocalRect.topLeft().toCGPoint()
258                    toPoint:viewLocalRect.bottomRight().toCGPoint()];
259            }
260        }
261#endif
262    }
263
264    // -------------------------------------------------------------------------
265
266    if (shouldHandleViewLockManually)
267        [view unlockFocus];
268
269    if (drawingOutsideOfDisplayCycle) {
270        redrawRoundedBottomCorners([view convertRect:region.boundingRect().toCGRect() toView:nil]);
271        [view.window flushWindow];
272    }
273
274
275    // Done flushing to NSWindow backingstore
276
277    QCocoaWindow *topLevelCocoaWindow = static_cast<QCocoaWindow *>(topLevelWindow->handle());
278    if (Q_UNLIKELY(topLevelCocoaWindow->m_needsInvalidateShadow)) {
279        qCDebug(lcQpaBackingStore) << "Invalidating window shadow for" << topLevelCocoaWindow;
280        [topLevelView.window invalidateShadow];
281        topLevelCocoaWindow->m_needsInvalidateShadow = false;
282    }
283}
284
285/*
286    When drawing outside of the display cycle, which Qt Widget does a lot,
287    we end up drawing over the NSThemeFrame, losing the rounded corners of
288    windows in the process.
289
290    To work around this, until we've enabled updates via setNeedsDisplay and/or
291    enabled layer-backed views, we ask the NSWindow to redraw the bottom corners
292    if they intersect with the flushed region.
293
294    This is the same logic used internally by e.g [NSView displayIfNeeded],
295    [NSRulerView _scrollToMatchContentView], and [NSClipView _immediateScrollToPoint:],
296    as well as the workaround used by WebKit to fix a similar bug:
297
298    https://trac.webkit.org/changeset/85376/webkit
299*/
300void QNSWindowBackingStore::redrawRoundedBottomCorners(CGRect windowRect) const
301{
302#if !defined(QT_APPLE_NO_PRIVATE_APIS)
303    Q_ASSERT(this->window()->handle());
304    NSWindow *window = static_cast<QCocoaWindow *>(this->window()->handle())->nativeWindow();
305
306    static SEL intersectBottomCornersWithRect = NSSelectorFromString(
307        [NSString stringWithFormat:@"_%s%s:", "intersectBottomCorners", "WithRect"]);
308    if (NSMethodSignature *signature = [window methodSignatureForSelector:intersectBottomCornersWithRect]) {
309        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
310        invocation.target = window;
311        invocation.selector = intersectBottomCornersWithRect;
312        [invocation setArgument:&windowRect atIndex:2];
313        [invocation invoke];
314
315        NSRect cornerOverlap = NSZeroRect;
316        [invocation getReturnValue:&cornerOverlap];
317        if (!NSIsEmptyRect(cornerOverlap)) {
318            static SEL maskRoundedBottomCorners = NSSelectorFromString(
319                [NSString stringWithFormat:@"_%s%s:", "maskRounded", "BottomCorners"]);
320            if ((signature = [window methodSignatureForSelector:maskRoundedBottomCorners])) {
321                invocation = [NSInvocation invocationWithMethodSignature:signature];
322                invocation.target = window;
323                invocation.selector = maskRoundedBottomCorners;
324                [invocation setArgument:&cornerOverlap atIndex:2];
325                [invocation invoke];
326            }
327        }
328    }
329#else
330    Q_UNUSED(windowRect);
331#endif
332}
333
334// ----------------------------------------------------------------------------
335
336QCALayerBackingStore::QCALayerBackingStore(QWindow *window)
337    : QCocoaBackingStore(window)
338{
339    qCDebug(lcQpaBackingStore) << "Creating QCALayerBackingStore for" << window;
340    m_buffers.resize(1);
341
342    observeBackingPropertiesChanges();
343    window->installEventFilter(this);
344}
345
346QCALayerBackingStore::~QCALayerBackingStore()
347{
348}
349
350void QCALayerBackingStore::observeBackingPropertiesChanges()
351{
352    Q_ASSERT(window()->handle());
353    NSView *view = static_cast<QCocoaWindow *>(window()->handle())->view();
354    m_backingPropertiesObserver = QMacNotificationObserver(view.window,
355        NSWindowDidChangeBackingPropertiesNotification, [this]() {
356            backingPropertiesChanged();
357        });
358}
359
360bool QCALayerBackingStore::eventFilter(QObject *watched, QEvent *event)
361{
362    Q_ASSERT(watched == window());
363
364    if (event->type() == QEvent::PlatformSurface) {
365        auto *surfaceEvent = static_cast<QPlatformSurfaceEvent*>(event);
366        if (surfaceEvent->surfaceEventType() == QPlatformSurfaceEvent::SurfaceCreated)
367            observeBackingPropertiesChanges();
368        else
369            m_backingPropertiesObserver = QMacNotificationObserver();
370    }
371
372    return false;
373}
374
375void QCALayerBackingStore::resize(const QSize &size, const QRegion &staticContents)
376{
377    qCDebug(lcQpaBackingStore) << "Resize requested to" << size;
378
379    if (!staticContents.isNull())
380        qCWarning(lcQpaBackingStore) << "QCALayerBackingStore does not support static contents";
381
382    m_requestedSize = size;
383}
384
385void QCALayerBackingStore::beginPaint(const QRegion &region)
386{
387    Q_UNUSED(region);
388
389    QMacAutoReleasePool pool;
390
391    qCInfo(lcQpaBackingStore) << "Beginning paint of" << region << "into backingstore of" << m_requestedSize;
392
393    ensureBackBuffer(); // Find an unused back buffer, or reserve space for a new one
394
395    const bool bufferWasRecreated = recreateBackBufferIfNeeded();
396
397    m_buffers.back()->lock(QPlatformGraphicsBuffer::SWWriteAccess);
398
399    // Although undocumented, QBackingStore::beginPaint expects the painted region
400    // to be cleared before use if the window has a surface format with an alpha.
401    // Fresh IOSurfaces are already cleared, so we don't need to clear those.
402    if (m_clearSurfaceOnPaint && !bufferWasRecreated && window()->format().hasAlpha()) {
403        qCDebug(lcQpaBackingStore) << "Clearing" << region << "before use";
404        QPainter painter(m_buffers.back()->asImage());
405        painter.setCompositionMode(QPainter::CompositionMode_Source);
406        for (const QRect &rect : region)
407            painter.fillRect(rect, Qt::transparent);
408    }
409
410    m_paintedRegion += region;
411}
412
413void QCALayerBackingStore::ensureBackBuffer()
414{
415    if (window()->format().swapBehavior() == QSurfaceFormat::SingleBuffer)
416        return;
417
418    // The current back buffer may have been assigned to a layer in a previous flush,
419    // but we deferred the swap. Do it now if the surface has been picked up by CA.
420    if (m_buffers.back() && m_buffers.back()->isInUse() && m_buffers.back() != m_buffers.front()) {
421        qCInfo(lcQpaBackingStore) << "Back buffer has been picked up by CA, swapping to front";
422        std::swap(m_buffers.back(), m_buffers.front());
423    }
424
425    if (Q_UNLIKELY(lcQpaBackingStore().isDebugEnabled())) {
426        // ┌───────┬───────┬───────┬─────┬──────┐
427        // │ front ┊ spare ┊ spare ┊ ... ┊ back │
428        // └───────┴───────┴───────┴─────┴──────┘
429        for (const auto &buffer : m_buffers) {
430            qCDebug(lcQpaBackingStore).nospace() << "  "
431                << (buffer == m_buffers.front() ? "front" :
432                    buffer == m_buffers.back()  ? " back" :
433                                                  "spare"
434                ) << ": " << buffer.get();
435        }
436    }
437
438    // Ensure our back buffer is ready to draw into. If not, find a buffer that
439    // is not in use, or reserve space for a new buffer if none can be found.
440    for (auto &buffer : backwards(m_buffers)) {
441        if (!buffer || !buffer->isInUse()) {
442            // Buffer is okey to use, swap if necessary
443            if (buffer != m_buffers.back())
444                std::swap(buffer, m_buffers.back());
445            qCDebug(lcQpaBackingStore) << "Using back buffer" << m_buffers.back().get();
446
447            static const int kMaxSwapChainDepth = 3;
448            if (m_buffers.size() > kMaxSwapChainDepth) {
449                qCDebug(lcQpaBackingStore) << "Reducing swap chain depth to" << kMaxSwapChainDepth;
450                m_buffers.erase(std::next(m_buffers.begin(), 1), std::prev(m_buffers.end(), 2));
451            }
452
453            break;
454        } else if (buffer == m_buffers.front()) {
455            // We've exhausted the available buffers, make room for a new one
456            const int swapChainDepth = m_buffers.size() + 1;
457            qCDebug(lcQpaBackingStore) << "Available buffers exhausted, increasing swap chain depth to" << swapChainDepth;
458            m_buffers.resize(swapChainDepth);
459            break;
460        }
461    }
462
463    Q_ASSERT(!m_buffers.back() || !m_buffers.back()->isInUse());
464}
465
466// Disabled until performance issue on 5K iMac Pro has been investigated further,
467// as rounding up during resize will typically result in full screen buffer sizes
468// and low frame rate also for smaller window sizes.
469#define USE_LAZY_BUFFER_ALLOCATION_DURING_LIVE_WINDOW_RESIZE 0
470
471bool QCALayerBackingStore::recreateBackBufferIfNeeded()
472{
473    const QCocoaWindow *platformWindow = static_cast<QCocoaWindow *>(window()->handle());
474    const qreal devicePixelRatio = platformWindow->devicePixelRatio();
475    QSize requestedBufferSize = m_requestedSize * devicePixelRatio;
476
477    const NSView *backingStoreView = platformWindow->view();
478    Q_UNUSED(backingStoreView);
479
480    auto bufferSizeMismatch = [&](const QSize requested, const QSize actual) {
481#if USE_LAZY_BUFFER_ALLOCATION_DURING_LIVE_WINDOW_RESIZE
482        if (backingStoreView.inLiveResize) {
483            // Prevent over-eager buffer allocation during window resize by reusing larger buffers
484            return requested.width() > actual.width() || requested.height() > actual.height();
485        }
486#endif
487        return requested != actual;
488    };
489
490    if (!m_buffers.back() || bufferSizeMismatch(requestedBufferSize, m_buffers.back()->size())) {
491#if USE_LAZY_BUFFER_ALLOCATION_DURING_LIVE_WINDOW_RESIZE
492        if (backingStoreView.inLiveResize) {
493            // Prevent over-eager buffer allocation during window resize by rounding up
494            QSize nativeScreenSize = window()->screen()->geometry().size() * devicePixelRatio;
495            requestedBufferSize = QSize(qNextPowerOfTwo(requestedBufferSize.width()),
496                qNextPowerOfTwo(requestedBufferSize.height())).boundedTo(nativeScreenSize);
497        }
498#endif
499
500        qCInfo(lcQpaBackingStore) << "Creating surface of" << requestedBufferSize
501            << "based on requested" << m_requestedSize << "and dpr =" << devicePixelRatio;
502
503        static auto pixelFormat = QImage::toPixelFormat(QImage::Format_ARGB32_Premultiplied);
504        m_buffers.back().reset(new GraphicsBuffer(requestedBufferSize, devicePixelRatio, pixelFormat, colorSpace()));
505        return true;
506    }
507
508    return false;
509}
510
511QPaintDevice *QCALayerBackingStore::paintDevice()
512{
513    Q_ASSERT(m_buffers.back());
514    return m_buffers.back()->asImage();
515}
516
517void QCALayerBackingStore::endPaint()
518{
519    qCInfo(lcQpaBackingStore) << "Paint ended with painted region" << m_paintedRegion;
520    m_buffers.back()->unlock();
521}
522
523void QCALayerBackingStore::flush(QWindow *flushedWindow, const QRegion &region, const QPoint &offset)
524{
525    Q_UNUSED(region);
526    Q_UNUSED(offset);
527
528    if (!prepareForFlush())
529        return;
530
531    if (flushedWindow != window()) {
532        flushSubWindow(flushedWindow);
533        return;
534    }
535
536    QMacAutoReleasePool pool;
537
538    NSView *flushedView = static_cast<QCocoaWindow *>(flushedWindow->handle())->view();
539
540    // If the backingstore is just flushed, without being painted to first, then we may
541    // end in a situation where the backingstore is flushed to a layer with a different
542    // scale factor than the one it was created for in beginPaint. This is the client's
543    // fault in not picking up the change in scale factor of the window and re-painting
544    // the backingstore accordingly. To smoothing things out, we warn about this situation,
545    // and change the layer's contentsScale to match the scale of the back buffer, so that
546    // we at least cover the whole layer. This is necessary since we set the view's
547    // contents placement policy to NSViewLayerContentsPlacementTopLeft, which means
548    // AppKit will not do any scaling on our behalf.
549    if (m_buffers.back()->devicePixelRatio() != flushedView.layer.contentsScale) {
550        qCWarning(lcQpaBackingStore) << "Back buffer dpr of" << m_buffers.back()->devicePixelRatio()
551            << "doesn't match" << flushedView.layer << "contents scale of" << flushedView.layer.contentsScale
552            << "- updating layer to match.";
553        flushedView.layer.contentsScale = m_buffers.back()->devicePixelRatio();
554    }
555
556    const bool isSingleBuffered = window()->format().swapBehavior() == QSurfaceFormat::SingleBuffer;
557
558    id backBufferSurface = (__bridge id)m_buffers.back()->surface();
559    if (!isSingleBuffered && flushedView.layer.contents == backBufferSurface) {
560        // We've managed to paint to the back buffer again before Core Animation had time
561        // to flush the transaction and persist the layer changes to the window server, or
562        // we've been asked to flush without painting anything. The layer already knows about
563        // the back buffer, and we don't need to re-apply it to pick up any possible surface
564        // changes, so bail out early.
565        qCInfo(lcQpaBackingStore).nospace() << "Skipping flush of " << flushedView
566            << ", layer already reflects back buffer";
567        return;
568    }
569
570    // Trigger a new display cycle if there isn't one. This ensures that our layer updates
571    // are committed as part of a display-cycle instead of on the next runloop pass. This
572    // means CA won't try to throttle us if we flush too fast, and we'll coalesce our flush
573    // with other pending view and layer updates.
574    flushedView.window.viewsNeedDisplay = YES;
575
576    if (isSingleBuffered) {
577        // The private API [CALayer reloadValueForKeyPath:@"contents"] would be preferable,
578        // but barring any side effects or performance issues we opt for the hammer for now.
579        flushedView.layer.contents = nil;
580    }
581
582    qCInfo(lcQpaBackingStore) << "Flushing" << backBufferSurface
583        << "to" << flushedView.layer << "of" << flushedView;
584
585    flushedView.layer.contents = backBufferSurface;
586
587    // Since we may receive multiple flushes before a new frame is started, we do not
588    // swap any buffers just yet. Instead we check in the next beginPaint if the layer's
589    // surface is in use, and if so swap to an unused surface as the new back buffer.
590
591    // Note: Ideally CoreAnimation would mark a surface as in use the moment we assign
592    // it to a layer, but as that's not the case we may end up painting to the same back
593    // buffer once more if we are painting faster than CA can ship the surfaces over to
594    // the window server.
595}
596
597void QCALayerBackingStore::flushSubWindow(QWindow *subWindow)
598{
599    qCInfo(lcQpaBackingStore) << "Flushing sub-window" << subWindow
600        << "via its own backingstore";
601
602    auto &subWindowBackingStore = m_subWindowBackingstores[subWindow];
603    if (!subWindowBackingStore) {
604        subWindowBackingStore.reset(new QCALayerBackingStore(subWindow));
605        QObject::connect(subWindow, &QObject::destroyed, this, &QCALayerBackingStore::windowDestroyed);
606        subWindowBackingStore->m_clearSurfaceOnPaint = false;
607    }
608
609    auto subWindowSize = subWindow->size();
610    static const auto kNoStaticContents = QRegion();
611    subWindowBackingStore->resize(subWindowSize, kNoStaticContents);
612
613    auto subWindowLocalRect = QRect(QPoint(), subWindowSize);
614    subWindowBackingStore->beginPaint(subWindowLocalRect);
615
616    QPainter painter(subWindowBackingStore->m_buffers.back()->asImage());
617    painter.setCompositionMode(QPainter::CompositionMode_Source);
618
619    NSView *backingStoreView = static_cast<QCocoaWindow *>(window()->handle())->view();
620    NSView *flushedView = static_cast<QCocoaWindow *>(subWindow->handle())->view();
621    auto subviewRect = [flushedView convertRect:flushedView.bounds toView:backingStoreView];
622    auto scale = flushedView.layer.contentsScale;
623    subviewRect = CGRectApplyAffineTransform(subviewRect, CGAffineTransformMakeScale(scale, scale));
624
625    m_buffers.back()->lock(QPlatformGraphicsBuffer::SWReadAccess);
626    const QImage *backingStoreImage = m_buffers.back()->asImage();
627    painter.drawImage(subWindowLocalRect, *backingStoreImage, QRectF::fromCGRect(subviewRect));
628    m_buffers.back()->unlock();
629
630    painter.end();
631    subWindowBackingStore->endPaint();
632    subWindowBackingStore->flush(subWindow, subWindowLocalRect, QPoint());
633
634    qCInfo(lcQpaBackingStore) << "Done flushing sub-window" << subWindow;
635}
636
637void QCALayerBackingStore::windowDestroyed(QObject *object)
638{
639    auto *window = static_cast<QWindow*>(object);
640    qCInfo(lcQpaBackingStore) << "Removing backingstore for sub-window" << window;
641    m_subWindowBackingstores.erase(window);
642}
643
644#ifndef QT_NO_OPENGL
645void QCALayerBackingStore::composeAndFlush(QWindow *window, const QRegion &region, const QPoint &offset,
646                                    QPlatformTextureList *textures, bool translucentBackground)
647{
648    if (!prepareForFlush())
649        return;
650
651    QPlatformBackingStore::composeAndFlush(window, region, offset, textures, translucentBackground);
652}
653#endif
654
655QImage QCALayerBackingStore::toImage() const
656{
657    if (!const_cast<QCALayerBackingStore*>(this)->prepareForFlush())
658        return QImage();
659
660    // We need to make a copy here, as the returned image could be used just
661    // for reading, in which case it won't detach, and then the underlying
662    // image data might change under the feet of the client when we re-use
663    // the buffer at a later point.
664    m_buffers.back()->lock(QPlatformGraphicsBuffer::SWReadAccess);
665    QImage imageCopy = m_buffers.back()->asImage()->copy();
666    m_buffers.back()->unlock();
667    return imageCopy;
668}
669
670void QCALayerBackingStore::backingPropertiesChanged()
671{
672    // Ideally this would be plumbed from the platform layer to QtGui, and
673    // the QBackingStore would be recreated, but we don't have that code yet,
674    // so at least make sure we update our backingstore when the backing
675    // properties (color space e.g.) are changed.
676
677    Q_ASSERT(window()->handle());
678
679    qCDebug(lcQpaBackingStore) << "Backing properties for" << window() << "did change";
680
681    qCDebug(lcQpaBackingStore) << "Updating color space of existing buffers";
682    for (auto &buffer : m_buffers) {
683        if (buffer)
684            buffer->setColorSpace(colorSpace());
685    }
686}
687
688QPlatformGraphicsBuffer *QCALayerBackingStore::graphicsBuffer() const
689{
690    return m_buffers.back().get();
691}
692
693bool QCALayerBackingStore::prepareForFlush()
694{
695    if (!m_buffers.back()) {
696        qCWarning(lcQpaBackingStore) << "Tried to flush backingstore without painting to it first";
697        return false;
698    }
699
700    // Update dirty state of buffers based on what was painted. The back buffer will be
701    // less dirty, since we painted to it, while other buffers will become more dirty.
702    // This allows us to minimize copies between front and back buffers on swap in the
703    // cases where the painted region overlaps with the previous frame (front buffer).
704    for (const auto &buffer : m_buffers) {
705        if (buffer == m_buffers.back())
706            buffer->dirtyRegion -= m_paintedRegion;
707        else
708            buffer->dirtyRegion += m_paintedRegion;
709    }
710
711    // After painting, the back buffer is only guaranteed to have content for the painted
712    // region, and may still have dirty areas that need to be synced up with the front buffer,
713    // if we have one. We know that the front buffer is always up to date.
714    if (!m_buffers.back()->dirtyRegion.isEmpty() && m_buffers.front() != m_buffers.back()) {
715        QRegion preserveRegion = m_buffers.back()->dirtyRegion;
716        qCDebug(lcQpaBackingStore) << "Preserving" << preserveRegion << "from front to back buffer";
717
718        m_buffers.front()->lock(QPlatformGraphicsBuffer::SWReadAccess);
719        const QImage *frontBuffer = m_buffers.front()->asImage();
720
721        const QRect frontSurfaceBounds(QPoint(0, 0), m_buffers.front()->size());
722        const qreal sourceDevicePixelRatio = frontBuffer->devicePixelRatio();
723
724        m_buffers.back()->lock(QPlatformGraphicsBuffer::SWWriteAccess);
725        QPainter painter(m_buffers.back()->asImage());
726        painter.setCompositionMode(QPainter::CompositionMode_Source);
727
728        // Let painter operate in device pixels, to make it easier to compare coordinates
729        const qreal targetDevicePixelRatio = painter.device()->devicePixelRatio();
730        painter.scale(1.0 / targetDevicePixelRatio, 1.0 / targetDevicePixelRatio);
731
732        for (const QRect &rect : preserveRegion) {
733            QRect sourceRect(rect.topLeft() * sourceDevicePixelRatio, rect.size() * sourceDevicePixelRatio);
734            QRect targetRect(rect.topLeft() * targetDevicePixelRatio, rect.size() * targetDevicePixelRatio);
735
736#ifdef QT_DEBUG
737            if (Q_UNLIKELY(!frontSurfaceBounds.contains(sourceRect.bottomRight()))) {
738                qCWarning(lcQpaBackingStore) << "Front buffer too small to preserve"
739                    << QRegion(sourceRect).subtracted(frontSurfaceBounds);
740            }
741#endif
742            painter.drawImage(targetRect, *frontBuffer, sourceRect);
743        }
744
745        m_buffers.back()->unlock();
746        m_buffers.front()->unlock();
747
748        // The back buffer is now completely in sync, ready to be presented
749        m_buffers.back()->dirtyRegion = QRegion();
750    }
751
752    // Prepare for another round of painting
753    m_paintedRegion = QRegion();
754
755    return true;
756}
757
758// ----------------------------------------------------------------------------
759
760QCALayerBackingStore::GraphicsBuffer::GraphicsBuffer(const QSize &size, qreal devicePixelRatio,
761                                const QPixelFormat &format, QCFType<CGColorSpaceRef> colorSpace)
762    : QIOSurfaceGraphicsBuffer(size, format)
763    , dirtyRegion(0, 0, size.width() / devicePixelRatio, size.height() / devicePixelRatio)
764    , m_devicePixelRatio(devicePixelRatio)
765{
766    setColorSpace(colorSpace);
767}
768
769QImage *QCALayerBackingStore::GraphicsBuffer::asImage()
770{
771    if (m_image.isNull()) {
772        qCDebug(lcQpaBackingStore) << "Setting up paint device for" << this;
773        CFRetain(surface());
774        m_image = QImage(data(), size().width(), size().height(),
775            bytesPerLine(), QImage::toImageFormat(format()),
776            QImageCleanupFunction(CFRelease), surface());
777        m_image.setDevicePixelRatio(m_devicePixelRatio);
778    }
779
780    Q_ASSERT_X(m_image.constBits() == data(), "QCALayerBackingStore",
781        "IOSurfaces should have have a fixed location in memory once created");
782
783    return &m_image;
784}
785
786#include "moc_qcocoabackingstore.cpp"
787
788QT_END_NAMESPACE
789