11#include <QtCore/qmath.h>
12#include <QtCore/private/qcore_mac_p.h>
13#include <QtGui/qpainter.h>
15#include <QuartzCore/CATransaction.h>
36 qCDebug(lcQpaBackingStore) <<
"Creating QCALayerBackingStore for" <<
window
41 observeBackingPropertiesChanges();
49void QCALayerBackingStore::observeBackingPropertiesChanges()
54 NSWindowDidChangeBackingPropertiesNotification, [
this]() {
55 backingPropertiesChanged();
66 observeBackingPropertiesChanges();
76 qCDebug(lcQpaBackingStore) <<
"Resize requested to" <<
size
77 <<
"with static contents" << staticContents;
79 m_requestedSize =
size;
80 m_staticContents = staticContents;
89 qCInfo(lcQpaBackingStore) <<
"Beginning paint of" << region <<
"into backingstore of" << m_requestedSize;
93 const bool bufferWasRecreated = recreateBackBufferIfNeeded();
101 qCDebug(lcQpaBackingStore) <<
"Clearing" << region <<
"before use";
109 updateDirtyStates(region);
112void QCALayerBackingStore::ensureBackBuffer()
117 if (
Q_UNLIKELY(lcQpaBackingStore().isDebugEnabled())) {
121 for (
const auto &
buffer : m_buffers) {
122 qCDebug(lcQpaBackingStore).nospace() <<
" "
123 << (
buffer == m_buffers.front() ?
"front" :
124 buffer == m_buffers.back() ?
" back" :
126 ) <<
": " <<
buffer.get();
132 for (
auto &
buffer : backwards(m_buffers)) {
135 if (
buffer != m_buffers.back())
136 std::swap(
buffer, m_buffers.back());
137 qCDebug(lcQpaBackingStore) <<
"Using back buffer" << m_buffers.back().get();
139 static const int kMaxSwapChainDepth = 3;
140 if (m_buffers.size() > kMaxSwapChainDepth) {
141 qCDebug(lcQpaBackingStore) <<
"Reducing swap chain depth to" << kMaxSwapChainDepth;
142 m_buffers.erase(std::next(m_buffers.begin(), 1), std::prev(m_buffers.end(), 2));
146 }
else if (
buffer == m_buffers.front()) {
148 const int swapChainDepth = m_buffers.size() + 1;
149 qCDebug(lcQpaBackingStore) <<
"Available buffers exhausted, increasing swap chain depth to" << swapChainDepth;
150 m_buffers.resize(swapChainDepth);
155 Q_ASSERT(!m_buffers.back() || !m_buffers.back()->isInUse());
161#define USE_LAZY_BUFFER_ALLOCATION_DURING_LIVE_WINDOW_RESIZE 0
163bool QCALayerBackingStore::recreateBackBufferIfNeeded()
167 QSize requestedBufferSize = m_requestedSize * devicePixelRatio;
169 const NSView *backingStoreView = platformWindow->
view();
172 auto bufferSizeMismatch = [&](
const QSize requested,
const QSize actual) {
173#if USE_LAZY_BUFFER_ALLOCATION_DURING_LIVE_WINDOW_RESIZE
174 if (backingStoreView.inLiveResize) {
176 return requested.
width() > actual.width() || requested.
height() > actual.height();
179 return requested != actual;
182 if (!m_buffers.back() || bufferSizeMismatch(requestedBufferSize, m_buffers.back()->size())) {
183#if USE_LAZY_BUFFER_ALLOCATION_DURING_LIVE_WINDOW_RESIZE
184 if (backingStoreView.inLiveResize) {
192 qCInfo(lcQpaBackingStore)<<
"Creating surface of" << requestedBufferSize
193 <<
"for" <<
window() <<
"based on requested" << m_requestedSize
194 <<
"dpr =" << devicePixelRatio <<
"and color space" <<
colorSpace();
197 auto *newBackBuffer =
new GraphicsBuffer(requestedBufferSize, devicePixelRatio, pixelFormat,
colorSpace());
199 if (!m_staticContents.
isEmpty() && m_buffers.back()) {
213 const QRegion backBufferRegion = m_staticContents - m_buffers.back()->dirtyRegion;
214 const QRegion frontBufferRegion = m_staticContents - backBufferRegion;
216 qCInfo(lcQpaBackingStore) <<
"Preserving static content" << backBufferRegion
217 <<
"from back buffer, and" << frontBufferRegion <<
"from front buffer";
220 blitBuffer(m_buffers.back().get(), backBufferRegion, newBackBuffer);
221 Q_ASSERT(frontBufferRegion.isEmpty() || m_buffers.front());
222 blitBuffer(m_buffers.front().get(), frontBufferRegion, newBackBuffer);
223 newBackBuffer->unlock();
231 newBackBuffer->dirtyRegion -= m_staticContents;
232 m_staticContents = {};
235 m_buffers.back().reset(newBackBuffer);
245 return m_buffers.back()->asImage();
250 qCInfo(lcQpaBackingStore) <<
"Paint ended. Back buffer valid region is now" << m_buffers.back()->validRegion();
251 m_buffers.back()->unlock();
259 if (!m_buffers.back()) {
260 qCInfo(lcQpaBackingStore) <<
"Scroll requested with no back buffer. Ignoring.";
264 const QPoint scrollDelta(dx, dy);
265 qCInfo(lcQpaBackingStore) <<
"Scrolling" << region <<
"by" << scrollDelta;
268 recreateBackBufferIfNeeded();
270 const QRegion inPlaceRegion = region - m_buffers.back()->dirtyRegion;
271 const QRegion frontBufferRegion = region - inPlaceRegion;
277 if (!inPlaceRegion.isEmpty()) {
281 const QRect inPlaceBoundingRect = inPlaceRegion.boundingRect();
283 qCDebug(lcQpaBackingStore) <<
"Scrolling" << inPlaceBoundingRect <<
"in place";
284 QImage *backBufferImage = m_buffers.back()->asImage();
285 const qreal devicePixelRatio = backBufferImage->devicePixelRatio();
286 const QPoint devicePixelDelta = scrollDelta * devicePixelRatio;
291 QRect(inPlaceBoundingRect.topLeft() * devicePixelRatio,
292 inPlaceBoundingRect.size() * devicePixelRatio),
296 if (!frontBufferRegion.isEmpty()) {
297 qCDebug(lcQpaBackingStore) <<
"Scrolling" << frontBufferRegion <<
"by copying from front buffer";
298 blitBuffer(m_buffers.front().get(), frontBufferRegion, m_buffers.back().get(), scrollDelta);
301 m_buffers.back()->unlock();
307 updateDirtyStates(region.
translated(scrollDelta));
309 qCInfo(lcQpaBackingStore) <<
"Scroll ended. Back buffer valid region is now" << m_buffers.back()->validRegion();
319 if (!m_buffers.back()) {
320 qCWarning(lcQpaBackingStore) <<
"Tried to flush backingstore without painting to it first";
324 finalizeBackBuffer();
326 if (flushedWindow !=
window()) {
327 flushSubWindow(flushedWindow);
331 if (m_buffers.front()->isInUse() && !m_buffers.front()->isDirty()) {
332 qCInfo(lcQpaBackingStore) <<
"Asked to flush, but front buffer is up to date. Ignoring.";
338 NSView *flushedView =
static_cast<QCocoaWindow *
>(flushedWindow->handle())->
view();
349 if (m_buffers.back()->devicePixelRatio() != flushedView.layer.contentsScale) {
350 qCWarning(lcQpaBackingStore) <<
"Back buffer dpr of" << m_buffers.back()->devicePixelRatio()
351 <<
"doesn't match" << flushedView.layer <<
"contents scale of" << flushedView.layer.contentsScale
352 <<
"- updating layer to match.";
353 flushedView.layer.contentsScale = m_buffers.back()->devicePixelRatio();
358 id backBufferSurface = (__bridge
id)m_buffers.back()->surface();
364 flushedView.window.viewsNeedDisplay = YES;
366 if (isSingleBuffered) {
369 flushedView.layer.contents = nil;
372 qCInfo(lcQpaBackingStore) <<
"Flushing" << backBufferSurface
373 <<
"to" << flushedView.layer <<
"of" << flushedView;
375 flushedView.layer.contents = backBufferSurface;
377 if (!isSingleBuffered) {
380 IOSurfaceIncrementUseCount(m_buffers.back()->surface());
382 if (m_buffers.back() != m_buffers.front()) {
383 qCInfo(lcQpaBackingStore) <<
"Swapping back buffer to front";
384 std::swap(m_buffers.back(), m_buffers.front());
385 IOSurfaceDecrementUseCount(m_buffers.back()->surface());
390void QCALayerBackingStore::flushSubWindow(
QWindow *subWindow)
392 qCInfo(lcQpaBackingStore) <<
"Flushing sub-window" << subWindow
393 <<
"via its own backingstore";
395 auto &subWindowBackingStore = m_subWindowBackingstores[subWindow];
396 if (!subWindowBackingStore) {
399 subWindowBackingStore->m_clearSurfaceOnPaint =
false;
402 auto subWindowSize = subWindow->size();
403 static const auto kNoStaticContents =
QRegion();
404 subWindowBackingStore->resize(subWindowSize, kNoStaticContents);
406 auto subWindowLocalRect =
QRect(
QPoint(), subWindowSize);
407 subWindowBackingStore->beginPaint(subWindowLocalRect);
413 NSView *flushedView =
static_cast<QCocoaWindow *
>(subWindow->handle())->
view();
414 auto subviewRect = [flushedView convertRect:flushedView.bounds toView:backingStoreView];
415 auto scale = flushedView.layer.contentsScale;
416 subviewRect = CGRectApplyAffineTransform(subviewRect, CGAffineTransformMakeScale(
scale,
scale));
419 const QImage *backingStoreImage = m_buffers.back()->asImage();
420 painter.
drawImage(subWindowLocalRect, *backingStoreImage, QRectF::fromCGRect(subviewRect));
421 m_buffers.back()->unlock();
424 subWindowBackingStore->endPaint();
425 subWindowBackingStore->flush(subWindow, subWindowLocalRect,
QPoint());
427 qCInfo(lcQpaBackingStore) <<
"Done flushing sub-window" << subWindow;
430void QCALayerBackingStore::windowDestroyed(
QObject *
object)
433 qCInfo(lcQpaBackingStore) <<
"Removing backingstore for sub-window" <<
window;
434 m_subWindowBackingstores.erase(
window);
438 qreal sourceDevicePixelRatio,
442 bool translucentBackground)
444 if (!m_buffers.back()) {
445 qCWarning(lcQpaBackingStore) <<
"Tried to flush backingstore without painting to it first";
449 finalizeBackBuffer();
456 if (!m_buffers.back())
466 QImage imageCopy = m_buffers.back()->asImage()->copy();
467 m_buffers.back()->unlock();
471void QCALayerBackingStore::backingPropertiesChanged()
480 qCDebug(lcQpaBackingStore) <<
"Backing properties for" <<
window() <<
"did change";
483 qCDebug(lcQpaBackingStore) <<
"Updating color space of existing buffers to" << newColorSpace;
484 for (
auto &
buffer : m_buffers) {
486 buffer->setColorSpace(newColorSpace);
492 return m_buffers.back().get();
495void QCALayerBackingStore::updateDirtyStates(
const QRegion &paintedRegion)
501 for (
const auto &
buffer : m_buffers) {
502 if (
buffer == m_buffers.back())
503 buffer->dirtyRegion -= paintedRegion;
505 buffer->dirtyRegion += paintedRegion;
509void QCALayerBackingStore::finalizeBackBuffer()
515 if (!m_buffers.back()->isDirty())
518 qCDebug(lcQpaBackingStore) <<
"Finalizing back buffer with dirty region" << m_buffers.back()->dirtyRegion;
520 if (m_buffers.back() != m_buffers.front()) {
522 blitBuffer(m_buffers.front().get(), m_buffers.back()->dirtyRegion, m_buffers.back().get());
523 m_buffers.back()->unlock();
525 qCDebug(lcQpaBackingStore) <<
"Front and back buffer is the same. Can not finalize back buffer.";
529 m_buffers.back()->dirtyRegion =
QRegion();
545void QCALayerBackingStore::blitBuffer(GraphicsBuffer *sourceBuffer,
const QRegion &sourceRegion,
546 GraphicsBuffer *destinationBuffer,
const QPoint &destinationOffset)
548 Q_ASSERT(sourceBuffer && destinationBuffer);
549 Q_ASSERT(sourceBuffer != destinationBuffer);
551 if (sourceRegion.isEmpty())
554 qCDebug(lcQpaBackingStore) <<
"Blitting" << sourceRegion <<
"of" << sourceBuffer
555 <<
"to" << sourceRegion.translated(destinationOffset) <<
"of" << destinationBuffer;
560 const QImage *sourceImage = sourceBuffer->asImage();
562 const QRect sourceBufferBounds(
QPoint(0, 0), sourceBuffer->size());
563 const qreal sourceDevicePixelRatio = sourceImage->devicePixelRatio();
570 painter.
scale(1.0 / destinationDevicePixelRatio, 1.0 / destinationDevicePixelRatio);
573 QRect sourceRect(
rect.topLeft() * sourceDevicePixelRatio,
574 rect.size() * sourceDevicePixelRatio);
575 QRect destinationRect((
rect.topLeft() + destinationOffset) * destinationDevicePixelRatio,
576 rect.size() * destinationDevicePixelRatio);
579 if (
Q_UNLIKELY(!sourceBufferBounds.contains(sourceRect.bottomRight()))) {
580 qCWarning(lcQpaBackingStore) <<
"Source buffer of size" << sourceBuffer->size()
581 <<
"is too small to blit" << sourceRect;
587 sourceBuffer->unlock();
592QCALayerBackingStore::GraphicsBuffer::GraphicsBuffer(
const QSize &
size,
qreal devicePixelRatio,
596 , m_devicePixelRatio(devicePixelRatio)
601QRegion QCALayerBackingStore::GraphicsBuffer::validRegion()
const
605 return fullRegion - dirtyRegion;
608QImage *QCALayerBackingStore::GraphicsBuffer::asImage()
610 if (m_image.isNull()) {
611 qCDebug(lcQpaBackingStore) <<
"Setting up paint device for" <<
this;
616 m_image.setDevicePixelRatio(m_devicePixelRatio);
620 "IOSurfaces should have have a fixed location in memory once created");
627#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...
QPlatformGraphicsBuffer * graphicsBuffer() const override
Accessor for a backingstores graphics buffer abstraction.
QCALayerBackingStore(QWindow *window)
bool eventFilter(QObject *watched, QEvent *event) override
Filters events if this object has been installed as an event filter for the watched object.
FlushResult rhiFlush(QWindow *window, qreal sourceDevicePixelRatio, const QRegion ®ion, const QPoint &offset, QPlatformTextureList *textures, bool translucentBackground) override
Flushes the given region from the specified window, and compositing it with the specified textures li...
void resize(const QSize &size, const QRegion &staticContents) override
static QCFType constructFromGet(const T &t)
QCFType< CGColorSpaceRef > colorSpace() const
QCocoaBackingStore(QWindow *window)
qreal devicePixelRatio() const override
Reimplement this function in subclass to return the device pixel ratio for the window.
static QPixelFormat toPixelFormat(QImage::Format format) noexcept
Converts format into a QPixelFormat.
@ Format_ARGB32_Premultiplied
static QImage::Format toImageFormat(QPixelFormat format) noexcept
Converts format into a QImage::Format.
void installEventFilter(QObject *filterObj)
Installs an event filter filterObj on this object.
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
void destroyed(QObject *=nullptr)
This signal is emitted immediately before the object obj is destroyed, after any instances of QPointe...
qreal devicePixelRatio() const
The QPainter class performs low-level painting on widgets and other paint devices.
QPaintDevice * device() const
Returns the paint device on which this painter is currently painting, or \nullptr if the painter is n...
void scale(qreal sx, qreal sy)
Scales the coordinate system by ({sx}, {sy}).
void setCompositionMode(CompositionMode mode)
Sets the composition mode to the given mode.
void drawImage(const QRectF &targetRect, const QImage &image, const QRectF &sourceRect, Qt::ImageConversionFlags flags=Qt::AutoColor)
Draws the rectangular portion source of the given image into the target rectangle in the paint device...
void fillRect(const QRectF &, const QBrush &)
Fills the given rectangle with the brush specified.
\inmodule QtCore\reentrant
\inmodule QtCore\reentrant
constexpr QSize size() const noexcept
Returns the size of the rectangle.
The QRegion class specifies a clip region for a painter.
bool isEmpty() const
Returns true if the region is empty; otherwise returns false.
QRegion translated(int dx, int dy) const
QRect geometry
the screen's geometry in pixels
constexpr QSize boundedTo(const QSize &) const noexcept
Returns a size holding the minimum width and height of this size and the given otherSize.
constexpr int height() const noexcept
Returns the height.
constexpr int width() const noexcept
Returns the width.
QSurfaceFormat format() const override
Returns the actual format of this window.
Combined button and popup list for selecting options.
QNSView * qnsview_cast(NSView *view)
Returns the view cast to a QNSview if possible.
void(* QImageCleanupFunction)(void *)
#define qCInfo(category,...)
#define qCWarning(category,...)
#define qCDebug(category,...)
constexpr quint32 qNextPowerOfTwo(quint32 v)
GLuint64 GLenum void * handle
GLint GLsizei GLsizei height
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLuint const GLuint GLuint const GLuint * textures
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum GLuint GLintptr offset
GLint GLsizei GLsizei GLenum format
GLenum GLenum GLenum GLenum GLenum scale
void qt_scrollRectInImage(QImage &img, const QRect &rect, const QPoint &offset)
static bool hasAlpha(const QImage &image)
#define Q_ASSERT_X(cond, x, msg)