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

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 typing import Sequence 

8  

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 

19  

20__all__ = ['GraphicsResource'] 

21  

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} 

29  

30  

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

46  

47  

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

52  

53  

54cdef class GraphicsResource: 

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

56  

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. 

60  

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. 

65  

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

67 when the object is garbage collected. 

68  

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

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

71  

72 Examples 

73 -------- 

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

75  

76 .. code-block:: python 

77  

78 resource = GraphicsResource.from_gl_buffer(vbo) 

79  

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 

83  

84 Or scope registration separately from mapping: 

85  

86 .. code-block:: python 

87  

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

93  

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 ) 

99  

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. 

109  

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

124  

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

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

127  

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

129 manager to scope registration and automatic cleanup:: 

130  

131 with GraphicsResource.from_gl_buffer(vbo) as resource: 

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

133 ... 

134  

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. 

143  

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

165  

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. 

175  

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

189  

190 Returns 

191 ------- 

192 GraphicsResource 

193 A new graphics resource wrapping the registered GL image. 

194  

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

216  

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

225  

226 def map(self, *, stream: Stream) -> Buffer: 

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

228  

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

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

231  

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

233  

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

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

236 # automatically unmapped here 

237  

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. 

244  

245 Returns 

246 ------- 

247 Buffer 

248 A buffer whose lifetime controls when the graphics resource is 

249 unmapped. 

250  

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

263  

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

282  

283 def unmap(self, *, stream: Stream | None = None) -> None: 

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

285  

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

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

288  

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. 

294  

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

309  

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

315  

316 def __exit__(self, exc_type: type | None, exc_val: BaseException | None, exc_tb: object) -> bool: 

317 self.close() 1el

318 return False 1el

319  

320 cpdef close(self, object stream=None): 

321 """Unregister this graphics resource from CUDA. 

322  

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

324 closing, the resource cannot be used again. 

325  

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

343  

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

348  

349 @property 

350 def handle(self) -> int: 

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

352 return as_intptr(self._handle) 1yastml

353  

354 @property 

355 def is_closed(self) -> bool: 

356 """Whether this graphics resource has been closed.""" 

357 return self._handle.get() == NULL 1w

358  

359 @property 

360 def resource_handle(self) -> int: 

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

362 return self.handle 1s

363  

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