Skip to content
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

Slider with custom style leaves spots outside of its bounds #14270

Closed
llfab opened this issue Jan 19, 2024 · 28 comments
Closed

Slider with custom style leaves spots outside of its bounds #14270

llfab opened this issue Jan 19, 2024 · 28 comments
Assignees

Comments

@llfab
Copy link
Sponsor

llfab commented Jan 19, 2024

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:
#9659

To Reproduce

Steps to reproduce the behavior:

  1. Load repro app from https://github.com/llfab/Samples/tree/main/AvaloniaAnimationTest
  2. Build and run
  3. Look at slider before draggin it
  4. 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.

SliderSpots.mp4

Environment

  • OS: Windows 11
  • Avalonia-Version: 11.0.6
@llfab llfab added the bug label Jan 19, 2024
@robloo
Copy link
Contributor

robloo commented Jan 19, 2024

Two thoughts here:

  1. 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: Canvas. Artifacts. Animation. #10873 (comment) (I can't find the original issue for it, perhaps it was a discussion)
  2. Border rendering with certain BorderThickness and CornerRadius is now known to have issues. I found this with BackgroundSizing: Add BackgroundSizing #14048 (comment)

image

Checking the Fluent Slider theme it does indeed use Border. I suspect this is related to one of the two areas above.

@llfab
Copy link
Sponsor Author

llfab commented Jan 19, 2024

Thanks for the feedback. ...and indeed the style I created uses corner radius. So any thoughts and how to work around that?

@robloo
Copy link
Contributor

robloo commented Jan 19, 2024

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.

@kekekeks kekekeks self-assigned this Jan 21, 2024
@robloo
Copy link
Contributor

robloo commented Jan 24, 2024

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 #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.

@llfab
Copy link
Sponsor Author

llfab commented Jan 24, 2024

Thanks for this analysis. Would you recommend to set RenderOptions.EdgeMode="Aliased" ?

@robloo
Copy link
Contributor

robloo commented Jan 27, 2024

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.

f2.mp4

@llfab
Copy link
Sponsor Author

llfab commented Jan 27, 2024

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?

@Gillibald
Copy link
Contributor

Could you try playing with RequiresFullOpacityHandling that creates a full layer for the sub tree

@llfab
Copy link
Sponsor Author

llfab commented Jan 27, 2024

Could you try playing with RequiresFullOpacityHandling that creates a full layer for the sub tree

For the slider or the parent panel?

@robloo
Copy link
Contributor

robloo commented Jan 27, 2024

Another idea is to use a clipping geometry. That might be able to hide the issue but I haven't tried it myself.

@llfab
Copy link
Sponsor Author

llfab commented Jan 28, 2024

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

@robloo
Copy link
Contributor

robloo commented Jan 29, 2024

@llfab Good to know, I think the only possible work-around with no visible downsides is a special clipping geometry.

@llfab
Copy link
Sponsor Author

llfab commented Jan 29, 2024

@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?

@timunie
Copy link
Contributor

timunie commented Jan 29, 2024

@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:

image

<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>

@llfab
Copy link
Sponsor Author

llfab commented Jan 29, 2024

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();
    }

@llfab
Copy link
Sponsor Author

llfab commented Jan 29, 2024

But placing this everywhere in the UI seems cumbersome. Hence, is you feeling that this will be solved within the this year?

@timunie
Copy link
Contributor

timunie commented Jan 29, 2024

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.

@llfab
Copy link
Sponsor Author

llfab commented Jan 29, 2024

Great. From my side I would love to see this solved even if I can live with a couple more months...

@kekekeks
Copy link
Member

kekekeks commented Feb 6, 2024

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:
image

@llfab
Copy link
Sponsor Author

llfab commented Feb 6, 2024

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?

@llfab
Copy link
Sponsor Author

llfab commented Feb 6, 2024

And: would that be fixed with SkiaSharp 3.x and the according Skia version?

@maxkatz6
Copy link
Member

maxkatz6 commented Feb 6, 2024

No, it wasn't fixed in SkiaSharp 3.0

@kekekeks
Copy link
Member

kekekeks commented Feb 7, 2024

It goes away if we use an intermediate texture and render it with clipping, but it would require some infrastructure changes.

@llfab
Copy link
Sponsor Author

llfab commented Feb 7, 2024

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...

@mattleibow
Copy link
Contributor

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...

@kekekeks
Copy link
Member

kekekeks commented Mar 3, 2024

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();
    }
}

image
image

@kekekeks
Copy link
Member

kekekeks commented Mar 3, 2024

https://issues.skia.org/issues/327877721

@grokys
Copy link
Member

grokys commented Mar 7, 2024

Should have been fixed by #14806.

@grokys grokys closed this as completed Mar 7, 2024
grokys added a commit that referenced this issue May 10, 2024
By default disable the `UseSaveLayerRootClip` option: this will re-enable subpixel rendering, fixing #15015 but causing #14270 to reappear.

Until the required API is added to SkiaSharp you have to choose one or the other :(
maxkatz6 pushed a commit that referenced this issue May 12, 2024
By default disable the `UseSaveLayerRootClip` option: this will re-enable subpixel rendering, fixing #15015 but causing #14270 to reappear.

Until the required API is added to SkiaSharp you have to choose one or the other :(
grokys added a commit that referenced this issue Jun 3, 2024
By default disable the `UseSaveLayerRootClip` option: this will re-enable subpixel rendering, fixing #15015 but causing #14270 to reappear.

Until the required API is added to SkiaSharp you have to choose one or the other :(
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants