Resource creation in Win2D


Previously, we talked about handling lost devices in Win2D. We decided against putting knowledge about lost device handling in the resources themselves – so when the device is lost the Win2D resources are lost with it…

It would seem then that we’re back to square one – something somewhere still needs to handle lost devices, and at this point it seems that it’s still up to the app developer.  Enter CanvasControl (and CanvasAnimatedControl). These controls already play an important role in making Win2D interact nicely with XAML.

Whiteboard where CanvasControl was first discussedCanvasControl manages an Image control with a CanvasImageSource set as its source.  It tracks resize events to resize the CanvasImageSource as appropriate.  When the image is resized, the control needs to ask the app to redraw the contents of the CanvasImageSource by raising the Draw event.  (CanvasAnimatedControl works similarly with CanvasSwapChain and SwapChainPanel). 

This allowed us to write the following code:

<!—XAML –> 

<canvas:CanvasControl x:Name=”canvasDraw=”Canvas_Draw/>
// C#
CanvasRenderTarget renderTarget;

public MainPage()
{
    this.InitializeComponent();
    
    renderTarget = new CanvasRenderTarget(canvas, 256, 256);
    using (var ds = renderTarget.CreateDrawingSession())
    {
        ds.Clear(Colors.Black);
        ds.DrawText(“Hello”, 0, 0, Colors.White);
    }
}

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

This draws “Hello” to an off-screen render target and then, in the Draw handler, renders it to fill the entire control. If the control is resized then the control will raise the Draw event to request that the app redraw the contents of the control.  Notice also that CanvasControl also handily creates the device for you. 

Since CanvasControl is in control of the execution of Canvas_Draw it is able to catch any exceptions that might be thrown.  So if anything involved in the drawing process throws a device lost exception it is able to catch it and recreate the device.  Conceptually the code in CanvasControl looks like this:

try
{
    if (ThereIsNoDevice())
    {
        CreateNewDevice();
    }
    using (var ds = this.imageSource.CreateDrawingSession())
    {
        var args = new CanvasDrawEventArgs(ds);
        drawEvent(this, args);
    }
}
catch (DeviceLostException)
{
    HandleLostDevice();
}

This version unfortunately totally fails to cope with losing the device – there’s no way for the app to know that it needs to recreate the render target after the device has been lost. Also, what would happen if the device had been lost between the call to “this.InitializeComponent()” and constructing the CanvasRenderTarget?  CanvasControl needs to be in charge of executing the resource creation code as well as the drawing code.  This is where the CreateResources event comes in:

<!—XAML –> 
<canvas:CanvasControl x:Name=”canvasDraw=”Canvas_DrawCreateResources="Canvas_CreateResources"/>
// C#
CanvasRenderTarget renderTarget;

public MainPage()
{
    this.InitializeComponent();
}

void Canvas_CreateResources(CanvasControl sender, object args)
{
    renderTarget = new CanvasRenderTarget(sender, 256, 256);
    using (var ds = renderTarget.CreateDrawingSession())
    {
        ds.Clear(Colors.Black);
        ds.DrawText(“Hello”, 0, 0, Colors.White);
    }
}

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

Now, CanvasControl is able to raise CreateResources whenever it creates a new device.  Even better, it is able wrap the event code in a try/catch so that it can handle the case where the device lost is raised in the CreateResources handler.

Conceptually the CanvasControl code looks like this:

try
{
    if (ThereIsNoDevice())
    {
        CreateNewDevice();
        createResourceEvent(this, null);
    }
    using (var ds = this.imageSource.CreateDrawingSession())
    {
        var args = new CanvasDrawEventArgs(ds);
        drawEvent(this, args);
    }
}
catch (DeviceLostException)
{
    HandleDeviceLost();
}

And we're done!

That’s all there is to it.  Of course, CanvasControl is implemented in C++ / WRL, so the code is a little more verbosethan the pseudo code.  But it’s all there and we moved the virtual “Make CanvasControl handle lost device” sticky-note from the “in-progress” to the “done” column and started working on the next task.

…and indeed we would have been done, had it not been for async loading, which we’ll discuss in the next part.


Comments (1)

  1. Roland says:

    I am little puzzled… it seems I always need some control (with it's device) before I can load some resource.

    Is there any chance to load resources upfront (e.g. from my package's assets during app initialization) and to share them even across controls?

Skip to main content