Friday, March 4, 2011

Compiling FTGL-2.1.3~rc5 on Windows Using the Mingw Compiler Bundled With Qt

If you are trying to compile FTGL on Windows, you may have run into the same problem I did: it's not really set up to make that easy. I couldn't get it to work with Cygwin's configure tool. So I tried to make my own Makefile. After searching a fair amount, I found this page to be useful:

http://www.pitt.edu/~mwr12/compcorner/linuxstuff/ftgl.html

I followed Step 3, adding the following to the bottom of FTGL/ftgl.h just before the last #endif:

#ifdef WIN32
    #include <GL/gl.h>
    #include <GL/glu.h>
    #include <windows.h>
#endif

Then, after studying the Makefile for FTGL, and after looking at the script in Step 5 (from the website linked above), I finally came up with a Makefile that worked for me:

# Makefile.in generated by automake 1.10.1 from Makefile.am.
# src/Makefile.  Generated from Makefile.in by configure.

# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
# 2003, 2004, 2005, 2006, 2007, 2008  Free Software Foundation, Inc.
# This Makefile.in is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
# with or without modifications, as long as this notice is preserved.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE.


top_builddir = ..
COMPILER_DIR = C:/Qt/2010.05/mingw
CONFIG_HEADER = $(top_builddir)/config.h
CPPFLAGS =  -Wall -Wpointer-arith -Wcast-align -Wcast-qual -Wshadow -Wsign-compare
CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(CPPFLAGS)
CXX = g++ -g -O2
DEFAULT_INCLUDES = -I. -I$(top_builddir) -IFTGL/
DEFS = -DHAVE_CONFIG_H
FT2_CFLAGS = -I$(COMPILER_DIR)/include/freetype -I$(COMPILER_DIR)/include
FT2_LIBS = -L$(COMPILER_DIR)/lib -lfreetype
GL_CFLAGS = -I$(COMPILER_DIR)/include
GL_LIBS = -lglu32 -lopengl32
HEADER_FLAGS = -I$(COMPILER_DIR)/include
NULL = 
libftgl_dll_CXXFLAGS = $(FT2_CFLAGS) $(GL_CFLAGS)
libftgl_dll_LIBADD = $(GL_LIBS) $(FT2_LIBS)

libftgl_a_objects = \
    FTBuffer.o \
    FTCharmap.o \
    FTContour.o \
    FTFace.o \
    FTGlyphContainer.o \
    FTLibrary.o \
    FTPoint.o \
    FTSize.o \
    FTVectoriser.o \
    $(ftglyph_objects) \
    $(ftfont_objects) \
    $(ftlayout_objects) \
    $(NULL)

libftgl_a_SOURCES = \
    FTBuffer.cpp \
    FTCharmap.cpp \
    FTCharmap.h \
    FTCharToGlyphIndexMap.h \
    FTContour.cpp \
    FTContour.h \
    FTFace.cpp \
    FTFace.h \
    FTGlyphContainer.cpp \
    FTGlyphContainer.h \
    FTInternals.h \
    FTLibrary.cpp \
    FTLibrary.h \
    FTList.h \
    FTPoint.cpp \
    FTSize.cpp \
    FTSize.h \
    FTVector.h \
    FTVectoriser.cpp \
    FTVectoriser.h \
    FTUnicode.h \
    $(ftglyph_sources) \
    $(ftfont_sources) \
    $(ftlayout_sources) \
    $(ftgl_headers) \
    $(NULL)

ftgl_headers = \
    FTGL/ftgl.h \
    FTGL/FTBBox.h \
    FTGL/FTBuffer.h \
    FTGL/FTPoint.h \
    FTGL/FTGlyph.h \
    FTGL/FTBitmapGlyph.h \
    FTGL/FTBufferGlyph.h \
    FTGL/FTExtrdGlyph.h \
    FTGL/FTOutlineGlyph.h \
    FTGL/FTPixmapGlyph.h \
    FTGL/FTPolyGlyph.h \
    FTGL/FTTextureGlyph.h \
    FTGL/FTFont.h \
    FTGL/FTGLBitmapFont.h \
    FTGL/FTBufferFont.h \
    FTGL/FTGLExtrdFont.h \
    FTGL/FTGLOutlineFont.h \
    FTGL/FTGLPixmapFont.h \
    FTGL/FTGLPolygonFont.h \
    FTGL/FTGLTextureFont.h \
    FTGL/FTLayout.h \
    FTGL/FTSimpleLayout.h \
    ${NULL}

ftglyph_objects = \
    FTGlyph/FTGlyph.o \
    FTGlyph/FTGlyphGlue.o \
    FTGlyph/FTBitmapGlyph.o \
    FTGlyph/FTBufferGlyph.o \
    FTGlyph/FTExtrudeGlyph.o \
    FTGlyph/FTOutlineGlyph.o \
    FTGlyph/FTPixmapGlyph.o \
    FTGlyph/FTPolygonGlyph.o \
    FTGlyph/FTTextureGlyph.o \
    $(NULL)

ftglyph_sources = \
    FTGlyph/FTGlyph.cpp \
    FTGlyph/FTGlyphImpl.h \
    FTGlyph/FTGlyphGlue.cpp \
    FTGlyph/FTBitmapGlyph.cpp \
    FTGlyph/FTBitmapGlyphImpl.h \
    FTGlyph/FTBufferGlyph.cpp \
    FTGlyph/FTBufferGlyphImpl.h \
    FTGlyph/FTExtrudeGlyph.cpp \
    FTGlyph/FTExtrudeGlyphImpl.h \
    FTGlyph/FTOutlineGlyph.cpp \
    FTGlyph/FTOutlineGlyphImpl.h \
    FTGlyph/FTPixmapGlyph.cpp \
    FTGlyph/FTPixmapGlyphImpl.h \
    FTGlyph/FTPolygonGlyph.cpp \
    FTGlyph/FTPolygonGlyphImpl.h \
    FTGlyph/FTTextureGlyph.cpp \
    FTGlyph/FTTextureGlyphImpl.h \
    $(NULL)

ftfont_objects = \
    FTFont/FTFont.o \
    FTFont/FTFontGlue.o \
    FTFont/FTBitmapFont.o \
    FTFont/FTBufferFont.o \
    FTFont/FTExtrudeFont.o \
    FTFont/FTOutlineFont.o \
    FTFont/FTPixmapFont.o \
    FTFont/FTPolygonFont.o \
    FTFont/FTTextureFont.o \
    $(NULL)

ftfont_sources = \
    FTFont/FTFont.cpp \
    FTFont/FTFontImpl.h \
    FTFont/FTFontGlue.cpp \
    FTFont/FTBitmapFont.cpp \
    FTFont/FTBitmapFontImpl.h \
    FTFont/FTBufferFont.cpp \
    FTFont/FTBufferFontImpl.h \
    FTFont/FTExtrudeFont.cpp \
    FTFont/FTExtrudeFontImpl.h \
    FTFont/FTOutlineFont.cpp \
    FTFont/FTOutlineFontImpl.h \
    FTFont/FTPixmapFont.cpp \
    FTFont/FTPixmapFontImpl.h \
    FTFont/FTPolygonFont.cpp \
    FTFont/FTPolygonFontImpl.h \
    FTFont/FTTextureFont.cpp \
    FTFont/FTTextureFontImpl.h \
    $(NULL)

ftlayout_objects = \
    FTLayout/FTLayout.o \
    FTLayout/FTLayoutGlue.o \
    FTLayout/FTSimpleLayout.o \
    $(NULL)

ftlayout_sources = \
    FTLayout/FTLayout.cpp \
    FTLayout/FTLayoutImpl.h \
    FTLayout/FTLayoutGlue.cpp \
    FTLayout/FTSimpleLayout.cpp \
    FTLayout/FTSimpleLayoutImpl.h \
    $(NULL)

all: libftgl.a ftgl.dll

.cpp.o:
 $(CXXCOMPILE) -c $(HEADER_FLAGS) -DFTGL_LIBRARY_STATIC -o $@ $<

libftgl.a: $(libftgl_a_objects)
 ar -r $@ $(libftgl_a_objects)

ftgl.dll: $(libftgl_a_SOURCES)
 $(CXXCOMPILE) $(libftgl_a_SOURCES) $(libftgl_dll_CXXFLAGS) $(libftgl_dll_LIBADD) -DFTGL_LIBRARY -DFTGL_DLL_EXPORTS -shared -Wl,--out-implib,libftgl.dll.a -o ftgl.dll
  
clean:
 @-del /S *.o
 @-del ftgl.dll
 @-del libftgl.dll.a
 @-del libftgl.a


This will compile the library for static and dynamic use. You will need to copy the FTGL folder to mingw/include. Then, to use as a static library, copy the resulting file src/libftgl.a to mingw/lib. To use as a dynamic library, copy the file src/libftgl.dll.a to mingw/lib and rename it to libftgl.a, and then copy src/ftgl.dll to the folder in your application.

As a side note, I am using this in a Qt application, so I had to update the .pro file to include the following line:

LIBS += -lftgl -lfreetype -lglu32 -lopengl32

When using ftgl.dll, I didn't need to add the '-lglu32 -lopengl32'. After using the static library, however, the linker complained about not having opengl functions defined. Also note the ordering of the libraries. Placing the opengl includes before the ftgl include didn't work. I had to put them after to satisfy the linker.

I could then follow the tutorials and get them to work. Also, another side note: The resulting text turned out to be far bigger than the -1,1 box in opengl, since it is measured in bigger units. Either scaling or using projection should help with that.

Another issue I found when using FTGLTextureFont is that the depth test needed to be set correctly, or the letter boxes seemed to overlap each other, blocking some of the letters. Setting the depth function using glDepthFunc(GL_LEQUAL); solved this problem for the most part. Another problem remained: at some font sizes, some of the letters seem to be clipped by either their neighbor or their own box. It is especially noticable on the letters 'e' and 'o'. This problem can be avoided by changing the font size to a size where this doesn't happen and then scaling to compensate.

Tuesday, February 8, 2011

Compiling FreeType-2.4.4 with the MinGW compiler bundled with Qt on Windows XP

I have a Qt application that uses OpenGL directly to render some things. Consequently, I wanted to add text support in OpenGL. After looking at a few options, trying one option and rejecting it, I decided to try FTGL. FTGL needs Freetype to compile, so I had to try and install Freetype. I went with Freetype-2.4.4 to get some of the new features, so I had to compile it from source (the pre-built version was only at 2.3.5). I ran into some interesting problems while compiling. The error looked something like this:

>mingw32-make
process_begin: CreateProcess(NULL, type builds\newline, ...) failed.
make (e=2): The system cannot find the file specified.
mingw32-make: *** [dos_setup] Error 2

After scouring through many of the makefiles, I finally narrowed the problem to the builds\detect.mk file. Specifically, starting at line 134. After puzzling about that for a while, I finally managed to figure out the reason: I had cygwin binaries in the path. They were conflicting with the mingw files. Removing the cygwin files from the path fixed the problem:

>mingw32-make

FreeType build system -- automatic system detection

The following settings are used:

  platform                     win32
  compiler                     gcc
  configuration directory      .\builds\win32
  configuration rules          .\builds\win32\w32-gcc.mk

If this does not correspond to your system or settings please remove the file
'config.mk' from this directory then read the INSTALL file for help.

Otherwise, simply type 'make' again to build the library.
or 'make refdoc' to build the API reference (the latter needs python).

Doing another 'mingw32-make' command finished up the compilation, putting the resulting files in \objs.

Now we'll see if it works...

Edit:

It did indeed work. I copied libfreetype.a to mingw/lib. I also copied ft2build.h and the freetype folder with the other header files to mingw/include. Using the library involved including the appropriate headers:

#include <ft2build.h>
#include FT_FREETYPE_H

- and then following the example on the Freetype website. The only other problem was that the Qt .pro file needed to be updated to include the following line:

LIBS += -lfreetype

Tuesday, October 5, 2010

Save Using ActiveRecord Without Rolling Back If Callbacks Fail

I recently came across an interesting situation when using Ruby on Rails. I wanted to make all of my models so that they could not be destroyed. The plan was to make all of the database records be "soft deleted", where each table had a column storing whether a record was "deleted". This was easily accomplished. Then, I wanted to prevent the records from being deleted. In my models, all I had to do was add a before_destroy callback and return false:

class Model < ActiveRecord::Base
  before_destroy :beforeDestroyMethod

  def beforeDestroyMethod
    return false
  end
end

All operations on the model instances are contained within transactions. If any of the before_* callbacks return false, the transaction is rolled back. This is all well and good, provided you don't want to insert a record in a different table in the before_destroy method. I happened to want to log any attempt to destroy records from the database, and I wanted to use the database to store the log entry:

class Model < ActiveRecord::Base
  before_destroy :beforeDestroyMethod

  def beforeDestroyMethod
    log = LogEntry.new
    log.action_type = "Destroy Object"
    log.save
    return false
  end
end

Unfortunately, when the before_destroy method returned false, it also rolled back the log entry insertion.

After looking around, I found a partial answer. If you call 'establish_connection' in your Rails model, you will get a new database connection for that class and all classes that inherit from it:

class LogEntry < ActiveRecord::Base
  establish_connection ENV['RAILS_ENV']
end

I am using ENV['RAILS_ENV'] so that it creates a connection for the development or the production database, depending on which environment it's in. You can also specify a different database entirely.

My other models can now work as I had intended before:

class Model < ActiveRecord::Base
  before_destroy :beforeDestroyMethod

  def beforeDestroyMethod
    log = LogEntry.new
    log.action_type = "Destroy Object"
    log.save
    return false
  end
end
So, that seems to have solved the problem. Well, mostly. What happens if you want to add a log entry if someone tries to destroy a log entry? You have the exact same problem as before. This time, I used a different trick for this one case:

class LogEntry < ActiveRecord::Base
  establish_connection ENV['RAILS_ENV']

  before_destroy :beforeDestroyMethod

  def beforeDestroyMethod
    query = "insert into log_entries(action_type) values ("Destroy Log Entry");"
    ActiveRecord::Base.connection_pool.with_connection do |conn|
      conn.execute(query)
    end
    return false
  end
end

This time, I just grab a database connection from the connection pool. Unfortunately, you have to utilize raw queries to use the connection, but it gets the job done.

Alternatively, I could have also got a connection by using the checkout method:

class LogEntry < ActiveRecord::Base
  establish_connection ENV['RAILS_ENV']

  before_destroy :beforeDestroyMethod

  def beforeDestroyMethod
    query = "insert into log_entries(action_type) values ("Destroy Log Entry");"
    conn = ActiveRecord::Base.connection_pool.checkout
    conn.execute(query)
    ActiveRecord::Base.connection_pool.checkin(conn)
    return false
  end
end

Thus, my problem was solved.