Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qcocoabackingstore.mm
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#include <AppKit/AppKit.h>
6
8
9#include "qcocoawindow.h"
10#include "qcocoahelpers.h"
11
12#include <QtCore/qmath.h>
13#include <QtCore/private/qcore_mac_p.h>
14#include <QtGui/qpainter.h>
15
16#include <QuartzCore/CATransaction.h>
17
18QT_BEGIN_NAMESPACE
19
20QCocoaBackingStore::QCocoaBackingStore(QWindow *window)
21 : QPlatformBackingStore(window)
22{
23}
24
25QCFType<CGColorSpaceRef> QCocoaBackingStore::colorSpace() const
26{
27 const auto *platformWindow = static_cast<QCocoaWindow *>(window()->handle());
28 const QNSView *view = qnsview_cast(platformWindow->view());
29 return QCFType<CGColorSpaceRef>::constructFromGet(view.colorSpace.CGColorSpace);
30}
31
32// ----------------------------------------------------------------------------
33
34QCALayerBackingStore::QCALayerBackingStore(QWindow *window)
35 : QCocoaBackingStore(window)
36{
37 qCDebug(lcQpaBackingStore) << "Creating QCALayerBackingStore for" << window
38 << "with" << window->format();
39
40 m_buffers.resize(1);
41
42 observeBackingPropertiesChanges();
43 window->installEventFilter(this);
44}
45
49
50void QCALayerBackingStore::observeBackingPropertiesChanges()
51{
52 Q_ASSERT(window()->handle());
53 NSView *view = static_cast<QCocoaWindow *>(window()->handle())->view();
54 m_backingPropertiesObserver = QMacNotificationObserver(view.window,
55 NSWindowDidChangeBackingPropertiesNotification, [this]() {
56 backingPropertiesChanged();
57 });
58}
59
60bool QCALayerBackingStore::eventFilter(QObject *watched, QEvent *event)
61{
62 Q_ASSERT(watched == window());
63
64 if (event->type() == QEvent::PlatformSurface) {
65 auto *surfaceEvent = static_cast<QPlatformSurfaceEvent*>(event);
66 if (surfaceEvent->surfaceEventType() == QPlatformSurfaceEvent::SurfaceCreated)
67 observeBackingPropertiesChanges();
68 else
69 m_backingPropertiesObserver = QMacNotificationObserver();
70 }
71
72 return false;
73}
74
75void QCALayerBackingStore::resize(const QSize &size, const QRegion &staticContents)
76{
77 qCDebug(lcQpaBackingStore) << "Resize requested to" << size
78 << "with static contents" << staticContents;
79
80 m_requestedSize = size;
81 m_staticContents = staticContents;
82}
83
84void QCALayerBackingStore::beginPaint(const QRegion &region)
85{
86 Q_UNUSED(region);
87
88 QMacAutoReleasePool pool;
89
90 qCInfo(lcQpaBackingStore) << "Beginning paint of" << region << "into backingstore of" << m_requestedSize;
91
92 if (m_requestedSize.isEmpty()) {
93 // We can't create IOSurfaces with and empty size, so instead reset our back buffer
94 qCDebug(lcQpaBackingStore) << "Size is empty, throwing away back buffer";
95 m_buffers.back().reset(nullptr);
96 return;
97 }
98
99 ensureBackBuffer(); // Find an unused back buffer, or reserve space for a new one
100
101 const bool bufferWasRecreated = recreateBackBufferIfNeeded();
102
103 m_buffers.back()->lock(QPlatformGraphicsBuffer::SWWriteAccess);
104
105 // Although undocumented, QBackingStore::beginPaint expects the painted region
106 // to be cleared before use if the window has a surface format with an alpha.
107 // Fresh IOSurfaces are already cleared, so we don't need to clear those.
108 if (m_clearSurfaceOnPaint && !bufferWasRecreated && window()->format().hasAlpha()) {
109 qCDebug(lcQpaBackingStore) << "Clearing" << region << "before use";
110 QPainter painter(m_buffers.back()->asImage());
111 painter.setCompositionMode(QPainter::CompositionMode_Source);
112 for (const QRect &rect : region)
113 painter.fillRect(rect, Qt::transparent);
114 }
115
116 // We assume the client is going to paint the entire region
117 updateDirtyStates(region);
118}
119
120void QCALayerBackingStore::ensureBackBuffer()
121{
122 if (window()->format().swapBehavior() == QSurfaceFormat::SingleBuffer)
123 return;
124
125 if (Q_UNLIKELY(lcQpaBackingStore().isDebugEnabled())) {
126 // ┌───────┬───────┬───────┬─────┬──────┐
127 // │ front ┊ spare ┊ spare ┊ ... ┊ back │
128 // └───────┴───────┴───────┴─────┴──────┘
129 for (const auto &buffer : m_buffers) {
130 qCDebug(lcQpaBackingStore).nospace() << " "
131 << (buffer == m_buffers.front() ? "front" :
132 buffer == m_buffers.back() ? " back" :
133 "spare"
134 ) << ": " << buffer.get();
135 }
136 }
137
138 // Ensure our back buffer is ready to draw into. If not, find a buffer that
139 // is not in use, or reserve space for a new buffer if none can be found.
140 for (auto &buffer : backwards(m_buffers)) {
141 if (!buffer || !buffer->isInUse()) {
142 // Buffer is okey to use, swap if necessary
143 if (buffer != m_buffers.back())
144 std::swap(buffer, m_buffers.back());
145 qCDebug(lcQpaBackingStore) << "Using back buffer" << m_buffers.back().get();
146
147 static const int kMaxSwapChainDepth = 3;
148 if (m_buffers.size() > kMaxSwapChainDepth) {
149 qCDebug(lcQpaBackingStore) << "Reducing swap chain depth to" << kMaxSwapChainDepth;
150 m_buffers.erase(std::next(m_buffers.begin(), 1), std::prev(m_buffers.end(), 2));
151 }
152
153 break;
154 } else if (buffer == m_buffers.front()) {
155 // We've exhausted the available buffers, make room for a new one
156 const int swapChainDepth = m_buffers.size() + 1;
157 qCDebug(lcQpaBackingStore) << "Available buffers exhausted, increasing swap chain depth to" << swapChainDepth;
158 m_buffers.resize(swapChainDepth);
159 break;
160 }
161 }
162
163 Q_ASSERT(!m_buffers.back() || !m_buffers.back()->isInUse());
164}
165
166// Disabled until performance issue on 5K iMac Pro has been investigated further,
167// as rounding up during resize will typically result in full screen buffer sizes
168// and low frame rate also for smaller window sizes.
169#define USE_LAZY_BUFFER_ALLOCATION_DURING_LIVE_WINDOW_RESIZE 0
170
171bool QCALayerBackingStore::recreateBackBufferIfNeeded()
172{
173 const QCocoaWindow *platformWindow = static_cast<QCocoaWindow *>(window()->handle());
174 const qreal devicePixelRatio = platformWindow->devicePixelRatio();
175 QSize requestedBufferSize = m_requestedSize * devicePixelRatio;
176
177 const NSView *backingStoreView = platformWindow->view();
178 Q_UNUSED(backingStoreView);
179
180 auto bufferSizeMismatch = [&](const QSize requested, const QSize actual) {
182 if (backingStoreView.inLiveResize) {
183 // Prevent over-eager buffer allocation during window resize by reusing larger buffers
184 return requested.width() > actual.width() || requested.height() > actual.height();
185 }
186#endif
187 return requested != actual;
188 };
189
190 if (!m_buffers.back() || bufferSizeMismatch(requestedBufferSize, m_buffers.back()->size())) {
192 if (backingStoreView.inLiveResize) {
193 // Prevent over-eager buffer allocation during window resize by rounding up
194 QSize nativeScreenSize = window()->screen()->geometry().size() * devicePixelRatio;
195 requestedBufferSize = QSize(qNextPowerOfTwo(requestedBufferSize.width()),
196 qNextPowerOfTwo(requestedBufferSize.height())).boundedTo(nativeScreenSize);
197 }
198#endif
199
200 qCInfo(lcQpaBackingStore)<< "Creating surface of" << requestedBufferSize
201 << "for" << window() << "based on requested" << m_requestedSize
202 << "dpr =" << devicePixelRatio << "and color space" << colorSpace();
203
204 static auto pixelFormat = QImage::toPixelFormat(QImage::Format_ARGB32_Premultiplied);
205 auto *newBackBuffer = new GraphicsBuffer(requestedBufferSize, devicePixelRatio, pixelFormat, colorSpace());
206 newBackBuffer->setObjectName("Qt Raster Backingstore");
207
208 if (!m_staticContents.isEmpty() && m_buffers.back()) {
209 // We implicitly support static backingstore content as a result of
210 // finalizing the back buffer on flush, where we copy any non-painted
211 // areas from the front buffer. But there is no guarantee that a resize
212 // will always come after a flush, where we have a pristine front buffer
213 // to copy from. It may come after a few begin/endPaints, where the back
214 // buffer then contains (part of) the latest state. We also have the case
215 // of single-buffered backingstore, where the front and back buffer is
216 // the same, which means we must do the copy from the old back buffer
217 // to the newly resized buffer now, before we replace it below.
218
219 // If the back buffer has been partially filled already, we need to
220 // copy parts of the static content from that. The rest we copy from
221 // the front buffer.
222 const QRegion backBufferRegion = m_staticContents - m_buffers.back()->dirtyRegion;
223 const QRegion frontBufferRegion = m_staticContents - backBufferRegion;
224
225 qCInfo(lcQpaBackingStore) << "Preserving static content" << backBufferRegion
226 << "from back buffer, and" << frontBufferRegion << "from front buffer";
227
228 newBackBuffer->lock(QPlatformGraphicsBuffer::SWWriteAccess);
229 blitBuffer(m_buffers.back().get(), backBufferRegion, newBackBuffer);
230 Q_ASSERT(frontBufferRegion.isEmpty() || m_buffers.front());
231 blitBuffer(m_buffers.front().get(), frontBufferRegion, newBackBuffer);
232 newBackBuffer->unlock();
233
234 // The new back buffer now is valid for the static contents region.
235 // We don't need to maintain the static contents region for resizes
236 // of any other buffers in the swap chain, as these will finalize
237 // their content on flush from the buffer we just filled, and we
238 // don't need to mark them dirty for the area we just filled, as
239 // new buffers are fully dirty when created.
240 newBackBuffer->dirtyRegion -= m_staticContents;
241 m_staticContents = {};
242 }
243
244 m_buffers.back().reset(newBackBuffer);
245 return true;
246 }
247
248 return false;
249}
250
252{
253 if (m_buffers.back()) {
254 return m_buffers.back()->asImage();
255 } else {
256 static QImage fallbackDevice;
257 return &fallbackDevice;
258 }
259}
260
262{
263 if (!m_buffers.back())
264 return;
265
266 qCInfo(lcQpaBackingStore) << "Paint ended. Back buffer valid region is now" << m_buffers.back()->validRegion();
267 m_buffers.back()->unlock();
268
269 // Since we can have multiple begin/endPaint rounds before a flush
270 // we defer finalizing the back buffer until its content is needed.
271}
272
273bool QCALayerBackingStore::scroll(const QRegion &region, int dx, int dy)
274{
275 if (!m_buffers.back()) {
276 qCInfo(lcQpaBackingStore) << "Scroll requested with no back buffer. Ignoring.";
277 return false;
278 }
279
280 const QPoint scrollDelta(dx, dy);
281 qCInfo(lcQpaBackingStore) << "Scrolling" << region << "by" << scrollDelta;
282
283 ensureBackBuffer();
284 recreateBackBufferIfNeeded();
285
286 const QRegion inPlaceRegion = region - m_buffers.back()->dirtyRegion;
287 const QRegion frontBufferRegion = region - inPlaceRegion;
288
289 QMacAutoReleasePool pool;
290
291 m_buffers.back()->lock(QPlatformGraphicsBuffer::SWWriteAccess);
292
293 if (!inPlaceRegion.isEmpty()) {
294 // We have to scroll everything in one go, instead of scrolling the
295 // individual rects of the region, as otherwise we may end up reading
296 // already overwritten (scrolled) pixels.
297 const QRect inPlaceBoundingRect = inPlaceRegion.boundingRect();
298
299 qCDebug(lcQpaBackingStore) << "Scrolling" << inPlaceBoundingRect << "in place";
300 QImage *backBufferImage = m_buffers.back()->asImage();
301 const qreal devicePixelRatio = backBufferImage->devicePixelRatio();
302 const QPoint devicePixelDelta = scrollDelta * devicePixelRatio;
303
304 extern void qt_scrollRectInImage(QImage &, const QRect &, const QPoint &);
305
306 qt_scrollRectInImage(*backBufferImage,
307 QRect(inPlaceBoundingRect.topLeft() * devicePixelRatio,
308 inPlaceBoundingRect.size() * devicePixelRatio),
309 devicePixelDelta);
310 }
311
312 if (!frontBufferRegion.isEmpty()) {
313 qCDebug(lcQpaBackingStore) << "Scrolling" << frontBufferRegion << "by copying from front buffer";
314 blitBuffer(m_buffers.front().get(), frontBufferRegion, m_buffers.back().get(), scrollDelta);
315 }
316
317 m_buffers.back()->unlock();
318
319 // Mark the target region as filled. Note: We do not mark the source region
320 // as dirty, even though the content has conceptually been "moved", as that
321 // would complicate things when preserving from the front buffer. This matches
322 // the behavior of other backingstore implementations using qt_scrollRectInImage.
323 updateDirtyStates(region.translated(scrollDelta));
324
325 qCInfo(lcQpaBackingStore) << "Scroll ended. Back buffer valid region is now" << m_buffers.back()->validRegion();
326
327 return true;
328}
329
330void QCALayerBackingStore::flush(QWindow *flushedWindow, const QRegion &region, const QPoint &offset)
331{
332 Q_UNUSED(region);
333 Q_UNUSED(offset);
334
335 if (!m_buffers.back()) {
336 qCWarning(lcQpaBackingStore) << "Flush requested with no back buffer. Ignoring.";
337 return;
338 }
339
340 if (flushedWindow != window()) {
341 flushSubWindow(flushedWindow);
342 return;
343 }
344
345 finalizeBackBuffer();
346
347 if (m_buffers.front()->isInUse() && !m_buffers.front()->isDirty()) {
348 qCInfo(lcQpaBackingStore) << "Asked to flush, but front buffer is up to date. Ignoring.";
349 return;
350 }
351
352 QMacAutoReleasePool pool;
353
354 NSView *flushedView = static_cast<QCocoaWindow *>(flushedWindow->handle())->view();
355 CALayer *layer = static_cast<QCocoaWindow *>(flushedWindow->handle())->contentLayer();
356
357 // If the backingstore is just flushed, without being painted to first, then we may
358 // end in a situation where the backingstore is flushed to a layer with a different
359 // scale factor than the one it was created for in beginPaint. This is the client's
360 // fault in not picking up the change in scale factor of the window and re-painting
361 // the backingstore accordingly. To smoothing things out, we warn about this situation,
362 // and change the layer's contentsScale to match the scale of the back buffer, so that
363 // we at least cover the whole layer. This is necessary since we set the view's
364 // contents placement policy to NSViewLayerContentsPlacementTopLeft, which means
365 // AppKit will not do any scaling on our behalf.
366 if (m_buffers.back()->devicePixelRatio() != layer.contentsScale) {
367 qCWarning(lcQpaBackingStore) << "Back buffer dpr of" << m_buffers.back()->devicePixelRatio()
368 << "doesn't match" << layer << "contents scale of" << layer.contentsScale
369 << "- updating layer to match.";
370 layer.contentsScale = m_buffers.back()->devicePixelRatio();
371 }
372
373 const bool isSingleBuffered = window()->format().swapBehavior() == QSurfaceFormat::SingleBuffer;
374
375 id backBufferSurface = (__bridge id)m_buffers.back()->surface();
376
377 // Trigger a new display cycle if there isn't one. This ensures that our layer updates
378 // are committed as part of a display-cycle instead of on the next runloop pass. This
379 // means CA won't try to throttle us if we flush too fast, and we'll coalesce our flush
380 // with other pending view and layer updates.
381 flushedView.window.viewsNeedDisplay = YES;
382
383 if (isSingleBuffered) {
384 // The private API [CALayer reloadValueForKeyPath:@"contents"] would be preferable,
385 // but barring any side effects or performance issues we opt for the hammer for now.
386 layer.contents = nil;
387 }
388
389 qCInfo(lcQpaBackingStore) << "Flushing" << backBufferSurface
390 << "to" << layer << "of" << flushedView;
391
392 layer.contents = backBufferSurface;
393
394 if (!isSingleBuffered) {
395 // Mark the surface as in use, so that we don't end up rendering
396 // to it while it's assigned to a layer.
397 IOSurfaceIncrementUseCount(m_buffers.back()->surface());
398
399 if (m_buffers.back() != m_buffers.front()) {
400 qCInfo(lcQpaBackingStore) << "Swapping back buffer to front";
401 std::swap(m_buffers.back(), m_buffers.front());
402 IOSurfaceDecrementUseCount(m_buffers.back()->surface());
403 }
404 }
405}
406
407void QCALayerBackingStore::flushSubWindow(QWindow *subWindow)
408{
409 qCInfo(lcQpaBackingStore) << "Flushing sub-window" << subWindow
410 << "via its own backingstore";
411
412 auto &subWindowBackingStore = m_subWindowBackingstores[subWindow];
413 if (!subWindowBackingStore) {
414 subWindowBackingStore.reset(new QCALayerBackingStore(subWindow));
415 QObject::connect(subWindow, &QObject::destroyed, this, &QCALayerBackingStore::windowDestroyed);
416 subWindowBackingStore->m_clearSurfaceOnPaint = false;
417 }
418
419 // Pick the buffer that holds the latest content. If the front buffer is not dirty
420 // it is fully up to date, e.g. after the top-level window has already flushed and
421 // swapped its buffers on resize, in which case the back buffer may be an older,
422 // smaller surface. Otherwise the latest content was just painted into the back
423 // buffer, so finalize it and read from there, as we do for the top-level window.
424 GraphicsBuffer *sourceBuffer = nullptr;
425 if (!m_buffers.front()->isDirty()) {
426 sourceBuffer = m_buffers.front().get();
427 } else {
428 finalizeBackBuffer();
429 sourceBuffer = m_buffers.back().get();
430 }
431
432 // Query platform window for subwindow size, so that we
433 // incorporate any effects of the QHighDpiScaling layer.
434 auto subWindowSize = subWindow->handle()->geometry().size();
435 static const auto kNoStaticContents = QRegion();
436 subWindowBackingStore->resize(subWindowSize, kNoStaticContents);
437
438 auto subWindowLocalRect = QRect(QPoint(), subWindowSize);
439 subWindowBackingStore->beginPaint(subWindowLocalRect);
440
441 QPainter painter(subWindowBackingStore->m_buffers.back()->asImage());
442 painter.setCompositionMode(QPainter::CompositionMode_Source);
443
444 NSView *backingStoreView = static_cast<QCocoaWindow *>(window()->handle())->view();
445 NSView *flushedView = static_cast<QCocoaWindow *>(subWindow->handle())->view();
446 auto subviewRect = [flushedView convertRect:flushedView.bounds toView:backingStoreView];
447 CALayer *layer = static_cast<QCocoaWindow *>(subWindow->handle())->contentLayer();
448 auto scale = layer.contentsScale;
449 subviewRect = CGRectApplyAffineTransform(subviewRect, CGAffineTransformMakeScale(scale, scale));
450
451 sourceBuffer->lock(QPlatformGraphicsBuffer::SWReadAccess);
452 const QImage *backingStoreImage = sourceBuffer->asImage();
453 painter.drawImage(subWindowLocalRect, *backingStoreImage, QRectF::fromCGRect(subviewRect));
454 sourceBuffer->unlock();
455
456 painter.end();
457 subWindowBackingStore->endPaint();
458 subWindowBackingStore->flush(subWindow, subWindowLocalRect, QPoint());
459
460 qCInfo(lcQpaBackingStore) << "Done flushing sub-window" << subWindow;
461}
462
463void QCALayerBackingStore::windowDestroyed(QObject *object)
464{
465 auto *window = static_cast<QWindow*>(object);
466 qCInfo(lcQpaBackingStore) << "Removing backingstore for sub-window" << window;
467 m_subWindowBackingstores.erase(window);
468}
469
471 qreal sourceDevicePixelRatio,
472 const QRegion &region,
473 const QPoint &offset,
474 QPlatformTextureList *textures,
475 bool translucentBackground,
476 qreal sourceTransformFactor)
477{
478 if (!m_buffers.back()) {
479 qCWarning(lcQpaBackingStore) << "Flush requested with no back buffer. Ignoring.";
480 return FlushFailed;
481 }
482
483 finalizeBackBuffer();
484
485 return QPlatformBackingStore::rhiFlush(window, sourceDevicePixelRatio,
486 region, offset, textures, translucentBackground, sourceTransformFactor);
487}
488
490{
491 if (!m_buffers.back())
492 return QImage();
493
494 const_cast<QCALayerBackingStore*>(this)->finalizeBackBuffer();
495
496 // We need to make a copy here, as the returned image could be used just
497 // for reading, in which case it won't detach, and then the underlying
498 // image data might change under the feet of the client when we re-use
499 // the buffer at a later point.
500 m_buffers.back()->lock(QPlatformGraphicsBuffer::SWReadAccess);
501 QImage imageCopy = m_buffers.back()->asImage()->copy();
502 m_buffers.back()->unlock();
503 return imageCopy;
504}
505
506void QCALayerBackingStore::backingPropertiesChanged()
507{
508 // Ideally this would be plumbed from the platform layer to QtGui, and
509 // the QBackingStore would be recreated, but we don't have that code yet,
510 // so at least make sure we update our backingstore when the backing
511 // properties (color space e.g.) are changed.
512
513 Q_ASSERT(window()->handle());
514
515 qCDebug(lcQpaBackingStore) << "Backing properties for" << window() << "did change";
516
517 const auto newColorSpace = colorSpace();
518 qCDebug(lcQpaBackingStore) << "Updating color space of existing buffers to" << newColorSpace;
519 for (auto &buffer : m_buffers) {
520 if (buffer)
521 buffer->setColorSpace(newColorSpace);
522 }
523}
524
526{
527 return m_buffers.back().get();
528}
529
530void QCALayerBackingStore::updateDirtyStates(const QRegion &paintedRegion)
531{
532 // Update dirty state of buffers based on what was painted. The back buffer will be
533 // less dirty, since we painted to it, while other buffers will become more dirty.
534 // This allows us to minimize copies between front and back buffers on swap in the
535 // cases where the painted region overlaps with the previous frame (front buffer).
536 for (const auto &buffer : m_buffers) {
537 if (buffer == m_buffers.back())
538 buffer->dirtyRegion -= paintedRegion;
539 else
540 buffer->dirtyRegion += paintedRegion;
541 }
542}
543
544void QCALayerBackingStore::finalizeBackBuffer()
545{
546 // After painting, the back buffer is only guaranteed to have content for the painted
547 // region, and may still have dirty areas that need to be synced up with the front buffer,
548 // if we have one. We know that the front buffer is always up to date.
549
550 if (!m_buffers.back()->isDirty())
551 return;
552
553 qCDebug(lcQpaBackingStore) << "Finalizing back buffer with dirty region" << m_buffers.back()->dirtyRegion;
554
555 if (m_buffers.back() != m_buffers.front()) {
556 m_buffers.back()->lock(QPlatformGraphicsBuffer::SWWriteAccess);
557 blitBuffer(m_buffers.front().get(), m_buffers.back()->dirtyRegion, m_buffers.back().get());
558 m_buffers.back()->unlock();
559 } else {
560 qCDebug(lcQpaBackingStore) << "Front and back buffer is the same. Can not finalize back buffer.";
561 }
562
563 // The back buffer is now completely in sync, ready to be presented
564 m_buffers.back()->dirtyRegion = QRegion();
565}
566
567/*
568 \internal
569
570 Blits \a sourceRegion from \a sourceBuffer to \a destinationBuffer,
571 at offset \a destinationOffset.
572
573 The source buffer is automatically locked for read only access
574 during the blit.
575
576 The destination buffer has to be locked for write access by the
577 caller.
578*/
579
580void QCALayerBackingStore::blitBuffer(GraphicsBuffer *sourceBuffer, const QRegion &sourceRegion,
581 GraphicsBuffer *destinationBuffer, const QPoint &destinationOffset)
582{
583 Q_ASSERT(sourceBuffer && destinationBuffer);
584 Q_ASSERT(sourceBuffer != destinationBuffer);
585
586 if (sourceRegion.isEmpty())
587 return;
588
589 qCDebug(lcQpaBackingStore) << "Blitting" << sourceRegion << "of" << sourceBuffer
590 << "to" << sourceRegion.translated(destinationOffset) << "of" << destinationBuffer;
591
592 Q_ASSERT(destinationBuffer->isLocked() == QPlatformGraphicsBuffer::SWWriteAccess);
593
594 sourceBuffer->lock(QPlatformGraphicsBuffer::SWReadAccess);
595 const QImage *sourceImage = sourceBuffer->asImage();
596
597 const QRect sourceBufferBounds(QPoint(0, 0), sourceBuffer->size());
598 const qreal sourceDevicePixelRatio = sourceImage->devicePixelRatio();
599
600 QPainter painter(destinationBuffer->asImage());
601 painter.setCompositionMode(QPainter::CompositionMode_Source);
602
603 // Let painter operate in device pixels, to make it easier to compare coordinates
604 const qreal destinationDevicePixelRatio = painter.device()->devicePixelRatio();
605 painter.scale(1.0 / destinationDevicePixelRatio, 1.0 / destinationDevicePixelRatio);
606
607 for (const QRect &rect : sourceRegion) {
608 QRect sourceRect(rect.topLeft() * sourceDevicePixelRatio,
609 rect.size() * sourceDevicePixelRatio);
610 QRect destinationRect((rect.topLeft() + destinationOffset) * destinationDevicePixelRatio,
611 rect.size() * destinationDevicePixelRatio);
612
613#ifdef QT_DEBUG
614 if (Q_UNLIKELY(!sourceBufferBounds.contains(sourceRect.bottomRight()))) {
615 qCWarning(lcQpaBackingStore) << "Source buffer of size" << sourceBuffer->size()
616 << "is too small to blit" << sourceRect;
617 }
618#endif
619 painter.drawImage(destinationRect, *sourceImage, sourceRect);
620 }
621
622 sourceBuffer->unlock();
623}
624
625// ----------------------------------------------------------------------------
626
627QCALayerBackingStore::GraphicsBuffer::GraphicsBuffer(const QSize &size, qreal devicePixelRatio,
628 const QPixelFormat &format, QCFType<CGColorSpaceRef> colorSpace)
629 : QIOSurfaceGraphicsBuffer(size, format)
630 , dirtyRegion(QRect(QPoint(0, 0), size / devicePixelRatio))
631 , m_devicePixelRatio(devicePixelRatio)
632{
633 setColorSpace(colorSpace);
634}
635
636QRegion QCALayerBackingStore::GraphicsBuffer::validRegion() const
637{
638
639 QRegion fullRegion = QRect(QPoint(0, 0), size() / m_devicePixelRatio);
640 return fullRegion - dirtyRegion;
641}
642
643QImage *QCALayerBackingStore::GraphicsBuffer::asImage()
644{
645 if (m_image.isNull()) {
646 qCDebug(lcQpaBackingStore) << "Setting up paint device for" << this;
647 CFRetain(surface());
648 m_image = QImage(data(), size().width(), size().height(),
649 bytesPerLine(), QImage::toImageFormat(format()),
650 QImageCleanupFunction(CFRelease), surface());
651 m_image.setDevicePixelRatio(m_devicePixelRatio);
652 }
653
654 Q_ASSERT_X(m_image.constBits() == data(), "QCALayerBackingStore",
655 "IOSurfaces should have have a fixed location in memory once created");
656
657 return &m_image;
658}
659
660QT_END_NAMESPACE
661
662#include "moc_qcocoabackingstore.cpp"
QPaintDevice * paintDevice() override
Implement this function to return the appropriate paint device.
QImage toImage() const override
Implemented in subclasses to return the content of the backingstore as a QImage.
bool scroll(const QRegion &region, int dx, int dy) override
Scrolls the given area dx pixels to the right and dy downward; both dx and dy may be negative.
void endPaint() override
This function is called after painting onto the surface has ended.
void flush(QWindow *, const QRegion &, const QPoint &) override
Flushes the given region from the specified window.
void beginPaint(const QRegion &region) override
This function is called before painting onto the surface begins, with the region in which the paintin...
FlushResult rhiFlush(QWindow *window, qreal sourceDevicePixelRatio, const QRegion &region, const QPoint &offset, QPlatformTextureList *textures, bool translucentBackground, qreal sourceTransformFactor) override
Flushes the given region from the specified window, and compositing it with the specified textures li...
QPlatformGraphicsBuffer * graphicsBuffer() const override
Accessor for a backingstores graphics buffer abstraction.
bool eventFilter(QObject *watched, QEvent *event) override
Filters events if this object has been installed as an event filter for the watched object.
void resize(const QSize &size, const QRegion &staticContents) override
#define USE_LAZY_BUFFER_ALLOCATION_DURING_LIVE_WINDOW_RESIZE
QT_BEGIN_NAMESPACE void qt_scrollRectInImage(QImage &, const QRect &, const QPoint &)