gliv-1.7
[gliv.git] / src / textures.c
blob1379fecc21e3873acc674feebfc8ac3cd0020419
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 <gfc@altern.org>
21 /***********************
22 * Texture management. *
23 ***********************/
25 #include "gliv.h"
26 #include "math_floats.h" /* ceilf() */
28 #include <GL/gl.h>
30 extern rt_struct *rt;
31 extern options_struct *options;
32 extern GtkWidget *gl_widget;
35 * Lowest power of two bigger than the argument and 64.
36 * glTexImage2D(3G): All implementations support texture images
37 * that are at least 64 texels (wide|high).
39 G_GNUC_CONST static gint gl_dim(gint p)
41 gint ret = 64;
43 while (ret < p)
44 ret += ret;
46 return ret;
49 /* Draws a piece of a multi-textures image or a whole mono-texture one. */
50 static void draw_rectangle(gfloat tex_x0, gfloat tex_x1,
51 gfloat tex_y0, gfloat tex_y1,
52 gfloat vert_x0, gfloat vert_x1,
53 gfloat vert_y0, gfloat vert_y1)
56 * (tex_x0 ; tex_y0) : Origin of the interesting part of the texture.
57 * (tex_x1 ; tex_y1) : Extremity of the interesting part of the texture.
58 * (vert_x0 ; vert_y0) : Origin of the rectangle.
59 * (vert_x1 ; vert_y1) : Extremity of the rectangle.
62 glBegin(GL_QUADS);
64 glTexCoord2f(tex_x0, tex_y0);
65 glVertex2f(vert_x0, vert_y0);
67 glTexCoord2f(tex_x1, tex_y0);
68 glVertex2f(vert_x1, vert_y0);
70 glTexCoord2f(tex_x1, tex_y1);
71 glVertex2f(vert_x1, vert_y1);
73 glTexCoord2f(tex_x0, tex_y1);
74 glVertex2f(vert_x0, vert_y1);
76 glEnd();
80 * Called twice for each rectangle to get rectangle
81 * coordinates to put in the display list.
83 static void compute_coordinates(texture_map * map,
84 gfloat * vert0, gfloat * vert1,
85 gfloat * tex0, gfloat * tex1,
86 gboolean is_x, gint id)
88 gint map_dim;
89 gboolean last;
91 if (is_x) {
92 map_dim = map->map_init->width;
93 last = (id == map->map_init->x_tiles - 1);
94 } else {
95 map_dim = map->map_init->height;
96 last = (id == map->map_init->y_tiles - 1);
99 if (id == 0) {
100 *vert0 = -map_dim / 2.0;
101 *tex0 = 0.0;
103 if (last) {
104 /* Single tile. */
105 *vert1 = -*vert0;
106 *tex1 = (gfloat) map_dim / gl_dim(map_dim);
107 } else {
109 * First tile.
110 * - 1.0: there is only one overlapping pixel.
112 *vert1 = *vert0 + rt->max_texture_size - 1.0;
113 *tex1 = (rt->max_texture_size - 1.0) / rt->max_texture_size;
115 } else {
117 * - 2.0: there are two overlapping pixels.
118 * + 1.0: in the first tile there is only one.
120 *vert0 = -map_dim / 2.0 + id * (rt->max_texture_size - 2.0) + 1.0;
122 if (last) {
123 /* Last tile. */
124 gint tile_dim;
125 gfloat gl_tile_dim;
127 tile_dim = map_dim - id * (rt->max_texture_size - 2);
128 gl_tile_dim = (gfloat) gl_dim(tile_dim);
130 *vert1 = map_dim / 2.0;
131 *tex0 = 1.0 / gl_tile_dim;
132 *tex1 = tile_dim / gl_tile_dim;
133 } else {
134 /* Middle tiles. */
135 *vert1 = *vert0 + rt->max_texture_size - 2.0;
136 *tex0 = 1.0 / rt->max_texture_size;
137 *tex1 = 1.0 - *tex0;
142 static void rectangle(texture_map * map, tile_dim * tile, gint i, gint j,
143 gint level)
145 gfloat ty0, ty1, tx0, tx1;
146 gfloat x0, x1, y0, y1;
147 gfloat mipmap_coeff;
149 mipmap_coeff = pow(MIPMAP_RATIO, -level);
151 compute_coordinates(map, &x0, &x1, &tx0, &tx1, TRUE, i);
152 compute_coordinates(map, &y0, &y1, &ty0, &ty1, FALSE, j);
154 x0 *= mipmap_coeff;
155 x1 *= mipmap_coeff;
156 y0 *= mipmap_coeff;
157 y1 *= mipmap_coeff;
159 draw_rectangle(tx0, tx1, ty0, ty1, x0, x1, y0, y1);
161 /* Used when drawing, to know which tiles are hidden. */
162 tile->x0 = x0;
163 tile->y0 = y0;
164 tile->x1 = x1;
165 tile->y1 = y1;
168 /* Shortcut to OpenGL parameters common to all textures. */
169 static void texture_parameter(void)
171 /* These filters will be changed but it's important to initialize them. */
172 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
173 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
175 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
176 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
179 static void make_texture(gliv_image * im, gint level, gint i, gint j)
181 static GdkPixbuf *tile_max_size = NULL;
182 texture_map *map;
183 gboolean last_tile;
184 GdkPixbuf *tile;
185 gint x, y;
186 gint w, h, gl_w, gl_h;
188 if (tile_max_size == NULL &&
189 im->width >= rt->max_texture_size && im->height >= rt->max_texture_size)
191 if (im->width != gl_dim(im->width) ||
192 im->height != gl_dim(im->height) ||
193 im->width > rt->max_texture_size ||
194 im->height > rt->max_texture_size)
195 /* First tile. */
196 tile_max_size = gdk_pixbuf_new(GDK_COLORSPACE_RGB, im->has_alpha, 8,
197 rt->max_texture_size,
198 rt->max_texture_size);
200 map = im->maps + level;
202 x = i * (rt->max_texture_size - 2);
203 y = j * (rt->max_texture_size - 2);
205 if (i + 1 == map->map_init->x_tiles) {
206 /* Last horizontal tile in this level. */
207 w = map->map_init->width - x;
208 gl_w = gl_dim(w);
210 /* Last level? */
211 last_tile = (level + 1 == im->nb_maps);
212 } else {
213 w = gl_w = rt->max_texture_size;
214 last_tile = FALSE;
217 if (j + 1 == map->map_init->y_tiles) {
218 /* Last vertical tile in this level. */
219 h = map->map_init->height - y;
220 gl_h = gl_dim(h);
221 } else {
222 h = gl_h = rt->max_texture_size;
223 last_tile = FALSE;
226 texture_parameter();
228 if (i == 0 && j == 0 && last_tile && w == gl_w && h == gl_h)
229 /* Special case, the image is OK for an OpenGL texture. */
230 tile = map->map_init->pixbuf;
231 else {
232 if (gl_w == rt->max_texture_size && gl_h == rt->max_texture_size &&
233 tile_max_size != NULL)
235 tile = tile_max_size;
236 else
237 tile = gdk_pixbuf_new(GDK_COLORSPACE_RGB, im->has_alpha, 8,
238 gl_w, gl_h);
240 gdk_pixbuf_copy_area(map->map_init->pixbuf, x, y, w, h, tile, 0, 0);
243 if (gl_w != w)
244 /* Right border: copy the last column. */
245 gdk_pixbuf_copy_area(tile, w - 1, 0, 1, h, tile, w, 0);
247 if (gl_h != h) {
248 /* Lower corner: copy the last line. */
249 gdk_pixbuf_copy_area(tile, 0, h - 1, w, 1, tile, 0, h);
251 if (gl_w != w)
252 /* Lower-right corner: copy the last pixel. */
253 gdk_pixbuf_copy_area(tile, w - 1, h - 1, 1, 1, tile, w, h);
256 glTexImage2D(GL_TEXTURE_2D, 0, 3 + im->has_alpha, gl_w, gl_h, 0,
257 im->has_alpha ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE,
258 gdk_pixbuf_get_pixels(tile));
260 if (tile != tile_max_size && tile != map->map_init->pixbuf)
261 gdk_pixbuf_unref(tile);
263 if (last_tile && tile_max_size != NULL) {
264 gdk_pixbuf_unref(tile_max_size);
265 tile_max_size = NULL;
269 static void tiles_parameters(texture_map * map, gboolean is_x)
271 gint *tiles;
272 gint dim;
274 if (is_x) {
275 tiles = &map->map_init->x_tiles;
276 dim = map->map_init->width;
277 } else {
278 tiles = &map->map_init->y_tiles;
279 dim = map->map_init->height;
282 *tiles = 1;
283 if (dim > rt->max_texture_size)
284 *tiles += (gint) ceilf((gfloat) (dim - rt->max_texture_size) /
285 (rt->max_texture_size - 2.0));
288 void prioritize_textures(gliv_image * im, gboolean is_prio)
290 GLclampf *priorities;
291 gint i;
293 if (im == NULL)
294 return;
297 * im->maps[0] has the biggest nb_tiles, so we don't change the
298 * priorities array for each map, we simply use the biggest for all.
300 priorities = g_new(GLclampf, im->maps[0].nb_tiles);
301 for (i = 0; i < im->maps[0].nb_tiles; i++)
302 priorities[i] = (gfloat) is_prio;
304 for (i = 0; i < im->nb_maps; i++)
305 glPrioritizeTextures(im->maps[i].nb_tiles, im->maps[i].tex_ids,
306 priorities);
308 g_free(priorities);
312 static void compute_gl_dimensions(texture_map * map)
314 tiles_parameters(map, TRUE);
315 tiles_parameters(map, FALSE);
317 map->nb_tiles = map->map_init->x_tiles * map->map_init->y_tiles;
319 map->tex_ids = g_new(guint, map->nb_tiles);
320 glGenTextures(map->nb_tiles, map->tex_ids);
322 map->list = glGenLists(map->nb_tiles);
325 static void create_a_map(gliv_image * im, gint level)
327 gint i, j;
328 gint id = 0;
329 texture_map *map;
331 map = im->maps + level;
332 compute_gl_dimensions(map);
334 map->tiles = g_new(tile_dim, map->nb_tiles);
336 for (j = 0; j < map->map_init->y_tiles; j++)
337 for (i = 0; i < map->map_init->x_tiles; i++) {
339 process_events();
341 glBindTexture(GL_TEXTURE_2D, map->tex_ids[id]);
342 make_texture(im, level, i, j);
344 glNewList(map->list + id, GL_COMPILE);
346 /* Redundant but need to be in the display list. */
347 glBindTexture(GL_TEXTURE_2D, map->tex_ids[id]);
349 rectangle(map, map->tiles + id, i, j, level);
350 glEndList();
352 id++;
356 /*** Dithering ***/
358 static void copy_alpha(GdkPixbuf * dest, GdkPixbuf * src)
360 guchar *dest_data, *src_data, *max_dest;
362 process_events();
364 src_data = gdk_pixbuf_get_pixels(src);
365 dest_data = gdk_pixbuf_get_pixels(dest);
366 max_dest = dest_data +
367 gdk_pixbuf_get_height(dest) * gdk_pixbuf_get_rowstride(dest);
369 while (dest_data < max_dest) {
370 dest_data[3] = src_data[3];
371 dest_data += 4;
372 src_data += 4;
376 /* Returns the original pixbuf if not asked to be destroyed. */
377 static GdkPixbuf *dither_pixbuf(texture_map * map, gboolean destroy_old)
379 GdkPixmap *pmap;
380 GdkPixbuf *old, *new;
381 GdkGC *gc;
382 gboolean has_alpha;
383 gint width, height;
385 gdk_rgb_init();
386 process_events();
388 old = map->map_init->pixbuf;
389 has_alpha = gdk_pixbuf_get_has_alpha(old);
390 width = map->map_init->width;
391 height = map->map_init->height;
393 pmap = gdk_pixmap_new(gl_widget->window, width, height, -1);
395 gc = gdk_gc_new(gl_widget->window);
397 if (has_alpha)
398 gdk_draw_rgb_32_image(pmap, gc, 0, 0, width, height,
399 GDK_RGB_DITHER_MAX, gdk_pixbuf_get_pixels(old),
400 gdk_pixbuf_get_rowstride(old));
401 else
402 gdk_pixbuf_render_to_drawable(old, pmap, gc, 0, 0, 0, 0, width, height,
403 GDK_RGB_DITHER_MAX, 0, 0);
405 if (has_alpha || destroy_old == FALSE)
406 /* We have to keep the old image. */
407 new = gdk_pixbuf_new(GDK_COLORSPACE_RGB, has_alpha, 8, width, height);
408 else
409 new = old;
411 process_events();
412 gdk_gc_unref(gc);
414 gdk_pixbuf_get_from_drawable(new, pmap, gdk_colormap_get_system(),
415 0, 0, 0, 0, width, height);
417 gdk_pixmap_unref(pmap);
419 if (has_alpha) {
420 copy_alpha(new, old);
422 if (destroy_old)
423 gdk_pixbuf_unref(old);
426 map->map_init->pixbuf = new;
427 return destroy_old ? NULL : old;
430 /* Returns the original pixbuf. */
431 static GdkPixbuf *create_first_map(gliv_image * im)
433 texture_map *map;
434 GdkPixbuf *orig;
436 map = im->maps;
437 map->map_init->width = im->width;
438 map->map_init->height = im->height;
440 if (options->dither)
441 /* We keep the pixbuf only if mipmaps are used. */
442 orig = dither_pixbuf(map, (im->nb_maps == 1));
443 else
444 orig = map->map_init->pixbuf;
446 create_a_map(im, 0);
448 if (options->dither)
449 gdk_pixbuf_unref(map->map_init->pixbuf);
451 g_free(map->map_init);
452 return orig;
455 void create_maps(gliv_image * im)
457 gint level;
458 texture_map *map;
459 GdkPixbuf *previous;
460 gint width, height;
462 previous = create_first_map(im);
464 if (im->nb_maps == 1) {
465 /* No mipmaps. */
467 if (options->dither == FALSE)
468 gdk_pixbuf_unref(previous);
470 prioritize_textures(im, FALSE);
471 return;
474 width = im->width;
475 height = im->height;
477 for (level = 1; level < im->nb_maps; level++) {
479 map = im->maps + level;
481 map->map_init = g_new(loading_data, 1);
483 width = map->map_init->width = width * MIPMAP_RATIO;
484 height = map->map_init->height = height * MIPMAP_RATIO;
485 map->map_init->pixbuf = gdk_pixbuf_scale_simple(previous, width, height,
486 GDK_INTERP_BILINEAR);
487 gdk_pixbuf_unref(previous);
489 if (options->dither)
491 * We must dither each map since rescaling does not preserve it.
492 * We keep the original image unless for the last level.
494 previous = dither_pixbuf(map, (level == im->nb_maps - 1));
496 create_a_map(im, level);
498 if (options->dither)
499 gdk_pixbuf_unref(map->map_init->pixbuf);
500 else
501 previous = map->map_init->pixbuf;
503 g_free(map->map_init);
506 if (options->dither == FALSE)
507 gdk_pixbuf_unref(previous);
509 prioritize_textures(im, FALSE);