Performance improvement by refactoring pixel styles (#704)

Co-authored-by: ArthurSonzogni <sonzogniarthur@gmail.com>
This commit is contained in:
Clément Roblot 2023-08-08 05:46:51 +07:00 committed by GitHub
parent 00e63993ce
commit e2a205ed0d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 80 additions and 96 deletions

View File

@ -38,6 +38,8 @@ current (development)
### Screen ### Screen
- Breaking: `WordBreakProperty` becomes a uint8_t enum. This yields a 0.8% - Breaking: `WordBreakProperty` becomes a uint8_t enum. This yields a 0.8%
performance improvement. performance improvement.
- Breaking: Remove user defined Pixel constructor and equality operator.
- Performance: 19% faster on benchmarks.
### Build ### Build

View File

@ -15,19 +15,15 @@ namespace ftxui {
/// @brief A unicode character and its associated style. /// @brief A unicode character and its associated style.
/// @ingroup screen /// @ingroup screen
struct Pixel { struct Pixel {
bool operator==(const Pixel& other) const; Pixel()
: blink(false),
// The graphemes stored into the pixel. To support combining characters, bold(false),
// like: a⃦, this can potentially contain multiple codepoints. dim(false),
std::string character = " "; inverted(false),
underlined(false),
// The hyperlink associated with the pixel. underlined_double(false),
// 0 is the default value, meaning no hyperlink. strikethrough(false),
uint8_t hyperlink = 0; automerge(false) {}
// Colors:
Color background_color = Color::Default;
Color foreground_color = Color::Default;
// A bit field representing the style: // A bit field representing the style:
bool blink : 1; bool blink : 1;
@ -39,15 +35,17 @@ struct Pixel {
bool strikethrough : 1; bool strikethrough : 1;
bool automerge : 1; bool automerge : 1;
Pixel() // The hyperlink associated with the pixel.
: blink(false), // 0 is the default value, meaning no hyperlink.
bold(false), uint8_t hyperlink = 0;
dim(false),
inverted(false), // The graphemes stored into the pixel. To support combining characters,
underlined(false), // like: a⃦, this can potentially contain multiple codepoints.
underlined_double(false), std::string character = " ";
strikethrough(false),
automerge(false) {} // Colors:
Color background_color = Color::Default;
Color foreground_color = Color::Default;
}; };
/// @brief Define how the Screen's dimensions should look like. /// @brief Define how the Screen's dimensions should look like.

View File

@ -91,7 +91,7 @@ Canvas::Canvas(int width, int height)
/// @param y the y coordinate of the cell. /// @param y the y coordinate of the cell.
Pixel Canvas::GetPixel(int x, int y) const { Pixel Canvas::GetPixel(int x, int y) const {
auto it = storage_.find(XY{x, y}); auto it = storage_.find(XY{x, y});
return (it == storage_.end()) ? Pixel{} : it->second.content; return (it == storage_.end()) ? Pixel() : it->second.content;
} }
/// @brief Draw a braille dot. /// @brief Draw a braille dot.

View File

@ -18,6 +18,23 @@
#include <windows.h> #include <windows.h>
#endif #endif
// Macro for hinting that an expression is likely to be false.
#if !defined(FTXUI_UNLIKELY)
#if defined(COMPILER_GCC) || defined(__clang__)
#define FTXUI_UNLIKELY(x) __builtin_expect(!!(x), 0)
#else
#define FTXUI_UNLIKELY(x) (x)
#endif // defined(COMPILER_GCC)
#endif // !defined(FTXUI_UNLIKELY)
#if !defined(FTXUI_LIKELY)
#if defined(COMPILER_GCC) || defined(__clang__)
#define FTXUI_LIKELY(x) __builtin_expect(!!(x), 1)
#else
#define FTXUI_LIKELY(x) (x)
#endif // defined(COMPILER_GCC)
#endif // !defined(FTXUI_LIKELY)
namespace ftxui { namespace ftxui {
namespace { namespace {
@ -53,80 +70,53 @@ void WindowsEmulateVT100Terminal() {
// NOLINTNEXTLINE(readability-function-cognitive-complexity) // NOLINTNEXTLINE(readability-function-cognitive-complexity)
void UpdatePixelStyle(const Screen* screen, void UpdatePixelStyle(const Screen* screen,
std::stringstream& ss, std::stringstream& ss,
Pixel& previous, const Pixel& prev,
const Pixel& next) { const Pixel& next) {
// See https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda // See https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
if (next.hyperlink != previous.hyperlink) { if (FTXUI_UNLIKELY(next.hyperlink != prev.hyperlink)) {
ss << "\x1B]8;;" << screen->Hyperlink(next.hyperlink) << "\x1B\\"; ss << "\x1B]8;;" << screen->Hyperlink(next.hyperlink) << "\x1B\\";
} }
if ((!next.bold && previous.bold) || // // Bold
(!next.dim && previous.dim)) { if (FTXUI_UNLIKELY(next.bold != prev.bold || next.dim != prev.dim)) {
ss << "\x1B[22m"; // BOLD_RESET and DIM_RESET // BOLD_AND_DIM_RESET:
// We might have wrongfully reset dim or bold because they share the same ss << ((prev.bold && !next.bold) || (prev.dim && !next.dim) ? "\x1B[22m"
// resetter. Take it into account so that the side effect will cause it to : "");
// be set again below. ss << (next.bold ? "\x1B[1m" : ""); // BOLD_SET
previous.bold = false; ss << (next.dim ? "\x1B[2m" : ""); // DIM_SET
previous.dim = false;
} }
if ((!next.underlined && previous.underlined) || // Underline
(!next.underlined_double && previous.underlined_double)) { if (FTXUI_UNLIKELY(next.underlined != prev.underlined ||
// We might have wrongfully reset underlined or underlinedbold because they next.underlined_double != prev.underlined_double)) {
// share the same resetter. Take it into account so that the side effect ss << (next.underlined ? "\x1B[4m" // UNDERLINE
// will cause it to be set again below. : next.underlined_double ? "\x1B[21m" // UNDERLINE_DOUBLE
ss << "\x1B[24m"; // UNDERLINED_RESET : "\x1B[24m"); // UNDERLINE_RESET
previous.underlined = false;
previous.underlined_double = false;
} }
if (next.bold && !previous.bold) { // Blink
ss << "\x1B[1m"; // BOLD_SET if (FTXUI_UNLIKELY(next.blink != prev.blink)) {
ss << (next.blink ? "\x1B[5m" // BLINK_SET
: "\x1B[25m"); // BLINK_RESET
} }
if (next.dim && !previous.dim) { // Inverted
ss << "\x1B[2m"; // DIM_SET if (FTXUI_UNLIKELY(next.inverted != prev.inverted)) {
ss << (next.inverted ? "\x1B[7m" // INVERTED_SET
: "\x1B[27m"); // INVERTED_RESET
} }
if (next.underlined && !previous.underlined) { // StrikeThrough
ss << "\x1B[4m"; // UNDERLINED_SET if (FTXUI_UNLIKELY(next.strikethrough != prev.strikethrough)) {
ss << (next.strikethrough ? "\x1B[9m" // CROSSED_OUT
: "\x1B[29m"); // CROSSED_OUT_RESET
} }
if (next.blink && !previous.blink) { if (FTXUI_UNLIKELY(next.foreground_color != prev.foreground_color ||
ss << "\x1B[5m"; // BLINK_SET next.background_color != prev.background_color)) {
}
if (!next.blink && previous.blink) {
ss << "\x1B[25m"; // BLINK_RESET
}
if (next.inverted && !previous.inverted) {
ss << "\x1B[7m"; // INVERTED_SET
}
if (!next.inverted && previous.inverted) {
ss << "\x1B[27m"; // INVERTED_RESET
}
if (next.strikethrough && !previous.strikethrough) {
ss << "\x1B[9m"; // CROSSED_OUT
}
if (!next.strikethrough && previous.strikethrough) {
ss << "\x1B[29m"; // CROSSED_OUT_RESET
}
if (next.underlined_double && !previous.underlined_double) {
ss << "\x1B[21m"; // DOUBLE_UNDERLINED_SET
}
if (next.foreground_color != previous.foreground_color ||
next.background_color != previous.background_color) {
ss << "\x1B[" + next.foreground_color.Print(false) + "m"; ss << "\x1B[" + next.foreground_color.Print(false) + "m";
ss << "\x1B[" + next.background_color.Print(true) + "m"; ss << "\x1B[" + next.background_color.Print(true) + "m";
} }
previous = next;
} }
struct TileEncoding { struct TileEncoding {
@ -373,18 +363,6 @@ bool ShouldAttemptAutoMerge(Pixel& pixel) {
} // namespace } // namespace
bool Pixel::operator==(const Pixel& other) const {
return character == other.character && //
background_color == other.background_color && //
foreground_color == other.foreground_color && //
blink == other.blink && //
bold == other.bold && //
dim == other.dim && //
inverted == other.inverted && //
underlined == other.underlined && //
automerge == other.automerge; //
}
/// A fixed dimension. /// A fixed dimension.
/// @see Fit /// @see Fit
/// @see Full /// @see Full
@ -435,25 +413,31 @@ Screen::Screen(int dimx, int dimy)
std::string Screen::ToString() const { std::string Screen::ToString() const {
std::stringstream ss; std::stringstream ss;
Pixel previous_pixel; const Pixel default_pixel;
const Pixel final_pixel; const Pixel* previous_pixel_ref = &default_pixel;
for (int y = 0; y < dimy_; ++y) { for (int y = 0; y < dimy_; ++y) {
// New line in between two lines.
if (y != 0) { if (y != 0) {
UpdatePixelStyle(this, ss, previous_pixel, final_pixel); UpdatePixelStyle(this, ss, *previous_pixel_ref, default_pixel);
previous_pixel_ref = &default_pixel;
ss << "\r\n"; ss << "\r\n";
} }
// After printing a fullwith character, we need to skip the next cell.
bool previous_fullwidth = false; bool previous_fullwidth = false;
for (const auto& pixel : pixels_[y]) { for (const auto& pixel : pixels_[y]) {
if (!previous_fullwidth) { if (!previous_fullwidth) {
UpdatePixelStyle(this, ss, previous_pixel, pixel); UpdatePixelStyle(this, ss, *previous_pixel_ref, pixel);
previous_pixel_ref = &pixel;
ss << pixel.character; ss << pixel.character;
} }
previous_fullwidth = (string_width(pixel.character) == 2); previous_fullwidth = (string_width(pixel.character) == 2);
} }
} }
UpdatePixelStyle(this, ss, previous_pixel, final_pixel); // Reset the style to default:
UpdatePixelStyle(this, ss, *previous_pixel_ref, default_pixel);
return ss.str(); return ss.str();
} }