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

113 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-07-29 01:38 +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: 1hibfnsotrmAjcdewagxklyupqCzDB

33 return 0 1fnsotrAeyupqD

34 if isinstance(flags, str): 1hibmjcdwagxklCzB

35 flags = (flags,) 1hibmjcdwagxklzB

36 result = 0 1hibmjcdwagxklCzB

37 for f in flags: 1hibmjcdwagxklCzB

38 try: 1hibmjcdwagxklCzB

39 result |= _REGISTER_FLAGS[f] 1hibmjcdwagxklCzB

40 except KeyError: 1z

41 raise ValueError( 1z

42 f"Unknown register flag {f!r}. " 1z

43 f"Valid flags: {', '.join(sorted(_REGISTER_FLAGS))}" 1z

44 ) from None 1z

45 return result 1hibmjcdwagxklCB

46  

47  

48cdef class GraphicsResource: 

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

50  

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. 

54  

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. 

59  

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

61 when the object is garbage collected. 

62  

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

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

65  

66 Examples 

67 -------- 

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

69  

70 .. code-block:: python 

71  

72 resource = GraphicsResource.from_gl_buffer(vbo) 

73  

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 

77  

78 Or scope registration separately from mapping: 

79  

80 .. code-block:: python 

81  

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

87  

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 ) 

93  

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. 

103  

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

118  

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

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

121  

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

123 manager to scope registration and automatic cleanup:: 

124  

125 with GraphicsResource.from_gl_buffer(vbo) as resource: 

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

127 ... 

128  

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. 

137  

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

147 cdef cydriver.CUgraphicsResource resource 

148 cdef cydriver.GLuint cy_buffer = <cydriver.GLuint>gl_buffer 1hibfnsotrmjcdewagxklyupq

149 cdef unsigned int cy_flags = _parse_register_flags(flags) 1hibfnsotrmjcdewagxklyupq

150 with nogil: 1hibfnsotrmjcdewagxklyupq

151 HANDLE_RETURN( 1hibfnsotrmjcdewagxklyupq

152 cydriver.cuGraphicsGLRegisterBuffer(&resource, cy_buffer, cy_flags) 1hibfnsotrmjcdewagxklyupq

153 ) 

154 self._handle = create_graphics_resource_handle(resource) 1hibfnsotrmjcdeagklyupq

155 self._mapped_buffer = None 1hibfnsotrmjcdeagklyupq

156 self._context_manager_stream = stream 1hibfnsotrmjcdeagklyupq

157 self._entered_buffer = None 1hibfnsotrmjcdeagklyupq

158 return self 1hibfnsotrmjcdeagklyupq

159  

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. 

169  

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

183  

184 Returns 

185 ------- 

186 GraphicsResource 

187 A new graphics resource wrapping the registered GL image. 

188  

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

197 cdef cydriver.CUgraphicsResource resource 

198 cdef cydriver.GLuint cy_image = <cydriver.GLuint>image 1A

199 cdef cydriver.GLenum cy_target = <cydriver.GLenum>target 1A

200 cdef unsigned int cy_flags = _parse_register_flags(flags) 1A

201 with nogil: 1A

202 HANDLE_RETURN( 1A

203 cydriver.cuGraphicsGLRegisterImage(&resource, cy_image, cy_target, cy_flags) 1A

204 ) 

205 self._handle = create_graphics_resource_handle(resource) 

206 self._mapped_buffer = None 

207 self._context_manager_stream = None 

208 self._entered_buffer = None 

209 return self 

210  

211 def _get_mapped_buffer(self) -> object: 

212 if self._mapped_buffer is None: 1hibfnsotrmjcdeagklupq

213 return None 1hibfnsotrmjcdeagklupq

214 cdef Buffer buf = <Buffer>self._mapped_buffer 1hbfcdeag

215 if not buf._h_ptr: 1hbfcdeag

216 self._mapped_buffer = None 1hcdg

217 return None 1hcdg

218 return self._mapped_buffer 1hbfcdea

219  

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

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

222  

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

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

225  

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

227  

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

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

230 # automatically unmapped here 

231  

232 Parameters 

233 ---------- 

234 stream : :class:`~cuda.core.Stream` 

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

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

237 the default stream. 

238  

239 Returns 

240 ------- 

241 Buffer 

242 A buffer whose lifetime controls when the graphics resource is 

243 unmapped. 

244  

245 Raises 

246 ------ 

247 RuntimeError 

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

249 CUDAError 

250 If the mapping fails. 

251 """ 

252 cdef cydriver.CUdeviceptr dev_ptr = 0 1hibfnjcdeagkl

253 cdef size_t size = 0 1hibfnjcdeagkl

254 if not self._handle: 1hibfnjcdeagkl

255 raise RuntimeError("GraphicsResource has been closed") 1n

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

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

258  

259 cdef Stream s_obj = Stream_accept(stream) 1hibfjcdeagkl

260 cdef cydriver.CUgraphicsResource raw = as_cu(self._handle) 1hibfjcdeagkl

261 cdef cydriver.CUstream cy_stream = as_cu(s_obj._h_stream) 1hibfjcdeagkl

262 with nogil: 1hibfjcdeagkl

263 HANDLE_RETURN( 1hibfjcdeagkl

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

265 ) 

266 HANDLE_RETURN( 1hibfjcdeagkl

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

268 ) 

269 cdef Buffer buf = Buffer_from_deviceptr_handle( 1hibfjcdeagkl

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

271 size, 

272 None, 

273 None, 

274 ) 

275 self._mapped_buffer = buf 1hibfjcdeagkl

276 return buf 1hibfjcdeagkl

277  

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

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

280  

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

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

283  

284 Parameters 

285 ---------- 

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

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

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

289  

290 Raises 

291 ------ 

292 RuntimeError 

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

294 CUDAError 

295 If the unmapping fails. 

296 """ 

297 if not self._handle: 1fsoa

298 raise RuntimeError("GraphicsResource has been closed") 1s

299 cdef object buf_obj = self._get_mapped_buffer() 1foa

300 if buf_obj is None: 1foa

301 raise RuntimeError("GraphicsResource is not mapped") 1o

302 cdef Buffer buf = <Buffer>buf_obj 1fa

303 buf.close(stream=stream) 1fa

304 self._mapped_buffer = None 1fa

305  

306 def __enter__(self) -> object: 

307 if self._context_manager_stream is None: 1e

308 return self 

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

310 return self._entered_buffer 1e

311  

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

313 self.close() 1e

314 return False 1e

315  

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

317 """Unregister this graphics resource from CUDA. 

318  

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

320 closing, the resource cannot be used again. 

321  

322 Parameters 

323 ---------- 

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

325 Optional override for the stream used to close the currently 

326 mapped buffer, if one exists. 

327 """ 

328 cdef Buffer buf 

329 if not self._handle: 1bfnsotrmcdeagupq

330 return 1t

331 cdef object buf_obj = self._get_mapped_buffer() 1bfnsotrmcdeagupq

332 if buf_obj is not None: 1bfnsotrmcdeagupq

333 buf = <Buffer>buf_obj 1be

334 buf.close(stream=stream) 1be

335 self._mapped_buffer = None 1be

336 self._handle.reset() 1bfnsotrmcdeagupq

337 self._context_manager_stream = None 1bfnsotrmcdeagupq

338 self._entered_buffer = None 1bfnsotrmcdeagupq

339  

340 @property 

341 def is_mapped(self) -> bool: 

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

343 return self._get_mapped_buffer() is not None 1hbrcdapq

344  

345 @property 

346 def handle(self) -> int: 

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

348 return as_intptr(self._handle) 1rmay

349  

350 @property 

351 def resource_handle(self) -> int: 

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

353 return self.handle 1r

354  

355 def __repr__(self) -> str: 

356 mapped_str = " mapped" if self.is_mapped else "" 1pq

357 closed_str = " closed" if not self._handle else "" 1pq

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