Coverage for cuda / core / _graphics.pyx: 100.00%

111 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-03-25 01:07 +0000

1# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. 

2# 

3# SPDX-License-Identifier: Apache-2.0 

4  

5from __future__ import annotations 

6  

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, default_stream 

16from cuda.core._utils.cuda_utils cimport HANDLE_RETURN 

17  

18__all__ = ['GraphicsResource'] 

19  

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} 

27  

28  

29def _parse_register_flags(flags): 

30 if flags is None: 1ijbgpvqwtoukcdeafhnlmzxrsCADB

31 return 0 1gpvqwtuezxrsD

32 if isinstance(flags, str): 1ijbokcdafhnlmCAB

33 flags = (flags,) 1ijbokcdafhnlmAB

34 result = 0 1ijbokcdafhnlmCAB

35 for f in flags: 1ijbokcdafhnlmCAB

36 try: 1ijbokcdafhnlmCAB

37 result |= _REGISTER_FLAGS[f] 1ijbokcdafhnlmCAB

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 1ijbokcdafhnlmCB

44  

45  

46cdef class GraphicsResource: 

47 """RAII wrapper for a CUDA graphics resource (``CUgraphicsResource``). 

48  

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. 

52  

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. 

57  

58 The resource is automatically unregistered when :meth:`close` is called or 

59 when the object is garbage collected. 

60  

61 :class:`GraphicsResource` objects should not be instantiated directly. 

62 Use the factory classmethods :meth:`from_gl_buffer` or :meth:`from_gl_image`. 

63  

64 Examples 

65 -------- 

66 Register an OpenGL VBO, map it to get a buffer, and write to it from CUDA: 

67  

68 .. code-block:: python 

69  

70 resource = GraphicsResource.from_gl_buffer(vbo) 

71  

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 

75  

76 Or scope registration separately from mapping: 

77  

78 .. code-block:: python 

79  

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 """ 

85  

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 ) 

91  

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. 

95  

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:: 

110  

111 with GraphicsResource.from_gl_buffer(vbo, stream=s) as buf: 

112 view = StridedMemoryView.from_buffer(buf, shape=(256,), dtype=np.float32) 

113  

114 If omitted, the returned resource can still be used as a context 

115 manager to scope registration and automatic cleanup:: 

116  

117 with GraphicsResource.from_gl_buffer(vbo) as resource: 

118 with resource.map(stream=s) as buf: 

119 ... 

120  

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. 

129  

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) 1ijbgpvqwtokcdeafhnlmzxrs

139 cdef cydriver.CUgraphicsResource resource 

140 cdef cydriver.GLuint cy_buffer = <cydriver.GLuint>gl_buffer 1ijbgpvqwtokcdeafhnlmzxrs

141 cdef unsigned int cy_flags = _parse_register_flags(flags) 1ijbgpvqwtokcdeafhnlmzxrs

142 with nogil: 1ijbgpvqwtokcdeafhnlmzxrs

143 HANDLE_RETURN( 1ijbgpvqwtokcdeafhnlmzxrs

144 cydriver.cuGraphicsGLRegisterBuffer(&resource, cy_buffer, cy_flags) 1ijbgpvqwtokcdeafhnlmzxrs

145 ) 

146 self._handle = create_graphics_resource_handle(resource) 1ijbgpvqwtokcdeafhnlmzxrs

147 self._mapped_buffer = None 1ijbgpvqwtokcdeafhnlmzxrs

148 self._context_manager_stream = stream 1ijbgpvqwtokcdeafhnlmzxrs

149 self._entered_buffer = None 1ijbgpvqwtokcdeafhnlmzxrs

150 return self 1ijbgpvqwtokcdeafhnlmzxrs

151  

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. 

157  

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). 

171  

172 Returns 

173 ------- 

174 GraphicsResource 

175 A new graphics resource wrapping the registered GL image. 

176  

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

198  

199 def _get_mapped_buffer(self): 

200 cdef Buffer buf 

201 if self._mapped_buffer is None: 1ijbgpvqwtoukcdeafhnlmxrs

202 return None 1ijbgpvqwtoukcdeafhnlmxrs

203 buf = <Buffer>self._mapped_buffer 1ibgcdeafh

204 if not buf._h_ptr: 1ibgcdeafh

205 self._mapped_buffer = None 1icdfh

206 return None 1icdfh

207 return self._mapped_buffer 1ibgcdea

208  

209 def map(self, *, stream: Stream | None = None) -> Buffer: 

210 """Map this graphics resource for CUDA access. 

211  

212 After mapping, a CUDA device pointer into the underlying graphics 

213 memory is available as a :class:`~cuda.core.Buffer`. 

214  

215 Can be used as a context manager for automatic unmapping:: 

216  

217 with resource.map(stream=s) as buf: 

218 # use buf.handle, buf.size, etc. 

219 # automatically unmapped here 

220  

221 Parameters 

222 ---------- 

223 stream : :class:`~cuda.core.Stream`, optional 

224 The CUDA stream on which to perform the mapping. If ``None``, 

225 the current default stream is used. 

226  

227 Returns 

228 ------- 

229 Buffer 

230 A buffer whose lifetime controls when the graphics resource is 

231 unmapped. 

232  

233 Raises 

234 ------ 

235 RuntimeError 

236 If the resource is already mapped or has been closed. 

237 CUDAError 

238 If the mapping fails. 

239 """ 

240 cdef Stream s_obj 

241 cdef cydriver.CUgraphicsResource raw 

242 cdef cydriver.CUstream cy_stream 

243 cdef cydriver.CUdeviceptr dev_ptr = 0 1ijbgpkcdeafhlm

244 cdef size_t size = 0 1ijbgpkcdeafhlm

245 cdef Buffer buf 

246 if not self._handle: 1ijbgpkcdeafhlm

247 raise RuntimeError("GraphicsResource has been closed") 1p

248 if self._get_mapped_buffer() is not None: 1ijbgkcdeafhlm

249 raise RuntimeError("GraphicsResource is already mapped") 1g

250  

251 s_obj = default_stream() if stream is None else Stream_accept(stream) 1ijbgkcdeafhlm

252 raw = as_cu(self._handle) 1ijbgkcdeafhlm

253 cy_stream = as_cu(s_obj._h_stream) 1ijbgkcdeafhlm

254 with nogil: 1ijbgkcdeafhlm

255 HANDLE_RETURN( 1ijbgkcdeafhlm

256 cydriver.cuGraphicsMapResources(1, &raw, cy_stream) 1ijbgkcdeafhlm

257 ) 

258 HANDLE_RETURN( 1ijbgkcdeafhlm

259 cydriver.cuGraphicsResourceGetMappedPointer(&dev_ptr, &size, raw) 1ijbgkcdeafhlm

260 ) 

261 buf = Buffer_from_deviceptr_handle( 1ijbgkcdeafhlm

262 deviceptr_create_mapped_graphics(dev_ptr, self._handle, s_obj._h_stream), 1ijbgkcdeafhlm

263 size, 

264 None, 

265 None, 

266 ) 

267 self._mapped_buffer = buf 1ijbgkcdeafhlm

268 return buf 1ijbgkcdeafhlm

269  

270 def unmap(self, *, stream: Stream | None = None): 

271 """Unmap this graphics resource, releasing it back to the graphics API. 

272  

273 After unmapping, the :class:`~cuda.core.Buffer` previously returned 

274 by :meth:`map` must not be used. 

275  

276 Parameters 

277 ---------- 

278 stream : :class:`~cuda.core.Stream`, optional 

279 If provided, overrides the stream that will be used when the 

280 mapped buffer is closed. Otherwise the mapping stream is reused. 

281  

282 Raises 

283 ------ 

284 RuntimeError 

285 If the resource is not currently mapped or has been closed. 

286 CUDAError 

287 If the unmapping fails. 

288 """ 

289 cdef object buf_obj 

290 cdef Buffer buf 

291 if not self._handle: 1gvqa

292 raise RuntimeError("GraphicsResource has been closed") 1v

293 buf_obj = self._get_mapped_buffer() 1gqa

294 if buf_obj is None: 1gqa

295 raise RuntimeError("GraphicsResource is not mapped") 1q

296 buf = <Buffer>buf_obj 1ga

297 buf.close(stream=stream) 1ga

298 self._mapped_buffer = None 1ga

299  

300 def __enter__(self): 

301 if self._context_manager_stream is None: 1en

302 return self 1n

303 self._entered_buffer = self.map(stream=self._context_manager_stream) 1e

304 return self._entered_buffer 1e

305  

306 def __exit__(self, exc_type, exc_val, exc_tb): 

307 self.close() 1en

308 return False 1en

309  

310 cpdef close(self, stream=None): 

311 """Unregister this graphics resource from CUDA. 

312  

313 If the resource is currently mapped, it is unmapped first. After 

314 closing, the resource cannot be used again. 

315  

316 Parameters 

317 ---------- 

318 stream : :class:`~cuda.core.Stream`, optional 

319 Optional override for the stream used to close the currently 

320 mapped buffer, if one exists. 

321 """ 

322 cdef object buf_obj 

323 cdef Buffer buf 

324 if not self._handle: 1bgpvqwtoucdeafhnxrs

325 return 1w

326 buf_obj = self._get_mapped_buffer() 1bgpvqwtoucdeafhnxrs

327 if buf_obj is not None: 1bgpvqwtoucdeafhnxrs

328 buf = <Buffer>buf_obj 1be

329 buf.close(stream=stream) 1be

330 self._mapped_buffer = None 1be

331 self._handle.reset() 1bgpvqwtoucdeafhnxrs

332 self._context_manager_stream = None 1bgpvqwtoucdeafhnxrs

333 self._entered_buffer = None 1bgpvqwtoucdeafhnxrs

334  

335 @property 

336 def is_mapped(self) -> bool: 

337 """Whether the resource is currently mapped for CUDA access.""" 

338 return self._get_mapped_buffer() is not None 1ibtucdafnrs

339  

340 @property 

341 def handle(self) -> int: 

342 """The raw ``CUgraphicsResource`` handle as a Python int.""" 

343 return as_intptr(self._handle) 1touanz

344  

345 @property 

346 def resource_handle(self) -> int: 

347 """Alias for :attr:`handle`.""" 

348 return self.handle 1t

349  

350 def __repr__(self): 

351 mapped_str = " mapped" if self.is_mapped else "" 1rs

352 closed_str = " closed" if not self._handle else "" 1rs

353 return f"<GraphicsResource handle={as_intptr(self._handle):#x}{mapped_str}{closed_str}>" 1rs