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 finalizeBackBuffer();
341
342 if (flushedWindow != window()) {
343 flushSubWindow(flushedWindow);
344 return;
345 }
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 // Query platform window for subwindow size, so that we
420 // incorporate any effects of the QHighDpiScaling layer.
421 auto subWindowSize = subWindow->handle()->geometry().size();
422 static const auto kNoStaticContents = QRegion();
423 subWindowBackingStore->resize(subWindowSize, kNoStaticContents);
424
425 auto subWindowLocalRect = QRect(QPoint(), subWindowSize);
426 subWindowBackingStore->beginPaint(subWindowLocalRect);
427
428 QPainter painter(subWindowBackingStore->m_buffers.back()->asImage());
429 painter.setCompositionMode(QPainter::CompositionMode_Source);
430
431 NSView *backingStoreView = static_cast<QCocoaWindow *>(window()->handle())->view();
432 NSView *flushedView = static_cast<QCocoaWindow *>(subWindow->handle())->view();
433 auto subviewRect = [flushedView convertRect:flushedView.bounds toView:backingStoreView];
434 CALayer *layer = static_cast<QCocoaWindow *>(subWindow->handle())->contentLayer();
435 auto scale = layer.contentsScale;
436 subviewRect = CGRectApplyAffineTransform(subviewRect, CGAffineTransformMakeScale(scale, scale));
437
438 m_buffers.back()->lock(QPlatformGraphicsBuffer::SWReadAccess);
439 const QImage *backingStoreImage = m_buffers.back()->asImage();
440 painter.drawImage(subWindowLocalRect, *backingStoreImage, QRectF::fromCGRect(subviewRect));
441 m_buffers.back()->unlock();
442
443 painter.end();
444 subWindowBackingStore->endPaint();
445 subWindowBackingStore->flush(subWindow, subWindowLocalRect, QPoint());
446
447 qCInfo(lcQpaBackingStore) << "Done flushing sub-window" << subWindow;
448}
449
450void QCALayerBackingStore::windowDestroyed(QObject *object)
451{
452 auto *window = static_cast<QWindow*>(object);
453 qCInfo(lcQpaBackingStore) << "Removing backingstore for sub-window" << window;
454 m_subWindowBackingstores.erase(window);
455}
456
458 qreal sourceDevicePixelRatio,
459 const QRegion &region,
460 const QPoint &offset,
461 QPlatformTextureList *textures,
462 bool translucentBackground,
463 qreal sourceTransformFactor)
464{
465 if (!m_buffers.back()) {
466 qCWarning(lcQpaBackingStore) << "Flush requested with no back buffer. Ignoring.";
467 return FlushFailed;
468 }
469
470 finalizeBackBuffer();
471
472 return QPlatformBackingStore::rhiFlush(window, sourceDevicePixelRatio,
473 region, offset, textures, translucentBackground, sourceTransformFactor);
474}
475
477{
478 if (!m_buffers.back())
479 return QImage();
480
481 const_cast<QCALayerBackingStore*>(this)->finalizeBackBuffer();
482
483 // We need to make a copy here, as the returned image could be used just
484 // for reading, in which case it won't detach, and then the underlying
485 // image data might change under the feet of the client when we re-use
486 // the buffer at a later point.
487 m_buffers.back()->lock(QPlatformGraphicsBuffer::SWReadAccess);
488 QImage imageCopy = m_buffers.back()->asImage()->copy();
489 m_buffers.back()->unlock();
490 return imageCopy;
491}
492
493void QCALayerBackingStore::backingPropertiesChanged()
494{
495 // Ideally this would be plumbed from the platform layer to QtGui, and
496 // the QBackingStore would be recreated, but we don't have that code yet,
497 // so at least make sure we update our backingstore when the backing
498 // properties (color space e.g.) are changed.
499
500 Q_ASSERT(window()->handle());
501
502 qCDebug(lcQpaBackingStore) << "Backing properties for" << window() << "did change";
503
504 const auto newColorSpace = colorSpace();
505 qCDebug(lcQpaBackingStore) << "Updating color space of existing buffers to" << newColorSpace;
506 for (auto &buffer : m_buffers) {
507 if (buffer)
508 buffer->setColorSpace(newColorSpace);
509 }
510}
511
513{
514 return m_buffers.back().get();
515}
516
517void QCALayerBackingStore::updateDirtyStates(const QRegion &paintedRegion)
518{
519 // Update dirty state of buffers based on what was painted. The back buffer will be
520 // less dirty, since we painted to it, while other buffers will become more dirty.
521 // This allows us to minimize copies between front and back buffers on swap in the
522 // cases where the painted region overlaps with the previous frame (front buffer).
523 for (const auto &buffer : m_buffers) {
524 if (buffer == m_buffers.back())
525 buffer->dirtyRegion -= paintedRegion;
526 else
527 buffer->dirtyRegion += paintedRegion;
528 }
529}
530
531void QCALayerBackingStore::finalizeBackBuffer()
532{
533 // After painting, the back buffer is only guaranteed to have content for the painted
534 // region, and may still have dirty areas that need to be synced up with the front buffer,
535 // if we have one. We know that the front buffer is always up to date.
536
537 if (!m_buffers.back()->isDirty())
538 return;
539
540 qCDebug(lcQpaBackingStore) << "Finalizing back buffer with dirty region" << m_buffers.back()->dirtyRegion;
541
542 if (m_buffers.back() != m_buffers.front()) {
543 m_buffers.back()->lock(QPlatformGraphicsBuffer::SWWriteAccess);
544 blitBuffer(m_buffers.front().get(), m_buffers.back()->dirtyRegion, m_buffers.back().get());
545 m_buffers.back()->unlock();
546 } else {
547 qCDebug(lcQpaBackingStore) << "Front and back buffer is the same. Can not finalize back buffer.";
548 }
549
550 // The back buffer is now completely in sync, ready to be presented
551 m_buffers.back()->dirtyRegion = QRegion();
552}
553
554/*
555 \internal
556
557 Blits \a sourceRegion from \a sourceBuffer to \a destinationBuffer,
558 at offset \a destinationOffset.
559
560 The source buffer is automatically locked for read only access
561 during the blit.
562
563 The destination buffer has to be locked for write access by the
564 caller.
565*/
566
567void QCALayerBackingStore::blitBuffer(GraphicsBuffer *sourceBuffer, const QRegion &sourceRegion,
568 GraphicsBuffer *destinationBuffer, const QPoint &destinationOffset)
569{
570 Q_ASSERT(sourceBuffer && destinationBuffer);
571 Q_ASSERT(sourceBuffer != destinationBuffer);
572
573 if (sourceRegion.isEmpty())
574 return;
575
576 qCDebug(lcQpaBackingStore) << "Blitting" << sourceRegion << "of" << sourceBuffer
577 << "to" << sourceRegion.translated(destinationOffset) << "of" << destinationBuffer;
578
579 Q_ASSERT(destinationBuffer->isLocked() == QPlatformGraphicsBuffer::SWWriteAccess);
580
581 sourceBuffer->lock(QPlatformGraphicsBuffer::SWReadAccess);
582 const QImage *sourceImage = sourceBuffer->asImage();
583
584 const QRect sourceBufferBounds(QPoint(0, 0), sourceBuffer->size());
585 const qreal sourceDevicePixelRatio = sourceImage->devicePixelRatio();
586
587 QPainter painter(destinationBuffer->asImage());
588 painter.setCompositionMode(QPainter::CompositionMode_Source);
589
590 // Let painter operate in device pixels, to make it easier to compare coordinates
591 const qreal destinationDevicePixelRatio = painter.device()->devicePixelRatio();
592 painter.scale(1.0 / destinationDevicePixelRatio, 1.0 / destinationDevicePixelRatio);
593
594 for (const QRect &rect : sourceRegion) {
595 QRect sourceRect(rect.topLeft() * sourceDevicePixelRatio,
596 rect.size() * sourceDevicePixelRatio);
597 QRect destinationRect((rect.topLeft() + destinationOffset) * destinationDevicePixelRatio,
598 rect.size() * destinationDevicePixelRatio);
599
600#ifdef QT_DEBUG
601 if (Q_UNLIKELY(!sourceBufferBounds.contains(sourceRect.bottomRight()))) {
602 qCWarning(lcQpaBackingStore) << "Source buffer of size" << sourceBuffer->size()
603 << "is too small to blit" << sourceRect;
604 }
605#endif
606 painter.drawImage(destinationRect, *sourceImage, sourceRect);
607 }
608
609 sourceBuffer->unlock();
610}
611
612// ----------------------------------------------------------------------------
613
614QCALayerBackingStore::GraphicsBuffer::GraphicsBuffer(const QSize &size, qreal devicePixelRatio,
615 const QPixelFormat &format, QCFType<CGColorSpaceRef> colorSpace)
616 : QIOSurfaceGraphicsBuffer(size, format)
617 , dirtyRegion(QRect(QPoint(0, 0), size / devicePixelRatio))
618 , m_devicePixelRatio(devicePixelRatio)
619{
620 setColorSpace(colorSpace);
621}
622
623QRegion QCALayerBackingStore::GraphicsBuffer::validRegion() const
624{
625
626 QRegion fullRegion = QRect(QPoint(0, 0), size() / m_devicePixelRatio);
627 return fullRegion - dirtyRegion;
628}
629
630QImage *QCALayerBackingStore::GraphicsBuffer::asImage()
631{
632 if (m_image.isNull()) {
633 qCDebug(lcQpaBackingStore) << "Setting up paint device for" << this;
634 CFRetain(surface());
635 m_image = QImage(data(), size().width(), size().height(),
636 bytesPerLine(), QImage::toImageFormat(format()),
637 QImageCleanupFunction(CFRelease), surface());
638 m_image.setDevicePixelRatio(m_devicePixelRatio);
639 }
640
641 Q_ASSERT_X(m_image.constBits() == data(), "QCALayerBackingStore",
642 "IOSurfaces should have have a fixed location in memory once created");
643
644 return &m_image;
645}
646
647QT_END_NAMESPACE
648
649#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 &)