Closed
Description
Background
flutter/engine#49262 updated Flutter Windows to render to an offscreen framebuffer instead of directly onto the window's surface. This will allow us to support platform views and multiple views.
Unfortunately, this change regresses package:material_floating_search_bar_2
's animation and causes weird artifacts.
`package:material_floating_search_bar_2` example...
(FYI there's a more minimal repro below)
import 'package:flutter/material.dart';
import 'package:material_floating_search_bar_2/material_floating_search_bar_2.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
// This seems to be necessary to repro:
debugShowCheckedModeBanner: false,
home: Scaffold(
resizeToAvoidBottomInset: false,
body: FloatingSearchBar(
// This seems to be necessary to repro:
transition: CircularFloatingSearchBarTransition(spacing: 16),
// This isn't necessary but it makes the artifacts more obvious:
transitionDuration: const Duration(seconds: 5),
builder: (BuildContext context, _) {
return Material(
// This seems to be necessary to repro:
borderRadius: BorderRadius.circular(8),
child: const Text('Hello world'),
);
},
body: Container(),
),
),
);
}
}
Minimal repro
Commit | Scenario | Works? |
---|---|---|
9c2a756 | Windows OpenGL | ✅ |
b417fb8 | Windows OpenGL | ❌ |
b417fb8 | Windows software rendering | ✅ |
b417fb8 | Linux OpenGL | ✅ |
b417fb8 | Web | ✅ |
Source code...
import 'package:flutter/material.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
// Pump frames non-stop. This isn't necessary to repro.
WidgetsBinding.instance.addPersistentFrameCallback((_) {
WidgetsBinding.instance.scheduleFrame();
});
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
// This is necessary to reproduce:
return Container(
color: Colors.white,
child: const Directionality(
textDirection: TextDirection.ltr,
child: BlueCircle(),
),
);
}
}
class BlueCircle extends StatelessWidget {
const BlueCircle({super.key});
@override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
const Opacity(
opacity: 0.3,
child: SizedBox.expand(
child: DecoratedBox(
decoration: BoxDecoration(color: Colors.black),
),
),
),
SizedBox.expand(
child: ClipOval(
clipper: const _CircularRevealClipper(),
child: DecoratedBox(
decoration: BoxDecoration(
color: Colors.blue,
// Removing this line fixes the app.
borderRadius: BorderRadius.circular(16.0),
),
child: const SizedBox(height: 256.0),
),
),
),
],
);
}
}
class _CircularRevealClipper extends CustomClipper<Rect> {
const _CircularRevealClipper();
@override
Rect getClip(Size size) {
return Rect.fromCircle(
center: Offset.zero,
radius: 500,
);
}
@override
bool shouldReclip(CustomClipper<Rect> oldClipper) => true;
}
TODO
-
The engine has a dirty region feature that can be disabled. Check if disabling this fixes the issue.RenderDoc showed that the framebuffer contains the expected texture. It appears that the compositor'sglFramebufferBlit
is the issue. - Enable Skia canvas trace commands to verify "good" and "bad" commits are drawing the same things
- Use a GPU frame debugger (like RenderDoc) to look at GL calls