Megosztás a következőn keresztül:


Kompozíció natív együttműködés a DirectX és a Direct2D használatával

Fontos

Ez a cikk a Windows App SDK/WinUI UWP-dokumentációjából lett adaptálva. A cikkben szereplő kódpéldák továbbra is UWP-specifikus API-kra hivatkoznak, mint például a CoreWindow, CoreApplicationView és CoreDispatcher, amelyek nem érhetők el a WinUI/Windows App SDK-ban. A WinUI-ekvivalensek a következők:

A kompozíciós interop interfészek (ICompositorInterop, ICompositionDrawingSurfaceInterop stb.) ugyanúgy működnek az objektumokkal Microsoft.UI.Composition .

A Microsoft.UI.Composition API az ICompositorInterop, az ICompositionDrawingSurfaceInterop és az ICompositionGraphicsDeviceInterop natív interoperációs felületeket biztosít, amelyek lehetővé teszik a tartalom közvetlen áthelyezését a compositorba.

A natív interoperáció a DirectX-textúrák által támogatott felületi objektumok köré épül. A felületek egy CompositionGraphicsDevice nevű gyári objektumból jönnek létre. Ezt az objektumot egy mögöttes Direct2D- vagy Direct3D-eszközobjektum is alátámasztja, amellyel videomemóriát foglal le a felületekhez. A kompozíciós API soha nem hozza létre az alapul szolgáló DirectX-eszközt. Az alkalmazás feladata létrehozni egyet, és átadni azt a CompositionGraphicsDevice objektumnak. Az alkalmazások egyszerre több CompositionGraphicsDevice objektumot is létrehozhatnak, és ugyanazt a DirectX-eszközt használhatják, mint a renderelő eszközt több CompositionGraphicsDevice objektumhoz.

Felület létrehozása

Minden CompositionGraphicsDevice felületgyárként szolgál. Minden felület kezdeti mérettel (0,0) jön létre, de nincs érvényes képpont. A kezdeti állapotban lévő felület azonnal felhasználható egy vizualizációs fában, például egy CompositionSurfaceBrush és egy SpriteVisual használatával, de kezdeti állapotában a felület nincs hatással a képernyő kimenetére. Ez minden szempontból teljesen átlátszó, még akkor is, ha a megadott alfa mód "átlátszatlan".

Előfordulhat, hogy a DirectX-eszközök használhatatlanná válhatnak. Ez többek között akkor fordulhat elő, ha az alkalmazás érvénytelen argumentumokat ad át bizonyos DirectX API-knak, vagy ha a rendszer alaphelyzetbe állítja a grafikus adaptert, vagy ha az illesztőprogram frissül. A Direct3D rendelkezik egy API-val, amelyet egy alkalmazás aszinkron módon felderíthet, ha az eszköz valamilyen okból elvész. Ha egy DirectX-eszköz elveszik, az alkalmazásnak el kell vetnie, létre kell hoznia egy újat, és továbbítania kell azt a korábban rossz DirectX-eszközhöz társított CompositionGraphicsDevice objektumoknak.

Képpontok betöltése felületre

Ha képpontokat szeretne betölteni a felületre, az alkalmazásnak meg kell hívnia a BeginDraw metódust , amely egy textúra vagy Direct2D-környezetet képviselő DirectX-felületet ad vissza, attól függően, hogy az alkalmazás mit kér. Az alkalmazásnak ezután képpontokat kell renderelnie vagy feltöltenie ebbe az anyagmintába. Amikor az alkalmazás elkészült, meg kell hívnia az EndDraw metódust . Csak ezen a ponton érhetők el az új képpontok a kompozícióhoz, de továbbra sem jelennek meg a képernyőn a következő alkalommal, amikor a vizualizációfa összes módosítása véglegesítésre kerül. Ha a vizualizációs fa véglegesítése az EndDraw meghívása előtt történik, akkor a folyamatban lévő frissítés nem látható a képernyőn, és a felület továbbra is megjeleníti a BeginDraw előtti tartalmat. Az EndDraw meghívása esetén a BeginDraw által visszaadott textúra vagy Direct2D környezeti mutató érvénytelen lesz. Az alkalmazásoknak soha nem szabad gyorsítótáraznia ezt a mutatót az EndDraw-híváson túl.

Az alkalmazás egyszerre csak egy felületen hívhatja meg a BeginDraw-t, az adott CompositionGraphicsDevice-hez. A BeginDraw hívása után az alkalmazásnak az EndDraw-t kell meghívnia ezen a felületen, mielőtt meghívja a BeginDraw-t egy másikra. Mivel az API agilis, az alkalmazás felelős ezeknek a hívásoknak a szinkronizálásáért, ha több feldolgozószálból szeretne renderelni. Ha egy alkalmazás megszakítja az egyik felület megjelenítését, és ideiglenesen másikra szeretne váltani, az alkalmazás a SuspendDraw metódust használhatja. Ez lehetővé teszi egy másik BeginDraw sikerességét, de nem teszi elérhetővé az első felületi frissítést a képernyő-összeállításhoz. Ez lehetővé teszi, hogy az alkalmazás tranzakciós módon több frissítést is végrehajtson. A felület felfüggesztése után az alkalmazás a ResumeDraw metódus meghívásával folytathatja a frissítést, vagy deklarálhatja, hogy a frissítés az EndDraw meghívásával történik. Ez azt jelenti, hogy egy adott CompositionGraphicsDevice esetében egyszerre csak egy felület frissíthető aktívan. Minden grafikus eszköz a többitől függetlenül tartja ezt az állapotot, így egy alkalmazás egyszerre két felületre is renderelhet, ha különböző grafikus eszközökhöz tartoznak. Ez azonban megakadályozza, hogy a két felület videomemóriái össze legyenek állítva, és így kevésbé hatékony a memória.

A BeginDraw, a SuspendDraw, a ResumeDraw és az EndDraw metódus hibát ad vissza, ha az alkalmazás helytelen műveletet hajt végre (például érvénytelen argumentumokat ad át, vagy a BeginDraw meghívása egy felületen, mielőtt meghívja az EndDrawot egy másikra ). Az ilyen típusú hibák alkalmazáshibákat jelölnek, és mint ilyen, az elvárás az, hogy a rendszer gyorsan kezelje őket. A BeginDraw hibát is okozhat, ha a mögöttes DirectX-eszköz elveszett. Ez a hiba nem végzetes, mivel az alkalmazás újra létrehozhatja DirectX-eszközét, és újra próbálkozhat. Ezért az alkalmazásnak a renderelés kihagyásával várhatóan kezelnie kell az eszközvesztést. Ha a BeginDraw bármilyen okból meghiúsul, az alkalmazásnak nem szabad meghívnia az EndDraw-t sem, mivel a kezdés soha nem sikerült.

Görgetés

Teljesítménybeli okokból, amikor egy alkalmazás meghívja a BeginDraw-t , a visszaadott anyagminta tartalma nem garantált, hogy a felület korábbi tartalma lesz. Az alkalmazásnak feltételeznie kell, hogy a tartalom véletlenszerű, és mint ilyen, az alkalmazásnak biztosítania kell az összes képpont megérintését, akár a felület renderelés előtti törlésével, akár a teljes frissített téglalap lefedéséhez elegendő átlátszatlan tartalom rajzolásával. Ez azzal a ténnyel együtt, hogy a textúramutató csak a BeginDraw és az EndDraw hívások között érvényes, lehetetlenné teszi, hogy az alkalmazás a korábbi tartalmakat kimásolja a felületről. Ezért kínálunk egy scroll metódust, amely lehetővé teszi az alkalmazás számára, hogy azonos felületű képpontmásolást végezzen.

C++/WinRT-használati példa

Az alábbi példakód szemlélteti a kompozit együttműködés egy használati forgatókönyvét Windows App SDK környezetben. A példa egyesíti a Microsoft.UI.Composition Windows Runtime-alapú felülethez tartozó típusokat az interop fejlécekből származó típusokkal, és a szöveg COM-alapú DirectWrite és Direct2D API-k használatával történő renderelésére szolgáló kóddal. A példa a BeginDraw és az EndDraw használatával teszi zökkenőmentessé a technológiák közötti együttműködést. A példa a DirectWrite-et használja a szöveg elrendezéséhez, majd a Direct2D-t használja a megjelenítéshez. A kompozíciós grafikus eszköz közvetlenül az inicializáláskor fogadja el a Direct2D-eszközt. Ez lehetővé teszi, hogy a BeginDraw egy ID2D1DeviceContext interfészmutatót adjon vissza, ami lényegesen hatékonyabb, mint ha az alkalmazás Direct2D-környezetet hoz létre a visszaadott ID3D11Texture2D illesztő körbefuttatásához minden rajzműveletnél.

Az alábbi C++/WinRT-kód példájának kipróbálásához először hozzon létre egy új WinUI-alkalmazásprojektet a Visual Studióban (a követelményekért lásd a Visual Studio C++/WinRT-támogatását). Cserélje le a pch.h és App.cpp forráskódfájlok tartalmát az alább található kódlistákra, majd építse meg és futtassa a programot. Az alkalmazás a "Hello, World!" sztringet transzparens háttéren fekete szövegben jeleníti meg.

// 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>());
}