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 ***********************/
29 #include "math_floats.h" /* ceilf(), powf() */
32 #include "gl_widget.h"
34 #include "dithering.h"
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
)
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.
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
);
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
)
98 map_dim
= map
->map_init
->width
;
99 last
= (id
== map
->map_init
->x_tiles
- 1);
101 map_dim
= map
->map_init
->height
;
102 last
= (id
== map
->map_init
->y_tiles
- 1);
106 *vert0
= -map_dim
/ 2.0;
112 *tex1
= (gfloat
) map_dim
/ gl_dim(map_dim
);
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
;
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;
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
;
141 *vert1
= *vert0
+ rt
->max_texture_size
- 2.0;
142 *tex0
= 1.0 / rt
->max_texture_size
;
148 static void rectangle(texture_map
* map
, tile_dim
* tile
, gint i
, gint j
,
151 gfloat ty0
, ty1
, tx0
, tx1
;
152 gfloat x0
, x1
, y0
, y1
;
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
);
165 draw_rectangle(tx0
, tx1
, ty0
, ty1
, x0
, x1
, y0
, y1
);
167 /* Used when drawing, to know which tiles are hidden. */
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
);
192 GdkPixbuf
*tile_max_size
;
196 /* Runs in a separate thread. */
197 static GdkPixbuf
*make_tile(texture_data
* data
)
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. */
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
;
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
,
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
,
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,
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
);
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
;
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
))
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
;
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
);
274 data
->last_tile
= (level
+ 1 == im
->nb_maps
);
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
);
285 data
->h
= data
->gl_h
= rt
->max_texture_size
;
286 data
->last_tile
= FALSE
;
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
;
314 static void tiles_parameters(texture_map
* map
, gboolean is_x
)
320 tiles
= &map
->map_init
->x_tiles
;
321 dim
= map
->map_init
->width
;
323 tiles
= &map
->map_init
->y_tiles
;
324 dim
= map
->map_init
->height
;
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
;
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
,
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
)
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
);
398 report_opengl_errors();
405 gboolean destroy_old
;
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
)
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
;
431 /* Returns the original pixbuf if not asked to be destroyed. */
432 static GdkPixbuf
*dither(texture_map
* map
, gboolean destroy_old
)
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
)
449 map
->map_init
->width
= im
->width
;
450 map
->map_init
->height
= im
->height
;
453 /* We keep the pixbuf only if mipmaps are used. */
454 orig
= dither(map
, (im
->nb_maps
== 1));
456 orig
= map
->map_init
->pixbuf
;
461 g_object_unref(map
->map_init
->pixbuf
);
463 g_free(map
->map_init
);
467 /* Wrapper for do_threaded(). */
472 GdkInterpType interp
;
475 static GdkPixbuf
*rescale(rescale_arg
* arg
)
477 return gdk_pixbuf_scale_simple(arg
->image
, arg
->width
, arg
->height
,
481 void create_maps(gliv_image
* im
)
489 previous
= create_first_map(im
);
491 if (im
->nb_maps
== 1) {
494 if (options
->dither
== FALSE
)
495 g_object_unref(previous
);
497 prioritize_textures(im
, FALSE
);
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
;
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
);
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
);
532 g_object_unref(map
->map_init
->pixbuf
);
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
);