Skip to content

[Flutter GPU] Support configurable vertex layouts. #145013

Closed

Description

@bdero

Depends on #116168.

Activity

  1. moved this to 🤔 Needs Triage in Flutter GPUon Mar 12, 2024
  2. moved this from 🤔 Needs Triage to 💡 Features in Flutter GPUon Mar 12, 2024
  3. added
    c: new featureNothing broken; request for a new capability
    engineflutter/engine related. See also e: labels.
    c: proposalA detailed proposal for a change to Flutter
    on Mar 13, 2024
  4. added
    P2Important issues not at the top of the work list
    on Mar 18, 2024
  5. bdero commented on Oct 17, 2024

    @bdero
    MemberAuthor

    This is unblocked now that flutter-team-archive/engine#55856 has landed.

  6. bdero commented on May 10, 2026

    @bdero
    MemberAuthor

    Time to revisit this. The HAL work tracked in #116168 landed in October 2024, and after the cross-HAL design pass below the next step is a minimum-safe v1 that ships useful capability today without backing us into a corner once the deferred features come online. Posting this as a proposal so it can be torn apart before code.

    v1 scope (no Impeller HAL changes required)

    Everything below is expressible with ShaderStageIOSlot + ShaderStageBufferLayout + VertexDescriptor exactly as they exist today.

    Dart surface:

    // Closed enum of the 12 four-byte-element vertex formats that Impeller's
    // (type, bit_width, vec_size, columns) tuple can already express. Packed,
    // normalized, half-float, BGRA-swizzled, and 64-bit formats are deferred
    // until the HAL grows a real format enum or a normalize bit. Adding them
    // later is purely additive (new enum values).
    enum VertexFormat {
      float32, float32x2, float32x3, float32x4,
      uint32,  uint32x2,  uint32x3,  uint32x4,
      sint32,  sint32x2,  sint32x3,  sint32x4;
    
      int get bytesPerElement;
      int get componentCount;
    }
    
    class VertexAttribute {
      const VertexAttribute({
        required this.location,
        required this.bufferBinding,
        required this.offsetInBytes,
        required this.format,
      });
      final int location;
      final int bufferBinding;
      final int offsetInBytes;
      final VertexFormat format;
    }
    
    class VertexBufferLayout {
      const VertexBufferLayout({
        required this.binding,
        required this.strideInBytes,
        // No stepMode/stepRate parameters in v1. Both can be added later as
        // named parameters with defaults without breaking existing callers.
      });
      final int binding;
      final int strideInBytes;
    }
    
    class VertexLayout {
      const VertexLayout({
        required this.buffers,
        required this.attributes,
      });
      final List<VertexBufferLayout> buffers;
      final List<VertexAttribute> attributes;
    }

    Pipeline + RenderPass:

    // GpuContext gains an optional vertexLayout parameter.
    // null means "use the impellerc-generated interleaved default" (today's behavior).
    RenderPipeline createRenderPipeline(
      Shader vertex,
      Shader fragment, {
      VertexLayout? vertexLayout,
    });
    
    // RenderPass.bindVertexBuffer gains an optional slot parameter so callers
    // can bind multiple vertex buffers in an SoA layout. Default is 0, which
    // preserves the current single-buffer call sites unchanged.
    void bindVertexBuffer(
      BufferView bufferView,
      int vertexCount, {
      int slot = 0,
    });

    Validation at createRenderPipeline time (each throws a Dart exception with a specific message):

    • format is one of the 12 enum values above.
    • attribute.offsetInBytes + format.bytesPerElement is at most layout.strideInBytes.
    • attribute.bufferBinding references a VertexBufferLayout.binding actually present in vertexLayout.buffers.
    • attribute.location corresponds to a vertex shader input declared in the bound shader (impellerc reflection has the data).
    • The number of buffers and attributes is within the limits reported by the device's Capabilities.

    What v1 enables

    This closes the "no instancing, 4-byte-element formats only" slice of #145013, which is what most 3D asset pipelines actually need to load a glTF and draw it.

    What v1 explicitly defers, and why each is a non-corner

    Feature HAL gate How it gets added without breaking v1
    VertexStepMode { vertex, instance } Add inputRate to ShaderStageBufferLayout New VertexStepMode stepMode = VertexStepMode.vertex named parameter on VertexBufferLayout. Existing const calls compile unchanged.
    stepRate: int (instance divisor > 1) VK_KHR_vertex_attribute_divisor (or 1.4 core) on the Vulkan backend New int stepRate = 1 named parameter after stepMode.
    Normalized integer formats (unorm8x4, snorm16x2, ...) HAL grows a normalize bit on ShaderStageIOSlot or a real flat format enum New VertexFormat enum values. Existing values keep their semantics.
    Packed formats (unorm10_10_10_2) HAL representation that doesn't fit (type, bit_width, vec_size, columns) New VertexFormat enum values.
    unorm8x4_bgra swizzled format Same as packed New enum value.
    Broadcast (stride=0 or divisor=0) Vulkan extension support; rarely needed Add VertexStepMode.constant or stepRate=0 once it lands.
    Dynamic per-draw layout (vkCmdSetVertexInputEXT shape) Impeller doesn't currently use VK_EXT_vertex_input_dynamic_state New RenderPass.setVertexLayout(VertexLayout), gated on Capabilities.supportsDynamicVertexLayout. Doesn't touch the v1 API surface.
    64-bit attributes Vulkan-only optional feature, vanishingly rare in practice New enum values.
    defaultVertexLayout retrieval (start from impellerc's layout and modify it) Pure ergonomics, nothing in v1 prevents it Add RenderPipeline.defaultVertexLayout getter or a VertexLayout.fromShader(Shader) constructor.

    The two ways v1 could have locked us in are format expressiveness and attribute keying. Both are deliberately closed:

    • Format: a closed enum, not a tuple of (VertexElementType, bitWidth, components). Adding packed and normalized formats later is appending to the enum. No call-site migration.
    • Attribute keying: integer location, matching WebGPU, Vulkan, and Metal. impellerc already emits locations for the default layout. Name-based lookup is a future ergonomic helper, not load-bearing.

    Concrete first PR

    1. Add the four Dart classes (VertexFormat, VertexAttribute, VertexBufferLayout, VertexLayout).
    2. Plumb the optional vertexLayout through GpuContext.createRenderPipeline to flutter::gpu::RenderPipeline, which builds an impeller::VertexDescriptor from the supplied layout instead of falling through to the impellerc reflection default.
    3. Add the optional slot named parameter to RenderPass.bindVertexBuffer.
    4. Validation plus a gpu_test.dart SoA test case (a triangle drawn from one position buffer plus one color buffer).

    Spinoff issues to file alongside the PR

    • [Impeller] Add step mode (VkVertexInputRate-equivalent) to ShaderStageBufferLayout. Required for Flutter GPU instancing.
    • [Impeller] Add a flat vertex attribute format enum or normalize bit to ShaderStageIOSlot. Required for normalized, packed, BGRA, half-float, and 64-bit vertex formats. Closes [Impeller] Add feature to allow attribute mapping of uint32s host-side to vec4s shader-side #102778 once landed.
    • [Flutter GPU] Expose vertex step mode and step rate. Depends on the first HAL issue above.
    • [Flutter GPU] Expose normalized and packed vertex formats. Depends on the second HAL issue above.
    • [Flutter GPU] Dynamic per-draw vertex layout. Depends on Impeller adopting VK_EXT_vertex_input_dynamic_state.
  7. added and removed on May 10, 2026
  8. self-assigned this
    on May 10, 2026
  9. flutter-triage-bot commented on May 10, 2026

    @flutter-triage-bot

    The triaged-engine label is irrelevant if there is no team-engine label or fyi-engine label.

  10. moved this from 💡 Features to ✅ Done in Flutter GPUon May 12, 2026
  11. added a commit that references this issue on May 12, 2026
    12d86bc
  12. github-actions commented on May 26, 2026

    @github-actions

    This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

  13. locked as resolved and limited conversation to collaborators on May 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

P2Important issues not at the top of the work listc: new featureNothing broken; request for a new capabilityc: proposalA detailed proposal for a change to Flutterengineflutter/engine related. See also e: labels.flutter-gputeam-fluttergpuOwned by Flutter GPU team

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions