Skip to content

Commit e39c1df

Browse files
authored
test: skip CUDA-GL interop tests when the GL context is not on an NVIDIA GPU (#2986)
* add reason getter why gl context is not on nvidia GPU * add new test to unit-test non-NVIDIA GL driver vendor
1 parent 5e3a668 commit e39c1df

4 files changed

Lines changed: 68 additions & 2 deletions

File tree

‎cuda_bindings/tests/test_graphics_apis.py‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@
99

1010
import pyglet
1111
import pytest
12-
from cuda_python_test_helpers.graphics import is_gl_context_unavailable, open_gl_window
12+
from cuda_python_test_helpers.graphics import (
13+
gl_context_not_on_nvidia_gpu_reason,
14+
is_gl_context_unavailable,
15+
open_gl_window,
16+
)
1317

1418
from cuda.bindings import runtime as cudart
1519

@@ -63,6 +67,8 @@ def _gl_context():
6367

6468
tex_id = None
6569
try:
70+
if reason := gl_context_not_on_nvidia_gpu_reason():
71+
pytest.skip(reason)
6672
tex_id, target = _allocate_gl_texture(win)
6773
yield int(tex_id.value), int(target)
6874
finally:

‎cuda_core/tests/test_graphics.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import pyglet
1717
import pytest
1818
from cuda_python_test_helpers.graphics import (
19+
gl_context_not_on_nvidia_gpu_reason,
1920
is_gl_context_unavailable,
2021
open_gl_window,
2122
select_headless_egl_device_for_cuda,
@@ -167,6 +168,8 @@ def _gl_context_and_buffer(nbytes=1024):
167168

168169
buf_id = None
169170
try:
171+
if reason := gl_context_not_on_nvidia_gpu_reason():
172+
pytest.skip(reason)
170173
buf_id = _allocate_gl_buffer(win, nbytes)
171174
yield int(buf_id.value), nbytes
172175
finally:
@@ -195,6 +198,8 @@ def _gl_context_and_texture(width=16, height=16):
195198

196199
tex_id = None
197200
try:
201+
if reason := gl_context_not_on_nvidia_gpu_reason():
202+
pytest.skip(reason)
198203
tex_id, target = _allocate_gl_texture(win, width, height)
199204
yield int(tex_id.value), int(target)
200205
finally:

‎cuda_core/tests/test_helpers.py‎

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#
33
# SPDX-License-Identifier: Apache-2.0
44

5+
import ctypes
56
import sys
67
import time
78
import types
@@ -360,7 +361,11 @@ def test_oom_diagnostics_probe_basics_is_live_and_cheap(init_cuda):
360361
# ---------------------------------------------------------------------------
361362

362363
import pytest
363-
from cuda_python_test_helpers.graphics import is_gl_context_unavailable, open_gl_window
364+
from cuda_python_test_helpers.graphics import (
365+
gl_context_not_on_nvidia_gpu_reason,
366+
is_gl_context_unavailable,
367+
open_gl_window,
368+
)
364369

365370

366371
@pytest.mark.thread_unsafe(reason="patches the process-wide pyglet module")
@@ -437,3 +442,28 @@ def test_is_gl_context_unavailable_accepts_genuine(exc):
437442
)
438443
def test_is_gl_context_unavailable_rejects_unrelated(exc):
439444
assert is_gl_context_unavailable(exc) is False
445+
446+
447+
def _patch_gl_vendor(monkeypatch, vendor):
448+
# Only GL_VENDOR is answered; any other query (e.g. GL_RENDERER) returns NULL.
449+
gl = types.SimpleNamespace(
450+
GL_VENDOR=1,
451+
GL_RENDERER=2,
452+
glGetString=lambda name: ctypes.c_char_p(vendor if name == 1 else None),
453+
)
454+
monkeypatch.setitem(sys.modules, "pyglet.gl", types.SimpleNamespace(gl=gl))
455+
456+
457+
@pytest.mark.thread_unsafe(reason="patches the process-wide pyglet.gl module")
458+
def test_gl_context_not_on_nvidia_gpu_reason_accepts_nvidia(monkeypatch):
459+
_patch_gl_vendor(monkeypatch, b"NVIDIA Corporation")
460+
assert gl_context_not_on_nvidia_gpu_reason() is None
461+
462+
463+
@pytest.mark.thread_unsafe(reason="patches the process-wide pyglet.gl module")
464+
@pytest.mark.parametrize("vendor", [b"AMD", b"Mesa", None])
465+
def test_gl_context_not_on_nvidia_gpu_reason_rejects_other_vendors(monkeypatch, vendor):
466+
_patch_gl_vendor(monkeypatch, vendor)
467+
reason = gl_context_not_on_nvidia_gpu_reason()
468+
assert reason is not None
469+
assert "not on an NVIDIA GPU" in reason

‎cuda_python_test_helpers/cuda_python_test_helpers/graphics.py‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,31 @@ def open_gl_window():
128128
return None
129129

130130

131+
def gl_context_not_on_nvidia_gpu_reason() -> str | None:
132+
"""Return a skip reason if the current GL context is not on an NVIDIA GPU, else None.
133+
134+
CUDA-GL interop requires the GL context to be rendered by an NVIDIA GPU.
135+
When the context is created on a GPU from another vendor (e.g. on
136+
multi-vendor GPU systems), `cuGraphicsGLRegister*` fails with
137+
`CUDA_ERROR_UNKNOWN`, which is indistinguishable from a real failure at
138+
the call site. Caller must have a current GL context.
139+
"""
140+
from pyglet.gl import gl
141+
142+
def _get_string(name):
143+
value = gl.glGetString(name)
144+
return ctypes.cast(value, ctypes.c_char_p).value.decode(errors="replace") if value else ""
145+
146+
vendor = _get_string(gl.GL_VENDOR)
147+
if "NVIDIA" in vendor:
148+
return None
149+
renderer = _get_string(gl.GL_RENDERER)
150+
return (
151+
f"GL context is not on an NVIDIA GPU (GL_VENDOR={vendor!r}, GL_RENDERER={renderer!r}); "
152+
"CUDA-GL interop requires one."
153+
)
154+
155+
131156
_GL_CONTEXT_UNAVAILABLE_EXC_NAMES = frozenset(
132157
{
133158
"NoSuchDisplayException",

0 commit comments

Comments
 (0)