Interoperabilità nativa della composizione con DirectX e Direct2D
L'API Windows.UI.Composition fornisce le interfacce di interoperabilità native ICompositorInterop, ICompositionDrawingSurfaceInterop e ICompositionGraphicsDeviceInterop che consentono di spostare il contenuto direttamente nel compositor.
L'interoperabilità nativa è strutturata intorno agli oggetti superficie supportati da trame DirectX. Le superfici vengono create da un oggetto factory denominato CompositionGraphicsDevice. Questo oggetto è supportato da un oggetto dispositivo Direct2D o Direct3D sottostante, usato per allocare memoria video per le superfici. L'API di composizione non crea mai il dispositivo DirectX sottostante. È responsabilità dell'applicazione crearne uno e passarlo all'oggetto CompositionGraphicsDevice. Un'applicazione può creare più oggetti CompositionGraphicsDevice alla volta e può usare lo stesso dispositivo DirectX del dispositivo di rendering per più oggetti CompositionGraphicsDevice.
Creazione di una superficie
Ogni CompositionGraphicsDevice funge da factory di superficie. Ogni superficie viene creata con una dimensione iniziale (che può essere 0,0), ma non con pixel validi. Una superficie nello stato iniziale può essere utilizzata immediatamente in una struttura ad albero visuale, ad esempio tramite CompositionSurfaceBrush e SpriteVisual, ma nello stato iniziale la superficie non ha alcun effetto sull'output dello schermo. È, in pratica, completamente trasparente, anche se la modalità alfa specificata è "opaca".
In alcuni casi, il rendering dei dispositivi DirectX potrebbe risultare inutilizzabile. Ciò può verificarsi per vari motivi, ad esempio se l'applicazione passa argomenti non validi a determinate API DirectX, se la scheda grafica viene reimpostata dal sistema o se il driver viene aggiornato. Direct3D ha un'API che un'applicazione può usare per individuare, in modo asincrono, se il dispositivo viene perso per qualsiasi motivo. Quando un dispositivo DirectX viene perso, l'applicazione deve eliminarlo, crearne uno nuovo e passarlo a qualsiasi oggetto CompositionGraphicsDevice precedentemente associato al dispositivo DirectX non valido.
Caricamento di pixel in una superficie
Per caricare i pixel nella superficie, l'applicazione deve chiamare il metodo BeginDraw, il quale restituisce un'interfaccia DirectX che rappresenta una trama o un contesto Direct2D, a seconda delle richieste dell'applicazione. L'applicazione deve quindi eseguire il rendering o caricare i pixel in tale trama. Al termine dell'applicazione, deve chiamare il metodo EndDraw. Solo a quel punto i nuovi pixel saranno disponibili per la composizione, ma non verranno visualizzati sullo schermo fino alla successiva esecuzione del commit di tutte le modifiche apportate alla struttura ad albero visuale. Se viene eseguito il commit della struttura ad albero visuale prima della chiamata di EndDraw, l'aggiornamento in corso non è visibile sullo schermo e la superficie continua a visualizzare il contenuto che aveva prima di BeginDraw. Quando viene chiamato EndDraw, il puntatore di contesto Direct2D o trama restituito da BeginDraw viene invalidato. Un'applicazione non deve mai memorizzare nella cache il puntatore oltre la chiamata EndDraw.
L'applicazione può chiamare BeginDraw solo su una superficie alla volta, per qualsiasi CompositionGraphicsDevice specificato. Dopo aver chiamato BeginDraw, l'applicazione deve chiamare EndDraw su tale superficie prima di chiamare BeginDraw su un'altra. Poiché l'API è agile, l'applicazione è responsabile della sincronizzazione di queste chiamate se vuole eseguire il rendering da più thread di lavoro. Se un'applicazione vuole interrompere il rendering di una superficie e passare temporaneamente a un'altra, l'applicazione può usare il metodo SuspendDraw. Ciò consente l'esito positivo di un altro BeginDraw, ma non rende disponibile il primo aggiornamento della superficie per la composizione sullo schermo. Ciò consente all'applicazione di eseguire più aggiornamenti in modo transazionale. Una volta sospesa una superficie, l'applicazione può continuare l'aggiornamento chiamando il metodo ResumeDraw oppure può dichiarare che l'aggiornamento viene eseguito chiamando EndDraw. Ciò significa che è possibile aggiornare attivamente una sola superficie alla volta per ogni oggetto CompositionGraphicsDevice specificato. Ogni dispositivo grafico mantiene questo stato indipendentemente dagli altri, quindi un'applicazione può eseguire il rendering su due superfici contemporaneamente se appartengono a dispositivi grafici diversi. Tuttavia, ciò impedisce di raggruppare la memoria video per queste due superfici e, di conseguenza, è meno efficiente per la memoria.
I metodi BeginDraw, SuspendDraw, ResumeDraw e EndDraw restituiscono errori se l'applicazione esegue un'operazione non corretta, ad esempio passando argomenti non validi o chiamando BeginDraw su una superficie prima di chiamare EndDraw su un'altra. Questi tipi di errori rappresentano bug dell'applicazione e, di conseguenza, si prevede che vengano gestiti con un errore rapido. BeginDraw può anche restituire un errore se il dispositivo DirectX sottostante viene perso. Questo errore non è irreversibile perché l'applicazione può ricreare il dispositivo DirectX e riprovare. Di conseguenza, si prevede che l'applicazione gestisca la perdita di dispositivi ignorando semplicemente il rendering. Se BeginDraw ha esito negativo per qualsiasi motivo, l'applicazione non deve anche chiamare EndDraw, perché l'inizio non ha mai avuto esito positivo al primo posto.
Scorrimento in corso
Per motivi di prestazioni, quando un'applicazione chiama BeginDraw il contenuto della trama restituita non è garantito che sia il contenuto precedente della superficie. L'applicazione deve presupporre che il contenuto sia casuale e, di conseguenza, l'applicazione deve assicurarsi che tutti i pixel vengano toccati cancellando la superficie prima del rendering o disegnando un contenuto opaco sufficiente per coprire l'intero rettangolo aggiornato. Ciò, combinato con il fatto che il puntatore di trama è valido solo tra le chiamate BeginDraw e EndDraw, rende impossibile all'applicazione copiare il contenuto precedente dalla superficie. Per questo motivo, offriamo un metodo Scroll, che consente all'applicazione di eseguire una copia pixel della stessa superficie.
Esempio di utilizzo di C++/WinRT
Nell'esempio di codice seguente viene illustrato uno scenario di interoperabilità. L'esempio combina i tipi dell'area di superficie basata su Windows Runtime di Composizione di Windows, insieme ai tipi delle intestazioni di interoperabilità e il codice che esegue il rendering del testo usando le API DirectWrite e Direct2D basate su COM. L'esempio usa BeginDraw e EndDraw per semplificare l'interoperabilità tra queste tecnologie. L'esempio usa DirectWrite per disporre il testo e quindi usa Direct2D per eseguirne il rendering. Il dispositivo grafico di composizione accetta il dispositivo Direct2D direttamente in fase di inizializzazione. Ciò consente a BeginDraw di restituire un puntatore all'interfaccia ID2D1DeviceContext, che è notevolmente più efficiente rispetto alla creazione di un contesto Direct2D per eseguire il wrapping di un'interfaccia ID3D11Texture2D restituita in ogni operazione di disegno.
Per provare l'esempio di codice C++/WinRT riportato di seguito, creare prima di tutto un nuovo progetto Core App (C++/WinRT) in Visual Studio (per i requisiti, vedere Supporto di Visual Studio per C++/WinRT). Sostituire il contenuto dei file pch.h
e dei file App.cpp
del codice sorgente con gli elenchi di codice riportati di seguito, quindi compilare ed eseguire. L'applicazione esegue il rendering della stringa "Hello, World!" in testo nero su uno sfondo trasparente.
// 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>());
}
Esempio di utilizzo di C++/CX
Nota
Questo esempio di codice esiste per facilitare la gestione dell'applicazione C++/CX. Ma consigliamo di usare C++/WinRT per le nuove applicazioni. C++/WinRT è una proiezione del linguaggio C++ 17 interamente standard e moderna per le API di Windows Runtime (WinRT), implementata come libreria basata su file di intestazione e progettata per fornirti accesso privilegiato alla moderna API di Windows.
L'esempio di codice C++/CX seguente omette le parti DirectWrite e Direct2D dell'esempio.
//------------------------------------------------------------------------------
//
// 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);
};