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

291 statements  

« prev     ^ index     » next       coverage.py v7.16.0, created at 2026-09-03 02:41 +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, OpaqueArray_check_open 

12from cuda.core.texture._array import ( 

13 _ARRAYFORMAT_TO_CU, 

14 _CU_TO_ARRAYFORMAT, 

15 _FORMAT_ELEM_SIZE, 

16 _validate_format_channels, 

17) 

18from cuda.core._memory._buffer cimport Buffer, Buffer_check_open 

19from cuda.core.texture._mipmapped_array cimport MipmappedArray, MipmappedArray_check_open 

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

21from cuda.core._resource_handles cimport ( 

22 TexObjectHandle, 

23 as_cu, 

24 as_intptr, 

25 create_tex_object_handle_array, 

26 create_tex_object_handle_linear, 

27 create_tex_object_handle_mipmap, 

28 get_last_error, 

29) 

30from cuda.core._utils.cuda_utils cimport ( 

31 HANDLE_RETURN, 

32 _get_current_device_id, 

33) 

34  

35from cuda.core.typing import AddressModeType, FilterModeType, ReadModeType 

36  

37from dataclasses import dataclass 

38  

39from cuda.core._utils.cuda_utils import check_or_create_options 

40  

41  

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

43_TRSF_READ_AS_INTEGER = 0x01 

44_TRSF_NORMALIZED_COORDINATES = 0x02 

45_TRSF_SRGB = 0x10 

46_TRSF_DISABLE_TRILINEAR_OPTIMIZATION = 0x20 

47_TRSF_SEAMLESS_CUBEMAP = 0x40 

48  

49  

50# Bridge between the public sampling StrEnums and the driver integer values. 

51_ADDRESSMODE_TO_CU = { 

52 AddressModeType.WRAP: int(cydriver.CU_TR_ADDRESS_MODE_WRAP), 

53 AddressModeType.CLAMP: int(cydriver.CU_TR_ADDRESS_MODE_CLAMP), 

54 AddressModeType.MIRROR: int(cydriver.CU_TR_ADDRESS_MODE_MIRROR), 

55 AddressModeType.BORDER: int(cydriver.CU_TR_ADDRESS_MODE_BORDER), 

56} 

57_FILTERMODE_TO_CU = { 

58 FilterModeType.POINT: int(cydriver.CU_TR_FILTER_MODE_POINT), 

59 FilterModeType.LINEAR: int(cydriver.CU_TR_FILTER_MODE_LINEAR), 

60} 

61  

62  

63def _normalize_enum(name, value, enum_type): 

64 """Coerce ``value`` to ``enum_type`` (a StrEnum), accepting a plain str.""" 

65 if isinstance(value, enum_type): 1colpqnfebdagjIBEkHh

66 return value 1colpqnfebdagjBEkHh

67 try: 1lIBE

68 return enum_type(value) 1lIBE

69 except ValueError as e: 1lIBE

70 valid = ", ".join(repr(m.value) for m in enum_type) 1lIBE

71 raise ValueError( 1lIBE

72 f"{name} must be a {enum_type.__name__} or one of {{{valid}}}, got {value!r}" 1lIBE

73 ) from e 1lIBE

74  

75  

76class ResourceDescriptor: 

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

78  

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

80  

81 - :meth:`from_opaque_array` wraps a :class:`OpaqueArray` (works for both 

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

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

84 sampling (texture only, not surface). 

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

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

87 normalized coordinates, or addressing modes. 

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

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

90  

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

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

93  

94 .. versionadded:: 1.1.0 

95 """ 

96  

97 __slots__ = ( 

98 "_kind", "_source", 

99 "_format", "_num_channels", 

100 "_size_bytes", 

101 "_width", "_height", "_pitch_bytes", 

102 ) 

103  

104 def __init__(self): 

105 raise RuntimeError( 1P

106 "ResourceDescriptor cannot be instantiated directly. " 

107 "Use ResourceDescriptor.from_* factories." 

108 ) 

109  

110 @classmethod 

111 def from_opaque_array(cls, array): 

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

113 if not isinstance(array, OpaqueArray): 1colpqnvwxfegjtkh

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

115 OpaqueArray_check_open(<OpaqueArray>array) 1icolpqnvwxfegjtkh

116 self = cls.__new__(cls) 1colpqnvwxfegjtkh

117 self._kind = "array" 1colpqnvwxfegjtkh

118 self._source = array 1colpqnvwxfegjtkh

119 self._format = None 1colpqnvwxfegjtkh

120 self._num_channels = None 1colpqnvwxfegjtkh

121 self._size_bytes = None 1colpqnvwxfegjtkh

122 self._width = None 1colpqnvwxfegjtkh

123 self._height = None 1colpqnvwxfegjtkh

124 self._pitch_bytes = None 1colpqnvwxfegjtkh

125 return self 1colpqnvwxfegjtkh

126  

127 @classmethod 

128 def from_mipmapped_array(cls, mipmapped_array): 

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

130  

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

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

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

134 :meth:`MipmappedArray.get_level`). 

135 """ 

136 if not isinstance(mipmapped_array, _PyMipmappedArray): 1nJyCd

137 raise TypeError( 1J

138 f"mipmapped_array must be a MipmappedArray, got " 1J

139 f"{type(mipmapped_array).__name__}" 1J

140 ) 

141 MipmappedArray_check_open(<MipmappedArray>mipmapped_array) 1nyCd

142 self = cls.__new__(cls) 1nyCd

143 self._kind = "mipmapped_array" 1nyCd

144 self._source = mipmapped_array 1nyCd

145 self._format = None 1nyCd

146 self._num_channels = None 1nyCd

147 self._size_bytes = None 1nyCd

148 self._width = None 1nyCd

149 self._height = None 1nyCd

150 self._pitch_bytes = None 1nyCd

151 return self 1nyCd

152  

153 @classmethod 

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

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

156  

157 Parameters 

158 ---------- 

159 buffer : Buffer 

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

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

162 format : ArrayFormatType, str, or numpy.dtype 

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

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

165 num_channels : int 

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

167 size_bytes : int, optional 

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

169 exceed it. 

170  

171 Notes 

172 ----- 

173 Texture objects built from a linear resource ignore the 

174 :class:`TextureObjectOptions` addressing/filtering fields — kernels read 

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

176 """ 

177 if not isinstance(buffer, Buffer): 1rKNzFDsmb

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

179 Buffer_check_open(<Buffer>buffer) 1rKzFDsmb

180 fmt = _validate_format_channels(format, num_channels) 1rKzFDsmb

181 cu_format = _ARRAYFORMAT_TO_CU[fmt] 1rzFDsmb

182  

183 buf_size = int(buffer.size) 1rzFDsmb

184 elem = _FORMAT_ELEM_SIZE[cu_format] * int(num_channels) 1rzFDsmb

185 if size_bytes is None: 1rzFDsmb

186 size = buf_size 1rmb

187 else: 

188 size = int(size_bytes) 1zFDs

189 if size > buf_size: 1zFDs

190 raise ValueError( 1F

191 f"size_bytes ({size}) exceeds buffer.size ({buf_size})" 1F

192 ) 

193 if size < elem: 1rzDsmb

194 raise ValueError( 1D

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

196 ) 

197 if size % elem != 0: 1rzsmb

198 raise ValueError( 1z

199 f"size_bytes ({size}) must be a multiple of element size " 1z

200 f"({elem} bytes for {fmt.name} x {num_channels})" 1z

201 ) 

202  

203 self = cls.__new__(cls) 1rsmb

204 self._kind = "linear" 1rsmb

205 self._source = buffer 1rsmb

206 self._format = cu_format 1rsmb

207 self._num_channels = int(num_channels) 1rsmb

208 self._size_bytes = size 1rsmb

209 self._width = None 1rsmb

210 self._height = None 1rsmb

211 self._pitch_bytes = None 1rsmb

212 return self 1rsmb

213  

214 @classmethod 

215 def from_pitch2d( 

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

217 ): 

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

219  

220 Parameters 

221 ---------- 

222 buffer : Buffer 

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

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

225 format : ArrayFormatType, str, or numpy.dtype 

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

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

228 num_channels : int 

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

230 width : int 

231 Image width, in elements. 

232 height : int 

233 Image height, in rows. 

234 pitch_bytes : int 

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

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

237 ``CU_DEVICE_ATTRIBUTE_TEXTURE_PITCH_ALIGNMENT``. 

238 """ 

239 if not isinstance(buffer, Buffer): 1LMOGuAma

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

241 Buffer_check_open(<Buffer>buffer) 1LMGuAma

242 fmt = _validate_format_channels(format, num_channels) 1LMGuAma

243 cu_format = _ARRAYFORMAT_TO_CU[fmt] 1GuAma

244  

245 w = int(width) 1GuAma

246 h = int(height) 1GuAma

247 p = int(pitch_bytes) 1GuAma

248 if w < 1: 1GuAma

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

250 if h < 1: 1GuAma

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

252 elem = _FORMAT_ELEM_SIZE[cu_format] * int(num_channels) 1uAma

253 min_pitch = w * elem 1uAma

254 if p < min_pitch: 1uAma

255 raise ValueError( 1A

256 f"pitch_bytes ({p}) must be >= width * element_bytes ({min_pitch})" 1A

257 ) 

258 if p * h > int(buffer.size): 1uma

259 raise ValueError( 1u

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

261 ) 

262  

263 self = cls.__new__(cls) 1ma

264 self._kind = "pitch2d" 1ma

265 self._source = buffer 1ma

266 self._format = cu_format 1ma

267 self._num_channels = int(num_channels) 1ma

268 self._size_bytes = None 1ma

269 self._width = w 1ma

270 self._height = h 1ma

271 self._pitch_bytes = p 1ma

272 return self 1ma

273  

274 @property 

275 def kind(self): 

276 return self._kind 1colpqnryvwmCxfebdagjkh

277  

278 @property 

279 def source(self): 

280 return self._source 1colpqnryvwxfebdagjkh

281  

282 @property 

283 def format(self): 

284 """The element :class:`~cuda.core.typing.ArrayFormatType` (``None`` for array-backed).""" 

285 return None if self._format is None else _CU_TO_ARRAYFORMAT[self._format] 1ra

286  

287 @property 

288 def num_channels(self): 

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

290 return self._num_channels 1r

291  

292 @property 

293 def size_bytes(self): 

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

295 return self._size_bytes 

296  

297 @property 

298 def width(self): 

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

300 return self._width 

301  

302 @property 

303 def height(self): 

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

305 return self._height 

306  

307 @property 

308 def pitch_bytes(self): 

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

310 return self._pitch_bytes 

311  

312 def __repr__(self): 

313 if self._kind == "linear": 1ra

314 return ( 1r

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

316 f"num_channels={self._num_channels}, size_bytes={self._size_bytes})" 1r

317 ) 

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

319 return ( 1a

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

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

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

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

324 ) 

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

326  

327  

328@dataclass 

329class TextureObjectOptions: 

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

331  

332 Attributes 

333 ---------- 

334 address_mode : AddressModeType or tuple of AddressModeType 

335 Boundary behavior per axis. May be a single 

336 :class:`~cuda.core.typing.AddressModeType` (applied to all axes) or a 

337 tuple of 1-3 entries (one per dimension). Plain strings are accepted. 

338 filter_mode : FilterModeType 

339 Texel sampling mode. Default ``POINT``. Plain strings are accepted. 

340 read_mode : ReadModeType 

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

342 Plain strings are accepted. 

343 normalized_coords : bool 

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

345 srgb : bool 

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

347 disable_trilinear_optimization : bool 

348 If True, request exact trilinear filtering. 

349 seamless_cubemap : bool 

350 If True, enable seamless cubemap edge filtering. 

351 max_anisotropy : int 

352 Maximum anisotropy; 0 disables anisotropic filtering. 

353 mipmap_filter_mode : FilterModeType 

354 Filtering between mipmap levels. Default ``POINT``. Plain strings are 

355 accepted. 

356 mipmap_level_bias : float 

357 min_mipmap_level_clamp : float 

358 max_mipmap_level_clamp : float 

359 border_color : tuple of float or None 

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

361 zero. 

362  

363 .. versionadded:: 1.1.0 

364 """ 

365  

366 address_mode: AddressModeType | str | tuple[AddressModeType | str, ...] = AddressModeType.CLAMP 

367 filter_mode: FilterModeType | str = FilterModeType.POINT 

368 read_mode: ReadModeType | str = ReadModeType.ELEMENT_TYPE 

369 normalized_coords: bool = False 

370 srgb: bool = False 

371 disable_trilinear_optimization: bool = False 

372 seamless_cubemap: bool = False 

373 max_anisotropy: int = 0 

374 mipmap_filter_mode: FilterModeType | str = FilterModeType.POINT 

375 mipmap_level_bias: float = 0.0 

376 min_mipmap_level_clamp: float = 0.0 

377 max_mipmap_level_clamp: float = 0.0 

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

379  

380 def __post_init__(self): 

381 self.filter_mode = _normalize_enum("filter_mode", self.filter_mode, FilterModeType) 1colpqnfebdagjIBEkHh

382 self.read_mode = _normalize_enum("read_mode", self.read_mode, ReadModeType) 1colpqnfebdagjBEkHh

383 self.mipmap_filter_mode = _normalize_enum( 1colpqnfebdagjBkHh

384 "mipmap_filter_mode", self.mipmap_filter_mode, FilterModeType 1colpqnfebdagjBkHh

385 ) 

386  

387  

388def _normalize_address_modes(address_mode): 

389 """Return a 3-tuple of :class:`AddressModeType` values from a scalar or 

390 1-3 tuple. Individual entries may be plain strings.""" 

391 if isinstance(address_mode, (AddressModeType, str)): 1colpqfebdagjkh

392 m = _normalize_enum("address_mode", address_mode, AddressModeType) 1cfebdagjkh

393 return (m, m, m) 1cfebdagjkh

394 try: 1colpq

395 modes = tuple(address_mode) 1colpq

396 except TypeError as e: 1p

397 raise TypeError( 1p

398 "address_mode must be an AddressModeType or a tuple of AddressModeType" 

399 ) from e 1p

400 if not 1 <= len(modes) <= 3: 1colq

401 raise ValueError( 1oq

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

403 ) 

404 modes = tuple( 1cl

405 _normalize_enum(f"address_mode[{i}]", m, AddressModeType) 1cl

406 for i, m in enumerate(modes) 1cl

407 ) 

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

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

410 return tuple(padded) 1c

411  

412  

413cdef class TextureObject: 

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

415  

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

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

418 lifetime of this object to prevent dangling handles. 

419  

420 Construct via :meth:`cuda.core.Device.create_texture_object`. Passes to 

421 kernels as a 64-bit handle (via the ``handle`` property). 

422  

423 .. versionadded:: 1.1.0 

424 """ 

425  

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

427 raise RuntimeError( 1Q

428 "TextureObject cannot be instantiated directly. " 

429 "Use Device.create_texture_object()." 

430 ) 

431  

432 @property 

433 def handle(self): 

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

435 return as_intptr(self._handle) 1cebdagh

436  

437 @property 

438 def is_closed(self) -> bool: 

439 """Whether this texture object has been closed.""" 

440 return self._handle.get() == NULL 1f

441  

442 @property 

443 def resource(self): 

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

445 return self._source_ref 1ebd

446  

447 @property 

448 def options(self): 

449 """The :class:`TextureObjectOptions` this texture was built from.""" 

450 return self._options 1e

451  

452 @property 

453 def device(self): 

454 from cuda.core._device import Device 

455 return Device(self._device_id) 

456  

457 cpdef close(self): 

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

459  

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

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

462 Idempotent. 

463 """ 

464 self._handle.reset() 1cfebdagh

465 self._source_ref = None 1cfebdagh

466  

467 def __enter__(self): 

468 return self 

469  

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

471 self.close() 

472  

473 def __repr__(self): 

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

475  

476  

477def _create_texture_object(resource, options): 

478 """Create a :class:`TextureObject` on the current device. 

479  

480 Backs :meth:`cuda.core.Device.create_texture_object`. ``resource`` is a 

481 :class:`ResourceDescriptor`; ``options`` is a :class:`TextureObjectOptions` 

482 (or a mapping accepted by it). 

483 """ 

484 if not isinstance(resource, ResourceDescriptor): 1colpqnfebdagjtkHh

485 raise TypeError( 1H

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

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

488 ) 

489 cdef object opts = check_or_create_options( 1colpqnfebdagjtkh

490 TextureObjectOptions, options, "Texture object options" 1colpqnfebdagjtkh

491 ) 

492  

493 cdef cydriver.CUDA_RESOURCE_DESC res_desc 

494 cdef cydriver.CUDA_TEXTURE_DESC tex_desc 

495 memset(&res_desc, 0, sizeof(res_desc)) 1colpqnfebdagjkh

496 memset(&tex_desc, 0, sizeof(tex_desc)) 1colpqnfebdagjkh

497  

498 # --- Resource descriptor --- 

499 cdef OpaqueArray arr 

500 cdef MipmappedArray mip 

501 cdef Buffer buf 

502 cdef intptr_t devptr 

503 if resource.kind == "array": 1colpqnfebdagjkh

504 arr = <OpaqueArray>resource.source 1colpqnfegjkh

505 OpaqueArray_check_open(arr) 1colpqnfegjkh

506 res_desc.resType = cydriver.CU_RESOURCE_TYPE_ARRAY 1colpqfegjkh

507 res_desc.res.array.hArray = as_cu(arr._handle) 1colpqfegjkh

508 elif resource.kind == "mipmapped_array": 1nbda

509 mip = <MipmappedArray>resource.source 1nd

510 MipmappedArray_check_open(mip) 1nd

511 res_desc.resType = cydriver.CU_RESOURCE_TYPE_MIPMAPPED_ARRAY 1d

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

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

514 buf = <Buffer>resource.source 1b

515 Buffer_check_open(buf) 1b

516 devptr = int(buf.handle) 1b

517 res_desc.resType = cydriver.CU_RESOURCE_TYPE_LINEAR 1b

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

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

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

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

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

523 buf = <Buffer>resource.source 1a

524 Buffer_check_open(buf) 1a

525 devptr = int(buf.handle) 1a

526 res_desc.resType = cydriver.CU_RESOURCE_TYPE_PITCH2D 1a

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

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

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

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

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

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

533 else: 

534 raise NotImplementedError( 

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

536 ) 

537  

538 # --- Texture descriptor --- 

539 # filter_mode/read_mode/mipmap_filter_mode are normalized to their 

540 # StrEnum types by TextureObjectOptions.__post_init__; address_mode is 

541 # normalized (and str-coerced) here. 

542 modes = _normalize_address_modes(opts.address_mode) 1colpqfebdagjkh

543 tex_desc.addressMode[0] = <cydriver.CUaddress_mode>_ADDRESSMODE_TO_CU[modes[0]] 1cfebdagjkh

544 tex_desc.addressMode[1] = <cydriver.CUaddress_mode>_ADDRESSMODE_TO_CU[modes[1]] 1cfebdagjkh

545 tex_desc.addressMode[2] = <cydriver.CUaddress_mode>_ADDRESSMODE_TO_CU[modes[2]] 1cfebdagjkh

546  

547 tex_desc.filterMode = <cydriver.CUfilter_mode>_FILTERMODE_TO_CU[opts.filter_mode] 1cfebdagjkh

548  

549 cdef unsigned int flags = 0 1cfebdagjkh

550 # CU_TRSF_READ_AS_INTEGER suppresses normalization, so it maps to 

551 # ReadModeType.ELEMENT_TYPE. 

552 if opts.read_mode == ReadModeType.ELEMENT_TYPE: 1cfebdagjkh

553 flags |= _TRSF_READ_AS_INTEGER 1cfebdagjkh

554 if opts.normalized_coords: 1cfebdagjkh

555 flags |= _TRSF_NORMALIZED_COORDINATES 1ed

556 if opts.srgb: 1cfebdagjkh

557 flags |= _TRSF_SRGB 

558 if opts.disable_trilinear_optimization: 1cfebdagjkh

559 flags |= _TRSF_DISABLE_TRILINEAR_OPTIMIZATION 

560 if opts.seamless_cubemap: 1cfebdagjkh

561 flags |= _TRSF_SEAMLESS_CUBEMAP 

562 tex_desc.flags = flags 1cfebdagjkh

563  

564 if opts.max_anisotropy < 0: 1cfebdagjkh

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

566 tex_desc.maxAnisotropy = <unsigned int>opts.max_anisotropy 1cfebdagjh

567  

568 tex_desc.mipmapFilterMode = <cydriver.CUfilter_mode>_FILTERMODE_TO_CU[opts.mipmap_filter_mode] 1cfebdagjh

569 tex_desc.mipmapLevelBias = <float>opts.mipmap_level_bias 1cfebdagjh

570 tex_desc.minMipmapLevelClamp = <float>opts.min_mipmap_level_clamp 1cfebdagjh

571 tex_desc.maxMipmapLevelClamp = <float>opts.max_mipmap_level_clamp 1cfebdagjh

572  

573 cdef int i 

574 if opts.border_color is None: 1cfebdagjh

575 for i in range(4): 1cfebdagh

576 tex_desc.borderColor[i] = 0.0 1cfebdagh

577 else: 

578 bc = tuple(opts.border_color) 1j

579 if len(bc) != 4: 1j

580 raise ValueError( 1j

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

582 ) 

583 for i in range(4): 

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

585  

586 cdef TexObjectHandle h 

587 if resource.kind == "array": 1cfebdagh

588 h = create_tex_object_handle_array(res_desc, tex_desc, arr._handle) 1cfegh

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

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

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

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

593 if not h: 1cfebdagh

594 HANDLE_RETURN(get_last_error()) 

595  

596 cdef TextureObject self = TextureObject.__new__(TextureObject) 1cfebdagh

597 self._handle = h 1cfebdagh

598 self._source_ref = resource 1cfebdagh

599 self._options = opts 1cfebdagh

600 self._device_id = _get_current_device_id() 1cfebdagh

601 return self 1cfebdagh