Skip to content

Commit

Permalink
Replace use of Skia's private GrRectanizer with a copy of the equival…
Browse files Browse the repository at this point in the history
…ent code (flutter#42430)

Skia would like clients to not use their private types. This ports the
same functionality into Flutter's codebase with tests.

The implementation was copied from
[Skia](https://github.com/google/skia/blob/fa87b7c5ba222f8ceb603f36011e7bbdc4bf1084/src/gpu/RectanizerSkyline.cpp)
and then modified to match Flutter's style and have a unit test.

## 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] and the [C++,
Objective-C, Java style guides].
- [ ] I listed at least one issue that this PR fixes in the description
above.
- [x] I added new tests to check the change I am making or feature I am
adding, or Hixie said the PR is test-exempt. See [testing the engine]
for instructions on writing and running engine tests.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I signed the [CLA].
- [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/wiki/Tree-hygiene#overview
[Tree Hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene
[Flutter Style Guide]:
https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo
[C++, Objective-C, Java style guides]:
https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
[testing the engine]:
https://github.com/flutter/flutter/wiki/Testing-the-engine
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes
[Discord]: https://github.com/flutter/flutter/wiki/Chat
  • Loading branch information
kjlubick authored May 31, 2023
1 parent 8e67cc3 commit b9860dc
Show file tree
Hide file tree
Showing 9 changed files with 312 additions and 18 deletions.
4 changes: 4 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -1677,6 +1677,8 @@ ORIGIN: ../../../flutter/impeller/typographer/glyph_atlas.cc + ../../../flutter/
ORIGIN: ../../../flutter/impeller/typographer/glyph_atlas.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/typographer/lazy_glyph_atlas.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/typographer/lazy_glyph_atlas.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/typographer/rectangle_packer.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/typographer/rectangle_packer.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/typographer/text_frame.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/typographer/text_frame.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/typographer/text_render_context.cc + ../../../flutter/LICENSE
Expand Down Expand Up @@ -4338,6 +4340,8 @@ FILE: ../../../flutter/impeller/typographer/glyph_atlas.cc
FILE: ../../../flutter/impeller/typographer/glyph_atlas.h
FILE: ../../../flutter/impeller/typographer/lazy_glyph_atlas.cc
FILE: ../../../flutter/impeller/typographer/lazy_glyph_atlas.h
FILE: ../../../flutter/impeller/typographer/rectangle_packer.cc
FILE: ../../../flutter/impeller/typographer/rectangle_packer.h
FILE: ../../../flutter/impeller/typographer/text_frame.cc
FILE: ../../../flutter/impeller/typographer/text_frame.h
FILE: ../../../flutter/impeller/typographer/text_render_context.cc
Expand Down
2 changes: 2 additions & 0 deletions impeller/typographer/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ impeller_component("typographer") {
"glyph_atlas.h",
"lazy_glyph_atlas.cc",
"lazy_glyph_atlas.h",
"rectangle_packer.cc",
"rectangle_packer.h",
"text_frame.cc",
"text_frame.h",
"text_render_context.cc",
Expand Down
15 changes: 7 additions & 8 deletions impeller/typographer/backends/skia/text_render_context_skia.cc
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,13 @@
#include "impeller/base/allocation.h"
#include "impeller/core/allocator.h"
#include "impeller/typographer/backends/skia/typeface_skia.h"
#include "impeller/typographer/rectangle_packer.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkFont.h"
#include "third_party/skia/include/core/SkFontMetrics.h"
#include "third_party/skia/include/core/SkRSXform.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "third_party/skia/src/core/SkIPoint16.h" // nogncheck
#include "third_party/skia/src/gpu/GrRectanizer.h" // nogncheck

namespace impeller {

Expand Down Expand Up @@ -62,7 +61,7 @@ static size_t PairsFitInAtlasOfSize(
const FontGlyphPair::Set& pairs,
const ISize& atlas_size,
std::vector<Rect>& glyph_positions,
const std::shared_ptr<GrRectanizer>& rect_packer) {
const std::shared_ptr<RectanglePacker>& rect_packer) {
if (atlas_size.IsEmpty()) {
return false;
}
Expand All @@ -76,7 +75,7 @@ static size_t PairsFitInAtlasOfSize(

const auto glyph_size =
ISize::Ceil((pair.glyph.bounds * pair.font.GetMetrics().scale).size);
SkIPoint16 location_in_atlas;
IPoint16 location_in_atlas;
if (!rect_packer->addRect(glyph_size.width + kPadding, //
glyph_size.height + kPadding, //
&location_in_atlas //
Expand All @@ -98,7 +97,7 @@ static bool CanAppendToExistingAtlas(
const FontGlyphPairRefVector& extra_pairs,
std::vector<Rect>& glyph_positions,
ISize atlas_size,
const std::shared_ptr<GrRectanizer>& rect_packer) {
const std::shared_ptr<RectanglePacker>& rect_packer) {
TRACE_EVENT0("impeller", __FUNCTION__);
if (!rect_packer || atlas_size.IsEmpty()) {
return false;
Expand All @@ -114,7 +113,7 @@ static bool CanAppendToExistingAtlas(

const auto glyph_size =
ISize::Ceil((pair.glyph.bounds * pair.font.GetMetrics().scale).size);
SkIPoint16 location_in_atlas;
IPoint16 location_in_atlas;
if (!rect_packer->addRect(glyph_size.width + kPadding, //
glyph_size.height + kPadding, //
&location_in_atlas //
Expand Down Expand Up @@ -148,8 +147,8 @@ ISize OptimumAtlasSizeForFontGlyphPairs(
: ISize(kMinAtlasSize, kMinAtlasSize);
size_t total_pairs = pairs.size() + 1;
do {
auto rect_packer = std::shared_ptr<GrRectanizer>(
GrRectanizer::Factory(current_size.width, current_size.height));
auto rect_packer = std::shared_ptr<RectanglePacker>(
RectanglePacker::Factory(current_size.width, current_size.height));

auto remaining_pairs = PairsFitInAtlasOfSize(pairs, current_size,
glyph_positions, rect_packer);
Expand Down
4 changes: 2 additions & 2 deletions impeller/typographer/glyph_atlas.cc
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ std::shared_ptr<SkBitmap> GlyphAtlasContext::GetBitmap() const {
return bitmap_;
}

std::shared_ptr<skgpu::Rectanizer> GlyphAtlasContext::GetRectPacker() const {
std::shared_ptr<RectanglePacker> GlyphAtlasContext::GetRectPacker() const {
return rect_packer_;
}

Expand All @@ -41,7 +41,7 @@ void GlyphAtlasContext::UpdateBitmap(std::shared_ptr<SkBitmap> bitmap) {
}

void GlyphAtlasContext::UpdateRectPacker(
std::shared_ptr<skgpu::Rectanizer> rect_packer) {
std::shared_ptr<RectanglePacker> rect_packer) {
rect_packer_ = std::move(rect_packer);
}

Expand Down
10 changes: 4 additions & 6 deletions impeller/typographer/glyph_atlas.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,9 @@
#include "impeller/geometry/rect.h"
#include "impeller/renderer/pipeline.h"
#include "impeller/typographer/font_glyph_pair.h"
#include "impeller/typographer/rectangle_packer.h"

class SkBitmap;
namespace skgpu {
class Rectanizer;
}

namespace impeller {

Expand Down Expand Up @@ -150,21 +148,21 @@ class GlyphAtlasContext {

//----------------------------------------------------------------------------
/// @brief Retrieve the previous (if any) rect packer.
std::shared_ptr<skgpu::Rectanizer> GetRectPacker() const;
std::shared_ptr<RectanglePacker> GetRectPacker() const;

//----------------------------------------------------------------------------
/// @brief Update the context with a newly constructed glyph atlas.
void UpdateGlyphAtlas(std::shared_ptr<GlyphAtlas> atlas, ISize size);

void UpdateBitmap(std::shared_ptr<SkBitmap> bitmap);

void UpdateRectPacker(std::shared_ptr<skgpu::Rectanizer> rect_packer);
void UpdateRectPacker(std::shared_ptr<RectanglePacker> rect_packer);

private:
std::shared_ptr<GlyphAtlas> atlas_;
ISize atlas_size_;
std::shared_ptr<SkBitmap> bitmap_;
std::shared_ptr<skgpu::Rectanizer> rect_packer_;
std::shared_ptr<RectanglePacker> rect_packer_;

FML_DISALLOW_COPY_AND_ASSIGN(GlyphAtlasContext);
};
Expand Down
173 changes: 173 additions & 0 deletions impeller/typographer/rectangle_packer.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
// 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 "impeller/typographer/rectangle_packer.h"

#include <algorithm>
#include <vector>

namespace impeller {

// Pack rectangles and track the current silhouette
// Based, in part, on Jukka Jylanki's work at http://clb.demon.fi
// and ported from Skia's implementation
// https://github.com/google/skia/blob/b5de4b8ae95c877a9ecfad5eab0765bc22550301/src/gpu/RectanizerSkyline.cpp
class SkylineRectanglePacker final : public RectanglePacker {
public:
SkylineRectanglePacker(int w, int h) : RectanglePacker(w, h) {
this->reset();
}

~SkylineRectanglePacker() final {}

void reset() final {
areaSoFar_ = 0;
skyline_.clear();
skyline_.push_back(SkylineSegment{0, 0, this->width()});
}

bool addRect(int w, int h, IPoint16* loc) final;

float percentFull() const final {
return areaSoFar_ / ((float)this->width() * this->height());
}

private:
struct SkylineSegment {
int x_;
int y_;
int width_;
};

std::vector<SkylineSegment> skyline_;

int32_t areaSoFar_;

// Can a width x height rectangle fit in the free space represented by
// the skyline segments >= 'skylineIndex'? If so, return true and fill in
// 'y' with the y-location at which it fits (the x location is pulled from
// 'skylineIndex's segment.
bool rectangleFits(int skylineIndex, int width, int height, int* y) const;
// Update the skyline structure to include a width x height rect located
// at x,y.
void addSkylineLevel(int skylineIndex, int x, int y, int width, int height);
};

bool SkylineRectanglePacker::addRect(int width, int height, IPoint16* loc) {
if ((unsigned)width > (unsigned)this->width() ||
(unsigned)height > (unsigned)this->height()) {
return false;
}

// find position for new rectangle
int bestWidth = this->width() + 1;
int bestX = 0;
int bestY = this->height() + 1;
int bestIndex = -1;
for (int i = 0; i < (int)skyline_.size(); ++i) {
int y;
if (this->rectangleFits(i, width, height, &y)) {
// minimize y position first, then width of skyline
if (y < bestY || (y == bestY && skyline_[i].width_ < bestWidth)) {
bestIndex = i;
bestWidth = skyline_[i].width_;
bestX = skyline_[i].x_;
bestY = y;
}
}
}

// add rectangle to skyline
if (-1 != bestIndex) {
this->addSkylineLevel(bestIndex, bestX, bestY, width, height);
loc->x_ = bestX;
loc->y_ = bestY;

areaSoFar_ += width * height;
return true;
}

loc->x_ = 0;
loc->y_ = 0;
return false;
}

bool SkylineRectanglePacker::rectangleFits(int skylineIndex,
int width,
int height,
int* ypos) const {
int x = skyline_[skylineIndex].x_;
if (x + width > this->width()) {
return false;
}

int widthLeft = width;
int i = skylineIndex;
int y = skyline_[skylineIndex].y_;
while (widthLeft > 0 && i < (int)skyline_.size()) {
y = std::max(y, skyline_[i].y_);
if (y + height > this->height()) {
return false;
}
widthLeft -= skyline_[i].width_;
++i;
}

*ypos = y;
return true;
}

void SkylineRectanglePacker::addSkylineLevel(int skylineIndex,
int x,
int y,
int width,
int height) {
SkylineSegment newSegment;
newSegment.x_ = x;
newSegment.y_ = y + height;
newSegment.width_ = width;
skyline_.insert(std::next(skyline_.begin(), skylineIndex), newSegment);

FML_DCHECK(newSegment.x_ + newSegment.width_ <= this->width());
FML_DCHECK(newSegment.y_ <= this->height());

// delete width of the new skyline segment from following ones
for (int i = skylineIndex + 1; i < (int)skyline_.size(); ++i) {
// The new segment subsumes all or part of skyline_[i]
FML_DCHECK(skyline_[i - 1].x_ <= skyline_[i].x_);

if (skyline_[i].x_ < skyline_[i - 1].x_ + skyline_[i - 1].width_) {
int shrink = skyline_[i - 1].x_ + skyline_[i - 1].width_ - skyline_[i].x_;

skyline_[i].x_ += shrink;
skyline_[i].width_ -= shrink;

if (skyline_[i].width_ <= 0) {
// fully consumed, remove item at index i
skyline_.erase(std::next(skyline_.begin(), i));
--i;
} else {
// only partially consumed
break;
}
} else {
break;
}
}

// merge skyline_s
for (int i = 0; i < ((int)skyline_.size()) - 1; ++i) {
if (skyline_[i].y_ == skyline_[i + 1].y_) {
skyline_[i].width_ += skyline_[i + 1].width_;
skyline_.erase(std::next(skyline_.begin(), i));
--i;
}
}
}

RectanglePacker* RectanglePacker::Factory(int width, int height) {
return new SkylineRectanglePacker(width, height);
}

} // namespace impeller
71 changes: 71 additions & 0 deletions impeller/typographer/rectangle_packer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// 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.

#pragma once

#include "flutter/fml/logging.h"

#include <cstdint>

namespace impeller {

struct IPoint16 {
int16_t x() const { return x_; }
int16_t y() const { return y_; }

int16_t x_;
int16_t y_;
};

//------------------------------------------------------------------------------
/// @brief Packs rectangles into a specified area without rotating them.
///
class RectanglePacker {
public:
//----------------------------------------------------------------------------
/// @brief Return an empty packer with area specified by width and height.
///
static RectanglePacker* Factory(int width, int height);

virtual ~RectanglePacker() {}

//----------------------------------------------------------------------------
/// @brief Attempt to add a rect without moving already placed rectangles.
///
/// @param[in] width The width of the rectangle to add.
/// @param[in] height The height of the rectangle to add.
/// @param[out] loc If successful, will be set to the position of the
/// upper-left corner of the rectangle.
///
/// @return Return true on success; false on failure.
///
virtual bool addRect(int width, int height, IPoint16* loc) = 0;

//----------------------------------------------------------------------------
/// @brief Returns how much area has been filled with rectangles.
///
/// @return Percentage as a decimal between 0.0 and 1.0
///
virtual float percentFull() const = 0;

//----------------------------------------------------------------------------
/// @brief Empty out all previously added rectangles.
///
virtual void reset() = 0;

protected:
RectanglePacker(int width, int height) : width_(width), height_(height) {
FML_DCHECK(width >= 0);
FML_DCHECK(height >= 0);
}

int width() const { return width_; }
int height() const { return height_; }

private:
const int width_;
const int height_;
};

} // namespace impeller
Loading

0 comments on commit b9860dc

Please sign in to comment.