Coverage for cuda/core/_graphics.pyx: 100.00%
113 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-13 01:38 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-13 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: 1hibfpvqwtnujcdeoagmklzxrsCADB
33 return 0 1fpvqwtuezxrsD
34 if isinstance(flags, str): 1hibnjcdoagmklCAB
35 flags = (flags,) 1hibnjcdoagmklAB
36 result = 0 1hibnjcdoagmklCAB
37 for f in flags: 1hibnjcdoagmklCAB
38 try: 1hibnjcdoagmklCAB
39 result |= _REGISTER_FLAGS[f] 1hibnjcdoagmklCAB
40 except KeyError: 1A
41 raise ValueError( 1A
42 f"Unknown register flag {f!r}. " 1A
43 f"Valid flags: {', '.join(sorted(_REGISTER_FLAGS))}" 1A
44 ) from None 1A
45 return result 1hibnjcdoagmklCB
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) 1hibfpvqwtnjcdeoagmklzxrs
147 cdef cydriver.CUgraphicsResource resource
148 cdef cydriver.GLuint cy_buffer = <cydriver.GLuint>gl_buffer 1hibfpvqwtnjcdeoagmklzxrs
149 cdef unsigned int cy_flags = _parse_register_flags(flags) 1hibfpvqwtnjcdeoagmklzxrs
150 with nogil: 1hibfpvqwtnjcdeoagmklzxrs
151 HANDLE_RETURN( 1hibfpvqwtnjcdeoagmklzxrs
152 cydriver.cuGraphicsGLRegisterBuffer(&resource, cy_buffer, cy_flags) 1hibfpvqwtnjcdeoagmklzxrs
153 )
154 self._handle = create_graphics_resource_handle(resource) 1hibfpvqwtnjcdeoagmklzxrs
155 self._mapped_buffer = None 1hibfpvqwtnjcdeoagmklzxrs
156 self._context_manager_stream = stream 1hibfpvqwtnjcdeoagmklzxrs
157 self._entered_buffer = None 1hibfpvqwtnjcdeoagmklzxrs
158 return self 1hibfpvqwtnjcdeoagmklzxrs
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) 1u
197 cdef cydriver.CUgraphicsResource resource
198 cdef cydriver.GLuint cy_image = <cydriver.GLuint>image 1u
199 cdef cydriver.GLenum cy_target = <cydriver.GLenum>target 1u
200 cdef unsigned int cy_flags = _parse_register_flags(flags) 1u
201 with nogil: 1u
202 HANDLE_RETURN( 1u
203 cydriver.cuGraphicsGLRegisterImage(&resource, cy_image, cy_target, cy_flags) 1u
204 )
205 self._handle = create_graphics_resource_handle(resource) 1u
206 self._mapped_buffer = None 1u
207 self._context_manager_stream = None 1u
208 self._entered_buffer = None 1u
209 return self 1u
211 def _get_mapped_buffer(self) -> object:
212 cdef Buffer buf
213 if self._mapped_buffer is None: 1hibfpvqwtnujcdeoagmklxrs
214 return None 1hibfpvqwtnujcdeoagmklxrs
215 buf = <Buffer>self._mapped_buffer 1hbfcdeag
216 if not buf._h_ptr: 1hbfcdeag
217 self._mapped_buffer = None 1hcdg
218 return None 1hcdg
219 return self._mapped_buffer 1hbfcdea
221 def map(self, *, stream: Stream) -> Buffer:
222 """Map this graphics resource for CUDA access.
224 After mapping, a CUDA device pointer into the underlying graphics
225 memory is available as a :class:`~cuda.core.Buffer`.
227 Can be used as a context manager for automatic unmapping::
229 with resource.map(stream=s) as buf:
230 # use buf.handle, buf.size, etc.
231 # automatically unmapped here
233 Parameters
234 ----------
235 stream : :class:`~cuda.core.Stream`
236 Keyword-only. The CUDA stream on which to perform the mapping.
237 Must be passed explicitly; pass ``device.default_stream`` to use
238 the default stream.
240 Returns
241 -------
242 Buffer
243 A buffer whose lifetime controls when the graphics resource is
244 unmapped.
246 Raises
247 ------
248 RuntimeError
249 If the resource is already mapped or has been closed.
250 CUDAError
251 If the mapping fails.
252 """
253 cdef Stream s_obj
254 cdef cydriver.CUgraphicsResource raw
255 cdef cydriver.CUstream cy_stream
256 cdef cydriver.CUdeviceptr dev_ptr = 0 1hibfpjcdeagkl
257 cdef size_t size = 0 1hibfpjcdeagkl
258 cdef Buffer buf
259 if not self._handle: 1hibfpjcdeagkl
260 raise RuntimeError("GraphicsResource has been closed") 1p
261 if self._get_mapped_buffer() is not None: 1hibfjcdeagkl
262 raise RuntimeError("GraphicsResource is already mapped") 1f
264 s_obj = Stream_accept(stream) 1hibfjcdeagkl
265 raw = as_cu(self._handle) 1hibfjcdeagkl
266 cy_stream = as_cu(s_obj._h_stream) 1hibfjcdeagkl
267 with nogil: 1hibfjcdeagkl
268 HANDLE_RETURN( 1hibfjcdeagkl
269 cydriver.cuGraphicsMapResources(1, &raw, cy_stream) 1hibfjcdeagkl
270 )
271 HANDLE_RETURN( 1hibfjcdeagkl
272 cydriver.cuGraphicsResourceGetMappedPointer(&dev_ptr, &size, raw) 1hibfjcdeagkl
273 )
274 buf = Buffer_from_deviceptr_handle( 1hibfjcdeagkl
275 deviceptr_create_mapped_graphics(dev_ptr, self._handle, s_obj._h_stream), 1hibfjcdeagkl
276 size,
277 None,
278 None,
279 )
280 self._mapped_buffer = buf 1hibfjcdeagkl
281 return buf 1hibfjcdeagkl
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 cdef object buf_obj
303 cdef Buffer buf
304 if not self._handle: 1fvqa
305 raise RuntimeError("GraphicsResource has been closed") 1v
306 buf_obj = self._get_mapped_buffer() 1fqa
307 if buf_obj is None: 1fqa
308 raise RuntimeError("GraphicsResource is not mapped") 1q
309 buf = <Buffer>buf_obj 1fa
310 buf.close(stream=stream) 1fa
311 self._mapped_buffer = None 1fa
313 def __enter__(self) -> object:
314 if self._context_manager_stream is None: 1em
315 return self 1m
316 self._entered_buffer = self.map(stream=self._context_manager_stream) 1e
317 return self._entered_buffer 1e
319 def __exit__(self, exc_type: type | None, exc_val: BaseException | None, exc_tb: object) -> bool:
320 self.close() 1em
321 return False 1em
323 cpdef close(self, object stream=None):
324 """Unregister this graphics resource from CUDA.
326 If the resource is currently mapped, it is unmapped first. After
327 closing, the resource cannot be used again.
329 Parameters
330 ----------
331 stream : :class:`~cuda.core.Stream`, optional
332 Optional override for the stream used to close the currently
333 mapped buffer, if one exists.
334 """
335 cdef object buf_obj
336 cdef Buffer buf
337 if not self._handle: 1bfpvqwtnucdeoagmxrs
338 return 1w
339 buf_obj = self._get_mapped_buffer() 1bfpvqwtnucdeoagmxrs
340 if buf_obj is not None: 1bfpvqwtnucdeoagmxrs
341 buf = <Buffer>buf_obj 1be
342 buf.close(stream=stream) 1be
343 self._mapped_buffer = None 1be
344 self._handle.reset() 1bfpvqwtnucdeoagmxrs
345 self._context_manager_stream = None 1bfpvqwtnucdeoagmxrs
346 self._entered_buffer = None 1bfpvqwtnucdeoagmxrs
348 @property
349 def is_mapped(self) -> bool:
350 """Whether the resource is currently mapped for CUDA access."""
351 return self._get_mapped_buffer() is not None 1hbtucdamrs
353 @property
354 def handle(self) -> int:
355 """The raw ``CUgraphicsResource`` handle as a Python int."""
356 return as_intptr(self._handle) 1tnuamz
358 @property
359 def resource_handle(self) -> int:
360 """Alias for :attr:`handle`."""
361 return self.handle 1t
363 def __repr__(self) -> str:
364 mapped_str = " mapped" if self.is_mapped else "" 1rs
365 closed_str = " closed" if not self._handle else "" 1rs
366 return f"<GraphicsResource handle={as_intptr(self._handle):#x}{mapped_str}{closed_str}>" 1rs