Здравствуйте! Это прямое продолжение статьи №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 , текстура кирпичная 😀
На этом всё. В следующей статье разберём слои — с ними итоговую сцену можно собирать по частям, а не рисовать каждый объект отдельно.
При желании материально поддержать перевод и структурирование информации — средства можете отправить через сбор в ЮМани.
ссылка на оригинал статьи https://habr.com/ru/articles/1033582/