scummvm/backends/graphics3d/android/android-graphics3d.cpp
Le Philousophe a13d0879f2 ANDROID: Convert mouse coordinates between screen ones and virtual ones
In 2D graphical backend it's handled in the WindowedGraphicsManager
which we inherit from through OpenGLGraphicsManager
2022-08-15 20:19:08 +02:00

1027 lines
30 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
// 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/tokenizer.h"
#include "graphics/conversion.h"
#include "graphics/opengl/shader.h"
#include "graphics/opengl/context.h"
#include "backends/graphics3d/android/android-graphics3d.h"
#include "backends/platform/android/android.h"
#include "backends/platform/android/jni-android.h"
// These helper macros let us setup our context only when the game has different settings than us
#define CONTEXT_SAVE_STATE(gl_param) GLboolean saved ## gl_param; GLCALL(saved ## gl_param = glIsEnabled(gl_param))
#define CONTEXT_SET_ENABLE(gl_param) if (!(saved ## gl_param)) { GLCALL(glEnable(gl_param)); }
#define CONTEXT_SET_DISABLE(gl_param) if (saved ## gl_param) { GLCALL(glDisable(gl_param)); }
// These helper macros do the opposite to get back what the game expected
#define CONTEXT_RESET_ENABLE(gl_param) if (!(saved ## gl_param)) { GLCALL(glDisable(gl_param)); }
#define CONTEXT_RESET_DISABLE(gl_param) if (saved ## gl_param) { GLCALL(glEnable(gl_param)); }
static GLES8888Texture *loadBuiltinTexture(JNI::BitmapResources resource) {
const Graphics::Surface *src = JNI::getBitmapResource(JNI::BitmapResources::TOUCH_ARROWS_BITMAP);
if (!src) {
error("Failed to fetch touch arrows bitmap");
}
GLES8888Texture *ret = new GLES8888Texture();
ret->allocBuffer(src->w, src->h);
Graphics::Surface *dst = ret->surface();
Graphics::crossBlit(
(byte *)dst->getPixels(), (const byte *)src->getPixels(),
dst->pitch, src->pitch,
src->w, src->h,
src->format, dst->format);
delete src;
return ret;
}
AndroidGraphics3dManager::AndroidGraphics3dManager() :
_screenChangeID(0),
_graphicsMode(0),
_fullscreen(true),
_ar_correction(true),
_force_redraw(false),
_game_texture(0),
_frame_buffer(0),
_cursorX(0),
_cursorY(0),
_overlay_texture(0),
_overlay_background(nullptr),
_show_overlay(false),
_mouse_texture(nullptr),
_mouse_texture_palette(nullptr),
_mouse_texture_rgb(nullptr),
_mouse_hotspot(),
_mouse_dont_scale(false),
_show_mouse(false),
_touchcontrols_texture(nullptr),
_old_touch_mode(OSystem_Android::TOUCH_MODE_TOUCHPAD) {
if (JNI::egl_bits_per_pixel == 16) {
// We default to RGB565 and RGBA5551 which is closest to what we setup in Java side
_game_texture = new GLES565Texture();
_overlay_texture = new GLES5551Texture();
_overlay_background = new GLES565Texture();
_mouse_texture_palette = new GLESFakePalette5551Texture();
} else {
// If not 16, this must be 24 or 32 bpp so make use of them
_game_texture = new GLES888Texture();
_overlay_texture = new GLES8888Texture();
_overlay_background = new GLES888Texture();
_mouse_texture_palette = new GLESFakePalette8888Texture();
}
_mouse_texture = _mouse_texture_palette;
_touchcontrols_texture = loadBuiltinTexture(JNI::BitmapResources::TOUCH_ARROWS_BITMAP);
initSurface();
// in 3D, not in overlay
dynamic_cast<OSystem_Android *>(g_system)->applyTouchSettings(true, false);
}
AndroidGraphics3dManager::~AndroidGraphics3dManager() {
// Reinitialize OpenGL for other manager
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
glUseProgram(0);
deinitSurface();
delete _frame_buffer;
delete _game_texture;
delete _overlay_texture;
delete _overlay_background;
delete _mouse_texture_palette;
delete _mouse_texture_rgb;
delete _touchcontrols_texture;
}
static void logExtensions() {
const char *ext_string =
reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS));
LOGI("Extensions:");
Common::String exts;
Common::StringTokenizer tokenizer(ext_string, " ");
while (!tokenizer.empty()) {
Common::String token = tokenizer.nextToken();
exts += token + " ";
if (exts.size() > 100) {
LOGI("\t%s", exts.c_str());
exts = "";
}
}
if (exts.size() > 0) {
LOGI("\t%s", exts.c_str());
}
}
void AndroidGraphics3dManager::initSurface() {
LOGD("initializing 3D surface");
assert(!JNI::haveSurface());
JNI::initSurface();
_screenChangeID = JNI::surface_changeid;
// Initialize OpenGLES context.
OpenGLContext.initialize(OpenGL::kContextGLES2);
logExtensions();
GLESTexture::initGL();
if (_game_texture) {
_game_texture->reinit();
// We had a frame buffer initialized, we must renew it as the game textured got renewed
if (_frame_buffer) {
delete _frame_buffer;
_frame_buffer = new OpenGL::FrameBuffer(_game_texture->getTextureName(),
_game_texture->width(), _game_texture->height(),
_game_texture->texWidth(), _game_texture->texHeight());
}
}
// We don't have any content to display: just make sure surface is clean
if (_overlay_background) {
_overlay_background->release();
}
if (_overlay_texture) {
_overlay_texture->reinit();
initOverlay();
}
if (_mouse_texture) {
_mouse_texture->reinit();
}
if (_touchcontrols_texture) {
_touchcontrols_texture->reinit();
}
dynamic_cast<OSystem_Android *>(g_system)->getTouchControls().init(
this, JNI::egl_surface_width, JNI::egl_surface_height);
updateScreenRect();
// double buffered, flip twice
clearScreen(kClearUpdate, 2);
}
void AndroidGraphics3dManager::deinitSurface() {
if (!JNI::haveSurface()) {
return;
}
LOGD("deinitializing 3D surface");
_screenChangeID = JNI::surface_changeid;
// release texture resources
if (_game_texture) {
_game_texture->release();
}
if (_overlay_texture) {
_overlay_texture->release();
}
if (_overlay_background) {
_overlay_background->release();
}
if (_mouse_texture) {
_mouse_texture->release();
}
dynamic_cast<OSystem_Android *>(g_system)->getTouchControls().init(
nullptr, 0, 0);
if (_touchcontrols_texture) {
_touchcontrols_texture->release();
}
OpenGLContext.reset();
JNI::deinitSurface();
}
void AndroidGraphics3dManager::updateScreen() {
//ENTER();
GLTHREADCHECK;
if (!JNI::haveSurface()) {
return;
}
if (!_force_redraw &&
!_game_texture->dirty() &&
!_overlay_texture->dirty() &&
!_mouse_texture->dirty()) {
return;
}
_force_redraw = false;
// Save the game state
GLint savedBlendSrcRGB, savedBlendDstRGB, savedBlendSrcAlpha, savedBlendDstAlpha,
savedBlendEqRGB, savedBlendEqAlpha;
GLint savedViewport[4];
CONTEXT_SAVE_STATE(GL_BLEND);
GLCALL(glGetIntegerv(GL_BLEND_SRC_RGB, &savedBlendSrcRGB));
GLCALL(glGetIntegerv(GL_BLEND_DST_RGB, &savedBlendDstRGB));
GLCALL(glGetIntegerv(GL_BLEND_SRC_ALPHA, &savedBlendSrcAlpha));
GLCALL(glGetIntegerv(GL_BLEND_DST_ALPHA, &savedBlendDstAlpha));
GLCALL(glGetIntegerv(GL_BLEND_EQUATION_RGB, &savedBlendEqRGB));
GLCALL(glGetIntegerv(GL_BLEND_EQUATION_ALPHA, &savedBlendEqAlpha));
CONTEXT_SAVE_STATE(GL_CULL_FACE);
CONTEXT_SAVE_STATE(GL_DEPTH_TEST);
CONTEXT_SAVE_STATE(GL_DITHER);
CONTEXT_SAVE_STATE(GL_POLYGON_OFFSET_FILL);
CONTEXT_SAVE_STATE(GL_SAMPLE_ALPHA_TO_COVERAGE);
CONTEXT_SAVE_STATE(GL_SAMPLE_COVERAGE);
CONTEXT_SAVE_STATE(GL_SCISSOR_TEST);
CONTEXT_SAVE_STATE(GL_STENCIL_TEST);
GLCALL(glGetIntegerv(GL_VIEWPORT, savedViewport));
if (_frame_buffer) {
_frame_buffer->detach();
}
// Make sure everything we need is correctly set up
// Enable what we need and disable the other if it is not already
CONTEXT_SET_ENABLE(GL_BLEND);
if (savedBlendSrcRGB != GL_SRC_ALPHA ||
savedBlendDstRGB != GL_ONE_MINUS_SRC_ALPHA ||
savedBlendSrcAlpha != GL_SRC_ALPHA ||
savedBlendDstAlpha != GL_ONE_MINUS_SRC_ALPHA) {
GLCALL(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
}
if (savedBlendEqRGB != GL_FUNC_ADD ||
savedBlendEqAlpha != GL_FUNC_ADD) {
GLCALL(glBlendEquation(GL_FUNC_ADD));
}
CONTEXT_SET_DISABLE(GL_CULL_FACE);
CONTEXT_SET_DISABLE(GL_DEPTH_TEST);
CONTEXT_SET_DISABLE(GL_DITHER);
CONTEXT_SET_DISABLE(GL_POLYGON_OFFSET_FILL);
CONTEXT_SET_DISABLE(GL_SAMPLE_ALPHA_TO_COVERAGE);
CONTEXT_SET_DISABLE(GL_SAMPLE_COVERAGE);
CONTEXT_SET_DISABLE(GL_SCISSOR_TEST);
CONTEXT_SET_DISABLE(GL_STENCIL_TEST);
glViewport(0, 0, JNI::egl_surface_width, JNI::egl_surface_height);
// clear pointer leftovers in dead areas
clearScreen(kClear);
_game_texture->drawTextureRect();
if (_show_overlay) {
if (_overlay_background && _overlay_background->getTextureName() != 0) {
GLCALL(_overlay_background->drawTextureRect());
}
GLCALL(_overlay_texture->drawTextureRect());
if (_show_mouse && !_mouse_texture->isEmpty()) {
_mouse_texture->drawTexture(_cursorX - _mouse_hotspot_scaled.x, _cursorY - _mouse_hotspot_scaled.y,
_mouse_width_scaled, _mouse_width_scaled);
}
}
dynamic_cast<OSystem_Android *>(g_system)->getTouchControls().draw();
if (!JNI::swapBuffers()) {
LOGW("swapBuffers failed: 0x%x", glGetError());
}
// Here we restore back the GLES state so if we enabled something we disable it back if it needs too and vice versa
CONTEXT_RESET_ENABLE(GL_BLEND);
if (savedGL_BLEND && (
savedBlendSrcRGB != GL_SRC_ALPHA ||
savedBlendDstRGB != GL_ONE_MINUS_SRC_ALPHA ||
savedBlendSrcAlpha != GL_SRC_ALPHA ||
savedBlendDstAlpha != GL_ONE_MINUS_SRC_ALPHA)) {
GLCALL(glBlendFuncSeparate(savedBlendSrcRGB, savedBlendDstRGB,
savedBlendSrcAlpha, savedBlendDstAlpha));
}
if (savedGL_BLEND && (
savedBlendEqRGB != GL_FUNC_ADD ||
savedBlendEqAlpha != GL_FUNC_ADD)) {
GLCALL(glBlendEquationSeparate(savedBlendEqRGB, savedBlendEqAlpha));
}
CONTEXT_RESET_DISABLE(GL_CULL_FACE);
CONTEXT_RESET_DISABLE(GL_DEPTH_TEST);
CONTEXT_RESET_DISABLE(GL_DITHER);
CONTEXT_RESET_DISABLE(GL_POLYGON_OFFSET_FILL);
CONTEXT_RESET_DISABLE(GL_SAMPLE_ALPHA_TO_COVERAGE);
CONTEXT_RESET_DISABLE(GL_SAMPLE_COVERAGE);
CONTEXT_RESET_DISABLE(GL_SCISSOR_TEST);
CONTEXT_RESET_DISABLE(GL_STENCIL_TEST);
// Restore game viewport
GLCALL(glViewport(savedViewport[0], savedViewport[1], savedViewport[2], savedViewport[3]));
// Don't keep our texture attached to avoid the engine writing on it if it forgets to setup its own texture
GLCALL(glBindTexture(GL_TEXTURE_2D, 0));
// Unload our program to make sure engine will use its own
GLESBaseTexture::unbindShader();
if (_frame_buffer) {
_frame_buffer->attach();
}
}
void AndroidGraphics3dManager::displayMessageOnOSD(const Common::U32String &msg) {
ENTER("%s", msg.encode().c_str());
JNI::displayMessageOnOSD(msg);
}
Common::Point AndroidGraphics3dManager::convertScreenToVirtual(int &x, int &y) const {
const GLESBaseTexture *tex = getActiveTexture();
const Common::Rect &screenRect = tex->getDrawRect();
// Clip in place the coordinates that comes handy to call setMousePosition
x = CLIP<int>(x, screenRect.left, screenRect.right - 1);
y = CLIP<int>(y, screenRect.top, screenRect.bottom - 1);
// Now convert this to virtual coordinates using texture virtual size
const uint16 virtualWidth = tex->width();
const uint16 virtualHeight = tex->height();
int virtualX = ((x - screenRect.left) * virtualWidth + screenRect.width() / 2) / screenRect.width();
int virtualY = ((y - screenRect.top) * virtualHeight + screenRect.height() / 2) / screenRect.height();
return Common::Point(CLIP<int>(virtualX, 0, virtualWidth - 1),
CLIP<int>(virtualY, 0, virtualHeight - 1));
}
Common::Point AndroidGraphics3dManager::convertVirtualToScreen(int x, int y) const {
const GLESBaseTexture *tex = getActiveTexture();
const uint16 virtualWidth = tex->width();
const uint16 virtualHeight = tex->height();
const Common::Rect &screenRect = tex->getDrawRect();
int screenX = screenRect.left + (x * screenRect.width() + virtualWidth / 2) / virtualWidth;
int screenY = screenRect.top + (y * screenRect.height() + virtualHeight / 2) / virtualHeight;
return Common::Point(CLIP<int>(screenX, screenRect.left, screenRect.right - 1),
CLIP<int>(screenY, screenRect.top, screenRect.bottom - 1));
}
bool AndroidGraphics3dManager::notifyMousePosition(Common::Point &mouse) {
// At entry, mouse is in screen coordinates like the texture draw rectangle
int x = mouse.x, y = mouse.y;
Common::Point vMouse = convertScreenToVirtual(x, y);
// Our internal mouse position is in screen coordinates
// convertScreenToVirtual just clipped coordinates so we are safe
setMousePosition(x, y);
// Now modify mouse to translate to virtual coordinates for the caller
mouse = vMouse;
return true;
}
const OSystem::GraphicsMode *AndroidGraphics3dManager::getSupportedGraphicsModes() const {
static const OSystem::GraphicsMode s_supportedGraphicsModes[] = {
{ "default", "Default", 0 },
{ 0, 0, 0 },
};
return s_supportedGraphicsModes;
}
int AndroidGraphics3dManager::getDefaultGraphicsMode() const {
return 0;
}
bool AndroidGraphics3dManager::setGraphicsMode(int mode, uint flags) {
return true;
}
int AndroidGraphics3dManager::getGraphicsMode() const {
return _graphicsMode;
}
bool AndroidGraphics3dManager::hasFeature(OSystem::Feature f) const {
if (f == OSystem::kFeatureCursorPalette ||
f == OSystem::kFeatureOpenGLForGame ||
f == OSystem::kFeatureAspectRatioCorrection) {
return true;
}
if (f == OSystem::kFeatureOverlaySupportsAlpha) {
return _overlay_texture->getPixelFormat().aBits() > 3;
}
return false;
}
void AndroidGraphics3dManager::setFeatureState(OSystem::Feature f, bool enable) {
switch (f) {
case OSystem::kFeatureFullscreenMode:
_fullscreen = enable;
updateScreenRect();
break;
case OSystem::kFeatureAspectRatioCorrection:
_ar_correction = enable;
updateScreenRect();
break;
default:
break;
}
}
bool AndroidGraphics3dManager::getFeatureState(OSystem::Feature f) const {
switch (f) {
case OSystem::kFeatureCursorPalette:
return true;
case OSystem::kFeatureFullscreenMode:
return _fullscreen;
case OSystem::kFeatureAspectRatioCorrection:
return _ar_correction;
default:
return false;
}
}
void AndroidGraphics3dManager::showOverlay() {
ENTER();
if (_show_overlay) {
return;
}
_old_touch_mode = JNI::getTouchMode();
// in 3D, in overlay
dynamic_cast<OSystem_Android *>(g_system)->applyTouchSettings(true, true);
_show_overlay = true;
_force_redraw = true;
// If there is a game running capture the screen, so that it can be shown "below" the overlay.
if (_overlay_background) {
_overlay_background->release();
if (g_engine) {
GLint savedViewport[4];
GLCALL(glGetIntegerv(GL_VIEWPORT, savedViewport));
if (_frame_buffer) {
_frame_buffer->detach();
}
GLCALL(glViewport(0, 0, JNI::egl_surface_width, JNI::egl_surface_height));
_overlay_background->allocBuffer(_overlay_texture->width(), _overlay_texture->height());
_overlay_background->setDrawRect(0, 0,
JNI::egl_surface_width, JNI::egl_surface_height);
_overlay_background->readPixels();
// Restore game viewport
GLCALL(glViewport(savedViewport[0], savedViewport[1], savedViewport[2], savedViewport[3]));
if (_frame_buffer) {
_frame_buffer->attach();
}
}
}
warpMouse(_overlay_texture->width() / 2, _overlay_texture->height() / 2);
}
void AndroidGraphics3dManager::hideOverlay() {
ENTER();
if (!_show_overlay) {
return;
}
_show_overlay = false;
_overlay_background->release();
// Restore touch mode active before overlay was shown
JNI::setTouchMode(_old_touch_mode);
warpMouse(_game_texture->width() / 2, _game_texture->height() / 2);
// double buffered, flip twice
clearScreen(kClearUpdate, 2);
}
void AndroidGraphics3dManager::clearOverlay() {
ENTER();
GLTHREADCHECK;
_overlay_texture->fillBuffer(0);
}
void AndroidGraphics3dManager::grabOverlay(Graphics::Surface &surface) const {
ENTER("%p", &surface);
GLTHREADCHECK;
const Graphics::Surface *overlaySurface = _overlay_texture->surface_const();
assert(surface.w >= overlaySurface->w);
assert(surface.h >= overlaySurface->h);
assert(surface.format.bytesPerPixel == overlaySurface->format.bytesPerPixel);
const byte *src = (const byte *)overlaySurface->getPixels();
byte *dst = (byte *)surface.getPixels();
Graphics::copyBlit(dst, src, surface.pitch, overlaySurface->pitch,
overlaySurface->w, overlaySurface->h, overlaySurface->format.bytesPerPixel);
}
void AndroidGraphics3dManager::copyRectToOverlay(const void *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;
_overlay_texture->updateBuffer(x, y, w, h, buf, pitch);
}
int16 AndroidGraphics3dManager::getOverlayHeight() const {
return _overlay_texture->height();
}
int16 AndroidGraphics3dManager::getOverlayWidth() const {
return _overlay_texture->width();
}
Graphics::PixelFormat AndroidGraphics3dManager::getOverlayFormat() const {
return _overlay_texture->getPixelFormat();
}
int16 AndroidGraphics3dManager::getHeight() const {
return _game_texture->height();
}
int16 AndroidGraphics3dManager::getWidth() const {
return _game_texture->width();
}
void AndroidGraphics3dManager::setPalette(const byte *colors, uint start, uint num) {
// We should never end up here in 3D
assert(false);
}
void AndroidGraphics3dManager::grabPalette(byte *colors, uint start, uint num) const {
// We should never end up here in 3D
assert(false);
}
Graphics::Surface *AndroidGraphics3dManager::lockScreen() {
// We should never end up here in 3D
assert(false);
return nullptr;
}
void AndroidGraphics3dManager::unlockScreen() {
// We should never end up here in 3D
assert(false);
}
void AndroidGraphics3dManager::fillScreen(uint32 col) {
// We should never end up here in 3D
assert(false);
}
void AndroidGraphics3dManager::copyRectToScreen(const void *buf, int pitch,
int x, int y, int w, int h) {
// We should never end up here in 3D
assert(false);
}
void AndroidGraphics3dManager::initSize(uint width, uint height,
const Graphics::PixelFormat *format) {
// resize game texture
ENTER("%d, %d, %p", width, height, format);
// We do only 3D with this manager and in 3D there is no format
assert(format == nullptr);
bool engineSupportsArbitraryResolutions = !g_engine ||
g_engine->hasFeature(Engine::kSupportsArbitraryResolutions);
if (engineSupportsArbitraryResolutions) {
width = JNI::egl_surface_width;
height = JNI::egl_surface_height;
}
GLTHREADCHECK;
_game_texture->allocBuffer(width, height);
delete _frame_buffer;
_frame_buffer = new OpenGL::FrameBuffer(_game_texture->getTextureName(),
_game_texture->width(), _game_texture->height(),
_game_texture->texWidth(), _game_texture->texHeight());
_frame_buffer->attach();
// 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);
updateScreenRect();
clearScreen(kClear);
_game_texture->setGameTexture();
}
int AndroidGraphics3dManager::getScreenChangeID() const {
return _screenChangeID;
}
bool AndroidGraphics3dManager::showMouse(bool visible) {
ENTER("%d", visible);
_show_mouse = visible;
return true;
}
void AndroidGraphics3dManager::warpMouse(int x, int y) {
// x and y are in virtual coordinates
ENTER("%d, %d", x, y);
// Check active coordinate instead of screen coordinate to avoid warping
// the mouse if it is still within the same virtual pixel
// Don't take the risk of modifying _cursorX and _cursorY
int cx = _cursorX, cy = _cursorY;
const Common::Point currentMouse = convertScreenToVirtual(cx, cy);
if (currentMouse.x == x && currentMouse.y == y) {
// Same virtual coordinates: nothing to do
return;
}
const Common::Point sMouse = convertVirtualToScreen(x, y);
// Our internal mouse position is in screen coordinates
// convertVirtualToScreen just clipped coordinates so we are safe
setMousePosition(sMouse.x, sMouse.y);
// Events pushed to Android system are in screen coordinates too
// They are converted back by notifyMousePosition later
Common::Event e;
e.type = Common::EVENT_MOUSEMOVE;
e.mouse = sMouse;
dynamic_cast<OSystem_Android *>(g_system)->pushEvent(e);
}
void AndroidGraphics3dManager::updateCursorScaling() {
// By default we use the unscaled versions.
_mouse_hotspot_scaled = _mouse_hotspot;
_mouse_width_scaled = _mouse_texture->width();
_mouse_height_scaled = _mouse_texture->height();
// In case scaling is actually enabled we will scale the cursor according
// to the game screen.
uint16 w = _game_texture->width();
uint16 h = _game_texture->height();
if (!_mouse_dont_scale && w && h) {
const frac_t screen_scale_factor_x = intToFrac(_game_texture->getDrawRect().width()) / w;
const frac_t screen_scale_factor_y = intToFrac(_game_texture->getDrawRect().height()) / h;
_mouse_hotspot_scaled = Common::Point(
fracToInt(_mouse_hotspot_scaled.x * screen_scale_factor_x),
fracToInt(_mouse_hotspot_scaled.y * screen_scale_factor_y));
_mouse_width_scaled = fracToInt(_mouse_width_scaled * screen_scale_factor_x);
_mouse_height_scaled = fracToInt(_mouse_height_scaled * screen_scale_factor_y);
}
}
void AndroidGraphics3dManager::setMouseCursor(const void *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);
if (JNI::egl_bits_per_pixel == 16) {
_mouse_texture_rgb = new GLES5551Texture();
} else {
_mouse_texture_rgb = new GLES8888Texture();
}
_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);
_mouse_texture->setKeycolor(keycolor);
}
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();
uint16 bpp = _mouse_texture->getPixelFormat().bytesPerPixel;
byte *tmp = new byte[pitch * h];
// meh, a n-bit cursor without alpha bits... this is so silly
if (!crossBlit(tmp, (const byte *)buf, pitch, w * format->bytesPerPixel, w, h,
_mouse_texture->getPixelFormat(),
*format)) {
LOGE("crossblit failed");
delete[] tmp;
_mouse_texture->allocBuffer(0, 0);
return;
}
if (format->bytesPerPixel == 2) {
const uint16 *s = (const uint16 *)buf;
byte *d = tmp;
for (uint16 y = 0; y < h; ++y, d += pitch / 2 - w)
for (uint16 x = 0; x < w; ++x, d++)
if (*s++ == (keycolor & 0xffff)) {
memset(d, 0, bpp);
}
} else if (format->bytesPerPixel == 4) {
const uint32 *s = (const uint32 *)buf;
byte *d = tmp;
for (uint16 y = 0; y < h; ++y, d += pitch / 2 - w)
for (uint16 x = 0; x < w; ++x, d++)
if (*s++ == (keycolor & 0xffffffff)) {
memset(d, 0, bpp);
}
} else {
error("AndroidGraphics3dManager::setMouseCursor: invalid bytesPerPixel %d", format->bytesPerPixel);
}
_mouse_texture->updateBuffer(0, 0, w, h, tmp, pitch);
delete[] tmp;
}
_mouse_hotspot = Common::Point(hotspotX, hotspotY);
_mouse_dont_scale = dontScale;
updateCursorScaling();
}
void AndroidGraphics3dManager::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;
}
_mouse_texture->setPalette(colors, start, num);
}
bool AndroidGraphics3dManager::lockMouse(bool lock) {
_show_mouse = lock;
return true;
}
#ifdef USE_RGB_COLOR
Graphics::PixelFormat AndroidGraphics3dManager::getScreenFormat() const {
return _game_texture->getPixelFormat();
}
Common::List<Graphics::PixelFormat> AndroidGraphics3dManager::getSupportedFormats() const {
Common::List<Graphics::PixelFormat> res;
// empty list
return res;
}
#endif
void AndroidGraphics3dManager::updateScreenRect() {
Common::Rect rect(0, 0, JNI::egl_surface_width, JNI::egl_surface_height);
_overlay_texture->setDrawRect(rect);
// Clear the overlay background so it is not displayed distorted while resizing
_overlay_background->release();
uint16 w = _game_texture->width();
uint16 h = _game_texture->height();
if (w && h && _ar_correction) {
float dpi[2];
JNI::getDPI(dpi);
float screen_ar;
if (dpi[0] != 0.0 && dpi[1] != 0.0) {
// horizontal orientation
screen_ar = (dpi[1] * JNI::egl_surface_width) /
(dpi[0] * JNI::egl_surface_height);
} else {
screen_ar = float(JNI::egl_surface_width) / float(JNI::egl_surface_height);
}
float game_ar = float(w) / float(h);
if (screen_ar > game_ar) {
rect.setWidth(round(JNI::egl_surface_height * game_ar));
rect.moveTo((JNI::egl_surface_width - rect.width()) / 2, 0);
} else {
rect.setHeight(round(JNI::egl_surface_width / game_ar));
rect.moveTo((JNI::egl_surface_height - rect.height()) / 2, 0);
}
}
_game_texture->setDrawRect(rect);
updateCursorScaling();
}
const GLESBaseTexture *AndroidGraphics3dManager::getActiveTexture() const {
if (_show_overlay) {
return _overlay_texture;
} else {
return _game_texture;
}
}
void AndroidGraphics3dManager::initOverlay() {
// minimum of 320x200
// (surface can get smaller when opening the virtual keyboard on *QVGA*)
int overlay_width = MAX(JNI::egl_surface_width, 320);
int overlay_height = MAX(JNI::egl_surface_height, 200);
LOGI("overlay size is %ux%u", overlay_width, overlay_height);
_overlay_texture->allocBuffer(overlay_width, overlay_height);
_overlay_texture->setDrawRect(0, 0,
JNI::egl_surface_width, JNI::egl_surface_height);
}
void AndroidGraphics3dManager::clearScreen(FixupType type, byte count) {
assert(count > 0);
bool sm = _show_mouse;
_show_mouse = false;
CONTEXT_SAVE_STATE(GL_SCISSOR_TEST);
CONTEXT_SET_DISABLE(GL_SCISSOR_TEST);
for (byte i = 0; i < count; ++i) {
// clear screen
GLCALL(glClearColor(0, 0, 0, 1 << 16));
GLCALL(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT));
switch (type) {
case kClear:
break;
case kClearSwap:
JNI::swapBuffers();
break;
case kClearUpdate:
_force_redraw = true;
updateScreen();
break;
}
}
CONTEXT_RESET_DISABLE(GL_SCISSOR_TEST);
_show_mouse = sm;
_force_redraw = true;
}
float AndroidGraphics3dManager::getHiDPIScreenFactor() const {
// TODO: Use JNI to get DisplayMetrics.density, which according to the documentation
// seems to be what we want.
// "On a medium-density screen, DisplayMetrics.density equals 1.0; on a high-density
// screen it equals 1.5; on an extra-high-density screen, it equals 2.0; and on a
// low-density screen, it equals 0.75. This figure is the factor by which you should
// multiply the dp units in order to get the actual pixel count for the current screen."
return 2.f;
}
AndroidCommonGraphics::State AndroidGraphics3dManager::getState() const {
AndroidCommonGraphics::State state;
state.screenWidth = getWidth();
state.screenHeight = getHeight();
state.aspectRatio = getFeatureState(OSystem::kFeatureAspectRatioCorrection);
state.fullscreen = getFeatureState(OSystem::kFeatureFullscreenMode);
state.cursorPalette = getFeatureState(OSystem::kFeatureCursorPalette);
#ifdef USE_RGB_COLOR
state.pixelFormat = _2d_pixel_format;
#endif
return state;
}
bool AndroidGraphics3dManager::setState(const AndroidCommonGraphics::State &state) {
// In 3d we don't have a pixel format so we ignore it but store it for when leaving 3d mode
initSize(state.screenWidth, state.screenHeight, nullptr);
#ifdef USE_RGB_COLOR
_2d_pixel_format = state.pixelFormat;
#endif
setFeatureState(OSystem::kFeatureAspectRatioCorrection, state.aspectRatio);
setFeatureState(OSystem::kFeatureFullscreenMode, state.fullscreen);
setFeatureState(OSystem::kFeatureCursorPalette, state.cursorPalette);
return true;
}
void AndroidGraphics3dManager::touchControlNotifyChanged() {
// Make sure we redraw the screen
_force_redraw = true;
}
void AndroidGraphics3dManager::touchControlDraw(int16 x, int16 y, int16 w, int16 h, const Common::Rect &clip) {
_touchcontrols_texture->drawTexture(x, y, w, h, clip);
}