Coverage for cuda / core / _graphics.pyx: 100.00%
111 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-25 01:07 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-25 01:07 +0000
1# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2#
3# SPDX-License-Identifier: Apache-2.0
5from __future__ import annotations
7from cuda.bindings cimport cydriver
8from cuda.core._resource_handles cimport (
9 create_graphics_resource_handle,
10 deviceptr_create_mapped_graphics,
11 as_cu,
12 as_intptr,
13)
14from cuda.core._memory._buffer cimport Buffer, Buffer_from_deviceptr_handle
15from cuda.core._stream cimport Stream, Stream_accept, default_stream
16from cuda.core._utils.cuda_utils cimport HANDLE_RETURN
18__all__ = ['GraphicsResource']
20_REGISTER_FLAGS = {
21 "none": cydriver.CU_GRAPHICS_REGISTER_FLAGS_NONE,
22 "read_only": cydriver.CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY,
23 "write_discard": cydriver.CU_GRAPHICS_REGISTER_FLAGS_WRITE_DISCARD,
24 "surface_load_store": cydriver.CU_GRAPHICS_REGISTER_FLAGS_SURFACE_LDST,
25 "texture_gather": cydriver.CU_GRAPHICS_REGISTER_FLAGS_TEXTURE_GATHER,
26}
29def _parse_register_flags(flags):
30 if flags is None: 1ijbgpvqwtoukcdeafhnlmzxrsCADB
31 return 0 1gpvqwtuezxrsD
32 if isinstance(flags, str): 1ijbokcdafhnlmCAB
33 flags = (flags,) 1ijbokcdafhnlmAB
34 result = 0 1ijbokcdafhnlmCAB
35 for f in flags: 1ijbokcdafhnlmCAB
36 try: 1ijbokcdafhnlmCAB
37 result |= _REGISTER_FLAGS[f] 1ijbokcdafhnlmCAB
38 except KeyError: 1A
39 raise ValueError( 1A
40 f"Unknown register flag {f!r}. " 1A
41 f"Valid flags: {', '.join(sorted(_REGISTER_FLAGS))}" 1A
42 ) from None 1A
43 return result 1ijbokcdafhnlmCB
46cdef class GraphicsResource:
47 """RAII wrapper for a CUDA graphics resource (``CUgraphicsResource``).
49 A :class:`GraphicsResource` represents an OpenGL buffer or image that has
50 been registered for access by CUDA. This enables zero-copy sharing of GPU
51 data between CUDA compute kernels and graphics renderers.
53 Mapping the resource returns a :class:`~cuda.core.Buffer` whose lifetime
54 controls when the graphics resource is unmapped. This keeps stream-ordered
55 cleanup tied to the mapped pointer itself rather than to mutable state on
56 the :class:`GraphicsResource` object.
58 The resource is automatically unregistered when :meth:`close` is called or
59 when the object is garbage collected.
61 :class:`GraphicsResource` objects should not be instantiated directly.
62 Use the factory classmethods :meth:`from_gl_buffer` or :meth:`from_gl_image`.
64 Examples
65 --------
66 Register an OpenGL VBO, map it to get a buffer, and write to it from CUDA:
68 .. code-block:: python
70 resource = GraphicsResource.from_gl_buffer(vbo)
72 with resource.map(stream=s) as buf:
73 view = StridedMemoryView.from_buffer(buf, shape=(256,), dtype=np.float32)
74 # view.ptr is a CUDA device pointer into the GL buffer
76 Or scope registration separately from mapping:
78 .. code-block:: python
80 with GraphicsResource.from_gl_buffer(vbo) as resource:
81 with resource.map(stream=s) as buf:
82 # ... launch kernels using buf.handle, buf.size ...
83 pass
84 """
86 def __init__(self):
87 raise RuntimeError( 1E
88 "GraphicsResource objects cannot be instantiated directly. "
89 "Use GraphicsResource.from_gl_buffer() or GraphicsResource.from_gl_image()."
90 )
92 @classmethod
93 def from_gl_buffer(cls, int gl_buffer, *, flags=None, stream=None) -> GraphicsResource:
94 """Register an OpenGL buffer object for CUDA access.
96 Parameters
97 ----------
98 gl_buffer : int
99 The OpenGL buffer name (``GLuint``) to register.
100 flags : str or sequence of str, optional
101 Registration flags specifying intended usage. Accepted values:
102 ``"none"``, ``"read_only"``, ``"write_discard"``,
103 ``"surface_load_store"``, ``"texture_gather"``.
104 Multiple flags can be combined by passing a sequence
105 (e.g., ``("surface_load_store", "read_only")``).
106 Defaults to ``None`` (no flags).
107 stream : :class:`~cuda.core.Stream`, optional
108 If provided, the resource can be used directly as a context manager
109 and it will be mapped on entry::
111 with GraphicsResource.from_gl_buffer(vbo, stream=s) as buf:
112 view = StridedMemoryView.from_buffer(buf, shape=(256,), dtype=np.float32)
114 If omitted, the returned resource can still be used as a context
115 manager to scope registration and automatic cleanup::
117 with GraphicsResource.from_gl_buffer(vbo) as resource:
118 with resource.map(stream=s) as buf:
119 ...
121 Returns
122 -------
123 GraphicsResource
124 A new graphics resource wrapping the registered GL buffer.
125 The returned resource can be used as a context manager. If
126 *stream* was given, entering maps the resource and yields a
127 :class:`~cuda.core.Buffer`; otherwise entering yields the
128 :class:`GraphicsResource` itself and closes it on exit.
130 Raises
131 ------
132 CUDAError
133 If the registration fails (e.g., no current GL context, invalid
134 buffer name, or operating system error).
135 ValueError
136 If an unknown flag string is provided.
137 """
138 cdef GraphicsResource self = GraphicsResource.__new__(cls) 1ijbgpvqwtokcdeafhnlmzxrs
139 cdef cydriver.CUgraphicsResource resource
140 cdef cydriver.GLuint cy_buffer = <cydriver.GLuint>gl_buffer 1ijbgpvqwtokcdeafhnlmzxrs
141 cdef unsigned int cy_flags = _parse_register_flags(flags) 1ijbgpvqwtokcdeafhnlmzxrs
142 with nogil: 1ijbgpvqwtokcdeafhnlmzxrs
143 HANDLE_RETURN( 1ijbgpvqwtokcdeafhnlmzxrs
144 cydriver.cuGraphicsGLRegisterBuffer(&resource, cy_buffer, cy_flags) 1ijbgpvqwtokcdeafhnlmzxrs
145 )
146 self._handle = create_graphics_resource_handle(resource) 1ijbgpvqwtokcdeafhnlmzxrs
147 self._mapped_buffer = None 1ijbgpvqwtokcdeafhnlmzxrs
148 self._context_manager_stream = stream 1ijbgpvqwtokcdeafhnlmzxrs
149 self._entered_buffer = None 1ijbgpvqwtokcdeafhnlmzxrs
150 return self 1ijbgpvqwtokcdeafhnlmzxrs
152 @classmethod
153 def from_gl_image(
154 cls, int image, int target, *, flags=None
155 ) -> GraphicsResource:
156 """Register an OpenGL texture or renderbuffer for CUDA access.
158 Parameters
159 ----------
160 image : int
161 The OpenGL texture or renderbuffer name (``GLuint``) to register.
162 target : int
163 The OpenGL target type (e.g., ``GL_TEXTURE_2D``).
164 flags : str or sequence of str, optional
165 Registration flags specifying intended usage. Accepted values:
166 ``"none"``, ``"read_only"``, ``"write_discard"``,
167 ``"surface_load_store"``, ``"texture_gather"``.
168 Multiple flags can be combined by passing a sequence
169 (e.g., ``("surface_load_store", "read_only")``).
170 Defaults to ``None`` (no flags).
172 Returns
173 -------
174 GraphicsResource
175 A new graphics resource wrapping the registered GL image.
177 Raises
178 ------
179 CUDAError
180 If the registration fails.
181 ValueError
182 If an unknown flag string is provided.
183 """
184 cdef GraphicsResource self = GraphicsResource.__new__(cls) 1u
185 cdef cydriver.CUgraphicsResource resource
186 cdef cydriver.GLuint cy_image = <cydriver.GLuint>image 1u
187 cdef cydriver.GLenum cy_target = <cydriver.GLenum>target 1u
188 cdef unsigned int cy_flags = _parse_register_flags(flags) 1u
189 with nogil: 1u
190 HANDLE_RETURN( 1u
191 cydriver.cuGraphicsGLRegisterImage(&resource, cy_image, cy_target, cy_flags) 1u
192 )
193 self._handle = create_graphics_resource_handle(resource) 1u
194 self._mapped_buffer = None 1u
195 self._context_manager_stream = None 1u
196 self._entered_buffer = None 1u
197 return self 1u
199 def _get_mapped_buffer(self):
200 cdef Buffer buf
201 if self._mapped_buffer is None: 1ijbgpvqwtoukcdeafhnlmxrs
202 return None 1ijbgpvqwtoukcdeafhnlmxrs
203 buf = <Buffer>self._mapped_buffer 1ibgcdeafh
204 if not buf._h_ptr: 1ibgcdeafh
205 self._mapped_buffer = None 1icdfh
206 return None 1icdfh
207 return self._mapped_buffer 1ibgcdea
209 def map(self, *, stream: Stream | None = None) -> Buffer:
210 """Map this graphics resource for CUDA access.
212 After mapping, a CUDA device pointer into the underlying graphics
213 memory is available as a :class:`~cuda.core.Buffer`.
215 Can be used as a context manager for automatic unmapping::
217 with resource.map(stream=s) as buf:
218 # use buf.handle, buf.size, etc.
219 # automatically unmapped here
221 Parameters
222 ----------
223 stream : :class:`~cuda.core.Stream`, optional
224 The CUDA stream on which to perform the mapping. If ``None``,
225 the current default stream is used.
227 Returns
228 -------
229 Buffer
230 A buffer whose lifetime controls when the graphics resource is
231 unmapped.
233 Raises
234 ------
235 RuntimeError
236 If the resource is already mapped or has been closed.
237 CUDAError
238 If the mapping fails.
239 """
240 cdef Stream s_obj
241 cdef cydriver.CUgraphicsResource raw
242 cdef cydriver.CUstream cy_stream
243 cdef cydriver.CUdeviceptr dev_ptr = 0 1ijbgpkcdeafhlm
244 cdef size_t size = 0 1ijbgpkcdeafhlm
245 cdef Buffer buf
246 if not self._handle: 1ijbgpkcdeafhlm
247 raise RuntimeError("GraphicsResource has been closed") 1p
248 if self._get_mapped_buffer() is not None: 1ijbgkcdeafhlm
249 raise RuntimeError("GraphicsResource is already mapped") 1g
251 s_obj = default_stream() if stream is None else Stream_accept(stream) 1ijbgkcdeafhlm
252 raw = as_cu(self._handle) 1ijbgkcdeafhlm
253 cy_stream = as_cu(s_obj._h_stream) 1ijbgkcdeafhlm
254 with nogil: 1ijbgkcdeafhlm
255 HANDLE_RETURN( 1ijbgkcdeafhlm
256 cydriver.cuGraphicsMapResources(1, &raw, cy_stream) 1ijbgkcdeafhlm
257 )
258 HANDLE_RETURN( 1ijbgkcdeafhlm
259 cydriver.cuGraphicsResourceGetMappedPointer(&dev_ptr, &size, raw) 1ijbgkcdeafhlm
260 )
261 buf = Buffer_from_deviceptr_handle( 1ijbgkcdeafhlm
262 deviceptr_create_mapped_graphics(dev_ptr, self._handle, s_obj._h_stream), 1ijbgkcdeafhlm
263 size,
264 None,
265 None,
266 )
267 self._mapped_buffer = buf 1ijbgkcdeafhlm
268 return buf 1ijbgkcdeafhlm
270 def unmap(self, *, stream: Stream | None = None):
271 """Unmap this graphics resource, releasing it back to the graphics API.
273 After unmapping, the :class:`~cuda.core.Buffer` previously returned
274 by :meth:`map` must not be used.
276 Parameters
277 ----------
278 stream : :class:`~cuda.core.Stream`, optional
279 If provided, overrides the stream that will be used when the
280 mapped buffer is closed. Otherwise the mapping stream is reused.
282 Raises
283 ------
284 RuntimeError
285 If the resource is not currently mapped or has been closed.
286 CUDAError
287 If the unmapping fails.
288 """
289 cdef object buf_obj
290 cdef Buffer buf
291 if not self._handle: 1gvqa
292 raise RuntimeError("GraphicsResource has been closed") 1v
293 buf_obj = self._get_mapped_buffer() 1gqa
294 if buf_obj is None: 1gqa
295 raise RuntimeError("GraphicsResource is not mapped") 1q
296 buf = <Buffer>buf_obj 1ga
297 buf.close(stream=stream) 1ga
298 self._mapped_buffer = None 1ga
300 def __enter__(self):
301 if self._context_manager_stream is None: 1en
302 return self 1n
303 self._entered_buffer = self.map(stream=self._context_manager_stream) 1e
304 return self._entered_buffer 1e
306 def __exit__(self, exc_type, exc_val, exc_tb):
307 self.close() 1en
308 return False 1en
310 cpdef close(self, stream=None):
311 """Unregister this graphics resource from CUDA.
313 If the resource is currently mapped, it is unmapped first. After
314 closing, the resource cannot be used again.
316 Parameters
317 ----------
318 stream : :class:`~cuda.core.Stream`, optional
319 Optional override for the stream used to close the currently
320 mapped buffer, if one exists.
321 """
322 cdef object buf_obj
323 cdef Buffer buf
324 if not self._handle: 1bgpvqwtoucdeafhnxrs
325 return 1w
326 buf_obj = self._get_mapped_buffer() 1bgpvqwtoucdeafhnxrs
327 if buf_obj is not None: 1bgpvqwtoucdeafhnxrs
328 buf = <Buffer>buf_obj 1be
329 buf.close(stream=stream) 1be
330 self._mapped_buffer = None 1be
331 self._handle.reset() 1bgpvqwtoucdeafhnxrs
332 self._context_manager_stream = None 1bgpvqwtoucdeafhnxrs
333 self._entered_buffer = None 1bgpvqwtoucdeafhnxrs
335 @property
336 def is_mapped(self) -> bool:
337 """Whether the resource is currently mapped for CUDA access."""
338 return self._get_mapped_buffer() is not None 1ibtucdafnrs
340 @property
341 def handle(self) -> int:
342 """The raw ``CUgraphicsResource`` handle as a Python int."""
343 return as_intptr(self._handle) 1touanz
345 @property
346 def resource_handle(self) -> int:
347 """Alias for :attr:`handle`."""
348 return self.handle 1t
350 def __repr__(self):
351 mapped_str = " mapped" if self.is_mapped else "" 1rs
352 closed_str = " closed" if not self._handle else "" 1rs
353 return f"<GraphicsResource handle={as_intptr(self._handle):#x}{mapped_str}{closed_str}>" 1rs