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


Новые программы oszone.net Читать ленту новостей RSS
Работа с платёжными картами через эквайринг ПАО Сбербанк. Программа выполняет операции:• Прием и возврат платежей через ...
Бесплатная, сетевая программа для просмотра справочника кодов банков (БИК). Есть возможность сортировки справочника по л...
Программа выполняет следующие операции: * Запрос на поиск реквизитов по наименованию, адресу, региону и ФИО; * Фильтр по...
Утилита для массового переименования файлов. ReNamer позволяет добавлять или изменять префиксы и постфиксы в именах файл...
Программа предназначена для автоматизации работы компьютерных клубов. Внимательно следит за работой администраторов, вед...
OSzone.net Microsoft Разработка приложений Языки программирования Windows и C++: DirectComposition: API режима сохранения RSS

Windows и C++: DirectComposition: API режима сохранения

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

В графических API наблюдается тенденция разделения на два очень разных лагеря. Существуют API прямого режима (immediate-mode API) с хорошо известными примерами, в том числе Direct2D и Direct3D. Но есть и API режима сохранения (retained-mode API), такие как Windows Presentation Foundation (WPF), любой XAML или декларативный API. Современные браузеры обеспечивают четкое разделение между двумя графическими режимами за счет Scalable Vector Graphics, который предоставляет API режима сохранения, и элемента Canvas, который предоставляет API прямого режима.

Режим сохранения предполагает, что графический API будет сохранять некое представление сцены, например граф или дерево объектов, которыми потом можно будет манипулировать в течение некоторого времени. Это удобно и упрощает разработку интерактивных приложений. API прямого режима, напротив, не включают граф сцены и вместо этого полагаются на то, что сцена будет конструироваться приложением, используя последовательность команд рисования. Это дает колоссальные преимущества в производительности. Такой API прямого режима, как Direct2D, обычно буферизует вершинные данные от множества геометрических элементов (geometries), объединяет большое количество команд рисования и многое другое. Это особенно выигрышно при работе с конвейером рендеринга текста, когда глифы нужно сначала записать в текстуру и понизить разрешение (down-sample) перед фильтрацией ClearType и до того, как текст начнет свой путь к мишени рендера (render target). Это одна из причин, по которой многие другие графические API и все большее количество сторонних приложений теперь полагаются на Direct2D и DirectWrite при рендеринге текста.

Выбор между двумя режимами традиционно сводится к компромиссу между производительностью и скоростью разработки. Direct2D как API прямого режима можно выбрать из соображений абсолютной производительности, а WPF (API режима сохранения) — из соображений продуктивности труда или удобства. DirectComposition меняет это уравнение, позволяя разработчикам смешивать эти режимы гораздо более естественным способом. Он размывает границу между API прямого режима и режима сохранения, так как предоставляет для графики режим сохранения, но безо всяких издержек в отношении памяти или производительности. Это достигается за счет концентрации на композиции битовых карт, а не попытке конкурировать с другими графическими API. DirectComposition просто предоставляет визуальное дерево и инфраструктуру композиции, чтобы битовыми картами, рендеринг которых осуществляется с применением других технологий, можно было легко манипулировать и составлять друг с другом. И в отличие от WPF DirectComposition является неотъемлемой частью инфраструктуры графической подсистемы самой ОС и исключает все проблемы с производительностью, традиционно преследующие WPF-приложения.

Если вы читали две мои предыдущие статьи по DirectComposition (msdn.microsoft.com/magazine/dn745861 и msdn.microsoft.com/magazine/dn786854), то должны были уже прочувствовать, на что способен механизм композиции. Теперь я хочу сделать все то, что описывал ранее, гораздо нагляднее и показать, как можно использовать DirectComposition для манипуляций над визуальными элементами (визуалами), рисуемыми с помощью Direct2D, таким образом, чтобы это было удобно разработчикам, привыкшим к API режима сохранения. Я продемонстрирую, как создать простое окно, представляющее окружности как «объекты», которые можно создавать и перемещать, с полной поддержкой проверки на попадание курсора мыши в границы объектов (далее для краткости — проверки на попадание) и изменений в Z-порядке. Как это может выглядеть, представлено в примере на рис. 1.

*

Рис. 1. Перетаскивание окружностей

Хотя окружности на рис. 1 нарисованы с помощью Direct2D, приложение рисует окружность только раз на поверхности композиции. Затем эта поверхность становится общей для визуальных элементов композиции в визуальном дереве, связанном с окном. Каждый визуальный элемент определяет смещение относительно окна, в котором его контент (поверхность композиции) размещается в какой-то позиции и в конечном счете визуализируется механизмом композиции. Пользователь может создавать новые окружности и перемещать их мышью, пером или пальцем. Всякий раз, когда выбирается некая окружность, она перемещается наверх в Z-порядке, чтобы эта окружность появлялась поверх любых других окружностей в окне. Конечно, мне не требуется API режима сохранения для получения столь простого эффекта — это лишь хороший пример того, как DirectComposition API работает совместно с Direct2D в достижении некоторых впечатляющих визуальных эффектов. Моя цель заключается в создании интерактивного приложения, чей обработчик WM_PAINT не отвечает за поддержание актуальности пикселей окна.

Я начну с нового класса SampleWindow, производного от шаблона класса Window, с которым я познакомил вас в прошлой статье. Шаблон класса Window просто облегчает диспетчеризацию сообщений в C++:

struct SampleWindow : Window<SampleWindow>
{
};

Как и в любом современном Windows-приложении, я должен обрабатывать динамическое масштабирование DPI, поэтому я добавлю два поля типа float для отслеживания масштабных множителей DPI для осей X и Y:

float m_dpiX = 0.0f;
float m_dpiY = 0.0f;

Вы можете инициализировать их по требованию, как я показывал в прошлой статье, или в обработчике сообщения WM_CREATE. В любом случае вы должны вызвать функцию MonitorFromWindow, чтобы определить монитор, на экране которого располагается самая большая часть нового окна. Затем вы просто вызываете функцию GetDpiForMonitor, чтобы получить действующие значения DPI. Я неоднократно демонстрировал все это в предыдущих статьях и в своих учебных курсах, поэтому не стану повторяться.

Я буду использовать Direct2D-объект геометрии эллипса для описания окружности, которую следует нарисовать, чтобы иметь возможность впоследствии задействовать тот же объект геометрии для проверки на попадание. Хотя рисовать структуру D2D1_ELLIPSE эффективнее, чем объект геометрии, последний обеспечивает проверку на попадание, и рисунок будет сохраняться. Я буду отслеживать как Direct2D-фабрику, так и геометрию эллипса:

ComPtr<ID2D1Factory2> m_factory;
ComPtr<ID2D1EllipseGeometry> m_geometry;

В предыдущей статье я показал, как создать объект Direct2D-устройства напрямую с помощью функции D2D1CreateDevice, не используя Direct2D-фабрику. Это вполне приемлемый вариант, но в нем есть один подвох. Ресурсы Direct2D-фабрики, хоть и являются аппаратно-независимыми и их не нужно создавать заново при потере устройства, можно использовать только с Direct2D-устройствами, созданными той же Direct2D-фабрикой. Поскольку я хочу заранее создать геометрию эллипса, мне нужен объект Direct2D-фабрики, который этим и займется. В принципе, можно было бы подождать, пока я создам Direct2D-устройство вызовом функции D2D1CreateDevice и получу нижележащую фабрику методом GetFactory, а затем использую этот объект фабрики для создания геометрии, но такой вариант выглядит уж слишком громоздким. Вместо этого я просто создам Direct2D-фабрику и с ее помощью создам как геометрию эллипса, так и объект устройства. Рис. 2 иллюстрирует, как создать объекты Direct2D-фабрики и геометрии.

Рис. 2. Создание объектов Direct2D-фабрики и геометрии

void CreateFactoryAndGeometry()
{
  D2D1_FACTORY_OPTIONS options = {};
  #ifdef _DEBUG
  options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
  #endif
  HR(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED,
                       options,
                       m_factory.GetAddressOf()));
  D2D1_ELLIPSE const ellipse = Ellipse(Point2F(50.0f, 50.0f),
                                       49.0f,
                                       49.0f);
  HR(m_factory->CreateEllipseGeometry(ellipse,
                                      m_geometry.GetAddressOf()));
}

После этого конструктор SampleWindow может вызвать метод CreateFactoryAndGeometry, чтобы подготовить эти аппаратно-независимые ресурсы. Как видите, эллипс определен вокруг центральной точки, находящейся по смещению на 50 пикселей по осям X и Y, а также с радиусами 49 пикселей по обеим осям, что превращает эллипс в окружность. Я создам поверхность композиции размером 100×100. Радиусы в 49 пикселей выбраны мной потому, что штрих по умолчанию (default stroke), рисуемый Direct2D, накрывает периметр, и из-за этого в ином случае он был бы отсечен.

Теперь, что касается аппаратно-специфичных ресурсов. Мне требуются поддерживающее Direct3D-устройство, устройство композиции для передачи изменений в визуальное дерево, мишень композиции для поддержания актуальности визуального дерева, корневой визуал, который будет представлять родителя всех визуальных элементов окружностей (circle visuals), и общая поверхность композиции:

ComPtr<ID3D11Device> m_device3D;
ComPtr<IDCompositionDesktopDevice> m_device;
ComPtr<IDCompositionTarget> m_target;
ComPtr<IDCompositionVisual2> m_rootVisual;
ComPtr<IDCompositionSurface> m_surface;

Я рассказывал обо всех этих объектах в своих предыдущих статьях по DirectX и, в частности, в последних двух статьях, посвященных DirectComposition. Кроме того, я рассматривал и демонстрировал, как вы должны обрабатывать создание и потерю устройства, поэтому повторяться здесь не буду. Я просто обрисую метод CreateDevice2D, который нужно обновить для использования ранее созданной Direct2D-фабрики:

ComPtr<ID2D1Device> CreateDevice2D()
{
  ComPtr<IDXGIDevice3> deviceX;
  HR(m_device3D.As(&deviceX));
  ComPtr<ID2D1Device> device2D;
  HR(m_factory->CreateDevice(deviceX.Get(),
    device2D.GetAddressOf()));
  return device2D;
}

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

HR(m_device->CreateSurface(
  static_cast<unsigned>(LogicalToPhysical(100, m_dpiX)),
  static_cast<unsigned>(LogicalToPhysical(100, m_dpiY)),
  DXGI_FORMAT_B8G8R8A8_UNORM,
  DXGI_ALPHA_MODE_PREMULTIPLIED,
  m_surface.ReleaseAndGetAddressOf()));

После этого я могу вызвать метод BeginDraw поверхности композиции для получения контекста Direct2D-устройства, с помощью которого будет осуществляться буферизация команд рисования:

HR(m_surface->BeginDraw(
  nullptr,
  __uuidof(dc),
  reinterpret_cast<void **>(dc.GetAddressOf()),
  &offset));

Далее нужно сообщить Direct2D, как масштабировать любые команды рисования:

dc->SetDpi(m_dpiX,
           m_dpiY);

и преобразовать вывод по смещению, предоставленному DirectComposition:

dc->SetTransform(Matrix3x2F::Translation(PhysicalToLogical(offset.x, m_dpiX),
                                         PhysicalToLogical(offset.y, m_dpiY)));

PhysicalToLogical — вспомогательная функция, рутинно используемая мной для масштабирования DPI при комбинировании API, имеющих разные уровни поддержки такого масштабирования (или вообще не поддерживающих масштабирование DPI). Функция PhysicalToLogical и соответствующая функция LogicalToPhysical приведены на рис. 3.

Рис. 3. Преобразования между логическими и физическими пикселями

template <typename T>
static float PhysicalToLogical(T const pixel,
                               float const dpi)
{
  return pixel * 96.0f / dpi;
}
template <typename T>
static float LogicalToPhysical(T const pixel,
                               float const dpi)
{
  return pixel * dpi / 96.0f;
}

Теперь я могу просто нарисовать синюю окружность сплошной кистью только для этой цели:

ComPtr<ID2D1SolidColorBrush> brush;
D2D1_COLOR_F const color = ColorF(0.0f, 0.5f, 1.0f, 0.8f);
HR(dc->CreateSolidColorBrush(color,
                             brush.GetAddressOf()));

Я должен очистить мишень рендера до заполнения геометрии эллипса, а затем нанести штрихи или нарисовать его контур модифицированной кистью:

dc->Clear();
dc->FillGeometry(m_geometry.Get(),
                 brush.Get());
brush->SetColor(ColorF(1.0f, 1.0f, 1.0f));
dc->DrawGeometry(m_geometry.Get(),
                 brush.Get());

Наконец, нужно вызвать метод EndDraw, чтобы указать, что поверхность готова для композиции:

HR(m_surface->EndDraw());

Пора заняться созданием окружностей. В своих предыдущих статьях я создавал только один корневой визуал, но в этом приложении нужно создавать визуалы по запросу, поэтому я оберну соответствующий код в удобный вспомогательный метод:

ComPtr<IDCompositionVisual2> CreateVisual()
{
  ComPtr<IDCompositionVisual2> visual;
  HR(m_device->CreateVisual(visual.GetAddressOf()));
  return visual;
}

Один из интересных аспектов DirectComposition API заключается в том, что, по сути, это интерфейс механизма композиции только для записи. Хотя он сохраняет визуальное дерево вашего окна, он не предоставляет никаких аксессоров get, с помощью которых вы могли бы запросить какую-либо информацию от визуального дерева. Любые данные, такие как позиция или Z-порядок визуала, должны сохраняться приложением напрямую. Это предотвращает лишнюю нагрузку на память, а также исключает потенциально возможные условия гонок (race conditions) между мировым представлением приложения и транзакционным состоянием механизма композиции. Поэтому я создам структуру Circle для отслеживания позиции каждой окружности:

struct Circle
{
  ComPtr<IDCompositionVisual2> Visual;
  float LogicalX = 0.0f;
  float LogicalY = 0.0f;
};

Visual, по сути, представляет аксессоры set структуры Circle, а поля LogicalX и LogicalY — аксессоры get. Я могу задать позицию визуала с помощью интерфейса IDCompositionVisual2, сохранить эту позицию и впоследствии получить ее с другими полями. Это необходимо как для проверки на попадание, так и для восстановления окружностей после потери устройства. Чтобы избежать рассинхронизации, я просто предоставлю вспомогательный метод для обновления объекта Visual на основе его логической позиции. DirectComposition API не имеет ни малейшего представления о том, как может быть расположен и отмасштабирован контент, поэтому необходимые вычисления DPI мне придется выполнять самостоятельно:

void UpdateVisualOffset(float const dpiX,
                        float const dpiY)
{
  HR(Visual->SetOffsetX(LogicalToPhysical(LogicalX, dpiX)));
  HR(Visual->SetOffsetY(LogicalToPhysical(LogicalY, dpiY)));
}

Я добавлю другой вспомогательный метод, который собственно и устанавливает логическое смещение окружности. Он опирается на UpdateVisualOffset, гарантируя синхронизацию структуры Circle и визуального объекта:

void SetLogicalOffset(float const logicalX,
                      float const logicalY,
                      float const dpiX,
                      float const dpiY)
{
  LogicalX = logicalX;
  LogicalY = logicalY;
  UpdateVisualOffset(dpiX,
                       dpiY);
}

Наконец, поскольку окружности добавляются в приложение, мне потребуется простой конструктор для инициализации структуры, вступающий во владение ссылкой на IDCompositionVisual2:

Circle(ComPtr<IDCompositionVisual2> && visual,
       float const logicalX,
       float const logicalY,
       float const dpiX,
       float const dpiY) :
  Visual(move(visual))
{
  SetLogicalOffset(logicalX,
                   logicalY,
                   dpiX,
                   dpiY);
}

Теперь я могу отслеживать все окружности в приложении с помощью стандартного контейнера-списка:

list<Circle> m_circles;

Попутно я добавлю член для отслеживания любой выбранной окружности:

Circle * m_selected = nullptr;
float m_mouseX = 0.0f;
float m_mouseY = 0.0f;

Смещение мыши тоже поможет добиться естественного перемещения. Я рассмотрю соответствующие служебные операции, связанные с взаимодействием с мышью, благодаря которым в конечном счете будут создаваться окружности и у меня будет возможность перемещать их в окне. Метод CreateDeviceResources нужен для повторного создания всех визуальных объектов (при потере устройства) на основе ранее созданных окружностей. Это не дело, если все окружности вдруг исчезнут. Поэтому сразу после создания или воссоздания корневого визуала и общей поверхности я буду перебирать этот список, создавать новые визуальные объекты и позиционировать их заново так, чтобы они соответствовали существующему состоянию. Рис. 4 иллюстрирует, как свести все это воедино, используя то, что я уже подготовил.

Рис. 4. Создание стека устройства и визуального дерева

void CreateDeviceResources()
{
  ASSERT(!IsDeviceCreated());
  CreateDevice3D();
  ComPtr<ID2D1Device> const device2D = CreateDevice2D();
  HR(DCompositionCreateDevice2(
      device2D.Get(),
      __uuidof(m_device),
      reinterpret_cast<void **>(m_device.ReleaseAndGetAddressOf())));
  HR(m_device->CreateTargetForHwnd(m_window,
                                   true,
                                   m_target.ReleaseAndGetAddressOf()));
  m_rootVisual = CreateVisual();
  HR(m_target->SetRoot(m_rootVisual.Get()));
  CreateDeviceScaleResources();
  for (Circle & circle : m_circles)
  {
    circle.Visual = CreateVisual();
    HR(circle.Visual->SetContent(m_surface.Get()));
    HR(m_rootVisual->AddVisual(circle.Visual.Get(), false, nullptr));
    circle.UpdateVisualOffset(m_dpiX, m_dpiY);
  }
  HR(m_device->Commit());
}

Другая часть служебных операций относится к масштабированию DPI. Поверхность композиции, содержащая пиксели окружности, рендеринг которых выполняется Direct2D, должна быть создана заново для масштабирования; кроме того нужно заново позиционировать и сами визуалы, чтобы их смещения были пропорциональны друг другу и окну-владельцу. Обработчик сообщения WM_DPICHANGED сначала создает заново поверхность композиции (с помощью метода CreateDeviceScaleResources), а затем обновляет контент и позицию для каждой окружности:

if (!IsDeviceCreated()) return;
CreateDeviceScaleResources();
for (Circle & circle : m_circles)
{
  HR(circle.Visual->SetContent(m_surface.Get()));
  circle.UpdateVisualOffset(m_dpiX, m_dpiY);
}
HR(m_device->Commit());

Далее я реализую взаимодействие с указателем. Я разрешу пользователю создавать новые окружности, если он щелкает левой кнопкой мыши при нажатой клавише Ctrl. Обработчик сообщений WM_LBUTTONDOWN выглядит примерно так:

if (wparam & MK_CONTROL)
{
  // Создаем новую окружность
}
else
{
  // Ищем существующую окружность
}
HR(m_device->Commit());

Исходя из того, что нужно создать новую окружность, я начну с создания нового визуала и указания общего контента перед его добавлением в качестве дочернего элемента корневого визуала:

ComPtr<IDCompositionVisual2> visual = CreateVisual();
HR(visual->SetContent(m_surface.Get()));
HR(m_rootVisual->AddVisual(visual.Get(), false, nullptr));

Новый визуал добавляется перед любыми существующими. Это работа второго параметра метода AddVisual. Если бы я установил его в true, то новый визуал был бы размещен за любыми существующими визуалами того же уровня (siblings). Далее я должен добавить структуру Circle в список, чтобы впоследствии можно было обрабатывать проверку на попадание, потерю устройства и масштабирование DPI:

m_circles.emplace_front(move(visual),
       PhysicalToLogical(LOWORD(lparam), m_dpiX) - 50.0f,
       PhysicalToLogical(HIWORD(lparam), m_dpiY) - 50.0f,
       m_dpiX,
       m_dpiY);

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

SetCapture(m_window);
m_selected = &m_circles.front();
m_mouseX = 50.0f;
m_mouseY = 50.0f;

Смещение мыши позволяет плавно перетаскивать любую окружность независимо от того, где именно на окружности изначально находился курсор мыши. Поиск существующей окружности требует немного больше работы. И здесь я вновь должен вручную позаботиться о распознавании DPI. К счастью, Direct2D резко упрощает эту задачу. Сначала нужно перебрать все окружности в естественном Z-порядке. Я уже поместил новые окружности в начало списка, так что остается лишь перебрать этот список от начала к концу:

for (auto circle = begin(m_circles); circle != end(m_circles); ++circle)
{
}

Я не использую выражение for на основе диапазона, поскольку в данном случае удобнее работать непосредственно с итераторами. А где же окружности? Каждая окружность отслеживает свою логическую позицию относительно верхнего левого угла окна. В LPARAM сообщения от мыши также содержится физическая позиция курсора мыши относительно верхнего левого угла окна. Но этого недостаточно для их трансляции в общую координатную систему, так как фигура, которую мне нужно проверять на попадания, не является простым прямоугольником. Фигура определяется объектом геометрии, и Direct2D предоставляет метод FillContainsPoint для реализации проверки на попадания. Фокус в том, что объект геометрии сообщает лишь форму окружности, но не ее позицию. Чтобы проверка на попадания работала эффективно, мне придется сначала транслировать позицию курсора мыши так, чтобы она стала относительна объекта геометрии. Это не сложно:

D2D1_POINT_2F const point =
  Point2F(LOWORD(lparam) - LogicalToPhysical(circle->LogicalX, m_dpiX),
          HIWORD(lparam) - LogicalToPhysical(circle->LogicalY, m_dpiY));

Но я еще не готов к вызову метода FillContainsPoint. Другая проблема в том, что объект геометрии ничего не знает о мишени рендера. Когда я использовал объект геометрии, чтобы нарисовать окружность, именно мишень рендера обеспечивала масштабирование геометрии под значения DPI мишени. Поэтому мне нужен какой-то способ масштабирования геометрии до выполнения проверки на попадание — такой, который отражал бы размер окружности соответственно тому, что видит пользователь на экране. И вновь Direct2D спешит на помощь. FillContainsPoint принимает необязательную матрицу 3×2 для преобразования геометрии до проверки того, содержится ли данная точка внутри фигуры. Я могу просто определить преобразование масштабирования на основе значений DPI окна:

D2D1_MATRIX_3X2_F const transform = Matrix3x2F::Scale(m_dpiX / 96.0f,
                                                      m_dpiY / 96.0f);

Затем метод FillContainsPoint сообщит мне, содержится ли эта точка в окружности:

BOOL contains = false;
HR(m_geometry->FillContainsPoint(point,
                                 transform,
                                 &contains));
if (contains)
{
  // Переупорядочиваем и выбираем окружность
  break;
}

Если точка содержится в окружности, я должен переупорядочить визуалы композиции так, чтобы визуал выбранной окружности находится вверху Z-порядка. Для этого можно удалить дочерний визуал и добавить его перед любыми существующими визуалами:

HR(m_rootVisual->RemoveVisual(circle->Visual.Get()));
HR(m_rootVisual->AddVisual(circle->Visual.Get(), false, nullptr));

Мне также требуется сохранить актуальность своего списка, переместив окружность в его начало:

m_circles.splice(begin(m_circles), m_circles, circle);

Затем я предполагаю, что пользователь хочет перетащить окружность:

SetCapture(m_window);
m_selected = &*circle;
m_mouseX = PhysicalToLogical(point.x, m_dpiX);
m_mouseY = PhysicalToLogical(point.y, m_dpiY);

Здесь я вычисляю смещение позиции курсора мыши относительно выбранной окружности. Тем самым окружность не совершит визуального «скачка» к центру курсора мыши в момент начала перетаскивания, и дальнейшее движение будет плавным. Реагируя на сообщение WM_MOUSEMOVE, я даю возможность продолжать это перемещение окружности до тех пор, пока не будет выбрана другая окружность:

if (!m_selected) return;
m_selected->SetLogicalOffset(
  PhysicalToLogical(GET_X_LPARAM(lparam), m_dpiX) - m_mouseX,
  PhysicalToLogical(GET_Y_LPARAM(lparam), m_dpiY) - m_mouseY,
  m_dpiX,
  m_dpiY);
HR(m_device->Commit());

Метод SetLogicalOffset структуры Circle обновляет логическую позицию, поддерживаемую окружностью, а также физическую позицию визуала композиции. Для раскрытия содержимого LPARAM я использую макросы GET_X_LPARAM и GET_Y_LPARAM вместо обычных макросов LOWORD и HIWORD. Хотя позиция, сообщаемая в WM_MOUSEMOVE, относительна верхнему левому углу окна, она будет включать отрицательные координаты, если захватывается ввод от мыши и окружность перетаскивается выше или левее окна. Как обычно, изменения в визуальном дереве должны быть переданы и зафиксированы, чтобы они вступили в силу. Любое перемещение подходит к концу в обработчике сообщений WM_LBUTTONUP, когда кнопка мыши отпускается и происходит сброс указателя m_selected:

ReleaseCapture();
m_selected = nullptr;

И в заключение самое интересное. Наиболее яркое доказательство тому, что вы имеете дело с графикой в режиме сохранения, обработчик сообщений WM_PAINT на рис. 5.

Рис. 5. Обработчик сообщений WM_PAINT режима сохранения

void PaintHandler()
{
  try
  {
    if (IsDeviceCreated())
    {
      HR(m_device3D->GetDeviceRemovedReason());
    }
    else
    {
      CreateDeviceResources();
    }
    VERIFY(ValidateRect(m_window, nullptr));
  }
  catch (ComException const & e)
  {
    ReleaseDeviceResources();
  }
}

Метод CreateDeviceResources заранее создает стек устройства. Пока все идет нормально, обработчик сообщений WM_PAINT ничего не делает, кроме проверки того, действительно ли окно. Если обнаруживается потеря устройства, различные блоки catch будут освобождать устройство и при необходимости объявлять окно недействительным. Следующее принятое сообщение WM_PAINT привет к повторному созданию аппаратных ресурсов. В следующем выпуске своей рубрики я покажу, как создавать визуальные эффекты, не связанные напрямую с пользовательским вводом. Поскольку механизм композиции берет на себя больше работы, связанной с рендерингом, без участия приложения, появляется возможность того, что устройство будет потеряно, а приложение даже не узнает об этом. Вот почему вызывается метод GetDeviceRemovedReason. Если механизм композиции обнаружит потерю устройства, он отправит окну приложения сообщение WM_PAINT только для того, чтобы приложение могло проверить, потеряно ли устройство, вызвав метод GetDeviceRemovedReason Direct3D-устройства. Поэкспериментируйте с DirectComposition, используя проект-пример, сопутствующий этой статье!

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


Оценить статью:
Вверх
Комментарии посетителей RSS

Чтобы оставить комментарий, зарегистрируйтесь или войдите с учетной записью социальной сети.