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

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 libc.string cimport memset 

8  

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) 

29  

30from dataclasses import dataclass 

31  

32from cuda.core._utils.cuda_utils import check_or_create_options 

33  

34  

35@dataclass 

36class MipmappedArrayOptions: 

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

38  

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. 

58  

59 .. versionadded:: 1.1.0 

60 """ 

61  

62 shape: tuple[int, ...] 

63 format: object 

64 num_channels: int 

65 num_levels: int 

66 is_surface_load_store: bool = False 

67  

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

74  

75  

76cdef class MipmappedArray: 

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

78  

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. 

85  

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

87  

88 .. versionadded:: 1.1.0 

89 """ 

90  

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

92 raise RuntimeError( 1o

93 "MipmappedArray cannot be instantiated directly. " 

94 "Use Device.create_mipmapped_array()." 

95 ) 

96  

97 def get_level(self, level): 

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

99  

100 Parameters 

101 ---------- 

102 level : int 

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

104  

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 ) 

120  

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

128  

129 @property 

130 def handle(self): 

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

132 return as_intptr(self._handle) 1ag

133  

134 @property 

135 def shape(self): 

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

137 return self._shape 1a

138  

139 @property 

140 def format(self): 

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

142 return _CU_TO_ARRAYFORMAT[self._format] 1a

143  

144 @property 

145 def num_channels(self): 

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

147 return self._num_channels 1a

148  

149 @property 

150 def num_levels(self): 

151 """Number of mip levels.""" 

152 return int(self._num_levels) 1abf

153  

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

159  

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

165  

166 cpdef close(self): 

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

168  

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

175  

176 def __enter__(self): 

177 return self 

178  

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

180 self.close() 

181  

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 ) 

189  

190  

191def _create_mipmapped_array(options): 

192 """Allocate a new :class:`MipmappedArray` on the current device. 

193  

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

202  

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

210  

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

220  

221 cdef MipmappedArrayHandle h = create_mipmapped_array_handle(desc3d, c_levels) 1acbdehifg

222 if not h: 1acbdehifg

223 HANDLE_RETURN(get_last_error()) 

224  

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