mirror of
https://github.com/bec-project/bec_widgets.git
synced 2026-09-06 16:40:56 +02:00
108 lines
3.7 KiB
Python
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")
|