Поддержка ориентации экрана (DirectX и C++)
Приложение универсальная платформа Windows (UWP) может поддерживать несколько ориентаций экрана при обработке события DisplayInformation::OrientationChanged. Здесь мы обсудим рекомендации по обработке поворота экрана в приложении UWP DirectX, чтобы графическое оборудование устройства Windows 10 эффективно и эффективно использовалось.
Прежде чем начать, помните, что графическое оборудование всегда выводит данные пикселей таким же образом, независимо от ориентации устройства. Устройства с Windows 10 могут определять текущую ориентацию дисплея (с некоторыми датчиками или с переключателем программного обеспечения) и разрешать пользователям изменять параметры отображения. Из-за этого Windows 10 обрабатывает поворот изображений, чтобы убедиться, что они являются "вертикальными" на основе ориентации устройства. По умолчанию приложение получает уведомление о том, что что-то изменилось в ориентации, например размер окна. В этом случае Windows 10 немедленно поворачивает изображение для окончательного отображения. Для трех из четырех конкретных ориентаций экрана (рассмотренных далее), Windows 10 использует дополнительные графические ресурсы и вычисления для отображения окончательного изображения.
Для приложений DirectX UWP объект DisplayInformation предоставляет базовые данные ориентации отображения, которые может запрашивать ваше приложение. Ориентация по умолчанию — альбомная, где ширина пикселя дисплея больше высоты; альтернативная ориентация — книжная, где экран поворачивается 90 градусов в любом направлении, а ширина становится меньше высоты.
Windows 10 определяет четыре конкретных режима ориентации дисплея:
- Альбомная ориентация — ориентация отображения по умолчанию для Windows 10 и считается базовым или удостоверяемым углом поворота (0 градусов).
- Портрет — дисплей был повернут по часовой стрелке 90 градусов (или по часовой стрелке 270 градусов).
- Альбомная, перевернутая — дисплей был повернут на 180 градусов (перевернутый вниз).
- Портрет, перевернутый — дисплей был повернут по часовой стрелке 270 градусов (или по часовой стрелке 90 градусов).
Когда отображение поворачивается с одной ориентации на другую, Windows 10 внутренне выполняет операцию поворота, чтобы выровнять рисованное изображение с новой ориентацией, и пользователь видит вертикальное изображение на экране.
Кроме того, в Windows 10 отображаются анимации автоматического перехода для создания плавного взаимодействия с пользователем при переходе с одной ориентации на другую. По мере смены ориентации дисплея пользователь видит эти сдвиги в виде фиксированного масштаба и анимации поворота отображаемого изображения экрана. Время выделяется windows 10 приложению для макета в новой ориентации.
В целом это общий процесс обработки изменений в ориентации экрана:
- Используйте сочетание значений границ окна и данных ориентации отображения, чтобы сохранить цепочку буферов в соответствии с собственной ориентацией дисплея устройства.
- Уведомите Windows 10 о ориентации цепочки буферов с помощью IDXGISwapChain1::SetRotation.
- Измените код отрисовки, чтобы создать изображения, выровненные с ориентацией пользователя устройства.
Изменение размера цепочки буферов и предварительная смена его содержимого
Чтобы выполнить базовый размер дисплея и предварительно повернуть его содержимое в приложении UWP DirectX, выполните следующие действия:
- Обработка события DisplayInformation::OrientationChanged.
- Измените размер цепочки буферов на новые размеры окна.
- Вызовите IDXGISwapChain1::SetRotation , чтобы задать ориентацию цепочки буферов.
- Повторно создайте все зависящие от размера окна ресурсы, такие как целевые объекты отрисовки и другие буферы данных пикселей.
Теперь рассмотрим эти шаги немного более подробно.
Первым шагом является регистрация обработчика для события DisplayInformation::OrientationChanged . Это событие возникает в приложении при каждом изменении ориентации экрана, например при повороте дисплея.
Для обработки события DisplayInformation::OrientationChanged необходимо подключить обработчик для DisplayInformation::OrientationChanged в требуемом методе SetWindow, который является одним из методов интерфейса IFrameworkView, который должен реализовать поставщик представлений.
В этом примере кода обработчик событий для DisplayInformation::OrientationChanged — это метод OnOrientationChanged. При вызове DisplayInformation::OrientationChanged он в свою очередь вызывает метод с именем SetCurrentOrientation , который затем вызывает CreateWindowSizeDependentResources.
void App::SetWindow(CoreWindow^ window)
{
// ... Other UI event handlers assigned here ...
currentDisplayInformation->OrientationChanged +=
ref new TypedEventHandler<DisplayInformation^, Object^>(this, &App::OnOrientationChanged);
// ...
}
}
void App::OnOrientationChanged(DisplayInformation^ sender, Object^ args)
{
m_deviceResources->SetCurrentOrientation(sender->CurrentOrientation);
m_main->CreateWindowSizeDependentResources();
}
// This method is called in the event handler for the OrientationChanged event.
void DX::DeviceResources::SetCurrentOrientation(DisplayOrientations currentOrientation)
{
if (m_currentOrientation != currentOrientation)
{
m_currentOrientation = currentOrientation;
CreateWindowSizeDependentResources();
}
}
Затем измените размер цепочки буферов для новой ориентации экрана и подготовите ее к смене содержимого графического конвейера при выполнении отрисовки. В этом примере DirectXBase::CreateWindowSizeDependentResources — это метод, который обрабатывает вызов IDXGISwapChain::ResizeBuffers, настройка трехмерной и трехмерной матрицы поворота, вызов SetRotation и восстановление ресурсов.
void DX::DeviceResources::CreateWindowSizeDependentResources()
{
// Clear the previous window size specific context.
ID3D11RenderTargetView* nullViews[] = {nullptr};
m_d3dContext->OMSetRenderTargets(ARRAYSIZE(nullViews), nullViews, nullptr);
m_d3dRenderTargetView = nullptr;
m_d2dContext->SetTarget(nullptr);
m_d2dTargetBitmap = nullptr;
m_d3dDepthStencilView = nullptr;
m_d3dContext->Flush();
// Calculate the necessary render target size in pixels.
m_outputSize.Width = DX::ConvertDipsToPixels(m_logicalSize.Width, m_dpi);
m_outputSize.Height = DX::ConvertDipsToPixels(m_logicalSize.Height, m_dpi);
// Prevent zero size DirectX content from being created.
m_outputSize.Width = max(m_outputSize.Width, 1);
m_outputSize.Height = max(m_outputSize.Height, 1);
// The width and height of the swap chain must be based on the window's
// natively-oriented width and height. If the window is not in the native
// orientation, the dimensions must be reversed.
DXGI_MODE_ROTATION displayRotation = ComputeDisplayRotation();
bool swapDimensions = displayRotation == DXGI_MODE_ROTATION_ROTATE90 || displayRotation == DXGI_MODE_ROTATION_ROTATE270;
m_d3dRenderTargetSize.Width = swapDimensions ? m_outputSize.Height : m_outputSize.Width;
m_d3dRenderTargetSize.Height = swapDimensions ? m_outputSize.Width : m_outputSize.Height;
if (m_swapChain != nullptr)
{
// If the swap chain already exists, resize it.
HRESULT hr = m_swapChain->ResizeBuffers(
2, // Double-buffered swap chain.
lround(m_d3dRenderTargetSize.Width),
lround(m_d3dRenderTargetSize.Height),
DXGI_FORMAT_B8G8R8A8_UNORM,
0
);
if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET)
{
// If the device was removed for any reason, a new device and swap chain will need to be created.
HandleDeviceLost();
// Everything is set up now. Do not continue execution of this method. HandleDeviceLost will reenter this method
// and correctly set up the new device.
return;
}
else
{
DX::ThrowIfFailed(hr);
}
}
else
{
// Otherwise, create a new one using the same adapter as the existing Direct3D device.
DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {0};
swapChainDesc.Width = lround(m_d3dRenderTargetSize.Width); // Match the size of the window.
swapChainDesc.Height = lround(m_d3dRenderTargetSize.Height);
swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; // This is the most common swap chain format.
swapChainDesc.Stereo = false;
swapChainDesc.SampleDesc.Count = 1; // Don't use multi-sampling.
swapChainDesc.SampleDesc.Quality = 0;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.BufferCount = 2; // Use double-buffering to minimize latency.
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // All UWP apps must use this SwapEffect.
swapChainDesc.Flags = 0;
swapChainDesc.Scaling = DXGI_SCALING_NONE;
swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_IGNORE;
// This sequence obtains the DXGI factory that was used to create the Direct3D device above.
ComPtr<IDXGIDevice3> dxgiDevice;
DX::ThrowIfFailed(
m_d3dDevice.As(&dxgiDevice)
);
ComPtr<IDXGIAdapter> dxgiAdapter;
DX::ThrowIfFailed(
dxgiDevice->GetAdapter(&dxgiAdapter)
);
ComPtr<IDXGIFactory2> dxgiFactory;
DX::ThrowIfFailed(
dxgiAdapter->GetParent(IID_PPV_ARGS(&dxgiFactory))
);
DX::ThrowIfFailed(
dxgiFactory->CreateSwapChainForCoreWindow(
m_d3dDevice.Get(),
reinterpret_cast<IUnknown*>(m_window.Get()),
&swapChainDesc,
nullptr,
&m_swapChain
)
);
// Ensure that DXGI does not queue more than one frame at a time. This both reduces latency and
// ensures that the application will only render after each VSync, minimizing power consumption.
DX::ThrowIfFailed(
dxgiDevice->SetMaximumFrameLatency(1)
);
}
// Set the proper orientation for the swap chain, and generate 2D and
// 3D matrix transformations for rendering to the rotated swap chain.
// Note the rotation angle for the 2D and 3D transforms are different.
// This is due to the difference in coordinate spaces. Additionally,
// the 3D matrix is specified explicitly to avoid rounding errors.
switch (displayRotation)
{
case DXGI_MODE_ROTATION_IDENTITY:
m_orientationTransform2D = Matrix3x2F::Identity();
m_orientationTransform3D = ScreenRotation::Rotation0;
break;
case DXGI_MODE_ROTATION_ROTATE90:
m_orientationTransform2D =
Matrix3x2F::Rotation(90.0f) *
Matrix3x2F::Translation(m_logicalSize.Height, 0.0f);
m_orientationTransform3D = ScreenRotation::Rotation270;
break;
case DXGI_MODE_ROTATION_ROTATE180:
m_orientationTransform2D =
Matrix3x2F::Rotation(180.0f) *
Matrix3x2F::Translation(m_logicalSize.Width, m_logicalSize.Height);
m_orientationTransform3D = ScreenRotation::Rotation180;
break;
case DXGI_MODE_ROTATION_ROTATE270:
m_orientationTransform2D =
Matrix3x2F::Rotation(270.0f) *
Matrix3x2F::Translation(0.0f, m_logicalSize.Width);
m_orientationTransform3D = ScreenRotation::Rotation90;
break;
default:
throw ref new FailureException();
}
//SDM: only instance of SetRotation
DX::ThrowIfFailed(
m_swapChain->SetRotation(displayRotation)
);
// Create a render target view of the swap chain back buffer.
ComPtr<ID3D11Texture2D> backBuffer;
DX::ThrowIfFailed(
m_swapChain->GetBuffer(0, IID_PPV_ARGS(&backBuffer))
);
DX::ThrowIfFailed(
m_d3dDevice->CreateRenderTargetView(
backBuffer.Get(),
nullptr,
&m_d3dRenderTargetView
)
);
// Create a depth stencil view for use with 3D rendering if needed.
CD3D11_TEXTURE2D_DESC depthStencilDesc(
DXGI_FORMAT_D24_UNORM_S8_UINT,
lround(m_d3dRenderTargetSize.Width),
lround(m_d3dRenderTargetSize.Height),
1, // This depth stencil view has only one texture.
1, // Use a single mipmap level.
D3D11_BIND_DEPTH_STENCIL
);
ComPtr<ID3D11Texture2D> depthStencil;
DX::ThrowIfFailed(
m_d3dDevice->CreateTexture2D(
&depthStencilDesc,
nullptr,
&depthStencil
)
);
CD3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc(D3D11_DSV_DIMENSION_TEXTURE2D);
DX::ThrowIfFailed(
m_d3dDevice->CreateDepthStencilView(
depthStencil.Get(),
&depthStencilViewDesc,
&m_d3dDepthStencilView
)
);
// Set the 3D rendering viewport to target the entire window.
m_screenViewport = CD3D11_VIEWPORT(
0.0f,
0.0f,
m_d3dRenderTargetSize.Width,
m_d3dRenderTargetSize.Height
);
m_d3dContext->RSSetViewports(1, &m_screenViewport);
// Create a Direct2D target bitmap associated with the
// swap chain back buffer and set it as the current target.
D2D1_BITMAP_PROPERTIES1 bitmapProperties =
D2D1::BitmapProperties1(
D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED),
m_dpi,
m_dpi
);
ComPtr<IDXGISurface2> dxgiBackBuffer;
DX::ThrowIfFailed(
m_swapChain->GetBuffer(0, IID_PPV_ARGS(&dxgiBackBuffer))
);
DX::ThrowIfFailed(
m_d2dContext->CreateBitmapFromDxgiSurface(
dxgiBackBuffer.Get(),
&bitmapProperties,
&m_d2dTargetBitmap
)
);
m_d2dContext->SetTarget(m_d2dTargetBitmap.Get());
// Grayscale text anti-aliasing is recommended for all UWP apps.
m_d2dContext->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);
}
После сохранения текущих значений высоты и ширины окна при следующем вызове этого метода преобразуйте значения независимых пикселей (DIP) устройства для границ отображения в пиксели. В примере вызывается ConvertDipsToPixels, которая является простой функцией, которая запускает этот код:
floor((dips * dpi / 96.0f) + 0.5f);
Добавьте значение 0,5f, чтобы обеспечить округление до ближайшего целого числа.
В стороне координаты CoreWindow всегда определяются в dips. Для Windows 10 и более ранних версий Windows DIP определяется как 1/96-й дюйм и соответствует определению ОС. Когда ориентация отображения поворачивается на книжный режим, приложение перевернет ширину и высоту CoreWindow, а целевой размер отрисовки (границы) должен измениться соответствующим образом. Так как координаты Direct3D всегда находятся в физических пикселях, необходимо преобразовать значения DIP CoreWindow в целые значения пикселей, прежде чем передавать эти значения в Direct3D, чтобы настроить цепочку буферов.
Мудреный процесс, вы выполняете немного больше работы, чем вы хотите, если вы просто измените размер цепочки буферов: вы фактически поворачиваете компоненты Direct2D и Direct3D изображения, прежде чем составить их для презентации, и вы сообщаете цепочке буферов, что вы отрисовали результаты в новой ориентации. Ниже приведены дополнительные сведения об этом процессе, как показано в примере кода для DX::D eviceResources::CreateWindowSizeDependentResources:
Определите новую ориентацию дисплея. Если экран перевернулся с альбомной на книжную или наоборот, переключите значения высоты и ширины, измененные с значений DIP на пиксели, конечно, для границ отображения.
Затем проверьте, была ли создана цепочка буферов. Если он не создан, создайте его путем вызова IDXGIFactory2::CreateSwapChainForCoreWindow. В противном случае измените размер буферов существующей цепочки буферов на новые измерения отображения путем вызова IDXGISwapchain:ResizeBuffers. Несмотря на то, что вам не нужно изменять размер цепочки буферов для события поворота , вы выводите содержимое, которое уже вращалось конвейером отрисовки, в конце концов, существуют другие события изменения размера, такие как события привязки и заливки, которые требуют изменения размера.
После этого задайте соответствующее преобразование матрицы 2-D или 3-D для применения к пикселям или вершинам (соответственно) в графическом конвейере при отрисовке их в цепочку буферов. У нас есть 4 возможных матриц поворота:
- ландшафт (DXGI_MODE_ROTATION_IDENTITY)
- портрет (DXGI_MODE_ROTATION_ROTATE270)
- альбомная, перевернутая (DXGI_MODE_ROTATION_ROTATE180)
- книжная, перевернутая (DXGI_MODE_ROTATION_ROTATE90)
Выбрана правильная матрица на основе данных, предоставленных Windows 10 (например, результатов DisplayInformation::OrientationChanged) для определения ориентации отображения, и она будет умножена на координаты каждого пикселя (Direct2D) или вершины (Direct3D) в сцене, эффективно поворачивая их, чтобы выровнять ориентацию экрана. (Обратите внимание, что в Direct2D источник экрана определяется как левый верхний угол, а в Direct3D источник определяется как логический центр окна.)
Дополнительные сведения о преобразованиях 2-D, используемых для поворота и их определении, см. в разделе "Определение матриц для поворота экрана" (2-D). Дополнительные сведения о трехмерных преобразованиях, используемых для поворота, см. в разделе "Определение матриц для поворота экрана" (3-D).
Теперь вот важный бит: вызовите IDXGISwapChain1::SetRotation и предоставьте его обновленной матрице поворота, как показано ниже:
m_swapChain->SetRotation(rotation);
Вы также сохраняете выбранную матрицу поворота, где метод отрисовки может получить его при вычислении новой проекции. Вы будете использовать эту матрицу при отрисовке окончательной проекции 3-D или составной макет 2-D. (Он не применяется автоматически для вас.)
После этого создайте новый целевой объект отрисовки для 3-D представления, а также новый буфер элементов глубины для представления. Задайте 3-D представление представления для вращаемой сцены путем вызова ID3D11DeviceContext:RSSetViewports.
Наконец, если у вас есть 2-D изображения для поворота или размещения, создайте целевой объект отрисовки 2-D в виде записываемого растрового изображения для измененной цепочки буферов с помощью ID2D1DeviceContext::CreateBitmapFromDxgiSurface и составной макет для обновленной ориентации. Задайте все свойства, необходимые для целевого объекта отрисовки, например режим защиты от псевдонима (как показано в примере кода).
Теперь представим цепочку буферов.
Уменьшение задержки поворота с помощью CoreWindowResizeManager
По умолчанию Windows 10 предоставляет короткое, но заметное окно времени для любого приложения независимо от модели приложения или языка, чтобы завершить поворот изображения. Тем не менее, вероятность того, что, когда приложение выполняет вычисление поворота с помощью одного из описанных здесь методов, оно будет сделано хорошо до закрытия этого периода времени. Вы хотите получить это время назад и завершить анимацию поворота, правильно? Вот где вступает CoreWindowResizeManager.
Вот как использовать CoreWindowResizeManager: при вызове события DisplayInformation::OrientationChanged вызов CoreWindowResizeManager::GetForCurrentView в обработчике для получения экземпляра CoreWindowResizeManager и при завершении и представлении макета новой ориентации вызовите NotifyLayoutCompleted Чтобы сообщить Windows, что оно может завершить анимацию поворота и отобразить экран приложения.
Вот как выглядит код в обработчике событий для DisplayInformation::OrientationChanged :
CoreWindowResizeManager^ resizeManager = Windows::UI::Core::CoreWindowResizeManager::GetForCurrentView();
// ... build the layout for the new display orientation ...
resizeManager->NotifyLayoutCompleted();
Когда пользователь поворачивает ориентацию дисплея, Windows 10 отображает анимацию независимо от приложения в качестве обратной связи с пользователем. Анимация выполняется в следующем порядке:
- Windows 10 сжимает исходный образ.
- Windows 10 содержит образ в течение времени, необходимого для перестроения нового макета. Это время, которое вы хотите уменьшить, потому что ваше приложение, вероятно, не нуждается во всем.
- По истечении срока действия окна макета или при получении уведомления о завершении макета Windows поворачивает изображение, а затем перекрестно увеличивается до новой ориентации.
Как показано в третьем маркере, когда приложение вызывает NotifyLayoutCompleted, Windows 10 останавливает окно времени ожидания, завершает анимацию поворота и возвращает элемент управления приложению, который теперь рисуется в новой ориентации отображения. Общий эффект заключается в том, что ваше приложение теперь чувствует себя немного больше жидкости и реагирования, и работает немного эффективнее!
Приложение A. Применение матриц для поворота экрана (2-D)
В примере кода в изменении размера цепочки буферов и предварительном повороте его содержимого (и в примере смены цепочки буферов DXGI), возможно, вы заметили, что у нас были отдельные матрицы поворота для выходных данных Direct2D и direct3D. Давайте рассмотрим 2-D матрицы, сначала.
Существует две причины, по которым нельзя применить одни и те же матрицы поворота к содержимому Direct2D и Direct3D:
В одной из них используются различные модели координат декартов. Direct2D использует праворучное правило, где координата Y увеличивается положительное значение, движущееся вверх от источника. Однако Direct3D использует правило слева, где координата y увеличивается в положительном значении вправо от источника. Результатом является источник координат экрана, расположенный в левом верхнем углу для Direct2D, в то время как источник экрана (плоскость проекции) находится в нижнем левом углу для Direct3D. (Дополнительные сведения см . в системах координат 3-D.)
Два, матрица поворота 3-D должна быть указана явным образом, чтобы избежать ошибок округления.
Цепочка буферов предполагает, что источник расположен в левом нижнем углу, поэтому необходимо выполнить поворот для выравнивания правой системы координат Direct2D с левой рукой, используемой цепочкой буферов. В частности, вы изменяете положение изображения под новой левой ориентацией, умножая матрицу поворота с матрицей перевода для источника вращающейся системы координат и преобразуя изображение из пространства координат CoreWindow в пространство координат цепочки буферов. Приложение также должно последовательно применять это преобразование, если целевой объект отрисовки Direct2D подключен к цепочке буферов. Однако если приложение рисует промежуточные поверхности, которые не связаны непосредственно с цепочкой буферов, не применяйте это преобразование пространства координат.
Код для выбора правильной матрицы из четырех возможных поворотов может выглядеть следующим образом (учитывайте перевод в новый источник системы координат):
// Set the proper orientation for the swap chain, and generate 2D and
// 3D matrix transformations for rendering to the rotated swap chain.
// Note the rotation angle for the 2D and 3D transforms are different.
// This is due to the difference in coordinate spaces. Additionally,
// the 3D matrix is specified explicitly to avoid rounding errors.
switch (displayRotation)
{
case DXGI_MODE_ROTATION_IDENTITY:
m_orientationTransform2D = Matrix3x2F::Identity();
m_orientationTransform3D = ScreenRotation::Rotation0;
break;
case DXGI_MODE_ROTATION_ROTATE90:
m_orientationTransform2D =
Matrix3x2F::Rotation(90.0f) *
Matrix3x2F::Translation(m_logicalSize.Height, 0.0f);
m_orientationTransform3D = ScreenRotation::Rotation270;
break;
case DXGI_MODE_ROTATION_ROTATE180:
m_orientationTransform2D =
Matrix3x2F::Rotation(180.0f) *
Matrix3x2F::Translation(m_logicalSize.Width, m_logicalSize.Height);
m_orientationTransform3D = ScreenRotation::Rotation180;
break;
case DXGI_MODE_ROTATION_ROTATE270:
m_orientationTransform2D =
Matrix3x2F::Rotation(270.0f) *
Matrix3x2F::Translation(0.0f, m_logicalSize.Width);
m_orientationTransform3D = ScreenRotation::Rotation90;
break;
default:
throw ref new FailureException();
}
После получения правильной матрицы поворота и источника для изображения 2-D задайте его с помощью вызова ID2D1DeviceContext::SetTransform между вызовами ID2D1DeviceContext::BeginDraw и ID2D1DeviceContext::EndDraw.
Предупреждение Direct2D не имеет стека преобразований. Если приложение также использует id2D1DeviceContext::SetTransform в составе кода рисования, эта матрица должна быть перемножена на любое другое примененное преобразование.
ID2D1DeviceContext* context = m_deviceResources->GetD2DDeviceContext();
Windows::Foundation::Size logicalSize = m_deviceResources->GetLogicalSize();
context->SaveDrawingState(m_stateBlock.Get());
context->BeginDraw();
// Position on the bottom right corner.
D2D1::Matrix3x2F screenTranslation = D2D1::Matrix3x2F::Translation(
logicalSize.Width - m_textMetrics.layoutWidth,
logicalSize.Height - m_textMetrics.height
);
context->SetTransform(screenTranslation * m_deviceResources->GetOrientationTransform2D());
DX::ThrowIfFailed(
m_textFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_TRAILING)
);
context->DrawTextLayout(
D2D1::Point2F(0.f, 0.f),
m_textLayout.Get(),
m_whiteBrush.Get()
);
// Ignore D2DERR_RECREATE_TARGET here. This error indicates that the device
// is lost. It will be handled during the next call to Present.
HRESULT hr = context->EndDraw();
При следующем представлении цепочки буферов изображение 2-D будет повернуто в соответствии с новой ориентацией дисплея.
Приложение B. Применение матриц для поворота экрана (3-D)
В примере кода в изменении размера цепочки буферов и предварительном повороте его содержимого (и в примере смены цепочки буферов DXGI) мы определили определенную матрицу преобразования для каждой возможной ориентации экрана. Теперь давайте рассмотрим матрицы для поворота трехмерных сцен. Как и раньше, вы создаете набор матриц для каждой из 4 возможных ориентаций. Чтобы предотвратить округление ошибок и таким образом незначительные визуальные артефакты, объявите матрицы явным образом в коде.
Эти трехмерные матрицы поворота настроены следующим образом. Матрицы, показанные в следующем примере кода, представляют собой стандартные матрицы поворота для 0, 90, 180 и 270 градусов поворотов вершин, определяющих точки в трехмерном пространстве сцены камеры. Значение координаты каждой вершины [x, y, z] в сцене умножается на эту матрицу поворота при вычислении 2-D проекции сцены.
// 0-degree Z-rotation
static const XMFLOAT4X4 Rotation0(
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f
);
// 90-degree Z-rotation
static const XMFLOAT4X4 Rotation90(
0.0f, 1.0f, 0.0f, 0.0f,
-1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f
);
// 180-degree Z-rotation
static const XMFLOAT4X4 Rotation180(
-1.0f, 0.0f, 0.0f, 0.0f,
0.0f, -1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f
);
// 270-degree Z-rotation
static const XMFLOAT4X4 Rotation270(
0.0f, -1.0f, 0.0f, 0.0f,
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f
);
}
Вы задаете тип поворота в цепочке буферов с вызовом IDXGISwapChain1::SetRotation, как показано ниже.
m_swapChain->SetRotation(rotation);
Теперь в методе отрисовки реализуйте код, аналогичный следующему:
struct ConstantBuffer // This struct is provided for illustration.
{
// Other constant buffer matrices and data are defined here.
float4x4 projection; // Current matrix for projection
} ;
ConstantBuffer m_constantBufferData; // Constant buffer resource data
// ...
// Rotate the projection matrix as it will be used to render to the rotated swap chain.
m_constantBufferData.projection = mul(m_constantBufferData.projection, m_rotationTransform3D);
Теперь при вызове метода отрисовки он умножает текущую матрицу поворота (как указано в переменной класса m_orientationTransform3D) с текущей матрицей проекции и назначает результаты этой операции в качестве новой матрицы проекции для отрисовщика. Представить цепочку буферов, чтобы увидеть сцену в обновленной ориентации дисплея.