5#include <AppKit/AppKit.h>
12#include <QtCore/qmath.h>
13#include <QtCore/private/qcore_mac_p.h>
14#include <QtGui/qpainter.h>
16#include <QuartzCore/CATransaction.h>
20QCocoaBackingStore::QCocoaBackingStore(QWindow *window)
21 : QPlatformBackingStore(window)
25QCFType<CGColorSpaceRef> QCocoaBackingStore::colorSpace()
const
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);
35 : QCocoaBackingStore(window)
37 qCDebug(lcQpaBackingStore) <<
"Creating QCALayerBackingStore for" << window
38 <<
"with" << window->format();
42 observeBackingPropertiesChanges();
43 window->installEventFilter(
this);
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();
62 Q_ASSERT(watched == window());
64 if (event->type() == QEvent::PlatformSurface) {
65 auto *surfaceEvent =
static_cast<QPlatformSurfaceEvent*>(event);
66 if (surfaceEvent->surfaceEventType() == QPlatformSurfaceEvent::SurfaceCreated)
67 observeBackingPropertiesChanges();
69 m_backingPropertiesObserver = QMacNotificationObserver();
77 qCDebug(lcQpaBackingStore) <<
"Resize requested to" << size
78 <<
"with static contents" << staticContents;
80 m_requestedSize = size;
81 m_staticContents = staticContents;
88 QMacAutoReleasePool pool;
90 qCInfo(lcQpaBackingStore) <<
"Beginning paint of" << region <<
"into backingstore of" << m_requestedSize;
92 if (m_requestedSize.isEmpty()) {
94 qCDebug(lcQpaBackingStore) <<
"Size is empty, throwing away back buffer";
95 m_buffers.back().reset(
nullptr);
101 const bool bufferWasRecreated = recreateBackBufferIfNeeded();
103 m_buffers.back()->lock(QPlatformGraphicsBuffer::SWWriteAccess);
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);
117 updateDirtyStates(region);
122 if (window()->format().swapBehavior() == QSurfaceFormat::SingleBuffer)
125 if (Q_UNLIKELY(lcQpaBackingStore().isDebugEnabled())) {
129 for (
const auto &buffer : m_buffers) {
130 qCDebug(lcQpaBackingStore).nospace() <<
" "
131 << (buffer == m_buffers.front() ?
"front" :
132 buffer == m_buffers.back() ?
" back" :
134 ) <<
": " << buffer.get();
140 for (
auto &buffer : backwards(m_buffers)) {
141 if (!buffer || !buffer->isInUse()) {
143 if (buffer != m_buffers.back())
144 std::swap(buffer, m_buffers.back());
145 qCDebug(lcQpaBackingStore) <<
"Using back buffer" << m_buffers.back().get();
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));
154 }
else if (buffer == m_buffers.front()) {
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);
163 Q_ASSERT(!m_buffers.back() || !m_buffers.back()->isInUse());
169#define USE_LAZY_BUFFER_ALLOCATION_DURING_LIVE_WINDOW_RESIZE 0
174 const qreal devicePixelRatio = platformWindow->devicePixelRatio();
175 QSize requestedBufferSize = m_requestedSize * devicePixelRatio;
177 const NSView *backingStoreView = platformWindow->view();
178 Q_UNUSED(backingStoreView);
180 auto bufferSizeMismatch = [&](
const QSize requested,
const QSize actual) {
182 if (backingStoreView.inLiveResize) {
184 return requested.width() > actual.width() || requested.height() > actual.height();
187 return requested != actual;
190 if (!m_buffers.back() || bufferSizeMismatch(requestedBufferSize, m_buffers.back()->size())) {
192 if (backingStoreView.inLiveResize) {
194 QSize nativeScreenSize = window()->screen()->geometry().size() * devicePixelRatio;
195 requestedBufferSize = QSize(qNextPowerOfTwo(requestedBufferSize.width()),
196 qNextPowerOfTwo(requestedBufferSize.height())).boundedTo(nativeScreenSize);
200 qCInfo(lcQpaBackingStore)<<
"Creating surface of" << requestedBufferSize
201 <<
"for" << window() <<
"based on requested" << m_requestedSize
202 <<
"dpr =" << devicePixelRatio <<
"and color space" << colorSpace();
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");
208 if (!m_staticContents.isEmpty() && m_buffers.back()) {
222 const QRegion backBufferRegion = m_staticContents - m_buffers.back()->dirtyRegion;
223 const QRegion frontBufferRegion = m_staticContents - backBufferRegion;
225 qCInfo(lcQpaBackingStore) <<
"Preserving static content" << backBufferRegion
226 <<
"from back buffer, and" << frontBufferRegion <<
"from front buffer";
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();
240 newBackBuffer->dirtyRegion -= m_staticContents;
241 m_staticContents = {};
244 m_buffers.back().reset(newBackBuffer);
253 if (m_buffers.back()) {
254 return m_buffers.back()->asImage();
256 static QImage fallbackDevice;
257 return &fallbackDevice;
263 if (!m_buffers.back())
266 qCInfo(lcQpaBackingStore) <<
"Paint ended. Back buffer valid region is now" << m_buffers.back()->validRegion();
267 m_buffers.back()->unlock();
275 if (!m_buffers.back()) {
276 qCInfo(lcQpaBackingStore) <<
"Scroll requested with no back buffer. Ignoring.";
280 const QPoint scrollDelta(dx, dy);
281 qCInfo(lcQpaBackingStore) <<
"Scrolling" << region <<
"by" << scrollDelta;
284 recreateBackBufferIfNeeded();
286 const QRegion inPlaceRegion = region - m_buffers.back()->dirtyRegion;
287 const QRegion frontBufferRegion = region - inPlaceRegion;
289 QMacAutoReleasePool pool;
291 m_buffers.back()->lock(QPlatformGraphicsBuffer::SWWriteAccess);
293 if (!inPlaceRegion.isEmpty()) {
297 const QRect inPlaceBoundingRect = inPlaceRegion.boundingRect();
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;
307 QRect(inPlaceBoundingRect.topLeft() * devicePixelRatio,
308 inPlaceBoundingRect.size() * devicePixelRatio),
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);
317 m_buffers.back()->unlock();
323 updateDirtyStates(region.translated(scrollDelta));
325 qCInfo(lcQpaBackingStore) <<
"Scroll ended. Back buffer valid region is now" << m_buffers.back()->validRegion();
335 if (!m_buffers.back()) {
336 qCWarning(lcQpaBackingStore) <<
"Flush requested with no back buffer. Ignoring.";
340 finalizeBackBuffer();
342 if (flushedWindow != window()) {
343 flushSubWindow(flushedWindow);
347 if (m_buffers.front()->isInUse() && !m_buffers.front()->isDirty()) {
348 qCInfo(lcQpaBackingStore) <<
"Asked to flush, but front buffer is up to date. Ignoring.";
352 QMacAutoReleasePool pool;
354 NSView *flushedView =
static_cast<QCocoaWindow *>(flushedWindow->handle())->view();
355 CALayer *layer =
static_cast<
QCocoaWindow *>(flushedWindow->handle())->contentLayer();
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();
373 const bool isSingleBuffered = window()->format().swapBehavior() == QSurfaceFormat::SingleBuffer;
375 id backBufferSurface = (__bridge id)m_buffers.back()->surface();
381 flushedView.window.viewsNeedDisplay = YES;
383 if (isSingleBuffered) {
386 layer.contents = nil;
389 qCInfo(lcQpaBackingStore) <<
"Flushing" << backBufferSurface
390 <<
"to" << layer <<
"of" << flushedView;
392 layer.contents = backBufferSurface;
394 if (!isSingleBuffered) {
397 IOSurfaceIncrementUseCount(m_buffers.back()->surface());
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());
409 qCInfo(lcQpaBackingStore) <<
"Flushing sub-window" << subWindow
410 <<
"via its own backingstore";
412 auto &subWindowBackingStore = m_subWindowBackingstores[subWindow];
413 if (!subWindowBackingStore) {
415 QObject::connect(subWindow, &QObject::destroyed,
this, &QCALayerBackingStore::windowDestroyed);
416 subWindowBackingStore->m_clearSurfaceOnPaint =
false;
421 auto subWindowSize = subWindow->handle()->geometry().size();
422 static const auto kNoStaticContents = QRegion();
423 subWindowBackingStore->resize(subWindowSize, kNoStaticContents);
425 auto subWindowLocalRect = QRect(QPoint(), subWindowSize);
426 subWindowBackingStore->beginPaint(subWindowLocalRect);
428 QPainter painter(subWindowBackingStore->m_buffers.back()->asImage());
429 painter.setCompositionMode(QPainter::CompositionMode_Source);
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));
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();
444 subWindowBackingStore->endPaint();
445 subWindowBackingStore->flush(subWindow, subWindowLocalRect, QPoint());
447 qCInfo(lcQpaBackingStore) <<
"Done flushing sub-window" << subWindow;
452 auto *window =
static_cast<QWindow*>(object);
453 qCInfo(lcQpaBackingStore) <<
"Removing backingstore for sub-window" << window;
454 m_subWindowBackingstores.erase(window);
458 qreal sourceDevicePixelRatio,
459 const QRegion ®ion,
460 const QPoint &offset,
461 QPlatformTextureList *textures,
462 bool translucentBackground,
463 qreal sourceTransformFactor)
465 if (!m_buffers.back()) {
466 qCWarning(lcQpaBackingStore) <<
"Flush requested with no back buffer. Ignoring.";
470 finalizeBackBuffer();
472 return QPlatformBackingStore::rhiFlush(window, sourceDevicePixelRatio,
473 region, offset, textures, translucentBackground, sourceTransformFactor);
478 if (!m_buffers.back())
487 m_buffers.back()->lock(QPlatformGraphicsBuffer::SWReadAccess);
488 QImage imageCopy = m_buffers.back()->asImage()->copy();
489 m_buffers.back()->unlock();
500 Q_ASSERT(window()->handle());
502 qCDebug(lcQpaBackingStore) <<
"Backing properties for" << window() <<
"did change";
504 const auto newColorSpace = colorSpace();
505 qCDebug(lcQpaBackingStore) <<
"Updating color space of existing buffers to" << newColorSpace;
506 for (
auto &buffer : m_buffers) {
508 buffer->setColorSpace(newColorSpace);
514 return m_buffers.back().get();
523 for (
const auto &buffer : m_buffers) {
524 if (buffer == m_buffers.back())
525 buffer->dirtyRegion -= paintedRegion;
527 buffer->dirtyRegion += paintedRegion;
537 if (!m_buffers.back()->isDirty())
540 qCDebug(lcQpaBackingStore) <<
"Finalizing back buffer with dirty region" << m_buffers.back()->dirtyRegion;
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();
547 qCDebug(lcQpaBackingStore) <<
"Front and back buffer is the same. Can not finalize back buffer.";
551 m_buffers.back()->dirtyRegion = QRegion();
555
556
557
558
559
560
561
562
563
564
565
568 GraphicsBuffer *destinationBuffer,
const QPoint &destinationOffset)
570 Q_ASSERT(sourceBuffer && destinationBuffer);
571 Q_ASSERT(sourceBuffer != destinationBuffer);
573 if (sourceRegion.isEmpty())
576 qCDebug(lcQpaBackingStore) <<
"Blitting" << sourceRegion <<
"of" << sourceBuffer
577 <<
"to" << sourceRegion.translated(destinationOffset) <<
"of" << destinationBuffer;
579 Q_ASSERT(destinationBuffer->isLocked() == QPlatformGraphicsBuffer::SWWriteAccess);
581 sourceBuffer->lock(QPlatformGraphicsBuffer::SWReadAccess);
582 const QImage *sourceImage = sourceBuffer->asImage();
584 const QRect sourceBufferBounds(QPoint(0, 0), sourceBuffer->size());
585 const qreal sourceDevicePixelRatio = sourceImage->devicePixelRatio();
587 QPainter painter(destinationBuffer->asImage());
588 painter.setCompositionMode(QPainter::CompositionMode_Source);
591 const qreal destinationDevicePixelRatio = painter.device()->devicePixelRatio();
592 painter.scale(1.0 / destinationDevicePixelRatio, 1.0 / destinationDevicePixelRatio);
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);
601 if (Q_UNLIKELY(!sourceBufferBounds.contains(sourceRect.bottomRight()))) {
602 qCWarning(lcQpaBackingStore) <<
"Source buffer of size" << sourceBuffer->size()
603 <<
"is too small to blit" << sourceRect;
606 painter.drawImage(destinationRect, *sourceImage, sourceRect);
609 sourceBuffer->unlock();
615 const QPixelFormat &format, QCFType<CGColorSpaceRef> colorSpace)
616 : QIOSurfaceGraphicsBuffer(size, format)
617 , dirtyRegion(QRect(QPoint(0, 0), size / devicePixelRatio))
618 , m_devicePixelRatio(devicePixelRatio)
620 setColorSpace(colorSpace);
626 QRegion fullRegion = QRect(QPoint(0, 0), size() / m_devicePixelRatio);
627 return fullRegion - dirtyRegion;
632 if (m_image.isNull()) {
633 qCDebug(lcQpaBackingStore) <<
"Setting up paint device for" <<
this;
635 m_image = QImage(data(), size().width(), size().height(),
636 bytesPerLine(), QImage::toImageFormat(format()),
637 QImageCleanupFunction(CFRelease), surface());
638 m_image.setDevicePixelRatio(m_devicePixelRatio);
641 Q_ASSERT_X(m_image.constBits() == data(),
"QCALayerBackingStore",
642 "IOSurfaces should have have a fixed location in memory once created");
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 ®ion, 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 ®ion) 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 ®ion, 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 &)