Performance Optimizations vs. Customization in FlNodes Rendering #46
-
|
Hey @cabaucom376 @mj-963! As collaborators, I’d love to hear your thoughts on the strengths of FlNodes and where we should focus our priorities. I've been working on performance optimizations, and some solutions I’ve implemented significantly reduce rendering overhead—but they come at the cost of customization flexibility. The Optimization: Batching Draw CallsOne of the key optimizations I’ve introduced is batching draw calls for links. Instead of creating and rendering individual paths for each link, we now combine multiple links into a single ✅ Pros of Batching:
🚨 But There's a Trade-off:
The Problem with Gradients in Cached PaintsGradients require a bounding rectangle ( paint.shader = LinearGradient(
colors: [startColor, endColor],
begin: Alignment.centerLeft,
end: Alignment.centerRight,
).createShader(Rect.fromPoints(start, end));The problem? If we cache
Potential Solutions1️⃣ Prioritize Performance (Recommended)
2️⃣ Use GPU Shaders for Gradients (More Complex)
3️⃣ Fallback to Per-Link Rendering When Needed
What Do You Think?I personally lean towards favoring performance because it aligns with Flutter's philosophy and provides a better developer experience. However, I’d love to get your opinions before making a final decision. Would you prefer we keep gradients at the cost of performance, or should we fully optimize rendering and drop gradient support? Looking forward to your thoughts! 🚀 |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments
-
|
I personally lean toward customization. Prioritizing flexibility for package consumers over hyper-optimizations seems like a worthwhile tradeoff. While gradient support isn’t a major concern for me, revisiting issue #19 makes a strong case for allowing users to define their own link styles. This would enable greater adaptability in diagramming contexts—whether they want to use arrows, squares, or any other custom shapes. I also wonder if there’s a way to define an abstract API interface that users could plug into, allowing them to implement custom link logic as needed (I'm not familiar with the current linking implementation forgive me if my assumptions are ignorant). This could align well with monorepo support—keeping the abstract linking logic in the core package while implementing the optimized linking approach within the fully featured fl_nodes package. |
Beta Was this translation helpful? Give feedback.
-
|
what if we make it an opt-in customization mode?. With this approach, the default behavior remains high-performance (using batched draw calls), but users who need detailed customizations like individual gradients per link can enable per-link rendering. For example, we could add a flag in the configuration (e.g., // In FlNodeEditorConfig:
class FlNodeEditorConfig {
final bool perLinkRendering;
// ... other configuration fields
const FlNodeEditorConfig({
this.perLinkRendering = false,
// ... other fields
});
FlNodeEditorConfig copyWith({
bool? perLinkRendering,
// ... other fields
}) {
return FlNodeEditorConfig(
perLinkRendering: perLinkRendering ?? this.perLinkRendering,
// ... other fields
);
}
}
// In NodeEditorRenderBox, update the link painting logic:
void _paintLinks(Canvas canvas) {
if (config.perLinkRendering) {
// Per-link rendering for customization.
for (final linkDrawData in linksData) {
_paintBezierLink(canvas, linkDrawData);
}
} else {
// Batched rendering for high performance.
_paintBatchedLinks(canvas);
}
}
// Dummy implementation for batched rendering (for illustration):
void _paintBatchedLinks(Canvas canvas) {
final combinedPath = Path();
for (final linkDrawData in linksData) {
// Add each link's path to combinedPath (omitting individual gradient details)
combinedPath.addPath(_buildLinkPath(linkDrawData), Offset.zero);
}
final Paint paint = Paint()
..color = Colors.white // use a flat color since gradients are dropped
..style = PaintingStyle.stroke
..strokeWidth = style.nodeStyle.linkStyle.lineWidth;
canvas.drawPath(combinedPath, paint);
}
Path _buildLinkPath(LinkDrawData linkDrawData) {
final path = Path();
// Construct a cubic bezier path for the link (as before)
// ...
return path;
}With this approach, by default perLinkRendering remains false so we get the performance benefits of batching. Users who require custom gradients and individual link styles can opt in by setting perLinkRendering to true. I believe this solution strikes a good balance between performance and customization while keeping the API user-friendly. Looking forward to your thoughts. |
Beta Was this translation helpful? Give feedback.
-
|
Hi @mj-963 and @cabaucom376! I just wanted to let you know that in the end I've opted for the hybrid approach as suggested by MJ. This will provide extreme performance gains while leaving the door open to customization. |
Beta Was this translation helpful? Give feedback.
Hi @mj-963 and @cabaucom376! I just wanted to let you know that in the end I've opted for the hybrid approach as suggested by MJ. This will provide extreme performance gains while leaving the door open to customization.