Coverage for cuda/core/texture/_texture.pyx: 93.29%

283 statements  

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

7from libc.stdint cimport intptr_t 

8from libc.string cimport memset 

9  

10from cuda.bindings cimport cydriver 

11from cuda.core.texture._array cimport OpaqueArray 

12from cuda.core.texture._array import ArrayFormat, _FORMAT_ELEM_SIZE, _validate_format_channels 

13from cuda.core._memory._buffer cimport Buffer 

14from cuda.core.texture._mipmapped_array cimport MipmappedArray 

15from cuda.core.texture._mipmapped_array import MipmappedArray as _PyMipmappedArray 

16from cuda.core._resource_handles cimport ( 

17 TexObjectHandle, 

18 as_cu, 

19 as_intptr, 

20 create_tex_object_handle_array, 

21 create_tex_object_handle_linear, 

22 create_tex_object_handle_mipmap, 

23 get_last_error, 

24) 

25from cuda.core._utils.cuda_utils cimport ( 

26 HANDLE_RETURN, 

27 _get_current_device_id, 

28) 

29  

30from dataclasses import dataclass 

31from enum import IntEnum 

32  

33  

34# Driver texture-descriptor flag bits (CU_TRSF_*). 

35_TRSF_READ_AS_INTEGER = 0x01 

36_TRSF_NORMALIZED_COORDINATES = 0x02 

37_TRSF_SRGB = 0x10 

38_TRSF_DISABLE_TRILINEAR_OPTIMIZATION = 0x20 

39_TRSF_SEAMLESS_CUBEMAP = 0x40 

40  

41  

42class AddressMode(IntEnum): 

43 """Boundary behavior for out-of-range texture coordinates.""" 

44 WRAP = cydriver.CU_TR_ADDRESS_MODE_WRAP 

45 CLAMP = cydriver.CU_TR_ADDRESS_MODE_CLAMP 

46 MIRROR = cydriver.CU_TR_ADDRESS_MODE_MIRROR 

47 BORDER = cydriver.CU_TR_ADDRESS_MODE_BORDER 

48  

49  

50class FilterMode(IntEnum): 

51 """Texel sampling mode.""" 

52 POINT = cydriver.CU_TR_FILTER_MODE_POINT 

53 LINEAR = cydriver.CU_TR_FILTER_MODE_LINEAR 

54  

55  

56class ReadMode(IntEnum): 

57 """How sampled values are returned to the kernel. 

58  

59 - ``ELEMENT_TYPE``: return the raw element value (integer formats stay 

60 integer, float stays float). 

61 - ``NORMALIZED_FLOAT``: integer formats are promoted to a normalized 

62 ``float`` in ``[0, 1]`` (unsigned) or ``[-1, 1]`` (signed). 

63 Float formats are unaffected. 

64 """ 

65 ELEMENT_TYPE = 0 

66 NORMALIZED_FLOAT = 1 

67  

68  

69class ResourceDescriptor: 

70 """Describes the memory backing a :class:`TextureObject`. 

71  

72 Construct via the ``from_*`` classmethods: 

73  

74 - :meth:`from_array` wraps a :class:`OpaqueArray` (works for both 

75 :class:`TextureObject` and :class:`SurfaceObject`). 

76 - :meth:`from_mipmapped_array` wraps a :class:`MipmappedArray` for mipmapped 

77 sampling (texture only, not surface). 

78 - :meth:`from_linear` wraps a :class:`Buffer` as a typed 1D fetch. Texture 

79 objects built from a linear resource do not support filtering, 

80 normalized coordinates, or addressing modes. 

81 - :meth:`from_pitch2d` wraps a :class:`Buffer` as a row-pitched 2D image. 

82 Supports filtering and 2D addressing, but only 2D access. 

83  

84 Linear and pitch2D resources cannot back a :class:`SurfaceObject` — those 

85 require an :class:`OpaqueArray` allocated with ``is_surface_load_store=True``. 

86 """ 

87  

88 __slots__ = ( 

89 "_kind", "_source", 

90 "_format", "_num_channels", 

91 "_size_bytes", 

92 "_width", "_height", "_pitch_bytes", 

93 ) 

94  

95 def __init__(self): 

96 raise RuntimeError( 1N

97 "ResourceDescriptor cannot be instantiated directly. " 

98 "Use ResourceDescriptor.from_* factories." 

99 ) 

100  

101 @classmethod 

102 def from_array(cls, array): 

103 """Build a resource descriptor backed by a :class:`OpaqueArray`.""" 

104 if not isinstance(array, OpaqueArray): 1cpoqrvwxeginjmkuh

105 raise TypeError(f"array must be a OpaqueArray, got {type(array).__name__}") 

106 self = cls.__new__(cls) 1cpoqrvwxeginjmkuh

107 self._kind = "array" 1cpoqrvwxeginjmkuh

108 self._source = array 1cpoqrvwxeginjmkuh

109 self._format = None 1cpoqrvwxeginjmkuh

110 self._num_channels = None 1cpoqrvwxeginjmkuh

111 self._size_bytes = None 1cpoqrvwxeginjmkuh

112 self._width = None 1cpoqrvwxeginjmkuh

113 self._height = None 1cpoqrvwxeginjmkuh

114 self._pitch_bytes = None 1cpoqrvwxeginjmkuh

115 return self 1fcpoqrvwxeginjmkuh

116  

117 @classmethod 

118 def from_mipmapped_array(cls, mipmapped_array): 

119 """Build a resource descriptor backed by a :class:`MipmappedArray`. 

120  

121 Suitable for binding to a :class:`TextureObject` for mipmapped 

122 sampling. Not valid as a :class:`SurfaceObject` backing: surfaces 

123 require a single :class:`OpaqueArray` level (obtain via 

124 :meth:`MipmappedArray.get_level`). 

125 """ 

126 if not isinstance(mipmapped_array, _PyMipmappedArray): 1GyAd

127 raise TypeError( 1G

128 f"mipmapped_array must be a MipmappedArray, got " 1G

129 f"{type(mipmapped_array).__name__}" 1G

130 ) 

131 self = cls.__new__(cls) 1yAd

132 self._kind = "mipmapped_array" 1yAd

133 self._source = mipmapped_array 1fyAd

134 self._format = None 1yAd

135 self._num_channels = None 1yAd

136 self._size_bytes = None 1yAd

137 self._width = None 1yAd

138 self._height = None 1yAd

139 self._pitch_bytes = None 1yAd

140 return self 1yAd

141  

142 @classmethod 

143 def from_linear(cls, buffer, *, format, num_channels, size_bytes=None): 

144 """Build a resource descriptor for a linear (typed 1D) texture fetch. 

145  

146 Parameters 

147 ---------- 

148 buffer : Buffer 

149 Device-memory backing. Must remain alive for the lifetime of any 

150 :class:`TextureObject` built from this descriptor. 

151 format : ArrayFormat 

152 Element format. 

153 num_channels : int 

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

155 size_bytes : int, optional 

156 Bytes of ``buffer`` to bind. Defaults to ``buffer.size``. Must not 

157 exceed it. 

158  

159 Notes 

160 ----- 

161 Texture objects built from a linear resource ignore the 

162 :class:`TextureDescriptor` addressing/filtering fields — kernels read 

163 through a typed 1D fetch with bounds checking only. 

164 """ 

165 if not isinstance(buffer, Buffer): 1sIJBEDtlb

166 raise TypeError(f"buffer must be a Buffer, got {type(buffer).__name__}") 1J

167 _validate_format_channels(format, num_channels) 1sIBEDtlb

168  

169 buf_size = int(buffer.size) 1sBEDtlb

170 elem = _FORMAT_ELEM_SIZE[int(format)] * int(num_channels) 1sBEDtlb

171 if size_bytes is None: 1sBEDtlb

172 size = buf_size 1slb

173 else: 

174 size = int(size_bytes) 1BEDt

175 if size > buf_size: 1BEDt

176 raise ValueError( 1E

177 f"size_bytes ({size}) exceeds buffer.size ({buf_size})" 1E

178 ) 

179 if size < elem: 1sBDtlb

180 raise ValueError( 1D

181 f"size_bytes ({size}) must be at least one element ({elem} bytes)" 1D

182 ) 

183 if size % elem != 0: 1sBtlb

184 raise ValueError( 1B

185 f"size_bytes ({size}) must be a multiple of element size " 1B

186 f"({elem} bytes for {format.name} x {num_channels})" 1B

187 ) 

188  

189 self = cls.__new__(cls) 1stlb

190 self._kind = "linear" 1stlb

191 self._source = buffer 1stlb

192 self._format = int(format) 1stlb

193 self._num_channels = int(num_channels) 1stlb

194 self._size_bytes = size 1stlb

195 self._width = None 1stlb

196 self._height = None 1stlb

197 self._pitch_bytes = None 1stlb

198 return self 1stlb

199  

200 @classmethod 

201 def from_pitch2d( 

202 cls, buffer, *, format, num_channels, width, height, pitch_bytes 

203 ): 

204 """Build a resource descriptor for a row-pitched 2D image. 

205  

206 Parameters 

207 ---------- 

208 buffer : Buffer 

209 Device-memory backing. Must remain alive for the lifetime of any 

210 :class:`TextureObject` built from this descriptor. 

211 format : ArrayFormat 

212 Element format. 

213 num_channels : int 

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

215 width : int 

216 Image width, in elements. 

217 height : int 

218 Image height, in rows. 

219 pitch_bytes : int 

220 Distance between consecutive rows, in bytes. Must be at least 

221 ``width * format_size * num_channels`` and meet the driver's 

222 ``CU_DEVICE_ATTRIBUTE_TEXTURE_PITCH_ALIGNMENT``. 

223 """ 

224 if not isinstance(buffer, Buffer): 1KLMFzCla

225 raise TypeError(f"buffer must be a Buffer, got {type(buffer).__name__}") 1M

226 _validate_format_channels(format, num_channels) 1KLFzCla

227  

228 w = int(width) 1FzCla

229 h = int(height) 1FzCla

230 p = int(pitch_bytes) 1FzCla

231 if w < 1: 1FzCla

232 raise ValueError(f"width must be >= 1, got {w}") 1F

233 if h < 1: 1FzCla

234 raise ValueError(f"height must be >= 1, got {h}") 1F

235 elem = _FORMAT_ELEM_SIZE[int(format)] * int(num_channels) 1zCla

236 min_pitch = w * elem 1zCla

237 if p < min_pitch: 1zCla

238 raise ValueError( 1C

239 f"pitch_bytes ({p}) must be >= width * element_size ({min_pitch})" 1C

240 ) 

241 if p * h > int(buffer.size): 1zla

242 raise ValueError( 1z

243 f"pitch_bytes * height ({p * h}) exceeds buffer.size ({int(buffer.size)})" 1z

244 ) 

245  

246 self = cls.__new__(cls) 1la

247 self._kind = "pitch2d" 1la

248 self._source = buffer 1la

249 self._format = int(format) 1la

250 self._num_channels = int(num_channels) 1la

251 self._size_bytes = None 1la

252 self._width = w 1la

253 self._height = h 1la

254 self._pitch_bytes = p 1la

255 return self 1la

256  

257 @property 

258 def kind(self): 

259 return self._kind 1cpoqrsyvwlAxebdaginjmkh

260  

261 @property 

262 def source(self): 

263 return self._source 1cpoqrsyvwxebdaginjmkh

264  

265 @property 

266 def format(self): 

267 """The element :class:`ArrayFormat` (``None`` for array-backed).""" 

268 return None if self._format is None else ArrayFormat(self._format) 1sa

269  

270 @property 

271 def num_channels(self): 

272 """Channels per element (``None`` for array-backed).""" 

273 return self._num_channels 1s

274  

275 @property 

276 def size_bytes(self): 

277 """Bytes bound for a linear resource (``None`` for other kinds).""" 

278 return self._size_bytes 

279  

280 @property 

281 def width(self): 

282 """Pitch2D image width, in elements (``None`` for other kinds).""" 

283 return self._width 

284  

285 @property 

286 def height(self): 

287 """Pitch2D image height, in rows (``None`` for other kinds).""" 

288 return self._height 

289  

290 @property 

291 def pitch_bytes(self): 

292 """Pitch2D row pitch, in bytes (``None`` for other kinds).""" 

293 return self._pitch_bytes 

294  

295 def __repr__(self): 

296 if self._kind == "linear": 1sa

297 return ( 1s

298 f"ResourceDescriptor(kind='linear', format={self.format.name}, " 1s

299 f"num_channels={self._num_channels}, size_bytes={self._size_bytes})" 1s

300 ) 

301 if self._kind == "pitch2d": 1a

302 return ( 1a

303 f"ResourceDescriptor(kind='pitch2d', format={self.format.name}, " 1a

304 f"num_channels={self._num_channels}, " 1a

305 f"width={self._width}, height={self._height}, " 1a

306 f"pitch_bytes={self._pitch_bytes})" 1a

307 ) 

308 return f"ResourceDescriptor(kind={self._kind!r})" 

309  

310  

311@dataclass 

312class TextureDescriptor: 

313 """Sampling state for a :class:`TextureObject` (mirrors ``CUDA_TEXTURE_DESC``). 

314  

315 Attributes 

316 ---------- 

317 address_mode : tuple of AddressMode 

318 Boundary behavior per axis. May be a single :class:`AddressMode` (applied 

319 to all axes) or a tuple of 1-3 entries (one per dimension). 

320 filter_mode : FilterMode 

321 Texel sampling mode. Default ``POINT``. 

322 read_mode : ReadMode 

323 How sampled integer values are returned. Default ``ELEMENT_TYPE``. 

324 normalized_coords : bool 

325 If True, coordinates are in ``[0, 1]`` instead of pixel indices. 

326 srgb : bool 

327 If True, perform sRGB → linear conversion on read (8-bit formats only). 

328 disable_trilinear_optimization : bool 

329 If True, request exact trilinear filtering. 

330 seamless_cubemap : bool 

331 If True, enable seamless cubemap edge filtering. 

332 max_anisotropy : int 

333 Maximum anisotropy; 0 disables anisotropic filtering. 

334 mipmap_filter_mode : FilterMode 

335 Filtering between mipmap levels. Default ``POINT``. 

336 mipmap_level_bias : float 

337 min_mipmap_level_clamp : float 

338 max_mipmap_level_clamp : float 

339 border_color : tuple of float or None 

340 4-tuple used when ``address_mode`` includes ``BORDER``; ``None`` means 

341 zero. 

342 """ 

343  

344 address_mode: AddressMode | tuple[AddressMode, ...] = AddressMode.CLAMP 

345 filter_mode: FilterMode = FilterMode.POINT 

346 read_mode: ReadMode = ReadMode.ELEMENT_TYPE 

347 normalized_coords: bool = False 

348 srgb: bool = False 

349 disable_trilinear_optimization: bool = False 

350 seamless_cubemap: bool = False 

351 max_anisotropy: int = 0 

352 mipmap_filter_mode: FilterMode = FilterMode.POINT 

353 mipmap_level_bias: float = 0.0 

354 min_mipmap_level_clamp: float = 0.0 

355 max_mipmap_level_clamp: float = 0.0 

356 border_color: tuple[float, ...] | None = None 

357  

358  

359def _normalize_address_modes(address_mode): 

360 """Return a 3-tuple of AddressMode values from a scalar or 1-3 tuple.""" 

361 if isinstance(address_mode, AddressMode): 1cpoqrebdaginjmkh

362 return (address_mode, address_mode, address_mode) 1cebdaginjmkh

363 try: 1cpoqr

364 modes = tuple(address_mode) 1cpoqr

365 except TypeError as e: 1q

366 raise TypeError( 1q

367 "address_mode must be an AddressMode or a tuple of AddressMode" 

368 ) from e 1q

369 if not 1 <= len(modes) <= 3: 1cpor

370 raise ValueError( 1pr

371 f"address_mode tuple must have 1-3 entries, got {len(modes)}" 1pr

372 ) 

373 for i, m in enumerate(modes): 1co

374 if not isinstance(m, AddressMode): 1co

375 raise TypeError( 1o

376 f"address_mode[{i}] must be an AddressMode, got {type(m).__name__}" 1o

377 ) 

378 # Pad to 3 entries by repeating the last one. 

379 padded = list(modes) + [modes[-1]] * (3 - len(modes)) 1c

380 return tuple(padded) 1c

381  

382  

383cdef class TextureObject: 

384 """A bindless texture handle for kernel-side sampled reads. 

385  

386 Wraps ``cuTexObjectCreate``. The underlying memory resource (e.g. the 

387 :class:`OpaqueArray` referenced by the descriptor) is kept alive for the 

388 lifetime of this object to prevent dangling handles. 

389  

390 Construct via :meth:`from_descriptor`. Passes to kernels as a 64-bit 

391 handle (via the ``handle`` property). 

392 """ 

393  

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

395 raise RuntimeError( 1O

396 "TextureObject cannot be instantiated directly. " 

397 "Use TextureObject.from_descriptor()." 

398 ) 

399  

400 @classmethod 

401 def from_descriptor(cls, *, resource, texture_descriptor): 

402 """Create a texture object from a resource + sampling descriptor. 

403  

404 Parameters 

405 ---------- 

406 resource : ResourceDescriptor 

407 texture_descriptor : TextureDescriptor 

408 """ 

409 if not isinstance(resource, ResourceDescriptor): 1cpoqrebdaginjmkHuh

410 raise TypeError( 1H

411 f"resource must be a ResourceDescriptor, got " 1H

412 f"{type(resource).__name__}" 1H

413 ) 

414 if not isinstance(texture_descriptor, TextureDescriptor): 1cpoqrebdaginjmkuh

415 raise TypeError( 1u

416 f"texture_descriptor must be a TextureDescriptor, got " 1u

417 f"{type(texture_descriptor).__name__}" 1u

418 ) 

419  

420 cdef cydriver.CUDA_RESOURCE_DESC res_desc 

421 cdef cydriver.CUDA_TEXTURE_DESC tex_desc 

422 memset(&res_desc, 0, sizeof(res_desc)) 1cpoqrebdaginjmkh

423 memset(&tex_desc, 0, sizeof(tex_desc)) 1cpoqrebdaginjmkh

424  

425 # --- Resource descriptor --- 

426 cdef OpaqueArray arr 

427 cdef MipmappedArray mip 

428 cdef Buffer buf 

429 cdef intptr_t devptr 

430 if resource.kind == "array": 1cpoqrebdaginjmkh

431 arr = <OpaqueArray>resource.source 1cpoqreginjmkh

432 res_desc.resType = cydriver.CU_RESOURCE_TYPE_ARRAY 1cpoqreginjmkh

433 res_desc.res.array.hArray = as_cu(arr._handle) 1cpoqreginjmkh

434 elif resource.kind == "mipmapped_array": 1bda

435 mip = <MipmappedArray>resource.source 1d

436 res_desc.resType = cydriver.CU_RESOURCE_TYPE_MIPMAPPED_ARRAY 1d

437 res_desc.res.mipmap.hMipmappedArray = as_cu(mip._handle) 1d

438 elif resource.kind == "linear": 1ba

439 buf = <Buffer>resource.source 1b

440 devptr = int(buf.handle) 1b

441 res_desc.resType = cydriver.CU_RESOURCE_TYPE_LINEAR 1b

442 res_desc.res.linear.devPtr = <cydriver.CUdeviceptr>devptr 1b

443 res_desc.res.linear.format = <cydriver.CUarray_format><int>resource._format 1b

444 res_desc.res.linear.numChannels = <unsigned int>resource._num_channels 1b

445 res_desc.res.linear.sizeInBytes = <size_t>resource._size_bytes 1b

446 elif resource.kind == "pitch2d": 1a

447 buf = <Buffer>resource.source 1a

448 devptr = int(buf.handle) 1a

449 res_desc.resType = cydriver.CU_RESOURCE_TYPE_PITCH2D 1a

450 res_desc.res.pitch2D.devPtr = <cydriver.CUdeviceptr>devptr 1a

451 res_desc.res.pitch2D.format = <cydriver.CUarray_format><int>resource._format 1a

452 res_desc.res.pitch2D.numChannels = <unsigned int>resource._num_channels 1a

453 res_desc.res.pitch2D.width = <size_t>resource._width 1a

454 res_desc.res.pitch2D.height = <size_t>resource._height 1a

455 res_desc.res.pitch2D.pitchInBytes = <size_t>resource._pitch_bytes 1a

456 else: 

457 raise NotImplementedError( 

458 f"ResourceDescriptor kind {resource.kind!r} is not yet supported" 

459 ) 

460  

461 # --- Texture descriptor --- 

462 modes = _normalize_address_modes(texture_descriptor.address_mode) 1cpoqrebdaginjmkh

463 tex_desc.addressMode[0] = <cydriver.CUaddress_mode><int>modes[0] 1cebdaginjmkh

464 tex_desc.addressMode[1] = <cydriver.CUaddress_mode><int>modes[1] 1cebdaginjmkh

465 tex_desc.addressMode[2] = <cydriver.CUaddress_mode><int>modes[2] 1cebdaginjmkh

466  

467 if not isinstance(texture_descriptor.filter_mode, FilterMode): 1cebdaginjmkh

468 raise TypeError( 1n

469 f"filter_mode must be a FilterMode, got " 1n

470 f"{type(texture_descriptor.filter_mode).__name__}" 1n

471 ) 

472 tex_desc.filterMode = <cydriver.CUfilter_mode><int>texture_descriptor.filter_mode 1cebdagijmkh

473  

474 if not isinstance(texture_descriptor.read_mode, ReadMode): 1cebdagijmkh

475 raise TypeError( 1m

476 f"read_mode must be a ReadMode, got " 1m

477 f"{type(texture_descriptor.read_mode).__name__}" 1m

478 ) 

479  

480 cdef unsigned int flags = 0 1cebdagijkh

481 # CU_TRSF_READ_AS_INTEGER suppresses normalization, so it maps to 

482 # ReadMode.ELEMENT_TYPE. 

483 if texture_descriptor.read_mode == ReadMode.ELEMENT_TYPE: 1cebdagijkh

484 flags |= _TRSF_READ_AS_INTEGER 1cebdagijkh

485 if texture_descriptor.normalized_coords: 1cebdagijkh

486 flags |= _TRSF_NORMALIZED_COORDINATES 1ed

487 if texture_descriptor.srgb: 1cebdagijkh

488 flags |= _TRSF_SRGB 

489 if texture_descriptor.disable_trilinear_optimization: 1cebdagijkh

490 flags |= _TRSF_DISABLE_TRILINEAR_OPTIMIZATION 

491 if texture_descriptor.seamless_cubemap: 1cebdagijkh

492 flags |= _TRSF_SEAMLESS_CUBEMAP 

493 tex_desc.flags = flags 1cebdagijkh

494  

495 if texture_descriptor.max_anisotropy < 0: 1cebdagijkh

496 raise ValueError("max_anisotropy must be >= 0") 1k

497 tex_desc.maxAnisotropy = <unsigned int>texture_descriptor.max_anisotropy 1cebdagijh

498  

499 if not isinstance(texture_descriptor.mipmap_filter_mode, FilterMode): 1cebdagijh

500 raise TypeError( 1j

501 f"mipmap_filter_mode must be a FilterMode, got " 1j

502 f"{type(texture_descriptor.mipmap_filter_mode).__name__}" 1j

503 ) 

504 tex_desc.mipmapFilterMode = <cydriver.CUfilter_mode><int>texture_descriptor.mipmap_filter_mode 1cebdagih

505 tex_desc.mipmapLevelBias = <float>texture_descriptor.mipmap_level_bias 1cebdagih

506 tex_desc.minMipmapLevelClamp = <float>texture_descriptor.min_mipmap_level_clamp 1cebdagih

507 tex_desc.maxMipmapLevelClamp = <float>texture_descriptor.max_mipmap_level_clamp 1cebdagih

508  

509 cdef int i 

510 if texture_descriptor.border_color is None: 1cebdagih

511 for i in range(4): 1cebdagh

512 tex_desc.borderColor[i] = 0.0 1cebdagh

513 else: 

514 bc = tuple(texture_descriptor.border_color) 1i

515 if len(bc) != 4: 1i

516 raise ValueError( 1i

517 f"border_color must have 4 elements, got {len(bc)}" 1i

518 ) 

519 for i in range(4): 

520 tex_desc.borderColor[i] = <float>bc[i] 

521  

522 cdef TexObjectHandle h 

523 if resource.kind == "array": 1cebdagh

524 h = create_tex_object_handle_array(res_desc, tex_desc, arr._handle) 1cegh

525 elif resource.kind == "mipmapped_array": 1bda

526 h = create_tex_object_handle_mipmap(res_desc, tex_desc, mip._handle) 1d

527 else: # linear or pitch2d — both backed by a device Buffer 

528 h = create_tex_object_handle_linear(res_desc, tex_desc, buf._h_ptr) 1ba

529 if not h: 1cebdagh

530 HANDLE_RETURN(get_last_error()) 

531  

532 cdef TextureObject self = cls.__new__(cls) 1cebdagh

533 self._handle = h 1cebdagh

534 self._source_ref = resource 1cebdagh

535 self._texture_desc = texture_descriptor 1cebdagh

536 self._device_id = _get_current_device_id() 1cebdagh

537 return self 1cebdagh

538  

539 @property 

540 def handle(self): 

541 """The underlying ``CUtexObject`` as an integer (64-bit kernel arg).""" 

542 return as_intptr(self._handle) 1cebdagh

543  

544 @property 

545 def resource(self): 

546 """The :class:`ResourceDescriptor` this texture was built from.""" 

547 return self._source_ref 1ebd

548  

549 @property 

550 def texture_descriptor(self): 

551 """The :class:`TextureDescriptor` this texture was built from.""" 

552 return self._texture_desc 1e

553  

554 @property 

555 def device(self): 

556 from cuda.core._device import Device 

557 return Device(self._device_id) 

558  

559 cpdef close(self): 

560 """Release this object's reference to the underlying ``CUtexObject``. 

561  

562 Destruction (``cuTexObjectDestroy``) and release of the backing resource 

563 happen via the handle's deleter when the last reference is dropped. 

564 Idempotent. 

565 """ 

566 self._handle.reset() 1cebdagh

567 self._source_ref = None 1cebdagh

568  

569 def __enter__(self): 

570 return self 

571  

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

573 self.close() 

574  

575 def __repr__(self): 

576 return f"TextureObject(handle=0x{as_intptr(self._handle):x})"