Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Speed up terrain drawing with glDrawArrays from OpenGL 2.0. #120

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions SConstruct
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,13 @@ def configure(env, server_only):
configfile.add("HAVE_OPENGL ", "Defined when OpenGL support is present and compiled")
env.Append(LIBS=gl_libraries)

if not server_only:
if conf.CheckLib('epoxy') and conf.CheckCXXHeader('epoxy/gl.h'):
env.Append(LIBS=["epoxy"])
else:
print("Could not find epoxy/gl.h")
missing.append("epoxy")

#Do checks for fribidi
if not server_only and conf.CheckLib('fribidi') and conf.CheckCXXHeader('fribidi/fribidi.h'):
configfile.add("HAVE_FRIBIDI ", "Defined when FRIBIDI support is present and compiled")
Expand Down
2 changes: 1 addition & 1 deletion debian/control
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Source: glob2
Section: games
Priority: optional
Maintainer: Stephane Magnenat <stephane at magnenat dot net>
Build-Depends: debhelper (>> 6.0.0), quilt (>= 0.40), scons, libsdl2-dev (>=2.0.0), libsdl2-image-dev (>=2.0.0), libsdl2-net-dev (>=2.0.0), libsdl2-ttf-dev, libglu1-mesa-dev | libglu-dev, libvorbis-dev, libspeex-dev, libfreetype6-dev, libboost-dev, libboost-thread-dev, libboost-date-time-dev, libfribidi-dev, portaudio19-dev, libboost-math-dev
Build-Depends: debhelper (>> 6.0.0), quilt (>= 0.40), scons, libsdl2-dev (>=2.0.0), libsdl2-image-dev (>=2.0.0), libsdl2-net-dev (>=2.0.0), libsdl2-ttf-dev, libglu1-mesa-dev | libglu-dev, libvorbis-dev, libspeex-dev, libfreetype6-dev, libboost-dev, libboost-thread-dev, libboost-date-time-dev, libfribidi-dev, portaudio19-dev, libboost-math-dev, libepoxy-dev
Standards-Version: 3.8.1
Homepage: http://globulation2.org

Expand Down
27 changes: 25 additions & 2 deletions libgag/include/SDLGraphicContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -158,14 +158,23 @@ namespace GAGCore
protected:
friend struct Color;
friend class GraphicContext;
friend class Sprite;
//! the underlying software SDL surface
SDL_Surface *sdlsurface;
//! which animation or texture atlas this surface is part of.
Sprite* sprite = nullptr;
//! The clipping rect, we do not draw outside it
SDL_Rect clipRect;
//! this surface has been modified since latest blit
bool dirty;
//! texture index if GPU (GL) is used
unsigned int texture;
//! sprite sheet coordinates
int texX = 0;
int texY = 0;
//! width and height of this tile if using atlas
int w = 0;
int h = 0;
//! texture divisor
float texMultX, texMultY;

Expand Down Expand Up @@ -209,8 +218,8 @@ namespace GAGCore
virtual void shiftHSV(float hue, float sat, float lum);

// accessors
virtual int getW(void) { return sdlsurface->w; }
virtual int getH(void) { return sdlsurface->h; }
virtual int getW(void) { if (sprite) return w; return sdlsurface->w; }
virtual int getH(void) { if (sprite) return h; return sdlsurface->h; }

// capability querying
virtual bool canDrawStretchedSprite(void) { return false; }
Expand Down Expand Up @@ -375,6 +384,8 @@ namespace GAGCore

virtual void drawSurface(int x, int y, int w, int h, DrawableSurface *surface, int sx, int sy, int sw, int sh, Uint8 alpha = Color::ALPHA_OPAQUE);
virtual void drawSurface(float x, float y, float w, float h, DrawableSurface *surface, int sx, int sy, int sw, int sh, Uint8 alpha = Color::ALPHA_OPAQUE);

void finishDrawingSprite(Sprite* sprite, Uint8 alpha);

virtual void drawAlphaMap(const std::valarray<float> &map, int mapW, int mapH, int x, int y, int cellW, int cellH, const Color &color);
virtual void drawAlphaMap(const std::valarray<unsigned char> &map, int mapW, int mapH, int x, int y, int cellW, int cellH, const Color &color);
Expand Down Expand Up @@ -416,10 +427,21 @@ namespace GAGCore
RotatedImage(DrawableSurface *s) { orig = s; }
~RotatedImage();
};

friend class GraphicContext;

std::string fileName;
std::vector <DrawableSurface *> images;
std::vector <RotatedImage *> rotated;

// Sprite sheet stuff to efficiently draw terrain/water/units.
#ifdef HAVE_OPENGL
std::vector <float> vertices;
std::vector <float> texCoords;
unsigned int vbo;
unsigned int texCoordBuffer;
std::unique_ptr<const DrawableSurface> atlas = nullptr;
#endif
Color actColor;

friend class DrawableSurface;
Expand All @@ -428,6 +450,7 @@ namespace GAGCore
void loadFrame(SDL_RWops *frameStream, SDL_RWops *rotatedStream);
//! Check if index is within bound and return true, assert false and return false otherwise
bool checkBound(int index);
void createTextureAtlas();
//! Return a rotated drawable surface for actColor, create it if necessary
virtual DrawableSurface *getRotatedSurface(int index);

Expand Down
106 changes: 88 additions & 18 deletions libgag/src/GraphicContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@
#include <string>
#include <sstream>
#include <iostream>
#include <epoxy/gl.h>
#ifdef _MSC_VER
#include <epoxy/wgl.h>
#else
#include <epoxy/glx.h>
#endif
#include "SDL_ttf.h"
#include <SDL_image.h>
#include <math.h>
Expand All @@ -37,6 +43,7 @@
#include <config.h>
#endif

#define GL_GLEXT_PROTOTYPES
#ifdef HAVE_OPENGL
#if defined(__APPLE__) || defined(OPENGL_HEADER_DIRECTORY_OPENGL)
#include <OpenGL/gl.h>
Expand Down Expand Up @@ -303,6 +310,8 @@ namespace GAGCore
void DrawableSurface::allocateTexture(void)
{
#ifdef HAVE_OPENGL
if (sprite)
return;
if (_gc->optionFlags & GraphicContext::USEGPU)
{
glGenTextures(1, reinterpret_cast<GLuint*>(&texture));
Expand Down Expand Up @@ -345,6 +354,10 @@ namespace GAGCore
void DrawableSurface::uploadToTexture(void)
{
#ifdef HAVE_OPENGL
if (sprite)
{
return;
}
if (_gc->optionFlags & GraphicContext::USEGPU)
{
glState.setTexture(texture);
Expand Down Expand Up @@ -1049,22 +1062,22 @@ namespace GAGCore

void DrawableSurface::drawSurface(int x, int y, DrawableSurface *surface, Uint8 alpha)
{
drawSurface(x, y, surface, 0, 0, surface->getW(), surface->getH(), alpha);
drawSurface(x, y, surface, surface->texX, surface->texY, surface->getW(), surface->getH(), alpha);
}

void DrawableSurface::drawSurface(float x, float y, DrawableSurface *surface, Uint8 alpha)
{
drawSurface(x, y, surface, 0, 0, surface->getW(), surface->getH(), alpha);
drawSurface(x, y, surface, surface->texX, surface->texY, surface->getW(), surface->getH(), alpha);
}

void DrawableSurface::drawSurface(int x, int y, int w, int h, DrawableSurface *surface, Uint8 alpha)
{
drawSurface(x, y, w, h, surface, 0, 0, surface->getW(), surface->getH(), alpha);
drawSurface(x, y, w, h, surface, surface->texX, surface->texY, surface->getW(), surface->getH(), alpha);
}

void DrawableSurface::drawSurface(float x, float y, float w, float h, DrawableSurface *surface, Uint8 alpha)
{
drawSurface(x, y, w, h, surface, 0, 0, surface->getW(), surface->getH(), alpha);
drawSurface(x, y, w, h, surface, surface->texX, surface->texY, surface->getW(), surface->getH(), alpha);
}

void DrawableSurface::drawSurface(int x, int y, DrawableSurface *surface, int sx, int sy, int sw, int sh, Uint8 alpha)
Expand Down Expand Up @@ -1617,22 +1630,22 @@ namespace GAGCore

void GraphicContext::drawSurface(int x, int y, DrawableSurface *surface, Uint8 alpha)
{
drawSurface(x, y, surface, 0, 0, surface->getW(), surface->getH(), alpha);
drawSurface(x, y, surface, surface->texX, surface->texY, surface->getW(), surface->getH(), alpha);
}

void GraphicContext::drawSurface(float x, float y, DrawableSurface *surface, Uint8 alpha)
{
drawSurface(x, y, surface, 0, 0, surface->getW(), surface->getH(), alpha);
drawSurface(x, y, surface, surface->texX, surface->texY, surface->getW(), surface->getH(), alpha);
}

void GraphicContext::drawSurface(int x, int y, int w, int h, DrawableSurface *surface, Uint8 alpha)
{
drawSurface(x, y, w, h, surface, 0, 0, surface->getW(), surface->getH(), alpha);
drawSurface(x, y, w, h, surface, surface->texX, surface->texY, surface->getW(), surface->getH(), alpha);
}

void GraphicContext::drawSurface(float x, float y, float w, float h, DrawableSurface *surface, Uint8 alpha)
{
drawSurface(x, y, w, h, surface, 0, 0, surface->getW(), surface->getH(), alpha);
drawSurface(x, y, w, h, surface, surface->texX, surface->texY, surface->getW(), surface->getH(), alpha);
}

void GraphicContext::drawSurface(int x, int y, DrawableSurface *surface, int sx, int sy, int sw, int sh, Uint8 alpha)
Expand Down Expand Up @@ -1682,22 +1695,79 @@ namespace GAGCore

// draw
glState.setTexture(surface->texture);
glBegin(GL_QUADS);
glTexCoord2f(static_cast<float>(sx) * surface->texMultX, static_cast<float>(sy) * surface->texMultY);
glVertex2f(x, y);
glTexCoord2f(static_cast<float>(sx + sw) * surface->texMultX, static_cast<float>(sy) * surface->texMultY);
glVertex2f(x+w, y);
glTexCoord2f(static_cast<float>(sx + sw) * surface->texMultX, static_cast<float>(sy + sh) * surface->texMultY);
glVertex2f(x+w, y+h);
glTexCoord2f(static_cast<float>(sx) * surface->texMultX, static_cast<float>(sy + sh) * surface->texMultY);
glVertex2f(x, y+h);
glEnd();
if (surface->sprite && alpha == Color::ALPHA_OPAQUE)
{
surface->sprite->vertices.insert(surface->sprite->vertices.end(), { x, y, x + w, y, x + w, y + h, x, y + h });
surface->sprite->texCoords.insert(surface->sprite->texCoords.end(), {
static_cast<float>(sx) * surface->texMultX, static_cast<float>(sy) * surface->texMultY,
static_cast<float>(sx + sw) * surface->texMultX, static_cast<float>(sy) * surface->texMultY,
static_cast<float>(sx + sw) * surface->texMultX, static_cast<float>(sy + sh) * surface->texMultY,
static_cast<float>(sx) * surface->texMultX, static_cast<float>(sy + sh) * surface->texMultY
});
}
else
{
glBegin(GL_QUADS);
glTexCoord2f(static_cast<float>(sx) * surface->texMultX, static_cast<float>(sy) * surface->texMultY);
glVertex2f(x, y);
glTexCoord2f(static_cast<float>(sx + sw) * surface->texMultX, static_cast<float>(sy) * surface->texMultY);
glVertex2f(x + w, y);
glTexCoord2f(static_cast<float>(sx + sw) * surface->texMultX, static_cast<float>(sy + sh) * surface->texMultY);
glVertex2f(x + w, y + h);
glTexCoord2f(static_cast<float>(sx) * surface->texMultX, static_cast<float>(sy + sh) * surface->texMultY);
glVertex2f(x, y + h);
glEnd();
}
}
else
#endif
DrawableSurface::drawSurface(static_cast<int>(x), static_cast<int>(y), static_cast<int>(w), static_cast<int>(h), surface, sx, sy, sw, sh, alpha);
}

// Lets us efficiently draw terrain and water.
void GraphicContext::finishDrawingSprite(Sprite* sprite, Uint8 alpha)
{
#ifdef HAVE_OPENGL
if (_gc->optionFlags & GraphicContext::USEGPU)
{
if (!sprite->atlas)
{
// No sprite sheet, so we have nothing to draw.
assert(!sprite->vertices.size());
assert(!sprite->texCoords.size());
return;
}
if (sprite->vertices.empty() || sprite->texCoords.empty())
{
// No data.
return;
}
// state change
glState.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glState.doBlend(true);
glState.doTexture(true);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glColor4ub(255, 255, 255, alpha);
glState.setTexture(sprite->atlas->texture);
glBindBuffer(GL_ARRAY_BUFFER, sprite->vbo);
glBufferData(GL_ARRAY_BUFFER, sprite->vertices.size() * sizeof(float), &sprite->vertices[0], GL_STREAM_DRAW);
glVertexPointer(2, GL_FLOAT, 0, 0);
glBindBuffer(GL_ARRAY_BUFFER, sprite->texCoordBuffer);
glBufferData(GL_ARRAY_BUFFER, sprite->texCoords.size() * sizeof(float), &sprite->texCoords[0], GL_STREAM_DRAW);
glTexCoordPointer(2, GL_FLOAT, 0, 0);
glDrawArrays(GL_QUADS, 0, sprite->vertices.size() / 2);

sprite->vertices.clear();
sprite->texCoords.clear();

glBindBuffer(GL_ARRAY_BUFFER, 0);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}
#endif
}

void GraphicContext::drawAlphaMap(const std::valarray<float> &map, int mapW, int mapH, int x, int y, int cellW, int cellH, const Color &color)
{
#ifdef HAVE_OPENGL
Expand Down
93 changes: 93 additions & 0 deletions libgag/src/Sprite.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,40 @@
#include <iostream>
#include <sstream>

#if __cplusplus >= 201402L
#include <memory>
using std::make_unique;
#else
#if BOOST_VERSION >= 107500
#include <boost/smart_ptr/make_unique.hpp>
#elif BOOST_VERSION >= 106300
#include <boost/make_unique.hpp>
#elif BOOST_VERSION >= 105700
#include <boost/move/make_unique.hpp>
#else
#error "Can't make_unique when there's no Boost and C++ standard is earlier than C++14"
#endif
using boost::make_unique;
#endif // __cplusplus

#define GL_GLEXT_PROTOTYPES
#ifdef HAVE_OPENGL
#if defined(__APPLE__) || defined(OPENGL_HEADER_DIRECTORY_OPENGL)
#include <OpenGL/gl.h>
#include <OpenGL/glext.h>
#include <OpenGL/glu.h>
#define GL_TEXTURE_RECTANGLE_NV GL_TEXTURE_RECTANGLE_EXT
#else
#include <epoxy/gl.h>
#ifdef _MSC_VER
#include <epoxy/wgl.h>
#else
#include <epoxy/glx.h>
#endif // _MSC_VER
#endif // defined(__APPLE__)
#endif // ifdef HAVE_OPENGL


namespace GAGCore
{
Sprite::RotatedImage::~RotatedImage()
Expand Down Expand Up @@ -67,9 +101,68 @@ namespace GAGCore
SDL_RWclose(rotatedStream);
i++;
}
// TODO: How to cache rotated images?
if (std::any_of(images.begin(), images.end(), [](DrawableSurface *s) {return s != nullptr; }))
{
createTextureAtlas();
}

return getFrameCount() > 0;
}

// Create texture atlas for images array
// Using a sprite sheet lets us efficiently drawn terrain and water with a few calls
// to glDrawArrays, rather than 272 individual calls to glBegin...glEnd.
void Sprite::createTextureAtlas()
{
#ifdef HAVE_OPENGL
size_t numImages = images.size();
int w = 0, h = 0;
for (auto image : images)
{
if (!image)
return;
if (!w || !h)
{
w = image->getW();
h = image->getH();
}
if (image->getW() != w || image->getH() != h)
return;
}
int tileWidth = images[0]->getW();
int tileHeight = images[0]->getH();
int sheetWidth = tileWidth * (static_cast<int>(sqrt(numImages)) + 1);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should probably use a loop that tries all the possible width and height combinations here where width and height are <= the maximum texture size (glGetIntegerv(GL_MAX_TEXTURE_SIZE)). May need multiple sprite sheets if the maximum texture size is too small.

int sheetHeight = tileHeight * (static_cast<int>(sqrt(numImages)) + 1);
std::unique_ptr<DrawableSurface> atlas = make_unique<DrawableSurface>(sheetWidth, sheetHeight);
int x = 0, y = 0;
for (auto image: images)
{
atlas->drawSurface(x, y, image);
image->texX = x;
image->texY = y;
image->texMultX = 1.f;
image->texMultY = 1.f;
image->w = tileWidth;
image->h = tileHeight;
x += tileWidth;
if (sheetWidth - x < tileWidth) {
x = 0;
y += tileHeight;
}
}
atlas->uploadToTexture();
for (auto image : images)
{
image->texture = atlas->texture;
image->sprite = this;
image->setRes(sheetWidth, sheetHeight);
}
this->atlas = std::move(atlas);
glGenBuffers(1, &vbo);
glGenBuffers(1, &texCoordBuffer);
#endif
}

DrawableSurface *Sprite::getRotatedSurface(int index)
{
Expand Down
Loading