Stop mapping all Unicode through CJK fonts and cut D2D working-set growth
Custom font fallback no longer covers the whole BMP/SIP, so Segoe UI Emoji can paint color glyphs again; CJK still prefers Noto Sans SC then YaHei. Drop GaussianBlur compatible targets during BeginDraw (they retained GPU memory as the candidate window grew), grow the composition swapchain in 64px steps without shrinking, and reuse Card chrome plus cached text formats instead of reallocating them every layout.
This commit is contained in:
1 parent
5421c1dd1b
commit
2ac86ea0e2
5 files changed
+152
-122
No files matched your search
@@ -338,6 +338,9 @@ class Card : public Panel
|
||||
public:
|
||||
Card(Brush brush, float padding);
|
||||
|
||||
void SetBrush(Brush brush);
|
||||
void SetPadding(float padding);
|
||||
|
||||
SizeF Measure(const SizeF &availableSize) override;
|
||||
void Arrange(const RectF &finalRect) override;
|
||||
void Render(DeviceResources &deviceResources) override;
|
||||
|
||||
+41
-10
@@ -178,20 +178,51 @@ ComPtr<IDWriteTextLayout> CreateCachedTextLayout(IDWriteFactory *factory, const
|
||||
}
|
||||
|
||||
ComPtr<IDWriteTextFormat> format;
|
||||
if (FAILED(factory->CreateTextFormat(fontFamily.c_str(), nullptr, fontWeight, DWRITE_FONT_STYLE_NORMAL,
|
||||
DWRITE_FONT_STRETCH_NORMAL, fontSize, L"", format.GetAddressOf())))
|
||||
struct TextFormatKey
|
||||
{
|
||||
if (FAILED(factory->CreateTextFormat(UiFontFallbackFamily(), nullptr, fontWeight, DWRITE_FONT_STYLE_NORMAL,
|
||||
DWRITE_FONT_STRETCH_NORMAL, fontSize, L"", format.GetAddressOf())))
|
||||
std::wstring family;
|
||||
float size = 0.0f;
|
||||
DWRITE_FONT_WEIGHT weight = DWRITE_FONT_WEIGHT_NORMAL;
|
||||
DWRITE_TEXT_ALIGNMENT textAlignment = DWRITE_TEXT_ALIGNMENT_LEADING;
|
||||
DWRITE_PARAGRAPH_ALIGNMENT paragraphAlignment = DWRITE_PARAGRAPH_ALIGNMENT_NEAR;
|
||||
DWRITE_WORD_WRAPPING wordWrapping = DWRITE_WORD_WRAPPING_NO_WRAP;
|
||||
ComPtr<IDWriteTextFormat> format;
|
||||
};
|
||||
static std::vector<TextFormatKey> formatCache;
|
||||
for (auto &entry : formatCache)
|
||||
{
|
||||
if (entry.family == fontFamily && entry.size == fontSize && entry.weight == fontWeight &&
|
||||
entry.textAlignment == textAlignment && entry.paragraphAlignment == paragraphAlignment &&
|
||||
entry.wordWrapping == wordWrapping && entry.format)
|
||||
{
|
||||
return layout;
|
||||
format = entry.format;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
format->SetTextAlignment(textAlignment);
|
||||
format->SetParagraphAlignment(paragraphAlignment);
|
||||
format->SetWordWrapping(wordWrapping);
|
||||
ApplyUiFontFallback(factory, format.Get());
|
||||
if (!format)
|
||||
{
|
||||
if (FAILED(factory->CreateTextFormat(fontFamily.c_str(), nullptr, fontWeight, DWRITE_FONT_STYLE_NORMAL,
|
||||
DWRITE_FONT_STRETCH_NORMAL, fontSize, L"", format.GetAddressOf())))
|
||||
{
|
||||
if (FAILED(factory->CreateTextFormat(UiFontFallbackFamily(), nullptr, fontWeight, DWRITE_FONT_STYLE_NORMAL,
|
||||
DWRITE_FONT_STRETCH_NORMAL, fontSize, L"", format.GetAddressOf())))
|
||||
{
|
||||
return layout;
|
||||
}
|
||||
}
|
||||
format->SetTextAlignment(textAlignment);
|
||||
format->SetParagraphAlignment(paragraphAlignment);
|
||||
format->SetWordWrapping(wordWrapping);
|
||||
TextFormatKey entry;
|
||||
entry.family = fontFamily;
|
||||
entry.size = fontSize;
|
||||
entry.weight = fontWeight;
|
||||
entry.textAlignment = textAlignment;
|
||||
entry.paragraphAlignment = paragraphAlignment;
|
||||
entry.wordWrapping = wordWrapping;
|
||||
entry.format = format;
|
||||
formatCache.push_back(std::move(entry));
|
||||
}
|
||||
|
||||
if (FAILED(factory->CreateTextLayout(text.c_str(), static_cast<UINT32>(text.size()), format.Get(), width, height,
|
||||
layout.GetAddressOf())))
|
||||
|
||||
+28
-10
@@ -7,6 +7,16 @@
|
||||
|
||||
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;
|
||||
@@ -128,10 +138,10 @@ bool DeviceResources::EnsureForComposition(HWND hwnd)
|
||||
|
||||
RECT rc = {};
|
||||
GetClientRect(hwnd, &rc);
|
||||
const UINT width = static_cast<UINT>((std::max)(rc.right, 1L));
|
||||
const UINT height = static_cast<UINT>((std::max)(rc.bottom, 1L));
|
||||
const UINT width = AlignSwapExtent(static_cast<UINT>((std::max)(rc.right, 1L)));
|
||||
const UINT height = AlignSwapExtent(static_cast<UINT>((std::max)(rc.bottom, 1L)));
|
||||
|
||||
if (composition_ && deviceContext_ && swapChain_ && pixelWidth_ == width && pixelHeight_ == height)
|
||||
if (composition_ && deviceContext_ && swapChain_ && pixelWidth_ >= width && pixelHeight_ >= height)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -210,11 +220,13 @@ bool DeviceResources::EnsureForComposition(HWND hwnd)
|
||||
return true;
|
||||
}
|
||||
|
||||
pixelWidth_ = width;
|
||||
pixelHeight_ = height;
|
||||
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, width, height, DXGI_FORMAT_B8G8R8A8_UNORM, 0)))
|
||||
if (FAILED(swapChain_->ResizeBuffers(0, newWidth, newHeight, DXGI_FORMAT_B8G8R8A8_UNORM, 0)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -232,13 +244,19 @@ void DeviceResources::Resize(UINT width, UINT height)
|
||||
pixelHeight_ = height;
|
||||
return;
|
||||
}
|
||||
if (composition_ && swapChain_ && deviceContext_ && (pixelWidth_ != width || pixelHeight_ != height))
|
||||
if (composition_ && swapChain_ && deviceContext_)
|
||||
{
|
||||
pixelWidth_ = width;
|
||||
pixelHeight_ = height;
|
||||
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, width, height, DXGI_FORMAT_B8G8R8A8_UNORM, 0)))
|
||||
if (SUCCEEDED(swapChain_->ResizeBuffers(0, pixelWidth_, pixelHeight_, DXGI_FORMAT_B8G8R8A8_UNORM, 0)))
|
||||
{
|
||||
BindCompositionSurface();
|
||||
}
|
||||
|
||||
+46
-15
@@ -61,26 +61,57 @@ void ApplyUiFontFallback(IDWriteFactory *factory, IDWriteTextFormat *format)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ComPtr<IDWriteFontFallbackBuilder> builder;
|
||||
if (FAILED(factory2->CreateFontFallbackBuilder(builder.GetAddressOf())))
|
||||
|
||||
static ComPtr<IDWriteFontFallback> cachedFallback;
|
||||
static IDWriteFactory *cachedFactory = nullptr;
|
||||
if (!cachedFallback || cachedFactory != factory)
|
||||
{
|
||||
return;
|
||||
}
|
||||
WCHAR const *families[] = {L"Noto Sans SC", UiFontFallbackFamily()};
|
||||
DWRITE_UNICODE_RANGE allText{0, 0x10FFFF};
|
||||
if (FAILED(builder->AddMapping(&allText, 1, families, 2, nullptr, nullptr, nullptr, 1.0f)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
ComPtr<IDWriteFontFallback> customFallback;
|
||||
if (FAILED(builder->CreateFontFallback(customFallback.GetAddressOf())))
|
||||
{
|
||||
return;
|
||||
ComPtr<IDWriteFontFallbackBuilder> builder;
|
||||
if (FAILED(factory2->CreateFontFallbackBuilder(builder.GetAddressOf())))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Only CJK: mapping all of Unicode to Noto/YaHei replaces system fallback
|
||||
// and makes Segoe UI Emoji (COLR) tofu.
|
||||
static const DWRITE_UNICODE_RANGE kCjk[] = {
|
||||
{0x2E80, 0x2EFF}, {0x2F00, 0x2FDF}, {0x3000, 0x303F}, {0x3040, 0x30FF},
|
||||
{0x3100, 0x312F}, {0x31A0, 0x31BF}, {0x31C0, 0x31EF}, {0x3200, 0x32FF},
|
||||
{0x3300, 0x33FF}, {0x3400, 0x4DBF}, {0x4E00, 0x9FFF}, {0xF900, 0xFAFF},
|
||||
{0xFF00, 0xFFEF}, {0x20000, 0x2A6DF}, {0x2A700, 0x2B73F}, {0x2B740, 0x2B81F},
|
||||
{0x2B820, 0x2CEAF}, {0x2CEB0, 0x2EBEF}, {0x30000, 0x3134F},
|
||||
};
|
||||
WCHAR const *cjkFamilies[] = {L"Noto Sans SC", UiFontFallbackFamily()};
|
||||
if (FAILED(builder->AddMapping(kCjk, static_cast<UINT32>(sizeof(kCjk) / sizeof(kCjk[0])), cjkFamilies, 2,
|
||||
nullptr, nullptr, nullptr, 1.0f)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (FontFamilyExists(factory, L"Segoe UI Emoji"))
|
||||
{
|
||||
static const DWRITE_UNICODE_RANGE kEmoji[] = {
|
||||
{0x200D, 0x200D}, {0x2600, 0x27BF}, {0xFE00, 0xFE0F}, {0x1F000, 0x1F02F},
|
||||
{0x1F0A0, 0x1F0FF}, {0x1F300, 0x1FAFF}, {0x1F1E6, 0x1F1FF},
|
||||
};
|
||||
WCHAR const *emojiFamilies[] = {L"Segoe UI Emoji"};
|
||||
builder->AddMapping(kEmoji, static_cast<UINT32>(sizeof(kEmoji) / sizeof(kEmoji[0])), emojiFamilies, 1,
|
||||
nullptr, nullptr, nullptr, 1.0f);
|
||||
}
|
||||
|
||||
ComPtr<IDWriteFontFallback> customFallback;
|
||||
if (FAILED(builder->CreateFontFallback(customFallback.GetAddressOf())))
|
||||
{
|
||||
return;
|
||||
}
|
||||
cachedFallback = customFallback;
|
||||
cachedFactory = factory;
|
||||
}
|
||||
|
||||
ComPtr<IDWriteTextFormat1> format1;
|
||||
if (SUCCEEDED(format->QueryInterface(IID_PPV_ARGS(&format1))))
|
||||
{
|
||||
format1->SetFontFallback(customFallback.Get());
|
||||
format1->SetFontFallback(cachedFallback.Get());
|
||||
}
|
||||
}
|
||||
} // namespace msimeui
|
||||
+34
-87
@@ -204,100 +204,31 @@ bool IsSameRect(const RectF &lhs, const RectF &rhs)
|
||||
return lhs.x == rhs.x && lhs.y == rhs.y && lhs.width == rhs.width && lhs.height == rhs.height;
|
||||
}
|
||||
|
||||
void DrawLayeredMistShadow(ID2D1RenderTarget *target, const RectF &bounds, float radius)
|
||||
{
|
||||
for (int i = 1; i <= 20; ++i)
|
||||
{
|
||||
const float t = static_cast<float>(i) / 20.0f;
|
||||
const float spread = 1.15f * static_cast<float>(i);
|
||||
const float offsetY = 0.4f * static_cast<float>(i);
|
||||
const float alpha = 0.14f * (1.0f - t) * (1.0f - t);
|
||||
ComPtr<ID2D1SolidColorBrush> brush;
|
||||
if (FAILED(target->CreateSolidColorBrush(D2D1::ColorF(0, 0, 0, alpha), brush.GetAddressOf())))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const auto rounded = D2D1::RoundedRect(
|
||||
D2D1::RectF(bounds.x - spread, bounds.y - spread + offsetY, bounds.x + bounds.width + spread,
|
||||
bounds.y + bounds.height + spread + offsetY),
|
||||
radius + spread, radius + spread);
|
||||
target->FillRoundedRectangle(rounded, brush.Get());
|
||||
}
|
||||
}
|
||||
|
||||
bool DrawGaussianMistShadow(ID2D1RenderTarget *target, const RectF &bounds, float radius)
|
||||
{
|
||||
ComPtr<ID2D1DeviceContext> dc;
|
||||
if (FAILED(target->QueryInterface(IID_PPV_ARGS(dc.GetAddressOf()))))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto drawPass = [&](float stdDeviation, float alpha, float offsetY) -> bool {
|
||||
const float pad = stdDeviation * 3.0f + 4.0f;
|
||||
const D2D1_SIZE_F bitmapSize = {bounds.width + pad * 2.0f, bounds.height + pad * 2.0f};
|
||||
if (bitmapSize.width < 2.0f || bitmapSize.height < 2.0f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ComPtr<ID2D1BitmapRenderTarget> compatible;
|
||||
if (FAILED(target->CreateCompatibleRenderTarget(bitmapSize, compatible.GetAddressOf())))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
compatible->BeginDraw();
|
||||
compatible->Clear(D2D1::ColorF(0, 0.0f));
|
||||
ComPtr<ID2D1SolidColorBrush> fill;
|
||||
if (FAILED(compatible->CreateSolidColorBrush(D2D1::ColorF(0, 0, 0, alpha), fill.GetAddressOf())))
|
||||
{
|
||||
compatible->EndDraw();
|
||||
return false;
|
||||
}
|
||||
const auto shape = D2D1::RoundedRect(
|
||||
D2D1::RectF(pad, pad, pad + bounds.width, pad + bounds.height), radius, radius);
|
||||
compatible->FillRoundedRectangle(shape, fill.Get());
|
||||
if (FAILED(compatible->EndDraw()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ComPtr<ID2D1Bitmap> bitmap;
|
||||
if (FAILED(compatible->GetBitmap(bitmap.GetAddressOf())))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ComPtr<ID2D1Effect> blur;
|
||||
if (FAILED(dc->CreateEffect(CLSID_D2D1GaussianBlur, blur.GetAddressOf())))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
blur->SetInput(0, bitmap.Get());
|
||||
blur->SetValue(D2D1_GAUSSIANBLUR_PROP_STANDARD_DEVIATION, stdDeviation);
|
||||
blur->SetValue(D2D1_GAUSSIANBLUR_PROP_BORDER_MODE, D2D1_BORDER_MODE_SOFT);
|
||||
blur->SetValue(D2D1_GAUSSIANBLUR_PROP_OPTIMIZATION, D2D1_GAUSSIANBLUR_OPTIMIZATION_QUALITY);
|
||||
dc->DrawImage(blur.Get(), D2D1::Point2F(bounds.x - pad, bounds.y - pad + offsetY));
|
||||
return true;
|
||||
};
|
||||
|
||||
// Win11: wide ambient fog + tighter contact shadow, mostly downward.
|
||||
const bool ambient = drawPass(11.0f, 0.42f, 3.0f);
|
||||
const bool mid = drawPass(6.0f, 0.30f, 4.0f);
|
||||
const bool contact = drawPass(2.8f, 0.48f, 3.0f);
|
||||
return ambient || mid || contact;
|
||||
}
|
||||
|
||||
void DrawWin11WindowShadow(ID2D1RenderTarget *target, const RectF &bounds, float radius)
|
||||
{
|
||||
if (!target || bounds.width <= 0.0f || bounds.height <= 0.0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!DrawGaussianMistShadow(target, bounds, radius))
|
||||
// Soft layered fills only. GaussianBlur + CreateCompatibleRenderTarget during
|
||||
// BeginDraw retains GPU working-set across keystrokes (swapchain-sized
|
||||
// intermediates are not reliably released by the D2D effect graph).
|
||||
static const float kSpreads[] = {3.0f, 7.0f, 12.0f, 18.0f, 24.0f};
|
||||
static const float kAlphas[] = {0.22f, 0.14f, 0.09f, 0.05f, 0.03f};
|
||||
static const float kOffsetY[] = {1.0f, 2.0f, 3.5f, 5.0f, 7.0f};
|
||||
for (int i = 0; i < 5; ++i)
|
||||
{
|
||||
DrawLayeredMistShadow(target, bounds, radius);
|
||||
ComPtr<ID2D1SolidColorBrush> brush;
|
||||
if (FAILED(target->CreateSolidColorBrush(D2D1::ColorF(0, 0, 0, kAlphas[i]), brush.GetAddressOf())))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const float spread = kSpreads[i];
|
||||
const auto rounded = D2D1::RoundedRect(
|
||||
D2D1::RectF(bounds.x - spread, bounds.y - spread + kOffsetY[i], bounds.x + bounds.width + spread,
|
||||
bounds.y + bounds.height + spread + kOffsetY[i]),
|
||||
radius + spread, radius + spread);
|
||||
target->FillRoundedRectangle(rounded, brush.Get());
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
@@ -1768,6 +1699,22 @@ Card::Card(Brush brush, float padding) : brush_(brush), padding_(padding)
|
||||
{
|
||||
}
|
||||
|
||||
void Card::SetBrush(Brush brush)
|
||||
{
|
||||
brush_ = brush;
|
||||
InvalidateVisual();
|
||||
}
|
||||
|
||||
void Card::SetPadding(float padding)
|
||||
{
|
||||
if (padding_ == padding)
|
||||
{
|
||||
return;
|
||||
}
|
||||
padding_ = padding;
|
||||
InvalidateMeasure();
|
||||
}
|
||||
|
||||
SizeF Card::Measure(const SizeF &availableSize)
|
||||
{
|
||||
const SizeF inner = {std::max(availableSize.width - padding_ * 2.0f, 0.0f),
|
||||
|
||||
Reference in new issue
Block a user