fix(new_editor): tune native d3d12 text sizing

This commit is contained in:
2026-04-21 21:37:52 +08:00
parent 0f84e52c21
commit 01dabcf6b0

View File

@@ -10,6 +10,7 @@ namespace XCEngine::UI::Editor::Host {
namespace {
constexpr float kDefaultFontSize = 16.0f;
constexpr float kTextVisualScaleCompensation = 1.08f;
std::string HrToString(const char* operation, HRESULT hr) {
char buffer[128] = {};
@@ -25,6 +26,10 @@ float ResolveFontSize(float fontSize) {
return fontSize > 0.0f ? fontSize : kDefaultFontSize;
}
float ResolveDisplayFontSize(float fontSize) {
return ResolveFontSize(fontSize) * kTextVisualScaleCompensation;
}
struct GlyphRunRecord {
Microsoft::WRL::ComPtr<IDWriteFontFace> fontFace = {};
std::vector<UINT16> glyphIndices = {};
@@ -255,6 +260,79 @@ void CompositeCoverage(
}
}
bool CreateGlyphRunFromRecord(
const GlyphRunRecord& record,
DWRITE_GLYPH_RUN& outGlyphRun) {
if (record.fontFace == nullptr || record.glyphIndices.empty()) {
outGlyphRun = {};
return false;
}
outGlyphRun = {};
outGlyphRun.fontFace = record.fontFace.Get();
outGlyphRun.fontEmSize = record.fontEmSize;
outGlyphRun.glyphCount = static_cast<UINT32>(record.glyphIndices.size());
outGlyphRun.glyphIndices = record.glyphIndices.data();
outGlyphRun.glyphAdvances = record.glyphAdvances.data();
outGlyphRun.glyphOffsets =
record.glyphOffsets.empty() ? nullptr : record.glyphOffsets.data();
outGlyphRun.isSideways = record.isSideways;
outGlyphRun.bidiLevel = record.bidiLevel;
return true;
}
bool AnalyzeGlyphRun(
IDWriteFactory& dwriteFactory,
const GlyphRunRecord& record,
float dpiScale,
Microsoft::WRL::ComPtr<IDWriteGlyphRunAnalysis>& outAnalysis,
RECT& outBounds,
std::string& outError) {
outAnalysis.Reset();
outBounds = {};
outError.clear();
DWRITE_GLYPH_RUN glyphRun = {};
if (!CreateGlyphRunFromRecord(record, glyphRun)) {
return true;
}
HRESULT hr = dwriteFactory.CreateGlyphRunAnalysis(
&glyphRun,
dpiScale,
nullptr,
DWRITE_RENDERING_MODE_NATURAL,
record.measuringMode,
record.baselineOriginX,
record.baselineOriginY,
outAnalysis.ReleaseAndGetAddressOf());
if (FAILED(hr) || outAnalysis == nullptr) {
outError = HrToString("IDWriteFactory::CreateGlyphRunAnalysis", hr);
return false;
}
hr = outAnalysis->GetAlphaTextureBounds(DWRITE_TEXTURE_CLEARTYPE_3x1, &outBounds);
if (FAILED(hr)) {
outError = HrToString("IDWriteGlyphRunAnalysis::GetAlphaTextureBounds", hr);
return false;
}
return true;
}
bool HasArea(const RECT& rect) {
return rect.right > rect.left && rect.bottom > rect.top;
}
RECT UnionBounds(const RECT& lhs, const RECT& rhs) {
RECT result = {};
result.left = (std::min)(lhs.left, rhs.left);
result.top = (std::min)(lhs.top, rhs.top);
result.right = (std::max)(lhs.right, rhs.right);
result.bottom = (std::max)(lhs.bottom, rhs.bottom);
return result;
}
} // namespace
bool D3D12UiTextSystem::Initialize() {
@@ -317,8 +395,8 @@ bool D3D12UiTextSystem::ShapeTextRun(
}
const float dpiScale = ClampDpiScale(m_dpiScale);
const float scaledFontSize = ResolveFontSize(fontSize) * dpiScale;
IDWriteTextFormat* textFormat = GetTextFormat(scaledFontSize);
const float resolvedFontSize = ResolveDisplayFontSize(fontSize);
IDWriteTextFormat* textFormat = GetTextFormat(resolvedFontSize);
if (textFormat == nullptr) {
outError = "ShapeTextRun could not resolve the DirectWrite text format.";
return false;
@@ -330,7 +408,7 @@ bool D3D12UiTextSystem::ShapeTextRun(
static_cast<UINT32>(wideText.size()),
textFormat,
4096.0f,
scaledFontSize * 2.0f,
resolvedFontSize * 2.0f,
textLayout.ReleaseAndGetAddressOf());
if (FAILED(hr) || textLayout == nullptr) {
outError = HrToString("IDWriteFactory::CreateTextLayout", hr);
@@ -344,7 +422,7 @@ bool D3D12UiTextSystem::ShapeTextRun(
return false;
}
outRun.width = textMetrics.widthIncludingTrailingWhitespace;
outRun.height = (std::max)(textMetrics.height, scaledFontSize * 1.6f);
outRun.height = (std::max)(textMetrics.height, resolvedFontSize * 1.6f);
GlyphRunCollector* collector = new GlyphRunCollector(dpiScale);
hr = textLayout->Draw(
@@ -499,8 +577,8 @@ float D3D12UiTextSystem::MeasureTextWidth(
}
const float dpiScale = ClampDpiScale(m_dpiScale);
const float scaledFontSize = ResolveFontSize(request.fontSize) * dpiScale;
IDWriteTextFormat* textFormat = GetTextFormat(scaledFontSize);
const float resolvedFontSize = ResolveDisplayFontSize(request.fontSize);
IDWriteTextFormat* textFormat = GetTextFormat(resolvedFontSize);
if (textFormat == nullptr) {
return 0.0f;
}
@@ -511,7 +589,7 @@ float D3D12UiTextSystem::MeasureTextWidth(
static_cast<UINT32>(text.size()),
textFormat,
4096.0f,
scaledFontSize * 2.0f,
resolvedFontSize * 2.0f,
textLayout.ReleaseAndGetAddressOf());
if (FAILED(hr) || textLayout == nullptr) {
return 0.0f;
@@ -523,6 +601,46 @@ float D3D12UiTextSystem::MeasureTextWidth(
return 0.0f;
}
GlyphRunCollector* collector = new GlyphRunCollector(dpiScale);
hr = textLayout->Draw(
nullptr,
collector,
0.0f,
0.0f);
const std::vector<GlyphRunRecord> glyphRuns = collector->GetGlyphRuns();
collector->Release();
if (FAILED(hr)) {
return 0.0f;
}
bool hasUnionBounds = false;
RECT unionBounds = {};
for (const GlyphRunRecord& record : glyphRuns) {
Microsoft::WRL::ComPtr<IDWriteGlyphRunAnalysis> analysis = {};
RECT bounds = {};
std::string error = {};
if (!AnalyzeGlyphRun(
*m_dwriteFactory.Get(),
record,
dpiScale,
analysis,
bounds,
error)) {
return 0.0f;
}
if (!HasArea(bounds)) {
continue;
}
unionBounds = hasUnionBounds ? UnionBounds(unionBounds, bounds) : bounds;
hasUnionBounds = true;
}
if (hasUnionBounds) {
return static_cast<float>(unionBounds.right - unionBounds.left) / dpiScale;
}
DWRITE_OVERHANG_METRICS overhangMetrics = {};
float width = textMetrics.widthIncludingTrailingWhitespace;
if (SUCCEEDED(textLayout->GetOverhangMetrics(&overhangMetrics))) {
@@ -530,7 +648,7 @@ float D3D12UiTextSystem::MeasureTextWidth(
width += (std::max)(overhangMetrics.right, 0.0f);
}
return std::ceil(width) / dpiScale;
return std::ceil(width * dpiScale) / dpiScale;
}
bool D3D12UiTextSystem::RasterizeTextMask(
@@ -558,8 +676,8 @@ bool D3D12UiTextSystem::RasterizeTextMask(
}
const float dpiScale = ClampDpiScale(m_dpiScale);
const float scaledFontSize = ResolveFontSize(fontSize) * dpiScale;
IDWriteTextFormat* textFormat = GetTextFormat(scaledFontSize);
const float resolvedFontSize = ResolveDisplayFontSize(fontSize);
IDWriteTextFormat* textFormat = GetTextFormat(resolvedFontSize);
if (textFormat == nullptr) {
outError = "RasterizeTextMask could not resolve the DirectWrite text format.";
return false;
@@ -571,7 +689,7 @@ bool D3D12UiTextSystem::RasterizeTextMask(
static_cast<UINT32>(wideText.size()),
textFormat,
4096.0f,
scaledFontSize * 2.0f,
resolvedFontSize * 2.0f,
textLayout.ReleaseAndGetAddressOf());
if (FAILED(hr) || textLayout == nullptr) {
outError = HrToString("IDWriteFactory::CreateTextLayout", hr);
@@ -585,33 +703,12 @@ bool D3D12UiTextSystem::RasterizeTextMask(
return false;
}
DWRITE_OVERHANG_METRICS overhangMetrics = {};
textLayout->GetOverhangMetrics(&overhangMetrics);
const float leftPad = (std::max)(overhangMetrics.left, 0.0f);
const float topPad = (std::max)(overhangMetrics.top, 0.0f);
const float rightPad = (std::max)(overhangMetrics.right, 0.0f);
const float bottomPad = (std::max)(overhangMetrics.bottom, 0.0f);
outRun.width = (std::max)(
1u,
static_cast<UINT>(std::ceil(
textMetrics.widthIncludingTrailingWhitespace + leftPad + rightPad)));
outRun.height = (std::max)(
1u,
static_cast<UINT>(std::ceil(
(std::max)(textMetrics.height, scaledFontSize * 1.6f) + topPad + bottomPad)));
outRun.offsetX = -leftPad;
outRun.offsetY = -topPad;
outRun.rgbaPixels.assign(
static_cast<std::size_t>(outRun.width) * static_cast<std::size_t>(outRun.height) * 4u,
0u);
GlyphRunCollector* collector = new GlyphRunCollector(dpiScale);
hr = textLayout->Draw(
nullptr,
collector,
leftPad,
topPad);
0.0f,
0.0f);
const std::vector<GlyphRunRecord> glyphRuns = collector->GetGlyphRuns();
collector->Release();
if (FAILED(hr)) {
@@ -619,41 +716,76 @@ bool D3D12UiTextSystem::RasterizeTextMask(
return false;
}
bool hasUnionBounds = false;
RECT unionBounds = {};
for (const GlyphRunRecord& record : glyphRuns) {
Microsoft::WRL::ComPtr<IDWriteGlyphRunAnalysis> analysis = {};
RECT bounds = {};
if (!AnalyzeGlyphRun(
*m_dwriteFactory.Get(),
record,
dpiScale,
analysis,
bounds,
outError)) {
return false;
}
if (!HasArea(bounds)) {
continue;
}
unionBounds = hasUnionBounds ? UnionBounds(unionBounds, bounds) : bounds;
hasUnionBounds = true;
}
if (!hasUnionBounds) {
DWRITE_OVERHANG_METRICS overhangMetrics = {};
textLayout->GetOverhangMetrics(&overhangMetrics);
const float leftPad = (std::max)(overhangMetrics.left, 0.0f);
const float topPad = (std::max)(overhangMetrics.top, 0.0f);
const float rightPad = (std::max)(overhangMetrics.right, 0.0f);
const float bottomPad = (std::max)(overhangMetrics.bottom, 0.0f);
outRun.width = (std::max)(
1u,
static_cast<UINT>(std::ceil(
(textMetrics.widthIncludingTrailingWhitespace + leftPad + rightPad) * dpiScale)));
outRun.height = (std::max)(
1u,
static_cast<UINT>(std::ceil(
((std::max)(textMetrics.height, resolvedFontSize * 1.6f) + topPad + bottomPad) *
dpiScale)));
outRun.offsetX = -leftPad * dpiScale;
outRun.offsetY = -topPad * dpiScale;
outRun.rgbaPixels.assign(
static_cast<std::size_t>(outRun.width) * static_cast<std::size_t>(outRun.height) * 4u,
0u);
return true;
}
outRun.width = static_cast<UINT>(unionBounds.right - unionBounds.left);
outRun.height = static_cast<UINT>(unionBounds.bottom - unionBounds.top);
outRun.offsetX = static_cast<float>(unionBounds.left);
outRun.offsetY = static_cast<float>(unionBounds.top);
outRun.rgbaPixels.assign(
static_cast<std::size_t>(outRun.width) * static_cast<std::size_t>(outRun.height) * 4u,
0u);
for (const GlyphRunRecord& record : glyphRuns) {
if (record.fontFace == nullptr || record.glyphIndices.empty()) {
continue;
}
DWRITE_GLYPH_RUN glyphRun = {};
glyphRun.fontFace = record.fontFace.Get();
glyphRun.fontEmSize = record.fontEmSize;
glyphRun.glyphCount = static_cast<UINT32>(record.glyphIndices.size());
glyphRun.glyphIndices = record.glyphIndices.data();
glyphRun.glyphAdvances = record.glyphAdvances.data();
glyphRun.glyphOffsets =
record.glyphOffsets.empty() ? nullptr : record.glyphOffsets.data();
glyphRun.isSideways = record.isSideways;
glyphRun.bidiLevel = record.bidiLevel;
Microsoft::WRL::ComPtr<IDWriteGlyphRunAnalysis> analysis = {};
hr = m_dwriteFactory->CreateGlyphRunAnalysis(
&glyphRun,
dpiScale,
nullptr,
DWRITE_RENDERING_MODE_NATURAL,
record.measuringMode,
record.baselineOriginX,
record.baselineOriginY,
analysis.ReleaseAndGetAddressOf());
if (FAILED(hr) || analysis == nullptr) {
outError = HrToString("IDWriteFactory::CreateGlyphRunAnalysis", hr);
return false;
}
RECT bounds = {};
hr = analysis->GetAlphaTextureBounds(DWRITE_TEXTURE_CLEARTYPE_3x1, &bounds);
if (FAILED(hr)) {
outError = HrToString("IDWriteGlyphRunAnalysis::GetAlphaTextureBounds", hr);
if (!AnalyzeGlyphRun(
*m_dwriteFactory.Get(),
record,
dpiScale,
analysis,
bounds,
outError)) {
return false;
}
@@ -675,11 +807,17 @@ bool D3D12UiTextSystem::RasterizeTextMask(
return false;
}
RECT localBounds = {};
localBounds.left = bounds.left - unionBounds.left;
localBounds.top = bounds.top - unionBounds.top;
localBounds.right = bounds.right - unionBounds.left;
localBounds.bottom = bounds.bottom - unionBounds.top;
CompositeCoverage(
outRun.rgbaPixels,
outRun.width,
outRun.height,
bounds,
localBounds,
alphaTexture.data(),
static_cast<std::size_t>(boundsWidth) * 3u,
3u);