Coverage for cuda/core/texture/_array.pyx: 90.16%

244 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-07-19 01:12 +0000

1# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. 

2# 

3# SPDX-License-Identifier: Apache-2.0 

4  

5from __future__ import annotations 

6  

7cimport cpython 

8from libc.stdint cimport intptr_t 

9from libc.string cimport memset 

10  

11from cuda.bindings cimport cydriver 

12from cuda.core._memory._buffer cimport Buffer 

13from cuda.core._resource_handles cimport ( 

14 OpaqueArrayHandle, 

15 as_cu, 

16 as_intptr, 

17 create_array_handle, 

18 create_array_handle_owning, 

19 create_array_handle_ref, 

20 get_last_error, 

21) 

22from cuda.core._stream cimport Stream, Stream_accept 

23from cuda.core._utils.cuda_utils cimport ( 

24 HANDLE_RETURN, 

25 _get_current_device_id, 

26) 

27  

28import numpy 

29  

30from dataclasses import dataclass 

31  

32from cuda.core._utils.cuda_utils import check_or_create_options 

33from cuda.core.typing import ArrayFormatType 

34  

35  

36# Bridge between the public ArrayFormatType StrEnum and the driver 

37# CUarray_format integer values. OpaqueArray stores the driver int internally 

38# (see ._format), so all conversions funnel through these two maps. 

39_ARRAYFORMAT_TO_CU = { 

40 ArrayFormatType.UINT8: int(cydriver.CU_AD_FORMAT_UNSIGNED_INT8), 

41 ArrayFormatType.UINT16: int(cydriver.CU_AD_FORMAT_UNSIGNED_INT16), 

42 ArrayFormatType.UINT32: int(cydriver.CU_AD_FORMAT_UNSIGNED_INT32), 

43 ArrayFormatType.INT8: int(cydriver.CU_AD_FORMAT_SIGNED_INT8), 

44 ArrayFormatType.INT16: int(cydriver.CU_AD_FORMAT_SIGNED_INT16), 

45 ArrayFormatType.INT32: int(cydriver.CU_AD_FORMAT_SIGNED_INT32), 

46 ArrayFormatType.FLOAT16: int(cydriver.CU_AD_FORMAT_HALF), 

47 ArrayFormatType.FLOAT32: int(cydriver.CU_AD_FORMAT_FLOAT), 

48} 

49_CU_TO_ARRAYFORMAT = {cu: fmt for fmt, cu in _ARRAYFORMAT_TO_CU.items()} 

50  

51  

52# Every ArrayFormatType value is spelled as a NumPy dtype name, so the eight 

53# formats map 1:1 to NumPy dtypes. This lets callers pass a dtype object (or 

54# anything numpy.dtype() accepts) instead of the enum, matching the precedent 

55# set by TensorMapDescriptorOptions.data_type. 

56_NUMPY_DTYPE_TO_ARRAYFORMAT = { 

57 numpy.dtype(fmt.value): fmt for fmt in ArrayFormatType 

58} 

59  

60  

61# Bytes per element (single channel), keyed by the driver CUarray_format int. 

62_FORMAT_ELEM_SIZE = { 

63 _ARRAYFORMAT_TO_CU[ArrayFormatType.UINT8]: 1, 

64 _ARRAYFORMAT_TO_CU[ArrayFormatType.INT8]: 1, 

65 _ARRAYFORMAT_TO_CU[ArrayFormatType.UINT16]: 2, 

66 _ARRAYFORMAT_TO_CU[ArrayFormatType.INT16]: 2, 

67 _ARRAYFORMAT_TO_CU[ArrayFormatType.FLOAT16]: 2, 

68 _ARRAYFORMAT_TO_CU[ArrayFormatType.UINT32]: 4, 

69 _ARRAYFORMAT_TO_CU[ArrayFormatType.INT32]: 4, 

70 _ARRAYFORMAT_TO_CU[ArrayFormatType.FLOAT32]: 4, 

71} 

72  

73  

74def _normalize_array_format(format): 

75 """Coerce ``format`` to an :class:`ArrayFormatType`. 

76  

77 Accepts, in order of preference: 

78  

79 * an :class:`ArrayFormatType`; 

80 * a plain ``str`` naming one of its values (e.g. ``"float32"``); 

81 * a NumPy dtype object (or anything ``numpy.dtype()`` accepts, such as 

82 ``numpy.float32``) whose canonical dtype maps 1:1 to one of the eight 

83 supported formats. 

84  

85 Raises :class:`ValueError` on anything else.""" 

86 if isinstance(format, ArrayFormatType): 1stuvwgmijklnbofedQHGTIFaJEKDcURLMVWXYZ0N1S234qx5Ory6P7CzABp

87 return format 1stuvwgmbofedHGTIaJEKDcULMVWXYZ0N1234qx5Ory6P7CzABp

88 if isinstance(format, str): 1ijklnQFRS

89 try: 1n

90 return ArrayFormatType(format) 1n

91 except ValueError as e: 

92 valid = ", ".join(repr(f.value) for f in ArrayFormatType) 

93 raise ValueError( 

94 f"format must be an ArrayFormatType or one of {{{valid}}}, got {format!r}" 

95 ) from e 

96 # Fall back to interpreting ``format`` as a NumPy dtype (dtype object, 

97 # scalar type, etc.). Unknown dtypes are reported against the supported set. 

98 try: 1ijklQFRS

99 dt = numpy.dtype(format) 1ijklQFRS

100 except TypeError as e: 1QRS

101 raise ValueError( 1QRS

102 f"format must be an ArrayFormatType, str, or NumPy dtype, got {format!r}" 1QRS

103 ) from e 1QRS

104 try: 1ijklF

105 return _NUMPY_DTYPE_TO_ARRAYFORMAT[dt] 1ijklF

106 except KeyError as e: 1F

107 valid = ", ".join(repr(f.value) for f in ArrayFormatType) 1F

108 raise ValueError( 1F

109 f"NumPy dtype {dt!r} has no ArrayFormatType equivalent; " 1F

110 f"supported formats: {{{valid}}}" 1F

111 ) from e 1F

112  

113  

114def _validate_format_channels(format, num_channels): 

115 """Validate the ``(format, num_channels)`` pair shared by the array, 

116 mipmap, and texture factories. Returns the normalized 

117 :class:`ArrayFormatType`. Raises on an invalid combination.""" 

118 fmt = _normalize_array_format(format) 1stuvwgmijklnbofedQHGTIFaJEKDcURLMVWXYZ0N1S234qx5Ory6P7CzABp

119 if isinstance(num_channels, bool) or num_channels not in (1, 2, 4): 1stuvwgmijklnbofedHGTIaJEKDcULMVWXYZ0N1234qx5Ory6P7CzABp

120 raise ValueError(f"num_channels must be 1, 2, or 4, got {num_channels!r}") 1TUW1

121 return fmt 1stuvwgmijklnbofedHGIaJEKDcLMVXYZ0N234qx5Ory6P7CzABp

122  

123  

124def _validate_array_shape(shape): 

125 """Coerce ``shape`` to a tuple of ints and validate rank (1-3) and that 

126 every extent is >= 1. Returns the normalized tuple.""" 

127 try: 1stuvwgmijklnbofedHGIaJEKDcLMNqxOryPCzABp

128 shape_t = tuple(int(s) for s in shape) 1stuvwgmijklnbofedHGIaJEKDcLMNqxOryPCzABp

129 except TypeError as e: 1H

130 raise TypeError(f"shape must be a tuple of ints, got {type(shape).__name__}") from e 1H

131 if not 1 <= len(shape_t) <= 3: 1stuvwgmijklnbofedGIaJEKDcLMNqxOryPCzABp

132 raise ValueError(f"shape rank must be 1, 2, or 3, got {len(shape_t)}") 1I

133 for i, dim in enumerate(shape_t): 1hstuvwgmijklnbofedGaJEKDcLMNqxOryPCzABp

134 if dim < 1: 1stuvwgmijklnbofedGaJEKDcLMNqxOryPCzABp

135 raise ValueError(f"shape[{i}] must be >= 1, got {dim}") 1GL

136 return shape_t 1stuvwgmijklnbofedaJEKDcMNqxOryPCzABp

137  

138  

139@dataclass 

140class OpaqueArrayOptions: 

141 """Options for :meth:`cuda.core.Device.create_opaque_array`. 

142  

143 Attributes 

144 ---------- 

145 shape : tuple of int 

146 ``(width,)``, ``(width, height)``, or ``(width, height, depth)`` in 

147 elements. 

148 format : ArrayFormatType, str, or numpy.dtype 

149 Element format. Accepts an :class:`~cuda.core.typing.ArrayFormatType`, 

150 a plain string (e.g. ``"float32"``), or a NumPy dtype object. 

151 num_channels : int 

152 Channels per element. Must be 1, 2, or 4. 

153 is_surface_load_store : bool 

154 If True, allocate with ``CUDA_ARRAY3D_SURFACE_LDST`` so the array can be 

155 bound as a :class:`~cuda.core.texture.SurfaceObject` for kernel-side 

156 writes. Default False. 

157  

158 .. versionadded:: 1.1.0 

159 """ 

160  

161 shape: tuple[int, ...] 

162 format: object 

163 num_channels: int 

164 is_surface_load_store: bool = False 

165  

166 def __post_init__(self): 

167 self.format = _validate_format_channels(self.format, self.num_channels) 1stuvwgmijklnbofedQHGTIFaqxryCzABp

168 self.shape = _validate_array_shape(self.shape) 1stuvwgmijklnbofedHGIaqxryCzABp

169  

170  

171cdef void _fill_array_endpoint( 

172 cydriver.CUDA_MEMCPY3D* p, OpaqueArray arr, bint is_src 

173) noexcept: 

174 """Populate the src or dst array fields of a CUDA_MEMCPY3D struct.""" 

175 if is_src: 1bfedac

176 p.srcMemoryType = cydriver.CU_MEMORYTYPE_ARRAY 1bfedac

177 p.srcArray = as_cu(arr._handle) 1bfedac

178 p.srcXInBytes = 0 1bfedac

179 p.srcY = 0 1bfedac

180 p.srcZ = 0 1bfedac

181 else: 

182 p.dstMemoryType = cydriver.CU_MEMORYTYPE_ARRAY 1bac

183 p.dstArray = as_cu(arr._handle) 1bac

184 p.dstXInBytes = 0 1bac

185 p.dstY = 0 1bac

186 p.dstZ = 0 1bac

187  

188  

189cdef int _fill_host_endpoint( 

190 cydriver.CUDA_MEMCPY3D* p, 

191 object obj, 

192 bint is_src, 

193 size_t width_bytes, 

194 size_t height, 

195 size_t required, 

196 cpython.Py_buffer* pybuf_out, 

197) except -1: 

198 """Populate src/dst host fields from a buffer-protocol ``obj``. 

199  

200 Acquires a Py_buffer view; the caller is responsible for releasing it 

201 (this function always returns with the view held when it returns 1). 

202 """ 

203 cdef int flags = cpython.PyBUF_SIMPLE 1edac

204 if not is_src: 1edac

205 flags |= cpython.PyBUF_WRITABLE 1edac

206 if cpython.PyObject_GetBuffer(obj, pybuf_out, flags) != 0: 1edac

207 raise TypeError( 

208 f"Source/destination must be a Buffer or a contiguous " 

209 f"buffer-protocol object, got {type(obj).__name__}" 

210 ) 

211 if <size_t>pybuf_out.len < required: 1edac

212 cpython.PyBuffer_Release(pybuf_out) 1e

213 raise ValueError( 1e

214 f"Host buffer has {pybuf_out.len} bytes, smaller than the array " 1e

215 f"extent ({required} bytes)" 1e

216 ) 

217 if is_src: 1dac

218 p.srcMemoryType = cydriver.CU_MEMORYTYPE_HOST 1ac

219 p.srcHost = pybuf_out.buf 1ac

220 p.srcPitch = width_bytes 1ac

221 p.srcHeight = height 1ac

222 p.srcXInBytes = 0 1ac

223 p.srcY = 0 1ac

224 p.srcZ = 0 1ac

225 else: 

226 p.dstMemoryType = cydriver.CU_MEMORYTYPE_HOST 1dac

227 p.dstHost = pybuf_out.buf 1dac

228 p.dstPitch = width_bytes 1dac

229 p.dstHeight = height 1dac

230 p.dstXInBytes = 0 1dac

231 p.dstY = 0 1dac

232 p.dstZ = 0 1dac

233 return 1 1dac

234  

235  

236cdef int _fill_linear_endpoint( 

237 cydriver.CUDA_MEMCPY3D* p, 

238 object obj, 

239 bint is_src, 

240 size_t width_bytes, 

241 size_t height, 

242 size_t depth, 

243 cpython.Py_buffer* pybuf_out, 

244) except -1: 

245 """Populate the src or dst linear fields. Returns 1 if pybuf_out was 

246 filled (caller must release it), 0 otherwise. 

247 """ 

248 cdef intptr_t ptr 

249 cdef size_t required = width_bytes * height * depth 1bfedac

250 if isinstance(obj, Buffer): 1bfedac

251 if <size_t>(<Buffer>obj).size < required: 1bf

252 raise ValueError( 1f

253 f"Buffer size ({(<Buffer>obj).size} bytes) is smaller than " 1f

254 f"the array extent ({required} bytes)" 1f

255 ) 

256 ptr = int((<Buffer>obj).handle) 1b

257 if is_src: 1b

258 p.srcMemoryType = cydriver.CU_MEMORYTYPE_DEVICE 1b

259 p.srcDevice = <cydriver.CUdeviceptr>ptr 1b

260 p.srcPitch = width_bytes 1b

261 p.srcHeight = height 1b

262 p.srcXInBytes = 0 1b

263 p.srcY = 0 1b

264 p.srcZ = 0 1b

265 else: 

266 p.dstMemoryType = cydriver.CU_MEMORYTYPE_DEVICE 1b

267 p.dstDevice = <cydriver.CUdeviceptr>ptr 1b

268 p.dstPitch = width_bytes 1b

269 p.dstHeight = height 1b

270 p.dstXInBytes = 0 1b

271 p.dstY = 0 1b

272 p.dstZ = 0 1b

273 return 0 1b

274 return _fill_host_endpoint( 1edac

275 p, obj, is_src, width_bytes, height, required, pybuf_out 

276 ) 

277  

278  

279cdef _copy3d(OpaqueArray arr, object other, Stream stream, bint to_array): 

280 """Issue a full-array async 3D memcpy between ``arr`` and ``other``. 

281  

282 Direction is determined by ``to_array``: True copies *into* arr, False 

283 copies *out of* arr. ``stream`` must already be a concrete :class:`Stream` 

284 (callers coerce via :func:`Stream_accept`). 

285 """ 

286 cdef cydriver.CUDA_MEMCPY3D params 

287 cdef cpython.Py_buffer pybuf 

288 cdef int got_buffer = 0 1bfedac

289 cdef intptr_t stream_handle 

290 cdef cydriver.CUstream c_stream 

291  

292 memset(&params, 0, sizeof(params)) 1bfedac

293 width_bytes, height, depth = arr._extent_bytes() 1bfedac

294 params.WidthInBytes = <size_t>width_bytes 1bfedac

295 params.Height = <size_t>height 1bfedac

296 params.Depth = <size_t>depth 1bfedac

297  

298 try: 1bfedac

299 if to_array: 1bfedac

300 got_buffer = _fill_linear_endpoint( 1bfeac

301 &params, other, True, width_bytes, height, depth, &pybuf 1bfeac

302 ) 

303 _fill_array_endpoint(&params, arr, False) 1bac

304 else: 

305 _fill_array_endpoint(&params, arr, True) 1bfedac

306 got_buffer = _fill_linear_endpoint( 1bfedac

307 &params, other, False, width_bytes, height, depth, &pybuf 1bfedac

308 ) 

309  

310 stream_handle = int((<Stream>stream).handle) 1bdac

311 c_stream = <cydriver.CUstream><void*>stream_handle 1bdac

312 with nogil: 1bdac

313 HANDLE_RETURN(cydriver.cuMemcpy3DAsync(&params, c_stream)) 1bdac

314 finally: 

315 if got_buffer: 1bdac

316 cpython.PyBuffer_Release(&pybuf) 1dac

317  

318  

319cdef class OpaqueArray: 

320 """An opaque, hardware-laid-out GPU allocation for texture/surface access. 

321  

322 Distinct from :class:`Buffer`: a ``CUarray`` has no exposed device pointer 

323 and can only be accessed from kernels through a :class:`TextureObject` or 

324 :class:`SurfaceObject`. Its memory layout is chosen by the driver for 2D/3D 

325 spatial locality. 

326  

327 **Copy-only interop.** Because the layout is opaque and there is no linear 

328 device pointer, a ``OpaqueArray`` cannot expose ``__cuda_array_interface__`` / 

329 DLPack and cannot be shared zero-copy with NumPy, CuPy, numba-cuda, or 

330 PyTorch. Moving data in or out is therefore always a copy: use 

331 :meth:`copy_from` / :meth:`copy_to` against a linear :class:`Buffer` or a 

332 host buffer-protocol object. There is no allocation helper — allocate the 

333 linear :class:`Buffer` yourself (e.g. ``mr.allocate(arr.size_bytes, 

334 stream=s)``) and copy. 

335  

336 Construct via :meth:`cuda.core.Device.create_opaque_array`. Only plain 

337 1D/2D/3D allocations are supported in this initial version; layered/cubemap/ 

338 sparse variants will follow once their shape semantics are settled. 

339  

340 .. versionadded:: 1.1.0 

341 """ 

342  

343 def __init__(self, *args, **kwargs): 

344 raise RuntimeError( 18

345 "OpaqueArray cannot be instantiated directly. " 

346 "Use Device.create_opaque_array()." 

347 ) 

348  

349 @classmethod 

350 def _from_handle(cls, intptr_t handle, bint owning, *, device_id=None): 

351 """Wrap an externally-allocated ``CUarray``. 

352  

353 Intended for graphics interop (``cuGraphicsSubResourceGetMappedArray``) 

354 where the array is owned by the graphics API. With ``owning=False`` the 

355 underlying ``CUarray`` is never destroyed by this object. Shape, format, 

356 and channel count are queried from the driver. 

357 """ 

358 cdef cydriver.CUarray raw = <cydriver.CUarray><void*>handle 

359 cdef OpaqueArrayHandle h 

360 if owning: 

361 h = create_array_handle_owning(raw) 

362 else: 

363 h = create_array_handle_ref(raw) 

364 cdef int dev = _get_current_device_id() if device_id is None else int(device_id) 

365 return _array_from_handle(h, dev) 

366  

367 @property 

368 def handle(self): 

369 """The underlying ``CUarray`` as an integer.""" 

370 return as_intptr(self._handle) 1gDcp

371  

372 @property 

373 def shape(self): 

374 """Allocation shape, in elements.""" 

375 return self._shape 1gmEDc

376  

377 @property 

378 def format(self): 

379 """The element :class:`~cuda.core.typing.ArrayFormatType`.""" 

380 return _CU_TO_ARRAYFORMAT[self._format] 1gijklnD

381  

382 @property 

383 def num_channels(self): 

384 """Channels per element (1, 2, or 4).""" 

385 return self._num_channels 1gD

386  

387 @property 

388 def element_bytes(self): 

389 """Bytes per element (format size * channels).""" 

390 return _FORMAT_ELEM_SIZE[self._format] * self._num_channels 1gm

391  

392 @property 

393 def device(self): 

394 """The :class:`Device` this array was allocated on.""" 

395 from cuda.core._device import Device 1g

396 return Device(self._device_id) 1g

397  

398 @property 

399 def is_surface_load_store(self): 

400 """True if this array was created with ``CUDA_ARRAY3D_SURFACE_LDST`` 

401 and can be bound as a :class:`SurfaceObject`.""" 

402 return self._surface_load_store 1gmqxrp

403  

404 def _extent_bytes(self): 

405 """Return (width_bytes, height, depth) for cuMemcpy3D, with height/depth 

406 normalized to >=1 for lower-rank arrays.""" 

407 cdef int rank = len(self._shape) 1bfedac

408 cdef size_t w = <size_t>self._shape[0] * <size_t>( 1bfedac

409 _FORMAT_ELEM_SIZE[self._format] * self._num_channels 1bfedac

410 ) 

411 cdef size_t h = <size_t>(self._shape[1] if rank >= 2 else 1) 1bfedac

412 cdef size_t d = <size_t>(self._shape[2] if rank >= 3 else 1) 1bfedac

413 return w, h, d 1bfedac

414  

415 def copy_from(self, src, *, stream) -> None: 

416 """Copy a full-array's worth of data into this array. 

417  

418 Parameters 

419 ---------- 

420 src : Buffer or buffer-protocol object 

421 Source data. Must contain at least ``self.size_bytes`` bytes 

422 of contiguous data. 

423 stream : Stream or GraphBuilder 

424 Stream to issue the copy on. A :class:`~cuda.core.graph.GraphBuilder` 

425 is accepted so the copy can be captured into a graph. 

426 """ 

427 _copy3d(self, src, Stream_accept(stream), to_array=True) 1bofeac

428  

429 def copy_to(self, dst, *, stream): 

430 """Copy a full-array's worth of data out of this array. 

431  

432 Parameters 

433 ---------- 

434 dst : Buffer or writable buffer-protocol object 

435 Destination. Must have at least ``self.size_bytes`` bytes of 

436 writable, contiguous space. 

437 stream : Stream or GraphBuilder 

438 Stream to issue the copy on. A :class:`~cuda.core.graph.GraphBuilder` 

439 is accepted so the copy can be captured into a graph. 

440  

441 Returns 

442 ------- 

443 The ``dst`` object, for parity with :meth:`Buffer.copy_to`. 

444 """ 

445 _copy3d(self, dst, Stream_accept(stream), to_array=False) 1bofedac

446 return dst 1bdac

447  

448 @property 

449 def size_bytes(self): 

450 """Total bytes of array storage (``prod(shape) * element_bytes``).""" 

451 cdef size_t n = 1 1gb

452 for s in self._shape: 1gb

453 n *= <size_t>s 1gb

454 return n * <size_t>(_FORMAT_ELEM_SIZE[self._format] * self._num_channels) 1gb

455  

456 cpdef close(self): 

457 """Release this object's reference to the underlying ``CUarray``. 

458  

459 Destruction (``cuArrayDestroy``) happens via the handle's deleter when 

460 the last reference is dropped; for a non-owning handle (graphics interop 

461 or a mipmap-level view) nothing is destroyed. Idempotent: a second call 

462 (or destruction after ``close()``) is a no-op. 

463 """ 

464 self._handle.reset() 1stuvwgmijklnbofedaEDcqryzABp

465  

466 def __enter__(self): 

467 return self 

468  

469 def __exit__(self, exc_type, exc, tb): 

470 self.close() 

471  

472 def __repr__(self): 

473 return ( 

474 f"OpaqueArray(shape={self._shape}, " 

475 f"format={_CU_TO_ARRAYFORMAT[self._format].name}, " 

476 f"num_channels={self._num_channels})" 

477 ) 

478  

479  

480cdef OpaqueArray _array_from_handle(OpaqueArrayHandle h, int device_id): 

481 """Wrap an existing OpaqueArrayHandle as a OpaqueArray, querying the driver for the 

482 array's shape/format/channels/surface-flag metadata. 

483  

484 Any owning/non-owning semantics and parent (mipmap) dependency are already 

485 captured structurally inside ``h``'s C++ box. 

486 """ 

487 if not h: 1EDc

488 HANDLE_RETURN(get_last_error()) 

489  

490 cdef OpaqueArray self = OpaqueArray.__new__(OpaqueArray) 1EDc

491 self._handle = h 1EDc

492 self._device_id = device_id 1EDc

493  

494 cdef cydriver.CUDA_ARRAY3D_DESCRIPTOR desc 

495 cdef cydriver.CUarray raw = as_cu(h) 1EDc

496 with nogil: 1EDc

497 HANDLE_RETURN(cydriver.cuArray3DGetDescriptor(&desc, raw)) 1EDc

498  

499 if desc.Depth > 0: 1EDc

500 self._shape = (int(desc.Width), int(desc.Height), int(desc.Depth)) 

501 elif desc.Height > 0: 1EDc

502 self._shape = (int(desc.Width), int(desc.Height)) 1EDc

503 else: 

504 self._shape = (int(desc.Width),) 

505 self._format = desc.Format 1EDc

506 self._num_channels = desc.NumChannels 1EDc

507 self._surface_load_store = bool(desc.Flags & cydriver.CUDA_ARRAY3D_SURFACE_LDST) 1EDc

508 return self 1EDc

509  

510  

511def _create_opaque_array(options): 

512 """Allocate a new :class:`OpaqueArray` on the current device. 

513  

514 Backs :meth:`cuda.core.Device.create_opaque_array`. ``options`` is an 

515 :class:`OpaqueArrayOptions` (or a mapping accepted by it); it is validated 

516 at construction, so ``shape`` is already a normalized tuple and ``format`` 

517 an :class:`~cuda.core.typing.ArrayFormatType`. 

518 """ 

519 cdef object opts = check_or_create_options( 1stuvwgmijklnbofedaqxryCzABp

520 OpaqueArrayOptions, options, "Opaque array options" 1stuvwgmijklnbofedaqxryCzABp

521 ) 

522 shape_t = opts.shape 1stuvwgmijklnbofedaqxryCzABp

523  

524 cdef cydriver.CUarray_format c_format = <cydriver.CUarray_format>_ARRAYFORMAT_TO_CU[opts.format] 1stuvwgmijklnbofedaqxryCzABp

525 cdef cydriver.CUDA_ARRAY3D_DESCRIPTOR desc3d 

526 cdef int rank = len(shape_t) 1stuvwgmijklnbofedaqxryCzABp

527 cdef unsigned int flags = ( 

528 cydriver.CUDA_ARRAY3D_SURFACE_LDST if opts.is_surface_load_store else 0 1stuvwgmijklnbofedaqxryCzABp

529 ) 

530  

531 # cuArray3DCreate handles 1D/2D/3D uniformly (Height/Depth 0 sentinels), 

532 # so a single descriptor + create_array_handle covers every shape. 

533 memset(&desc3d, 0, sizeof(desc3d)) 1stuvwgmijklnbofedaqxryCzABp

534 desc3d.Width = <size_t>shape_t[0] 1stuvwgmijklnbofedaqxryCzABp

535 desc3d.Height = <size_t>(shape_t[1] if rank >= 2 else 0) 1stuvwgmijklnbofedaqxryCzABp

536 desc3d.Depth = <size_t>(shape_t[2] if rank >= 3 else 0) 1stuvwgmijklnbofedaqxryCzABp

537 desc3d.Format = c_format 1stuvwgmijklnbofedaqxryCzABp

538 desc3d.NumChannels = <unsigned int>opts.num_channels 1stuvwgmijklnbofedaqxryCzABp

539 desc3d.Flags = flags 1stuvwgmijklnbofedaqxryCzABp

540  

541 cdef OpaqueArrayHandle h = create_array_handle(desc3d) 1stuvwgmijklnbofedaqxryCzABp

542 if not h: 1stuvwgmijklnbofedaqxryCzABp

543 HANDLE_RETURN(get_last_error()) 

544  

545 cdef OpaqueArray self = OpaqueArray.__new__(OpaqueArray) 1stuvwgmijklnbofedaqxryCzABp

546 self._handle = h 1stuvwgmijklnbofedaqxryCzABp

547 self._shape = shape_t 1stuvwgmijklnbofedaqxryCzABp

548 self._format = c_format 1stuvwgmijklnbofedaqxryCzABp

549 self._num_channels = opts.num_channels 1stuvwgmijklnbofedaqxryCzABp

550 self._surface_load_store = bool(opts.is_surface_load_store) 1stuvwgmijklnbofedaqxryCzABp

551 self._device_id = _get_current_device_id() 1stuvwgmijklnbofedaqxryCzABp

552 return self 1stuvwgmijklnbofedaqxryCzABp