Coverage for cuda / core / _graphics.pyx: 99.12%
113 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-08 01:07 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-08 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 as_cu,
11 as_intptr,
12)
13from cuda.core._stream cimport Stream, Stream_accept
14from cuda.core._utils.cuda_utils cimport HANDLE_RETURN
16from cuda.core._memory import Buffer
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: 1fgnojqpikbcdaerlmusvt
31 return 0 1gnojqpkrlmv
32 if isinstance(flags, str): 1fibcdaeust
33 flags = (flags,) 1fibcdaest
34 result = 0 1fibcdaeust
35 for f in flags: 1fibcdaeust
36 try: 1fibcdaeust
37 result |= _REGISTER_FLAGS[f] 1fibcdaeust
38 except KeyError: 1s
39 raise ValueError( 1s
40 f"Unknown register flag {f!r}. " 1s
41 f"Valid flags: {', '.join(sorted(_REGISTER_FLAGS))}" 1s
42 ) from None 1s
43 return result 1fibcdaeut
46class _MappedBufferContext:
47 """Context manager returned by :meth:`GraphicsResource.map`.
49 Wraps a :class:`~cuda.core.Buffer` and ensures the graphics resource
50 is unmapped when the context exits. Can also be used without ``with``
51 by calling :meth:`GraphicsResource.unmap` explicitly.
52 """
53 __slots__ = ('_buffer', '_resource', '_stream')
55 def __init__(self, buffer, resource, stream):
56 self._buffer = buffer 1fgbcdae
57 self._resource = resource 1fgbcdae
58 self._stream = stream 1fgbcdae
60 def __enter__(self):
61 return self._buffer 1bcae
63 def __exit__(self, exc_type, exc_val, exc_tb):
64 self._resource.unmap(stream=self._stream) 1bcae
65 return False 1bcae
67 # Delegate Buffer attributes so the return value of map() is directly usable
68 @property
69 def handle(self):
70 return self._buffer.handle 1d
72 @property
73 def size(self):
74 return self._buffer.size 1d
76 def __repr__(self):
77 return repr(self._buffer)
80cdef class GraphicsResource:
81 """RAII wrapper for a CUDA graphics resource (``CUgraphicsResource``).
83 A :class:`GraphicsResource` represents an OpenGL buffer or image that has
84 been registered for access by CUDA. This enables zero-copy sharing of GPU
85 data between CUDA compute kernels and graphics renderers.
87 The resource is automatically unregistered when :meth:`close` is called or
88 when the object is garbage collected.
90 :class:`GraphicsResource` objects should not be instantiated directly.
91 Use the factory classmethods :meth:`from_gl_buffer` or :meth:`from_gl_image`.
93 Examples
94 --------
95 Register an OpenGL VBO, map it to get a :class:`~cuda.core.Buffer`, and
96 write to it from CUDA:
98 .. code-block:: python
100 resource = GraphicsResource.from_gl_buffer(vbo)
102 with resource.map(stream=s) as buf:
103 view = StridedMemoryView.from_buffer(buf, shape=(256,), dtype=np.float32)
104 # view.ptr is a CUDA device pointer into the GL buffer
106 Or use explicit map/unmap for render loops:
108 .. code-block:: python
110 buf = resource.map(stream=s)
111 # ... launch kernels using buf ...
112 resource.unmap(stream=s)
113 """
115 def __init__(self):
116 raise RuntimeError( 1w
117 "GraphicsResource objects cannot be instantiated directly. "
118 "Use GraphicsResource.from_gl_buffer() or GraphicsResource.from_gl_image()."
119 )
121 @classmethod
122 def from_gl_buffer(cls, int gl_buffer, *, flags=None) -> GraphicsResource:
123 """Register an OpenGL buffer object for CUDA access.
125 Parameters
126 ----------
127 gl_buffer : int
128 The OpenGL buffer name (``GLuint``) to register.
129 flags : str or sequence of str, optional
130 Registration flags specifying intended usage. Accepted values:
131 ``"none"``, ``"read_only"``, ``"write_discard"``,
132 ``"surface_load_store"``, ``"texture_gather"``.
133 Multiple flags can be combined by passing a sequence
134 (e.g., ``("surface_load_store", "read_only")``).
135 Defaults to ``None`` (no flags).
137 Returns
138 -------
139 GraphicsResource
140 A new graphics resource wrapping the registered GL buffer.
142 Raises
143 ------
144 CUDAError
145 If the registration fails (e.g., no current GL context, invalid
146 buffer name, or operating system error).
147 ValueError
148 If an unknown flag string is provided.
149 """
150 cdef GraphicsResource self = GraphicsResource.__new__(cls) 1fgnojqpibcdaerlm
151 cdef cydriver.CUgraphicsResource resource
152 cdef cydriver.GLuint cy_buffer = <cydriver.GLuint>gl_buffer 1fgnojqpibcdaerlm
153 cdef unsigned int cy_flags = _parse_register_flags(flags) 1fgnojqpibcdaerlm
154 with nogil: 1fgnojqpibcdaerlm
155 HANDLE_RETURN( 1fgnojqpibcdaerlm
156 cydriver.cuGraphicsGLRegisterBuffer(&resource, cy_buffer, cy_flags) 1fgnojqpibcdaerlm
157 )
158 self._handle = create_graphics_resource_handle(resource) 1fgnojqpibcdaerlm
159 self._mapped = False 1fgnojqpibcdaerlm
160 return self 1fgnojqpibcdaerlm
162 @classmethod
163 def from_gl_image(
164 cls, int image, int target, *, flags=None
165 ) -> GraphicsResource:
166 """Register an OpenGL texture or renderbuffer for CUDA access.
168 Parameters
169 ----------
170 image : int
171 The OpenGL texture or renderbuffer name (``GLuint``) to register.
172 target : int
173 The OpenGL target type (e.g., ``GL_TEXTURE_2D``).
174 flags : str or sequence of str, optional
175 Registration flags specifying intended usage. Accepted values:
176 ``"none"``, ``"read_only"``, ``"write_discard"``,
177 ``"surface_load_store"``, ``"texture_gather"``.
178 Multiple flags can be combined by passing a sequence
179 (e.g., ``("surface_load_store", "read_only")``).
180 Defaults to ``None`` (no flags).
182 Returns
183 -------
184 GraphicsResource
185 A new graphics resource wrapping the registered GL image.
187 Raises
188 ------
189 CUDAError
190 If the registration fails.
191 ValueError
192 If an unknown flag string is provided.
193 """
194 cdef GraphicsResource self = GraphicsResource.__new__(cls) 1k
195 cdef cydriver.CUgraphicsResource resource
196 cdef cydriver.GLuint cy_image = <cydriver.GLuint>image 1k
197 cdef cydriver.GLenum cy_target = <cydriver.GLenum>target 1k
198 cdef unsigned int cy_flags = _parse_register_flags(flags) 1k
199 with nogil: 1k
200 HANDLE_RETURN( 1k
201 cydriver.cuGraphicsGLRegisterImage(&resource, cy_image, cy_target, cy_flags) 1k
202 )
203 self._handle = create_graphics_resource_handle(resource) 1k
204 self._mapped = False 1k
205 return self 1k
207 def map(self, *, stream: Stream | None = None):
208 """Map this graphics resource for CUDA access.
210 After mapping, a CUDA device pointer into the underlying graphics
211 memory is available as a :class:`~cuda.core.Buffer`.
213 Can be used as a context manager for automatic unmapping::
215 with resource.map(stream=s) as buf:
216 # use buf.handle, buf.size, etc.
217 # automatically unmapped here
219 Or called directly for explicit control::
221 mapped = resource.map(stream=s)
222 buf = mapped._buffer # or use mapped.handle, mapped.size
223 # ... do work ...
224 resource.unmap(stream=s)
226 Parameters
227 ----------
228 stream : :class:`~cuda.core.Stream`, optional
229 The CUDA stream on which to perform the mapping. If ``None``,
230 the default stream (``0``) is used.
232 Returns
233 -------
234 _MappedBufferContext
235 An object that is both a context manager and provides access
236 to the underlying :class:`~cuda.core.Buffer`. When used with
237 ``with``, the resource is unmapped on exit.
239 Raises
240 ------
241 RuntimeError
242 If the resource is already mapped or has been closed.
243 CUDAError
244 If the mapping fails.
245 """
246 if not self._handle: 1fgnbcdae
247 raise RuntimeError("GraphicsResource has been closed") 1n
248 if self._mapped: 1fgbcdae
249 raise RuntimeError("GraphicsResource is already mapped") 1g
251 cdef cydriver.CUgraphicsResource raw = as_cu(self._handle) 1fgbcdae
252 cdef cydriver.CUstream cy_stream = <cydriver.CUstream>0 1fgbcdae
253 cdef Stream s_obj = None 1fgbcdae
254 if stream is not None: 1fgbcdae
255 s_obj = Stream_accept(stream) 1a
256 cy_stream = as_cu(s_obj._h_stream) 1a
258 cdef cydriver.CUdeviceptr dev_ptr = 0 1fgbcdae
259 cdef size_t size = 0 1fgbcdae
260 with nogil: 1fgbcdae
261 HANDLE_RETURN( 1fgbcdae
262 cydriver.cuGraphicsMapResources(1, &raw, cy_stream) 1fgbcdae
263 )
264 HANDLE_RETURN( 1fgbcdae
265 cydriver.cuGraphicsResourceGetMappedPointer(&dev_ptr, &size, raw) 1fgbcdae
266 )
267 self._mapped = True 1fgbcdae
268 buf = Buffer.from_handle(int(dev_ptr), size, owner=self) 1fgbcdae
269 return _MappedBufferContext(buf, self, stream) 1fgbcdae
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 The CUDA stream on which to perform the unmapping. If ``None``,
281 the default stream (``0``) is used.
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 if not self._handle: 1gojbcdae
291 raise RuntimeError("GraphicsResource has been closed") 1o
292 if not self._mapped: 1gjbcdae
293 raise RuntimeError("GraphicsResource is not mapped") 1j
295 cdef cydriver.CUgraphicsResource raw = as_cu(self._handle) 1gbcdae
296 cdef cydriver.CUstream cy_stream = <cydriver.CUstream>0 1gbcdae
297 if stream is not None: 1gbcdae
298 cy_stream = as_cu((<Stream>Stream_accept(stream))._h_stream) 1a
299 with nogil: 1gbcdae
300 HANDLE_RETURN( 1gbcdae
301 cydriver.cuGraphicsUnmapResources(1, &raw, cy_stream) 1gbcdae
302 )
303 self._mapped = False 1gbcdae
305 cpdef close(self):
306 """Unregister this graphics resource from CUDA.
308 If the resource is currently mapped, it is unmapped first (on the
309 default stream). After closing, the resource cannot be used again.
310 """
311 cdef cydriver.CUgraphicsResource raw
312 cdef cydriver.CUstream cy_stream
313 if not self._handle: 1fgnojqpikbcdalm
314 return 1q
315 if self._mapped: 1fgnojqpikbcdalm
316 # Best-effort unmap before unregister
317 raw = as_cu(self._handle) 1f
318 cy_stream = <cydriver.CUstream>0 1f
319 with nogil: 1f
320 cydriver.cuGraphicsUnmapResources(1, &raw, cy_stream) 1f
321 self._mapped = False 1f
322 self._handle.reset() 1fgnojqpikbcdalm
324 @property
325 def is_mapped(self) -> bool:
326 """Whether the resource is currently mapped for CUDA access."""
327 return self._mapped 1fpkbcd
329 @property
330 def handle(self) -> int:
331 """The raw ``CUgraphicsResource`` handle as a Python int."""
332 return as_intptr(self._handle) 1pikr
334 def __repr__(self):
335 mapped_str = " mapped" if self._mapped else "" 1lm
336 closed_str = " closed" if not self._handle else "" 1lm
337 return f"<GraphicsResource handle={as_intptr(self._handle):#x}{mapped_str}{closed_str}>" 1lm