Sign in to follow this  
  • entries
    42
  • comments
    85
  • views
    46481

Engine Overview

Sign in to follow this  

275 views

I think I've put this off long enough. I'm going to explain how my program is structured and why. There's so much to cover, its ridiculous. I'll start with the SDL-based framework I'm using and the sub-systems it runs. Its really very simple. I'm going to post all my code for anyone interested. The engine uses SDL, OpenGL and the GLEW library, and eventually OpenAL for audio. The GLU library is linked solely for it's mipmap-generation function. I found the code replacements for gluLookAt and gluPerspective somewhere, and if I find a mipmap generator, its outta there! Those are all the libraries.

I'm developing on XP using 2008 express, but trying to avoid platform-specific code so I can release for Mac and Linux too. I'm running into problems deploying for Vista(!), so I'm not sure cross-platform dev will happen, but I think its worth it to try to remain platform agnostic. The entire project is in C++; I'm not using a scripting language for a project that doesn't need it. Someday I'll voyage into Python and Lua. Anyway, here's the engine core and I'll explain below.

SDL_Framework.hpp

#ifndef MJS_FRAMEWORK_H
#define MJS_FRAMEWORK_H

#include
#include "Include.hpp"
#include "Version.hpp"
#include "Log.hpp"
#include "Util.hpp"
#include "Event.hpp"
//#include "Audio.hpp"
#include "Render.hpp"
#include "Math.hpp"
#include "Resource.hpp"
#include "Camera.hpp"
#include "Render.hpp" //for vertex count
#include "Texture.hpp" //for texture::flipVertical()
//#include "GUI.hpp"


const int MOUSE_B_COUNT = 5; // SDL mouse buttons


class SDL_Framework //: public GUI_EventListener
{
public:
SDL_Framework();
virtual ~SDL_Framework();

void Execute();

protected:
virtual int PreExecute() = 0;
virtual int PostExecute() = 0;
virtual int Update(ulong dT) = 0;
virtual void mouseEvent(const MouseEvent& evt) = 0;
virtual void keyEvent(const KeyEvent& evt) = 0;
//virtual void guiEvent(const GUI_Event& evt) = 0;

bool toggleWindowMode();
void takeScreenshot();
bool setVideoMode();
bool setVideoMode(int, int, int);

bool executing;
std::string app_name;

vec2 cursor_pos;
vec2 cursor_delta;
bool mouse_locked;

private:
bool ConfigEngine();

// Input
bool keys[SDLK_LAST];
bool mouse_buttons[MOUSE_B_COUNT];

// Video
SDL_Surface *v_screen; // video screen pointer
const SDL_VideoInfo *v_info; // screen info
int v_flags; // video flags
int v_bpp; // bits per pixel
int v_frames; // how many frames so far this second
bool vertical_sync;
bool anti_aliasing;

// Time
uint e_sec, e_min, e_hour; // elapsed time vars
ulong lastFrameIndex;
ulong thisFrameIndex;
float averageFPS;
uint maxFPS;
uint minFPS;
ulong totalFrames;
uint totalSeconds;
};


#endif






SDL_Framework.cpp
First is the Execute function.
Last is everything else.

void SDL_Framework::Execute()
{
SDL_version sdl_ver;
SDL_VERSION(&sdl_ver);
bool dllGood = SDL_VERSION_ATLEAST(sdl_ver.major, sdl_ver.minor, sdl_ver.patch);
string accept = "unknown";
if(dllGood) accept="(GOOD)"; else accept="(FAILED)";
log_write(LOG_FILE, ">>> SDL compile version: %d.%d.%d", sdl_ver.major, sdl_ver.minor, sdl_ver.patch);
sdl_ver = *SDL_Linked_Version();
log_write(LOG_FILE, ">>> SDL runtime version: %d.%d.%d %s", sdl_ver.major, sdl_ver.minor, sdl_ver.patch, accept.c_str());

if(!dllGood)
{
log_write("!!! Incorrect SDL version");
return;
}

if( !ConfigEngine() ) return;

if( !resource::InitResources() ) return;

//resource::logFiles();

if(SDL_WasInit(SDL_INIT_VIDEO)==0)
{
if(SDL_Init(SDL_INIT_VIDEO) > 0) log_write("!!! SDL_Init failed");
}

char driverName[256];
log_write("+ SDL Video Driver (%s)", SDL_VideoDriverName(driverName, 256));

char title[256];
sprintf(title, "%s v%d.%d.%d", app_name.c_str(), VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
SDL_WM_SetCaption(title, ApplicationTitle);
SDL_WM_SetIcon( IMG_Load( iconFile ), NULL); //must be after video is initialized, before first call to SDL_SetVideoMode

// you must set the video mode in SDL before you can access GL functions
if( !setVideoMode() ) return;

render::InitRenderer();
//if( !audio::initAudio() ) log_write("! audio system cannot initialize!");

SDL_ShowCursor(SDL_DISABLE);
//SDL_WM_GrabInput(SDL_GRAB_ON);
SDL_EnableUNICODE(1);

SDL_SysWMinfo info;
SDL_VERSION(&info.version); // this is important!
if(!SDL_GetWMInfo(&info)){ log_write("!!! Window info cannot be obtained!"); return; }


/// Position the window
#ifdef __WIN32__
if(!render::isFullscreen()) SetWindowPos(info.window, HWND_TOP, 50, 50, render::getWidth(), render::getHeight(), SWP_NOSIZE);
//GetLastError()
//log_write(LOG_TERMINAL, "- WIN cant change window pos!");
#endif

if( PreExecute() != 0 )
{
log_write("-!! Fatal error occured during Pre-Execute");
return;
}


//////////////////////////
// MAIN LOOP //
//////////////////////////
executing = true;

static ulong dT = 0; // delta time in seconds
static ulong lastTime = 0;
lastFrameIndex = 0;
thisFrameIndex = SDL_GetTicks();

while(executing)
{

// Time
lastFrameIndex = thisFrameIndex;
thisFrameIndex = SDL_GetTicks();
dT = (thisFrameIndex - lastFrameIndex);

if( dT > 300 ) dT = 300;

lastTime += dT;

if(lastTime >= 1000) // update the FPS
{
lastTime = lastTime - 1000;
e_sec++;
if(e_sec==60)
{
e_sec=0;
e_min++;
//seedRandom(e_min); // reseed the random generator every minute (problem for deterministic playback)
}
if(e_min==60)
{
e_min=0;
e_hour++;
}
render::setFPS(v_frames);

//log_write(LOG_FILE, "| FPS: %d", render::getFPS());
if(v_frames > maxFPS) maxFPS = v_frames;
if(v_frames < minFPS) minFPS = v_frames;
totalFrames += v_frames;
totalSeconds++;
if(totalFrames < 4000000000) // bounds check
averageFPS = (float)totalFrames / ((float)totalSeconds);
v_frames=0;
}


render::clear();

if(Update(dT)!=0) executing = false;

//render::checkGLError();

v_frames++;

SDL_GL_SwapBuffers();

// this is so the CPU gets a break when v-sync is disabled, otherwise, cpu usage is around 100%!!!
//if(!render::isFullscreen())
SDL_Delay(1); // this should only be enabled in windowed mode, or for saving the battery on laptops

render::resetVertexCount();


// Input / Events
for(int a=0; aif(keys[a]) keyEvent( KeyEvent(a, KEY_STILL_DOWN, 0) );

// generates mouse events to maintain the button state
for(int b=0; bif(mouse_buttons) mouseEvent( MouseEvent(b+1, ME_HELD, (int)cursor_pos.x, (int)cursor_pos.y) );


SDL_PumpEvents();
SDL_Event event;
while( SDL_PollEvent(&event) )
{
switch( event.type )
{
case SDL_MOUSEMOTION:
if( mouse_locked )
{
// keep mouse in the middle and add the displacement to cursor_pos
SDL_WarpMouse(render::getMiddleX(), render::getMiddleY());
// invert the y coord system for SDL/OpenGL compatibility
cursor_delta = vec2( (float)(event.motion.x-cursor_pos.x), (float)((render::getHeight()-event.motion.y)-cursor_pos.y));
cursor_pos += cursor_delta;
}else
cursor_pos = vec2((float)event.motion.x, (float)render::getHeight() - (float)event.motion.y);

if(cursor_pos.x > (float)render::getWidth()) cursor_pos.x = (float)render::getWidth(); else if(cursor_pos.x < 0) cursor_pos.x = 0;
if(cursor_pos.y > (float)render::getHeight()) cursor_pos.y = (float)render::getHeight(); else if(cursor_pos.y < 0) cursor_pos.y = 0;

mouseEvent( MouseEvent(ME_NULL, ME_MOVE, (int)cursor_pos.x, (int)cursor_pos.y) );
break;

case SDL_MOUSEBUTTONDOWN:
mouseEvent( MouseEvent(event.button.button, ME_PRESSED, (int)cursor_pos.x, (int)cursor_pos.y) );
mouse_buttons[event.button.button-1] = true;
break;

case SDL_MOUSEBUTTONUP:
mouseEvent( MouseEvent(event.button.button, ME_RELEASED, (int)cursor_pos.x, (int)cursor_pos.y) );
mouse_buttons[event.button.button-1] = false;
break;

case SDL_KEYDOWN:
{
int correctKey = event.key.keysym.sym;
if( event.key.keysym.sym >= 32 && event.key.keysym.sym <= 126 ) correctKey = event.key.keysym.unicode;
keyEvent( KeyEvent(correctKey, KEY_DOWN, event.key.keysym.mod) );
keys[event.key.keysym.sym] = true;
}
break;

case SDL_KEYUP:
keyEvent( KeyEvent(event.key.keysym.sym, KEY_UP, 0) );
keys[event.key.keysym.sym] = false;
break;

default:
switch(event.type)
{
case SDL_ACTIVEEVENT:
break;
case SDL_SYSWMEVENT:
break;
case SDL_VIDEORESIZE:
//if(!setVideoMode(event.resize.w, event.resize.h, v_bpp)) log_write("- Cannot set Video Mode!!!");
break;
case SDL_VIDEOEXPOSE: // the screen needs to be redrawn, not useful right now
break;
case SDL_QUIT: //User hit the X (or equivelent)
executing = false;
break;
default:
log_write(LOG_FILE, "- unknown event: %d", event.type);
break;
}
}
}// while

}// END OF MAIN LOOP



//////////////////////////
// EXIT //
//////////////////////////

if( PostExecute() != 0 ) log_write("-!! Error occured during Post-Execute");

resource::logFiles();
resource::UninitResources();

render::UninitRenderer();

//if( !audio::uninitAudio() ) log_write("! audio system cannot uninitialize!");

SDL_Quit();
}








#include "SDL_Framework.hpp"

using namespace std;

// window defaults
int win_width = 800;
int win_height = 600;
int full_width = 800;
int full_height = 600;

const char* ApplicationTitle = "MMX";
const char* configFile = "data/config.cfg";
const char* iconFile = "data/icon.bmp";



SDL_Framework::SDL_Framework()
{
log_init();

#ifdef _DEBUG
log_write(">>> DEBUG %d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
#endif
#ifdef MJS_RELEASE
log_write(">>> RELEASE %d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
#endif

executing = false;

// TIME
e_hour = 0;
e_min = 0;
e_sec = 0;
lastFrameIndex = 0;
thisFrameIndex = 0;
averageFPS = 0.0f;
maxFPS = 0;
minFPS = 10000;
totalFrames = 0;
totalSeconds = 0;

// INPUT
memset(keys, false, SDLK_LAST);
memset(mouse_buttons, false, MOUSE_B_COUNT);
cursor_pos = vec2(0.0f, 0.0f);
cursor_delta = vec2(0.0f, 0.0f);
mouse_locked = false;

// VIDEO
render::setWidth(win_width);
render::setHeight(win_height);
v_bpp = 16;
v_flags = 0;
v_screen = 0;
v_info= 0;
v_frames = 0;

vertical_sync = false;
anti_aliasing = false;

}



SDL_Framework::~SDL_Framework()
{
log_write(">>> Elapsed time [%d:%d:%d]", e_hour, e_min, e_sec);
log_write(">>> Average FPS: %.2f", averageFPS);
log_write(">>> Min FPS: %d", minFPS);
log_write(">>> Max FPS: %d", maxFPS);
log_write(">>> exiting...");
log_deinit();

}


// ConfigEngine reads the config file and returns the applications' path for use with the resource manager
// If the config file doesn't exist, one is created. If it has bad values, defaults are used.

bool SDL_Framework::ConfigEngine()
{
FILE* fp;
fopen_s(&fp, configFile, "rt");

if( fp == NULL )
{
log_write("+ Cannot find config file, creating...");
fopen_s(&fp, configFile, "wt");
if(fp==NULL)
{
log_write("!!! Cannot create new config file!!");
return false;
}
fputs("[FULLSCREEN] 0\n", fp);
fputs("[VIDEO] 800 600 16\n", fp);
fputs("[VSYNC] 0\n", fp);
fputs("[AA] 0\n", fp);
fclose(fp);

fopen_s(&fp, configFile, "rt"); // open the new config file
}

char buffer[256];

fgets(buffer, 256, fp);
int fullscreen;
sscanf(buffer, "[FULLSCREEN] %d", &fullscreen);
render::setFullscreen((bool)fullscreen);

fgets(buffer, 256, fp);
int temp_w, temp_h;
sscanf(buffer, "[VIDEO] %d %d %d", &temp_w, &temp_h, &v_bpp);
render::setWidth(temp_w);
render::setHeight(temp_h);
if(render::isFullscreen()){ full_width = temp_w; full_height = temp_h; }
else{ win_width = temp_w; win_height = temp_h; }

fgets(buffer, 256, fp);
int vsync;
sscanf(buffer,"[VSYNC] %d", &vsync);
vertical_sync = (bool)vsync;

fgets(buffer, 256, fp);
int aa;
sscanf(buffer,"[AA] %d", &aa);
anti_aliasing = (bool)aa;

fclose(fp);
return true;
}



bool SDL_Framework::setVideoMode(int nw, int nh, int nbpp)
{
render::setWidth(nw);
render::setHeight(nh);
v_bpp = nbpp;
return setVideoMode();
}



bool SDL_Framework::setVideoMode()
{
SDL_GL_SetAttribute( SDL_GL_RED_SIZE, 5 );
SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, 5 );
SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, 5 );
SDL_GL_SetAttribute( SDL_GL_ALPHA_SIZE, 8 );
SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, 8);
SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );
//SDL_GL_SetAttribute( SDL_GL_STENCIL_SIZE, 1);

if( anti_aliasing )
{
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); // AA
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4); // AA
}else
{
SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1); // find out if this and AA can co-exist
}

log_write("+ Vertical-Sync: %d", SDL_GL_SetAttribute( SDL_GL_SWAP_CONTROL, vertical_sync));

if(render::isFullscreen()==true)
v_flags = SDL_OPENGL | SDL_FULLSCREEN;
else
v_flags = SDL_OPENGL /* | SDL_RESIZABLE*/;

if(SDL_VideoModeOK(render::getWidth(), render::getHeight(), v_bpp, v_flags)!=0)
{
if(v_screen) SDL_FreeSurface(v_screen);
v_screen = SDL_SetVideoMode(render::getWidth(), render::getHeight(), v_bpp, v_flags);

if(render::isFullscreen()==true && (v_screen->flags==SDL_OPENGL || SDL_FULLSCREEN))
log_write("+ Going fullscreen...");
else
if(render::isFullscreen()==false && v_screen->flags==SDL_OPENGL /* || SDL_RESIZABLE*/)
log_write("+ Going to window mode...");

if(v_screen==NULL)
{
log_write("!!! Video FAILED: (%dx%dx%d): %s" , render::getWidth(), render::getHeight(), v_bpp, SDL_GetError());
return false;
}
else
log_write("+ Video: (%dx%dx%d)", render::getWidth(), render::getHeight(), v_bpp);
}
else
{
log_write("!!! Video mode not available: (%d)x(%d)x(%d)", render::getWidth(), render::getHeight(), v_bpp);
return false;
}
// Information about the current video settings.
v_info = SDL_GetVideoInfo();
//log_write(LOG_FILE, "+ Best bpp:%d", v_info->vfmt->BitsPerPixel);
return true;
}




bool SDL_Framework::toggleWindowMode()
{
if(render::isFullscreen()==true) render::setFullscreen(false); else render::setFullscreen(true);
/*
if(render::isFullscreen() && setVideoMode(full_width, full_height, v_bpp))
{
log_write("+ Window mode toggle successful");
Camera::getActive()->setZoom(Camera::getActive()->getZoom());
//camera->reset();
}else if(!render::isFullscreen() && setVideoMode(win_width, win_height, v_bpp))
{
log_write("+ Window mode toggle successful");
Camera::getActive()->setZoom(Camera::getActive()->getZoom());
}
else
{
log_write("-!- Window mode toggle failed. Cannot set new video mode.");
return false;
}*/

return true;
}



void SDL_Framework::takeScreenshot()
{
unsigned char* buffster = new unsigned char[render::getWidth()*render::getHeight()*3];
glReadPixels(0, 0, render::getWidth(), render::getHeight(), GL_RGB, GL_UNSIGNED_BYTE, buffster);
SDL_Surface* screenshot = SDL_CreateRGBSurface(SDL_SWSURFACE, render::getWidth(), render::getHeight(), 24,
#if SDL_BYTEORDER == SDL_LIL_ENDIAN
0x000000FF, 0x0000FF00, 0x00FF0000, 0
#else
0x00FF0000, 0x0000FF00, 0x000000FF, 0
#endif
);

texture::flipVertical(buffster, render::getWidth(), render::getHeight(), 3);
memcpy(screenshot->pixels, buffster, (render::getWidth()*render::getHeight()*3) );

char sname[255];
sprintf(sname, "%s/screenshots/screenshot_%d%d.bmp", resource::getTopDir().c_str(), e_min, e_sec);
SDL_SaveBMP(screenshot, sname);
SDL_FreeSurface(screenshot);
delete buffster; buffster=0;
}







The include.hpp header just includes SDL and GLEW (GL extension header). I'll explain each header later. All of my code seems fairly simple and practical to ME, so I hope none of this is confusing. The SDL_Framework is just a class that initializes all subsystems required for the game, and runs the main loop, sending Update() calls to the game's application class (that derives from SDL_Framework). So from the program's entry point...

int main(int argc, char *argv[])
{
MMX_App app;
app.Execute();
}




... I create an instance of the game and call it's Execute() function, derived from SDL_Framework.

Execute() configures the engine from file, initializes the video and sound, and calls the virtual PreExecute() function before running the main loop. The (tenatively) MMX_App class uses the PreExecute() call to load anything it needs before starting execution. Really it just needs to load the menu, or even just the loading screen for the menu which can load on-demand in the main loop-
the idea is to get the program on-screen as fast as possible so people aren't wondering if its running.

The main loop has three tasks. It collects and processes user input, it updates the time variables, and it updates the derived class via the Update() function, passing the deltaTime as an argument. Currently, the key and mouse-button states are kept in two bool arrays. This was just temporary implementation until the need for a more complex system arose. I suppose I could put the duties of the input system into an object, but they're fine in the engine loop for now. The video vars are stored in the framework class instead of a render class because they manage the SDL video surface. I wanted to keep the rendering calls separate from the framework. The window mode and screenshot capture are in-progess (kinda). The time vars are self-explanatory.

You can see that its a straight-forward system. You will probably also notice that I keep my resource and rendering calls in namspaces instead of classes. There's plenty more to follow!
Sign in to follow this  


2 Comments


Recommended Comments

"... and if I find a mipmap generator, ..."

Try glGenerateMipmapEXT(GL_TEXTURE_2D);
If this extension is not supported and OpenGL >= 1.4 try:
glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
For even older cards (OpenGL < 1.4) try:
glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_TRUE);

Share this comment


Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now