Вступительное слово.
В данной статье рассмотрю начало работы с графикой, а именно что нужно сделать, чтобы вывести геометрию любой сложности (в рамках 2D) и любого цвета. Всё остальное рассмотрим дальше, в следующих статьях.
Из прошлых статей известно, что есть клиентская область окна, где у нас полная власть над содержимым, и неклиентская, где тоже власть, но меньше. А отсюда можно легко прийти к тому, что есть некие массивы, которые содержат информацию о пикселях клиентской области, и их нам надо менять. По сути, так оно и работает, только я очень упростил.
Интересующий нас массив называется буфером. Есть их два типа - Back и Front. Первый - туда непосредственно идёт запись, а уже итоговый результат помещается в Front и отображается на экране. Но это тоже упрощение.
Сами буферы и различные функции относятся к интерфейсу IDXGISwapChain. Но это так, к слову, мы пока особо ничего такого затрагивать не будем. (DXGI - это Microsoft DirectX Graphics Infrastructure (Графическая инфраструктура DirectX).)
В целом для лучшего понимания нужно учитывать, что есть такая цепочка:
Физическая видеокарта (GPU)
Драйвер видеокарты (Display Driver)
DXGI Адаптер (IDXGIAdapter)
DXGI Устройство (IDXGIDevice) -> D3D11 Устройство (ID3D11Device)
D2D1 Устройство (ID2D1Device) -> D2D1 Фабрика (ID2D1Factory)
D2D1 Контекст устройства (ID2D1DeviceContext)
Рендер-таргет (ID2D1RenderTarget)
Окно (HWND) → Desktop Window Manager (DWM)
Физическая видеокарта это и есть видеокарта
Драйвер видеокарты (Display Driver) - включает в себя WDDM 2.0+ (Windows Display Driver Model), а он уже на Kernel Mode и User Mode(nvldumd.dll если драйвер от нвидиа и atiumd64.dll если драйвер от амд), DXGI Runtime (dxgi.dll)
-
DXGI Адаптер (IDXGIAdapter) - логическое представление видеокарта в DXGI, от него отходят:
-
IDXGIObject, от него:
-
IDXGIAdapter(Адаптер (видеокарта)), от него:
IDXGIOutput1 (Расширенные возможности)
IDXGIOutput6 (HDR поддержка)
IDXGIAdapter2 (Дополнительная информация)
-
IDXGIDevice (Устройство (логическое))
IDXGIFactory (Фабрика для создания объектов). Собственно этим будем очень часто пользоваться.
-
Рендер-таргет (ID2D1RenderTarget) - собственно сегодня с ним познакомимся, от него наследуется разные варианты куда рисовать.
Вся работа с графикой состоит в первую очередь из создания IDXGIFactory, но в Direct2D это называется ID2D1Factory. Ещё важно то, что к подобным данным мы напрямую доступа не имеем (реализация скрыта), поэтому храним указатель на объект, и доступ к его функциям осуществляется через разыменование, а также любые действия, такие как создание, - через передачу указатель на указатель на ID2D1Factory. Собственно, где угодно пишем:
ID2D1Factory* pFactory = nullptr;
Теперь - создание фабрики. Вызываем функцию D2D1CreateFactory. Первый аргумент - это флаг: D2D1_FACTORY_TYPE_SINGLE_THREADED (однопоточное приложение) или D2D1_FACTORY_TYPE_MULTI_THREADED (многопоточное приложение), второй аргумент - это указатель на указатель на ID2D1Factory.
D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &pFactory);
Как я говорил, есть IDXGISwapChain, а в ней - буферы, но инициализирует их цель рендеринга (Render Target) (упрощение). Цель рендеринга, как можно догадаться из названия, определяет, куда будет происходить рисование. Всего их четыре вида, но нас интересует пока один - ID2D1HwndRenderTarget. Это оконный вывод, который привязан к конкретному окну. И тут, как с ID2D1Factory: создаём через ID2D1Factory, разыменовываем и вызываем функцию CreateHwndRenderTarget. Аргументы:
Аргументы функции CreateHwndRenderTarget
-
ссылка на константу D2D1_RENDER_TARGET_PROPERTIES название renderTargetProperties - это настройки самой цели рендера. Получаем структуру требуемую, через другую функцию RenderTargetProperties во внутреннем пространстве(namespace) D2D1 (пишем D2D1::RenderTargetProperties), аргументы:
-
флаг D2D1_RENDER_TARGET_TYPE название type. Объяснение: Тип рендеринга(CPU или GPU). Варианты флага:
флаг D2D1_RENDER_TARGET_TYPE_SOFTWARE значение 0x1 - программный рендеринг (CPU).
флаг D2D1_RENDER_TARGET_TYPE_HARDWARE значение 0x2 - аппаратное ускорение (GPU).
флаг D2D1_RENDER_TARGET_TYPE_DEFAULT значение 0x0 - система сама определит(стоит по умолчанию(но лучше в ручную указать)).
-
ссылка на константу D2D1_PIXEL_FORMAT название pixelFormat. Устанавливает значение, которое является результатом функции PixelFormat в namespace D2D1, аргументы:
-
какой-либо флаг DXGI_FORMAT - задаёт кол-во байтов под канал и порядок цветовых каналов. Флаги:
DXGI_FORMAT_UNKNOWN - авто-выбор (обычно B8G8R8A8_UNORM).
DXGI_FORMAT_B8G8R8A8_UNORM - 8 бит на канал, первый канал Blue, после Green , Red и последние Aplha.
DXGI_FORMAT_R8G8B8A8_UNORM - 8 бит на канал, первый канал Reg, после Green , Blue и последние Aplha.
DXGI_FORMAT_R16G16B16A16_FLOAT - 16 бит на канал, первый канал Reg, после Green , Blue и последние Aplha.
-
DXGI_FORMAT_R32G32B32A32_FLOAT - 32 бит на канал, первый канал Reg, после Green , Blue и последние Aplha.
К слову если 8 бит на канал, то это значение нормализовано в шейдере как значения от 0 до 1, если 16 бит то от -65504.0, +65504.0, если 32 то от -3.4e38 до +3.4e38.
-
Какой-либо флаг D2D1_ALPHA_MODE - задаёт режим прозрачности. Флаги:
флаг D2D1_ALPHA_MODE_UNKNOWN значение 0 - Система выбирает автоматически.
флаг D2D1_ALPHA_MODE_PREMULTIPLIED значение 1 - Предумноженный альфа-канал. Формула RGB = (R×A, G×A, B×A).
флаг D2D1_ALPHA_MODE_STRAIGHT значение 2 - Прямой (не предумноженный) альфа-канал. Формула RGB = (R, G, B) независимо от альфы.
флаг D2D1_ALPHA_MODE_IGNORE значение 3 - Альфа-канал игнорируется.
тип FLOAT название dpiX - значение dpi по оси X.
тип FLOAT название dpiY - значение dpi по оси Y.
-
тип D2D1_RENDER_TARGET_USAGE название usage - определяет тип рендер-таргета, возможность выполнения в память, сжатие, отправка на сервер. Флаги:
флаг D2D1_RENDER_TARGET_USAGE_NONE - Стандартный рендер-таргет без специальных возможностей.
флаг D2D1_RENDER_TARGET_USAGE_FORCE_BITMAP_REMOTING - Принудительное использование битмап-рендеринга(по сути изображение в памяти). Схема такая: Локальный рендеринг → Битмап → Сжатие → Передача по сети → Отображение на клиенте.
флаг D2D1_RENDER_TARGET_USAGE_GDI_COMPATIBLE - Возможность взаимодействия со старым GDI API
-
тип D2D1_FEATURE_LEVEL_DEFAULT название minLevel - минимальная поддержка DirectX. (9 или 10, при выборе 10 производительность будет в разы лучше). Флаг:
флаг D2D1_FEATURE_LEVEL_9 - DirectX 9
-
флаг D2D1_FEATURE_LEVEL_10 - DirectX 10
Разницы там много, начиная что в DirectX 10 есть Shader Model 4.0 , возможность создания сложных эффектов ну и так далее.
-
-
-
тип ссылка на константу D2D1_HWND_RENDER_TARGET_PROPERTIES название hwndRenderTargetProperties, получаем благодаря функции HwndRenderTargetProperties, по сути очередные настройки. Аргументы:
Аргументы функции
тип HWND название hwnd - ид(дескриптор) окна.
тип D2D1::SizeU название pixelSize - D2D1::SizeU это структура где два поля тип UINT32(unsigned int) первый это ширина, второй высота. По умолчанию если аргумент содержит структуру SizeU содержащую нули, для быстрого получения структуры используйте функцию D2D1::Size(x,y);
-
флаги D2D1_PRESENT_OPTIONS:
флаг
D2D1_PRESENT_OPTIONS_NONEзначение 0 - VSYNC включён.флаг
D2D1_PRESENT_OPTIONS_IMMEDIATELYзначение 2 - VSYNC отключён. Производительность выше.флаг
D2D1_PRESENT_OPTIONS_RETAIN_CONTENTSзначение 1 - Особый режим, при нём не нужна очистка(об этом дальше), вы просто каждый раз рисуете что-то новое, не убирая старое.
тип ссылка на указатель на структуру ID2D1HwndRenderTarget.
ID2D1HwndRenderTarget* pRenderTarget = nullptr;
pFactory->CreateHwndRenderTarget(
D2D1::RenderTargetProperties(D2D1_RENDER_TARGET_TYPE_HARDWARE,D2D1::PixelFormat(DXGI_FORMAT_R8G8B8A8_UNORM, D2D1_ALPHA_MODE_UNKNOWN),0.0f,0.0f, D2D1_RENDER_TARGET_USAGE_NONE, D2D1_FEATURE_LEVEL_10),
D2D1::HwndRenderTargetProperties(hMainHwnd, size),
&pRenderTarget
);
Теперь создав цель рендеринга, можно приступить к рисованию.
Рисование начинается с разыменование ID2D1HwndRenderTarget и вызова функции BeginDraw, она ничего не принимает и не возвращает, но выполняет ряд важных действий:
Список действий функции BeginDraw
Захват мьютекса доступа к GPU контексту(это по сути различные данные нужны для графической работы - Ресурсы видеопамяти, Командным буферам GPU и т.п.). Если кто-то читает и ни разу не читал о многопоточности, то захват мьютекса это обеспечение гарантий того, что ничто не получит доступ к чему-то через различные для этого инструменты(вызов функции и получения копии и т.п. В общем многие способы).
Подготовка командного буфера. Если по простому именно в нём копятся все команды для рисования.
-
Валидация состояния устройства (видеокарты), система проверяет всё ли хорошо, если нет, то получается ошибки разных видов:
GPU перестал отвечать (5+ секунд) - DXGI_ERROR_DEVICE_HUNG
Видеокарта физически извлечена - DXGI_ERROR_DEVICE_REMOVED
Драйвер выполнил сброс устройства - DXGI_ERROR_DEVICE_RESET
Внутренняя ошибка драйвера - DXGI_ERROR_DRIVER_INTERNAL_ERROR
-
Неверные параметры вызовов - DXGI_ERROR_INVALID_CALL
Но в целом, об этом в другой статье.
Блокировка back buffer лишь для записи, как с первым пунктом, но сугубо для других операций.
Сброс счётчиков ошибок и метрик.
Теперь EndDraw:
Список действий функции EndDraw
Финализация командного буфера. По сути выполняются проверки , сортировки и отправка GPU.
Синхронизация CPU-GPU. По сути ожидание, пока GPU выполнит все команды.
Презентация кадра (только для HwndRenderTarget). По сути отправка одного кадра и подготовка нового и обработка потери устройства.
Проверка ошибок и состояния устройства. Если GPU не доступно, будут ошибки и нужно будет пересоздать устройство, но об этом в следующих статьях(и не много ниже, что бы понять что за ошибка, достаточно записать код полученный результатом от EndDraw и проверить). Но так же в целом проверяет буферы и т.п.
Освобождение ресурсов и разблокировка.
Сбор метрик и статистики.
Обработка особых сценариев. По сути восстановление после ошибок.
Оптимизации для следующего кадра.
Перед рисованием есть ещё одна важная тема: многим ресурсам, например, тем, что созданы через ID2D1HwndRenderTarget, нужно вручную освобождать память, как и самому ID2D1HwndRenderTarget. Они называются устройство-зависимые ресурсы (Device-Dependent). Ещё есть устройство-независимые ресурсы (Device-Independent). Немного о них и о том, что вообще надо делать:
-
Устройство-зависимые ресурсы (Device-Dependent):
Ресурсы, которые хранятся в видеопамяти GPU и привязаны к конкретному графическому устройству. Если что-то с устройством случится, ресурсы нужно будет пересоздать.
Ситуации при которых надо освободить ресурсы и создать по новой.
-
Код ошибка(результат функции)(HRESULT) D2DERR_RECREATE_TARGET из EndDraw(). Основные причины:
Основные причины
Изменения в системе графики: обновление драйвера видеокарты, переключение между дискретной и интегрированной графикой (в ноутбуках), физическое отключение монитора или видеокарты.
Изменение режима отображения: смена разрешения экрана, выход из полноэкранного режима (например, по Alt+Tab) или блокировка экрана (Ctrl+Alt+Del).
Проблемы с драйвером или памятью: аварийный сброс драйвера видеокарты (TDR - Timeout Detection and Recovery) или исчерпание видеопамяти (VRAM).
Ошибки в коде приложения: некорректное использование API, например, рисование за границами битмапа, может вызывать сбой на некоторых видеокартах.
Изменение разрешения экрана. В оконную процедуру придёт сообщение WM_DISPLAYCHANGE.
Переключение полноэкранного режима. В оконную процедуру придёт сообщение WM_SIZE.
Смена активного GPU.(Переключение между видеокартами или на горячую если вытащили). В оконную процедуру придёт сообщение WM_POWERBROADCAST(wParam должно равно быть PBT_APMRESUMEAUTOMATIC).
TDR (Timeout Detection & Recovery). GPU завис и был сброшен системой. То есть EndDraw вернуло DXGI_ERROR_DEVICE_RESET или DXGI_ERROR_DEVICE_REMOVED или DXGI_ERROR_DEVICE_HUNG. Или когда драйвер видеокарты обновился.
Изменение DPI/масштабирования. В оконную процедуру придёт сообщение WM_DPICHANGED.
Потеря фокуса полноэкранного приложения. В оконную процедуру придёт сообщение WM_ACTIVATEAPP.
Изменение формата пикселей/цветового пространства. В оконную процедуру придёт сообщение WM_DISPLAYCHANGE.
Системные события питания. Спящий режим/гибернация. В оконную процедуру придёт сообщение WM_POWERBROADCAST. Если wParam равен PBT_APMSUSPEND, то это обозначение что устройство ушло в сон. Если PBT_APMRESUMESUSPEND выход из сна, если PBT_APMRESUMEAUTOMATIC - GPU мог измениться (ноутбук подключили к док-станции).
Изменение конфигурации мониторов. Подключение/отключение мониторов. В оконную процедуру придёт сообщение WM_DEVICECHANGE, wParam должен быть равен DBT_DEVNODES_CHANGED.
Ошибки выделения видеопамяти. Нехватка VRAM. Это будет в следующих статьях.
Смена темы оформления Windows. В оконную процедуру придут такие сообщения как WM_THEMECHANGED или WM_SYSCOLORCHANGE.
Обновление Windows. Критические системные обновления. Отследит сложно. Но если обновление задело графику, то это сломает скорей всего приложение.
Собственно, что сделать. Вызывайте функцию Release из каждого ресурса, и по новой создаёте.
-
-
Устройство-независимые ресурсы (Device-Independent):
Собственно, что нужно сделать: вызовите метод Release у каждого такого ресурса, а затем создайте их заново.
Что бы начать рисовать геометрические объекты, нужно задавать их координаты, а делается это через различные структуры:
Структуры для задавания координат
структура D2D1_POINT_2F - первое поле тип FLOAT и хранит координату по оси X, второе поле тип FLOAT и хранит координату по оси Y. Способ получения через функцию Point2F в пространстве имён D2D1, аргументы функции идентичные полям структуры.
структура D2D1_RECT_F - первое поле тип FLOAT и хранит координату по оси X верхнего левого угла прямоугольника , второе поле тип FLOAT и хранит координату по оси Y верхнего левого угла прямоугольника, третье поле тип FLOAT и хранит координату X правого нижнего угла прямоугольника, четвёртое поле тип FLOAT и хранит координату Y правого нижнего угла прямоугольника. Способ получения через функцию RectF в пространстве имён D2D1, аргументы функции идентичные полям структуры.
структура D2D1_ROUNDED_RECT - первое поле структура D2D1_RECT_F , второе поле тип FLOAT хранит радиус по оси X, третье поле тип FLOAT хранит радиус по оси Y. Способ получения через функцию RoundedRect в пространстве имён D2D1.
cтруктура D2D1_ARC_SEGMENT - первое поле
pointтипа D2D1_POINT_2F хранит конечную точку дуги, второе полеsizeтипа D2D1_SIZE_F хранит размеры эллипса (радиусы), третье полеrotationAngleтипа FLOAT хранит угол поворота эллипса в градусах, четвертое полеsweepDirectionтипа D2D1_SWEEP_DIRECTION задает направление обхода дуги(флаг D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE число 0 - рисуется против часовой. Флаг D2D1_SWEEP_DIRECTION_CLOCKWISE число 1 - рисуется по часовой), пятое полеarcSizeтипа D2D1_ARC_SIZE задает размер дуги (малая или большая). Способ получения через функцию ArcSegment в пространстве имён D2D1.cтруктура D2D1_BEZIER_SEGMENT - первое поле
point1типа D2D1_POINT_2F хранит первую контрольную точку, второе полеpoint2типа D2D1_POINT_2F хранит вторую контрольную точку, третье полеpoint3типа D2D1_POINT_2F хранит конечную точку кривой Безье. Способ получения через функцию BezierSegment в пространстве имён D2D1.cтруктура D2D1_QUADRATIC_BEZIER_SEGMENT - первое поле
point1типа D2D1_POINT_2F хранит контрольную точку, второе полеpoint2типа D2D1_POINT_2F хранит конечную точку квадратичной кривой Безье. Способ получения через функцию QuadraticBezierSegment в пространстве имён D2D1.указатель на любую структуру наследующуюся от ID2D1Geometry - создаётся и заполняется через фабрику(разыменование ID2D1Factory и вызов функции CreatePathGeometry с передачей одного аргумента - указатель на указатель на структуру ID2D1PathGeometry. После создания, разыменовываем и вызываем функцию Open и передаём указатель на указатель на ID2D1GeometrySink это по сути набор геометрии. О заполнении позже.
А ещё, кроме координат, нужно указать цвет. И, хотя это может показаться неочевидным, мы не можем сделать это просто - для этого нам нужно передать указатель на указатель на структуру ID2D1SolidColorBrush. При этом это самый простой вид кисти (кисть хранит информацию о цвете, но о них тоже поговорим позже). Она, как и другие, наследуется от ID2D1Brush. Пишем:
ID2D1SolidColorBrush* brush = nullptr;
Чтобы создать кисть, разыменуйте указатель ID2D1HwndRenderTarget* и вызовите метод CreateSolidColorBrush. Первый аргумент - структура D2D1_COLOR_F, у которой первое поле имеет тип FLOAT и название r (red), а далее три аналогичных поля: g (green), b (blue) и a (alpha).
Получить структуру можно через перечисление ColorF в пространстве имён D2D1 (например, D2D1::ColorF::AliceBlue). Если нужно указать свой цвет, используйте конструктор:
D2D1::ColorF(r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f);
где вместо букв подставляются значения цветовых компонентов.
Второй аргумент - указатель на указатель на ID2D1SolidColorBrush. В результате получите кисть с нужным цветом.
ID2D1SolidColorBrush* brush = nullptr;
pRenderTarget->CreateSolidColorBrush(D2D1::ColorF::ColorF(255.0f / 255.0f,255.0f / 255.0f,255.0f / 255.0f,255.0f / 255.0f), &brush);
Алгоритм определения устройство-зависимых ресурсов: по интерфейсу создания - если ресурс создаётся через рендер-таргет, он является устройство-зависимым.
Создав кисть, можно перейти к функциям рисования: квадратов, прямых, сложной геометрии и т.п.
Рисуем базовые примитивы - линии и фигуры. (Все функции находятся в рендер-таргете, вызываются через разыменование указателя):
Базовые примитивы - линии и фигуры
-
Линии:
-
функция DrawLine возвращает тип void - принимает 4 аргумента:
структура D2D1_POINT_2F назначение - первая координата линии . Получить экземпляр можно в качестве результат функции D2D1::Point2F , первый аргумент которой это координата X (float тип), а второй тип координата Y(float тип) .
структура D2D1_POINT_2F назначение - вторая координата линии.
указатель на структуру ID2D1Brush (по сути любая кисть наследуется от этого класса) назначение - цвет линии.
тип FLOAT назначение - толщина линии(в пикселях).
-
-
Прямоугольник:
-
функция DrawRectangle возвращает тип void - принимает 3 аргумента:
структура D2D1_RECT_F назначение - прямоугольник для обводки. Получить экземпляр можно в качестве результат функции D2D1::RectF, аргументы которой это координаты left, top, right, bottom (все типа FLOAT).
указатель на структуру ID2D1Brush назначение - кисть для обводки.
-
тип FLOAT назначение - толщина обводки (в пикселях).
Рисуется контур прямоугольника с заданной толщиной обводки.
-
функция FillRectangle возвращает тип void - принимает 2 аргумента:
структура D2D1_RECT_F назначение - прямоугольник для заливки.
-
указатель на структуру ID2D1Brush назначение - кисть для заливки.
Полностью закрашенный прямоугольник.
-
функция DrawRoundedRectangle возвращает тип void - принимает 3 аргумента:
структура D2D1_ROUNDED_RECT назначение - скругленный прямоугольник для обводки. Получить экземпляр можно в качестве результат функции D2D1::RoundedRect, первый аргумент которой это D2D1_RECT_F, второй и третий - радиусы скругления по X и Y (тип FLOAT).
указатель на структуру ID2D1Brush назначение - кисть для обводки.
-
тип FLOAT назначение - толщина обводки (в пикселях).
Контур прямоугольника со скругленными углами.
-
функция FillRoundedRectangle возвращает тип void - принимает 2 аргумента:
структура D2D1_ROUNDED_RECT назначение - скругленный прямоугольник для заливки.
-
указатель на структуру ID2D1Brush назначение - кисть для заливки.
Полностью закрашенный прямоугольник со скругленными углами.
-
-
Круг или Эллипс:
-
функция DrawEllipse возвращает тип void - принимает 3 аргумента:
структура D2D1_ELLIPSE назначение - эллипс для обводки. Получить экземпляр можно в качестве результат функции D2D1::Ellipse, первый аргумент которой это центр (D2D1_POINT_2F), второй и третий - радиусы по X и Y (тип FLOAT).
указатель на структуру ID2D1Brush назначение - кисть для обводки.
-
тип FLOAT назначение - толщина обводки (в пикселях).
контур эллипса или круга.
-
функция FillEllipse возвращает тип void - принимает 2 аргумента:
структура D2D1_ELLIPSE назначение - эллипс для заливки.
-
указатель на структуру ID2D1Brush назначение - кисть для заливки.
полностью закрашенный эллипс или круг.
-
функция DrawGeometry возвращает тип void - принимает 3 аргумента:
указатель на интерфейс ID2D1Geometry назначение - геометрия для обводки.
указатель на структуру ID2D1Brush назначение - кисть для обводки.
-
тип FLOAT назначение - толщина обводки (в пикселях).
контур произвольной фигуры (многоугольника или сложной формы).
-
функция FillGeometry возвращает тип void - принимает 2 аргумента:
указатель на интерфейс ID2D1Geometry назначение - геометрия для заливки.
-
указатель на структуру ID2D1Brush назначение - кисть для заливки.
полностью закрашенная произвольная фигура.
-
-
Сложная геометрия:
Как говорил ранее, надо заполнить ID2D1GeometrySink для этого через разыменование и вызываем различные функции:
-
Всё начинается с BeginFigure(добавление новой модели геометрической), первый аргумент D2D1_POINT_2F - начальная точка, второй аргумент флаг D2D1_FIGURE_BEGIN(указывают, будет ли фигура заполняться цветом при использовании метода FillGeometry ):
флаг D2D1_FIGURE_BEGIN_FILLED - фигура будет заполнена цветом. Возвращает реальные границы фигуры. То есть, по сути, пишем если наша геометрическая модель замкнутая.
флаг D2D1_FIGURE_BEGIN_HOLLOW - фигура будет создает фигуру только для контура (обводки). Фигура не будет заполнена (останется пустой). Возвращает нулевые границы. То есть, по сути, пишем если наша геометрическая модель не замкнутая.
-
Различные функции для добавления точек:
функция addLine возвращает void и добавляет точку к предыдущей точки добавленной или к начальной. Принимает единственный аргумент, структура D2D1_POINT_2F.
функция AddLines возвращает void и добавляет последовательность линий, соединяя точки из массива последовательно от текущей позиции. Принимает два аргумента: указатель на массив структур D2D1_POINT_2F и количество точек в массиве.
функция AddArc возвращает void и добавляет дугу от текущей позиции до указанной конечной точки. Принимает единственный аргумент - указатель на структуру D2D1_ARC_SEGMENT.
функция AddBezier возвращает void и добавляет кубическую кривую Безье от текущей позиции до конечной точки через две контрольные точки. Принимает единственный аргумент - указатель на структуру D2D1_BEZIER_SEGMENT.
Функция AddQuadraticBezier возвращает void и добавляет квадратичную кривую Безье от текущей позиции до конечной точки через одну контрольную точку. Принимает единственный аргумент - указатель на структуру D2D1_QUADRATIC_BEZIER_SEGMENT.
Функция AddBeziers возвращает void и добавляет последовательность кубических кривых Безье, соединяя их последовательно от текущей позиции. Принимает два аргумента: указатель на массив структур D2D1_BEZIER_SEGMENT и количество кривых в массиве.
Функция AddQuadraticBeziers возвращает void и добавляет последовательность квадратичных кривых Безье, соединяя их последовательно от текущей позиции. Принимает два аргумента: указатель на массив структур D2D1_QUADRATIC_BEZIER_SEGMENT и количество кривых в массиве.
-
Различные манипуляции:
-
Функция SetFillMode возвращает void и устанавливает правило заливки для всей геометрии. Принимает один аргумент: значение D2D1_FILL_MODE, определяющее алгоритм заливки (ALTERNATE или WINDING). Флаги:
D2D1_FILL_MODE_ALTERNATE (0) - правило четности (alternate fill mode). Луч, выпущенный из точки, пересекает контур фигуры. Если количество пересечений нечетное - точка заливается, если четное - не заливается.
D2D1_FILL_MODE_WINDING (1) - правило ненулевого витка (winding fill mode). Учитывает направление обхода контура. Если суммарное число витков (с учетом направления) не равно нулю - точка заливается.
-
Функция SetSegmentFlags возвращает void и устанавливает флаги для последующих сегментов пути. Принимает один аргумент: значение D2D1_PATH_SEGMENT, содержащее битовые флаги управления отображением сегментов. Про D2D1_PATH_SEGMENT:
-
Перечисление D2D1_PATH_SEGMENT - устанавливает флаги для сегментов пути. Принимает значения (можно комбинировать через OR):
D2D1_PATH_SEGMENT_NONE (0x00000000) - без специальных флагов.
D2D1_PATH_SEGMENT_FORCE_UNSTROKED (0x00000001) - сегмент не будет обводиться при вызове DrawGeometry
D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN (0x00000002) - принудительно использовать скругленные соединения линий
-
-
-
Заканчивать фигуру через вызов EndFigure возвращает void и завершает текущую фигуру , принимает флаг(должна быть фигура открыта или закрыта, если первое, то последняя точка не соединяется с первой, иначе соединяется):
D2D1_FIGURE_END_OPEN значение 0 - фигура открыта.
D2D1_FIGURE_END_CLOSED значение 1 - фигура закрыта.
Когда закончили добавлять вызываете Close, это фиксирует геометрию, при этом для ID2D1GeometrySink вызываете Release.
Теперь, что бы отрисовать вашу геометрию вызываете DrawGeometry и первый аргумент передаёте указатель на ID2D1PathGeometry , вторым аргументом кисть, а третий тип FLOAT - ширина. Или заполнить генерацию. FillGeometry - первый аргумент указатель на
ID2D1PathGeometry , второй тип кисти.
-
На этом, по сути, введение завершается. Далее будет рассмотрена отрисовка текстур, текста и т.п. - постепенное углубление в графику.
Также важно отметить, что для сложной геометрии существуют интересные функции, например, встроенные проверки пересечений и другие. Однако, я считаю целесообразным вынести эту тему в отдельную статью вместе с кодом-примером.
Итоговый код-пример со всеми фигурами (логика вынесена в отдельную функцию, добавлена функция SafeRelease, которая освобождает ресурс, если он не nullptr):
Итоговый кодо-пример со всеми примерами рисования
#include <windows.h>
#include <d2d1.h>
#include <d2d1helper.h>
#include <wincodec.h>
#pragma comment(lib, "d2d1.lib")
ID2D1Factory* pFactory = nullptr;
ID2D1HwndRenderTarget* pRenderTarget = nullptr;
ID2D1SolidColorBrush* pBlackBrush = nullptr;
ID2D1SolidColorBrush* pRedBrush = nullptr;
ID2D1SolidColorBrush* pBlueBrush = nullptr;
ID2D1SolidColorBrush* pGreenBrush = nullptr;
template<class T>
void SafeRelease(T** ppT)
{
if (*ppT)
{
(*ppT)->Release();
*ppT = nullptr;
}
}
// Создание устройство-независимых ресурсов
HRESULT CreateDeviceIndependentResources()
{
HRESULT hr = S_OK;
// Создаем фабрику Direct2D
hr = D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED,
&pFactory
);
return hr;
}
// Создание устройство-зависимых ресурсов
HRESULT CreateDeviceResources(HWND hwnd)
{
HRESULT hr = S_OK;
if (!pRenderTarget)
{
RECT rc;
GetClientRect(hwnd, &rc);
D2D1_SIZE_U size = D2D1::SizeU(
rc.right - rc.left,
rc.bottom - rc.top
);
// Настройки рендер-таргета
D2D1_RENDER_TARGET_PROPERTIES rtProps = D2D1::RenderTargetProperties();
rtProps.pixelFormat = D2D1::PixelFormat(
DXGI_FORMAT_B8G8R8A8_UNORM,
D2D1_ALPHA_MODE_IGNORE
);
rtProps.type = D2D1_RENDER_TARGET_TYPE_DEFAULT;
rtProps.usage = D2D1_RENDER_TARGET_USAGE_NONE;
// Настройки HWND рендер-таргета
D2D1_HWND_RENDER_TARGET_PROPERTIES hwndRtProps = D2D1::HwndRenderTargetProperties(
hwnd,
size,
D2D1_PRESENT_OPTIONS_IMMEDIATELY
);
// Создаем рендер-таргет
hr = pFactory->CreateHwndRenderTarget(
rtProps,
hwndRtProps,
&pRenderTarget
);
if (SUCCEEDED(hr))
{
// Создаем кисти разных цветов
hr = pRenderTarget->CreateSolidColorBrush(
D2D1::ColorF(D2D1::ColorF::Black),
&pBlackBrush
);
}
if (SUCCEEDED(hr))
{
hr = pRenderTarget->CreateSolidColorBrush(
D2D1::ColorF(D2D1::ColorF::Red),
&pRedBrush
);
}
if (SUCCEEDED(hr))
{
hr = pRenderTarget->CreateSolidColorBrush(
D2D1::ColorF(D2D1::ColorF::Blue),
&pBlueBrush
);
}
if (SUCCEEDED(hr))
{
hr = pRenderTarget->CreateSolidColorBrush(
D2D1::ColorF(D2D1::ColorF::Green),
&pGreenBrush
);
}
}
return hr;
}
// Освобождение устройство-зависимых ресурсов
void DiscardDeviceResources()
{
SafeRelease(&pRenderTarget);
SafeRelease(&pBlackBrush);
SafeRelease(&pRedBrush);
SafeRelease(&pBlueBrush);
SafeRelease(&pGreenBrush);
}
// Функция отрисовки сложной геометрии
void DrawComplexGeometry()
{
ID2D1PathGeometry* pPathGeometry = nullptr;
ID2D1GeometrySink* pSink = nullptr;
// Создаем PathGeometry через фабрику
HRESULT hr = pFactory->CreatePathGeometry(&pPathGeometry);
if (SUCCEEDED(hr))
{
// Открываем GeometrySink для записи
hr = pPathGeometry->Open(&pSink);
}
if (SUCCEEDED(hr))
{
// Начинаем фигуру (замкнутую)
pSink->BeginFigure(
D2D1::Point2F(300.0f, 100.0f),
D2D1_FIGURE_BEGIN_FILLED
);
// Добавляем линии
pSink->AddLine(D2D1::Point2F(400.0f, 200.0f));
pSink->AddLine(D2D1::Point2F(300.0f, 300.0f));
// Добавляем квадратичную кривую Безье
D2D1_QUADRATIC_BEZIER_SEGMENT quadBezier = D2D1::QuadraticBezierSegment(
D2D1::Point2F(200.0f, 200.0f), // контрольная точка
D2D1::Point2F(300.0f, 100.0f) // конечная точка
);
pSink->AddQuadraticBezier(&quadBezier);
// Завершаем фигуру (замкнутую)
pSink->EndFigure(D2D1_FIGURE_END_CLOSED);
// Закрываем sink
hr = pSink->Close();
}
if (SUCCEEDED(hr))
{
// Рисуем контур сложной геометрии
pRenderTarget->DrawGeometry(pPathGeometry, pBlueBrush, 3.0f);
// Заливаем сложную геометрию
pRenderTarget->FillGeometry(pPathGeometry, pGreenBrush);
}
SafeRelease(&pSink);
SafeRelease(&pPathGeometry);
}
// Основная функция отрисовки
void OnRender(HWND hwnd)
{
HRESULT hr = CreateDeviceResources(hwnd);
if (SUCCEEDED(hr) && !(pRenderTarget->CheckWindowState() & D2D1_WINDOW_STATE_OCCLUDED))
{
pRenderTarget->BeginDraw();
// Очищаем область белым цветом
pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White));
// 1. Рисуем линию
pRenderTarget->DrawLine(
D2D1::Point2F(50.0f, 50.0f),
D2D1::Point2F(150.0f, 50.0f),
pBlackBrush,
2.0f
);
// 2. Рисуем прямоугольник (контур)
pRenderTarget->DrawRectangle(
D2D1::RectF(50.0f, 70.0f, 150.0f, 120.0f),
pRedBrush,
2.0f
);
// 3. Рисуем залитый прямоугольник
pRenderTarget->FillRectangle(
D2D1::RectF(170.0f, 70.0f, 270.0f, 120.0f),
pRedBrush
);
// 4. Рисуем скругленный прямоугольник (контур)
D2D1_ROUNDED_RECT roundedRect = D2D1::RoundedRect(
D2D1::RectF(50.0f, 140.0f, 150.0f, 190.0f),
10.0f, 10.0f
);
pRenderTarget->DrawRoundedRectangle(
roundedRect,
pBlueBrush,
2.0f
);
// 5. Рисуем залитый скругленный прямоугольник
D2D1_ROUNDED_RECT filledRoundedRect = D2D1::RoundedRect(
D2D1::RectF(170.0f, 140.0f, 270.0f, 190.0f),
15.0f, 15.0f
);
pRenderTarget->FillRoundedRectangle(
filledRoundedRect,
pBlueBrush
);
// 6. Рисуем эллипс (контур)
D2D1_ELLIPSE ellipse = D2D1::Ellipse(
D2D1::Point2F(100.0f, 250.0f),
30.0f, 20.0f
);
pRenderTarget->DrawEllipse(
ellipse,
pGreenBrush,
2.0f
);
// 7. Рисуем залитый эллипс (круг)
D2D1_ELLIPSE filledEllipse = D2D1::Ellipse(
D2D1::Point2F(200.0f, 250.0f),
25.0f, 25.0f
);
pRenderTarget->FillEllipse(
filledEllipse,
pGreenBrush
);
// 8. Рисуем сложную геометрию
DrawComplexGeometry();
hr = pRenderTarget->EndDraw();
// Если устройство потеряно, пересоздаем ресурсы
if (hr == D2DERR_RECREATE_TARGET)
{
DiscardDeviceResources();
}
}
}
// Оконная процедура
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_SIZE:
{
if (pRenderTarget)
{
RECT rc;
GetClientRect(hwnd, &rc);
D2D1_SIZE_U size = D2D1::SizeU(
rc.right - rc.left,
rc.bottom - rc.top
);
pRenderTarget->Resize(size);
InvalidateRect(hwnd, nullptr, FALSE);
}
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
BeginPaint(hwnd, &ps);
OnRender(hwnd);
EndPaint(hwnd, &ps);
}
break;
case WM_DISPLAYCHANGE:
InvalidateRect(hwnd, nullptr, FALSE);
break;
case WM_DESTROY:
DiscardDeviceResources();
SafeRelease(&pFactory);
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
return 0;
}
// Точка входа
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow)
{
// Регистрируем класс окна
WNDCLASSEX wcex = { sizeof(WNDCLASSEX) };
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(nullptr, IDI_APPLICATION);
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = nullptr;
wcex.lpszClassName = L"Direct2DDemo";
wcex.hIconSm = LoadIcon(nullptr, IDI_APPLICATION);
if (!RegisterClassEx(&wcex))
return -1;
// Создаем устройство-независимые ресурсы
if (FAILED(CreateDeviceIndependentResources()))
return -1;
// Создаем окно
HWND hwnd = CreateWindow(
L"Direct2DDemo",
L"Direct2D Demo - Основы графики",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
800, 600,
nullptr, nullptr, hInstance, nullptr
);
if (!hwnd)
return -1;
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
// Цикл сообщений
MSG msg = { };
while (GetMessage(&msg, nullptr, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}