[Impeller] remove SDF code paths. (flutter/engine#41754)

From my investigations, we're not likely to be able to use SDF in the near term. To make planned refactors of the text rendering easier to land, I've moved the primary piece of code for generating the SDFs into a new TU for testing.

The shader itself has been deleted so that we don't ship + register it.
This commit is contained in:
Jonah Williams 2023-05-05 16:43:03 -07:00 committed by GitHub
parent 2ef6ee34aa
commit aa966cb2f1
7 changed files with 11 additions and 214 deletions

View File

@ -286,8 +286,6 @@ ContentContext::ContentContext(std::shared_ptr<Context> context)
CreateDefaultPipeline<SrgbToLinearFilterPipeline>(*context_);
glyph_atlas_pipelines_[{}] =
CreateDefaultPipeline<GlyphAtlasPipeline>(*context_);
glyph_atlas_sdf_pipelines_[{}] =
CreateDefaultPipeline<GlyphAtlasSdfPipeline>(*context_);
geometry_color_pipelines_[{}] =
CreateDefaultPipeline<GeometryColorPipeline>(*context_);
yuv_to_rgb_filter_pipelines_[{}] =

View File

@ -32,8 +32,6 @@
#include "impeller/entity/conical_gradient_fill.frag.h"
#include "impeller/entity/glyph_atlas.frag.h"
#include "impeller/entity/glyph_atlas.vert.h"
#include "impeller/entity/glyph_atlas_sdf.frag.h"
#include "impeller/entity/glyph_atlas_sdf.vert.h"
#include "impeller/entity/gradient_fill.vert.h"
#include "impeller/entity/linear_gradient_fill.frag.h"
#include "impeller/entity/linear_to_srgb_filter.frag.h"
@ -173,8 +171,6 @@ using SrgbToLinearFilterPipeline =
SrgbToLinearFilterFragmentShader>;
using GlyphAtlasPipeline =
RenderPipelineT<GlyphAtlasVertexShader, GlyphAtlasFragmentShader>;
using GlyphAtlasSdfPipeline =
RenderPipelineT<GlyphAtlasSdfVertexShader, GlyphAtlasSdfFragmentShader>;
using PorterDuffBlendPipeline =
RenderPipelineT<BlendVertexShader, PorterDuffBlendFragmentShader>;
// Instead of requiring new shaders for clips, the solid fill stages are used
@ -473,11 +469,6 @@ class ContentContext {
return GetPipeline(glyph_atlas_pipelines_, opts);
}
std::shared_ptr<Pipeline<PipelineDescriptor>> GetGlyphAtlasSdfPipeline(
ContentContextOptions opts) const {
return GetPipeline(glyph_atlas_sdf_pipelines_, opts);
}
std::shared_ptr<Pipeline<PipelineDescriptor>> GetGeometryColorPipeline(
ContentContextOptions opts) const {
return GetPipeline(geometry_color_pipelines_, opts);
@ -731,7 +722,6 @@ class ContentContext {
mutable Variants<SrgbToLinearFilterPipeline> srgb_to_linear_filter_pipelines_;
mutable Variants<ClipPipeline> clip_pipelines_;
mutable Variants<GlyphAtlasPipeline> glyph_atlas_pipelines_;
mutable Variants<GlyphAtlasSdfPipeline> glyph_atlas_sdf_pipelines_;
mutable Variants<GeometryColorPipeline> geometry_color_pipelines_;
mutable Variants<YUVToRGBFilterPipeline> yuv_to_rgb_filter_pipelines_;
mutable Variants<PorterDuffBlendPipeline> porter_duff_blend_pipelines_;

View File

@ -74,7 +74,6 @@ std::optional<Rect> TextContents::GetCoverage(const Entity& entity) const {
return bounds->TransformBounds(entity.GetTransformation());
}
template <class TPipeline>
static bool CommonRender(
const ContentContext& renderer,
const Entity& entity,
@ -85,11 +84,11 @@ static bool CommonRender(
std::shared_ptr<GlyphAtlas>
atlas, // NOLINT(performance-unnecessary-value-param)
Command& cmd) {
using VS = typename TPipeline::VertexShader;
using FS = typename TPipeline::FragmentShader;
using VS = GlyphAtlasPipeline::VertexShader;
using FS = GlyphAtlasPipeline::FragmentShader;
// Common vertex uniforms for all glyphs.
typename VS::FrameInfo frame_info;
VS::FrameInfo frame_info;
frame_info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize());
VS::BindFrameInfo(cmd, pass.GetTransientsBuffer().EmplaceUniform(frame_info));
@ -109,7 +108,7 @@ static bool CommonRender(
}
sampler_desc.mip_filter = MipFilter::kNearest;
typename FS::FragInfo frag_info;
FS::FragInfo frag_info;
frag_info.text_color = ToVector(color.Premultiply());
FS::BindFragInfo(cmd, pass.GetTransientsBuffer().EmplaceUniform(frag_info));
@ -183,7 +182,7 @@ static bool CommonRender(
.Round();
for (const auto& point : unit_points) {
typename VS::PerVertexData vtx;
VS::PerVertexData vtx;
if (entity.GetTransformation().IsTranslationScaleOnly()) {
// Rouding up here prevents the bounds from becoming 1 pixel too small
@ -199,19 +198,16 @@ static bool CommonRender(
point * glyph_position.glyph.bounds.size);
}
vtx.uv = uv_origin + point * uv_size;
vtx.has_color =
glyph_position.glyph.type == Glyph::Type::kBitmap ? 1.0 : 0.0;
if constexpr (std::is_same_v<TPipeline, GlyphAtlasPipeline>) {
vtx.has_color =
glyph_position.glyph.type == Glyph::Type::kBitmap ? 1.0 : 0.0;
}
vertex_builder.AppendVertex(std::move(vtx));
vertex_builder.AppendVertex(vtx);
}
}
}
auto vertex_buffer =
vertex_builder.CreateVertexBuffer(pass.GetTransientsBuffer());
cmd.BindVertices(std::move(vertex_buffer));
cmd.BindVertices(vertex_buffer);
if (!pass.AddCommand(cmd)) {
return false;
@ -220,30 +216,6 @@ static bool CommonRender(
return true;
}
bool TextContents::RenderSdf(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const {
auto atlas =
ResolveAtlas(GlyphAtlas::Type::kSignedDistanceField,
renderer.GetGlyphAtlasContext(), renderer.GetContext());
if (!atlas || !atlas->IsValid()) {
VALIDATION_LOG << "Cannot render glyphs without prepared atlas.";
return false;
}
// Information shared by all glyph draw calls.
Command cmd;
cmd.label = "TextFrameSDF";
auto opts = OptionsFromPassAndEntity(pass, entity);
opts.primitive_type = PrimitiveType::kTriangle;
cmd.pipeline = renderer.GetGlyphAtlasSdfPipeline(opts);
cmd.stencil_reference = entity.GetStencilDepth();
return CommonRender<GlyphAtlasSdfPipeline>(renderer, entity, pass, GetColor(),
frame_, offset_, atlas, cmd);
}
bool TextContents::Render(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const {
@ -276,8 +248,8 @@ bool TextContents::Render(const ContentContext& renderer,
cmd.pipeline = renderer.GetGlyphAtlasPipeline(opts);
cmd.stencil_reference = entity.GetStencilDepth();
return CommonRender<GlyphAtlasPipeline>(renderer, entity, pass, color, frame_,
offset_, atlas, cmd);
return CommonRender(renderer, entity, pass, color, frame_, offset_, atlas,
cmd);
}
} // namespace impeller

View File

@ -48,11 +48,6 @@ class TextContents final : public Contents {
const Entity& entity,
RenderPass& pass) const override;
// TODO(dnfield): remove this https://github.com/flutter/flutter/issues/111640
bool RenderSdf(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const;
private:
TextFrame frame_;
Color color_;

View File

@ -2151,34 +2151,6 @@ TEST_P(EntityTest, TTTBlendColor) {
}
}
TEST_P(EntityTest, SdfText) {
auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
SkFont font;
font.setSize(30);
auto blob = SkTextBlob::MakeFromString(
"the quick brown fox jumped over the lazy dog (but with sdf).", font);
auto frame = TextFrameFromTextBlob(blob);
auto lazy_glyph_atlas = std::make_shared<LazyGlyphAtlas>();
lazy_glyph_atlas->AddTextFrame(frame);
EXPECT_FALSE(lazy_glyph_atlas->HasColor());
auto text_contents = std::make_shared<TextContents>();
text_contents->SetTextFrame(frame);
text_contents->SetGlyphAtlas(std::move(lazy_glyph_atlas));
text_contents->SetColor(Color(1.0, 0.0, 0.0, 1.0));
Entity entity;
entity.SetTransformation(
Matrix::MakeTranslation(Vector3{200.0, 200.0, 0.0}) *
Matrix::MakeScale(GetContentScale()));
entity.SetContents(text_contents);
// Force SDF rendering.
return text_contents->RenderSdf(context, entity, pass);
};
ASSERT_TRUE(OpenPlaygroundHere(callback));
}
TEST_P(EntityTest, AtlasContentsSubAtlas) {
auto boston = CreateTextureForFixture("boston.jpg");

View File

@ -49,9 +49,6 @@ static FontGlyphPair::Set CollectUniqueFontGlyphPairs(
while (const TextFrame* frame = frame_iterator()) {
for (const TextRun& run : frame->GetRuns()) {
const Font& font = run.GetFont();
// TODO(dnfield): If we're doing SDF here, we should be using a consistent
// point size.
// https://github.com/flutter/flutter/issues/112016
for (const TextRun::GlyphPosition& glyph_position :
run.GetGlyphPositions()) {
set.insert({font, glyph_position.glyph});
@ -171,121 +168,6 @@ ISize OptimumAtlasSizeForFontGlyphPairs(
}
} // namespace
/// Compute signed-distance field for an 8-bpp grayscale image (values greater
/// than 127 are considered "on") For details of this algorithm, see "The 'dead
/// reckoning' signed distance transform" [Grevera 2004]
static void ConvertBitmapToSignedDistanceField(uint8_t* pixels,
uint16_t width,
uint16_t height) {
if (!pixels || width == 0 || height == 0) {
return;
}
using ShortPoint = TPoint<uint16_t>;
// distance to nearest boundary point map
std::vector<Scalar> distance_map(width * height);
// nearest boundary point map
std::vector<ShortPoint> boundary_point_map(width * height);
// Some helpers for manipulating the above arrays
#define image(_x, _y) (pixels[(_y)*width + (_x)] > 0x7f)
#define distance(_x, _y) distance_map[(_y)*width + (_x)]
#define nearestpt(_x, _y) boundary_point_map[(_y)*width + (_x)]
const Scalar maxDist = hypot(width, height);
const Scalar distUnit = 1;
const Scalar distDiag = sqrt(2);
// Initialization phase: set all distances to "infinity"; zero out nearest
// boundary point map
for (uint16_t y = 0; y < height; ++y) {
for (uint16_t x = 0; x < width; ++x) {
distance(x, y) = maxDist;
nearestpt(x, y) = ShortPoint{0, 0};
}
}
// Immediate interior/exterior phase: mark all points along the boundary as
// such
for (uint16_t y = 1; y < height - 1; ++y) {
for (uint16_t x = 1; x < width - 1; ++x) {
bool inside = image(x, y);
if (image(x - 1, y) != inside || image(x + 1, y) != inside ||
image(x, y - 1) != inside || image(x, y + 1) != inside) {
distance(x, y) = 0;
nearestpt(x, y) = ShortPoint{x, y};
}
}
}
// Forward dead-reckoning pass
for (uint16_t y = 1; y < height - 2; ++y) {
for (uint16_t x = 1; x < width - 2; ++x) {
if (distance_map[(y - 1) * width + (x - 1)] + distDiag < distance(x, y)) {
nearestpt(x, y) = nearestpt(x - 1, y - 1);
distance(x, y) = hypot(x - nearestpt(x, y).x, y - nearestpt(x, y).y);
}
if (distance(x, y - 1) + distUnit < distance(x, y)) {
nearestpt(x, y) = nearestpt(x, y - 1);
distance(x, y) = hypot(x - nearestpt(x, y).x, y - nearestpt(x, y).y);
}
if (distance(x + 1, y - 1) + distDiag < distance(x, y)) {
nearestpt(x, y) = nearestpt(x + 1, y - 1);
distance(x, y) = hypot(x - nearestpt(x, y).x, y - nearestpt(x, y).y);
}
if (distance(x - 1, y) + distUnit < distance(x, y)) {
nearestpt(x, y) = nearestpt(x - 1, y);
distance(x, y) = hypot(x - nearestpt(x, y).x, y - nearestpt(x, y).y);
}
}
}
// Backward dead-reckoning pass
for (uint16_t y = height - 2; y >= 1; --y) {
for (uint16_t x = width - 2; x >= 1; --x) {
if (distance(x + 1, y) + distUnit < distance(x, y)) {
nearestpt(x, y) = nearestpt(x + 1, y);
distance(x, y) = hypot(x - nearestpt(x, y).x, y - nearestpt(x, y).y);
}
if (distance(x - 1, y + 1) + distDiag < distance(x, y)) {
nearestpt(x, y) = nearestpt(x - 1, y + 1);
distance(x, y) = hypot(x - nearestpt(x, y).x, y - nearestpt(x, y).y);
}
if (distance(x, y + 1) + distUnit < distance(x, y)) {
nearestpt(x, y) = nearestpt(x, y + 1);
distance(x, y) = hypot(x - nearestpt(x, y).x, y - nearestpt(x, y).y);
}
if (distance(x + 1, y + 1) + distDiag < distance(x, y)) {
nearestpt(x, y) = nearestpt(x + 1, y + 1);
distance(x, y) = hypot(x - nearestpt(x, y).x, y - nearestpt(x, y).y);
}
}
}
// Interior distance negation pass; distances outside the figure are
// considered negative
// Also does final quantization.
for (uint16_t y = 0; y < height; ++y) {
for (uint16_t x = 0; x < width; ++x) {
if (!image(x, y)) {
distance(x, y) = -distance(x, y);
}
float norm_factor = 13.5;
float dist = distance(x, y);
float clamped_dist = fmax(-norm_factor, fmin(dist, norm_factor));
float scaled_dist = clamped_dist / norm_factor;
uint8_t quantized_value = ((scaled_dist + 1) / 2) * UINT8_MAX;
pixels[y * width + x] = quantized_value;
}
}
#undef image
#undef distance
#undef nearestpt
}
static void DrawGlyph(SkCanvas* canvas,
const FontGlyphPair& font_glyph,
const Rect& location,
@ -353,7 +235,6 @@ static std::shared_ptr<SkBitmap> CreateAtlasBitmap(const GlyphAtlas& atlas,
SkImageInfo image_info;
switch (atlas.GetType()) {
case GlyphAtlas::Type::kSignedDistanceField:
case GlyphAtlas::Type::kAlphaBitmap:
image_info = SkImageInfo::MakeA8(atlas_size.width, atlas_size.height);
break;
@ -563,10 +444,6 @@ std::shared_ptr<GlyphAtlas> TextRenderContextSkia::CreateGlyphAtlas(
// ---------------------------------------------------------------------------
PixelFormat format;
switch (type) {
case GlyphAtlas::Type::kSignedDistanceField:
ConvertBitmapToSignedDistanceField(
reinterpret_cast<uint8_t*>(bitmap->getPixels()), atlas_size.width,
atlas_size.height);
case GlyphAtlas::Type::kAlphaBitmap:
format = PixelFormat::kA8UNormInt;
break;

View File

@ -32,13 +32,6 @@ class GlyphAtlas {
//----------------------------------------------------------------------------
/// @brief Describes how the glyphs are represented in the texture.
enum class Type {
//--------------------------------------------------------------------------
/// The glyphs are represented at a fixed size in an 8-bit grayscale texture
/// where the value of each pixel represents a signed-distance field that
/// stores the glyph outlines.
///
kSignedDistanceField,
//--------------------------------------------------------------------------
/// The glyphs are reprsented at their requested size using only an 8-bit
/// alpha channel.