Interopérabilité native de composition avec DirectX et Direct2D
L’API Windows.UI.Composition fournit les interfaces d’interopérabilité native ICompositorInterop, ICompositionDrawingSurfaceInterop et ICompositionGraphicsDeviceInterop qui permettent de déplacer le contenu directement dans le compositeur.
L’interopérabilité native est structurée autour d’objets surface soutenus par des textures DirectX. Les surfaces sont créées à partir d’un objet de fabrique appelé CompositionGraphicsDevice. Cet objet est soutenu par un objet d’appareil Direct2D ou Direct3D sous-jacent, qu’il utilise pour allouer de la mémoire vidéo pour les surfaces. L’API de composition ne crée jamais l’appareil DirectX sous-jacent. Il incombe à l’application de créer une application et de la transmettre à l’objet CompositionGraphicsDevice . Une application peut créer plusieurs objets CompositionGraphicsDevice à la fois et utiliser le même appareil DirectX que l’appareil de rendu pour plusieurs objets CompositionGraphicsDevice .
Création d’une surface
Chaque CompositionGraphicsDevice sert de fabrique de surface. Chaque surface est créée avec une taille initiale (qui peut être de 0,0), mais pas de pixels valides. Une surface dans son état initial peut être consommée immédiatement dans une arborescence visuelle, par exemple, via un Objet CompositionSurfaceBrush et un SpriteVisual, mais dans son état initial, la surface n’a aucun effet sur la sortie de l’écran. C’est, à tous fins, entièrement transparent, même si le mode alpha spécifié est « opaque ».
Parfois, les appareils DirectX peuvent être rendus inutilisables. Cela peut se produire, entre autres raisons, si l’application transmet des arguments non valides à certaines API DirectX, ou si la carte graphique est réinitialisée par le système ou si le pilote est mis à jour. Direct3D a une API qu’une application peut utiliser pour découvrir, de manière asynchrone, si l’appareil est perdu pour une raison quelconque. Lorsqu’un appareil DirectX est perdu, l’application doit l’ignorer, en créer une nouvelle et la transmettre à tous les objets CompositionGraphicsDevice précédemment associés à l’appareil DirectX incorrect.
Chargement de pixels dans une surface
Pour charger des pixels dans la surface, l’application doit appeler la méthode BeginDraw , qui retourne une interface DirectX représentant une texture ou un contexte Direct2D, en fonction de ce que l’application demande. L’application doit ensuite afficher ou charger des pixels dans cette texture. Une fois l’application terminée, elle doit appeler la méthode EndDraw . Seuls à ce stade sont les nouveaux pixels disponibles pour la composition, mais ils ne s’affichent toujours pas à l’écran tant que toutes les modifications apportées à l’arborescence visuelle ne sont pas validées. Si l’arborescence visuelle est validée avant l’appel de EndDraw, la mise à jour en cours n’est pas visible à l’écran et la surface continue d’afficher le contenu qu’elle avait avant BeginDraw. Lorsque EndDraw est appelé, la texture ou le pointeur de contexte Direct2D retourné par BeginDraw est invalidé. Une application ne doit jamais mettre en cache ce pointeur au-delà de l’appel EndDraw .
L’application peut uniquement appeler BeginDraw sur une surface à la fois, pour n’importe quelle CompositionGraphicsDevice donnée. Après avoir appelé BeginDraw, l’application doit appeler EndDraw sur cette surface avant d’appeler BeginDraw sur une autre. Comme l’API est agile, l’application est chargée de synchroniser ces appels s’il souhaite effectuer le rendu à partir de plusieurs threads de travail. Si une application souhaite interrompre le rendu d’une surface et basculer temporairement vers une autre, l’application peut utiliser la méthode SuspendDraw . Cela permet à un autre BeginDraw de réussir, mais ne rend pas la première mise à jour de surface disponible pour la composition à l’écran. Cela permet à l’application d’effectuer plusieurs mises à jour de manière transactionnelle. Une fois qu’une surface est suspendue, l’application peut continuer la mise à jour en appelant la méthode ResumeDraw, ou elle peut déclarer que la mise à jour est effectuée en appelant EndDraw. Cela signifie qu’une seule surface peut être activement mise à jour à la fois pour n’importe quelle CompositionGraphicsDevice donnée. Chaque appareil graphique conserve cet état indépendamment des autres, afin qu’une application puisse s’afficher simultanément sur deux surfaces si elles appartiennent à différents appareils graphiques. Toutefois, cela empêche la mémoire vidéo de ces deux surfaces d’être regroupées et, par conséquent, est moins efficace en mémoire.
Les méthodes BeginDraw, SuspendDraw, ResumeDraw et EndDraw retournent des échecs si l’application effectue une opération incorrecte (par exemple, en passant des arguments non valides ou en appelant BeginDraw sur une surface avant d’appeler EndDraw sur une autre). Ces types d’échecs représentent des bogues d’application et, par conséquent, l’attente est qu’ils sont gérés avec un échec rapide. BeginDraw peut également retourner un échec si l’appareil DirectX sous-jacent est perdu. Cette défaillance n’est pas irrécupérable, car l’application peut recréer son appareil DirectX et réessayer. Par conséquent, l’application est censée gérer la perte d’appareil en ignorant simplement le rendu. Si BeginDraw échoue pour une raison quelconque, l’application ne doit pas appeler EndDraw, car le début n’a jamais réussi à la première place.
Scrolling
Pour des raisons de performances, lorsqu’une application appelle BeginDraw , le contenu de la texture retournée n’est pas garanti comme le contenu précédent de la surface. L’application doit supposer que le contenu est aléatoire et, par conséquent, l’application doit s’assurer que tous les pixels sont touchés, soit en désactivant la surface avant le rendu, soit en dessinant suffisamment de contenu opaque pour couvrir l’intégralité du rectangle mis à jour. Cela, combiné au fait que le pointeur de texture n’est valide qu’entre les appels BeginDraw et EndDraw , rend impossible pour l’application de copier le contenu précédent hors de la surface. Pour cette raison, nous proposons une méthode Scroll , qui permet à l’application d’effectuer une copie de pixels de même surface.
Exemple d’utilisation de C++/WinRT
L’exemple de code suivant illustre un scénario d’interopérabilité. L’exemple combine les types de la surface d’exposition Windows Runtime de la composition Windows, ainsi que les types des en-têtes d’interopérabilité et du code qui restitue du texte à l’aide des API DirectWrite et Direct2D basées sur COM. L’exemple utilise BeginDraw et EndDraw pour faciliter l’interopérabilité entre ces technologies. L’exemple utilise DirectWrite pour mettre en page le texte, puis il utilise Direct2D pour le rendre. L’appareil graphique de composition accepte l’appareil Direct2D directement au moment de l’initialisation. Cela permet à BeginDraw de retourner un pointeur d’interface ID2D1DeviceContext , qui est considérablement plus efficace que de créer un contexte Direct2D pour encapsuler une interface ID3D11Texture2D retournée à chaque opération de dessin.
Pour tester l’exemple de code C++/WinRT ci-dessous, commencez par créer un projet Core App (C++/WinRT) dans Visual Studio (pour connaître les exigences, consultez la prise en charge de Visual Studio pour C++/WinRT). Remplacez le contenu de vos pch.h
fichiers de code source par App.cpp
les listes de code ci-dessous, puis générez et exécutez. L’application affiche la chaîne « Hello, World ! » en texte noir sur un arrière-plan transparent.
// 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/Windows.UI.Composition.h>
#include <winrt/Windows.UI.Core.h>
#include <winrt/Windows.UI.Input.h>
// 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::Windows::UI::Composition;
using namespace winrt::Windows::UI::Core;
namespace abi
{
using namespace ABI::Windows::Foundation;
using namespace ABI::Windows::Graphics::DirectX;
using namespace ABI::Windows::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>());
}
Exemple d’utilisation C++/CX
Remarque
Cet exemple de code existe pour vous aider à gérer votre application C++/CX. Mais nous recommandons que vous utilisiez C++/WinRT pour de nouvelles applications. C++/WinRT est une projection de langage C++17 moderne entièrement standard pour les API Windows Runtime (WinRT), implémentée en tant que bibliothèque basée sur un fichier d’en-tête et conçue pour vous fournir un accès de première classe à l’API Windows moderne.
L’exemple de code C++/CX ci-dessous omet les parties DirectWrite et Direct2D de l’exemple.
//------------------------------------------------------------------------------
//
// Copyright (C) Microsoft. All rights reserved.
//
//------------------------------------------------------------------------------
#include "stdafx.h"
using namespace Microsoft::WRL;
using namespace Windows::Foundation;
using namespace Windows::Graphics::DirectX;
using namespace Windows::UI::Composition;
// This is an app-provided helper to render lines of text
class SampleText
{
private:
// The text to draw
ComPtr<IDWriteTextLayout> _text;
// The composition surface that we use in the visual tree
ComPtr<ICompositionDrawingSurfaceInterop> _drawingSurfaceInterop;
// The device that owns the surface
ComPtr<ICompositionGraphicsDevice> _compositionGraphicsDevice;
// For managing our event notifier
EventRegistrationToken _deviceReplacedEventToken;
public:
SampleText(IDWriteTextLayout* text, ICompositionGraphicsDevice* compositionGraphicsDevice) :
_text(text),
_compositionGraphicsDevice(compositionGraphicsDevice)
{
// Create the surface just big enough to hold the formatted text block.
DWRITE_TEXT_METRICS metrics;
FailFastOnFailure(text->GetMetrics(&metrics));
Windows::Foundation::Size surfaceSize = { metrics.width, metrics.height };
ComPtr<ICompositionDrawingSurface> drawingSurface;
FailFastOnFailure(_compositionGraphicsDevice->CreateDrawingSurface(
surfaceSize,
DirectXPixelFormat::DirectXPixelFormat_B8G8R8A8UIntNormalized,
DirectXAlphaMode::DirectXAlphaMode_Ignore,
&drawingSurface));
// Cache the interop pointer, since that's what we always use.
FailFastOnFailure(drawingSurface.As(&_drawingSurfaceInterop));
// Draw the text
DrawText();
// If the rendering device is lost, the application will recreate and replace it. We then
// own redrawing our pixels.
FailFastOnFailure(_compositionGraphicsDevice->add_RenderingDeviceReplaced(
Callback<RenderingDeviceReplacedEventHandler>([this](
ICompositionGraphicsDevice* source, IRenderingDeviceReplacedEventArgs* args)
-> HRESULT
{
// Draw the text again
DrawText();
return S_OK;
}).Get(),
&_deviceReplacedEventToken));
}
~SampleText()
{
FailFastOnFailure(_compositionGraphicsDevice->remove_RenderingDeviceReplaced(
_deviceReplacedEventToken));
}
// Return the underlying surface to the caller
ComPtr<ICompositionSurface> get_Surface()
{
// To the caller, the fact that we have a drawing surface is an implementation detail.
// Return the base interface instead
ComPtr<ICompositionSurface> surface;
FailFastOnFailure(_drawingSurfaceInterop.As(&surface));
return surface;
}
private:
// We may detect device loss on BeginDraw calls. This helper handles this condition or other
// errors.
bool CheckForDeviceRemoved(HRESULT hr)
{
if (SUCCEEDED(hr))
{
// Everything is fine -- go ahead and draw
return true;
}
else 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;
}
else
{
// Any other error is unexpected and, therefore, fatal
FailFast();
}
}
// 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).
ComPtr<ID2D1DeviceContext> d2dDeviceContext;
POINT offset;
if (CheckForDeviceRemoved(_drawingSurfaceInterop->BeginDraw(nullptr,
__uuidof(ID2D1DeviceContext), &d2dDeviceContext, &offset)))
{
// 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.
ComPtr<ID2D1SolidColorBrush> brush;
FailFastOnFailure(d2dDeviceContext->CreateSolidColorBrush(
D2D1::ColorF(D2D1::ColorF::Black, 1.0f), &brush));
// 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(offset.x, offset.y), _text.Get(),
brush.Get());
// Our update is done. EndDraw never indicates rendering device removed, so any
// failure here is unexpected and, therefore, fatal.
FailFastOnFailure(_drawingSurfaceInterop->EndDraw());
}
}
};
class SampleApp
{
ComPtr<ICompositor> _compositor;
ComPtr<ID2D1Device> _d2dDevice;
ComPtr<ICompositionGraphicsDevice> _compositionGraphicsDevice;
std::vector<ComPtr<SampleText>> _textSurfaces;
public:
// Run once when the application starts up
void Initialize(ICompositor* compositor)
{
// Cache the compositor (created outside of this method)
_compositor = compositor;
// Create a Direct2D device (helper implementation not shown here)
FailFastOnFailure(CreateDirect2DDevice(&_d2dDevice));
// To create a composition graphics device, we need to QI for another interface
ComPtr<ICompositorInterop> compositorInterop;
FailFastOnFailure(_compositor.As(&compositorInterop));
// Create a graphics device backed by our D3D device
FailFastOnFailure(compositorInterop->CreateGraphicsDevice(
_d2dDevice.Get(),
&_compositionGraphicsDevice));
}
// Called when Direct3D signals the device lost event
void OnDirect3DDeviceLost()
{
// Create a new device
FailFastOnFailure(CreateDirect2DDevice(_d2dDevice.ReleaseAndGetAddressOf()));
// Restore our composition graphics device to good health
ComPtr<ICompositionGraphicsDeviceInterop> compositionGraphicsDeviceInterop;
FailFastOnFailure(_compositionGraphicsDevice.As(&compositionGraphicsDeviceInterop));
FailFastOnFailure(compositionGraphicsDeviceInterop->SetRenderingDevice(_d2dDevice.Get()));
}
// Create a surface that is asynchronously filled with an image
ComPtr<ICompositionSurface> CreateSurfaceFromTextLayout(IDWriteTextLayout* text)
{
// Create our wrapper object that will handle downloading and decoding the image (assume
// throwing new here)
SampleText* textSurface = new SampleText(text, _compositionGraphicsDevice.Get());
// Keep our image alive
_textSurfaces.push_back(textSurface);
// The caller is only interested in the underlying surface
return textSurface->get_Surface();
}
// Create a visual that holds an image
ComPtr<IVisual> CreateVisualFromTextLayout(IDWriteTextLayout* text)
{
// Create a sprite visual
ComPtr<ISpriteVisual> spriteVisual;
FailFastOnFailure(_compositor->CreateSpriteVisual(&spriteVisual));
// The sprite visual needs a brush to hold the image
ComPtr<ICompositionSurfaceBrush> surfaceBrush;
FailFastOnFailure(_compositor->CreateSurfaceBrushWithSurface(
CreateSurfaceFromTextLayout(text).Get(),
&surfaceBrush));
// Associate the brush with the visual
ComPtr<ICompositionBrush> brush;
FailFastOnFailure(surfaceBrush.As(&brush));
FailFastOnFailure(spriteVisual->put_Brush(brush.Get()));
// Return the visual to the caller as the base class
ComPtr<IVisual> visual;
FailFastOnFailure(spriteVisual.As(&visual));
return visual;
}
private:
// This helper (implementation not shown here) creates a Direct2D device and registers
// for a device loss notification on the underlying Direct3D device. When that notification is
// raised, assume the OnDirect3DDeviceLost method is called.
HRESULT CreateDirect2DDevice(ID2D1Device** ppDevice);
};