Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion cuda_bindings/tests/test_graphics_apis.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@

import pyglet
import pytest
from cuda_python_test_helpers.graphics import is_gl_context_unavailable, open_gl_window
from cuda_python_test_helpers.graphics import (
gl_context_not_on_nvidia_gpu_reason,
is_gl_context_unavailable,
open_gl_window,
)

from cuda.bindings import runtime as cudart

Expand Down Expand Up @@ -63,6 +67,8 @@ def _gl_context():

tex_id = None
try:
if reason := gl_context_not_on_nvidia_gpu_reason():
pytest.skip(reason)
tex_id, target = _allocate_gl_texture(win)
yield int(tex_id.value), int(target)
finally:
Expand Down
5 changes: 5 additions & 0 deletions cuda_core/tests/test_graphics.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import pyglet
import pytest
from cuda_python_test_helpers.graphics import (
gl_context_not_on_nvidia_gpu_reason,
is_gl_context_unavailable,
open_gl_window,
select_headless_egl_device_for_cuda,
Expand Down Expand Up @@ -130,6 +131,8 @@ def _gl_context_and_buffer(nbytes=1024):

buf_id = None
try:
if reason := gl_context_not_on_nvidia_gpu_reason():
pytest.skip(reason)
buf_id = _allocate_gl_buffer(win, nbytes)
yield int(buf_id.value), nbytes
finally:
Expand Down Expand Up @@ -158,6 +161,8 @@ def _gl_context_and_texture(width=16, height=16):

tex_id = None
try:
if reason := gl_context_not_on_nvidia_gpu_reason():
pytest.skip(reason)
tex_id, target = _allocate_gl_texture(win, width, height)
yield int(tex_id.value), int(target)
finally:
Expand Down
32 changes: 31 additions & 1 deletion cuda_core/tests/test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#
# SPDX-License-Identifier: Apache-2.0

import ctypes
import sys
import time
import types
Expand Down Expand Up @@ -360,7 +361,11 @@ def test_oom_diagnostics_probe_basics_is_live_and_cheap(init_cuda):
# ---------------------------------------------------------------------------

import pytest
from cuda_python_test_helpers.graphics import is_gl_context_unavailable, open_gl_window
from cuda_python_test_helpers.graphics import (
gl_context_not_on_nvidia_gpu_reason,
is_gl_context_unavailable,
open_gl_window,
)


@pytest.mark.thread_unsafe(reason="patches the process-wide pyglet module")
Expand Down Expand Up @@ -437,3 +442,28 @@ def test_is_gl_context_unavailable_accepts_genuine(exc):
)
def test_is_gl_context_unavailable_rejects_unrelated(exc):
assert is_gl_context_unavailable(exc) is False


def _patch_gl_vendor(monkeypatch, vendor):
# Only GL_VENDOR is answered; any other query (e.g. GL_RENDERER) returns NULL.
gl = types.SimpleNamespace(
GL_VENDOR=1,
GL_RENDERER=2,
glGetString=lambda name: ctypes.c_char_p(vendor if name == 1 else None),
)
monkeypatch.setitem(sys.modules, "pyglet.gl", types.SimpleNamespace(gl=gl))


@pytest.mark.thread_unsafe(reason="patches the process-wide pyglet.gl module")
def test_gl_context_not_on_nvidia_gpu_reason_accepts_nvidia(monkeypatch):
_patch_gl_vendor(monkeypatch, b"NVIDIA Corporation")
assert gl_context_not_on_nvidia_gpu_reason() is None


@pytest.mark.thread_unsafe(reason="patches the process-wide pyglet.gl module")
@pytest.mark.parametrize("vendor", [b"AMD", b"Mesa", None])
def test_gl_context_not_on_nvidia_gpu_reason_rejects_other_vendors(monkeypatch, vendor):
_patch_gl_vendor(monkeypatch, vendor)
reason = gl_context_not_on_nvidia_gpu_reason()
assert reason is not None
assert "not on an NVIDIA GPU" in reason
25 changes: 25 additions & 0 deletions cuda_python_test_helpers/cuda_python_test_helpers/graphics.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,31 @@ def open_gl_window():
return None


def gl_context_not_on_nvidia_gpu_reason() -> str | None:
"""Return a skip reason if the current GL context is not on an NVIDIA GPU, else None.

CUDA-GL interop requires the GL context to be rendered by an NVIDIA GPU.
When the context is created on a GPU from another vendor (e.g. on
multi-vendor GPU systems), `cuGraphicsGLRegister*` fails with
`CUDA_ERROR_UNKNOWN`, which is indistinguishable from a real failure at
the call site. Caller must have a current GL context.
"""
from pyglet.gl import gl

def _get_string(name):
value = gl.glGetString(name)
return ctypes.cast(value, ctypes.c_char_p).value.decode(errors="replace") if value else ""

vendor = _get_string(gl.GL_VENDOR)
if "NVIDIA" in vendor:
return None
renderer = _get_string(gl.GL_RENDERER)
return (
f"GL context is not on an NVIDIA GPU (GL_VENDOR={vendor!r}, GL_RENDERER={renderer!r}); "
"CUDA-GL interop requires one."
)


_GL_CONTEXT_UNAVAILABLE_EXC_NAMES = frozenset(
{
"NoSuchDisplayException",
Expand Down
Loading