apply back 335 and 336, will need to update deps and put a zip out
[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 glBitmap().
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
269 #ifdef _WIN32
270
271   PangoLayoutIter *iter;  
272   iter = pango_layout_get_iter(layout);
273   font_ascent_pango_units = pango_layout_iter_get_baseline(iter);
274   pango_layout_iter_free(iter);
275
276 #else
277
278   // I don't believe that's standard preprocessor syntax?
279 #if !PANGO_VERSION_CHECK(1,22,0)
280   PangoLayoutIter *iter;  
281   iter = pango_layout_get_iter(layout);
282   font_ascent_pango_units = pango_layout_iter_get_baseline(iter);
283   pango_layout_iter_free(iter);
284 #else
285   font_ascent_pango_units = pango_layout_get_baseline(layout);
286 #endif
287
288 #endif
289
290   pango_layout_get_extents(layout, NULL, &log_rect);
291   g_object_unref(G_OBJECT(layout));
292   font_descent_pango_units = log_rect.height - font_ascent_pango_units;
293
294   font_ascent = PANGO_PIXELS_CEIL(font_ascent_pango_units);
295   font_descent = PANGO_PIXELS_CEIL(font_descent_pango_units);
296   y_offset_bitmap_render_pango_units = (font_ascent * PANGO_SCALE) - font_ascent_pango_units;
297 }
298
299 void gtk_glwidget_destroy_font()
300 {
301   if (!_debug_font_created) {
302     Error("Programming error: gtk_glwidget_destroy_font() called when font "
303           "does not exist");
304   }
305
306   font_ascent = -1;
307   font_descent = -1;
308   y_offset_bitmap_render_pango_units = -1;
309   g_object_unref(G_OBJECT(ft2_context));
310   _debug_font_created = 0;
311 }
312
313
314 // Renders the input text at the current location with the current color.
315 // The X position of the current location is used to place the left edge of the text image,
316 // where the text image bounds are defined as the logical extents of the line of text.
317 // The Y position of the current location is used to place the bottom of the text image.
318 // You should offset the Y position by the amount returned by gtk_glwidget_font_descent()
319 // if you want to place the baseline of the text image at the current Y position.
320 // Note: A problem with this function is that if the lower left corner of the text falls
321 // just a hair outside of the viewport (meaning the current raster position is invalid),
322 // then no text will be rendered.  The solution to this is a very hacky one.  You can search
323 // Google for "glDrawPixels clipping".
324 void gtk_glwidget_print_string(const char *s)
325 {
326   // The idea for this code initially came from the font-pangoft2.c example that comes with GtkGLExt.
327
328   PangoLayout *layout;
329   PangoRectangle log_rect;
330   FT_Bitmap bitmap;
331   unsigned char *begin_bitmap_buffer;
332   GLfloat color[4];
333   GLint previous_unpack_alignment;
334   GLboolean previous_blend_enabled;
335   GLint previous_blend_func_src;
336   GLint previous_blend_func_dst;
337   GLfloat previous_red_bias;
338   GLfloat previous_green_bias;
339   GLfloat previous_blue_bias;
340   GLfloat previous_alpha_scale;
341
342   if (!_debug_font_created) {
343     Error("Programming error: gtk_glwidget_print_string() called but font does not exist; "
344           "you should have called gtk_glwidget_create_font() first");
345   }
346
347   layout = pango_layout_new(ft2_context);
348   pango_layout_set_width(layout, -1); // -1 no wrapping.  All text on one line.
349   pango_layout_set_text(layout, s, -1); // -1 null-terminated string.
350   pango_layout_get_extents(layout, NULL, &log_rect);
351
352   if (log_rect.width > 0 && log_rect.height > 0) {
353     bitmap.rows = font_ascent + font_descent;
354     bitmap.width = PANGO_PIXELS_CEIL(log_rect.width);
355     bitmap.pitch = -bitmap.width; // Rendering it "upside down" for OpenGL.
356     begin_bitmap_buffer = (unsigned char *) g_malloc(bitmap.rows * bitmap.width);
357     memset(begin_bitmap_buffer, 0, bitmap.rows * bitmap.width);
358     bitmap.buffer = begin_bitmap_buffer + (bitmap.rows - 1) * bitmap.width; // See pitch above.
359     bitmap.num_grays = 0xff;
360     bitmap.pixel_mode = FT_PIXEL_MODE_GRAY;
361     pango_ft2_render_layout_subpixel(&bitmap, layout, -log_rect.x,
362                                      y_offset_bitmap_render_pango_units);
363     qglGetFloatv(GL_CURRENT_COLOR, color);
364
365     // Save state.  I didn't see any OpenGL push/pop operations for these.
366     // Question: Is saving/restoring this state necessary?  Being safe.
367     qglGetIntegerv(GL_UNPACK_ALIGNMENT, &previous_unpack_alignment);
368     previous_blend_enabled = qglIsEnabled(GL_BLEND);
369     qglGetIntegerv(GL_BLEND_SRC, &previous_blend_func_src);
370     qglGetIntegerv(GL_BLEND_DST, &previous_blend_func_dst);
371     qglGetFloatv(GL_RED_BIAS, &previous_red_bias);
372     qglGetFloatv(GL_GREEN_BIAS, &previous_green_bias);
373     qglGetFloatv(GL_BLUE_BIAS, &previous_blue_bias);
374     qglGetFloatv(GL_ALPHA_SCALE, &previous_alpha_scale);
375
376     qglPixelStorei(GL_UNPACK_ALIGNMENT, 1);
377     qglEnable(GL_BLEND);
378     qglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
379     qglPixelTransferf(GL_RED_BIAS, color[0]);
380     qglPixelTransferf(GL_GREEN_BIAS, color[1]);
381     qglPixelTransferf(GL_BLUE_BIAS, color[2]);
382     qglPixelTransferf(GL_ALPHA_SCALE, color[3]);
383
384     qglDrawPixels(bitmap.width, bitmap.rows,
385                   GL_ALPHA, GL_UNSIGNED_BYTE, begin_bitmap_buffer);
386     g_free(begin_bitmap_buffer);
387
388     // Restore state in reverse order of how we set it.
389     qglPixelTransferf(GL_ALPHA_SCALE, previous_alpha_scale);
390     qglPixelTransferf(GL_BLUE_BIAS, previous_blue_bias);
391     qglPixelTransferf(GL_GREEN_BIAS, previous_green_bias);
392     qglPixelTransferf(GL_RED_BIAS, previous_red_bias);
393     qglBlendFunc(previous_blend_func_src, previous_blend_func_dst);
394     if (!previous_blend_enabled) { qglDisable(GL_BLEND); }
395     qglPixelStorei(GL_UNPACK_ALIGNMENT, previous_unpack_alignment);
396   }
397
398   g_object_unref(G_OBJECT(layout));
399 }
400
401 void gtk_glwidget_print_char(char s)
402 {
403   char str[2];
404   str[0] = s;
405   str[1] = '\0';
406   gtk_glwidget_print_string(str);
407 }