From 4e9e67eb51315c192310621d4a7c63c73f875e96 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Fri, 10 Oct 2025 10:43:04 -0400 Subject: [PATCH 1/7] Fix #1122: Reinstate graphics APIs --- .../bindings/_lib/cyruntime/cyruntime.pxi | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/cuda_bindings/cuda/bindings/_lib/cyruntime/cyruntime.pxi b/cuda_bindings/cuda/bindings/_lib/cyruntime/cyruntime.pxi index 7d5960ced1..c18bd1ca2e 100644 --- a/cuda_bindings/cuda/bindings/_lib/cyruntime/cyruntime.pxi +++ b/cuda_bindings/cuda/bindings/_lib/cyruntime/cyruntime.pxi @@ -13,7 +13,7 @@ # c.b._lib.cyruntime.utils), but was merged into one. from libc.string cimport memset -cimport cuda.bindings.cydriver as cydriver +cimport cuda.bindings._bindings.cydriver as cydriver cdef cudaError_t _cudaEGLStreamProducerPresentFrame(cyruntime.cudaEglStreamConnection* conn, cyruntime.cudaEglFrame eglframe, cudaStream_t* pStream) except ?cudaErrorCallRequiresNewerDriver nogil: cdef cudaError_t err = cudaSuccess @@ -25,7 +25,7 @@ cdef cudaError_t _cudaEGLStreamProducerPresentFrame(cyruntime.cudaEglStreamConne err = getDriverEglFrame(&cueglFrame, eglframe) if err != cudaSuccess: return err - # err = cydriver._cuEGLStreamProducerPresentFrame(conn, cueglFrame, pStream) + err = cydriver._cuEGLStreamProducerPresentFrame(conn, cueglFrame, pStream) return err cdef cudaError_t _cudaEGLStreamProducerReturnFrame(cyruntime.cudaEglStreamConnection* conn, cyruntime.cudaEglFrame* eglframe, cudaStream_t* pStream) except ?cudaErrorCallRequiresNewerDriver nogil: @@ -38,7 +38,7 @@ cdef cudaError_t _cudaEGLStreamProducerReturnFrame(cyruntime.cudaEglStreamConnec err = cudaErrorInvalidResourceHandle return err cdef cydriver.CUeglFrame cueglFrame - # err = cydriver._cuEGLStreamProducerReturnFrame(conn, &cueglFrame, pStream) + err = cydriver._cuEGLStreamProducerReturnFrame(conn, &cueglFrame, pStream) if err != cudaSuccess: return err err = getRuntimeEglFrame(eglframe, cueglFrame) @@ -52,7 +52,7 @@ cdef cudaError_t _cudaGraphicsResourceGetMappedEglFrame(cyruntime.cudaEglFrame* return err cdef cydriver.CUeglFrame cueglFrame memset(&cueglFrame, 0, sizeof(cueglFrame)) - # err = cydriver._cuGraphicsResourceGetMappedEglFrame(&cueglFrame, resource, index, mipLevel) + err = cydriver._cuGraphicsResourceGetMappedEglFrame(&cueglFrame, resource, index, mipLevel) if err != cudaSuccess: return err err = getRuntimeEglFrame(eglFrame, cueglFrame) @@ -67,7 +67,7 @@ cdef cudaError_t _cudaVDPAUGetDevice(int* device, cyruntime.VdpDevice vdpDevice, err = cudaFree(0) if err != cudaSuccess: return err - # err = cydriver._cuVDPAUGetDevice(device, vdpDevice, vdpGetProcAddress) + err = cydriver._cuVDPAUGetDevice(device, vdpDevice, vdpGetProcAddress) return err cdef cudaError_t _cudaGraphicsVDPAURegisterVideoSurface(cudaGraphicsResource** resource, cyruntime.VdpVideoSurface vdpSurface, unsigned int flags) except ?cudaErrorCallRequiresNewerDriver nogil: @@ -76,7 +76,7 @@ cdef cudaError_t _cudaGraphicsVDPAURegisterVideoSurface(cudaGraphicsResource** r err = cudaFree(0) if err != cudaSuccess: return err - # err = cydriver._cuGraphicsVDPAURegisterVideoSurface(resource, vdpSurface, flags) + err = cydriver._cuGraphicsVDPAURegisterVideoSurface(resource, vdpSurface, flags) return err cdef cudaError_t _cudaGraphicsVDPAURegisterOutputSurface(cudaGraphicsResource** resource, cyruntime.VdpOutputSurface vdpSurface, unsigned int flags) except ?cudaErrorCallRequiresNewerDriver nogil: @@ -85,7 +85,7 @@ cdef cudaError_t _cudaGraphicsVDPAURegisterOutputSurface(cudaGraphicsResource** err = cudaFree(0) if err != cudaSuccess: return err - # err = cydriver._cuGraphicsVDPAURegisterOutputSurface(resource, vdpSurface, flags) + err = cydriver._cuGraphicsVDPAURegisterOutputSurface(resource, vdpSurface, flags) return err cdef cudaError_t _cudaGLGetDevices(unsigned int* pCudaDeviceCount, int* pCudaDevices, unsigned int cudaDeviceCount, cyruntime.cudaGLDeviceList deviceList) except ?cudaErrorCallRequiresNewerDriver nogil: @@ -94,7 +94,7 @@ cdef cudaError_t _cudaGLGetDevices(unsigned int* pCudaDeviceCount, int* pCudaDev err = cudaFree(0) if err != cudaSuccess: return err - # err = cydriver._cuGLGetDevices_v2(pCudaDeviceCount, pCudaDevices, cudaDeviceCount, deviceList) + err = cydriver._cuGLGetDevices_v2(pCudaDeviceCount, pCudaDevices, cudaDeviceCount, deviceList) return err cdef cudaError_t _cudaGraphicsGLRegisterImage(cudaGraphicsResource** resource, cyruntime.GLuint image, cyruntime.GLenum target, unsigned int flags) except ?cudaErrorCallRequiresNewerDriver nogil: @@ -103,7 +103,7 @@ cdef cudaError_t _cudaGraphicsGLRegisterImage(cudaGraphicsResource** resource, c err = cudaFree(0) if err != cudaSuccess: return err - # err = cydriver._cuGraphicsGLRegisterImage(resource, image, target, flags) + err = cydriver._cuGraphicsGLRegisterImage(resource, image, target, flags) return err cdef cudaError_t _cudaGraphicsGLRegisterBuffer(cudaGraphicsResource** resource, cyruntime.GLuint buffer, unsigned int flags) except ?cudaErrorCallRequiresNewerDriver nogil: @@ -112,7 +112,7 @@ cdef cudaError_t _cudaGraphicsGLRegisterBuffer(cudaGraphicsResource** resource, err = cudaFree(0) if err != cudaSuccess: return err - # err = cydriver._cuGraphicsGLRegisterBuffer(resource, buffer, flags) + err = cydriver._cuGraphicsGLRegisterBuffer(resource, buffer, flags) return err cdef cudaError_t _cudaGraphicsEGLRegisterImage(cudaGraphicsResource_t* pCudaResource, cyruntime.EGLImageKHR image, unsigned int flags) except ?cudaErrorCallRequiresNewerDriver nogil: @@ -121,7 +121,7 @@ cdef cudaError_t _cudaGraphicsEGLRegisterImage(cudaGraphicsResource_t* pCudaReso err = cudaFree(0) if err != cudaSuccess: return err - # err = cydriver._cuGraphicsEGLRegisterImage(pCudaResource, image, flags) + err = cydriver._cuGraphicsEGLRegisterImage(pCudaResource, image, flags) return err cdef cudaError_t _cudaEGLStreamConsumerConnect(cyruntime.cudaEglStreamConnection* conn, cyruntime.EGLStreamKHR eglStream) except ?cudaErrorCallRequiresNewerDriver nogil: @@ -130,7 +130,7 @@ cdef cudaError_t _cudaEGLStreamConsumerConnect(cyruntime.cudaEglStreamConnection err = cudaFree(0) if err != cudaSuccess: return err - # err = cydriver._cuEGLStreamConsumerConnect(conn, eglStream) + err = cydriver._cuEGLStreamConsumerConnect(conn, eglStream) return err cdef cudaError_t _cudaEGLStreamConsumerConnectWithFlags(cyruntime.cudaEglStreamConnection* conn, cyruntime.EGLStreamKHR eglStream, unsigned int flags) except ?cudaErrorCallRequiresNewerDriver nogil: @@ -139,7 +139,7 @@ cdef cudaError_t _cudaEGLStreamConsumerConnectWithFlags(cyruntime.cudaEglStreamC err = cudaFree(0) if err != cudaSuccess: return err - # err = cydriver._cuEGLStreamConsumerConnectWithFlags(conn, eglStream, flags) + err = cydriver._cuEGLStreamConsumerConnectWithFlags(conn, eglStream, flags) return err cdef cudaError_t _cudaEGLStreamConsumerDisconnect(cyruntime.cudaEglStreamConnection* conn) except ?cudaErrorCallRequiresNewerDriver nogil: @@ -148,7 +148,7 @@ cdef cudaError_t _cudaEGLStreamConsumerDisconnect(cyruntime.cudaEglStreamConnect err = cudaFree(0) if err != cudaSuccess: return err - # err = cydriver._cuEGLStreamConsumerDisconnect(conn) + err = cydriver._cuEGLStreamConsumerDisconnect(conn) return err cdef cudaError_t _cudaEGLStreamConsumerAcquireFrame(cyruntime.cudaEglStreamConnection* conn, cudaGraphicsResource_t* pCudaResource, cudaStream_t* pStream, unsigned int timeout) except ?cudaErrorCallRequiresNewerDriver nogil: @@ -157,7 +157,7 @@ cdef cudaError_t _cudaEGLStreamConsumerAcquireFrame(cyruntime.cudaEglStreamConne err = cudaFree(0) if err != cudaSuccess: return err - # err = cydriver._cuEGLStreamConsumerAcquireFrame(conn, pCudaResource, pStream, timeout) + err = cydriver._cuEGLStreamConsumerAcquireFrame(conn, pCudaResource, pStream, timeout) return err cdef cudaError_t _cudaEGLStreamConsumerReleaseFrame(cyruntime.cudaEglStreamConnection* conn, cudaGraphicsResource_t pCudaResource, cudaStream_t* pStream) except ?cudaErrorCallRequiresNewerDriver nogil: @@ -166,7 +166,7 @@ cdef cudaError_t _cudaEGLStreamConsumerReleaseFrame(cyruntime.cudaEglStreamConne err = cudaFree(0) if err != cudaSuccess: return err - # err = cydriver._cuEGLStreamConsumerReleaseFrame(conn, pCudaResource, pStream) + err = cydriver._cuEGLStreamConsumerReleaseFrame(conn, pCudaResource, pStream) return err cdef cudaError_t _cudaEGLStreamProducerConnect(cyruntime.cudaEglStreamConnection* conn, cyruntime.EGLStreamKHR eglStream, cyruntime.EGLint width, cyruntime.EGLint height) except ?cudaErrorCallRequiresNewerDriver nogil: @@ -175,7 +175,7 @@ cdef cudaError_t _cudaEGLStreamProducerConnect(cyruntime.cudaEglStreamConnection err = cudaFree(0) if err != cudaSuccess: return err - # err = cydriver._cuEGLStreamProducerConnect(conn, eglStream, width, height) + err = cydriver._cuEGLStreamProducerConnect(conn, eglStream, width, height) return err cdef cudaError_t _cudaEGLStreamProducerDisconnect(cyruntime.cudaEglStreamConnection* conn) except ?cudaErrorCallRequiresNewerDriver nogil: @@ -184,7 +184,7 @@ cdef cudaError_t _cudaEGLStreamProducerDisconnect(cyruntime.cudaEglStreamConnect err = cudaFree(0) if err != cudaSuccess: return err - # err = cydriver._cuEGLStreamProducerDisconnect(conn) + err = cydriver._cuEGLStreamProducerDisconnect(conn) return err cdef cudaError_t _cudaEventCreateFromEGLSync(cudaEvent_t* phEvent, cyruntime.EGLSyncKHR eglSync, unsigned int flags) except ?cudaErrorCallRequiresNewerDriver nogil: @@ -193,7 +193,7 @@ cdef cudaError_t _cudaEventCreateFromEGLSync(cudaEvent_t* phEvent, cyruntime.EGL err = cudaFree(0) if err != cudaSuccess: return err - # err = cydriver._cuEventCreateFromEGLSync(phEvent, eglSync, flags) + err = cydriver._cuEventCreateFromEGLSync(phEvent, eglSync, flags) return err ## utility functions From 9a94b778a553fa52645088d925ee9058239b0b1e Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Fri, 10 Oct 2025 11:23:16 -0400 Subject: [PATCH 2/7] Add a test --- cuda_bindings/tests/test_graphics_apis.py | 43 +++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 cuda_bindings/tests/test_graphics_apis.py diff --git a/cuda_bindings/tests/test_graphics_apis.py b/cuda_bindings/tests/test_graphics_apis.py new file mode 100644 index 0000000000..21ccd8c843 --- /dev/null +++ b/cuda_bindings/tests/test_graphics_apis.py @@ -0,0 +1,43 @@ +# SPDX-FileCopyrightText: Copyright (c) 2021-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: LicenseRef-NVIDIA-SOFTWARE-LICENSE + +from cuda.bindings import runtime as cudart + +try: + import PySide6 +except ImportError: + PySide6 = None + +import pytest + + +@pytest.mark.skipif(PySide6 is None, reason="PySide6 not installed") +def test_graphics_api_smoketest(): + from PySide6 import QtGui, QtOpenGL + + class GLWidget(QtOpenGL.QOpenGLWindow): + def initializeGL(self): + self.m_texture = QtOpenGL.QOpenGLTexture(QtOpenGL.QOpenGLTexture.Target.Target2D) + self.m_texture.setFormat(QtOpenGL.QOpenGLTexture.TextureFormat.RGBA8_UNorm) + self.m_texture.setSize(512, 512) + self.m_texture.allocateStorage() + + err, self.gfx_resource = cudart.cudaGraphicsGLRegisterImage( + self.m_texture.textureId(), + self.m_texture.target().value, + cudart.cudaGraphicsRegisterFlags.cudaGraphicsRegisterFlagsWriteDiscard, + ) + error_name = cudart.cudaGetErrorName(err)[1].decode() + + # We either have everything set up correctly and we get a gfx_resource, + # or we get an error. Either way, we know the API actually did something, + # which is enough for this basic smoketest. + if error_name == "cudaSuccess": + assert int(self.gfx_resource) != 0 + else: + assert error_name == "cudaErrorInvalidValue" + + app = QtGui.QGuiApplication([]) + win = GLWidget() + win.initializeGL() + win.show() From 7b1057f558285c977727bd62ce5a11dbcf7f5f27 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Fri, 10 Oct 2025 15:34:24 -0400 Subject: [PATCH 3/7] Update cuda_bindings/tests/test_graphics_apis.py Co-authored-by: Leo Fang --- cuda_bindings/tests/test_graphics_apis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cuda_bindings/tests/test_graphics_apis.py b/cuda_bindings/tests/test_graphics_apis.py index 21ccd8c843..d49ae125c1 100644 --- a/cuda_bindings/tests/test_graphics_apis.py +++ b/cuda_bindings/tests/test_graphics_apis.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2021-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: LicenseRef-NVIDIA-SOFTWARE-LICENSE from cuda.bindings import runtime as cudart From 8d257b6237d58ed3f8aaa576d4769224a38e3c3a Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Fri, 10 Oct 2025 15:37:40 -0400 Subject: [PATCH 4/7] Use importorskip --- cuda_bindings/tests/test_graphics_apis.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/cuda_bindings/tests/test_graphics_apis.py b/cuda_bindings/tests/test_graphics_apis.py index d49ae125c1..45463745e7 100644 --- a/cuda_bindings/tests/test_graphics_apis.py +++ b/cuda_bindings/tests/test_graphics_apis.py @@ -1,18 +1,12 @@ # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: LicenseRef-NVIDIA-SOFTWARE-LICENSE -from cuda.bindings import runtime as cudart - -try: - import PySide6 -except ImportError: - PySide6 = None - import pytest +from cuda.bindings import runtime as cudart -@pytest.mark.skipif(PySide6 is None, reason="PySide6 not installed") def test_graphics_api_smoketest(): + _ = pytest.importorskip("PySide6") from PySide6 import QtGui, QtOpenGL class GLWidget(QtOpenGL.QOpenGLWindow): From 4c25b0d0c370c353a08efefce67a1a657f759091 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Mon, 13 Oct 2025 08:57:03 -0400 Subject: [PATCH 5/7] Improve tests --- cuda_bindings/pyproject.toml | 1 + cuda_bindings/tests/test_graphics_apis.py | 56 +++++++++++------------ 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/cuda_bindings/pyproject.toml b/cuda_bindings/pyproject.toml index 250f8e4076..bc0dbf1977 100644 --- a/cuda_bindings/pyproject.toml +++ b/cuda_bindings/pyproject.toml @@ -42,6 +42,7 @@ test = [ "numpy>=1.21.1", "pytest>=6.2.4", "pytest-benchmark>=3.4.1", + "pyglet>=2.1.9" ] [project.urls] diff --git a/cuda_bindings/tests/test_graphics_apis.py b/cuda_bindings/tests/test_graphics_apis.py index 45463745e7..7bfb08a3f8 100644 --- a/cuda_bindings/tests/test_graphics_apis.py +++ b/cuda_bindings/tests/test_graphics_apis.py @@ -6,32 +6,30 @@ def test_graphics_api_smoketest(): - _ = pytest.importorskip("PySide6") - from PySide6 import QtGui, QtOpenGL - - class GLWidget(QtOpenGL.QOpenGLWindow): - def initializeGL(self): - self.m_texture = QtOpenGL.QOpenGLTexture(QtOpenGL.QOpenGLTexture.Target.Target2D) - self.m_texture.setFormat(QtOpenGL.QOpenGLTexture.TextureFormat.RGBA8_UNorm) - self.m_texture.setSize(512, 512) - self.m_texture.allocateStorage() - - err, self.gfx_resource = cudart.cudaGraphicsGLRegisterImage( - self.m_texture.textureId(), - self.m_texture.target().value, - cudart.cudaGraphicsRegisterFlags.cudaGraphicsRegisterFlagsWriteDiscard, - ) - error_name = cudart.cudaGetErrorName(err)[1].decode() - - # We either have everything set up correctly and we get a gfx_resource, - # or we get an error. Either way, we know the API actually did something, - # which is enough for this basic smoketest. - if error_name == "cudaSuccess": - assert int(self.gfx_resource) != 0 - else: - assert error_name == "cudaErrorInvalidValue" - - app = QtGui.QGuiApplication([]) - win = GLWidget() - win.initializeGL() - win.show() + pyglet = pytest.importorskip("pyglet") + + tex = pyglet.image.Texture.create(512, 512) + + err, gfx_resource = cudart.cudaGraphicsGLRegisterImage( + tex.id, tex.target, cudart.cudaGraphicsRegisterFlags.cudaGraphicsRegisterFlagsWriteDiscard + ) + error_name = cudart.cudaGetErrorName(err)[1].decode() + if error_name == "cudaSuccess": + assert int(gfx_resource) != 0 + else: + assert error_name in ("cudaErrorInvalidValue", "cudaErrorUnknown") + + +def test_cuda_register_image_invalid(): + """Exercise cudaGraphicsGLRegisterImage with dummy handle only using CUDA runtime API.""" + fake_gl_texture_id = 1 + fake_gl_target = 0x0DE1 + flags = cudart.cudaGraphicsRegisterFlags.cudaGraphicsRegisterFlagsWriteDiscard + + err, resource = cudart.cudaGraphicsGLRegisterImage(fake_gl_texture_id, fake_gl_target, flags) + err_name = cudart.cudaGetErrorName(err)[1].decode() + err_str = cudart.cudaGetErrorString(err)[1].decode() + + if err == 0: + cudart.cudaGraphicsUnregisterResource(resource) + raise AssertionError("Expected error from invalid GL texture ID") From 7c04da3b9793b928b359e837a5b84abc4e2eb2c2 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Mon, 13 Oct 2025 09:20:22 -0400 Subject: [PATCH 6/7] Fix test --- cuda_bindings/tests/test_graphics_apis.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/cuda_bindings/tests/test_graphics_apis.py b/cuda_bindings/tests/test_graphics_apis.py index 7bfb08a3f8..c8f982d4ca 100644 --- a/cuda_bindings/tests/test_graphics_apis.py +++ b/cuda_bindings/tests/test_graphics_apis.py @@ -6,9 +6,15 @@ def test_graphics_api_smoketest(): - pyglet = pytest.importorskip("pyglet") - - tex = pyglet.image.Texture.create(512, 512) + # Due to lazy importing in pyglet, pytest.importorskip doesn't work + try: + import pyglet + + tex = pyglet.image.Texture.create(512, 512) + except ImportError: + pytest.skip("pyglet not available or could not create GL context") + # return to make linters happy + return err, gfx_resource = cudart.cudaGraphicsGLRegisterImage( tex.id, tex.target, cudart.cudaGraphicsRegisterFlags.cudaGraphicsRegisterFlagsWriteDiscard From 800f8bf0616a5fe7e48b3d958a73a7c333d6f413 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Mon, 13 Oct 2025 10:19:41 -0400 Subject: [PATCH 7/7] Fix Windows tests --- cuda_bindings/tests/test_graphics_apis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cuda_bindings/tests/test_graphics_apis.py b/cuda_bindings/tests/test_graphics_apis.py index c8f982d4ca..ae2f074d51 100644 --- a/cuda_bindings/tests/test_graphics_apis.py +++ b/cuda_bindings/tests/test_graphics_apis.py @@ -11,7 +11,7 @@ def test_graphics_api_smoketest(): import pyglet tex = pyglet.image.Texture.create(512, 512) - except ImportError: + except (ImportError, AttributeError): pytest.skip("pyglet not available or could not create GL context") # return to make linters happy return