Skip to content

Commit e28ce39

Browse files
authored
Add notes on performance for Qt bitmap present (#139)
1 parent e80eadc commit e28ce39

File tree

1 file changed

+38
-25
lines changed

1 file changed

+38
-25
lines changed

rendercanvas/qt.py

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
QtCore = importlib.import_module(".QtCore", libname)
2727
QtGui = importlib.import_module(".QtGui", libname)
2828
QtWidgets = importlib.import_module(".QtWidgets", libname)
29+
# Uncomment the line below to try QtOpenGLWidgets.QOpenGLWidget instead of QWidget
30+
# QtOpenGLWidgets = importlib.import_module(".QtOpenGLWidgets", libname)
2931
try:
3032
# pyqt6
3133
WA_PaintOnScreen = QtCore.Qt.WidgetAttribute.WA_PaintOnScreen
@@ -374,39 +376,50 @@ def _rc_force_draw(self):
374376
self.repaint()
375377

376378
def _rc_present_bitmap(self, *, data, format, **kwargs):
377-
width, height = data.shape[1], data.shape[0] # width, height
378-
rect1 = QtCore.QRect(0, 0, width, height)
379-
rect2 = self.rect()
379+
# Notes on performance:
380+
#
381+
# In the early stage of https://github.com/pygfx/rendercanvas/pull/138,
382+
# with a single copy-buffer, running the cube example on my M1, with
383+
# bitmap-present, I get about 75 FPS.
384+
#
385+
# AK: I tried to make this a QLabel and update a QPixmap by doing
386+
# self._pixmap.convertFromImage(qImage), but this is much slower.
387+
#
388+
# AK: I tried to maintain a self._qimage, so that it maybe gets bound
389+
# internally to a texture, but that even makes it slightly slower.
390+
#
391+
# AK: I tried inheriting from QOpenGLWidget, because I saw a blog post
392+
# (https://doc.qt.io/archives/qt-5.15/qtopengl-2dpainting-example.html)
393+
# that says it will make the painter hardware accelerated.
394+
# Interestingly, the content is drawn different to screen, as if the
395+
# rect args to drawImage are interpreted differently (or wrong), which
396+
# suggests that the painter *does* take a different path. Also, it can
397+
# be observed that the CPU usage is less that with QWidget. However, the
398+
# performance does not significantly increase (in my tests)
399+
#
400+
# If I understand things correctly, Qt uses composition on the CPU, so
401+
# there is an inherent limit to the performance. Rendering with GL likely
402+
# includes downloading the rendered image for composition.
403+
#
404+
# Also see https://github.com/pygfx/rendercanvas/pull/139
380405

381-
painter = QtGui.QPainter(self)
382-
# backingstore = self.backingStore()
383-
# backingstore.beginPaint(rect2)
384-
# painter = QtGui.QPainter(backingstore.paintDevice())
385-
386-
# We want to simply blit the image (copy pixels one-to-one on framebuffer).
387-
# Maybe Qt does this when the sizes match exactly (like they do here).
388-
# Converting to a QPixmap and painting that only makes it slower.
389-
390-
# Just in case, set render hints that may hurt performance.
391-
painter.setRenderHints(
392-
painter.RenderHint.Antialiasing | painter.RenderHint.SmoothPixmapTransform,
393-
False,
394-
)
406+
width, height = data.shape[1], data.shape[0] # width, height
395407

408+
# Wrap the data in a QImage (no copy)
396409
qtformat = BITMAP_FORMAT_MAP[format]
397410
bytes_per_line = data.strides[0]
398411
image = QtGui.QImage(data, width, height, bytes_per_line, qtformat)
399412

400-
painter.drawImage(rect2, image, rect1)
401-
402-
# Uncomment for testing purposes
403-
# painter.setPen(QtGui.QColor("#0000ff"))
404-
# painter.setFont(QtGui.QFont("Arial", 30))
405-
# painter.drawText(100, 100, "This is an image")
413+
# Prep drawImage rects
414+
rect1 = QtCore.QRect(0, 0, width, height)
415+
rect2 = self.rect()
406416

417+
# Paint the image. Nearest neighbor interpolation, like the other backends.
418+
painter = QtGui.QPainter(self)
419+
painter.setRenderHints(painter.RenderHint.Antialiasing, False)
420+
painter.setRenderHints(painter.RenderHint.SmoothPixmapTransform, False)
421+
painter.drawImage(rect2, image, rect1)
407422
painter.end()
408-
# backingstore.endPaint()
409-
# backingstore.flush(rect2)
410423

411424
def _rc_set_logical_size(self, width, height):
412425
width, height = int(width), int(height)

0 commit comments

Comments
 (0)