Async resource loading in Win2D


Previously, we looked at handling lost devices, why we put CanvasControl in charge of drawing and resource creation policy, the problems with async loading and some ways we tried to solve them. This time we’ll talk about the actual solution.

CanvasControl is in charge of deciding when to raise CreateResources and Draw. In order to support async CreateResources, the handler can register an IAsyncAction to be tracked by the control:

// C#
void Canvas_CreateResources(
    CanvasControl sender,
    CanvasCreateResourcesEventArgs args)
{
    args.TrackAsyncAction(CreateResourcesAsync(sender).AsAsyncAction());
}

async Task CreateResourcesAsync(CanvasControl sender)
{
    bitmap1 = await CanvasBitmap.LoadAsync(sender, "hello.png");
    bitmap2 = await CanvasBitmap.LoadAsync(sender, "goodbye.png");
}

void Canvas_Draw(CanvasControl sender, CanvasDrawEventArgs args)
{
    args.DrawingSession.DrawImage(
        bitmap1, 
        new Rect(0, 0, sender.ActualWidth, sender.ActualHeight));

    args.DrawingSession.DrawImage(bitmap2);
}

Doing this will:

  • ensure that the Draw event is not invoked until after the IAsyncAction has completed
  • clear the contents of the control to the ClearColor while resources are being created
  • redraw the control once resources have been loaded
  • catch exceptions thrown during CreateResourcesAsync
  • catch exceptions thrown during the Draw event
  • handle device lost exceptions by Canceling any previous IAsyncAction and waiting until they’ve completed before raising CreateResources again

We’re really confident now that this addresses all the issues…much more so than we were the first couple of times we were confident that we’d solved this problem. Smile


Comments (12)

  1. OmariO says:

    Hi.

    What if  I don't want to wait until all the images loaded and prefer to draw each image as soon as it is ready?

  2. DamyanPepper says:

    @OmariO: There's nothing built-in to Win2D to support this directly, however, the building blocks are there.  You do raise a good point though and I've filed backlog item for us to address this scenario in depth.  Expect either a blog post or new feature with a more complete answer.

    I see the common uses here to be using CreateResources to load just-enough that you can render a background and loading bar while the rest of the resources load, or loading per-level specific assets.

  3. The Grand User says:

    @OmariO: You might look into Reactive Extensions for that. If the Win2D team wanted to integrate Rx into the project that'd be great, though I'd presume unlikely.

  4. juanpablogc says:

    As you see I am learning Win2D to apply to controls point of view. A control when it's added to the VisualTree has a Loaded event that is called when it's inserted in that

    I tell this because there is a property 'ReadyToDraw' that was making me crashing the app yesterday until I realized:

    (Called in the constructor)

    private async void Initialize()

           {

               canvasControl = new CanvasControl() { ClearColor = Colors.Transparent };

               this.Children.Insert(0, canvasControl);

               while (!canvasControl.ReadyToDraw)

               {

                   await Task.Delay(60);

               }

    Would be great if instead ReadyToDraw, the Control in the Loaded Event is ready to draw and I can forget that property, or that property is totally necessary?

    After the await I make a rendertarget,  an effect and I add the draw event handler and I invalidate, really great.

    And just a small tech question, it is not about the post, but might be Win2D has the answer. Until now the screen was 'fix' I mean WP8 has a screen resolution and the app of W8 usually has full size so everywhere tells that the screen resolution is var bounds = Window.Current.Bounds.

    But no with W10 is resizable so  I want to know the device screen resolution not the Bounds of the window, Win2D has some method for that? Thank you (It's to scale my images according to the resolution, easy with standard code)

  5. ReadyToDraw state is different from Loaded if your CreateResources handler uses TrackAsyncAction.  In that case the control will not be ready to draw until the async resource creation has completed, which could take arbitrarily long if it does lots of work (for instance loading large numbers of bitmaps).

    I'm a little confused why you need to block on this state in your Initialize method, though.  Your Draw handler already will not be called until the async resource creation has completed.  Perhaps you are doing some work in Initialize that would be better moved into Draw, or done as part of the async operation that TrackAsyncAction is monitoring?

  6. > I want to know the device screen resolution

    What do you want this information for?  I'm having a hard time thinking what you could do with it that would be more useful than the size + dpi of your application window.

  7. juanpablogc says:

    Ok understood the purpose of ReadyToDraw, thank you.

    Suppose window 10 app opens (usually I do not know why), windowed. not maximized. So in that case I do not know the screen resolution just windows bounds.

    I create the background, to improve performance (I've tested it), that adapts its source with a converter:

    BitmapImage bi = new BitmapImage();

    bi.DecodePixelWidth = Int32.Parse((string)parameter); //Here would be great screen resolution

    bi.UriSource = new Uri((string)value);

    If I reduce the DecodePixelWidth for a low specs tablet it really improves the UI response that simply adding the Source to an image control a full hd image and let it go, and I am not going to add resources for plenty of resolutions.

    And yes I could use sizechanged, but I think that the worst case is maximized and having that one is enough.

  8. DamyanPepper says:

    Note that BitmapImage is a XAML type, not a Win2D type.  Are you using it with Win2D in some way?

    I think that what I'm hearing here is that you want to use DecodePixelWidth in order to load a smaller version of the image when the screen is small.  I think that the right way to determine this is to look at the DPI rather than screen size.

    The resource might help: msdn.microsoft.com/…/hh465362.aspx

    These forums may provide more helpful answers to general XAML development questions: social.msdn.microsoft.com/…/home

  9. Note that it is not in general possible to use screen size to determine the maximum resolution a window could ever be resized to.  There are many reasons this could change later:

    – A PC monitor could be switched to a different resolution

    – On a multimon system, the app could be moved to a different display

    – A tablet could be docked to a station that provides a higher resolution screen than when it is used standalone

    – A phone or tablet could be connected to an external display using Miracast

    I think the only robust approach is to load the right size resources to fit your current window size, then if the window later changes size, consider whether to reload different resources at this new resolution.  There is no reliable way to predict the biggest possible size that could be encountered in the future.

  10. Boris says:

    Getting exception "Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))".

  11. Simon Tao says:

    Hi Boris, can you file an issue on GitHub: github.com/…/issues Please include your system configuration (OS, VS) and what code you're running that gets this exception. A repro project is ideal. Thanks!

Skip to main content