Coverage for cuda/core/texture/_mipmapped_array.pyx: 87.32%
71 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-19 01:12 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-19 01:12 +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.string cimport memset
9from cuda.bindings cimport cydriver
10from cuda.core.texture._array cimport _array_from_handle
11from cuda.core.texture._array import (
12 _ARRAYFORMAT_TO_CU,
13 _CU_TO_ARRAYFORMAT,
14 _validate_array_shape,
15 _validate_format_channels,
16)
17from cuda.core._resource_handles cimport (
18 OpaqueArrayHandle,
19 MipmappedArrayHandle,
20 as_intptr,
21 create_array_level_handle,
22 create_mipmapped_array_handle,
23 get_last_error,
24)
25from cuda.core._utils.cuda_utils cimport (
26 HANDLE_RETURN,
27 _get_current_device_id,
28)
30from dataclasses import dataclass
32from cuda.core._utils.cuda_utils import check_or_create_options
35@dataclass
36class MipmappedArrayOptions:
37 """Options for :meth:`cuda.core.Device.create_mipmapped_array`.
39 Attributes
40 ----------
41 shape : tuple of int
42 ``(width,)``, ``(width, height)``, or ``(width, height, depth)`` in
43 elements, for the base (level 0) mip.
44 format : ArrayFormatType, str, or numpy.dtype
45 Element format. Accepts an :class:`~cuda.core.typing.ArrayFormatType`,
46 a plain string (e.g. ``"float32"``), or a NumPy dtype object.
47 num_channels : int
48 Channels per element. Must be 1, 2, or 4.
49 num_levels : int
50 Number of mip levels to allocate; must be >= 1. The driver caps this at
51 the log2 of the largest dimension; passing a larger value yields a
52 driver error.
53 is_surface_load_store : bool
54 If True, allocate with ``CUDA_ARRAY3D_SURFACE_LDST`` so individual
55 levels (obtained via :meth:`MipmappedArray.get_level`) can be bound as
56 a :class:`~cuda.core.texture.SurfaceObject` for kernel-side writes.
57 Default False.
59 .. versionadded:: 1.1.0
60 """
62 shape: tuple[int, ...]
63 format: object
64 num_channels: int
65 num_levels: int
66 is_surface_load_store: bool = False
68 def __post_init__(self):
69 self.format = _validate_format_channels(self.format, self.num_channels) 1acbdemnlkhifg
70 self.shape = _validate_array_shape(self.shape) 1acbdelkhifg
71 self.num_levels = int(self.num_levels) 1acbdekhifg
72 if self.num_levels < 1: 1acbdekhifg
73 raise ValueError(f"num_levels must be >= 1, got {self.num_levels}") 1k
76cdef class MipmappedArray:
77 """A mipmapped CUDA array for texture/surface access across levels.
79 Wraps ``CUmipmappedArray``. Each mip level is a distinct, hardware-laid-out
80 allocation accessible only via a :class:`TextureObject` (or by retrieving
81 the level's :class:`OpaqueArray` and binding it as a :class:`SurfaceObject`).
82 Destroying the :class:`MipmappedArray` destroys all level arrays
83 implicitly, so the :class:`OpaqueArray` instances returned by :meth:`get_level`
84 are non-owning and hold a strong reference back to their parent.
86 Construct via :meth:`cuda.core.Device.create_mipmapped_array`.
88 .. versionadded:: 1.1.0
89 """
91 def __init__(self, *args, **kwargs):
92 raise RuntimeError( 1o
93 "MipmappedArray cannot be instantiated directly. "
94 "Use Device.create_mipmapped_array()."
95 )
97 def get_level(self, level):
98 """Return a non-owning :class:`OpaqueArray` view of the given mip level.
100 Parameters
101 ----------
102 level : int
103 Mip level index in ``[0, num_levels)``.
105 Returns
106 -------
107 OpaqueArray
108 A non-owning :class:`OpaqueArray` wrapping the level's ``CUarray``.
109 The :class:`MipmappedArray` is kept alive for the lifetime of the
110 returned :class:`OpaqueArray`; the underlying storage is released only
111 when this :class:`MipmappedArray` is destroyed.
112 """
113 lvl = int(level) 1cbde
114 if lvl < 0: 1cbde
115 raise ValueError(f"level must be >= 0, got {lvl}") 1jb
116 if lvl >= <int>self._num_levels: 1cbde
117 raise ValueError( 1b
118 f"level ({lvl}) must be < num_levels ({self._num_levels})" 1b
119 )
121 cdef OpaqueArrayHandle h_level = create_array_level_handle(self._handle, <unsigned int>lvl) 1cde
122 if not h_level: 1cde
123 HANDLE_RETURN(get_last_error())
124 # The returned OpaqueArray is non-owning; its C++ box embeds this mipmap's
125 # handle, so the parent's storage structurally outlives the level view
126 # (no Python parent reference needed).
127 return _array_from_handle(h_level, self._device_id) 1cde
129 @property
130 def handle(self):
131 """The underlying ``CUmipmappedArray`` as an integer."""
132 return as_intptr(self._handle) 1ag
134 @property
135 def shape(self):
136 """Base-level (level 0) allocation shape, in elements."""
137 return self._shape 1a
139 @property
140 def format(self):
141 """The element :class:`~cuda.core.typing.ArrayFormatType`."""
142 return _CU_TO_ARRAYFORMAT[self._format] 1a
144 @property
145 def num_channels(self):
146 """Channels per element (1, 2, or 4)."""
147 return self._num_channels 1a
149 @property
150 def num_levels(self):
151 """Number of mip levels."""
152 return int(self._num_levels) 1abf
154 @property
155 def is_surface_load_store(self):
156 """True if this mipmap (and each of its levels) was created with
157 ``CUDA_ARRAY3D_SURFACE_LDST`` and can back a :class:`SurfaceObject`."""
158 return self._surface_load_store 1a
160 @property
161 def device(self):
162 """The :class:`Device` this mipmap was allocated on."""
163 from cuda.core._device import Device 1a
164 return Device(self._device_id) 1a
166 cpdef close(self):
167 """Release this object's reference to the underlying ``CUmipmappedArray``.
169 Destruction (``cuMipmappedArrayDestroy``) happens via the handle's
170 deleter when the last reference is dropped. A level :class:`OpaqueArray`
171 from :meth:`get_level` holds its own reference to this mipmap's storage,
172 so it stays valid until both it and this object are released. Idempotent.
173 """
174 self._handle.reset() 1acbdhifg
176 def __enter__(self):
177 return self
179 def __exit__(self, exc_type, exc, tb):
180 self.close()
182 def __repr__(self):
183 return (
184 f"MipmappedArray(shape={self._shape}, "
185 f"format={_CU_TO_ARRAYFORMAT[self._format].name}, "
186 f"num_channels={self._num_channels}, "
187 f"num_levels={self._num_levels})"
188 )
191def _create_mipmapped_array(options):
192 """Allocate a new :class:`MipmappedArray` on the current device.
194 Backs :meth:`cuda.core.Device.create_mipmapped_array`. ``options`` is a
195 :class:`MipmappedArrayOptions` (or a mapping accepted by it); its fields are
196 validated at construction.
197 """
198 cdef object opts = check_or_create_options( 1acbdehifg
199 MipmappedArrayOptions, options, "Mipmapped array options" 1acbdehifg
200 )
201 shape_t = opts.shape 1acbdehifg
203 cdef cydriver.CUarray_format c_format = <cydriver.CUarray_format>_ARRAYFORMAT_TO_CU[opts.format] 1acbdehifg
204 cdef cydriver.CUDA_ARRAY3D_DESCRIPTOR desc3d
205 cdef int rank = len(shape_t) 1acbdehifg
206 cdef unsigned int flags = (
207 cydriver.CUDA_ARRAY3D_SURFACE_LDST if opts.is_surface_load_store else 0 1acbdehifg
208 )
209 cdef unsigned int c_levels = <unsigned int>opts.num_levels 1acbdehifg
211 # Mipmap creation uses the 3D descriptor regardless of rank; lower-rank
212 # shapes use Height=0/Depth=0 sentinels, matching cuArray3DCreate.
213 memset(&desc3d, 0, sizeof(desc3d)) 1acbdehifg
214 desc3d.Width = <size_t>shape_t[0] 1acbdehifg
215 desc3d.Height = <size_t>(shape_t[1] if rank >= 2 else 0) 1acbdehifg
216 desc3d.Depth = <size_t>(shape_t[2] if rank >= 3 else 0) 1acbdehifg
217 desc3d.Format = c_format 1acbdehifg
218 desc3d.NumChannels = <unsigned int>opts.num_channels 1acbdehifg
219 desc3d.Flags = flags 1acbdehifg
221 cdef MipmappedArrayHandle h = create_mipmapped_array_handle(desc3d, c_levels) 1acbdehifg
222 if not h: 1acbdehifg
223 HANDLE_RETURN(get_last_error())
225 cdef MipmappedArray self = MipmappedArray.__new__(MipmappedArray) 1acbdehifg
226 self._handle = h 1acbdehifg
227 self._shape = shape_t 1acbdehifg
228 self._format = c_format 1acbdehifg
229 self._num_channels = opts.num_channels 1acbdehifg
230 self._num_levels = <unsigned int>opts.num_levels 1acbdehifg
231 self._surface_load_store = bool(opts.is_surface_load_store) 1acbdehifg
232 self._device_id = _get_current_device_id() 1acbdehifg
233 return self 1acbdehifg