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
« 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
5from __future__ import annotations
7from libc.stdint cimport intptr_t
8from libc.string cimport memset
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)
35from cuda.core.typing import AddressModeType, FilterModeType, ReadModeType
37from dataclasses import dataclass
39from cuda.core._utils.cuda_utils import check_or_create_options
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
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}
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
76class ResourceDescriptor:
77 """Describes the memory backing a :class:`TextureObject`.
79 Construct via the ``from_*`` classmethods:
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.
91 Linear and pitch2D resources cannot back a :class:`SurfaceObject` — those
92 require an :class:`OpaqueArray` allocated with ``is_surface_load_store=True``.
94 .. versionadded:: 1.1.0
95 """
97 __slots__ = (
98 "_kind", "_source",
99 "_format", "_num_channels",
100 "_size_bytes",
101 "_width", "_height", "_pitch_bytes",
102 )
104 def __init__(self):
105 raise RuntimeError( 1P
106 "ResourceDescriptor cannot be instantiated directly. "
107 "Use ResourceDescriptor.from_* factories."
108 )
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
127 @classmethod
128 def from_mipmapped_array(cls, mipmapped_array):
129 """Build a resource descriptor backed by a :class:`MipmappedArray`.
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
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.
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.
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
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 )
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
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.
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
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 )
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
274 @property
275 def kind(self):
276 return self._kind 1colpqnryvwmCxfebdagjkh
278 @property
279 def source(self):
280 return self._source 1colpqnryvwxfebdagjkh
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
287 @property
288 def num_channels(self):
289 """Channels per element (``None`` for array-backed)."""
290 return self._num_channels 1r
292 @property
293 def size_bytes(self):
294 """Bytes bound for a linear resource (``None`` for other kinds)."""
295 return self._size_bytes
297 @property
298 def width(self):
299 """Pitch2D image width, in elements (``None`` for other kinds)."""
300 return self._width
302 @property
303 def height(self):
304 """Pitch2D image height, in rows (``None`` for other kinds)."""
305 return self._height
307 @property
308 def pitch_bytes(self):
309 """Pitch2D row pitch, in bytes (``None`` for other kinds)."""
310 return self._pitch_bytes
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})"
328@dataclass
329class TextureObjectOptions:
330 """Sampling state for a :class:`TextureObject` (mirrors ``CUDA_TEXTURE_DESC``).
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.
363 .. versionadded:: 1.1.0
364 """
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
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 )
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
413cdef class TextureObject:
414 """A bindless texture handle for kernel-side sampled reads.
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.
420 Construct via :meth:`cuda.core.Device.create_texture_object`. Passes to
421 kernels as a 64-bit handle (via the ``handle`` property).
423 .. versionadded:: 1.1.0
424 """
426 def __init__(self, *args, **kwargs):
427 raise RuntimeError( 1Q
428 "TextureObject cannot be instantiated directly. "
429 "Use Device.create_texture_object()."
430 )
432 @property
433 def handle(self):
434 """The underlying ``CUtexObject`` as an integer (64-bit kernel arg)."""
435 return as_intptr(self._handle) 1cebdagh
437 @property
438 def is_closed(self) -> bool:
439 """Whether this texture object has been closed."""
440 return self._handle.get() == NULL 1f
442 @property
443 def resource(self):
444 """The :class:`ResourceDescriptor` this texture was built from."""
445 return self._source_ref 1ebd
447 @property
448 def options(self):
449 """The :class:`TextureObjectOptions` this texture was built from."""
450 return self._options 1e
452 @property
453 def device(self):
454 from cuda.core._device import Device
455 return Device(self._device_id)
457 cpdef close(self):
458 """Release this object's reference to the underlying ``CUtexObject``.
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
467 def __enter__(self):
468 return self
470 def __exit__(self, exc_type, exc, tb):
471 self.close()
473 def __repr__(self):
474 return f"TextureObject(handle=0x{as_intptr(self._handle):x})"
477def _create_texture_object(resource, options):
478 """Create a :class:`TextureObject` on the current device.
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 )
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
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 )
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
547 tex_desc.filterMode = <cydriver.CUfilter_mode>_FILTERMODE_TO_CU[opts.filter_mode] 1cfebdagjkh
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
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
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
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]
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())
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