BackdropFilter shader UV sampling is incorrect on beta when using ClipRect #178798
Copy link
Copy link
Closed
Closed
Copy link
Labels
P2Important issues not at the top of the work listImportant issues not at the top of the work liste: impellerImpeller rendering backend issues and features requestsImpeller rendering backend issues and features requeststeam-engineOwned by Engine teamOwned by Engine teamtriaged-engineTriaged by Engine teamTriaged by Engine team
Description
This issue might be related to #170820
Steps to reproduce
- Run the code sample on the current beta channel, and on 3.38.0 (stable)
- They produce different outputs
Expected results
The UV's of BackdropFilter used to stay consistent, even if it was clipped. Output on 3.38.0:
Notice how the top left of the shader's rect is not entirely black, and the purple border isn't visible, because UV (0,0) is the top left of the screen, not of the clip rect.
Actual results
Output on 3.39.0-0.1.pre:
Notice how the UV now seem to be in the coordinate space of the ClipRectLayer
Code sample
Code sample
assets/shaders/uv.frag:
#version 460 core
precision mediump float;
#include <flutter/runtime_effect.glsl>
uniform vec2 uSize;
uniform sampler2D uTexture;
out vec4 fragColor;
void main() {
vec2 fragCoord = FlutterFragCoord().xy;
vec2 screenUV = vec2(fragCoord.x / uSize.x, fragCoord.y / uSize.y);
vec4 texColor = texture(uTexture, screenUV);
#ifdef IMPELLER_TARGET_OPENGLES
screenUV.y = 1.0 - screenUV.y;
#endif
// Check if we're within 20px of any edge
float borderWidth = 20.0;
bool inBorder = fragCoord.x < borderWidth ||
fragCoord.x > (uSize.x - borderWidth) ||
fragCoord.y < borderWidth ||
fragCoord.y > (uSize.y - borderWidth);
if (inBorder) {
fragColor = vec4(0.5, 0.0, 0.5, 1.0); // Purple border
} else {
fragColor = vec4(screenUV, 0.0, 1.0);
fragColor = mix(fragColor, texColor, .5);
}
}pubspec.yaml
name: flutter_sandbox
description: "A new Flutter project."
publish_to: 'none'
version: 0.1.0
environment:
sdk: ^3.9.2
dependencies:
flutter:
sdk: flutter
flutter_shaders: ^0.1.3
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
flutter:
uses-material-design: true
shaders:
- assets/shaders/uv.fraglib/main.dart:
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_shaders/flutter_shaders.dart';
void main() {
runApp(const MainApp());
}
class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Stack(
children: [
Positioned.fill(child: GridPaper()),
Positioned.fill(
child: ShaderBuilder(
assetKey: 'assets/shaders/uv.frag',
(context, shader, child) => MyShaderWidget(shader: shader),
),
),
],
),
),
);
}
}
class MyShaderWidget extends SingleChildRenderObjectWidget {
const MyShaderWidget({super.key, required this.shader, super.child});
final FragmentShader shader;
@override
RenderObject createRenderObject(BuildContext context) {
return RenderMyShader(shader);
}
}
class RenderMyShader extends RenderProxyBox {
RenderMyShader(this.shader);
final FragmentShader shader;
final LayerHandle<ClipRectLayer> _clipRectLayer =
LayerHandle<ClipRectLayer>();
final LayerHandle<BackdropFilterLayer> _backdropFilterLayer =
LayerHandle<BackdropFilterLayer>();
static const inset = EdgeInsets.all(66);
@override
void paint(PaintingContext context, Offset offset) {
final rect = offset & size;
_clipRectLayer.layer = context.pushClipRect(
true,
offset,
inset.deflateRect(rect),
(context, offset) {
final layer = _backdropFilterLayer.layer ??= BackdropFilterLayer();
layer.filter = ImageFilter.shader(shader);
context.pushLayer(layer, (context, offset) {}, offset);
},
oldLayer: _clipRectLayer.layer,
);
super.paint(context, offset);
}
@override
void dispose() {
_clipRectLayer.layer = null;
_backdropFilterLayer.layer = null;
super.dispose();
}
}
Logs
No response
Flutter Doctor output
Doctor output
[✓] Flutter (Channel beta, 3.39.0-0.1.pre, on macOS 15.7.2 24G325 darwin-arm64, locale
en-US)
[✓] Android toolchain - develop for Android devices (Android SDK version 36.1.0-rc1)
[✓] Xcode - develop for iOS and macOS (Xcode 26.0)
[✓] Chrome - develop for the web
[✓] Connected device (4 available)
[✓] Network resources
• No issues found!Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
P2Important issues not at the top of the work listImportant issues not at the top of the work liste: impellerImpeller rendering backend issues and features requestsImpeller rendering backend issues and features requeststeam-engineOwned by Engine teamOwned by Engine teamtriaged-engineTriaged by Engine teamTriaged by Engine team
Activity
gaaclarke commentedon Nov 20, 2025
To clarify, the OP is expecting that image because they expect the purple rect to be part of the clipped region.
Like this unclipped:
I'm not sure if this is desired behavior or not. The behavior was not documented anywhere I can find either in comments or in tests. That means it's not technically a breaking change. We have to decide if this is the behavior we want or not though.
gaaclarke commentedon Nov 20, 2025
Here is a more simple version:
Details
If you switch the code from a backdrop filter to a gradient you can see that gradients are exhibiting the same behavior as the fragment shaders. The top righthand corner in the clipped region is is (0, 255, 0). That suggests to me that the current behavior is the expected one.
Details
Also, if you read the documentation for the ClipRect it says "ClipRect prevents its child from painting outside its bounds". That suggests to me that the current behavior is the correct behavior because FragCoords should not be drawing or considering fragments outside of the bounds of the ClipRect.
The change is not technically a breaking change because it broke no tests. I can look into doing a courtesy notification.
gaaclarke commentedon Nov 20, 2025
@timcreatedit also fyi there are customer submitted tests in flutter. When your project becomes more mature it's worth considering contributing there: https://github.com/flutter/tests
gaaclarke commentedon Nov 20, 2025
Reopening this since I'd like to codify it in our own test so that it doesn't change again in the future.
gaaclarke commentedon Nov 21, 2025
@jason-simmons made a good point that Canvas.drawPaint with a fragment shader behaves like the old behavior. Also, the example above with the gradient is potentially flawed since the padding is also affecting the widget size and not just the cliprect size. We need to circle back to this. We should be careful too of performance, it may be that we can get better performance by editing the uniforms instead of tweaking the vertices.
gaaclarke commentedon Nov 21, 2025
I converted the reproduction to an impeller unittest, it's a bit easier to see for engine types: #178940
Documents and fixes behavior when clipping background filter fragment…
Documents and fixes behavior when clipping background filter fragment…
Documents and fixes behavior when clipping background filter fragment…
github-actions commentedon Dec 11, 2025
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
flutter doctor -vand a minimal reproduction of the issue.Documents and fixes behavior when clipping background filter fragment…