Coverage for cuda/core/_graphics.pyx: 100.00%
116 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-03 02:41 +0000
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-03 02:41 +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: 1ijwbcdfeyxpnagBzCAstmqrlkhuo
33 return 0 1wfeyxpCstqruo
34 if isinstance(flags, str): 1ijbcdnagBzAmlkh
35 flags = (flags,) 1vijbcdnagzAmlkh
36 result = 0 1ijbcdnagBzAmlkh
37 for f in flags: 1ijbcdnagBzAmlkh
38 try: 1ijbcdnagBzAmlkh
39 result |= _REGISTER_FLAGS[f] 1ijbcdnagBzAmlkh
40 except KeyError: 1z
41 raise ValueError( 1z
42 f"Unknown register flag {f!r}. " 1z
43 f"Valid flags: {', '.join(sorted(_REGISTER_FLAGS))}" 1vz
44 ) from None 1z
45 return result 1ijbcdnagBAmlkh
48cdef inline int GraphicsResource_check_open(GraphicsResource self) except -1:
49 if not self._handle: 1ijbcdfepagkhuo
50 raise RuntimeError("GraphicsResource has been closed") 1pu
51 return 0 1ijbcdfeagkho
54cdef class GraphicsResource:
55 """RAII wrapper for a CUDA graphics resource (``CUgraphicsResource``).
57 A :class:`GraphicsResource` represents an OpenGL buffer or image that has
58 been registered for access by CUDA. This enables zero-copy sharing of GPU
59 data between CUDA compute kernels and graphics renderers.
61 Mapping the resource returns a :class:`~cuda.core.Buffer` whose lifetime
62 controls when the graphics resource is unmapped. This keeps stream-ordered
63 cleanup tied to the mapped pointer itself rather than to mutable state on
64 the :class:`GraphicsResource` object.
66 The resource is automatically unregistered when :meth:`close` is called or
67 when the object is garbage collected.
69 :class:`GraphicsResource` objects should not be instantiated directly.
70 Use the factory classmethods :meth:`from_gl_buffer` or :meth:`from_gl_image`.
72 Examples
73 --------
74 Register an OpenGL VBO, map it to get a buffer, and write to it from CUDA:
76 .. code-block:: python
78 resource = GraphicsResource.from_gl_buffer(vbo)
80 with resource.map(stream=s) as buf:
81 view = StridedMemoryView.from_buffer(buf, shape=(256,), dtype=np.float32)
82 # view.ptr is a CUDA device pointer into the GL buffer
84 Or scope registration separately from mapping:
86 .. code-block:: python
88 with GraphicsResource.from_gl_buffer(vbo) as resource:
89 with resource.map(stream=s) as buf:
90 # ... launch kernels using buf.handle, buf.size ...
91 pass
92 """
94 def __init__(self) -> None:
95 raise RuntimeError( 1D
96 "GraphicsResource objects cannot be instantiated directly. "
97 "Use GraphicsResource.from_gl_buffer() or GraphicsResource.from_gl_image()."
98 )
100 @classmethod
101 def from_gl_buffer(
102 cls,
103 int gl_buffer,
104 *,
105 flags: str | tuple[str, ...] | list[str] | None = None,
106 stream: Stream | None = None
107 ) -> GraphicsResource:
108 """Register an OpenGL buffer object for CUDA access.
110 Parameters
111 ----------
112 gl_buffer : int
113 The OpenGL buffer name (``GLuint``) to register.
114 flags : str or sequence of str, optional
115 Registration flags specifying intended usage. Accepted values:
116 ``"none"``, ``"read_only"``, ``"write_discard"``,
117 ``"surface_load_store"``, ``"texture_gather"``.
118 Multiple flags can be combined by passing a sequence
119 (e.g., ``("surface_load_store", "read_only")``).
120 Defaults to ``None`` (no flags).
121 stream : :class:`~cuda.core.Stream`, optional
122 If provided, the resource can be used directly as a context manager
123 and it will be mapped on entry::
125 with GraphicsResource.from_gl_buffer(vbo, stream=s) as buf:
126 view = StridedMemoryView.from_buffer(buf, shape=(256,), dtype=np.float32)
128 If omitted, the returned resource can still be used as a context
129 manager to scope registration and automatic cleanup::
131 with GraphicsResource.from_gl_buffer(vbo) as resource:
132 with resource.map(stream=s) as buf:
133 ...
135 Returns
136 -------
137 GraphicsResource
138 A new graphics resource wrapping the registered GL buffer.
139 The returned resource can be used as a context manager. If
140 *stream* was given, entering maps the resource and yields a
141 :class:`~cuda.core.Buffer`; otherwise entering yields the
142 :class:`GraphicsResource` itself and closes it on exit.
144 Raises
145 ------
146 CUDAError
147 If the registration fails (e.g., no current GL context, invalid
148 buffer name, or operating system error).
149 ValueError
150 If an unknown flag string is provided.
151 """
152 cdef GraphicsResource self = GraphicsResource.__new__(cls) 1ijwbcdfeyxpnagsmqrlkhuo
153 cdef cydriver.CUgraphicsResource resource
154 cdef cydriver.GLuint cy_buffer = <cydriver.GLuint>gl_buffer 1ijwbcdfeyxpnagsmqrlkhuo
155 cdef unsigned int cy_flags = _parse_register_flags(flags) 1ijwbcdfeyxpnagsmqrlkhuo
156 with nogil: 1ijwbcdfeyxpnagsmqrlkhuo
157 HANDLE_RETURN( 1ijwbcdfeyxpnagsmqrlkhuo
158 cydriver.cuGraphicsGLRegisterBuffer(&resource, cy_buffer, cy_flags) 1ijwbcdfeyxpnagsmqrlkhuo
159 )
160 self._handle = create_graphics_resource_handle(resource) 1ijwbcdfeyxpnagsmqrlkhuo
161 self._mapped_buffer = None 1ijwbcdfeyxpnagsmqrlkhuo
162 self._context_manager_stream = stream 1ijwbcdfeyxpnagsmqrlkhuo
163 self._entered_buffer = None 1ijwbcdfeyxpnagsmqrlkhuo
164 return self 1ijwbcdfeyxpnagsmqrlkhuo
166 @classmethod
167 def from_gl_image(
168 cls,
169 int image,
170 int target,
171 *,
172 flags: str | tuple[str, ...] | list[str] | None = None
173 ) -> GraphicsResource:
174 """Register an OpenGL texture or renderbuffer for CUDA access.
176 Parameters
177 ----------
178 image : int
179 The OpenGL texture or renderbuffer name (``GLuint``) to register.
180 target : int
181 The OpenGL target type (e.g., ``GL_TEXTURE_2D``).
182 flags : str or sequence of str, optional
183 Registration flags specifying intended usage. Accepted values:
184 ``"none"``, ``"read_only"``, ``"write_discard"``,
185 ``"surface_load_store"``, ``"texture_gather"``.
186 Multiple flags can be combined by passing a sequence
187 (e.g., ``("surface_load_store", "read_only")``).
188 Defaults to ``None`` (no flags).
190 Returns
191 -------
192 GraphicsResource
193 A new graphics resource wrapping the registered GL image.
195 Raises
196 ------
197 CUDAError
198 If the registration fails.
199 ValueError
200 If an unknown flag string is provided.
201 """
202 cdef GraphicsResource self = GraphicsResource.__new__(cls) 1t
203 cdef cydriver.CUgraphicsResource resource
204 cdef cydriver.GLuint cy_image = <cydriver.GLuint>image 1t
205 cdef cydriver.GLenum cy_target = <cydriver.GLenum>target 1t
206 cdef unsigned int cy_flags = _parse_register_flags(flags) 1t
207 with nogil: 1t
208 HANDLE_RETURN( 1t
209 cydriver.cuGraphicsGLRegisterImage(&resource, cy_image, cy_target, cy_flags) 1t
210 )
211 self._handle = create_graphics_resource_handle(resource) 1t
212 self._mapped_buffer = None 1t
213 self._context_manager_stream = None 1t
214 self._entered_buffer = None 1t
215 return self 1t
217 def _get_mapped_buffer(self) -> object:
218 if self._mapped_buffer is None: 1ijwbcdfexpnagstmqrlkhuo
219 return None 1ijwbcdfexpnagstmqrlkhuo
220 cdef Buffer buf = <Buffer>self._mapped_buffer 1ibcdfeagh
221 if not buf._h_ptr: 1ibcdfeagh
222 self._mapped_buffer = None 1icdgh
223 return None 1icdgh
224 return self._mapped_buffer 1ibcdfea
226 def map(self, *, stream: Stream) -> Buffer:
227 """Map this graphics resource for CUDA access.
229 After mapping, a CUDA device pointer into the underlying graphics
230 memory is available as a :class:`~cuda.core.Buffer`.
232 Can be used as a context manager for automatic unmapping::
234 with resource.map(stream=s) as buf:
235 # use buf.handle, buf.size, etc.
236 # automatically unmapped here
238 Parameters
239 ----------
240 stream : :class:`~cuda.core.Stream`
241 Keyword-only. The CUDA stream on which to perform the mapping.
242 Must be passed explicitly; pass ``device.default_stream`` to use
243 the default stream.
245 Returns
246 -------
247 Buffer
248 A buffer whose lifetime controls when the graphics resource is
249 unmapped.
251 Raises
252 ------
253 RuntimeError
254 If the resource is already mapped or has been closed.
255 CUDAError
256 If the mapping fails.
257 """
258 cdef cydriver.CUdeviceptr dev_ptr = 0 1ijbcdfepagkh
259 cdef size_t size = 0 1ijbcdfepagkh
260 GraphicsResource_check_open(self) 1ijbcdfepagkh
261 if self._get_mapped_buffer() is not None: 1ijbcdfeagkh
262 raise RuntimeError("GraphicsResource is already mapped") 1f
264 cdef Stream s_obj = Stream_accept(stream) 1ijbcdfeagkh
265 cdef cydriver.CUgraphicsResource raw = as_cu(self._handle) 1ijbcdfeagkh
266 cdef cydriver.CUstream cy_stream = as_cu(s_obj._h_stream) 1ijbcdfeagkh
267 with nogil: 1ijbcdfeagkh
268 HANDLE_RETURN( 1ijbcdfeagkh
269 cydriver.cuGraphicsMapResources(1, &raw, cy_stream) 1ijbcdfeagkh
270 )
271 HANDLE_RETURN( 1ijbcdfeagkh
272 cydriver.cuGraphicsResourceGetMappedPointer(&dev_ptr, &size, raw) 1ijbcdfeagkh
273 )
274 cdef Buffer buf = Buffer_from_deviceptr_handle( 1ijbcdfeagkh
275 deviceptr_create_mapped_graphics(dev_ptr, self._handle, s_obj._h_stream), 1ijbcdfeagkh
276 size,
277 None,
278 None,
279 )
280 self._mapped_buffer = buf 1ijbcdfeagkh
281 return buf 1ijbcdfeagkh
283 def unmap(self, *, stream: Stream | None = None) -> None:
284 """Unmap this graphics resource, releasing it back to the graphics API.
286 After unmapping, the :class:`~cuda.core.Buffer` previously returned
287 by :meth:`map` must not be used.
289 Parameters
290 ----------
291 stream : :class:`~cuda.core.Stream`, optional
292 If provided, overrides the stream that will be used when the
293 mapped buffer is closed. Otherwise the mapping stream is reused.
295 Raises
296 ------
297 RuntimeError
298 If the resource is not currently mapped or has been closed.
299 CUDAError
300 If the unmapping fails.
301 """
302 GraphicsResource_check_open(self) 1fauo
303 cdef object buf_obj = self._get_mapped_buffer() 1fao
304 if buf_obj is None: 1fao
305 raise RuntimeError("GraphicsResource is not mapped") 1o
306 cdef Buffer buf = <Buffer>buf_obj 1fa
307 buf.close(stream=stream) 1fa
308 self._mapped_buffer = None 1fa
310 def __enter__(self) -> object:
311 if self._context_manager_stream is None: 1el
312 return self 1l
313 self._entered_buffer = self.map(stream=self._context_manager_stream) 1e
314 return self._entered_buffer 1e
316 def __exit__(self, exc_type: type | None, exc_val: BaseException | None, exc_tb: object) -> bool:
317 self.close() 1el
318 return False 1el
320 cpdef close(self, object stream=None):
321 """Unregister this graphics resource from CUDA.
323 If the resource is currently mapped, it is unmapped first. After
324 closing, the resource cannot be used again.
326 Parameters
327 ----------
328 stream : :class:`~cuda.core.Stream`, optional
329 Optional override for the stream used to close the currently
330 mapped buffer, if one exists.
331 """
332 cdef Buffer buf
333 if not self._handle: 1wbcdfexpnagstmqrlhuo
334 return 1w
335 cdef object buf_obj = self._get_mapped_buffer() 1wbcdfexpnagstmqrlhuo
336 if buf_obj is not None: 1wbcdfexpnagstmqrlhuo
337 buf = <Buffer>buf_obj 1be
338 buf.close(stream=stream) 1be
339 self._mapped_buffer = None 1be
340 self._handle.reset() 1wbcdfexpnagstmqrlhuo
341 self._context_manager_stream = None 1wbcdfexpnagstmqrlhuo
342 self._entered_buffer = None 1wbcdfexpnagstmqrlhuo
344 @property
345 def is_mapped(self) -> bool:
346 """Whether the resource is currently mapped for CUDA access."""
347 return self._get_mapped_buffer() is not None 1ibcdastqrl
349 @property
350 def handle(self) -> int:
351 """The raw ``CUgraphicsResource`` handle as a Python int."""
352 return as_intptr(self._handle) 1yastml
354 @property
355 def is_closed(self) -> bool:
356 """Whether this graphics resource has been closed."""
357 return self._handle.get() == NULL 1w
359 @property
360 def resource_handle(self) -> int:
361 """Alias for :attr:`handle`."""
362 return self.handle 1s
364 def __repr__(self) -> str:
365 mapped_str = " mapped" if self.is_mapped else "" 1qr
366 closed_str = " closed" if not self._handle else "" 1qr
367 return f"<GraphicsResource handle={as_intptr(self._handle):#x}{mapped_str}{closed_str}>" 1qr