Coverage for cuda/core/_graphics.pyx: 94.69%
113 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-29 01:38 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-29 01:38 +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 typing import Sequence
9from cuda.bindings cimport cydriver
10from cuda.core._resource_handles cimport (
11 create_graphics_resource_handle,
12 deviceptr_create_mapped_graphics,
13 as_cu,
14 as_intptr,
15)
16from cuda.core._memory._buffer cimport Buffer, Buffer_from_deviceptr_handle
17from cuda.core._stream cimport Stream, Stream_accept
18from cuda.core._utils.cuda_utils cimport HANDLE_RETURN
20__all__ = ['GraphicsResource']
22_REGISTER_FLAGS = {
23 "none": cydriver.CU_GRAPHICS_REGISTER_FLAGS_NONE,
24 "read_only": cydriver.CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY,
25 "write_discard": cydriver.CU_GRAPHICS_REGISTER_FLAGS_WRITE_DISCARD,
26 "surface_load_store": cydriver.CU_GRAPHICS_REGISTER_FLAGS_SURFACE_LDST,
27 "texture_gather": cydriver.CU_GRAPHICS_REGISTER_FLAGS_TEXTURE_GATHER,
28}
31def _parse_register_flags(flags: str | Sequence[str] | None) -> int:
32 if flags is None: 1hibfnsotrmAjcdewagxklyupqCzDB
33 return 0 1fnsotrAeyupqD
34 if isinstance(flags, str): 1hibmjcdwagxklCzB
35 flags = (flags,) 1hibmjcdwagxklzB
36 result = 0 1hibmjcdwagxklCzB
37 for f in flags: 1hibmjcdwagxklCzB
38 try: 1hibmjcdwagxklCzB
39 result |= _REGISTER_FLAGS[f] 1hibmjcdwagxklCzB
40 except KeyError: 1z
41 raise ValueError( 1z
42 f"Unknown register flag {f!r}. " 1z
43 f"Valid flags: {', '.join(sorted(_REGISTER_FLAGS))}" 1z
44 ) from None 1z
45 return result 1hibmjcdwagxklCB
48cdef class GraphicsResource:
49 """RAII wrapper for a CUDA graphics resource (``CUgraphicsResource``).
51 A :class:`GraphicsResource` represents an OpenGL buffer or image that has
52 been registered for access by CUDA. This enables zero-copy sharing of GPU
53 data between CUDA compute kernels and graphics renderers.
55 Mapping the resource returns a :class:`~cuda.core.Buffer` whose lifetime
56 controls when the graphics resource is unmapped. This keeps stream-ordered
57 cleanup tied to the mapped pointer itself rather than to mutable state on
58 the :class:`GraphicsResource` object.
60 The resource is automatically unregistered when :meth:`close` is called or
61 when the object is garbage collected.
63 :class:`GraphicsResource` objects should not be instantiated directly.
64 Use the factory classmethods :meth:`from_gl_buffer` or :meth:`from_gl_image`.
66 Examples
67 --------
68 Register an OpenGL VBO, map it to get a buffer, and write to it from CUDA:
70 .. code-block:: python
72 resource = GraphicsResource.from_gl_buffer(vbo)
74 with resource.map(stream=s) as buf:
75 view = StridedMemoryView.from_buffer(buf, shape=(256,), dtype=np.float32)
76 # view.ptr is a CUDA device pointer into the GL buffer
78 Or scope registration separately from mapping:
80 .. code-block:: python
82 with GraphicsResource.from_gl_buffer(vbo) as resource:
83 with resource.map(stream=s) as buf:
84 # ... launch kernels using buf.handle, buf.size ...
85 pass
86 """
88 def __init__(self) -> None:
89 raise RuntimeError( 1E
90 "GraphicsResource objects cannot be instantiated directly. "
91 "Use GraphicsResource.from_gl_buffer() or GraphicsResource.from_gl_image()."
92 )
94 @classmethod
95 def from_gl_buffer(
96 cls,
97 int gl_buffer,
98 *,
99 flags: str | tuple[str, ...] | list[str] | None = None,
100 stream: Stream | None = None
101 ) -> GraphicsResource:
102 """Register an OpenGL buffer object for CUDA access.
104 Parameters
105 ----------
106 gl_buffer : int
107 The OpenGL buffer name (``GLuint``) to register.
108 flags : str or sequence of str, optional
109 Registration flags specifying intended usage. Accepted values:
110 ``"none"``, ``"read_only"``, ``"write_discard"``,
111 ``"surface_load_store"``, ``"texture_gather"``.
112 Multiple flags can be combined by passing a sequence
113 (e.g., ``("surface_load_store", "read_only")``).
114 Defaults to ``None`` (no flags).
115 stream : :class:`~cuda.core.Stream`, optional
116 If provided, the resource can be used directly as a context manager
117 and it will be mapped on entry::
119 with GraphicsResource.from_gl_buffer(vbo, stream=s) as buf:
120 view = StridedMemoryView.from_buffer(buf, shape=(256,), dtype=np.float32)
122 If omitted, the returned resource can still be used as a context
123 manager to scope registration and automatic cleanup::
125 with GraphicsResource.from_gl_buffer(vbo) as resource:
126 with resource.map(stream=s) as buf:
127 ...
129 Returns
130 -------
131 GraphicsResource
132 A new graphics resource wrapping the registered GL buffer.
133 The returned resource can be used as a context manager. If
134 *stream* was given, entering maps the resource and yields a
135 :class:`~cuda.core.Buffer`; otherwise entering yields the
136 :class:`GraphicsResource` itself and closes it on exit.
138 Raises
139 ------
140 CUDAError
141 If the registration fails (e.g., no current GL context, invalid
142 buffer name, or operating system error).
143 ValueError
144 If an unknown flag string is provided.
145 """
146 cdef GraphicsResource self = GraphicsResource.__new__(cls) 1hibfnsotrmjcdewagxklyupq
147 cdef cydriver.CUgraphicsResource resource
148 cdef cydriver.GLuint cy_buffer = <cydriver.GLuint>gl_buffer 1hibfnsotrmjcdewagxklyupq
149 cdef unsigned int cy_flags = _parse_register_flags(flags) 1hibfnsotrmjcdewagxklyupq
150 with nogil: 1hibfnsotrmjcdewagxklyupq
151 HANDLE_RETURN( 1hibfnsotrmjcdewagxklyupq
152 cydriver.cuGraphicsGLRegisterBuffer(&resource, cy_buffer, cy_flags) 1hibfnsotrmjcdewagxklyupq
153 )
154 self._handle = create_graphics_resource_handle(resource) 1hibfnsotrmjcdeagklyupq
155 self._mapped_buffer = None 1hibfnsotrmjcdeagklyupq
156 self._context_manager_stream = stream 1hibfnsotrmjcdeagklyupq
157 self._entered_buffer = None 1hibfnsotrmjcdeagklyupq
158 return self 1hibfnsotrmjcdeagklyupq
160 @classmethod
161 def from_gl_image(
162 cls,
163 int image,
164 int target,
165 *,
166 flags: str | tuple[str, ...] | list[str] | None = None
167 ) -> GraphicsResource:
168 """Register an OpenGL texture or renderbuffer for CUDA access.
170 Parameters
171 ----------
172 image : int
173 The OpenGL texture or renderbuffer name (``GLuint``) to register.
174 target : int
175 The OpenGL target type (e.g., ``GL_TEXTURE_2D``).
176 flags : str or sequence of str, optional
177 Registration flags specifying intended usage. Accepted values:
178 ``"none"``, ``"read_only"``, ``"write_discard"``,
179 ``"surface_load_store"``, ``"texture_gather"``.
180 Multiple flags can be combined by passing a sequence
181 (e.g., ``("surface_load_store", "read_only")``).
182 Defaults to ``None`` (no flags).
184 Returns
185 -------
186 GraphicsResource
187 A new graphics resource wrapping the registered GL image.
189 Raises
190 ------
191 CUDAError
192 If the registration fails.
193 ValueError
194 If an unknown flag string is provided.
195 """
196 cdef GraphicsResource self = GraphicsResource.__new__(cls) 1A
197 cdef cydriver.CUgraphicsResource resource
198 cdef cydriver.GLuint cy_image = <cydriver.GLuint>image 1A
199 cdef cydriver.GLenum cy_target = <cydriver.GLenum>target 1A
200 cdef unsigned int cy_flags = _parse_register_flags(flags) 1A
201 with nogil: 1A
202 HANDLE_RETURN( 1A
203 cydriver.cuGraphicsGLRegisterImage(&resource, cy_image, cy_target, cy_flags) 1A
204 )
205 self._handle = create_graphics_resource_handle(resource)
206 self._mapped_buffer = None
207 self._context_manager_stream = None
208 self._entered_buffer = None
209 return self
211 def _get_mapped_buffer(self) -> object:
212 if self._mapped_buffer is None: 1hibfnsotrmjcdeagklupq
213 return None 1hibfnsotrmjcdeagklupq
214 cdef Buffer buf = <Buffer>self._mapped_buffer 1hbfcdeag
215 if not buf._h_ptr: 1hbfcdeag
216 self._mapped_buffer = None 1hcdg
217 return None 1hcdg
218 return self._mapped_buffer 1hbfcdea
220 def map(self, *, stream: Stream) -> Buffer:
221 """Map this graphics resource for CUDA access.
223 After mapping, a CUDA device pointer into the underlying graphics
224 memory is available as a :class:`~cuda.core.Buffer`.
226 Can be used as a context manager for automatic unmapping::
228 with resource.map(stream=s) as buf:
229 # use buf.handle, buf.size, etc.
230 # automatically unmapped here
232 Parameters
233 ----------
234 stream : :class:`~cuda.core.Stream`
235 Keyword-only. The CUDA stream on which to perform the mapping.
236 Must be passed explicitly; pass ``device.default_stream`` to use
237 the default stream.
239 Returns
240 -------
241 Buffer
242 A buffer whose lifetime controls when the graphics resource is
243 unmapped.
245 Raises
246 ------
247 RuntimeError
248 If the resource is already mapped or has been closed.
249 CUDAError
250 If the mapping fails.
251 """
252 cdef cydriver.CUdeviceptr dev_ptr = 0 1hibfnjcdeagkl
253 cdef size_t size = 0 1hibfnjcdeagkl
254 if not self._handle: 1hibfnjcdeagkl
255 raise RuntimeError("GraphicsResource has been closed") 1n
256 if self._get_mapped_buffer() is not None: 1hibfjcdeagkl
257 raise RuntimeError("GraphicsResource is already mapped") 1f
259 cdef Stream s_obj = Stream_accept(stream) 1hibfjcdeagkl
260 cdef cydriver.CUgraphicsResource raw = as_cu(self._handle) 1hibfjcdeagkl
261 cdef cydriver.CUstream cy_stream = as_cu(s_obj._h_stream) 1hibfjcdeagkl
262 with nogil: 1hibfjcdeagkl
263 HANDLE_RETURN( 1hibfjcdeagkl
264 cydriver.cuGraphicsMapResources(1, &raw, cy_stream) 1hibfjcdeagkl
265 )
266 HANDLE_RETURN( 1hibfjcdeagkl
267 cydriver.cuGraphicsResourceGetMappedPointer(&dev_ptr, &size, raw) 1hibfjcdeagkl
268 )
269 cdef Buffer buf = Buffer_from_deviceptr_handle( 1hibfjcdeagkl
270 deviceptr_create_mapped_graphics(dev_ptr, self._handle, s_obj._h_stream), 1hibfjcdeagkl
271 size,
272 None,
273 None,
274 )
275 self._mapped_buffer = buf 1hibfjcdeagkl
276 return buf 1hibfjcdeagkl
278 def unmap(self, *, stream: Stream | None = None) -> None:
279 """Unmap this graphics resource, releasing it back to the graphics API.
281 After unmapping, the :class:`~cuda.core.Buffer` previously returned
282 by :meth:`map` must not be used.
284 Parameters
285 ----------
286 stream : :class:`~cuda.core.Stream`, optional
287 If provided, overrides the stream that will be used when the
288 mapped buffer is closed. Otherwise the mapping stream is reused.
290 Raises
291 ------
292 RuntimeError
293 If the resource is not currently mapped or has been closed.
294 CUDAError
295 If the unmapping fails.
296 """
297 if not self._handle: 1fsoa
298 raise RuntimeError("GraphicsResource has been closed") 1s
299 cdef object buf_obj = self._get_mapped_buffer() 1foa
300 if buf_obj is None: 1foa
301 raise RuntimeError("GraphicsResource is not mapped") 1o
302 cdef Buffer buf = <Buffer>buf_obj 1fa
303 buf.close(stream=stream) 1fa
304 self._mapped_buffer = None 1fa
306 def __enter__(self) -> object:
307 if self._context_manager_stream is None: 1e
308 return self
309 self._entered_buffer = self.map(stream=self._context_manager_stream) 1e
310 return self._entered_buffer 1e
312 def __exit__(self, exc_type: type | None, exc_val: BaseException | None, exc_tb: object) -> bool:
313 self.close() 1e
314 return False 1e
316 cpdef close(self, object stream=None):
317 """Unregister this graphics resource from CUDA.
319 If the resource is currently mapped, it is unmapped first. After
320 closing, the resource cannot be used again.
322 Parameters
323 ----------
324 stream : :class:`~cuda.core.Stream`, optional
325 Optional override for the stream used to close the currently
326 mapped buffer, if one exists.
327 """
328 cdef Buffer buf
329 if not self._handle: 1bfnsotrmcdeagupq
330 return 1t
331 cdef object buf_obj = self._get_mapped_buffer() 1bfnsotrmcdeagupq
332 if buf_obj is not None: 1bfnsotrmcdeagupq
333 buf = <Buffer>buf_obj 1be
334 buf.close(stream=stream) 1be
335 self._mapped_buffer = None 1be
336 self._handle.reset() 1bfnsotrmcdeagupq
337 self._context_manager_stream = None 1bfnsotrmcdeagupq
338 self._entered_buffer = None 1bfnsotrmcdeagupq
340 @property
341 def is_mapped(self) -> bool:
342 """Whether the resource is currently mapped for CUDA access."""
343 return self._get_mapped_buffer() is not None 1hbrcdapq
345 @property
346 def handle(self) -> int:
347 """The raw ``CUgraphicsResource`` handle as a Python int."""
348 return as_intptr(self._handle) 1rmay
350 @property
351 def resource_handle(self) -> int:
352 """Alias for :attr:`handle`."""
353 return self.handle 1r
355 def __repr__(self) -> str:
356 mapped_str = " mapped" if self.is_mapped else "" 1pq
357 closed_str = " closed" if not self._handle else "" 1pq
358 return f"<GraphicsResource handle={as_intptr(self._handle):#x}{mapped_str}{closed_str}>" 1pq