Background
PR #2095 (design issue #2188) merged the texture/surface stack on 2026-06-27. I was cc'd on #2188 but didn't get a chance to review before it was merged/closed, my apology. The functional design (5 types, factory set, folded format + num_channels, copy-only interop, graph-capture parity, resource-handle lifetime rewrite) is sound. This issue captures API-style items to address before v1.1.0 ships (milestone due 2026-07-08).
Items
1. Allocation shape: Device.create_*(options=…) instead of classmethod factories
Every other user-allocatable resource in cuda.core is created via Device.create_* (create_stream, create_event, …) or mr.allocate(size, stream=…). OpaqueArray.from_descriptor(…) / MipmappedArray.from_descriptor(…) introduce a new per-type-classmethod pattern.
Move to:
Device.create_opaque_array(options=OpaqueArrayOptions(…)) -> OpaqueArrayDevice.create_mipmapped_array(options=MipmappedArrayOptions(…)) -> MipmappedArrayDevice.create_texture_object(*, resource, options=TextureObjectOptions(…)) -> TextureObjectDevice.create_surface_object(*, resource) -> SurfaceObject
Ties the resource to whichever device/context is current at construction, mirroring Stream / Event. Removes the classmethod entry points from the types themselves (the private _from_handle graphics-interop escape hatch stays).
Current entry point:
| @classmethod |
| deffrom_descriptor(cls, *, shape, format, num_channels, is_surface_load_store=False): |
| """Allocate a new CUDA array. |
| |
| Parameters |
| ---------- |
| shape : tuple of int |
| ``(width,)``, ``(width, height)``, or ``(width, height, depth)`` |
| in elements. |
| format : ArrayFormat |
| Element format. |
| num_channels : int |
| Channels per element. Must be 1, 2, or 4. |
| is_surface_load_store : bool |
| If True, allocate with ``CUDA_ARRAY3D_SURFACE_LDST`` so the array |
| can be bound as a :class:`SurfaceObject` for kernel-side writes. |
| Default False. |
| |
| Returns |
| ------- |
| OpaqueArray |
| """ |
2. Missing XxxOptions dataclasses
cuda.core convention: multi-field creation goes through an XxxOptions dataclass — StreamOptions, EventOptions, LaunchConfig, DeviceMemoryResourceOptions, PinnedMemoryResourceOptions, ManagedMemoryResourceOptions, VirtualMemoryResourceOptions, TensorMapDescriptorOptions. OpaqueArray.from_descriptor currently takes 4 loose kwargs; MipmappedArray.from_descriptor takes 5.
@dataclassclassOpaqueArrayOptions:
shape: tuple[int, ...]
format: numpy.dtype|ArrayFormatType# see item 3num_channels: intis_surface_load_store: bool=False@dataclassclassMipmappedArrayOptions:
shape: tuple[int, ...]
format: numpy.dtype|ArrayFormatTypenum_channels: intnum_levels: intis_surface_load_store: bool=False
Also rename TextureDescriptor → TextureObjectOptions: CUDA_TEXTURE_DESC is pure creation input (not queryable back), so "descriptor" was borrowing the driver naming inappropriately. TextureObjectOptions matches TensorMapDescriptorOptions / StreamOptions / etc.
3. format accepts numpy.dtype (matches TensorMapDescriptor precedent)
TensorMapDescriptorOptions.data_type already accepts NumPy / ml_dtypes dtype objects with TensorMapDataType retained for compatibility:
| data_type : object, optional |
| Explicit dtype override. Prefer NumPy or ``ml_dtypes`` dtype objects; |
| :class:`TensorMapDataType` remains accepted for compatibility. |
| interleave : TensorMapInterleave, optional |
| Interleave layout. Default ``NONE``. |
| swizzle : TensorMapSwizzle, optional |
| Swizzle mode. Default ``NONE``. |
| l2_promotion : TensorMapL2Promotion, optional |
| L2 promotion mode. Default ``NONE``. |
| oob_fill : TensorMapOOBFill, optional |
| Out-of-bounds fill mode. Default ``NONE``. |
| """ |
| box_dim: tuple[int, ...] |
| element_strides: tuple[int, ...] |None=None |
| data_type: object=None |
The 8 current ArrayFormat values map 1:1 to numpy dtypes. Apply the same pattern to format: numpy.dtype | ml_dtypes.* | ArrayFormatType, prefer dtypes, keep the enum as fallback / escape-hatch for driver formats without numpy equivalents (BC1..BC7, UNORM_INTx in CTK 13+, BFLOAT16 via ml_dtypes).
4. Enum style: IntEnum in root namespace → StrEnum in cuda.core.typing
Every other cuda.core enum unified pre-1.0 lives in cuda.core.typing as a StrEnum with semantic string values and a Type suffix (SourceCodeType, GraphMemoryType, VirtualMemoryHandleType, ObjectCodeFormatType, PCHStatusType, CompilerBackendType, GraphConditionalType, ManagedMemoryLocationType, VirtualMemoryLocationType, VirtualMemoryGranularityType, VirtualMemoryAccessType, VirtualMemoryAllocationType).
Existing pattern (SourceCodeType):
| classSourceCodeType(StrEnum): |
| """Source language passed to :class:`~cuda.core.Program`. |
| |
| * ``CXX`` — CUDA C++ source. |
| * ``PTX`` — PTX assembly text. |
| * ``NVVM`` — NVVM IR (LLVM bitcode). |
| """ |
| |
| CXX="c++" |
| PTX="ptx" |
| NVVM="nvvm" |
The texture enums are IntEnum backed by cydriver.CU_TR_*, sit in cuda.core.texture root, and drop the Type suffix:
| classAddressMode(IntEnum): |
| """Boundary behavior for out-of-range texture coordinates.""" |
| WRAP=cydriver.CU_TR_ADDRESS_MODE_WRAP |
| CLAMP=cydriver.CU_TR_ADDRESS_MODE_CLAMP |
| MIRROR=cydriver.CU_TR_ADDRESS_MODE_MIRROR |
| BORDER=cydriver.CU_TR_ADDRESS_MODE_BORDER |
| |
| classFilterMode(IntEnum): |
| """Texel sampling mode.""" |
| POINT=cydriver.CU_TR_FILTER_MODE_POINT |
| LINEAR=cydriver.CU_TR_FILTER_MODE_LINEAR |
| |
| classReadMode(IntEnum): |
| """How sampled values are returned to the kernel. |
| |
| - ``ELEMENT_TYPE``: return the raw element value (integer formats stay |
| integer, float stays float). |
| - ``NORMALIZED_FLOAT``: integer formats are promoted to a normalized |
| ``float`` in ``[0, 1]`` (unsigned) or ``[-1, 1]`` (signed). |
| Float formats are unaffected. |
| """ |
| ELEMENT_TYPE=0 |
| NORMALIZED_FLOAT=1 |
Bring them in line:
# cuda/core/typing.pyclassAddressModeType(StrEnum):
WRAP="wrap"CLAMP="clamp"MIRROR="mirror"BORDER="border"classFilterModeType(StrEnum):
POINT="point"LINEAR="linear"classReadModeType(StrEnum):
ELEMENT_TYPE="element_type"NORMALIZED_FLOAT="normalized_float"# Optional if item 3's numpy path covers everything:classArrayFormatType(StrEnum):
UINT8="uint8"
...
5. ResourceDescriptor.from_array → from_opaque_array
The type rename Array → OpaqueArray (item 1 of #2188) was to remove "array" ambiguity; leaving ResourceDescriptor.from_array(array: OpaqueArray) puts it right back:
| @classmethod |
| deffrom_array(cls, array): |
| """Build a resource descriptor backed by a :class:`OpaqueArray`.""" |
from_array → from_opaque_arrayfrom_mipmapped_array — unchangedfrom_linear — unchanged (matches CUresourceType.LINEAR; renaming to from_buffer collides with from_pitch2d, which also takes a Buffer)from_pitch2d — unchanged
6. Drop SurfaceObject.from_array sugar
TextureObject has only from_descriptor. SurfaceObject has both from_array(array) and from_descriptor(resource=…):
| @classmethod |
| deffrom_array(cls, array): |
| """Create a surface object directly from an :class:`OpaqueArray`. |
| |
| The array must have been created with ``is_surface_load_store=True``. |
| """ |
from_array collapses from_descriptor(resource=ResourceDescriptor.from_array(a)) into one call — same speculative-sugar rule that killed the arr.to_buffer(mr, stream) helper in #2188 decision 2. Remove.
If item 1 lands, both go away (single entry point becomes Device.create_surface_object(*, resource)).
7. OpaqueArray.element_size → element_bytes
Byte-count accessors elsewhere on the type spell _bytes explicitly (size_bytes, pitch_bytes). element_size returns a byte count and should follow:
| @property |
| defelement_size(self): |
| """Bytes per element (format size * channels).""" |
Not addressed here
- Descriptor round-trip introspection (
cuTexObjectGetResourceDesc etc.) — deferred in the PR.
-- Leo's bot
Background
PR #2095 (design issue #2188) merged the texture/surface stack on 2026-06-27. I was cc'd on #2188 but didn't get a chance to review before it was merged/closed, my apology. The functional design (5 types, factory set, folded
format+num_channels, copy-only interop, graph-capture parity, resource-handle lifetime rewrite) is sound. This issue captures API-style items to address before v1.1.0 ships (milestone due 2026-07-08).Items
1. Allocation shape:
Device.create_*(options=…)instead of classmethod factoriesEvery other user-allocatable resource in
cuda.coreis created viaDevice.create_*(create_stream,create_event, …) ormr.allocate(size, stream=…).OpaqueArray.from_descriptor(…)/MipmappedArray.from_descriptor(…)introduce a new per-type-classmethod pattern.Move to:
Device.create_opaque_array(options=OpaqueArrayOptions(…)) -> OpaqueArrayDevice.create_mipmapped_array(options=MipmappedArrayOptions(…)) -> MipmappedArrayDevice.create_texture_object(*, resource, options=TextureObjectOptions(…)) -> TextureObjectDevice.create_surface_object(*, resource) -> SurfaceObjectTies the resource to whichever device/context is current at construction, mirroring
Stream/Event. Removes the classmethod entry points from the types themselves (the private_from_handlegraphics-interop escape hatch stays).Current entry point:
cuda-python/cuda_core/cuda/core/texture/_array.pyi
Lines 58 to 79 in 2780efd
2. Missing
XxxOptionsdataclassescuda.coreconvention: multi-field creation goes through anXxxOptionsdataclass —StreamOptions,EventOptions,LaunchConfig,DeviceMemoryResourceOptions,PinnedMemoryResourceOptions,ManagedMemoryResourceOptions,VirtualMemoryResourceOptions,TensorMapDescriptorOptions.OpaqueArray.from_descriptorcurrently takes 4 loose kwargs;MipmappedArray.from_descriptortakes 5.Also rename
TextureDescriptor→TextureObjectOptions:CUDA_TEXTURE_DESCis pure creation input (not queryable back), so "descriptor" was borrowing the driver naming inappropriately.TextureObjectOptionsmatchesTensorMapDescriptorOptions/StreamOptions/ etc.3.
formatacceptsnumpy.dtype(matchesTensorMapDescriptorprecedent)TensorMapDescriptorOptions.data_typealready accepts NumPy /ml_dtypesdtype objects withTensorMapDataTyperetained for compatibility:cuda-python/cuda_core/cuda/core/_tensor_map.pyi
Lines 88 to 102 in 2780efd
The 8 current
ArrayFormatvalues map 1:1 to numpy dtypes. Apply the same pattern toformat:numpy.dtype | ml_dtypes.* | ArrayFormatType, prefer dtypes, keep the enum as fallback / escape-hatch for driver formats without numpy equivalents (BC1..BC7,UNORM_INTxin CTK 13+,BFLOAT16viaml_dtypes).4. Enum style:
IntEnumin root namespace →StrEnumincuda.core.typingEvery other
cuda.coreenum unified pre-1.0 lives incuda.core.typingas aStrEnumwith semantic string values and aTypesuffix (SourceCodeType,GraphMemoryType,VirtualMemoryHandleType,ObjectCodeFormatType,PCHStatusType,CompilerBackendType,GraphConditionalType,ManagedMemoryLocationType,VirtualMemoryLocationType,VirtualMemoryGranularityType,VirtualMemoryAccessType,VirtualMemoryAllocationType).Existing pattern (
SourceCodeType):cuda-python/cuda_core/cuda/core/typing.py
Lines 62 to 72 in 2780efd
The texture enums are
IntEnumbacked bycydriver.CU_TR_*, sit incuda.core.textureroot, and drop theTypesuffix:cuda-python/cuda_core/cuda/core/texture/_texture.pyi
Lines 11 to 33 in 2780efd
Bring them in line:
5.
ResourceDescriptor.from_array→from_opaque_arrayThe type rename
Array→OpaqueArray(item 1 of #2188) was to remove "array" ambiguity; leavingResourceDescriptor.from_array(array: OpaqueArray)puts it right back:cuda-python/cuda_core/cuda/core/texture/_texture.pyi
Lines 58 to 60 in 2780efd
from_array→from_opaque_arrayfrom_mipmapped_array— unchangedfrom_linear— unchanged (matchesCUresourceType.LINEAR; renaming tofrom_buffercollides withfrom_pitch2d, which also takes aBuffer)from_pitch2d— unchanged6. Drop
SurfaceObject.from_arraysugarTextureObjecthas onlyfrom_descriptor.SurfaceObjecthas bothfrom_array(array)andfrom_descriptor(resource=…):cuda-python/cuda_core/cuda/core/texture/_surface.pyi
Lines 32 to 37 in 2780efd
from_arraycollapsesfrom_descriptor(resource=ResourceDescriptor.from_array(a))into one call — same speculative-sugar rule that killed thearr.to_buffer(mr, stream)helper in #2188 decision 2. Remove.If item 1 lands, both go away (single entry point becomes
Device.create_surface_object(*, resource)).7.
OpaqueArray.element_size→element_bytesByte-count accessors elsewhere on the type spell
_bytesexplicitly (size_bytes,pitch_bytes).element_sizereturns a byte count and should follow:cuda-python/cuda_core/cuda/core/texture/_array.pyi
Lines 107 to 109 in 2780efd
Not addressed here
cuTexObjectGetResourceDescetc.) — deferred in the PR.-- Leo's bot