Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[rmkit][fb] speed up draw_rect functions #78

Merged
merged 2 commits into from
Feb 4, 2021
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 60 additions & 27 deletions src/rmkit/fb/fb.cpy
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
#include "../../vendor/stb/stb_image.h"
#include "../../vendor/stb/stb_image_write.h"

#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)

using namespace std

DEBUG_FB_INFO := getenv("DEBUG_FB_INFO") != NULL
Expand Down Expand Up @@ -220,7 +223,7 @@ namespace framebuffer:
self._set_pixel(i, j, WHITE)
break
default:
if dither != 1.0:
if unlikely(dither != 1.0):
if fast_rand() / float(2 << 15) < dither:
self._set_pixel(i, j, color)
else:
Expand Down Expand Up @@ -248,6 +251,12 @@ namespace framebuffer:
//
// note that dithering does not work with GRAY, RUBBER or ERASER
inline void draw_rect(int o_x, o_y, w, h, color, fill=true, float dither=1.0):
update_dirty(dirty_area, o_x, o_y)
update_dirty(dirty_area, o_x+w, o_y+h)

_draw_rect_fast(o_x, o_y, w, h, color, fill, dither)

inline void _draw_rect_fast(int o_x, o_y, w, h, color, fill=true, float dither=1.0):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do see a huge reduction in the number of times update_dirty is called, which seems like a net benefit in any case, so this looks like a helpful part for sure.

self.dirty = 1
#ifdef DEBUG_FB
fprintf(stderr, "DRAWING RECT X: %i Y: %i W: %i H: %i, COLOR: %i\n", o_x, o_y, w, h, color)
Expand All @@ -256,19 +265,30 @@ namespace framebuffer:
if o_y >= self.height || o_x >= self.width || o_y < 0 || o_x < 0:
return

update_dirty(dirty_area, o_x, o_y)
update_dirty(dirty_area, o_x+w, o_y+h)
if likely(fill):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like almost every function calls this with fill=true. I think the only place where fill=false would happen is in the original draw_rect itself right?

What about making draw_rect include something like this? (I haven't tested, just a thought)

if fill:
  _draw_rect_fast(x, y, w, h, ...)
else:
  _draw_rect_fast(x, y, w, 1, ...)     // top
  _draw_rect_fast(x, y+h-1, w, 1, ...) // bottom
  _draw_rect_fast(x, y, 1, h, ...)     // left
  _draw_rect_fast(x+w-1, y, 1, h, ...) // right

The main difference I think is that do_dithering could still be inlined in _draw_rect_fast since it would only have one call site.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that looks good to me

for j 0 h:
if j+o_y >= self.height:
break

for j 0 h:
if j+o_y >= self.height:
break
for i 0 w:
if i+o_x >= self.width:
break
Copy link
Collaborator

@mrichards42 mrichards42 Feb 3, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder how expensive these comparisons are (they might not be at all) compared to:

w = min(w, self.width-x)
h = min(h, self.height-y)
for j 0 h:
  for i 0 w:
    do_dithering(self.fbmem, i+o_x, j+o_y, color, dither)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i had a little trouble with this just now (and had trouble the first couple times i tried it a while back)


do_dithering(self.fbmem, i+o_x, j+o_y, color, dither)
else:
for j 0 h:
if j+o_y >= self.height:
break

do_dithering(self.fbmem, o_x, j+o_y, color, dither)
do_dithering(self.fbmem, w+o_x-1, j+o_y, color, dither)

for i 0 w:
if i+o_x >= self.width:
break

if fill || (j == 0 || i == 0 || j == h-1 || i == w-1):
do_dithering(self.fbmem, i+o_x, j+o_y, color, dither)
do_dithering(self.fbmem, i+o_x, o_y, color, dither)
do_dithering(self.fbmem, i+o_x, h+o_y-1, color, dither)

inline remarkable_color to_rgb565(char *src, int offset):
r := src[offset]
Expand Down Expand Up @@ -454,15 +474,18 @@ namespace framebuffer:
int w = stroke
int h = stroke

update_dirty(dirty_area, x0-radius, y0-radius)
update_dirty(dirty_area, x0+radius, y0+radius)

while(x <= y):
self.draw_rect(x+x0, y+y0, w, h, color, true);
self.draw_rect(-x+x0, y+y0, w, h, color, true);
self.draw_rect(x+x0, -y+y0, w, h, color, true);
self.draw_rect(-x+x0, -y+y0, w, h, color, true);
self.draw_rect(y+x0, x+y0, w, h, color, true);
self.draw_rect(-y+x0, x+y0, w, h, color, true);
self.draw_rect(y+x0, -x+y0, w, h, color, true);
self.draw_rect(-y+x0, -x+y0, w, h, color, true);
_draw_rect_fast(x+x0, y+y0, w, h, color, true);
_draw_rect_fast(-x+x0, y+y0, w, h, color, true);
_draw_rect_fast(x+x0, -y+y0, w, h, color, true);
_draw_rect_fast(-x+x0, -y+y0, w, h, color, true);
_draw_rect_fast(y+x0, x+y0, w, h, color, true);
_draw_rect_fast(-y+x0, x+y0, w, h, color, true);
_draw_rect_fast(y+x0, -x+y0, w, h, color, true);
_draw_rect_fast(-y+x0, -x+y0, w, h, color, true);

if(d <= 0):
x++;
Expand All @@ -478,7 +501,10 @@ namespace framebuffer:
w := stroke
h := stroke

self.draw_rect(x, y, w, h, color, true);
update_dirty(dirty_area, x0-r, y0-r)
update_dirty(dirty_area, x0+r, y0+r)

_draw_rect_fast(x, y, w, h, color, true);
d := (3-2*(int)r);
while (x <= y):
if (d <= 0):
Expand All @@ -488,21 +514,24 @@ namespace framebuffer:
y--;
x++;

self.draw_rect(x+x0, y+y0, w, h, color, true);
self.draw_rect(-x+x0, y+y0, w, h, color, true);
self.draw_rect(x+x0, -y+y0, w, h, color, true);
self.draw_rect(-x+x0, -y+y0, w, h, color, true);
self.draw_rect(y+x0, x+y0, w, h, color, true);
self.draw_rect(-y+x0, x+y0, w, h, color, true);
self.draw_rect(y+x0, -x+y0, w, h, color, true);
_draw_rect_fast(x+x0, y+y0, w, h, color, true);
_draw_rect_fast(-x+x0, y+y0, w, h, color, true);
_draw_rect_fast(x+x0, -y+y0, w, h, color, true);
_draw_rect_fast(-x+x0, -y+y0, w, h, color, true);
_draw_rect_fast(y+x0, x+y0, w, h, color, true);
_draw_rect_fast(-y+x0, x+y0, w, h, color, true);
_draw_rect_fast(y+x0, -x+y0, w, h, color, true);

self.draw_rect(-y+x0, -x+y0, w, h, color, true);
_draw_rect_fast(-y+x0, -x+y0, w, h, color, true);

def draw_circle_filled(int x0, y0, radius, stroke, color):
update_dirty(dirty_area, x0-radius, y0-radius)
update_dirty(dirty_area, x0+radius, y0+radius)

for x := -radius; x <= radius; x++:
for y := -radius; y <= radius; y++:
if (x*x+y*y) <= radius*radius:
self.draw_rect(x+x0, y+y0, stroke, stroke, color, true)
_draw_rect_fast(x+x0, y+y0, stroke, stroke, color, true)

// function: draw_circle
//
Expand Down Expand Up @@ -548,13 +577,17 @@ namespace framebuffer:
fprintf(stderr, "DRAWING LINE %i %i %i %i\n", x0, y0, x1, y1)
#endif
self.dirty = 1

update_dirty(dirty_area, x0, y0)
update_dirty(dirty_area, x1, y1)

dx := abs(x1-x0)
sx := x0<x1 ? 1 : -1
dy := -abs(y1-y0)
sy := y0<y1 ? 1 : -1
err := dx+dy /* error value e_xy */
while (true): /* loop */
self.draw_rect(x0, y0, width, width, color,true,dither)
_draw_rect_fast(x0, y0, width, width, color,true,dither)
// self.fbmem[y0*self.width + x0] = color
if (x0==x1 && y0==y1) break;
e2 := 2*err
Expand Down