-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Description
The most immediate source of code duplication between the CanvasKit and Skwasm backends is in the implementation of the core dart:ui primitives, such as Canvas, Paint, and Path. For nearly every primitive, there is a CanvasKit-specific version (Ck...) and a Skwasm-specific version (Skwasm...).
Current State & Evidence of Duplication
Both backends implement the same interfaces (e.g., LayerCanvas, which implements ui.Canvas), but do so in parallel, leading to nearly identical code. The methods in these classes contain the same high-level logic: validate arguments, convert Flutter types to a native representation, make the native call, and manage the lifecycle of the native object.
This pattern is repeated for dozens of methods. A clear example is drawRect:
lib/src/engine/canvaskit/canvas.dart (CkCanvas)
@override
void drawRect(ui.Rect rect, ui.Paint paint) {
assert(rectIsValid(rect));
final skPaint = (paint as CkPaint).toSkPaint();
skCanvas.drawRect(toSkRect(rect), skPaint);
skPaint.delete();
}lib/src/engine/skwasm/skwasm_impl/canvas.dart (SkwasmCanvas)
@override
void drawRect(ui.Rect rect, ui.Paint paint) {
final paintHandle = (paint as SkwasmPaint).toRawPaint();
withStackScope((StackScope s) {
canvasDrawRect(_handle, s.convertRectToNative(rect), paintHandle);
});
paintDispose(paintHandle);
}The logic is identical: get a native paint object, convert the Rect, make the native call, and dispose of the paint object. Only the final, backend-specific API call is different.
Duplication Leads to Bugs and Inconsistent Behavior
A more critical issue with this parallel implementation is that bug fixes and edge-case handling applied to one backend are often not ported to the other. This leads to a divergence in behavior and hard-to-diagnose, backend-specific bugs.
A perfect historical example of this was a crash that occurred when creating an ImageFilter.blur with sigma values of zero. This was fixed in CanvasKit, but because the logic was not shared, the bug persisted in the Skwasm backend until the fix was manually ported. Unifying the implementations would have prevented this discrepancy.
Proposed Unification
To eliminate this duplication, the common logic should be extracted into a shared base class or a composition layer. The backend-specific implementations (Ck... and Skwasm...) would then inherit this shared logic and would only be responsible for providing the final, backend-specific native call.
This ensures that validation, type conversion, lifecycle management, and bug fixes are implemented once and applied to both backends simultaneously, improving correctness and consistency across the entire web platform.