Здравствуйте! Это прямое продолжение статьи №9.

Прежде чем начать, важно помнить: Direct2D основан на Direct3D. Раньше мы использовали HWNDRenderTarget, а начиная с Windows 8 появился новый интерфейс, который даёт гораздо более высокую производительность при отрисовке текстур с разными координатами. Однако теперь мы сами должны сделать то, что раньше делал HWNDRenderTarget - к счастью, действий не так много.

Сначала напомню о двух буферах: буфере состояний и буфере команд.

Состояние - это информация о том, где мы рисуем, какой кистью, с какими настройками. То есть все текущие параметры.

Команда - конкретное действие: нарисовать линию, битмап и тому подобное. Команды опираются на текущее состояние.

Оба эти буфера хранит объект DeviceContext (контекст устройства). А созданием ресурсов (кистей, битмапов), управлением их жизнью и всей памятью занимается объект Device (устройство). Остаётся третий главный объект - SwapChain (цепочка обмена). У него два буфера: задний и передний. Пока мы выполняем команды, результат попадает в задний буфер. Когда мы явно вызываем обмен, буферы меняются местами: задний становится передним и выводится на экран, а его содержимое больше не меняется до следующего обмена.

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

Теперь о спрайтах. Спрайты всегда хранятся в спрайтовом пакете (sprite batch). Пакет привязан к одной текстуре, а каждый спрайт - это объект, который говорит, какую часть текстуры взять и как её отрисовать на экране.

Перейдём от теории к практике. Рассмотрим функцию, выполняющую всю необходимую инициализацию.

Код
HRESULT InitDirect2D(HWND hWnd)
{
    HRESULT hr;

    ComPtr<ID3D11Device> d3dDevice;
    hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr,
        D3D11_CREATE_DEVICE_SINGLETHREADED | D3D11_CREATE_DEVICE_BGRA_SUPPORT,
        nullptr, 0, D3D11_SDK_VERSION, d3dDevice.GetAddressOf(), nullptr, nullptr);
    if (FAILED(hr)) return hr;

    ComPtr<IDXGIDevice> dxgiDevice;
    hr = d3dDevice.As(&dxgiDevice);
    if (FAILED(hr)) return hr;

    ComPtr<IDXGIAdapter> adapter;
    hr = dxgiDevice->GetAdapter(adapter.GetAddressOf());
    if (FAILED(hr)) return hr;

    ComPtr<IDXGIFactory2> dxgiFactory;
    hr = adapter->GetParent(IID_PPV_ARGS(dxgiFactory.GetAddressOf()));
    if (FAILED(hr)) return hr;

    RECT rc;
    GetClientRect(hWnd, &rc);
    UINT width = rc.right - rc.left;
    UINT height = rc.bottom - rc.top;
    if (width == 0) width = 800;
    if (height == 0) height = 600;

    DXGI_SWAP_CHAIN_DESC1 swapDesc = {};
    swapDesc.Width = width;
    swapDesc.Height = height;
    swapDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
    swapDesc.SampleDesc.Count = 1;
    swapDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    swapDesc.BufferCount = 2;
    swapDesc.Scaling = DXGI_SCALING_NONE;
    swapDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
    swapDesc.AlphaMode = DXGI_ALPHA_MODE_IGNORE;

    hr = dxgiFactory->CreateSwapChainForHwnd(d3dDevice.Get(), hWnd, &swapDesc, nullptr, nullptr, g_swapChain.GetAddressOf());
    if (FAILED(hr)) return hr;

    D2D1_FACTORY_OPTIONS options = {};
#ifdef _DEBUG
    options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
#endif
    hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, options, g_d2dFactory.GetAddressOf());
    if (FAILED(hr)) return hr;

    ComPtr<ID2D1Device> baseDevice;
    hr = g_d2dFactory->CreateDevice(dxgiDevice.Get(), baseDevice.GetAddressOf());
    if (FAILED(hr)) return hr;
    hr = baseDevice.As(&g_d2dDevice);
    if (FAILED(hr)) return hr;

    ComPtr<ID2D1DeviceContext> baseContext;
    hr = g_d2dDevice->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, baseContext.GetAddressOf());
    if (FAILED(hr)) return hr;
    hr = baseContext.As(&g_d2dContext);
    if (FAILED(hr)) return hr;

    hr = CreateRenderTarget();
    if (FAILED(hr)) return hr;

    hr = LoadTextureFromFile(L"image.png");
    if (FAILED(hr)) return hr;

    hr = g_d2dContext->CreateSpriteBatch(g_spriteBatch.GetAddressOf());
    return hr;
}

HRESULT CreateRenderTarget()
{
    ComPtr<IDXGISurface> backBuffer;
    HRESULT hr = g_swapChain->GetBuffer(0, IID_PPV_ARGS(backBuffer.GetAddressOf()));
    if (FAILED(hr)) return hr;

    D2D1_BITMAP_PROPERTIES1 props = D2D1::BitmapProperties1(
        D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
        D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE)
    );

    hr = g_d2dContext->CreateBitmapFromDxgiSurface(backBuffer.Get(), props, g_targetBitmap.GetAddressOf());
    if (SUCCEEDED(hr))
        g_d2dContext->SetTarget(g_targetBitmap.Get());

    return hr;
}

HRESULT LoadTextureFromFile(const std::wstring& filename)
{
    ComPtr<IWICImagingFactory> wicFactory;
    HRESULT hr = CoCreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(wicFactory.GetAddressOf()));
    if (FAILED(hr)) return hr;

    ComPtr<IWICBitmapDecoder> decoder;
    hr = wicFactory->CreateDecoderFromFilename(filename.c_str(), nullptr, GENERIC_READ, WICDecodeMetadataCacheOnLoad, decoder.GetAddressOf());
    if (FAILED(hr)) return hr;

    ComPtr<IWICBitmapFrameDecode> frame;
    hr = decoder->GetFrame(0, frame.GetAddressOf());
    if (FAILED(hr)) return hr;

    ComPtr<IWICFormatConverter> converter;
    hr = wicFactory->CreateFormatConverter(converter.GetAddressOf());
    if (FAILED(hr)) return hr;

    hr = converter->Initialize(frame.Get(), GUID_WICPixelFormat32bppPBGRA, WICBitmapDitherTypeNone, nullptr, 0.0f, WICBitmapPaletteTypeCustom);
    if (FAILED(hr)) return hr;

    hr = g_d2dContext->CreateBitmapFromWicBitmap(converter.Get(), nullptr, g_spriteTexture.GetAddressOf());
    return hr;
}

Разберём по шагам. Сначала создаётся Device, который относится к Direct3D. Функция D3D11CreateDevice:

ComPtr<ID3D11Device> d3dDevice;
hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr,
    D3D11_CREATE_DEVICE_SINGLETHREADED | D3D11_CREATE_DEVICE_BGRA_SUPPORT,
    nullptr, 0, D3D11_SDK_VERSION, d3dDevice.GetAddressOf(), nullptr, nullptr);
Описание D3D11CreateDevice

Аргументы:

Тип: IDXGIAdapter* Название: pAdapter Значение (диапазон): Любой указатель на адаптер(Видеокарта), полученный через EnumAdapters, или nullptr Текущее значение: nullptr Объяснение: nullptr заставляет систему использовать адаптер по умолчанию (основной GPU).

Тип: D3D_DRIVER_TYPE Название: DriverType Значение (диапазон): D3D_DRIVER_TYPE_HARDWARE, D3D_DRIVER_TYPE_WARP, D3D_DRIVER_TYPE_REFERENCE, D3D_DRIVER_TYPE_SOFTWARE Текущее значение: D3D_DRIVER_TYPE_HARDWARE Объяснение: Используется рекомендуемый драйвер с максимальной производительностью, D3D_DRIVER_TYPE_REFERENCE - более точно, но есть минусы.

Тип: HMODULE Название: Software Значение (диапазон): Дескриптор DLL программного растеризатора или NULL Текущее значение: nullptr Объяснение: При DriverType = HARDWARE не используется.

Тип: UINT Название: Flags Значение (диапазон): Комбинация флагов D3D11_CREATE_DEVICE_FLAG (битовая маска) Текущее значение: D3D11_CREATE_DEVICE_SINGLETHREADED | D3D11_CREATE_DEVICE_BGRA_SUPPORT Объяснение: SINGLETHREADED обещает однопоточный доступ и ускоряет работу; BGRA_SUPPORT обязательно для Direct2D, работающего с форматом B8G8R8A8.

Тип: const D3D_FEATURE_LEVEL* Название: pFeatureLevels Значение (диапазон): Массив D3D_FEATURE_LEVEL или nullptr Текущее значение: nullptr Объяснение: nullptr позволяет системе выбрать максимально доступную версию Direct 3D 11.

Тип: UINT Название: FeatureLevels Значение (диапазон): Количество элементов в pFeatureLevels, иначе 0 Текущее значение: 0 Объяснение: Соответствует pFeatureLevels = nullptr. Тип: UINT Название: SDKVersion Значение (диапазон): Константа D3D11_SDK_VERSION Текущее значение: D3D11_SDK_VERSION Объяснение: Обеспечивает проверку совместимости заголовков и библиотеки.

Тип: ID3D11Device** Название: ppDevice Значение (диапазон): Адрес указателя для создаваемого устройства Текущее значение: d3dDevice.GetAddressOf() Объяснение: Сюда записывается указатель на новое устройство Direct3D 11.

Тип: D3D_FEATURE_LEVEL* Название: pFeatureLevel Значение (диапазон): Адрес переменной для выбранного уровня или nullptr Текущее значение: nullptr Объяснение: просто возможность проверить какой уровень Direct3D 11 был выбран по итогу.

Тип: ID3D11DeviceContext** Название: ppImmediateContext Значение (диапазон): Адрес для немедленного контекста или nullptr Текущее значение: nullptr Объяснение: Мы не используем Immediate Context D3D11, весь рендеринг идёт через контекст Direct2D.

Далее получаем DXGI-интерфейсы, привязанные к тому же GPU, что и d3dDevice. IDXGIDevice - мост между D3D и DXGI, IDXGIAdapter - описание конкретной видеокарты, IDXGIFactory2 - фабрика, которая создаст SwapChain, совместимый с нашим устройством:

ComPtr<IDXGIDevice> dxgiDevice;
hr = d3dDevice.As(&dxgiDevice);
Описание As

Тип: REFIID Название: riid Значение (диапазон): Идентификатор интерфейса (IID) Текущее значение: IID_IDXGIDevice Объяснение: Запрашиваем интерфейс DXGI-устройства, чтобы получить доступ к адаптеру и фабрике.

Тип: void** Название: ppvObject Значение (диапазон): Адрес указателя для запрошенного интерфейса Текущее значение: dxgiDevice.GetAddressOf() Объяснение: После успешного вызова dxgiDevice содержит IDXGIDevice.

Возвращает адаптер - объект, описывающий конкретную видеокарту, на которой было создано устройство. Этот шаг гарантирует, что все последующие объекты DXGI будут связаны с тем же GPU, что и наше D3D-устройство:

ComPtr<IDXGIAdapter> adapter;
hr = dxgiDevice->GetAdapter(adapter.GetAddressOf());
Описание GetAdapter

Тип: IDXGIAdapter** Название: pAdapter Значение (диапазон): Адрес, принимающий указатель на адаптер Текущее значение: adapter.GetAddressOf() Объяснение: Получаем адаптер, на котором создано устройство, гарантируя привязку к тому же GPU.

Получает родительский объект адаптера - фабрику DXGI. Мы запрашиваем интерфейс IDXGIFactory2, который умеет создавать цепочки обмена (swap chains) для окон. Фабрика автоматически принадлежит тому же адаптеру, что и наше устройство.

ComPtr<IDXGIFactory2> dxgiFactory;
hr = adapter->GetParent(IID_PPV_ARGS(dxgiFactory.GetAddressOf()));
Описание GetParent

Тип: REFIID Название: riid Значение (диапазон): IID родительского интерфейса (фабрики) Текущее значение: IID_IDXGIFactory2 Объяснение: Запрашиваем фабрику версии 2, чтобы иметь доступ к CreateSwapChainForHwnd.

Тип: void** Название: ppParent Значение (диапазон): Адрес для получения родительского объекта Текущее значение: dxgiFactory.GetAddressOf() Объяснение: Получаем IDXGIFactory2, привязанную к нашему адаптеру.

Структура описывающая SwapChain:

DXGI_SWAP_CHAIN_DESC1 swapDesc = {};
swapDesc.Width = width;
swapDesc.Height = height;
swapDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
swapDesc.SampleDesc.Count = 1;
swapDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapDesc.BufferCount = 2;
swapDesc.Scaling = DXGI_SCALING_NONE;
swapDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
swapDesc.AlphaMode = DXGI_ALPHA_MODE_IGNORE;
Описание структуры DXGI_SWAP_CHAIN_DESC1

Тип: UINT Название: Width Значение (диапазон): 1…максимальное разрешение адаптера Текущее значение: клиентская ширина окна (если 0, то 800) Объяснение: Ширина заднего буфера; защита от нуля предотвращает ошибки при свёрнутом окне.

Тип: UINT Название: Height Значение (диапазон): 1…максимальное разрешение Текущее значение: клиентская высота окна (если 0, то 600) Объяснение: Высота заднего буфера, аналогично защищена от нуля.

Тип: DXGI_FORMAT Название: Format Значение (диапазон): Формат, поддерживаемый как цель рендеринга Текущее значение: DXGI_FORMAT_B8G8R8A8_UNORM Объяснение: Родной для Direct2D 8-битный BGRA-формат.

Тип: DXGI_SAMPLE_DESC Название: SampleDesc Значение (диапазон): Count 1…32, Quality зависит от адаптера Текущее значение: Count = 1, Quality = 0 Объяснение: MSAA выключен (1 сэмпл на пиксель).

Тип: DXGI_USAGE Название: BufferUsage Значение (диапазон): Комбинация RENDER_TARGET_OUTPUT, SHADER_INPUT, UNORDERED_ACCESS Текущее значение: DXGI_USAGE_RENDER_TARGET_OUTPUT Объяснение: Буфер только для вывода; отсутствие SHADER_INPUT требует флага CANNOT_DRAW при создании D2D-битмапа.

Тип: UINT Название: BufferCount Значение (диапазон): 2…16 для FLIP_DISCARD Текущее значение: 2 Объяснение: Двойная буферизация: один буфер показывается, второй рисуется. Тип: DXGI_SCALING Название: Scaling Значение (диапазон): DXGI_SCALING_NONE или DXGI_SCALING_STRETCH Текущее значение: DXGI_SCALING_NONE Объяснение: Автоматическое растяжение отключено; размеры буфера контролируются вручную при WM_SIZE.

Тип: DXGI_SWAP_EFFECT Название: SwapEffect Значение (диапазон): DISCARD, SEQUENTIAL, FLIP_DISCARD, FLIP_SEQUENTIAL Текущее значение: DXGI_SWAP_EFFECT_FLIP_DISCARD Объяснение: Современная flip-модель с отбрасыванием содержимого после Present; максимальная производительность.

Тип: DXGI_ALPHA_MODE Название: AlphaMode Значение (диапазон): IGNORE, PREMULTIPLIED, STRAIGHT Текущее значение: DXGI_ALPHA_MODE_IGNORE Объяснение: Альфа-канал не участвует в композитинге окна – окно непрозрачное.

Создаёт цепочку обмена (swap chain) для вывода графики в конкретное окно. Цепочка владеет двумя (или более) буферами - передним и задним. Когда мы рисуем в задний буфер, а затем вызываем Present(), буферы меняются местами, и изображение появляется на экране. Здесь задаются размеры буферов, формат пикселей, количество буферов и другие параметры:

hr = dxgiFactory->CreateSwapChainForHwnd(d3dDevice.Get(), hWnd, &swapDesc, nullptr, nullptr, g_swapChain.GetAddressOf());
Описание CreateSwapChainForHwnd

Тип: IUnknown* Название: pDevice Значение (диапазон): Указатель на устройство Direct3D (приводится к IUnknown) Текущее значение: d3dDevice.Get() Объяснение: Устройство, которое будет выполнять рендеринг в буферы цепочки.

Тип: HWND Название: hWnd Значение (диапазон): Дескриптор существующего окна Текущее значение: переданный HWND Объяснение: Окно, в которое будет выводиться изображение.

Тип: const DXGI_SWAP_CHAIN_DESC1* Название: pDesc Значение (диапазон): Указатель на заполненную структуру описания Текущее значение: &swapDesc Объяснение: Задаёт все свойства буферов (размер, формат, количество и т.д.).

Тип: const DXGI_SWAP_CHAIN_FULLSCREEN_DESC* Название: pFullscreenDesc Значение (диапазон): Описание полноэкранного режима или nullptr Текущее значение: nullptr Объяснение: Оставаёмся в оконном режиме.

Тип: IDXGIOutput* Название: pRestrictToOutput Значение (диапазон): Указатель на конкретный монитор или nullptr Текущее значение: nullptr Объяснение: Система сама выбирает монитор с окном.

Тип: IDXGISwapChain1** Название: ppSwapChain Значение (диапазон): Адрес для получателя swap chain Текущее значение: g_swapChain.GetAddressOf() Объяснение: Создаваемая цепочка управления кадрами.

Создание фабрики Direct2D:

    D2D1_FACTORY_OPTIONS options = {};
#ifdef _DEBUG
    options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
#endif
    hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, options, g_d2dFactory.GetAddressOf());
Описание D2D1CreateFactory

Тип: D2D1_FACTORY_TYPE Название: type Значение (диапазон): SINGLE_THREADED или MULTI_THREADED Текущее значение: D2D1_FACTORY_TYPE_SINGLE_THREADED Объяснение: Однопоточный вариант отключает синхронизацию, повышая производительность.

Тип: const D2D1_FACTORY_OPTIONS* Название: pFactoryOptions Значение (диапазон): Структура с debugLevel (NONE, INFORMATION, WARNING, ERROR) Текущее значение: &options с debugLevel = D2D1_DEBUG_LEVEL_INFORMATION (в Debug) Объяснение: Включает подробный отладочный вывод в Debug-сборке; в Release - NONE.

Тип: ID2D1Factory** Название: ppFactory Значение (диапазон): Адрес получателя фабрики Текущее значение: g_d2dFactory.GetAddressOf() Объяснение: Точка входа в Direct2D, используется для создания устройства и общих ресурсов.

Создаёт устройство Direct2D, привязанное к тому же физическому GPU, что и наше DXGI-устройство. Оно управляет ресурсами Direct2D (битмапами, кистями) и создаёт контексты для рисования:

 ComPtr<ID2D1Device> baseDevice;
 hr = g_d2dFactory->CreateDevice(dxgiDevice.Get(), baseDevice.GetAddressOf());
Описание CreateDevice

Тип: IDXGIDevice* Название: dxgiDevice Значение (диапазон): указатель на DXGI-устройство, полученный из того же D3D-устройства Текущее значение: dxgiDevice.Get() Объяснение: связывает Direct2D с конкретным физическим GPU; все ресурсы, создаваемые через это D2D-устройство, будут размещены на этом GPU.

Тип: ID2D1Device** Название: d2dDevice Значение (диапазон): адрес указателя, который получит созданное устройство Direct2D Текущее значение: baseDevice.GetAddressOf() Объяснение: после успешного вызова baseDevice указывает на базовый интерфейс ID2D1Device; далее его можно повысить до ID2D1Device1 через As(), чтобы получить доступ к расширенным возможностям.

Создаёт контекст устройства Direct2D - главный объект для выполнения всех команд рисования. Он хранит состояние (текущую цель, кисти, трансформации) и записывает действия в командный буфер, который затем отправляется на GPU:

ComPtr<ID2D1DeviceContext> baseContext;
hr = g_d2dDevice->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, baseContext.GetAddressOf());
Описание CreateDeviceContext

Тип: D2D1_DEVICE_CONTEXT_OPTIONS Название: options Значение (диапазон): NONE (0) или ENABLE_GDI_COMPATIBLE_RENDERING Текущее значение: D2D1_DEVICE_CONTEXT_OPTIONS_NONE Объяснение: Стандартный контекст без поддержки GDI, наиболее быстрый.

Тип: ID2D1DeviceContext** Название: deviceContext Значение (диапазон): Адрес для получателя контекста Текущее значение: baseContext.GetAddressOf() Объяснение: Создаётся контекст для записи команд рисования; затем повышается до ID2D1DeviceContext1.

Функция CreateRenderTarget:

Получает задний буфер цепочки обмена в виде DXGI-поверхности (IDXGISurface). Индекс 0 всегда соответствует буферу, готовому для рисования в текущем кадре:

ComPtr<IDXGISurface> backBuffer;
HRESULT hr = g_swapChain->GetBuffer(0, IID_PPV_ARGS(backBuffer.GetAddressOf()));
Описание GetBuffer

Тип: UINT Название: Buffer Значение (диапазон): Индекс буфера от 0 до BufferCount-1 Текущее значение: 0 Объяснение: При двойной буферизации индекс 0 всегда является задним буфером.

Тип: REFIID Название: riid Значение (диапазон): IID интерфейса поверхности Текущее значение: IID_IDXGISurface Объяснение: Запрашиваем DXGI-поверхность, чтобы обернуть её в D2D-битмап. Тип: void** Название: ppSurface Значение (диапазон): Адрес указателя для поверхности Текущее значение: backBuffer.GetAddressOf() Объяснение: Получаем IDXGISurface заднего буфера.

Задаёт свойства будущего битмапа Direct2D: флаги TARGET (можно использовать как цель) и CANNOT_DRAW (нельзя использовать как источник для рисования). Флаг CANNOT_DRAW обязателен, потому что поверхность swap chain создана без возможности чтения как текстуры:

D2D1_BITMAP_PROPERTIES1 props = D2D1::BitmapProperties1(
    D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
    D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE)
);
Описание конструктора BitmapProperties1

Тип: D2D1_BITMAP_OPTIONS Название: bitmapOptions Значение (диапазон): Комбинация TARGET, CANNOT_DRAW, CPU_READ Текущее значение: D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW Объяснение: TARGET разрешает цель рендеринга; CANNOT_DRAW запрещает использовать как источник – обязательно для поверхностей без SHADER_INPUT.

Тип: D2D1_PIXEL_FORMAT Название: pixelFormat Значение (диапазон): Структура из DXGI_FORMAT и D2D1_ALPHA_MODE Текущее значение: D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE) Объяснение: Формат пикселя должен совпадать со swap chain; IGNORE отключает обработку альфы при выводе.

Тип: FLOAT (неявно) Название: dpiX, dpiY Значение (диапазон): Положительное число Текущее значение: 96.0f (по умолчанию) Объяснение: Разрешение битмапа в точках на дюйм; стандартное значение подходит для большинства случаев.

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

hr = g_d2dContext->CreateBitmapFromDxgiSurface(backBuffer.Get(), props, g_targetBitmap.GetAddressOf());
Описание CreateBitmapFromDxgiSurface

Тип: IDXGISurface* Название: dxgiSurface Значение (диапазон): Указатель на DXGI-поверхность Текущее значение: backBuffer.Get() Объяснение: Поверхность, которую Direct2D «обернёт» в свой битмап без копирования.

Тип: const D2D1_BITMAP_PROPERTIES1* Название: bitmapProperties Значение (диапазон): Указатель на свойства битмапа или nullptr Текущее значение: &props Объяснение: Передаём опции, в которых критически важен флаг CANNOT_DRAW.

Тип: ID2D1Bitmap1** Название: bitmap Значение (диапазон): Адрес для создаваемого битмапа Текущее значение: g_targetBitmap.GetAddressOf() Объяснение: Выходной битмап, который затем становится целью рендеринга.

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

g_d2dContext->SetTarget(g_targetBitmap.Get());
Описание SetTarget

Тип: ID2D1Image* Название: image Значение (диапазон): Указатель на любое D2D-изображение Текущее значение: g_targetBitmap.Get() (приводится к ID2D1Image) Объяснение: Устанавливает текущую цель рисования; все последующие вызовы будут направлены в задний буфер.

Функция LoadTextureFromFile:

Создаёт фабрику Windows Imaging Component (WIC) - системный компонент для работы с растровыми изображениями. Фабрика открывает файлы, декодирует пиксельные данные и преобразует форматы:

ComPtr<IWICImagingFactory> wicFactory;
HRESULT hr = CoCreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(wicFactory.GetAddressOf()));
Описание CoCreateInstance

Тип: REFCLSID Название: rclsid Значение (диапазон): CLSID зарегистрированного COM-класса Текущее значение: CLSID_WICImagingFactory Объяснение: Класс фабрики Windows Imaging Component для работы с изображениями.

Тип: LPUNKNOWN Название: pUnkOuter Значение (диапазон): nullptr или агрегирующий объект Текущее значение: nullptr Объяснение: Агрегация не используется.

Тип: DWORD Название: dwClsContext Значение (диапазон): CLSCTX_INPROC_SERVER, LOCAL_SERVER и др. Текущее значение: CLSCTX_INPROC_SERVER Объяснение: Загрузка в процесс как DLL для минимальных накладных расходов.

Тип: REFIID Название: riid Значение (диапазон): IID требуемого интерфейса Текущее значение: IID_IWICImagingFactory Объяснение: Запрашиваем интерфейс фабрики WIC.

Тип: LPVOID* Название: ppv Значение (диапазон): Адрес получателя объекта Текущее значение: wicFactory.GetAddressOf() Объяснение: После успешного создания wicFactory указывает на фабрику WIC.

Открывает файл изображения по указанному пути и создаёт декодер, который читает его содержимое. Декодер автоматически определяет формат файла (PNG, JPEG, BMP и т.д.):

ComPtr<IWICBitmapDecoder> decoder;
hr = wicFactory->CreateDecoderFromFilename(filename.c_str(), nullptr, GENERIC_READ, WICDecodeMetadataCacheOnLoad, decoder.GetAddressOf());
Описание CreateDecoderFromFilename

Тип: LPCWSTR Название: wzFilename Значение (диапазон): Путь к файлу в Unicode Текущее значение: filename.c_str() Объяснение: Файл изображения, который необходимо декодировать.

Тип: const GUID* Название: pguidVendor Значение (диапазон): GUID кодека-вендора или nullptr Текущее значение: nullptr Объяснение: Автоматический выбор кодека по сигнатуре файла.

Тип: DWORD Название: dwDesiredAccess Значение (диапазон): GENERIC_READ или GENERIC_WRITE Текущее значение: GENERIC_READ Объяснение: Открываем файл только для чтения.

Тип: WICDecodeOptions Название: metadataCacheOptions Значение (диапазон): CacheOnLoad, CacheOnDemand, NoCache Текущее значение: WICDecodeMetadataCacheOnLoad Объяснение: Метаданные кэшируются сразу при загрузке.

Тип: IWICBitmapDecoder** Название: ppIDecoder Значение (диапазон): Адрес получателя декодера Текущее значение: decoder.GetAddressOf() Объяснение: Созданный декодер для извлечения кадров.

Извлекает первый кадр декодированного изображения. Для статических изображений он единственный; для анимированных (например, GIF) можно получить и остальные кадры:

ComPtr<IWICBitmapFrameDecode> frame;
hr = decoder->GetFrame(0, frame.GetAddressOf());
Описание GetFrame

Тип: UINT Название: nFrameIndex Значение (диапазон): Индекс кадра (0 … кол-во кадров - 1) Текущее значение: 0 Объяснение: Первый (обычно единственный) кадр статического изображения.

Тип: IWICBitmapFrameDecode** Название: ppIFrameDecode Значение (диапазон): Адрес для получателя кадра Текущее значение: frame.GetAddressOf() Объяснение: Получаем декодированное изображение кадра.

Создаёт конвертер, преобразующий пиксели изображения из одного формата в другой:

ComPtr<IWICFormatConverter> converter;
hr = wicFactory->CreateFormatConverter(converter.GetAddressOf());
CreateFormatConverter

Тип: IWICFormatConverter** Название: ppConverter Значение (диапазон): Адрес для создаваемого конвертера Текущее значение: converter.GetAddressOf() Объяснение: Создаёт пустой конвертер, который будет инициализирован далее.

Настраивает конвертер: на вход подаётся исходный кадр, на выходе - формат 32bppPBGRA (8 бит на канал, premultiplied alpha, порядок BGRA). Такой формат напрямую совместим с Direct2D и исключает дополнительные преобразования при рисовании:

hr = converter->Initialize(frame.Get(), GUID_WICPixelFormat32bppPBGRA, WICBitmapDitherTypeNone, nullptr, 0.0f, WICBitmapPaletteTypeCustom);
Описание Initialize

Тип: IWICBitmapSource* Название: pISource Значение (диапазон): Исходное изображение Текущее значение: frame.Get() Объяснение: Кадр, который нужно преобразовать в нужный формат.

Тип: REFWICPixelFormatGUID Название: dstFormat Значение (диапазон): GUID нужного формата пикселей Текущее значение: GUID_WICPixelFormat32bppPBGRA Объяснение: Premultiplied BGRA - оптимальный формат для Direct2D, исключает дополнительное преобразование.

Тип: WICBitmapDitherType Название: dither Значение (диапазон): None, Ordered4x4, Ordered8x8 и др. Текущее значение: WICBitmapDitherTypeNone Объяснение: Дизеринг не нужен для 32-битного цвета.

Тип: IWICPalette* Название: pIPalette Значение (диапазон): Указатель на палитру или nullptr Текущее значение: nullptr Объяснение: Изображение без палитры (true color).

Тип: double Название: alphaThresholdPercent Значение (диапазон): 0.0 – 100.0 Текущее значение: 0.0 Объяснение: Порог прозрачности при работе с палитрой; здесь не используется.

Тип: WICBitmapPaletteType Название: paletteTranslate Значение (диапазон): Custom, MedianCut и т.д. Текущее значение: WICBitmapPaletteTypeCustom Объяснение: Тип преобразования палитры; не влияет при отсутствии палитры.

Создаёт битмап Direct2D из подготовленного WIC-источника. Так как изображение уже приведено к нужному формату, дополнительные свойства не требуются. Полученный битмап можно использовать как текстуру спрайта:

hr = g_d2dContext->CreateBitmapFromWicBitmap(converter.Get(), nullptr, g_spriteTexture.GetAddressOf());
Описание CreateBitmapFromWicBitmap

Тип: IWICBitmapSource* Название: wicBitmapSource Значение (диапазон): Любой WIC-источник Текущее значение: converter.Get() Объяснение: Конвертированное изображение в формате PBGRA.

Тип: const D2D1_BITMAP_PROPERTIES* Название: bitmapProperties Значение (диапазон): Свойства или nullptr Текущее значение: nullptr Объяснение: nullptr означает использование формата и DPI из WIC-источника.

Тип: ID2D1Bitmap** Название: bitmap Значение (диапазон): Адрес для создаваемого битмапа Текущее значение: g_spriteTexture.GetAddressOf() Объяснение: Готовый D2D-битмап для использования в качестве спрайта.

Возвращаемся к функции инициализации: создаёт спрайтовый пакет (ID2D1SpriteBatch), привязанный к данному контексту. Он позволяет добавлять множество спрайтов и отрисовывать их одной командой, что значительно повышает производительность при большом количестве двумерных объектов.

Описание CreateSpriteBatch

Тип: ID2D1SpriteBatch** Название: spriteBatch Значение (диапазон): Адрес получателя объекта SpriteBatch Текущее значение: g_spriteBatch.GetAddressOf() Объяснение: Создаёт пустой пакет спрайтов, привязанный к контексту, для эффективной пакетной отрисовки.

Собственно теперь о том как добавлять и по итогу отрисовать спрайты:

Функция рисования
void RenderFrame()
{
    if (!g_d2dContext || !g_targetBitmap) return;

    g_d2dContext->BeginDraw();
    g_d2dContext->Clear(D2D1::ColorF(0.1f, 0.1f, 0.15f));

    if (g_spriteTexture && g_spriteBatch)
    {
        g_spriteBatch->Clear();

        const int count = 7;
        D2D1_RECT_F   dstRects[7];
        D2D1_RECT_U   srcRects[7];
        D2D1_COLOR_F  colors[7];
        D2D1_MATRIX_3X2_F transforms[7];

        // 1. Обычный спрайт
        dstRects[0] = { 100, 100, 300, 300 };
        srcRects[0] = { 0, 0, 256, 256 };
        colors[0] = D2D1::ColorF(1, 1, 1, 1);
        transforms[0] = D2D1::Matrix3x2F::Identity();

        // 2. Tint + прозрачность
        dstRects[1] = { 400, 100, 600, 300 };
        srcRects[1] = { 0, 0, 256, 256 };
        colors[1] = D2D1::ColorF(1.0f, 0.5f, 0.5f, 0.8f);
        transforms[1] = D2D1::Matrix3x2F::Identity();

        // 3. Поворот
        dstRects[2] = { 150, 400, 350, 600 };
        srcRects[2] = { 0, 0, 256, 256 };
        colors[2] = D2D1::ColorF(1, 1, 1, 1);
        transforms[2] = D2D1::Matrix3x2F::Rotation(45, D2D1::Point2F(250, 500));

        // 4. Масштаб
        dstRects[3] = { 450, 400, 650, 600 };
        srcRects[3] = { 0, 0, 256, 256 };
        colors[3] = D2D1::ColorF(1, 1, 1, 1);
        transforms[3] = D2D1::Matrix3x2F::Scale(1.5f, 0.7f, D2D1::Point2F(550, 500));

        // 5. Смещение источника (атлас)
        dstRects[4] = { 750, 100, 950, 300 };
        srcRects[4] = { 100, 100, 200, 200 };
        colors[4] = D2D1::ColorF(1, 1, 1, 1);
        transforms[4] = D2D1::Matrix3x2F::Identity();

        // 6. Наклон (shear) по X на 30 градусов
        dstRects[5] = { 20, 20, 220, 220 };
        srcRects[5] = { 0, 0, 256, 256 };
        colors[5] = D2D1::ColorF(1, 1, 1, 1);
        transforms[5] = D2D1::Matrix3x2F::Skew(0.57735f, 0.0f, D2D1::Point2F(120, 120));

        // 7. Отражение по горизонтали
        dstRects[6] = { 400, 400, 600, 600 };
        srcRects[6] = { 0, 0, 256, 256 };
        colors[6] = D2D1::ColorF(1, 1, 1, 1);
        transforms[6] = D2D1::Matrix3x2F::Scale(-1.0f, 1.0f, D2D1::Point2F(500, 500));

        g_spriteBatch->AddSprites(count,
            dstRects, srcRects, colors, transforms,
            sizeof(D2D1_RECT_F),
            sizeof(D2D1_RECT_U),
            sizeof(D2D1_COLOR_F),
            sizeof(D2D1_MATRIX_3X2_F));

        g_d2dContext->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
        g_d2dContext->DrawSpriteBatch(g_spriteBatch.Get(), g_spriteTexture.Get(),
            D2D1_BITMAP_INTERPOLATION_MODE_LINEAR, D2D1_SPRITE_OPTIONS_NONE);
    }

    HRESULT hr = g_d2dContext->EndDraw();
    if (SUCCEEDED(hr))
        g_swapChain->Present(0, 0);
    else if (hr == D2DERR_RECREATE_TARGET)
    {
        g_targetBitmap.Reset();
        CreateRenderTarget();
    }
}

Переводим контекст устройства в режим запись (в буфер состояний и команд):

g_d2dContext->BeginDraw()

Заполнение фона, определённым цветом:

g_d2dContext->Clear(D2D1::ColorF(0.1f, 0.1f, 0.15f));

Очистка спрайтового пакета:

g_spriteBatch->Clear();
dstRects[0] = { 100, 100, 300, 300 };
srcRects[0] = { 0, 0, 256, 256 };
colors[0] = D2D1::ColorF(1, 1, 1, 1);
transforms[0] = D2D1::Matrix3x2F::Identity();

dstRects - Спрайт будет нарисован в области экрана от точки (100, 100) до точки (300, 300). Ширина и высота этой области по 200 пикселей, то есть текстура спрайта будет растянута или сжата до размера 200×200 независимо от своего исходного разрешения.

srcRects - Указывает, какую часть загруженной картинки использовать для этого спрайта. Здесь от (0,0) до (256,256) - то есть вся текстура целиком.

colors - Цветовой оттенок (tint) и уровень непрозрачности, применяемые к спрайту

transforms - Матрица 3×2, описывающая двумерное преобразование (перемещение, поворот, масштаб, наклон). Единичная матрица (Identity) не вносит никаких искажений

D2D1::Matrix3x2F::Rotation(45, D2D1::Point2F(250, 500));
или
D2D1::Matrix3x2F::Scale(1.5f, 0.7f, D2D1::Point2F(550, 500));
или
D2D1::Matrix3x2F::Skew(0.57735f, 0.0f, D2D1::Point2F(120, 120));

Rotation - Создаёт матрицу, поворачивающую все точки на 45° относительно (250, 500).

Scale - Создаёт матрицу неравномерного масштаба: ширина спрайта увеличивается в 1.5 раза, высота уменьшается в 0.7 раза.

Skew - Создаёт матрицу наклона (shear). Спрайт будет скошен по горизонтали: верх сдвинется вправо, низ влево (при положительном skewX), имитируя наклон под 30°. Точка (120, 120) остаётся неподвижной, поэтому спрайт не смещается целиком, а искажается вокруг своего центра.

g_spriteBatch->AddSprites(count,
    dstRects, srcRects, colors, transforms,
    sizeof(D2D1_RECT_F),
    sizeof(D2D1_RECT_U),
    sizeof(D2D1_COLOR_F),
    sizeof(D2D1_MATRIX_3X2_F));

Метод добавляет группу спрайтов в пакет. Каждый спрайт задаётся четвёркой параметров: прямоугольник‑назначения на экране, исходный прямоугольник в текстуре, цветовой оттенок с прозрачностью и аффинная трансформация. Данные копируются внутрь батча; фактическая отрисовка происходит позже, при вызове DrawSpriteBatch. Все массивы должны содержать ровно spriteCount элементов, а шаги (strides) позволяют как плотно упаковать данные, так и расположить их с пропусками

g_d2dContext->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);

устанавливает, будут ли края спрайтов сглаживаться. Режим ALIASED отключает сглаживание, делая края чёткими и предотвращая артефакты на стыках тайлов в атласе.

g_d2dContext->DrawSpriteBatch(g_spriteBatch.Get(), g_spriteTexture.Get(),
    D2D1_BITMAP_INTERPOLATION_MODE_LINEAR, D2D1_SPRITE_OPTIONS_NONE);

Отрисовка всех спрайтов, первый аргумент это пакет спрайтов, второй аргумент - текстура, третий аргумент то как будет интерполироваться пиксели текстуры при масштабировании. spriteOptions - дополнительные параметры.

Собственно код полностью ( и ещё глобальные ресурсы + точка входа, оконная процедура, ничего нового):

Код итоговый
#include <windows.h>
#include <d2d1_3.h>
#include <d3d11.h>
#include <dxgi1_2.h>
#include <wincodec.h>
#include <wrl/client.h>
#include <string>

#pragma comment(lib, "d2d1")
#pragma comment(lib, "d3d11")
#pragma comment(lib, "dxgi")
#pragma comment(lib, "windowscodecs")

using Microsoft::WRL::ComPtr;


ComPtr<ID2D1Factory3> g_d2dFactory;
ComPtr<ID2D1Device3> g_d2dDevice;
ComPtr<ID2D1DeviceContext3> g_d2dContext;
ComPtr<IDXGISwapChain1> g_swapChain;
ComPtr<ID2D1Bitmap1> g_targetBitmap;
ComPtr<ID2D1Bitmap1> g_spriteTexture;
ComPtr<ID2D1SpriteBatch> g_spriteBatch;
HWND g_hwnd = nullptr;

HRESULT InitDirect2D(HWND hWnd);
void CleanupDirect2D();
HRESULT CreateRenderTarget();
HRESULT LoadTextureFromFile(const std::wstring& filename);
void RenderFrame();
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

// ------------------------------------------------------------------
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR, int nCmdShow)
{
    CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);

    const wchar_t CLASSNAME[] = L"D2DSpriteWindow";
    WNDCLASSW wc = {};
    wc.lpfnWndProc = WndProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = CLASSNAME;
    RegisterClassW(&wc);

    g_hwnd = CreateWindowExW(0, CLASSNAME, L"Direct2D Sprite Demo",
        WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
        1920, 1080, nullptr, nullptr, hInstance, nullptr);

    ShowWindow(g_hwnd, nCmdShow);
    UpdateWindow(g_hwnd);

    MSG msg = {};
    while (true)
    {
        if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
        {
            if (msg.message == WM_QUIT) break;
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        else
        {
            RenderFrame();
        }
    }

    CoUninitialize();
    return 0;
}

// ------------------------------------------------------------------
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
    case WM_CREATE:
        if (FAILED(InitDirect2D(hwnd))) {
            PostQuitMessage(1);
            return -1;
        }
        return 0;

    case WM_DESTROY:
        CleanupDirect2D();
        PostQuitMessage(0);
        return 0;

    case WM_SIZE:
        if (g_swapChain && wParam != SIZE_MINIMIZED)
        {

            g_d2dContext->SetTarget(nullptr);
            g_targetBitmap.Reset();
            g_d2dContext->Flush();

            RECT rc;
            GetClientRect(hwnd, &rc);
            UINT width = rc.right - rc.left;
            UINT height = rc.bottom - rc.top;
            if (width == 0 || height == 0) return 0;

            HRESULT hr = g_swapChain->ResizeBuffers(0, width, height, DXGI_FORMAT_UNKNOWN, 0);
            if (SUCCEEDED(hr))
            {
                CreateRenderTarget();
            }
            else
            {
                OutputDebugString(L"ResizeBuffers failed, recreating Direct2D\n");
                CleanupDirect2D();
                InitDirect2D(g_hwnd);
            }
        }
        return 0;
    }
    return DefWindowProc(hwnd, msg, wParam, lParam);
}

// ------------------------------------------------------------------
HRESULT InitDirect2D(HWND hWnd)
{
    HRESULT hr;

    ComPtr<ID3D11Device> d3dDevice;
    hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr,
        D3D11_CREATE_DEVICE_SINGLETHREADED | D3D11_CREATE_DEVICE_BGRA_SUPPORT,
        nullptr, 0, D3D11_SDK_VERSION, d3dDevice.GetAddressOf(), nullptr, nullptr);
    if (FAILED(hr)) return hr;

    ComPtr<IDXGIDevice> dxgiDevice;
    hr = d3dDevice.As(&dxgiDevice);
    if (FAILED(hr)) return hr;

    ComPtr<IDXGIAdapter> adapter;
    hr = dxgiDevice->GetAdapter(adapter.GetAddressOf());
    if (FAILED(hr)) return hr;

    ComPtr<IDXGIFactory2> dxgiFactory;
    hr = adapter->GetParent(IID_PPV_ARGS(dxgiFactory.GetAddressOf()));
    if (FAILED(hr)) return hr;

    RECT rc;
    GetClientRect(hWnd, &rc);
    UINT width = rc.right - rc.left;
    UINT height = rc.bottom - rc.top;
    if (width == 0) width = 800;
    if (height == 0) height = 600;

    DXGI_SWAP_CHAIN_DESC1 swapDesc = {};
    swapDesc.Width = width;
    swapDesc.Height = height;
    swapDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
    swapDesc.SampleDesc.Count = 1;
    swapDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    swapDesc.BufferCount = 2;
    swapDesc.Scaling = DXGI_SCALING_NONE;
    swapDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
    swapDesc.AlphaMode = DXGI_ALPHA_MODE_IGNORE;

    hr = dxgiFactory->CreateSwapChainForHwnd(d3dDevice.Get(), hWnd, &swapDesc, nullptr, nullptr, g_swapChain.GetAddressOf());
    if (FAILED(hr)) return hr;

    D2D1_FACTORY_OPTIONS options = {};
#ifdef _DEBUG
    options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
#endif
    hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, options, g_d2dFactory.GetAddressOf());
    if (FAILED(hr)) return hr;

    ComPtr<ID2D1Device> baseDevice;
    hr = g_d2dFactory->CreateDevice(dxgiDevice.Get(), baseDevice.GetAddressOf());
    if (FAILED(hr)) return hr;
    hr = baseDevice.As(&g_d2dDevice);
    if (FAILED(hr)) return hr;

    ComPtr<ID2D1DeviceContext> baseContext;
    hr = g_d2dDevice->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, baseContext.GetAddressOf());
    if (FAILED(hr)) return hr;
    hr = baseContext.As(&g_d2dContext);
    if (FAILED(hr)) return hr;

    hr = CreateRenderTarget();
    if (FAILED(hr)) return hr;

    hr = LoadTextureFromFile(L"image.png");
    if (FAILED(hr)) return hr;

    hr = g_d2dContext->CreateSpriteBatch(g_spriteBatch.GetAddressOf());
    return hr;
}

HRESULT CreateRenderTarget()
{
    ComPtr<IDXGISurface> backBuffer;
    HRESULT hr = g_swapChain->GetBuffer(0, IID_PPV_ARGS(backBuffer.GetAddressOf()));
    if (FAILED(hr)) return hr;

    D2D1_BITMAP_PROPERTIES1 props = D2D1::BitmapProperties1(
        D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
        D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE)
    );

    hr = g_d2dContext->CreateBitmapFromDxgiSurface(backBuffer.Get(), props, g_targetBitmap.GetAddressOf());
    if (SUCCEEDED(hr))
        g_d2dContext->SetTarget(g_targetBitmap.Get());

    return hr;
}

HRESULT LoadTextureFromFile(const std::wstring& filename)
{
    ComPtr<IWICImagingFactory> wicFactory;
    HRESULT hr = CoCreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(wicFactory.GetAddressOf()));
    if (FAILED(hr)) return hr;

    ComPtr<IWICBitmapDecoder> decoder;
    hr = wicFactory->CreateDecoderFromFilename(filename.c_str(), nullptr, GENERIC_READ, WICDecodeMetadataCacheOnLoad, decoder.GetAddressOf());
    if (FAILED(hr)) return hr;

    ComPtr<IWICBitmapFrameDecode> frame;
    hr = decoder->GetFrame(0, frame.GetAddressOf());
    if (FAILED(hr)) return hr;

    ComPtr<IWICFormatConverter> converter;
    hr = wicFactory->CreateFormatConverter(converter.GetAddressOf());
    if (FAILED(hr)) return hr;

    hr = converter->Initialize(frame.Get(), GUID_WICPixelFormat32bppPBGRA, WICBitmapDitherTypeNone, nullptr, 0.0f, WICBitmapPaletteTypeCustom);
    if (FAILED(hr)) return hr;

    hr = g_d2dContext->CreateBitmapFromWicBitmap(converter.Get(), nullptr, g_spriteTexture.GetAddressOf());
    return hr;
}

void RenderFrame()
{
    if (!g_d2dContext || !g_targetBitmap) return;

    g_d2dContext->BeginDraw();
    g_d2dContext->Clear(D2D1::ColorF(0.1f, 0.1f, 0.15f));

    if (g_spriteTexture && g_spriteBatch)
    {
        g_spriteBatch->Clear();

        const int count = 7;
        D2D1_RECT_F   dstRects[7];
        D2D1_RECT_U   srcRects[7];
        D2D1_COLOR_F  colors[7];
        D2D1_MATRIX_3X2_F transforms[7];

        // 1. Обычный спрайт
        dstRects[0] = { 100, 100, 300, 300 };
        srcRects[0] = { 0, 0, 256, 256 };
        colors[0] = D2D1::ColorF(1, 1, 1, 1);
        transforms[0] = D2D1::Matrix3x2F::Identity();

        // 2. Tint + прозрачность
        dstRects[1] = { 400, 100, 600, 300 };
        srcRects[1] = { 0, 0, 256, 256 };
        colors[1] = D2D1::ColorF(1.0f, 0.5f, 0.5f, 0.8f);
        transforms[1] = D2D1::Matrix3x2F::Identity();

        // 3. Поворот
        dstRects[2] = { 150, 400, 350, 600 };
        srcRects[2] = { 0, 0, 256, 256 };
        colors[2] = D2D1::ColorF(1, 1, 1, 1);
        transforms[2] = D2D1::Matrix3x2F::Rotation(45, D2D1::Point2F(250, 500));

        // 4. Масштаб
        dstRects[3] = { 450, 400, 650, 600 };
        srcRects[3] = { 0, 0, 256, 256 };
        colors[3] = D2D1::ColorF(1, 1, 1, 1);
        transforms[3] = D2D1::Matrix3x2F::Scale(1.5f, 0.7f, D2D1::Point2F(550, 500));

        // 5. Смещение источника (атлас)
        dstRects[4] = { 750, 100, 950, 300 };
        srcRects[4] = { 100, 100, 200, 200 };
        colors[4] = D2D1::ColorF(1, 1, 1, 1);
        transforms[4] = D2D1::Matrix3x2F::Identity();

        // 6. Наклон (shear) по X на 30 градусов
        dstRects[5] = { 20, 20, 220, 220 };
        srcRects[5] = { 0, 0, 256, 256 };
        colors[5] = D2D1::ColorF(1, 1, 1, 1);
        transforms[5] = D2D1::Matrix3x2F::Skew(0.57735f, 0.0f, D2D1::Point2F(120, 120));

        // 7. Отражение по горизонтали
        dstRects[6] = { 400, 400, 600, 600 };
        srcRects[6] = { 0, 0, 256, 256 };
        colors[6] = D2D1::ColorF(1, 1, 1, 1);
        transforms[6] = D2D1::Matrix3x2F::Scale(-1.0f, 1.0f, D2D1::Point2F(500, 500));

        g_spriteBatch->AddSprites(count,
            dstRects, srcRects, colors, transforms,
            sizeof(D2D1_RECT_F),
            sizeof(D2D1_RECT_U),
            sizeof(D2D1_COLOR_F),
            sizeof(D2D1_MATRIX_3X2_F));

        g_d2dContext->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
        g_d2dContext->DrawSpriteBatch(g_spriteBatch.Get(), g_spriteTexture.Get(),
            D2D1_BITMAP_INTERPOLATION_MODE_LINEAR, D2D1_SPRITE_OPTIONS_NONE);
    }

    HRESULT hr = g_d2dContext->EndDraw();
    if (SUCCEEDED(hr))
        g_swapChain->Present(0, 0);
    else if (hr == D2DERR_RECREATE_TARGET)
    {
        g_targetBitmap.Reset();
        CreateRenderTarget();
    }
}

void CleanupDirect2D()
{
    g_spriteBatch.Reset();
    g_spriteTexture.Reset();
    g_targetBitmap.Reset();
    g_d2dContext.Reset();
    g_d2dDevice.Reset();
    g_swapChain.Reset();
    g_d2dFactory.Reset();
}

Image.png 200 на 200 , текстура кирпичная :D

Результат кода
Результат кода

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

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