Coverage for cuda / core / _graphics.pyx: 100.00%
111 statements
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-22 01:37 +0000
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-22 01:37 +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
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: 1hibfpvqwtnujcdeoagmklzxrsCADB
31 return 0 1fpvqwtuezxrsD
32 if isinstance(flags, str): 1hibnjcdoagmklCAB
33 flags = (flags,) 1hibnjcdoagmklAB
34 result = 0 1hibnjcdoagmklCAB
35 for f in flags: 1hibnjcdoagmklCAB
36 try: 1hibnjcdoagmklCAB
37 result |= _REGISTER_FLAGS[f] 1hibnjcdoagmklCAB
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 1hibnjcdoagmklCB
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) 1hibfpvqwtnjcdeoagmklzxrs
139 cdef cydriver.CUgraphicsResource resource
140 cdef cydriver.GLuint cy_buffer = <cydriver.GLuint>gl_buffer 1hibfpvqwtnjcdeoagmklzxrs
141 cdef unsigned int cy_flags = _parse_register_flags(flags) 1hibfpvqwtnjcdeoagmklzxrs
142 with nogil: 1hibfpvqwtnjcdeoagmklzxrs
143 HANDLE_RETURN( 1hibfpvqwtnjcdeoagmklzxrs
144 cydriver.cuGraphicsGLRegisterBuffer(&resource, cy_buffer, cy_flags) 1hibfpvqwtnjcdeoagmklzxrs
145 )
146 self._handle = create_graphics_resource_handle(resource) 1hibfpvqwtnjcdeoagmklzxrs
147 self._mapped_buffer = None 1hibfpvqwtnjcdeoagmklzxrs
148 self._context_manager_stream = stream 1hibfpvqwtnjcdeoagmklzxrs
149 self._entered_buffer = None 1hibfpvqwtnjcdeoagmklzxrs
150 return self 1hibfpvqwtnjcdeoagmklzxrs
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: 1hibfpvqwtnujcdeoagmklxrs
202 return None 1hibfpvqwtnujcdeoagmklxrs
203 buf = <Buffer>self._mapped_buffer 1hbfcdeag
204 if not buf._h_ptr: 1hbfcdeag
205 self._mapped_buffer = None 1hcdg
206 return None 1hcdg
207 return self._mapped_buffer 1hbfcdea
209 def map(self, *, stream: Stream) -> 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`
224 Keyword-only. The CUDA stream on which to perform the mapping.
225 Must be passed explicitly; pass ``device.default_stream`` to use
226 the default stream.
228 Returns
229 -------
230 Buffer
231 A buffer whose lifetime controls when the graphics resource is
232 unmapped.
234 Raises
235 ------
236 RuntimeError
237 If the resource is already mapped or has been closed.
238 CUDAError
239 If the mapping fails.
240 """
241 cdef Stream s_obj
242 cdef cydriver.CUgraphicsResource raw
243 cdef cydriver.CUstream cy_stream
244 cdef cydriver.CUdeviceptr dev_ptr = 0 1hibfpjcdeagkl
245 cdef size_t size = 0 1hibfpjcdeagkl
246 cdef Buffer buf
247 if not self._handle: 1hibfpjcdeagkl
248 raise RuntimeError("GraphicsResource has been closed") 1p
249 if self._get_mapped_buffer() is not None: 1hibfjcdeagkl
250 raise RuntimeError("GraphicsResource is already mapped") 1f
252 s_obj = Stream_accept(stream) 1hibfjcdeagkl
253 raw = as_cu(self._handle) 1hibfjcdeagkl
254 cy_stream = as_cu(s_obj._h_stream) 1hibfjcdeagkl
255 with nogil: 1hibfjcdeagkl
256 HANDLE_RETURN( 1hibfjcdeagkl
257 cydriver.cuGraphicsMapResources(1, &raw, cy_stream) 1hibfjcdeagkl
258 )
259 HANDLE_RETURN( 1hibfjcdeagkl
260 cydriver.cuGraphicsResourceGetMappedPointer(&dev_ptr, &size, raw) 1hibfjcdeagkl
261 )
262 buf = Buffer_from_deviceptr_handle( 1hibfjcdeagkl
263 deviceptr_create_mapped_graphics(dev_ptr, self._handle, s_obj._h_stream), 1hibfjcdeagkl
264 size,
265 None,
266 None,
267 )
268 self._mapped_buffer = buf 1hibfjcdeagkl
269 return buf 1hibfjcdeagkl
271 def unmap(self, *, stream: Stream | None = None):
272 """Unmap this graphics resource, releasing it back to the graphics API.
274 After unmapping, the :class:`~cuda.core.Buffer` previously returned
275 by :meth:`map` must not be used.
277 Parameters
278 ----------
279 stream : :class:`~cuda.core.Stream`, optional
280 If provided, overrides the stream that will be used when the
281 mapped buffer is closed. Otherwise the mapping stream is reused.
283 Raises
284 ------
285 RuntimeError
286 If the resource is not currently mapped or has been closed.
287 CUDAError
288 If the unmapping fails.
289 """
290 cdef object buf_obj
291 cdef Buffer buf
292 if not self._handle: 1fvqa
293 raise RuntimeError("GraphicsResource has been closed") 1v
294 buf_obj = self._get_mapped_buffer() 1fqa
295 if buf_obj is None: 1fqa
296 raise RuntimeError("GraphicsResource is not mapped") 1q
297 buf = <Buffer>buf_obj 1fa
298 buf.close(stream=stream) 1fa
299 self._mapped_buffer = None 1fa
301 def __enter__(self):
302 if self._context_manager_stream is None: 1em
303 return self 1m
304 self._entered_buffer = self.map(stream=self._context_manager_stream) 1e
305 return self._entered_buffer 1e
307 def __exit__(self, exc_type, exc_val, exc_tb):
308 self.close() 1em
309 return False 1em
311 cpdef close(self, stream=None):
312 """Unregister this graphics resource from CUDA.
314 If the resource is currently mapped, it is unmapped first. After
315 closing, the resource cannot be used again.
317 Parameters
318 ----------
319 stream : :class:`~cuda.core.Stream`, optional
320 Optional override for the stream used to close the currently
321 mapped buffer, if one exists.
322 """
323 cdef object buf_obj
324 cdef Buffer buf
325 if not self._handle: 1bfpvqwtnucdeoagmxrs
326 return 1w
327 buf_obj = self._get_mapped_buffer() 1bfpvqwtnucdeoagmxrs
328 if buf_obj is not None: 1bfpvqwtnucdeoagmxrs
329 buf = <Buffer>buf_obj 1be
330 buf.close(stream=stream) 1be
331 self._mapped_buffer = None 1be
332 self._handle.reset() 1bfpvqwtnucdeoagmxrs
333 self._context_manager_stream = None 1bfpvqwtnucdeoagmxrs
334 self._entered_buffer = None 1bfpvqwtnucdeoagmxrs
336 @property
337 def is_mapped(self) -> bool:
338 """Whether the resource is currently mapped for CUDA access."""
339 return self._get_mapped_buffer() is not None 1hbtucdamrs
341 @property
342 def handle(self) -> int:
343 """The raw ``CUgraphicsResource`` handle as a Python int."""
344 return as_intptr(self._handle) 1tnuamz
346 @property
347 def resource_handle(self) -> int:
348 """Alias for :attr:`handle`."""
349 return self.handle 1t
351 def __repr__(self):
352 mapped_str = " mapped" if self.is_mapped else "" 1rs
353 closed_str = " closed" if not self._handle else "" 1rs
354 return f"<GraphicsResource handle={as_intptr(self._handle):#x}{mapped_str}{closed_str}>" 1rs