(Starting a new thread since that other one's gone all off-topic and this is a slightly different question anyway)
I've realized that I really can't proceed with at least two of my AllegroGL projects due to AllegroGL's shoddy text handling. Using just FreeType and writing the rest myself seems like too much work, and with all the other Allegro... quirks... one has to deal with, I thought I might as well leave AllegroGL altogether. So I'm looking for a library that provides:
Window/OpenGL context creation.
Audio output.
Keyboard/mouse/joystick input.
TTF font loading and rendering (Unicode/UTF-8!) with at least basic justification support.
Some form of timing.
Cross-platform support (I'm willing to negotiate on this one as long as Windows is supported though).
EDIT: Production-ready code.
EDIT2: Decent documentation.
</li>
SDL fails the last condition. SFML seems to have a godawful text API, but appears nice otherwise.
Do you have any other suggestions on libraries to try out?
Do you have any other suggestions on libraries to try out?
Sure. Allegro.
Allegro 4.9 current svn that is (to be Allegro 5).
It does all the things you listed, the only downside is that it's a WIP, so some things are a bit rough. It's usable already though.
Updated the list.
[EDIT]
... and from what little Allegro 4.9 documentation I can find, it does not in fact appear to satisfy my demands.
As I said, it's usable right now, but installation may be a bit rough and the API is not in feature freeze yet.
There is documentation (this isn't obvious), not yet up to the level of A4 but sure to get there. The examples are easy enough to follow anyway.
It's probably at least worth a quick look.
Use Fudgefont. It loads TTF files as Allegro fonts and they look quite beautiful when rendered with alpha blending. It loads them with Freetype. It looks like the sourceforge page for it isn't functioning, so I've attached my modified version of the source and header file.
When you say "basic justification support" do you mean similar to Allegro's left/right/centre functions?
A5 also does TTF loading quite well. I have some tutorials linked in my signature, but they have a critical flaw in the timer code, so disregard that portion of them if you decide to take a look.
[potential thread derailment; deleted]
Using just FreeType and writing the rest myself seems like too much work
It's actually a lot less work than you think. FreeType passes you vectors that describe the edges of arbitrarily-shaped polygons, and GLU has code to turn that sort of polygon into triangles. All you really need to do is write glue code with a tiny bit to turn Beziers into line segments, assuming you're happy with doing your fonts as geometry rather than textures.
I'm at work, so can't excise the relevant bits from my code, but check out my old Ice Field Frenzy (ChristmasHack '07 entry) code, or wait for me to post later.
A license that isn't (L)GPL.
SDL fails the last condition.
I see, but their page states:
SDL is distributed under GNU LGPL version 2. This license allows you to use SDL freely in commercial programs as long as you link with the dynamic library.
That leads me to a conlusion that as long as you link dynamic version of the library you cat have closed code. Can you elaborate your problem?
I think it's the open code that it becomes a problem. The (L)GPL is a bit viral.
Harry Carey: thanks, but as far as I can see that only does the TTF loading bit - it still goes through AllegroGL's horribly broken font and text handling.
Can you elaborate your problem?
A lesser evil is still evil.
(Elaborating in detail would take too much time)
ClanLib, maybe? It claims to have a BSD style license.
A lesser evil is still evil.
(Elaborating in detail would take too much time)
Allright, fair enough.
I think it's the open code that it becomes a problem. The (L)GPL is a bit viral.
I'm not sure I'm following your chain of thoughts. I know that when you actually use GPL'ed code you have to GPL the whole source of yours, however when you link dynamically the library it's ok.
I'm not sure I'm following your chain of thoughts.
It is generally hard to follow thoughts of reactionary zealots. Even harder than those of the normal ones.
How does A5 stack up against SDL?
I know that when you actually use GPL'ed code you have to GPL the whole source of yours, however when you link dynamically the library it's ok.
That only qualifies for LGPL. While I'm not sure this point has actually been tested in court (which is the only way to know if something is legal or illegal in the us), the GPL actually effects any linked code, dynamic or static. Since your code does directly use it, which makes it a derivative, which makes it subject to the GPL.
That only qualifies for LGPL. While I'm not sure this point has actually been tested in court (which is the only way to know if something is legal or illegal in the us), the GPL actually effects any linked code, dynamic or static. Since your code does directly use it, which makes it a derivative, which makes it subject to the GPL.
All right. SDL seems to be LGPL. But anyway, it hasn't been tested. By the way, has it been tested with GPL?
A lesser evil is still evil.
To expand a little bit more: ensuring LGPL compliance isn't as straightforward as you think it is. Can we get back on topic now, please?
To expand a little bit more: ensuring LGPL compliance isn't as straightforward as you think it is. Can we get back on topic now, please?
Fair enough. Sure, sorry for this little debate.
I just digged out of my memory that Richard Phipps was working with PTK. Looks like it can be used for free. Maybe it can be worthwile to look at.
How does A5 stack up against SDL?In license terms, Allegro is more liberal as discussed above. In ideological terms, SDL is lower level but has much more mature OpenGL support and as a result of both of those facts, it supports a much wider array of platforms. In terms of being in the process of an API refresh, both are equal. Except that now I'd say that Allegro's is more likely to arrive. Back to my real promise, code to do TTF in OpenGL. There's much more to it than I thought, because of the way I've chosen to optimise it. Much of it is unnecessary fluff. And some of it probably just replicates the STL, I don't know. Code it relies on includes my DataCollector template. It exists only to aggregate things it is given one at a time into C-style arrays and is the main thing I think STL can probably do for you. Here:
template class DataCollector
{
public:
DataCollector() {Array = NULL; NumAllocated = NumStored = 0;}
~DataCollector() {Flush();}
void Flush()
{
free(Array); Array = NULL;
NumAllocated = NumStored = 0;
}
void Add(T v)
{
if(NumAllocated == NumStored)
{
NumAllocated += 256;
Array = (T *)realloc(Array, sizeof(T)*NumAllocated);
}
Array[NumStored++] = v;
}
T Get(int index) {return Array[index];}
int GetNum() {return NumStored;}
T *GetArray() {return Array;}
T &operator [](int n) {return &Array[n];}
private:
T *Array;
int NumAllocated, NumStored;
};
There are also my VertexArray and DrawElements handlers, which are their own separate classes so that they can opt to use VBOs if available. If you don't care about VBOs then they're an unnecessary complication.
ebgf_DrawElements.h (don't worry about CResource — it's the mechanism I use to maintain objects that are otherwise lost when I destroy and create contexts, e.g. when switching to fullscreen mode or, because of the way SDL is set up, when the user resizes the window):
class CDrawElements: public CResource
{
public:
CDrawElements(GLenum mode, GLsizei count, GLenum type, GLvoid *pointer, bool copy = true);
~CDrawElements();
void Draw();
virtual void Backup();
virtual bool Restore();
private:
GLenum mode;
GLsizei count;
GLenum type;
GLvoid *ptr;
GLuint buffer;
int DataSize;
};
And .cpp:
#include "ebgf_GLExts.h"
CDrawElements::CDrawElements(GLenum mode, GLsizei count, GLenum type, GLvoid *pointer, bool copy)
{
this->mode = mode;
this->count = count;
this->type = type;
int basesize;
switch(type)
{
case GL_UNSIGNED_BYTE: basesize = sizeof(GLubyte); break;
case GL_UNSIGNED_SHORT: basesize = sizeof(GLushort); break;
case GL_UNSIGNED_INT: basesize = sizeof(GLuint); break;
}
DataSize = basesize*count;
if(copy)
{
ptr = new unsigned char[DataSize];
memcpy(ptr, pointer, DataSize);
}
else
ptr = pointer;
Restore();
Filename = new char[50];
sprintf(Filename, "DrawElements::%016x", (const unsigned int)this);
__EBGF_StoreHash(this);
}
CDrawElements::~CDrawElements()
{
Backup();
delete[] (unsigned char *)ptr;
EBGF_ReturnResource(this, false);
}
void CDrawElements::Backup()
{
if(AvailableGLExtensions&GLEXT_VBO)
glDeleteBuffersARB(1, &buffer);
}
bool CDrawElements::Restore()
{
buffer = 0;
if(AvailableGLExtensions&GLEXT_VBO)
{
//VBO extension supported, upload
glGenBuffersARB(1, &buffer);
glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, buffer);
glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, DataSize, ptr, GL_STATIC_DRAW_ARB);
}
return true;
}
void CDrawElements::Draw()
{
if(!buffer)
glDrawElements(mode, count, type, ptr);
else
{
glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, buffer);
glDrawElements(mode, count, type, 0);
glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
}
}
And VertexArray, which is almost a copy'n'paste job of DrawElements (I've reduced code redundancy in another version of my code, I just can't find it right now):
class CVertexArray: public CResource
{
public:
CVertexArray(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer, int number);
~CVertexArray();
void Enable();
void Disable();
virtual void Backup();
virtual bool Restore();
private:
GLint size;
GLenum type;
GLsizei stride;
GLvoid *ptr;
GLuint buffer;
int DataSize;
};
#include "ebgf_GLExts.h"
CVertexArray::CVertexArray(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer, int number)
{
ptr = NULL;
this->size = size;
this->type = type;
this->stride = stride;
int basesize;
switch(type)
{
case GL_SHORT: basesize = sizeof(GLshort); break;
case GL_INT: basesize = sizeof(GLint); break;
case GL_FLOAT: basesize = sizeof(GLfloat); break;
case GL_DOUBLE: basesize = sizeof(GLdouble); break;
}
DataSize = (basesize*size + stride) * number;
ptr = malloc(DataSize);
memcpy(ptr, pointer, DataSize);
Restore();
Filename = new char[50];
sprintf(Filename, "VArray::%016x", (unsigned int)this);
__EBGF_StoreHash(this);
}
CVertexArray::~CVertexArray()
{
Backup();
free(ptr);
EBGF_ReturnResource(this, false);
}
void CVertexArray::Backup()
{
if(buffer)
glDeleteBuffersARB(1, &buffer);
buffer = 0;
}
bool CVertexArray::Restore()
{
buffer = 0;
if(AvailableGLExtensions&GLEXT_VBO)
{
//VBO extension supported, try to upload
glGenBuffersARB(1, &buffer);
if(1)//glIsBufferARB(buffer))
{
glBindBufferARB(GL_ARRAY_BUFFER_ARB, buffer);
glBufferDataARB(GL_ARRAY_BUFFER_ARB, DataSize, ptr, GL_STATIC_DRAW_ARB);
}
else
buffer = 0;
}
return true;
}
void CVertexArray::Enable()
{
glPushClientAttrib(GL_CLIENT_VERTEX_ARRAY_BIT);
glEnableClientState(GL_VERTEX_ARRAY);
if(!buffer)
glVertexPointer(size, type, stride, ptr);
else
{
glBindBufferARB(GL_ARRAY_BUFFER_ARB, buffer);
glVertexPointer(size, type, stride, NULL);
}
}
void CVertexArray::Disable()
{
if(buffer)
glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
glPopClientAttrib();
}
So, up to here, mostly dull dry support stuff that adds a useful thing or two but creates complexity. You don't need an equivalent of my VertexArray or DrawElements holders, they're just the mechanism I use for utilising VBOs in a transparent fashion.
The first class that is actually really helpful for TTF is my Polygon class. You feed it a sequence of vertices and then later you can ask it to draw the resulting polygon. Really it's just a thin layer on top of the GLU tesselator, utilising my pet classes for VBOs.
#ifndef WIN32
#define __stdcall
#endif
class CPolygon
{
public:
CPolygon();
~CPolygon();
void Draw();
void AddVert(GLfloat x, GLfloat y);
void NewContour();
void Finish();
private:
static void __stdcall Begin(GLenum type);
static void __stdcall End(void);
static void __stdcall Combine(GLdouble coords[3], void *vertex_data[4], GLfloat weight[4], void **outData);
static void __stdcall Vertex(void *Data);
static int StoreVert(GLfloat x, GLfloat y);
CVertexArray *Array;
struct Plate
{
Plate() {Elements = NULL; Next = NULL;}
~Plate() {delete Elements;}
CDrawElements *Elements;
Plate *Next;
} *PlateList;
static Plate *PlateWorker;
static DataCollector *TVList;
static DataCollector *VPtr;
static GLUtesselator *tesselator;
static GLenum CurType;
};
DataCollector *CPolygon::TVList;
DataCollector *CPolygon::VPtr;
GLUtesselator *CPolygon::tesselator;
GLenum CPolygon::CurType;
CPolygon::Plate *CPolygon::PlateWorker;
int *BasePtr;
void __stdcall CPolygon::Begin(GLenum type)
{
CurType = type;
VPtr = new DataCollector;
}
void __stdcall CPolygon::End(void)
{
Plate *NP = new Plate;
NP->Elements = new CDrawElements(CurType, VPtr->GetNum(), GL_UNSIGNED_INT, VPtr->GetArray());
NP->Next = PlateWorker;
PlateWorker = NP;
delete VPtr; VPtr = NULL;
}
void __stdcall CPolygon::Vertex(void *Data)
{
VPtr->Add((GLuint)((int *)Data - BasePtr));
}
void __stdcall CPolygon::Combine(GLdouble coords[3], void *vertex_data[4], GLfloat weight[4], void **outData)
{
*outData = &BasePtr[StoreVert((GLfloat)coords[0], (GLfloat)coords[1])];
}
int CPolygon::StoreVert(GLfloat x, GLfloat y)
{
// ensure storage is available
int r = TVList->GetNum() >> 1;
TVList->Add(x); TVList->Add(y);
return r;
}
void CPolygon::AddVert(GLfloat x, GLfloat y)
{
int c = StoreVert(x, y);
// pass to gluTesselate
GLdouble VD[3] = {x, y, 0};
gluTessVertex(tesselator, VD, &BasePtr[c]);
}
typedef GLvoid (__stdcall *CallBackFunc)();
CPolygon::CPolygon()
{
// generate tesselator
tesselator = gluNewTess();
// prepare vertex list
TVList = new DataCollector;
PlateWorker = NULL;
Array = NULL;
PlateList = NULL;
// register callbacks
gluTessCallback(tesselator, GLU_TESS_BEGIN, (CallBackFunc)Begin);
gluTessCallback(tesselator, GLU_TESS_END, (CallBackFunc)End);
gluTessCallback(tesselator, GLU_TESS_COMBINE, (CallBackFunc)Combine);
gluTessCallback(tesselator, GLU_TESS_VERTEX, (CallBackFunc)Vertex);
// begin polygon
gluBeginPolygon(tesselator);
gluTessBeginContour(tesselator);
}
void CPolygon::NewContour()
{
gluTessEndContour(tesselator);
gluTessBeginContour(tesselator);
}
void CPolygon::Finish()
{
// end final polygon
gluTessEndContour(tesselator);
gluEndPolygon(tesselator);
// store vertices
Array = new CVertexArray(2, GL_FLOAT, 0, TVList->GetArray(), TVList->GetNum() >> 1);
/* at this point, lots of info is sent to my callbacks */
// free memory
delete TVList; TVList = NULL;
// free tesselator
gluDeleteTess(tesselator);
// take copy of plates
PlateList = PlateWorker; PlateWorker = NULL;
}
CPolygon::~CPolygon()
{
delete Array;
while(PlateList)
{
Plate *N = PlateList->Next;
delete PlateList;
PlateList = N;
}
}
void CPolygon::Draw()
{
if(!Array) return;
Array->Enable();
Plate *P = PlateList;
while(P)
{
P->Elements->Draw();
P = P->Next;
}
Array->Disable();
}
Which just leaves my tiny bit of code for turning a TTF glyph into a polygon. I'm going to have to cut things a bit here since I've lazily just used a 7bit ASCII glyph table and that isn't what you want. Just imagine you are set up so that the Glyphs struct is large enough to hold a continuous array spanning every glyph you may want to use. That'd be a really bad unicode implementation decision, but I don't really expect you to use my code verbatim anyway.
#include
#include FT_FREETYPE_H
#include FT_OUTLINE_H
class CFont: public CResource
{
struct Glyph
{
CPolygon *Graphic;
float Width;
} *Glyphs;
static Glyph *CurGlyph;
FT_Face face;
static FT_Pos CurPos[2];
static void TTF_AddVert(GLfloat x, GLfloat y);
static int TTF_MoveTo(const FT_Vector *to, void *user);
static int TTF_AddLine(const FT_Vector *to, void *user);
static int TTF_AddQuadratic(const FT_Vector *control, const FT_Vector *to, void *user);
static int TTF_AddCubic(const FT_Vector *control1, const FT_Vector *control2, const FT_Vector *to, void *user);
void GetChar(int character);
static bool InContour;
};
#define GLYPH_MULTIPLIER 128
bool CFont::Open(const char *name)
{
if(FT_New_Face(__ebgf_TTFLibrary, name, 0, &face)) return false;
FT_Set_Char_Size(face, GLYPH_MULTIPLIER << 6, GLYPH_MULTIPLIER << 6, 72, 72); // default size is 1x1, with 72dpi
return true;
}
bool CFont::InContour;
FT_Pos CFont::CurPos[2];
CFont::Glyph *CFont::CurGlyph;
void CFont::TTF_AddVert(GLfloat x, GLfloat y)
{
CurGlyph->Graphic->AddVert(x, y);
}
int CFont::TTF_MoveTo(const FT_Vector *to, void *user)
{
/* pen up, move, pen down — breaks polygon */
if(!InContour || CurPos[0] != to->x || CurPos[1] != to->y)
{
if(InContour)
CurGlyph->Graphic->NewContour();
InContour = true;
CurPos[0] = to->x;
CurPos[1] = to->y;
TTF_AddVert((GLfloat)CurPos[0], (GLfloat)CurPos[1]);
}
return 0;
}
int CFont::TTF_AddLine(const FT_Vector *to, void *user)
{
/* move */
if(CurPos[0] != to->x || CurPos[1] != to->y)
TTF_AddVert((GLfloat)(CurPos[0] = to->x), (GLfloat)(CurPos[1] = to->y));
return 0;
}
#define BEZIER_SEGMENTS 2
int CFont::TTF_AddQuadratic(const FT_Vector *control, const FT_Vector *to, void *user)
{
/* quadratic bezier from current location to 'to', with control point 'control' */
for(int c = 1; c < BEZIER_SEGMENTS; c++)
{
float t = (float)c / BEZIER_SEGMENTS;
float coeff1 = (1-t) * (1-t);
float coeff2 = 2 * t * (1-t);
float coeff3 = t * t;
TTF_AddVert(
coeff1*CurPos[0] + coeff2 * control->x + coeff3*to->x,
coeff1*CurPos[1] + coeff2 * control->y + coeff3*to->y
);
}
TTF_AddVert((GLfloat)(CurPos[0] = to->x), (GLfloat)(CurPos[1] = to->y));
return 0;
}
int CFont::TTF_AddCubic(const FT_Vector *control1, const FT_Vector *control2, const FT_Vector *to, void *user)
{
/* cubic bezier - rarely happens */
for(int c = 1; c < BEZIER_SEGMENTS; c++)
{
float t = (float)c / BEZIER_SEGMENTS;
float coeff1 = (1-t) * (1-t) * (1-t);
float coeff2 = 3 * t * (1-t) * (1-t);
float coeff3 = 3 * t * t;
float coeff4 = t * t * t;
TTF_AddVert(
coeff1*CurPos[0] + coeff2 * control1->x + coeff3*control2->x + coeff4*to->x,
coeff1*CurPos[1] + coeff2 * control1->y + coeff3*control2->y + coeff4*to->y
);
}
TTF_AddVert((GLfloat)(CurPos[0] = to->x), (GLfloat)(CurPos[1] = to->y));
return 0;
}
void CFont::GetChar(int character)
{
FT_Outline outline;
FT_Outline_Funcs func_interface;
func_interface.shift = 0;
func_interface.delta = 0;
func_interface.move_to = (FT_Outline_MoveToFunc)TTF_MoveTo;
func_interface.line_to = (FT_Outline_LineToFunc)TTF_AddLine;
func_interface.conic_to = (FT_Outline_ConicToFunc)TTF_AddQuadratic;
func_interface.cubic_to = (FT_Outline_CubicToFunc)TTF_AddCubic;
/* lookup glyph */
if(FT_Load_Char(face, character, FT_LOAD_NO_BITMAP|FT_LOAD_NO_HINTING)) return;
outline = face->glyph->outline;
CurPos[0] = CurPos[1] = 0;
CurGlyph = &Glyphs[character];
CurGlyph->Graphic = new CPolygon;
InContour = false;
// throw font shape into tesselator
FT_Outline_Decompose( &outline, &func_interface, NULL);
Glyphs[character].Width = (float)face->glyph->advance.x;
CurGlyph->Graphic->Finish();
}
And these are called just once, when the program is first loaded and before it is shut:
FT_Library __ebgf_TTFLibrary = NULL;
void __EBGF_FreeFontLibrary()
{
FT_Done_FreeType(__ebgf_TTFLibrary);
}
void __EBGF_SetupFontLibrary()
{
FT_Init_FreeType(&__ebgf_TTFLibrary);
atexit(__EBGF_FreeFontLibrary);
}
In my 7bit ASCII world, I have the following print function, which does the obvious stuff + pair kerning:
void CFont::Print(const char *fmt, ...)
{
char String[2048];
va_list varlist;
va_start(varlist, fmt);
// vsnprintf(String, 2048, fmt, varlist);
vsprintf(String, fmt, varlist);
va_end(varlist);
glPushAttrib(GL_ENABLE_BIT);
glDisable(GL_CULL_FACE);
fmt = String;
glPushMatrix();
glScalef(1.0f / (64.0f * GLYPH_MULTIPLIER), 1.0f / (64.0f * GLYPH_MULTIPLIER), 1.0f / (64.0f * GLYPH_MULTIPLIER));
while(*fmt)
{
int idx = (*fmt)&127;
if(Glyphs[idx].Graphic) Glyphs[idx].Graphic->Draw();
glTranslatef(Glyphs[idx].Width, 0, 0);
int glyph_index1;
int glyph_index2;
FT_Vector kerning;
glyph_index1 = FT_Get_Char_Index(face, (FT_ULong)fmt[0]);
glyph_index2 = FT_Get_Char_Index(face, (FT_ULong)fmt[1]);
FT_Get_Kerning(face, glyph_index1, glyph_index2, FT_KERNING_UNFITTED, &kerning);
glTranslatef((GLfloat)kerning.x, 0, 0);
fmt++;
}
glPopMatrix();
glPopAttrib();
}
So, yes, a lot of my code is a mess and I don't expect you to use it verbatim. But it hopefully shows that linking FreeType to GLU is really quite easy.How does A5 stack up against SDL?
And if it does so unfavourably in some way, you can always make a suggestion and affect how the yet unfinished A5 will stack up to SDL as the former is under active development...
you can always make a suggestion and affect how the yet unfinished A5 will stack up to SDL as the former is under active development...
And I intend to keep it that way even if I don't get anywhere near as much actual work done on it myself as I want to (especially in a reasonable amount of time, fshooks could have been done last year)
So, yes, a lot of my code is a mess and I don't expect you to use it verbatim. But it hopefully shows that linking FreeType to GLU is really quite easy.
Someone want to add that to A5's ttf addon? Or as a new addon?
I think it'll need to wait until either SiegeLord's primitive stuff is in the library, giving implicit access to VBOs and suchlike from on top of Allegro with no regard whatsoever for which particular driver is in use, or — if it is planned at all — until it's clear how and to what extent Allegro will expose OpenGL (specifically, how the user will keep track of resources that are tied to specific rendering context if there are some operations that cause Allegro to destroy the current context and create a new one).
FreeType obviously isn't OpenGL specific, and GLU actually isn't either. You can link against and use GLU without linking against or using the main body of OpenGL, so probably waiting to see what SiegeLord comes up with is smartest. He's already assured me that it'll be set up to allow vertex data to be left on the GPU. Then we can have a nice TTF-as-geometry library that's platform agnostic.
SFML seems very good. Very nice tutorials, nicely organized homepage, nice APIs. I will give it a try.