microsoft / WindowsCompositionSamples

The Windows Composition Samples have moved here: https://github.com/microsoft/WindowsAppSDK-Samples/tree/main/Samples/SceneGraph
https://github.com/microsoft/WindowsAppSDK-Samples/tree/main/Samples/SceneGraph
MIT License
1.12k stars 287 forks source link

Vector Scaling and Direct2D Interop #332

Closed legistek closed 5 years ago

legistek commented 5 years ago

There's something I don't quite understand about the way scaling works with vector graphics and I'm not able to get it quite working the way I want.

Consider two different ways of drawing an ellipse:

1) Use CreateEllipseGeometry, CreateSpriteShape and CreateShapeVisual. The visual scales perfectly including during animations. No pixellation.

2) Use Direct2D interop. Get a surface from CompositionGraphicsDevice.CreateDrawingSurface, get the D2D device context through ICompositionDrawingSurfaceInterop.BeginDraw, draw, get a brush with CreateSurfaceBrush, and set it to the Brush property of a SpriteVisual. Not surprisingly, in this case, scaling the Visual results in pixellation because the Visual just has a bitmapped surface to work with.

My question is how is Windows Composition able to keep the vector graphics crisp during animation while I can't using D2D? I assume at some level you're using D2D to render to a surface underlying the Visual. Is it re-rendering at every animation frame? And if so can I hook into this frame "tick" to re-render my own D2D surface during animation - or whenever a Visual property such as Scale changes? Note I'm comfortable doing this in C++ or C# so using interfaces that are hidden from C# doesn't bother me.

BTW this is related to https://github.com/microsoft/WindowsCompositionSamples/issues/330 because I can find no way of hit testing other than using D2D rendered to surfaces. But doing so seems to prevent smooth crisp scaling of vector graphics and text.

If you're still taking suggestions, I would just have Visual implement ID2DRenderTarget. Then have it remember its rendering instructions and actually render to a real surface under the hood when necessary. (I'd be surprised if you're not already doing something like this, in which case it would be great if more of it were exposed).

Thanks!

legistek commented 5 years ago

Out of curiosity I obtained all the interface Guids implemented by SpriteVisual through its IInspectable and Googled them. I found a really interesting one:

fa92376b-164b-432a-bf53-c7fe2d51cabc which is referenced in https://github.com/Microsoft/microsoft-ui-xaml/blob/master/test/MUXControlsTestApp/Utilities/CompositionPropertyLogger.cs

The interface is ICompositionNotifyPropertyChangedPartner and it looks like a callback mechanism - exactly what I was hoping for - that could conceivably trigger a re-render when the Visual's transformation matrix changes during an animation. Code is below. These interfaces aren't in the SDK headers or documented anywhere. Are they safe to use?

        [ComImport]
        [ComVisible(true)]
        [System.Runtime.InteropServices.Guid("FA92376B-164B-432A-BF53-C7FE2D51CABC")]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        public interface ICompositionNotifyPropertyChangedPartner
        {
            void SetPropertyChangedListener(
                NotificationProperties propertyId,
                ICompositionPropertyChangedListenerPartner callback);
        }

and ICompositionPropertyChangedListenerPartner:

        [ComImport]
        [System.Runtime.InteropServices.Guid("5C495A03-13DE-4F33-B81F-36E0803B3919")]
        [ComVisible(true)]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        public interface ICompositionPropertyChangedListenerPartner
        {
            void NotifyColorPropertyChanged(
              [In, MarshalAs(UnmanagedType.IInspectable)] object target,
              [In] uint propertyId,
              [In] Windows.UI.Color value);

            void NotifyMatrix3x2PropertyChanged(
              [In, MarshalAs(UnmanagedType.IInspectable)] object target,
              [In] uint propertyId,
              [In] Matrix3x2 value);

            void NotifyMatrix4x4PropertyChanged(
              [In, MarshalAs(UnmanagedType.IInspectable)] object target,
              [In] uint propertyId,
              [In] Matrix4x4 value);

            // Bug 3647518:Property change callbacks need to support Quaternion properties
            // D2DQuaternion requires including an additional header file (shared\d3dxmath.h),
            // which doesn't play nicely with all of the places that include dcompp.h
            // [PreserveSig]
            // int NotifyQuaternionPropertyChanged(
            //     CompositionObject target,
            //     DCOMPOSITION_EXPRESSION_NOTIFICATION_PROPERTY propertyId,
            //     [In] ref Quaternion value);

            void NotifyReferencePropertyChanged(
          [In, MarshalAs(UnmanagedType.IInspectable)] object target,
          [In] uint propertyId);

            void NotifySinglePropertyChanged(
              [In, MarshalAs(UnmanagedType.IInspectable)] object target,
              [In] uint propertyId,
              [In] float value);

            void NotifyVector2PropertyChanged(
              [In, MarshalAs(UnmanagedType.IInspectable)] object target,
              [In] uint propertyId,
              [In] Vector2 value);

            void NotifyVector3PropertyChanged(
              [In, MarshalAs(UnmanagedType.IInspectable)] object target,
              [In] uint propertyId,
              [In] Vector3 value);

            void NotifyVector4PropertyChanged(
              [In, MarshalAs(UnmanagedType.IInspectable)] object target,
              [In] uint propertyId,
              [In] Vector4 value);
        };
legistek commented 5 years ago

Yeah this totally works. I'll close the issue. Thanks in advance.

PS - If anyone has concerns about using these undocumented interfaces please chime in.