[g-a-devel][Fwd: GNOME magnifier changes]
- From: Brian Cameron <Brian Cameron Sun COM>
- To: gnome-accessibility-devel gnome org
- Subject: [g-a-devel][Fwd: GNOME magnifier changes]
- Date: Tue, 07 Oct 2003 16:06:32 -0500
Accessibility Developers:
I think I made some good progress improving the performance of
gnome-magnifier. I have added timing output information and also have
found a few modifications that seem to improve its performance. I'l
explain the changes in detail below:
Now "--timing-test" doesn't just iterate for 50 loops, it keeps looping
indefinately. When the "region of interest" hits an edge, then the magnifier
scrolling automatically changes direction, so it just bounces around the
screen forever. You can control the x/y scroll deltas with
--timing-scroll-delta-x and --timing-scroll-delta-y. They are 10 by
default.
I also added -timing-output which passes a bonobo property to the client,
and the client prints out timing information if this is set. I found this
useful for getting performance information (I decoupled it from --timing-test
so I could get performance output even in situations where I don't want to
run the timing test).
In zoom_region_queue_update, I now add the zoom_region_process_updates
idle handler with a priority of "GDK_PRIORITY_REDRAW - 1". This
ensures that the scaling is done before GTK updates the screen, which
(if my understanding is correct) will ensure that the update refreshes
the screen with the most current data, rather than from the data from
the last iteration of the loop. This seems, to my eyeball, to make the
magnifier look better.
The biggest change I made was because I noticed that zoom_region_scroll
calls either gdk_window_scroll or gdk_invalidate_rect (depending on
whether smooth or fast scrolling is used). Either function ends up
calling gdk_window_invalidate_maybe_recurse, which sets up an IDLE handler
of priority GDK_PRIORITY_REDRAW. The problem happens if zoom_region_scroll
gets called before this idle handler completes. This causes idle handlers
to start stacking up and the magnifier starts churning.
I corrected this by setting up an idle handler of priority
"GDK_PRIORITY_REDRAW + 1" in the zoom_region_scroll_function. This way I
can tell when GTK+ is finished updating the screen. In impl_zoom_region_set_roi
I skip the update if GTK+ hasn't finished updating from the last round.
This especially improves the performance if the rate specified on the
command line is lower than the computer can really handle.
This churning problem is especially nasty because the time it takes for
a frame to be displayed can vary widely. So, if the refresh rate is set
to 200 and the magnifier is processing each frame at 180, things are okay.
But if a single frame comes in that is longer than 200, it will cause the
magnifier to churn for several more frames, even if the subsequent frames
are all under 200. So with my change, it is possible to set the rate
more closely to the fastest rate that the computer can handle and the
occasional slow frame won't cause any churning.
The magnifier now gives the following sort of information for each frame:
Timing Information - window-dimensions=(160, 720) (360, 920)
Scroll Idle Start
Zoom Region = 0.000022 s (avg. 0.070062 s)
.
Zoom Region = 0.134355 s (avg. 0.070651 s)
.
Scroll Idle = 0.146202 s (avg. 0.174565 s) (max 0.822918 s)
The above information indicates that the scroll rate was 146ms for this
frame, 175ms on average, with a maximum of 822ms per frame. So a good
rate for this magnification level would probably be about 135. Note that
without my above "churning patch" you would have to set it above 174 for
it to not churn.
If you like the work, let me know and I will check it into CVS head.
Or if you want me to work on it some more, I can do that too.
--
Brian
Index: magnifier-main.c
===================================================================
RCS file: /cvs/GNOME-RE/gnome-mag/magnifier/magnifier-main.c,v
retrieving revision 1.19
diff -u -p -r1.19 magnifier-main.c
--- magnifier-main.c 2003/04/03 13:22:17 1.19
+++ magnifier-main.c 2003/09/30 21:53:31
@@ -23,6 +23,7 @@
#include <string.h>
#include <stdlib.h>
#include <popt.h>
+#include <sys/time.h>
#include <gdk/gdkwindow.h>
#include <gdk/gdkx.h>
#include <gtk/gtk.h>
@@ -54,6 +55,9 @@ typedef struct {
int invert_image;
int no_initial_region;
int timing_test;
+ int timing_output;
+ int timing_scroll_delta_x;
+ int timing_scroll_delta_y;
int smooth_scroll;
int border_width;
unsigned long border_color;
@@ -80,8 +84,11 @@ static MagnifierOptions global_options =
0,
0,
0,
+ 10,
+ 10,
0,
0,
+ 0,
0
};
@@ -101,6 +108,9 @@ struct poptOption magnifier_options [] =
{"invert-image", 'i', POPT_ARG_NONE, &global_options.invert_image, 'i', "invert the image colormap", NULL},
{"no-initial-region", '\0', POPT_ARG_NONE, &global_options.no_initial_region, '\0', "don't create an initial zoom region", NULL},
{"timing-test", '\0', POPT_ARG_NONE, &global_options.timing_test, '\0', "carry out timing benchmark test", NULL},
+ {"timing-output", '\0', POPT_ARG_NONE, &global_options.timing_output, '\0', "carry out timing benchmark test", NULL},
+ {"timing-scroll-delta-x", '\0', POPT_ARG_INT, &global_options.timing_scroll_delta_x, '\0', "carry out timing benchmark test", NULL},
+ {"timing-scroll-delta-y", '\0', POPT_ARG_INT, &global_options.timing_scroll_delta_y, '\0', "carry out timing benchmark test", NULL},
{"smoothing-type", '\0', POPT_ARG_STRING, &global_options.smoothing_type, '\0', "image smoothing algorithm to apply (bilinear-interpolation | none)", NULL},
{"fullscreen", 'f', POPT_ARG_NONE, &global_options.fullscreen, '\0', "fullscreen magnification, covers entire target display [REQUIRES --source-display and --target-display]", NULL},
{"smooth-scrolling", '\0', POPT_ARG_NONE, &global_options.smooth_scroll, '\0', "use smooth scrolling", NULL},
@@ -121,39 +131,71 @@ init_rect_bounds (GNOME_Magnifier_RectBo
bounds->y2 = y2;
}
+static int screen_width, screen_height;
+
static int
magnifier_main_test_image (gpointer data)
{
- static int test_i_foo = 0;
+ static int timing_x_pos = 0;
+ static int timing_y_pos = 0;
+ static int x_direction = 1;
+ static int y_direction = 1;
+ float since_last_timing, timing_val;
Magnifier *magnifier = (Magnifier *) data;
GNOME_Magnifier_ZoomRegionList *zoom_regions;
CORBA_Environment ev;
GNOME_Magnifier_RectBounds roi;
+
CORBA_exception_init (&ev);
+ int x_roi, y_roi;
+
+ x_roi = global_options.timing_scroll_delta_x * timing_x_pos;
+ y_roi = global_options.timing_scroll_delta_y * timing_y_pos;
+ roi.x1 = x_roi;
+ roi.x2 = 200 + roi.x1;
+
+ if (global_options.horizontal_split)
+ roi.y1 = y_roi + screen_height;
+ else
+ roi.y1 = y_roi;
+ roi.y2 = 200 + roi.y1;
+
+ fprintf(stderr, "\nTiming Information - window-dimensions=(%d, %d) (%d, %d)\n", roi.x1, roi.y1, roi.x2, roi.y2);
+
+ x_roi = global_options.timing_scroll_delta_x * (timing_x_pos + x_direction);
+ y_roi = global_options.timing_scroll_delta_y * (timing_y_pos + y_direction);
+
+ if (x_roi + 200 > screen_width)
+ x_direction = -1;
+ else if (x_roi < 0)
+ x_direction = 1;
+
+ if (y_roi + 200 > screen_height)
+ y_direction = -1;
+ else if (y_roi < 0)
+ y_direction = 1;
- roi.x1 = 10 * test_i_foo;
- roi.y1 = 10 * test_i_foo;
- roi.x2 = 200 + 10 * test_i_foo;
- roi.y2 = 200 + 10 * test_i_foo;
- ++test_i_foo;
+ timing_x_pos += x_direction;
+ timing_y_pos += y_direction;
if (!IS_MAGNIFIER (magnifier))
return FALSE;
+
magnifier->priv->cursor_x = (roi.x2 + roi.x1) / 2;
magnifier->priv->cursor_y = (roi.y2 + roi.y1) / 2;
+
zoom_regions =
GNOME_Magnifier_Magnifier_getZoomRegions (
BONOBO_OBJREF (magnifier),
&ev);
- if (zoom_regions && (zoom_regions->_length > 0))
+ if (zoom_regions && (zoom_regions->_length > 0)) {
+
GNOME_Magnifier_ZoomRegion_setROI (
zoom_regions->_buffer[0], &roi, &ev);
-
- if (test_i_foo < 50)
- return TRUE;
+ }
- return FALSE;
+ return TRUE;
}
static int last_x = 0, last_y = 0;
@@ -238,6 +280,7 @@ magnifier_main_refresh_all (gpointer dat
regions = GNOME_Magnifier_Magnifier_getZoomRegions (
BONOBO_OBJREF (magnifier),
&ev);
+
#ifdef DEBUG
fprintf (stderr, "refreshing %d regions\n", regions->_length);
#endif
@@ -272,7 +315,7 @@ main (int argc, char** argv)
GNOME_Magnifier_RectBounds *roi = GNOME_Magnifier_RectBounds__alloc();
GNOME_Magnifier_RectBounds *viewport = GNOME_Magnifier_RectBounds__alloc();
CORBA_any *viewport_any;
- int w, h, x = 0, y = 0;
+ int x = 0, y = 0;
guint pan_handle = 0, refresh_handle = 0;
CORBA_Environment ev;
Bonobo_PropertyBag properties;
@@ -280,9 +323,9 @@ main (int argc, char** argv)
Magnifier *magnifier;
if (!bonobo_init (&argc, argv))
- {
- g_error ("Could not initialize Bonobo");
- }
+ {
+ g_error ("Could not initialize Bonobo");
+ }
CORBA_exception_init (&ev);
ctx = poptGetContext ("magnifier",
@@ -339,26 +382,27 @@ main (int argc, char** argv)
bonobo_pbclient_set_ulong (properties, "cursor-color",
global_options.cursor_color,
NULL);
- w = gdk_screen_get_width (
+
+ screen_width = gdk_screen_get_width (
gdk_display_get_screen (magnifier->target_display,
magnifier->target_screen_num));
- h = gdk_screen_get_height (
+ screen_height = gdk_screen_get_height (
gdk_display_get_screen (magnifier->target_display,
magnifier->target_screen_num));
if (global_options.vertical_split) {
- w /= 2;
- x = w;
+ screen_width /= 2;
+ x = screen_width;
}
if (global_options.horizontal_split) {
- h /= 2;
+ screen_height /= 2;
}
- fprintf (stderr, "initial viewport %d %d\n", (int) w,
- (int) h);
+ fprintf (stderr, "initial viewport %d %d\n", (int) screen_width,
+ (int) screen_height);
viewport_any = CORBA_any__alloc ();
- init_rect_bounds (viewport, x, y, x + w, y + h);
+ init_rect_bounds (viewport, x, y, x + screen_width, y + screen_height);
viewport_any->_type = TC_GNOME_Magnifier_RectBounds;
viewport_any->_value = ORBit_copy_value (viewport,
TC_GNOME_Magnifier_RectBounds);
@@ -379,7 +423,7 @@ main (int argc, char** argv)
fprintf (stderr, "creating an initial zoom region.\n");
init_rect_bounds (roi, 0, 0, 100, 100);
- init_rect_bounds (viewport, 0, 0, w, h);
+ init_rect_bounds (viewport, 0, 0, screen_width, screen_height);
zoom_region =
GNOME_Magnifier_Magnifier_createZoomRegion (
BONOBO_OBJREF (magnifier),
@@ -396,6 +440,8 @@ main (int argc, char** argv)
if (BONOBO_EX (&ev))
fprintf (stderr, "EXCEPTION\n");
+ bonobo_pbclient_set_boolean (properties, "timing-output",
+ global_options.timing_output, &ev);
bonobo_pbclient_set_boolean (properties, "smooth-scroll-policy",
scroll_policy, &ev);
bonobo_pbclient_set_long (properties, "border_size",
@@ -430,10 +476,11 @@ main (int argc, char** argv)
pan_handle = gtk_timeout_add (
global_options.mouse_poll_time,
magnifier_main_pan_image, magnifier);
- } else
+ } else {
refresh_handle = gtk_timeout_add (
global_options.refresh_time,
magnifier_main_test_image, magnifier);
+ }
bonobo_main ();
Index: zoom-region.c
===================================================================
RCS file: /cvs/GNOME-RE/gnome-mag/magnifier/zoom-region.c,v
retrieving revision 1.19
diff -u -p -r1.19 zoom-region.c
--- zoom-region.c 2003/06/26 13:47:35 1.19
+++ zoom-region.c 2003/09/30 21:53:32
@@ -51,7 +51,8 @@ enum {
ZOOM_REGION_XALIGN_PROP,
ZOOM_REGION_YALIGN_PROP,
ZOOM_REGION_VIEWPORT_PROP,
- ZOOM_REGION_TESTPATTERN_PROP
+ ZOOM_REGION_TESTPATTERN_PROP,
+ ZOOM_REGION_TIMING_OUTPUT_PROP
} PropIdx;
typedef enum {
@@ -60,6 +61,25 @@ typedef enum {
ZOOM_REGION_ERROR_TOO_BIG
} ZoomRegionPixmapCreationError;
+typedef struct timing {
+ struct timeval scale_start;
+ struct timeval scale_end;
+ struct timeval idle_start;
+ struct timeval idle_end;
+ float scale_val;
+ float pointer_val;
+ float idle_val;
+ float scale_total;
+ float idle_total;
+ long num_scale_samples;
+ long num_idle_samples;
+} TimingStruct;
+
+float timing_idle_max = 0;
+
+static TimingStruct mag_timing;
+static gboolean processing_updates = FALSE;
+
#ifdef TEST_XTST_CURSOR
static Cursor *x_cursors;
static Window cursor_window = None;
@@ -570,7 +590,14 @@ zoom_region_queue_update (ZoomRegion *zo
DEBUG_RECT ("queueing update", *rect);
zoom_region->priv->q =
g_list_prepend (zoom_region->priv->q, rect);
- g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
+
+ /*
+ * Give the zoom_region_process_updates idle handler
+ * a priority of GDK_PRIORITY_REDRAW - 1 to ensure that
+ * the scales are done before the GTK functions update
+ * the screen.
+ */
+ g_idle_add_full (GDK_PRIORITY_REDRAW - 1,
zoom_region_process_updates,
zoom_region,
NULL);
@@ -883,6 +910,7 @@ zoom_region_paint (ZoomRegion *zoom_regi
GdkRectangle *area)
{
GdkRectangle paint_area;
+
DEBUG_RECT ("painting", *area);
DEBUG_RECT ("painting (clipped)", *area);
zoom_region_paint_border (zoom_region, area);
@@ -1048,15 +1076,67 @@ zoom_region_scroll_smooth (ZoomRegion *z
gdk_window_end_paint (window);
}
+static gboolean
+gdk_timing_idle (gpointer data)
+{
+ ZoomRegion *zoom_region = data;
+
+ processing_updates = FALSE;
+
+ gettimeofday(&mag_timing.idle_end, NULL);
+
+ if (zoom_region->timing_output) {
+ mag_timing.num_idle_samples++;
+ mag_timing.idle_val =
+ (mag_timing.idle_end.tv_sec - mag_timing.idle_start.tv_sec) +
+ ((float)(mag_timing.idle_end.tv_usec - mag_timing.idle_start.tv_usec) /
+ 1000000.0);
+
+ mag_timing.idle_total += mag_timing.idle_val;
+
+ if (mag_timing.idle_val > timing_idle_max)
+ timing_idle_max = mag_timing.idle_val;
+
+ fprintf(stderr, " Scroll Idle = %f s (avg. %f s) (max %f s)\n",
+ mag_timing.idle_val, (mag_timing.idle_total /
+ mag_timing.num_idle_samples), timing_idle_max);
+ }
+
+ return FALSE;
+}
+
static void
zoom_region_scroll (ZoomRegion *zoom_region, int dx, int dy)
{
GdkRectangle scroll_rect, expose_rect_h, expose_rect_v;
gboolean can_scroll;
+
+ /*
+ * The zoom_region_scroll_fast and zoom_region_scroll_smooth cause an
+ * idle handler of priority GDK_PRIORITY_REDRAW to be set which handles
+ * the update of the screen. This is because gdk_window_scroll and
+ * gdk_window_invalidate_rect cause gdk_window_invalidate_region to be
+ * called, which calls gdk_window_invalidate_maybe_recurse to be called
+ * which does the work of setting up this idle handler.
+ *
+ * By setting up an idle handler of priority GDK_PRIORITY_REDRAW + 1,
+ * it can be determined when GTK+ is finished with the update.
+ */
+ if (zoom_region->timing_output) {
+ fprintf(stderr, " Scroll Idle Start\n");
+ }
+
+ gettimeofday(&mag_timing.idle_start, NULL);
+
+ processing_updates = TRUE;
+ g_idle_add_full (GDK_PRIORITY_REDRAW + 1,
+ gdk_timing_idle, zoom_region, NULL);
+
can_scroll = zoom_region_calculate_scroll_rects (zoom_region, dx, dy,
&scroll_rect,
&expose_rect_h,
&expose_rect_v);
+
if (zoom_region->smooth_scroll_policy > GNOME_Magnifier_ZoomRegion_SCROLL_FAST)
{
zoom_region_scroll_smooth (zoom_region,
@@ -1137,9 +1217,9 @@ zoom_region_update_pointer (ZoomRegion *
gint mouse_x_return, mouse_y_return;
guint mask_return;
- if (!zoom_region->priv)
+ if (!zoom_region->priv || !zoom_region->priv->parent)
return FALSE;
- if (!zoom_region->priv->parent) return FALSE;
+
magnifier = zoom_region->priv->parent;
/* TODO: there's really no reason we should be using magnifier->priv->root here */
if (magnifier && magnifier->priv && magnifier_get_root (magnifier))
@@ -1226,6 +1306,7 @@ zoom_region_update_pointer (ZoomRegion *
return TRUE;
}
}
+
return FALSE;
}
@@ -1427,9 +1508,13 @@ zoom_region_update (ZoomRegion *zoom_reg
GdkPixbuf *subimage;
GdkGC *gc;
GdkRectangle source_rect;
+
+ gettimeofday(&mag_timing.scale_start, NULL);
+
DEBUG_RECT ("unclipped update rect", update_rect);
source_rect = zoom_region_clip_to_exposed_target (zoom_region, update_rect);
DEBUG_RECT ("update rect clipped to exposed target", source_rect);
+
subimage = zoom_region_get_source_subwindow (zoom_region, source_rect);
if (subimage && zoom_region->priv->w && zoom_region->priv->w->window)
@@ -1455,6 +1540,7 @@ zoom_region_update (ZoomRegion *zoom_reg
zoom_region_post_process_pixbuf (zoom_region, subimage,
zoom_region->priv->scaled_pixbuf);
+
gc = gdk_gc_new (zoom_region->priv->w->window);
gdk_pixbuf_render_to_drawable (zoom_region->priv->scaled_pixbuf,
@@ -1469,6 +1555,7 @@ zoom_region_update (ZoomRegion *zoom_reg
GDK_RGB_DITHER_NONE,
0,
0);
+
if (magnifier_error_check ())
g_warning ("Could not render scaled image to drawable; out of memory!\n");
g_object_unref (gc);
@@ -1476,10 +1563,27 @@ zoom_region_update (ZoomRegion *zoom_reg
gdk_window_begin_paint_rect (zoom_region->priv->w->window, &paint_rect);
zoom_region_paint (zoom_region, &paint_rect);
gdk_window_end_paint (zoom_region->priv->w->window);
+
} else {
if (subimage)
fprintf (stderr, "update on uninitialized zoom region!\n");
}
+
+ gettimeofday(&mag_timing.scale_end, NULL);
+
+ if (zoom_region->timing_output) {
+
+ mag_timing.num_scale_samples++;
+ mag_timing.scale_val =
+ (mag_timing.scale_end.tv_sec - mag_timing.scale_start.tv_sec) +
+ ((float)(mag_timing.scale_end.tv_usec - mag_timing.scale_start.tv_usec) /
+ 1000000.0);
+ mag_timing.scale_total += mag_timing.scale_val;
+
+ fprintf(stderr, " Zoom Region = %f s (avg. %f s)\n",
+ mag_timing.scale_val, (mag_timing.scale_total /
+ mag_timing.num_scale_samples));
+ }
}
static void
@@ -1524,6 +1628,7 @@ zoom_region_process_updates (gpointer da
{
ZoomRegion *zoom_region = (ZoomRegion *) data;
/* TODO: lock the queue when copying it? */
+
zoom_region_coalesce_updates (zoom_region);
if (zoom_region->priv->q != NULL) {
GList *last = g_list_last (zoom_region->priv->q);
@@ -1539,7 +1644,7 @@ zoom_region_process_updates (gpointer da
return TRUE;
}
else
- return FALSE;
+ return FALSE;
}
static void
@@ -1669,6 +1774,9 @@ zoom_region_get_property (BonoboProperty
GNOME_Magnifier_RectBounds,
NULL);
break;
+ case ZOOM_REGION_TIMING_OUTPUT_PROP:
+ BONOBO_ARG_SET_BOOLEAN (arg, zoom_region->timing_output);
+ break;
default:
bonobo_exception_set (ev, ex_Bonobo_PropertyBag_NotFound);
};
@@ -1755,6 +1863,13 @@ zoom_region_set_property (BonoboProperty
NULL);
zoom_region_set_viewport (zoom_region, &bounds);
break;
+ case ZOOM_REGION_TIMING_OUTPUT_PROP:
+ zoom_region->timing_output = BONOBO_ARG_GET_BOOLEAN (arg);
+ if (zoom_region->timing_output)
+ fprintf (stderr, "Setting timing test property to true\n");
+ else
+ fprintf (stderr, "Setting timing test property to false\n");
+ break;
default:
bonobo_exception_set (ev, ex_Bonobo_PropertyBag_NotFound);
};
@@ -1767,6 +1882,18 @@ impl_zoom_region_set_roi (PortableServer
{
ZoomRegion *zoom_region =
ZOOM_REGION (bonobo_object_from_servant (servant));
+
+ /*
+ * Do not bother trying to update the screen if the last
+ * screen update has not had time to complete.
+ */
+ if (processing_updates) {
+ if (zoom_region->timing_output) {
+ fprintf(stderr, "Last update has not finished, skipping update.\n");
+ }
+ return;
+ }
+
zoom_region->roi = *bounds;
zoom_region_align (zoom_region);
}
@@ -1938,6 +2065,11 @@ zoom_region_class_init (ZoomRegionClass
epv->getROI = impl_zoom_region_get_roi;
epv->moveResize = impl_zoom_region_move_resize;
epv->dispose = impl_zoom_region_dispose;
+
+ mag_timing.num_scale_samples = 0;
+ mag_timing.num_idle_samples = 0;
+ mag_timing.scale_total = 0;
+ mag_timing.idle_total = 0;
}
static void
@@ -2116,6 +2248,19 @@ zoom_region_properties_init (ZoomRegion
Bonobo_PROPERTY_READABLE |
Bonobo_PROPERTY_WRITEABLE);
+ def = bonobo_arg_new (BONOBO_ARG_BOOLEAN);
+ BONOBO_ARG_SET_BOOLEAN (def, FALSE);
+
+ bonobo_property_bag_add (zoom_region->properties,
+ "timing-output",
+ ZOOM_REGION_TIMING_OUTPUT_PROP,
+ BONOBO_ARG_BOOLEAN,
+ def,
+ "timing output",
+ Bonobo_PROPERTY_READABLE |
+ Bonobo_PROPERTY_WRITEABLE);
+
+ bonobo_arg_release (def);
}
static void
@@ -2159,6 +2304,7 @@ zoom_region_init (ZoomRegion *zoom_regio
BONOBO_OBJECT (zoom_region->properties));
zoom_region->priv = g_malloc (sizeof (ZoomRegionPrivate));
zoom_region_private_init (zoom_region->priv);
+ zoom_region->timing_output = FALSE;
g_timeout_add_full (G_PRIORITY_DEFAULT_IDLE,
200,
zoom_region_update_pointer_timeout,
Index: zoom-region.h
===================================================================
RCS file: /cvs/GNOME-RE/gnome-mag/magnifier/zoom-region.h,v
retrieving revision 1.3
diff -u -p -r1.3 zoom-region.h
--- zoom-region.h 2002/10/14 19:06:34 1.3
+++ zoom-region.h 2003/09/30 21:53:32
@@ -60,6 +60,7 @@ typedef struct {
GNOME_Magnifier_RectBounds viewport;
ZoomRegionPrivate *priv;
CoalesceFunc coalesce_func;
+ gboolean timing_output;
} ZoomRegion;
typedef struct {
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]