Udostępnij za pośrednictwem


Tworzenie natywnej współpracy z directX i Direct2D

Ważna

Ten artykuł jest adaptowany z dokumentacji platformy UWP dla pakietu Windows App SDK/WinUI. Niektóre przykłady kodu w tym artykule nadal odwołują się do interfejsów API specyficznych dla platformy UWP, takich jak CoreWindow, CoreApplicationViewi CoreDispatcher, które nie są dostępne w zestawie SDK aplikacji WinUI/Windows. Odpowiedniki winUI to:

Interfejsy stosowane do interoperacyjności kompozycji (ICompositorInterop, ICompositionDrawingSurfaceInterop itp.) funkcjonują w identyczny sposób jak w przypadku obiektów Microsoft.UI.Composition.

Interfejs API Microsoft.UI.Composition udostępnia interfejs ICompositorInterop, interfejs ICompositionDrawingSurfaceInterop i interfejs ICompositionGraphicsDeviceInterop, które są natywnymi interfejsami międzyoperacyjnymi i umożliwiają przenoszenie zawartości bezpośrednio do kompozytora.

Natywna współpraca jest ustrukturyzowana wokół obiektów powierzchni, które są wspierane przez tekstury DirectX. Powierzchnie są tworzone na podstawie obiektu "factory" o nazwie CompositionGraphicsDevice. Ten obiekt jest wspierany przez bazowy obiekt urządzenia Direct2D lub Direct3D, którego używa do przydzielania pamięci wideo dla powierzchni. Interfejs API kompozycji nigdy nie tworzy bazowego urządzenia DirectX. Aplikacja jest odpowiedzialna za utworzenie jednego i przekazanie go do obiektu CompositionGraphicsDevice. Aplikacja może jednocześnie utworzyć więcej niż jeden obiekt CompositionGraphicsDevice i może używać tego samego urządzenia DirectX co urządzenie renderowania dla wielu obiektów CompositionGraphicsDevice .

Tworzenie powierzchni

Każde urządzenie CompositionGraphicsDevice służy jako fabryka powierzchni. Każda powierzchnia jest tworzona z początkowym rozmiarem (który może wynosić 0,0), ale bez prawidłowych pikseli. Powierzchnia w stanie początkowym może być natychmiast zużywana w drzewie wizualnym, na przykład za pośrednictwem elementu CompositionSurfaceBrush i SpriteVisual, ale w początkowym stanie powierzchnia nie ma wpływu na dane wyjściowe ekranu. Jest to, pod każdym względem, całkowicie przezroczyste, nawet jeśli określony tryb alfa jest "nieprzezroczysty".

Czasami urządzenia DirectX mogą być renderowane jako bezużyteczne. Może się to zdarzyć, między innymi, jeśli aplikacja przekazuje nieprawidłowe argumenty do niektórych API DirectX, jeśli karta graficzna zostanie zresetowana przez system lub gdy sterownik zostanie zaktualizowany. Direct3D ma interfejs API, którego aplikacja może używać do odnajdywania asynchronicznego, jeśli urządzenie zostanie utracone z jakiegokolwiek powodu. Po utracie urządzenia DirectX aplikacja musi ją odrzucić, utworzyć nową i przekazać ją do dowolnych obiektów CompositionGraphicsDevice skojarzonych wcześniej z nieprawidłowym urządzeniem DirectX.

Ładowanie pikseli do powierzchni

Aby załadować piksele do powierzchni, aplikacja musi wywołać metodę BeginDraw , która zwraca interfejs DirectX reprezentujący teksturę lub kontekst Direct2D, w zależności od żądań aplikacji. Aplikacja musi następnie renderować lub przekazywać piksele do tej tekstury. Po zakończeniu działania aplikacji musi ona wywołać metodę EndDraw . Tylko w tym momencie nowe piksele są dostępne do użycia przy kompozycji, ale nadal nie są wyświetlane na ekranie, dopóki nie zostaną zatwierdzone wszystkie zmiany w drzewie wizualnym. Jeśli drzewo wizualne zostanie zatwierdzone przed wywołaniem EndDraw, trwająca aktualizacja nie jest widoczna na ekranie, a powierzchnia nadal wyświetla zawartość, którą miała przed BeginDraw. Po wywołaniu funkcji EndDraw tekstura lub wskaźnik kontekstu Direct2D zwrócony przez funkcję BeginDraw jest unieważniany. Aplikacja nigdy nie powinna buforować tego wskaźnika poza wywołaniem EndDraw .

Aplikacja może wywołać tylko metodę BeginDraw na jednej powierzchni w danym urządzeniu CompositionGraphicsDevice. Po wywołaniu metody BeginDraw aplikacja musi wywołać metodę EndDraw na tej powierzchni przed wywołaniem metody BeginDraw na innej. Ponieważ interfejs API jest zwinny, aplikacja jest odpowiedzialna za synchronizowanie tych wywołań, jeśli chce wykonać renderowanie z wielu wątków procesów roboczych. Jeśli aplikacja chce przerwać renderowanie jednej powierzchni i tymczasowo przełączyć się do innej, aplikacja może użyć metody SuspendDraw . To umożliwia sukces kolejnego BeginDraw, ale nie udostępnia pierwszej aktualizacji powierzchni do kompozycji ekranowej. Dzięki temu aplikacja może wykonywać wiele aktualizacji w sposób transakcyjny. Po wstrzymaniu powierzchni aplikacja może kontynuować aktualizację przez wywołanie metody ResumeDraw lub zadeklarować, że aktualizacja została zakończona, wywołując EndDraw. Oznacza to, że tylko jedna powierzchnia może być aktywnie aktualizowana w danym momencie dla dowolnego elementu CompositionGraphicsDevice. Każde urządzenie graficzne zachowuje ten stan niezależnie od innych, więc aplikacja może renderować się na dwóch powierzchniach jednocześnie, jeśli należą do różnych urządzeń graficznych. Jednak uniemożliwia to wspólne użycie pamięci wideo dla tych dwóch powierzchni i, w związku z tym, jest mniej efektywne pod względem wykorzystania pamięci.

Metody BeginDraw, SuspendDraw, ResumeDraw i EndDraw zwracają błędy, jeśli aplikacja wykonuje nieprawidłową operację (taką jak przekazywanie nieprawidłowych argumentów lub wywoływanie elementu BeginDraw na powierzchni przed wywołaniem elementu EndDraw na innym). Tego rodzaju błędy reprezentują błędy aplikacji, a w związku z tym oczekuje się, że będą one obsługiwane zgodnie z zasadą szybkiego niepowodzenia. Funkcja BeginDraw może również zwrócić błąd w przypadku utraty bazowego urządzenia DirectX. Ten błąd nie jest krytyczny, ponieważ aplikacja może ponownie utworzyć urządzenie DirectX i spróbować ponownie. W związku z tym oczekuje się, że aplikacja będzie obsługiwać utratę urządzenia, pomijając renderowanie. Jeśli element BeginDraw zakończy się niepowodzeniem z jakiegokolwiek powodu, aplikacja nie powinna również wywoływać elementu EndDraw, ponieważ początek nigdy nie zakończył się pomyślnie.

Przewijanie

Ze względów wydajnościowych, gdy aplikacja wywołuje BeginDraw, zawartość zwróconej tekstury nie jest gwarantowana jako poprzednia zawartość powierzchni. Aplikacja musi założyć, że zawartość jest losowa i w związku z tym musi upewnić się, że wszystkie piksele zostają uwzględnione, np. poprzez wyczyszczenie powierzchni przed renderowaniem lub narysowanie wystarczająco dużo nieprzezroczystej treści, aby pokryć cały zaktualizowany prostokąt. W połączeniu z faktem, że wskaźnik tekstury jest prawidłowy tylko między wywołaniami BeginDraw i EndDraw , uniemożliwia aplikacji skopiowanie poprzedniej zawartości z powierzchni. Z tego powodu oferujemy metodę Scroll , która umożliwia aplikacji wykonywanie kopii pikseli tej samej powierzchni.

Przykład użycia języka C++/WinRT

Poniższy przykład kodu ilustruje scenariusz współdziałania międzyoperacyjności kompozycji w kontekście zestawu SDK aplikacji systemu Windows. Przykład łączy typy z powierzchni opartej na środowisku uruchomieniowym systemu Windows Microsoft.UI.Composition wraz z typami z nagłówków międzyoperacyjności oraz kod, który renderuje tekst przy użyciu interfejsów API DirectWrite i Direct2D opartych na technologii COM. W przykładzie użyto metody BeginDraw i EndDraw , aby bezproblemowo współdziałać między tymi technologiami. W przykładzie użyto funkcji DirectWrite do ułonienia tekstu, a następnie renderowanie go przy użyciu funkcji Direct2D. Urządzenie graficzne kompozycji akceptuje urządzenie Direct2D bezpośrednio w momencie inicjalizacji. Dzięki temu BeginDraw zwraca wskaźnik interfejsu ID2D1DeviceContext, co jest znacznie bardziej wydajne niż tworzenie kontekstu Direct2D, aby owinąć zwrócony interfejs ID3D11Texture2D podczas każdej operacji rysowania.

Aby wypróbować poniższy przykład kodu C++/WinRT, najpierw utwórz nowy projekt aplikacji WinUI w programie Visual Studio (aby uzyskać wymagania, zobacz Obsługa programu Visual Studio dla języka C++/WinRT). Zastąp zawartość plików kodu źródłowego pch.h i App.cpp poniższymi listami kodu, a następnie skompiluj i uruchom. Aplikacja renderuje ciąg "Hello, World!" w czarnym tekście na przezroczystym tle.

// NOTE: This example uses UWP-specific APIs. For WinUI, replace CoreWindow with your app's Window handle.
// pch.h
#pragma once
#include <windows.h>
#include <D2d1_1.h>
#include <D3d11_4.h>
#include <Dwrite.h>
#include <Windows.Graphics.DirectX.Direct3D11.interop.h>
#include <Windows.ui.composition.interop.h>
#include <unknwn.h>

#include <winrt/Windows.ApplicationModel.Core.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Graphics.DirectX.h>
#include <winrt/Windows.Graphics.DirectX.Direct3D11.h>
#include <winrt/Microsoft.UI.Composition.h>
#include <winrt/Windows.UI.Core.h>
#include <winrt/Windows.UI.Input.h>
// NOTE: This example uses UWP-specific APIs. For WinUI, replace CoreWindow with your app's Window handle.
// App.cpp
//*********************************************************
//
// Copyright (c) Microsoft. All rights reserved.
// This code is licensed under the MIT License (MIT).
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH 
// THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//*********************************************************

#include "pch.h"

using namespace winrt;
using namespace winrt::Windows::ApplicationModel::Core;
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::Foundation::Numerics;
using namespace winrt::Windows::Graphics::DirectX;
using namespace winrt::Windows::Graphics::DirectX::Direct3D11;
using namespace winrt::Windows::UI;
using namespace winrt::Microsoft::UI::Composition;
using namespace winrt::Windows::UI::Core;

namespace abi
{
    using namespace ABI::Windows::Foundation;
    using namespace ABI::Windows::Graphics::DirectX;
    using namespace ABI::Microsoft::UI::Composition;
}

// An app-provided helper to render lines of text.
struct SampleText
{
    SampleText(winrt::com_ptr<::IDWriteTextLayout> const& text, CompositionGraphicsDevice const& compositionGraphicsDevice) :
        m_text(text),
        m_compositionGraphicsDevice(compositionGraphicsDevice)
    {
        // Create the surface just big enough to hold the formatted text block.
        DWRITE_TEXT_METRICS metrics;
        winrt::check_hresult(m_text->GetMetrics(&metrics));
        winrt::Windows::Foundation::Size surfaceSize{ metrics.width, metrics.height };

        CompositionDrawingSurface drawingSurface{ m_compositionGraphicsDevice.CreateDrawingSurface(
            surfaceSize,
            DirectXPixelFormat::B8G8R8A8UIntNormalized,
            DirectXAlphaMode::Premultiplied) };

        // Cache the interop pointer, since that's what we always use.
        m_drawingSurfaceInterop = drawingSurface.as<abi::ICompositionDrawingSurfaceInterop>();

        // Draw the text
        DrawText();

        // If the rendering device is lost, the application will recreate and replace it. We then
        // own redrawing our pixels.
        m_deviceReplacedEventToken = m_compositionGraphicsDevice.RenderingDeviceReplaced(
            [this](CompositionGraphicsDevice const&, RenderingDeviceReplacedEventArgs const&)
            {
                // Draw the text again.
                DrawText();
                return S_OK;
            });
    }

    ~SampleText()
    {
        m_compositionGraphicsDevice.RenderingDeviceReplaced(m_deviceReplacedEventToken);
    }

    // Return the underlying surface to the caller.
    auto Surface()
    {
        // To the caller, the fact that we have a drawing surface is an implementation detail.
        // Return the base interface instead.
        return m_drawingSurfaceInterop.as<ICompositionSurface>();
    }

private:
    // The text to draw.
    winrt::com_ptr<::IDWriteTextLayout> m_text;

    // The composition surface that we use in the visual tree.
    winrt::com_ptr<abi::ICompositionDrawingSurfaceInterop> m_drawingSurfaceInterop;

    // The device that owns the surface.
    CompositionGraphicsDevice m_compositionGraphicsDevice{ nullptr };
    //winrt::com_ptr<abi::ICompositionGraphicsDevice> m_compositionGraphicsDevice2;

    // For managing our event notifier.
    winrt::event_token m_deviceReplacedEventToken;

    // We may detect device loss on BeginDraw calls. This helper handles this condition or other
    // errors.
    bool CheckForDeviceRemoved(HRESULT hr)
    {
        if (hr == S_OK)
        {
            // Everything is fine: go ahead and draw.
            return true;
        }

        if (hr == DXGI_ERROR_DEVICE_REMOVED)
        {
            // We can't draw at this time, but this failure is recoverable. Just skip drawing for
            // now. We will be asked to draw again once the Direct3D device is recreated.
            return false;
        }

        // Any other error is unexpected and, therefore, fatal.
        winrt::check_hresult(hr);
        return true;
    }

    // Renders the text into our composition surface
    void DrawText()
    {
        // Begin our update of the surface pixels. If this is our first update, we are required
        // to specify the entire surface, which nullptr is shorthand for (but, as it works out,
        // any time we make an update we touch the entire surface, so we always pass nullptr).
        winrt::com_ptr<::ID2D1DeviceContext> d2dDeviceContext;
        POINT offset;
        if (CheckForDeviceRemoved(m_drawingSurfaceInterop->BeginDraw(nullptr,
            __uuidof(ID2D1DeviceContext), d2dDeviceContext.put_void(), &offset)))
        {
            d2dDeviceContext->Clear(D2D1::ColorF(D2D1::ColorF::Black, 0.f));

            // Create a solid color brush for the text. A more sophisticated application might want
            // to cache and reuse a brush across all text elements instead, taking care to recreate
            // it in the event of device removed.
            winrt::com_ptr<::ID2D1SolidColorBrush> brush;
            winrt::check_hresult(d2dDeviceContext->CreateSolidColorBrush(
                D2D1::ColorF(D2D1::ColorF::Black, 1.0f), brush.put()));

            // Draw the line of text at the specified offset, which corresponds to the top-left
            // corner of our drawing surface. Notice we don't call BeginDraw on the D2D device
            // context; this has already been done for us by the composition API.
            d2dDeviceContext->DrawTextLayout(D2D1::Point2F((float)offset.x, (float)offset.y), m_text.get(),
                brush.get());

            // Our update is done. EndDraw never indicates rendering device removed, so any
            // failure here is unexpected and, therefore, fatal.
            winrt::check_hresult(m_drawingSurfaceInterop->EndDraw());
        }
    }
};

struct DeviceLostEventArgs
{
    DeviceLostEventArgs(IDirect3DDevice const& device) : m_device(device) {}
    IDirect3DDevice Device() { return m_device; }
    static DeviceLostEventArgs Create(IDirect3DDevice const& device) { return DeviceLostEventArgs{ device }; }

private:
    IDirect3DDevice m_device;
};

struct DeviceLostHelper
{
    DeviceLostHelper() = default;

    ~DeviceLostHelper()
    {
        StopWatchingCurrentDevice();
        m_onDeviceLostHandler = nullptr;
    }

    IDirect3DDevice CurrentlyWatchedDevice() { return m_device; }

    void WatchDevice(winrt::com_ptr<::IDXGIDevice> const& dxgiDevice)
    {
        // If we're currently listening to a device, then stop.
        StopWatchingCurrentDevice();

        // Set the current device to the new device.
        m_device = nullptr;
        winrt::check_hresult(::CreateDirect3D11DeviceFromDXGIDevice(dxgiDevice.get(), reinterpret_cast<::IInspectable**>(winrt::put_abi(m_device))));

        // Get the DXGI Device.
        m_dxgiDevice = dxgiDevice;

        // QI For the ID3D11Device4 interface.
        winrt::com_ptr<::ID3D11Device4> d3dDevice{ m_dxgiDevice.as<::ID3D11Device4>() };

        // Create a wait struct.
        m_onDeviceLostHandler = nullptr;
        m_onDeviceLostHandler = ::CreateThreadpoolWait(DeviceLostHelper::OnDeviceLost, (PVOID)this, nullptr);

        // Create a handle and a cookie.
        m_eventHandle.attach(::CreateEvent(nullptr, false, false, nullptr));
        winrt::check_bool(bool{ m_eventHandle });
        m_cookie = 0;

        // Register for device lost.
        ::SetThreadpoolWait(m_onDeviceLostHandler, m_eventHandle.get(), nullptr);
        winrt::check_hresult(d3dDevice->RegisterDeviceRemovedEvent(m_eventHandle.get(), &m_cookie));
    }

    void StopWatchingCurrentDevice()
    {
        if (m_dxgiDevice)
        {
            // QI For the ID3D11Device4 interface.
            auto d3dDevice{ m_dxgiDevice.as<::ID3D11Device4>() };

            // Unregister from the device lost event.
            ::CloseThreadpoolWait(m_onDeviceLostHandler);
            d3dDevice->UnregisterDeviceRemoved(m_cookie);

            // Clear member variables.
            m_onDeviceLostHandler = nullptr;
            m_eventHandle.close();
            m_cookie = 0;
            m_device = nullptr;
        }
    }

    void DeviceLost(winrt::delegate<DeviceLostHelper const*, DeviceLostEventArgs const&> const& handler)
    {
        m_deviceLost = handler;
    }

    winrt::delegate<DeviceLostHelper const*, DeviceLostEventArgs const&> m_deviceLost;

private:
    void RaiseDeviceLostEvent(IDirect3DDevice const& oldDevice)
    {
        m_deviceLost(this, DeviceLostEventArgs::Create(oldDevice));
    }

    static void CALLBACK OnDeviceLost(PTP_CALLBACK_INSTANCE /* instance */, PVOID context, PTP_WAIT /* wait */, TP_WAIT_RESULT /* waitResult */)
    {
        auto deviceLostHelper = reinterpret_cast<DeviceLostHelper*>(context);
        auto oldDevice = deviceLostHelper->m_device;
        deviceLostHelper->StopWatchingCurrentDevice();
        deviceLostHelper->RaiseDeviceLostEvent(oldDevice);
    }

private:
    IDirect3DDevice m_device;
    winrt::com_ptr<::IDXGIDevice> m_dxgiDevice;
    PTP_WAIT m_onDeviceLostHandler{ nullptr };
    winrt::handle m_eventHandle;
    DWORD m_cookie{ 0 };
};

struct SampleApp : implements<SampleApp, IFrameworkViewSource, IFrameworkView>
{
    IFrameworkView CreateView()
    {
        return *this;
    }

    void Initialize(CoreApplicationView const&)
    {
    }

    // Run once when the application starts up
    void Initialize()
    {
        // Create a Direct2D device.
        CreateDirect2DDevice();

        // To create a composition graphics device, we need to QI for another interface
        winrt::com_ptr<abi::ICompositorInterop> compositorInterop{ m_compositor.as<abi::ICompositorInterop>() };

        // Create a graphics device backed by our D3D device
        winrt::com_ptr<abi::ICompositionGraphicsDevice> compositionGraphicsDeviceIface;
        winrt::check_hresult(compositorInterop->CreateGraphicsDevice(
            m_d2dDevice.get(),
            compositionGraphicsDeviceIface.put()));
        m_compositionGraphicsDevice = compositionGraphicsDeviceIface.as<CompositionGraphicsDevice>();
    }

    void Load(hstring const&)
    {
    }

    void Uninitialize()
    {
    }

    void Run()
    {
        CoreWindow window = CoreWindow::GetForCurrentThread();
        window.Activate();

        CoreDispatcher dispatcher = window.Dispatcher();
        dispatcher.ProcessEvents(CoreProcessEventsOption::ProcessUntilQuit);
    }

    void SetWindow(CoreWindow const& window)
    {
        m_compositor = Compositor{};
        m_target = m_compositor.CreateTargetForCurrentView();
        ContainerVisual root = m_compositor.CreateContainerVisual();
        m_target.Root(root);

        Initialize();

        winrt::check_hresult(
            ::DWriteCreateFactory(
                DWRITE_FACTORY_TYPE_SHARED,
                __uuidof(m_dWriteFactory),
                reinterpret_cast<::IUnknown**>(m_dWriteFactory.put())
            )
        );

        winrt::check_hresult(
            m_dWriteFactory->CreateTextFormat(
                L"Segoe UI",
                nullptr,
                DWRITE_FONT_WEIGHT_REGULAR,
                DWRITE_FONT_STYLE_NORMAL,
                DWRITE_FONT_STRETCH_NORMAL,
                36.f,
                L"en-US",
                m_textFormat.put()
            )
        );

        Rect windowBounds{ window.Bounds() };
        std::wstring text{ L"Hello, World!" };

        winrt::check_hresult(
            m_dWriteFactory->CreateTextLayout(
                text.c_str(),
                (uint32_t)text.size(),
                m_textFormat.get(),
                windowBounds.Width,
                windowBounds.Height,
                m_textLayout.put()
            )
        );

        Visual textVisual{ CreateVisualFromTextLayout(m_textLayout) };
        textVisual.Size({ windowBounds.Width, windowBounds.Height });
        root.Children().InsertAtTop(textVisual);
    }

    // Called when Direct3D signals the device lost event.
    void OnDirect3DDeviceLost(DeviceLostHelper const* /* sender */, DeviceLostEventArgs const& /* args */)
    {
        // Create a new Direct2D device.
        CreateDirect2DDevice();

        // Restore our composition graphics device to good health.
        winrt::com_ptr<abi::ICompositionGraphicsDeviceInterop> compositionGraphicsDeviceInterop{ m_compositionGraphicsDevice.as<abi::ICompositionGraphicsDeviceInterop>() };
        winrt::check_hresult(compositionGraphicsDeviceInterop->SetRenderingDevice(m_d2dDevice.get()));
    }

    // Create a surface that is asynchronously filled with an image
    ICompositionSurface CreateSurfaceFromTextLayout(winrt::com_ptr<::IDWriteTextLayout> const& text)
    {
        // Create our wrapper object that will handle downloading and decoding the image (assume
        // throwing new here).
        SampleText textSurface{ text, m_compositionGraphicsDevice };

        // The caller is only interested in the underlying surface.
        return textSurface.Surface();
    }

    // Create a visual that holds an image.
    Visual CreateVisualFromTextLayout(winrt::com_ptr<::IDWriteTextLayout> const& text)
    {
        // Create a sprite visual
        SpriteVisual spriteVisual{ m_compositor.CreateSpriteVisual() };

        // The sprite visual needs a brush to hold the image.
        CompositionSurfaceBrush surfaceBrush{
            m_compositor.CreateSurfaceBrush(CreateSurfaceFromTextLayout(text))
        };

        // Associate the brush with the visual.
        CompositionBrush brush{ surfaceBrush.as<CompositionBrush>() };
        spriteVisual.Brush(brush);

        // Return the visual to the caller as an IVisual.
        return spriteVisual;
    }

private:
    CompositionTarget m_target{ nullptr };
    Compositor m_compositor{ nullptr };
    winrt::com_ptr<::ID2D1Device> m_d2dDevice;
    winrt::com_ptr<::IDXGIDevice> m_dxgiDevice;
    //winrt::com_ptr<abi::ICompositionGraphicsDevice> m_compositionGraphicsDevice;
    CompositionGraphicsDevice m_compositionGraphicsDevice{ nullptr };
    std::vector<SampleText> m_textSurfaces;
    DeviceLostHelper m_deviceLostHelper;
    winrt::com_ptr<::IDWriteFactory> m_dWriteFactory;
    winrt::com_ptr<::IDWriteTextFormat> m_textFormat;
    winrt::com_ptr<::IDWriteTextLayout> m_textLayout;


    // This helper creates a Direct2D device, and registers for a device loss
    // notification on the underlying Direct3D device. When that notification is
    // raised, the OnDirect3DDeviceLost method is called.
    void CreateDirect2DDevice()
    {
        uint32_t createDeviceFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;

        // Array with DirectX hardware feature levels in order of preference.
        D3D_FEATURE_LEVEL featureLevels[] =
        {
            D3D_FEATURE_LEVEL_11_1,
            D3D_FEATURE_LEVEL_11_0,
            D3D_FEATURE_LEVEL_10_1,
            D3D_FEATURE_LEVEL_10_0,
            D3D_FEATURE_LEVEL_9_3,
            D3D_FEATURE_LEVEL_9_2,
            D3D_FEATURE_LEVEL_9_1
        };

        // Create the Direct3D 11 API device object and a corresponding context.
        winrt::com_ptr<::ID3D11Device> d3DDevice;
        winrt::com_ptr<::ID3D11DeviceContext> d3DImmediateContext;
        D3D_FEATURE_LEVEL d3dFeatureLevel{ D3D_FEATURE_LEVEL_9_1 };

        winrt::check_hresult(
            ::D3D11CreateDevice(
                nullptr, // Default adapter.
                D3D_DRIVER_TYPE_HARDWARE,
                0, // Not asking for a software driver, so not passing a module to one.
                createDeviceFlags, // Set debug and Direct2D compatibility flags.
                featureLevels,
                ARRAYSIZE(featureLevels),
                D3D11_SDK_VERSION,
                d3DDevice.put(),
                &d3dFeatureLevel,
                d3DImmediateContext.put()
            )
        );

        // Initialize Direct2D resources.
        D2D1_FACTORY_OPTIONS d2d1FactoryOptions{ D2D1_DEBUG_LEVEL_NONE };

        // Initialize the Direct2D Factory.
        winrt::com_ptr<::ID2D1Factory1> d2D1Factory;
        winrt::check_hresult(
            ::D2D1CreateFactory(
                D2D1_FACTORY_TYPE_SINGLE_THREADED,
                __uuidof(d2D1Factory),
                &d2d1FactoryOptions,
                d2D1Factory.put_void()
            )
        );

        // Create the Direct2D device object.
        // Obtain the underlying DXGI device of the Direct3D device.
        m_dxgiDevice = d3DDevice.as<::IDXGIDevice>();

        m_d2dDevice = nullptr;
        winrt::check_hresult(
            d2D1Factory->CreateDevice(m_dxgiDevice.get(), m_d2dDevice.put())
        );

        m_deviceLostHelper.WatchDevice(m_dxgiDevice);
        m_deviceLostHelper.DeviceLost({ this, &SampleApp::OnDirect3DDeviceLost });
    }
};

int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
    CoreApplication::Run(winrt::make<SampleApp>());
}