Interoperação nativa de composição com DirectX e Direct2D
A API Windows.UI.Composition fornece as interfaces de interoperação nativas ICompositorInterop, ICompositionDrawingSurfaceInterop e ICompositionGraphicsDeviceInterop, permitindo que o conteúdo seja movido diretamente para o compositor.
A interoperação nativa é estruturada em torno de objetos de superfície que são apoiados por texturas DirectX. As superfícies são criadas a partir de um objeto de fábrica chamado CompositionGraphicsDevice. Esse objeto é apoiado por um objeto de dispositivo Direct2D ou Direct3D subjacente, que ele usa para alocar memória de vídeo para superfícies. A API de composição nunca cria o dispositivo DirectX subjacente. É responsabilidade do aplicativo criar um e passá-lo para o objeto CompositionGraphicsDevice . Um aplicativo pode criar mais de um objeto CompositionGraphicsDevice por vez e pode usar o mesmo dispositivo DirectX que o dispositivo de renderização para vários objetos CompositionGraphicsDevice .
Criando uma superfície
Cada CompositionGraphicsDevice serve como uma fábrica de superfície. Cada superfície é criada com um tamanho inicial (que pode ser 0,0), mas sem pixels válidos. Uma superfície em seu estado inicial pode ser consumida imediatamente em uma árvore visual, por exemplo, por meio de um CompositionSurfaceBrush e um SpriteVisual, mas em seu estado inicial a superfície não tem efeito na saída da tela. É, para todos os efeitos, totalmente transparente, mesmo que o modo alfa especificado seja "opaco".
Ocasionalmente, os dispositivos DirectX podem ser inutilizados. Isso pode acontecer, entre outros motivos, se o aplicativo passar argumentos inválidos para determinadas APIs do DirectX, se o adaptador gráfico for redefinido pelo sistema ou se o driver for atualizado. O Direct3D tem uma API que um aplicativo pode usar para descobrir, de forma assíncrona, se o dispositivo for perdido por qualquer motivo. Quando um dispositivo DirectX é perdido, o aplicativo deve descartá-lo, criar um novo e passá-lo para qualquer objeto CompositionGraphicsDevice anteriormente associado ao dispositivo DirectX inválido.
Carregando pixels em uma superfície
Para carregar pixels na superfície, o aplicativo deve chamar o método BeginDraw , que retorna uma interface DirectX que representa uma textura ou contexto Direct2D, dependendo do que o aplicativo solicita. Em seguida, o aplicativo deve renderizar ou carregar pixels nessa textura. Quando o aplicativo estiver pronto, ele deverá chamar o método EndDraw . Somente nesse ponto os novos pixels estão disponíveis para composição, mas eles ainda não aparecem na tela até a próxima vez que todas as alterações na árvore visual forem confirmadas. Se a árvore visual for confirmada antes de EndDraw ser chamado, a atualização que está em andamento não ficará visível na tela e a superfície continuará a exibir o conteúdo que tinha antes de BeginDraw. Quando EndDraw é chamado, a textura ou o ponteiro de contexto Direct2D retornado por BeginDraw é invalidado. Um aplicativo nunca deve armazenar em cache esse ponteiro além da chamada EndDraw .
O aplicativo só pode chamar BeginDraw em uma superfície por vez, para qualquer CompositionGraphicsDevice fornecido. Depois de chamar BeginDraw, o aplicativo deve chamar EndDraw nessa superfície antes de chamar BeginDraw em outra. Como a API é ágil, o aplicativo é responsável por sincronizar essas chamadas se desejar executar a renderização de vários threads de trabalho. Se um aplicativo quiser interromper a renderização de uma superfície e alternar para outra temporariamente, o aplicativo poderá usar o método SuspendDraw . Isso permite que outro BeginDraw seja bem-sucedido, mas não disponibiliza a primeira atualização de superfície para composição na tela. Isso permite que o aplicativo execute várias atualizações de maneira transacional. Depois que uma superfície é suspensa, o aplicativo pode continuar a atualização chamando o método ResumeDraw ou pode declarar que a atualização foi feita chamando EndDraw. Isso significa que apenas uma superfície pode ser atualizada ativamente por vez para qualquer CompositionGraphicsDevice. Cada dispositivo gráfico mantém esse estado independentemente dos outros, portanto, um aplicativo pode renderizar em duas superfícies simultaneamente se elas pertencerem a dispositivos gráficos diferentes. No entanto, isso impede que a memória de vídeo para essas duas superfícies seja agrupada e, como tal, é menos eficiente em termos de memória.
Os métodos BeginDraw, SuspendDraw, ResumeDraw e EndDraw retornarão falhas se o aplicativo executar uma operação incorreta (como passar argumentos inválidos ou chamar BeginDraw em uma superfície antes de chamar EndDraw em outra). Esses tipos de falhas representam bugs de aplicativos e, como tal, a expectativa é que eles sejam tratados com uma falha rápida. BeginDraw também pode retornar uma falha se o dispositivo DirectX subjacente for perdido. Essa falha não é fatal, pois o aplicativo pode recriar seu dispositivo DirectX e tentar novamente. Dessa forma, espera-se que o aplicativo lide com a perda de dispositivos simplesmente ignorando a renderização. Se BeginDraw falhar por qualquer motivo, o aplicativo também não deverá chamar EndDraw, pois o início nunca foi bem-sucedido em primeiro lugar.
Rolagem
Por motivos de desempenho, quando um aplicativo chama BeginDraw , o conteúdo da textura retornada não é garantido como o conteúdo anterior da superfície. O aplicativo deve assumir que o conteúdo é aleatório e, como tal, o aplicativo deve garantir que todos os pixels sejam tocados, limpando a superfície antes da renderização ou desenhando conteúdo opaco suficiente para cobrir todo o retângulo atualizado. Isso, combinado com o fato de que o ponteiro de textura só é válido entre chamadas BeginDraw e EndDraw , torna impossível para o aplicativo copiar o conteúdo anterior da superfície. Por esse motivo, oferecemos um método Scroll , que permite que o aplicativo execute uma cópia de pixel na mesma superfície.
Exemplo de uso do C++/WinRT
O exemplo de código a seguir ilustra um cenário de interoperação. O exemplo combina tipos da área de superfície baseada no Tempo de Execução do Windows do Windows Composition, juntamente com tipos dos cabeçalhos de interoperabilidade e o código que renderiza o texto usando as APIs DirectWrite e Direct2D baseadas em COM. O exemplo usa BeginDraw e EndDraw para simplificar a interoperação entre essas tecnologias. O exemplo usa DirectWrite para dispor o texto e, em seguida, usa Direct2D para renderizá-lo. O dispositivo gráfico de composição aceita o dispositivo Direct2D diretamente no momento da inicialização. Isso permite que BeginDraw retorne um ponteiro de interface ID2D1DeviceContext , que é consideravelmente mais eficiente do que fazer com que o aplicativo crie um contexto Direct2D para encapsular uma interface ID3D11Texture2D retornada em cada operação de desenho.
Para experimentar o exemplo de código do C++/WinRT abaixo, primeiro crie um novo projeto de Aplicativo Principal (C++/WinRT) no Visual Studio (para obter os requisitos, consulte Suporte do Visual Studio para C++/WinRT). Substitua o conteúdo dos arquivos pch.h
e App.cpp
do código-fonte pelas listagens de código abaixo e, em seguida, compile e execute. O aplicativo renderiza a cadeia de caracteres "Hello, World!" em texto preto em um plano de fundo transparente.
// 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>());
}
Exemplo de uso do C++/CX
Observação
Este exemplo de código existe para ajudá-lo a manter seu aplicativo C++/CX. Entretanto, recomendamos usar C++/WinRT para novos aplicativos. C++/WinRT é uma projeção de linguagem C++17 completamente moderna e padrão para APIs do WinRT (Windows Runtime), implementada como uma biblioteca com base em cabeçalho e arquivo, projetada para fornecer acesso de primeira classe à API moderna do Windows.
O exemplo de código C++/CX abaixo omite as partes DirectWrite e Direct2D do exemplo.
//------------------------------------------------------------------------------
//
// 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);
};