Files
bec_widgets/gpu_bench.py
T

108 lines
3.7 KiB
Python

"""Benchmark the pyqtgraph 0.14 OpenGL viewport against the raster path.
Measures *render* cost only: the curve data is generated up front and cycled
frame to frame, so numpy work does not pollute the timing. vsync is disabled
via the swap interval so the OpenGL path is not pinned to the display refresh.
python gpu_bench.py [n_points] [n_curves]
"""
import sys
import time
import numpy as np
import pyqtgraph as pg
from qtpy import QtCore, QtGui, QtWidgets
N_POINTS = int(sys.argv[1]) if len(sys.argv) > 1 else 100_000
N_CURVES = int(sys.argv[2]) if len(sys.argv) > 2 else 3
FRAMES = 60
N_PRESET = 10
def _bench(use_opengl: bool, label: str, datasets, x) -> float | None:
"""Time FRAMES repaints, returning fps -- or None if the frames never rendered.
An unexposed or occluded window turns `repaint()` into a no-op for the raster
viewport while the OpenGL viewport still renders into its own framebuffer.
That yields wildly optimistic raster numbers, so every curve paint is counted
and the result is discarded unless each curve painted once per frame.
"""
pg.setConfigOption("useOpenGL", use_opengl)
pg.setConfigOption("enableExperimental", use_opengl)
win = pg.GraphicsLayoutWidget()
win.resize(1200, 800)
plot = win.addPlot()
curves = []
for c in range(N_CURVES):
curve = pg.PlotDataItem(pen=pg.mkPen(pg.intColor(c), width=1))
plot.addItem(curve)
curves.append(curve)
plot.enableAutoRange(False)
plot.setXRange(0, N_POINTS)
plot.setYRange(-2, 2)
win.show()
win.raise_()
win.activateWindow()
deadline = time.perf_counter() + 1.0
while time.perf_counter() < deadline:
QtWidgets.QApplication.processEvents()
paints = 0
original_paint = pg.PlotCurveItem.paint
def counting_paint(self, *args, **kwargs):
nonlocal paints
paints += 1
return original_paint(self, *args, **kwargs)
pg.PlotCurveItem.paint = counting_paint
viewport = win.viewport().__class__.__name__
try:
start = time.perf_counter()
for i in range(FRAMES):
for c, curve in enumerate(curves):
curve.setData(x, datasets[(i + c) % N_PRESET])
win.viewport().repaint()
QtWidgets.QApplication.processEvents()
elapsed = time.perf_counter() - start
finally:
pg.PlotCurveItem.paint = original_paint
win.close()
expected = FRAMES * N_CURVES
if paints < expected:
print(
f" {label:<26} viewport={viewport:<22} INVALID "
f"({paints}/{expected} curve paints -- window not rendering)"
)
return None
fps = FRAMES / elapsed
print(f" {label:<26} viewport={viewport:<22} {fps:7.1f} fps ({1000 / fps:6.1f} ms/frame)")
return fps
if __name__ == "__main__":
# disable vsync before the QApplication so QOpenGLWidget is not capped at ~60 Hz
fmt = QtGui.QSurfaceFormat.defaultFormat()
fmt.setSwapInterval(0)
QtGui.QSurfaceFormat.setDefaultFormat(fmt)
app = QtWidgets.QApplication(sys.argv)
print(f"pyqtgraph {pg.__version__} | Qt {QtCore.qVersion()} | swapInterval=0")
print(f"{N_CURVES} curves x {N_POINTS:,} points, {FRAMES} frames (data pre-generated)\n")
x = np.arange(N_POINTS, dtype=np.float64)
rng = np.random.default_rng(0)
datasets = [np.sin(x * 0.001 + k) + rng.normal(0, 0.1, N_POINTS) for k in range(N_PRESET)]
raster = _bench(False, "raster (QPainter)", datasets, x)
opengl = _bench(True, "opengl (QOpenGLWidget)", datasets, x)
if raster is None or opengl is None:
print(" -> no speedup reported: at least one run did not actually render")
sys.exit(1)
print(f" -> speedup {opengl / raster:.2f}x")