mirror of
https://github.com/bec-project/bec_widgets.git
synced 2026-09-06 16:40:56 +02:00
fix(plots): let use_opengl override the software-renderer default
This commit is contained in:
@@ -107,11 +107,26 @@ def _env_override() -> bool | None:
|
||||
return None
|
||||
|
||||
|
||||
def opengl_available(requested: bool = True) -> bool:
|
||||
@lru_cache(maxsize=4)
|
||||
def _log_software_renderer(renderer: str) -> None:
|
||||
"""Report the software-renderer decision once per process, not per widget."""
|
||||
logger.info(
|
||||
f"OpenGL is software rendered ({renderer}); keeping the raster viewport by default. "
|
||||
f"Set the plot's use_opengl property to True to use it anyway, or {ENV_VAR}=1 "
|
||||
"to opt in for the whole session."
|
||||
)
|
||||
|
||||
|
||||
def opengl_available(requested: bool = True, explicit: bool = False) -> bool:
|
||||
"""Decide whether a widget that asked for OpenGL should actually get it.
|
||||
|
||||
Args:
|
||||
requested(bool): Whether the widget wants the OpenGL viewport at all.
|
||||
explicit(bool): True when a caller deliberately asked -- a `use_opengl`
|
||||
property set or an RPC call -- rather than this being the construction
|
||||
default. A deliberate request overrides the software-renderer default,
|
||||
since the caller has said what they want; it still cannot conjure a
|
||||
context that does not exist, and `BEC_WIDGETS_OPENGL=0` still wins.
|
||||
|
||||
Returns:
|
||||
bool: True if the OpenGL viewport should be installed.
|
||||
@@ -123,13 +138,10 @@ def opengl_available(requested: bool = True) -> bool:
|
||||
return False
|
||||
if opengl_info() is None:
|
||||
return False
|
||||
if override is True:
|
||||
if override is True or explicit:
|
||||
return True
|
||||
if is_software_renderer():
|
||||
logger.info(
|
||||
"OpenGL is available but software rendered; keeping the raster viewport. "
|
||||
f"Set {ENV_VAR}=1 to override."
|
||||
)
|
||||
_log_software_renderer(opengl_info()["renderer"])
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
@@ -166,7 +166,7 @@ class PlotBase(BECWidget, QWidget):
|
||||
self.plot_widget = pg.GraphicsLayoutWidget(parent=self)
|
||||
# GraphicsLayoutWidget forwards no viewport argument to GraphicsView, so the
|
||||
# viewport is swapped after construction instead.
|
||||
self.use_opengl = self.USE_OPENGL
|
||||
self._set_use_opengl(self.USE_OPENGL, explicit=False)
|
||||
self.plot_widget.ci.setContentsMargins(0, 0, 0, 0)
|
||||
self.plot_item = pg.PlotItem(viewBox=BECViewBox(enableMenu=True))
|
||||
self.plot_widget.addItem(self.plot_item)
|
||||
@@ -322,8 +322,12 @@ class PlotBase(BECWidget, QWidget):
|
||||
Whether the plot currently renders through an OpenGL viewport.
|
||||
|
||||
Reflects the live viewport rather than the requested value: setting this to
|
||||
True is best effort, and stays False when no hardware-accelerated context is
|
||||
available (see `bec_widgets.utils.gpu_acceleration.opengl_available`).
|
||||
True is best effort and stays False when no OpenGL context can be created
|
||||
at all (see `bec_widgets.utils.gpu_acceleration.opengl_available`).
|
||||
|
||||
Setting it is an explicit request, so it overrides the software-renderer
|
||||
default that keeps freshly built plots on the raster viewport. That is what
|
||||
makes it possible to compare the two on a remote console.
|
||||
"""
|
||||
return isinstance(self.plot_widget.viewport(), QOpenGLWidget)
|
||||
|
||||
@@ -335,8 +339,19 @@ class PlotBase(BECWidget, QWidget):
|
||||
Args:
|
||||
value(bool): Whether the OpenGL viewport is wanted.
|
||||
"""
|
||||
if value and not opengl_available(True):
|
||||
if self.use_opengl:
|
||||
self._set_use_opengl(value, explicit=True)
|
||||
|
||||
def _set_use_opengl(self, value: bool, explicit: bool) -> None:
|
||||
"""
|
||||
Apply an OpenGL viewport request.
|
||||
|
||||
Args:
|
||||
value(bool): Whether the OpenGL viewport is wanted.
|
||||
explicit(bool): Whether this came from a deliberate request rather than
|
||||
the construction default. See `opengl_available`.
|
||||
"""
|
||||
if value and not opengl_available(True, explicit=explicit):
|
||||
if isinstance(self.plot_widget.viewport(), QOpenGLWidget):
|
||||
set_view_opengl(self.plot_widget, False)
|
||||
return
|
||||
set_view_opengl(self.plot_widget, value)
|
||||
|
||||
@@ -19,12 +19,15 @@ def _reset_opengl_probe(monkeypatch):
|
||||
# hold on to the real cached function: monkeypatch may swap the module
|
||||
# attribute for a stub, and it is only restored after this fixture resumes
|
||||
probe = gpu_acceleration.opengl_info
|
||||
notice = gpu_acceleration._log_software_renderer
|
||||
probe.cache_clear()
|
||||
notice.cache_clear()
|
||||
monkeypatch.delenv(ENV_VAR, raising=False)
|
||||
previous = pg.getConfigOption("useOpenGL")
|
||||
yield
|
||||
pg.setConfigOption("useOpenGL", previous)
|
||||
probe.cache_clear()
|
||||
notice.cache_clear()
|
||||
|
||||
|
||||
def _fake_renderer(monkeypatch, renderer: str | None):
|
||||
@@ -50,6 +53,33 @@ def test_opengl_refused_on_software_renderer(monkeypatch, renderer):
|
||||
assert opengl_available(requested=True) is False
|
||||
|
||||
|
||||
def test_explicit_request_overrides_software_renderer(monkeypatch):
|
||||
"""The use_opengl property must be able to force OpenGL on a remote console."""
|
||||
_fake_renderer(monkeypatch, "llvmpipe (LLVM 15.0.7, 256 bits)")
|
||||
assert opengl_available(requested=True) is False
|
||||
assert opengl_available(requested=True, explicit=True) is True
|
||||
|
||||
|
||||
def test_explicit_request_cannot_invent_a_context(monkeypatch):
|
||||
_fake_renderer(monkeypatch, None)
|
||||
assert opengl_available(requested=True, explicit=True) is False
|
||||
|
||||
|
||||
def test_env_var_off_beats_an_explicit_request(monkeypatch):
|
||||
_fake_renderer(monkeypatch, "NVIDIA GeForce RTX 3090")
|
||||
monkeypatch.setenv(ENV_VAR, "0")
|
||||
assert opengl_available(requested=True, explicit=True) is False
|
||||
|
||||
|
||||
def test_software_renderer_notice_is_logged_once(monkeypatch, caplog):
|
||||
"""opengl_available runs per widget; the notice must not repeat per plot."""
|
||||
_fake_renderer(monkeypatch, "llvmpipe (LLVM 15.0.7, 256 bits)")
|
||||
with caplog.at_level("INFO"):
|
||||
for _ in range(5):
|
||||
opengl_available(requested=True)
|
||||
assert sum("software rendered" in r.message for r in caplog.records) <= 1
|
||||
|
||||
|
||||
def test_env_var_forces_opengl_on_software_renderer(monkeypatch):
|
||||
_fake_renderer(monkeypatch, "llvmpipe (LLVM 15.0.7, 256 bits)")
|
||||
monkeypatch.setenv(ENV_VAR, "1")
|
||||
|
||||
@@ -569,8 +569,37 @@ def test_use_opengl_can_be_toggled_at_runtime(qtbot, mocked_client):
|
||||
def test_use_opengl_declines_without_hardware_context(qtbot, mocked_client, monkeypatch):
|
||||
"""Requesting OpenGL on a software renderer leaves the raster viewport in place."""
|
||||
pb = create_widget(qtbot, PlotBase, client=mocked_client)
|
||||
monkeypatch.setattr(plot_base_module, "opengl_available", lambda requested=True: False)
|
||||
monkeypatch.setattr(
|
||||
plot_base_module, "opengl_available", lambda requested=True, explicit=False: False
|
||||
)
|
||||
|
||||
pb.use_opengl = True
|
||||
assert pb.use_opengl is False
|
||||
assert not isinstance(pb.plot_widget.viewport(), QOpenGLWidget)
|
||||
|
||||
|
||||
def test_software_renderer_defaults_to_raster_but_property_can_force(qtbot, mocked_client):
|
||||
"""On a remote console the default is raster, yet use_opengl must still work."""
|
||||
from bec_widgets.utils import gpu_acceleration
|
||||
|
||||
real = gpu_acceleration.opengl_available
|
||||
|
||||
def fake(requested=True, explicit=False):
|
||||
# pretend we are on llvmpipe: refused by default, honoured when explicit
|
||||
return bool(requested and explicit)
|
||||
|
||||
monkeypatch = pytest.MonkeyPatch()
|
||||
monkeypatch.setattr(plot_base_module, "opengl_available", fake)
|
||||
try:
|
||||
pb = create_widget(qtbot, PlotBase, client=mocked_client)
|
||||
# construction takes the software-renderer default
|
||||
assert pb.use_opengl is False
|
||||
# an explicit request overrides it
|
||||
pb.use_opengl = True
|
||||
assert pb.use_opengl is True
|
||||
assert isinstance(pb.plot_widget.viewport(), QOpenGLWidget)
|
||||
pb.use_opengl = False
|
||||
assert pb.use_opengl is False
|
||||
finally:
|
||||
monkeypatch.undo()
|
||||
assert gpu_acceleration.opengl_available is real
|
||||
|
||||
Reference in New Issue
Block a user