Skip to content

[Impeller] ISize::MipCount returns one less than the canonical mip chain length and uses min instead of max #186176

Description

@bdero

impeller::ISize::MipCount (in impeller/geometry/size.h) computes the maximum mip chain length as floor(log2(min(width, height))) clamped at 1. The canonical formula used by Vulkan, Metal, OpenGL, D3D, and WebGPU is floor(log2(max(width, height))) + 1.

The current Impeller formula differs in two ways:

  1. It uses the smaller dimension (min) instead of the larger (max).
  2. It returns one less level than the canonical count (no + 1).

Why this matters

For a 1024×1 texture, the canonical mip chain length is 11. Impeller's formula returns 1, meaning Allocator::CreateTexture silently allocates fewer mip levels than the underlying API would otherwise support. Subsequent writes to higher mip levels then fail at the Impeller boundary.

This came up while landing the Flutter GPU mipLevelCount work in #185890. The Dart-side Texture.fullMipCount helper was originally written to match the canonical formula, which caused tests to fail because the Impeller allocator silently capped at the smaller value. The Dart helper was then updated to mirror Impeller's actual behavior to avoid the silent-undercount footgun, and a doc comment now calls out the discrepancy.

Proposed fix

  1. Update ISize::MipCount to floor(log2(max(width, height))) + 1, clamped at 1 for empty/zero inputs.
  2. Audit callers for any places that depended on the smaller-dimension or off-by-one behavior, particularly the texture-allocation paths in each backend.
  3. Once Impeller is updated, restore the canonical formula in Texture.fullMipCount on the Dart side and update the doc comment.

Repro

Impeller:

ISize{1024, 1}.MipCount();  // returns 1
// Canonical answer: 11

Flutter GPU:

gpu.gpuContext.createTexture(StorageMode.hostVisible, 1024, 1, mipLevelCount: 11);
// Today: throws because the Dart-side `fullMipCount` returns 1, matching
// Impeller's allocator cap. With a corrected `ISize::MipCount`, this would
// allocate the expected chain.

Low priority IMO. Flutter GPU's Dart API works correctly given Impeller's current behavior. This is about lifting an artificial restriction on what mip chains the engine can allocate.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work liste: impellerImpeller rendering backend issues and features requestsengineflutter/engine related. See also e: labels.team-engineOwned by Engine teamtriaged-engineTriaged by Engine team

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions