Adding text to OpenGl canvas


#1

Hi

I’m trying to add text to my OpenGL canvas and I’m following Nehes tutorial 13 (http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=13). Nehes source code compiles and run nicely on my computer but when I port the essential OpenGL-code of the project to my JUCE-application I don’t get any text displayed.

I have successfully ported other pieces of code from Nehes tutorials in the past and I can’t figure out why this one fails for me. Also, I have compared the projects settings for both project and they are aligned.

Can someone please give me a hint on what might be the problem. This might be a tough question from the sparse information.

Best Regards
Thomas


#2

It sure is, with that little information. You didn’t even say which NeHe tutorial. Or what platform you’re developing on.

There’s so many variables in an OpenGL app, there’s no way to tell.

Bruce


#3

[quote=“Bruce Wheaton”]It sure is, with that little information. You didn’t even say which NeHe tutorial. Or what platform you’re developing on.

There’s so many variables in an OpenGL app, there’s no way to tell.

Bruce[/quote]

I’m working in windows XP and you can find the tutorial 13 in the link I put in above. The tutorial concerns bitmap fonts.

Thomas


#4

Sorry, missed the link. I have similar working on Mac and Linux, and I have this in for Windows, although I haven’t tried it for ages:

[code]#if JUCE_WIN32 || JUCE_WIN64
void GPUContext::makeFontList (GLuint listBase, String fontName, int fontSize)
{
HFONT fontID;
LOGFONT logFont;

logFont.lfHeight			= -fontSize;
logFont.lfWidth				= 0;
logFont.lfEscapement		= 0;
logFont.lfOrientation		= 0;
logFont.lfWeight			= FW_BOLD;
logFont.lfItalic			= FALSE;
logFont.lfUnderline			= FALSE;
logFont.lfStrikeOut			= FALSE;

logFont.lfCharSet			= ANSI_CHARSET;
logFont.lfOutPrecision		= OUT_DEFAULT_PRECIS;
logFont.lfClipPrecision		= CLIP_DEFAULT_PRECIS;

logFont.lfQuality			= DEFAULT_QUALITY;
logFont.lfPitchAndFamily	= DEFAULT_PITCH;

if (fontName == String::empty)
	strcpy(logFont.lfFaceName, "Courier");
else
	strcpy(logFont.lfFaceName, fontName.toUTF8());

fontID						= CreateFontIndirect(&logFont);
SelectObject(dc, fontID);
wglUseFontBitmaps(dc, 0, 256, (long)listBase);
DeleteObject(fontID);

}
#endif
[/code]

I think you’ll need to post code though. Presuming you checked your font loading with the debugger, and it all seems fine, the problems may lie in an OpenGL state. Remember that callLists uses window position, not the same position as your other drawing, for instance - there’s a lot that go wrong.

glWindowPos2f(midX - (kOverlayBoxWidth / 2) + kOverlaySpacer, lowY + kOverlayBoxHeight - kOverlaySpacer - 7); glCallLists(label.length(), GL_BYTE, label.toUTF8());

Bruce


#5

[quote=“Bruce Wheaton”]Sorry, missed the link. I have similar working on Mac and Linux, and I have this in for Windows, although I haven’t tried it for ages:

[code]#if JUCE_WIN32 || JUCE_WIN64
void GPUContext::makeFontList (GLuint listBase, String fontName, int fontSize)
{
HFONT fontID;
LOGFONT logFont;

logFont.lfHeight			= -fontSize;
logFont.lfWidth				= 0;
logFont.lfEscapement		= 0;
logFont.lfOrientation		= 0;
logFont.lfWeight			= FW_BOLD;
logFont.lfItalic			= FALSE;
logFont.lfUnderline			= FALSE;
logFont.lfStrikeOut			= FALSE;

logFont.lfCharSet			= ANSI_CHARSET;
logFont.lfOutPrecision		= OUT_DEFAULT_PRECIS;
logFont.lfClipPrecision		= CLIP_DEFAULT_PRECIS;

logFont.lfQuality			= DEFAULT_QUALITY;
logFont.lfPitchAndFamily	= DEFAULT_PITCH;

if (fontName == String::empty)
	strcpy(logFont.lfFaceName, "Courier");
else
	strcpy(logFont.lfFaceName, fontName.toUTF8());

fontID						= CreateFontIndirect(&logFont);
SelectObject(dc, fontID);
wglUseFontBitmaps(dc, 0, 256, (long)listBase);
DeleteObject(fontID);

}
#endif
[/code]

I think you’ll need to post code though. Presuming you checked your font loading with the debugger, and it all seems fine, the problems may lie in an OpenGL state. Remember that callLists uses window position, not the same position as your other drawing, for instance - there’s a lot that go wrong.

glWindowPos2f(midX - (kOverlayBoxWidth / 2) + kOverlaySpacer, lowY + kOverlayBoxHeight - kOverlaySpacer - 7); glCallLists(label.length(), GL_BYTE, label.toUTF8());

Bruce[/quote]

Hi Bruce

Thanks a lot for giving a hand here.

I’ll try out your code when I’m back at work tomorrow. If it fails I will post my code.

BTW, how would you check the font loading using the debugger - would you just check if the pointer to th font is null?. Sorry I don’t have a lot of C/C++ coding experience?

Again, thanks for your support!

Thomas


#6

Looks like glUseFontBitmaps returns a bool with whether it succeeded or not. Windows being the red-headed step-child in my project, the error isn’t checked for.

On Linux, it was also common for no font to be found.

Bruce


#7

If you’re aiming for cross-platform fonts, surely it’d be quite simple to create a GlyphArrangement, get the path from it, and fill that path using gl?


#8

I’m sure that would be ‘best’, but I wouldn’t guess it would be simple, especially since things are in flux in OpenGL world (lots will be deprecated soon), so there’s some research to do.

If some programming genius wanted to add path based OpenGL fonts to juce, that would be wicked. [hint, hint]

I’m muddling along on bitmap fonts, since I’m essentially doing OSDs, not lovely text.

In an ideal world, you would do an OpenGL backend someday, and we could use that in place of native platform drawing. It’s been discussed before, or at least variations on the theme have, but it’s more than I can get my head around.

Bruce


#9

Hi again

Below, I’ve put in a simple code example of how I have ported Nehes bitmap font code example to a project. The project contains a rotating square to show that the openGL canvas can contain graphic:

Using the debugger I found that the font loading is working correctly!

Btw. I’m working on a spectrum analyser application a need to add text to the frequency and dB axis. If this can be done much simpler I’m open for suggestions :slight_smile:

Looking forward to hear from you!

#include <windows.h>
#include <stdio.h>
#include <math.h>	
#include <stdarg.h>		
#include <cstdio>
#include <gl/gl.h>
#include <gl/glu.h>
#include <gl/glaux.h>

#include "../../../juce.h"
#include "fftw3.h"

double PI = 3.14159265358979;

float	rot;	

// Copied from Nehes bitmap font tutorial 13
HDC			hDC=NULL;	// Private GDI Device Context
HGLRC		hRC=NULL;	// Permanent Rendering Context
HWND		hWnd=NULL;	// Holds Our Window Handle
HINSTANCE	hInstance;		// Holds The Instance Of The Application

GLuint		base;		// Base Display List For The Font Set
GLfloat		cnt1;			// 1st Counter Used To Move Text & For Coloring
GLfloat		cnt2;			// 2nd Counter Used To Move Text & For Coloring
// ------------------------------------------

class OpenGLCanvas  :	public OpenGLComponent,
	public Timer
{
public:

	OpenGLCanvas()
	{
		rot = 0;
		startTimer(40);
	}

	~OpenGLCanvas()
	{
	}

	void newOpenGLContextCreated()
	{
		
		// Copied from Nehes bitmap font tutorial 13
		rot = 0;
		glShadeModel(GL_SMOOTH);							// Enable Smooth Shading
		glClearColor(0.0f, 0.0f, 0.0f, 0.5f);				// Black Background
		glClearDepth(1.0f);									// Depth Buffer Setup
		glEnable(GL_DEPTH_TEST);							// Enables Depth Testing
		glDepthFunc(GL_LEQUAL);								// The Type Of Depth Testing To Do
		glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);	// Really Nice Perspective Calculations

		BuildFont();
		// ------------------------------------------
	}

	void renderOpenGL()	
	{
		
		gluPerspective (45.0f,
			this->getWidth() / (GLfloat) this->getHeight(),
			0.1f,
			100.0f);

		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);	// Clear Screen And Depth Buffer
		glLoadIdentity();									// Reset The Current Modelview Matrix
		glTranslatef(0.0f,0.0f,-1.0f);						// Move One Unit Into The Screen

		glRotatef(rot,0,0,1.0f);
		glColor3f(1.0f,0.0f,0.0f);
		glOrtho(0.0, 1.0, 0.0, 1.0, -1.0, 1.0);
		glBegin(GL_POLYGON);
		glVertex3f (0.25f, 0.25f, -0.01f);
		glVertex3f (0.75f, 0.25f, -0.01f);
		glVertex3f (0.75f, 0.75f, -0.01f);
		glVertex3f (0.25f, 0.75f, -0.01f);
		glEnd();

		rot += 1;

		// Copied from Nehes bitmap font tutorial 13
		glColor3f(1.0f*float(cos(cnt1)),1.0f*float(sin(cnt2)),1.0f-0.5f*float(cos(cnt1+cnt2)));
		glRasterPos2f(-0.45f+0.05f*float(cos(cnt1)), 0.32f*float(sin(cnt2)));
		glPrint("Active OpenGL Text With NeHe - %7.2f", cnt1);	// Print GL Text To The Screen
		cnt1+=0.051f;										// Increase The First Counter
		cnt2+=0.005f;										// Increase The First Counter
		// ------------------------------------------
	}

	void resized()
	{
	}

	void timerCallback()
	{
		repaint();
	}

	// Copied from Nehes bitmap font tutorial 13
	void BuildFont(void)					// Build Our Bitmap Font
	{
		HFONT	font;						// Windows Font ID
		HFONT	oldfont;					// Used For Good House Keeping

		base = glGenLists(96);				// Storage For 96 Characters

		font = CreateFont(	-24,			// Height Of Font
			0,								// Width Of Font
			0,								// Angle Of Escapement
			0,								// Orientation Angle
			FW_BOLD,						// Font Weight
			FALSE,							// Italic
			FALSE,							// Underline
			FALSE,							// Strikeout
			ANSI_CHARSET,					// Character Set Identifier
			OUT_TT_PRECIS,					// Output Precision
			CLIP_DEFAULT_PRECIS,			// Clipping Precision
			ANTIALIASED_QUALITY,			// Output Quality
			FF_DONTCARE|DEFAULT_PITCH,		// Family And Pitch
			"Courier New");					// Font Name

		oldfont = (HFONT)SelectObject(hDC, font);           // Selects The Font We Want
		wglUseFontBitmaps(hDC, 32, 96, base);				// Builds 96 Characters Starting At Character 32
		SelectObject(hDC, oldfont);							// Selects The Font We Want
		DeleteObject(font);									// Delete The Font
	}

	void KillFont(void)						// Delete The Font List
	{
		glDeleteLists(base, 96);			// Delete All 96 Characters
	}

	void glPrint(const char *fmt, ...)		// Custom GL "Print" Routine
	{
		char		text[256];				// Holds Our String
		va_list		ap;						// Pointer To List Of Arguments

		if (fmt == NULL)					// If There's No Text
			return;							// Do Nothing

		va_start(ap, fmt);					// Parses The String For Variables
		vsprintf_s(text, fmt, ap);			// And Converts Symbols To Actual Numbers
		va_end(ap);							// Results Are Stored In Text

		glPushAttrib(GL_LIST_BIT);			// Pushes The Display List Bits
		glListBase(base - 32);				// Sets The Base Character to 32
		glCallLists(strlen(text), GL_UNSIGNED_BYTE, text);	// Draws The Display List Text
		glPopAttrib();						// Pops The Display List Bits
	}
	// ------------------------------------------
}; 

//==============================================================================
/** This is the component that sits inside the "hello world" window, filling its
content area. In this example, we'll just write "hello world" inside it.
*/
class WindowContent    :	public Component,
	public Timer

{
	OpenGLCanvas* myOpenGLCanvas;

public:
	WindowContent()
	{
		// Setup OpenGl component
		myOpenGLCanvas = new OpenGLCanvas();
		addAndMakeVisible(myOpenGLCanvas);
		myOpenGLCanvas->makeCurrentContextActive();
		myOpenGLCanvas->broughtToFront();
	}

	~WindowContent()
	{
		deleteAllChildren ();
	}

	void paint (Graphics& g)
	{
	}

	void resized()
	{
		int x = this->getWidth();
		int y = this->getHeight();

		myOpenGLCanvas->setBounds(0,0,x,y);
	}

	void timerCallback()
	{
		repaint();
	}
};

//==============================================================================
/** This is the top-level window that we'll pop up. Inside it, we'll create and
show a HelloWorldContentComponent component.
*/
class MainWindow  : public DocumentWindow
{
public:
	//==============================================================================
	MainWindow()
		: DocumentWindow (T("DSP App"),
		Colours::lightgrey, 
		DocumentWindow::allButtons, 
		true)
	{
		setContentComponent(new WindowContent());

		setVisible (true);


		// centre the window on the desktop with this size
		centreWithSize (800, 800);

		setResizable(true,true);

		setResizeLimits(800,800,1600,1024);

	}

	~MainWindow()
	{
		// (the content component will be deleted automatically, so no need to do it here)
	}

	//==============================================================================
	void closeButtonPressed()
	{
		// When the user presses the close button, we'll tell the app to quit. This 
		// window will be deleted by the app object as it closes down.
		JUCEApplication::quit();
	}
};


//==============================================================================
/** This is the application object that is started up when Juce starts. It handles
the initialisation and shutdown of the whole application.
*/
class MainSetup : public JUCEApplication
{
	/* Important! NEVER embed objects directly inside your JUCEApplication class! Use
	ONLY pointers to objects, which you should create during the initialise() method
	(NOT in the constructor!) and delete in the shutdown() method (NOT in the
	destructor!)

	This is because the application object gets created before Juce has been properly
	initialised, so any embedded objects would also get constructed too soon.
	*/
	MainWindow* mainWindow;

public:
	//==============================================================================
	MainSetup()
		: mainWindow (0)
	{
		// NEVER do anything in here that could involve any Juce function being called
		// - leave all your startup tasks until the initialise() method.
	}

	~MainSetup()
	{
		// Your shutdown() method should already have done all the things necessary to
		// clean up this app object, so you should never need to put anything in
		// the destructor.

		// Making any Juce calls in here could be very dangerous...
	}

	//==============================================================================
	void initialise (const String& commandLine)
	{
		// just create the main window...
		mainWindow = new MainWindow();

		/*  ..and now return, which will fall into to the main event
		dispatch loop, and this will run until something calls
		JUCEAppliction::quit().

		In this case, JUCEAppliction::quit() will be called by the
		hello world window being clicked.
		*/
	}

	void shutdown()
	{
		// clear up..

		if (mainWindow != 0)
			delete mainWindow;
	}

	//==============================================================================
	const String getApplicationName()
	{
		return T("test...");
	}

	const String getApplicationVersion()
	{
		return T("1.0");
	}

	bool moreThanOneInstanceAllowed()
	{
		return true;
	}

	void anotherInstanceStarted (const String& commandLine)
	{
	}
};


//==============================================================================
// This macro creates the application's main() function..
START_JUCE_APPLICATION (MainSetup)

#10

Ok, a few things:

OpenGLContextCreated could/will be called many times in your app’s run. You’re creating a fresh font and set of lists every time. I don’t think that’s a problem, and I think OpenGL will clear up the old stuff, but just something to be aware of.

I don’t see where cnt1 and cnt2 are initialized. Maybe you should set them in the OpenGLContextCreated method, or at least the constructor.

And - since nothing else looks too odd, that’s what I’d start with. Print your text with constants you know are in your screen, like:
glRasterPos2f(100, 100);
and then extend from there.

Do you have OpenGL SuperBible yet BTW? It was a big help while I was getting this stuff done - it also has a bitmap font code.

Bruce


#11

[quote=“Bruce Wheaton”]Ok, a few things:

OpenGLContextCreated could/will be called many times in your app’s run. You’re creating a fresh font and set of lists every time. I don’t think that’s a problem, and I think OpenGL will clear up the old stuff, but just something to be aware of.

I don’t see where cnt1 and cnt2 are initialized. Maybe you should set them in the OpenGLContextCreated method, or at least the constructor.

And - since nothing else looks too odd, that’s what I’d start with. Print your text with constants you know are in your screen, like:
glRasterPos2f(100, 100);
and then extend from there.

Do you have OpenGL SuperBible yet BTW? It was a big help while I was getting this stuff done - it also has a bitmap font code.

Bruce[/quote]

Hi again

Thanks for letting me know of the OpenGLConetextCreated() part.

I forgot to add the initialization of the cnt1 and cnt2 when posting the code here - when added and initialized properly it still doesn’t work!

I have also tried to mess with the glRasterPos2f(x,y) wihtout luck!

I have just acquired the OpenGL SuperBible and will take a look!

Thanks again for your support …

Thomas :slight_smile:


#12

Try glWindowPos first though. That’s what I have - when I tried glRasterPos I got nothing.

Bruce


#13

[quote=“Bruce Wheaton”]Try glWindowPos first though. That’s what I have - when I tried glRasterPos I got nothing.

Bruce[/quote]

Just tried glWindowPos but the compiler can’t recognize that method, I get:

error C3861: ‘glWindowPos2i’: identifier not found

I read that glWindowPos() is only supported from OpenGl version 1.4 and up. I now wonder if I’m running an OpenGL older than 1.4 - how can I check if I’m running the right version?

I’ve just read about the opengl32.dll API (http://www.nullterminator.net/opengl32.html) the hDC handle worries me a bit - it is used when building the font:

oldfont = (HFONT)SelectObject(hDC, font); // Selects The Font We Want wglUseFontBitmaps(hDC, 32, 96, base); // Builds 96 Characters Starting At Character 32 SelectObject(hDC, oldfont); // Selects The Font We Want

I’m not sure how to set this handle in Juce properly - any suggestions?

Sorry for all the noise here

Thomas