Coverage for cuda/core/texture/_mipmapped_array.pyx: 87.50%
72 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 cuda.bindings cimport cydriver
8from cuda.core._context cimport Context
9from cuda.core.texture._array cimport _array_from_handle
10from cuda.core.texture._array import (
11 _ARRAYFORMAT_TO_CU,
12 _CU_TO_ARRAYFORMAT,
13 _validate_array_shape,
14 _validate_format_channels,
15)
16from cuda.core._resource_handles cimport (
17 OpaqueArrayHandle,
18 MipmappedArrayHandle,
19 as_intptr,
20 create_array_level_handle,
21 create_mipmapped_array_handle,
22 get_last_error,
23)
24from cuda.core._utils.cuda_utils cimport HANDLE_RETURN
26from dataclasses import dataclass
28from cuda.core._utils.cuda_utils import check_or_create_options
31@dataclass
32class MipmappedArrayOptions:
33 """Options for :meth:`cuda.core.Device.create_mipmapped_array`.
35 Attributes
36 ----------
37 shape : tuple of int
38 ``(width,)``, ``(width, height)``, or ``(width, height, depth)`` in
39 elements, for the base (level 0) mip.
40 format : ArrayFormatType, str, or numpy.dtype
41 Element format. Accepts an :class:`~cuda.core.typing.ArrayFormatType`,
42 a plain string (e.g. ``"float32"``), or a NumPy dtype object.
43 num_channels : int
44 Channels per element. Must be 1, 2, or 4.
45 num_levels : int
46 Number of mip levels to allocate; must be >= 1. The driver caps this at
47 the log2 of the largest dimension; passing a larger value yields a
48 driver error.
49 is_surface_load_store : bool
50 If True, allocate with ``CUDA_ARRAY3D_SURFACE_LDST`` so individual
51 levels (obtained via :meth:`MipmappedArray.get_level`) can be bound as
52 a :class:`~cuda.core.texture.SurfaceObject` for kernel-side writes.
53 Default False.
55 .. versionadded:: 1.1.0
56 """
58 shape: tuple[int, ...]
59 format: object
60 num_channels: int
61 num_levels: int
62 is_surface_load_store: bool = False
64 def __post_init__(self):
65 self.format = _validate_format_channels(self.format, self.num_channels) 1facbdenomlijgh
66 self.shape = _validate_array_shape(self.shape) 1facbdemlijgh
67 self.num_levels = int(self.num_levels) 1facbdelijgh
68 if self.num_levels < 1: 1facbdelijgh
69 raise ValueError(f"num_levels must be >= 1, got {self.num_levels}") 1l
72cdef class MipmappedArray:
73 """A mipmapped CUDA array for texture/surface access across levels.
75 Wraps ``CUmipmappedArray``. Each mip level is a distinct, hardware-laid-out
76 allocation accessible only via a :class:`TextureObject` (or by retrieving
77 the level's :class:`OpaqueArray` and binding it as a :class:`SurfaceObject`).
78 Destroying the :class:`MipmappedArray` destroys all level arrays
79 implicitly, so the :class:`OpaqueArray` instances returned by :meth:`get_level`
80 are non-owning and hold a strong reference back to their parent.
82 Construct via :meth:`cuda.core.Device.create_mipmapped_array`.
84 .. versionadded:: 1.1.0
85 """
87 def __init__(self, *args, **kwargs):
88 raise RuntimeError( 1p
89 "MipmappedArray cannot be instantiated directly. "
90 "Use Device.create_mipmapped_array()."
91 )
93 def get_level(self, level):
94 """Return a non-owning :class:`OpaqueArray` view of the given mip level.
96 Parameters
97 ----------
98 level : int
99 Mip level index in ``[0, num_levels)``.
101 Returns
102 -------
103 OpaqueArray
104 A non-owning :class:`OpaqueArray` wrapping the level's ``CUarray``.
105 The :class:`MipmappedArray` is kept alive for the lifetime of the
106 returned :class:`OpaqueArray`; the underlying storage is released only
107 when this :class:`MipmappedArray` is destroyed.
108 """
109 MipmappedArray_check_open(self) 1fcbde
110 lvl = int(level) 1cbde
111 if lvl < 0: 1cbde
112 raise ValueError(f"level must be >= 0, got {lvl}") 1b
113 if lvl >= <int>self._num_levels: 1cbde
114 raise ValueError( 1b
115 f"level ({lvl}) must be < num_levels ({self._num_levels})" 1kb
116 )
118 cdef OpaqueArrayHandle h_level = create_array_level_handle(self._handle, <unsigned int>lvl) 1cde
119 if not h_level: 1cde
120 HANDLE_RETURN(get_last_error())
121 # The returned OpaqueArray is non-owning; its C++ box embeds this mipmap's
122 # handle, so the parent's storage structurally outlives the level view
123 # (no Python parent reference needed).
124 return _array_from_handle(h_level, self._device_id) 1cde
126 @property
127 def handle(self):
128 """The underlying ``CUmipmappedArray`` as an integer."""
129 return as_intptr(self._handle) 1ah
131 @property
132 def is_closed(self) -> bool:
133 """Whether this mipmapped array has been closed."""
134 return self._handle.get() == NULL 1f
136 @property
137 def shape(self):
138 """Base-level (level 0) allocation shape, in elements."""
139 return self._shape 1a
141 @property
142 def format(self):
143 """The element :class:`~cuda.core.typing.ArrayFormatType`."""
144 return _CU_TO_ARRAYFORMAT[self._format] 1a
146 @property
147 def num_channels(self):
148 """Channels per element (1, 2, or 4)."""
149 return self._num_channels 1a
151 @property
152 def num_levels(self):
153 """Number of mip levels."""
154 return int(self._num_levels) 1abg
156 @property
157 def is_surface_load_store(self):
158 """True if this mipmap (and each of its levels) was created with
159 ``CUDA_ARRAY3D_SURFACE_LDST`` and can back a :class:`SurfaceObject`."""
160 return self._surface_load_store 1a
162 @property
163 def device(self):
164 """The :class:`Device` this mipmap was allocated on."""
165 from cuda.core._device import Device 1a
166 return Device(self._device_id) 1a
168 cpdef close(self):
169 """Release this object's reference to the underlying ``CUmipmappedArray``.
171 Destruction (``cuMipmappedArrayDestroy``) happens via the handle's
172 deleter when the last reference is dropped. A level :class:`OpaqueArray`
173 from :meth:`get_level` holds its own reference to this mipmap's storage,
174 so it stays valid until both it and this object are released. Idempotent.
175 """
176 self._handle.reset() 1facbdijgh
178 def __enter__(self):
179 return self
181 def __exit__(self, exc_type, exc, tb):
182 self.close()
184 def __repr__(self):
185 return (
186 f"MipmappedArray(shape={self._shape}, "
187 f"format={_CU_TO_ARRAYFORMAT[self._format].name}, "
188 f"num_channels={self._num_channels}, "
189 f"num_levels={self._num_levels})"
190 )
192def _create_mipmapped_array(options, Context ctx, int device_id):
193 """Allocate a new :class:`MipmappedArray` on the specified device.
195 Backs :meth:`cuda.core.Device.create_mipmapped_array`. ``options`` is a
196 :class:`MipmappedArrayOptions` (or a mapping accepted by it); its fields are
197 validated at construction.
198 """
199 cdef object opts = check_or_create_options( 1facbdeijgh
200 MipmappedArrayOptions, options, "Mipmapped array options" 1facbdeijgh
201 )
202 shape_t = opts.shape 1facbdeijgh
204 cdef cydriver.CUarray_format c_format = <cydriver.CUarray_format>_ARRAYFORMAT_TO_CU[opts.format] 1facbdeijgh
205 cdef int rank = len(shape_t) 1facbdeijgh
206 cdef unsigned int flags = (
207 cydriver.CUDA_ARRAY3D_SURFACE_LDST if opts.is_surface_load_store else 0 1facbdeijgh
208 )
209 cdef unsigned int c_levels = <unsigned int>opts.num_levels 1facbdeijgh
211 # Mipmap creation uses the 3D descriptor regardless of rank; lower-rank
212 # shapes use Height=0/Depth=0 sentinels, matching cuArray3DCreate.
213 cdef cydriver.CUDA_ARRAY3D_DESCRIPTOR desc3d = cydriver.CUDA_ARRAY3D_DESCRIPTOR(
214 Width=<size_t>shape_t[0], 1facbdeijgh
215 Height=<size_t>(shape_t[1] if rank >= 2 else 0), 1facbdeijgh
216 Depth=<size_t>(shape_t[2] if rank >= 3 else 0), 1facbdeijgh
217 Format=c_format, 1facbdeijgh
218 NumChannels=<unsigned int>opts.num_channels, 1facbdeijgh
219 Flags=flags, 1facbdeijgh
220 )
222 cdef MipmappedArrayHandle h = create_mipmapped_array_handle( 1facbdeijgh
223 ctx._h_context, desc3d, c_levels)
224 if not h: 1facbdeijgh
225 HANDLE_RETURN(get_last_error())
227 cdef MipmappedArray self = MipmappedArray.__new__(MipmappedArray) 1facbdeijgh
228 self._handle = h 1facbdeijgh
229 self._shape = shape_t 1facbdeijgh
230 self._format = c_format 1facbdeijgh
231 self._num_channels = opts.num_channels 1facbdeijgh
232 self._num_levels = <unsigned int>opts.num_levels 1facbdeijgh
233 self._surface_load_store = bool(opts.is_surface_load_store) 1facbdeijgh
234 self._device_id = device_id 1facbdeijgh
235 return self 1facbdeijgh