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 ***********************/
26 #include "math_floats.h" /* ceilf() */
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
)
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.
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
);
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
)
92 map_dim
= map
->map_init
->width
;
93 last
= (id
== map
->map_init
->x_tiles
- 1);
95 map_dim
= map
->map_init
->height
;
96 last
= (id
== map
->map_init
->y_tiles
- 1);
100 *vert0
= -map_dim
/ 2.0;
106 *tex1
= (gfloat
) map_dim
/ gl_dim(map_dim
);
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
;
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;
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
;
135 *vert1
= *vert0
+ rt
->max_texture_size
- 2.0;
136 *tex0
= 1.0 / rt
->max_texture_size
;
142 static void rectangle(texture_map
* map
, tile_dim
* tile
, gint i
, gint j
,
145 gfloat ty0
, ty1
, tx0
, tx1
;
146 gfloat x0
, x1
, y0
, y1
;
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
);
159 draw_rectangle(tx0
, tx1
, ty0
, ty1
, x0
, x1
, y0
, y1
);
161 /* Used when drawing, to know which tiles are hidden. */
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
;
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
)
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
;
211 last_tile
= (level
+ 1 == im
->nb_maps
);
213 w
= gl_w
= rt
->max_texture_size
;
217 if (j
+ 1 == map
->map_init
->y_tiles
) {
218 /* Last vertical tile in this level. */
219 h
= map
->map_init
->height
- y
;
222 h
= gl_h
= rt
->max_texture_size
;
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
;
232 if (gl_w
== rt
->max_texture_size
&& gl_h
== rt
->max_texture_size
&&
233 tile_max_size
!= NULL
)
235 tile
= tile_max_size
;
237 tile
= gdk_pixbuf_new(GDK_COLORSPACE_RGB
, im
->has_alpha
, 8,
240 gdk_pixbuf_copy_area(map
->map_init
->pixbuf
, x
, y
, w
, h
, tile
, 0, 0);
244 /* Right border: copy the last column. */
245 gdk_pixbuf_copy_area(tile
, w
- 1, 0, 1, h
, tile
, w
, 0);
248 /* Lower corner: copy the last line. */
249 gdk_pixbuf_copy_area(tile
, 0, h
- 1, w
, 1, tile
, 0, h
);
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
)
275 tiles
= &map
->map_init
->x_tiles
;
276 dim
= map
->map_init
->width
;
278 tiles
= &map
->map_init
->y_tiles
;
279 dim
= map
->map_init
->height
;
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
;
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
,
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
)
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
++) {
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
);
358 static void copy_alpha(GdkPixbuf
* dest
, GdkPixbuf
* src
)
360 guchar
*dest_data
, *src_data
, *max_dest
;
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];
376 /* Returns the original pixbuf if not asked to be destroyed. */
377 static GdkPixbuf
*dither_pixbuf(texture_map
* map
, gboolean destroy_old
)
380 GdkPixbuf
*old
, *new;
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
);
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
));
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
);
414 gdk_pixbuf_get_from_drawable(new, pmap
, gdk_colormap_get_system(),
415 0, 0, 0, 0, width
, height
);
417 gdk_pixmap_unref(pmap
);
420 copy_alpha(new, 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
)
437 map
->map_init
->width
= im
->width
;
438 map
->map_init
->height
= im
->height
;
441 /* We keep the pixbuf only if mipmaps are used. */
442 orig
= dither_pixbuf(map
, (im
->nb_maps
== 1));
444 orig
= map
->map_init
->pixbuf
;
449 gdk_pixbuf_unref(map
->map_init
->pixbuf
);
451 g_free(map
->map_init
);
455 void create_maps(gliv_image
* im
)
462 previous
= create_first_map(im
);
464 if (im
->nb_maps
== 1) {
467 if (options
->dither
== FALSE
)
468 gdk_pixbuf_unref(previous
);
470 prioritize_textures(im
, FALSE
);
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
);
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
);
499 gdk_pixbuf_unref(map
->map_init
->pixbuf
);
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
);