process_events() update, the number of processed events is bounded.
[gliv.git] / src / textures.c
blob88702f627191fea90e96e010057730524b33ab60
1 /*
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 * Textures management *
23 ***********************/
25 #include <GL/gl.h>
27 #include "gliv.h"
28 #include "textures.h"
29 #include "math_floats.h" /* ceilf(), powf() */
30 #include "options.h"
31 #include "params.h"
32 #include "gl_widget.h"
33 #include "thread.h"
34 #include "dithering.h"
36 extern rt_struct *rt;
37 extern options_struct *options;
38 extern GtkWidget *gl_widget;
41 * Lowest power of two bigger than the argument and 64.
42 * glTexImage2D(3G): All implementations support texture images
43 * that are at least 64 texels (wide|high).
45 G_GNUC_CONST static gint gl_dim(gint p)
47 gint ret = 64;
49 while (ret < p)
50 ret += ret;
52 return ret;
55 /* Draws a piece of a multi-textures image or a whole mono-texture one. */
56 static void draw_rectangle(gfloat tex_x0, gfloat tex_x1,
57 gfloat tex_y0, gfloat tex_y1,
58 gfloat vert_x0, gfloat vert_x1,
59 gfloat vert_y0, gfloat vert_y1)
62 * (tex_x0 ; tex_y0) : Origin of the interesting part of the texture.
63 * (tex_x1 ; tex_y1) : Extremity of the interesting part of the texture.
64 * (vert_x0 ; vert_y0) : Origin of the rectangle.
65 * (vert_x1 ; vert_y1) : Extremity of the rectangle.
68 glBegin(GL_QUADS);
70 glTexCoord2f(tex_x0, tex_y0);
71 glVertex2f(vert_x0, vert_y0);
73 glTexCoord2f(tex_x1, tex_y0);
74 glVertex2f(vert_x1, vert_y0);
76 glTexCoord2f(tex_x1, tex_y1);
77 glVertex2f(vert_x1, vert_y1);
79 glTexCoord2f(tex_x0, tex_y1);
80 glVertex2f(vert_x0, vert_y1);
82 glEnd();
86 * Called twice for each rectangle to get rectangle
87 * coordinates to put in the display list.
89 static void compute_coordinates(texture_map * map,
90 gfloat * vert0, gfloat * vert1,
91 gfloat * tex0, gfloat * tex1,
92 gboolean is_x, gint id)
94 gint map_dim;
95 gboolean last;
97 if (is_x) {
98 map_dim = map->map_init->width;
99 last = (id == map->map_init->x_tiles - 1);
100 } else {
101 map_dim = map->map_init->height;
102 last = (id == map->map_init->y_tiles - 1);
105 if (id == 0) {
106 *vert0 = -map_dim / 2.0;
107 *tex0 = 0.0;
109 if (last) {
110 /* Single tile. */
111 *vert1 = -*vert0;
112 *tex1 = (gfloat) map_dim / gl_dim(map_dim);
113 } else {
115 * First tile.
116 * - 1.0: there is only one overlapping pixel.
118 *vert1 = *vert0 + rt->max_texture_size - 1.0;
119 *tex1 = (rt->max_texture_size - 1.0) / rt->max_texture_size;
121 } else {
123 * - 2.0: there are two overlapping pixels.
124 * + 1.0: in the first tile there is only one.
126 *vert0 = -map_dim / 2.0 + id * (rt->max_texture_size - 2.0) + 1.0;
128 if (last) {
129 /* Last tile. */
130 gint tile_dim;
131 gfloat gl_tile_dim;
133 tile_dim = map_dim - id * (rt->max_texture_size - 2);
134 gl_tile_dim = (gfloat) gl_dim(tile_dim);
136 *vert1 = map_dim / 2.0;
137 *tex0 = 1.0 / gl_tile_dim;
138 *tex1 = tile_dim / gl_tile_dim;
139 } else {
140 /* Middle tiles. */
141 *vert1 = *vert0 + rt->max_texture_size - 2.0;
142 *tex0 = 1.0 / rt->max_texture_size;
143 *tex1 = 1.0 - *tex0;
148 static void rectangle(texture_map * map, tile_dim * tile, gint i, gint j,
149 gint level)
151 gfloat ty0, ty1, tx0, tx1;
152 gfloat x0, x1, y0, y1;
153 gfloat mipmap_coeff;
155 mipmap_coeff = powf(MIPMAP_RATIO, -level);
157 compute_coordinates(map, &x0, &x1, &tx0, &tx1, TRUE, i);
158 compute_coordinates(map, &y0, &y1, &ty0, &ty1, FALSE, j);
160 x0 *= mipmap_coeff;
161 x1 *= mipmap_coeff;
162 y0 *= mipmap_coeff;
163 y1 *= mipmap_coeff;
165 draw_rectangle(tx0, tx1, ty0, ty1, x0, x1, y0, y1);
167 /* Used when drawing, to know which tiles are hidden. */
168 tile->x0 = x0;
169 tile->y0 = y0;
170 tile->x1 = x1;
171 tile->y1 = y1;
174 /* Shortcut to OpenGL parameters common to all textures. */
175 static void texture_parameter(void)
177 /* We don't change the filter for small mip maps. */
178 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
179 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
181 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
182 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
185 typedef struct {
186 gint i, j;
187 gint w, h;
188 gint gl_w, gl_h;
189 gint x, y;
190 gboolean last_tile;
191 GdkPixbuf *pixbuf;
192 GdkPixbuf *tile_max_size;
193 gboolean has_alpha;
194 } texture_data;
196 /* Runs in a separate thread. */
197 static GdkPixbuf *make_tile(texture_data * data)
199 GdkPixbuf *tile;
201 if (data->i == 0 && data->j == 0 && data->last_tile &&
202 data->w == data->gl_w && data->h == data->gl_h)
203 /* Special case, the image is OK for an OpenGL texture. */
204 return data->pixbuf;
206 if (data->gl_w == rt->max_texture_size &&
207 data->gl_h == rt->max_texture_size && data->tile_max_size != NULL)
209 tile = data->tile_max_size;
210 else
211 tile = gdk_pixbuf_new(GDK_COLORSPACE_RGB, data->has_alpha, 8,
212 data->gl_w, data->gl_h);
214 gdk_pixbuf_copy_area(data->pixbuf, data->x, data->y, data->w, data->h,
215 tile, 0, 0);
217 if (data->gl_w != data->w)
218 /* Right border: copy the last column. */
219 gdk_pixbuf_copy_area(tile, data->w - 1, 0, 1, data->h,
220 tile, data->w, 0);
222 if (data->gl_h != data->h) {
223 /* Lower corner: copy the last line. */
224 gdk_pixbuf_copy_area(tile, 0, data->h - 1, data->w, 1,
225 tile, 0, data->h);
227 if (data->gl_w != data->w)
228 /* Lower-right corner: copy the last pixel. */
229 gdk_pixbuf_copy_area(tile, data->w - 1, data->h - 1, 1, 1,
230 tile, data->w, data->h);
233 return tile;
236 static void make_texture(gliv_image * im, gint level, gint i, gint j)
238 guint texture0, texture1;
239 static GdkPixbuf *tile_max_size = NULL;
240 texture_data *data;
241 texture_map *map;
242 GdkPixbuf *tile;
244 if (tile_max_size == NULL &&
245 im->width >= rt->max_texture_size &&
246 im->height >= rt->max_texture_size &&
247 (im->width != gl_dim(im->width) ||
248 im->height != gl_dim(im->height) ||
249 im->width > rt->max_texture_size || im->height > rt->max_texture_size))
250 /* First tile. */
251 tile_max_size = gdk_pixbuf_new(GDK_COLORSPACE_RGB, im->has_alpha, 8,
252 rt->max_texture_size,
253 rt->max_texture_size);
255 map = im->maps + level;
257 data = g_new(texture_data, 1);
258 data->pixbuf = map->map_init->pixbuf;
259 data->tile_max_size = tile_max_size;
260 data->has_alpha = im->has_alpha;
262 data->i = i;
263 data->j = j;
265 data->x = i * (rt->max_texture_size - 2);
266 data->y = j * (rt->max_texture_size - 2);
268 if (i + 1 == map->map_init->x_tiles) {
269 /* Last horizontal tile in this level. */
270 data->w = map->map_init->width - data->x;
271 data->gl_w = gl_dim(data->w);
273 /* Last level? */
274 data->last_tile = (level + 1 == im->nb_maps);
275 } else {
276 data->w = data->gl_w = rt->max_texture_size;
277 data->last_tile = FALSE;
280 if (j + 1 == map->map_init->y_tiles) {
281 /* Last vertical tile in this level. */
282 data->h = map->map_init->height - data->y;
283 data->gl_h = gl_dim(data->h);
284 } else {
285 data->h = data->gl_h = rt->max_texture_size;
286 data->last_tile = FALSE;
289 texture_parameter();
291 /* We may change the current texture while waiting for the thread. */
292 glGetIntegerv(GL_TEXTURE_BINDING_2D, &texture0);
293 tile = do_threaded((GThreadFunc) make_tile, data);
294 glGetIntegerv(GL_TEXTURE_BINDING_2D, &texture1);
296 if (texture0 != texture1)
297 glBindTexture(GL_TEXTURE_2D, texture0);
299 glTexImage2D(GL_TEXTURE_2D, 0, 3 + im->has_alpha, data->gl_w, data->gl_h, 0,
300 im->has_alpha ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE,
301 gdk_pixbuf_get_pixels(tile));
303 if (tile != tile_max_size && tile != map->map_init->pixbuf)
304 g_object_unref(tile);
306 if (data->last_tile && tile_max_size != NULL) {
307 g_object_unref(tile_max_size);
308 tile_max_size = NULL;
311 g_free(data);
314 static void tiles_parameters(texture_map * map, gboolean is_x)
316 gint *tiles;
317 gint dim;
319 if (is_x) {
320 tiles = &map->map_init->x_tiles;
321 dim = map->map_init->width;
322 } else {
323 tiles = &map->map_init->y_tiles;
324 dim = map->map_init->height;
327 *tiles = 1;
328 if (dim > rt->max_texture_size)
329 *tiles += (gint) ceilf((gfloat) (dim - rt->max_texture_size) /
330 (rt->max_texture_size - 2.0));
333 void prioritize_textures(gliv_image * im, gboolean is_prio)
335 GLclampf *priorities;
336 gint i;
338 if (im == NULL)
339 return;
342 * im->maps[0] has the biggest nb_tiles, so we don't change the
343 * priorities array for each map, we simply use the biggest for all.
345 priorities = g_new(GLclampf, im->maps[0].nb_tiles);
346 for (i = 0; i < im->maps[0].nb_tiles; i++)
347 priorities[i] = (gfloat) is_prio;
349 for (i = 0; i < im->nb_maps; i++)
350 glPrioritizeTextures(im->maps[i].nb_tiles, im->maps[i].tex_ids,
351 priorities);
353 g_free(priorities);
357 static void compute_gl_dimensions(texture_map * map)
359 tiles_parameters(map, TRUE);
360 tiles_parameters(map, FALSE);
362 map->nb_tiles = map->map_init->x_tiles * map->map_init->y_tiles;
364 map->tex_ids = g_new(guint, map->nb_tiles);
365 glGenTextures(map->nb_tiles, map->tex_ids);
367 map->list = glGenLists(map->nb_tiles);
370 static void create_a_map(gliv_image * im, gint level)
372 gint i, j;
373 gint id = 0;
374 texture_map *map;
376 map = im->maps + level;
377 compute_gl_dimensions(map);
379 map->tiles = g_new(tile_dim, map->nb_tiles);
381 for (j = 0; j < map->map_init->y_tiles; j++)
382 for (i = 0; i < map->map_init->x_tiles; i++) {
384 glBindTexture(GL_TEXTURE_2D, map->tex_ids[id]);
385 make_texture(im, level, i, j);
387 glNewList(map->list + id, GL_COMPILE);
389 /* Redundant but need to be in the display list. */
390 glBindTexture(GL_TEXTURE_2D, map->tex_ids[id]);
392 rectangle(map, map->tiles + id, i, j, level);
393 glEndList();
395 id++;
398 report_opengl_errors();
401 /*** Dithering ***/
403 typedef struct {
404 texture_map *map;
405 gboolean destroy_old;
406 } dithering;
408 /* Runs in a separate thread. */
409 static GdkPixbuf *_dither(dithering * todo)
411 GdkPixbuf *old, *dithered;
413 old = todo->map->map_init->pixbuf;
415 if (todo->destroy_old)
416 dithered = old;
417 else
418 dithered = gdk_pixbuf_new(gdk_pixbuf_get_colorspace(old),
419 gdk_pixbuf_get_has_alpha(old),
420 gdk_pixbuf_get_bits_per_sample(old),
421 gdk_pixbuf_get_width(old),
422 gdk_pixbuf_get_height(old));
424 dither_pixbuf(dithered, old);
426 todo->map->map_init->pixbuf = dithered;
428 return old;
431 /* Returns the original pixbuf if not asked to be destroyed. */
432 static GdkPixbuf *dither(texture_map * map, gboolean destroy_old)
434 dithering todo;
436 todo.map = map;
437 todo.destroy_old = destroy_old;
439 return do_threaded((GThreadFunc) _dither, &todo);
442 /* Returns the original pixbuf. */
443 static GdkPixbuf *create_first_map(gliv_image * im)
445 texture_map *map;
446 GdkPixbuf *orig;
448 map = im->maps;
449 map->map_init->width = im->width;
450 map->map_init->height = im->height;
452 if (options->dither)
453 /* We keep the pixbuf only if mipmaps are used. */
454 orig = dither(map, (im->nb_maps == 1));
455 else
456 orig = map->map_init->pixbuf;
458 create_a_map(im, 0);
460 if (options->dither)
461 g_object_unref(map->map_init->pixbuf);
463 g_free(map->map_init);
464 return orig;
467 /* Wrapper for do_threaded(). */
468 typedef struct {
469 GdkPixbuf *image;
470 gint width;
471 gint height;
472 GdkInterpType interp;
473 } rescale_arg;
475 static GdkPixbuf *rescale(rescale_arg * arg)
477 return gdk_pixbuf_scale_simple(arg->image, arg->width, arg->height,
478 arg->interp);
481 void create_maps(gliv_image * im)
483 gint level;
484 texture_map *map;
485 GdkPixbuf *previous;
486 gint width, height;
487 rescale_arg *arg;
489 previous = create_first_map(im);
491 if (im->nb_maps == 1) {
492 /* No mipmaps. */
494 if (options->dither == FALSE)
495 g_object_unref(previous);
497 prioritize_textures(im, FALSE);
498 return;
501 width = im->width;
502 height = im->height;
503 arg = g_new(rescale_arg, 1);
505 for (level = 1; level < im->nb_maps; level++) {
507 map = im->maps + level;
509 map->map_init = g_new(loading_data, 1);
511 width = map->map_init->width = width * MIPMAP_RATIO;
512 height = map->map_init->height = height * MIPMAP_RATIO;
514 arg->image = previous;
515 arg->width = width;
516 arg->height = height;
517 arg->interp = GDK_INTERP_BILINEAR;
518 map->map_init->pixbuf = do_threaded((GThreadFunc) rescale, arg);
520 g_object_unref(previous);
522 if (options->dither)
524 * We must dither each map since rescaling does not preserve it.
525 * We keep the original image except for the last level.
527 previous = dither(map, (level == im->nb_maps - 1));
529 create_a_map(im, level);
531 if (options->dither)
532 g_object_unref(map->map_init->pixbuf);
533 else
534 previous = map->map_init->pixbuf;
536 g_free(map->map_init);
539 if (options->dither == FALSE)
540 g_object_unref(previous);
542 prioritize_textures(im, FALSE);