Johannes Schickel a401f0a19e ALL: Replace cursorTargetScale in OSystem API with a simple "do not scale" logic.
All uses of the old target scale API actually wanted to disallow scaling of
the mouse cursor. This commit adapts our API to this and thus simplifies
backend implementations.

Some backends, most notable the Wii and Android, did some implementation of
the cursor target scale, which I didn't adapt yet. I added a TODO for the
porters there.
2012-06-03 02:02:57 +02:00

830 lines
20 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.
*
*/
#if defined(__ANDROID__)
// Allow use of stuff in <time.h>
#define FORBIDDEN_SYMBOL_EXCEPTION_time_h
// Disable printf override in common/forbidden.h to avoid
// clashes with log.h from the Android SDK.
// That header file uses
// __attribute__ ((format(printf, 3, 4)))
// which gets messed up by our override mechanism; this could
// be avoided by either changing the Android SDK to use the equally
// legal and valid
// __attribute__ ((format(printf, 3, 4)))
// or by refining our printf override to use a varadic macro
// (which then wouldn't be portable, though).
// Anyway, for now we just disable the printf override globally
// for the Android port
#define FORBIDDEN_SYMBOL_EXCEPTION_printf
#include "common/endian.h"
#include "graphics/conversion.h"
#include "backends/platform/android/android.h"
#include "backends/platform/android/jni.h"
static inline GLfixed xdiv(int numerator, int denominator) {
assert(numerator < (1 << 16));
return (numerator << 16) / denominator;
}
const OSystem::GraphicsMode *OSystem_Android::getSupportedGraphicsModes() const {
static const OSystem::GraphicsMode s_supportedGraphicsModes[] = {
{ "default", "Default", 0 },
{ "filter", "Linear filtering", 1 },
{ 0, 0, 0 },
};
return s_supportedGraphicsModes;
}
int OSystem_Android::getDefaultGraphicsMode() const {
return 0;
}
bool OSystem_Android::setGraphicsMode(int mode) {
ENTER("%d", mode);
if (_game_texture)
_game_texture->setLinearFilter(mode == 1);
if (_overlay_texture)
_overlay_texture->setLinearFilter(mode == 1);
if (_mouse_texture)
_mouse_texture->setLinearFilter(mode == 1);
_graphicsMode = mode;
return true;
}
int OSystem_Android::getGraphicsMode() const {
return _graphicsMode;
}
#ifdef USE_RGB_COLOR
Graphics::PixelFormat OSystem_Android::getScreenFormat() const {
return _game_texture->getPixelFormat();
}
Common::List<Graphics::PixelFormat> OSystem_Android::getSupportedFormats() const {
Common::List<Graphics::PixelFormat> res;
res.push_back(GLES565Texture::pixelFormat());
res.push_back(GLES5551Texture::pixelFormat());
res.push_back(GLES4444Texture::pixelFormat());
res.push_back(Graphics::PixelFormat::createFormatCLUT8());
return res;
}
Common::String OSystem_Android::getPixelFormatName(const Graphics::PixelFormat &format) const {
if (format.bytesPerPixel == 1)
return "CLUT8";
if (format.aLoss == 8)
return Common::String::format("RGB%u%u%u",
8 - format.rLoss,
8 - format.gLoss,
8 - format.bLoss);
return Common::String::format("RGBA%u%u%u%u",
8 - format.rLoss,
8 - format.gLoss,
8 - format.bLoss,
8 - format.aLoss);
}
void OSystem_Android::initTexture(GLESBaseTexture **texture,
uint width, uint height,
const Graphics::PixelFormat *format) {
assert(texture);
Graphics::PixelFormat format_clut8 =
Graphics::PixelFormat::createFormatCLUT8();
Graphics::PixelFormat format_current;
Graphics::PixelFormat format_new;
if (*texture)
format_current = (*texture)->getPixelFormat();
else
format_current = Graphics::PixelFormat();
if (format)
format_new = *format;
else
format_new = format_clut8;
if (format_current != format_new) {
if (*texture)
LOGD("switching pixel format from: %s",
getPixelFormatName((*texture)->getPixelFormat()).c_str());
delete *texture;
if (format_new == GLES565Texture::pixelFormat())
*texture = new GLES565Texture();
else if (format_new == GLES5551Texture::pixelFormat())
*texture = new GLES5551Texture();
else if (format_new == GLES4444Texture::pixelFormat())
*texture = new GLES4444Texture();
else {
// TODO what now?
if (format_new != format_clut8)
LOGE("unsupported pixel format: %s",
getPixelFormatName(format_new).c_str());
*texture = new GLESFakePalette565Texture;
}
LOGD("new pixel format: %s",
getPixelFormatName((*texture)->getPixelFormat()).c_str());
}
(*texture)->allocBuffer(width, height);
}
#endif
void OSystem_Android::initSurface() {
LOGD("initializing surface");
assert(!JNI::haveSurface());
_screen_changeid = JNI::surface_changeid;
_egl_surface_width = JNI::egl_surface_width;
_egl_surface_height = JNI::egl_surface_height;
assert(_egl_surface_width > 0 && _egl_surface_height > 0);
JNI::initSurface();
// Initialize OpenGLES context.
GLESTexture::initGLExtensions();
if (_game_texture)
_game_texture->reinit();
if (_overlay_texture) {
_overlay_texture->reinit();
initOverlay();
}
if (_mouse_texture)
_mouse_texture->reinit();
}
void OSystem_Android::deinitSurface() {
if (!JNI::haveSurface())
return;
LOGD("deinitializing surface");
_screen_changeid = JNI::surface_changeid;
_egl_surface_width = 0;
_egl_surface_height = 0;
// release texture resources
if (_game_texture)
_game_texture->release();
if (_overlay_texture)
_overlay_texture->release();
if (_mouse_texture)
_mouse_texture->release();
JNI::deinitSurface();
}
void OSystem_Android::initViewport() {
LOGD("initializing viewport");
assert(JNI::haveSurface());
// Turn off anything that looks like 3D ;)
GLCALL(glDisable(GL_CULL_FACE));
GLCALL(glDisable(GL_DEPTH_TEST));
GLCALL(glDisable(GL_LIGHTING));
GLCALL(glDisable(GL_FOG));
GLCALL(glDisable(GL_DITHER));
GLCALL(glShadeModel(GL_FLAT));
GLCALL(glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST));
GLCALL(glEnable(GL_BLEND));
GLCALL(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
GLCALL(glEnableClientState(GL_VERTEX_ARRAY));
GLCALL(glEnableClientState(GL_TEXTURE_COORD_ARRAY));
GLCALL(glEnable(GL_TEXTURE_2D));
GLCALL(glViewport(0, 0, _egl_surface_width, _egl_surface_height));
GLCALL(glMatrixMode(GL_PROJECTION));
GLCALL(glLoadIdentity());
GLCALL(glOrthof(0, _egl_surface_width, _egl_surface_height, 0, -1, 1));
GLCALL(glMatrixMode(GL_MODELVIEW));
GLCALL(glLoadIdentity());
clearFocusRectangle();
}
void OSystem_Android::initOverlay() {
// minimum of 320x200
// (surface can get smaller when opening the virtual keyboard on *QVGA*)
int overlay_width = MAX(_egl_surface_width, 320);
int overlay_height = MAX(_egl_surface_height, 200);
// the 'normal' theme layout uses a max height of 400 pixels. if the
// surface is too big we use only a quarter of the size so that the widgets
// don't get too small. if the surface height has less than 800 pixels, this
// enforces the 'lowres' layout, which will be scaled back up by factor 2x,
// but this looks way better than the 'normal' layout scaled by some
// calculated factors
while (overlay_height > 480) {
overlay_width /= 2;
overlay_height /= 2;
}
LOGI("overlay size is %ux%u", overlay_width, overlay_height);
_overlay_texture->allocBuffer(overlay_width, overlay_height);
_overlay_texture->setDrawRect(0, 0,
_egl_surface_width, _egl_surface_height);
}
void OSystem_Android::initSize(uint width, uint height,
const Graphics::PixelFormat *format) {
ENTER("%d, %d, %p", width, height, format);
GLTHREADCHECK;
#ifdef USE_RGB_COLOR
initTexture(&_game_texture, width, height, format);
#else
_game_texture->allocBuffer(width, height);
#endif
updateScreenRect();
updateEventScale();
// Don't know mouse size yet - it gets reallocated in
// setMouseCursor. We need the palette allocated before
// setMouseCursor however, so just take a guess at the desired
// size (it's small).
_mouse_texture_palette->allocBuffer(20, 20);
clearScreen(kClear);
}
void OSystem_Android::clearScreen(FixupType type, byte count) {
assert(count > 0);
bool sm = _show_mouse;
_show_mouse = false;
GLCALL(glDisable(GL_SCISSOR_TEST));
for (byte i = 0; i < count; ++i) {
// clear screen
GLCALL(glClearColorx(0, 0, 0, 1 << 16));
GLCALL(glClear(GL_COLOR_BUFFER_BIT));
switch (type) {
case kClear:
break;
case kClearSwap:
JNI::swapBuffers();
break;
case kClearUpdate:
_force_redraw = true;
updateScreen();
break;
}
}
if (!_show_overlay)
GLCALL(glEnable(GL_SCISSOR_TEST));
_show_mouse = sm;
_force_redraw = true;
}
void OSystem_Android::updateScreenRect() {
Common::Rect rect(0, 0, _egl_surface_width, _egl_surface_height);
_overlay_texture->setDrawRect(rect);
uint16 w = _game_texture->width();
uint16 h = _game_texture->height();
if (w && h && !_fullscreen) {
if (_ar_correction && w == 320 && h == 200)
h = 240;
float dpi[2];
JNI::getDPI(dpi);
float screen_ar;
if (dpi[0] != 0.0 && dpi[1] != 0.0) {
// horizontal orientation
screen_ar = (dpi[1] * _egl_surface_width) /
(dpi[0] * _egl_surface_height);
} else {
screen_ar = float(_egl_surface_width) / float(_egl_surface_height);
}
float game_ar = float(w) / float(h);
if (screen_ar > game_ar) {
rect.setWidth(round(_egl_surface_height * game_ar));
rect.moveTo((_egl_surface_width - rect.width()) / 2, 0);
} else {
rect.setHeight(round(_egl_surface_width / game_ar));
rect.moveTo((_egl_surface_height - rect.height()) / 2, 0);
}
}
glScissor(rect.left, rect.top, rect.width(), rect.height());
_game_texture->setDrawRect(rect);
}
int OSystem_Android::getScreenChangeID() const {
return _screen_changeid;
}
int16 OSystem_Android::getHeight() {
return _game_texture->height();
}
int16 OSystem_Android::getWidth() {
return _game_texture->width();
}
void OSystem_Android::setPalette(const byte *colors, uint start, uint num) {
ENTER("%p, %u, %u", colors, start, num);
#ifdef USE_RGB_COLOR
assert(_game_texture->hasPalette());
#endif
GLTHREADCHECK;
if (!_use_mouse_palette)
setCursorPaletteInternal(colors, start, num);
const Graphics::PixelFormat &pf = _game_texture->getPalettePixelFormat();
byte *p = _game_texture->palette() + start * 2;
for (uint i = 0; i < num; ++i, colors += 3, p += 2)
WRITE_UINT16(p, pf.RGBToColor(colors[0], colors[1], colors[2]));
}
void OSystem_Android::grabPalette(byte *colors, uint start, uint num) {
ENTER("%p, %u, %u", colors, start, num);
#ifdef USE_RGB_COLOR
assert(_game_texture->hasPalette());
#endif
GLTHREADCHECK;
const Graphics::PixelFormat &pf = _game_texture->getPalettePixelFormat();
const byte *p = _game_texture->palette_const() + start * 2;
for (uint i = 0; i < num; ++i, colors += 3, p += 2)
pf.colorToRGB(READ_UINT16(p), colors[0], colors[1], colors[2]);
}
void OSystem_Android::copyRectToScreen(const byte *buf, int pitch,
int x, int y, int w, int h) {
ENTER("%p, %d, %d, %d, %d, %d", buf, pitch, x, y, w, h);
GLTHREADCHECK;
_game_texture->updateBuffer(x, y, w, h, buf, pitch);
}
void OSystem_Android::updateScreen() {
//ENTER();
GLTHREADCHECK;
if (!JNI::haveSurface())
return;
if (!_force_redraw &&
!_game_texture->dirty() &&
!_overlay_texture->dirty() &&
!_mouse_texture->dirty())
return;
_force_redraw = false;
// clear pointer leftovers in dead areas
// also, HTC's GLES drivers are made of fail and don't preserve the buffer
// ( http://www.khronos.org/registry/egl/specs/EGLTechNote0001.html )
if ((_show_overlay || _htc_fail) && !_fullscreen)
clearScreen(kClear);
GLCALL(glPushMatrix());
if (_shake_offset != 0 ||
(!_focus_rect.isEmpty() &&
!Common::Rect(_game_texture->width(),
_game_texture->height()).contains(_focus_rect))) {
// These are the only cases where _game_texture doesn't
// cover the entire screen.
clearScreen(kClear);
// Move everything up by _shake_offset (game) pixels
GLCALL(glTranslatex(0, -_shake_offset << 16, 0));
}
// TODO this doesnt work on those sucky drivers, do it differently
// if (_show_overlay)
// GLCALL(glColor4ub(0x9f, 0x9f, 0x9f, 0x9f));
if (_focus_rect.isEmpty()) {
_game_texture->drawTextureRect();
} else {
GLCALL(glPushMatrix());
GLCALL(glScalex(xdiv(_egl_surface_width, _focus_rect.width()),
xdiv(_egl_surface_height, _focus_rect.height()),
1 << 16));
GLCALL(glTranslatex(-_focus_rect.left << 16,
-_focus_rect.top << 16, 0));
GLCALL(glScalex(xdiv(_game_texture->width(), _egl_surface_width),
xdiv(_game_texture->height(), _egl_surface_height),
1 << 16));
_game_texture->drawTextureRect();
GLCALL(glPopMatrix());
}
int cs = _mouse_targetscale;
if (_show_overlay) {
// TODO see above
// GLCALL(glColor4ub(0xff, 0xff, 0xff, 0xff));
// ugly, but the modern theme sets a wacko factor, only god knows why
cs = 1;
GLCALL(_overlay_texture->drawTextureRect());
}
if (_show_mouse && !_mouse_texture->isEmpty()) {
GLCALL(glPushMatrix());
const Common::Point &mouse = getEventManager()->getMousePos();
// Scale up ScummVM -> OpenGL (pixel) coordinates
if (_show_overlay) {
GLCALL(glScalex(xdiv(_egl_surface_width,
_overlay_texture->width()),
xdiv(_egl_surface_height,
_overlay_texture->height()),
1 << 16));
} else {
const Common::Rect &r = _game_texture->getDrawRect();
GLCALL(glTranslatex(r.left << 16,
r.top << 16,
0));
GLCALL(glScalex(xdiv(r.width(), _game_texture->width()),
xdiv(r.height(), _game_texture->height()),
1 << 16));
}
GLCALL(glTranslatex((-_mouse_hotspot.x * cs) << 16,
(-_mouse_hotspot.y * cs) << 16,
0));
// Note the extra half texel to position the mouse in
// the middle of the x,y square:
GLCALL(glTranslatex((mouse.x << 16) | 1 << 15,
(mouse.y << 16) | 1 << 15, 0));
GLCALL(glScalex(cs << 16, cs << 16, 1 << 16));
_mouse_texture->drawTextureOrigin();
GLCALL(glPopMatrix());
}
GLCALL(glPopMatrix());
if (!JNI::swapBuffers())
LOGW("swapBuffers failed: 0x%x", glGetError());
}
Graphics::Surface *OSystem_Android::lockScreen() {
ENTER();
GLTHREADCHECK;
Graphics::Surface *surface = _game_texture->surface();
assert(surface->pixels);
return surface;
}
void OSystem_Android::unlockScreen() {
ENTER();
GLTHREADCHECK;
assert(_game_texture->dirty());
}
void OSystem_Android::setShakePos(int shake_offset) {
ENTER("%d", shake_offset);
if (_shake_offset != shake_offset) {
_shake_offset = shake_offset;
_force_redraw = true;
}
}
void OSystem_Android::fillScreen(uint32 col) {
ENTER("%u", col);
GLTHREADCHECK;
_game_texture->fillBuffer(col);
}
void OSystem_Android::setFocusRectangle(const Common::Rect& rect) {
ENTER("%d, %d, %d, %d", rect.left, rect.top, rect.right, rect.bottom);
if (_enable_zoning) {
_focus_rect = rect;
_force_redraw = true;
}
}
void OSystem_Android::clearFocusRectangle() {
ENTER();
if (_enable_zoning) {
_focus_rect = Common::Rect();
_force_redraw = true;
}
}
void OSystem_Android::showOverlay() {
ENTER();
_show_overlay = true;
_force_redraw = true;
updateEventScale();
warpMouse(_overlay_texture->width() / 2, _overlay_texture->height() / 2);
GLCALL(glDisable(GL_SCISSOR_TEST));
}
void OSystem_Android::hideOverlay() {
ENTER();
_show_overlay = false;
updateEventScale();
warpMouse(_game_texture->width() / 2, _game_texture->height() / 2);
// double buffered, flip twice
clearScreen(kClearUpdate, 2);
GLCALL(glEnable(GL_SCISSOR_TEST));
}
void OSystem_Android::clearOverlay() {
ENTER();
GLTHREADCHECK;
_overlay_texture->fillBuffer(0);
}
void OSystem_Android::grabOverlay(OverlayColor *buf, int pitch) {
ENTER("%p, %d", buf, pitch);
GLTHREADCHECK;
const Graphics::Surface *surface = _overlay_texture->surface_const();
assert(surface->format.bytesPerPixel == sizeof(buf[0]));
const byte *src = (const byte *)surface->pixels;
uint h = surface->h;
do {
memcpy(buf, src, surface->w * surface->format.bytesPerPixel);
src += surface->pitch;
// This 'pitch' is pixels not bytes
buf += pitch;
} while (--h);
}
void OSystem_Android::copyRectToOverlay(const OverlayColor *buf, int pitch,
int x, int y, int w, int h) {
ENTER("%p, %d, %d, %d, %d, %d", buf, pitch, x, y, w, h);
GLTHREADCHECK;
// This 'pitch' is pixels not bytes
_overlay_texture->updateBuffer(x, y, w, h, buf, pitch * sizeof(buf[0]));
}
int16 OSystem_Android::getOverlayHeight() {
return _overlay_texture->height();
}
int16 OSystem_Android::getOverlayWidth() {
return _overlay_texture->width();
}
Graphics::PixelFormat OSystem_Android::getOverlayFormat() const {
return _overlay_texture->getPixelFormat();
}
bool OSystem_Android::showMouse(bool visible) {
ENTER("%d", visible);
_show_mouse = visible;
return true;
}
void OSystem_Android::setMouseCursor(const byte *buf, uint w, uint h,
int hotspotX, int hotspotY,
uint32 keycolor, bool dontScale,
const Graphics::PixelFormat *format) {
ENTER("%p, %u, %u, %d, %d, %u, %d, %p", buf, w, h, hotspotX, hotspotY,
keycolor, dontScale, format);
GLTHREADCHECK;
#ifdef USE_RGB_COLOR
if (format && format->bytesPerPixel > 1) {
if (_mouse_texture != _mouse_texture_rgb) {
LOGD("switching to rgb mouse cursor");
assert(!_mouse_texture_rgb);
_mouse_texture_rgb = new GLES5551Texture();
_mouse_texture_rgb->setLinearFilter(_graphicsMode == 1);
}
_mouse_texture = _mouse_texture_rgb;
} else {
if (_mouse_texture != _mouse_texture_palette)
LOGD("switching to paletted mouse cursor");
_mouse_texture = _mouse_texture_palette;
delete _mouse_texture_rgb;
_mouse_texture_rgb = 0;
}
#endif
_mouse_texture->allocBuffer(w, h);
if (_mouse_texture == _mouse_texture_palette) {
assert(keycolor < 256);
byte *p = _mouse_texture_palette->palette() + _mouse_keycolor * 2;
WRITE_UINT16(p, READ_UINT16(p) | 1);
_mouse_keycolor = keycolor;
p = _mouse_texture_palette->palette() + _mouse_keycolor * 2;
WRITE_UINT16(p, READ_UINT16(p) & ~1);
}
if (w == 0 || h == 0)
return;
if (_mouse_texture == _mouse_texture_palette) {
_mouse_texture->updateBuffer(0, 0, w, h, buf, w);
} else {
uint16 pitch = _mouse_texture->pitch();
byte *tmp = new byte[pitch * h];
// meh, a 16bit cursor without alpha bits... this is so silly
if (!crossBlit(tmp, buf, pitch, w * 2, w, h,
_mouse_texture->getPixelFormat(),
*format)) {
LOGE("crossblit failed");
delete[] tmp;
_mouse_texture->allocBuffer(0, 0);
return;
}
uint16 *s = (uint16 *)buf;
uint16 *d = (uint16 *)tmp;
for (uint16 y = 0; y < h; ++y, d += pitch / 2 - w)
for (uint16 x = 0; x < w; ++x, d++)
if (*s++ != (keycolor & 0xffff))
*d |= 1;
_mouse_texture->updateBuffer(0, 0, w, h, tmp, pitch);
delete[] tmp;
}
_mouse_hotspot = Common::Point(hotspotX, hotspotY);
// TODO: Adapt to the new "do not scale" cursor logic.
_mouse_targetscale = 1;
}
void OSystem_Android::setCursorPaletteInternal(const byte *colors,
uint start, uint num) {
const Graphics::PixelFormat &pf =
_mouse_texture_palette->getPalettePixelFormat();
byte *p = _mouse_texture_palette->palette() + start * 2;
for (uint i = 0; i < num; ++i, colors += 3, p += 2)
WRITE_UINT16(p, pf.RGBToColor(colors[0], colors[1], colors[2]));
p = _mouse_texture_palette->palette() + _mouse_keycolor * 2;
WRITE_UINT16(p, READ_UINT16(p) & ~1);
}
void OSystem_Android::setCursorPalette(const byte *colors,
uint start, uint num) {
ENTER("%p, %u, %u", colors, start, num);
GLTHREADCHECK;
if (!_mouse_texture->hasPalette()) {
LOGD("switching to paletted mouse cursor");
_mouse_texture = _mouse_texture_palette;
delete _mouse_texture_rgb;
_mouse_texture_rgb = 0;
}
setCursorPaletteInternal(colors, start, num);
_use_mouse_palette = true;
}
void OSystem_Android::disableCursorPalette() {
// when disabling the cursor palette, and we're running a clut8 game,
// it expects the game palette to be used for the cursor
if (_game_texture->hasPalette()) {
const byte *src = _game_texture->palette_const();
byte *dst = _mouse_texture_palette->palette();
const Graphics::PixelFormat &pf_src =
_game_texture->getPalettePixelFormat();
const Graphics::PixelFormat &pf_dst =
_mouse_texture_palette->getPalettePixelFormat();
uint8 r, g, b;
for (uint i = 0; i < 256; ++i, src += 2, dst += 2) {
pf_src.colorToRGB(READ_UINT16(src), r, g, b);
WRITE_UINT16(dst, pf_dst.RGBToColor(r, g, b));
}
byte *p = _mouse_texture_palette->palette() + _mouse_keycolor * 2;
WRITE_UINT16(p, READ_UINT16(p) & ~1);
}
}
#endif