Repository navigation
cuda.core.texture: API-style follow-up before v1.1.0 release #2292
Description
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:
cuda-python/cuda_core/cuda/core/texture/_array.pyi
Lines 58 to 79 in 2780efd
| @classmethod | |
| def from_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.
@dataclass
class OpaqueArrayOptions:
shape: tuple[int, ...]
format: numpy.dtype | ArrayFormatType # see item 3
num_channels: int
is_surface_load_store: bool = False
@dataclass
class MipmappedArrayOptions:
shape: tuple[int, ...]
format: numpy.dtype | ArrayFormatType
num_channels: int
num_levels: int
is_surface_load_store: bool = FalseAlso 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:
cuda-python/cuda_core/cuda/core/_tensor_map.pyi
Lines 88 to 102 in 2780efd
| 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):
cuda-python/cuda_core/cuda/core/typing.py
Lines 62 to 72 in 2780efd
| class SourceCodeType(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:
cuda-python/cuda_core/cuda/core/texture/_texture.pyi
Lines 11 to 33 in 2780efd
| class AddressMode(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 | |
| class FilterMode(IntEnum): | |
| """Texel sampling mode.""" | |
| POINT = cydriver.CU_TR_FILTER_MODE_POINT | |
| LINEAR = cydriver.CU_TR_FILTER_MODE_LINEAR | |
| class ReadMode(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.py
class AddressModeType(StrEnum):
WRAP = "wrap"
CLAMP = "clamp"
MIRROR = "mirror"
BORDER = "border"
class FilterModeType(StrEnum):
POINT = "point"
LINEAR = "linear"
class ReadModeType(StrEnum):
ELEMENT_TYPE = "element_type"
NORMALIZED_FLOAT = "normalized_float"
# Optional if item 3's numpy path covers everything:
class ArrayFormatType(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:
cuda-python/cuda_core/cuda/core/texture/_texture.pyi
Lines 58 to 60 in 2780efd
| @classmethod | |
| def from_array(cls, array): | |
| """Build a resource descriptor backed by a :class:`OpaqueArray`.""" |
from_array→from_opaque_arrayfrom_mipmapped_array— unchangedfrom_linear— unchanged (matchesCUresourceType.LINEAR; renaming tofrom_buffercollides withfrom_pitch2d, which also takes aBuffer)from_pitch2d— unchanged
6. Drop SurfaceObject.from_array sugar
TextureObject has only from_descriptor. SurfaceObject has both from_array(array) and from_descriptor(resource=…):
cuda-python/cuda_core/cuda/core/texture/_surface.pyi
Lines 32 to 37 in 2780efd
| @classmethod | |
| def from_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:
cuda-python/cuda_core/cuda/core/texture/_array.pyi
Lines 107 to 109 in 2780efd
| @property | |
| def element_size(self): | |
| """Bytes per element (format size * channels).""" |
Not addressed here
- Descriptor round-trip introspection (
cuTexObjectGetResourceDescetc.) — deferred in the PR.
-- Leo's bot
Activity
cuda.core.texture: address API-review follow-ups (NVIDIA#2292)
cuda.core.texture: fix stale address_mode entry-rejection test (NVIDI…
cuda.core.texture: register new StrEnums in enum-coverage test (NVIDI…
cuda.core.texture: API-style follow-up before v1.1.0 (#2292) (#2307)