惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

The Hacker News
The Hacker News
Jina AI
Jina AI
aimingoo的专栏
aimingoo的专栏
博客园_首页
B
Blog RSS Feed
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Hugging Face - Blog
Hugging Face - Blog
B
Blog
MyScale Blog
MyScale Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
L
LINUX DO - 热门话题
D
Docker
A
Arctic Wolf
G
Google Developers Blog
C
Cybersecurity and Infrastructure Security Agency CISA
Google DeepMind News
Google DeepMind News
Cisco Talos Blog
Cisco Talos Blog
P
Privacy International News Feed
I
Intezer
博客园 - 叶小钗
T
Threat Research - Cisco Blogs
MongoDB | Blog
MongoDB | Blog
美团技术团队
C
CERT Recently Published Vulnerability Notes
Project Zero
Project Zero
S
Securelist
P
Proofpoint News Feed
Simon Willison's Weblog
Simon Willison's Weblog
V
Visual Studio Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
博客园 - 三生石上(FineUI控件)
Forbes - Security
Forbes - Security
C
CXSECURITY Database RSS Feed - CXSecurity.com
The Cloudflare Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
V
Vulnerabilities – Threatpost
N
News and Events Feed by Topic
博客园 - Franky
The Last Watchdog
The Last Watchdog
T
Tailwind CSS Blog
T
Tenable Blog
Hacker News: Ask HN
Hacker News: Ask HN
雷峰网
雷峰网
Know Your Adversary
Know Your Adversary
Scott Helme
Scott Helme
S
SegmentFault 最新的问题
L
Lohrmann on Cybersecurity
F
Full Disclosure
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Direct 2D #11. Анимации
Игорь · 2026-06-27 · via Все публикации подряд на Хабре

Простой

12 мин

119

Всем здравствуйте! Появилось время, и сразу пишу вам. Сегодня поговорим об анимации.

Важно понимать, что Direct2D - это низкоуровневое API, и готовых инструментов в нём нет. Однако существуют вспомогательные компоненты: Windows Animation Manager (WAM) и DirectComposition. Первый появился в Windows 7, второй - в Windows 8.

Windows Animation Manager создан для того, чтобы избавить разработчика от математических расчётов анимации: линейной интерполяции, кривых ускорения/замедления, эффекта пружины и т. д. Вы просто задаёте системе начальные и конечные координаты и эффект, а WAM самостоятельно вычисляет.

Ключевые понятия WAM:

IUIAnimationManager - главный управляющий объект. Он создаёт анимации, отслеживает их выполнение и уведомляет о необходимости обновления кадра.

IUIAnimationVariable - анимируемая переменная. Это может быть координата X, прозрачность, масштаб или любое другое число с плавающей запятой. Вы просто устанавливаете текущее значение переменной, например: «эта переменная сейчас равна 100».

IUIAnimationTransition - закон изменения переменной во времени. WAM предоставляет богатую библиотеку встроенных переходов: линейный (CreateLinearTransition) , с ускорением и замедлением (CreateAccelerateDecelerateTransition), "пружина" (CreateSpringTransition) и многие другие(IUIAnimationTransitionLibrary и IUIAnimationTransitionLibrary2).

IUIAnimationStoryboard - "раскадровка", контейнер, в который вы собираете один или несколько переходов и запускаете их одновременно или последовательно.

В качестве краткого примера (к 11-й статье вы уже умеете работать с графикой, и нет нужды каждый раз писать полотно текста) приведём фрагмент:

В классе окна или приложения объявим необходимые COM-указатели:

// WAM
CComPtr<IUIAnimationManager>          pAnimManager;
CComPtr<IUIAnimationTransitionLibrary> pTransitionLibrary;
CComPtr<IUIAnimationVariable>          pAnimVarX;
CComPtr<IUIAnimationStoryboard>        pStoryboard;

// Direct2D (упрощённо)
CComPtr<ID2D1Factory>          pD2DFactory;
CComPtr<ID2D1HwndRenderTarget> pRenderTarget;
CComPtr<ID2D1SolidColorBrush>  pBrush;

При старте приложения (например, в обработчике WM_CREATE) инициализируется COM и создаются необходимые объекты.

// Инициализация COM (если ещё не сделано)
CoInitialize(NULL);

// Создаём менеджер анимаций
CoCreateInstance(CLSID_UIAnimationManager, NULL, CLSCTX_INPROC_SERVER,
                 IID_IUIAnimationManager, (void**)&pAnimManager);

// Создаём библиотеку переходов
CoCreateInstance(CLSID_UIAnimationTransitionLibrary, NULL, CLSCTX_INPROC_SERVER,
                 IID_IUIAnimationTransitionLibrary, (void**)&pTransitionLibrary);

// Создаём переменную для координаты X. Начальное значение = 0
pAnimManager->CreateAnimationVariable(0.0, &pAnimVarX);

Ниже приведены пояснения к аргументам используемых функций.

CoInitialize:

Первый аргумент зарезервирован и должен принимать значение NULL.

CoCreateInstance:

Первый аргумент - идентификатор класса (CLSID).

Второй аргумент - указатель на управляющий IUnknown при агрегации объектов; в нашем случае агрегация не используется, поэтому передаётся NULL.

Третий аргумент - контекст выполнения. Значение CLSCTX_INPROC_SERVER указывает, что объект должен быть загружен как DLL внутри текущего процесса.

Четвёртый аргумент - указатель на требуемый интерфейс (IID).

Пятый аргумент - адрес переменной, в которую будет передан указатель на созданный объект.

CreateAnimationVariable:

Первый аргумент - начальное значение переменной (тип DOUBLE).

Второй аргумент - адрес указателя на объект IUIAnimationVariable, который будет создан.

Теперь предположим, что мы хотим анимировать перемещение прямоугольника из позиции X = 0 в X = 300 за 2 секунды с эффектом ускорения и замедления.

// Создаём переход: ускорение-замедление, длительность 2 сек, конечное значение 300
CComPtr<IUIAnimationTransition> pTransition;
pTransitionLibrary->CreateAccelerateDecelerateTransition(
    2.0,                 // длительность в секундах
    300.0,               // конечное значение
    0.3,                 // доля ускорения (0..1)
    0.3,                 // доля замедления (0..1)
    &pTransition
);

// Создаём раскадровку
pAnimManager->CreateStoryboard(&pStoryboard);

// Добавляем переход к переменной X
pStoryboard->AddTransition(pAnimVarX, pTransition);

// Запускаем раскадровку (сразу)
pStoryboard->Schedule(0);  // 0 = запустить немедленно

Пояснение аргументов методов:

CreateAccelerateDecelerateTransition - уже описан в комментариях выше (параметры: ускорение, замедление и продолжительность).

CreateStoryboard (метод IUIAnimationManager):

Единственный аргумент - адрес указателя на создаваемую раскадровку (IUIAnimationStoryboard*). Раскадровка представляет собой контейнер, который может содержать один или несколько переходов, применяемых к разным переменным. Все переходы внутри одной раскадровки запускаются одновременно (если не заданы индивидуальные задержки).

AddTransition:

Первый аргумент - указатель на анимируемую переменную (IUIAnimationVariable*).

Второй аргумент - указатель на ранее созданный переход (IUIAnimationTransition*). Именно этот переход определяет закон изменения переменной во времени.

Schedule:

Первый аргумент - момент времени (в секундах) относительно текущего времени системы, с которого должна запуститься раскадровка. Обычно передаётся 0.0 для немедленного старта.

После вызова Schedule анимация начинает выполняться. WAM автоматически вычисляет промежуточные значения переменной в зависимости от текущего времени.

В каждом кадре (например, в обработчике WM_PAINT или в отдельном потоке рендеринга) необходимо выполнить следующие шаги:

Обновить состояние WAM, передав прошедшее время (с помощью метода Update менеджера).

Получить текущее значение анимируемой переменной (метод GetValue).

void RenderFrame()
{
    // 1. Получить время, прошедшее с прошлого кадра (в секундах)
    static double lastTime = 0.0;
    double currentTime = GetCurrentTimeInSeconds(); // ваша функция получения времени
    double deltaTime = currentTime - lastTime;
    lastTime = currentTime;

    // 2. Обновить WAM. Передаём дельту времени (в секундах)
    HRESULT hr = pAnimManager->Update(deltaTime);
    // Если анимация завершилась, можно обработать и, например, запустить обратную

    // 3. Получить текущее значение X
    double currentX;
    pAnimVarX->GetValue(&currentX);

    // 4. Начать рисование через Direct2D
    pRenderTarget->BeginDraw();
    pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White));

    // Рисуем прямоугольник размером 50x50 в точке (currentX, 50)
    D2D1_RECT_F rect = D2D1::RectF((FLOAT)currentX, 50.0f, (FLOAT)(currentX + 50), 100.0f);
    pRenderTarget->FillRectangle(&rect, pBrush);

    // Завершаем рисование
    hr = pRenderTarget->EndDraw();
    if (hr == D2DERR_RECREATE_TARGET) { /* восстановить ресурсы */ }
}

Чтобы организовать движение прямоугольника туда-обратно, можно подписаться на событие завершения раскадровки. Однако для простоты реализуем циклическую проверку: в каждом кадре или в отдельном цикле отслеживаем состояние анимации, и если она завершена, запускаем новую раскадровку с обратным направлением движения.

// После pAnimManager->Update(deltaTime) можно проверить статус:
UI_ANIMATION_MANAGER_STATUS status;
pAnimManager->GetStatus(&status);
if (status == UI_ANIMATION_MANAGER_IDLE)
{
    // Анимация завершена - запускаем новую в обратном направлении
    // Получаем текущее значение X (например, 300)
    double currentX;
    pAnimVarX->GetValue(&currentX);
    // Создаём переход к 0 (или в зависимости от текущего положения)
    CComPtr<IUIAnimationTransition> pReverseTransition;
    pTransitionLibrary->CreateAccelerateDecelerateTransition(
        2.0, 0.0, 0.3, 0.3, &pReverseTransition
    );
    pAnimManager->CreateStoryboard(&pStoryboard);
    pStoryboard->AddTransition(pAnimVarX, pReverseTransition);
    pStoryboard->Schedule(0);
}

Пояснение аргументов:

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

UI_ANIMATION_MANAGER_IDLE - все анимации завершены, менеджер находится в состоянии ожидания;

UI_ANIMATION_MANAGER_BUSY - выполняется как минимум одна раскадровка;

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

Обратите внимание: в вызов Update необходимо передавать актуальное время в секундах. Для его измерения с высокой точностью используйте функцию QueryPerformanceCounter.

double GetCurrentTimeInSeconds()
{
    static LARGE_INTEGER frequency = {0};
    if (frequency.QuadPart == 0)
        QueryPerformanceFrequency(&frequency);
    LARGE_INTEGER now;
    QueryPerformanceCounter(&now);
    return (double)now.QuadPart / (double)frequency.QuadPart;
}

При завершении приложения необходимо освободить все COM-объекты (при использовании умных указателей освобождение произойдёт автоматически при выходе за область видимости). Windows Animation Manager не требует явного вызова завершения или освобождения ресурсов.

DirectComposition. Теперь - о нём.

Если WAM работает на центральном процессоре и лишь вычисляет промежуточные значения, то DirectComposition (доступный начиная с Windows 8) функционирует на стороне видеокарты в отдельном потоке. Он принимает готовые растровые изображения (битмапы), применяет к ним трансформации, эффекты и анимации, после чего выводит результирующую сцену на экран с высокой частотой кадров.

Ключевые понятия DirectComposition:

Визуальное дерево (Visual Tree) - иерархия объектов IDCompositionVisual. Корневой визуальный элемент привязан к окну, дочерние позиционируются относительно родителя. Это позволяет легко строить сложные сцены: например, персонаж представляет собой родительский элемент, а его руки и ноги - дочерние визуалы.

Свойства визуала - положение (OffsetX, OffsetY), трансформации (поворот, масштаб), прозрачность и эффекты.

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

Commit - после настройки дерева и анимаций вызывается метод Commit, после чего все изменения применяются.

Перейдём к примеру.

// Direct3D 11 (нужен для DirectComposition)
CComPtr<ID3D11Device>           m_pD3D11Device;

// DirectComposition
CComPtr<IDCompositionDevice>    m_pDCompDevice;
CComPtr<IDCompositionTarget>    m_pDCompTarget;
CComPtr<IDCompositionVisual>    m_pVisual;

// Direct2D (для создания контента)
CComPtr<ID2D1Factory>           m_pD2DFactory;
CComPtr<ID2D1Device>            m_pD2DDevice;
CComPtr<ID2D1DeviceContext>     m_pD2DContext;
CComPtr<ID2D1Bitmap1>           m_pD2DBitmap;
CComPtr<ID2D1SolidColorBrush>   m_pBrush;

// Объекты анимации
CComPtr<IDCompositionAnimation> m_pAnimX;
CComPtr<IDCompositionAnimation> m_pAnimOpacity;

Инициализация. Данный шаг выполняется однократно при запуске приложения (например, в обработчике сообщения WM_CREATE).

HRESULT Init(HWND hwnd)
{
    HRESULT hr = S_OK;

    // 1. Создаём устройство Direct3D 11 с поддержкой BGRA (нужно для D2D)
    D3D_FEATURE_LEVEL featureLevel;
    hr = D3D11CreateDevice(
        nullptr,
        D3D_DRIVER_TYPE_HARDWARE,
        nullptr,
        D3D11_CREATE_DEVICE_BGRA_SUPPORT, // Важно для Direct2D!
        nullptr,
        0,
        D3D11_SDK_VERSION,
        &m_pD3D11Device,
        &featureLevel,
        nullptr
    );

    // 2. Получаем интерфейс IDXGIDevice от устройства D3D
    CComPtr<IDXGIDevice> pDXGIDevice;
    if (SUCCEEDED(hr))
        hr = m_pD3D11Device->QueryInterface(&pDXGIDevice);

    // 3. Создаём устройство DirectComposition[reference:1]
    if (SUCCEEDED(hr))
        hr = DCompositionCreateDevice(pDXGIDevice, __uuidof(IDCompositionDevice),
                                      reinterpret_cast<void**>(&m_pDCompDevice));

    // 4. Создаём цель композиции для нашего окна[reference:2]
    if (SUCCEEDED(hr))
        hr = m_pDCompDevice->CreateTargetForHwnd(hwnd, TRUE, &m_pDCompTarget);

    // 5. Создаём фабрику Direct2D
    if (SUCCEEDED(hr))
        hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pD2DFactory);

    // 6. Создаём устройство Direct2D из устройства DXGI
    if (SUCCEEDED(hr))
        hr = m_pD2DFactory->CreateDevice(pDXGIDevice, &m_pD2DDevice);

    // 7. Создаём контекст устройства Direct2D
    if (SUCCEEDED(hr))
        hr = m_pD2DDevice->CreateDeviceContext(
            D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
            &m_pD2DContext
        );

    // 8. Создаём битмап с контентом (квадрат 200x200)
    if (SUCCEEDED(hr))
        hr = CreateBitmapContent();

    // 9. Создаём визуал и устанавливаем ему контент[reference:3]
    if (SUCCEEDED(hr))
        hr = m_pDCompDevice->CreateVisual(&m_pVisual);

    if (SUCCEEDED(hr))
        hr = m_pVisual->SetContent(m_pD2DBitmap);

    // 10. Добавляем визуал в корень дерева
    if (SUCCEEDED(hr))
        hr = m_pDCompTarget->SetRoot(m_pVisual);

    // 11. Создаём анимации
    if (SUCCEEDED(hr))
        hr = CreateAnimations();

    // 12. Применяем анимации к визуалу
    if (SUCCEEDED(hr))
    {
        // Анимация положения по X: перемещаемся от 0 до 500
        hr = m_pVisual->SetOffsetX(m_pAnimX);
    }

    if (SUCCEEDED(hr))
    {
        // Анимация прозрачности: применяем эффект с анимацией
        CComPtr<IDCompositionEffect> pEffect;
        hr = m_pDCompDevice->CreateEffect(&pEffect);
        if (SUCCEEDED(hr))
        {
            // Устанавливаем анимацию для свойства Opacity эффекта
            hr = pEffect->SetOpacity(m_pAnimOpacity);
            if (SUCCEEDED(hr))
                hr = m_pVisual->SetEffect(pEffect);
        }
    }

    // 13. Фиксируем все изменения — анимация запускается![reference:4]
    if (SUCCEEDED(hr))
        hr = m_pDCompDevice->Commit();

    return hr;
}

Создание контента (квадрата). Функция, выполняющая отрисовку квадрата в битмапе Direct2D:

HRESULT CreateBitmapContent()
{
    HRESULT hr = S_OK;

    // Размер битмапа
    D2D1_SIZE_U size = D2D1::SizeU(200, 200);

    // Свойства битмапа
    D2D1_BITMAP_PROPERTIES1 props = D2D1::BitmapProperties1(
        D2D1_BITMAP_OPTIONS_TARGET,                     // Можно использовать как цель
        D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM,   // Формат пикселей
                          D2D1_ALPHA_MODE_PREMULTIPLIED),
        96.0f, 96.0f                                    // DPI
    );

    // Создаём битмап в контексте D2D
    hr = m_pD2DContext->CreateBitmap(size, nullptr, 0, props, &m_pD2DBitmap);

    // Рисуем на битмапе
    if (SUCCEEDED(hr))
    {
        // Устанавливаем битмап как цель рисования
        m_pD2DContext->SetTarget(m_pD2DBitmap);
        m_pD2DContext->BeginDraw();
        m_pD2DContext->Clear(D2D1::ColorF(D2D1::ColorF::CornflowerBlue));

        // Создаём кисть и рисуем квадрат с обводкой
        m_pD2DContext->CreateSolidColorBrush(
            D2D1::ColorF(D2D1::ColorF::OrangeRed),
            &m_pBrush
        );

        D2D1_RECT_F rect = D2D1::RectF(20.0f, 20.0f, 180.0f, 180.0f);
        m_pD2DContext->FillRectangle(rect, m_pBrush);
        m_pD2DContext->DrawRectangle(rect, m_pBrush, 5.0f);

        hr = m_pD2DContext->EndDraw();
        m_pD2DContext->SetTarget(nullptr);
    }

    return hr;
}

Создание анимаций. Здесь мы определяем, как именно будут меняться свойства во времени. Вместо использования WAM мы создаём анимационные кривые вручную.

HRESULT CreateAnimations()
{
    HRESULT hr = S_OK;

    // --- Анимация движения по X (от 0 до 500 и обратно) ---
    hr = m_pDCompDevice->CreateAnimation(&m_pAnimX);
    if (SUCCEEDED(hr))
    {
        // Начинаем с 0
        m_pAnimX->SetAbsoluteBeginTime(0);
        m_pAnimX->SetKeyframes(nullptr, 0, nullptr);

        // Первый сегмент: за 3 секунды перемещаемся к 500 с замедлением в конце
        m_pAnimX->AddCubic(
            0.0f,                           // Начальное смещение по времени
            0.0f,                           // Начальное значение
            3.0f,                           // Длительность (сек)
            500.0f,                         // Конечное значение
            0.0f, 0.0f,                     // Контрольные точки (0 = линейная)
            1.0f, 1.0f
        );

        // Второй сегмент: за 3 секунды возвращаемся к 0
        m_pAnimX->AddCubic(
            0.0f,
            500.0f,
            3.0f,
            0.0f,
            0.0f, 0.0f,
            1.0f, 1.0f
        );

        // Указываем, что анимация должна повторяться бесконечно
        m_pAnimX->SetRepeatCount(-1);
    }

    // --- Анимация прозрачности (от 0.2 до 1.0) ---
    hr = m_pDCompDevice->CreateAnimation(&m_pAnimOpacity);
    if (SUCCEEDED(hr))
    {
        m_pAnimOpacity->SetAbsoluteBeginTime(0);

        // Плавно увеличиваем прозрачность от 0.2 до 1.0 за 2 секунды
        m_pAnimOpacity->AddCubic(
            0.0f, 0.2f, 2.0f, 1.0f,
            0.0f, 0.0f, 1.0f, 1.0f
        );

        // Плавно уменьшаем обратно до 0.2 за 2 секунды
        m_pAnimOpacity->AddCubic(
            0.0f, 1.0f, 2.0f, 0.2f,
            0.0f, 0.0f, 1.0f, 1.0f
        );

        // Бесконечное повторение
        m_pAnimOpacity->SetRepeatCount(-1);
    }

    return hr;
}

Пояснение аргументов методов:

CreateAnimation - единственный аргумент - выходной указатель, в который записывается адрес созданного объекта анимации (IDCompositionAnimation*).

SetAbsoluteBeginTime - устанавливает абсолютное время начала анимации относительно глобальной временной шкалы DirectComposition. Передача 0 означает, что анимация начинается с момента применения к свойству (то есть с момента вызова Commit).

SetKeyframes

Этот метод предназначен для задания ключевых кадров (более сложный способ построения анимации). В приведённом коде переданы значения nullptr, 0, nullptr - это фактически сбрасывает предыдущие ключевые кадры (если они были). Аргументы метода:

первый - массив элементов

второй - количество элементов

третий - отдельный ключевой кадр (в данном случае не используется).

AddCubic (добавление кубического сегмента)

Первый сегмент (движение по оси X от 0 до 500 за 3 секунды):

аргумент 1 - смещение внутри анимации, с которого начинается сегмент (0.0f);

аргумент 2 - значение, соответствующее началу сегмента (0.0f);

аргумент 3 - длительность сегмента в секундах (3.0f);

аргумент 4 - конечное значение сегмента (координата X станет равной 500.0f);

аргументы 5-6 - координаты первой контрольной точки кубической кривой Безье;

аргументы 7-8 - координаты второй контрольной точки.

Второй сегмент (движение от 500 обратно к 0 за 3 секунды):

beginValue = 500.0f - старт с 500;

endValue = 0.0f - возврат к 0;

длительность - снова 3 секунды;

контрольные точки такие же (0,0 и 1,1), что обеспечивает плавный старт и финиш.

SetRepeatCount

Задаёт количество повторений анимации после её первого выполнения. Значение -1 (передаётся как UINT, но в коде используется литерал -1, который интерпретируется как 0xFFFFFFFF) означает бесконечное повторение.

Анимация прозрачности (второй блок) создаётся аналогично - через m_pAnimOpacity.

Важно отметить: после вызова Commit() анимация начинает выполняться автоматически, без какого-либо участия приложения. Вам не нужно организовывать цикл обновления, вызывать BeginDraw/EndDraw в каждом кадре или синхронизироваться с WAM. DirectComposition обрабатывает анимацию в отдельном потоке на видеокарте.

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

На этом всё. По сути, вы можете создать свой аналог этих библиотек. Думаю, следующая статья будет посвящена эффектам и слоям, а потом будет создан движок игры, например, как в Stardew Valley, опираясь исключительно на свои же статьи, показав, что не так страшен чёрт, как его рисуют, и что даже такая низкоуровневая API, как Direct2D, вполне подходит инди-разработчикам.

Но, возможно, перед этим я рассмотрю XAudio2 (чтобы в игре был звук), а также сетевую часть (Winsock).

По сути, основная цель - создать игру на тех инструментах, которые рекомендует Microsoft.

При желании материально поддержать перевод и структурирование информации - средства можете отправить через сбор в ЮМани.