From 4d2573038867df3857501ad6bad8ef0d75807112 Mon Sep 17 00:00:00 2001 From: Nicolay Korslund Date: Wed, 8 Sep 2010 15:09:39 +0200 Subject: [PATCH] Added SDL+OpenGL 2D rendering backend (ony basic features) --- rend2d/servers/sdl_gl_driver.cpp | 321 ++++++++++++++++++++++++ rend2d/servers/sdl_gl_driver.hpp | 132 ++++++++++ rend2d/tests/Makefile | 10 +- rend2d/tests/output/sdl_move_test.out | 0 rend2d/tests/output/sdlgl_move_test.out | 0 rend2d/tests/sdl_move_test.cpp | 30 +++ rend2d/tests/sdlgl_move_test.cpp | 31 +++ 7 files changed, 522 insertions(+), 2 deletions(-) create mode 100644 rend2d/servers/sdl_gl_driver.cpp create mode 100644 rend2d/servers/sdl_gl_driver.hpp create mode 100644 rend2d/tests/output/sdl_move_test.out create mode 100644 rend2d/tests/output/sdlgl_move_test.out create mode 100644 rend2d/tests/sdl_move_test.cpp create mode 100644 rend2d/tests/sdlgl_move_test.cpp diff --git a/rend2d/servers/sdl_gl_driver.cpp b/rend2d/servers/sdl_gl_driver.cpp new file mode 100644 index 000000000..aadb6abce --- /dev/null +++ b/rend2d/servers/sdl_gl_driver.cpp @@ -0,0 +1,321 @@ +#include "sdl_gl_driver.hpp" + +#include +#include +#include +#include +#include + +using namespace Mangle::Rend2D; + +void SDLGL_Sprite::draw(Sprite *s, // Must be SDLGL_Sprite + int x, int y, // Destination position + int sx, int sy, // Source position + int w, int h // Amount to draw. -1 means remainder. + ) +{ + // Get source surface + SDLGL_Sprite *other = dynamic_cast(s); + assert(other != NULL); + SDL_Surface *img = other->getSurface(); + + // Check coordinate validity + assert(sx <= img->w && sy <= img->h); + assert(x <= surface->w && y <= surface->h); + assert(sx >= 0 && sy >= 0); + + // Compute width and height if necessary + if(w == -1) w = img->w - sx; + if(h == -1) h = img->h - sy; + + // Check them if they're valid + assert(w >= 0 && w <= img->w); + assert(h >= 0 && h <= img->h); + + SDL_Rect dest; + dest.x = x; + dest.y = y; + dest.w = w; + dest.h = h; + + SDL_Rect src; + src.x = sx; + src.y = sy; + src.w = w; + src.h = h; + + // Do the Blitman + SDL_BlitSurface(img, &src, surface, &dest); +} + +SDLGL_Sprite::SDLGL_Sprite(SDL_Surface *s, bool autoDelete) + : surface(s), autoDel(autoDelete) +{ + assert(surface != NULL); +} + +SDLGL_Sprite::~SDLGL_Sprite() +{ + if(autoDel) + SDL_FreeSurface(surface); +} + +void SDLGL_Sprite::fill(int value) +{ + SDL_FillRect(surface, NULL, value); +} + +int SDLGL_Sprite::width() { return surface->w; } +int SDLGL_Sprite::height() { return surface->h; } + +SDLGLDriver::SDLGLDriver() : display(NULL), realDisp(NULL) +{ + if (SDL_InitSubSystem( SDL_INIT_VIDEO ) == -1) + throw std::runtime_error("Error initializing SDL video"); +} +SDLGLDriver::~SDLGLDriver() +{ + if(display) delete display; + SDL_Quit(); +} + +// Surface used for the screen. Since OpenGL surfaces must have sizes +// that are powers of 2, we have to "fake" the returned display size +// to match the screen, not the surface itself. If we don't use this, +// the client program will get confused about the actual size of our +// screen, thinking it is bigger than it is. +struct FakeSizeSprite : SDLGL_Sprite +{ + int fakeW, fakeH; + + FakeSizeSprite(SDL_Surface *s, int fw, int fh) + : SDLGL_Sprite(s), fakeW(fw), fakeH(fh) + {} + + int width() { return fakeW; } + int height() { return fakeH; } +}; + +static int makePow2(int num) +{ + assert(num); + if((num & (num-1)) != 0) + { + int cnt = 0; + while(num) + { + num >>= 1; + cnt++; + } + num = 1 << cnt; + } + return num; +} + +void SDLGLDriver::setVideoMode(int width, int height, int bpp, bool fullscreen) +{ + unsigned int flags; + + if(display) delete display; + + flags = SDL_OPENGL; + + if (fullscreen) + flags |= SDL_FULLSCREEN; + + SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 ); + SDL_GL_SetAttribute( SDL_GL_SWAP_CONTROL, 1 ); + + // Create the surface and check it + screen = SDL_SetVideoMode(width, height, bpp, flags); + if(screen == NULL) + throw std::runtime_error("Failed setting SDL video mode"); + + // Expand width and height to be powers of 2 + int width2 = makePow2(width); + int height2 = makePow2(height); + + // Create a new SDL surface of this size + const SDL_PixelFormat& fmt = *(screen->format); + realDisp = SDL_CreateRGBSurface(SDL_SWSURFACE,width2,height2, + fmt.BitsPerPixel, + fmt.Rmask,fmt.Gmask,fmt.Bmask,fmt.Amask); + + // Create a sprite directly representing the display surface. This + // allows the user to blit to it directly. + display = new FakeSizeSprite(realDisp, width, height); + + // Set up the OpenGL format + nOfColors = fmt.BytesPerPixel; + + if(nOfColors == 4) + { + if (fmt.Rmask == 0x000000ff) + texture_format = GL_RGBA; + else + texture_format = GL_BGRA; + } + else if(nOfColors == 3) + { + if (fmt.Rmask == 0x000000ff) + texture_format = GL_RGB; + else + texture_format = GL_BGR; + } + else + assert(0 && "unsupported screen format"); + + glEnable(GL_TEXTURE_2D); + + // Have OpenGL generate a texture object handle for us + glGenTextures( 1, &texture ); + + // Bind the texture object + glBindTexture( GL_TEXTURE_2D, texture ); + + // Set the texture's stretching properties + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); +} + +void SDLGLDriver::updateNoSwap() +{ + if(!realDisp) return; + + // Fist, set up the screen texture: + + // Bind the texture object + glBindTexture( GL_TEXTURE_2D, texture ); + + // Edit the texture object's image data + glTexImage2D( GL_TEXTURE_2D, 0, nOfColors, realDisp->w, realDisp->h, 0, + texture_format, GL_UNSIGNED_BYTE, realDisp->pixels ); + + glClear(GL_COLOR_BUFFER_BIT| GL_DEPTH_BUFFER_BIT); + glLoadIdentity(); + + // OpenGL barf. Set up the projection to match our screen + int vPort[4]; + glGetIntegerv(GL_VIEWPORT, vPort); + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + glOrtho(0, vPort[2], 0, vPort[3], -1, 1); + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + + glBegin( GL_QUADS ); + + // Bottom-left vertex (corner) + glTexCoord2i( 0, 0 ); + glVertex3f(0,0,0); + + // Bottom-right vertex (corner) + glTexCoord2i( 1, 0 ); + glVertex3f( realDisp->w, 0, 0.f ); + + // Top-right vertex (corner) + glTexCoord2i( 1, 1 ); + glVertex3f( realDisp->w, realDisp->h, 0.f ); + + // Top-left vertex (corner) + glTexCoord2i( 0, 1 ); + glVertex3f( 0, realDisp->h, 0.f ); + glEnd(); + + /* + glBegin(GL_TRIANGLES); + glColor3ub(255, 0, 0); + glVertex2d(0, 0); + + glColor3ub(0, 255, 0); + glVertex2d(100,0); + + glColor3ub(0, 0, 255); + glVertex2d(50, 50); + glEnd(); + */ + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); +} + +void SDLGLDriver::swap() +{ + SDL_GL_SwapBuffers(); +} + +void SDLGLDriver::update() +{ + updateNoSwap(); + swap(); +} + +/// Set the window title, as well as the title of the window when +/// "iconified" +void SDLGLDriver::setWindowTitle(const std::string &title, + const std::string &icon) +{ + SDL_WM_SetCaption( title.c_str(), icon.c_str() ); +} + +// Convert the given surface to display format. +static SDL_Surface* convertImage(SDL_Surface* surf) +{ + if(surf != NULL) + { + // Convert the image to the display buffer format, for faster + // blitting + SDL_Surface *surf2 = SDL_DisplayFormat(surf); + SDL_FreeSurface(surf); + surf = surf2; + } + return surf; +} + +/// Load sprite from an image file, using SDL_image. +Sprite* SDLGLDriver::loadImage(const std::string &file) +{ + SDL_Surface *surf = IMG_Load(file.c_str()); + surf = convertImage(surf); + if(surf == NULL) + throw std::runtime_error("SDL failed to load image file '" + file + "'"); + return spriteFromSDL(surf); +} + +/// Load sprite from an SDL_RWops structure. autoFree determines +/// whether the RWops struct should be closed/freed after use. +Sprite* SDLGLDriver::loadImage(SDL_RWops *src, bool autoFree) +{ + SDL_Surface *surf = IMG_Load_RW(src, autoFree); + surf = convertImage(surf); + if(surf == NULL) + throw std::runtime_error("SDL failed to load image"); + return spriteFromSDL(surf); +} + +/// Load a sprite from an image file stored in memory. Uses +/// SDL_image. +Sprite* SDLGLDriver::loadImage(const void* data, size_t size) +{ + SDL_RWops *rw = SDL_RWFromConstMem(data, size); + return loadImage(rw, true); +} + +void SDLGLDriver::setGamma(float red, float green, float blue) +{ + SDL_SetGamma(red,green,blue); +} + +/// Convert an existing SDL surface into a sprite +Sprite* SDLGLDriver::spriteFromSDL(SDL_Surface *surf, bool autoFree) +{ + assert(surf); + return new SDLGL_Sprite(surf, autoFree); +} + +void SDLGLDriver::sleep(int ms) { SDL_Delay(ms); } +unsigned int SDLGLDriver::ticks() { return SDL_GetTicks(); } diff --git a/rend2d/servers/sdl_gl_driver.hpp b/rend2d/servers/sdl_gl_driver.hpp new file mode 100644 index 000000000..d116e3659 --- /dev/null +++ b/rend2d/servers/sdl_gl_driver.hpp @@ -0,0 +1,132 @@ +#ifndef MANGLE_DRAW2D_SDLGL_H +#define MANGLE_DRAW2D_SDLGL_H + +/** This driver is similar to SDLDriver, except that it uses SDL on + top of OpenGL. + + I've decided to make it a separate file instead of just adding + optional OpenGL support to the original, so that pure SDL users + don't have to add OpenGL as a dependency. + */ + +#include "../driver.hpp" + +// Predeclarations keep the streets safe at night +struct SDL_Surface; +struct SDL_RWops; + +namespace Mangle +{ + namespace Rend2D + { + /// SDL-implementation of Sprite + struct SDLGL_Sprite : Sprite + { + /** Draw a sprite in the given position. Can only draw other SDL + sprites. + */ + void draw(Sprite *s, // Must be SDLGL_Sprite + int x, int y, // Destination position + int sx=0, int sy=0, // Source position + int w=-1, int h=-1 // Amount to draw. -1 means remainder. + ); + + SDLGL_Sprite(SDL_Surface *s, bool autoDelete=true); + ~SDLGL_Sprite(); + + // Information retrieval + virtual int width(); + virtual int height(); + SDL_Surface *getSurface() { return surface; } + + // Fill with a given pixel value + void fill(int value); + + private: + // The SDL surface + SDL_Surface* surface; + + // If true, delete this surface when the canvas is destructed + bool autoDel; + }; + + class SDLGLDriver : public Driver + { + // The main display surface + SDLGL_Sprite *display; + + // The screen surface. This is completely unused. + SDL_Surface *screen; + + // The display surface and main GL texture. These are used when + // drawing the entire screen as one surface, as a drop-in + // replacement for SDLDriver. + SDL_Surface *realDisp; + unsigned int texture; + int nOfColors, texture_format; + + public: + SDLGLDriver(); + ~SDLGLDriver(); + + /// Sets the video mode. Will create the window if it is not + /// already set up. Note that for SDL, bpp=0 means use current + /// bpp. + void setVideoMode(int width, int height, int bpp=0, bool fullscreen=false); + + /// Update the screen + void update(); + + /// Calls SDL_GL_SwapBuffers + void swap(); + + /// Draw surface to screen but do not call SDL_GL_SwapBuffers() + void updateNoSwap(); + + /// Set the window title, as well as the title of the window + /// when "iconified" + void setWindowTitle(const std::string &title, + const std::string &icon); + + // Include overloads from our Glorious parent + using Driver::setWindowTitle; + + /// Load sprite from an image file, using SDL_image. + Sprite* loadImage(const std::string &file); + + /// Load sprite from an SDL_RWops structure. autoFree determines + /// whether the RWops struct should be closed/freed after use. + Sprite* loadImage(SDL_RWops *src, bool autoFree=false); + + /// Load a sprite from an image file stored in memory. Uses + /// SDL_image. + Sprite* loadImage(const void* data, size_t size); + + /// Set gamma value + void setGamma(float gamma) { setGamma(gamma,gamma,gamma); } + + /// Set gamma individually for red, green, blue + void setGamma(float red, float green, float blue); + + /// Convert an existing SDL surface into a sprite + Sprite* spriteFromSDL(SDL_Surface *surf, bool autoFree = true); + + // Get width and height + int width() { return display ? display->width() : 0; } + int height() { return display ? display->height() : 0; } + + /// Get the screen sprite + Sprite *getScreen() { return display; } + + /// Not really a graphic-related function, but very + /// handly. Sleeps the given number of milliseconds using + /// SDL_Delay(). + void sleep(int ms); + + /// Get the number of ticks since SDL initialization, using + /// SDL_GetTicks(). + unsigned int ticks(); + }; + } +} +#endif diff --git a/rend2d/tests/Makefile b/rend2d/tests/Makefile index efbbda5d6..d430f60a9 100644 --- a/rend2d/tests/Makefile +++ b/rend2d/tests/Makefile @@ -1,9 +1,15 @@ -GCC=g++ -Wall +GCC=g++ -Wall -Werror -all: sdl_test +all: sdl_test sdl_move_test sdlgl_move_test sdl_test: sdl_test.cpp $(GCC) $< ../servers/sdl_driver.cpp -o $@ -I/usr/include/SDL/ -lSDL -lSDL_image +sdl_move_test: sdl_move_test.cpp ../servers/sdl_driver.cpp + $(GCC) $^ -o $@ -I/usr/include/SDL/ -lSDL -lSDL_image + +sdlgl_move_test: sdlgl_move_test.cpp ../servers/sdl_gl_driver.cpp + $(GCC) $^ -o $@ -I/usr/include/SDL/ -lSDL -lSDL_image -lGL + clean: rm *_test diff --git a/rend2d/tests/output/sdl_move_test.out b/rend2d/tests/output/sdl_move_test.out new file mode 100644 index 000000000..e69de29bb diff --git a/rend2d/tests/output/sdlgl_move_test.out b/rend2d/tests/output/sdlgl_move_test.out new file mode 100644 index 000000000..e69de29bb diff --git a/rend2d/tests/sdl_move_test.cpp b/rend2d/tests/sdl_move_test.cpp new file mode 100644 index 000000000..bfbca98fa --- /dev/null +++ b/rend2d/tests/sdl_move_test.cpp @@ -0,0 +1,30 @@ +#include +#include + +using namespace std; + +#include "../servers/sdl_driver.hpp" + +using namespace Mangle::Rend2D; + +int main() +{ + SDLDriver sdl; + + sdl.setVideoMode(640,480,0,false); + sdl.setWindowTitle("Testing 123"); + Sprite *screen = sdl.getScreen(); + const char* imgName = "tile1-blue.png"; + Sprite *image = sdl.loadImage(imgName); + + for(int frames=0; frames<170; frames++) + { + screen->fill(0); + for(int j=0; j<10; j++) + for(int i=0; i<25; i++) + screen->draw(image, 2*frames+30*j, 20*i); + sdl.update(); + sdl.sleep(10); + } + return 0; +} diff --git a/rend2d/tests/sdlgl_move_test.cpp b/rend2d/tests/sdlgl_move_test.cpp new file mode 100644 index 000000000..b769ee837 --- /dev/null +++ b/rend2d/tests/sdlgl_move_test.cpp @@ -0,0 +1,31 @@ +#include +#include + +using namespace std; + +#include "../servers/sdl_gl_driver.hpp" + +using namespace Mangle::Rend2D; + +int main() +{ + SDLGLDriver sdl; + + sdl.setVideoMode(640,480,0,false); + sdl.setWindowTitle("Testing 123"); + Sprite *screen = sdl.getScreen(); + const char* imgName = "tile1-blue.png"; + Sprite *image = sdl.loadImage(imgName); + + for(int frames=0; frames<170; frames++) + { + screen->fill(0); + for(int j=0; j<10; j++) + for(int i=0; i<25; i++) + screen->draw(image, 2*frames+30*j, 20*i); + sdl.update(); + sdl.sleep(5); + } + + return 0; +}