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

111 statements  

« prev     ^ index     » next       coverage.py v7.14.0, created at 2026-05-22 01:37 +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 

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

31 return 0 1fpvqwtuezxrsD

32 if isinstance(flags, str): 1hibnjcdoagmklCAB

33 flags = (flags,) 1hibnjcdoagmklAB

34 result = 0 1hibnjcdoagmklCAB

35 for f in flags: 1hibnjcdoagmklCAB

36 try: 1hibnjcdoagmklCAB

37 result |= _REGISTER_FLAGS[f] 1hibnjcdoagmklCAB

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

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

139 cdef cydriver.CUgraphicsResource resource 

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

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

142 with nogil: 1hibfpvqwtnjcdeoagmklzxrs

143 HANDLE_RETURN( 1hibfpvqwtnjcdeoagmklzxrs

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

145 ) 

146 self._handle = create_graphics_resource_handle(resource) 1hibfpvqwtnjcdeoagmklzxrs

147 self._mapped_buffer = None 1hibfpvqwtnjcdeoagmklzxrs

148 self._context_manager_stream = stream 1hibfpvqwtnjcdeoagmklzxrs

149 self._entered_buffer = None 1hibfpvqwtnjcdeoagmklzxrs

150 return self 1hibfpvqwtnjcdeoagmklzxrs

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

202 return None 1hibfpvqwtnujcdeoagmklxrs

203 buf = <Buffer>self._mapped_buffer 1hbfcdeag

204 if not buf._h_ptr: 1hbfcdeag

205 self._mapped_buffer = None 1hcdg

206 return None 1hcdg

207 return self._mapped_buffer 1hbfcdea

208  

209 def map(self, *, stream: Stream) -> 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` 

224 Keyword-only. The CUDA stream on which to perform the mapping. 

225 Must be passed explicitly; pass ``device.default_stream`` to use 

226 the default stream. 

227  

228 Returns 

229 ------- 

230 Buffer 

231 A buffer whose lifetime controls when the graphics resource is 

232 unmapped. 

233  

234 Raises 

235 ------ 

236 RuntimeError 

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

238 CUDAError 

239 If the mapping fails. 

240 """ 

241 cdef Stream s_obj 

242 cdef cydriver.CUgraphicsResource raw 

243 cdef cydriver.CUstream cy_stream 

244 cdef cydriver.CUdeviceptr dev_ptr = 0 1hibfpjcdeagkl

245 cdef size_t size = 0 1hibfpjcdeagkl

246 cdef Buffer buf 

247 if not self._handle: 1hibfpjcdeagkl

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

249 if self._get_mapped_buffer() is not None: 1hibfjcdeagkl

250 raise RuntimeError("GraphicsResource is already mapped") 1f

251  

252 s_obj = Stream_accept(stream) 1hibfjcdeagkl

253 raw = as_cu(self._handle) 1hibfjcdeagkl

254 cy_stream = as_cu(s_obj._h_stream) 1hibfjcdeagkl

255 with nogil: 1hibfjcdeagkl

256 HANDLE_RETURN( 1hibfjcdeagkl

257 cydriver.cuGraphicsMapResources(1, &raw, cy_stream) 1hibfjcdeagkl

258 ) 

259 HANDLE_RETURN( 1hibfjcdeagkl

260 cydriver.cuGraphicsResourceGetMappedPointer(&dev_ptr, &size, raw) 1hibfjcdeagkl

261 ) 

262 buf = Buffer_from_deviceptr_handle( 1hibfjcdeagkl

263 deviceptr_create_mapped_graphics(dev_ptr, self._handle, s_obj._h_stream), 1hibfjcdeagkl

264 size, 

265 None, 

266 None, 

267 ) 

268 self._mapped_buffer = buf 1hibfjcdeagkl

269 return buf 1hibfjcdeagkl

270  

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

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

273  

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

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

276  

277 Parameters 

278 ---------- 

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

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

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

282  

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 cdef object buf_obj 

291 cdef Buffer buf 

292 if not self._handle: 1fvqa

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

294 buf_obj = self._get_mapped_buffer() 1fqa

295 if buf_obj is None: 1fqa

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

297 buf = <Buffer>buf_obj 1fa

298 buf.close(stream=stream) 1fa

299 self._mapped_buffer = None 1fa

300  

301 def __enter__(self): 

302 if self._context_manager_stream is None: 1em

303 return self 1m

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

305 return self._entered_buffer 1e

306  

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

308 self.close() 1em

309 return False 1em

310  

311 cpdef close(self, stream=None): 

312 """Unregister this graphics resource from CUDA. 

313  

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

315 closing, the resource cannot be used again. 

316  

317 Parameters 

318 ---------- 

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

320 Optional override for the stream used to close the currently 

321 mapped buffer, if one exists. 

322 """ 

323 cdef object buf_obj 

324 cdef Buffer buf 

325 if not self._handle: 1bfpvqwtnucdeoagmxrs

326 return 1w

327 buf_obj = self._get_mapped_buffer() 1bfpvqwtnucdeoagmxrs

328 if buf_obj is not None: 1bfpvqwtnucdeoagmxrs

329 buf = <Buffer>buf_obj 1be

330 buf.close(stream=stream) 1be

331 self._mapped_buffer = None 1be

332 self._handle.reset() 1bfpvqwtnucdeoagmxrs

333 self._context_manager_stream = None 1bfpvqwtnucdeoagmxrs

334 self._entered_buffer = None 1bfpvqwtnucdeoagmxrs

335  

336 @property 

337 def is_mapped(self) -> bool: 

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

339 return self._get_mapped_buffer() is not None 1hbtucdamrs

340  

341 @property 

342 def handle(self) -> int: 

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

344 return as_intptr(self._handle) 1tnuamz

345  

346 @property 

347 def resource_handle(self) -> int: 

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

349 return self.handle 1t

350  

351 def __repr__(self): 

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

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

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