Skip to content

[Impeller] Stroked cubic bezier paths render with visible segmentation instead of smooth continuous lines #180587

@amantinband

Description

@amantinband

Steps to reproduce

  1. Create a CustomPainter that draws a Path with multiple connected cubic bezier curves using PaintingStyle.stroke
  2. Run on macOS with Impeller enabled (default)
  3. Observe discontinuities/gaps between curve segments
  4. Run with --no-enable-impeller - the issue disappears

Expected results

Stroked paths with connected cubic bezier curves should render as smooth, continuous lines with no visible gaps between segments - matching how Skia renders the same path.

Actual results

With Impeller, stroked cubic bezier paths show visible discontinuities/gaps at segment junctions. The stroke appears "fragmented" even though:

  • The path data is identical (verified via logging)
  • Fill rendering of the same path is correct
  • Hit-testing on the path works correctly (points are on the actual curve)

Code sample

import 'package:flutter/material.dart';

void main() => runApp(const MaterialApp(home: Scaffold(body: BezierTest())));

class BezierTest extends StatelessWidget {
  const BezierTest({super.key});

  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      size: Size.infinite,
      painter: BezierPainter(),
    );
  }
}

class BezierPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.blue
      ..style = PaintingStyle.stroke
      ..strokeWidth = 2.0;

    // Connected cubic bezier curves forming a closed shape
    final path = Path();
    
    // These are real values from a vector graphics app
    path.moveTo(1247.1, 467.6);
    path.cubicTo(1257.1, 477.9, 1284.6, 477.7, 1294.7, 467.6);
    path.cubicTo(1303.6, 458.7, 1303.6, 434.5, 1294.7, 425.5);
    path.cubicTo(1284.8, 415.7, 1258.2, 415.8, 1248.2, 425.5);
    path.cubicTo(1239.2, 434.3, 1238.3, 458.6, 1247.1, 467.6);
    path.close();

    canvas.drawPath(path, paint);
    
    // For comparison, fill renders correctly:
    final fillPaint = Paint()
      ..color = Colors.red.withOpacity(0.3)
      ..style = PaintingStyle.fill;
    canvas.drawPath(path, fillPaint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

Screenshots

Impeller rendering:
Image

Skia rendering:
Image

Flutter version

╰─ flutter doctor -v
[✓] Flutter (Channel stable, 3.38.1, on macOS 26.2 25C5048a darwin-arm64, locale en-IL)
    [131ms]
    • Flutter version 3.38.1 on channel stable at
      /opt/homebrew/Caskroom/flutter/3.32.6/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision b45fa18946 (8 weeks ago), 2025-11-12 22:09:06 -0600
    • Engine revision b5990e5ccc
    • Dart version 3.10.0
    • DevTools version 2.51.1
    • Feature flags: enable-web, enable-linux-desktop, enable-macos-desktop,
      enable-windows-desktop, enable-android, enable-ios, cli-animations,
      enable-native-assets, omit-legacy-version-file, enable-lldb-debugging

Affected platforms

  • macOS
  • iOS (please test and check if applicable)

Workaround

Disable Impeller.

flutter run --no-enable-impeller

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