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

113 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-03-08 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 as_cu, 

11 as_intptr, 

12) 

13from cuda.core._stream cimport Stream, Stream_accept 

14from cuda.core._utils.cuda_utils cimport HANDLE_RETURN 

15  

16from cuda.core._memory import Buffer 

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

31 return 0 1gnojqpkrlmv

32 if isinstance(flags, str): 1fibcdaeust

33 flags = (flags,) 1fibcdaest

34 result = 0 1fibcdaeust

35 for f in flags: 1fibcdaeust

36 try: 1fibcdaeust

37 result |= _REGISTER_FLAGS[f] 1fibcdaeust

38 except KeyError: 1s

39 raise ValueError( 1s

40 f"Unknown register flag {f!r}. " 1s

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

42 ) from None 1s

43 return result 1fibcdaeut

44  

45  

46class _MappedBufferContext: 

47 """Context manager returned by :meth:`GraphicsResource.map`. 

48  

49 Wraps a :class:`~cuda.core.Buffer` and ensures the graphics resource 

50 is unmapped when the context exits. Can also be used without ``with`` 

51 by calling :meth:`GraphicsResource.unmap` explicitly. 

52 """ 

53 __slots__ = ('_buffer', '_resource', '_stream') 

54  

55 def __init__(self, buffer, resource, stream): 

56 self._buffer = buffer 1fgbcdae

57 self._resource = resource 1fgbcdae

58 self._stream = stream 1fgbcdae

59  

60 def __enter__(self): 

61 return self._buffer 1bcae

62  

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

64 self._resource.unmap(stream=self._stream) 1bcae

65 return False 1bcae

66  

67 # Delegate Buffer attributes so the return value of map() is directly usable 

68 @property 

69 def handle(self): 

70 return self._buffer.handle 1d

71  

72 @property 

73 def size(self): 

74 return self._buffer.size 1d

75  

76 def __repr__(self): 

77 return repr(self._buffer) 

78  

79  

80cdef class GraphicsResource: 

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

82  

83 A :class:`GraphicsResource` represents an OpenGL buffer or image that has 

84 been registered for access by CUDA. This enables zero-copy sharing of GPU 

85 data between CUDA compute kernels and graphics renderers. 

86  

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

88 when the object is garbage collected. 

89  

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

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

92  

93 Examples 

94 -------- 

95 Register an OpenGL VBO, map it to get a :class:`~cuda.core.Buffer`, and 

96 write to it from CUDA: 

97  

98 .. code-block:: python 

99  

100 resource = GraphicsResource.from_gl_buffer(vbo) 

101  

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

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

104 # view.ptr is a CUDA device pointer into the GL buffer 

105  

106 Or use explicit map/unmap for render loops: 

107  

108 .. code-block:: python 

109  

110 buf = resource.map(stream=s) 

111 # ... launch kernels using buf ... 

112 resource.unmap(stream=s) 

113 """ 

114  

115 def __init__(self): 

116 raise RuntimeError( 1w

117 "GraphicsResource objects cannot be instantiated directly. " 

118 "Use GraphicsResource.from_gl_buffer() or GraphicsResource.from_gl_image()." 

119 ) 

120  

121 @classmethod 

122 def from_gl_buffer(cls, int gl_buffer, *, flags=None) -> GraphicsResource: 

123 """Register an OpenGL buffer object for CUDA access. 

124  

125 Parameters 

126 ---------- 

127 gl_buffer : int 

128 The OpenGL buffer name (``GLuint``) to register. 

129 flags : str or sequence of str, optional 

130 Registration flags specifying intended usage. Accepted values: 

131 ``"none"``, ``"read_only"``, ``"write_discard"``, 

132 ``"surface_load_store"``, ``"texture_gather"``. 

133 Multiple flags can be combined by passing a sequence 

134 (e.g., ``("surface_load_store", "read_only")``). 

135 Defaults to ``None`` (no flags). 

136  

137 Returns 

138 ------- 

139 GraphicsResource 

140 A new graphics resource wrapping the registered GL buffer. 

141  

142 Raises 

143 ------ 

144 CUDAError 

145 If the registration fails (e.g., no current GL context, invalid 

146 buffer name, or operating system error). 

147 ValueError 

148 If an unknown flag string is provided. 

149 """ 

150 cdef GraphicsResource self = GraphicsResource.__new__(cls) 1fgnojqpibcdaerlm

151 cdef cydriver.CUgraphicsResource resource 

152 cdef cydriver.GLuint cy_buffer = <cydriver.GLuint>gl_buffer 1fgnojqpibcdaerlm

153 cdef unsigned int cy_flags = _parse_register_flags(flags) 1fgnojqpibcdaerlm

154 with nogil: 1fgnojqpibcdaerlm

155 HANDLE_RETURN( 1fgnojqpibcdaerlm

156 cydriver.cuGraphicsGLRegisterBuffer(&resource, cy_buffer, cy_flags) 1fgnojqpibcdaerlm

157 ) 

158 self._handle = create_graphics_resource_handle(resource) 1fgnojqpibcdaerlm

159 self._mapped = False 1fgnojqpibcdaerlm

160 return self 1fgnojqpibcdaerlm

161  

162 @classmethod 

163 def from_gl_image( 

164 cls, int image, int target, *, flags=None 

165 ) -> GraphicsResource: 

166 """Register an OpenGL texture or renderbuffer for CUDA access. 

167  

168 Parameters 

169 ---------- 

170 image : int 

171 The OpenGL texture or renderbuffer name (``GLuint``) to register. 

172 target : int 

173 The OpenGL target type (e.g., ``GL_TEXTURE_2D``). 

174 flags : str or sequence of str, optional 

175 Registration flags specifying intended usage. Accepted values: 

176 ``"none"``, ``"read_only"``, ``"write_discard"``, 

177 ``"surface_load_store"``, ``"texture_gather"``. 

178 Multiple flags can be combined by passing a sequence 

179 (e.g., ``("surface_load_store", "read_only")``). 

180 Defaults to ``None`` (no flags). 

181  

182 Returns 

183 ------- 

184 GraphicsResource 

185 A new graphics resource wrapping the registered GL image. 

186  

187 Raises 

188 ------ 

189 CUDAError 

190 If the registration fails. 

191 ValueError 

192 If an unknown flag string is provided. 

193 """ 

194 cdef GraphicsResource self = GraphicsResource.__new__(cls) 1k

195 cdef cydriver.CUgraphicsResource resource 

196 cdef cydriver.GLuint cy_image = <cydriver.GLuint>image 1k

197 cdef cydriver.GLenum cy_target = <cydriver.GLenum>target 1k

198 cdef unsigned int cy_flags = _parse_register_flags(flags) 1k

199 with nogil: 1k

200 HANDLE_RETURN( 1k

201 cydriver.cuGraphicsGLRegisterImage(&resource, cy_image, cy_target, cy_flags) 1k

202 ) 

203 self._handle = create_graphics_resource_handle(resource) 1k

204 self._mapped = False 1k

205 return self 1k

206  

207 def map(self, *, stream: Stream | None = None): 

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

209  

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

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

212  

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

214  

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

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

217 # automatically unmapped here 

218  

219 Or called directly for explicit control:: 

220  

221 mapped = resource.map(stream=s) 

222 buf = mapped._buffer # or use mapped.handle, mapped.size 

223 # ... do work ... 

224 resource.unmap(stream=s) 

225  

226 Parameters 

227 ---------- 

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

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

230 the default stream (``0``) is used. 

231  

232 Returns 

233 ------- 

234 _MappedBufferContext 

235 An object that is both a context manager and provides access 

236 to the underlying :class:`~cuda.core.Buffer`. When used with 

237 ``with``, the resource is unmapped on exit. 

238  

239 Raises 

240 ------ 

241 RuntimeError 

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

243 CUDAError 

244 If the mapping fails. 

245 """ 

246 if not self._handle: 1fgnbcdae

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

248 if self._mapped: 1fgbcdae

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

250  

251 cdef cydriver.CUgraphicsResource raw = as_cu(self._handle) 1fgbcdae

252 cdef cydriver.CUstream cy_stream = <cydriver.CUstream>0 1fgbcdae

253 cdef Stream s_obj = None 1fgbcdae

254 if stream is not None: 1fgbcdae

255 s_obj = Stream_accept(stream) 1a

256 cy_stream = as_cu(s_obj._h_stream) 1a

257  

258 cdef cydriver.CUdeviceptr dev_ptr = 0 1fgbcdae

259 cdef size_t size = 0 1fgbcdae

260 with nogil: 1fgbcdae

261 HANDLE_RETURN( 1fgbcdae

262 cydriver.cuGraphicsMapResources(1, &raw, cy_stream) 1fgbcdae

263 ) 

264 HANDLE_RETURN( 1fgbcdae

265 cydriver.cuGraphicsResourceGetMappedPointer(&dev_ptr, &size, raw) 1fgbcdae

266 ) 

267 self._mapped = True 1fgbcdae

268 buf = Buffer.from_handle(int(dev_ptr), size, owner=self) 1fgbcdae

269 return _MappedBufferContext(buf, self, stream) 1fgbcdae

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 The CUDA stream on which to perform the unmapping. If ``None``, 

281 the default stream (``0``) is used. 

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 if not self._handle: 1gojbcdae

291 raise RuntimeError("GraphicsResource has been closed") 1o

292 if not self._mapped: 1gjbcdae

293 raise RuntimeError("GraphicsResource is not mapped") 1j

294  

295 cdef cydriver.CUgraphicsResource raw = as_cu(self._handle) 1gbcdae

296 cdef cydriver.CUstream cy_stream = <cydriver.CUstream>0 1gbcdae

297 if stream is not None: 1gbcdae

298 cy_stream = as_cu((<Stream>Stream_accept(stream))._h_stream) 1a

299 with nogil: 1gbcdae

300 HANDLE_RETURN( 1gbcdae

301 cydriver.cuGraphicsUnmapResources(1, &raw, cy_stream) 1gbcdae

302 ) 

303 self._mapped = False 1gbcdae

304  

305 cpdef close(self): 

306 """Unregister this graphics resource from CUDA. 

307  

308 If the resource is currently mapped, it is unmapped first (on the 

309 default stream). After closing, the resource cannot be used again. 

310 """ 

311 cdef cydriver.CUgraphicsResource raw 

312 cdef cydriver.CUstream cy_stream 

313 if not self._handle: 1fgnojqpikbcdalm

314 return 1q

315 if self._mapped: 1fgnojqpikbcdalm

316 # Best-effort unmap before unregister 

317 raw = as_cu(self._handle) 1f

318 cy_stream = <cydriver.CUstream>0 1f

319 with nogil: 1f

320 cydriver.cuGraphicsUnmapResources(1, &raw, cy_stream) 1f

321 self._mapped = False 1f

322 self._handle.reset() 1fgnojqpikbcdalm

323  

324 @property 

325 def is_mapped(self) -> bool: 

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

327 return self._mapped 1fpkbcd

328  

329 @property 

330 def handle(self) -> int: 

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

332 return as_intptr(self._handle) 1pikr

333  

334 def __repr__(self): 

335 mapped_str = " mapped" if self._mapped else "" 1lm

336 closed_str = " closed" if not self._handle else "" 1lm

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