Skip to content

Commit

Permalink
Merge pull request #840 from MichaelBell/patch-pretty-poly-perf
Browse files Browse the repository at this point in the history
Improve pretty_poly performance
  • Loading branch information
Gadgetoid authored Sep 11, 2023
2 parents ed3ce45 + 80e1e16 commit c3919bd
Show file tree
Hide file tree
Showing 8 changed files with 206 additions and 64 deletions.
22 changes: 16 additions & 6 deletions libraries/pico_vector/alright_fonts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ namespace alright_fonts {
}
}

void render_character(text_metrics_t &tm, uint16_t codepoint, pretty_poly::point_t<int> origin, pretty_poly::mat3_t transform) {
template<typename mat_t>
void render_character(text_metrics_t &tm, uint16_t codepoint, pretty_poly::point_t<int> origin, mat_t transform) {
if(tm.face.glyphs.count(codepoint) == 1) {
glyph_t glyph = tm.face.glyphs[codepoint];

Expand All @@ -51,26 +52,35 @@ namespace alright_fonts {
unsigned scale = tm.size << 9;

std::vector<pretty_poly::contour_t<int8_t>> contours;
contours.reserve(glyph.contours.size());

unsigned int total_points = 0;
for(auto i = 0u; i < glyph.contours.size(); i++) {
unsigned int count = glyph.contours[i].count;
point_t<int8_t> *points = (point_t<int8_t> *)malloc(sizeof(point_t<int8_t>) * count);
total_points += glyph.contours[i].count;;
}

point_t<int8_t> *points = (point_t<int8_t> *)malloc(sizeof(point_t<int8_t>) * total_points);

for(auto i = 0u; i < glyph.contours.size(); i++) {
const unsigned int count = glyph.contours[i].count;
for(auto j = 0u; j < count; j++) {
point_t<float> point(glyph.contours[i].points[j].x, glyph.contours[i].points[j].y);
point *= transform;
points[j] = point_t<int8_t>(point.x, point.y);
}
contours.emplace_back(points, count);
points += count;
}

pretty_poly::draw_polygon<int8_t>(contours, origin, scale);

for(auto contour : contours) {
free(contour.points);
}
free(contours[0].points);
}
}

template void render_character<pretty_poly::mat3_t>(text_metrics_t &tm, uint16_t codepoint, pretty_poly::point_t<int> origin, pretty_poly::mat3_t transform);
template void render_character<pretty_poly::mat2_t>(text_metrics_t &tm, uint16_t codepoint, pretty_poly::point_t<int> origin, pretty_poly::mat2_t transform);

/*
load functions
*/
Expand Down
3 changes: 2 additions & 1 deletion libraries/pico_vector/alright_fonts.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,6 @@ namespace alright_fonts {
*/

void render_character(text_metrics_t &tm, uint16_t codepoint, pretty_poly::point_t<int> origin);
void render_character(text_metrics_t &tm, uint16_t codepoint, pretty_poly::point_t<int> origin, pretty_poly::mat3_t transform);
template<typename mat_t>
void render_character(text_metrics_t &tm, uint16_t codepoint, pretty_poly::point_t<int> origin, mat_t transform);
}
2 changes: 1 addition & 1 deletion libraries/pico_vector/pico_vector.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ add_library(pico_vector

target_include_directories(pico_vector INTERFACE ${CMAKE_CURRENT_LIST_DIR})

target_link_libraries(pico_vector pico_stdlib)
target_link_libraries(pico_vector pico_stdlib hardware_interp)
34 changes: 16 additions & 18 deletions libraries/pico_vector/pico_vector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,44 +10,42 @@ namespace pimoroni {
}

void PicoVector::rotate(std::vector<pretty_poly::contour_t<picovector_point_type>> &contours, Point origin, float angle) {
pretty_poly::mat3_t t2 = pretty_poly::mat3_t::translation(origin.x, origin.y);
pretty_poly::mat3_t t1 = pretty_poly::mat3_t::translation(-origin.x, -origin.y);
angle = 2 * M_PI * (angle / 360.0f);
pretty_poly::mat3_t r = pretty_poly::mat3_t::rotation(angle);
pretty_poly::point_t<picovector_point_type> t{(picovector_point_type)origin.x, (picovector_point_type)origin.y};
angle = (2 * (float)M_PI / 360.f) * angle;
pretty_poly::mat2_t r = pretty_poly::mat2_t::rotation(angle);
for(auto &contour : contours) {
for(auto i = 0u; i < contour.count; i++) {
contour.points[i] *= t1;
contour.points[i] -= t;
contour.points[i] *= r;
contour.points[i] *= t2;
contour.points[i] += t;
}
}
}

void PicoVector::translate(std::vector<pretty_poly::contour_t<picovector_point_type>> &contours, Point translation) {
pretty_poly::mat3_t t = pretty_poly::mat3_t::translation(translation.x, translation.y);
pretty_poly::point_t<picovector_point_type> t{(picovector_point_type)translation.x, (picovector_point_type)translation.y};
for(auto &contour : contours) {
for(auto i = 0u; i < contour.count; i++) {
contour.points[i] *= t;
contour.points[i] += t;
}
}
}

void PicoVector::rotate(pretty_poly::contour_t<picovector_point_type> &contour, Point origin, float angle) {
pretty_poly::mat3_t t2 = pretty_poly::mat3_t::translation(origin.x, origin.y);
pretty_poly::mat3_t t1 = pretty_poly::mat3_t::translation(-origin.x, -origin.y);
angle = 2 * M_PI * (angle / 360.0f);
pretty_poly::mat3_t r = pretty_poly::mat3_t::rotation(angle);
pretty_poly::point_t<picovector_point_type> t{(picovector_point_type)origin.x, (picovector_point_type)origin.y};
angle = (2 * (float)M_PI / 360.f) * angle;
pretty_poly::mat2_t r = pretty_poly::mat2_t::rotation(angle);
for(auto i = 0u; i < contour.count; i++) {
contour.points[i] *= t1;
contour.points[i] -= t;
contour.points[i] *= r;
contour.points[i] *= t2;
contour.points[i] += t;
}
}

void PicoVector::translate(pretty_poly::contour_t<picovector_point_type> &contour, Point translation) {
pretty_poly::mat3_t t = pretty_poly::mat3_t::translation(translation.x, translation.y);
pretty_poly::point_t<picovector_point_type> t{(picovector_point_type)translation.x, (picovector_point_type)translation.y};
for(auto i = 0u; i < contour.count; i++) {
contour.points[i] *= t;
contour.points[i] += t;
}
}

Expand Down Expand Up @@ -115,8 +113,8 @@ namespace pimoroni {
pretty_poly::point_t<float> caret(0, 0);

// Prepare a transformation matrix for character and offset rotation
angle = 2 * M_PI * (angle / 360.0f);
pretty_poly::mat3_t transform = pretty_poly::mat3_t::rotation(angle);
angle = (2 * (float)M_PI / 360.f) * angle;
pretty_poly::mat2_t transform = pretty_poly::mat2_t::rotation(angle);

// Align text from the bottom left
caret.y += text_metrics.line_height;
Expand Down
166 changes: 133 additions & 33 deletions libraries/pico_vector/pretty_poly.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include "pretty_poly.hpp"

#include "hardware/interp.h"

#ifdef PP_DEBUG
#define debug(...) printf(__VA_ARGS__)
Expand Down Expand Up @@ -79,35 +80,63 @@ namespace pretty_poly {
std::swap(sx, ex);
}

/*sx <<= settings::antialias;
ex <<= settings::antialias;
sy <<= settings::antialias;
ey <<= settings::antialias;*/
// Early out if line is completely outside the tile, or has no lines
if (ey < 0 || sy >= (int)node_buffer_size || sy == ey) return;

debug(" + line segment from %d, %d to %d, %d\n", sx, sy, ex, ey);

// Determine how many in-bounds lines to render
int y = std::max(0, sy);
int count = std::min((int)node_buffer_size, ey) - y;

// Handle cases where x is completely off to one side or other
if (std::max(sx, ex) <= 0) {
while (count--) {
nodes[y][node_counts[y]++] = 0;
++y;
}
return;
}

const int full_tile_width = (tile_bounds.w << settings::antialias);
if (std::min(sx, ex) >= full_tile_width) {
while (count--) {
nodes[y][node_counts[y]++] = full_tile_width;
++y;
}
return;
}

// Normal case
int x = sx;
int y = sy;
int e = 0;

int xinc = sign(ex - sx);
int einc = abs(ex - sx) + 1;
const int xinc = sign(ex - sx);
const int einc = abs(ex - sx) + 1;
const int dy = ey - sy;

// If sy < 0 jump to the start, note this does use a divide
// but potentially saves many wasted loops below, so is likely worth it.
if (sy < 0) {
e = einc * -sy;
int xjump = e / dy;
e -= dy * xjump;
x += xinc * xjump;
}

interp1->base[1] = full_tile_width;
interp1->accum[0] = x;

// todo: preclamp sy and ey (and therefore count) no need to perform
// that test inside the loop
int dy = ey - sy;
int count = dy;
debug(" + line segment from %d, %d to %d, %d\n", sx, sy, ex, ey);
// loop over scanlines
while(count--) {
// consume accumulated error
while(e > dy) {e -= dy; x += xinc;}

if(y >= 0 && y < (int)node_buffer_size) {
// clamp node x value to tile bounds
int nx = std::max(std::min(x, (int)(tile_bounds.w << settings::antialias)), 0);
debug(" + adding node at %d, %d\n", x, y);
// add node to node list
nodes[y][node_counts[y]++] = nx;
}
while(e > dy) {e -= dy; interp1->add_raw[0] = xinc;}

// clamp node x value to tile bounds
const int nx = interp1->peek[0];
debug(" + adding node at %d, %d\n", x, y);
// add node to node list
nodes[y][node_counts[y]++] = nx;

// step to next scanline and accumulate error
y++;
Expand Down Expand Up @@ -138,14 +167,23 @@ namespace pretty_poly {
}
}

void render_nodes(const tile_t &tile) {
void render_nodes(const tile_t &tile, rect_t &bounds) {
int maxy = -1;
bounds.y = 0;
bounds.x = tile.bounds.w;
int maxx = 0;
int anitialias_mask = (1 << settings::antialias) - 1;

for(auto y = 0; y < (int)node_buffer_size; y++) {
if(node_counts[y] == 0) {
if (y == bounds.y) ++bounds.y;
continue;
}

std::sort(&nodes[y][0], &nodes[y][0] + node_counts[y]);

uint8_t* row_data = &tile.data[(y >> settings::antialias) * tile.stride];
bool rendered_any = false;
for(auto i = 0u; i < node_counts[y]; i += 2) {
int sx = nodes[y][i + 0];
int ex = nodes[y][i + 1];
Expand All @@ -154,13 +192,55 @@ namespace pretty_poly {
continue;
}

rendered_any = true;

maxx = std::max((ex - 1) >> settings::antialias, maxx);

debug(" - render span at %d from %d to %d\n", y, sx, ex);

for(int x = sx; x < ex; x++) {
tile.data[(x >> settings::antialias) + (y >> settings::antialias) * tile.stride]++;
}
if (settings::antialias) {
int ax = sx >> settings::antialias;
const int aex = ex >> settings::antialias;

bounds.x = std::min(ax, bounds.x);

if (ax == aex) {
row_data[ax] += ex - sx;
continue;
}

row_data[ax] += (1 << settings::antialias) - (sx & anitialias_mask);
for(ax++; ax < aex; ax++) {
row_data[ax] += (1 << settings::antialias);
}

// This might add 0 to the byte after the end of the row, we pad the tile data
// by 1 byte to ensure that is OK
row_data[ax] += ex & anitialias_mask;
}
else {
bounds.x = std::min(sx, bounds.x);
for(int x = sx; x < ex; x++) {
row_data[x]++;
}
}
}

if (rendered_any) {
debug(" - rendered line %d\n", y);
maxy = y;
}
else if (y == bounds.y) {
debug(" - render nothing on line %d\n", y);
++bounds.y;
}
}

bounds.y >>= settings::antialias;
maxy >>= settings::antialias;
bounds.w = (maxx >= bounds.x) ? maxx + 1 - bounds.x : 0;
bounds.h = (maxy >= bounds.y) ? maxy + 1 - bounds.y : 0;
debug(" - rendered tile bounds %d, %d (%d x %d)\n", bounds.x, bounds.y, bounds.w, bounds.h);
}

template<typename T>
Expand All @@ -172,7 +252,7 @@ namespace pretty_poly {
}

template<typename T>
void draw_polygon(std::vector<contour_t<T>> contours, point_t<int> origin, int scale) {
void draw_polygon(const std::vector<contour_t<T>>& contours, point_t<int> origin, int scale) {

debug("> draw polygon with %lu contours\n", contours.size());

Expand All @@ -194,8 +274,16 @@ namespace pretty_poly {
debug(" - bounds %d, %d (%d x %d)\n", polygon_bounds.x, polygon_bounds.y, polygon_bounds.w, polygon_bounds.h);
debug(" - clip %d, %d (%d x %d)\n", settings::clip.x, settings::clip.y, settings::clip.w, settings::clip.h);

interp_hw_save_t interp1_save;
interp_save(interp1, &interp1_save);

interp_config cfg = interp_default_config();
interp_config_set_clamp(&cfg, true);
interp_config_set_signed(&cfg, true);
interp_set_config(interp1, 0, &cfg);
interp1->base[0] = 0;

memset(nodes, 0, node_buffer_size * sizeof(unsigned) * 32);
//memset(nodes, 0, node_buffer_size * sizeof(unsigned) * 32);

// iterate over tiles
debug(" - processing tiles\n");
Expand All @@ -218,22 +306,34 @@ namespace pretty_poly {
memset(tile.data, 0, tile_buffer_size);

// build the nodes for each contour
for(contour_t<T> &contour : contours) {
for(const contour_t<T> &contour : contours) {
debug(" : build nodes for contour\n");
build_nodes(contour, tile, origin, scale);
}

debug(" : render the tile\n");
// render the tile
render_nodes(tile);
rect_t bounds;
render_nodes(tile, bounds);
if (bounds.empty()) {
continue;
}

tile.data += bounds.x + tile.stride * bounds.y;
tile.bounds.x += bounds.x;
tile.bounds.y += bounds.y;
tile.bounds.w = bounds.w;
tile.bounds.h = bounds.h;

settings::callback(tile);
}
}

interp_restore(interp1, &interp1_save);
}
}

template void pretty_poly::draw_polygon<int>(std::vector<contour_t<int>> contours, point_t<int> origin, int scale);
template void pretty_poly::draw_polygon<float>(std::vector<contour_t<float>> contours, point_t<int> origin, int scale);
template void pretty_poly::draw_polygon<uint8_t>(std::vector<contour_t<uint8_t>> contours, point_t<int> origin, int scale);
template void pretty_poly::draw_polygon<int8_t>(std::vector<contour_t<int8_t>> contours, point_t<int> origin, int scale);
template void pretty_poly::draw_polygon<int>(const std::vector<contour_t<int>>& contours, point_t<int> origin, int scale);
template void pretty_poly::draw_polygon<float>(const std::vector<contour_t<float>>& contours, point_t<int> origin, int scale);
template void pretty_poly::draw_polygon<uint8_t>(const std::vector<contour_t<uint8_t>>& contours, point_t<int> origin, int scale);
template void pretty_poly::draw_polygon<int8_t>(const std::vector<contour_t<int8_t>>& contours, point_t<int> origin, int scale);
Loading

0 comments on commit c3919bd

Please sign in to comment.