Files
scummvm/backends/platform/android/texture.cpp
T
dhewg a93229cae5 ANDROID: Don't use compressed textures for the game screen
Some GLES drivers suck so much that uploading data to the GPU takes
ages. CLUT8 games now use a faked paletted texture, which internally
uses a RGB565 hardware texture (Android's native pixel format).
This seems to be the only way to efficiently implement constant
changing textures with GLES1 - at the cost of extra buffers.
Then again, we can now use glTexSubImage2D to only update the dirty
rects, which wasn't possible before because glCompressedTexSubImage2D
is only usable on GLES2. This commit does exactly that.
Overall, the CPU usage is massively reduced for CLUT8 games.
2011-03-13 16:50:43 +01:00

525 lines
13 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $URL$
* $Id$
*
*/
#if defined(__ANDROID__)
#include "base/main.h"
#include "graphics/surface.h"
#include "common/rect.h"
#include "common/array.h"
#include "common/util.h"
#include "common/tokenizer.h"
#include "backends/platform/android/texture.h"
#include "backends/platform/android/android.h"
// Unfortunately, Android devices are too varied to make broad assumptions :/
#define TEXSUBIMAGE_IS_EXPENSIVE 0
// Supported GL extensions
static bool npot_supported = false;
#ifdef GL_OES_draw_texture
static bool draw_tex_supported = false;
#endif
static inline GLfixed xdiv(int numerator, int denominator) {
assert(numerator < (1 << 16));
return (numerator << 16) / denominator;
}
template <class T>
static T nextHigher2(T k) {
if (k == 0)
return 1;
--k;
for (uint i = 1; i < sizeof(T) * CHAR_BIT; i <<= 1)
k = k | k >> i;
return k + 1;
}
void GLESTexture::initGLExtensions() {
const char *ext_string =
reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS));
LOGI("Extensions: %s", ext_string);
Common::StringTokenizer tokenizer(ext_string, " ");
while (!tokenizer.empty()) {
Common::String token = tokenizer.nextToken();
if (token == "GL_ARB_texture_non_power_of_two")
npot_supported = true;
#ifdef GL_OES_draw_texture
if (token == "GL_OES_draw_texture")
draw_tex_supported = true;
#endif
}
}
GLESTexture::GLESTexture(GLenum glFormat, GLenum glType,
Graphics::PixelFormat pixelFormat) :
_glFormat(glFormat),
_glType(glType),
_texture_name(0),
_surface(),
_texture_width(0),
_texture_height(0),
_all_dirty(false),
_dirty_rect(),
_pixelFormat(pixelFormat),
_palettePixelFormat()
{
GLCALL(glGenTextures(1, &_texture_name));
}
GLESTexture::~GLESTexture() {
release();
}
void GLESTexture::release() {
debug("Destroying texture %u", _texture_name);
GLCALL(glDeleteTextures(1, &_texture_name));
}
void GLESTexture::reinit() {
GLCALL(glGenTextures(1, &_texture_name));
if (hasPalette()) {
// paletted textures are in a local buffer, don't wipe it
initSize();
} else {
// bypass allocBuffer() shortcut to reinit the texture properly
_texture_width = 0;
_texture_height = 0;
allocBuffer(_surface.w, _surface.h);
}
setDirty();
}
void GLESTexture::initSize() {
// Allocate room for the texture now, but pixel data gets uploaded
// later (perhaps with multiple TexSubImage2D operations).
GLCALL(glBindTexture(GL_TEXTURE_2D, _texture_name));
GLCALL(glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
GLCALL(glTexImage2D(GL_TEXTURE_2D, 0, _glFormat,
_texture_width, _texture_height,
0, _glFormat, _glType, 0));
}
void GLESTexture::allocBuffer(GLuint w, GLuint h) {
_surface.w = w;
_surface.h = h;
_surface.bytesPerPixel = _pixelFormat.bytesPerPixel;
// Already allocated a sufficiently large buffer?
if (w <= _texture_width && h <= _texture_height)
return;
if (npot_supported) {
_texture_width = _surface.w;
_texture_height = _surface.h;
} else {
_texture_width = nextHigher2(_surface.w);
_texture_height = nextHigher2(_surface.h);
}
_surface.pitch = _texture_width * _pixelFormat.bytesPerPixel;
initSize();
}
void GLESTexture::updateBuffer(GLuint x, GLuint y, GLuint w, GLuint h,
const void *buf, int pitch_buf) {
ENTER("%u, %u, %u, %u, %p, %d", x, y, w, h, buf, pitch_buf);
GLCALL(glBindTexture(GL_TEXTURE_2D, _texture_name));
GLCALL(glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
setDirtyRect(Common::Rect(x, y, x + w, y + h));
if (static_cast<int>(w) * _pixelFormat.bytesPerPixel == pitch_buf) {
GLCALL(glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h,
_glFormat, _glType, buf));
} else {
// GLES removed the ability to specify pitch, so we
// have to do this ourselves.
#if TEXSUBIMAGE_IS_EXPENSIVE
byte *tmp = new byte[w * h * _pixelFormat.bytesPerPixel];
assert(tmp);
const byte *src = static_cast<const byte *>(buf);
byte *dst = tmp;
GLuint count = h;
do {
memcpy(dst, src, w * _pixelFormat.bytesPerPixel);
dst += w * _pixelFormat.bytesPerPixel;
src += pitch_buf;
} while (--count);
GLCALL(glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h,
_glFormat, _glType, tmp));
delete[] tmp;
#else
// This version avoids the intermediate copy at the expense of
// repeat glTexSubImage2D calls. On some devices this is worse.
const byte *src = static_cast<const byte *>(buf);
do {
GLCALL(glTexSubImage2D(GL_TEXTURE_2D, 0, x, y,
w, 1, _glFormat, _glType, src));
++y;
src += pitch_buf;
} while (--h);
#endif
}
}
void GLESTexture::fillBuffer(uint32 color) {
uint rowbytes = _surface.w * _pixelFormat.bytesPerPixel;
byte *tmp = new byte[rowbytes];
assert(tmp);
if (_pixelFormat.bytesPerPixel == 1 ||
((color & 0xff) == ((color >> 8) & 0xff))) {
memset(tmp, color & 0xff, rowbytes);
} else {
uint16 *p = (uint16 *)tmp;
Common::set_to(p, p + _surface.w, (uint16)color);
}
GLCALL(glBindTexture(GL_TEXTURE_2D, _texture_name));
GLCALL(glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
for (GLuint y = 0; y < _surface.h; ++y)
GLCALL(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, y, _surface.w, 1,
_glFormat, _glType, tmp));
delete[] tmp;
setDirty();
}
void GLESTexture::drawTexture(GLshort x, GLshort y, GLshort w, GLshort h) {
GLCALL(glBindTexture(GL_TEXTURE_2D, _texture_name));
#ifdef GL_OES_draw_texture
// Great extension, but only works under specific conditions.
// Still a work-in-progress - disabled for now.
if (false && draw_tex_supported && !hasPalette()) {
//GLCALL(glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE));
const GLint crop[4] = { 0, _surface.h, _surface.w, -_surface.h };
GLCALL(glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop));
// Android GLES bug?
GLCALL(glColor4ub(0xff, 0xff, 0xff, 0xff));
GLCALL(glDrawTexiOES(x, y, 0, w, h));
} else
#endif
{
const GLfixed tex_width = xdiv(_surface.w, _texture_width);
const GLfixed tex_height = xdiv(_surface.h, _texture_height);
const GLfixed texcoords[] = {
0, 0,
tex_width, 0,
0, tex_height,
tex_width, tex_height,
};
GLCALL(glTexCoordPointer(2, GL_FIXED, 0, texcoords));
const GLshort vertices[] = {
x, y,
x + w, y,
x, y + h,
x + w, y + h,
};
GLCALL(glVertexPointer(2, GL_SHORT, 0, vertices));
assert(ARRAYSIZE(vertices) == ARRAYSIZE(texcoords));
GLCALL(glDrawArrays(GL_TRIANGLE_STRIP, 0, ARRAYSIZE(vertices) / 2));
}
clearDirty();
}
const Graphics::PixelFormat &GLESTexture::getPixelFormat() const {
return _pixelFormat;
}
GLES4444Texture::GLES4444Texture() :
GLESTexture(GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, pixelFormat()) {
}
GLES4444Texture::~GLES4444Texture() {
}
GLES5551Texture::GLES5551Texture() :
GLESTexture(GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, pixelFormat()) {
}
GLES5551Texture::~GLES5551Texture() {
}
GLES565Texture::GLES565Texture() :
GLESTexture(GL_RGB, GL_UNSIGNED_SHORT_5_6_5, pixelFormat()) {
}
GLES565Texture::~GLES565Texture() {
}
GLESPaletteTexture::GLESPaletteTexture(GLenum glFormat, GLenum glType,
Graphics::PixelFormat palettePixelFormat) :
GLESTexture(glFormat, glType,
Graphics::PixelFormat::createFormatCLUT8()),
_texture(0)
{
_palettePixelFormat = palettePixelFormat;
_paletteSize = _palettePixelFormat.bytesPerPixel * 256;
}
GLESPaletteTexture::~GLESPaletteTexture() {
delete[] _texture;
}
void GLESPaletteTexture::allocBuffer(GLuint w, GLuint h) {
GLESTexture::allocBuffer(w, h);
// Texture gets uploaded later (from drawTexture())
byte *new_buffer = new byte[_paletteSize +
_texture_width * _texture_height];
assert(new_buffer);
if (_texture) {
// preserve palette
memcpy(new_buffer, _texture, _paletteSize);
delete[] _texture;
}
_texture = new_buffer;
_surface.pixels = _texture + _paletteSize;
}
void GLESPaletteTexture::fillBuffer(uint32 color) {
assert(_surface.pixels);
memset(_surface.pixels, color & 0xff, _surface.pitch * _surface.h);
setDirty();
}
void GLESPaletteTexture::updateBuffer(GLuint x, GLuint y, GLuint w, GLuint h,
const void *buf, int pitch_buf) {
setDirtyRect(Common::Rect(x, y, x + w, y + h));
const byte * src = static_cast<const byte *>(buf);
byte *dst = static_cast<byte *>(_surface.getBasePtr(x, y));
do {
memcpy(dst, src, w);
dst += _surface.pitch;
src += pitch_buf;
} while (--h);
}
void GLESPaletteTexture::drawTexture(GLshort x, GLshort y, GLshort w,
GLshort h) {
if (dirty()) {
GLCALL(glBindTexture(GL_TEXTURE_2D, _texture_name));
const size_t texture_size = _paletteSize +
_texture_width * _texture_height;
GLCALL(glCompressedTexImage2D(GL_TEXTURE_2D, 0, _glType,
_texture_width, _texture_height,
0, texture_size, _texture));
}
GLESTexture::drawTexture(x, y, w, h);
}
GLESPalette888Texture::GLESPalette888Texture() :
GLESPaletteTexture(GL_RGB, GL_PALETTE8_RGB8_OES,
Graphics::PixelFormat(3, 8, 8, 8, 0, 16, 8, 0, 0)) {
}
GLESPalette888Texture::~GLESPalette888Texture() {
}
GLESPalette8888Texture::GLESPalette8888Texture() :
GLESPaletteTexture(GL_RGBA, GL_PALETTE8_RGBA8_OES,
Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0)) {
}
GLESPalette8888Texture::~GLESPalette8888Texture() {
}
GLESPalette565Texture::GLESPalette565Texture() :
GLESPaletteTexture(GL_RGB, GL_PALETTE8_R5_G6_B5_OES,
Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0)) {
}
GLESPalette565Texture::~GLESPalette565Texture() {
}
GLESPalette4444Texture::GLESPalette4444Texture() :
GLESPaletteTexture(GL_RGBA, GL_PALETTE8_RGBA4_OES,
Graphics::PixelFormat(2, 4, 4, 4, 4, 12, 8, 4, 0)) {
}
GLESPalette4444Texture::~GLESPalette4444Texture() {
}
GLESPalette5551Texture::GLESPalette5551Texture() :
GLESPaletteTexture(GL_RGBA, GL_PALETTE8_RGB5_A1_OES,
Graphics::PixelFormat(2, 5, 5, 5, 1, 11, 6, 1, 0)) {
}
GLESPalette5551Texture::~GLESPalette5551Texture() {
}
GLESFakePaletteTexture::GLESFakePaletteTexture(GLenum glFormat, GLenum glType,
Graphics::PixelFormat pixelFormat) :
GLESTexture(glFormat, glType, pixelFormat),
_palette(0),
_pixels(0),
_buf(0)
{
_palettePixelFormat = pixelFormat;
_fake_format = Graphics::PixelFormat::createFormatCLUT8();
_palette = new uint16[256];
assert(_palette);
}
GLESFakePaletteTexture::~GLESFakePaletteTexture() {
delete[] _buf;
delete[] _pixels;
delete[] _palette;
}
void GLESFakePaletteTexture::allocBuffer(GLuint w, GLuint h) {
GLESTexture::allocBuffer(w, h);
delete[] _buf;
delete[] _pixels;
_pixels = new byte[w * h];
assert(_pixels);
// fixup surface, for the outside this is a CLUT8 surface
_surface.pixels = _pixels;
_surface.bytesPerPixel = 1;
_surface.pitch = w;
_buf = new uint16[w * h];
assert(_buf);
}
void GLESFakePaletteTexture::fillBuffer(uint32 color) {
assert(_surface.pixels);
memset(_surface.pixels, color & 0xff, _surface.pitch * _surface.h);
setDirty();
}
void GLESFakePaletteTexture::updateBuffer(GLuint x, GLuint y, GLuint w,
GLuint h, const void *buf,
int pitch_buf) {
setDirtyRect(Common::Rect(x, y, x + w, y + h));
const byte *src = (const byte *)buf;
byte *dst = _pixels + y * _surface.pitch + x;
do {
memcpy(dst, src, w);
dst += _surface.pitch;
src += pitch_buf;
} while (--h);
}
void GLESFakePaletteTexture::drawTexture(GLshort x, GLshort y, GLshort w,
GLshort h) {
if (_all_dirty) {
_dirty_rect.top = 0;
_dirty_rect.left = 0;
_dirty_rect.bottom = _surface.h;
_dirty_rect.right = _surface.w;
_all_dirty = false;
}
if (!_dirty_rect.isEmpty()) {
int16 dwidth = _dirty_rect.width();
int16 dheight = _dirty_rect.height();
byte *src = _pixels + _dirty_rect.top * _surface.pitch +
_dirty_rect.left;
uint16 *dst = _buf;
uint pitch_delta = _surface.pitch - dwidth;
for (uint16 j = 0; j < dheight; ++j) {
for (uint16 i = 0; i < dwidth; ++i)
*dst++ = _palette[*src++];
src += pitch_delta;
}
GLCALL(glBindTexture(GL_TEXTURE_2D, _texture_name));
GLCALL(glTexSubImage2D(GL_TEXTURE_2D, 0,
_dirty_rect.left, _dirty_rect.top,
dwidth, dheight, _glFormat, _glType, _buf));
}
GLESTexture::drawTexture(x, y, w, h);
}
const Graphics::PixelFormat &GLESFakePaletteTexture::getPixelFormat() const {
return _fake_format;
}
GLESFakePalette565Texture::GLESFakePalette565Texture() :
GLESFakePaletteTexture(GL_RGB, GL_UNSIGNED_SHORT_5_6_5,
GLES565Texture::pixelFormat()) {
}
GLESFakePalette565Texture::~GLESFakePalette565Texture() {
}
#endif