Avalonia
Avalonia copied to clipboard
Slider with custom style leaves spots outside of its bounds
Describe the bug
Slider with custom style leaves spots outside of its bounds when being dragged. See below video. I have tested that the "spots" are outside of the sliders bounds. We have had a similar issue in the past: https://github.com/AvaloniaUI/Avalonia/issues/9659
To Reproduce
Steps to reproduce the behavior:
- Load repro app from https://github.com/llfab/Samples/tree/main/AvaloniaAnimationTest
- Build and run
- Look at slider before draggin it
- Start dragging slider and observe left side
Expected behavior
There should not be any slider colored pixels outside of its bounds
Video
If applicable, add screenshots to help explain your problem.
https://github.com/AvaloniaUI/Avalonia/assets/31031996/49f5c4b3-7e3e-422f-bbaf-11d0f47c77c7
Environment
- OS: Windows 11
- Avalonia-Version: 11.0.6
Two thoughts here:
- We had an old issue where there were rendering artifacts. At the time it was thought to be caused by Skia and was most often visible with borders. See: https://github.com/AvaloniaUI/Avalonia/issues/10873#issuecomment-1493046032 (I can't find the original issue for it, perhaps it was a discussion)
- ~Border rendering with certain BorderThickness and CornerRadius is now known to have issues. I found this with BackgroundSizing: https://github.com/AvaloniaUI/Avalonia/pull/14048#issue-2057771245~
Checking the Fluent Slider theme it does indeed use Border. I suspect this is related to one of the two areas above.
Thanks for the feedback. ...and indeed the style I created uses corner radius. So any thoughts and how to work around that?
On the off chance it's related to the border rendering issues can you try the BackgroundSizing PR package referenced above? That would eliminate that option fairly quickly if you still see an issue.
I realize this does not appear at first. It is an artifact that occurs later on. That means it likely isn't a border rendering issue due to incorrect geometries (which would appear always) and is instead likely case 1 mentioned above.
Also notice that it gets worse the more the control redraws itself -- so transparency is involved. It is likely caused by SkiaSharp anti-aliasing outside expected bounds that aren't invalidated. This keeps stacking some of the anit-aliased pixels so they appear more and more during redraws until its a solid pixel.
You can likely work around it by adjusting the anti-aliasing on the border in the slider's control template. See https://github.com/AvaloniaUI/Avalonia/pull/9584
It's either a bug in SkiaSharp that will go away with 3.0 -- or we have to specially adjust the drawing area invalidation code to account for more than just the bounds of the control. It also needs to account for Skia's anti-aliasing algorithm which currently draws outside what is expected. I suspect something is already done like this for box shadows.
This is all a best guess but I've been watching this class of issue for a while trying to understand it better.
Thanks for this analysis. Would you recommend to set RenderOptions.EdgeMode="Aliased"
?
Thanks for this analysis. Would you recommend to set RenderOptions.EdgeMode="Aliased" ?
Only if you can tolerate the pixilation in the rounded corners. I'm not sure which looks worse for your scenario.
I also see a case of this in the current ControlCatalog. Notice when the ColorPicker flyout is open, and the color is changing, the selected color (in the DropDownButton content area) gets the artifacts. These artifacts accumulate slowly over time and only occur around the corner radius. As soon as the flyout closes the UI is invalidated properly and the artifacts go away on redraw.
https://github.com/AvaloniaUI/Avalonia/assets/17993847/fc35cf40-7278-4525-a07d-34ad83ace92c
I don't believe we can tolerate Aliased. So, we could either do a workaround and try to invalidate the background panel once the slider changes (how would that be done?) or we sit and wait if you think this will solve itself during the next 12 months. What do you think?
Could you try playing with RequiresFullOpacityHandling
that creates a full layer for the sub tree
Could you try playing with
RequiresFullOpacityHandling
that creates a full layer for the sub tree
For the slider or the parent panel?
Another idea is to use a clipping geometry. That might be able to hide the issue but I haven't tried it myself.
Double checked:
-
RequiresFullOpacityHandling
does not change the behavior. Neither for the surrounding panel nor for the slider -
RenderOptions.EdgeMode="Aliased"
prevents the problem but indeed the corner radius looks rougher
@llfab Good to know, I think the only possible work-around with no visible downsides is a special clipping geometry.
@llfab Good to know, I think the only possible work-around with no visible downsides is a special clipping geometry.
Good. Any kind of coding hints on how to do that?
@llfab tbh I would just add another Panel blow the Border which has 1px of Margin.
Hope this sketch makes it a bit more clear:
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="AvaloniaAnimationTest.AnimationControl"
Padding="4">
+ <Panel Background="Tansparent" Padding="1">
<Border Name="animatedBorderHost"
Background="DarkGray"
CornerRadius="2"
ClipToBounds="True"
Height="8"
HorizontalAlignment="Stretch">
<Border Name="innerBorder"
Background="Green"
CornerRadius="4"
Width="200"
HorizontalAlignment="Left"/>
</Border>
+ </Panel>
</UserControl>
That did not work. The only workaround that seems to work is:
<Border x:Name="_myBorder"
Background="Transparent"
Padding="2 0 0 0">
<Slider x:Name="_mySlider"
Classes="largeSize"
Orientation="Horizontal"
HorizontalAlignment="Left"
VerticalAlignment="Center"
ValueChanged="OnSliderValueChanged"
Minimum="-1"
Maximum="1"
TickPlacement="TopLeft"
Ticks="0"/>
</Border>
private void OnSliderValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
_myBorder.InvalidateVisual();
}
But placing this everywhere in the UI seems cumbersome. Hence, is you feeling that this will be solved within the this year?
We marked the issue with priortiy, to I hope we will be able to find a solution soonish. I had a similar issue where I needed to infalte dirty rect by some px. While this could also solve this, it will add more work on the renderer where not needed. So maybe we can find a better solution here.
Great. From my side I would love to see this solved even if I can live with a couple more months...
It seems to be a Skia bug. SKCanvas.DrawRoundRect
seems to be somewhat ignoring the clip bounds.
I. e.
private SKRoundRect _rect;
private SKPaint _paint = new();
public ClipTest()
{
_rect = new SKRoundRect();
var skArray = new SKPoint[4];
skArray[0].X = 8;
skArray[0].Y = 8;
skArray[1].X = 0;
skArray[1].Y = 0;
skArray[2].X =0;
skArray[2].Y = 0;
skArray[3].X =8;
skArray[3].Y = 8;
_rect.SetRectRadii(new SKRect(0, 0, 40, 40), skArray);
}
protected override void SkiaRender()
{
_paint.IsAntialias = true;
_paint.Color = SKColors.Aqua;
Canvas.SetMatrix(SKMatrix.CreateTranslation(200, 200));
for (var c = 0; c < 255; c++)
{
Canvas.Save();
Canvas.ClipRect(_rect.Rect);
Canvas.Clear(SKColors.Black);
_paint.Color = new SKColor(255, (byte)c, 255);
Canvas.DrawRoundRect(_rect, _paint);
Canvas.Restore();
}
}
produces this:
It seems to be a Skia bug.
SKCanvas.DrawRoundRect
seems to be somewhat ignoring the clip bounds.
Thanks Nikita. Do you think Skia or SkiaSharp bug?
And: would that be fixed with SkiaSharp 3.x and the according Skia version?
No, it wasn't fixed in SkiaSharp 3.0
It goes away if we use an intermediate texture and render it with clipping, but it would require some infrastructure changes.
Thanks Nikita for checking. Expensive but temporary workarounds may not be the best way. Question is whether it's Skia or SkiaSharp and whether we could make either team fix the root cause within say the next 8 months or so...
If this is a skia bug, can you confirm with skia fiddle and mayne file a bug with them? Mayne it is fixed in a later skia? I could do another native bump...
void draw(SkCanvas* canvas) {
SkPaint paint;
paint.setAntiAlias(true);
SkPath path;
SkRRect rrect;
SkVector corners[] = {{8,8}, {0,0}, {0,0}, {8,8}};
rrect.setRectRadii({0, 0, 40, 40}, corners);
canvas->translate(20,20);
for(int c=0; c<255; c++)
{
canvas->save();
canvas->clipRect(SkRect::MakeWH(40, 40), true);
canvas->clear(SkColorSetARGB(0xff, 0xFF, 0xff, 0xff));
canvas->drawRRect(rrect, paint);
canvas->restore();
}
}
https://issues.skia.org/issues/327877721
Should have been fixed by #14806.