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

113 statements  

« prev     ^ index     » next       coverage.py v7.14.1, created at 2026-06-13 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: 1hibfpvqwtnujcdeoagmklzxrsCADB

33 return 0 1fpvqwtuezxrsD

34 if isinstance(flags, str): 1hibnjcdoagmklCAB

35 flags = (flags,) 1hibnjcdoagmklAB

36 result = 0 1hibnjcdoagmklCAB

37 for f in flags: 1hibnjcdoagmklCAB

38 try: 1hibnjcdoagmklCAB

39 result |= _REGISTER_FLAGS[f] 1hibnjcdoagmklCAB

40 except KeyError: 1A

41 raise ValueError( 1A

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

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

44 ) from None 1A

45 return result 1hibnjcdoagmklCB

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

147 cdef cydriver.CUgraphicsResource resource 

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

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

150 with nogil: 1hibfpvqwtnjcdeoagmklzxrs

151 HANDLE_RETURN( 1hibfpvqwtnjcdeoagmklzxrs

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

153 ) 

154 self._handle = create_graphics_resource_handle(resource) 1hibfpvqwtnjcdeoagmklzxrs

155 self._mapped_buffer = None 1hibfpvqwtnjcdeoagmklzxrs

156 self._context_manager_stream = stream 1hibfpvqwtnjcdeoagmklzxrs

157 self._entered_buffer = None 1hibfpvqwtnjcdeoagmklzxrs

158 return self 1hibfpvqwtnjcdeoagmklzxrs

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

197 cdef cydriver.CUgraphicsResource resource 

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

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

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

201 with nogil: 1u

202 HANDLE_RETURN( 1u

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

204 ) 

205 self._handle = create_graphics_resource_handle(resource) 1u

206 self._mapped_buffer = None 1u

207 self._context_manager_stream = None 1u

208 self._entered_buffer = None 1u

209 return self 1u

210  

211 def _get_mapped_buffer(self) -> object: 

212 cdef Buffer buf 

213 if self._mapped_buffer is None: 1hibfpvqwtnujcdeoagmklxrs

214 return None 1hibfpvqwtnujcdeoagmklxrs

215 buf = <Buffer>self._mapped_buffer 1hbfcdeag

216 if not buf._h_ptr: 1hbfcdeag

217 self._mapped_buffer = None 1hcdg

218 return None 1hcdg

219 return self._mapped_buffer 1hbfcdea

220  

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

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

223  

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

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

226  

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

228  

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

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

231 # automatically unmapped here 

232  

233 Parameters 

234 ---------- 

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

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

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

238 the default stream. 

239  

240 Returns 

241 ------- 

242 Buffer 

243 A buffer whose lifetime controls when the graphics resource is 

244 unmapped. 

245  

246 Raises 

247 ------ 

248 RuntimeError 

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

250 CUDAError 

251 If the mapping fails. 

252 """ 

253 cdef Stream s_obj 

254 cdef cydriver.CUgraphicsResource raw 

255 cdef cydriver.CUstream cy_stream 

256 cdef cydriver.CUdeviceptr dev_ptr = 0 1hibfpjcdeagkl

257 cdef size_t size = 0 1hibfpjcdeagkl

258 cdef Buffer buf 

259 if not self._handle: 1hibfpjcdeagkl

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

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

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

263  

264 s_obj = Stream_accept(stream) 1hibfjcdeagkl

265 raw = as_cu(self._handle) 1hibfjcdeagkl

266 cy_stream = as_cu(s_obj._h_stream) 1hibfjcdeagkl

267 with nogil: 1hibfjcdeagkl

268 HANDLE_RETURN( 1hibfjcdeagkl

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

270 ) 

271 HANDLE_RETURN( 1hibfjcdeagkl

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

273 ) 

274 buf = Buffer_from_deviceptr_handle( 1hibfjcdeagkl

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

276 size, 

277 None, 

278 None, 

279 ) 

280 self._mapped_buffer = buf 1hibfjcdeagkl

281 return buf 1hibfjcdeagkl

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

303 cdef Buffer buf 

304 if not self._handle: 1fvqa

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

306 buf_obj = self._get_mapped_buffer() 1fqa

307 if buf_obj is None: 1fqa

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

309 buf = <Buffer>buf_obj 1fa

310 buf.close(stream=stream) 1fa

311 self._mapped_buffer = None 1fa

312  

313 def __enter__(self) -> object: 

314 if self._context_manager_stream is None: 1em

315 return self 1m

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

317 return self._entered_buffer 1e

318  

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

320 self.close() 1em

321 return False 1em

322  

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

324 """Unregister this graphics resource from CUDA. 

325  

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

327 closing, the resource cannot be used again. 

328  

329 Parameters 

330 ---------- 

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

332 Optional override for the stream used to close the currently 

333 mapped buffer, if one exists. 

334 """ 

335 cdef object buf_obj 

336 cdef Buffer buf 

337 if not self._handle: 1bfpvqwtnucdeoagmxrs

338 return 1w

339 buf_obj = self._get_mapped_buffer() 1bfpvqwtnucdeoagmxrs

340 if buf_obj is not None: 1bfpvqwtnucdeoagmxrs

341 buf = <Buffer>buf_obj 1be

342 buf.close(stream=stream) 1be

343 self._mapped_buffer = None 1be

344 self._handle.reset() 1bfpvqwtnucdeoagmxrs

345 self._context_manager_stream = None 1bfpvqwtnucdeoagmxrs

346 self._entered_buffer = None 1bfpvqwtnucdeoagmxrs

347  

348 @property 

349 def is_mapped(self) -> bool: 

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

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

352  

353 @property 

354 def handle(self) -> int: 

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

356 return as_intptr(self._handle) 1tnuamz

357  

358 @property 

359 def resource_handle(self) -> int: 

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

361 return self.handle 1t

362  

363 def __repr__(self) -> str: 

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

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

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