fix(new_editor): tune native d3d12 text sizing
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user