Skip to content

Instantly share code, notes, and snippets.

@torarnv
Created September 20, 2019 14:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save torarnv/8b34109d671bbde6e7c3ea404b99dabb to your computer and use it in GitHub Desktop.
Save torarnv/8b34109d671bbde6e7c3ea404b99dabb to your computer and use it in GitHub Desktop.
diff --git i/examples/gui/openglwindow/main.cpp w/examples/gui/openglwindow/main.cpp
index 90c93f0d37..fb40e71459 100644
--- i/examples/gui/openglwindow/main.cpp
+++ w/examples/gui/openglwindow/main.cpp
@@ -139,6 +139,12 @@ void TriangleWindow::render()
const qreal retinaScale = devicePixelRatio();
glViewport(0, 0, width() * retinaScale, height() * retinaScale);
+// GLint dims[4] = {0};
+// glGetIntegerv(GL_VIEWPORT, dims);
+// GLint fbWidth = dims[2];
+// GLint fbHeight = dims[3];
+ // qDebug() << "GL" << fbWidth << fbHeight;
+
glClear(GL_COLOR_BUFFER_BIT);
m_program->bind();
diff --git i/examples/gui/openglwindow/openglwindow.cpp w/examples/gui/openglwindow/openglwindow.cpp
index a0c85006bd..c73f0f4824 100644
--- i/examples/gui/openglwindow/openglwindow.cpp
+++ w/examples/gui/openglwindow/openglwindow.cpp
@@ -56,6 +56,8 @@
#include <QtGui/QOpenGLPaintDevice>
#include <QtGui/QPainter>
+#include <QDebug>
+
//! [1]
OpenGLWindow::OpenGLWindow(QWindow *parent)
: QWindow(parent)
@@ -137,6 +139,8 @@ void OpenGLWindow::renderNow()
needsInitialize = true;
}
+
+ // qDebug() << "before" << size();
m_context->makeCurrent(this);
@@ -149,6 +153,8 @@ void OpenGLWindow::renderNow()
m_context->swapBuffers(this);
+ //qDebug() << "after" << size();
+
if (m_animating)
renderLater();
}
diff --git i/src/plugins/platforms/cocoa/qnsview_drawing.mm w/src/plugins/platforms/cocoa/qnsview_drawing.mm
index 425c79d6a7..c0a4e3bf94 100644
--- i/src/plugins/platforms/cocoa/qnsview_drawing.mm
+++ w/src/plugins/platforms/cocoa/qnsview_drawing.mm
@@ -71,23 +71,7 @@
return YES;
}
-- (void)drawRect:(NSRect)dirtyRect
-{
- Q_UNUSED(dirtyRect);
-
- 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();
-
- qCDebug(lcQpaDrawing) << "[QNSView drawRect:]" << m_platformWindow->window() << exposedRegion;
- m_platformWindow->handleExposeEvent(exposedRegion);
-}
+// ----------------------- Layer setup -----------------------
- (BOOL)layerEnabledByMacOS
{
@@ -123,38 +107,106 @@
return surfaceType == QWindow::MetalSurface || surfaceType == QWindow::VulkanSurface;
}
+/*
+ This method is called by AppKit when layer-backing is requested by
+ setting wantsLayer too YES (via -[NSView _updateLayerBackedness]),
+ or in cases where AppKit itself decides that a view should be
+ layer-backed.
+
+ Note however that some code paths in AppKit will not go via this
+ method for creating the backing layer, and will instead create the
+ layer manually, and just call setLayer. An example of this is when
+ an NSOpenGLContext is attached to a view, in which case AppKit will
+ create a new layer in NSOpenGLContextSetLayerOnViewIfNecessary.
+
+ For this reason we leave the implementation of this override as
+ minimal as possible, only focusing on creating the appropriate
+ layer type, and then leave it up to setLayer to do the work of
+ making sure the layer is set up correctly.
+*/
- (CALayer *)makeBackingLayer
{
if ([self shouldUseMetalLayer]) {
// Check if Metal is supported. If it isn't then it's most likely
// too late at this point and the QWindow will be non-functional,
// but we can at least print a warning.
- if (![MTLCreateSystemDefaultDevice() autorelease]) {
- qWarning() << "QWindow initialization error: Metal is not supported";
- return [super makeBackingLayer];
+ if ([MTLCreateSystemDefaultDevice() autorelease]) {
+ return [CAMetalLayer layer];
+ } else {
+ qCWarning(lcQpaDrawing) << "Failed to create QWindow::MetalSurface."
+ << "Metal is not supported by any of the GPUs in this system.";
}
-
- CAMetalLayer *layer = [CAMetalLayer layer];
-
- // Set the contentsScale for the layer. This is normally done in
- // viewDidChangeBackingProperties, however on startup that function
- // is called before the layer is created here. The layer's drawableSize
- // is updated from layoutSublayersOfLayer as usual.
- layer.contentsScale = self.window.backingScaleFactor;
-
- return layer;
}
return [super makeBackingLayer];
}
+/*
+ This method is called by AppKit whenever the view is asked to change
+ its layer, which can happen both as a result of enabling layer-backing,
+ or when a layer is set explicitly. The latter can happen both when a
+ view is layer-hosting, or when AppKit internals are switching out the
+ layer-backed view, as described above for makeBackingLayer.
+*/
- (void)setLayer:(CALayer *)layer
{
- qCDebug(lcQpaDrawing) << "Making" << self << "layer-backed with" << layer
- << "due to being" << ([self layerExplicitlyRequested] ? "explicitly requested"
+ 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");
+
+ if (layer.delegate && layer.delegate != self) {
+ qCWarning(lcQpaDrawing) << "Layer already has delegate" << layer.delegate
+ << "This delegate is responsible for all view updates for" << self;
+ } else {
+ layer.delegate = self;
+ }
+
[super setLayer:layer];
- layer.delegate = self;
+
+ // When adding a view to a view hierarchy the backing properties will change
+ // which results in updating the contents scale, but in case of switching the
+ // layer on a view that's already in a view hierarchy we need to manually ensure
+ // the scale is up to date.
+ if (self.superview)
+ [self updateLayerContentsScale];
+
+ if (true || (self.opaque && lcQpaDrawing().isDebugEnabled())) {
+ // If the view claims to be opaque we expect it to fill the entire
+ // layer with content, in which case we want to detect any areas
+ // where it doesn't.
+ layer.backgroundColor = NSColor.magentaColor.CGColor;
+ }
+
+}
+
+// ----------------------- Layer updates -----------------------
+
+- (void)updateLayerContentsScale
+{
+ // We want the layer's contents scale to match the content it is representing.
+ // In most cases the content will follow the window backing scale factor, but
+ // when rendering with OpenGL and wantsBestResolutionOpenGLSurface is set to
+ // NO the framebuffer will be 1x. By using the devicePixelRatio of the platform
+ // window we hook into [NSView convertSizeToBacking], which takes all this into
+ // account and gives us the ideal scale factor for the layer. Note that this
+ // does not actually check the contents for its actual size, it just uses
+ // heuristics to decide whether to pick up the window backing scale or not.
+ auto idealContentScale = m_platformWindow->devicePixelRatio();
+ qCDebug(lcQpaDrawing) << "Updating" << self.layer << "content scale to" << idealContentScale;
+ self.layer.contentsScale = idealContentScale;
+}
+
+/*
+ This method is called by AppKit to determine whether it should update
+ the contentScale of the layer to match the window backing scale.
+
+ We always return NO since we're updating the contents scale manually.
+*/
+- (BOOL)layer:(CALayer *)layer shouldInheritContentsScale:(CGFloat)scale fromWindow:(NSWindow *)window
+{
+ Q_UNUSED(layer); Q_UNUSED(scale); Q_UNUSED(window);
+ return NO;
}
- (NSViewLayerContentsRedrawPolicy)layerContentsRedrawPolicy
@@ -164,17 +216,69 @@
return NSViewLayerContentsRedrawDuringViewResize;
}
-#if 0 // Disabled until we enable lazy backingstore resizing
- (NSViewLayerContentsPlacement)layerContentsPlacement
{
- // Always place the layer at top left without any automatic scaling,
- // so that we can re-use larger layers when resizing a window down.
+ // Always place the layer at top left without any automatic scaling.
+ // This will highlight situations where we're missing content for the
+ // layer by not responding to the displayLayer: request synchronously.
+ // It also allows us to re-use larger layers when resizing a window down.
return NSViewLayerContentsPlacementTopLeft;
}
-#endif
+- (void)viewDidChangeBackingProperties
+{
+ qCDebug(lcQpaDrawing) << "Backing properties changed for" << self;
+
+ if (self.layer)
+ self.layer.contentsScale = self.window.backingScaleFactor;
+
+ // Ideally we would plumb this situation thought QPA in a way that lets
+ // clients invalidate their own caches, recreate QBackingStore, etc.
+ // For now we trigger an expose, and let QCocoaBackingStore deal with
+ // buffer invalidation internally.
+ [self setNeedsDisplay:YES];
+}
+
+// ----------------------- Draw callbacks -----------------------
+
+/*
+ This method is called by AppKit for the non-layer case, where we are
+ drawing into the NSWindow's surface.
+*/
+- (void)drawRect:(NSRect)dirtyBoundingRect
+{
+ 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);
+}
+
+/*
+ This method is called by AppKit when we are layer-backed, where
+ we are drawing into the layer.
+*/
- (void)displayLayer:(CALayer *)layer
{
+ Q_ASSERT_X(self.layer && layer == self.layer, "QNSView",
+ "The displayLayer code path should only be hit for our own layer");
+
+ if (!m_platformWindow)
+ return;
+
if (!NSThread.isMainThread) {
// Qt is calling AppKit APIs such as -[NSOpenGLContext setView:] on secondary threads,
// which we shouldn't do. This may result in AppKit (wrongly) triggering a display on
@@ -184,29 +288,8 @@
return;
}
- Q_ASSERT(layer == self.layer);
-
- if (!m_platformWindow)
- return;
-
qCDebug(lcQpaDrawing) << "[QNSView displayLayer]" << m_platformWindow->window();
-
- // FIXME: Find out if there's a way to resolve the dirty rect like in drawRect:
m_platformWindow->handleExposeEvent(QRectF::fromCGRect(self.bounds).toRect());
}
-- (void)viewDidChangeBackingProperties
-{
- qCDebug(lcQpaDrawing) << "Backing properties changed for" << self;
-
- if (self.layer)
- self.layer.contentsScale = self.window.backingScaleFactor;
-
- // Ideally we would plumb this situation thought QPA in a way that lets
- // clients invalidate their own caches, recreate QBackingStore, etc.
- // For now we trigger an expose, and let QCocoaBackingStore deal with
- // buffer invalidation internally.
- [self setNeedsDisplay:YES];
-}
-
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment