microsoft-ui-xaml icon indicating copy to clipboard operation
microsoft-ui-xaml copied to clipboard

Should be able to load bitmaps in non-UI threads

Open jtorjo opened this issue 5 years ago • 8 comments

Proposal: Should be able to load bitmaps in non-UI threads

Summary

We should be able to load bitmaps in non-UI threads. I'm talking about BitmapImage and WriteableBitmap classes.

Rationale

Forcing loading of bitmaps in the UI thread is a huge bottleneck when you need to deal with hundreds of bitmaps.

I completely understand forcing drawing the bitmaps only in the UI thread, but just loading them should not be bound in the UI thread.

Important Notes

Simply doing var bmp = new BitmapImage(); on a non UI thread throws an exception. This should not be the case. This piece of code should work:

Task.Run(async () => {
	var root = ApplicationData.Current.LocalFolder.Path + "\\test.png";
	var bmp = new BitmapImage();
	var file = await StorageFile.GetFileFromPathAsync(root);
	var stream = await file.OpenAsync(FileAccessMode.Read);
	bmp.SetSource(stream);
});

Or, give me an equivalent, like, static BitmapImage BitmapImage.LoadFromStream(...).

For WriteableBitmap, again, doing var bmp = new WriteableBitmap(100, 100); on a non-UI thread will throw an exception. Should not be the case.

Executing the following in a non-UI thread should work.

public static BitmapSource soft_bmp_to_bmp(SoftwareBitmap soft_bmp) {
	release.assert(soft_bmp != null);
	WriteableBitmap bmp = new WriteableBitmap(soft_bmp.PixelWidth, soft_bmp.PixelHeight);
	soft_bmp.CopyToBuffer( bmp.PixelBuffer);
	return bmp;
}

jtorjo avatar Apr 15 '20 07:04 jtorjo

As mention in the Optimize Image Resources section here https://docs.microsoft.com/en-gb/windows/uwp/debug-test-perf/optimize-animations-and-media#optimize-image-resources , decoding is actually already done off the UI thread even if you create the BitmapImage itself on the UI thread, as long as you use SetSourceAsync(...)

To preserve XAML optimisations you should also make sure the BitmapImage is in the UI tree before calling SetSourceAsync, to make sure the image is decoded at a preferred resolution for it's target display area.

JohnnyWestlake avatar Apr 15 '20 13:04 JohnnyWestlake

@JohnnyWestlake That is fine. But at this time, you can't even create a BitmapImage on a non-UI thread.

jtorjo avatar Apr 15 '20 13:04 jtorjo

@JohnnyWestlake That is fine. But at this time, you can't even create a BitmapImage on a non-UI thread.

Yes, but it is a UI construct for use on UI only, and by design of how XAML works, UI thread elements need to be manipulated on the UI thread to stop the universe imploding / requiring thousands of locks and waits.

Creating a blank BitmapImage on the UI thread doesn't cost much, seeing as it doesn't do much on creation. It's just a container. Calling SetSourceAsync doesn't cost much either, as the decoding will typically not be handled on the UI thread... creating on the UI and calling SetSource does however, but it's also explicitly advised not to do it.

So as to your original issue of I completely understand forcing drawing the bitmaps only in the UI thread, but just loading them should not be bound in the UI thread., well, bitmap loading already is not forced to the UI thread at the moment, if you follow the advised pattern.

There are options if you want to work entirely off the UI thread (using the composition layer), but you'll lose a ton of automatic optimisation.

JohnnyWestlake avatar Apr 15 '20 13:04 JohnnyWestlake

@JohnnyWestlake Sorry for the late reply. I've done some tests creating lots of BitmapImages on the UI thread. It is indeed very fast.

In this case, SetSourceAsync should be callable from any thread - since you're doing the processing on another thread, why force calling this from the UI thread? (I did test, and an exception gets thrown if SetSourceAsync is called from a non-UI thread)

Also, my initial proposal for WritableImages still stands - we should be able to create/modify them on a different thread.

jtorjo avatar Apr 17 '20 09:04 jtorjo

We can consider this, but this tends to be a bit challenging. Allowing SetSourceAsync to be called on a background thread limits the complexity of the change, but is only really useful if the UI thread has already created all of the BitmapImages it needs. And if those BitmapImages haven't already been added into the UI on the UI thread, then it is still necessary to hop back to the UI thread to add them, which limits the value to likely just being starting the download slightly sooner (where "slightly" assumes the UI thread isn't blocking for a longer time).

Being able to also create BitmapImages on a background thread would be a little more useful, but is also significantly more complex to support, due to its ties to various UI thread objects. And, of course, it would still be necessary to hop to the UI thread in order to add the BitmapImages to the UI.

Another general concern we've had here is that most of XAML requires the UI thread, with the DependencyObject.Dispatcher property being the big exception, since that helps to be able to hop back to the UI thread. Adding more non-UI-thread APIs means we now need to see how to make it clear to developers what APIs do or don't require the UI thread.

codendone avatar Apr 24 '20 17:04 codendone

@codenone Sorry for the late reply. It would be really useful to allow SetSourceAsync from a non-UI thread. This way, I don't have to call into the UI thread, while caching bitmaps.

Adding more non-UI-thread APIs means we now need to see how to make it clear to developers what APIs do or don't require the UI thread.

Agreed 100%. Personally, I think loading bitmaps in non-UI thread pretty much means I'll add them to the UI programmatically, not via XAML.

Also , please don't forget about WriteableBitmap :)

jtorjo avatar May 05 '20 12:05 jtorjo

Microsoft, you know, you just suck with your own "native" WinUI. It's just a joke! I've spend a half of a day just trying to load a primitive png manually to the ImageSource from file, and even GPT couldn't help me with that. It's just nonsense that you are trying to implement something cross-platform, when can't even manage to create something convenient for your own native devs! In 2024th it's... "System.Runtime.InteropServices.COMException: '' with empty Message! 2024th! Seriously?!! COMException?!!!

            BitmapDecoder decoder = await BitmapDecoder.CreateAsync(stream);

With .Net 8 WinUI is still full of mess! Such a shame!

Seriously?!!!

agat366 avatar Jan 09 '24 21:01 agat366

Microsoft, you know, you just suck with your own "native" WinUI. It's just a joke! I've spend a half of a day just trying to load a primitive png manually to the ImageSource from file, and even GPT couldn't help me with that. It's just nonsense that you are trying to implement something cross-platform, when can't even manage to create something convenient for your own native devs! In 2024th it's... "System.Runtime.InteropServices.COMException: '' with empty Message! 2024th! Seriously?!! COMException?!!!

            BitmapDecoder decoder = await BitmapDecoder.CreateAsync(stream);

With .Net 8 WinUI is still full of mess! Such a shame!

Seriously?!!!

Same here.

vzhilong avatar Oct 16 '24 12:10 vzhilong