Win2D and Composition.md

The Windows 10 April 2018 Update includes a new set of APIs in the Composition namespace that support various geometries such as lines, ellipses, rectangles and paths. However, during the insider cycle, it wasn’t possible to create arbitrary paths as there was a reliance on an implementation of IGeometrySource2D that could not be found. Fast forward until today when a new version of the Win2D package has been released – V1.22.0 and now we can make use of CanvasGeometry class to create our paths.

Here is quick walk-through on getting started with the Geometry APIs.

  1. Create a blank UWP app.

  2. Add the Win2D.UWP nuget package.

  3. Update the MainPage.xaml so that the Grid has the name “RootGrid”:

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" x:Name="RootGrid"> </Grid>
  4. Switch to the MainPage.xaml.cs code behind and update the constructor to:

    public MainPage() { InitializeComponent(); Loaded += OnLoaded; }
  5. Add the OnLoaded method:

    private void OnLoaded(object sender, RoutedEventArgs routedEventArgs) { var c = Window.Current.Compositor; // Need this so we can add multiple shapes to a sprite var shapeContainer = c.CreateContainerShape(); // Rounded Rectangle - just the rounded rect properties var roundedRectangle = c.CreateRoundedRectangleGeometry(); roundedRectangle.CornerRadius = new Vector2(20); roundedRectangle.Size = new Vector2(400, 300); // Need to create a sprite shape from the rounded rect var roundedRectSpriteShape = c.CreateSpriteShape(roundedRectangle); roundedRectSpriteShape.FillBrush = c.CreateColorBrush(Colors.Red); roundedRectSpriteShape.StrokeBrush = c.CreateColorBrush(Colors.Green); roundedRectSpriteShape.StrokeThickness = 5; roundedRectSpriteShape.Offset = new Vector2(20); // Now we must add that share to the container shapeContainer.Shapes.Add(roundedRectSpriteShape); // Let's create another shape var roundedRectSpriteShape2 = c.CreateSpriteShape(roundedRectangle); roundedRectSpriteShape2.FillBrush = c.CreateColorBrush(Colors.Purple); roundedRectSpriteShape2.StrokeBrush = c.CreateColorBrush(Colors.Yellow); roundedRectSpriteShape2.StrokeThickness = 3; roundedRectSpriteShape2.Offset = new Vector2(90); roundedRectSpriteShape2.CenterPoint = new Vector2(200, 150); roundedRectSpriteShape2.RotationAngleInDegrees = 45; // Add it to the container - as it is added after the previous shape, it will appear on top shapeContainer.Shapes.Add(roundedRectSpriteShape2); // Create paths and animate them SetupPathAndAnimation(c, shapeContainer); // Now we need to create a ShapeVisual and add the ShapeContainer to it. var shapeVisual = c.CreateShapeVisual(); shapeVisual.Shapes.Add(shapeContainer); shapeVisual.Size = new Vector2(1000, 1000); // Display the shapeVisual ElementCompositionPreview.SetElementChildVisual(RootGrid, shapeVisual); }
  6. Add the empty (for now) SetupPathAndAnimation:

    private static void SetupPathAndAnimation(Compositor c, CompositionContainerShape shapeContainer) { // Empty for now! }
  7. Compile and run this - you will see the following. I believe this code is pretty self-explanatory.

  8. image

  9. Now we will add a path and animate it. Add the following classes and enum to the project (these are just some helpers I created):

    using System.Collections.Generic; using System.Linq; using System.Numerics; using Microsoft.Graphics.Canvas.Geometry; namespace YourNamespace { public static class PathBuilderExtensions { public static CanvasPathBuilder BuildPathWithLines( this CanvasPathBuilder builder, IEnumerable<Vector2> vectors, CanvasFigureLoop canvasFigureLoop) { var first = true; foreach (var vector2 in vectors) { if (first) { builder.BeginFigure(vector2); first = false; } else { builder.AddLine(vector2); } } builder.EndFigure(canvasFigureLoop); return builder; } public static CanvasPathBuilder BuildPathWithLines( this CanvasPathBuilder builder, IEnumerable<(float x, float y)> nodes, CanvasFigureLoop canvasFigureLoop) { var vectors = nodes.Select(n => new Vector2(n.x, n.y)); return BuildPathWithLines(builder, vectors, canvasFigureLoop); } } public class PathNode { private Vector2 _vector2; public PathNode(Vector2 vector2) { _vector2 = vector2; } } public enum NodeType { Line, Arc, CubicBezier, Geometry, QuadraticBezier } }
  10. Replace the SetupPathAndAnimation() method with:

    private static void SetupPathAndAnimation(Compositor c, CompositionContainerShape shapeContainer) { var startPathBuilder = new CanvasPathBuilder(new CanvasDevice()); // Use my helper to create a W shaped path startPathBuilder.BuildPathWithLines(new(float x, float y)[] { (10, 10), (30, 80), (50, 30), (70, 80), (90, 10) }, CanvasFigureLoop.Open); // Add another path startPathBuilder.BuildPathWithLines(new(float x, float y)[] { (105, 30), (105, 80) }, CanvasFigureLoop.Open); // Create geometry and path that represents the start position of an animation var startGeometry = CanvasGeometry.CreatePath(startPathBuilder); var startPath = new CompositionPath(startGeometry); // Now create the end state paths var endPathBuilder = new CanvasPathBuilder(new CanvasDevice()); endPathBuilder.BuildPathWithLines(new(float x, float y)[] { (10, 10), (30, 10), (50, 10), (70, 10), (90, 10) }, CanvasFigureLoop.Open); endPathBuilder.BuildPathWithLines(new(float x, float y)[] { (105, 30), (105, 80) }, CanvasFigureLoop.Open); var endGeometry = CanvasGeometry.CreatePath(endPathBuilder); var endPath = new CompositionPath(endGeometry); // Create a CompositionPathGeometery from the Win2D GeometeryPath var pathGeometry = c.CreatePathGeometry(startPath); // Create a CompositionSpriteShape from the path var pathShape = c.CreateSpriteShape(pathGeometry); pathShape.StrokeBrush = c.CreateColorBrush(Colors.Purple); pathShape.StrokeThickness = 5; pathShape.Offset = new Vector2(20); // Add the pathShape to the ShapeContainer that we used elsewhere // This will ensure it is rendered shapeContainer.Shapes.Add(pathShape); // Create an animation using the start and endpaths var animation = c.CreatePathKeyFrameAnimation(); animation.Target = "Geometry.Path"; animation.Duration = TimeSpan.FromSeconds(1); animation.InsertKeyFrame(0, startPath); animation.InsertKeyFrame(1, endPath); animation.IterationBehavior = AnimationIterationBehavior.Forever; animation.Direction = AnimationDirection.AlternateReverse; pathGeometry.StartAnimation(nameof(pathGeometry.Path), animation); }
  11. Compile and run the code - you will notice that two paths are now being rendered and the W is animating.

  12. image

This is obviously a very simple example of using the new Composition Geometery APIs, but should be enough to get you started.

I have been fortunate enough to have had early access to a device running Windows 10 on ARM for the purpose of exploring the developer experience. In case you have heard of Windows 10 on ARM, it is a build of Windows 10 Desktop that runs on the ARM64 processor. This enables Windows 10s and Pro to run on a new family of devices that have all-day battery life and can be Always Connected via mobile data networks. A great feature of Windows 10 on ARM is the fact that it can run Store Apps and existing x86 win32 applications unmodified via emulation. This means your existing productivity apps (such as Office Apps, notepad++, etc.) will run well – you could even run Visual Studio 2017, although I wouldn’t expect great performance there.

Disclaimer: The device I have is pre-production so I cannot talk about device build quality or overall performance.

You can learn more about Windows 10 on ARM and the Always Connected PC via the video from Build 2017 below – after the video I will walk through the steps I took to be able to remote debug an x86 UWP on the Windows 10 on ARM device.

Remote Debugging

The great thing about remote debugging a Windows 10 on ARM device is that it is exactly the same as remote debugging any other Windows 10 device.

Upgrade to Windows 10 Pro

My Windows 10 for ARM device came with Windows 10 S installed – given the price point, it makes sense that the device would be targeting Students and the like. However, if we want to use it as part of the development cycle, the device needs to be upgraded to Windows 10 Pro. Microsoft has documented the upgrade process here. As an MVP with a Visual Studio Subscription, I have product keys so I could update simply enough.

Enable Developer Mode

Once you are running Windows 10 Pro, it’s time to enable developer mode.

Note: Ensure you are connected to the internet for this process so that the dev packages are downloaded and installed.

Open Settings, search for Developer and choose “Use developer features”:

SettingsDeveloper

By default, the system may be set to “Microsoft Store Apps” or “Sideload apps” – change this to developer mode. You will see a dialog displayed to confirm the setting:

image

Hit “Yes” and you should see a message “Searching for Developer Mode Package”, followed by “Installing Developer Mode Package”. This may take a few minutes.

Once completed, you should see a message that states “Developer Mode package installed. Remote tooling for desktop is now enabled.”

Important: Ensure you switch “Device discovery” on – this will allow the developer machine to be able to “see” the device on the network and remote deploy/debug.

Remote Debugging a UWP App

In order to remote debug a UWP app, you need to ensure that both the Windows 10 on ARM device and the developer PC are on the same network subnet. As the Windows 10 on ARM device is probably connected to WiFi, you will likely need to WiFi connect your developer PC too.

In the following steps I will walk through the creation of blank UWP app and the steps to remote debug it – note that this is the same process for remote debugging on an x86/x64 Windows Desktop as well.

  1. On the developer machine, Open Visual Studio
  2. Choose “Create New Project”, “Visual C#”, “Windows Universal” and then select the “Blank App” template. I named the app “RemoteTest”.
  3. When the target/minimum platform dialog appears, chose “Windows 10 Fall Creators Update (10.0; Build 16299)” as the target Version, and “Windows 10 November Update (10.0; Build 10586)” as the Minimum version.
  4. Once the solution has been created and loaded, ensure the build platform is x86 or ARM (at the time of writing only x86 is working reliably for me but ARM should work fine – x64 is not supported) and you need to change the execution target to “Remote Machine”:
    1. clip_image002
  5. The “Remote Connections” dialog will be displayed. You may also see a firewall prompt the first time you display this dialog – “Allow Access”:
    1. clip_image002[11]
  6. If the network is setup correctly, Visual Studio will then automatically detect your remote systems (if not, check that “Device discovery” is enabled in the ""Developers features” settings on the remote machine). In the image below, the Windows 10 on ARM device is auto detected:
    1. clip_image002[5]
  7. Select the desired remote:
    1. clip_image002[7]
  8. Once Visual Studio has updated the configuration of the project, you can run with debugging by hitting F5 or by selecting “Remote Machine":
    1. clip_image002[9]
  9. This should launch a build – you can watch the build progress in the output window. You will see the following tasks:
    1. The build task start and complete
    2. The deploy task start (note: that you may see a firewall prompt on the remote machine during this process –”Allow Access”).
      1. clip_image001
    3. The first time you deploy may take a few minutes to complete. Also, Visual Studio may display a message informing you that remote operations are taking longer than expected – be patient. The Deploy task has the following stages:
      1. Updating the layout
      2. Copying files
      3. Checking whether frameworks are installed (any missing frameworks on the remote machine will be installed as necessary)
      4. Registering the application to run from layout
      5. Deployment complete
    4. The app should then launch on the remote machine.
  10. At this point you can set breakpoints and debug as you would expect.

I hope this post shows that there is no arcane magic needed to develop and test UWP applications on Windows 10 for ARM. You can also see that x86 applications run perfectly well.