Skip to main content
gamedev.net
🔒 Locked 🎮 Unity

C++ for a Java programmer

Started by Optus Jul 14, 2006 at 3:02 PM 1 replies 1k views
Original Post
Optus
Optus
Hi, I am an aspiring game dever, attempting for the first time to switch to C++ as the language of choice for my engine. Previously, I had created a fairly comprehensive 3D framework built upon LWJGL and Java. This framework also included a wrapper for a physics engine (www.trueaxis.com). I finally became fed up at maintaining the JNI/C++ code for each new version of TrueAxis, but badly wanted the new features! So, I endeavor to port, perhaps slightly redesign, my Java engine in C++. I think I'm mostly aware of the fundamental differences between Java and C++, especially with regards to memory management. Except, I find myself paraniod that I may be violating some C++ taboos with my working code so far. So, if you have a free moment, could you spend some time to critique my classes? I'm using Windows XP Pro sp2, Visual C++ 2005 Express and SDL. One thing I liked about developing in Java, was the seamless platform portability. So I'd like to try to remain compatible with Linux in my new engine, also since TrueAxis now has Linux support as well. So the first thing I wanted to do was wrap SDL's nasty display code away. I did so in a callback method reminiscent of JOGL or even OGRE. SDLCallback.h

#ifndef SDLCALLBACK_H
#define SDLCALLBACK_H

class SDLCallback {
public:
	/* Called when GL and SDL are fully initialized */
	virtual void Initialize () = 0;
	/* Called every frame */
	virtual void Render () = 0;
	/* Called when screen is resized */
	virtual void DisplayResized (int width, int height) = 0;
	/* Called when Iconified/Deiconified or Device changes */
	virtual void DisplayChanged (bool active, bool devicelost) = 0;
	/* Called at destruction phase while SDL and GL are valid */
	virtual void DisplayReleasing () = 0;
	/* Called when SDL quit message is received */
	virtual void DisplayCloseRequested () = 0;
	/* Called when the display is closed SDL and GL are not initialized */
	virtual void DisplayClosed () = 0;
};

#endif

SDLDisplay.h

#ifndef SDLDISPLAY_H
#define SDLDISPLAY_H
#include <string>
#include <vector>
using namespace std;
#include <SDL.h>
#include "SDLCallback.h"

class SDLDisplay {
public:
	SDLDisplay ();
	~SDLDisplay ();
	void SetDefaultDisplay ();
	void SetWidth(int value) { width = value; }
	void SetHeight(int value) { height = value; }
	void SetFrequency(int value) { frequency = value; }
	void SetColorDepth(int value) { colorDepth = value; }
	void SetX(int value) { x = value; }
	void SetY(int value) { y = value; }
	void SetAlpha(int value) { alpha = value; }
	void SetDepth(int value) { depth = value; }
	void SetStencil(int value) { stencil = value; }
	void SetFullscreen(bool value) { fullscreen = value; }
	void SetUndecorated(bool value) { undecorated = value; }
	void SetVsync(bool value) { vsync = value; }
	void AddListener (SDLCallback* listener);
	void Create ();
	void Destroy ();
	int GetWidth () { return width; }
	int GetHeight () { return height; }
private:
	SDL_Surface *screen;
	SDL_Event event;
	bool running;

	int width, height, frequency, colorDepth, x, y;
	int alpha, depth, stencil, samples, numAuxBuffers, accumBpp, accumAlpha;
	bool created, fullscreen, undecorated, vsync, stereo;
	std::string title;
	std::vector<SDLCallback*> callbacks;
};

#endif

SDLDisplay.cpp

#include "SDLDisplay.h"
#include "SDLCallback.h"

SDLDisplay::SDLDisplay () {
	this->SetDefaultDisplay();	
}
SDLDisplay::~SDLDisplay() {

}
void SDLDisplay::Create() {
	created = true;
	running = true;

	const SDL_VideoInfo *videoInfo;
	int result;

	result = SDL_Init(SDL_INIT_VIDEO);	
	if (result < 0) Destroy();

	videoInfo = SDL_GetVideoInfo();

	if (!videoInfo) Destroy();

	int sdlFlags = SDL_OPENGL | SDL_GL_DOUBLEBUFFER | SDL_HWPALETTE | SDL_RESIZABLE | SDL_HWSURFACE | SDL_HWACCEL;

	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

	screen = SDL_SetVideoMode(this->width, this->height, this->colorDepth, sdlFlags);

	if (!screen) Destroy();
	
	for (unsigned int i = 0; i < callbacks.size(); i++) 
		callbacks->Initialize();		

	for (unsigned int i = 0; i < callbacks.size(); i++) 		
		callbacks->DisplayResized(width, height);		
	

	while (running) {
		while (SDL_PollEvent (&event)) {
			switch (event.type) {
				case SDL_ACTIVEEVENT:
					for (unsigned int i = 0; i < callbacks.size(); i++) 
						callbacks->DisplayChanged(event.active.gain != 0, false);						
					break;
				case SDL_VIDEORESIZE:
					screen = SDL_SetVideoMode(event.resize.w, event.resize.h, colorDepth, sdlFlags);
					width = event.resize.w;
					height = event.resize.h;
					for (unsigned int i = 0; i < callbacks.size(); i++) 
						callbacks->DisplayResized(width, height);						
					break;
				case SDL_KEYDOWN:

					break;
				case SDL_QUIT:
					for (unsigned int i = 0; i < callbacks.size(); i++) 
						callbacks->DisplayCloseRequested();											
					break;
			}
		}
		for (unsigned int i = 0; i < callbacks.size(); i++) 
			callbacks->Render();
		SDL_GL_SwapBuffers( );
	}
	for (unsigned int i = 0; i < callbacks.size(); i++) 
		callbacks->DisplayReleasing();
	SDL_Quit();
	for (unsigned int i = 0; i < callbacks.size(); i++) 
		callbacks->DisplayClosed();
	exit(0);
}
void SDLDisplay::Destroy () {
	running = false;
}
void SDLDisplay::SetDefaultDisplay() {
	width = 1280;
	height = 1024;
	frequency = 75;
	colorDepth = 32;
	alpha = 8;
	stencil = 8;
	depth = 8;
	vsync = false;
	fullscreen = false;
}
void SDLDisplay::AddListener(SDLCallback *listener) {
	this->callbacks.push_back(listener);
}

Now I wanted to get an FPS read out on the screen so I created a few new classes. FontManager.h

#ifndef FONTMANAGER_H
#define FONTMANAGER_H

#include "SDL_ttf.h"

#include <map>
#include <string>

using namespace std;

class FontManager {
public:
	class Font {
	public:
		TextureData* textureData;
		vector<int>* charWidths;
		TTF_Font* font;
		Font (TextureData* textureData, vector<int>* charWidths, TTF_Font* font) {
			this->textureData = textureData;
			this->charWidths = charWidths;
			this->font = font;
		}
		~Font () {
			TTF_CloseFont(font);
			delete charWidths;
			delete textureData;
		}
	};
	std::string characters;
	FontManager () {
		characters.append(" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~");		
		TTF_Init();
	}	
	~FontManager () {
		TTF_Quit();
	}
	Font* LoadFont (std::string filename) {
		unsigned int textureDim = 1024;
		unsigned int charDim = 64;
		SDL_Color color = {255, 255, 255, 255};
		TTF_Font* font = TTF_OpenFont(filename.c_str(), 48);		
		SDL_Surface* fontImage = SDL_CreateRGBSurface(SDL_SWSURFACE, textureDim, textureDim, 24, 0xff000000, 0x00ff0000, 0x0000ff00, 0); 
		SDL_SetAlpha(fontImage, SDL_SRCALPHA, 0);
		int r2 = SDL_FillRect(fontImage, &fontImage->clip_rect, SDL_MapRGBA(fontImage->format, 0, 0, 0, 0));

		std::vector<int>* charWidths = new std::vector<int>;
		unsigned int charIdx = 0;
		for (unsigned int i = 0; i < textureDim; i += charDim) {
			for (unsigned int j = 0; j < textureDim; j += charDim, charIdx++) {
				if (charIdx > characters.size()) break;
				char c = characters[charIdx];				
				std::string cs;
				cs += c;

				int fw, fh;
				TTF_SizeText(font, cs.c_str(), &fw, &fh);
				charWidths->push_back(fw);

				SDL_Surface* fontSurf = TTF_RenderText_Blended(font, cs.c_str(), color);				

				SDL_Rect destRect;
				destRect.x = j;
				destRect.y = i;
				destRect.w = fw;
				destRect.h = fh;
				int ret = SDL_BlitSurface(fontSurf, &fontSurf->clip_rect, fontImage, &destRect);				
				SDL_FreeSurface(fontSurf);
			}
		}
		TextureData* td = new TextureData(fontImage);

		return new Font (td, charWidths, font);
	}
};

#endif

TextManager.h

#ifndef TEXTMANAGER_H
#define TEXTMANAGER_H

#include <map>
#include <vector>

using namespace std;

class TextManager {
public:
	class Text {
	public:
		bool show;
		std::string message;
		float x, y;
		float xscale, yscale;
		float r, g, b, a;
		FontManager::Font* font;
		Text () {
			r = 1;
			g = 1;
			b = 1;
			a = 1;
			xscale = 1;
			yscale = 1;
			x = 0;
			y = 0;			
			show = true;
		}
		~Text () {

		}
	};
	TextManager () {	
		FONT_DIM = 16;	
	}
	void Render () {
		glEnable(GL_BLEND);
		glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ONE);
		glActiveTexture(GL_TEXTURE0);
		
		for (unsigned int i = 0; i < texts.size(); i++) {
			RenderText(texts);
		}

		glDisable(GL_BLEND);
	}

	void Add (TextManager::Text* t) {
		texts.push_back(t);
	}
	void AddFont (FontManager::Font* f) {
		Texture* t = new Texture(f->textureData);
		int listBase = glGenLists(8 * 16);
		unsigned int cnt = 0;
		std::vector<int>* charWidths = f->charWidths;
		for (unsigned int y = 0; y < 8; y++) {
			for (unsigned int x = 0; x < 16; x++, cnt++) {
				glNewList(listBase + cnt, GL_COMPILE);
				float fdim = FONT_DIM;
				if (cnt < charWidths->size()) {
					//fdim = (float) charWidths->at(cnt);
				}
				float uoffset = (float) x / FONT_DIM;
				uoffset += fdim / FONT_DIM / FONT_DIM;
				glBegin(GL_QUADS);
				glMultiTexCoord2f(GL_TEXTURE0, (float) x / FONT_DIM, (float) y / FONT_DIM);
				glVertex2f(0, 0);
				glMultiTexCoord2f(GL_TEXTURE0, (float) x / FONT_DIM, (float) (y+1) / FONT_DIM);
				glVertex2f(0, FONT_DIM);
				glMultiTexCoord2f(GL_TEXTURE0, uoffset, (float) (y+1) / FONT_DIM);
				glVertex2f(fdim, FONT_DIM);
				glMultiTexCoord2f(GL_TEXTURE0, uoffset, (float) y / FONT_DIM);
				glVertex2f(fdim, 0);
				glEnd();
				if (cnt < charWidths->size()) {
					glTranslatef(fdim, 0, 0);
				} else {
					glTranslatef(FONT_DIM, 0, 0);
				}
				glEndList();
			}
		}
		fontTextures[f] = t;
		fontBase[f] = listBase;
	}

private:
	std::vector<TextManager::Text*> texts;
	std::map<FontManager::Font*, Texture*> fontTextures;
	std::map<FontManager::Font*, int> fontBase;
	float FONT_DIM;	

	void RenderText (Text* t) {
		if (t->show){ 
			fontTextures[t->font]->Bind();
			glListBase(-32 + fontBase[t->font]);
			glColor4f(t->r, t->g, t->b, t->a);

			glPushMatrix();
			glTranslatef(t->x, t->y, 0);
			glScalef(t->xscale, t->yscale, 1);
			glCallLists(t->message.size(), GL_UNSIGNED_BYTE, t->message.c_str());
			glPopMatrix();
		}
	}
};

#endif

And the main implementation of the SDLCallback main.cpp


#include <SDL.h>

#pragma comment (lib, "SDLmain.lib")
#pragma comment (lib, "SDL.lib")
#pragma comment (lib, "opengl32.lib")
#pragma comment (lib, "glu32.lib")
#pragma comment (lib, "SDL_image.lib")
#pragma comment (lib, "SDL_ttf.lib")

#include <windows.h>
#include <GL/glew.h>
#include <GL/gl.h>
#include <GL/glu.h>

#include <sstream>

#include "SDLDisplay.h"
#include "TextureData.h"
#include "Texture.h"
#include "FontManager.h"
#include "TextManager.h"
#include "Timer.h"

class ListenTest : public SDLCallback {
private:
	SDLDisplay* sd;

public:
	TextureData* td;
	Texture* t;
	FontManager* fm;
	TextManager* textManager;
	TextManager::Text* FpsText;
	
	Timer* frameTimer;

	ListenTest () {
		td = new TextureData(std::string("test.png"), TextureData::TextureType::PNG);
		fm = new FontManager();
		
		sd = new SDLDisplay;
		sd->SetDefaultDisplay();
		sd->SetWidth(1024);
		sd->SetHeight(768);
		sd->AddListener(this);
		sd->Create();
	}
	void Initialize () {
		GLenum err = glewInit();

		FontManager::Font* font = fm->LoadFont("tangle.ttf");
		textManager = new TextManager();
		textManager->AddFont(font);
		
		FpsText = new TextManager::Text();
		FpsText->font = font;
		FpsText->message.append("Rawr!");
		FpsText->x = 10;
		FpsText->y = 10;
		
		textManager->Add(FpsText);

		glClearColor(1, 0, 0, 0.2f);

		frameTimer = new Timer();

		t = new Texture(td);	
	}
	void Render () {
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
		GLfloat ratio = (GLfloat)sd->GetWidth() / (GLfloat)sd->GetHeight();
		glViewport(0, 0, (GLsizei)sd->GetWidth(), (GLsizei)sd->GetHeight());

		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		gluPerspective(45.0f, ratio, 0.1f, 100.0f);

		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();

		gluLookAt(0, 4, 2, 0, 0, 0, 0, 1, 0);

		t->Bind();
		glBegin(GL_QUADS);
		glTexCoord2f(1, 1);
		glVertex3f(-1, 0, -1);
		glTexCoord2f(1, 0);
		glVertex3f(-1, 0, 1);
		glTexCoord2f(0, 0);
		glVertex3f(1, 0, 1);
		glTexCoord2f(0, 1);
		glVertex3f(1, 0, -1);
		glEnd();

		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		glOrtho(0, sd->GetWidth(), sd->GetHeight(), 0, 0, 1); 
		glMatrixMode(GL_MODELVIEW);
		glPushMatrix();
		glLoadIdentity();

		glDisable(GL_DEPTH_TEST);
		glEnable(GL_TEXTURE_2D);
		glDisable(GL_LIGHTING);

		textManager->Render();
		glEnable(GL_DEPTH_TEST);

		glPopMatrix();

		double frameTime = 1 / frameTimer->GetElapsedSeconds();

		ostringstream sstest;
		sstest << frameTime;
		FpsText->message.clear();
		FpsText->message.append(sstest.str());

		glFinish();

		frameTimer->Update();
	}
	void DisplayResized (int width, int height) {	    
	}
	void DisplayChanged (bool active, bool lost) {
	}
	void DisplayReleasing () {
		delete t;
		delete td;
		delete FpsText;
		delete textManager;
		delete fm;
	}
	void DisplayCloseRequested () {
		sd->Destroy();
	}
	void DisplayClosed () {
	}
};

int main(int argc, char* argv[])
{  
	ListenTest* lt = new ListenTest;

	delete lt;

	return 0;
}

I have a few questions: 1) Is there anything "wrong" with how I've programmed these functions? Anything that inspires that ugly feeling in you? 2) Am I leaking memory everywhere? I try to ensure that every new has a delete, or SDL_Init has it's SDL_Quit. Do I need to worry about the stl containers I've used? 3) Do I over use the STL containers? 4) Am I hopelessly tied to windows already, or is there some hope for linux compatibility? Anything else... Thanks for any insight you guys can provide. And if by chance any of this stuff is useful, feel free to steal the code to your heart's content.
Zahlman
Zahlman
Quote:
Original post by Optus
I'm using Windows XP Pro sp2, Visual C++ 2005 Express and SDL. One thing I liked about developing in Java, was the seamless platform portability. So I'd like to try to remain compatible with Linux in my new engine, also since TrueAxis now has Linux support as well.

So the first thing I wanted to do was wrap SDL's nasty display code away. I did so in a callback method reminiscent of JOGL or even OGRE.

SDLCallback.h
*** Source Snippet Removed ***


Good idea, and good so far.

Quote:

SDLDisplay.h
*** Source Snippet Removed ***


The large amount of set methods is a warning signal, but at least they're not get/set pairs (ugh). Consider setting things in the constructor where possible. Also, inline short constructors, and don't mention destructors at all if they're not going to do anything. Instead of setDefaultDisplay(), you can set up all those values in the default constructor and provide a multiple-argument one to handle custom values, or just write the multiple-argument one and have a bunch of default parameters. (This is a C++ism. Note that you cannot easily create the effect of "keyword arguments".)

The vector of callback pointers raises issues. Yes, you do need that level of indirection for polymorphism to work. But now you have a "pointer ownership" question on your hands. A vector will NOT delete pointed-at things in its destructor. It cleans up its toys (the elements, i.e. the pointers themselves); you clean up yours.

SDLDisplay.cpp*** Source Snippet Removed ***


Use initializer lists in constructors.

Don't exit() or SDL_Quit() from inside the "main loop". (Also, 'Create()' should probably be renamed. 'Run()' maybe?) That stuff is really low-level, global "OMG BRING EVERYTHING TO A HALT RIGHT NOW" stuff, which you don't want wrapped inside an object abstraction.

If screen initialization fails, right now you will still try to initialize and displayResized() any pre-loaded callbacks. This is bad news. Just return from the function at that point. Better yet, do the actual initialization work in the constructor, and throw an exception on failure.

Use iterators to iterate through vectors. The same principle as java.lang.Iterators, but the interface is rather different (and oriented towards the C++ non-polymorphic-by-default, "you don't pay for - or get - what you don't ask for" bias):

typedef std::vector<SDLCallback*> callbackContainer;for (callbackContainer::iterator it = callbacks.begin();     it != callbacks.end(); ++it) { // <-- notice pre-increment;      // that is idiomatic and *sometimes* faster  it->Initialize();}


Now, if you later changed 'callbacks' to a std::list (which doesn't support operator[], you would have no changes to any of the loops; you just change the typedef (which you can store as a class member).


FontManager.h*** Source Snippet Removed ***


The LoadFont() implementation is long enough that it should definitely go in an implementation file.

Initialize stuff using initializer lists (and anyway, get out of the habit of using explicit 'this' where it isn't necessary):

Font (TextureData* textureData, vector<int>* charWidths, TTF_Font* font) :  textureData(textureData), charWidths(charWidths), font(font) {}


This way of initializing things is idiomatic, often more efficient, never less efficient, and sometimes the only legal option. Also notice that because of C++'s scoping rules, you can have the parameters and members share names, and there is no need to disambiguate. The name inside the parentheses is the parameter (since that is in a tighter scope), and the name outside has to be the member (because you couldn't possibly be initializing the parameter).

The separation of Font and FontManager may be premature. Try just using a Font(std::string) constructor first, instead.

The 'characters' string could be initialized by the constructor. Better yet, it could be a static const member, since it's just a table for the use of FontManagers:

class FontManager {  static const std::string characters;  // etc.}const std::string FontManager::characters = " !\"#$%&'()*+,-./0123456789:;<=>?@""ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";// note the technique for line-wrapping string literals. They will be pasted// together at compile-time - not a compiler optimization, but a language spec


Don't store a pointer to the vector like that. Store an actual object, pass by const reference to the constructor, and use that parameter to initialize (via copy-construction) the member in the initialization list.

You can initialize SDL_Rects with 'aggregate initialization' syntax, because they are just plain structs: 'SDL_Rect destRect = { j, i, fw, fh };'. Alternatively, consider making a wrapper for SDL_Rect which has (a) constructor(s).

For the other classes, similar advice applies; you should have the general idea by now.
load_bitmap_file
load_bitmap_file
EDIT2: Whoops, beaten by Zahlman. You can probably glaze over most of this post then. (But do read the C++ FAQ Lite!)

1. In C++ there's no need to new everything and manually manage the memory. I'm assuming you already know about the stack and the heap. You generally allocate on the heap with: very large objects, objects that need to outlive their scope, and when you want polymorphic behavior for stored objects in a container. Mostly everything else goes on the stack (things like static variables and globals go in a separate special place).

2. It's dangerous dealing with raw new'd pointers so you will want to look into smart pointers, namely boost::shared_ptr. There are other types in the boost smart pointer library, and also std::auto_ptr, but make sure you understand their usage before throwing them in your code. For example, std::auto_ptr cannot be used with the STL containers (but boost::shared_ptr can).

3. The STL is there for you to use it, don't worry about "overusing" it.

4. I've never used SDL or OpenGL but both are supposed to be cross-platform. You've included but I didn't see any windows code (I probably overlooked something). Does your timer class use it? Without more code, I can't say, but it doesn't look like you're very tied down at all.

EDIT: Read the C++ FAQ Lite. It has a ton of good information.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.