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 ***********************/
27 #include "math_floats.h" /* powf() */
31 #include "dithering.h"
36 extern options_struct
*options
;
37 extern GtkWidget
*gl_widget
;
39 /* Draws a piece of a multi-textures image or a whole mono-texture one. */
40 static void draw_rectangle(gfloat tex_x0
, gfloat tex_x1
,
41 gfloat tex_y0
, gfloat tex_y1
,
42 gfloat vert_x0
, gfloat vert_x1
,
43 gfloat vert_y0
, gfloat vert_y1
)
46 * (tex_x0 ; tex_y0) : Origin of the interesting part of the texture.
47 * (tex_x1 ; tex_y1) : Extremity of the interesting part of the texture.
48 * (vert_x0 ; vert_y0) : Origin of the rectangle.
49 * (vert_x1 ; vert_y1) : Extremity of the rectangle.
54 glTexCoord2f(tex_x0
, tex_y0
);
55 glVertex2f(vert_x0
, vert_y0
);
57 glTexCoord2f(tex_x1
, tex_y0
);
58 glVertex2f(vert_x1
, vert_y0
);
60 glTexCoord2f(tex_x1
, tex_y1
);
61 glVertex2f(vert_x1
, vert_y1
);
63 glTexCoord2f(tex_x0
, tex_y1
);
64 glVertex2f(vert_x0
, vert_y1
);
70 * Called twice for each rectangle to get rectangle
71 * coordinates to put in the display list.
73 static void compute_coordinates(texture_map
* map
,
74 gfloat
* vert0
, gfloat
* vert1
,
75 gfloat
* tex0
, gfloat
* tex1
,
76 gint map_dim
, gint pos
, gint size
)
78 gboolean last
= pos
+ size
>= map_dim
;
80 *vert0
= -map_dim
/ 2.0 + pos
;
88 *tex1
= (gfloat
) map_dim
/ size
;
92 * - 1.0: there is only one overlapping pixel.
94 *vert1
= *vert0
+ size
- 1.0;
95 *tex1
= (size
- 0.5) / size
;
101 *vert1
= map_dim
/ 2.0 - 1.0;
102 *tex1
= (gfloat
) (map_dim
- pos
) / size
;
105 *vert1
= *vert0
+ size
- 1.0;
111 printf("pos:%d map_dim:%d size:%d v0:%f v1:%f t0:%f t1:%f\n", pos
, map_dim
,
112 size
, *vert0
, *vert1
, *tex0
, *tex1
);
116 static void rectangle(texture_map
* map
, tile_dim
* tile
, gint x
, gint y
,
117 gint w
, gint h
, gint level
)
119 gfloat ty0
, ty1
, tx0
, tx1
;
120 gfloat x0
, x1
, y0
, y1
;
123 mipmap_coeff
= powf(MIPMAP_RATIO
, -level
);
125 compute_coordinates(map
, &x0
, &x1
, &tx0
, &tx1
, map
->width
, x
, w
);
126 compute_coordinates(map
, &y0
, &y1
, &ty0
, &ty1
, map
->height
, y
, h
);
133 draw_rectangle(tx0
, tx1
, ty0
, ty1
, x0
, x1
, y0
, y1
);
135 /* Used when drawing, to know which tiles are hidden. */
142 /* Shortcut to OpenGL parameters common to all textures. */
143 static void texture_parameter(void)
145 /* We don't change the filter for small mip maps. */
146 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_MIN_FILTER
, GL_LINEAR
);
147 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_MAG_FILTER
, GL_LINEAR
);
149 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_WRAP_S
, GL_CLAMP
);
150 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_WRAP_T
, GL_CLAMP
);
157 GdkPixbuf
*tile_max_size
;
161 /* Runs in a separate thread. */
162 static GdkPixbuf
*make_tile(texture_data
* data
)
167 if (data
->x
== 0 && data
->y
== 0 &&
168 data
->w
== data
->map
->width
&& data
->h
== data
->map
->height
) {
169 /* Special case, the image is OK for an OpenGL texture. */
170 g_object_ref(data
->map
->pixbuf
);
171 return data
->map
->pixbuf
;
174 if (data
->w
== rt
->max_texture_size
&& data
->h
== rt
->max_texture_size
) {
175 tile
= data
->tile_max_size
;
178 tile
= gdk_pixbuf_new(GDK_COLORSPACE_RGB
, data
->has_alpha
, 8,
182 if (data
->x
+ data
->w
>= data
->map
->width
)
183 real_w
= data
->map
->width
- data
->x
;
186 if (data
->y
+ data
->h
>= data
->map
->height
)
187 real_h
= data
->map
->height
- data
->y
;
189 gdk_pixbuf_copy_area(data
->map
->pixbuf
, data
->x
, data
->y
, real_w
, real_h
,
192 if (data
->w
!= real_w
)
193 /* Right border: copy the last column. */
194 gdk_pixbuf_copy_area(tile
, real_w
- 1, 0, 1, real_h
, tile
, real_w
, 0);
196 if (data
->h
!= real_h
) {
197 /* Lower corner: copy the last line. */
198 gdk_pixbuf_copy_area(tile
, 0, real_h
- 1, real_w
, 1, tile
, 0, real_h
);
200 if (data
->w
!= real_w
)
201 /* Lower-right corner: copy the last pixel. */
202 gdk_pixbuf_copy_area(tile
, real_w
- 1, real_h
- 1, 1, 1,
203 tile
, real_w
, real_h
);
209 static void make_texture(GlivImage
* im
, gint level
, gint x
, gint y
,
212 gint texture0
, texture1
;
213 static GdkPixbuf
*tile_max_size
= NULL
;
218 if (tile_max_size
== NULL
&&
219 w
== rt
->max_texture_size
&& h
== rt
->max_texture_size
)
221 gdk_pixbuf_new(GDK_COLORSPACE_RGB
, im
->has_alpha
, 8,
222 rt
->max_texture_size
, rt
->max_texture_size
);
224 map
= im
->maps
+ level
;
226 data
= g_new(texture_data
, 1);
228 data
->tile_max_size
= tile_max_size
;
229 if (data
->tile_max_size
)
230 g_object_ref(tile_max_size
);
231 data
->has_alpha
= im
->has_alpha
;
239 /* We may change the current texture while waiting for the thread. */
240 glGetIntegerv(GL_TEXTURE_BINDING_2D
, &texture0
);
241 tile
= do_threaded((GThreadFunc
) make_tile
, data
);
242 glGetIntegerv(GL_TEXTURE_BINDING_2D
, &texture1
);
244 if (data
->tile_max_size
)
245 g_object_unref(data
->tile_max_size
);
247 if (texture0
!= texture1
)
248 glBindTexture(GL_TEXTURE_2D
, texture0
);
250 glTexImage2D(GL_TEXTURE_2D
, 0, 3 + im
->has_alpha
, w
, h
, 0,
251 im
->has_alpha
? GL_RGBA
: GL_RGB
, GL_UNSIGNED_BYTE
,
252 gdk_pixbuf_get_pixels(tile
));
255 g_object_unref(tile
);
257 if (x
+ w
>= map
->width
&& y
+ h
>= map
->height
&& tile_max_size
!= NULL
) {
258 g_object_unref(tile_max_size
);
259 tile_max_size
= NULL
;
265 void prioritize_textures(GlivImage
* im
, gboolean is_prio
)
267 GLclampf
*priorities
;
274 * im->maps[0] has the biggest nb_tiles, so we don't change the
275 * priorities array for each map, we simply use the biggest for all.
277 priorities
= g_new(GLclampf
, im
->maps
[0].nb_tiles
);
278 for (i
= 0; i
< im
->maps
[0].nb_tiles
; i
++)
279 priorities
[i
] = (gfloat
) is_prio
;
281 for (i
= 0; i
< im
->nb_maps
; i
++)
282 glPrioritizeTextures(im
->maps
[i
].nb_tiles
, im
->maps
[i
].tex_ids
,
289 static void compute_gl_dimensions(texture_map
* map
)
291 map
->x_tiles
= make_tiles(map
->width
);
292 map
->y_tiles
= make_tiles(map
->height
);
294 map
->nb_tiles
= map
->x_tiles
->nb_tiles
* map
->y_tiles
->nb_tiles
;
296 map
->tex_ids
= g_new(guint
, map
->nb_tiles
);
297 glGenTextures(map
->nb_tiles
, map
->tex_ids
);
299 map
->list
= glGenLists(map
->nb_tiles
);
302 static void create_a_map(GlivImage
* im
, gint level
)
307 struct tiles_iterator
*x_iterator
, *y_iterator
;
309 map
= im
->maps
+ level
;
310 compute_gl_dimensions(map
);
312 map
->tiles
= g_new(tile_dim
, map
->nb_tiles
);
315 y_iterator
= tiles_iterator_new(map
->y_tiles
);
317 while ((h
= tiles_iterator_next(y_iterator
)) > 0) {
318 x_iterator
= tiles_iterator_new(map
->x_tiles
);
320 while ((w
= tiles_iterator_next(x_iterator
)) > 0) {
322 glBindTexture(GL_TEXTURE_2D
, map
->tex_ids
[id
]);
324 printf("%d: [x:%d y:%d w:%d h:%d]\n", id
, x
, y
, w
, h
);
327 make_texture(im
, level
, x
, y
, w
, h
);
329 glNewList(map
->list
+ id
, GL_COMPILE
);
331 /* Redundant but need to be in the display list. */
332 glBindTexture(GL_TEXTURE_2D
, map
->tex_ids
[id
]);
334 rectangle(map
, map
->tiles
+ id
, x
, y
, w
, h
, level
);
345 destroy_tiles(map
->x_tiles
);
346 destroy_tiles(map
->y_tiles
);
353 gboolean destroy_old
;
356 /* Runs in a separate thread. */
357 static GdkPixbuf
*_dither(dithering
* todo
)
359 GdkPixbuf
*old
, *dithered
;
361 old
= todo
->map
->pixbuf
;
363 if (todo
->destroy_old
)
366 dithered
= gdk_pixbuf_new(gdk_pixbuf_get_colorspace(old
),
367 gdk_pixbuf_get_has_alpha(old
),
368 gdk_pixbuf_get_bits_per_sample(old
),
369 gdk_pixbuf_get_width(old
),
370 gdk_pixbuf_get_height(old
));
372 dither_pixbuf(dithered
, old
);
374 todo
->map
->pixbuf
= dithered
;
379 /* Returns the original pixbuf if not asked to be destroyed. */
380 static GdkPixbuf
*dither(texture_map
* map
, gboolean destroy_old
)
385 todo
.destroy_old
= destroy_old
;
387 return do_threaded((GThreadFunc
) _dither
, &todo
);
390 /* Returns the original pixbuf. */
391 static GdkPixbuf
*create_first_map(GlivImage
* im
)
397 map
->width
= im
->width
;
398 map
->height
= im
->height
;
401 /* We keep the pixbuf only if mipmaps are used. */
402 orig
= dither(map
, (im
->nb_maps
== 1));
409 g_object_unref(map
->pixbuf
);
414 /* Wrapper for do_threaded(). */
419 GdkInterpType interp
;
422 static GdkPixbuf
*rescale(rescale_arg
* arg
)
424 return gdk_pixbuf_scale_simple(arg
->image
, arg
->width
, arg
->height
,
428 void create_maps(GlivImage
* im
)
436 previous
= create_first_map(im
);
438 if (im
->nb_maps
== 1) {
441 if (options
->dither
== FALSE
)
442 g_object_unref(previous
);
444 prioritize_textures(im
, FALSE
);
450 arg
= g_new(rescale_arg
, 1);
452 for (level
= 1; level
< im
->nb_maps
; level
++) {
454 map
= im
->maps
+ level
;
456 width
= map
->width
= width
* MIPMAP_RATIO
;
457 height
= map
->height
= height
* MIPMAP_RATIO
;
459 arg
->image
= previous
;
461 arg
->height
= height
;
462 arg
->interp
= GDK_INTERP_BILINEAR
;
463 map
->pixbuf
= do_threaded((GThreadFunc
) rescale
, arg
);
465 g_object_unref(previous
);
469 * We must dither each map since rescaling does not preserve it.
470 * We keep the original image except for the last level.
472 previous
= dither(map
, (level
== im
->nb_maps
- 1));
474 create_a_map(im
, level
);
477 g_object_unref(map
->pixbuf
);
479 previous
= map
->pixbuf
;
482 if (options
->dither
== FALSE
)
483 g_object_unref(previous
);
485 prioritize_textures(im
, FALSE
);