Coverage for cuda/core/texture/_texture.pyx: 94.44%
306 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-10 02:27 +0000
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-10 02:27 +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._context cimport Context
12from cuda.core.texture._array cimport OpaqueArray, OpaqueArray_check_open
13from cuda.core.texture._array import (
14 _ARRAYFORMAT_TO_CU,
15 _CU_TO_ARRAYFORMAT,
16 _FORMAT_ELEM_SIZE,
17 _validate_format_channels,
18)
19from cuda.core._memory._buffer cimport Buffer, Buffer_check_open
20from cuda.core.texture._mipmapped_array cimport MipmappedArray, MipmappedArray_check_open
21from cuda.core.texture._mipmapped_array import MipmappedArray as _PyMipmappedArray
22from cuda.core._resource_handles cimport (
23 ContextHandle,
24 TexObjectHandle,
25 as_cu,
26 as_intptr,
27 create_tex_object_handle_array,
28 create_tex_object_handle_linear,
29 create_tex_object_handle_mipmap,
30 get_array_context,
31 get_last_error,
32 get_mipmapped_array_context,
33)
34from cuda.core._utils.cuda_utils cimport HANDLE_RETURN
36from cuda.core.typing import AddressModeType, FilterModeType, ReadModeType
38from dataclasses import dataclass
40from cuda.core._utils.cuda_utils import check_or_create_options
43# Driver texture-descriptor flag bits (CU_TRSF_*).
44_TRSF_READ_AS_INTEGER = 0x01
45_TRSF_NORMALIZED_COORDINATES = 0x02
46_TRSF_SRGB = 0x10
47_TRSF_DISABLE_TRILINEAR_OPTIMIZATION = 0x20
48_TRSF_SEAMLESS_CUBEMAP = 0x40
51# Bridge between the public sampling StrEnums and the driver integer values.
52_ADDRESSMODE_TO_CU = {
53 AddressModeType.WRAP: int(cydriver.CU_TR_ADDRESS_MODE_WRAP),
54 AddressModeType.CLAMP: int(cydriver.CU_TR_ADDRESS_MODE_CLAMP),
55 AddressModeType.MIRROR: int(cydriver.CU_TR_ADDRESS_MODE_MIRROR),
56 AddressModeType.BORDER: int(cydriver.CU_TR_ADDRESS_MODE_BORDER),
57}
58_FILTERMODE_TO_CU = {
59 FilterModeType.POINT: int(cydriver.CU_TR_FILTER_MODE_POINT),
60 FilterModeType.LINEAR: int(cydriver.CU_TR_FILTER_MODE_LINEAR),
61}
64def _normalize_enum(name, value, enum_type):
65 """Coerce ``value`` to ``enum_type`` (a StrEnum), accepting a plain str."""
66 if isinstance(value, enum_type): 1tdqnrsphbfceaikLEHmKgj
67 return value 1tdqnrsphbfceaikEHmKgj
68 try: 1nLEH
69 return enum_type(value) 1nLEH
70 except ValueError as e: 1nLEH
71 valid = ", ".join(repr(m.value) for m in enum_type) 1nLEH
72 raise ValueError( 1nLEH
73 f"{name} must be a {enum_type.__name__} or one of {{{valid}}}, got {value!r}" 1nLEH
74 ) from e 1nLEH
77class ResourceDescriptor:
78 """Describes the memory backing a :class:`TextureObject`.
80 Construct via the ``from_*`` classmethods:
82 - :meth:`from_opaque_array` wraps a :class:`OpaqueArray` (works for both
83 :class:`TextureObject` and :class:`SurfaceObject`).
84 - :meth:`from_mipmapped_array` wraps a :class:`MipmappedArray` for mipmapped
85 sampling (texture only, not surface).
86 - :meth:`from_linear` wraps a :class:`Buffer` as a typed 1D fetch. Texture
87 objects built from a linear resource do not support filtering,
88 normalized coordinates, or addressing modes.
89 - :meth:`from_pitch2d` wraps a :class:`Buffer` as a row-pitched 2D image.
90 Supports filtering and 2D addressing, but only 2D access.
92 Linear and pitch2D resources cannot back a :class:`SurfaceObject` — those
93 require an :class:`OpaqueArray` allocated with ``is_surface_load_store=True``.
95 .. versionadded:: 1.1.0
96 """
98 __slots__ = (
99 "_kind", "_source",
100 "_format", "_num_channels",
101 "_size_bytes",
102 "_width", "_height", "_pitch_bytes",
103 )
105 def __init__(self):
106 raise RuntimeError( 1S
107 "ResourceDescriptor cannot be instantiated directly. "
108 "Use ResourceDescriptor.from_* factories."
109 )
111 @classmethod
112 def from_opaque_array(cls, array):
113 """Build a resource descriptor backed by a :class:`OpaqueArray`."""
114 if not isinstance(array, OpaqueArray): 1tdqnrspyzAhfikwmgj
115 raise TypeError(f"array must be a OpaqueArray, got {type(array).__name__}")
116 OpaqueArray_check_open(<OpaqueArray>array) 1tdqnrspyzAhfikwmgj
117 self = cls.__new__(cls) 1tdqnrspyzAhfikwmgj
118 self._kind = "array" 1tdqnrspyzAhfikwmgj
119 self._source = array 1tdqnrspyzAhfikwmgj
120 self._format = None 1tdqnrspyzAhfikwmgj
121 self._num_channels = None 1tdqnrspyzAhfikwmgj
122 self._size_bytes = None 1tdqnrspyzAhfikwmgj
123 self._width = None 1tdqnrspyzAhfikwmgj
124 self._height = None 1tdqnrspyzAhfikwmgj
125 self._pitch_bytes = None 1tdqnrspyzAhfikwmgj
126 return self 1tdqnrspyzAhfikwmgj
128 @classmethod
129 def from_mipmapped_array(cls, mipmapped_array):
130 """Build a resource descriptor backed by a :class:`MipmappedArray`.
132 Suitable for binding to a :class:`TextureObject` for mipmapped
133 sampling. Not valid as a :class:`SurfaceObject` backing: surfaces
134 require a single :class:`OpaqueArray` level (obtain via
135 :meth:`MipmappedArray.get_level`).
136 """
137 if not isinstance(mipmapped_array, _PyMipmappedArray): 1pMBFe
138 raise TypeError( 1M
139 f"mipmapped_array must be a MipmappedArray, got " 1M
140 f"{type(mipmapped_array).__name__}" 1M
141 )
142 MipmappedArray_check_open(<MipmappedArray>mipmapped_array) 1pBFe
143 self = cls.__new__(cls) 1pBFe
144 self._kind = "mipmapped_array" 1pBFe
145 self._source = mipmapped_array 1pBFe
146 self._format = None 1pBFe
147 self._num_channels = None 1pBFe
148 self._size_bytes = None 1pBFe
149 self._width = None 1pBFe
150 self._height = None 1pBFe
151 self._pitch_bytes = None 1pBFe
152 return self 1pBFe
154 @classmethod
155 def from_linear(cls, buffer, *, format, num_channels, size_bytes=None):
156 """Build a resource descriptor for a linear (typed 1D) texture fetch.
158 Parameters
159 ----------
160 buffer : Buffer
161 Device-memory backing. Must remain alive for the lifetime of any
162 :class:`TextureObject` built from this descriptor.
163 format : ArrayFormatType, str, or numpy.dtype
164 Element format. Accepts an :class:`~cuda.core.typing.ArrayFormatType`,
165 a plain string (e.g. ``"float32"``), or a NumPy dtype object.
166 num_channels : int
167 Channels per element. Must be 1, 2, or 4.
168 size_bytes : int, optional
169 Bytes of ``buffer`` to bind. Defaults to ``buffer.size``. Must not
170 exceed it.
172 Notes
173 -----
174 Texture objects built from a linear resource ignore the
175 :class:`TextureObjectOptions` addressing/filtering fields — kernels read
176 through a typed 1D fetch with bounds checking only.
177 """
178 if not isinstance(buffer, Buffer): 1uNQCIGvobc
179 raise TypeError(f"buffer must be a Buffer, got {type(buffer).__name__}") 1Q
180 Buffer_check_open(<Buffer>buffer) 1uNCIGvobc
181 fmt = _validate_format_channels(format, num_channels) 1uNCIGvobc
182 cu_format = _ARRAYFORMAT_TO_CU[fmt] 1uCIGvobc
184 buf_size = int(buffer.size) 1uCIGvobc
185 elem = _FORMAT_ELEM_SIZE[cu_format] * int(num_channels) 1uCIGvobc
186 if size_bytes is None: 1uCIGvobc
187 size = buf_size 1uobc
188 else:
189 size = int(size_bytes) 1CIGv
190 if size > buf_size: 1CIGv
191 raise ValueError( 1I
192 f"size_bytes ({size}) exceeds buffer.size ({buf_size})" 1I
193 )
194 if size < elem: 1uCGvobc
195 raise ValueError( 1G
196 f"size_bytes ({size}) must be at least one element ({elem} bytes)" 1G
197 )
198 if size % elem != 0: 1uCvobc
199 raise ValueError( 1C
200 f"size_bytes ({size}) must be a multiple of element size " 1C
201 f"({elem} bytes for {fmt.name} x {num_channels})" 1C
202 )
204 self = cls.__new__(cls) 1uvobc
205 self._kind = "linear" 1uvobc
206 self._source = buffer 1uvobc
207 self._format = cu_format 1uvobc
208 self._num_channels = int(num_channels) 1uvobc
209 self._size_bytes = size 1uvobc
210 self._width = None 1uvobc
211 self._height = None 1uvobc
212 self._pitch_bytes = None 1uvobc
213 return self 1uvobc
215 @classmethod
216 def from_pitch2d(
217 cls, buffer, *, format, num_channels, width, height, pitch_bytes
218 ):
219 """Build a resource descriptor for a row-pitched 2D image.
221 Parameters
222 ----------
223 buffer : Buffer
224 Device-memory backing. Must remain alive for the lifetime of any
225 :class:`TextureObject` built from this descriptor.
226 format : ArrayFormatType, str, or numpy.dtype
227 Element format. Accepts an :class:`~cuda.core.typing.ArrayFormatType`,
228 a plain string (e.g. ``"float32"``), or a NumPy dtype object.
229 num_channels : int
230 Channels per element. Must be 1, 2, or 4.
231 width : int
232 Image width, in elements.
233 height : int
234 Image height, in rows.
235 pitch_bytes : int
236 Distance between consecutive rows, in bytes. Must be at least
237 ``width * format_size * num_channels`` and meet the driver's
238 ``CU_DEVICE_ATTRIBUTE_TEXTURE_PITCH_ALIGNMENT``.
239 """
240 if not isinstance(buffer, Buffer): 1OPRJxDoa
241 raise TypeError(f"buffer must be a Buffer, got {type(buffer).__name__}") 1R
242 Buffer_check_open(<Buffer>buffer) 1OPJxDoa
243 fmt = _validate_format_channels(format, num_channels) 1OPJxDoa
244 cu_format = _ARRAYFORMAT_TO_CU[fmt] 1JxDoa
246 w = int(width) 1JxDoa
247 h = int(height) 1JxDoa
248 p = int(pitch_bytes) 1JxDoa
249 if w < 1: 1JxDoa
250 raise ValueError(f"width must be >= 1, got {w}") 1J
251 if h < 1: 1JxDoa
252 raise ValueError(f"height must be >= 1, got {h}") 1J
253 elem = _FORMAT_ELEM_SIZE[cu_format] * int(num_channels) 1xDoa
254 min_pitch = w * elem 1xDoa
255 if p < min_pitch: 1xDoa
256 raise ValueError( 1D
257 f"pitch_bytes ({p}) must be >= width * element_bytes ({min_pitch})" 1D
258 )
259 if p * h > int(buffer.size): 1xoa
260 raise ValueError( 1x
261 f"pitch_bytes * height ({p * h}) exceeds buffer.size ({int(buffer.size)})" 1x
262 )
264 self = cls.__new__(cls) 1oa
265 self._kind = "pitch2d" 1oa
266 self._source = buffer 1oa
267 self._format = cu_format 1oa
268 self._num_channels = int(num_channels) 1oa
269 self._size_bytes = None 1oa
270 self._width = w 1oa
271 self._height = h 1oa
272 self._pitch_bytes = p 1oa
273 return self 1oa
275 @property
276 def kind(self):
277 return self._kind 1tdqnrspuByzoFAhbfceaikmgj
279 @property
280 def source(self):
281 return self._source 1tdqnrspuByzAhbfceaikmgj
283 @property
284 def format(self):
285 """The element :class:`~cuda.core.typing.ArrayFormatType` (``None`` for array-backed)."""
286 return None if self._format is None else _CU_TO_ARRAYFORMAT[self._format] 1ua
288 @property
289 def num_channels(self):
290 """Channels per element (``None`` for array-backed)."""
291 return self._num_channels 1u
293 @property
294 def size_bytes(self):
295 """Bytes bound for a linear resource (``None`` for other kinds)."""
296 return self._size_bytes
298 @property
299 def width(self):
300 """Pitch2D image width, in elements (``None`` for other kinds)."""
301 return self._width
303 @property
304 def height(self):
305 """Pitch2D image height, in rows (``None`` for other kinds)."""
306 return self._height
308 @property
309 def pitch_bytes(self):
310 """Pitch2D row pitch, in bytes (``None`` for other kinds)."""
311 return self._pitch_bytes
313 def __repr__(self):
314 if self._kind == "linear": 1ua
315 return ( 1u
316 f"ResourceDescriptor(kind='linear', format={self.format.name}, " 1u
317 f"num_channels={self._num_channels}, size_bytes={self._size_bytes})" 1u
318 )
319 if self._kind == "pitch2d": 1a
320 return ( 1a
321 f"ResourceDescriptor(kind='pitch2d', format={self.format.name}, " 1a
322 f"num_channels={self._num_channels}, " 1a
323 f"width={self._width}, height={self._height}, " 1a
324 f"pitch_bytes={self._pitch_bytes})" 1a
325 )
326 return f"ResourceDescriptor(kind={self._kind!r})"
329@dataclass
330class TextureObjectOptions:
331 """Sampling state for a :class:`TextureObject` (mirrors ``CUDA_TEXTURE_DESC``).
333 Attributes
334 ----------
335 address_mode : AddressModeType or tuple of AddressModeType
336 Boundary behavior per axis. May be a single
337 :class:`~cuda.core.typing.AddressModeType` (applied to all axes) or a
338 tuple of 1-3 entries (one per dimension). Plain strings are accepted.
339 filter_mode : FilterModeType
340 Texel sampling mode. Default ``POINT``. Plain strings are accepted.
341 read_mode : ReadModeType
342 How sampled integer values are returned. Default ``ELEMENT_TYPE``.
343 Plain strings are accepted.
344 normalized_coords : bool
345 If True, coordinates are in ``[0, 1]`` instead of pixel indices.
346 srgb : bool
347 If True, perform sRGB → linear conversion on read (8-bit formats only).
348 disable_trilinear_optimization : bool
349 If True, request exact trilinear filtering.
350 seamless_cubemap : bool
351 If True, enable seamless cubemap edge filtering.
352 max_anisotropy : int
353 Maximum anisotropy; 0 disables anisotropic filtering.
354 mipmap_filter_mode : FilterModeType
355 Filtering between mipmap levels. Default ``POINT``. Plain strings are
356 accepted.
357 mipmap_level_bias : float
358 min_mipmap_level_clamp : float
359 max_mipmap_level_clamp : float
360 border_color : tuple of float or None
361 4-tuple used when ``address_mode`` includes ``BORDER``; ``None`` means
362 zero.
364 .. versionadded:: 1.1.0
365 """
367 address_mode: AddressModeType | str | tuple[AddressModeType | str, ...] = AddressModeType.CLAMP
368 filter_mode: FilterModeType | str = FilterModeType.POINT
369 read_mode: ReadModeType | str = ReadModeType.ELEMENT_TYPE
370 normalized_coords: bool = False
371 srgb: bool = False
372 disable_trilinear_optimization: bool = False
373 seamless_cubemap: bool = False
374 max_anisotropy: int = 0
375 mipmap_filter_mode: FilterModeType | str = FilterModeType.POINT
376 mipmap_level_bias: float = 0.0
377 min_mipmap_level_clamp: float = 0.0
378 max_mipmap_level_clamp: float = 0.0
379 border_color: tuple[float, ...] | None = None
381 def __post_init__(self):
382 self.filter_mode = _normalize_enum("filter_mode", self.filter_mode, FilterModeType) 1tdqnrsphbfceaikLEHmKgj
383 self.read_mode = _normalize_enum("read_mode", self.read_mode, ReadModeType) 1tdqnrsphbfceaikEHmKgj
384 self.mipmap_filter_mode = _normalize_enum( 1tdqnrsphbfceaikEmKgj
385 "mipmap_filter_mode", self.mipmap_filter_mode, FilterModeType 1tdqnrsphbfceaikEmKgj
386 )
389def _normalize_address_modes(address_mode):
390 """Return a 3-tuple of :class:`AddressModeType` values from a scalar or
391 1-3 tuple. Individual entries may be plain strings."""
392 if isinstance(address_mode, (AddressModeType, str)): 1dqnrshbfceaikmgj
393 m = _normalize_enum("address_mode", address_mode, AddressModeType) 1dhbfceaikmgj
394 return (m, m, m) 1dhbfceaikmgj
395 try: 1dqnrs
396 modes = tuple(address_mode) 1dqnrs
397 except TypeError as e: 1r
398 raise TypeError( 1r
399 "address_mode must be an AddressModeType or a tuple of AddressModeType"
400 ) from e 1r
401 if not 1 <= len(modes) <= 3: 1dqns
402 raise ValueError( 1qs
403 f"address_mode tuple must have 1-3 entries, got {len(modes)}" 1qs
404 )
405 modes = tuple( 1dn
406 _normalize_enum(f"address_mode[{i}]", m, AddressModeType) 1dn
407 for i, m in enumerate(modes) 1dn
408 )
409 # Pad to 3 entries by repeating the last one.
410 padded = list(modes) + [modes[-1]] * (3 - len(modes)) 1d
411 return tuple(padded) 1d
414cdef class TextureObject:
415 """A bindless texture handle for kernel-side sampled reads.
417 Wraps ``cuTexObjectCreate``. The underlying memory resource (e.g. the
418 :class:`OpaqueArray` referenced by the descriptor) is kept alive for the
419 lifetime of this object to prevent dangling handles.
421 Construct via :meth:`cuda.core.Device.create_texture_object`. Passes to
422 kernels as a 64-bit handle (via the ``handle`` property).
424 .. versionadded:: 1.1.0
425 """
427 def __init__(self, *args, **kwargs):
428 raise RuntimeError( 1T
429 "TextureObject cannot be instantiated directly. "
430 "Use Device.create_texture_object()."
431 )
433 @property
434 def handle(self):
435 """The underlying ``CUtexObject`` as an integer (64-bit kernel arg)."""
436 return as_intptr(self._handle) 1dfceaij
438 @property
439 def is_closed(self) -> bool:
440 """Whether this texture object has been closed."""
441 return self._handle.get() == NULL 1h
443 @property
444 def resource(self):
445 """The :class:`ResourceDescriptor` this texture was built from."""
446 return self._source_ref 1fce
448 @property
449 def options(self):
450 """The :class:`TextureObjectOptions` this texture was built from."""
451 return self._options 1f
453 @property
454 def device(self):
455 from cuda.core._device import Device 1bg
456 return Device(self._device_id) 1bg
458 cpdef close(self):
459 """Release this object's reference to the underlying ``CUtexObject``.
461 Destruction (``cuTexObjectDestroy``) and release of the backing resource
462 happen via the handle's deleter when the last reference is dropped.
463 Idempotent.
464 """
465 self._handle.reset() 1dhbfceaigj
466 self._source_ref = None 1dhbfceaigj
468 def __enter__(self):
469 return self 1bg
471 def __exit__(self, exc_type, exc, tb):
472 self.close() 1bg
474 def __repr__(self):
475 return f"TextureObject(handle=0x{as_intptr(self._handle):x})"
478def _create_texture_object(
479 resource, options, Context ctx, int device_id):
480 """Create a :class:`TextureObject` on the specified device.
482 Backs :meth:`cuda.core.Device.create_texture_object`. ``resource`` is a
483 :class:`ResourceDescriptor`; ``options`` is a :class:`TextureObjectOptions`
484 (or a mapping accepted by it).
485 """
486 if not isinstance(resource, ResourceDescriptor): 1tdqnrsphbfceaikwmKgj
487 raise TypeError( 1K
488 f"resource must be a ResourceDescriptor, got " 1K
489 f"{type(resource).__name__}" 1K
490 )
491 cdef object opts = check_or_create_options( 1tdqnrsphbfceaikwmgj
492 TextureObjectOptions, options, "Texture object options" 1tdqnrsphbfceaikwmgj
493 )
495 cdef cydriver.CUDA_RESOURCE_DESC res_desc
496 cdef cydriver.CUDA_TEXTURE_DESC tex_desc
497 memset(&res_desc, 0, sizeof(res_desc)) 1tdqnrsphbfceaikmgj
498 memset(&tex_desc, 0, sizeof(tex_desc)) 1tdqnrsphbfceaikmgj
500 # --- Resource descriptor ---
501 cdef OpaqueArray arr
502 cdef MipmappedArray mip
503 cdef Buffer buf
504 cdef intptr_t devptr
505 cdef ContextHandle resource_context
506 cdef int resource_device_id
507 if resource.kind == "array": 1tdqnrsphbfceaikmgj
508 arr = <OpaqueArray>resource.source 1tdqnrsphfikmgj
509 OpaqueArray_check_open(arr) 1tdqnrsphfikmgj
510 resource_context = get_array_context(arr._handle) 1tdqnrshfikmgj
511 resource_device_id = arr._device_id 1tdqnrshfikmgj
512 res_desc.resType = cydriver.CU_RESOURCE_TYPE_ARRAY 1tdqnrshfikmgj
513 res_desc.res.array.hArray = as_cu(arr._handle) 1tdqnrshfikmgj
514 elif resource.kind == "mipmapped_array": 1pbcea
515 mip = <MipmappedArray>resource.source 1pe
516 MipmappedArray_check_open(mip) 1pe
517 resource_context = get_mipmapped_array_context(mip._handle) 1e
518 resource_device_id = mip._device_id 1e
519 res_desc.resType = cydriver.CU_RESOURCE_TYPE_MIPMAPPED_ARRAY 1e
520 res_desc.res.mipmap.hMipmappedArray = as_cu(mip._handle) 1e
521 elif resource.kind == "linear": 1bca
522 buf = <Buffer>resource.source 1bc
523 Buffer_check_open(buf) 1bc
524 resource_device_id = buf.device_id # -1 for memory not bound to a device 1bc
525 devptr = int(buf.handle) 1bc
526 res_desc.resType = cydriver.CU_RESOURCE_TYPE_LINEAR 1bc
527 res_desc.res.linear.devPtr = <cydriver.CUdeviceptr>devptr 1bc
528 res_desc.res.linear.format = <cydriver.CUarray_format><int>resource._format 1bc
529 res_desc.res.linear.numChannels = <unsigned int>resource._num_channels 1bc
530 res_desc.res.linear.sizeInBytes = <size_t>resource._size_bytes 1bc
531 elif resource.kind == "pitch2d": 1a
532 buf = <Buffer>resource.source 1a
533 Buffer_check_open(buf) 1a
534 resource_device_id = buf.device_id # -1 for memory not bound to a device 1a
535 devptr = int(buf.handle) 1a
536 res_desc.resType = cydriver.CU_RESOURCE_TYPE_PITCH2D 1a
537 res_desc.res.pitch2D.devPtr = <cydriver.CUdeviceptr>devptr 1a
538 res_desc.res.pitch2D.format = <cydriver.CUarray_format><int>resource._format 1a
539 res_desc.res.pitch2D.numChannels = <unsigned int>resource._num_channels 1a
540 res_desc.res.pitch2D.width = <size_t>resource._width 1a
541 res_desc.res.pitch2D.height = <size_t>resource._height 1a
542 res_desc.res.pitch2D.pitchInBytes = <size_t>resource._pitch_bytes 1a
543 else:
544 raise NotImplementedError(
545 f"ResourceDescriptor kind {resource.kind!r} is not yet supported"
546 )
547 if resource_device_id >= 0 and resource_device_id != device_id: 1tdqnrshbfceaikmgj
548 raise ValueError(
549 f"resource belongs to device {resource_device_id}, "
550 f"but texture creation was requested on device {device_id}"
551 )
552 if resource_context and as_cu(resource_context) != as_cu(ctx._h_context): 1tdqnrshbfceaikmgj
553 raise ValueError("resource is not compatible with this Device object") 1t
555 # --- Texture descriptor ---
556 # filter_mode/read_mode/mipmap_filter_mode are normalized to their
557 # StrEnum types by TextureObjectOptions.__post_init__; address_mode is
558 # normalized (and str-coerced) here.
559 modes = _normalize_address_modes(opts.address_mode) 1dqnrshbfceaikmgj
560 tex_desc.addressMode[0] = <cydriver.CUaddress_mode>_ADDRESSMODE_TO_CU[modes[0]] 1dhbfceaikmgj
561 tex_desc.addressMode[1] = <cydriver.CUaddress_mode>_ADDRESSMODE_TO_CU[modes[1]] 1dhbfceaikmgj
562 tex_desc.addressMode[2] = <cydriver.CUaddress_mode>_ADDRESSMODE_TO_CU[modes[2]] 1dhbfceaikmgj
564 tex_desc.filterMode = <cydriver.CUfilter_mode>_FILTERMODE_TO_CU[opts.filter_mode] 1dhbfceaikmgj
566 cdef unsigned int flags = 0 1dhbfceaikmgj
567 # CU_TRSF_READ_AS_INTEGER suppresses normalization, so it maps to
568 # ReadModeType.ELEMENT_TYPE.
569 if opts.read_mode == ReadModeType.ELEMENT_TYPE: 1dhbfceaikmgj
570 flags |= _TRSF_READ_AS_INTEGER 1dhbfceaikmgj
571 if opts.normalized_coords: 1dhbfceaikmgj
572 flags |= _TRSF_NORMALIZED_COORDINATES 1fe
573 if opts.srgb: 1dhbfceaikmgj
574 flags |= _TRSF_SRGB
575 if opts.disable_trilinear_optimization: 1dhbfceaikmgj
576 flags |= _TRSF_DISABLE_TRILINEAR_OPTIMIZATION
577 if opts.seamless_cubemap: 1dhbfceaikmgj
578 flags |= _TRSF_SEAMLESS_CUBEMAP
579 tex_desc.flags = flags 1dhbfceaikmgj
581 if opts.max_anisotropy < 0: 1dhbfceaikmgj
582 raise ValueError("max_anisotropy must be >= 0") 1m
583 tex_desc.maxAnisotropy = <unsigned int>opts.max_anisotropy 1dhbfceaikgj
585 tex_desc.mipmapFilterMode = <cydriver.CUfilter_mode>_FILTERMODE_TO_CU[opts.mipmap_filter_mode] 1dhbfceaikgj
586 tex_desc.mipmapLevelBias = <float>opts.mipmap_level_bias 1dhbfceaikgj
587 tex_desc.minMipmapLevelClamp = <float>opts.min_mipmap_level_clamp 1dhbfceaikgj
588 tex_desc.maxMipmapLevelClamp = <float>opts.max_mipmap_level_clamp 1dhbfceaikgj
590 cdef int i
591 if opts.border_color is None: 1dhbfceaikgj
592 for i in range(4): 1dhbfceaigj
593 tex_desc.borderColor[i] = 0.0 1dhbfceaigj
594 else:
595 bc = tuple(opts.border_color) 1k
596 if len(bc) != 4: 1k
597 raise ValueError( 1k
598 f"border_color must have 4 elements, got {len(bc)}" 1k
599 )
600 for i in range(4):
601 tex_desc.borderColor[i] = <float>bc[i]
603 cdef TexObjectHandle h
604 if resource.kind == "array": 1dhbfceaigj
605 h = create_tex_object_handle_array( 1dhfigj
606 ctx._h_context, res_desc, tex_desc, arr._handle) 1dhfigj
607 elif resource.kind == "mipmapped_array": 1bcea
608 h = create_tex_object_handle_mipmap( 1e
609 ctx._h_context, res_desc, tex_desc, mip._handle) 1e
610 else: # linear or pitch2d — both backed by a device Buffer
611 h = create_tex_object_handle_linear( 1bca
612 ctx._h_context, res_desc, tex_desc, buf._h_ptr) 1bca
613 if not h: 1dhbfceaigj
614 HANDLE_RETURN(get_last_error())
616 cdef TextureObject self = TextureObject.__new__(TextureObject) 1dhbfceaigj
617 self._handle = h 1dhbfceaigj
618 self._source_ref = resource 1dhbfceaigj
619 self._options = opts 1dhbfceaigj
620 self._device_id = device_id 1dhbfceaigj
621 return self 1dhbfceaigj