]> git.draconx.ca Git - rrace.git/commitdiff
x11: Improve redraw of empty spaces.
authorNick Bowler <nbowler@draconx.ca>
Wed, 16 Nov 2022 05:16:29 +0000 (00:16 -0500)
committerNick Bowler <nbowler@draconx.ca>
Wed, 16 Nov 2022 06:35:12 +0000 (01:35 -0500)
When the game area is showing only the middle portion with the empty
border, we can clear this with 4 or fewer draw calls instead of 16.
Let's handle this as a special case in the tile drawing code.

Moreover, we don't need to draw anything in these spaces on expose
events, as the exposed areas are already filled with the background.

src/motif_ui.c
src/x11.c

index 127de061bbc1a84608cfe95a09a953631bc25bb1..fefcfedef7505a6509074f76e5ffe1d2e9d57ecb 100644 (file)
@@ -323,6 +323,8 @@ static void game_expose(Widget w, void *data, void *cb_data)
 {
        XmDrawingAreaCallbackStruct *cbs = cb_data;
        XExposeEvent *e = &cbs->event->xexpose;
+       struct app_state *state = data;
+       uint_least32_t *gp = state->board.game;
        Dimension width, height;
        uint_fast32_t mask;
 
@@ -330,8 +332,15 @@ static void game_expose(Widget w, void *data, void *cb_data)
        if (!(width /= 5) || !(height /= 5))
                return;
 
-       mask = expose_mask(e->x, e->y, e->width, e->height, width, height);
-       x11_redraw_game(data, mask);
+       /*
+        * Only draw exposed nonempty tiles; exposed areas are filled with the
+        * background automatically and thus exposed empty spaces don't need
+        * to be drawn again.
+        */
+       mask = gp[0] | gp[1] | gp[2];
+       mask &= expose_mask(e->x, e->y, e->width, e->height, width, height);
+
+       x11_redraw_game(state, mask);
 }
 
 static void goal_expose(Widget w, void *data, void *cb_data)
index 0a9bef0a78684ad47ae791c2fd017cc3a1fc0be6..874bfdc57399c4cf6ecf5b7ae2c2d5f2981501ef 100644 (file)
--- a/src/x11.c
+++ b/src/x11.c
@@ -138,6 +138,74 @@ static void draw_tile(struct app_state *state, Display *display, Drawable d,
        XFillRectangle(display, d, state->tile_gc, tx+2, ty+2, tw-4, th-4);
 }
 
+/*
+ * Clear a contiguous rectangle of tiles from top-left (x0, y0) to
+ * bottom-right (x1, y1).
+ */
+static void
+clear_tiles(struct app_state *state, Display *display, Drawable d,
+            int x0, int y0, int x1, int y1, Dimension w, Dimension h)
+{
+       XRectangle r = { x0*w, y0*h, (x1+1)*w, (y1+1)*h };
+
+#if X11_RENDER_DEBUG
+       XSetForeground(display, state->tile_gc, 0xff0000);
+       XFillRectangles(display, d, state->tile_gc, &r, 1);
+       XFlush(display);
+       usleep(70000);
+#endif
+
+       XClearArea(display, d, r.x, r.y, r.width, r.height, 0);
+}
+
+/*
+ * Efficiently clear all the border tiles in the game area.  The mask indicates
+ * which tiles need clearing, but for the border clear it is safe to wipe an
+ * entire row or column of the border using a single XClearArea.
+ *
+ * The idea is to pick whichever row or column has the most tiles to clear,
+ * clear them, and then repeat until none are left (repeats at most 4 times).
+ */
+static void clear_border(struct app_state *state, Display *display, Drawable d,
+                         Dimension w, Dimension h, uint_fast32_t mask)
+{
+       uint_fast32_t best_mask = 0;
+       int best_count = -1;
+       int i, best = -1;
+
+       if (!(mask &= ~GOAL_MASK & 0x1ffffff))
+               return;
+
+       for (i = 0; i < 4; i++) {
+               uint_fast32_t this_mask, tmp;
+               int this_count = 0;
+
+               if (i & 2)
+                       this_mask = board_column(4*(i & 1));
+               else
+                       this_mask = board_row(4*(i & 1));
+
+               /* Count set bits */
+               for (tmp = mask & this_mask; tmp; this_count++)
+                       tmp &= tmp - 1;
+
+               if (this_count > best_count) {
+                       best_count = this_count;
+                       best_mask = this_mask;
+                       best = i;
+               }
+       }
+
+       switch (best) {
+       case 0: clear_tiles(state, display, d, 0, 0, 4, 0, w, h); break;
+       case 1: clear_tiles(state, display, d, 0, 4, 4, 4, w, h); break;
+       case 2: clear_tiles(state, display, d, 0, 0, 0, 4, w, h); break;
+       case 3: clear_tiles(state, display, d, 4, 0, 4, 4, w, h); break;
+       }
+
+       clear_border(state, display, d, w, h, mask & ~best_mask);
+}
+
 static int
 redraw_tile(struct app_state *state, Display *display, Drawable d,
             uint_fast32_t bit0, uint_fast32_t bit1, uint_fast32_t bit2,
@@ -146,6 +214,16 @@ redraw_tile(struct app_state *state, Display *display, Drawable d,
        uint_fast32_t pos = board_position(x, y);
        unsigned char tile = 0;
 
+#if X11_RENDER_DEBUG
+       if (d == XtWindow(state->game) || d == XtWindow(state->goal)) {
+               XRectangle r = { x*w, y*h, w, h };
+               XSetForeground(display, state->tile_gc, 0xff0000);
+               XFillRectangles(display, d, state->tile_gc, &r, 1);
+               XFlush(display);
+               usleep(70000);
+       }
+#endif
+
        if (bit0 & pos) tile |= 1;
        if (bit1 & pos) tile |= 2;
        if (bit2 & pos) tile |= 4;
@@ -177,18 +255,13 @@ void x11_redraw_goal(struct app_state *state, uint_fast32_t mask)
        int i;
 
        XtVaGetValues(state->goal, XtNwidth, &w, XtNheight, &h, (char *)NULL);
+       w /= 3; h /= 3;
+
        for (i = 0; i < 9; i++) {
                int x = i%3, y = i/3;
 
                if (mask & 1) {
-#if X11_RENDER_DEBUG
-                       XRectangle r = { w/3*(i%3), w/3*(i/3), w/3, h/3 };
-                       XSetForeground(display, state->tile_gc, 0xff0000);
-                       XFillRectangles(display, goal, state->tile_gc, &r, 1);
-                       XFlush(display);
-                       usleep(70000);
-#endif
-                       redraw_goal_tile(state, display, goal, x, y, w/3, h/3);
+                       redraw_goal_tile(state, display, goal, x, y, w, h);
                }
 
                /*
@@ -273,15 +346,14 @@ void x11_redraw_game(struct app_state *state, uint_fast32_t mask)
                gp = buf;
        }
 
+       /* Optimize the game end case where the outer frame is cleared */
+       if (((gp[0] | gp[1] | gp[2]) & ~GOAL_MASK) == 0) {
+               clear_border(state, display, game, w, h, mask);
+               mask &= GOAL_MASK;
+       }
+
        for (i = 0; i < 25; i++) {
                if (mask & 1) {
-#if X11_RENDER_DEBUG
-                       XRectangle r = { w*(i%5), w*(i/5), w, h };
-                       XSetForeground(display, state->tile_gc, 0xff0000);
-                       XFillRectangles(display, game, state->tile_gc, &r, 1);
-                       XFlush(display);
-                       usleep(70000);
-#endif
                        redraw_tile(state, display, game,
                                    gp[0], gp[1], gp[2],
                                    i%5, i/5, w, h);