]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/glwidget.cpp
uncrustify! now the code is only ugly on the *inside*
[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
37 #include <pango/pangoft2.h>
38
39 #include "glwidget.h"
40 #include "qgl.h"
41
42 typedef int* attribs_t;
43 typedef const attribs_t* configs_iterator;
44
45 int config_rgba32[] = {
46         GDK_GL_RGBA,
47         GDK_GL_DOUBLEBUFFER,
48         GDK_GL_RED_SIZE, 8,
49         GDK_GL_BLUE_SIZE, 8,
50         GDK_GL_GREEN_SIZE, 8,
51         GDK_GL_ALPHA_SIZE, 8,
52         GDK_GL_ATTRIB_LIST_NONE,
53 };
54
55 int config_rgba[] = {
56         GDK_GL_RGBA,
57         GDK_GL_DOUBLEBUFFER,
58         GDK_GL_RED_SIZE, 1,
59         GDK_GL_BLUE_SIZE, 1,
60         GDK_GL_GREEN_SIZE, 1,
61         GDK_GL_ALPHA_SIZE, 1,
62         GDK_GL_ATTRIB_LIST_NONE,
63 };
64
65 const attribs_t configs[] = {
66         config_rgba32,
67         config_rgba,
68 };
69
70 GdkGLConfig* glconfig_new(){
71         GdkGLConfig* glconfig = NULL;
72
73         for ( configs_iterator i = configs, end = configs + 2; i != end && glconfig == NULL; ++i )
74         {
75                 glconfig = gdk_gl_config_new( *i );
76         }
77
78         if ( glconfig == NULL ) {
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         GdkGLConfig* glconfig = NULL;
163
164         for ( configs_iterator i = configs_with_depth, end = configs_with_depth + 6; i != end && glconfig == NULL; ++i )
165         {
166                 glconfig = gdk_gl_config_new( *i );
167         }
168
169         if ( glconfig == NULL ) {
170                 return gdk_gl_config_new_by_mode( (GdkGLConfigMode)( GDK_GL_MODE_RGBA | GDK_GL_MODE_DOUBLE | GDK_GL_MODE_DEPTH ) );
171         }
172
173         return glconfig;
174 }
175
176 GtkWidget* WINAPI gtk_glwidget_new( gboolean zbuffer, GtkWidget* share ){
177         GtkWidget* drawing_area = gtk_drawing_area_new();
178         GdkGLConfig* glconfig = ( zbuffer ) ? glconfig_new_with_depth() : glconfig_new();
179         GdkGLContext* shared_context = ( share ) ? gtk_widget_get_gl_context( share ) : NULL;
180
181         gtk_widget_set_gl_capability( drawing_area, glconfig, shared_context, TRUE, GDK_GL_RGBA_TYPE );
182
183         return drawing_area;
184 }
185
186 void WINAPI gtk_glwidget_destroy_context( GtkWidget *widget ){
187 }
188
189 void WINAPI gtk_glwidget_create_context( GtkWidget *widget ){
190 }
191
192 void WINAPI gtk_glwidget_swap_buffers( GtkWidget *widget ){
193         GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable( widget );
194         gdk_gl_drawable_swap_buffers( gldrawable );
195 }
196
197 gboolean WINAPI gtk_glwidget_make_current( GtkWidget *widget ){
198         GdkGLContext *glcontext = gtk_widget_get_gl_context( widget );
199         GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable( widget );
200         return gdk_gl_drawable_gl_begin( gldrawable, glcontext );
201 }
202
203
204 // Think about rewriting this font stuff to use OpenGL display lists and glBitmap().
205 // Bit maps together with display lists may offer a performance increase, but
206 // they would not allow antialiased fonts.
207 #ifdef _WIN32
208 static const char font_string[] = "Courier bold";
209 static const int font_height = 9;
210 #else
211 static const char font_string[] = "Monospace";
212 static const int font_height = 10;
213 #endif
214 static int font_ascent = -1;
215 static int font_descent = -1;
216 static int y_offset_bitmap_render_pango_units = -1;
217 static PangoContext *ft2_context = NULL;
218 static int _debug_font_created = 0;
219
220
221 // Units are pixels.  Returns a positive value [most likely].
222 int gtk_glwidget_font_ascent(){
223         if ( !_debug_font_created ) {
224                 Error( "Programming error: gtk_glwidget_font_ascent() called but font does not exist; "
225                            "you should have called gtk_glwidget_create_font() first" );
226         }
227
228         return font_ascent;
229 }
230
231 // Units are pixels.  Returns a positive value [most likely].
232 int gtk_glwidget_font_descent(){
233         if ( !_debug_font_created ) {
234                 Error( "Programming error: gtk_glwidget_font_descent() called but font does not exist; "
235                            "you should have called gtk_glwidget_create_font() first" );
236         }
237
238         return font_descent;
239 }
240
241
242 void gtk_glwidget_create_font(){
243         PangoFontDescription *font_desc;
244         PangoLayout *layout;
245         PangoRectangle log_rect;
246         int font_ascent_pango_units;
247         int font_descent_pango_units;
248
249         if ( _debug_font_created ) {
250                 Error( "Programming error: gtk_glwidget_create_font() was already called; "
251                            "you must call gtk_glwidget_destroy_font() before creating font again" );
252         }
253         _debug_font_created = 1;
254
255         // This call is deprecated so we'll have to fix it sometime.
256         ft2_context = pango_ft2_get_context( 72, 72 );
257
258         font_desc = pango_font_description_from_string( font_string );
259         pango_font_description_set_size( font_desc, font_height * PANGO_SCALE );
260         pango_context_set_font_description( ft2_context, font_desc );
261         pango_font_description_free( font_desc );
262
263         layout = pango_layout_new( ft2_context );
264
265         // I don't believe that's standard preprocessor syntax?
266 #if !PANGO_VERSION_CHECK( 1,22,0 )
267         PangoLayoutIter *iter;
268         iter = pango_layout_get_iter( layout );
269         font_ascent_pango_units = pango_layout_iter_get_baseline( iter );
270         pango_layout_iter_free( iter );
271 #else
272         font_ascent_pango_units = pango_layout_get_baseline( layout );
273 #endif
274
275         pango_layout_get_extents( layout, NULL, &log_rect );
276         g_object_unref( G_OBJECT( layout ) );
277         font_descent_pango_units = log_rect.height - font_ascent_pango_units;
278
279         font_ascent = PANGO_PIXELS_CEIL( font_ascent_pango_units );
280         font_descent = PANGO_PIXELS_CEIL( font_descent_pango_units );
281         y_offset_bitmap_render_pango_units = ( font_ascent * PANGO_SCALE ) - font_ascent_pango_units;
282 }
283
284 void gtk_glwidget_destroy_font(){
285         if ( !_debug_font_created ) {
286                 Error( "Programming error: gtk_glwidget_destroy_font() called when font "
287                            "does not exist" );
288         }
289
290         font_ascent = -1;
291         font_descent = -1;
292         y_offset_bitmap_render_pango_units = -1;
293         g_object_unref( G_OBJECT( ft2_context ) );
294         _debug_font_created = 0;
295 }
296
297
298 // Renders the input text at the current location with the current color.
299 // The X position of the current location is used to place the left edge of the text image,
300 // where the text image bounds are defined as the logical extents of the line of text.
301 // The Y position of the current location is used to place the bottom of the text image.
302 // You should offset the Y position by the amount returned by gtk_glwidget_font_descent()
303 // if you want to place the baseline of the text image at the current Y position.
304 // Note: A problem with this function is that if the lower left corner of the text falls
305 // just a hair outside of the viewport (meaning the current raster position is invalid),
306 // then no text will be rendered.  The solution to this is a very hacky one.  You can search
307 // Google for "glDrawPixels clipping".
308 void gtk_glwidget_print_string( const char *s ){
309         // The idea for this code initially came from the font-pangoft2.c example that comes with GtkGLExt.
310
311         PangoLayout *layout;
312         PangoRectangle log_rect;
313         FT_Bitmap bitmap;
314         unsigned char *begin_bitmap_buffer;
315         GLfloat color[4];
316         GLint previous_unpack_alignment;
317         GLboolean previous_blend_enabled;
318         GLint previous_blend_func_src;
319         GLint previous_blend_func_dst;
320         GLfloat previous_red_bias;
321         GLfloat previous_green_bias;
322         GLfloat previous_blue_bias;
323         GLfloat previous_alpha_scale;
324
325         if ( !_debug_font_created ) {
326                 Error( "Programming error: gtk_glwidget_print_string() called but font does not exist; "
327                            "you should have called gtk_glwidget_create_font() first" );
328         }
329
330         layout = pango_layout_new( ft2_context );
331         pango_layout_set_width( layout, -1 ); // -1 no wrapping.  All text on one line.
332         pango_layout_set_text( layout, s, -1 ); // -1 null-terminated string.
333         pango_layout_get_extents( layout, NULL, &log_rect );
334
335         if ( log_rect.width > 0 && log_rect.height > 0 ) {
336                 bitmap.rows = font_ascent + font_descent;
337                 bitmap.width = PANGO_PIXELS_CEIL( log_rect.width );
338                 bitmap.pitch = -bitmap.width; // Rendering it "upside down" for OpenGL.
339                 begin_bitmap_buffer = (unsigned char *) g_malloc( bitmap.rows * bitmap.width );
340                 memset( begin_bitmap_buffer, 0, bitmap.rows * bitmap.width );
341                 bitmap.buffer = begin_bitmap_buffer + ( bitmap.rows - 1 ) * bitmap.width; // See pitch above.
342                 bitmap.num_grays = 0xff;
343                 bitmap.pixel_mode = FT_PIXEL_MODE_GRAY;
344                 pango_ft2_render_layout_subpixel( &bitmap, layout, -log_rect.x,
345                                                                                   y_offset_bitmap_render_pango_units );
346                 qglGetFloatv( GL_CURRENT_COLOR, color );
347
348                 // Save state.  I didn't see any OpenGL push/pop operations for these.
349                 // Question: Is saving/restoring this state necessary?  Being safe.
350                 qglGetIntegerv( GL_UNPACK_ALIGNMENT, &previous_unpack_alignment );
351                 previous_blend_enabled = qglIsEnabled( GL_BLEND );
352                 qglGetIntegerv( GL_BLEND_SRC, &previous_blend_func_src );
353                 qglGetIntegerv( GL_BLEND_DST, &previous_blend_func_dst );
354                 qglGetFloatv( GL_RED_BIAS, &previous_red_bias );
355                 qglGetFloatv( GL_GREEN_BIAS, &previous_green_bias );
356                 qglGetFloatv( GL_BLUE_BIAS, &previous_blue_bias );
357                 qglGetFloatv( GL_ALPHA_SCALE, &previous_alpha_scale );
358
359                 qglPixelStorei( GL_UNPACK_ALIGNMENT, 1 );
360                 qglEnable( GL_BLEND );
361                 qglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
362                 qglPixelTransferf( GL_RED_BIAS, color[0] );
363                 qglPixelTransferf( GL_GREEN_BIAS, color[1] );
364                 qglPixelTransferf( GL_BLUE_BIAS, color[2] );
365                 qglPixelTransferf( GL_ALPHA_SCALE, color[3] );
366
367                 qglDrawPixels( bitmap.width, bitmap.rows,
368                                            GL_ALPHA, GL_UNSIGNED_BYTE, begin_bitmap_buffer );
369                 g_free( begin_bitmap_buffer );
370
371                 // Restore state in reverse order of how we set it.
372                 qglPixelTransferf( GL_ALPHA_SCALE, previous_alpha_scale );
373                 qglPixelTransferf( GL_BLUE_BIAS, previous_blue_bias );
374                 qglPixelTransferf( GL_GREEN_BIAS, previous_green_bias );
375                 qglPixelTransferf( GL_RED_BIAS, previous_red_bias );
376                 qglBlendFunc( previous_blend_func_src, previous_blend_func_dst );
377                 if ( !previous_blend_enabled ) {
378                         qglDisable( GL_BLEND );
379                 }
380                 qglPixelStorei( GL_UNPACK_ALIGNMENT, previous_unpack_alignment );
381         }
382
383         g_object_unref( G_OBJECT( layout ) );
384 }
385
386 void gtk_glwidget_print_char( char s ){
387         char str[2];
388         str[0] = s;
389         str[1] = '\0';
390         gtk_glwidget_print_string( str );
391 }