13
0

canvas: add a drawing-request-freeze/thaw API

If queue_draw is "frozen", we simply accumulate drawing
requests in a (union) rectangle, and when finally "thawed"
the canvas submits a single redraw request for the entire
accumulated rect.

Although in theory this is all that GTK/GDK does for
draw requests, callgrind reveals significant costs
associated with the actual calltree for GtkWidget::queue_draw_area().

One potential cost is that GDK also maintains a list of
invalidated rectangles in addition to the union, and
for MIDI regions with thousands of notes, this can represent
real overhead. This approach dispenses with the rect list,
since our Canvas drawing model only uses the union rectangle
anyway.
This commit is contained in:
Paul Davis 2022-03-29 08:52:17 -06:00
parent 1769b20dd1
commit b6d0f8f661
2 changed files with 46 additions and 8 deletions

View File

@ -55,6 +55,7 @@ uint32_t Canvas::tooltip_timeout_msecs = 750;
/** Construct a new Canvas */ /** Construct a new Canvas */
Canvas::Canvas () Canvas::Canvas ()
: _root (this) : _root (this)
, _queue_draw_frozen (0)
, _bg_color (Gtkmm2ext::rgba_to_color (0, 1.0, 0.0, 1.0)) , _bg_color (Gtkmm2ext::rgba_to_color (0, 1.0, 0.0, 1.0))
, _last_render_start_timestamp(0) , _last_render_start_timestamp(0)
, _use_intermediate_surface (false) , _use_intermediate_surface (false)
@ -230,6 +231,25 @@ Canvas::dump (ostream& o) const
_root.dump (o); _root.dump (o);
} }
void
Canvas::freeze_queue_draw ()
{
_queue_draw_frozen++;
}
void
Canvas::thaw_queue_draw ()
{
if (_queue_draw_frozen) {
_queue_draw_frozen--;
if (_queue_draw_frozen == 0 && !frozen_area.empty()) {
request_redraw (frozen_area);
frozen_area = Rect();
}
}
}
/** Called when an item has been shown or hidden. /** Called when an item has been shown or hidden.
* @param item Item that has been shown or hidden. * @param item Item that has been shown or hidden.
*/ */
@ -238,6 +258,11 @@ Canvas::item_shown_or_hidden (Item* item)
{ {
Rect bbox = item->bounding_box (); Rect bbox = item->bounding_box ();
if (bbox) { if (bbox) {
if (_queue_draw_frozen) {
frozen_area = frozen_area.extend (compute_draw_item_area (item, bbox));
return;
}
if (item->item_to_window (bbox).intersection (visible_area ())) { if (item->item_to_window (bbox).intersection (visible_area ())) {
queue_draw_item_area (item, bbox); queue_draw_item_area (item, bbox);
} }
@ -416,41 +441,48 @@ Canvas::item_moved (Item* item, Rect pre_change_parent_bounding_box)
void void
Canvas::queue_draw_item_area (Item* item, Rect area) Canvas::queue_draw_item_area (Item* item, Rect area)
{ {
request_redraw (compute_draw_item_area (item, area));
}
Rect
Canvas::compute_draw_item_area (Item* item, Rect area)
{
Rect r;
if ((area.width()) > 1.0 && (area.height() > 1.0)) { if ((area.width()) > 1.0 && (area.height() > 1.0)) {
/* item has a rectangular bounding box, which may fall /* item has a rectangular bounding box, which may fall
* on non-integer locations. Expand it appropriately. * on non-integer locations. Expand it appropriately.
*/ */
Rect r = item->item_to_window (area, false); r = item->item_to_window (area, false);
r.x0 = floor (r.x0); r.x0 = floor (r.x0);
r.y0 = floor (r.y0); r.y0 = floor (r.y0);
r.x1 = ceil (r.x1); r.x1 = ceil (r.x1);
r.y1 = ceil (r.y1); r.y1 = ceil (r.y1);
//std::cerr << "redraw box, adjust from " << area << " to " << r << std::endl; //std::cerr << "redraw box, adjust from " << area << " to " << r << std::endl;
request_redraw (r);
return;
} else if (area.width() > 1.0 && area.height() == 1.0) { } else if (area.width() > 1.0 && area.height() == 1.0) {
/* horizontal line, which may fall on non-integer /* horizontal line, which may fall on non-integer
* coordinates. * coordinates.
*/ */
Rect r = item->item_to_window (area, false); r = item->item_to_window (area, false);
r.y0 = floor (r.y0); r.y0 = floor (r.y0);
r.y1 = ceil (r.y1); r.y1 = ceil (r.y1);
//std::cerr << "redraw HLine, adjust from " << area << " to " << r << std::endl; //std::cerr << "redraw HLine, adjust from " << area << " to " << r << std::endl;
request_redraw (r);
} else if (area.width() == 1.0 && area.height() > 1.0) { } else if (area.width() == 1.0 && area.height() > 1.0) {
/* vertical single pixel line, which may fall on non-integer /* vertical single pixel line, which may fall on non-integer
* coordinates * coordinates
*/ */
Rect r = item->item_to_window (area, false); r = item->item_to_window (area, false);
r.x0 = floor (r.x0); r.x0 = floor (r.x0);
r.x1 = ceil (r.x1); r.x1 = ceil (r.x1);
//std::cerr << "redraw VLine, adjust from " << area << " to " << r << std::endl; //std::cerr << "redraw VLine, adjust from " << area << " to " << r << std::endl;
request_redraw (r);
} else { } else {
/* impossible? one of width or height must be zero ... */ /* impossible? one of width or height must be zero ... */
//std::cerr << "redraw IMPOSSIBLE of " << area << std::endl; //std::cerr << "redraw IMPOSSIBLE of " << area << std::endl;
request_redraw (item->item_to_window (area, false)); r = item->item_to_window (area, false);
} }
return r;
} }
void void

View File

@ -107,6 +107,9 @@ public:
return &_root; return &_root;
} }
void freeze_queue_draw ();
void thaw_queue_draw ();
void set_background_color (Gtkmm2ext::Color); void set_background_color (Gtkmm2ext::Color);
Gtkmm2ext::Color background_color() const { return _bg_color; } Gtkmm2ext::Color background_color() const { return _bg_color; }
@ -185,6 +188,8 @@ public:
protected: protected:
Root _root; Root _root;
uint32_t _queue_draw_frozen;
Rect frozen_area;
Gtkmm2ext::Color _bg_color; Gtkmm2ext::Color _bg_color;
mutable gint64 _last_render_start_timestamp; mutable gint64 _last_render_start_timestamp;
@ -192,6 +197,7 @@ protected:
static uint32_t tooltip_timeout_msecs; static uint32_t tooltip_timeout_msecs;
void queue_draw_item_area (Item *, Rect); void queue_draw_item_area (Item *, Rect);
Rect compute_draw_item_area (Item *, Rect);
virtual void pick_current_item (int state) = 0; virtual void pick_current_item (int state) = 0;
virtual void pick_current_item (Duple const &, int state) = 0; virtual void pick_current_item (Duple const &, int state) = 0;