macOS: Remove support for surface-backed views

Our deployment target is 10.14, which enables layer-backing by default,
and our layer-backing support nowadays is stable enough that we don't
need to maintain any of the old code paths for compatibility.

The wantsBestResolutionOpenGLSurface property on NSView is only relevant
for surface-backed views, so we no longer need to deal with it.

Change-Id: I8aef4ac99371113d463ac35eee648a8a2fd1ea72
Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
This commit is contained in:
Tor Arne Vestbø 2020-08-05 11:07:45 +02:00
parent d1111632e2
commit 1fc7ca091b
8 changed files with 32 additions and 416 deletions

View File

@ -58,21 +58,6 @@ protected:
QCFType<CGColorSpaceRef> colorSpace() const;
};
class QNSWindowBackingStore : public QCocoaBackingStore
{
public:
QNSWindowBackingStore(QWindow *window);
~QNSWindowBackingStore();
void resize(const QSize &size, const QRegion &staticContents) override;
void flush(QWindow *, const QRegion &, const QPoint &) override;
private:
bool windowHasUnifiedToolbar() const;
QImage::Format format() const override;
void redrawRoundedBottomCorners(CGRect) const;
};
class QCALayerBackingStore : public QObject, public QCocoaBackingStore
{
Q_OBJECT

View File

@ -64,276 +64,6 @@ QCFType<CGColorSpaceRef> QCocoaBackingStore::colorSpace() const
// ----------------------------------------------------------------------------
QNSWindowBackingStore::QNSWindowBackingStore(QWindow *window)
: QCocoaBackingStore(window)
{
// Choose an appropriate window depth based on the requested surface format.
// On deep color displays the default bit depth is 16-bit, so unless we need
// that level of precision we opt out of it (and the expensive RGB32 -> RGB64
// conversions that come with it if our backingstore depth does not match).
NSWindow *nsWindow = static_cast<QCocoaWindow *>(window->handle())->view().window;
auto colorSpaceName = NSColorSpaceFromDepth(nsWindow.depthLimit);
static const int kDefaultBitDepth = 8;
auto surfaceFormat = window->requestedFormat();
auto bitsPerSample = qMax(kDefaultBitDepth, qMax(surfaceFormat.redBufferSize(),
qMax(surfaceFormat.greenBufferSize(), surfaceFormat.blueBufferSize())));
// NSBestDepth does not seem to guarantee a window depth deep enough for the
// given bits per sample, even if documented as such. For example, requesting
// 10 bits per sample will not give us a 16-bit format, even if that's what's
// available. Work around this by manually bumping the bit depth.
bitsPerSample = !(bitsPerSample & (bitsPerSample - 1))
? bitsPerSample : qNextPowerOfTwo(bitsPerSample);
auto bestDepth = NSBestDepth(colorSpaceName, bitsPerSample, 0, NO, nullptr);
// Disable dynamic depth limit, otherwise our depth limit will be overwritten
// by AppKit if the window moves to a screen with a different depth. We call
// this before setting the depth limit, as the call will reset the depth to 0.
[nsWindow setDynamicDepthLimit:NO];
qCDebug(lcQpaBackingStore) << "Using" << NSBitsPerSampleFromDepth(bestDepth)
<< "bit window depth for" << nsWindow;
nsWindow.depthLimit = bestDepth;
}
QNSWindowBackingStore::~QNSWindowBackingStore()
{
}
bool QNSWindowBackingStore::windowHasUnifiedToolbar() const
{
Q_ASSERT(window()->handle());
return static_cast<QCocoaWindow *>(window()->handle())->m_drawContentBorderGradient;
}
QImage::Format QNSWindowBackingStore::format() const
{
if (windowHasUnifiedToolbar())
return QImage::Format_ARGB32_Premultiplied;
return QRasterBackingStore::format();
}
void QNSWindowBackingStore::resize(const QSize &size, const QRegion &staticContents)
{
qCDebug(lcQpaBackingStore) << "Resize requested to" << size;
QRasterBackingStore::resize(size, staticContents);
// The window shadow rendered by AppKit is based on the shape/content of the
// NSWindow surface. Technically any flush of the backingstore can result in
// a potentially new shape of the window, and would need a shadow invalidation,
// but this is likely too expensive to do at every flush for the few cases where
// clients change the shape dynamically. One case where we do know that the shadow
// likely needs invalidation, if the window has partially transparent content,
// is after a resize, where AppKit's default shadow may be based on the previous
// window content.
QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window()->handle());
if (cocoaWindow->isContentView() && !cocoaWindow->isOpaque())
cocoaWindow->m_needsInvalidateShadow = true;
}
/*!
Flushes the given \a region from the specified \a window onto the
screen.
The \a window is the top level window represented by this backingstore,
or a non-transient child of that window.
If the \a window is a child window, the \a region will be in child window
coordinates, and the \a offset will be the child window's offset in relation
to the backingstore's top level window.
*/
void QNSWindowBackingStore::flush(QWindow *window, const QRegion &region, const QPoint &offset)
{
if (m_image.isNull())
return;
// Use local pool so that any stale image references are cleaned up after flushing
QMacAutoReleasePool pool;
const QWindow *topLevelWindow = this->window();
Q_ASSERT(topLevelWindow->handle() && window->handle());
Q_ASSERT(!topLevelWindow->handle()->isForeignWindow() && !window->handle()->isForeignWindow());
QNSView *topLevelView = qnsview_cast(static_cast<QCocoaWindow *>(topLevelWindow->handle())->view());
QNSView *view = qnsview_cast(static_cast<QCocoaWindow *>(window->handle())->view());
if (lcQpaBackingStore().isDebugEnabled()) {
QString targetViewDescription;
if (view != topLevelView) {
QDebug targetDebug(&targetViewDescription);
targetDebug << "onto" << topLevelView << "at" << offset;
}
qCDebug(lcQpaBackingStore) << "Flushing" << region << "of" << view << qPrintable(targetViewDescription);
}
// Normally a NSView is drawn via drawRect, as part of the display cycle in the
// main runloop, via setNeedsDisplay and friends. AppKit will lock focus on each
// individual view, starting with the top level and then traversing any subviews,
// calling drawRect for each of them. This pull model results in expose events
// sent to Qt, which result in drawing to the backingstore and flushing it.
// Qt may also decide to paint and flush the backingstore via e.g. timers,
// or other events such as mouse events, in which case we're in a push model.
// If there is no focused view, it means we're in the latter case, and need
// to manually flush the NSWindow after drawing to its graphic context.
const bool drawingOutsideOfDisplayCycle = ![NSView focusView];
// We also need to ensure the flushed view has focus, so that the graphics
// context is set up correctly (coordinate system, clipping, etc). Outside
// of the normal display cycle there is no focused view, as explained above,
// so we have to handle it manually. There's also a corner case inside the
// normal display cycle due to way QWidgetRepaintManager composits native child
// widgets, where we'll get a flush of a native child during the drawRect of
// its parent/ancestor, and the parent/ancestor being the one locked by AppKit.
// In this case we also need to lock and unlock focus manually.
const bool shouldHandleViewLockManually = [NSView focusView] != view;
if (shouldHandleViewLockManually && !QT_IGNORE_DEPRECATIONS([view lockFocusIfCanDraw])) {
qWarning() << "failed to lock focus of" << view;
return;
}
const qreal devicePixelRatio = m_image.devicePixelRatio();
// If the flushed window is a content view, and we're filling the drawn area
// completely, or it doesn't have a window background we need to preserve,
// we can get away with copying instead of blending the backing store.
QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window->handle());
const NSCompositingOperation compositingOperation = cocoaWindow->isContentView()
&& (cocoaWindow->isOpaque() || view.window.backgroundColor == NSColor.clearColor)
? NSCompositingOperationCopy : NSCompositingOperationSourceOver;
#ifdef QT_DEBUG
static bool debugBackingStoreFlush = [[NSUserDefaults standardUserDefaults]
boolForKey:@"QtCocoaDebugBackingStoreFlush"];
#endif
// -------------------------------------------------------------------------
// The current contexts is typically a NSWindowGraphicsContext, but can be
// NSBitmapGraphicsContext e.g. when debugging the view hierarchy in Xcode.
// If we need to distinguish things here in the future, we can use e.g.
// [NSGraphicsContext drawingToScreen], or the attributes of the context.
NSGraphicsContext *graphicsContext = [NSGraphicsContext currentContext];
Q_ASSERT_X(graphicsContext, "QCocoaBackingStore",
"Focusing the view should give us a current graphics context");
// Tag backingstore image with color space based on the window.
// Note: This does not copy the underlying image data.
QCFType<CGImageRef> cgImage = CGImageCreateCopyWithColorSpace(
QCFType<CGImageRef>(m_image.toCGImage()), colorSpace());
// Create temporary image to use for blitting, without copying image data
NSImage *backingStoreImage = [[[NSImage alloc] initWithCGImage:cgImage size:NSZeroSize] autorelease];
QRegion clippedRegion = region;
for (QWindow *w = window; w; w = w->parent()) {
if (!w->mask().isEmpty()) {
clippedRegion &= w == window ? w->mask()
: w->mask().translated(window->mapFromGlobal(w->mapToGlobal(QPoint(0, 0))));
}
}
for (const QRect &viewLocalRect : clippedRegion) {
QPoint backingStoreOffset = viewLocalRect.topLeft() + offset;
QRect backingStoreRect(backingStoreOffset * devicePixelRatio, viewLocalRect.size() * devicePixelRatio);
if (graphicsContext.flipped) // Flip backingStoreRect to match graphics context
backingStoreRect.moveTop(m_image.height() - (backingStoreRect.y() + backingStoreRect.height()));
CGRect viewRect = viewLocalRect.toCGRect();
[backingStoreImage drawInRect:viewRect fromRect:backingStoreRect.toCGRect()
operation:compositingOperation fraction:1.0 respectFlipped:YES hints:nil];
#ifdef QT_DEBUG
if (Q_UNLIKELY(debugBackingStoreFlush)) {
[[NSColor colorWithCalibratedRed:drand48() green:drand48() blue:drand48() alpha:0.3] set];
[NSBezierPath fillRect:viewRect];
if (drawingOutsideOfDisplayCycle) {
[[[NSColor magentaColor] colorWithAlphaComponent:0.5] set];
[NSBezierPath strokeLineFromPoint:viewLocalRect.topLeft().toCGPoint()
toPoint:viewLocalRect.bottomRight().toCGPoint()];
}
}
#endif
}
// -------------------------------------------------------------------------
if (shouldHandleViewLockManually)
QT_IGNORE_DEPRECATIONS([view unlockFocus]);
if (drawingOutsideOfDisplayCycle) {
redrawRoundedBottomCorners([view convertRect:region.boundingRect().toCGRect() toView:nil]);
QT_IGNORE_DEPRECATIONS([view.window flushWindow]);
}
// Done flushing to NSWindow backingstore
QCocoaWindow *topLevelCocoaWindow = static_cast<QCocoaWindow *>(topLevelWindow->handle());
if (Q_UNLIKELY(topLevelCocoaWindow->m_needsInvalidateShadow)) {
qCDebug(lcQpaBackingStore) << "Invalidating window shadow for" << topLevelCocoaWindow;
[topLevelView.window invalidateShadow];
topLevelCocoaWindow->m_needsInvalidateShadow = false;
}
}
/*
When drawing outside of the display cycle, which Qt Widget does a lot,
we end up drawing over the NSThemeFrame, losing the rounded corners of
windows in the process.
To work around this, until we've enabled updates via setNeedsDisplay and/or
enabled layer-backed views, we ask the NSWindow to redraw the bottom corners
if they intersect with the flushed region.
This is the same logic used internally by e.g [NSView displayIfNeeded],
[NSRulerView _scrollToMatchContentView], and [NSClipView _immediateScrollToPoint:],
as well as the workaround used by WebKit to fix a similar bug:
https://trac.webkit.org/changeset/85376/webkit
*/
void QNSWindowBackingStore::redrawRoundedBottomCorners(CGRect windowRect) const
{
#if !defined(QT_APPLE_NO_PRIVATE_APIS)
Q_ASSERT(this->window()->handle());
NSWindow *window = static_cast<QCocoaWindow *>(this->window()->handle())->nativeWindow();
static SEL intersectBottomCornersWithRect = NSSelectorFromString(
[NSString stringWithFormat:@"_%s%s:", "intersectBottomCorners", "WithRect"]);
if (NSMethodSignature *signature = [window methodSignatureForSelector:intersectBottomCornersWithRect]) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
invocation.target = window;
invocation.selector = intersectBottomCornersWithRect;
[invocation setArgument:&windowRect atIndex:2];
[invocation invoke];
NSRect cornerOverlap = NSZeroRect;
[invocation getReturnValue:&cornerOverlap];
if (!NSIsEmptyRect(cornerOverlap)) {
static SEL maskRoundedBottomCorners = NSSelectorFromString(
[NSString stringWithFormat:@"_%s%s:", "maskRounded", "BottomCorners"]);
if ((signature = [window methodSignatureForSelector:maskRoundedBottomCorners])) {
invocation = [NSInvocation invocationWithMethodSignature:signature];
invocation.target = window;
invocation.selector = maskRoundedBottomCorners;
[invocation setArgument:&cornerOverlap atIndex:2];
[invocation invoke];
}
}
}
#else
Q_UNUSED(windowRect);
#endif
}
// ----------------------------------------------------------------------------
QCALayerBackingStore::QCALayerBackingStore(QWindow *window)
: QCocoaBackingStore(window)
{

View File

@ -81,7 +81,6 @@ private:
static NSOpenGLPixelFormat *pixelFormatForSurfaceFormat(const QSurfaceFormat &format);
bool setDrawable(QPlatformSurface *surface);
void prepareDrawable(QCocoaWindow *platformWindow);
void updateSurfaceFormat();
NSOpenGLContext *m_context = nil;

View File

@ -393,8 +393,6 @@ bool QCocoaGLContext::setDrawable(QPlatformSurface *surface)
if (view == QT_IGNORE_DEPRECATIONS(m_context.view))
return true;
prepareDrawable(cocoaWindow);
// Setting the drawable may happen on a separate thread as a result of
// a call to makeCurrent, so we need to set up the observers before we
// associate the view with the context. That way we will guarantee that
@ -410,12 +408,8 @@ bool QCocoaGLContext::setDrawable(QPlatformSurface *surface)
m_updateObservers.clear();
if (view.layer) {
m_updateObservers.append(QMacNotificationObserver(view, NSViewFrameDidChangeNotification, updateCallback));
m_updateObservers.append(QMacNotificationObserver(view.window, NSWindowDidChangeScreenNotification, updateCallback));
} else {
m_updateObservers.append(QMacNotificationObserver(view, QT_IGNORE_DEPRECATIONS(NSViewGlobalFrameDidChangeNotification), updateCallback));
}
m_updateObservers.append(QMacNotificationObserver(view, NSViewFrameDidChangeNotification, updateCallback));
m_updateObservers.append(QMacNotificationObserver(view.window, NSWindowDidChangeScreenNotification, updateCallback));
m_updateObservers.append(QMacNotificationObserver([NSApplication sharedApplication],
NSApplicationDidChangeScreenParametersNotification, updateCallback));
@ -437,30 +431,6 @@ bool QCocoaGLContext::setDrawable(QPlatformSurface *surface)
return true;
}
void QCocoaGLContext::prepareDrawable(QCocoaWindow *platformWindow)
{
// We generally want high-DPI GL surfaces, unless the user has explicitly disabled them
bool prefersBestResolutionOpenGLSurface = qt_mac_resolveOption(YES,
platformWindow->window(), "_q_mac_wantsBestResolutionOpenGLSurface",
"QT_MAC_WANTS_BEST_RESOLUTION_OPENGL_SURFACE");
auto *view = platformWindow->view();
// The only case we have to opt out ourselves is when using the Apple software renderer
// in combination with surface-backed views, as these together do not support high-DPI.
if (prefersBestResolutionOpenGLSurface) {
int rendererID = 0;
[m_context getValues:&rendererID forParameter:NSOpenGLContextParameterCurrentRendererID];
bool isSoftwareRenderer = (rendererID & kCGLRendererIDMatchingMask) == kCGLRendererGenericFloatID;
if (isSoftwareRenderer && !view.layer) {
qCInfo(lcQpaOpenGLContext) << "Disabling high resolution GL surface due to software renderer";
prefersBestResolutionOpenGLSurface = false;
}
}
QT_IGNORE_DEPRECATIONS(view.wantsBestResolutionOpenGLSurface) = prefersBestResolutionOpenGLSurface;
}
// NSOpenGLContext is not re-entrant. Even when using separate contexts per thread,
// view, and window, calls into the API will still deadlock. For more information
// see https://openradar.appspot.com/37064579
@ -494,19 +464,17 @@ void QCocoaGLContext::swapBuffers(QPlatformSurface *surface)
return;
}
if (QT_IGNORE_DEPRECATIONS(m_context.view).layer) {
// Flushing an NSOpenGLContext will hit the screen immediately, ignoring
// any Core Animation transactions in place. This may result in major
// visual artifacts if the flush happens out of sync with the size
// of the layer, view, and window reflected by other parts of the UI,
// e.g. if the application flushes in the resize event or a timer during
// window resizing, instead of in the expose event.
auto *cocoaWindow = static_cast<QCocoaWindow *>(surface);
if (cocoaWindow->geometry().size() != cocoaWindow->m_exposedRect.size()) {
qCInfo(lcQpaOpenGLContext) << "Window exposed size does not match geometry (yet)."
<< "Skipping flush to avoid visual artifacts.";
return;
}
// Flushing an NSOpenGLContext will hit the screen immediately, ignoring
// any Core Animation transactions in place. This may result in major
// visual artifacts if the flush happens out of sync with the size
// of the layer, view, and window reflected by other parts of the UI,
// e.g. if the application flushes in the resize event or a timer during
// window resizing, instead of in the expose event.
auto *cocoaWindow = static_cast<QCocoaWindow *>(surface);
if (cocoaWindow->geometry().size() != cocoaWindow->m_exposedRect.size()) {
qCInfo(lcQpaOpenGLContext) << "Window exposed size does not match geometry (yet)."
<< "Skipping flush to avoid visual artifacts.";
return;
}
QMutexLocker locker(&s_reentrancyMutex);

View File

@ -259,8 +259,8 @@ bool QCocoaIntegration::hasCapability(QPlatformIntegration::Capability cap) cons
// AppKit expects rendering to happen on the main thread, and we can
// easily end up in situations where rendering on secondary threads
// will result in visual artifacts, bugs, or even deadlocks, when
// building with SDK 10.14 or higher which enbles view layer-backing.
return QMacVersion::buildSDK() < QOperatingSystemVersion(QOperatingSystemVersion::MacOSMojave);
// layer-backed.
return false;
case OpenGL:
case BufferQueueingOpenGL:
#endif
@ -333,13 +333,7 @@ QPlatformBackingStore *QCocoaIntegration::createPlatformBackingStore(QWindow *wi
return nullptr;
}
QPlatformBackingStore *backingStore = nullptr;
if (platformWindow->view().layer)
backingStore = new QCALayerBackingStore(window);
else
backingStore = new QNSWindowBackingStore(window);
return backingStore;
return new QCALayerBackingStore(window);
}
QAbstractEventDispatcher *QCocoaIntegration::createEventDispatcher() const

View File

@ -261,8 +261,6 @@ public: // for QNSView
bool m_inSetStyleMask;
QCocoaMenuBar *m_menubar;
bool m_needsInvalidateShadow;
bool m_frameStrutEventsEnabled;
QRect m_exposedRect;
int m_registerTouchCount;

View File

@ -145,7 +145,6 @@ QCocoaWindow::QCocoaWindow(QWindow *win, WId nativeHandle)
, m_inSetGeometry(false)
, m_inSetStyleMask(false)
, m_menubar(nullptr)
, m_needsInvalidateShadow(false)
, m_frameStrutEventsEnabled(false)
, m_registerTouchCount(0)
, m_resizableTransientParent(false)
@ -1056,27 +1055,15 @@ void QCocoaWindow::setMask(const QRegion &region)
{
qCDebug(lcQpaWindow) << "QCocoaWindow::setMask" << window() << region;
if (m_view.layer) {
if (!region.isEmpty()) {
QCFType<CGMutablePathRef> maskPath = CGPathCreateMutable();
for (const QRect &r : region)
CGPathAddRect(maskPath, nullptr, r.toCGRect());
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.path = maskPath;
m_view.layer.mask = maskLayer;
} else {
m_view.layer.mask = nil;
}
if (!region.isEmpty()) {
QCFType<CGMutablePathRef> maskPath = CGPathCreateMutable();
for (const QRect &r : region)
CGPathAddRect(maskPath, nullptr, r.toCGRect());
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.path = maskPath;
m_view.layer.mask = maskLayer;
} else {
if (isContentView()) {
// Setting the mask requires invalidating the NSWindow shadow, but that needs
// to happen after the backingstore has been redrawn, so that AppKit can pick
// up the new window shape based on the backingstore content. Doing a display
// directly here is not an option, as the window might not be exposed at this
// time, and so would not result in an updated backingstore.
m_needsInvalidateShadow = true;
[m_view setNeedsDisplay:YES];
}
m_view.layer.mask = nil;
}
}

View File

@ -43,7 +43,13 @@
- (void)initDrawing
{
[self updateLayerBacking];
if (qt_mac_resolveOption(-1, m_platformWindow->window(),
"_q_mac_wantsLayer", "QT_MAC_WANTS_LAYER") != -1) {
qCWarning(lcQpaDrawing) << "Layer-backing is always enabled."
<< " QT_MAC_WANTS_LAYER/_q_mac_wantsLayer has no effect.";
}
self.wantsLayer = YES;
}
- (BOOL)isOpaque
@ -60,40 +66,6 @@
// ----------------------- Layer setup -----------------------
- (void)updateLayerBacking
{
self.wantsLayer = [self layerEnabledByMacOS]
|| [self layerExplicitlyRequested]
|| [self shouldUseMetalLayer];
}
- (BOOL)layerEnabledByMacOS
{
// AppKit has its own logic for this, but if we rely on that, our layers are created
// by AppKit at a point where we've already set up other parts of the platform plugin
// based on the presence of layers or not. Once we've rewritten these parts to support
// dynamically picking up layer enablement we can let AppKit do its thing.
return QMacVersion::buildSDK() >= QOperatingSystemVersion::MacOSMojave
&& QMacVersion::currentRuntime() >= QOperatingSystemVersion::MacOSMojave;
}
- (BOOL)layerExplicitlyRequested
{
static bool wantsLayer = [&]() {
int wantsLayer = qt_mac_resolveOption(-1, m_platformWindow->window(),
"_q_mac_wantsLayer", "QT_MAC_WANTS_LAYER");
if (wantsLayer != -1 && [self layerEnabledByMacOS]) {
qCWarning(lcQpaDrawing) << "Layer-backing cannot be explicitly controlled on 10.14 when built against the 10.14 SDK";
return true;
}
return wantsLayer == 1;
}();
return wantsLayer;
}
- (BOOL)shouldUseMetalLayer
{
// MetalSurface needs a layer, and so does VulkanSurface (via MoltenVK)
@ -146,8 +118,7 @@
{
qCDebug(lcQpaDrawing) << "Making" << self
<< (self.wantsLayer ? "layer-backed" : "layer-hosted")
<< "with" << layer << "due to being" << ([self layerExplicitlyRequested] ? "explicitly requested"
: [self shouldUseMetalLayer] ? "needed by surface type" : "enabled by macOS");
<< "with" << layer;
if (layer.delegate && layer.delegate != self) {
qCWarning(lcQpaDrawing) << "Layer already has delegate" << layer.delegate
@ -244,22 +215,6 @@
{
Q_ASSERT_X(!self.layer, "QNSView",
"The drawRect code path should not be hit when we are layer backed");
if (!m_platformWindow)
return;
QRegion exposedRegion;
const NSRect *dirtyRects;
NSInteger numDirtyRects;
[self getRectsBeingDrawn:&dirtyRects count:&numDirtyRects];
for (int i = 0; i < numDirtyRects; ++i)
exposedRegion += QRectF::fromCGRect(dirtyRects[i]).toRect();
if (exposedRegion.isEmpty())
exposedRegion = QRectF::fromCGRect(dirtyBoundingRect).toRect();
qCDebug(lcQpaDrawing) << "[QNSView drawRect:]" << m_platformWindow->window() << exposedRegion;
m_platformWindow->handleExposeEvent(exposedRegion);
}
/*