[patch] make partial redraw requests work
Hi, this patch adds set(), isEmpty(), and merge() methods to core::Rectangle. These methods are then used to maintain a rectangle that contains all screen areas for which a redraw was requested. When fltk really draws to the screen, the drawing is limited to this area. The most obvious improvement is visible on pages with very large images that take some time to load. While the image is loaded the rest of the page is no longer flickering - at least if there is no other stuff that requests complete redraws. Johannes diff --git a/dw/fltkviewbase.cc b/dw/fltkviewbase.cc --- a/dw/fltkviewbase.cc +++ b/dw/fltkviewbase.cc @@ -42,6 +42,7 @@ FltkViewBase::FltkViewBase (int x, int y canvasWidth = 1; canvasHeight = 1; bgColor = WHITE; + redrawArea.set (0, 0, 0, 0); } FltkViewBase::~FltkViewBase () @@ -50,9 +51,18 @@ FltkViewBase::~FltkViewBase () void FltkViewBase::draw () { - Group::draw (); + int d = damage(); + ::fltk::Rectangle rect (x(), y(), w(), h()); - ::fltk::Rectangle rect (x(), y(), w(), h()); + //Group::draw (); + + if (d == DAMAGE_VALUE) { + ::fltk::push_clip( + translateCanvasXToViewX(redrawArea.x), + translateCanvasYToViewY(redrawArea.y), + redrawArea.width, + redrawArea.height); + } /* fltk-clipping seems not to use widget coordinates */ ::fltk::intersect_with_clip(rect); @@ -68,6 +78,12 @@ void FltkViewBase::draw () area.width = rect.w(); area.height = rect.h(); theLayout->expose (this, &area); + + if (d == DAMAGE_VALUE) { + ::fltk::pop_clip(); + } + + redrawArea.set (0, 0, 0, 0); } core::ButtonState getDwButtonState () @@ -223,9 +239,9 @@ void FltkViewBase::finishDrawing (core:: void FltkViewBase::queueDraw (core::Rectangle *area) { - /** \bug Inefficient */ + redrawArea.merge (area); - redraw (DAMAGE_EXPOSE); + redraw (DAMAGE_VALUE); } void FltkViewBase::queueDrawTotal () diff --git a/dw/fltkviewbase.hh b/dw/fltkviewbase.hh --- a/dw/fltkviewbase.hh +++ b/dw/fltkviewbase.hh @@ -14,6 +14,7 @@ class FltkViewBase: public FltkView, pub { private: int bgColor; + core::Rectangle redrawArea; protected: core::Layout *theLayout; diff --git a/dw/types.cc b/dw/types.cc --- a/dw/types.cc +++ b/dw/types.cc @@ -27,10 +27,7 @@ namespace core { Rectangle::Rectangle (int x, int y, int width, int height) { - this->x = x; - this->y = y; - this->width = width; - this->height = height; + set (x, y, width, height); } /** @@ -111,6 +108,35 @@ bool Rectangle::isPointWithin (int x, in x >= this->x && y >= this->y && x < this->x + width && y < this->y + height; } + +/** + * \brief Enlarge the rectangle so that it also includes otherRect. + * If a rectangle has zero size, it is ignored. + */ +void Rectangle::merge (Rectangle *otherRect) +{ + if (otherRect->isEmpty()) { + return; + } + + if (isEmpty()) { + x = otherRect->x; + y = otherRect->y; + width = otherRect->width; + height = otherRect->height; + return; + } + + width = ::misc::max(x + width, otherRect->x + otherRect->width); + height = ::misc::max(y + height, otherRect->y + otherRect->height); + + x = ::misc::min(x, otherRect->x); + y = ::misc::min(y, otherRect->y); + + width -= x; + height -= y; +} + // ---------------------------------------------------------------------- diff --git a/dw/types.hh b/dw/types.hh --- a/dw/types.hh +++ b/dw/types.hh @@ -68,9 +68,18 @@ public: inline Rectangle () { } Rectangle (int x, int y, int width, int height); + void set (int x, int y, int w, int h) { + this->x = x; + this->y = y; + this->width = w; + this->height = h; + } + bool intersectsWith (Rectangle *otherRect, Rectangle *dest); + void merge (Rectangle *otherRect); bool isSubsetOf (Rectangle *otherRect); bool isPointWithin (int x, int y); + bool isEmpty() {return width == 0 && height == 0;}; }; /**
Argh, forget about my last patch please. It was much too complicated. fltk does all that for us! The following looks much simpler, doesn't it :-) --- a/dw/fltkviewbase.cc Wed Nov 07 22:39:38 2007 +0100 +++ b/dw/fltkviewbase.cc Fri Nov 09 22:40:14 2007 +0100 @@ -223,9 +223,13 @@ void FltkViewBase::finishDrawing (core:: void FltkViewBase::queueDraw (core::Rectangle *area) { - /** \bug Inefficient */ - redraw (DAMAGE_EXPOSE); + redraw(::fltk::Rectangle( + translateCanvasXToViewX(area->x), + translateCanvasYToViewY(area->y), + area->width, + area->height)); + } Sorry for the confusion, Johannes
On Fri, Nov 09, 2007 at 10:51:11PM +0100, Johannes Hofmann wrote:
Argh, forget about my last patch please. It was much too complicated. fltk does all that for us! The following looks much simpler, doesn't it :-)
Yes!
Sorry for the confusion,
It looked quite nice! ;-) Reverted and applied in CVS. -- Cheers Jorge.-
On Fri, Nov 09, 2007 at 08:22:41PM +0100, Johannes Hofmann wrote:
Hi,
this patch adds set(), isEmpty(), and merge() methods to core::Rectangle. These methods are then used to maintain a rectangle that contains all screen areas for which a redraw was requested. When fltk really draws to the screen, the drawing is limited to this area. The most obvious improvement is visible on pages with very large images that take some time to load. While the image is loaded the rest of the page is no longer flickering - at least if there is no other stuff that requests complete redraws.
Committed! -- Cheers Jorge.-
Hi Johannes, I'm glad that someone is dealing with this problem. I'll write more about it, here just some first remarks, after only taking a glance at this and other patches. On Fri, Nov 09, Johannes Hofmann wrote:
Hi,
this patch adds set(), isEmpty(), and merge() methods to core::Rectangle. These methods are then used to maintain a rectangle that contains all screen areas for which a redraw was requested. When fltk really draws to the screen, the drawing is limited to this area. The most obvious improvement is visible on pages with very large images that take some time to load. While the image is loaded the rest of the page is no longer flickering - at least if there is no other stuff that requests complete redraws.
You may take a look at the old implementation (gtk-based, somewhere in dw_viewport.c), combining the areas of different redraw requests is already implemented there. Perhaps it is better to adopt the old code, which has been working for a while. Some notes on the general problem. There are several cases when areas are to be redrawn, I hope this list is complete: 1. to react on expose events, 2. if a widget redraws itself, and 3. when scrolling. Regarding the problem, how to restrict drawing to a minimum, case 1 is probably neglectable, since the number of expose events is mostly rather small (only when moving windows, changing there order etc.). It seems that the following code:
+ ::fltk::Rectangle rect (x(), y(), w(), h()); + if (d == DAMAGE_VALUE) {
may be used to distinguish it from the critical cases 2 and 3. For case 1, still the whole viewport is drawn, optimization should be secondary. Sebastian
Hi Sebastian, this patch is obsolete, as fltk does all that for us: The void Widget::redraw(const Rectangle& r) method marks a rectangle for later redraw. See my followup mail for a much shorter patch that does the same thing. It's already in cvs. I will try to summarize how fltk handles redraws (as I have understood it so far): * Redraw requests from the program itself or from the outside (expose events) are accumulated by fltk and will result in a later call to the draw() methods of the various widgets. Information about what is to be drawn is passed via a bitmap that can be accessed using Widget::damage() and via the current clipping region. * A program can require redraws using void Widget::redraw(const Rectangle& r). In this case fltk ensures, that r will not be clipped during a subsequent draw(). * A program can require redraws using void Widget::redraw(uchar c). In this case fltk ensures, that at least the bits that are on in c will be turned on in the damage() bitmap during a subsequent draw(). This pretty simple mechanism is used by fltk code itself to avoid unnecessary redraws. Here are some examples: * The void Group::draw_child(Widget& w) method looks at the current clip region and returns immediately if w is clipped. * If a widget notices that it needs redrawing (e.g. a button that has been pushed) it will set a flag in its own damage() bitmap and then call redraw(DAMAGE_CHILD). If then at drawing time just the DAMAGE_CHILD bit is on in the global damage() bitmap, Group calls the draw() methods of all those child widgets that have indicated that they need a redraw in their on damage() bitmap. All other widgets will not redraw. * ScrollGroup uses the DAMAGE_SCROLL bit in damage() to indicate that a redraw is necessary because of a scroll movement. If this is the only bit in damage() at drawing time, an optimized aproach is used for redrawing: The part of the screen that was already visible before the scroll movement will be copied to the new position and only the rest is actually redrawn. Some of these methods are already used in the current cvs-version of dw2: * void FltkViewBase::draw () only calls theLayout->expose () for the not clipped area. * If just DAMAGE_CHILD is on in damage(), FltkViewBase::draw () lets Group::draw() draw those child widgets that have requested a redraw. * void FltkViewBase::queueDraw (core::Rectangle *area) uses void Widget::redraw(const Rectangle& r) to request redrawing only for a specific area. * void FltkViewport::draw () uses the same mechanism as ScrollGroup to optimize redraws that are necessary because of scroll movements. Cheers, Johannes On Sun, Nov 18, 2007 at 09:22:43PM +0100, Sebastian Geerken wrote:
Hi Johannes,
I'm glad that someone is dealing with this problem. I'll write more about it, here just some first remarks, after only taking a glance at this and other patches.
On Fri, Nov 09, Johannes Hofmann wrote:
Hi,
this patch adds set(), isEmpty(), and merge() methods to core::Rectangle. These methods are then used to maintain a rectangle that contains all screen areas for which a redraw was requested. When fltk really draws to the screen, the drawing is limited to this area. The most obvious improvement is visible on pages with very large images that take some time to load. While the image is loaded the rest of the page is no longer flickering - at least if there is no other stuff that requests complete redraws.
You may take a look at the old implementation (gtk-based, somewhere in dw_viewport.c), combining the areas of different redraw requests is already implemented there. Perhaps it is better to adopt the old code, which has been working for a while.
Some notes on the general problem. There are several cases when areas are to be redrawn, I hope this list is complete:
1. to react on expose events, 2. if a widget redraws itself, and 3. when scrolling.
Regarding the problem, how to restrict drawing to a minimum, case 1 is probably neglectable, since the number of expose events is mostly rather small (only when moving windows, changing there order etc.). It seems that the following code:
+ ::fltk::Rectangle rect (x(), y(), w(), h()); + if (d == DAMAGE_VALUE) {
may be used to distinguish it from the critical cases 2 and 3. For case 1, still the whole viewport is drawn, optimization should be secondary.
Sebastian
_______________________________________________ Dillo-dev mailing list Dillo-dev@dillo.org http://lists.auriga.wearlab.de/cgi-bin/mailman/listinfo/dillo-dev
On Mon, Nov 19, Johannes Hofmann wrote:
Hi Sebastian,
this patch is obsolete, as fltk does all that for us: The void Widget::redraw(const Rectangle& r) method marks a rectangle for later redraw. See my followup mail for a much shorter patch that does the same thing. It's already in cvs.
I will try to summarize how fltk handles redraws (as I have understood it so far):
* Redraw requests from the program itself or from the outside (expose events) are accumulated by fltk and will result in a later call to the draw() methods of the various widgets. Information about what is to be drawn is passed via a bitmap that can be accessed using Widget::damage() and via the current clipping region.
* A program can require redraws using void Widget::redraw(const Rectangle& r). In this case fltk ensures, that r will not be clipped during a subsequent draw().
* A program can require redraws using void Widget::redraw(uchar c). In this case fltk ensures, that at least the bits that are on in c will be turned on in the damage() bitmap during a subsequent draw().
This is actually a bit more complicated, as I remember. A widget redraw is referred in canvas coordinates, but FLTK referres to viewport coordinates. I once described this in a mail (unfortunately not in the code), which I cannot find now, so here again an example: 1. A widget requests a redraw for the canvas region (100, 100, 100 x 100) [x, y, w x h]. If the viewport position is at (50, 50), this is translated to (50, 50, 100 x 100). Notice, that the drawing is not done immediately at this time. 2. The viewport is scrolled to (50, 100) (i.e. scrolled down 50px). 3. Now, the idle function is activated, and draws the region (50, 50, 100 x 100). This is, since the viewport position has changed, the canvas region (100, 150, 100 x 100). This means, that the upper half of the originally requestet *canvas* region is not drawn (it has been scrolled out of the original viewport region, so to speak). So, Dw (or the FLTK part of Dw) has to do some work on its own. See Dw_gtk_viewport_queue_draw in dw_gtk_viewport.c (old code) for details. I'm not currently aware of all implications of it, I'll have to take another deeper look at the old code. Sebastian
On Tue, Nov 20, 2007 at 08:11:35PM +0100, Sebastian Geerken wrote:
On Mon, Nov 19, Johannes Hofmann wrote:
Hi Sebastian,
this patch is obsolete, as fltk does all that for us: The void Widget::redraw(const Rectangle& r) method marks a rectangle for later redraw. See my followup mail for a much shorter patch that does the same thing. It's already in cvs.
I will try to summarize how fltk handles redraws (as I have understood it so far):
* Redraw requests from the program itself or from the outside (expose events) are accumulated by fltk and will result in a later call to the draw() methods of the various widgets. Information about what is to be drawn is passed via a bitmap that can be accessed using Widget::damage() and via the current clipping region.
* A program can require redraws using void Widget::redraw(const Rectangle& r). In this case fltk ensures, that r will not be clipped during a subsequent draw().
* A program can require redraws using void Widget::redraw(uchar c). In this case fltk ensures, that at least the bits that are on in c will be turned on in the damage() bitmap during a subsequent draw().
This is actually a bit more complicated, as I remember.
Indeed. fltk2 tries to be smarter here than fltk1 was. Not sure yet whether this is good or bad. The attached test program shows how in fltk2 a redraw(<flag>) and a redraw(<rectangle>) results in two separate drawing operations with different damage flags and clip rectangles set.
A widget redraw is referred in canvas coordinates, but FLTK referres to viewport coordinates. I once described this in a mail (unfortunately not in the code), which I cannot find now, so here again an example:
1. A widget requests a redraw for the canvas region (100, 100, 100 x 100) [x, y, w x h]. If the viewport position is at (50, 50), this is translated to (50, 50, 100 x 100).
Notice, that the drawing is not done immediately at this time.
2. The viewport is scrolled to (50, 100) (i.e. scrolled down 50px).
3. Now, the idle function is activated, and draws the region (50, 50, 100 x 100). This is, since the viewport position has changed, the canvas region (100, 150, 100 x 100). This means, that the upper half of the originally requestet *canvas* region is not drawn (it has been scrolled out of the original viewport region, so to speak).
So, Dw (or the FLTK part of Dw) has to do some work on its own. See Dw_gtk_viewport_queue_draw in dw_gtk_viewport.c (old code) for details.
Do you think this is worth the effort? Why not just do a complete redraw whenever a partial redraw request and a scroll movement collide and must be dealt with within a single draw operation? Alternatively we could store the region that needs redraw separately in canvas coordinates and do the translation to viewport coordinates at draw time. No problem, but more complex code. Johannes
I'm not currently aware of all implications of it, I'll have to take another deeper look at the old code.
Sebastian
_______________________________________________ Dillo-dev mailing list Dillo-dev@dillo.org http://lists.auriga.wearlab.de/cgi-bin/mailman/listinfo/dillo-dev
Ooops test program was missing in my last mail. Here it comes: #include <stdio.h> #include <fltk/Window.h> #include <fltk/Widget.h> #include <fltk/Button.h> #include <fltk/run.h> #include <fltk/draw.h> #include <fltk/damage.h> using namespace fltk; Widget *test; class Test : public Widget { public: Test(int _x, int _y, int _w, int _h) : Widget(_x, _y, _w, _h) {}; void draw() { Rectangle rect(x(), y(), w(), h()); intersect_with_clip(rect); fprintf(stderr, "damage 0x%x clip %d %d %d %d\n", damage(), rect.x(), rect.y(), rect.w(), rect.h()); } }; void button_cb() { test->redraw(DAMAGE_SCROLL); test->redraw(Rectangle(10, 10, 10, 10)); } int main(int argc, char **argv) { Window *window = new Window(300, 180); window->begin(); test = new Test(20, 40, 260, 100); Button *b = new Button(0, 0, 60, 30, "redraw"); b->callback((Callback*) button_cb); window->end(); window->show(argc, argv); return run(); }
participants (3)
-
jcid@dillo.org
-
Johannes.Hofmann@gmx.de
-
sgeerken@dillo.org