QPainter::drawImage() Qt 5.10 中的故障

QPainter::drawImage() glitches in Qt 5.10

我的应用程序将 3D 渲染绘制到离线 QImage(由于历史原因没有 OpenGL),然后在像这样的小部件中绘制 QImage

void Render3dModel::paintEvent(QPaintEvent *event) {
    // Update model and render new image
    m_model.calculate();
    QImage image = m_model.getImage();

    // Draw image in widget
    QPainter painter(this);
    painter.drawImage(QPointF(0, 0), image);
}

使用 QWidget::update():

根据鼠标移动重新绘制图像
void Render3dModel::mouseMoveEvent(QMouseEvent *event) {
    m_model.rotate(event);
    update();
}

这种方法在 Qt 5.6 和 Qt 5.9 中运行快速且完美。然而,在 Qt 5.10 中重建完全相同的代码会导致奇怪的故障。两个帧的部分同时可见。这不是临时效果(例如延迟绘制),因为如果我停止移动鼠标,它实际上会持续显示两帧的部分。下面是一个示例(为清晰起见,even/odd 帧使用交替的灰色和白色背景):

我尝试在 QImage ringbuffer 中使用 image.copy() 创建深层副本,看看这是否是 destruction/reuse 和 QImage 之间的某个 race-condition 和一些 deferred/delayed 绘画,但这无济于事。

显然 QPainter 的机制在 Qt 5.9 和 Qt 5.10 之间发生了变化,但我无法在文档中找到任何线索。

请指教

更新

玩过几个 window 尺寸后,我得出的结论是,故障以某种方式对应于 upper-left 角与 "S" 对齐的相同尺寸矩形 "Section dimensions"标签。但是,正如您在屏幕截图中看到的那样,白色背景的框架在正确的位置绘制了圆锥体的那一部分。所以图像不是 shifted,而是 clipped 不知何故?!

我试过添加

painter.setClipping(false);
painter.setClipRect(QRect(0, 0, size().width(), size().height()));

但这没有效果。我的 Render3dModel 偶尔会从它的 parents 得到 paintEvent 吗?

更新 2

向 Kuba Ober 下面的最小示例添加 mouseMoveEvent 处理程序,在 MacBook(OS X 10.12.6,Intel HD Graphics 515)上重现该问题:

这可能是 Qt 5.10 中的回归,因为我无法在 Qt 5.9 中重现它吗?我应该提交错误报告吗?

更新 3

这似乎确实是 Qt 5.10 在 OS X 上的回归。已提交错误报告 https://bugreports.qt.io/browse/QTBUG-67998

单线程代码中不能存在竞争条件。没有delayed/deferred绘画之类的东西。到 paintEvent returns 时,该小部件的绘制已完成。

所见即所得。 getImage() 方法可能 returns 垃圾 - 也许是尺寸过小的图像 - 然后你画那个垃圾。将 getImage() 替换为简单的交替填充,您会发现效果很好:

// https://github.com/KubaO/Whosebugn/tree/master/questions/glitchy-paint-49930405
#include <QtWidgets>
#include <array>

class Render3dModel : public QWidget {
   struct Model {
      mutable int counter = {};
      QSize size;
      QFont font{"Helvetica", 48};
      QImage getImage() const {
         static auto const format = QPixmap{1,1}.toImage().format();
         QImage image{size, format};
         image.fill((counter & 1) ? Qt::blue : Qt::yellow);
         QPainter p(&image);
         p.setFont(font);
         p.setPen((counter & 1) ? Qt::yellow : Qt::blue);
         p.drawText(image.rect(), QString::number(counter));
         counter++;
         return image;
      }
   } m_model;
   void paintEvent(QPaintEvent *) override {
      m_model.size = size();
      auto image = m_model.getImage();
      QPainter{this}.drawImage(QPoint{0, 0}, image);
   }
};
int main(int argc, char **argv) {
   QApplication app{argc, argv};
   QWidget win;
   QVBoxLayout topLayout{&win};
   QTabWidget tabs;
   topLayout.addWidget(&tabs);
   // Tabs
   for (auto text : { "Shape", "Dimensions", "Layout"}) tabs.addTab(new QWidget, text);
   tabs.setCurrentIndex(1);
   QHBoxLayout tabLayout{tabs.currentWidget()};
   QGroupBox dims{"Section Dimensions"}, model{"3D Model"};
   QGridLayout dimsLayout{&dims}, modelLayout{&model};
   for (auto w : {&dims, &model}) tabLayout.addWidget(w);
   // Section Dimensions
   for (auto text : {"Diameter 1", "Diameter 2", "Length"}) {
      auto row = dimsLayout.rowCount();
      std::array<QWidget*, 3> widgets{{new QLabel{text}, new QDoubleSpinBox, new QLabel{"inch"}}};
      for (auto *w : widgets)
         dimsLayout.addWidget(w, row, dimsLayout.count() % widgets.size());
   }
   tabLayout.setAlignment(&dims, Qt::AlignLeft | Qt::AlignTop);
   dims.setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
   // Model
   Render3dModel render;
   modelLayout.addWidget(&render);
   model.setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
   model.setMinimumSize(250, 250);
   win.show();
   return app.exec();
}

我测试了@KubaOber 提供的最小示例,按照 OP 的建议添加了 mouseMoveEvent() 处理程序,但我无法在我的平台上重现该问题(Qt 5.10.1 - Linux).

在我看来,这可能是一个特定于平台的问题。 Qt 5.9 和 Qt 5.10 之间的 cocoa 平台插件有很多差异,特别是 QCocoaBackingStore 及其刷新( ) 方法 (changelog here).

如果最小的完整示例很好地显示了问题,我建议将其报告给 Qt 的错误跟踪器 https://bugreports.qt.io

这是你的错误:

void Render3dModel::paintEvent(QPaintEvent *event) {
    // Update model and render new image
    m_model.calculate();
    ...
}

您不应将模型更新与绘画事件联系起来。 paint 事件应该绘制模型的当前状态,并且您可能会获得多个后续 and/or 部分 paint 事件,例如揭示 window.

的部分内容

您在应用程序中看到的是部分公开事件,在本例中是 Qt 错误的结果,但通常您可能会收到部分公开事件并且应该准备好处理它们。