]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/glwidget.cpp
DONE:
[xonotic/netradiant.git] / radiant / glwidget.cpp
1 /*
2 Copyright (c) 2001, Loki software, inc.
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without modification,
6 are permitted provided that the following conditions are met:
7
8 Redistributions of source code must retain the above copyright notice, this list
9 of conditions and the following disclaimer.
10
11 Redistributions in binary form must reproduce the above copyright notice, this
12 list of conditions and the following disclaimer in the documentation and/or
13 other materials provided with the distribution.
14
15 Neither the name of Loki software nor the names of its contributors may be used
16 to endorse or promote products derived from this software without specific prior
17 written permission.
18
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
20 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
23 DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26 ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 // OpenGL widget based on GtkGLExt
32
33 #include "stdafx.h"
34
35 #include <gtk/gtkgl.h>
36 #include <pango/pangoft2.h>
37 #include "glwidget.h"
38 #include "qgl.h"
39
40 typedef int* attribs_t;
41 typedef const attribs_t* configs_iterator;
42
43 int config_rgba32[] = {
44   GDK_GL_RGBA,
45   GDK_GL_DOUBLEBUFFER,
46   GDK_GL_RED_SIZE, 8,
47   GDK_GL_BLUE_SIZE, 8,
48   GDK_GL_GREEN_SIZE, 8,
49   GDK_GL_ALPHA_SIZE, 8,
50   GDK_GL_ATTRIB_LIST_NONE,
51 };
52
53 int config_rgba[] = {
54   GDK_GL_RGBA,
55   GDK_GL_DOUBLEBUFFER,
56   GDK_GL_RED_SIZE, 1,
57   GDK_GL_BLUE_SIZE, 1,
58   GDK_GL_GREEN_SIZE, 1,
59   GDK_GL_ALPHA_SIZE, 1,
60   GDK_GL_ATTRIB_LIST_NONE,
61 };
62
63 const attribs_t configs[] = {
64   config_rgba32,
65   config_rgba,
66 };
67
68 GdkGLConfig* glconfig_new()
69 {
70   GdkGLConfig* glconfig = NULL;
71
72   for(configs_iterator i = configs, end = configs + 2; i != end && glconfig == NULL; ++i)
73   {
74     glconfig = gdk_gl_config_new(*i);
75   }
76
77   if(glconfig == NULL)
78   {
79     return gdk_gl_config_new_by_mode((GdkGLConfigMode)(GDK_GL_MODE_RGBA | GDK_GL_MODE_DOUBLE));
80   }
81
82   return glconfig;
83 }
84
85 int config_rgba32_depth32[] = {
86   GDK_GL_RGBA,
87   GDK_GL_DOUBLEBUFFER,
88   GDK_GL_RED_SIZE, 8,
89   GDK_GL_BLUE_SIZE, 8,
90   GDK_GL_GREEN_SIZE, 8,
91   GDK_GL_ALPHA_SIZE, 8,
92   GDK_GL_DEPTH_SIZE, 32,
93   GDK_GL_ATTRIB_LIST_NONE,
94 };
95
96 int config_rgba32_depth24[] = {
97   GDK_GL_RGBA,
98   GDK_GL_DOUBLEBUFFER,
99   GDK_GL_RED_SIZE, 8,
100   GDK_GL_BLUE_SIZE, 8,
101   GDK_GL_GREEN_SIZE, 8,
102   GDK_GL_ALPHA_SIZE, 8,
103   GDK_GL_DEPTH_SIZE, 24,
104   GDK_GL_ATTRIB_LIST_NONE,
105 };
106
107 int config_rgba32_depth16[] = {
108   GDK_GL_RGBA,
109   GDK_GL_DOUBLEBUFFER,
110   GDK_GL_RED_SIZE, 8,
111   GDK_GL_BLUE_SIZE, 8,
112   GDK_GL_GREEN_SIZE, 8,
113   GDK_GL_ALPHA_SIZE, 8,
114   GDK_GL_DEPTH_SIZE, 16,
115   GDK_GL_ATTRIB_LIST_NONE,
116 };
117
118 int config_rgba32_depth[] = {
119   GDK_GL_RGBA,
120   GDK_GL_DOUBLEBUFFER,
121   GDK_GL_RED_SIZE, 8,
122   GDK_GL_BLUE_SIZE, 8,
123   GDK_GL_GREEN_SIZE, 8,
124   GDK_GL_ALPHA_SIZE, 8,
125   GDK_GL_DEPTH_SIZE, 1,
126   GDK_GL_ATTRIB_LIST_NONE,
127 };
128
129 int config_rgba_depth16[] = {
130   GDK_GL_RGBA,
131   GDK_GL_DOUBLEBUFFER,
132   GDK_GL_RED_SIZE, 1,
133   GDK_GL_BLUE_SIZE, 1,
134   GDK_GL_GREEN_SIZE, 1,
135   GDK_GL_ALPHA_SIZE, 1,
136   GDK_GL_DEPTH_SIZE, 16,
137   GDK_GL_ATTRIB_LIST_NONE,
138 };
139
140 int config_rgba_depth[] = {
141   GDK_GL_RGBA,
142   GDK_GL_DOUBLEBUFFER,
143   GDK_GL_RED_SIZE, 1,
144   GDK_GL_BLUE_SIZE, 1,
145   GDK_GL_GREEN_SIZE, 1,
146   GDK_GL_ALPHA_SIZE, 1,
147   GDK_GL_DEPTH_SIZE, 1,
148   GDK_GL_ATTRIB_LIST_NONE,
149 };
150
151 const attribs_t configs_with_depth[] =
152 {
153   config_rgba32_depth32,
154   config_rgba32_depth24,
155   config_rgba32_depth16,
156   config_rgba32_depth,
157   config_rgba_depth16,
158   config_rgba_depth,
159 };
160
161 GdkGLConfig* glconfig_new_with_depth()
162 {
163   GdkGLConfig* glconfig = NULL;
164
165   for(configs_iterator i = configs_with_depth, end = configs_with_depth + 6; i != end && glconfig == NULL; ++i)
166   {
167     glconfig = gdk_gl_config_new(*i);
168   }
169
170   if(glconfig == NULL)
171   {
172     return gdk_gl_config_new_by_mode((GdkGLConfigMode)(GDK_GL_MODE_RGBA | GDK_GL_MODE_DOUBLE | GDK_GL_MODE_DEPTH));
173   }
174
175   return glconfig;
176 }
177
178 GtkWidget* WINAPI gtk_glwidget_new (gboolean zbuffer, GtkWidget* share)
179 {
180   GtkWidget* drawing_area = gtk_drawing_area_new();
181   GdkGLConfig* glconfig = (zbuffer) ? glconfig_new_with_depth() : glconfig_new();
182   GdkGLContext* shared_context = (share) ? gtk_widget_get_gl_context(share) : NULL;
183
184   gtk_widget_set_gl_capability (drawing_area, glconfig, shared_context, TRUE, GDK_GL_RGBA_TYPE);
185
186   return drawing_area;
187 }
188
189 void WINAPI gtk_glwidget_destroy_context (GtkWidget *widget)
190 {
191 }
192
193 void WINAPI gtk_glwidget_create_context (GtkWidget *widget)
194 {
195 }
196
197 void WINAPI gtk_glwidget_swap_buffers (GtkWidget *widget)
198 {
199   GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable (widget);
200   gdk_gl_drawable_swap_buffers (gldrawable);
201 }
202
203 gboolean WINAPI gtk_glwidget_make_current (GtkWidget *widget)
204 {
205   GdkGLContext *glcontext = gtk_widget_get_gl_context (widget);
206   GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable (widget);
207   return gdk_gl_drawable_gl_begin (gldrawable, glcontext);
208 }
209
210
211 // Think about rewriting this font stuff to use OpenGL display lists and bit maps.
212 // Bit maps together with display lists may offer a performance increase, but
213 // they would not allow antialiased fonts.
214 static const char font_string[] = "Monospace";
215 static const int font_height = 10;
216 static int font_ascent = -1;
217 static int font_descent = -1;
218 static int y_offset_bitmap_render_pango_units = -1;
219 static PangoContext *ft2_context = NULL;
220 static int _debug_font_created = 0;
221
222
223 // Units are pixels.  Returns a positive value [most likely].
224 int gtk_glwidget_font_ascent()
225 {
226   if (!_debug_font_created) {
227     Error("Programming error: gtk_glwidget_font_ascent() called but font does not exist; "
228           "you should have called gtk_glwidget_create_font() first");
229   }
230
231   return font_ascent;
232 }
233
234 // Units are pixels.  Returns a positive value [most likely].
235 int gtk_glwidget_font_descent()
236 {
237   if (!_debug_font_created) {
238     Error("Programming error: gtk_glwidget_font_descent() called but font does not exist; "
239           "you should have called gtk_glwidget_create_font() first");
240   }
241
242   return font_descent;
243 }
244
245 void gtk_glwidget_create_font()
246 {
247   PangoFontDescription *font_desc;
248   PangoLayout *layout;
249   PangoRectangle log_rect;
250   int font_ascent_pango_units;
251   int font_descent_pango_units;
252
253   if (_debug_font_created) {
254     Error("Programming error: gtk_glwidget_create_font() was already called; "
255           "you must call gtk_glwidget_destroy_font() before creating font again");
256   }
257   _debug_font_created = 1;
258
259   // This call is deprecated so we'll have to fix it sometime.
260   ft2_context = pango_ft2_get_context(72, 72);
261
262   font_desc = pango_font_description_from_string(font_string);
263   pango_font_description_set_size(font_desc, font_height * PANGO_SCALE);
264   pango_context_set_font_description(ft2_context, font_desc);
265   pango_font_description_free(font_desc);
266
267   layout = pango_layout_new(ft2_context);
268 #if !PANGO_VERSION_CHECK(1,22,0)
269   PangoLayoutIter *iter;  
270   iter = pango_layout_get_iter(layout);
271   font_ascent_pango_units = pango_layout_iter_get_baseline(iter);
272   pango_layout_iter_free(iter);
273 #else
274   font_ascent_pango_units = pango_layout_get_baseline(layout);
275 #endif
276   pango_layout_get_extents(layout, NULL, &log_rect);
277   g_object_unref(G_OBJECT(layout));
278   font_descent_pango_units = log_rect.height - font_ascent_pango_units;
279
280   font_ascent = PANGO_PIXELS_CEIL(font_ascent_pango_units);
281   font_descent = PANGO_PIXELS_CEIL(font_descent_pango_units);
282   y_offset_bitmap_render_pango_units = (font_ascent * PANGO_SCALE) - font_ascent_pango_units;
283 }
284
285 void gtk_glwidget_destroy_font()
286 {
287   if (!_debug_font_created) {
288     Error("Programming error: gtk_glwidget_destroy_font() called when font "
289           "does not exist");
290   }
291
292   font_ascent = -1;
293   font_descent = -1;
294   y_offset_bitmap_render_pango_units = -1;
295   g_object_unref(G_OBJECT(ft2_context));
296   _debug_font_created = 0;
297 }
298
299
300 // Renders the input text at the current location with the current color.
301 // The X position of the current location is used to place the left edge of the text image,
302 // where the text image bounds are defined as the logical extents of the line of text.
303 // The Y position of the current location is used to place the bottom of the text image.
304 // You should offset the Y position by the amount returned by gtk_glwidget_font_descent()
305 // if you want to place the baseline of the text image at the current Y position.
306 // Note: A problem with this function is that if the lower left corner of the text falls
307 // just a hair outside of the viewport (meaning the current raster position is invalid),
308 // then no text will be rendered.  The solution to this is a very hacky one.  You can search
309 // Google for "glDrawPixels clipping".
310 void gtk_glwidget_print_string(const char *s)
311 {
312   // Much of this code is copied from the font-pangoft2.c example that comes with GtkGLExt.
313
314   PangoLayout *layout;
315   PangoRectangle ink_rect;
316   PangoRectangle log_rect;
317   FT_Bitmap bitmap;
318   GLvoid *pixels;
319   guint32 *p;
320   GLfloat color[4];
321   guint32 rgb;
322   GLfloat alpha;
323   guint8 *row, *row_end;
324   int i;
325   GLint previous_unpack_alignment;
326   GLboolean previous_blend_enabled;
327   GLint previous_blend_src;
328   GLint previous_blend_dst;
329
330
331   if (!_debug_font_created) {
332     Error("Programming error: gtk_glwidget_print_string() called but font does not exist; "
333           "you should have called gtk_glwidget_create_font() first");
334   }
335
336   layout = pango_layout_new(ft2_context);
337   pango_layout_set_width(layout, -1); // -1 no wrapping.  All text on one line.
338   pango_layout_set_text(layout, s, -1); // -1 null-terminated string.
339   pango_layout_get_extents(layout, &ink_rect, &log_rect);
340
341   if (log_rect.width > 0 && log_rect.height > 0) {
342     bitmap.rows = font_ascent + font_descent;
343     bitmap.width = PANGO_PIXELS_CEIL(log_rect.width);
344     bitmap.pitch = bitmap.width;
345     bitmap.buffer = g_malloc(bitmap.rows * bitmap.width);
346     bitmap.num_grays = 0xff;
347     bitmap.pixel_mode = FT_PIXEL_MODE_GRAY;
348     memset(bitmap.buffer, 0, bitmap.rows * bitmap.width);
349     pango_ft2_render_layout_subpixel(&bitmap, layout, -log_rect.x,
350                                      y_offset_bitmap_render_pango_units);
351
352     pixels = g_malloc(bitmap.rows * bitmap.width * 4);
353     p = (guint32 *) pixels;
354     qglGetFloatv(GL_CURRENT_COLOR, color);
355 #if !defined(GL_VERSION_1_2) && G_BYTE_ORDER == G_LITTLE_ENDIAN
356     rgb =
357       (((guint32) (color[0] * 255.0)) << 0) |
358       (((guint32) (color[1] * 255.0)) << 8) |
359       (((guint32) (color[2] * 255.0)) << 16);
360 #else
361     rgb =
362       (((guint32) (color[0] * 255.0)) << 24) |
363       (((guint32) (color[1] * 255.0)) << 16) |
364       (((guint32) (color[2] * 255.0)) << 8);
365 #endif
366     alpha = color[3];
367
368     row = bitmap.buffer + bitmap.rows * bitmap.width; // Past the end.
369     row_end = bitmap.buffer; // Beginning.
370
371     if (alpha == 1.0) {
372       do {
373         row -= bitmap.width;
374         for (i = 0; i < bitmap.width; i++) {
375 #if !defined(GL_VERSION_1_2) && G_BYTE_ORDER == G_LITTLE_ENDIAN
376           *p++ = rgb | (((guint32) row[i]) << 24);
377 #else
378           *p++ = rgb | ((guint32) row[i]);
379 #endif
380         }
381       } while (row != row_end);
382     }
383
384     else { // Translucent.  Much less efficient, so try to avoid.
385       do {
386         row -= bitmap.width;
387         for (i = 0; i < bitmap.width; i++) {
388 #if !defined(GL_VERSION_1_2) && G_BYTE_ORDER == G_LITTLE_ENDIAN
389           *p++ = rgb | (((guint32) (alpha * row[i])) << 24);
390 #else
391           *p++ = rgb | ((guint32) (alpha * row[i]));
392 #endif
393         }
394       } while (row != row_end);
395     }
396
397     // Save state.  I didn't see any OpenGL push/pop operations for these.
398     // Question: Is saving/restoring this state necessary?
399     qglGetIntegerv(GL_UNPACK_ALIGNMENT, &previous_unpack_alignment);
400     previous_blend_enabled = qglIsEnabled(GL_BLEND);
401     qglGetIntegerv(GL_BLEND_SRC, &previous_blend_src);
402     qglGetIntegerv(GL_BLEND_DST, &previous_blend_dst);
403
404     qglPixelStorei(GL_UNPACK_ALIGNMENT, 4);
405     qglEnable(GL_BLEND);
406     qglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
407
408 #if !defined(GL_VERSION_1_2)
409     qglDrawPixels(bitmap.width, bitmap.rows,
410                   GL_RGBA, GL_UNSIGNED_BYTE, pixels);
411 #else
412     qglDrawPixels(bitmap.width, bitmap.rows,
413                   GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, pixels);
414 #endif
415
416     // Restore state in reverse order of how we set it.
417     qglBlendFunc(previous_blend_src, previous_blend_dst);
418     if (!previous_blend_enabled) { qglDisable(GL_BLEND); }
419     qglPixelStorei(GL_UNPACK_ALIGNMENT, previous_unpack_alignment);
420
421     g_free(bitmap.buffer);
422     g_free(pixels);
423   }
424
425   g_object_unref(G_OBJECT(layout));
426 }
427
428 void gtk_glwidget_print_char(char s)
429 {
430   char str[2];
431   str[0] = s;
432   str[1] = '\0';
433   gtk_glwidget_print_string(str);
434 }