[Impeller] backfilling TextContents unit tests (#161625)
issue: https://github.com/flutter/flutter/issues/149652 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
This commit is contained in:
parent
bdecbaec9d
commit
52cfc8b073
@ -160,6 +160,7 @@
|
|||||||
../../../flutter/impeller/entity/contents/filters/matrix_filter_contents_unittests.cc
|
../../../flutter/impeller/entity/contents/filters/matrix_filter_contents_unittests.cc
|
||||||
../../../flutter/impeller/entity/contents/host_buffer_unittests.cc
|
../../../flutter/impeller/entity/contents/host_buffer_unittests.cc
|
||||||
../../../flutter/impeller/entity/contents/test
|
../../../flutter/impeller/entity/contents/test
|
||||||
|
../../../flutter/impeller/entity/contents/text_contents_unittests.cc
|
||||||
../../../flutter/impeller/entity/contents/tiled_texture_contents_unittests.cc
|
../../../flutter/impeller/entity/contents/tiled_texture_contents_unittests.cc
|
||||||
../../../flutter/impeller/entity/draw_order_resolver_unittests.cc
|
../../../flutter/impeller/entity/draw_order_resolver_unittests.cc
|
||||||
../../../flutter/impeller/entity/entity_pass_target_unittests.cc
|
../../../flutter/impeller/entity/entity_pass_target_unittests.cc
|
||||||
|
@ -248,6 +248,7 @@ impeller_component("entity_unittests") {
|
|||||||
"contents/filters/inputs/filter_input_unittests.cc",
|
"contents/filters/inputs/filter_input_unittests.cc",
|
||||||
"contents/filters/matrix_filter_contents_unittests.cc",
|
"contents/filters/matrix_filter_contents_unittests.cc",
|
||||||
"contents/host_buffer_unittests.cc",
|
"contents/host_buffer_unittests.cc",
|
||||||
|
"contents/text_contents_unittests.cc",
|
||||||
"contents/tiled_texture_contents_unittests.cc",
|
"contents/tiled_texture_contents_unittests.cc",
|
||||||
"draw_order_resolver_unittests.cc",
|
"draw_order_resolver_unittests.cc",
|
||||||
"entity_pass_target_unittests.cc",
|
"entity_pass_target_unittests.cc",
|
||||||
@ -266,5 +267,6 @@ impeller_component("entity_unittests") {
|
|||||||
"../playground:playground_test",
|
"../playground:playground_test",
|
||||||
"//flutter/display_list/testing:display_list_testing",
|
"//flutter/display_list/testing:display_list_testing",
|
||||||
"//flutter/impeller/typographer/backends/skia:typographer_skia_backend",
|
"//flutter/impeller/typographer/backends/skia:typographer_skia_backend",
|
||||||
|
"//flutter/third_party/txt",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
#include "impeller/core/buffer_view.h"
|
#include "impeller/core/buffer_view.h"
|
||||||
#include "impeller/core/formats.h"
|
#include "impeller/core/formats.h"
|
||||||
#include "impeller/core/sampler_descriptor.h"
|
#include "impeller/core/sampler_descriptor.h"
|
||||||
#include "impeller/entity/contents/content_context.h"
|
|
||||||
#include "impeller/entity/entity.h"
|
#include "impeller/entity/entity.h"
|
||||||
#include "impeller/geometry/color.h"
|
#include "impeller/geometry/color.h"
|
||||||
#include "impeller/geometry/point.h"
|
#include "impeller/geometry/point.h"
|
||||||
@ -20,6 +19,9 @@
|
|||||||
|
|
||||||
namespace impeller {
|
namespace impeller {
|
||||||
|
|
||||||
|
using VS = GlyphAtlasPipeline::VertexShader;
|
||||||
|
using FS = GlyphAtlasPipeline::FragmentShader;
|
||||||
|
|
||||||
TextContents::TextContents() = default;
|
TextContents::TextContents() = default;
|
||||||
|
|
||||||
TextContents::~TextContents() = default;
|
TextContents::~TextContents() = default;
|
||||||
@ -72,6 +74,130 @@ void TextContents::SetTextProperties(Color color,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TextContents::ComputeVertexData(
|
||||||
|
VS::PerVertexData* vtx_contents,
|
||||||
|
const std::shared_ptr<TextFrame>& frame,
|
||||||
|
Scalar scale,
|
||||||
|
const Matrix& entity_transform,
|
||||||
|
Vector2 offset,
|
||||||
|
std::optional<GlyphProperties> glyph_properties,
|
||||||
|
const std::shared_ptr<GlyphAtlas>& atlas) {
|
||||||
|
// Common vertex information for all glyphs.
|
||||||
|
// All glyphs are given the same vertex information in the form of a
|
||||||
|
// unit-sized quad. The size of the glyph is specified in per instance data
|
||||||
|
// and the vertex shader uses this to size the glyph correctly. The
|
||||||
|
// interpolated vertex information is also used in the fragment shader to
|
||||||
|
// sample from the glyph atlas.
|
||||||
|
|
||||||
|
constexpr std::array<Point, 6> unit_points = {Point{0, 0}, Point{1, 0},
|
||||||
|
Point{0, 1}, Point{1, 0},
|
||||||
|
Point{0, 1}, Point{1, 1}};
|
||||||
|
|
||||||
|
ISize atlas_size = atlas->GetTexture()->GetSize();
|
||||||
|
bool is_translation_scale = entity_transform.IsTranslationScaleOnly();
|
||||||
|
Matrix basis_transform = entity_transform.Basis();
|
||||||
|
|
||||||
|
VS::PerVertexData vtx;
|
||||||
|
size_t i = 0u;
|
||||||
|
size_t bounds_offset = 0u;
|
||||||
|
for (const TextRun& run : frame->GetRuns()) {
|
||||||
|
const Font& font = run.GetFont();
|
||||||
|
Scalar rounded_scale = TextFrame::RoundScaledFontSize(scale);
|
||||||
|
FontGlyphAtlas* font_atlas = nullptr;
|
||||||
|
|
||||||
|
// Adjust glyph position based on the subpixel rounding
|
||||||
|
// used by the font.
|
||||||
|
Point subpixel_adjustment(0.5, 0.5);
|
||||||
|
switch (font.GetAxisAlignment()) {
|
||||||
|
case AxisAlignment::kNone:
|
||||||
|
break;
|
||||||
|
case AxisAlignment::kX:
|
||||||
|
subpixel_adjustment.x = 0.125;
|
||||||
|
break;
|
||||||
|
case AxisAlignment::kY:
|
||||||
|
subpixel_adjustment.y = 0.125;
|
||||||
|
break;
|
||||||
|
case AxisAlignment::kAll:
|
||||||
|
subpixel_adjustment.x = 0.125;
|
||||||
|
subpixel_adjustment.y = 0.125;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Point screen_offset = (entity_transform * Point(0, 0));
|
||||||
|
for (const TextRun::GlyphPosition& glyph_position :
|
||||||
|
run.GetGlyphPositions()) {
|
||||||
|
const FrameBounds& frame_bounds = frame->GetFrameBounds(bounds_offset);
|
||||||
|
bounds_offset++;
|
||||||
|
auto atlas_glyph_bounds = frame_bounds.atlas_bounds;
|
||||||
|
auto glyph_bounds = frame_bounds.glyph_bounds;
|
||||||
|
|
||||||
|
// If frame_bounds.is_placeholder is true, this is the first frame
|
||||||
|
// the glyph has been rendered and so its atlas position was not
|
||||||
|
// known when the glyph was recorded. Perform a slow lookup into the
|
||||||
|
// glyph atlas hash table.
|
||||||
|
if (frame_bounds.is_placeholder) {
|
||||||
|
if (!font_atlas) {
|
||||||
|
font_atlas =
|
||||||
|
atlas->GetOrCreateFontGlyphAtlas(ScaledFont{font, rounded_scale});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!font_atlas) {
|
||||||
|
VALIDATION_LOG << "Could not find font in the atlas.";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Point subpixel = TextFrame::ComputeSubpixelPosition(
|
||||||
|
glyph_position, font.GetAxisAlignment(), offset, rounded_scale);
|
||||||
|
|
||||||
|
std::optional<FrameBounds> maybe_atlas_glyph_bounds =
|
||||||
|
font_atlas->FindGlyphBounds(SubpixelGlyph{
|
||||||
|
glyph_position.glyph, //
|
||||||
|
subpixel, //
|
||||||
|
glyph_properties //
|
||||||
|
});
|
||||||
|
if (!maybe_atlas_glyph_bounds.has_value()) {
|
||||||
|
VALIDATION_LOG << "Could not find glyph position in the atlas.";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
atlas_glyph_bounds = maybe_atlas_glyph_bounds.value().atlas_bounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rect scaled_bounds = glyph_bounds.Scale(1.0 / rounded_scale);
|
||||||
|
// For each glyph, we compute two rectangles. One for the vertex
|
||||||
|
// positions and one for the texture coordinates (UVs). The atlas
|
||||||
|
// glyph bounds are used to compute UVs in cases where the
|
||||||
|
// destination and source sizes may differ due to clamping the sizes
|
||||||
|
// of large glyphs.
|
||||||
|
Point uv_origin =
|
||||||
|
(atlas_glyph_bounds.GetLeftTop() - Point(0.5, 0.5)) / atlas_size;
|
||||||
|
Point uv_size = (atlas_glyph_bounds.GetSize() + Point(1, 1)) / atlas_size;
|
||||||
|
|
||||||
|
Point unrounded_glyph_position =
|
||||||
|
basis_transform *
|
||||||
|
(glyph_position.position + scaled_bounds.GetLeftTop());
|
||||||
|
|
||||||
|
Point screen_glyph_position =
|
||||||
|
(screen_offset + unrounded_glyph_position + subpixel_adjustment)
|
||||||
|
.Floor();
|
||||||
|
|
||||||
|
for (const Point& point : unit_points) {
|
||||||
|
Point position;
|
||||||
|
if (is_translation_scale) {
|
||||||
|
position = (screen_glyph_position +
|
||||||
|
(basis_transform * point * scaled_bounds.GetSize()))
|
||||||
|
.Round();
|
||||||
|
} else {
|
||||||
|
position = entity_transform *
|
||||||
|
(glyph_position.position + scaled_bounds.GetLeftTop() +
|
||||||
|
point * scaled_bounds.GetSize());
|
||||||
|
}
|
||||||
|
vtx.uv = uv_origin + (uv_size * point);
|
||||||
|
vtx.position = position;
|
||||||
|
vtx_contents[i++] = vtx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool TextContents::Render(const ContentContext& renderer,
|
bool TextContents::Render(const ContentContext& renderer,
|
||||||
const Entity& entity,
|
const Entity& entity,
|
||||||
RenderPass& pass) const {
|
RenderPass& pass) const {
|
||||||
@ -100,17 +226,12 @@ bool TextContents::Render(const ContentContext& renderer,
|
|||||||
opts.primitive_type = PrimitiveType::kTriangle;
|
opts.primitive_type = PrimitiveType::kTriangle;
|
||||||
pass.SetPipeline(renderer.GetGlyphAtlasPipeline(opts));
|
pass.SetPipeline(renderer.GetGlyphAtlasPipeline(opts));
|
||||||
|
|
||||||
using VS = GlyphAtlasPipeline::VertexShader;
|
|
||||||
using FS = GlyphAtlasPipeline::FragmentShader;
|
|
||||||
|
|
||||||
// Common vertex uniforms for all glyphs.
|
// Common vertex uniforms for all glyphs.
|
||||||
VS::FrameInfo frame_info;
|
VS::FrameInfo frame_info;
|
||||||
frame_info.mvp =
|
frame_info.mvp =
|
||||||
Entity::GetShaderTransform(entity.GetShaderClipDepth(), pass, Matrix());
|
Entity::GetShaderTransform(entity.GetShaderClipDepth(), pass, Matrix());
|
||||||
ISize atlas_size = atlas->GetTexture()->GetSize();
|
|
||||||
bool is_translation_scale = entity.GetTransform().IsTranslationScaleOnly();
|
bool is_translation_scale = entity.GetTransform().IsTranslationScaleOnly();
|
||||||
Matrix entity_transform = entity.GetTransform();
|
Matrix entity_transform = entity.GetTransform();
|
||||||
Matrix basis_transform = entity_transform.Basis();
|
|
||||||
|
|
||||||
VS::BindFrameInfo(pass,
|
VS::BindFrameInfo(pass,
|
||||||
renderer.GetTransientsBuffer().EmplaceUniform(frame_info));
|
renderer.GetTransientsBuffer().EmplaceUniform(frame_info));
|
||||||
@ -147,17 +268,6 @@ bool TextContents::Render(const ContentContext& renderer,
|
|||||||
sampler_desc) // sampler
|
sampler_desc) // sampler
|
||||||
);
|
);
|
||||||
|
|
||||||
// Common vertex information for all glyphs.
|
|
||||||
// All glyphs are given the same vertex information in the form of a
|
|
||||||
// unit-sized quad. The size of the glyph is specified in per instance data
|
|
||||||
// and the vertex shader uses this to size the glyph correctly. The
|
|
||||||
// interpolated vertex information is also used in the fragment shader to
|
|
||||||
// sample from the glyph atlas.
|
|
||||||
|
|
||||||
constexpr std::array<Point, 6> unit_points = {Point{0, 0}, Point{1, 0},
|
|
||||||
Point{0, 1}, Point{1, 0},
|
|
||||||
Point{0, 1}, Point{1, 1}};
|
|
||||||
|
|
||||||
auto& host_buffer = renderer.GetTransientsBuffer();
|
auto& host_buffer = renderer.GetTransientsBuffer();
|
||||||
size_t vertex_count = 0;
|
size_t vertex_count = 0;
|
||||||
for (const auto& run : frame_->GetRuns()) {
|
for (const auto& run : frame_->GetRuns()) {
|
||||||
@ -168,112 +278,11 @@ bool TextContents::Render(const ContentContext& renderer,
|
|||||||
BufferView buffer_view = host_buffer.Emplace(
|
BufferView buffer_view = host_buffer.Emplace(
|
||||||
vertex_count * sizeof(VS::PerVertexData), alignof(VS::PerVertexData),
|
vertex_count * sizeof(VS::PerVertexData), alignof(VS::PerVertexData),
|
||||||
[&](uint8_t* contents) {
|
[&](uint8_t* contents) {
|
||||||
VS::PerVertexData vtx;
|
|
||||||
VS::PerVertexData* vtx_contents =
|
VS::PerVertexData* vtx_contents =
|
||||||
reinterpret_cast<VS::PerVertexData*>(contents);
|
reinterpret_cast<VS::PerVertexData*>(contents);
|
||||||
size_t i = 0u;
|
ComputeVertexData(vtx_contents, frame_, scale_,
|
||||||
size_t bounds_offset = 0u;
|
/*entity_transform=*/entity_transform, offset_,
|
||||||
for (const TextRun& run : frame_->GetRuns()) {
|
GetGlyphProperties(), atlas);
|
||||||
const Font& font = run.GetFont();
|
|
||||||
Scalar rounded_scale = TextFrame::RoundScaledFontSize(scale_);
|
|
||||||
FontGlyphAtlas* font_atlas = nullptr;
|
|
||||||
|
|
||||||
// Adjust glyph position based on the subpixel rounding
|
|
||||||
// used by the font.
|
|
||||||
Point subpixel_adjustment(0.5, 0.5);
|
|
||||||
switch (font.GetAxisAlignment()) {
|
|
||||||
case AxisAlignment::kNone:
|
|
||||||
break;
|
|
||||||
case AxisAlignment::kX:
|
|
||||||
subpixel_adjustment.x = 0.125;
|
|
||||||
break;
|
|
||||||
case AxisAlignment::kY:
|
|
||||||
subpixel_adjustment.y = 0.125;
|
|
||||||
break;
|
|
||||||
case AxisAlignment::kAll:
|
|
||||||
subpixel_adjustment.x = 0.125;
|
|
||||||
subpixel_adjustment.y = 0.125;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Point screen_offset = (entity_transform * Point(0, 0));
|
|
||||||
for (const TextRun::GlyphPosition& glyph_position :
|
|
||||||
run.GetGlyphPositions()) {
|
|
||||||
const FrameBounds& frame_bounds =
|
|
||||||
frame_->GetFrameBounds(bounds_offset);
|
|
||||||
bounds_offset++;
|
|
||||||
auto atlas_glyph_bounds = frame_bounds.atlas_bounds;
|
|
||||||
auto glyph_bounds = frame_bounds.glyph_bounds;
|
|
||||||
|
|
||||||
// If frame_bounds.is_placeholder is true, this is the first frame
|
|
||||||
// the glyph has been rendered and so its atlas position was not
|
|
||||||
// known when the glyph was recorded. Perform a slow lookup into the
|
|
||||||
// glyph atlas hash table.
|
|
||||||
if (frame_bounds.is_placeholder) {
|
|
||||||
if (!font_atlas) {
|
|
||||||
font_atlas = atlas->GetOrCreateFontGlyphAtlas(
|
|
||||||
ScaledFont{font, rounded_scale});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!font_atlas) {
|
|
||||||
VALIDATION_LOG << "Could not find font in the atlas.";
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Point subpixel = TextFrame::ComputeSubpixelPosition(
|
|
||||||
glyph_position, font.GetAxisAlignment(), offset_,
|
|
||||||
rounded_scale);
|
|
||||||
|
|
||||||
std::optional<FrameBounds> maybe_atlas_glyph_bounds =
|
|
||||||
font_atlas->FindGlyphBounds(SubpixelGlyph{
|
|
||||||
glyph_position.glyph, //
|
|
||||||
subpixel, //
|
|
||||||
GetGlyphProperties() //
|
|
||||||
});
|
|
||||||
if (!maybe_atlas_glyph_bounds.has_value()) {
|
|
||||||
VALIDATION_LOG << "Could not find glyph position in the atlas.";
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
atlas_glyph_bounds =
|
|
||||||
maybe_atlas_glyph_bounds.value().atlas_bounds;
|
|
||||||
}
|
|
||||||
|
|
||||||
Rect scaled_bounds = glyph_bounds.Scale(1.0 / rounded_scale);
|
|
||||||
// For each glyph, we compute two rectangles. One for the vertex
|
|
||||||
// positions and one for the texture coordinates (UVs). The atlas
|
|
||||||
// glyph bounds are used to compute UVs in cases where the
|
|
||||||
// destination and source sizes may differ due to clamping the sizes
|
|
||||||
// of large glyphs.
|
|
||||||
Point uv_origin =
|
|
||||||
(atlas_glyph_bounds.GetLeftTop() - Point(0.5, 0.5)) /
|
|
||||||
atlas_size;
|
|
||||||
Point uv_size =
|
|
||||||
(atlas_glyph_bounds.GetSize() + Point(1, 1)) / atlas_size;
|
|
||||||
|
|
||||||
Point unrounded_glyph_position =
|
|
||||||
basis_transform *
|
|
||||||
(glyph_position.position + scaled_bounds.GetLeftTop());
|
|
||||||
|
|
||||||
Point screen_glyph_position =
|
|
||||||
(screen_offset + unrounded_glyph_position + subpixel_adjustment)
|
|
||||||
.Floor();
|
|
||||||
|
|
||||||
for (const Point& point : unit_points) {
|
|
||||||
Point position;
|
|
||||||
if (is_translation_scale) {
|
|
||||||
position = (screen_glyph_position +
|
|
||||||
(basis_transform * point * scaled_bounds.GetSize()))
|
|
||||||
.Round();
|
|
||||||
} else {
|
|
||||||
position = entity_transform * (glyph_position.position +
|
|
||||||
scaled_bounds.GetLeftTop() +
|
|
||||||
point * scaled_bounds.GetSize());
|
|
||||||
}
|
|
||||||
vtx.uv = uv_origin + (uv_size * point);
|
|
||||||
vtx.position = position;
|
|
||||||
vtx_contents[i++] = vtx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
pass.SetVertexBuffer(std::move(buffer_view));
|
pass.SetVertexBuffer(std::move(buffer_view));
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
#include "impeller/entity/contents/content_context.h"
|
||||||
#include "impeller/entity/contents/contents.h"
|
#include "impeller/entity/contents/contents.h"
|
||||||
#include "impeller/geometry/color.h"
|
#include "impeller/geometry/color.h"
|
||||||
#include "impeller/typographer/font_glyph_pair.h"
|
#include "impeller/typographer/font_glyph_pair.h"
|
||||||
@ -61,6 +62,15 @@ class TextContents final : public Contents {
|
|||||||
const Entity& entity,
|
const Entity& entity,
|
||||||
RenderPass& pass) const override;
|
RenderPass& pass) const override;
|
||||||
|
|
||||||
|
static void ComputeVertexData(
|
||||||
|
GlyphAtlasPipeline::VertexShader::PerVertexData* vtx_contents,
|
||||||
|
const std::shared_ptr<TextFrame>& frame,
|
||||||
|
Scalar scale,
|
||||||
|
const Matrix& entity_transform,
|
||||||
|
Vector2 offset,
|
||||||
|
std::optional<GlyphProperties> glyph_properties,
|
||||||
|
const std::shared_ptr<GlyphAtlas>& atlas);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::optional<GlyphProperties> GetGlyphProperties() const;
|
std::optional<GlyphProperties> GetGlyphProperties() const;
|
||||||
|
|
||||||
|
@ -0,0 +1,167 @@
|
|||||||
|
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
#include "flutter/impeller/geometry/geometry_asserts.h"
|
||||||
|
#include "flutter/impeller/renderer/testing/mocks.h"
|
||||||
|
#include "flutter/testing/testing.h"
|
||||||
|
#include "impeller/entity/contents/text_contents.h"
|
||||||
|
#include "impeller/playground/playground_test.h"
|
||||||
|
#include "impeller/typographer/backends/skia/text_frame_skia.h"
|
||||||
|
#include "impeller/typographer/backends/skia/typographer_context_skia.h"
|
||||||
|
#include "third_party/googletest/googletest/include/gtest/gtest.h"
|
||||||
|
#include "txt/platform.h"
|
||||||
|
|
||||||
|
#pragma GCC diagnostic ignored "-Wunreachable-code"
|
||||||
|
|
||||||
|
namespace impeller {
|
||||||
|
namespace testing {
|
||||||
|
|
||||||
|
using TextContentsTest = PlaygroundTest;
|
||||||
|
INSTANTIATE_PLAYGROUND_SUITE(TextContentsTest);
|
||||||
|
|
||||||
|
using ::testing::Return;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
std::shared_ptr<TextFrame> MakeTextFrame(const std::string& text,
|
||||||
|
const std::string_view& font_fixture,
|
||||||
|
Scalar font_size) {
|
||||||
|
auto c_font_fixture = std::string(font_fixture);
|
||||||
|
auto mapping = flutter::testing::OpenFixtureAsSkData(c_font_fixture.c_str());
|
||||||
|
if (!mapping) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
sk_sp<SkFontMgr> font_mgr = txt::GetDefaultFontManager();
|
||||||
|
SkFont sk_font(font_mgr->makeFromData(mapping), font_size);
|
||||||
|
auto blob = SkTextBlob::MakeFromString(text.c_str(), sk_font);
|
||||||
|
if (!blob) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MakeTextFrameFromTextBlobSkia(blob);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<GlyphAtlas> CreateGlyphAtlas(
|
||||||
|
Context& context,
|
||||||
|
const TypographerContext* typographer_context,
|
||||||
|
HostBuffer& host_buffer,
|
||||||
|
GlyphAtlas::Type type,
|
||||||
|
Scalar scale,
|
||||||
|
const std::shared_ptr<GlyphAtlasContext>& atlas_context,
|
||||||
|
const std::shared_ptr<TextFrame>& frame) {
|
||||||
|
frame->SetPerFrameData(scale, /*offset=*/{0, 0},
|
||||||
|
/*properties=*/std::nullopt);
|
||||||
|
return typographer_context->CreateGlyphAtlas(context, type, host_buffer,
|
||||||
|
atlas_context, {frame});
|
||||||
|
}
|
||||||
|
|
||||||
|
Rect PerVertexDataPositionToRect(
|
||||||
|
GlyphAtlasPipeline::VertexShader::PerVertexData data[6]) {
|
||||||
|
Scalar right = FLT_MIN;
|
||||||
|
Scalar left = FLT_MAX;
|
||||||
|
Scalar top = FLT_MAX;
|
||||||
|
Scalar bottom = FLT_MIN;
|
||||||
|
for (int i = 0; i < 6; ++i) {
|
||||||
|
right = std::max(right, data[i].position.x);
|
||||||
|
left = std::min(left, data[i].position.x);
|
||||||
|
top = std::min(top, data[i].position.y);
|
||||||
|
bottom = std::max(bottom, data[i].position.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Rect::MakeLTRB(left, top, right, bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
Rect PerVertexDataUVToRect(
|
||||||
|
GlyphAtlasPipeline::VertexShader::PerVertexData data[6],
|
||||||
|
ISize texture_size) {
|
||||||
|
Scalar right = FLT_MIN;
|
||||||
|
Scalar left = FLT_MAX;
|
||||||
|
Scalar top = FLT_MAX;
|
||||||
|
Scalar bottom = FLT_MIN;
|
||||||
|
for (int i = 0; i < 6; ++i) {
|
||||||
|
right = std::max(right, data[i].uv.x * texture_size.width);
|
||||||
|
left = std::min(left, data[i].uv.x * texture_size.width);
|
||||||
|
top = std::min(top, data[i].uv.y * texture_size.height);
|
||||||
|
bottom = std::max(bottom, data[i].uv.y * texture_size.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Rect::MakeLTRB(left, top, right, bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_P(TextContentsTest, SimpleComputeVertexData) {
|
||||||
|
#ifndef FML_OS_MACOSX
|
||||||
|
GTEST_SKIP() << "Results aren't stable across linux and macos.";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
GlyphAtlasPipeline::VertexShader::PerVertexData data[6];
|
||||||
|
|
||||||
|
std::shared_ptr<TextFrame> text_frame =
|
||||||
|
MakeTextFrame("1", "ahem.ttf", /*font_size=*/50);
|
||||||
|
|
||||||
|
std::shared_ptr<TypographerContext> context = TypographerContextSkia::Make();
|
||||||
|
std::shared_ptr<GlyphAtlasContext> atlas_context =
|
||||||
|
context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
|
||||||
|
std::shared_ptr<HostBuffer> host_buffer = HostBuffer::Create(
|
||||||
|
GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter());
|
||||||
|
ASSERT_TRUE(context && context->IsValid());
|
||||||
|
std::shared_ptr<GlyphAtlas> atlas =
|
||||||
|
CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
|
||||||
|
GlyphAtlas::Type::kAlphaBitmap, /*scale=*/1.0f,
|
||||||
|
atlas_context, text_frame);
|
||||||
|
|
||||||
|
ISize texture_size = atlas->GetTexture()->GetSize();
|
||||||
|
TextContents::ComputeVertexData(data, text_frame, /*scale=*/1.0,
|
||||||
|
/*entity_transform=*/Matrix(),
|
||||||
|
/*offset=*/Vector2(0, 0),
|
||||||
|
/*glyph_properties=*/std::nullopt, atlas);
|
||||||
|
|
||||||
|
Rect position_rect = PerVertexDataPositionToRect(data);
|
||||||
|
Rect uv_rect = PerVertexDataUVToRect(data, texture_size);
|
||||||
|
// The -1 offset comes from Skia in `ComputeGlyphSize`. So since the font size
|
||||||
|
// is 50, the math appears to be to get back a 50x50 rect and apply 1 pixel
|
||||||
|
// of padding.
|
||||||
|
EXPECT_RECT_NEAR(position_rect, Rect::MakeXYWH(-1, -41, 52, 52));
|
||||||
|
// (0.5, 0.5) gets us sampling from the exact middle of the first pixel, the
|
||||||
|
// extra width takes us 0.5 past the end of the glyph too to sample fully the
|
||||||
|
// last pixel.
|
||||||
|
EXPECT_RECT_NEAR(uv_rect, Rect::MakeXYWH(0.5, 0.5, 53, 53));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(TextContentsTest, SimpleComputeVertexData2x) {
|
||||||
|
#ifndef FML_OS_MACOSX
|
||||||
|
GTEST_SKIP() << "Results aren't stable across linux and macos.";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
GlyphAtlasPipeline::VertexShader::PerVertexData data[6];
|
||||||
|
|
||||||
|
std::shared_ptr<TextFrame> text_frame =
|
||||||
|
MakeTextFrame("1", "ahem.ttf", /*font_size=*/50);
|
||||||
|
|
||||||
|
std::shared_ptr<TypographerContext> context = TypographerContextSkia::Make();
|
||||||
|
std::shared_ptr<GlyphAtlasContext> atlas_context =
|
||||||
|
context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
|
||||||
|
std::shared_ptr<HostBuffer> host_buffer = HostBuffer::Create(
|
||||||
|
GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter());
|
||||||
|
ASSERT_TRUE(context && context->IsValid());
|
||||||
|
Scalar font_scale = 2.f;
|
||||||
|
std::shared_ptr<GlyphAtlas> atlas = CreateGlyphAtlas(
|
||||||
|
*GetContext(), context.get(), *host_buffer,
|
||||||
|
GlyphAtlas::Type::kAlphaBitmap, font_scale, atlas_context, text_frame);
|
||||||
|
|
||||||
|
ISize texture_size = atlas->GetTexture()->GetSize();
|
||||||
|
TextContents::ComputeVertexData(
|
||||||
|
data, text_frame, font_scale,
|
||||||
|
/*entity_transform=*/Matrix::MakeScale({font_scale, font_scale, 1}),
|
||||||
|
/*offset=*/Vector2(0, 0),
|
||||||
|
/*glyph_properties=*/std::nullopt, atlas);
|
||||||
|
|
||||||
|
Rect position_rect = PerVertexDataPositionToRect(data);
|
||||||
|
Rect uv_rect = PerVertexDataUVToRect(data, texture_size);
|
||||||
|
EXPECT_RECT_NEAR(position_rect, Rect::MakeXYWH(-1, -81, 102, 102));
|
||||||
|
EXPECT_RECT_NEAR(uv_rect, Rect::MakeXYWH(0.5, 0.5, 103, 103));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace testing
|
||||||
|
} // namespace impeller
|
@ -102,6 +102,7 @@ impellerc("runtime_stages") {
|
|||||||
|
|
||||||
test_fixtures("file_fixtures") {
|
test_fixtures("file_fixtures") {
|
||||||
fixtures = [
|
fixtures = [
|
||||||
|
"//flutter/third_party/txt/third_party/fonts/ahem.ttf",
|
||||||
"//flutter/third_party/txt/third_party/fonts/HomemadeApple.ttf",
|
"//flutter/third_party/txt/third_party/fonts/HomemadeApple.ttf",
|
||||||
"//flutter/third_party/txt/third_party/fonts/NotoColorEmoji.ttf",
|
"//flutter/third_party/txt/third_party/fonts/NotoColorEmoji.ttf",
|
||||||
"//flutter/third_party/txt/third_party/fonts/Roboto-Medium.ttf",
|
"//flutter/third_party/txt/third_party/fonts/Roboto-Medium.ttf",
|
||||||
|
@ -141,6 +141,7 @@ bool TextFrame::IsFrameComplete() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const FrameBounds& TextFrame::GetFrameBounds(size_t index) const {
|
const FrameBounds& TextFrame::GetFrameBounds(size_t index) const {
|
||||||
|
FML_DCHECK(index < bound_values_.size());
|
||||||
return bound_values_[index];
|
return bound_values_[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user