/usr/src/castle-game-engine-4.1.1/window/gtk/why_not_using_gtk_idle.txt is in castle-game-engine-src 4.1.1-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 | This documents some history about GTK CastleWindow backend.
My initial approach was to use GTK/glib's mechanism for stuff like
PostRedisplay and Update, more precisely:
- PostRedisplay was doing
if not Closed then gtk_widget_queue_draw(GTK_WIDGET(GLAreaGtk));
- FlushRedisplay was doing
gdk_window_process_updates(GTK_WIDGET(WindowGtk)^.Window, g_true);
- signal_expose_event was calling DoDraw.
- GtkIdle (now called Application.ProcessUpdates)
was registered as GTK's idle function,
by gtk_idle_add_priority. GtkIdle was responsible for doing our
idle / timer events (Application.OnIdle/Timer, TCastleWindow.OnIdle/Timer).
At the beginning, it was registered with priority
GTK_PRIORITY_REDRAW, then G_PRIORITY_HIGH_IDLE.
But, multiple reasons later, after many code rearrangements,
I dropped these ideas. Now I just implement redrawing and handling idle
by my own methods, called outside of GTK loop processing functions.
Some reasoning:
------------------------------------------------------------------------------
About idle priority:
We would like to set it to GTK_PRIORITY_REDRAW (this is
G_PRIORITY_HIGH_IDLE + 20, smaller than G_PRIORITY_DEFAULT_IDLE,
which means it's more important). That's because our idle events
(Application.OnIdle, TCastleWindow.OnIdle etc.)
must be constantly called, even when AutoRedisplay = true
(i.e. when we always have some redraw pending), and with
~approximately the same frequency as draw events.
The default priority of gtk_idle_add, which is G_PRIORITY_DEFAULT_IDLE,
would make our idle events (Application.OnIdle etc.) never called
when AutoRedisplay = true.
Unfortunately, with GTK 2 (GTK 1 didn't have this problem),
GTK_PRIORITY_REDRAW causes a problem: after switching to alternative menu,
key shortcuts to menu items do not work.
I don't know what's the exact reason of this, I guess that I'm just
blocking some other internal gtk idles and so I'm causing some
mysterious problems.
Simple test case with some comments is in
../../../examples/window/window_menu.lpr
(and also gtk/tests/test_changing_menu/test_changing_menu.lpr
on michalis.ii SVN, temporarily private (too obscure...)).
G_PRIORITY_DEFAULT_IDLE doesn't have this problem, but it's leaving
us with the first problem: Application.OnIdle will not be called
continously if we redraw continously (like when AutoRedisplay = true).
- Old solution: call WindowsIdleAndTimer manually
after calling DoDraw in signal_expose_event.
- Later solution: we just do not depend on GTK's expose events
and gtk_widget_queue_draw for the job. We track RedisplayPosted
ourselves, and realize it from our GtkIdle.
------------------------------------------------------------------------------
Sometimes we had to temporarily uninstall GtkIdle from GTK.
Reasoning in TGLApplication.ProcessAllMessages:
GtkIdleInstalled must be temporarily turned off.
That's because when GtkIdleInstalled = true gtk_events_pending()
always returns true (because it thinks that it should call GtkIdle
in a loop).
Reasoning in TCastleWindow.FileDialog when using GtkFileChooser:
Temporarily set GtkIdleInstalled to false.
Reason? Just like for ProcessAllMessages: when
GtkIdleInstalled = true then gtk_events_pending()
always returns true (because it thinks that it should call GtkIdle
in a loop).
In this case, gtk_events_pending may be called inside gtk_dialog_run.
This seems to be the case for GTK starting from 2.14: on Ubuntu 8.10
open dialog hangs strangely (you cannot see the entries in open dialog,
and clicking on "file system" hangs etc.) without this
GtkIdleInstalled := false fix.
This was done by this code:
GtkIdleHandle: guint;
FGtkIdleInstalled: boolean;
procedure SetGtkIdleInstalled(value: boolean);
{ This controls whether GtkIdle is installed (i.e. registered to gtk).
It is initialized to true in CreateImplDepend, finalized to false
in DestroyImplDepend and for the most part stays as true.
BUT it must be temporarily disabled in ProcessAllMesages. }
property GtkIdleInstalled: boolean
read FGtkIdleInstalled write SetGtkIdleInstalled;
procedure TGLApplication.SetGtkIdleInstalled(value: boolean);
begin
if FGtkIdleInstalled <> value then
begin
FGtkIdleInstalled := value;
if FGtkIdleInstalled then
begin
GtkIdleHandle := gtk_idle_add_priority(G_PRIORITY_DEFAULT_IDLE,
@GtkIdle, nil);
end else
begin
gtk_idle_remove(GtkIdleHandle);
end;
end;
end;
------------------------------------------------------------------------------
About using signal_expose_event:
Looks like we cannot call here DoDraw, or any other callback.
Reason: with newer GTK / glib (Debian gtk 2.18.3-1 and glib 2.22.3-1)
signal "expose" is never delivered when another "expose" signal
already works.
(More info about this GTK change:
I didn't found exact evidence for this,
I suspect it's related to
http://library.gnome.org/devel/gtk/2.18/gtk-migrating-ClientSideWindows.html,
there's a text
"One change that can cause problems for some applications is that
GDK is more aggressive about optimizing away expose events.
Code that does more than just repainting exposed areas in
response to expose events may be affected by this.
"
If interested in digging more, probably looking at usage of
G_SIGNAL_NO_RECURSE (for GSignalFlags) in newer GTK src could
get something.
)
Which means that any event that could cause recursive
event loop will not work correctly, i.e. will not get expose events
from GTK. For example, calling GLWinMessages.MessageOk from OnDraw
will fail badly --- during modal message window will not receive
expose events when window is moved, resized etc.
So just PostRedisplay. Nearest GtkIdle will handle redraw.
This also means that we do whole PostRedisplay tracking ourselves,
by RedisplayPosted boolean. No point in using gtk_widget_queue_draw /
gdk_window_process_updates if they don't behave like we expect.
------------------------------------------------------------------------------
Eventually, the final problem that broke the straw: it seems
that expose signal cannot be generated when we're inside GTK's registered
idle event.
This concerns even the expose events that should be passed
to us from obscuring + showing the window again. Strangely, expose
after resizing the window still gets generated. But generally they don't work...
This means that making MessageOk from OnDraw, or OnIdle, still doesn't
work as expected...
Bottom line: it's not reliable to call any TCastleWindow events (that could
always call MessageOk and enter loop that will require more expose events
to run properly) from GTK's signal expose *or* GTK's registered idle.
------------------------------------------------------------------------------
Also, using GTK's signal expose was forcing us to use elaborate
AutoRedisplayAddToList hack. Reasoning and code below:
DoDraw took care of AutoRedisplay:
if AutoRedisplay then
begin
if Application.AutoRedisplayAddToList > 0 then
make sure that Application.AutoRedisplayList contains Self
(i.e. add Self to Application.AutoRedisplayList, unless
Application.AutoRedisplayList already contains Self)
else
PostRedisplay;
So specific CastleWindow implementations need not to worry about
AutoRedisplay. They only have to implement PostRedisplay.
Also, some implementations (currently this concerns gtk
implementations) should disallow doing PostRedisplay caused
by AutoRedisplay = true when they are inside ProcessAllMessages
(since it can, in some situations, as with GTK bindings,
mean that ProcessAllMessages would hang forever, since
there would be always pending message to redisplay the window).
Such implementations want to do
if AutoRedisplayAddToList = 0 then AutoRedisplayList.Clear;
Inc(AutoRedisplayAddToList);
at the beginning and then
Dec(AutoRedisplayAddToList);
if AutoRedisplayAddToList = 0 then AutoRedisplayList.PostRedisplay;
at the end, see castlewindow_gtk.inc ProcessAllMessages implementation
for example (with proper try...finally clause around).
Also such backends should do
AutoRedisplayList := TCastleWindowsList.Create;
and
FreeAndNil(AutoRedisplayList);
at appropriate places (e.g. in
TGLApplication.Create/DestroyImplDependent)
and
Application.AutoRedisplayList.Delete(Self);
in TCastleWindow.CloseImplDepend (to make sure that destroyed windows
are not a memebers of AutoRedisplayList).
Note that AutoRedisplayAddToList is an integer (not a simple boolean)
to be safe in case someone calls ProcessAllMessages recursively
(e.g. call ProcessAllMessages that calls OnMouseDown that calls
ProcessAllMessages inside). }
|