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

Garbage collector performance issue #5

Open
AWAS666 opened this issue Sep 6, 2024 · 7 comments
Open

Garbage collector performance issue #5

AWAS666 opened this issue Sep 6, 2024 · 7 comments
Labels
enhancement New feature or request question Further information is requested

Comments

@AWAS666
Copy link

AWAS666 commented Sep 6, 2024

Hey, using this in avalonia 11 and works pretty good out of the box, only minor issue I have that I do swap my bound image at like 30-60 fps and this makes it call the gc to dispose the old image alot.

Is there any way to circumvent this?

@kekyo kekyo added the question Further information is requested label Sep 10, 2024
@kekyo
Copy link
Owner

kekyo commented Sep 10, 2024

There is unfortunately no direct way to do this in the current version.
By the way, what format of object are you using to bind?

For example, if you are binding an SKBitmap, and instead of setting another instance of the SKBitmap, do you expect to directly rewrite the bitmap bits inside and reflect that, or is that the method you are using or planning?

@AWAS666
Copy link
Author

AWAS666 commented Sep 10, 2024

Well I'm currently generating SKimages in a background thread which calls an eventhandler which then replaces the bound source in the view model.

Not sure if there is a cleaner solution for it tho, I had been struggling with a variety of issues, currently it does sorta depend on the view model to dispose old frames...

Been tinkering with my own control but that still has a ton of other issues as I render directly to the canvas which makes stuff like double click events not work (and it flickers sometimes...)

@kekyo
Copy link
Owner

kekyo commented Sep 10, 2024

For example, what if there was a method like SKImageView.ForceUpdate() and you called it, and it re-evaluated the image instance regardless of the binding trigger?

(I just thought of this, so I haven't thought deeply about whether this method is appropriate or not.)

@AWAS666
Copy link
Author

AWAS666 commented Sep 10, 2024

I mean it doesn't sound like the cleanest solution there is, but it should work for my use case.

@kekyo
Copy link
Owner

kekyo commented Sep 10, 2024

Don't expect anything. If I get some time, I'll try to implement it :)

@kekyo kekyo added the enhancement New feature or request label Sep 10, 2024
@AWAS666
Copy link
Author

AWAS666 commented Oct 28, 2024

This is my own implementation now which is based on yours.
I only need avalonia, so I stripped it to a minimum.

Basically the issue is that a new "WriteableBitmap" gets created with each new Source, that causes the garbage collector to go mad.
This now keeps a single one.

Maybe you can also implement it on your end, but that might be quite some work...

Treat this as a minimal solution/example.

Ofc you still gotta manage disposal of old instances in the code that creates the bitmaps.

My code:
`
public class SKImageViewer : UserControl
{

 public static readonly StyledProperty<SKBitmap> SourceProperty =
     AvaloniaProperty.Register<SKImageViewer, SKBitmap>(nameof(Source));

 static SKImageViewer()
 {
     AffectsRender<SKImageViewer>(SourceProperty);
 }

 public SKImageViewer()
 {
     ClipToBounds = true;

     SourceProperty.Changed.Subscribe(new AnonymousObserver<AvaloniaPropertyChangedEventArgs<SKBitmap>>(
        e =>
        {
            base.InvalidateMeasure();
            base.InvalidateVisual();
        })
     );
 }

 private Size RenderSize =>
   this.Bounds.Size;
 private WriteableBitmap writableBitmap;

 protected override Size MeasureOverride(Size constraint) =>
    this.InternalMeasureArrangeOverride(constraint);

 protected override Size ArrangeOverride(Size arrangeSize) =>
     this.InternalMeasureArrangeOverride(arrangeSize);

 private Size InternalMeasureArrangeOverride(Size targetSize)
 {
     if (Source != null)
     {
         var self = new Size(Source.Width, Source.Height);
         var scaleFactor = ComputeScaleFactor(
             targetSize,
             self)
           ;
         return new(
            self.Width * scaleFactor.Width,
            self.Height * scaleFactor.Height);
     }
     else
     {
         return default;
     }
 }


 public SKBitmap Source
 {
     get => GetValue(SourceProperty);
     set => SetValue(SourceProperty, value);
 }

 public override void Render(DrawingContext drawingContext)
 {
     base.Render(drawingContext);
     if (Source == null) return;

     int width = Source.Width;
     int height = Source.Height;

     var info = new SKImageInfo(
         width, height, SKImageInfo.PlatformColorType, SKAlphaType.Premul);

     writableBitmap = writableBitmap ?? new WriteableBitmap(
          new(info.Width, info.Height), new(96.0, 96.0), PixelFormat.Bgra8888, AlphaFormat.Premul);
     using var locker = writableBitmap.Lock();
     using var surface = SKSurface.Create(info, locker.Address, locker.RowBytes);
     surface.Canvas.Clear();
     surface.Canvas.DrawBitmap(Source, default(SKPoint));
     drawingContext.DrawImage(writableBitmap, new(new(), this.RenderSize));
 }

 private Size ComputeScaleFactor(Size availableSize, Size contentSize)
 {
     // Compute scaling factors to use for axes
     double scaleX = 1.0;
     double scaleY = 1.0;

     // Compute scaling factors for both axes
     scaleX = availableSize.Width / contentSize.Width;
     scaleY = availableSize.Height / contentSize.Height;

     //Find maximum scale that we use for both axes
     double minscale = scaleX < scaleY ? scaleX : scaleY;
     scaleX = scaleY = minscale;

     //Return this as a size now
     return new Size(scaleX, scaleY);
 }

}
`

edit: added clear

@kekyo
Copy link
Owner

kekyo commented Oct 29, 2024

Thanks, I will refer your code!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants