graphics fixes

* zooming without real FBOs kinda works, but with lower quality
* hardware detection for glGenerateMipmaps
* possibility to disable bilinear filtering, mipmaps, framebuffers, and realtime mipmap generation in g_graphics
* otclient works well using 3D acceleration in VirtualBox again
* many fixes regarding FBOs fallback implementation
This commit is contained in:
Eduardo Bart 2012-03-21 09:41:43 -03:00
parent 01d5fad315
commit c7469e4454
11 changed files with 98 additions and 42 deletions

View File

@ -23,6 +23,7 @@
#include "framebuffer.h" #include "framebuffer.h"
#include "graphics.h" #include "graphics.h"
#include "texture.h" #include "texture.h"
#include <framework/platform/platformwindow.h>
uint FrameBuffer::boundFbo = 0; uint FrameBuffer::boundFbo = 0;
std::vector<bool> auxBuffers; std::vector<bool> auxBuffers;
@ -42,14 +43,14 @@ FrameBuffer::FrameBuffer(const Size& size)
void FrameBuffer::internalCreate() void FrameBuffer::internalCreate()
{ {
if(g_graphics.hasFBO()) { if(g_graphics.canUseFBO()) {
glGenFramebuffers(1, &m_fbo); glGenFramebuffers(1, &m_fbo);
if(!m_fbo) if(!m_fbo)
logFatal("Unable to create framebuffer object"); logFatal("Unable to create framebuffer object");
} else { // use auxiliar buffers when FBOs are not supported } else { // use auxiliar buffers when FBOs are not supported
m_fbo = 0; m_fbo = 0;
if(auxBuffers.size() == 0) { if(auxBuffers.size() == 0) {
int maxAuxs; int maxAuxs = 0;
glGetIntegerv(GL_AUX_BUFFERS, &maxAuxs); glGetIntegerv(GL_AUX_BUFFERS, &maxAuxs);
auxBuffers.resize(maxAuxs+1, false); auxBuffers.resize(maxAuxs+1, false);
} }
@ -57,16 +58,17 @@ void FrameBuffer::internalCreate()
if(auxBuffers[i] == false) { if(auxBuffers[i] == false) {
m_fbo = i; m_fbo = i;
auxBuffers[i] = true; auxBuffers[i] = true;
break;
} }
} }
if(!m_fbo) if(!m_fbo)
logFatal("There is no available auxiliar buffer for a new framebuffer"); logFatal("There is no available auxiliar buffer for a new framebuffer, total AUXs: ", auxBuffers.size()-1);
} }
} }
FrameBuffer::~FrameBuffer() FrameBuffer::~FrameBuffer()
{ {
if(g_graphics.hasFBO()) { if(g_graphics.canUseFBO()) {
glDeleteFramebuffers(1, &m_fbo); glDeleteFramebuffers(1, &m_fbo);
} else { } else {
auxBuffers[m_fbo] = false; auxBuffers[m_fbo] = false;
@ -84,7 +86,7 @@ void FrameBuffer::resize(const Size& size)
m_texture = TexturePtr(new Texture(size.width(), size.height(), 4)); m_texture = TexturePtr(new Texture(size.width(), size.height(), 4));
m_texture->setSmooth(true); m_texture->setSmooth(true);
if(g_graphics.hasFBO()) { if(g_graphics.canUseFBO()) {
internalBind(); internalBind();
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_texture->getId(), 0); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_texture->getId(), 0);
@ -121,11 +123,6 @@ void FrameBuffer::release()
g_graphics.setViewportSize(m_oldViewportSize); g_graphics.setViewportSize(m_oldViewportSize);
} }
void FrameBuffer::generateMipmaps()
{
m_texture->generateMipmaps();
}
void FrameBuffer::draw(const Rect& dest, const Rect& src) void FrameBuffer::draw(const Rect& dest, const Rect& src)
{ {
g_painter.drawTexturedRect(dest, m_texture, src); g_painter.drawTexturedRect(dest, m_texture, src);
@ -133,7 +130,10 @@ void FrameBuffer::draw(const Rect& dest, const Rect& src)
void FrameBuffer::draw(const Rect& dest) void FrameBuffer::draw(const Rect& dest)
{ {
g_painter.drawTexturedRect(dest, m_texture); if(g_graphics.canUseFBO())
g_painter.drawTexturedRect(dest, m_texture);
else
g_painter.drawTexturedRect(dest, m_texture, Rect(0, 0, g_window.getSize()));
} }
void FrameBuffer::internalBind() void FrameBuffer::internalBind()
@ -142,7 +142,7 @@ void FrameBuffer::internalBind()
return; return;
assert(boundFbo != m_fbo); assert(boundFbo != m_fbo);
if(g_graphics.hasFBO()) { if(g_graphics.canUseFBO()) {
glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
} else { } else {
int buffer = GL_AUX0 + m_fbo - 1; int buffer = GL_AUX0 + m_fbo - 1;
@ -157,11 +157,13 @@ void FrameBuffer::internalBind()
void FrameBuffer::internalRelease() void FrameBuffer::internalRelease()
{ {
assert(boundFbo == m_fbo); assert(boundFbo == m_fbo);
if(g_graphics.hasFBO()) { if(g_graphics.canUseFBO()) {
glBindFramebuffer(GL_FRAMEBUFFER, m_prevBoundFbo); glBindFramebuffer(GL_FRAMEBUFFER, m_prevBoundFbo);
} else { } else {
m_texture->bind(); m_texture->bind();
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, m_texture->getWidth(), m_texture->getHeight());
Size size = getSize();
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, size.width(), size.height());
int buffer = GL_BACK; int buffer = GL_BACK;
if(m_prevBoundFbo != 0) if(m_prevBoundFbo != 0)
@ -172,3 +174,14 @@ void FrameBuffer::internalRelease()
} }
boundFbo = m_prevBoundFbo; boundFbo = m_prevBoundFbo;
} }
Size FrameBuffer::getSize()
{
if(g_graphics.canUseFBO()) {
return m_texture->getSize();
} else {
// the buffer size is limited by the window size
return Size(std::min(m_texture->getWidth(), g_window.getWidth()),
std::min(m_texture->getHeight(), g_window.getHeight()));
}
}

View File

@ -36,14 +36,13 @@ public:
void resize(const Size& size); void resize(const Size& size);
void bind(bool clear = true); void bind(bool clear = true);
void release(); void release();
void generateMipmaps();
void draw(const Rect& dest); void draw(const Rect& dest);
void draw(const Rect& dest, const Rect& src); void draw(const Rect& dest, const Rect& src);
void setClearColor(const Color& color) { m_clearColor = color; } void setClearColor(const Color& color) { m_clearColor = color; }
TexturePtr getTexture() { return m_texture; } TexturePtr getTexture() { return m_texture; }
const Size& getSize() { return m_texture->getSize(); } Size getSize();
private: private:
void internalCreate(); void internalCreate();

View File

@ -30,22 +30,35 @@ Graphics g_graphics;
void Graphics::init() void Graphics::init()
{ {
logInfo("GPU ", glGetString(GL_RENDERER));
logInfo("OpenGL ", glGetString(GL_VERSION));
#ifndef OPENGL_ES2 #ifndef OPENGL_ES2
// init GL extensions // init GL extensions
GLenum err = glewInit(); GLenum err = glewInit();
if(err != GLEW_OK) if(err != GLEW_OK)
logFatal("Unable to init GLEW: ", glewGetErrorString(err)); logFatal("Unable to init GLEW: ", glewGetErrorString(err));
if(!GLEW_ARB_vertex_program || !GLEW_ARB_vertex_shader || if(!GLEW_ARB_vertex_program || !GLEW_ARB_vertex_shader ||
!GLEW_ARB_fragment_program || !GLEW_ARB_fragment_shader || !GLEW_ARB_fragment_program || !GLEW_ARB_fragment_shader ||
!GLEW_ARB_texture_non_power_of_two || !GLEW_ARB_multitexture) !GLEW_ARB_texture_non_power_of_two || !GLEW_ARB_multitexture)
logFatal("Some OpenGL 2.0 extensions is not supported by your system graphics, please try updating your video drivers or buy a new hardware."); logFatal("Some OpenGL 2.0 extensions is not supported by your system graphics, please try updating your video drivers or buy a new hardware.");
m_useFBO = GLEW_ARB_framebuffer_object;
m_useBilinearFiltering = true;
m_generateMipmaps = true;
m_generateHardwareMipmaps = m_useFBO; // glGenerateMipmap is supported when FBO is
m_generateRealtimeMipmaps = m_generateHardwareMipmaps;
#else
m_useFBO = true; // FBOs is always supported by mobile devices
m_useBilinearFiltering = true;
m_generateMipmaps = true;
m_generateHardwareMipmaps = true;
m_realtimeMipmapGeneration = false; // realtime mipmaps can be slow on mobile devices
#endif #endif
glEnable(GL_BLEND); glEnable(GL_BLEND);
logInfo("GPU ", glGetString(GL_RENDERER));
logInfo("OpenGL ", glGetString(GL_VERSION));
m_emptyTexture = TexturePtr(new Texture); m_emptyTexture = TexturePtr(new Texture);
g_painter.init(); g_painter.init();
@ -58,15 +71,6 @@ void Graphics::terminate()
m_emptyTexture.reset(); m_emptyTexture.reset();
} }
bool Graphics::hasFBO()
{
#ifndef OPENGL_ES2
return GLEW_ARB_framebuffer_object;
#else
return true;
#endif
}
void Graphics::resize(const Size& size) void Graphics::resize(const Size& size)
{ {
setViewportSize(size); setViewportSize(size);

View File

@ -32,7 +32,11 @@ public:
void init(); void init();
void terminate(); void terminate();
bool hasFBO(); bool canUseFBO() { return m_useFBO; }
bool canUseBilinearFiltering() { return m_useBilinearFiltering; }
bool canGenerateMipmaps() { return m_generateMipmaps; }
bool canGenerateHardwareMipmaps() { return m_generateHardwareMipmaps; }
bool canGenerateRealtimeMipmaps() { return m_generateRealtimeMipmaps; }
void resize(const Size& size); void resize(const Size& size);
void beginRender(); void beginRender();
@ -47,6 +51,12 @@ public:
private: private:
Size m_viewportSize; Size m_viewportSize;
TexturePtr m_emptyTexture; TexturePtr m_emptyTexture;
Boolean<false> m_useFBO;
Boolean<false> m_useBilinearFiltering;
Boolean<false> m_generateMipmaps;
Boolean<false> m_generateHardwareMipmaps;
Boolean<false> m_generateRealtimeMipmaps;
}; };
extern Graphics g_graphics; extern Graphics g_graphics;

View File

@ -99,6 +99,19 @@ uint Texture::internalLoadGLTexture(uchar *pixels, int channels, int width, int
void Texture::generateMipmaps() void Texture::generateMipmaps()
{ {
if(g_graphics.canGenerateHardwareMipmaps())
generateHardwareMipmaps();
else {
// fallback to software mipmaps generation, this can be slow
generateSoftwareMipmaps(getPixels());
}
}
void Texture::generateHardwareMipmaps()
{
if(!g_graphics.canGenerateHardwareMipmaps())
return;
bind(); bind();
if(!m_hasMipmaps) { if(!m_hasMipmaps) {
@ -111,6 +124,9 @@ void Texture::generateMipmaps()
void Texture::setSmooth(bool smooth) void Texture::setSmooth(bool smooth)
{ {
if(smooth && !g_graphics.canUseBilinearFiltering())
return;
if(smooth == m_smooth) if(smooth == m_smooth)
return; return;
@ -140,7 +156,7 @@ std::vector<uint8> Texture::getPixels()
return pixels; return pixels;
} }
void Texture::generateBilinearMipmaps(std::vector<uint8> inPixels) void Texture::generateSoftwareMipmaps(std::vector<uint8> inPixels)
{ {
bind(); bind();
@ -155,6 +171,7 @@ void Texture::generateBilinearMipmaps(std::vector<uint8> inPixels)
int mipmap = 1; int mipmap = 1;
while(true) { while(true) {
// this is a simple bilinear filtering algorithm, it combines every 4 pixels in one pixel
for(int x=0;x<outSize.width();++x) { for(int x=0;x<outSize.width();++x) {
for(int y=0;y<outSize.height();++y) { for(int y=0;y<outSize.height();++y) {
uint8 *inPixel[4]; uint8 *inPixel[4];
@ -180,13 +197,13 @@ void Texture::generateBilinearMipmaps(std::vector<uint8> inPixels)
usedPixels++; usedPixels++;
} }
// try to guess the alpha pixel more accurately
for(int i=0;i<4;++i) { for(int i=0;i<4;++i) {
if(usedPixels > 0) if(usedPixels > 0)
outPixel[i] = pixelsSum[i] / usedPixels; outPixel[i] = pixelsSum[i] / usedPixels;
else else
outPixel[i] = 0; outPixel[i] = 0;
} }
outPixel[3] = pixelsSum[3]/4; outPixel[3] = pixelsSum[3]/4;
} }
} }

View File

@ -34,11 +34,14 @@ public:
void bind() { glBindTexture(GL_TEXTURE_2D, m_textureId); } void bind() { glBindTexture(GL_TEXTURE_2D, m_textureId); }
/// Tries to generate mipmaps via hardware, otherwise fallback to software implementation
void generateMipmaps(); void generateMipmaps();
/// Generate mipmaps via hardware
void generateHardwareMipmaps();
/// Generate mipmaps via software, which has a special algorithm for combining alpha pixels
void generateSoftwareMipmaps(std::vector<uint8> inPixels);
// generate bilinear mipmaps optimized for alpha textures /// Activate texture altialising
void generateBilinearMipmaps(std::vector<uint8> inPixels);
void setSmooth(bool smooth); void setSmooth(bool smooth);
GLuint getId() { return m_textureId; } GLuint getId() { return m_textureId; }

View File

@ -39,7 +39,7 @@ X11Window::X11Window()
m_xic = 0; m_xic = 0;
m_screen = 0; m_screen = 0;
m_wmDelete = 0; m_wmDelete = 0;
m_size = Size(16,16); m_size = Size(600,480);
#ifndef OPENGL_ES2 #ifndef OPENGL_ES2
m_glxContext = 0; m_glxContext = 0;
@ -203,7 +203,6 @@ X11Window::X11Window()
void X11Window::init() void X11Window::init()
{ {
m_size = Size(200, 200);
internalOpenDisplay(); internalOpenDisplay();
internalCheckGL(); internalCheckGL();
internalChooseGLVisual(); internalChooseGLVisual();

View File

@ -24,6 +24,7 @@
#include <framework/graphics/painter.h> #include <framework/graphics/painter.h>
#include <framework/graphics/texture.h> #include <framework/graphics/texture.h>
#include <framework/graphics/texturemanager.h> #include <framework/graphics/texturemanager.h>
#include <framework/graphics/graphics.h>
void UIWidget::initImage() void UIWidget::initImage()
{ {
@ -167,7 +168,7 @@ void UIWidget::drawImage(const Rect& screenCoords)
m_imageTexture->setSmooth(m_imageSmooth); m_imageTexture->setSmooth(m_imageSmooth);
// this will increase fps when rendering larger images, like the background, and improve image quality // this will increase fps when rendering larger images, like the background, and improve image quality
if(m_imageSmooth && !m_imageTexture->hasMipmaps()) if(m_imageSmooth && g_graphics.canGenerateMipmaps() && !m_imageTexture->hasMipmaps())
m_imageTexture->generateMipmaps(); m_imageTexture->generateMipmaps();
g_painter.setColor(m_imageColor); g_painter.setColor(m_imageColor);

View File

@ -57,7 +57,8 @@ void UIWidget::drawText(const Rect& screenCoords)
m_textCachedScreenCoords = screenCoords; m_textCachedScreenCoords = screenCoords;
m_textCoordsBuffer.clear(); m_textCoordsBuffer.clear();
m_font->calculateDrawTextCoords(m_textCoordsBuffer, m_text, screenCoords, m_textAlign);
m_font->calculateDrawTextCoords(m_textCoordsBuffer, m_text, screenCoords.translated(m_textOffset), m_textAlign);
} }
g_painter.setColor(m_color); g_painter.setColor(m_color);

View File

@ -99,9 +99,13 @@ void MapView::draw(const Rect& rect)
} }
} }
} }
m_framebuffer->generateMipmaps();
m_framebuffer->release(); m_framebuffer->release();
// generating mipmaps each frame can be slow in older cards
if(g_graphics.canGenerateRealtimeMipmaps())
m_framebuffer->getTexture()->generateHardwareMipmaps();
m_mustDrawVisibleTilesCache = false; m_mustDrawVisibleTilesCache = false;
} }
@ -257,7 +261,7 @@ void MapView::updateVisibleTilesCache(int start)
} else { } else {
static std::vector<Point> points; static std::vector<Point> points;
points.clear(); points.clear();
assert(m_drawDimension.width() % 2 == 0 && m_drawDimension.height() % 2 == 0); //assert(m_drawDimension.width() % 2 == 0 && m_drawDimension.height() % 2 == 0);
Point quadTopLeft(m_drawDimension.width()/2 - 1, m_drawDimension.height()/2 - 1); Point quadTopLeft(m_drawDimension.width()/2 - 1, m_drawDimension.height()/2 - 1);
for(int step = 1; !(quadTopLeft.x < 0 && quadTopLeft.y < 0) && !stop; ++step) { for(int step = 1; !(quadTopLeft.x < 0 && quadTopLeft.y < 0) && !stop; ++step) {
int quadWidth = std::min(2*step, m_drawDimension.width()); int quadWidth = std::min(2*step, m_drawDimension.width());
@ -404,12 +408,14 @@ void MapView::setVisibleDimension(const Size& visibleDimension)
int possiblesTileSizes[] = {32,16,8,4,2,1}; int possiblesTileSizes[] = {32,16,8,4,2,1};
int tileSize = 0; int tileSize = 0;
Size drawDimension = visibleDimension + Size(3,3); Size drawDimension = visibleDimension + Size(3,3);
Size framebufferSize = m_framebuffer->getSize();
for(int candidateTileSize : possiblesTileSizes) { for(int candidateTileSize : possiblesTileSizes) {
Size candidateDrawSize = drawDimension * candidateTileSize; Size candidateDrawSize = drawDimension * candidateTileSize;
// found a valid size // found a valid size
if(candidateDrawSize <= m_framebuffer->getSize()) { if(candidateDrawSize.width() <= framebufferSize.width() && candidateDrawSize.height() <= framebufferSize.height()) {
tileSize = candidateTileSize; tileSize = candidateTileSize;
dump << candidateDrawSize << m_framebuffer->getSize() << tileSize;
break; break;
} }
} }

View File

@ -134,7 +134,10 @@ TexturePtr SpriteManager::loadSpriteTexture(int id)
TexturePtr spriteTex(new Texture(32, 32, 4, &pixels[0])); TexturePtr spriteTex(new Texture(32, 32, 4, &pixels[0]));
spriteTex->setSmooth(true); spriteTex->setSmooth(true);
spriteTex->generateBilinearMipmaps(pixels);
if(g_graphics.canGenerateMipmaps())
spriteTex->generateSoftwareMipmaps(pixels);
return spriteTex; return spriteTex;
} }