Skip to content

cuda.core.texture: API-style follow-up before v1.1.0 (#2292) - #2307

Merged
Andy-Jost merged 14 commits into
NVIDIA:mainfrom
Andy-Jost:ajost/texture-api-2292
Jul 8, 2026
Merged

cuda.core.texture: API-style follow-up before v1.1.0 (#2292)#2307
Andy-Jost merged 14 commits into
NVIDIA:mainfrom
Andy-Jost:ajost/texture-api-2292

Conversation

@Andy-Jost

Copy link
Copy Markdown
Contributor

Summary

Addresses all seven API-style items in #2292 to settle the cuda.core.texture public surface before it ships in v1.1.0. These are naming/shape refinements to the texture/surface stack merged in #2095 (design #2188); there are no changes to the underlying CUDA behavior. Because the surface first ships in v1.1.0, the renames need no deprecation shims and the new/renamed entry points carry .. versionadded:: 1.1.0.

Developed as a sequence of reviewable commits (one per item group); the PR is intended to squash-merge.

Changes

  • Allocation shape → Device.create_* + XxxOptions (items 1, 2): replace the per-type classmethod factories with Device.create_opaque_array, Device.create_mipmapped_array, Device.create_texture_object, and Device.create_surface_object, each taking an options dataclass (OpaqueArrayOptions, MipmappedArrayOptions, TextureObjectOptions). This matches the create_stream/create_event + StreamOptions/EventOptions convention. The private _from_handle graphics-interop escape hatch is retained.
  • Rename TextureDescriptorTextureObjectOptions (item 2):CUDA_TEXTURE_DESC is pure creation input, so the type follows the XxxOptions convention rather than the driver's "descriptor" naming.
  • format accepts NumPy dtypes (item 3): array/mipmap creation and ResourceDescriptor.from_linear/from_pitch2d now accept an ArrayFormatType, a plain str, or a NumPy dtype object, mirroring TensorMapDescriptorOptions.data_type.
  • Enums → cuda.core.typing as StrEnum (item 4):ArrayFormat/AddressMode/FilterMode/ReadMode become ArrayFormatType/AddressModeType/FilterModeType/ReadModeType in cuda.core.typing, with semantic string values and the Type suffix, matching every other cuda.core enum. Plain strings are accepted anywhere an enum is expected.
  • ResourceDescriptor.from_arrayfrom_opaque_array (item 5): removes the "array" ambiguity that the ArrayOpaqueArray rename was meant to eliminate.
  • Drop SurfaceObject.from_array sugar (item 6): a surface is now built from an explicit ResourceDescriptor.from_opaque_array(...), consistent with the single-entry-point rule.
  • OpaqueArray.element_sizeelement_bytes (item 7): the accessor returns a byte count, matching size_bytes / pitch_bytes.

Docs (api.rst, api_private.rst), the texture tests, and the four texture examples are updated throughout.

Test Coverage

  • Updated tests/test_texture_surface.py for the new API; negative-path tests reflect that validation now fires at options construction (ValueError) rather than at object creation.
  • Added tests for the NumPy-dtype and strformat paths (marked @pytest.mark.agent_authored).

Related Work

Closes#2292. Follow-up to #2095 (design #2188).

Notes

CI will build and run the tests. Local build/run was not possible on the dev machine (no GPU); pre-commit run --all-files passes.

…ze -> element_bytes (NVIDIA#2292)
Addresses items 5 and 7 of the pre-v1.1.0 texture API-style review.
- ResourceDescriptor.from_array -> ResourceDescriptor.from_opaque_array:
the Array -> OpaqueArray rename removed "array" ambiguity, so the factory
name should follow suit.
- OpaqueArray.element_size -> OpaqueArray.element_bytes: the accessor returns
a byte count, matching the _bytes convention already used by size_bytes and
pitch_bytes.
Both are renames of APIs not yet released (first ship in v1.1.0), so no
deprecation shim is needed; the new names carry a versionadded:: 1.1.0 note.
Call sites in the texture tests and the four texture examples are updated.
…VIDIA#2292)
Addresses item 4 of the pre-v1.1.0 texture API-style review.
The texture/surface enums were IntEnum subclasses backed by cydriver.CU_*
values, defined in the cuda.core.texture root namespace and lacking the Type
suffix. 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. Bring the texture
enums in line:
- ArrayFormat -> cuda.core.typing.ArrayFormatType
- AddressMode -> cuda.core.typing.AddressModeType
- FilterMode -> cuda.core.typing.FilterModeType
- ReadMode -> cuda.core.typing.ReadModeType
Internally the driver integer values are still needed, so _array and _texture
carry private CU<->StrEnum bridge maps; OpaqueArray/MipmappedArray keep storing
the driver int and convert on the .format boundary. Per the enum convention,
plain strings are accepted anywhere an enum is expected (via _normalize_enum /
_normalize_array_format), and invalid values raise ValueError. TextureDescriptor
gains a __post_init__ that normalizes its filter_mode/read_mode/
mipmap_filter_mode fields at construction.
Enums are dropped from the cuda.core.texture __all__ and documented under
cuda.core.typing. Tests, examples, and docs are updated to the new names; the
negative-path tests that previously expected a TypeError at texture creation
now expect a ValueError at descriptor construction.
Addresses item 3 of the pre-v1.1.0 texture API-style review.
TensorMapDescriptorOptions.data_type already accepts NumPy dtype objects and
keeps its enum as a fallback. Apply the same precedent to the texture format
argument: OpaqueArray / MipmappedArray creation and
ResourceDescriptor.from_linear / from_pitch2d now accept an ArrayFormatType,
a plain str, or a NumPy dtype object (anything numpy.dtype() accepts).
Each ArrayFormatType value is already spelled as a NumPy dtype name, so the
eight formats map 1:1 via a new _NUMPY_DTYPE_TO_ARRAYFORMAT table. All four
factories funnel through _validate_format_channels -> _normalize_array_format,
so dtype support is added in one place. ml_dtypes is intentionally not imported:
every current format is a standard NumPy dtype, and the ml_dtypes path only
becomes relevant if/when a bfloat16 format is added.
…VIDIA#2292)
Addresses the rename portion of item 2 of the pre-v1.1.0 texture API-style
review.
CUDA_TEXTURE_DESC is pure creation input (it cannot be queried back from a
texture object), so "descriptor" borrowed the driver naming inappropriately.
TextureObjectOptions matches the XxxOptions convention used throughout
cuda.core (StreamOptions, EventOptions, TensorMapDescriptorOptions, ...).
Pure rename of the dataclass and all references across the implementation,
stubs, __init__ __all__, docs, tests, and examples. The from_descriptor
texture_descriptor= keyword is left untouched here; the whole creation entry
point is replaced by Device.create_texture_object(*, resource, options) in a
later commit.
…array (NVIDIA#2292)
Addresses item 1 (array allocation) and item 2 (Options dataclasses) of the
pre-v1.1.0 texture API-style review.
Every other user-allocatable resource in cuda.core is created via Device.create_*
(create_stream, create_event, ...) with an XxxOptions dataclass. The texture
arrays used per-type classmethod factories (OpaqueArray.from_descriptor,
MipmappedArray.from_descriptor) with loose kwargs instead. Move them in line:
- add OpaqueArrayOptions and MipmappedArrayOptions dataclasses (validated in
__post_init__, mirroring TensorMapDescriptorOptions);
- add Device.create_opaque_array(options) and Device.create_mipmapped_array(
options), which gate on _check_context_initialized() and delegate to private
module factories that bind to the current device (same pattern as
create_event / create_graph_builder);
- remove the OpaqueArray.from_descriptor / MipmappedArray.from_descriptor
classmethods; the private _from_handle graphics-interop path is retained.
New entry points carry versionadded:: 1.1.0. Options are exported from
cuda.core.texture and documented in api.rst. Tests and the texture examples are
updated to Device().create_*_array(<Options>(...)).
…object (NVIDIA#2292)
Addresses item 1 (object creation) and item 6 (drop SurfaceObject.from_array
sugar) of the pre-v1.1.0 texture API-style review, completing the move to the
Device.create_* allocation shape.
- add Device.create_texture_object(*, resource, options) and
Device.create_surface_object(*, resource), gating on
_check_context_initialized() and delegating to private module factories that
bind to the current device (same pattern as create_opaque_array);
- remove TextureObject.from_descriptor, SurfaceObject.from_descriptor, and the
speculative SurfaceObject.from_array sugar (a surface is now built from an
explicit ResourceDescriptor.from_opaque_array(...) like every other backing);
- rename the sampling-options argument from texture_descriptor to options on the
new entry point, matching the create_* + XxxOptions convention (the queryable
TextureObject.texture_descriptor property is unchanged).
New entry points carry versionadded:: 1.1.0. Tests, examples, and api.rst are
updated; a make_surface() helper mirrors make_texture() in the fluid example.
…DIA#2292)
Mechanical pre-commit pass over the NVIDIA#2292 texture API series:
- ruff: drop now-unused imports in the texture example/test files and remove an
unused _normalize_array_format import from _texture.pyx (cython-lint);
- ruff-format: reflow the rewritten call sites in the tests and examples;
- stubgen-pyx: regenerate the .pyi stubs from the updated .pyx sources.
@Andy-JostAndy-Jost added this to the cuda.core v1.1.0 milestone Jul 7, 2026
@Andy-JostAndy-Jost added enhancement Any code-related improvements P0 High priority - Must do! cuda.core Everything related to the cuda.core module labels Jul 7, 2026
@Andy-JostAndy-Jost self-assigned this Jul 7, 2026
@copy-pr-bot

Copy link
Copy Markdown
Contributor

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@Andy-Jost

Copy link
Copy Markdown
ContributorAuthor

/ok to test

Apply the review findings from the texture API-style follow-up:
- Rename TextureObject.texture_descriptor -> options (and the internal
_texture_desc slot -> _options) to drop the borrowed "descriptor"
naming, matching the TextureObjectOptions rename.
- Make options a required argument on create_opaque_array /
create_mipmapped_array (shape/format/num_channels have no defaults),
so omitting it raises a clear error instead of a raw dataclass
TypeError. create_texture_object keeps options=None (all-default
sampling state).
- Add return and resource/options type annotations to the four
Device.create_* texture factories via a TYPE_CHECKING texture import
(mirroring the existing GraphBuilder pattern).
- Clarify in the create_* docstrings that the resource is created in the
current CUDA context (call set_current first), mirroring
create_stream / create_event.
- Add versionadded:: 1.1.0 to OpaqueArrayOptions, MipmappedArrayOptions,
TextureObjectOptions, and the new options property.
- Fix a stale negative-path test whose regex no longer matched the
shared check_or_create_options message; rename stale from_descriptor
test names to create_*; realign the gl_interop_fluid API-MAP comment
table after the enum renames.
- Regenerate .pyi stubs.
…A#2292)
The StrEnum migration (item 4 of NVIDIA#2292) makes a plain str acceptable
anywhere an AddressModeType is expected. A bad tuple entry like "bad" is
therefore a valid *type* but an invalid *value*, so _normalize_enum
rejects it with a ValueError (naming the offending position
address_mode[1]), not a TypeError. Update the test to assert ValueError
and rename it to test_address_mode_rejects_invalid_entry. This is the
second of the two CI failures on PR NVIDIA#2307; the first (the stale
options-type message) was fixed in the previous commit.
…A#2292)
test_enum_coverage.py::test_all_str_enums_in_cases requires every
StrEnum in cuda.core to be declared either in _CASES (bound to a
cuda_binding enum) or in _UNBOUND_STR_ENUMS. The four texture enums
moved into cuda.core.typing (item 4 of NVIDIA#2292) were not registered,
failing that guard.
- AddressModeType / FilterModeType are 1:1 wrappers of CUaddress_mode /
CUfilter_mode, so add mapping=None _CASES entries (count-check only;
the texture mapping dicts store cydriver-derived ints, not
driver.<Enum> members, so the isinstance-based mapping check does not
apply).
- ArrayFormatType exposes only the 8 NumPy-representable formats out of
CUarray_format's ~67 members, so it is a curated subset rather than a
1:1 wrapper -> _UNBOUND_STR_ENUMS.
- ReadModeType maps to the CU_TRSF_READ_AS_INTEGER flag bit, not a
CUenum -> _UNBOUND_STR_ENUMS.
This is the third local test failure; the other two (stale texture
negative-path tests) were fixed in the two preceding commits.
Comment on lines +1472 to +1475
Allocates an opaque, hardware-laid-out CUDA array for texture/surface
access. The array is created in the current CUDA context, so make this
device current with :meth:`set_current` before calling (mirroring
:meth:`create_stream` / :meth:`create_event`).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #2311. I think this behavior (using the current context rather than the device specified by self) is buggy, but it matches other methods like Device.create_stream and Device.create_event. Fixing it is out of scope for this PR.

@Andy-Jost

Copy link
Copy Markdown
ContributorAuthor

/ok to test

@Andy-Jost
Andy-Jost marked this pull request as ready for review July 7, 2026 18:20
@Andy-Jost
Andy-Jost requested review from juenglin, leofang and rparolin and removed request for leofangJuly 7, 2026 18:20
@github-actions

This comment has been minimized.

@Andy-JostAndy-Jost mentioned this pull request Jul 7, 2026
14 tasks
arr.close()


@pytest.mark.agent_authored(model="claude-opus-4.8")

@juenglinjuenglinJul 7, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the purpose of these markers (agent_authored)?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rwgk can answer that

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@juenglin see AGENTS.md

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@juenglin your question made me realize that I forgot to add a Purpose section to the PR #2245 description. That's fixed now. Please look there.

@rwgkrwgk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Andy-Jost what's your opinion regarding the suggested str type annotations?

def texture_descriptor(self):
"""The :class:`TextureDescriptor` this texture was built from."""
return self._texture_desc
def options(self):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Informational only:

Initially codex flagged this as: Exposes the caller’s mutable options. Mutating it later changes reported state without changing the immutable CUDA object. Return an immutable snapshot or remove the property.

When prompted, codex confirmed that there is no code that actually mutates the object. It think it's an acceptable risk.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer if the *Options classes were immutable but that's definitely out of scope here

address_mode: AddressMode | tuple[AddressMode, ...] = AddressMode.CLAMP
filter_mode: FilterMode = FilterMode.POINT
read_mode: ReadMode = ReadMode.ELEMENT_TYPE
address_mode: AddressModeType | tuple[AddressModeType, ...] = AddressModeType.CLAMP

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change:

Include str in all four enum-bearing field annotations:

address_mode: AddressModeType|str|tuple[AddressModeType|str, ...] =AddressModeType.CLAMPfilter_mode: FilterModeType|str=FilterModeType.POINTread_mode: ReadModeType|str=ReadModeType.ELEMENT_TYPEmipmap_filter_mode: FilterModeType|str=FilterModeType.POINT

Rationale (gpt-5.6-sol):

Before this PR, TextureDescriptor used IntEnum fields and the runtime rejected strings, so the enum-only annotations were accurate. This PR deliberately changes the runtime behavior: __post_init__ and _normalize_address_modes now accept and coerce plain strings, as required by item 4. Keeping enum-only annotations therefore makes the generated stub reject calls that the implementation supports, for example TextureObjectOptions(filter_mode="linear").

This is a compatible typing-only widening, not another runtime behavior change. It also matches existing cuda.core convention, including Program(..., code_type: SourceCodeType | str), Program.compile(target_type: ObjectCodeFormatType | str), Linker.link(target_type: ObjectCodeFormatType | str), and ManagedMemoryResourceOptions.preferred_location_type: ManagedMemoryLocationType | str | None. Although __post_init__ normalizes three fields back to enum instances, the pragmatic field annotations should describe the inputs accepted by the generated dataclass constructor.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed — done. I applied the str widening to the source annotations in _texture.pyx (TextureObjectOptions: address_mode, filter_mode, read_mode, mipmap_filter_mode) rather than the generated stub, since the .pyi is auto-regenerated by the stubgen pre-commit hook and would otherwise be clobbered. The regenerated stub now reflects it. As you noted, this matches existing convention (Program.compile(target_type: ObjectCodeFormatType | str), etc.) and is a typing-only widening.

@leofangleofangJul 8, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This discussion came after I reviewed the PR last night. I think based on our Tuesday meeting discussion for #2248 we don't want to do such widening ?

MANAGED = "managed"


class ArrayFormatType(StrEnum):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This lacks .. versionadded:: 1.1.0

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — added .. versionadded:: 1.1.0 here. Your comment made me audit the rest: the other three new enums (AddressModeType, FilterModeType, ReadModeType) had the same gap, and several texture classes (ResourceDescriptor, TextureObject, OpaqueArray, MipmappedArray, SurfaceObject) lacked a class-level marker. Added class-level markers to all and removed the redundant method-level ones. Pushed in fa7cb36.

arr.close()


@pytest.mark.agent_authored(model="claude-opus-4.8")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@juenglin see AGENTS.md

…tions
Address review feedback (Ralf):
- Add ``.. versionadded:: 1.1.0`` to the four new texture enums in typing.py
(ArrayFormatType, AddressModeType, FilterModeType, ReadModeType) and to the
texture classes that lacked a class-level marker (ResourceDescriptor,
TextureObject, OpaqueArray, MipmappedArray, SurfaceObject). Remove the
redundant method-level markers now covered at class level.
- Widen the enum-bearing TextureObjectOptions field annotations to include
``str`` (address_mode, filter_mode, read_mode, mipmap_filter_mode), matching
the runtime string coercion this PR adds and existing convention elsewhere in
cuda.core. Note "Plain strings are accepted" on the affected fields.
- Regenerate .pyi stubs.
@Andy-Jost

Copy link
Copy Markdown
ContributorAuthor

@rwgk Thanks for the review. I agree with the str annotations — since this PR makes TextureObjectOptions coerce plain strings at runtime, the annotations should describe what the constructor accepts, and it matches existing convention (Program.compile, Linker.link, ManagedMemoryResourceOptions). Pushed in fa7cb36:

  • Widened address_mode, filter_mode, read_mode, mipmap_filter_mode to include str (in _texture.pyx; stub regenerated) and noted "Plain strings are accepted" on each.
  • Added .. versionadded:: 1.1.0 to all four new texture enums and the texture classes that were missing a class-level marker (your ArrayFormatType comment prompted the wider audit).

No runtime behavior change beyond what the PR already introduced. Ready for another look.

rwgk
rwgk approved these changes Jul 8, 2026

@rwgkrwgk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great to me!

@Andy-Jost
Andy-Jost merged commit 8b20f77 into NVIDIA:mainJul 8, 2026
200 of 202 checks passed
@Andy-Jost
Andy-Jost deleted the ajost/texture-api-2292 branch July 8, 2026 17:20
@github-actions

Copy link
Copy Markdown
Doc Preview CI
Preview removed because the pull request was closed or merged.

Andy-Jost added a commit to Andy-Jost/cuda-python that referenced this pull request Jul 8, 2026
Add a Highlights section, backfill PR/issue links on New features entries,
and add Bug fixes entries for the v1.1.0 IPC import hardening (NVIDIA#2219, NVIDIA#2223,
NVIDIA#2224), the ManagedBuffer.accessed_by torn-state fix (NVIDIA#2222), and graph node
attachment lifetimes (NVIDIA#2280).
Highlights (per review): the .pyi type-stub support and the new
cuda.core.texture module; NVLink enumeration is covered in New features /
Bug fixes rather than as a highlight. Also adds a New features entry for the
cuda.core.texture module (NVIDIA#467, NVIDIA#2095, NVIDIA#2307).
Andy-Jost added a commit to Andy-Jost/cuda-python that referenced this pull request Jul 8, 2026
Add a Highlights section, backfill PR/issue links on New features entries,
and add Bug fixes entries for the v1.1.0 IPC import hardening (NVIDIA#2219, NVIDIA#2223,
NVIDIA#2224), the ManagedBuffer.accessed_by torn-state fix (NVIDIA#2222), and graph node
attachment lifetimes (NVIDIA#2280).
Highlights (per review): the .pyi type-stub support and the new
cuda.core.texture module; NVLink enumeration is covered in New features /
Bug fixes rather than as a highlight. Also adds a New features entry for the
cuda.core.texture module (NVIDIA#467, NVIDIA#2095, NVIDIA#2307).
Andy-Jost added a commit that referenced this pull request Jul 8, 2026
* docs(core): finalize 1.1.0 release notes
Add a Highlights section, backfill PR/issue links on New features entries,
and add Bug fixes entries for the v1.1.0 IPC import hardening (#2219, #2223,
#2224), the ManagedBuffer.accessed_by torn-state fix (#2222), and graph node
attachment lifetimes (#2280).
Highlights (per review): the .pyi type-stub support and the new
cuda.core.texture module; NVLink enumeration is covered in New features /
Bug fixes rather than as a highlight. Also adds a New features entry for the
cuda.core.texture module (#467, #2095, #2307).
* Reorder API refs + ensure each section has currentmodule
the compilation toolchain is one of the unique selling points of cuda.core, but we are burying it deeply
---------
Co-authored-by: Leo Fang <leof@nvidia.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cuda.coreEverything related to the cuda.core moduleenhancementAny code-related improvementsP0High priority - Must do!

Projects

None yet

Development

Successfully merging this pull request may close these issues.

cuda.core.texture: API-style follow-up before v1.1.0 release

4 participants

@Andy-Jost@rwgk@leofang@juenglin