Complete.Org: Mailing Lists: Archives: freeciv-dev: April 2004:
[Freeciv-Dev] (PR#8603) faster mapview scrolling
Home

[Freeciv-Dev] (PR#8603) faster mapview scrolling

[Top] [All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index] [Thread Index]
To: undisclosed-recipients: ;
Subject: [Freeciv-Dev] (PR#8603) faster mapview scrolling
From: "Jason Short" <jdorje@xxxxxxxxxxxxxxxxxxxxx>
Date: Mon, 26 Apr 2004 20:53:14 -0700
Reply-to: rt@xxxxxxxxxxx

<URL: http://rt.freeciv.org/Ticket/Display.html?id=8603 >

When the mapview scrolls, currently we call an 
update_map_canvas_visible().  This means if we scroll 30 pixels to the 
left we redraw everything.

In theory we only need to redraw those 30 pixels.  The others can just 
be copied.

This patch (which includes PR#8594) does this.  In scrolling, if there 
is any overlap, instead of doing a full update we do a copy and then 
redraw 2 separate sub-rectangles.

This makes a very marked difference in speed for some operations. 
Scrolling is substantially faster.  My measurements show that redraw 
time drops by a factor of 10 on my computer (from 100ms to 10ms with a 
standard mapview in iso-view; from 200ms to 25ms with a maximized 
mapview).  This actually makes scrolling usable with a maximized mapview.

It will probably have a much smaller effect on between-turn drawing, 
since recentering here is much less likely to be local.

It is probably fast enough to allow mapview "sliding" (see the RT ticket 
on sliding).  A 40 Hz update rate is reasonable.

There are several issues however:

- We copy_canvas from a canvas to itself.  The copy is likely to 
overlap.  This works fine in gui-gtk-2.0 (to my surprise).  Likely in 
other GUIs it does not work.  This can easily be solved by using a 
second backing store and swapping between them.

- When the mapview wraps around in GUI coordinates, we end up doing a 
full update even though it's not necessary.  This could be resolved 
using a gui_distance_vector function to find the difference in positions 
(except that this function doesn't exist yet).

- City descriptions must still be redrawn in full.  This is 
unnecessarily slow since we obviously only need to redraw a few of them. 
  It will also give bad results with anti-aliased fonts since these 
cannot be drawn multiple times.  This can also be solved with a second 
backing store.

- Likely there is some point beyond which it's faster to do the full 
update_map_canvas_visible than it is to do the partial update.  If the 
overlap between new and old mapviews is only 30x30 pixels for instance, 
it probably doesn't make sense to put in the extra overhead of the 
partial update.  It's easy to skip the partial update, if someone is 
willing to figure out how to calculate the point where we shouldn't do it.

jason

Index: client/mapview_common.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/client/mapview_common.c,v
retrieving revision 1.108
diff -u -r1.108 mapview_common.c
--- client/mapview_common.c     26 Apr 2004 21:26:58 -0000      1.108
+++ client/mapview_common.c     27 Apr 2004 03:18:58 -0000
@@ -56,13 +56,16 @@
 **************************************************************************/
 void refresh_tile_mapcanvas(int x, int y, bool write_to_screen)
 {
+  int canvas_x, canvas_y;
+
   assert(is_real_map_pos(x, y));
   if (!normalize_map_pos(&x, &y)) {
     return;
   }
 
-  if (tile_visible_mapcanvas(x, y)) {
-    update_map_canvas(x, y, 1, 1, FALSE);
+  if (map_to_canvas_pos(&canvas_x, &canvas_y, x, y)) {
+    canvas_y -= UNIT_TILE_HEIGHT - NORMAL_TILE_HEIGHT;
+    update_map_canvas(canvas_x, canvas_y, UNIT_TILE_WIDTH, UNIT_TILE_HEIGHT);
 
     if (update_city_text_in_refresh_tile
        && (draw_city_names || draw_city_productions)) {
@@ -382,13 +385,59 @@
   /* Then update everything. */
   if (mapview_canvas.gui_x0 != gui_x0 || mapview_canvas.gui_y0 != gui_y0) {
     int map_center_x, map_center_y;
+    int old_gui_x0 = mapview_canvas.gui_x0;
+    int old_gui_y0 = mapview_canvas.gui_y0;
+    const int width = mapview_canvas.width, height = mapview_canvas.height;
+    int common_x0, common_x1, common_y0, common_y1;
+    int update_x0, update_x1, update_y0, update_y1;
 
     mapview_canvas.gui_x0 = gui_x0;
     mapview_canvas.gui_y0 = gui_y0;
 
+    common_x0 = MAX(old_gui_x0, gui_x0);
+    common_x1 = MIN(old_gui_x0, gui_x0) + width;
+    common_y0 = MAX(old_gui_y0, gui_y0);
+    common_y1 = MIN(old_gui_y0, gui_y0) + height;
+
+    if (common_x1 > common_x0 && common_y1 > common_y0) {
+      /* Do a partial redraw only. */
+
+      if (old_gui_x0 < gui_x0) {
+       update_x0 = MAX(old_gui_x0 + width, gui_x0);
+       update_x1 = gui_x0 + width;
+      } else {
+       update_x0 = gui_x0;
+       update_x1 = MIN(old_gui_x0, gui_x0 + width);
+      }
+      if (old_gui_y0 < gui_y0) {
+       update_y0 = MAX(old_gui_y0 + height, gui_y0);
+       update_y1 = gui_y0 + height;
+      } else {
+       update_y0 = gui_y0;
+       update_y1 = MIN(old_gui_y0, gui_y0 + height);
+      }
+
+      dirty_all();
+      canvas_copy(mapview_canvas.store, mapview_canvas.store,
+                 common_x0 - old_gui_x0,
+                 common_y0 - old_gui_y0,
+                 common_x0 - gui_x0, common_y0 - gui_y0,
+                 common_x1 - common_x0, common_y1 - common_y0);
+      if (update_y1 > update_y0) {
+       update_map_canvas(0, update_y0 - gui_y0,
+                         width, update_y1 - update_y0);
+      }
+      if (update_x1 > update_x0) {
+       update_map_canvas(update_x0 - gui_x0, common_y0 - gui_y0,
+                         update_x1 - update_x0, common_y1 - common_y0);
+      }
+      show_city_descriptions();
+    } else {
+      update_map_canvas_visible();
+    }
+
     get_center_tile_mapcanvas(&map_center_x, &map_center_y);
     center_tile_overviewcanvas(map_center_x, map_center_y);
-    update_map_canvas_visible();
     update_map_canvas_scrollbars();
     if (hover_state == HOVER_GOTO || hover_state == HOVER_PATROL) {
       create_line_at_mouse_pos();
@@ -1289,132 +1338,44 @@
   x, y, width, and height are in map coordinates; they need not be
   normalized or even real.
 **************************************************************************/
-void update_map_canvas(int x, int y, int width, int height, 
-                      bool write_to_screen)
+void update_map_canvas(int canvas_x, int canvas_y, int width, int height)
 {
-  int canvas_start_x, canvas_start_y;
+  const int gui_x0 = mapview_canvas.gui_x0 + canvas_x;
+  const int gui_y0 = mapview_canvas.gui_y0 + canvas_y;
 
   freelog(LOG_DEBUG,
-         "update_map_canvas(pos=(%d,%d), size=(%d,%d), write_to_screen=%d)",
-         x, y, width, height, write_to_screen);
+         "update_map_canvas(pos=(%d,%d), size=(%d,%d))",
+         canvas_x, canvas_y, width, height);
 
   if (is_isometric) {
-    int x_itr, y_itr, i;
-
-    /* First refresh the tiles above the area to remove the old tiles'
-     * overlapping graphics. */
-    put_tile_iso(x - 1, y - 1, D_B_LR); /* top_left corner */
-
-    for (i = 0; i < height - 1; i++) { /* left side - last tile. */
-      put_tile_iso(x - 1, y + i, D_MB_LR);
-    }
-    put_tile_iso(x - 1, y + height - 1, D_TMB_R); /* last tile left side. */
-
-    for (i = 0; i < width - 1; i++) {
-      /* top side */
-      put_tile_iso(x + i, y - 1, D_MB_LR);
-    }
-    if (width > 1) {
-      /* last tile top side. */
-      put_tile_iso(x + width - 1, y - 1, D_TMB_L);
-    } else {
-      put_tile_iso(x + width - 1, y - 1, D_MB_L);
-    }
-
-    /* Now draw the tiles to be refreshed, from the top down to get the
-     * overlapping areas correct. */
-    for (x_itr = x; x_itr < x + width; x_itr++) {
-      for (y_itr = y; y_itr < y + height; y_itr++) {
-       put_tile_iso(x_itr, y_itr, D_FULL);
-      }
-    }
-
-    /* Then draw the tiles underneath to refresh the parts of them that
-     * overlap onto the area just drawn. */
-    put_tile_iso(x, y + height, D_TM_R);  /* bottom side */
-    for (i = 1; i < width; i++) {
-      int x1 = x + i;
-      int y1 = y + height;
-      put_tile_iso(x1, y1, D_TM_R);
-      put_tile_iso(x1, y1, D_T_L);
-    }
-
-    put_tile_iso(x + width, y, D_TM_L); /* right side */
-    for (i=1; i < height; i++) {
-      int x1 = x + width;
-      int y1 = y + i;
-      put_tile_iso(x1, y1, D_TM_L);
-      put_tile_iso(x1, y1, D_T_R);
-    }
-
-    put_tile_iso(x + width, y + height, D_T_LR); /* right-bottom corner */
+    gui_rect_iterate(gui_x0, gui_y0, width, height, map_x, map_y, draw) {
+      put_tile_iso(map_x, map_y, draw);
+    } gui_rect_iterate_end;
 
 
     /* Draw the goto lines on top of the whole thing. This is done last as
      * we want it completely on top. */
-    for (x_itr = x - 1; x_itr <= x + width; x_itr++) {
-      for (y_itr = y - 1; y_itr <= y + height; y_itr++) {
-       int x1 = x_itr;
-       int y1 = y_itr;
-       if (normalize_map_pos(&x1, &y1)) {
-         adjc_dir_iterate(x1, y1, x2, y2, dir) {
-           if (get_drawn(x1, y1, dir)) {
-             draw_segment(x1, y1, dir);
-           }
-         } adjc_dir_iterate_end;
-       }
+    gui_rect_iterate(gui_x0, gui_y0, width, height, map_x, map_y, draw) {
+      if (normalize_map_pos(&map_x, &map_y)) {
+       adjc_dir_iterate(map_x, map_y, adjc_x, adjc_y, dir) {
+         if (get_drawn(map_x, map_y, dir)) {
+           draw_segment(map_x, map_y, dir);
+         }
+       } adjc_dir_iterate_end;
       }
-    }
-
-
-    /* Lastly draw our changes to the screen. */
-    /* top left corner */
-    map_to_canvas_pos(&canvas_start_x, &canvas_start_y, x, y);
-
-    /* top left corner in isometric view */
-    canvas_start_x -= height * NORMAL_TILE_WIDTH / 2;
-
-    /* because of where get_canvas_xy() sets canvas_x */
-    canvas_start_x += NORMAL_TILE_WIDTH / 2;
-
-    /* And because units fill a little extra */
-    canvas_start_y += NORMAL_TILE_HEIGHT - UNIT_TILE_HEIGHT;
-
-    /* Here we draw a rectangle that includes the updated tiles.  This
-     * method can fail if the area wraps off one side of the screen and
-     * back to the other. */
-    dirty_rect(canvas_start_x, canvas_start_y,
-              (height + width) * NORMAL_TILE_WIDTH / 2,
-              (height + width) * NORMAL_TILE_HEIGHT / 2
-              + NORMAL_TILE_HEIGHT / 2);
+    } gui_rect_iterate_end;
   } else {
     /* not isometric */
-    int map_x, map_y;
-
-    for (map_y = y; map_y < y + height; map_y++) {
-      for (map_x = x; map_x < x + width; map_x++) {
-       /*
-        * We don't normalize until later because we want to draw
-        * black tiles for unreal positions.
-        */
-       put_tile(map_x, map_y);
-      }
-    }
-    /* Here we draw a rectangle that includes the updated tiles.  This
-     * method can fail if the area wraps off one side of the screen and
-     * back to the other. */
-    map_to_canvas_pos(&canvas_start_x, &canvas_start_y, x, y);
-    dirty_rect(canvas_start_x, canvas_start_y,
-              width * NORMAL_TILE_WIDTH,
-              height * NORMAL_TILE_HEIGHT);
+    gui_rect_iterate(gui_x0, gui_y0, width, height, map_x, map_y, draw) {
+      /*
+       * We don't normalize until later because we want to draw
+       * black tiles for unreal positions.
+       */
+      put_tile(map_x, map_y);
+    } gui_rect_iterate_end;
   }
 
-  if (write_to_screen) {
-    /* We never want a partial flush; that would leave the screen in an
-     * inconsistent state.  If the caller tells us to write_to_screen we
-     * simply flush everything immediately. */
-    flush_dirty();
-  }
+  dirty_rect(canvas_x, canvas_y, width, height);
 }
 
 /**************************************************************************
@@ -1422,8 +1383,6 @@
 **************************************************************************/
 void update_map_canvas_visible(void)
 {
-  int map_x0, map_y0;
-
   dirty_all();
 
   /* Clear the entire mapview.  This is necessary since if the mapview is
@@ -1434,24 +1393,7 @@
    * cleared. */
   canvas_put_rectangle(mapview_canvas.store, COLOR_STD_BLACK,
                       0, 0, mapview_canvas.width, mapview_canvas.height);
-
-  canvas_to_map_pos(&map_x0, &map_y0, 0, 0);
-  if (is_isometric) {
-    /* just find a big rectangle that includes the whole visible area. The
-       invisible tiles will not be drawn. */
-    int width, height;
-
-    width = mapview_canvas.tile_width + mapview_canvas.tile_height + 2;
-    height = width;
-    update_map_canvas(map_x0 - 1, map_y0 - mapview_canvas.tile_width - 1,
-                     width, height, FALSE);
-  } else {
-    update_map_canvas(map_x0, map_y0,
-                     mapview_canvas.tile_width + 1,
-                     mapview_canvas.tile_height + 1,
-                     FALSE);
-  }
-
+  update_map_canvas(0, 0, mapview_canvas.width, mapview_canvas.height);
   show_city_descriptions();
 }
 
Index: client/mapview_common.h
===================================================================
RCS file: /home/freeciv/CVS/freeciv/client/mapview_common.h,v
retrieving revision 1.59
diff -u -r1.59 mapview_common.h
--- client/mapview_common.h     26 Apr 2004 16:25:01 -0000      1.59
+++ client/mapview_common.h     27 Apr 2004 03:18:58 -0000
@@ -268,8 +268,7 @@
                    int canvas_x, int canvas_y,
                    enum draw_type draw, bool citymode);
 
-void update_map_canvas(int x, int y, int width, int height,
-                      bool write_to_screen);
+void update_map_canvas(int canvas_x, int canvas_y, int width, int height);
 void update_map_canvas_visible(void);
 
 void show_city_descriptions(void);
Index: client/packhand.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/client/packhand.c,v
retrieving revision 1.362
diff -u -r1.362 packhand.c
--- client/packhand.c   14 Apr 2004 17:18:36 -0000      1.362
+++ client/packhand.c   27 Apr 2004 03:18:59 -0000
@@ -578,10 +578,20 @@
 
   if ((draw_map_grid || draw_borders) && can_client_change_view()) {
     /* We have to make sure we update any workers on the map grid, then
-     * redraw the city descriptions on top of them. */
-    update_map_canvas(pcity->x - CITY_MAP_SIZE / 2,
-                     pcity->y - CITY_MAP_SIZE / 2,
-                     CITY_MAP_SIZE, CITY_MAP_SIZE, FALSE);
+     * redraw the city descriptions on top of them.  So we calculate the
+     * rectangle covered by the city's map, and update that.  Then we
+     * queue up a city description redraw for later. */
+    int canvas_x, canvas_y;
+    int width = get_citydlg_canvas_width();
+    int height = get_citydlg_canvas_height();
+
+    (void) map_to_canvas_pos(&canvas_x, &canvas_y, pcity->x, pcity->y);
+
+    canvas_x -= (width - NORMAL_TILE_WIDTH) / 2;
+    canvas_y -= (height - NORMAL_TILE_HEIGHT) / 2;
+    update_map_canvas(canvas_x - (width - NORMAL_TILE_WIDTH) / 2,
+                     canvas_y - (height - NORMAL_TILE_HEIGHT) / 2,
+                     width, height);
     queue_mapview_update(UPDATE_CITY_DESCRIPTIONS);
   } else {
     refresh_tile_mapcanvas(pcity->x, pcity->y, FALSE);

[Prev in Thread] Current Thread [Next in Thread]
  • [Freeciv-Dev] (PR#8603) faster mapview scrolling, Jason Short <=