#include "msimeui/DeviceResources.h" #include "msimeui/Fonts.h" #include #include namespace msimeui { namespace { UINT AlignSwapExtent(UINT value) { constexpr UINT kBucket = 64; value = (std::max)(value, 1U); return ((value + kBucket - 1U) / kBucket) * kBucket; } } // namespace bool DeviceResources::IsSameColor(const D2D1_COLOR_F &lhs, const D2D1_COLOR_F &rhs) { return lhs.r == rhs.r && lhs.g == rhs.g && lhs.b == rhs.b && lhs.a == rhs.a; } FLOAT DeviceResources::DpiForHwnd() const { if (!hwnd_) { return 96.0f; } const UINT dpi = GetDpiForWindow(hwnd_); return dpi > 0 ? static_cast(dpi) : 96.0f; } bool DeviceResources::EnsureFactories() { if (!d2dFactory_) { Microsoft::WRL::ComPtr factory1; if (SUCCEEDED(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, factory1.GetAddressOf()))) { d2dFactory1_ = factory1; d2dFactory_ = factory1; } else if (FAILED(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, d2dFactory_.GetAddressOf()))) { return false; } } if (!dwriteFactory_) { if (FAILED(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast(dwriteFactory_.GetAddressOf())))) { return false; } } if (!wicFactory_) { if (FAILED(CoCreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(wicFactory_.GetAddressOf())))) { return false; } } return true; } bool DeviceResources::EnsureForWindow(HWND hwnd) { hwnd_ = hwnd; composition_ = false; if (!EnsureFactories()) { return false; } if (hwndRenderTarget_) { const FLOAT dpi = DpiForHwnd(); hwndRenderTarget_->SetDpi(dpi, dpi); return true; } DiscardTarget(); RECT rc = {}; GetClientRect(hwnd, &rc); pixelWidth_ = static_cast((std::max)(rc.right, 1L)); pixelHeight_ = static_cast((std::max)(rc.bottom, 1L)); const auto size = D2D1::SizeU(pixelWidth_, pixelHeight_); if (FAILED(d2dFactory_->CreateHwndRenderTarget(D2D1::RenderTargetProperties(), D2D1::HwndRenderTargetProperties(hwnd, size), hwndRenderTarget_.GetAddressOf()))) { return false; } const FLOAT dpi = DpiForHwnd(); hwndRenderTarget_->SetDpi(dpi, dpi); return true; } bool DeviceResources::BindCompositionSurface() { if (!swapChain_ || !deviceContext_) { return false; } deviceContext_->SetTarget(nullptr); dxgiBitmap_.Reset(); Microsoft::WRL::ComPtr surface; if (FAILED(swapChain_->GetBuffer(0, IID_PPV_ARGS(surface.GetAddressOf())))) { return false; } const FLOAT dpi = DpiForHwnd(); const D2D1_BITMAP_PROPERTIES1 props = D2D1::BitmapProperties1(D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW, D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED), dpi, dpi); if (FAILED(deviceContext_->CreateBitmapFromDxgiSurface(surface.Get(), &props, dxgiBitmap_.GetAddressOf()))) { return false; } deviceContext_->SetTarget(dxgiBitmap_.Get()); deviceContext_->SetDpi(dpi, dpi); return true; } bool DeviceResources::EnsureForComposition(HWND hwnd) { hwnd_ = hwnd; if (!EnsureFactories() || !d2dFactory1_ || !hwnd) { return false; } RECT rc = {}; GetClientRect(hwnd, &rc); const UINT width = AlignSwapExtent(static_cast((std::max)(rc.right, 1L))); const UINT height = AlignSwapExtent(static_cast((std::max)(rc.bottom, 1L))); if (composition_ && deviceContext_ && swapChain_ && pixelWidth_ >= width && pixelHeight_ >= height) { const FLOAT dpi = DpiForHwnd(); deviceContext_->SetDpi(dpi, dpi); return true; } if (!composition_ || !d3dDevice_ || !swapChain_) { DiscardTarget(); composition_ = true; UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; D3D_FEATURE_LEVEL featureLevel = D3D_FEATURE_LEVEL_11_0; Microsoft::WRL::ComPtr ignored; if (FAILED(D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, flags, nullptr, 0, D3D11_SDK_VERSION, d3dDevice_.GetAddressOf(), &featureLevel, ignored.GetAddressOf()))) { if (FAILED(D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_WARP, nullptr, flags, nullptr, 0, D3D11_SDK_VERSION, d3dDevice_.GetAddressOf(), &featureLevel, ignored.GetAddressOf()))) { composition_ = false; return false; } } if (FAILED(d3dDevice_.As(&dxgiDevice_))) { composition_ = false; return false; } if (FAILED(d2dFactory1_->CreateDevice(dxgiDevice_.Get(), d2dDevice_.GetAddressOf())) || FAILED(d2dDevice_->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, deviceContext_.GetAddressOf()))) { composition_ = false; return false; } Microsoft::WRL::ComPtr adapter; Microsoft::WRL::ComPtr factory; if (FAILED(dxgiDevice_->GetAdapter(adapter.GetAddressOf())) || FAILED(adapter->GetParent(IID_PPV_ARGS(factory.GetAddressOf())))) { composition_ = false; return false; } DXGI_SWAP_CHAIN_DESC1 desc = {}; desc.Width = width; desc.Height = height; desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; desc.SampleDesc.Count = 1; desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; desc.BufferCount = 2; desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; desc.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED; desc.Scaling = DXGI_SCALING_STRETCH; if (FAILED(factory->CreateSwapChainForComposition(d3dDevice_.Get(), &desc, nullptr, swapChain_.GetAddressOf()))) { composition_ = false; return false; } if (FAILED(DCompositionCreateDevice(dxgiDevice_.Get(), IID_PPV_ARGS(dcompDevice_.GetAddressOf()))) || FAILED(dcompDevice_->CreateTargetForHwnd(hwnd, TRUE, dcompTarget_.GetAddressOf())) || FAILED(dcompDevice_->CreateVisual(dcompVisual_.GetAddressOf())) || FAILED(dcompVisual_->SetContent(swapChain_.Get())) || FAILED(dcompTarget_->SetRoot(dcompVisual_.Get()))) { composition_ = false; return false; } pixelWidth_ = width; pixelHeight_ = height; if (!BindCompositionSurface()) { composition_ = false; return false; } dcompDevice_->Commit(); return true; } const UINT newWidth = (std::max)(pixelWidth_, width); const UINT newHeight = (std::max)(pixelHeight_, height); pixelWidth_ = newWidth; pixelHeight_ = newHeight; deviceContext_->SetTarget(nullptr); dxgiBitmap_.Reset(); if (FAILED(swapChain_->ResizeBuffers(0, newWidth, newHeight, DXGI_FORMAT_B8G8R8A8_UNORM, 0))) { return false; } return BindCompositionSurface(); } void DeviceResources::Resize(UINT width, UINT height) { width = (std::max)(width, 1U); height = (std::max)(height, 1U); if (hwndRenderTarget_) { hwndRenderTarget_->Resize(D2D1::SizeU(width, height)); pixelWidth_ = width; pixelHeight_ = height; return; } if (composition_ && swapChain_ && deviceContext_) { const UINT neededW = AlignSwapExtent(width); const UINT neededH = AlignSwapExtent(height); if (pixelWidth_ >= neededW && pixelHeight_ >= neededH) { return; } pixelWidth_ = (std::max)(pixelWidth_, neededW); pixelHeight_ = (std::max)(pixelHeight_, neededH); deviceContext_->SetTarget(nullptr); dxgiBitmap_.Reset(); if (SUCCEEDED(swapChain_->ResizeBuffers(0, pixelWidth_, pixelHeight_, DXGI_FORMAT_B8G8R8A8_UNORM, 0))) { BindCompositionSurface(); } } } void DeviceResources::DiscardTarget() { if (deviceContext_) { deviceContext_->SetTarget(nullptr); } dxgiBitmap_.Reset(); hwndRenderTarget_.Reset(); deviceContext_.Reset(); d2dDevice_.Reset(); swapChain_.Reset(); dcompVisual_.Reset(); dcompTarget_.Reset(); dcompDevice_.Reset(); dxgiDevice_.Reset(); d3dDevice_.Reset(); brushCache_.clear(); bitmapCache_.clear(); composition_ = false; } HRESULT DeviceResources::Present() { if (!swapChain_) { return S_OK; } const HRESULT hr = swapChain_->Present(0, 0); if (dcompDevice_) { dcompDevice_->Commit(); } return hr; } ID2D1RenderTarget *DeviceResources::GetRenderTarget() const { if (deviceContext_) { return deviceContext_.Get(); } return hwndRenderTarget_.Get(); } ID2D1DeviceContext *DeviceResources::GetDeviceContext() const { return deviceContext_.Get(); } bool DeviceResources::UsesComposition() const { return composition_; } IDWriteFactory *DeviceResources::GetDWriteFactory() const { return dwriteFactory_.Get(); } ID2D1SolidColorBrush *DeviceResources::GetSolidColorBrush(const D2D1_COLOR_F &color) { ID2D1RenderTarget *target = GetRenderTarget(); if (!target) { return nullptr; } for (auto &entry : brushCache_) { if (entry.brush && IsSameColor(entry.color, color)) { return entry.brush.Get(); } } BrushCacheEntry entry; entry.color = color; if (FAILED(target->CreateSolidColorBrush(color, entry.brush.GetAddressOf()))) { return nullptr; } brushCache_.push_back(std::move(entry)); return brushCache_.back().brush.Get(); } IDWriteTextFormat *DeviceResources::GetTextFormat(const std::wstring &fontFamily, float fontSize, DWRITE_FONT_WEIGHT fontWeight, DWRITE_TEXT_ALIGNMENT textAlignment, DWRITE_PARAGRAPH_ALIGNMENT paragraphAlignment, DWRITE_WORD_WRAPPING wordWrapping) { if (!dwriteFactory_) { return nullptr; } for (auto &entry : textFormatCache_) { if (entry.fontFamily == fontFamily && entry.fontSize == fontSize && entry.fontWeight == fontWeight && entry.textAlignment == textAlignment && entry.paragraphAlignment == paragraphAlignment && entry.wordWrapping == wordWrapping && entry.format) { return entry.format.Get(); } } TextFormatCacheEntry entry; entry.fontFamily = fontFamily; entry.fontSize = fontSize; entry.fontWeight = fontWeight; entry.textAlignment = textAlignment; entry.paragraphAlignment = paragraphAlignment; entry.wordWrapping = wordWrapping; if (FAILED(dwriteFactory_->CreateTextFormat(entry.fontFamily.c_str(), nullptr, entry.fontWeight, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, entry.fontSize, L"", entry.format.GetAddressOf()))) { if (FAILED(dwriteFactory_->CreateTextFormat(L"Microsoft YaHei", nullptr, entry.fontWeight, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, entry.fontSize, L"", entry.format.GetAddressOf()))) { return nullptr; } } entry.format->SetTextAlignment(entry.textAlignment); entry.format->SetParagraphAlignment(entry.paragraphAlignment); entry.format->SetWordWrapping(entry.wordWrapping); ApplyUiFontFallback(dwriteFactory_.Get(), entry.format.Get()); textFormatCache_.push_back(std::move(entry)); return textFormatCache_.back().format.Get(); } ID2D1Bitmap *DeviceResources::GetBitmapFromFile(const std::wstring &filePath, D2D1_SIZE_F *size) { ID2D1RenderTarget *target = GetRenderTarget(); if (!target || !wicFactory_ || filePath.empty()) { return nullptr; } for (auto &entry : bitmapCache_) { if (entry.filePath == filePath && entry.bitmap) { if (size) { *size = entry.size; } return entry.bitmap.Get(); } } Microsoft::WRL::ComPtr decoder; if (FAILED(wicFactory_->CreateDecoderFromFilename(filePath.c_str(), nullptr, GENERIC_READ, WICDecodeMetadataCacheOnLoad, decoder.GetAddressOf()))) { return nullptr; } Microsoft::WRL::ComPtr frame; if (FAILED(decoder->GetFrame(0, frame.GetAddressOf()))) { return nullptr; } Microsoft::WRL::ComPtr converter; if (FAILED(wicFactory_->CreateFormatConverter(converter.GetAddressOf())) || FAILED(converter->Initialize(frame.Get(), GUID_WICPixelFormat32bppPBGRA, WICBitmapDitherTypeNone, nullptr, 0.0, WICBitmapPaletteTypeMedianCut))) { return nullptr; } BitmapCacheEntry entry; entry.filePath = filePath; if (FAILED(target->CreateBitmapFromWicBitmap(converter.Get(), nullptr, entry.bitmap.GetAddressOf()))) { return nullptr; } entry.size = entry.bitmap->GetSize(); bitmapCache_.push_back(std::move(entry)); if (size) { *size = bitmapCache_.back().size; } return bitmapCache_.back().bitmap.Get(); } } // namespace msimeui