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

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 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 

25  

26from dataclasses import dataclass 

27  

28from cuda.core._utils.cuda_utils import check_or_create_options 

29  

30  

31@dataclass 

32class MipmappedArrayOptions: 

33 """Options for :meth:`cuda.core.Device.create_mipmapped_array`. 

34  

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. 

54  

55 .. versionadded:: 1.1.0 

56 """ 

57  

58 shape: tuple[int, ...] 

59 format: object 

60 num_channels: int 

61 num_levels: int 

62 is_surface_load_store: bool = False 

63  

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

70  

71  

72cdef class MipmappedArray: 

73 """A mipmapped CUDA array for texture/surface access across levels. 

74  

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. 

81  

82 Construct via :meth:`cuda.core.Device.create_mipmapped_array`. 

83  

84 .. versionadded:: 1.1.0 

85 """ 

86  

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

88 raise RuntimeError( 1p

89 "MipmappedArray cannot be instantiated directly. " 

90 "Use Device.create_mipmapped_array()." 

91 ) 

92  

93 def get_level(self, level): 

94 """Return a non-owning :class:`OpaqueArray` view of the given mip level. 

95  

96 Parameters 

97 ---------- 

98 level : int 

99 Mip level index in ``[0, num_levels)``. 

100  

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 ) 

117  

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

125  

126 @property 

127 def handle(self): 

128 """The underlying ``CUmipmappedArray`` as an integer.""" 

129 return as_intptr(self._handle) 1ah

130  

131 @property 

132 def is_closed(self) -> bool: 

133 """Whether this mipmapped array has been closed.""" 

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

135  

136 @property 

137 def shape(self): 

138 """Base-level (level 0) allocation shape, in elements.""" 

139 return self._shape 1a

140  

141 @property 

142 def format(self): 

143 """The element :class:`~cuda.core.typing.ArrayFormatType`.""" 

144 return _CU_TO_ARRAYFORMAT[self._format] 1a

145  

146 @property 

147 def num_channels(self): 

148 """Channels per element (1, 2, or 4).""" 

149 return self._num_channels 1a

150  

151 @property 

152 def num_levels(self): 

153 """Number of mip levels.""" 

154 return int(self._num_levels) 1abg

155  

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

161  

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

167  

168 cpdef close(self): 

169 """Release this object's reference to the underlying ``CUmipmappedArray``. 

170  

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

177  

178 def __enter__(self): 

179 return self 

180  

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

182 self.close() 

183  

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 ) 

191  

192def _create_mipmapped_array(options, Context ctx, int device_id): 

193 """Allocate a new :class:`MipmappedArray` on the specified device. 

194  

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

203  

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

210  

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 ) 

221  

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()) 

226  

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