dalboris / vpaint

Experimental vector graphics and 2D animation editor

Home Page:http://www.vpaint.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Picking incorrect mouse location on macOS retina displays

dalboris opened this issue · comments

Related: #22 #108

This is extremely weird. On a macOS retina screen, there is no mouse offset bug when drawing (F2), but there is a mouse offset issue when picking (F1, or F3/F4 in some circumstances), EXCEPT when we have just resized the window, or some other window-switching operations.

out

Follow-up investigation.

Problem 1

Our GLWidget class overrides QOpenGLWidget::resizeGL() with the following implementation:

void GLWidget::resizeGL(int width, int height)
{
      viewportWidth_ = width;
      viewportHeight_ = height;
      glViewport(0, 0, width, height);
      emit viewResized();
}

This was okay back when we were inheriting QGLWidget, but now that we are inheriting the more modern QOpenGLWidget, this is bad since width/height are here in virtual pixels. With QOpenGLWidget, it is Qt's responsibility to call glViewport, not ours. Qt will automatically call glViewport itself whenever appropriate, with appropriate values, potentially bigger than the virtual width/height on Retina screen, see:

https://doc.qt.io/qt-5/qopenglwidget.html#paintGL
https://doc.qt.io/qt-5/qopenglwidget.html#resizeEvent

void QOpenGLWidget::paintGL()

This virtual function is called whenever the widget needs to be painted. Reimplement it in a subclass.

There is no need to call makeCurrent() because this has already been done when this function is called.

Before invoking this function, the context and the framebuffer are bound, and the viewport is set up by a call to glViewport(). No other state is set and no clearing or drawing is performed by the framework.

void QOpenGLWidget::resizeEvent(QResizeEvent *e)

Handles resize events that are passed in the e event parameter. Calls the virtual function resizeGL().

Note: Avoid overriding this function in derived classes. If that is not feasible, make sure that QOpenGLWidget's implementation is invoked too. Otherwise the underlying framebuffer object and related resources will not get resized properly and will lead to incorrect rendering.

This is what causes the discrepancy of behaviour between picking after startup/resizing vs. picking after any other operation. The other operations would have involved at least one other paintGL(), which would have called glViewport() with different values than our manual glViewport() based on virtual pixels.

Note that we are currently overriding resizeGL(), not resizeEvent(), so the situation isn't that bad. In particular, it's okay not to call the default implementation of resizeGL() since the default implementation does nothing anyway (see source). Still, we shouldn't call glViewport() our reimplementation. Finally, note that the arguments of resizeGL(), like resizeEvent(), are expressed in virtual pixels, that is, they are equal to the widget width() and height() (see source).

Problem 2

For picking purposes, we draw the object IDs to an offscreen buffer (fbo). The size of this buffer is determined via the following code:

    makeCurrent();
    GLint m_viewport[4];
    glGetIntegerv( GL_VIEWPORT, m_viewport );
    WINDOW_SIZE_X_ = m_viewport[2]; // fbo width
    WINDOW_SIZE_Y_ = m_viewport[3]; // fbo height

That is, it is based on whichever value has been passed to glViewport(). As we have seen above, currently, these values differ whether the last call of glViewport() was our manual call (=virtual size) or Qt's own call (=physical size).

Later, we query the value of the fbo based on mouse virtual positions. So we get the correct behaviour when the size of this fbo is the virtual size of the widget. This is currently the case just after resizing the widget, "thanks" to our broken implementation of GLWidget::resizeGL(int width, int height) (it's one of these cases where two errors cancel each others). But after any other operation than resizing the window, the fbo size becomes the physical size, thus the picking bug.

The proper fix should be to set the size of the fbo to be the virtual size, regardless of the current value of GL_VIEWPORT.