2 * This program is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU General Public License
4 * as published by the Free Software Foundation; either version 2
5 * of the License, or (at your option) any later version.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
16 * See the COPYING file for license information.
18 * Guillaume Chazarain <guichaz@yahoo.fr>
21 /***********************
22 * Rendering functions *
23 ***********************/
25 #include <stdlib.h> /* abs() */
28 #include "rendering.h"
30 #include "gliv-image.h"
33 #include "zoom_frame.h"
34 #include "scrollbars.h"
36 #include "files_list.h"
40 #include "next_image.h"
42 #include "transition.h"
43 #include "images_menus.h"
46 extern options_struct
*options
;
47 extern GlivImage
*current_image
;
48 extern GtkWidget
*gl_widget
;
50 static GTimeVal last_redraw
;
53 static void set_filter(gint filter
)
59 * Only the textures in the first map change their filter,
60 * since in the others maps the image is zoomed out.
62 map
= current_image
->maps
;
64 for (id
= 0; id
< map
->nb_tiles
; id
++) {
65 glBindTexture(GL_TEXTURE_2D
, map
->tex_ids
[id
]);
66 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_MIN_FILTER
, filter
);
67 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_MAG_FILTER
, filter
);
71 static gboolean
is_filtering_enabled(void)
76 /* Only the first map changes its filter. */
77 map
= current_image
->maps
;
79 glBindTexture(GL_TEXTURE_2D
, map
->tex_ids
[0]);
80 glGetTexParameteriv(GL_TEXTURE_2D
, GL_TEXTURE_MAG_FILTER
, &filter
);
82 return filter
== GL_LINEAR
;
85 static void call_lists(gboolean smooth
, gint level
)
90 if (level
== 0 && is_filtering_enabled() != smooth
)
91 set_filter(smooth
? GL_LINEAR
: GL_NEAREST
);
93 map
= current_image
->maps
+ level
;
95 for (id
= 0; id
< map
->nb_tiles
; id
++)
96 if (matrix_tile_visible(map
->tiles
+ id
))
97 glCallList(map
->list
+ id
);
100 static void draw_checker(void)
102 static guint tex_id
= 0;
104 gfloat half_w
, half_h
;
105 gboolean matrix_changed
;
107 glDisable(GL_DITHER
);
108 glClear(GL_COLOR_BUFFER_BIT
);
109 matrix_changed
= get_matrix_has_changed();
111 if (rt
->alpha_checks_changed
) {
113 gushort alpha1
[3] = {
115 options
->alpha1
.green
,
118 gushort alpha2
[3] = {
120 options
->alpha2
.green
,
125 glGenTextures(1, &tex_id
);
127 for (i
= 0; i
< 3; i
++) {
128 texture
[i
] = texture
[9 + i
] = alpha1
[i
];
129 texture
[3 + i
] = texture
[6 + i
] = alpha2
[i
];
132 glBindTexture(GL_TEXTURE_2D
, tex_id
);
134 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_MIN_FILTER
, GL_NEAREST
);
135 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_MAG_FILTER
, GL_NEAREST
);
137 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_WRAP_S
, GL_REPEAT
);
138 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_WRAP_T
, GL_REPEAT
);
140 glTexImage2D(GL_TEXTURE_2D
, 0, GL_RGB
, 2, 2, 0, GL_RGB
,
141 GL_UNSIGNED_SHORT
, texture
);
143 rt
->alpha_checks_changed
= FALSE
;
145 glBindTexture(GL_TEXTURE_2D
, tex_id
);
147 if (matrix_changed
== FALSE
)
148 /* glMatrixMode(GL_MODELVIEW); */
149 /* Save the matrix only if we will not replace it when redrawing. */
154 half_w
= rt
->wid_size
->width
/ 2.0;
155 half_h
= rt
->wid_size
->height
/ 2.0;
159 glTexCoord2f(0.0, 0.0);
160 glVertex2f(-half_w
, -half_h
);
162 glTexCoord2f(half_w
/ 16.0, 0.0);
163 glVertex2f(half_w
, -half_h
);
165 glTexCoord2f(half_w
/ 16.0, half_h
/ 16.0);
166 glVertex2f(half_w
, half_h
);
168 glTexCoord2f(0.0, half_h
/ 16.0);
169 glVertex2f(-half_w
, half_h
);
173 if (matrix_changed
== FALSE
)
174 /* glMatrixMode(GL_MODELVIEW); */
180 static gint
choose_mipmap_level(void)
182 gfloat zoom
, mipmap_ratio
= MIPMAP_RATIO
;
185 if (options
->mipmap
) {
186 zoom
= get_matrix_zoom();
188 while (mipmap_ratio
> zoom
&& level
< current_image
->nb_maps
) {
189 mipmap_ratio
*= MIPMAP_RATIO
;
194 /* Mipmaps should only be scaled down. */
198 static void display_last_image_notice(void)
200 static GdkGC
*gc
= NULL
;
204 gint x
= 10, y
= 10, dx
= 5, dy
= 5;
207 msg
= get_image_notice();
211 pc
= gtk_widget_get_pango_context(gl_widget
);
212 pl
= pango_layout_new(pc
);
216 gc
= gdk_gc_new(gl_widget
->window
);
218 pango_layout_set_text(pl
, msg
, -1);
220 gdk_gc_set_foreground(gc
, &(gl_widget
->style
->white
));
221 pango_layout_get_pixel_size(pl
, &w
, &h
);
222 gdk_draw_rectangle(gl_widget
->window
, gc
, TRUE
,
223 x
- dx
, y
- dy
, w
+ 2 * dx
, h
+ 2 * dy
);
225 gdk_gc_set_foreground(gc
, &(gl_widget
->style
->black
));
226 gdk_draw_layout(gl_widget
->window
, gc
, x
, y
, pl
);
231 static gboolean
need_alpha_checks(void)
233 return current_image
!= NULL
&& current_image
->has_alpha
&&
234 options
->alpha_checks
;
237 void draw_current_image(void)
241 gboolean alpha_checks
= need_alpha_checks();
247 * I don't know why but this seems to be required to see images instead
248 * black rectangles with Mesa's software rendering.
257 mipmap_level
= choose_mipmap_level();
258 if (options
->filtering
== FALSE
)
260 else if (mipmap_level
== 0)
261 filtering
= is_filtering_needed();
266 glPushAttrib(GL_ENABLE_BIT
);
270 call_lists(filtering
, mipmap_level
);
276 /* Called by the timer when a redraw is needed. */
277 static void redraw(void)
279 static GdkGLDrawable
*gldrawable
= NULL
;
282 if (is_in_transition())
285 if (gldrawable
== NULL
)
287 gldrawable
= gtk_widget_get_gl_drawable(gl_widget
);
289 gdk_gl_drawable_wait_gdk(gldrawable
);
292 if (need_alpha_checks() == FALSE
) {
293 glDisable(GL_DITHER
);
294 glClear(GL_COLOR_BUFFER_BIT
);
298 if (current_image
!= NULL
)
299 draw_current_image();
301 gdk_gl_drawable_swap_buffers(gldrawable
);
302 gdk_gl_drawable_wait_gl(gldrawable
);
304 display_last_image_notice();
305 refresh(REFRESH_SCROLL
);
307 frame_advance
= get_fps_limiter_delay(&last_redraw
);
308 if (frame_advance
&& abs(frame_advance
) < G_USEC_PER_SEC
/ options
->fps
)
309 advance
+= frame_advance
;
312 g_get_current_time(&last_redraw
);
315 /* Called the first time an image is displayed. */
318 gboolean list_changed
;
320 configure_matrix(current_image
);
321 prioritize_textures(current_image
, TRUE
);
323 refresh(REFRESH_NOW
| APPEND_HISTORY
| REFRESH_STATUS
);
325 /* A bit dumb, but needed on some cards. */
326 refresh(REFRESH_NOW
);
329 list_changed
= remove_obsolete_nodes();
331 do_later(G_PRIORITY_LOW
, cond_rebuild_menus
);
332 update_current_image_status(list_changed
);
334 refresh(REFRESH_TITLE
);
337 void zoom_in(gfloat ratio
)
339 gint pointer_x
, pointer_y
;
342 if (options
->zoom_pointer
) {
343 /* The pointer is the zoom center. */
344 gdk_window_get_pointer(gl_widget
->window
, &pointer_x
, &pointer_y
, NULL
);
345 x
= (gfloat
) pointer_x
;
346 y
= (gfloat
) pointer_y
;
348 /* The zoom center is the midle of the window. */
349 x
= rt
->wid_size
->width
/ 2.0;
350 y
= rt
->wid_size
->height
/ 2.0;
353 matrix_zoom(ratio
, x
, y
);
354 refresh(REFRESH_IMAGE
| REFRESH_STATUS
| APPEND_HISTORY
);
357 static gboolean
has_do_later(GSourceDummyMarshal idle_func
);
358 static void add_do_later(GSourceDummyMarshal idle_func
);
359 static void remove_do_later(GSourceDummyMarshal idle_func
);
361 static gboolean
refresh_image(void)
363 remove_do_later(redraw
);
364 refresh(REFRESH_IMAGE
);
369 gint
diff_timeval_us(GTimeVal
* after
, GTimeVal
* before
)
373 diff_timeval(&diff
, after
, before
);
374 return diff
.tv_sec
* G_USEC_PER_SEC
+ diff
.tv_usec
;
377 gint
get_fps_limiter_delay(GTimeVal
* previous_frame
)
382 if (options
->fps
<= 0)
385 g_get_current_time(&now
);
387 G_USEC_PER_SEC
/ options
->fps
- diff_timeval_us(&now
, previous_frame
);
392 void refresh(gint what
)
394 /* GTK does that automatically but with more overhead. */
395 if (what
& REFRESH_IMAGE
)
396 do_later(GDK_PRIORITY_REDRAW
, redraw
);
398 else if (what
& REFRESH_BURST
) {
399 if (!has_do_later(redraw
)) {
400 gint diff
= get_fps_limiter_delay(&last_redraw
) + advance
;
403 add_do_later(redraw
);
404 g_timeout_add(diff
/ 1000, (GtkFunction
) refresh_image
, NULL
);
406 refresh(REFRESH_IMAGE
);
408 } else if (what
& REFRESH_NOW
) {
409 remove_do_later(redraw
);
413 if (what
& REFRESH_STATUS
)
414 do_later(G_PRIORITY_HIGH
, update_status_bar
);
416 if (what
& APPEND_HISTORY
)
419 if (what
& REFRESH_TITLE
)
420 do_later(G_PRIORITY_DEFAULT
, update_window_title
);
422 if (what
& REFRESH_SCROLL
)
423 do_later(G_PRIORITY_LOW
, update_scrollbars
);
426 void update_current_image_status(gboolean find_number
)
428 if (current_image
!= NULL
) {
430 current_image
->number
= -1;
432 fill_ident(current_image
);
435 refresh(REFRESH_STATUS
);
438 static GHashTable
*idle_functions
= NULL
;
440 static void init_do_later(void)
442 if (idle_functions
== NULL
)
444 idle_functions
= g_hash_table_new(g_direct_hash
, NULL
);
447 static gboolean
has_do_later(GSourceDummyMarshal idle_func
)
450 return g_hash_table_lookup(idle_functions
, idle_func
) != NULL
;
453 static void add_do_later(GSourceDummyMarshal idle_func
)
456 g_hash_table_insert(idle_functions
, idle_func
, idle_func
);
459 static void remove_do_later(GSourceDummyMarshal idle_func
)
462 g_hash_table_remove(idle_functions
, idle_func
);
466 static gboolean
wrapper(gpointer data
)
468 GSourceDummyMarshal func
= data
;
469 remove_do_later(func
);
474 void do_later(gint priority
, GSourceDummyMarshal func
)
476 if (has_do_later(func
))
477 /* Already scheduled */
481 g_idle_add_full(priority
, wrapper
, func
, NULL
);