From 01dabcf6b0c66865482ca79ea6e94834b4e881ad Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Tue, 21 Apr 2026 21:37:52 +0800 Subject: [PATCH] fix(new_editor): tune native d3d12 text sizing --- .../app/Rendering/D3D12/D3D12UiTextSystem.cpp | 264 +++++++++++++----- 1 file changed, 201 insertions(+), 63 deletions(-) diff --git a/new_editor/app/Rendering/D3D12/D3D12UiTextSystem.cpp b/new_editor/app/Rendering/D3D12/D3D12UiTextSystem.cpp index af0406a6..25e6d709 100644 --- a/new_editor/app/Rendering/D3D12/D3D12UiTextSystem.cpp +++ b/new_editor/app/Rendering/D3D12/D3D12UiTextSystem.cpp @@ -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 fontFace = {}; std::vector 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(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& 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(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(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 glyphRuns = collector->GetGlyphRuns(); + collector->Release(); + if (FAILED(hr)) { + return 0.0f; + } + + bool hasUnionBounds = false; + RECT unionBounds = {}; + for (const GlyphRunRecord& record : glyphRuns) { + Microsoft::WRL::ComPtr 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(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(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(std::ceil( - textMetrics.widthIncludingTrailingWhitespace + leftPad + rightPad))); - outRun.height = (std::max)( - 1u, - static_cast(std::ceil( - (std::max)(textMetrics.height, scaledFontSize * 1.6f) + topPad + bottomPad))); - outRun.offsetX = -leftPad; - outRun.offsetY = -topPad; - outRun.rgbaPixels.assign( - static_cast(outRun.width) * static_cast(outRun.height) * 4u, - 0u); - GlyphRunCollector* collector = new GlyphRunCollector(dpiScale); hr = textLayout->Draw( nullptr, collector, - leftPad, - topPad); + 0.0f, + 0.0f); const std::vector 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 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(std::ceil( + (textMetrics.widthIncludingTrailingWhitespace + leftPad + rightPad) * dpiScale))); + outRun.height = (std::max)( + 1u, + static_cast(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(outRun.width) * static_cast(outRun.height) * 4u, + 0u); + return true; + } + + outRun.width = static_cast(unionBounds.right - unionBounds.left); + outRun.height = static_cast(unionBounds.bottom - unionBounds.top); + outRun.offsetX = static_cast(unionBounds.left); + outRun.offsetY = static_cast(unionBounds.top); + outRun.rgbaPixels.assign( + static_cast(outRun.width) * static_cast(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(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 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(boundsWidth) * 3u, 3u);