重要
本文改编自适用于 Windows 应用 SDK/WinUI 的 UWP 文档。 本文中的一些代码示例仍引用特定于 UWP 的 API,例如 CoreWindowCoreApplicationViewCoreDispatcherWinUI/Windows 应用 SDK 中不可用的 API。 WinUI 等效项包括:
-
CoreWindow→使用 AppWindow 或 WinUI Window 类 -
CoreDispatcher→使用 DispatcherQueue -
CoreApplicationView→使用 窗口 或 AppWindow
合成互操作接口(例如 ICompositorInterop、ICompositionDrawingSurfaceInterop 等)与Microsoft.UI.Composition对象的工作方式相同。
Microsoft.UI.Composition API 提供 ICompositorInterop、 ICompositionDrawingSurfaceInterop 和 ICompositionGraphicsDeviceInterop 本机互操作接口,允许将内容直接移动到 compositor 中。
本机互操作围绕由 DirectX 纹理支持的表面对象进行结构化。 图面是从名为 CompositionGraphicsDevice 的工厂对象创建的。 此对象由基础 Direct2D 或 Direct3D 设备对象提供支持,该对象用于为图面分配视频内存。 合成 API 永远不会创建基础 DirectX 设备。 应用程序负责创建一个并将其传递给 CompositionGraphicsDevice 对象。 应用程序一次可以创建多个 CompositionGraphicsDevice 对象,并且它可以使用与多个 CompositionGraphicsDevice 对象的呈现设备相同的 DirectX 设备。
创建图面
每个 CompositionGraphicsDevice 用作表面工厂。 每个图面都以初始大小(可能为 0,0)创建,但没有有效的像素。 处于初始状态的图面可能会立即在视觉树中被使用,例如,通过 CompositionSurfaceBrush 和 SpriteVisual,但在初始状态下,该图面对屏幕输出没有影响。 尽管指定的 alpha 模式是“不透明”,但在所有情况下,它是完全透明的。
有时,DirectX 设备可能呈现为不可用。 如果应用程序将无效参数传递给某些 DirectX API,或者图形适配器由系统重置,或者驱动程序更新,则可能会发生这种情况。 Direct3D 具有一个 API,如果设备因任何原因丢失,应用程序可以使用该 API 以异步方式发现。 当 DirectX 设备丢失时,应用程序必须放弃它,创建一个新设备,并将其传递给以前与错误的 DirectX 设备关联的任何 CompositionGraphicsDevice 对象。
将像素加载到图面中
若要将像素加载到图面中,应用程序必须调用 BeginDraw 方法,该方法返回表示纹理或 Direct2D 上下文的 DirectX 接口,具体取决于应用程序请求的内容。 然后,应用程序必须将像素呈现或上传到该纹理中。 应用程序完成后,它必须调用 EndDraw 方法。 此时只有新像素可用于图像合成,但在下次提交视觉树的所有更改之前,它们仍不会在屏幕上显示。 如果在调用 EndDraw 之前提交可视化树,则正在进行中的更新在屏幕上不可见,并且图面继续显示它在 BeginDraw 之前的内容。 调用 EndDraw 时,BeginDraw 返回的纹理或 Direct2D 上下文指针将失效。 应用程序不应缓存 EndDraw 调用之外的该指针。
对于任何特定的 CompositionGraphicsDevice,应用程序一次只能在一个图面上调用 BeginDraw。 调用 BeginDraw 后,应用程序必须在该图面上调用 EndDraw ,然后才能在另一个图面上调用 BeginDraw 。 由于 API 是敏捷的,因此,如果应用程序希望从多个工作线程执行呈现,则应用程序负责同步这些调用。 如果应用程序想要中断呈现一个图面并暂时切换到另一个图面,则应用程序可以使用 SuspendDraw 方法。 这允许另一个 BeginDraw 成功,但不使第一个表面更新可用于屏幕上的合成。 这样,应用程序就可以以事务方式执行多个更新。 暂停图面后,应用程序可以通过调用 ResumeDraw 方法继续更新,也可以声明更新是通过调用 EndDraw 完成的。 这意味着任何给定 CompositionGraphicsDevice 一次只能主动更新一个图面。 每个图形设备都独立于其他图形设备保持此状态,因此,如果应用程序属于不同的图形设备,应用程序可以同时呈现到两个图面。 但是,这排除了这两个图面的视频内存不能合并在一起,因此,内存利用效率较低。
如果应用程序执行错误操作(例如传递无效参数,或者在调用 EndDraw 之前在另一个图面上调用 BeginDraw),BeginDraw、SuspendDraw、ResumeDraw 和 EndDraw 方法将返回失败。 这些类型的故障表示应用程序 bug,因此,预期是采用快速失败策略来处理它们。 如果基础 DirectX 设备丢失,BeginDraw 也可能返回故障。 此失败并不致命,因为应用程序可以重新创建其 DirectX 设备,然后重试。 因此,应用程序应通过跳过呈现来处理设备丢失。 如果 BeginDraw 由于任何原因而失败,应用程序也不应调用 EndDraw,因为第一个开始永远不会成功。
滚动
出于性能原因,当应用程序调用 BeginDraw 时,不保证返回的纹理的内容是图面的先前内容。 应用程序必须假定内容是随机的,因此,应用程序必须确保所有像素都经过触摸,无论是在呈现之前清除图面还是绘制足够的不透明内容来覆盖整个更新的矩形。 这与纹理指针仅在 BeginDraw 和 EndDraw 调用之间有效这一事实相结合,使得应用程序无法将以前的内容从图面中复制出来。 因此,我们提供了一种 滚动 方法,它允许应用程序执行相同表面像素复制。
C++/WinRT 用法示例
下面的代码示例演示了 Windows 应用 SDK 的上下文中组合互操作的场景。 该示例将来自基于 Windows 运行时的 Microsoft.UI.Composition 表面区域的类型与互操作头文件中的类型组合在一起,并结合使用基于 COM 的 DirectWrite 和 Direct2D API 的代码来呈现文本。 该示例使用 BeginDraw 和 EndDraw 使这些技术之间的互操作变得无缝。 该示例使用 DirectWrite 来布局文本,然后使用 Direct2D 来呈现文本。 合成图形设备在初始化时直接接受 Direct2D 设备。 这允许 BeginDraw 返回 ID2D1DeviceContext 接口指针,这比让应用程序在每个绘图操作中创建 Direct2D 上下文来包装返回的 ID3D11Texture2D 接口要高效得多。
若要试用下面的 C++/WinRT 代码示例,请先在 Visual Studio 中创建新的 WinUI 应用项目(有关要求,请参阅 Visual Studio 对 C++/WinRT 的支持)。 将你的 pch.h 和 App.cpp 源代码文件的内容替换为下面的代码列表,然后生成并运行。 应用程序在透明背景上以黑色文本呈现字符串“Hello, World!” 。
// 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>());
}