-
Notifications
You must be signed in to change notification settings - Fork 28k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Windows] Compositor rendering regresses apps with faded backgrounds #140828
Comments
…49461) This reverts #49262 (00d7d23) as it regressed [`material_floating_search_bar`](https://pub.dev/packages/material_floating_search_bar_2)'s animation. This revert was created manually due to merge conflicts. Issue tracking bug: flutter/flutter#140828 Part of flutter/flutter#128904 <details> <summary>Minimal repro of the broken animation...</summary> Here's what the animation is supposed to look like: ![good](https://publish-01.obsidian.md/access/b48ac8ca270cd9dac18c4a64d11b1c02/assets/2023-12-28-compositor_animation_regression_good.gif) Here's what the animation actually looks like: ![bad](https://publish-01.obsidian.md/access/b48ac8ca270cd9dac18c4a64d11b1c02/assets/2023-12-28-compositor_animation_regression_bad.gif) Here is a minimal repro of the broken animation: ```dart // The Windows compositor changes regresses the animation in // the `material_floating_search_bar_2` package: // // https://pub.dev/packages/material_floating_search_bar_2/versions/0.5.0 // // Below is a minimal repro of the broken animation. This has two pieces: // // 1. The background fades to a grey color // 2. A box is "revealed" using a custom clipper // // On framework commit b417fb8 this animation is broken on Windows. // On framework commit 9c2a756 this animation works as expected on Windows. // // Good gif: https://publish-01.obsidian.md/access/b48ac8ca270cd9dac18c4a64d11b1c02/assets/2023-12-28-compositor_animation_regression_good.gif // Bad gif: https://publish-01.obsidian.md/access/b48ac8ca270cd9dac18c4a64d11b1c02/assets/2023-12-28-compositor_animation_regression_bad.gif import 'dart:math'; import 'dart:ui'; import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @OverRide Widget build(BuildContext context) { // Not using `MaterialApp` is necessary to reproduce: return Container( color: Colors.white, child: const Directionality( textDirection: TextDirection.ltr, child: FloatingSearchBar(), ), ); // Switching to `MaterialApp` fixes the issue: // return const MaterialApp( // home: Scaffold( // body: FloatingSearchBar(), // ), // ); } } class FloatingSearchBar extends StatefulWidget { const FloatingSearchBar({super.key}); @OverRide FloatingSearchBarState createState() => FloatingSearchBarState(); } class FloatingSearchBarState extends State<FloatingSearchBar> with SingleTickerProviderStateMixin { late final AnimationController _controller = AnimationController( vsync: this, duration: const Duration(seconds: 2), ); @OverRide void dispose() { _controller.dispose(); super.dispose(); } void _animate() { if (_controller.isDismissed || _controller.status == AnimationStatus.reverse) { _controller.forward(); } else { _controller.reverse(); } } @OverRide Widget build(BuildContext context) { return AnimatedBuilder( animation: _controller, builder: (BuildContext context, _) { return Stack( children: <Widget>[ if (!_controller.isDismissed) FadeTransition( opacity: _controller, child: const SizedBox.expand( child: DecoratedBox( decoration: BoxDecoration(color: Colors.black26), ), ), ), _buildSearchBar(), ], ); }, ); } Widget _buildSearchBar() { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: <Widget>[ // This is where the search text input would go... GestureDetector( onTap: () => _animate(), child: Text( switch (_controller.status) { AnimationStatus.forward || AnimationStatus.completed => 'Click to close', AnimationStatus.reverse || AnimationStatus.dismissed => 'Click to open', }, style: const TextStyle(color: Colors.black), ), ), // Below are where the search results would be. Clicking on the search // input above reveals the results below. // Removing this fixes the background's fade transition. ClipOval( clipper: _CircularRevealClipper( fraction: _controller.value, ), child: DecoratedBox( decoration: BoxDecoration( color: Colors.white, // Removing this line fixes the background's fade transition. borderRadius: BorderRadius.circular(16.0), ), child: const Padding( padding: EdgeInsets.all(64.0), child: Text( 'Hello world', style: TextStyle(color: Colors.black), ), ), ), ), ], ); } } class _CircularRevealClipper extends CustomClipper<Rect> { const _CircularRevealClipper({required this.fraction}); final double fraction; @OverRide Rect getClip(Size size) { final double halfWidth = size.width * 0.5; final double maxRadius = sqrt(halfWidth * halfWidth + size.height * size.height); final double radius = lerpDouble(0.0, maxRadius, fraction) ?? 0; return Rect.fromCircle( center: Offset(halfWidth, 0), radius: radius, ); } @OverRide bool shouldReclip(CustomClipper<Rect> oldClipper) { if (oldClipper is _CircularRevealClipper) { return oldClipper.fraction != fraction; } return true; } } ``` </details> [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
Skia tracingHere are timeline traces with Skia enabled:
You can open these using Engine commits:
Comparison of Linux and Windows tracesHere's a comparison of the traces for a single frame of the animation on Linux and Windows while on flutter/engine@00d7d23. Traces...Notable differences are bolded. Traces inside
Traces between
Traces inside
Notable differences between the traces:
|
I have a smaller minimal repro: a clipped circle stacked on top of a faded background: Minimal source code...import 'package:flutter/material.dart';
void main() {
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(),
),
);
// Switching to this fixes the issue:
// return const MaterialApp(
// home: Scaffold(
// body: 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;
} I verified the framebuffer's texture is properly rendered using RenderDoc. The blit appears to be broken somehow. I updated the Windows compositor to clear the window surface to red before blitting the backing store's framebuffer. If blitting works as expected, the red should not be visible as the entire surface should have been blitted over. However, red color remains: Patch to clear the window surface to red before blitting...Apply this patch on flutter/engine@00d7d23: diff --git a/shell/platform/windows/compositor_opengl.cc b/shell/platform/windows/compositor_opengl.cc
index 6da274b993..c19cab4af1 100644
--- a/shell/platform/windows/compositor_opengl.cc
+++ b/shell/platform/windows/compositor_opengl.cc
@@ -132,6 +132,10 @@ bool CompositorOpenGL::Present(const FlutterLayer** layers,
return false;
}
+ gl_->BindFramebuffer(GL_DRAW_FRAMEBUFFER, kWindowFrameBufferId);
+ gl_->ClearColor(1.0f, 0.0f, 0.0f, 0.0f);
+ gl_->Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+
auto source_id = layers[0]->backing_store->open_gl.framebuffer.name;
gl_->BindFramebuffer(GL_READ_FRAMEBUFFER, source_id);
gl_->BindFramebuffer(GL_DRAW_FRAMEBUFFER, kWindowFrameBufferId); This appears to be ANGLE's blit implementation on D3D11: https://github.com/google/angle/blob/95fe7e470418cd5ac18d203eb29a25101c650f14/src/libANGLE/renderer/d3d/d3d11/Blit11.cpp#L576 |
ANGLE bug reporthttps://chromium.slack.com/archives/CGHH6049K/p1704925434965249 I believe Flutter Windows is affected by what appears to be a bug in ANGLE's Example appHere is a Flutter app that draws a rounded rectangle with a circular clip on top of a faded background: Source code...This is broken on Flutter commit 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;
}
AnalysisFlutter Windows uses Skia and ANGLE to render to an OpenGL framebuffer. Flutter Windows then blits this framebuffer to the window's surface. The blit updates the entire window surface (the framebuffer and window surface have the same size). Using RenderDoc's texture viewer, we can inspect the blit (event ID 57): renderdoc.zip
Clear testIt appears that the blit is not updating the entire window surface. To test this hypothesis, I added a step to clear the window's surface to a red color immediately before blitting the framebuffer. Since the blit should update the entire window surface, no red color should be visible. However, the red color is visible: Screenshot of test app...Notice how red color is visible. This indicates the blit did not update the entire window surface. Here is the RenderDoc capture: red_test_capture.zip Debugging informationAffected Flutter version: b417fb8 Perfetto trace of Skia and its OpenGL calls...Inspect this in Chrome by navigating to The OpenGL calls are in the |
## Original pull request description This migrates the Windows embedder to `FlutterCompositor` so that the engine renders off-screen to a framebuffer instead of directly onto the window's surface. This will allow us to support platform views and multiple views on Windows. Addresses flutter/flutter#128904 ## Reland (again) #49262 was reverted as it regressed [`package:material_floating_search_bar_2`](https://pub.dev/packages/material_floating_search_bar_2/versions/0.5.0). See: flutter/flutter#140828 This pull request is split into the following commits: 1. d337378 is the previous reland pull request, unchanged 2. e866af0 disables the scissor test before blitting the framebuffer, allowing us to "force" copy the framebuffer's contents by ignoring scissoring values [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
Fixed by flutter/engine#49726 |
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 |
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)
Minimal repro
Download Windows app.zip
Source code...
Screenshot of the expected output...
Screenshot of the actual output...
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.The text was updated successfully, but these errors were encountered: