11#include <QtCore/qmath.h>
12#include <QtCore/private/qcore_mac_p.h>
13#include <QtGui/qpainter.h>
15#include <QuartzCore/CATransaction.h>
35 qCDebug(lcQpaBackingStore) <<
"Creating QCALayerBackingStore for" <<
window
40 observeBackingPropertiesChanges();
48void QCALayerBackingStore::observeBackingPropertiesChanges()
53 NSWindowDidChangeBackingPropertiesNotification, [
this]() {
54 backingPropertiesChanged();
65 observeBackingPropertiesChanges();
75 qCDebug(lcQpaBackingStore) <<
"Resize requested to" <<
size;
77 if (!staticContents.
isNull())
78 qCWarning(lcQpaBackingStore) <<
"QCALayerBackingStore does not support static contents";
80 m_requestedSize =
size;
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 <<
"based on requested" << m_requestedSize <<
"and dpr =" << devicePixelRatio;
196 m_buffers.back().reset(
new GraphicsBuffer(requestedBufferSize, devicePixelRatio, pixelFormat,
colorSpace()));
206 return m_buffers.back()->asImage();
211 qCInfo(lcQpaBackingStore) <<
"Paint ended. Back buffer valid region is now" << m_buffers.back()->validRegion();
212 m_buffers.back()->unlock();
220 if (!m_buffers.back()) {
221 qCInfo(lcQpaBackingStore) <<
"Scroll requested with no back buffer. Ignoring.";
225 const QPoint scrollDelta(dx, dy);
226 qCInfo(lcQpaBackingStore) <<
"Scrolling" << region <<
"by" << scrollDelta;
229 recreateBackBufferIfNeeded();
231 const QRegion inPlaceRegion = region - m_buffers.back()->dirtyRegion;
232 const QRegion frontBufferRegion = region - inPlaceRegion;
238 if (!inPlaceRegion.
isEmpty()) {
244 qCDebug(lcQpaBackingStore) <<
"Scrolling" << inPlaceBoundingRect <<
"in place";
245 QImage *backBufferImage = m_buffers.back()->asImage();
247 const QPoint devicePixelDelta = scrollDelta * devicePixelRatio;
253 inPlaceBoundingRect.
size() * devicePixelRatio),
257 if (!frontBufferRegion.
isEmpty()) {
258 qCDebug(lcQpaBackingStore) <<
"Scrolling" << frontBufferRegion <<
"by copying from front buffer";
259 preserveFromFrontBuffer(frontBufferRegion, scrollDelta);
262 m_buffers.back()->unlock();
268 updateDirtyStates(region.
translated(scrollDelta));
270 qCInfo(lcQpaBackingStore) <<
"Scroll ended. Back buffer valid region is now" << m_buffers.back()->validRegion();
280 if (!m_buffers.back()) {
281 qCWarning(lcQpaBackingStore) <<
"Tried to flush backingstore without painting to it first";
285 finalizeBackBuffer();
287 if (flushedWindow !=
window()) {
288 flushSubWindow(flushedWindow);
292 if (m_buffers.front()->isInUse() && !m_buffers.front()->isDirty()) {
293 qCInfo(lcQpaBackingStore) <<
"Asked to flush, but front buffer is up to date. Ignoring.";
299 NSView *flushedView =
static_cast<QCocoaWindow *
>(flushedWindow->handle())->
view();
310 if (m_buffers.back()->devicePixelRatio() != flushedView.layer.contentsScale) {
311 qCWarning(lcQpaBackingStore) <<
"Back buffer dpr of" << m_buffers.back()->devicePixelRatio()
312 <<
"doesn't match" << flushedView.layer <<
"contents scale of" << flushedView.layer.contentsScale
313 <<
"- updating layer to match.";
314 flushedView.layer.contentsScale = m_buffers.back()->devicePixelRatio();
319 id backBufferSurface = (__bridge
id)m_buffers.back()->surface();
325 flushedView.window.viewsNeedDisplay = YES;
327 if (isSingleBuffered) {
330 flushedView.layer.contents = nil;
333 qCInfo(lcQpaBackingStore) <<
"Flushing" << backBufferSurface
334 <<
"to" << flushedView.layer <<
"of" << flushedView;
336 flushedView.layer.contents = backBufferSurface;
338 if (!isSingleBuffered) {
341 IOSurfaceIncrementUseCount(m_buffers.back()->surface());
343 if (m_buffers.back() != m_buffers.front()) {
344 qCInfo(lcQpaBackingStore) <<
"Swapping back buffer to front";
345 std::swap(m_buffers.back(), m_buffers.front());
346 IOSurfaceDecrementUseCount(m_buffers.back()->surface());
351void QCALayerBackingStore::flushSubWindow(
QWindow *subWindow)
353 qCInfo(lcQpaBackingStore) <<
"Flushing sub-window" << subWindow
354 <<
"via its own backingstore";
356 auto &subWindowBackingStore = m_subWindowBackingstores[subWindow];
357 if (!subWindowBackingStore) {
360 subWindowBackingStore->m_clearSurfaceOnPaint =
false;
363 auto subWindowSize = subWindow->
size();
364 static const auto kNoStaticContents =
QRegion();
365 subWindowBackingStore->resize(subWindowSize, kNoStaticContents);
367 auto subWindowLocalRect =
QRect(
QPoint(), subWindowSize);
368 subWindowBackingStore->beginPaint(subWindowLocalRect);
374 NSView *flushedView =
static_cast<QCocoaWindow *
>(subWindow->handle())->
view();
375 auto subviewRect = [flushedView convertRect:flushedView.bounds toView:backingStoreView];
376 auto scale = flushedView.layer.contentsScale;
377 subviewRect = CGRectApplyAffineTransform(subviewRect, CGAffineTransformMakeScale(
scale,
scale));
380 const QImage *backingStoreImage = m_buffers.back()->asImage();
381 painter.
drawImage(subWindowLocalRect, *backingStoreImage, QRectF::fromCGRect(subviewRect));
382 m_buffers.back()->unlock();
385 subWindowBackingStore->endPaint();
386 subWindowBackingStore->flush(subWindow, subWindowLocalRect,
QPoint());
388 qCInfo(lcQpaBackingStore) <<
"Done flushing sub-window" << subWindow;
391void QCALayerBackingStore::windowDestroyed(
QObject *
object)
394 qCInfo(lcQpaBackingStore) <<
"Removing backingstore for sub-window" <<
window;
395 m_subWindowBackingstores.erase(
window);
399 qreal sourceDevicePixelRatio,
403 bool translucentBackground)
405 if (!m_buffers.back()) {
406 qCWarning(lcQpaBackingStore) <<
"Tried to flush backingstore without painting to it first";
410 finalizeBackBuffer();
417 if (!m_buffers.back())
427 QImage imageCopy = m_buffers.back()->asImage()->copy();
428 m_buffers.back()->unlock();
432void QCALayerBackingStore::backingPropertiesChanged()
441 qCDebug(lcQpaBackingStore) <<
"Backing properties for" <<
window() <<
"did change";
443 qCDebug(lcQpaBackingStore) <<
"Updating color space of existing buffers";
444 for (
auto &
buffer : m_buffers) {
452 return m_buffers.back().get();
455void QCALayerBackingStore::updateDirtyStates(
const QRegion &paintedRegion)
461 for (
const auto &
buffer : m_buffers) {
462 if (
buffer == m_buffers.back())
463 buffer->dirtyRegion -= paintedRegion;
465 buffer->dirtyRegion += paintedRegion;
469void QCALayerBackingStore::finalizeBackBuffer()
475 if (!m_buffers.back()->isDirty())
479 preserveFromFrontBuffer(m_buffers.back()->dirtyRegion);
480 m_buffers.back()->unlock();
483 m_buffers.back()->dirtyRegion =
QRegion();
486void QCALayerBackingStore::preserveFromFrontBuffer(
const QRegion ®ion,
const QPoint &
offset)
489 if (m_buffers.front() == m_buffers.back())
492 qCDebug(lcQpaBackingStore) <<
"Preserving" << region <<
"of front buffer to"
498 const QImage *frontBuffer = m_buffers.front()->asImage();
500 const QRect frontSurfaceBounds(
QPoint(0, 0), m_buffers.front()->size());
508 painter.
scale(1.0 / targetDevicePixelRatio, 1.0 / targetDevicePixelRatio);
511 QRect sourceRect(
rect.topLeft() * sourceDevicePixelRatio,
512 rect.size() * sourceDevicePixelRatio);
514 rect.size() * targetDevicePixelRatio);
517 if (
Q_UNLIKELY(!frontSurfaceBounds.contains(sourceRect.bottomRight()))) {
518 qCWarning(lcQpaBackingStore) <<
"Front buffer too small to preserve"
525 m_buffers.front()->unlock();
530QCALayerBackingStore::GraphicsBuffer::GraphicsBuffer(
const QSize &
size,
qreal devicePixelRatio,
534 , m_devicePixelRatio(devicePixelRatio)
539QRegion QCALayerBackingStore::GraphicsBuffer::validRegion()
const
543 return fullRegion - dirtyRegion;
546QImage *QCALayerBackingStore::GraphicsBuffer::asImage()
548 if (m_image.isNull()) {
549 qCDebug(lcQpaBackingStore) <<
"Setting up paint device for" <<
this;
554 m_image.setDevicePixelRatio(m_devicePixelRatio);
558 "IOSurfaces should have have a fixed location in memory once created");
565#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
qreal devicePixelRatio() const
Returns the device pixel ratio for the image.
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 QPoint topLeft() const noexcept
Returns the position of the rectangle's top-left corner.
constexpr QSize size() const noexcept
Returns the size of the rectangle.
The QRegion class specifies a clip region for a painter.
QRect boundingRect() const noexcept
Returns the bounding rectangle of this region.
bool isEmpty() const
Returns true if the region is empty; otherwise returns false.
QRegion subtracted(const QRegion &r) const
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.
QSize size() const override
Returns the size of the window excluding any window frame.
Combined button and popup list for selecting options.
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)