Поиск на сайте: Расширенный поиск


Новые программы oszone.net Читать ленту новостей RSS
CheckBootSpeed - это диагностический пакет на основе скриптов PowerShell, создающий отчет о скорости загрузки Windows 7 ...
Вы когда-нибудь хотели создать установочный диск Windows, который бы автоматически установил систему, не задавая вопросо...
Если после установки Windows XP у вас перестала загружаться Windows Vista или Windows 7, вам необходимо восстановить заг...
Программа подготовки документов и ведения учетных и отчетных данных по командировкам. Используются формы, утвержденные п...
Red Button – это мощная утилита для оптимизации и очистки всех актуальных клиентских версий операционной системы Windows...
OSzone.net Microsoft Разработка приложений Языки программирования Windows и C++: Высокопроизводительное создание слоистых окон с помощью механизма композиции в Windows RSS

Windows и C++: Высокопроизводительное создание слоистых окон с помощью механизма композиции в Windows

Текущий рейтинг: 5 (проголосовало 3)
 Посетителей: 1515 | Просмотров: 2257 (сегодня 0)  Шрифт: - +

Я просто очарован слоистыми окнами (layered windows) с того момента, когда впервые наткнулся на них в Windows XP. Возможность уйти от прямоугольных или почти прямоугольных границ традиционного настольного окна всегда казалась мне очень привлекательной. А потом вышла Windows Vista. В этой версии Windows, над которой кто только не издевался, появился намек на начало чего-то куда более интересного и гибкого. И лишь сейчас, когда уже вышла Windows 8, мы оценили то, что было начато в Windows Vista. Но это же означает и постепенный закат слоистых окон.

В Windows Vista был введен сервис Desktop Window Manager. Его название и тогда, и теперь только сбивает с толку. Считайте его механизмом композиции в Windows. Этот механизм полностью изменил то, как визуализируются окна приложений на рабочем столе. Вместо того чтобы выполнять рендеринг каждого окна непосредственно в памяти видеокарты, рендеринг каждого окна осуществляется на поверхности вне экрана, или в буфере. Система выделяет по одной такой поверхности для каждого окна верхнего уровня, и рендеринг всей графики на основе GDI, Direct3D и Direct2D происходит на этих поверхностях. Их называют поверхностями переадресации (redirection surfaces), поскольку GDI-команды рисования и даже запросы вывода цепочки обмена Direct3D (swap chain) перенаправляются или копируются (в рамках GPU) на эти поверхности.

В некий момент независимо от конкретного окна механизм композиции решает, что пришла пора выполнить композицию рабочего стола на основе последней порции изменений. При этом происходит композиция всех поверхностей переадресации, добавление областей, отличных от клиентских (часто называемых хромом окна), возможное добавление каких-то теней и прочих эффектов и вывод конечного результата на адаптер дисплея.

Этот процесс композиции имеет массу преимуществ, которые я буду разъяснять в течение нескольких следующих выпусков своей рубрики — по мере более глубокого исследования композиции в Windows, но у него есть одно потенциально серьезное ограничение: эти поверхности переадресации непрозрачны. В большинстве приложений вы будете вполне счастливы и так, поскольку смешивание по альфа-каналу обходится весьма дорого с точки зрения производительности. Но это означает, что слоистые окна оказываются «на улице».

Если мне нужно слоистое окно, я получу удар по производительности. Специфические архитектурные ограничения я описывал в своей рубрике в статье «Layered Windows with Direct2D» (msdn.microsoft.com/magazine/ee819134). В двух словах, слоистые окна обрабатываются процессором в основном для проверки слоистых окон на попадание в их границы курсора мыши (hit testing) на основе альфа-значений пикселей. Это означает, что процессору нужна копия пикселей, которые образуют общую поверхность слоистого окна. Либо я выполняю рендеринг на процессоре, что намного медленнее, чем рендеринг в GPU, либо я переключаюсь на GPU, и тогда мне придется заплатить налог на пропускную способность шины, поскольку все результаты рендеринга должны копироваться из видеопамяти в системную память. В упомянутой выше статье я также показал, как можно было бы выжать максимум производительности из системы с помощью Direct2D, потому что только Direct2D позволяет выбирать, где осуществляется рендеринг — на процессоре или GPU. Загвоздка в том, что, хотя слоистое окно обязательно находится в системной памяти, механизм композиции немедленно копирует его в видеопамять, чтобы сама композиция слоистого окна все равно ускорялась аппаратными средствами.

Хотя я не могу предложить вам никакой надежды на возврат рельефности традиционных слоистых окон, у меня все же есть некоторые хорошие новости. У традиционных слоистых окон две интересные особенности. Первая — это смешивание по альфу-каналу индивидуально для каждого пиксела. Что бы я ни визуализировал на слоистом окне, оно будет смешиваться по альфа-каналу с рабочим столом и с любым содержимым, которое находится на данный конкретный момент позади окна. Вторая — возможность для User32 проверять слоистые окна на попадание в их границы курсора мыши (hit test) на основе альфа-значений пикселей, что позволяет пропускать сообщения от мыши, если пиксель в конкретной точке прозрачен. В Windows 8 и 8.1 компонент User32 изменился слабо, но изменилась возможность поддержки альфа-смешивания индивидуально каждого пикселя исключительно в GPU и без издержек передачи поверхности окна в системную память. То есть теперь можно создать эффект слоистого окна, не жертвуя производительностью, при условии, что не требуется проверка каждого пикселя на попадание курсора мыши. Но возможна проверка на попадание курсора мыши для окна в целом. Это сильно заинтересовало меня, так как это нечто, что система явно способна делать, но приложения просто никогда не могли получить доступ к этой мощи. Если вы тоже заинтригованы, тогда продолжайте читать, и я покажу вам, как это делается.

Ключ к этому — механизм композиции в Windows. Впервые он появился в Windows Vista как Desktop Window Manager с ограниченным API и популярным полупрозрачным Aero-интерфейсом. Затем вышла Windows 8 с новым DirectComposition API. Это просто более обширный API для того же механизма композиции. С выходом Windows 8 компания Microsoft наконец-то разрешила сторонним разработчикам получать доступ к мощи этого механизма, который существует столь долгое время. И, конечно, вам потребуется использовать такой графический API на основе Direct3D, как Direct2D. Но сначала вам придется иметь дело с непрозрачной поверхностью переадресации.

Как я уже упоминал, система выделяет по одной поверхности переадресации для каждого окна верхнего уровня. В Windows 8 теперь можно создать окно верхнего уровня и запросить его создание без поверхности переадресации. Строго говоря, это не имеет ничего общего со слоистыми окнами, поэтому не используйте расширенный оконный стиль WS_EX_LAYERED. (На самом деле поддержка слоистых окон немного улучшена в Windows 8, но подробнее об этом я расскажу в следующей статье.) Вместо этого вам нужно задействовать расширенный оконный стиль WS_EX_NOREDIRECTIONBITMAP, который сообщает механизму композиции не выделять поверхность переадресации для окна. Я начну с простого традиционного настольного окна. На рис. 1 приведен пример заполнения структуры WNDCLASS, регистрации оконного класса, создания окна и «прокачки» оконных сообщений. Здесь нет ничего нового, но эти основы по-прежнему значимы. Переменная window не используется, но вскоре понадобится. Вы можете скопировать этот код в проект на Visual C++ в Visual Studio или просто скомпилировать из командной строки:

cl /W4 /nologo Sample.cpp

Рис. 1. Создание традиционного окна

#ifndef UNICODE
#define UNICODE
#endif
#include <windows.h>
#pragma comment(lib, "user32.lib")
int __stdcall wWinMain(HINSTANCE module, HINSTANCE, PWSTR, int)
{
  WNDCLASS wc = {};
  wc.hCursor       = LoadCursor(nullptr, IDC_ARROW);
  wc.hInstance     = module;
  wc.lpszClassName = L"window";
  wc.style         = CS_HREDRAW | CS_VREDRAW;
  wc.lpfnWndProc =
  [] (HWND window, UINT message, WPARAM wparam,
  LPARAM lparam) -> LRESULT
  {
    if (WM_DESTROY == message)
    {
      PostQuitMessage(0);
      return 0;
    }
    return DefWindowProc(window, message, wparam, lparam);
  };   
  RegisterClass(&wc);
  HWND const window = CreateWindow(wc.lpszClassName, L"Sample",
                                   WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                                   CW_USEDEFAULT, CW_USEDEFAULT,
                                   CW_USEDEFAULT, CW_USEDEFAULT,
                                   nullptr, nullptr, module, nullptr);
  MSG message;
  while (BOOL result = GetMessage(&message, 0, 0, 0))
  {
    if (-1 != result) DispatchMessage(&message);
  }
}

На рис. 2 вы видите то, как это выглядит на моем рабочем столе. Заметьте: ничего необычного здесь нет. Хотя в примере нет никаких команд рисования и рендеринга, клиентская область окна непрозрачна, и механизм композиции добавляет область, отличную от клиентской, рамку и строку заголовка. Применение расширенного оконного стиля WS_EX_NOREDIRECTIONBITMAP для того, чтобы избавиться от непрозрачной поверхности переадресации, представляющей эту клиентскую область, заключается в том, что вы просто переключаетесь с функции CreateWindow на функцию CreateWindowEx с первым параметром, через который она принимает расширенные оконные стили:

HWND const window = CreateWindowEx(WS_EX_NOREDIRECTIONBITMAP,
                                   wc.lpszClassName, L"Sample",
                                   WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                                   CW_USEDEFAULT, CW_USEDEFAULT,
                                   CW_USEDEFAULT, CW_USEDEFAULT,
                                   nullptr, nullptr, module, nullptr);

*
Рис. 2. Традиционное окно на рабочем столе

Единственное, что изменилось, — добавление первого аргумента, расширенного оконного стиля WS_EX_NOREDIRECTIONBITMAP, и, конечно, использование функции CreateWindowEx вместо более простой CreateWindow. Однако результаты на рабочем столе далеки от кардинальных изменений. На рис. 3 показано, как это выглядит на моем рабочем столе. Заметьте, что теперь клиентская область окна полностью прозрачна. Перемещение окна по экрану проиллюстрирует вам этот факт. Я могу даже проигрывать видео на фоне, и оно ни в малейшей степени не будет загорожено окном. С другой стороны, проверка на попадание курсора мыши относится к окну в целом, и фокус окна больше не теряется при щелчке в клиентской области. Это связано с тем, что подсистема, отвечающая за проверку попадания курсора мыши и за ввод от мыши, не знает о том, что клиентская область прозрачна.

*
Рис. 3. Окно без поверхности переадресации

Разумеется, возникает следующий вопрос: как вообще можно визуализировать чего-либо в окне, если нет поверхности переадресации, предоставляемой механизму композиции? Ответ дают DirectComposition API и его глубокая интеграция с DirectX Graphics Infrastructure (DXGI). Это тот же способ, благодаря которому реализация XAML в Windows 8.1 обеспечивает невероятно высокопроизводительную композицию контента в XAML-приложениях. Механизм рендеринга Internet Explorer Trident тоже интенсивно использует DirectComposition для панорамирования и масштабирования на основе сенсорного ввода, для CSS3-анимаций, переходов и преобразований.

Я просто задействую его для композиции цепочки обмена (swap chain), которая поддерживает прозрачность (альфа-значения перемножаются с остальными значениями каждого пикселя в обратном порядке), и смешаю ее с остальным содержимым рабочего стола. Традиционные DirectX-приложения обычно создают цепочку обмена с помощью метода CreateSwapChainForHwnd фабрики DXGI. Эта цепочка поддерживается парой или набором буферов, содержимое которых в конечном счете будет обмениваться при выводе, позволяя приложению выполнять рендеринг следующего кадра, пока копируется предыдущий кадр. Поверхность цепочки обмена, на которой приложение выполняет рендеринг, — непрозрачный неотображаемый буфер (off-screen buffer). Когда приложение выводит цепочку обмена, DirectX копирует ее содержимое из буфера невидимых поверхностей (back buffer) цепочки обмена на поверхность переадресации окна. Как уже упоминалось, механизм композиции в итоге «складывает» все поверхности переадресации вместе, создавая рабочий стол в целом.

В данном случае у окна нет поверхности переадресации, поэтому метод CreateSwapChainForHwnd фабрики DXGI не применим. Однако мне все равно нужна цепочка обмена для поддержки Direct3D- и Direct2D-рендеринга. Именно для этого и предназначен метод CreateSwapChainForComposition фабрики DXGI. С его помощью я могу создать безоконную цепочку обмена наряду с ее буферами, но при отображении такой цепочки обмена копирования битов на поверхность переадресации не происходит (она просто отсутствует) — вместо этого она делается доступной механизму композиции напрямую. Тогда механизм композиции принимает поверхность цепочки обмена и напрямую использует ее вместо поверхности переадресации окна. Поскольку эта поверхность не является непрозрачной, а ее формат пикселей полностью поддерживает умноженные в обратном порядке (premultiplied) альфа-значения для каждого пикселя индивидуально, на рабочем столе мы получаем результат, где значения альфа-канала смешиваются с каждым пикселем. Кроме того, данный метод невероятно быстр, так как нет нужды в копировании внутри GPU и, естественно, никакие копии не передаются по шине в системную память.

Такова теория. Поря переходить к практике. DirectX покоится на фундаменте COM, поэтому для управления указателями на интерфейсы я задействую шаблон класса ComPtr из Windows Runtime C++ Template Library. Мне также понадобится включение и связывание с DXGI, Direct3D, Direct2D и DirectComposition API. Следующий код показывает, как это делается:

#include <wrl.h>
using namespace Microsoft::WRL;
#include <dxgi1_3.h>
#include <d3d11_2.h>
#include <d2d1_2.h>
#include <d2d1_2helper.h>
#include <dcomp.h>
#pragma comment(lib, "dxgi")
#pragma comment(lib, "d3d11")
#pragma comment(lib, "d2d1")
#pragma comment(lib, "dcomp")

Обычно я включаю все это в свой заранее скомпилированный заголовочный файл. В этом случае я должен был бы опустить директиву using и включить ее только в файл исходного кода своего приложения.

Я терпеть не могу примеры кода, переполненные обработкой ошибок, потому что это отвлекает от специфики рассматриваемого вопроса; в связи с этим я уберу обработку ошибок в класс исключения и функцию HR для проверки на ошибки. Простая реализация показана на рис. 4, но вы, конечно, можете выбрать свою политику обработки ошибок.

Рис. 4. Преобразование HRESULT-ошибок в исключения

struct ComException
{
  HRESULT result;
  ComException(HRESULT const value) :
    result(value)
  {}
};
void HR(HRESULT const result)
{
  if (S_OK != result)
  {
    throw ComException(result);
  }
}

Теперь я могу приступить к сборке стека рендеринга, а это, естественно, начинается с Direct3D-устройства. Здесь я буду краток, поскольку уже описывал инфраструктуру DirectX во всех деталях в своей статье «Introducing Direct2D 1.1» за март 2013 года (msdn.microsoft.com/magazine/dn198239). Вот указатель на интерфейс в Direct3D 11:

ComPtr<ID3D11Device> direct3dDevice;

Это указатель на интерфейс для устройства, и функцию D3D11CreateDevice можно использовать для создания устройства:

HR(D3D11CreateDevice(nullptr,    // адаптер
                     D3D_DRIVER_TYPE_HARDWARE,
                     nullptr,    // модуль
                     D3D11_CREATE_DEVICE_BGRA_SUPPORT,
                     nullptr, 0, // высший доступный уровень возможностей
                     D3D11_SDK_VERSION,
                     &direct3dDevice,
                     nullptr,    // реальный уровень возможностей
                     nullptr));  // контекст устройства

Здесь нет ничего особо удивительного. Я создаю Direct3D-устройство, поддерживаемое GPU. Флаг D3D11_CREATE_DEVICE_BGRA_SUPPORT разрешает взаимодействие с Direct2D. Семейство DirectX объединяется DXGI, который предоставляет общие механизмы управления ресурсами GPU через разнообразные DirectX API. Следовательно, я должен запросить у Direct3D-устройства его интерфейс DXGI:

ComPtr<IDXGIDevice> dxgiDevice;
HR(direct3dDevice.As(&dxgiDevice));

Метод As класса ComPtr — просто оболочка метода QueryInterface. Создав Direct3D-устройство, можно создать цепочку обмена, которая будет применяться для композиции. Для этого я сначала должен получить фабрику DXGI:

ComPtr<IDXGIFactory2> dxFactory;
HR(CreateDXGIFactory2(
  DXGI_CREATE_FACTORY_DEBUG,
  __uuidof(dxFactory),
  reinterpret_cast<void **>(dxFactory.GetAddressOf())));

Здесь я предпочел выводить дополнительную отладочную информацию — она оказывает неоценимую помощь при разработке. Самое трудное в создании цепочки обмена — выяснить, как описывать нужную цепочку для фабрики DXGI. Эта отладочная информация невероятно полезна в тонкой настройке обязательной структуры DXGI_SWAP_CHAIN_DESC1:

DXGI_SWAP_CHAIN_DESC1 description = {};

Это инициализирует все поля структуры нулями. После этого можно заполнить лишь интересующие вас свойства:

description.Format           = DXGI_FORMAT_B8G8R8A8_UNORM;     
description.BufferUsage      = DXGI_USAGE_RENDER_TARGET_OUTPUT;
description.SwapEffect       = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
description.BufferCount      = 2;                              
description.SampleDesc.Count = 1;                              
description.AlphaMode        = DXGI_ALPHA_MODE_PREMULTIPLIED;

Конкретный формат (32 бита на пиксель с восемью битами на каждый цветовой канал наряду с обратным умножением на 8-битный альфа-компонент) — не единственный вариант, но обеспечивает максимальную производительность и совместимость между устройствами и API.

Использование буфера цепочки обмена нужно задать таким, чтобы в него можно было направить вывод рендера. Это требуется для того, чтобы Direct2D-контекст устройства мог создать битовую карту, адресованную DXGI-поверхности, с командами рисования. Сама битовая карта Direct2D — это просто абстракция, поддерживаемая цепочкой обмена.

Цепочки обмена при композиции поддерживают только эффект с переворачиванием и последовательным обменом (flip-sequential swap effect). Именно так цепочка обмена взаимодействует с механизмом композиции в отсутствие поверхности переадресации. В модели переворачивания (flip model) все буферы напрямую совместно используются механизмом композиции. Этот механизм потом может скомпоновать рабочий стол непосредственно из буфера невидимых поверхностей цепочки обмена без дополнительного копирования. Обычно это самая эффективная модель. Кроме того, она требуется для композиции, поэтому именно ее я и задействую. Модели переворачивания обязательно требуются два буфера, но не поддерживает многоступенчатую выборку (multisampling), поэтому BufferCount задается равным 2, а SampleDesc.Count — равным 1. Этот счетчик определяет количество выборок на пиксель. Задав его равным 1, вы просто отключаете многоступенчатую выборку.

Наконец, крайне важен альфа-режим. Обычно он игнорировался бы для непрозрачных цепочек обмена, но в данном случае я на самом деле хочу включить прозрачность. Умноженные в обратном порядке альфа-значения (premultiplied alpha values) обычно обеспечивают максимальную производительность, и это тоже единственный вариант, поддерживаемый моделью переворачивания.

Последнее, что нужно сделать перед созданием цепочки обмена, — определить нужный размер буферов. Обычно, вызывая метод CreateSwapChainForHwnd, можно игнорировать этот размер, и тогда фабрика DXGI запросит у окна размер клиентской области. В этом случае DXGI не имеет ни малейшего представления, что я собираюсь делать с цепочкой обмена, поэтому я должен указать конкретный размер. После создания окна остается лишь запросить клиентскую область окна и соответственно обновить описание цепочки обмена:

RECT rect = {};
GetClientRect(window, &rect);
description.Width  = rect.right - rect.left;  
description.Height = rect.bottom - rect.top;

Теперь я могу создать цепочку обмена для композиции с этим описанием и создать указатель на Direct3D-устройство. Можно использовать указатель на интерфейс либо Direct3D, либо DXGI:

ComPtr<IDXGISwapChain1> swapChain;
HR(dxFactory->CreateSwapChainForComposition(dxgiDevice.Get(),
                                            &description,
                                            nullptr, // не ограничиваем
                                            swapChain.GetAddressOf()));

Создав цепочку обмена, можно использовать любой Direct3D- или Direct2D-код рендеринга графики для рисования окна приложения, применяя альфа-значения так, как это требуется для создания нужного уровня прозрачности. Здесь нет ничего нового, поэтому я вновь отсылаю вас к своей статье за март 2013 года, где изложена специфика рендеринга в цепочку обмена с помощью Direct2D. На рис. 5 дан простой пример, если вы следуете за мной. Только не забудьте включить поддержку индивидуального DPI для каждого монитора, как было описано в моей статье «Write High-DPI Apps for Windows 8.1» за февраль 2014 года (msdn.microsoft.com/magazine/dn574798).

Рис. 5. Рисование в цепочку обмена с помощью Direct2D

// Создаем однопоточную Direct2D-фабрику
// с отладочной информацией
ComPtr<ID2D1Factory2> d2Factory;
D2D1_FACTORY_OPTIONS const options = { D2D1_DEBUG_LEVEL_INFORMATION };
HR(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED,
                     options,
                     d2Factory.GetAddressOf()));
// Создаем Direct2D-устройство, которое
// обратно связано с Direct3D-устройством
ComPtr<ID2D1Device1> d2Device;
HR(d2Factory->CreateDevice(dxgiDevice.Get(),
                           d2Device.GetAddressOf()));
// Создаем Direct2D-контекст устройства, которое является
// реальной мишенью рендера и предоставляет команды рисования
ComPtr<ID2D1DeviceContext> dc;
HR(d2Device->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
                                 dc.GetAddressOf()));
// Получаем буфер невидимых поверхностей цепочки обмена
ComPtr<IDXGISurface2> surface;
HR(swapChain->GetBuffer(
    0, // индекс
    __uuidof(surface),
    reinterpret_cast<void **>(surface.GetAddressOf())));
// Создаем битовую карту Direct2D,
// которая указывает на поверхность цепочки обмена
D2D1_BITMAP_PROPERTIES1 properties = {};
properties.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED;
properties.pixelFormat.format    = DXGI_FORMAT_B8G8R8A8_UNORM;
properties.bitmapOptions         = D2D1_BITMAP_OPTIONS_TARGET |
                                   D2D1_BITMAP_OPTIONS_CANNOT_DRAW;
ComPtr<ID2D1Bitmap1> bitmap;
HR(dc->CreateBitmapFromDxgiSurface(surface.Get(),
                                   properties,
                                   bitmap.GetAddressOf()));
// Указываем контекст устройства
// на битовую карту для рендеринга
dc->SetTarget(bitmap.Get());
// Что-то рисуем
dc->BeginDraw();
dc->Clear();
ComPtr<ID2D1SolidColorBrush> brush;
D2D1_COLOR_F const brushColor = D2D1::ColorF(0.18f,  //красный
                                             0.55f,  // зеленый
                                             0.34f,  // голубой
                                             0.75f); // альфа
HR(dc->CreateSolidColorBrush(brushColor,
                             brush.GetAddressOf()));
D2D1_POINT_2F const ellipseCenter = D2D1::Point2F(150.0f,  // x
                                                  150.0f); // y
D2D1_ELLIPSE const ellipse = D2D1::Ellipse(ellipseCenter,
                                           100.0f,  // радиус x
                                           100.0f); // радиус y
dc->FillEllipse(ellipse,
                brush.Get());
HR(dc->EndDraw());
// Делаем цепочку обмена доступной механизму композиции
HR(swapChain->Present(1,   // синхронизация
                          0)); // флаги

Теперь я могу задействовать DirectComposition API, чтобы свести все воедино. Хотя механизм композиции в Windows имеет дело с рендерингом и композицией рабочего стола в целом, DirectComposition API позволяет использовать ту же технологию для композиции визуальных элементов ваших приложений. Приложение составляет воедино различные визуальные элементы, определяя тем самым внешний вид окна самого приложения. Эти элементы можно анимировать и преобразовывать самыми разными способами, чтобы создавать богатые и адаптивные UI. Сам процесс композиции выполняется вместе с композицией рабочего стола в целом, поэтому для повышения отзывчивости отвлекается больше потоков вашего приложения.

DirectComposition в основном выполняет композицию различных битовых карт. Как и в случае Direct2D, концепция битовой карты в большей мере является абстракцией, которая позволяет различным стекам рендеринга взаимодействовать и обеспечивать более плавный вывод более привлекательных UI приложений.

Как Direct3D и Direct2D, DirectComposition является DirectX API, поддерживаемым GPU. DirectComposition-устройство создается указанием на Direct3D-устройство — во многом так же, как Direct2D-устройство указывает на нижележащее Direct3D-устройство. Я применяю то же самое Direct3D-устройство, которое использовал ранее для создания цепочки обмена и Direct2D-мишени рендера, чтобы создать DirectComposition-устройство:

ComPtr<IDCompositionDevice> dcompDevice;
HR(DCompositionCreateDevice(
   dxgiDevice.Get(),
   __uuidof(dcompDevice),
   reinterpret_cast<void **>(dcompDevice.GetAddressOf())));

Функция DCompositionCreateDevice ожидает получения интерфейса DXGI устройства Direct3D и возвращает указатель на интерфейс IDCompositionDevice только что созданного DirectComposition-устройства. Последнее ведет себя как фабрика других объектов DirectComposition и обеспечивает поддержку крайне важного метода Commit, который передает пакет команд рендеринга механизму композиции для последующей композиции и представления.

Далее мне нужно создать мишень композиции (composition target), связывающую визуальные элементы, которые будут составлены с областью-получателем (destination), каковой является окно приложения:

ComPtr<IDCompositionTarget> target;
HR(dcompDevice->CreateTargetForHwnd(window,
                                    true, // самое верхнее
                                    target.GetAddressOf()));

Первый параметр метода CreateTargetForHwnd — это описатель окна, возвращенный функцией CreateWindowEx. Второй параметр указывает, как визуальные элементы будут комбинироваться с любыми другими элементами окна. Результатом является указатель на интерфейс IDCompositionTarget, единственный метод которого называется SetRoot. Он позволяет мне задавать корневой визуальный элемент в возможном дереве визуальных элементов, составляемых воедино. Мне не требуется все дерево визуальных элементов, но нужен хотя бы один визуальный объект, и для этого я могу снова обратиться к DirectComposition-устройству:

ComPtr<IDCompositionVisual> visual;
HR(dcompDevice->CreateVisual(visual.GetAddressOf()));

Объект visual содержит ссылку на битовую карту и предоставляет набор свойств, которые влияют на то, как будет выполняться рендеринг и композиция этого visual относительно других визуальных элементов в дереве и самой мишени. У меня уже есть контент, который этот visual должен передать механизму композиции. Это созданная мной ранее цепочка обмена:

HR(visual->SetContent(swapChain.Get()));

Объект visual готов, и я могу просто указать его как корень мишени композиции:

HR(target->SetRoot(visual.Get()));

Наконец, как только установлена форма визуального дерева, я могу просто информировать механизм композиции о том, что закончил свою работу, вызвав метод Commit устройства DirectComposition:

HR(dcompDevice->Commit());

В данном конкретном приложении, где визуальное дерево не изменяется, мне нужно вызвать Commit только раз в начале работы приложения. Изначально я предполагал, что метод Commit потребуется вызывать после вывода цепочки обмена, но это не так, потому что презентация цепочки обмена не синхронизируется с изменениями в визуальном дереве.

На рис. 6 показано, как выглядит теперь окно приложения, когда Direct2D выполнил рендеринг в цепочку обмена и DirectComposition предоставил частично прозрачную цепочку обмена механизму композиции.

*
Рис. 6. Рисование с помощью Direct2D на поверхности DirectComposition

Я рад, что наконец нашел решение старой проблемы: возможно высокопроизводительного создания окон, смешиваемых по альфа-каналу с остальным содержимым рабочего стола. И восхищен возможностями DirectComposition API и теми горизонтами, которые он открывает в проектировании и разработке пользовательских сред с помощью неуправляемого кода.

Хотите пририсовать к окну собственный «хром»? Без проблем — просто замените оконный стиль WS_OVERLAPPEDWINDOW на WS_POPUP при создании окна.

Автор: Кенни Керр  •  Иcточник: msdn.microsoft.com  •  Опубликована: 29.10.2014
Нашли ошибку в тексте? Сообщите о ней автору: выделите мышкой и нажмите CTRL + ENTER
Теги:   C++.


Оценить статью:
Вверх
Комментарии посетителей
Комментарии отключены. С вопросами по статьям обращайтесь в форум.