From c1dbb77342cefd57c7dbbd2eb3ee8fee031b85d5 Mon Sep 17 00:00:00 2001 From: rambetter Date: Tue, 16 Nov 2010 03:42:28 +0000 Subject: [PATCH] DONE: - Removed usage of gdk_gl_font_use_pango_font() which is no longer in GtkGLExt Git. Radiant now compiles on Linux From Scratch using current versions of software. - As a side effect to the above, font inconsistency issues (like really large intermittent font in GL views) are gone. Font looks better too. - Changing algorithm for labeling grid view to be more robust. Uses new functions gtk_glwidget_font_ascent() and gtk_glwidget_font_descent(), and uses locally defined "cushion" variables. - In xywindow.cpp, changing stepx and stepy based on 40 pixels, not 32. When coordinate numbers are very large the labels get too cluttered. - Added calls to gtk_gl_init() and gdk_gl_init() in main(). This is recommended according to the GtkGLExt reference manual. - Tested all changes on Ubuntu 10.10 and Debian 5.0 (Lenny). TODO: - In glDrawPixels(), instead of using a 32 bit pixel with GL_UNSIGNED_INT_8_8_8_8, see if we can use an 8 bit variant where each byte defines opacity and the GL current color is used. In other words, try to use the FT_Bitmap directly without conversion. - Examine every other use of gtk_glwidget_print_string() and gtk_glwidget_print_char() to make sure the positions are determined accurately. NOT TODO: - Decided not to use glBitmap() with display lists because it would disallow pretty antialiased fonts. git-svn-id: svn://svn.icculus.org/gtkradiant/GtkRadiant/trunk@335 8a3a26a2-13c4-0310-b231-cf6edde360e5 --- radiant/camwindow.cpp | 2 - radiant/glwidget.cpp | 241 +++++++++++++++++++++++++++++++++++++----- radiant/glwidget.h | 7 +- radiant/main.cpp | 7 ++ radiant/mainframe.cpp | 3 - radiant/xywindow.cpp | 52 ++++++--- 6 files changed, 260 insertions(+), 52 deletions(-) diff --git a/radiant/camwindow.cpp b/radiant/camwindow.cpp index 35c2bde5..81128dfc 100644 --- a/radiant/camwindow.cpp +++ b/radiant/camwindow.cpp @@ -62,8 +62,6 @@ void CamWnd::OnCreate () if (!MakeCurrent ()) Error ("glMakeCurrent failed"); - gtk_glwidget_create_font (m_pWidget); - // report OpenGL information Sys_Printf ("GL_VENDOR: %s\n", qglGetString (GL_VENDOR)); Sys_Printf ("GL_RENDERER: %s\n", qglGetString (GL_RENDERER)); diff --git a/radiant/glwidget.cpp b/radiant/glwidget.cpp index ab9f0b9d..a02083c3 100644 --- a/radiant/glwidget.cpp +++ b/radiant/glwidget.cpp @@ -33,6 +33,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "stdafx.h" #include +#include #include "glwidget.h" #include "qgl.h" @@ -206,46 +207,228 @@ gboolean WINAPI gtk_glwidget_make_current (GtkWidget *widget) return gdk_gl_drawable_gl_begin (gldrawable, glcontext); } -GLuint font_list_base; -static gchar font_string[] = "courier 8"; -static gint font_height; -void gtk_glwidget_create_font (GtkWidget *widget) +// Think about rewriting this font stuff to use OpenGL display lists and bit maps. +// Bit maps together with display lists may offer a performance increase, but +// they would not allow antialiased fonts. +static const char font_string[] = "Monospace"; +static const int font_height = 10; +static int font_ascent = -1; +static int font_descent = -1; +static int y_offset_bitmap_render_pango_units = -1; +static PangoContext *ft2_context = NULL; +static int _debug_font_created = 0; + + +// Units are pixels. Returns a positive value [most likely]. +int gtk_glwidget_font_ascent() { - PangoFontDescription *font_desc; - PangoFont *font; - PangoFontMetrics *font_metrics; - - font_list_base = qglGenLists (256); - - font_desc = pango_font_description_from_string (font_string); - - font = gdk_gl_font_use_pango_font (font_desc, 0, 256, font_list_base); - - if(font != NULL) - { - font_metrics = pango_font_get_metrics (font, NULL); - - font_height = pango_font_metrics_get_ascent (font_metrics) + - pango_font_metrics_get_descent (font_metrics); - font_height = PANGO_PIXELS (font_height); - - pango_font_metrics_unref (font_metrics); + if (!_debug_font_created) { + Error("Programming error: gtk_glwidget_font_ascent() called but font does not exist; " + "you should have called gtk_glwidget_create_font() first"); } - pango_font_description_free (font_desc); + return font_ascent; +} + +// Units are pixels. Returns a positive value [most likely]. +int gtk_glwidget_font_descent() +{ + if (!_debug_font_created) { + Error("Programming error: gtk_glwidget_font_descent() called but font does not exist; " + "you should have called gtk_glwidget_create_font() first"); + } + + return font_descent; +} + +void gtk_glwidget_create_font() +{ + PangoFontDescription *font_desc; + PangoLayout *layout; + PangoRectangle log_rect; + int font_ascent_pango_units; + int font_descent_pango_units; + + if (_debug_font_created) { + Error("Programming error: gtk_glwidget_create_font() was already called; " + "you must call gtk_glwidget_destroy_font() before creating font again"); + } + _debug_font_created = 1; + + // This call is deprecated so we'll have to fix it sometime. + ft2_context = pango_ft2_get_context(72, 72); + + font_desc = pango_font_description_from_string(font_string); + pango_font_description_set_size(font_desc, font_height * PANGO_SCALE); + pango_context_set_font_description(ft2_context, font_desc); + pango_font_description_free(font_desc); + + layout = pango_layout_new(ft2_context); +#if !PANGO_VERSION_CHECK(1,22,0) + PangoLayoutIter *iter; + iter = pango_layout_get_iter(layout); + font_ascent_pango_units = pango_layout_iter_get_baseline(iter); + pango_layout_iter_free(iter); +#else + font_ascent_pango_units = pango_layout_get_baseline(layout); +#endif + pango_layout_get_extents(layout, NULL, &log_rect); + g_object_unref(G_OBJECT(layout)); + font_descent_pango_units = log_rect.height - font_ascent_pango_units; + + font_ascent = PANGO_PIXELS_CEIL(font_ascent_pango_units); + font_descent = PANGO_PIXELS_CEIL(font_descent_pango_units); + y_offset_bitmap_render_pango_units = (font_ascent * PANGO_SCALE) - font_ascent_pango_units; +} + +void gtk_glwidget_destroy_font() +{ + if (!_debug_font_created) { + Error("Programming error: gtk_glwidget_destroy_font() called when font " + "does not exist"); + } + + font_ascent = -1; + font_descent = -1; + y_offset_bitmap_render_pango_units = -1; + g_object_unref(G_OBJECT(ft2_context)); + _debug_font_created = 0; } +// Renders the input text at the current location with the current color. +// The X position of the current location is used to place the left edge of the text image, +// where the text image bounds are defined as the logical extents of the line of text. +// The Y position of the current location is used to place the bottom of the text image. +// You should offset the Y position by the amount returned by gtk_glwidget_font_descent() +// if you want to place the baseline of the text image at the current Y position. +// Note: A problem with this function is that if the lower left corner of the text falls +// just a hair outside of the viewport (meaning the current raster position is invalid), +// then no text will be rendered. The solution to this is a very hacky one. You can search +// Google for "glDrawPixels clipping". void gtk_glwidget_print_string(const char *s) { - qglListBase(font_list_base); - qglCallLists(strlen(s), GL_UNSIGNED_BYTE, (unsigned char *)s); + // Much of this code is copied from the font-pangoft2.c example that comes with GtkGLExt. + + PangoLayout *layout; + PangoRectangle ink_rect; + PangoRectangle log_rect; + FT_Bitmap bitmap; + GLvoid *pixels; + guint32 *p; + GLfloat color[4]; + guint32 rgb; + GLfloat alpha; + guint8 *row, *row_end; + int i; + GLint previous_unpack_alignment; + GLboolean previous_blend_enabled; + GLint previous_blend_src; + GLint previous_blend_dst; + + + if (!_debug_font_created) { + Error("Programming error: gtk_glwidget_print_string() called but font does not exist; " + "you should have called gtk_glwidget_create_font() first"); + } + + layout = pango_layout_new(ft2_context); + pango_layout_set_width(layout, -1); // -1 no wrapping. All text on one line. + pango_layout_set_text(layout, s, -1); // -1 null-terminated string. + pango_layout_get_extents(layout, &ink_rect, &log_rect); + + if (log_rect.width > 0 && log_rect.height > 0) { + bitmap.rows = font_ascent + font_descent; + bitmap.width = PANGO_PIXELS_CEIL(log_rect.width); + bitmap.pitch = bitmap.width; + bitmap.buffer = g_malloc(bitmap.rows * bitmap.width); + bitmap.num_grays = 0xff; + bitmap.pixel_mode = FT_PIXEL_MODE_GRAY; + memset(bitmap.buffer, 0, bitmap.rows * bitmap.width); + pango_ft2_render_layout_subpixel(&bitmap, layout, -log_rect.x, + y_offset_bitmap_render_pango_units); + + pixels = g_malloc(bitmap.rows * bitmap.width * 4); + p = (guint32 *) pixels; + qglGetFloatv(GL_CURRENT_COLOR, color); +#if !defined(GL_VERSION_1_2) && G_BYTE_ORDER == G_LITTLE_ENDIAN + rgb = + (((guint32) (color[0] * 255.0)) << 0) | + (((guint32) (color[1] * 255.0)) << 8) | + (((guint32) (color[2] * 255.0)) << 16); +#else + rgb = + (((guint32) (color[0] * 255.0)) << 24) | + (((guint32) (color[1] * 255.0)) << 16) | + (((guint32) (color[2] * 255.0)) << 8); +#endif + alpha = color[3]; + + row = bitmap.buffer + bitmap.rows * bitmap.width; // Past the end. + row_end = bitmap.buffer; // Beginning. + + if (alpha == 1.0) { + do { + row -= bitmap.width; + for (i = 0; i < bitmap.width; i++) { +#if !defined(GL_VERSION_1_2) && G_BYTE_ORDER == G_LITTLE_ENDIAN + *p++ = rgb | (((guint32) row[i]) << 24); +#else + *p++ = rgb | ((guint32) row[i]); +#endif + } + } while (row != row_end); + } + + else { // Translucent. Much less efficient, so try to avoid. + do { + row -= bitmap.width; + for (i = 0; i < bitmap.width; i++) { +#if !defined(GL_VERSION_1_2) && G_BYTE_ORDER == G_LITTLE_ENDIAN + *p++ = rgb | (((guint32) (alpha * row[i])) << 24); +#else + *p++ = rgb | ((guint32) (alpha * row[i])); +#endif + } + } while (row != row_end); + } + + // Save state. I didn't see any OpenGL push/pop operations for these. + // Question: Is saving/restoring this state necessary? + qglGetIntegerv(GL_UNPACK_ALIGNMENT, &previous_unpack_alignment); + previous_blend_enabled = qglIsEnabled(GL_BLEND); + qglGetIntegerv(GL_BLEND_SRC, &previous_blend_src); + qglGetIntegerv(GL_BLEND_DST, &previous_blend_dst); + + qglPixelStorei(GL_UNPACK_ALIGNMENT, 4); + qglEnable(GL_BLEND); + qglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + +#if !defined(GL_VERSION_1_2) + qglDrawPixels(bitmap.width, bitmap.rows, + GL_RGBA, GL_UNSIGNED_BYTE, pixels); +#else + qglDrawPixels(bitmap.width, bitmap.rows, + GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, pixels); +#endif + + // Restore state in reverse order of how we set it. + qglBlendFunc(previous_blend_src, previous_blend_dst); + if (!previous_blend_enabled) { qglDisable(GL_BLEND); } + qglPixelStorei(GL_UNPACK_ALIGNMENT, previous_unpack_alignment); + + g_free(bitmap.buffer); + g_free(pixels); + } + + g_object_unref(G_OBJECT(layout)); } void gtk_glwidget_print_char(char s) { - qglListBase(font_list_base); - qglCallLists(1, GL_UNSIGNED_BYTE, (unsigned char *) &s); + char str[2]; + str[0] = s; + str[1] = '\0'; + gtk_glwidget_print_string(str); } - diff --git a/radiant/glwidget.h b/radiant/glwidget.h index 582a5ddb..341ada71 100644 --- a/radiant/glwidget.h +++ b/radiant/glwidget.h @@ -36,10 +36,11 @@ void WINAPI gtk_glwidget_swap_buffers (GtkWidget *widget); gboolean WINAPI gtk_glwidget_make_current (GtkWidget *widget); void WINAPI gtk_glwidget_destroy_context (GtkWidget *widget); void WINAPI gtk_glwidget_create_context (GtkWidget *widget); -void gtk_glwidget_create_font (GtkWidget *widget); - +void gtk_glwidget_create_font(); +int gtk_glwidget_font_ascent(); +int gtk_glwidget_font_descent(); void gtk_glwidget_print_string(const char *s); void gtk_glwidget_print_char(char s); - +void gtk_glwidget_destroy_font(); #endif /* _GLWIDGET_H_ */ diff --git a/radiant/main.cpp b/radiant/main.cpp index 0f7b434d..7e004003 100644 --- a/radiant/main.cpp +++ b/radiant/main.cpp @@ -34,6 +34,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #endif #include +#include #include #include "stdafx.h" #include @@ -44,6 +45,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include "watchbsp.h" #include "filters.h" +#include "glwidget.h" bool g_bBuildList = false; int g_argc; @@ -503,6 +505,11 @@ int main( int argc, char* argv[] ) { // gtk_disable_setlocale(); gtk_init(&argc, &argv); + gtk_gl_init(&argc, &argv); + gdk_gl_init(&argc, &argv); + + // TODO: Find a better place to call this. + gtk_glwidget_create_font(); if ((ptr = getenv ("Q3R_LIBGL")) != NULL) libgl = ptr; diff --git a/radiant/mainframe.cpp b/radiant/mainframe.cpp index 402d89d4..493520f0 100644 --- a/radiant/mainframe.cpp +++ b/radiant/mainframe.cpp @@ -3227,9 +3227,6 @@ void MainFrame::OnSleep() */ Sys_Printf("Done.\n"); - // bring back the GL font - gtk_glwidget_create_font (m_pCamWnd->GetWidget ()); - g_bScreenUpdates = true; Sys_Printf("Dispatching wake msg..."); diff --git a/radiant/xywindow.cpp b/radiant/xywindow.cpp index ef974f73..110a6f1c 100644 --- a/radiant/xywindow.cpp +++ b/radiant/xywindow.cpp @@ -2232,9 +2232,9 @@ void XYWnd::XY_DrawGrid() while ((step * m_fScale) < 4.0f) // make sure major grid spacing is at least 4 pixels on the screen step *= 8; //Sys_Printf("step after: %i\n", step); - while ((stepx * m_fScale) < 32.0f) // text step x must be at least 32 + while ((stepx * m_fScale) < 40.0f) // text step x must be at least 40 pixels stepx *= 2; - while ((stepy * m_fScale) < 32.0f) // text step y must be at least 32 + while ((stepy * m_fScale) < 40.0f) // text step y must be at least 40 pixels stepy *= 2; qglDisable(GL_TEXTURE_2D); @@ -2334,19 +2334,41 @@ void XYWnd::XY_DrawGrid() if ( g_qeglobals.d_savedinfo.show_coordinates) { qglColor3fv(g_qeglobals.d_savedinfo.colors[COLOR_GRIDTEXT]); - float offx = m_vOrigin[nDim2] + h - 6 / m_fScale, offy = m_vOrigin[nDim1] - w + 1 / m_fScale; - for (x=xb-((int)xb)%stepx; x<=xe ; x+=stepx) - { - qglRasterPos2f (x, offx); - sprintf (text, "%i",(int)x); - gtk_glwidget_print_string(text); - } - for (y=yb-((int)yb)%stepy; y<=ye ; y+=stepy) - { - qglRasterPos2f (offy, y); - sprintf (text, "%i",(int)y); - gtk_glwidget_print_string(text); - } + + // Pixels between top of label for vertical grid line and top of grid view window. + // Note: There is currently a bug where the top few pixels of the grid view are hidden + // under the border. So you should add about 5 to the desired value here. However, + // the font ascent reaches higher than all digits, so you can subtract a few from the final + // number. + const int pixelsTopCushion = 4; + + // Pixels between left of label and + // - left of grid view window (for horizontal grid line label) or + // - drawn vertical grid line (for vertical grid line label). + const int pixelsLeftCushion = 2; // IMPORTANT! Must be at least 1 otherwise labels might not be drawn + // because the origin of the text might be off screen due to rounding. + + // Pixels between baseline of horizontal grid line label and drawn horizontal grid line. + const int pixelsButtomCushion = 2; + + float yPosLabelsTop = m_vOrigin[nDim2] + h - (gtk_glwidget_font_ascent() + pixelsTopCushion) / m_fScale; + float xPosLabelsLeft = m_vOrigin[nDim1] - w + pixelsLeftCushion / m_fScale; + float leftCushion = pixelsLeftCushion / m_fScale; + float bottomOffset = (pixelsButtomCushion - gtk_glwidget_font_descent()) / m_fScale; + + // This renders the numbers along varying X on top of the grid view (labels vertical grid lines). + for (x = xb - ((int) xb) % stepx; x <= xe; x += stepx) { + qglRasterPos2f(x + leftCushion, yPosLabelsTop); + sprintf(text, "%i", (int) x); + gtk_glwidget_print_string(text); + } + + // This renders the numbers along varying Y on the left of the grid view (labels horizontal grid lines). + for (y = yb - ((int) yb) % stepy; y <= ye; y += stepy) { + qglRasterPos2f(xPosLabelsLeft, y + bottomOffset); + sprintf(text, "%i", (int) y); + gtk_glwidget_print_string(text); + } if (Active()) qglColor3fv(g_qeglobals.d_savedinfo.colors[COLOR_VIEWNAME]);