diff options
Diffstat (limited to 'backends/platform/android')
22 files changed, 4458 insertions, 2853 deletions
diff --git a/backends/platform/android/android.cpp b/backends/platform/android/android.cpp index c49745f8bd..b1d0727d1f 100644 --- a/backends/platform/android/android.cpp +++ b/backends/platform/android/android.cpp @@ -25,37 +25,43 @@  #if defined(__ANDROID__) -#include "backends/base-backend.h" -#include "base/main.h" -#include "graphics/surface.h" +// 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 "backends/platform/android/android.h" -#include "backends/platform/android/video.h" - -#include <jni.h> - -#include <string.h> -#include <unistd.h> -#include <pthread.h>  #include <sys/time.h> +#include <sys/resource.h> +#include <sys/system_properties.h>  #include <time.h> +#include <unistd.h> -#include "common/archive.h"  #include "common/util.h" +#include "common/textconsole.h"  #include "common/rect.h"  #include "common/queue.h"  #include "common/mutex.h"  #include "common/events.h"  #include "common/config-manager.h" -#include "backends/fs/posix/posix-fs-factory.h"  #include "backends/keymapper/keymapper.h"  #include "backends/saves/default/default-saves.h"  #include "backends/timer/default/default-timer.h" -#include "backends/plugins/posix/posix-provider.h" -#include "audio/mixer_intern.h" -#include "backends/platform/android/asset-archive.h" +#include "backends/platform/android/jni.h" +#include "backends/platform/android/android.h"  const char *android_log_tag = "ScummVM"; @@ -68,7 +74,8 @@ extern "C" {  								 expr, file, line);  	} -	void __assert2(const char *file, int line, const char *func, const char *expr) { +	void __assert2(const char *file, int line, const char *func, +					const char *expr) {  		__android_log_assert(expr, android_log_tag,  								"Assertion failure: '%s' in %s:%d (%s)",  								 expr, file, line, func); @@ -106,235 +113,25 @@ void checkGlError(const char *expr, const char *file, int line) {  }  #endif -static JavaVM *cached_jvm; -static jfieldID FID_Event_type; -static jfieldID FID_Event_synthetic; -static jfieldID FID_Event_kbd_keycode; -static jfieldID FID_Event_kbd_ascii; -static jfieldID FID_Event_kbd_flags; -static jfieldID FID_Event_mouse_x; -static jfieldID FID_Event_mouse_y; -static jfieldID FID_Event_mouse_relative; -static jfieldID FID_ScummVM_nativeScummVM; -static jmethodID MID_Object_wait; - -JNIEnv *JNU_GetEnv() { -	JNIEnv *env = 0; - -	jint res = cached_jvm->GetEnv((void **)&env, JNI_VERSION_1_2); - -	if (res != JNI_OK) { -		LOGE("GetEnv() failed: %d", res); -		abort(); -	} - -	return env; -} - -static void JNU_ThrowByName(JNIEnv *env, const char *name, const char *msg) { -	jclass cls = env->FindClass(name); - -	// if cls is 0, an exception has already been thrown -	if (cls != 0) -		env->ThrowNew(cls, msg); - -	env->DeleteLocalRef(cls); -} - -// floating point. use sparingly -template <class T> -static inline T scalef(T in, float numerator, float denominator) { -	return static_cast<float>(in) * numerator / denominator; -} - -static inline GLfixed xdiv(int numerator, int denominator) { -	assert(numerator < (1 << 16)); -	return (numerator << 16) / denominator; -} - -#ifdef DYNAMIC_MODULES -class AndroidPluginProvider : public POSIXPluginProvider { -protected: -	virtual void addCustomDirectories(Common::FSList &dirs) const; -}; -#endif - -class OSystem_Android : public BaseBackend, public PaletteManager { -private: -	// back pointer to (java) peer instance -	jobject _back_ptr; - -	jmethodID MID_displayMessageOnOSD; -	jmethodID MID_setWindowCaption; -	jmethodID MID_initBackend; -	jmethodID MID_audioSampleRate; -	jmethodID MID_showVirtualKeyboard; -	jmethodID MID_getSysArchives; -	jmethodID MID_getPluginDirectories; -	jmethodID MID_setupScummVMSurface; -	jmethodID MID_destroyScummVMSurface; -	jmethodID MID_swapBuffers; - -	int _screen_changeid; -	int _egl_surface_width; -	int _egl_surface_height; - -	bool _force_redraw; - -	// Game layer -	GLESPaletteTexture *_game_texture; -	int _shake_offset; -	Common::Rect _focus_rect; - -	// Overlay layer -	GLES4444Texture *_overlay_texture; -	bool _show_overlay; - -	// Mouse layer -	GLESPaletteATexture *_mouse_texture; -	Common::Point _mouse_hotspot; -	int _mouse_targetscale; -	bool _show_mouse; -	bool _use_mouse_palette; - -	Common::Queue<Common::Event> _event_queue; -	MutexRef _event_queue_lock; - -	bool _timer_thread_exit; -	pthread_t _timer_thread; -	static void *timerThreadFunc(void *arg); - -	bool _enable_zoning; -	bool _virtkeybd_on; - -	Common::SaveFileManager *_savefile; -	Audio::MixerImpl *_mixer; -	Common::TimerManager *_timer; -	FilesystemFactory *_fsFactory; -	Common::Archive *_asset_archive; -	timeval _startTime; - -	void setupScummVMSurface(); -	void destroyScummVMSurface(); -	void setupKeymapper(); -	void _setCursorPalette(const byte *colors, uint start, uint num); - -public: -	OSystem_Android(jobject am); -	virtual ~OSystem_Android(); -	bool initJavaHooks(JNIEnv *env, jobject self); - -	static OSystem_Android *fromJavaObject(JNIEnv *env, jobject obj); -	virtual void initBackend(); -	void addPluginDirectories(Common::FSList &dirs) const; -	void enableZoning(bool enable) { _enable_zoning = enable; } -	void setSurfaceSize(int width, int height) { -		_egl_surface_width = width; -		_egl_surface_height = height; -	} - -	virtual bool hasFeature(Feature f); -	virtual void setFeatureState(Feature f, bool enable); -	virtual bool getFeatureState(Feature f); -	virtual const GraphicsMode *getSupportedGraphicsModes() const; -	virtual int getDefaultGraphicsMode() const; -	bool setGraphicsMode(const char *name); -	virtual bool setGraphicsMode(int mode); -	virtual int getGraphicsMode() const; -	virtual void initSize(uint width, uint height, -							const Graphics::PixelFormat *format); - -	virtual int getScreenChangeID() const { -		return _screen_changeid; -	} - -	virtual int16 getHeight(); -	virtual int16 getWidth(); - -	virtual PaletteManager *getPaletteManager() { -		return this; -	} - -protected: -	// PaletteManager API -	virtual void setPalette(const byte *colors, uint start, uint num); -	virtual void grabPalette(byte *colors, uint start, uint num); - -public: -	virtual void copyRectToScreen(const byte *buf, int pitch, int x, int y, int w, int h); -	virtual void updateScreen(); -	virtual Graphics::Surface *lockScreen(); -	virtual void unlockScreen(); -	virtual void setShakePos(int shakeOffset); -	virtual void fillScreen(uint32 col); -	virtual void setFocusRectangle(const Common::Rect& rect); -	virtual void clearFocusRectangle(); - -	virtual void showOverlay(); -	virtual void hideOverlay(); -	virtual void clearOverlay(); -	virtual void grabOverlay(OverlayColor *buf, int pitch); -	virtual void copyRectToOverlay(const OverlayColor *buf, int pitch, int x, int y, int w, int h); -	virtual int16 getOverlayHeight(); -	virtual int16 getOverlayWidth(); - -	// RGBA 4444 -	virtual Graphics::PixelFormat getOverlayFormat() const { -		Graphics::PixelFormat format; - -		format.bytesPerPixel = 2; -		format.rLoss = 8 - 4; -		format.gLoss = 8 - 4; -		format.bLoss = 8 - 4; -		format.aLoss = 8 - 4; -		format.rShift = 3 * 4; -		format.gShift = 2 * 4; -		format.bShift = 1 * 4; -		format.aShift = 0 * 4; - -		return format; -	} - -	virtual bool showMouse(bool visible); - -	virtual void warpMouse(int x, int y); -	virtual void setMouseCursor(const byte *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, int cursorTargetScale, const Graphics::PixelFormat *format); -	virtual void setCursorPalette(const byte *colors, uint start, uint num); -	virtual void disableCursorPalette(bool disable); - -	virtual bool pollEvent(Common::Event &event); -	void pushEvent(const Common::Event& event); -	virtual uint32 getMillis(); -	virtual void delayMillis(uint msecs); - -	virtual MutexRef createMutex(void); -	virtual void lockMutex(MutexRef mutex); -	virtual void unlockMutex(MutexRef mutex); -	virtual void deleteMutex(MutexRef mutex); - -	virtual void quit(); - -	virtual void setWindowCaption(const char *caption); -	virtual void displayMessageOnOSD(const char *msg); -	virtual void showVirtualKeyboard(bool enable); - -	virtual Common::SaveFileManager *getSavefileManager(); -	virtual Audio::Mixer *getMixer(); -	virtual void getTimeAndDate(TimeDate &t) const; -	virtual Common::TimerManager *getTimerManager(); -	virtual FilesystemFactory *getFilesystemFactory(); -	virtual void logMessage(LogMessageType::Type type, const char *message); -	virtual void addSysArchivesToSearchSet(Common::SearchSet &s, int priority = 0); -}; - -OSystem_Android::OSystem_Android(jobject am) : -	_back_ptr(0), +OSystem_Android::OSystem_Android(int audio_sample_rate, int audio_buffer_size) : +	_audio_sample_rate(audio_sample_rate), +	_audio_buffer_size(audio_buffer_size),  	_screen_changeid(0), +	_egl_surface_width(0), +	_egl_surface_height(0), +	_htc_fail(false),  	_force_redraw(false),  	_game_texture(0),  	_overlay_texture(0),  	_mouse_texture(0), +	_mouse_texture_palette(0), +	_mouse_texture_rgb(0), +	_mouse_hotspot(), +	_mouse_keycolor(0),  	_use_mouse_palette(false), +	_graphicsMode(0), +	_fullscreen(true), +	_ar_correction(true),  	_show_mouse(false),  	_show_overlay(false),  	_enable_zoning(false), @@ -342,876 +139,306 @@ OSystem_Android::OSystem_Android(jobject am) :  	_mixer(0),  	_timer(0),  	_fsFactory(new POSIXFilesystemFactory()), -	_asset_archive(new AndroidAssetArchive(am)),  	_shake_offset(0), -	_event_queue_lock(createMutex()) { +	_event_queue_lock(createMutex()), +	_touch_pt_down(), +	_touch_pt_scroll(), +	_touch_pt_dt(), +	_eventScaleX(100), +	_eventScaleY(100), +	// TODO put these values in some option dlg? +	_touchpad_mode(true), +	_touchpad_scale(66), +	_dpad_scale(4), +	_fingersDown(0), +	_trackball_scale(2) { +	Common::String mf = getSystemProperty("ro.product.manufacturer"); + +	LOGI("Running on: [%s] [%s] [%s] [%s] [%s] SDK:%s ABI:%s", +			mf.c_str(), +			getSystemProperty("ro.product.model").c_str(), +			getSystemProperty("ro.product.brand").c_str(), +			getSystemProperty("ro.build.fingerprint").c_str(), +			getSystemProperty("ro.build.display.id").c_str(), +			getSystemProperty("ro.build.version.sdk").c_str(), +			getSystemProperty("ro.product.cpu.abi").c_str()); + +	mf.toLowercase(); +	_htc_fail = mf.contains("htc"); + +	if (_htc_fail) +		LOGI("Enabling HTC workaround");  }  OSystem_Android::~OSystem_Android() {  	ENTER(); -	delete _game_texture; -	delete _overlay_texture; -	delete _mouse_texture; - -	destroyScummVMSurface(); - -	JNIEnv *env = JNU_GetEnv(); -	//env->DeleteWeakGlobalRef(_back_ptr); -	env->DeleteGlobalRef(_back_ptr); -  	delete _savefile; -	delete _mixer;  	delete _timer; +	delete _mixer;  	delete _fsFactory; -	delete _asset_archive;  	deleteMutex(_event_queue_lock);  } -OSystem_Android *OSystem_Android::fromJavaObject(JNIEnv *env, jobject obj) { -	jlong peer = env->GetLongField(obj, FID_ScummVM_nativeScummVM); -	return (OSystem_Android *)peer; -} - -bool OSystem_Android::initJavaHooks(JNIEnv *env, jobject self) { -	// weak global ref to allow class to be unloaded -	// ... except dalvik doesn't implement NewWeakGlobalRef (yet) -	//_back_ptr = env->NewWeakGlobalRef(self); -	_back_ptr = env->NewGlobalRef(self); - -	jclass cls = env->GetObjectClass(_back_ptr); - -#define FIND_METHOD(name, signature) do {						\ -		MID_ ## name = env->GetMethodID(cls, #name, signature); \ -		if (MID_ ## name == 0)									\ -			return false;										\ -	} while (0) - -	FIND_METHOD(setWindowCaption, "(Ljava/lang/String;)V"); -	FIND_METHOD(displayMessageOnOSD, "(Ljava/lang/String;)V"); -	FIND_METHOD(initBackend, "()V"); -	FIND_METHOD(audioSampleRate, "()I"); -	FIND_METHOD(showVirtualKeyboard, "(Z)V"); -	FIND_METHOD(getSysArchives, "()[Ljava/lang/String;"); -	FIND_METHOD(getPluginDirectories, "()[Ljava/lang/String;"); -	FIND_METHOD(setupScummVMSurface, "()V"); -	FIND_METHOD(destroyScummVMSurface, "()V"); -	FIND_METHOD(swapBuffers, "()Z"); - -#undef FIND_METHOD - -	return true; -} - -static void ScummVM_create(JNIEnv *env, jobject self, jobject am) { -	OSystem_Android *cpp_obj = new OSystem_Android(am); - -	// Exception already thrown by initJavaHooks? -	if (!cpp_obj->initJavaHooks(env, self)) -		return; - -	env->SetLongField(self, FID_ScummVM_nativeScummVM, (jlong)cpp_obj); - -#ifdef DYNAMIC_MODULES -	PluginManager::instance().addPluginProvider(new AndroidPluginProvider()); -#endif -} - -static void ScummVM_nativeDestroy(JNIEnv *env, jobject self) { -	OSystem_Android *cpp_obj = OSystem_Android::fromJavaObject(env, self); -	delete cpp_obj; -} - -static void ScummVM_audioMixCallback(JNIEnv *env, jobject self, -										jbyteArray jbuf) { -	OSystem_Android *cpp_obj = OSystem_Android::fromJavaObject(env, self); -	jsize len = env->GetArrayLength(jbuf); -	jbyte *buf = env->GetByteArrayElements(jbuf, 0); - -	if (buf == 0) { -		warning("Unable to get Java audio byte array. Skipping"); -		return; -	} - -	Audio::MixerImpl *mixer = -		static_cast<Audio::MixerImpl *>(cpp_obj->getMixer()); -	assert(mixer); -	mixer->mixCallback(reinterpret_cast<byte *>(buf), len); - -	env->ReleaseByteArrayElements(jbuf, buf, 0); -} - -static void ScummVM_setConfManInt(JNIEnv *env, jclass cls, -									jstring key_obj, jint value) { -	ENTER("%p, %d", key_obj, (int)value); - -	const char *key = env->GetStringUTFChars(key_obj, 0); - -	if (key == 0) -		return; - -	ConfMan.setInt(key, value); - -	env->ReleaseStringUTFChars(key_obj, key); -} - -static void ScummVM_setConfManString(JNIEnv *env, jclass cls, jstring key_obj, -										jstring value_obj) { -	ENTER("%p, %p", key_obj, value_obj); - -	const char *key = env->GetStringUTFChars(key_obj, 0); - -	if (key == 0) -		return; - -	const char *value = env->GetStringUTFChars(value_obj, 0); - -	if (value == 0) { -		env->ReleaseStringUTFChars(key_obj, key); -		return; -	} - -	ConfMan.set(key, value); - -	env->ReleaseStringUTFChars(value_obj, value); -	env->ReleaseStringUTFChars(key_obj, key); -} -  void *OSystem_Android::timerThreadFunc(void *arg) {  	OSystem_Android *system = (OSystem_Android *)arg;  	DefaultTimerManager *timer = (DefaultTimerManager *)(system->_timer); -	JNIEnv *env = 0; -	jint res = cached_jvm->AttachCurrentThread(&env, 0); +	// renice this thread to boost the audio thread +	if (setpriority(PRIO_PROCESS, 0, 19) < 0) +		LOGW("couldn't renice the timer thread"); -	if (res != JNI_OK) { -		LOGE("AttachCurrentThread() failed: %d", res); -		abort(); -	} +	JNI::attachThread();  	struct timespec tv;  	tv.tv_sec = 0; -	tv.tv_nsec = 100 * 1000 * 1000;	// 100ms +	tv.tv_nsec = 10 * 1000 * 1000; // 10ms  	while (!system->_timer_thread_exit) { +		if (JNI::pause) { +			LOGD("timer thread going to sleep"); +			sem_wait(&JNI::pause_sem); +			LOGD("timer thread woke up"); +		} +  		timer->handler();  		nanosleep(&tv, 0);  	} -	res = cached_jvm->DetachCurrentThread(); - -	if (res != JNI_OK) { -		LOGE("DetachCurrentThread() failed: %d", res); -		abort(); -	} +	JNI::detachThread();  	return 0;  } -void OSystem_Android::initBackend() { -	ENTER(); - -	JNIEnv *env = JNU_GetEnv(); - -	ConfMan.setInt("autosave_period", 0); -	ConfMan.setInt("FM_medium_quality", true); - -	// must happen before creating TimerManager to avoid race in -	// creating EventManager -	setupKeymapper(); - -	// BUG: "transient" ConfMan settings get nuked by the options -	// screen. Passing the savepath in this way makes it stick -	// (via ConfMan.registerDefault) -	_savefile = new DefaultSaveFileManager(ConfMan.get("savepath")); -	_timer = new DefaultTimerManager(); - -	gettimeofday(&_startTime, 0); - -	jint sample_rate = env->CallIntMethod(_back_ptr, MID_audioSampleRate); -	if (env->ExceptionCheck()) { -		warning("Error finding audio sample rate - assuming 11025HZ"); - -		env->ExceptionDescribe(); -		env->ExceptionClear(); - -		sample_rate = 11025; -	} - -	_mixer = new Audio::MixerImpl(this, sample_rate); -	_mixer->setReady(true); - -	env->CallVoidMethod(_back_ptr, MID_initBackend); +void *OSystem_Android::audioThreadFunc(void *arg) { +	JNI::attachThread(); -	if (env->ExceptionCheck()) { -		error("Error in Java initBackend"); - -		env->ExceptionDescribe(); -		env->ExceptionClear(); -	} - -	_timer_thread_exit = false; -	pthread_create(&_timer_thread, 0, timerThreadFunc, this); +	OSystem_Android *system = (OSystem_Android *)arg; +	Audio::MixerImpl *mixer = system->_mixer; -	OSystem::initBackend(); +	uint buf_size = system->_audio_buffer_size; -	setupScummVMSurface(); -} +	JNIEnv *env = JNI::getEnv(); -void OSystem_Android::addPluginDirectories(Common::FSList &dirs) const { -	ENTER(); +	jbyteArray bufa = env->NewByteArray(buf_size); -	JNIEnv *env = JNU_GetEnv(); +	bool paused = true; -	jobjectArray array = -		(jobjectArray)env->CallObjectMethod(_back_ptr, MID_getPluginDirectories); -	if (env->ExceptionCheck()) { -		warning("Error finding plugin directories"); +	byte *buf; +	int offset, left, written; +	int samples, i; -		env->ExceptionDescribe(); -		env->ExceptionClear(); +	struct timespec tv_delay; +	tv_delay.tv_sec = 0; +	tv_delay.tv_nsec = 20 * 1000 * 1000; -		return; -	} +	uint msecs_full = buf_size * 1000 / (mixer->getOutputRate() * 2 * 2); -	jsize size = env->GetArrayLength(array); -	for (jsize i = 0; i < size; ++i) { -		jstring path_obj = (jstring)env->GetObjectArrayElement(array, i); +	struct timespec tv_full; +	tv_full.tv_sec = 0; +	tv_full.tv_nsec = msecs_full * 1000 * 1000; -		if (path_obj == 0) -			continue; +	bool silence; +	uint silence_count = 33; -		const char *path = env->GetStringUTFChars(path_obj, 0); -		if (path == 0) { -			warning("Error getting string characters from plugin directory"); +	while (!system->_audio_thread_exit) { +		if (JNI::pause) { +			JNI::setAudioStop(); -			env->ExceptionClear(); -			env->DeleteLocalRef(path_obj); +			paused = true; +			silence_count = 33; -			continue; +			LOGD("audio thread going to sleep"); +			sem_wait(&JNI::pause_sem); +			LOGD("audio thread woke up");  		} -		dirs.push_back(Common::FSNode(path)); - -		env->ReleaseStringUTFChars(path_obj, path); -		env->DeleteLocalRef(path_obj); -	} -} +		buf = (byte *)env->GetPrimitiveArrayCritical(bufa, 0); +		assert(buf); -bool OSystem_Android::hasFeature(Feature f) { -	return (f == kFeatureCursorHasPalette || -			f == kFeatureVirtualKeyboard || -			f == kFeatureOverlaySupportsAlpha); -} +		samples = mixer->mixCallback(buf, buf_size); -void OSystem_Android::setFeatureState(Feature f, bool enable) { -	ENTER("%d, %d", f, enable); +		silence = samples < 1; -	switch (f) { -	case kFeatureVirtualKeyboard: -		_virtkeybd_on = enable; -		showVirtualKeyboard(enable); -		break; -	default: -		break; -	} -} +		// looks stupid, and it is, but currently there's no way to detect +		// silence-only buffers from the mixer +		if (!silence) { +			silence = true; -bool OSystem_Android::getFeatureState(Feature f) { -	switch (f) { -	case kFeatureVirtualKeyboard: -		return _virtkeybd_on; -	default: -		return false; -	} -} - -const OSystem::GraphicsMode *OSystem_Android::getSupportedGraphicsModes() const { -	static const OSystem::GraphicsMode s_supportedGraphicsModes[] = { -		{ "default", "Default", 1 }, -		{ 0, 0, 0 }, -	}; - -	return s_supportedGraphicsModes; -} - - -int OSystem_Android::getDefaultGraphicsMode() const { -	return 1; -} - -bool OSystem_Android::setGraphicsMode(const char *mode) { -	ENTER("%s", mode); -	return true; -} - -bool OSystem_Android::setGraphicsMode(int mode) { -	ENTER("%d", mode); -	return true; -} - -int OSystem_Android::getGraphicsMode() const { -	return 1; -} - -void OSystem_Android::setupScummVMSurface() { -	ENTER(); - -	JNIEnv *env = JNU_GetEnv(); -	env->CallVoidMethod(_back_ptr, MID_setupScummVMSurface); - -	if (env->ExceptionCheck()) -		return; - -	// EGL set up with a new surface.  Initialise OpenGLES context. -	GLESTexture::initGLExtensions(); - -	// 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)); - -	if (!_game_texture) -		_game_texture = new GLESPaletteTexture(); -	else -		_game_texture->reinitGL(); - -	if (!_overlay_texture) -		_overlay_texture = new GLES4444Texture(); -	else -		_overlay_texture->reinitGL(); - -	if (!_mouse_texture) -		_mouse_texture = new GLESPaletteATexture(); -	else -		_mouse_texture->reinitGL(); - -	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::destroyScummVMSurface() { -	JNIEnv *env = JNU_GetEnv(); -	env->CallVoidMethod(_back_ptr, MID_destroyScummVMSurface); -	// Can't use OpenGLES functions after this -} - -void OSystem_Android::initSize(uint width, uint height, -								const Graphics::PixelFormat *format) { -	ENTER("%d, %d, %p", width, height, format); - -	_game_texture->allocBuffer(width, height); - -	// Cap at 320x200 or the ScummVM themes abort :/ -	GLuint overlay_width = MIN(_egl_surface_width, 320); -	GLuint overlay_height = MIN(_egl_surface_height, 200); -	_overlay_texture->allocBuffer(overlay_width, overlay_height); - -	// 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->allocBuffer(20, 20); -} - -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); - -	if (!_use_mouse_palette) -		_setCursorPalette(colors, start, num); - -	memcpy(_game_texture->palette() + start * 3, colors, num * 3); -} - -void OSystem_Android::grabPalette(byte *colors, uint start, uint num) { -	ENTER("%p, %u, %u", colors, start, num); -	memcpy(colors, _game_texture->palette_const() + start * 3, num * 3); -} - -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); - -	_game_texture->updateBuffer(x, y, w, h, buf, pitch); -} - -void OSystem_Android::updateScreen() { -	//ENTER(); - -	if (!_force_redraw && -			!_game_texture->dirty() && -			!_overlay_texture->dirty() && -			!_mouse_texture->dirty()) -		return; - -	_force_redraw = false; - -	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. -		GLCALL(glClearColorx(0, 0, 0, 1 << 16)); -		GLCALL(glClear(GL_COLOR_BUFFER_BIT)); - -		// Move everything up by _shake_offset (game) pixels -		GLCALL(glTranslatex(0, -_shake_offset << 16, 0)); -	} - -	if (_focus_rect.isEmpty()) { -		_game_texture->drawTexture(0, 0, -									_egl_surface_width, _egl_surface_height); -	} 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->drawTexture(0, 0, -									_egl_surface_width, _egl_surface_height); -		GLCALL(glPopMatrix()); -	} - -	int cs = _mouse_targetscale; - -	if (_show_overlay) { -		// ugly, but the modern theme sets a wacko factor, only god knows why -		cs = 1; - -		GLCALL(_overlay_texture->drawTexture(0, 0, -												_egl_surface_width, -												_egl_surface_height)); -	} - -	if (_show_mouse) { -		GLCALL(glPushMatrix()); - -		// Scale up ScummVM -> OpenGL (pixel) coordinates -		int texwidth, texheight; - -		if (_show_overlay) { -			texwidth = getOverlayWidth(); -			texheight = getOverlayHeight(); -		} else { -			texwidth = getWidth(); -			texheight = getHeight(); +			for (i = 0; i < samples; i += 2) +				// SID streams constant crap +				if (READ_UINT16(buf + i) > 32) { +					silence = false; +					break; +				}  		} -		GLCALL(glScalex(xdiv(_egl_surface_width, texwidth), -						xdiv(_egl_surface_height, texheight), -						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: -		const Common::Point& mouse = getEventManager()->getMousePos(); -		GLCALL(glTranslatex((mouse.x << 16) | 1 << 15, -							(mouse.y << 16) | 1 << 15, 0)); - -		GLCALL(glScalex(cs << 16, cs << 16, 1 << 16)); +		env->ReleasePrimitiveArrayCritical(bufa, buf, 0); -		_mouse_texture->drawTexture(); +		if (silence) { +			if (!paused) +				silence_count++; -		GLCALL(glPopMatrix()); -	} +			// only pause after a while to prevent toggle mania +			if (silence_count > 32) { +				if (!paused) { +					LOGD("AudioTrack pause"); -	GLCALL(glPopMatrix()); +					JNI::setAudioPause(); +					paused = true; +				} -	JNIEnv *env = JNU_GetEnv(); -	if (!env->CallBooleanMethod(_back_ptr, MID_swapBuffers)) { -		// Context lost -> need to reinit GL -		destroyScummVMSurface(); -		setupScummVMSurface(); -	} -} +				nanosleep(&tv_full, 0); -Graphics::Surface *OSystem_Android::lockScreen() { -	ENTER(); +				continue; +			} +		} -	Graphics::Surface *surface = _game_texture->surface(); -	assert(surface->pixels); +		if (paused) { +			LOGD("AudioTrack play"); -	return surface; -} +			JNI::setAudioPlay(); +			paused = false; -void OSystem_Android::unlockScreen() { -	ENTER(); +			silence_count = 0; +		} -	assert(_game_texture->dirty()); -} +		offset = 0; +		left = buf_size; +		written = 0; -void OSystem_Android::setShakePos(int shake_offset) { -	ENTER("%d", shake_offset); +		while (left > 0) { +			written = JNI::writeAudio(env, bufa, offset, left); -	if (_shake_offset != shake_offset) { -		_shake_offset = shake_offset; -		_force_redraw = true; -	} -} +			if (written < 0) { +				LOGE("AudioTrack error: %d", written); +				break; +			} -void OSystem_Android::fillScreen(uint32 col) { -	ENTER("%u", col); +			// buffer full +			if (written < left) +				nanosleep(&tv_delay, 0); -	assert(col < 256); -	_game_texture->fillBuffer(col); -} +			offset += written; +			left -= written; +		} -void OSystem_Android::setFocusRectangle(const Common::Rect& rect) { -	ENTER("%d, %d, %d, %d", rect.left, rect.top, rect.right, rect.bottom); +		if (written < 0) +			break; -	if (_enable_zoning) { -		_focus_rect = rect; -		_force_redraw = true; +		// prepare the next buffer, and run into the blocking AudioTrack.write  	} -} -void OSystem_Android::clearFocusRectangle() { -	ENTER(); +	JNI::setAudioStop(); -	if (_enable_zoning) { -		_focus_rect = Common::Rect(); -		_force_redraw = true; -	} -} +	env->DeleteLocalRef(bufa); -void OSystem_Android::showOverlay() { -	ENTER(); +	JNI::detachThread(); -	_show_overlay = true; -	_force_redraw = true; +	return 0;  } -void OSystem_Android::hideOverlay() { +void OSystem_Android::initBackend() {  	ENTER(); -	_show_overlay = false; -	_force_redraw = true; -} - -void OSystem_Android::clearOverlay() { -	ENTER(); +	_main_thread = pthread_self(); -	_overlay_texture->fillBuffer(0); +	ConfMan.registerDefault("fullscreen", true); +	ConfMan.registerDefault("aspect_ratio", true); -	// Shouldn't need this, but works around a 'blank screen' bug on Nexus1 -	updateScreen(); -} - -void OSystem_Android::grabOverlay(OverlayColor *buf, int pitch) { -	ENTER("%p, %d", buf, pitch); - -	// We support overlay alpha blending, so the pixel data here -	// shouldn't actually be used.	Let's fill it with zeros, I'm sure -	// it will be fine... -	const Graphics::Surface *surface = _overlay_texture->surface_const(); -	assert(surface->bytesPerPixel == sizeof(buf[0])); - -	int h = surface->h; - -	do { -		memset(buf, 0, surface->w * sizeof(buf[0])); - -		// 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); - -	const Graphics::Surface *surface = _overlay_texture->surface_const(); -	assert(surface->bytesPerPixel == sizeof(buf[0])); - -	// This 'pitch' is pixels not bytes -	_overlay_texture->updateBuffer(x, y, w, h, buf, pitch * sizeof(buf[0])); - -	// Shouldn't need this, but works around a 'blank screen' bug on Nexus1? -	updateScreen(); -} - -int16 OSystem_Android::getOverlayHeight() { -	return _overlay_texture->height(); -} - -int16 OSystem_Android::getOverlayWidth() { -	return _overlay_texture->width(); -} - -bool OSystem_Android::showMouse(bool visible) { -	ENTER("%d", visible); - -	_show_mouse = visible; - -	return true; -} - -void OSystem_Android::warpMouse(int x, int y) { -	ENTER("%d, %d", x, y); - -	// We use only the eventmanager's idea of the current mouse -	// position, so there is nothing extra to do here. -} - -void OSystem_Android::setMouseCursor(const byte *buf, uint w, uint h, -										int hotspotX, int hotspotY, -										uint32 keycolor, int cursorTargetScale, -										const Graphics::PixelFormat *format) { -	ENTER("%p, %u, %u, %d, %d, %u, %d, %p", buf, w, h, hotspotX, hotspotY, -			keycolor, cursorTargetScale, format); - -	assert(keycolor < 256); +	ConfMan.setInt("autosave_period", 0); +	ConfMan.setBool("FM_high_quality", false); +	ConfMan.setBool("FM_medium_quality", true); -	_mouse_texture->allocBuffer(w, h); +	// TODO hackity hack +	if (ConfMan.hasKey("multi_midi")) +		_touchpad_mode = !ConfMan.getBool("multi_midi"); -	// Update palette alpha based on keycolor -	byte *palette = _mouse_texture->palette(); -	int i = 256; +	// must happen before creating TimerManager to avoid race in +	// creating EventManager +	setupKeymapper(); -	do { -		palette[3] = 0xff; -		palette += 4; -	} while (--i); +	// BUG: "transient" ConfMan settings get nuked by the options +	// screen. Passing the savepath in this way makes it stick +	// (via ConfMan.registerDefault) +	_savefile = new DefaultSaveFileManager(ConfMan.get("savepath")); +	_timer = new DefaultTimerManager(); -	palette = _mouse_texture->palette(); -	palette[keycolor * 4 + 3] = 0x00; +	gettimeofday(&_startTime, 0); -	_mouse_texture->updateBuffer(0, 0, w, h, buf, w); +	_mixer = new Audio::MixerImpl(this, _audio_sample_rate); +	_mixer->setReady(true); -	_mouse_hotspot = Common::Point(hotspotX, hotspotY); -	_mouse_targetscale = cursorTargetScale; -} +	_timer_thread_exit = false; +	pthread_create(&_timer_thread, 0, timerThreadFunc, this); -void OSystem_Android::_setCursorPalette(const byte *colors, -										uint start, uint num) { -	byte *palette = _mouse_texture->palette() + start * 4; +	_audio_thread_exit = false; +	pthread_create(&_audio_thread, 0, audioThreadFunc, this); -	do { -		for (int i = 0; i < 3; ++i) -			palette[i] = colors[i]; +	initSurface(); +	initViewport(); -		// Leave alpha untouched to preserve keycolor +	_game_texture = new GLESFakePalette565Texture(); +	_overlay_texture = new GLES4444Texture(); +	_mouse_texture_palette = new GLESFakePalette5551Texture(); +	_mouse_texture = _mouse_texture_palette; -		palette += 4; -		colors += 3; -	} while (--num); -} +	initOverlay(); -void OSystem_Android::setCursorPalette(const byte *colors, -										uint start, uint num) { -	ENTER("%p, %u, %u", colors, start, num); +	// renice this thread to boost the audio thread +	if (setpriority(PRIO_PROCESS, 0, 19) < 0) +		warning("couldn't renice the main thread"); -	_setCursorPalette(colors, start, num); -	_use_mouse_palette = true; +	JNI::setReadyForEvents(true);  } -void OSystem_Android::disableCursorPalette(bool disable) { -	ENTER("%d", disable); +void OSystem_Android::addPluginDirectories(Common::FSList &dirs) const { +	ENTER(); -	_use_mouse_palette = !disable; +	JNI::getPluginDirectories(dirs);  } -void OSystem_Android::setupKeymapper() { -#ifdef ENABLE_KEYMAPPER -	using namespace Common; - -	Keymapper *mapper = getEventManager()->getKeymapper(); - -	HardwareKeySet *keySet = new HardwareKeySet(); - -	keySet->addHardwareKey( -		new HardwareKey("n", KeyState(KEYCODE_n), "n (vk)", -				kTriggerLeftKeyType, -				kVirtualKeyboardActionType)); - -	mapper->registerHardwareKeySet(keySet); - -	Keymap *globalMap = new Keymap("global"); -	Action *act; - -	act = new Action(globalMap, "VIRT", "Display keyboard", -						kVirtualKeyboardActionType); -	act->addKeyEvent(KeyState(KEYCODE_F7, ASCII_F7, 0)); - -	mapper->addGlobalKeymap(globalMap); - -	mapper->pushKeymap("global"); -#endif +bool OSystem_Android::hasFeature(Feature f) { +	return (f == kFeatureFullscreenMode || +			f == kFeatureAspectRatioCorrection || +			f == kFeatureCursorHasPalette || +			f == kFeatureVirtualKeyboard || +			f == kFeatureOverlaySupportsAlpha);  } -bool OSystem_Android::pollEvent(Common::Event &event) { -	//ENTER(); - -	lockMutex(_event_queue_lock); - -	if (_event_queue.empty()) { -		unlockMutex(_event_queue_lock); -		return false; -	} +void OSystem_Android::setFeatureState(Feature f, bool enable) { +	ENTER("%d, %d", f, enable); -	event = _event_queue.pop(); -	unlockMutex(_event_queue_lock); - -	switch (event.type) { -	case Common::EVENT_MOUSEMOVE: -		// TODO: only dirty/redraw move bounds -		_force_redraw = true; -		// fallthrough -	case Common::EVENT_LBUTTONDOWN: -	case Common::EVENT_LBUTTONUP: -	case Common::EVENT_RBUTTONDOWN: -	case Common::EVENT_RBUTTONUP: -	case Common::EVENT_WHEELUP: -	case Common::EVENT_WHEELDOWN: -	case Common::EVENT_MBUTTONDOWN: -	case Common::EVENT_MBUTTONUP: { -		// relative mouse hack -		if (event.kbd.flags == 1) { -			// Relative (trackball) mouse hack. -			const Common::Point& mouse_pos = -				getEventManager()->getMousePos(); -			event.mouse.x += mouse_pos.x; -			event.mouse.y += mouse_pos.y; -			event.mouse.x = CLIP(event.mouse.x, (int16)0, _show_overlay ? -									getOverlayWidth() : getWidth()); -			event.mouse.y = CLIP(event.mouse.y, (int16)0, _show_overlay ? -									getOverlayHeight() : getHeight()); -		} else { -			// Touchscreen events need to be converted -			// from device to game coords first. -			const GLESTexture *tex = _show_overlay -				? static_cast<GLESTexture *>(_overlay_texture) -				: static_cast<GLESTexture *>(_game_texture); -			event.mouse.x = scalef(event.mouse.x, tex->width(), -									_egl_surface_width); -			event.mouse.y = scalef(event.mouse.y, tex->height(), -									_egl_surface_height); -			event.mouse.x -= _shake_offset; -		} +	switch (f) { +	case kFeatureFullscreenMode: +		_fullscreen = enable; +		updateScreenRect();  		break; -	} -	case Common::EVENT_SCREEN_CHANGED: -		debug("EVENT_SCREEN_CHANGED"); -		_screen_changeid++; -		destroyScummVMSurface(); -		setupScummVMSurface(); +	case kFeatureAspectRatioCorrection: +		_ar_correction = enable; +		updateScreenRect(); +		break; +	case kFeatureVirtualKeyboard: +		_virtkeybd_on = enable; +		showVirtualKeyboard(enable);  		break;  	default:  		break;  	} - -	return true;  } -void OSystem_Android::pushEvent(const Common::Event& event) { -	lockMutex(_event_queue_lock); - -	// Try to combine multiple queued mouse move events -	if (event.type == Common::EVENT_MOUSEMOVE && -			!_event_queue.empty() && -			_event_queue.back().type == Common::EVENT_MOUSEMOVE) { -		Common::Event tail = _event_queue.back(); -		if (event.kbd.flags) { -			// relative movement hack -			tail.mouse.x += event.mouse.x; -			tail.mouse.y += event.mouse.y; -		} else { -			// absolute position, clear relative flag -			tail.kbd.flags = 0; -			tail.mouse.x = event.mouse.x; -			tail.mouse.y = event.mouse.y; -		} -	} else { -	  _event_queue.push(event); -	} - -	unlockMutex(_event_queue_lock); -} - -static void ScummVM_pushEvent(JNIEnv *env, jobject self, jobject java_event) { -	OSystem_Android *cpp_obj = OSystem_Android::fromJavaObject(env, self); - -	Common::Event event; -	event.type = (Common::EventType)env->GetIntField(java_event, -														FID_Event_type); - -	event.synthetic = -		env->GetBooleanField(java_event, FID_Event_synthetic); - -	switch (event.type) { -	case Common::EVENT_KEYDOWN: -	case Common::EVENT_KEYUP: -		event.kbd.keycode = (Common::KeyCode)env->GetIntField( -			java_event, FID_Event_kbd_keycode); -		event.kbd.ascii = static_cast<int>(env->GetIntField( -			java_event, FID_Event_kbd_ascii)); -		event.kbd.flags = static_cast<int>(env->GetIntField( -			java_event, FID_Event_kbd_flags)); -		break; -	case Common::EVENT_MOUSEMOVE: -	case Common::EVENT_LBUTTONDOWN: -	case Common::EVENT_LBUTTONUP: -	case Common::EVENT_RBUTTONDOWN: -	case Common::EVENT_RBUTTONUP: -	case Common::EVENT_WHEELUP: -	case Common::EVENT_WHEELDOWN: -	case Common::EVENT_MBUTTONDOWN: -	case Common::EVENT_MBUTTONUP: -		event.mouse.x = -			env->GetIntField(java_event, FID_Event_mouse_x); -		event.mouse.y = -			env->GetIntField(java_event, FID_Event_mouse_y); -		// This is a terrible hack.	 We stash "relativeness" -		// in the kbd.flags field until pollEvent() can work -		// it out. -		event.kbd.flags = env->GetBooleanField( -			java_event, FID_Event_mouse_relative) ? 1 : 0; -		break; +bool OSystem_Android::getFeatureState(Feature f) { +	switch (f) { +	case kFeatureFullscreenMode: +		return _fullscreen; +	case kFeatureAspectRatioCorrection: +		return _ar_correction; +	case kFeatureVirtualKeyboard: +		return _virtkeybd_on;  	default: -		break; +		return false;  	} - -	cpp_obj->pushEvent(event);  }  uint32 OSystem_Android::getMillis() { @@ -1219,7 +446,7 @@ uint32 OSystem_Android::getMillis() {  	gettimeofday(&curTime, 0); -	return (uint32)(((curTime.tv_sec - _startTime.tv_sec) * 1000) + \ +	return (uint32)(((curTime.tv_sec - _startTime.tv_sec) * 1000) +  			((curTime.tv_usec - _startTime.tv_usec) / 1000));  } @@ -1229,6 +456,7 @@ void OSystem_Android::delayMillis(uint msecs) {  OSystem::MutexRef OSystem_Android::createMutex() {  	pthread_mutexattr_t attr; +  	pthread_mutexattr_init(&attr);  	pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); @@ -1267,58 +495,38 @@ void OSystem_Android::deleteMutex(MutexRef mutex) {  void OSystem_Android::quit() {  	ENTER(); +	JNI::setReadyForEvents(false); + +	_audio_thread_exit = true; +	pthread_join(_audio_thread, 0); +  	_timer_thread_exit = true;  	pthread_join(_timer_thread, 0); + +	delete _game_texture; +	delete _overlay_texture; +	delete _mouse_texture_palette; +	delete _mouse_texture_rgb; + +	deinitSurface();  }  void OSystem_Android::setWindowCaption(const char *caption) {  	ENTER("%s", caption); -	JNIEnv *env = JNU_GetEnv(); -	jstring java_caption = env->NewStringUTF(caption); -	env->CallVoidMethod(_back_ptr, MID_setWindowCaption, java_caption); - -	if (env->ExceptionCheck()) { -		warning("Failed to set window caption"); - -		env->ExceptionDescribe(); -		env->ExceptionClear(); -	} - -	env->DeleteLocalRef(java_caption); +	JNI::setWindowCaption(caption);  }  void OSystem_Android::displayMessageOnOSD(const char *msg) {  	ENTER("%s", msg); -	JNIEnv *env = JNU_GetEnv(); -	jstring java_msg = env->NewStringUTF(msg); - -	env->CallVoidMethod(_back_ptr, MID_displayMessageOnOSD, java_msg); - -	if (env->ExceptionCheck()) { -		warning("Failed to display OSD message"); - -		env->ExceptionDescribe(); -		env->ExceptionClear(); -	} - -	env->DeleteLocalRef(java_msg); +	JNI::displayMessageOnOSD(msg);  }  void OSystem_Android::showVirtualKeyboard(bool enable) {  	ENTER("%d", enable); -	JNIEnv *env = JNU_GetEnv(); - -	env->CallVoidMethod(_back_ptr, MID_showVirtualKeyboard, enable); - -	if (env->ExceptionCheck()) { -		error("Error trying to show virtual keyboard"); - -		env->ExceptionDescribe(); -		env->ExceptionClear(); -	} +	JNI::showVirtualKeyboard(enable);  }  Common::SaveFileManager *OSystem_Android::getSavefileManager() { @@ -1355,37 +563,13 @@ FilesystemFactory *OSystem_Android::getFilesystemFactory() {  void OSystem_Android::addSysArchivesToSearchSet(Common::SearchSet &s,  												int priority) { -	s.add("ASSET", _asset_archive, priority, false); - -	JNIEnv *env = JNU_GetEnv(); - -	jobjectArray array = -		(jobjectArray)env->CallObjectMethod(_back_ptr, MID_getSysArchives); - -	if (env->ExceptionCheck()) { -		warning("Error finding system archive path"); - -		env->ExceptionDescribe(); -		env->ExceptionClear(); - -		return; -	} - -	jsize size = env->GetArrayLength(array); -	for (jsize i = 0; i < size; ++i) { -		jstring path_obj = (jstring)env->GetObjectArrayElement(array, i); -		const char *path = env->GetStringUTFChars(path_obj, 0); - -		if (path != 0) { -			s.addDirectory(path, path, priority); -			env->ReleaseStringUTFChars(path_obj, path); -		} +	ENTER(""); -		env->DeleteLocalRef(path_obj); -	} +	JNI::addSysArchivesToSearchSet(s, priority);  } -void OSystem_Android::logMessage(LogMessageType::Type type, const char *message) { +void OSystem_Android::logMessage(LogMessageType::Type type, +									const char *message) {  	switch (type) {  	case LogMessageType::kDebug:  		__android_log_write(ANDROID_LOG_DEBUG, android_log_tag, message); @@ -1401,177 +585,25 @@ void OSystem_Android::logMessage(LogMessageType::Type type, const char *message)  	}  } -static jint ScummVM_scummVMMain(JNIEnv *env, jobject self, jobjectArray args) { -	OSystem_Android *cpp_obj = OSystem_Android::fromJavaObject(env, self); - -	const int MAX_NARGS = 32; -	int res = -1; - -	int argc = env->GetArrayLength(args); -	if (argc > MAX_NARGS) { -		JNU_ThrowByName(env, "java/lang/IllegalArgumentException", -						"too many arguments"); -		return 0; -	} - -	char *argv[MAX_NARGS]; - -	// note use in cleanup loop below -	int nargs; - -	for (nargs = 0; nargs < argc; ++nargs) { -		jstring arg = (jstring)env->GetObjectArrayElement(args, nargs); - -		if (arg == 0) { -			argv[nargs] = 0; -		} else { -			const char *cstr = env->GetStringUTFChars(arg, 0); - -			argv[nargs] = const_cast<char *>(cstr); - -			// exception already thrown? -			if (cstr == 0) -				goto cleanup; -		} - -		env->DeleteLocalRef(arg); -	} - -	g_system = cpp_obj; -	assert(g_system); - -	LOGI("Entering scummvm_main with %d args", argc); - -	res = scummvm_main(argc, argv); - -	LOGI("Exiting scummvm_main"); - -	g_system->quit(); - -cleanup: -	nargs--; - -	for (int i = 0; i < nargs; ++i) { -		if (argv[i] == 0) -			continue; - -		jstring arg = (jstring)env->GetObjectArrayElement(args, nargs); +Common::String OSystem_Android::getSystemLanguage() const { +	return Common::String::format("%s_%s", +							getSystemProperty("persist.sys.language").c_str(), +							getSystemProperty("persist.sys.country").c_str()); +} -		// Exception already thrown? -		if (arg == 0) -			return res; +Common::String OSystem_Android::getSystemProperty(const char *name) const { +	char value[PROP_VALUE_MAX]; -		env->ReleaseStringUTFChars(arg, argv[i]); -		env->DeleteLocalRef(arg); -	} +	int len = __system_property_get(name, value); -	return res; +	return Common::String(value, len);  }  #ifdef DYNAMIC_MODULES  void AndroidPluginProvider::addCustomDirectories(Common::FSList &dirs) const { -	OSystem_Android *g_system_android = (OSystem_Android *)g_system; -	g_system_android->addPluginDirectories(dirs); +	((OSystem_Android *)g_system)->addPluginDirectories(dirs);  }  #endif -static void ScummVM_enableZoning(JNIEnv *env, jobject self, jboolean enable) { -	OSystem_Android *cpp_obj = OSystem_Android::fromJavaObject(env, self); -	cpp_obj->enableZoning(enable); -} - -static void ScummVM_setSurfaceSize(JNIEnv *env, jobject self, -									jint width, jint height) { -	OSystem_Android *cpp_obj = OSystem_Android::fromJavaObject(env, self); -	cpp_obj->setSurfaceSize(width, height); -} - -const static JNINativeMethod gMethods[] = { -	{ "create", "(Landroid/content/res/AssetManager;)V", -		(void *)ScummVM_create }, -	{ "nativeDestroy", "()V", -		(void *)ScummVM_nativeDestroy }, -	{ "scummVMMain", "([Ljava/lang/String;)I", -	 	(void *)ScummVM_scummVMMain }, -	{ "pushEvent", "(Lorg/inodes/gus/scummvm/Event;)V", -		(void *)ScummVM_pushEvent }, -	{ "audioMixCallback", "([B)V", -		(void *)ScummVM_audioMixCallback }, -	{ "setConfMan", "(Ljava/lang/String;I)V", -		(void *)ScummVM_setConfManInt }, -	{ "setConfMan", "(Ljava/lang/String;Ljava/lang/String;)V", -		(void *)ScummVM_setConfManString }, -	{ "enableZoning", "(Z)V", -		(void *)ScummVM_enableZoning }, -	{ "setSurfaceSize", "(II)V", -		(void *)ScummVM_setSurfaceSize }, -}; - -JNIEXPORT jint JNICALL -JNI_OnLoad(JavaVM *jvm, void *reserved) { -	cached_jvm = jvm; - -	JNIEnv *env; - -	if (jvm->GetEnv((void **)&env, JNI_VERSION_1_2)) -		return JNI_ERR; - -	jclass cls = env->FindClass("org/inodes/gus/scummvm/ScummVM"); -	if (cls == 0) -		return JNI_ERR; - -	if (env->RegisterNatives(cls, gMethods, ARRAYSIZE(gMethods)) < 0) -		return JNI_ERR; - -	FID_ScummVM_nativeScummVM = env->GetFieldID(cls, "nativeScummVM", "J"); -	if (FID_ScummVM_nativeScummVM == 0) -		return JNI_ERR; - -	jclass event = env->FindClass("org/inodes/gus/scummvm/Event"); -	if (event == 0) -		return JNI_ERR; - -	FID_Event_type = env->GetFieldID(event, "type", "I"); -	if (FID_Event_type == 0) -		return JNI_ERR; - -	FID_Event_synthetic = env->GetFieldID(event, "synthetic", "Z"); -	if (FID_Event_synthetic == 0) -		return JNI_ERR; - -	FID_Event_kbd_keycode = env->GetFieldID(event, "kbd_keycode", "I"); -	if (FID_Event_kbd_keycode == 0) -		return JNI_ERR; - -	FID_Event_kbd_ascii = env->GetFieldID(event, "kbd_ascii", "I"); -	if (FID_Event_kbd_ascii == 0) -		return JNI_ERR; - -	FID_Event_kbd_flags = env->GetFieldID(event, "kbd_flags", "I"); -	if (FID_Event_kbd_flags == 0) -		return JNI_ERR; - -	FID_Event_mouse_x = env->GetFieldID(event, "mouse_x", "I"); -	if (FID_Event_mouse_x == 0) -		return JNI_ERR; - -	FID_Event_mouse_y = env->GetFieldID(event, "mouse_y", "I"); -	if (FID_Event_mouse_y == 0) -		return JNI_ERR; - -	FID_Event_mouse_relative = env->GetFieldID(event, "mouse_relative", "Z"); -	if (FID_Event_mouse_relative == 0) -		return JNI_ERR; - -	cls = env->FindClass("java/lang/Object"); -	if (cls == 0) -		return JNI_ERR; - -	MID_Object_wait = env->GetMethodID(cls, "wait", "()V"); -	if (MID_Object_wait == 0) -		return JNI_ERR; - -	return JNI_VERSION_1_2; -} -  #endif + diff --git a/backends/platform/android/android.h b/backends/platform/android/android.h index 855fb04b5d..109d252a99 100644 --- a/backends/platform/android/android.h +++ b/backends/platform/android/android.h @@ -23,8 +23,24 @@   *   */ +#ifndef _ANDROID_H_ +#define _ANDROID_H_ +  #if defined(__ANDROID__) +#include "common/fs.h" +#include "common/archive.h" +#include "audio/mixer_intern.h" +#include "graphics/palette.h" +#include "graphics/surface.h" +#include "backends/base-backend.h" +#include "backends/plugins/posix/posix-provider.h" +#include "backends/fs/posix/posix-fs-factory.h" + +#include "backends/platform/android/texture.h" + +#include <pthread.h> +  #include <android/log.h>  #include <GLES/gl.h> @@ -33,6 +49,7 @@  // toggles start  //#define ANDROID_DEBUG_ENTER  //#define ANDROID_DEBUG_GL +//#define ANDROID_DEBUG_GL_CALLS  // toggles end  extern const char *android_log_tag; @@ -46,25 +63,246 @@ extern const char *android_log_tag;  #ifdef ANDROID_DEBUG_ENTER  #define ENTER(fmt, args...) LOGD("%s(" fmt ")", __FUNCTION__, ##args)  #else -#define ENTER(fmt, args...) /**/ +#define ENTER(fmt, args...) do {  } while (false)  #endif  #ifdef ANDROID_DEBUG_GL  extern void checkGlError(const char *expr, const char *file, int line); +#ifdef ANDROID_DEBUG_GL_CALLS +#define GLCALLLOG(x, before) \ +	do { \ +		if (before) \ +			LOGD("calling '%s' (%s:%d)", x, __FILE__, __LINE__); \ +		else \ +			LOGD("returned from '%s' (%s:%d)", x, __FILE__, __LINE__); \ +	} while (false) +#else +#define GLCALLLOG(x, before) do {  } while (false) +#endif +  #define GLCALL(x) \  	do { \ +		GLCALLLOG(#x, true); \  		(x); \ +		GLCALLLOG(#x, false); \  		checkGlError(#x, __FILE__, __LINE__); \  	} while (false) +#define GLTHREADCHECK \ +	do { \ +		assert(pthread_self() == _main_thread); \ +	} while (false) +  #else  #define GLCALL(x) do { (x); } while (false) +#define GLTHREADCHECK do {  } while (false) +#endif + +#ifdef DYNAMIC_MODULES +class AndroidPluginProvider : public POSIXPluginProvider { +protected: +	virtual void addCustomDirectories(Common::FSList &dirs) const; +}; +#endif + +class OSystem_Android : public BaseBackend, public PaletteManager { +private: +	// passed from the dark side +	int _audio_sample_rate; +	int _audio_buffer_size; + +	int _screen_changeid; +	int _egl_surface_width; +	int _egl_surface_height; +	bool _htc_fail; + +	bool _force_redraw; + +	// Game layer +	GLESBaseTexture *_game_texture; +	int _shake_offset; +	Common::Rect _focus_rect; + +	// Overlay layer +	GLES4444Texture *_overlay_texture; +	bool _show_overlay; + +	// Mouse layer +	GLESBaseTexture *_mouse_texture; +	GLESBaseTexture *_mouse_texture_palette; +	GLES5551Texture *_mouse_texture_rgb; +	Common::Point _mouse_hotspot; +	uint32 _mouse_keycolor; +	int _mouse_targetscale; +	bool _show_mouse; +	bool _use_mouse_palette; + +	int _graphicsMode; +	bool _fullscreen; +	bool _ar_correction; + +	pthread_t _main_thread; + +	bool _timer_thread_exit; +	pthread_t _timer_thread; +	static void *timerThreadFunc(void *arg); + +	bool _audio_thread_exit; +	pthread_t _audio_thread; +	static void *audioThreadFunc(void *arg); + +	bool _enable_zoning; +	bool _virtkeybd_on; + +	Common::SaveFileManager *_savefile; +	Audio::MixerImpl *_mixer; +	Common::TimerManager *_timer; +	FilesystemFactory *_fsFactory; +	timeval _startTime; + +	Common::String getSystemProperty(const char *name) const; + +	void initSurface(); +	void deinitSurface(); +	void initViewport(); + +	void initOverlay(); + +#ifdef USE_RGB_COLOR +	Common::String getPixelFormatName(const Graphics::PixelFormat &format) const; +	void initTexture(GLESBaseTexture **texture, uint width, uint height, +						const Graphics::PixelFormat *format);  #endif -// Fix JNIEXPORT declaration to actually do something useful -#undef JNIEXPORT -#define JNIEXPORT __attribute__ ((visibility("default"))) +	void setupKeymapper(); +	void setCursorPaletteInternal(const byte *colors, uint start, uint num); + +public: +	OSystem_Android(int audio_sample_rate, int audio_buffer_size); +	virtual ~OSystem_Android(); + +	virtual void initBackend(); +	void addPluginDirectories(Common::FSList &dirs) const; +	void enableZoning(bool enable) { _enable_zoning = enable; } + +	virtual bool hasFeature(Feature f); +	virtual void setFeatureState(Feature f, bool enable); +	virtual bool getFeatureState(Feature f); + +	virtual const GraphicsMode *getSupportedGraphicsModes() const; +	virtual int getDefaultGraphicsMode() const; +	virtual bool setGraphicsMode(int mode); +	virtual int getGraphicsMode() const; + +#ifdef USE_RGB_COLOR +	virtual Graphics::PixelFormat getScreenFormat() const; +	virtual Common::List<Graphics::PixelFormat> getSupportedFormats() const; +#endif + +	virtual void initSize(uint width, uint height, +							const Graphics::PixelFormat *format); + +	enum FixupType { +		kClear = 0,		// glClear +		kClearSwap,		// glClear + swapBuffers +		kClearUpdate	// glClear + updateScreen +	}; + +	void clearScreen(FixupType type, byte count = 1); + +	void updateScreenRect(); +	virtual int getScreenChangeID() const; + +	virtual int16 getHeight(); +	virtual int16 getWidth(); + +	virtual PaletteManager *getPaletteManager() { +		return this; +	} + +public: +	void pushEvent(int type, int arg1, int arg2, int arg3, int arg4, int arg5); +private: +	Common::Queue<Common::Event> _event_queue; +	MutexRef _event_queue_lock; + +	Common::Point _touch_pt_down, _touch_pt_scroll, _touch_pt_dt; +	int _eventScaleX; +	int _eventScaleY; +	bool _touchpad_mode; +	int _touchpad_scale; +	int _trackball_scale; +	int _dpad_scale; +	int _fingersDown; + +	void clipMouse(Common::Point &p); +	void scaleMouse(Common::Point &p, int x, int y, bool deductDrawRect = true); +	void updateEventScale(); + +protected: +	// PaletteManager API +	virtual void setPalette(const byte *colors, uint start, uint num); +	virtual void grabPalette(byte *colors, uint start, uint num); + +public: +	virtual void copyRectToScreen(const byte *buf, int pitch, int x, int y, +									int w, int h); +	virtual void updateScreen(); +	virtual Graphics::Surface *lockScreen(); +	virtual void unlockScreen(); +	virtual void setShakePos(int shakeOffset); +	virtual void fillScreen(uint32 col); +	virtual void setFocusRectangle(const Common::Rect& rect); +	virtual void clearFocusRectangle(); + +	virtual void showOverlay(); +	virtual void hideOverlay(); +	virtual void clearOverlay(); +	virtual void grabOverlay(OverlayColor *buf, int pitch); +	virtual void copyRectToOverlay(const OverlayColor *buf, int pitch, +									int x, int y, int w, int h); +	virtual int16 getOverlayHeight(); +	virtual int16 getOverlayWidth(); +	virtual Graphics::PixelFormat getOverlayFormat() const; + +	virtual bool showMouse(bool visible); + +	virtual void warpMouse(int x, int y); +	virtual void setMouseCursor(const byte *buf, uint w, uint h, int hotspotX, +								int hotspotY, uint32 keycolor, +								int cursorTargetScale, +								const Graphics::PixelFormat *format); +	virtual void setCursorPalette(const byte *colors, uint start, uint num); +	virtual void disableCursorPalette(bool disable); + +	virtual bool pollEvent(Common::Event &event); +	virtual uint32 getMillis(); +	virtual void delayMillis(uint msecs); + +	virtual MutexRef createMutex(void); +	virtual void lockMutex(MutexRef mutex); +	virtual void unlockMutex(MutexRef mutex); +	virtual void deleteMutex(MutexRef mutex); + +	virtual void quit(); + +	virtual void setWindowCaption(const char *caption); +	virtual void displayMessageOnOSD(const char *msg); +	virtual void showVirtualKeyboard(bool enable); + +	virtual Common::SaveFileManager *getSavefileManager(); +	virtual Audio::Mixer *getMixer(); +	virtual void getTimeAndDate(TimeDate &t) const; +	virtual Common::TimerManager *getTimerManager(); +	virtual FilesystemFactory *getFilesystemFactory(); +	virtual void logMessage(LogMessageType::Type type, const char *message); +	virtual void addSysArchivesToSearchSet(Common::SearchSet &s, +											int priority = 0); +	virtual Common::String getSystemLanguage() const; +}; + +#endif  #endif diff --git a/backends/platform/android/android.mk b/backends/platform/android/android.mk index 1bc3c3d21a..77fdb139d8 100644 --- a/backends/platform/android/android.mk +++ b/backends/platform/android/android.mk @@ -1,11 +1,12 @@  # Android specific build targets  # These must be incremented for each market upload -#ANDROID_VERSIONCODE = 6  Specified in dists/android/AndroidManifest.xml.in +ANDROID_VERSIONCODE = 6  ANDROID_PLUGIN_VERSIONCODE = 6  JAVA_FILES = \  	ScummVM.java \ +	ScummVMEvents.java \  	ScummVMApplication.java \  	ScummVMActivity.java \  	EditableSurfaceView.java \ @@ -21,6 +22,8 @@ JAVA_FILES_GEN = \  PATH_DIST = $(srcdir)/dists/android  PATH_RESOURCES = $(PATH_DIST)/res +PORT_DISTFILES = $(PATH_DIST)/README.Android +  RESOURCES = \  	$(PATH_RESOURCES)/values/strings.xml \  	$(PATH_RESOURCES)/layout/main.xml \ @@ -69,7 +72,8 @@ PATH_GEN = $(PATH_GEN_TOP)/$(PATH_REL)  PATH_CLASSES_MAIN = $(PATH_BUILD_CLASSES_MAIN_TOP)/$(PATH_REL)  PATH_CLASSES_PLUGIN = $(PATH_BUILD_CLASSES_PLUGIN_TOP)/$(PATH_REL) -FILE_MANIFEST = $(srcdir)/dists/android/AndroidManifest.xml +FILE_MANIFEST_SRC = $(srcdir)/dists/android/AndroidManifest.xml +FILE_MANIFEST = $(PATH_BUILD)/AndroidManifest.xml  FILE_DEX = $(PATH_BUILD)/classes.dex  FILE_DEX_PLUGIN = $(PATH_BUILD)/plugins/classes.dex  FILE_RESOURCES = resources.ap_ @@ -84,6 +88,10 @@ CLASSES_PLUGIN = $(addprefix $(PATH_CLASSES_PLUGIN)/, $(JAVA_FILES_PLUGIN:%.java  APK_MAIN = scummvm.apk  APK_PLUGINS = $(patsubst plugins/lib%.so, scummvm-engine-%.apk, $(PLUGINS)) +$(FILE_MANIFEST): $(FILE_MANIFEST_SRC) +	@$(MKDIR) -p $(@D) +	sed "s/@ANDROID_VERSIONCODE@/$(ANDROID_VERSIONCODE)/" < $< > $@ +  $(SRC_GEN): $(FILE_MANIFEST) $(filter %.xml,$(RESOURCES)) $(ANDROID_JAR8)  	@$(MKDIR) -p $(PATH_GEN_TOP)  	$(AAPT) package -m -J $(PATH_GEN_TOP) -M $< -S $(PATH_RESOURCES) -I $(ANDROID_JAR8) @@ -107,14 +115,13 @@ $(FILE_DEX_PLUGIN): $(CLASSES_PLUGIN)  	@$(MKDIR) -p $(@D)  	$(DX) --dex --output=$@ $(PATH_BUILD_CLASSES_PLUGIN_TOP) -$(PATH_BUILD)/%/AndroidManifest.xml $(PATH_STAGE_PREFIX).%/res/values/strings.xml: $(PATH_DIST)/mkmanifest.pl $(srcdir)/configure $(PATH_DIST)/AndroidManifest.xml -	$(PATH_DIST)/mkmanifest.pl --id=$* --configure=$(srcdir)/configure \ -		--version-name=$(VERSION) \ -		--version-code=$(ANDROID_PLUGIN_VERSIONCODE) \ -		--stringres=$(PATH_STAGE_PREFIX).$*/res/values/strings.xml \ -		--manifest=$(PATH_BUILD)/$*/AndroidManifest.xml \ -		--master-manifest=$(PATH_DIST)/AndroidManifest.xml \ -		--unpacklib=mylib/armeabi/lib$*.so +$(PATH_BUILD)/%/AndroidManifest.xml: $(PATH_DIST)/mkplugin.sh $(srcdir)/configure $(PATH_DIST)/plugin-manifest.xml +	@$(MKDIR) -p $(@D) +	$(PATH_DIST)/mkplugin.sh $(srcdir)/configure $* $(PATH_DIST)/plugin-manifest.xml $(ANDROID_PLUGIN_VERSIONCODE) $@ + +$(PATH_STAGE_PREFIX).%/res/values/strings.xml: $(PATH_DIST)/mkplugin.sh $(srcdir)/configure $(PATH_DIST)/plugin-manifest.xml +	@$(MKDIR) -p $(@D) +	$(PATH_DIST)/mkplugin.sh $(srcdir)/configure $* $(PATH_DIST)/plugin-strings.xml $(ANDROID_PLUGIN_VERSIONCODE) $@  $(PATH_STAGE_PREFIX).%/res/drawable/scummvm.png: $(PATH_RESOURCES)/drawable/scummvm.png  	@$(MKDIR) -p $(@D) @@ -163,6 +170,10 @@ release/%.apk: %.apk  androidrelease: $(addprefix release/, $(APK_MAIN) $(APK_PLUGINS)) +androidtestmain: $(APK_MAIN) +	$(ADB) install -r $(APK_MAIN) +	$(ADB) shell am start -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -n org.inodes.gus.scummvm/.Unpacker +  androidtest: $(APK_MAIN) $(APK_PLUGINS)  	@set -e; for apk in $^; do \  		$(ADB) install -r $$apk; \ @@ -173,7 +184,7 @@ androidtest: $(APK_MAIN) $(APK_PLUGINS)  androiddistdebug: all  	$(MKDIR) debug  	$(CP) $(APK_MAIN) $(APK_PLUGINS) debug/ -	for i in $(DIST_FILES_DOCS); do \ +	for i in $(DIST_FILES_DOCS) $(PORT_DISTFILES); do \  		sed 's/$$/\r/' < $$i > debug/`basename $$i`.txt; \  	done diff --git a/backends/platform/android/asset-archive.cpp b/backends/platform/android/asset-archive.cpp index 71ce25aa72..7c21b35281 100644 --- a/backends/platform/android/asset-archive.cpp +++ b/backends/platform/android/asset-archive.cpp @@ -35,11 +35,11 @@  #include "common/util.h"  #include "common/archive.h"  #include "common/debug.h" +#include "common/textconsole.h" +#include "backends/platform/android/jni.h"  #include "backends/platform/android/asset-archive.h" -extern JNIEnv *JNU_GetEnv(); -  // Must match android.content.res.AssetManager.ACCESS_*  const jint ACCESS_UNKNOWN = 0;  const jint ACCESS_RANDOM = 1; @@ -100,7 +100,7 @@ JavaInputStream::JavaInputStream(JNIEnv *env, jobject is) :  {  	_input_stream = env->NewGlobalRef(is);  	_buflen = 8192; -	_buf = static_cast<jbyteArray>(env->NewGlobalRef(env->NewByteArray(_buflen))); +	_buf = (jbyteArray)env->NewGlobalRef(env->NewByteArray(_buflen));  	jclass cls = env->GetObjectClass(_input_stream);  	MID_mark = env->GetMethodID(cls, "mark", "(I)V"); @@ -124,7 +124,7 @@ JavaInputStream::JavaInputStream(JNIEnv *env, jobject is) :  }  JavaInputStream::~JavaInputStream() { -	JNIEnv *env = JNU_GetEnv(); +	JNIEnv *env = JNI::getEnv();  	close(env);  	env->DeleteGlobalRef(_buf); @@ -139,11 +139,11 @@ void JavaInputStream::close(JNIEnv *env) {  }  uint32 JavaInputStream::read(void *dataPtr, uint32 dataSize) { -	JNIEnv *env = JNU_GetEnv(); +	JNIEnv *env = JNI::getEnv();  	if (_buflen < jint(dataSize)) {  		_buflen = dataSize; -	 +  		env->DeleteGlobalRef(_buf);  		_buf = static_cast<jbyteArray>(env->NewGlobalRef(env->NewByteArray(_buflen)));  	} @@ -171,7 +171,7 @@ uint32 JavaInputStream::read(void *dataPtr, uint32 dataSize) {  }  bool JavaInputStream::seek(int32 offset, int whence) { -	JNIEnv *env = JNU_GetEnv(); +	JNIEnv *env = JNI::getEnv();  	uint32 newpos;  	switch (whence) { @@ -305,7 +305,8 @@ AssetFdReadStream::AssetFdReadStream(JNIEnv *env, jobject assetfd) :  	_declared_len = env->CallLongMethod(_assetfd, MID_getDeclaredLength);  	jmethodID MID_getFileDescriptor = -		env->GetMethodID(cls, "getFileDescriptor", "()Ljava/io/FileDescriptor;"); +		env->GetMethodID(cls, "getFileDescriptor", +							"()Ljava/io/FileDescriptor;");  	assert(MID_getFileDescriptor);  	jobject javafd = env->CallObjectMethod(_assetfd, MID_getFileDescriptor);  	assert(javafd); @@ -318,7 +319,7 @@ AssetFdReadStream::AssetFdReadStream(JNIEnv *env, jobject assetfd) :  }  AssetFdReadStream::~AssetFdReadStream() { -	JNIEnv *env = JNU_GetEnv(); +	JNIEnv *env = JNI::getEnv();  	env->CallVoidMethod(_assetfd, MID_close);  	if (env->ExceptionCheck()) @@ -369,7 +370,7 @@ bool AssetFdReadStream::seek(int32 offset, int whence) {  }  AndroidAssetArchive::AndroidAssetArchive(jobject am) { -	JNIEnv *env = JNU_GetEnv(); +	JNIEnv *env = JNI::getEnv();  	_am = env->NewGlobalRef(am);  	jclass cls = env->GetObjectClass(_am); @@ -377,8 +378,8 @@ AndroidAssetArchive::AndroidAssetArchive(jobject am) {  								"(Ljava/lang/String;I)Ljava/io/InputStream;");  	assert(MID_open); -	MID_openFd = env->GetMethodID(cls, "openFd", -									"(Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;"); +	MID_openFd = env->GetMethodID(cls, "openFd", "(Ljava/lang/String;)" +								"Landroid/content/res/AssetFileDescriptor;");  	assert(MID_openFd);  	MID_list = env->GetMethodID(cls, "list", @@ -387,12 +388,12 @@ AndroidAssetArchive::AndroidAssetArchive(jobject am) {  }  AndroidAssetArchive::~AndroidAssetArchive() { -	JNIEnv *env = JNU_GetEnv(); +	JNIEnv *env = JNI::getEnv();  	env->DeleteGlobalRef(_am);  }  bool AndroidAssetArchive::hasFile(const Common::String &name) { -	JNIEnv *env = JNU_GetEnv(); +	JNIEnv *env = JNI::getEnv();  	jstring path = env->NewStringUTF(name.c_str());  	jobject result = env->CallObjectMethod(_am, MID_open, path, ACCESS_UNKNOWN);  	if (env->ExceptionCheck()) { @@ -412,7 +413,7 @@ bool AndroidAssetArchive::hasFile(const Common::String &name) {  }  int AndroidAssetArchive::listMembers(Common::ArchiveMemberList &member_list) { -	JNIEnv *env = JNU_GetEnv(); +	JNIEnv *env = JNI::getEnv();  	Common::List<Common::String> dirlist;  	dirlist.push_back(""); @@ -422,7 +423,8 @@ int AndroidAssetArchive::listMembers(Common::ArchiveMemberList &member_list) {  		dirlist.pop_back();  		jstring jpath = env->NewStringUTF(dir.c_str()); -		jobjectArray jpathlist = static_cast<jobjectArray>(env->CallObjectMethod(_am, MID_list, jpath)); +		jobjectArray jpathlist = +			(jobjectArray)env->CallObjectMethod(_am, MID_list, jpath);  		if (env->ExceptionCheck()) {  			warning("Error while calling AssetManager->list(%s). Ignoring.", @@ -439,19 +441,22 @@ int AndroidAssetArchive::listMembers(Common::ArchiveMemberList &member_list) {  		for (jsize i = 0; i < env->GetArrayLength(jpathlist); ++i) {  			jstring elem = (jstring)env->GetObjectArrayElement(jpathlist, i);  			const char *p = env->GetStringUTFChars(elem, 0); -			Common::String thispath = dir; -			if (!thispath.empty()) -				thispath += "/"; +			if (strlen(p)) { +				Common::String thispath = dir; + +				if (!thispath.empty()) +					thispath += "/"; -			thispath += p; +				thispath += p; -			// Assume files have a . in them, and directories don't -			if (strchr(p, '.')) { -				member_list.push_back(getMember(thispath)); -				++count; -			} else { -				dirlist.push_back(thispath); +				// Assume files have a . in them, and directories don't +				if (strchr(p, '.')) { +					member_list.push_back(getMember(thispath)); +					++count; +				} else { +					dirlist.push_back(thispath); +				}  			}  			env->ReleaseStringUTFChars(elem, p); @@ -469,7 +474,7 @@ Common::ArchiveMemberPtr AndroidAssetArchive::getMember(const Common::String &na  }  Common::SeekableReadStream *AndroidAssetArchive::createReadStreamForMember(const Common::String &path) const { -	JNIEnv *env = JNU_GetEnv(); +	JNIEnv *env = JNI::getEnv();  	jstring jpath = env->NewStringUTF(path.c_str());  	// Try openFd() first ... diff --git a/backends/platform/android/asset-archive.h b/backends/platform/android/asset-archive.h index 28e48426e9..6ec86e4cd0 100644 --- a/backends/platform/android/asset-archive.h +++ b/backends/platform/android/asset-archive.h @@ -23,6 +23,9 @@   *   */ +#ifndef _ANDROID_ASSET_H_ +#define _ANDROID_ASSET_H_ +  #if defined(__ANDROID__)  #include <jni.h> @@ -51,3 +54,5 @@ private:  };  #endif +#endif + diff --git a/backends/platform/android/events.cpp b/backends/platform/android/events.cpp new file mode 100644 index 0000000000..2f140f0c0b --- /dev/null +++ b/backends/platform/android/events.cpp @@ -0,0 +1,822 @@ +/* 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__) + +// 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/events.h" + +#include "backends/platform/android/android.h" +#include "backends/platform/android/jni.h" + +// $ANDROID_NDK/platforms/android-9/arch-arm/usr/include/android/keycodes.h +// http://android.git.kernel.org/?p=platform/frameworks/base.git;a=blob;f=libs/ui/Input.cpp +// http://android.git.kernel.org/?p=platform/frameworks/base.git;a=blob;f=core/java/android/view/KeyEvent.java + +// event type +enum { +	JE_SYS_KEY = 0, +	JE_KEY = 1, +	JE_DPAD = 2, +	JE_DOWN = 3, +	JE_SCROLL = 4, +	JE_TAP = 5, +	JE_DOUBLE_TAP = 6, +	JE_MULTI = 7, +	JE_BALL = 8, +	JE_QUIT = 0x1000 +}; + +// action type +enum { +	JACTION_DOWN = 0, +	JACTION_UP = 1, +	JACTION_MULTIPLE = 2, +	JACTION_POINTER_DOWN = 5, +	JACTION_POINTER_UP = 6 +}; + +// system keys +enum { +	JKEYCODE_SOFT_RIGHT = 2, +	JKEYCODE_HOME = 3, +	JKEYCODE_BACK = 4, +	JKEYCODE_CALL = 5, +	JKEYCODE_ENDCALL = 6, +	JKEYCODE_VOLUME_UP = 24, +	JKEYCODE_VOLUME_DOWN = 25, +	JKEYCODE_POWER = 26, +	JKEYCODE_CAMERA = 27, +	JKEYCODE_HEADSETHOOK = 79, +	JKEYCODE_FOCUS = 80, +	JKEYCODE_MENU = 82, +	JKEYCODE_SEARCH = 84, +	JKEYCODE_MUTE = 91, +	JKEYCODE_MEDIA_PLAY_PAUSE = 85, +	JKEYCODE_MEDIA_STOP = 86, +	JKEYCODE_MEDIA_NEXT = 87, +	JKEYCODE_MEDIA_PREVIOUS = 88, +	JKEYCODE_MEDIA_REWIND = 89, +	JKEYCODE_MEDIA_FAST_FORWARD = 90 +}; + +// five-way navigation control +enum { +	JKEYCODE_DPAD_UP = 19, +	JKEYCODE_DPAD_DOWN = 20, +	JKEYCODE_DPAD_LEFT = 21, +	JKEYCODE_DPAD_RIGHT = 22, +	JKEYCODE_DPAD_CENTER = 23 +}; + +// meta modifier +enum { +	JMETA_SHIFT = 0x01, +	JMETA_ALT = 0x02, +	JMETA_SYM = 0x04, +	JMETA_CTRL = 0x1000 +}; + +// map android key codes to our kbd codes +static const Common::KeyCode jkeymap[] = { +	Common::KEYCODE_INVALID, // KEYCODE_UNKNOWN +	Common::KEYCODE_INVALID, // KEYCODE_SOFT_LEFT +	Common::KEYCODE_INVALID, // KEYCODE_SOFT_RIGHT +	Common::KEYCODE_INVALID, // KEYCODE_HOME +	Common::KEYCODE_INVALID, // KEYCODE_BACK +	Common::KEYCODE_INVALID, // KEYCODE_CALL +	Common::KEYCODE_INVALID, // KEYCODE_ENDCALL +	Common::KEYCODE_0, // KEYCODE_0 +	Common::KEYCODE_1, // KEYCODE_1 +	Common::KEYCODE_2, // KEYCODE_2 +	Common::KEYCODE_3, // KEYCODE_3 +	Common::KEYCODE_4, // KEYCODE_4 +	Common::KEYCODE_5, // KEYCODE_5 +	Common::KEYCODE_6, // KEYCODE_6 +	Common::KEYCODE_7, // KEYCODE_7 +	Common::KEYCODE_8, // KEYCODE_8 +	Common::KEYCODE_9, // KEYCODE_9 +	Common::KEYCODE_ASTERISK, // KEYCODE_STAR +	Common::KEYCODE_HASH, // KEYCODE_POUND +	Common::KEYCODE_INVALID, // KEYCODE_DPAD_UP +	Common::KEYCODE_INVALID, // KEYCODE_DPAD_DOWN +	Common::KEYCODE_INVALID, // KEYCODE_DPAD_LEFT +	Common::KEYCODE_INVALID, // KEYCODE_DPAD_RIGHT +	Common::KEYCODE_INVALID, // KEYCODE_DPAD_CENTER +	Common::KEYCODE_INVALID, // KEYCODE_VOLUME_UP +	Common::KEYCODE_INVALID, // KEYCODE_VOLUME_DOWN +	Common::KEYCODE_INVALID, // KEYCODE_POWER +	Common::KEYCODE_INVALID, // KEYCODE_CAMERA +	Common::KEYCODE_INVALID, // KEYCODE_CLEAR +	Common::KEYCODE_a, // KEYCODE_A +	Common::KEYCODE_b, // KEYCODE_B +	Common::KEYCODE_c, // KEYCODE_C +	Common::KEYCODE_d, // KEYCODE_D +	Common::KEYCODE_e, // KEYCODE_E +	Common::KEYCODE_f, // KEYCODE_F +	Common::KEYCODE_g, // KEYCODE_G +	Common::KEYCODE_h, // KEYCODE_H +	Common::KEYCODE_i, // KEYCODE_I +	Common::KEYCODE_j, // KEYCODE_J +	Common::KEYCODE_k, // KEYCODE_K +	Common::KEYCODE_l, // KEYCODE_L +	Common::KEYCODE_m, // KEYCODE_M +	Common::KEYCODE_n, // KEYCODE_N +	Common::KEYCODE_o, // KEYCODE_O +	Common::KEYCODE_p, // KEYCODE_P +	Common::KEYCODE_q, // KEYCODE_Q +	Common::KEYCODE_r, // KEYCODE_R +	Common::KEYCODE_s, // KEYCODE_S +	Common::KEYCODE_t, // KEYCODE_T +	Common::KEYCODE_u, // KEYCODE_U +	Common::KEYCODE_v, // KEYCODE_V +	Common::KEYCODE_w, // KEYCODE_W +	Common::KEYCODE_x, // KEYCODE_X +	Common::KEYCODE_y, // KEYCODE_Y +	Common::KEYCODE_z, // KEYCODE_Z +	Common::KEYCODE_COMMA, // KEYCODE_COMMA +	Common::KEYCODE_PERIOD, // KEYCODE_PERIOD +	Common::KEYCODE_LALT, // KEYCODE_ALT_LEFT +	Common::KEYCODE_RALT, // KEYCODE_ALT_RIGHT +	Common::KEYCODE_LSHIFT, // KEYCODE_SHIFT_LEFT +	Common::KEYCODE_RSHIFT, // KEYCODE_SHIFT_RIGHT +	Common::KEYCODE_TAB, // KEYCODE_TAB +	Common::KEYCODE_SPACE, // KEYCODE_SPACE +	Common::KEYCODE_LCTRL, // KEYCODE_SYM +	Common::KEYCODE_INVALID, // KEYCODE_EXPLORER +	Common::KEYCODE_INVALID, // KEYCODE_ENVELOPE +	Common::KEYCODE_RETURN, // KEYCODE_ENTER +	Common::KEYCODE_BACKSPACE, // KEYCODE_DEL +	Common::KEYCODE_BACKQUOTE, // KEYCODE_GRAVE +	Common::KEYCODE_MINUS, // KEYCODE_MINUS +	Common::KEYCODE_EQUALS, // KEYCODE_EQUALS +	Common::KEYCODE_LEFTPAREN, // KEYCODE_LEFT_BRACKET +	Common::KEYCODE_RIGHTPAREN, // KEYCODE_RIGHT_BRACKET +	Common::KEYCODE_BACKSLASH, // KEYCODE_BACKSLASH +	Common::KEYCODE_SEMICOLON, // KEYCODE_SEMICOLON +	Common::KEYCODE_QUOTE, // KEYCODE_APOSTROPHE +	Common::KEYCODE_SLASH, // KEYCODE_SLASH +	Common::KEYCODE_AT, // KEYCODE_AT +	Common::KEYCODE_INVALID, // KEYCODE_NUM +	Common::KEYCODE_INVALID, // KEYCODE_HEADSETHOOK +	Common::KEYCODE_INVALID, // KEYCODE_FOCUS +	Common::KEYCODE_PLUS, // KEYCODE_PLUS +	Common::KEYCODE_INVALID, // KEYCODE_MENU +	Common::KEYCODE_INVALID, // KEYCODE_NOTIFICATION +	Common::KEYCODE_INVALID, // KEYCODE_SEARCH +	Common::KEYCODE_INVALID, // KEYCODE_MEDIA_PLAY_PAUSE +	Common::KEYCODE_INVALID, // KEYCODE_MEDIA_STOP +	Common::KEYCODE_INVALID, // KEYCODE_MEDIA_NEXT +	Common::KEYCODE_INVALID, // KEYCODE_MEDIA_PREVIOUS +	Common::KEYCODE_INVALID, // KEYCODE_MEDIA_REWIND +	Common::KEYCODE_INVALID, // KEYCODE_MEDIA_FAST_FORWARD +	Common::KEYCODE_INVALID, // KEYCODE_MUTE +	Common::KEYCODE_PAGEUP, // KEYCODE_PAGE_UP +	Common::KEYCODE_PAGEDOWN // KEYCODE_PAGE_DOWN +}; + +// floating point. use sparingly +template <class T> +static inline T scalef(T in, float numerator, float denominator) { +	return static_cast<float>(in) * numerator / denominator; +} + +void OSystem_Android::setupKeymapper() { +#ifdef ENABLE_KEYMAPPER +	using namespace Common; + +	Keymapper *mapper = getEventManager()->getKeymapper(); + +	HardwareKeySet *keySet = new HardwareKeySet(); + +	keySet->addHardwareKey( +		new HardwareKey("n", KeyState(KEYCODE_n), "n (vk)", +				kTriggerLeftKeyType, +				kVirtualKeyboardActionType)); + +	mapper->registerHardwareKeySet(keySet); + +	Keymap *globalMap = new Keymap("global"); +	Action *act; + +	act = new Action(globalMap, "VIRT", "Display keyboard", +						kVirtualKeyboardActionType); +	act->addKeyEvent(KeyState(KEYCODE_F7, ASCII_F7, 0)); + +	mapper->addGlobalKeymap(globalMap); + +	mapper->pushKeymap("global"); +#endif +} + +void OSystem_Android::warpMouse(int x, int y) { +	ENTER("%d, %d", x, y); + +	Common::Event e; + +	e.type = Common::EVENT_MOUSEMOVE; +	e.mouse.x = x; +	e.mouse.y = y; + +	clipMouse(e.mouse); + +	lockMutex(_event_queue_lock); +	_event_queue.push(e); +	unlockMutex(_event_queue_lock); +} + +void OSystem_Android::clipMouse(Common::Point &p) { +	const GLESBaseTexture *tex; + +	if (_show_overlay) +		tex = _overlay_texture; +	else +		tex = _game_texture; + +	p.x = CLIP(p.x, int16(0), int16(tex->width() - 1)); +	p.y = CLIP(p.y, int16(0), int16(tex->height() - 1)); +} + +void OSystem_Android::scaleMouse(Common::Point &p, int x, int y, +									bool deductDrawRect) { +	const GLESBaseTexture *tex; + +	if (_show_overlay) +		tex = _overlay_texture; +	else +		tex = _game_texture; + +	const Common::Rect &r = tex->getDrawRect(); + +	if (_touchpad_mode) { +		x = x * 100 / _touchpad_scale; +		y = y * 100 / _touchpad_scale; +	} + +	if (deductDrawRect) { +		x -= r.left; +		y -= r.top; +	} + +	p.x = scalef(x, tex->width(), r.width()); +	p.y = scalef(y, tex->height(), r.height()); +} + +void OSystem_Android::updateEventScale() { +	const GLESBaseTexture *tex; + +	if (_show_overlay) +		tex = _overlay_texture; +	else +		tex = _game_texture; + +	_eventScaleY = 100 * 480 / tex->height(); +	_eventScaleX = 100 * 640 / tex->width(); +} + +void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3, +								int arg4, int arg5) { +	Common::Event e; + +	switch (type) { +	case JE_SYS_KEY: +		switch (arg1) { +		case JACTION_DOWN: +			e.type = Common::EVENT_KEYDOWN; +			break; +		case JACTION_UP: +			e.type = Common::EVENT_KEYUP; +			break; +		default: +			LOGE("unhandled jaction on system key: %d", arg1); +			return; +		} + +		switch (arg2) { +		case JKEYCODE_BACK: +			e.kbd.keycode = Common::KEYCODE_ESCAPE; +			e.kbd.ascii = Common::ASCII_ESCAPE; + +			lockMutex(_event_queue_lock); +			_event_queue.push(e); +			unlockMutex(_event_queue_lock); + +			return; + +		// special case. we'll only get it's up event +		case JKEYCODE_MENU: +			e.type = Common::EVENT_MAINMENU; + +			lockMutex(_event_queue_lock); +			_event_queue.push(e); +			unlockMutex(_event_queue_lock); + +			return; + +		case JKEYCODE_CAMERA: +		case JKEYCODE_SEARCH: +			if (arg1 == JACTION_DOWN) +				e.type = Common::EVENT_RBUTTONDOWN; +			else +				e.type = Common::EVENT_RBUTTONUP; + +			e.mouse = getEventManager()->getMousePos(); + +			lockMutex(_event_queue_lock); +			_event_queue.push(e); +			unlockMutex(_event_queue_lock); + +			return; + +		default: +			LOGW("unmapped system key: %d", arg2); +			return; +		} + +		break; + +	case JE_KEY: +		switch (arg1) { +		case JACTION_DOWN: +			e.type = Common::EVENT_KEYDOWN; +			break; +		case JACTION_UP: +			e.type = Common::EVENT_KEYUP; +			break; +		default: +			LOGE("unhandled jaction on key: %d", arg1); +			return; +		} + +		if (arg2 < 1 || arg2 > ARRAYSIZE(jkeymap)) { +			if (arg3 < 1) { +				LOGE("received invalid keycode: %d (%d)", arg2, arg3); +				return; +			} else { +				// lets bet on the ascii code +				e.kbd.keycode = Common::KEYCODE_INVALID; +			} +		} else { +			e.kbd.keycode = jkeymap[arg2]; +		} + +		if (arg5 > 0) +			e.synthetic = true; + +		// map special keys to 'our' ascii codes +		switch (e.kbd.keycode) { +		case Common::KEYCODE_BACKSPACE: +			e.kbd.ascii = Common::ASCII_BACKSPACE; +			break; +		case Common::KEYCODE_TAB: +			e.kbd.ascii = Common::ASCII_TAB; +			break; +		case Common::KEYCODE_RETURN: +			e.kbd.ascii = Common::ASCII_RETURN; +			break; +		case Common::KEYCODE_ESCAPE: +			e.kbd.ascii = Common::ASCII_ESCAPE; +			break; +		case Common::KEYCODE_SPACE: +			e.kbd.ascii = Common::ASCII_SPACE; +			break; +		case Common::KEYCODE_F1: +			e.kbd.ascii = Common::ASCII_F1; +			break; +		case Common::KEYCODE_F2: +			e.kbd.ascii = Common::ASCII_F2; +			break; +		case Common::KEYCODE_F3: +			e.kbd.ascii = Common::ASCII_F3; +			break; +		case Common::KEYCODE_F4: +			e.kbd.ascii = Common::ASCII_F4; +			break; +		case Common::KEYCODE_F5: +			e.kbd.ascii = Common::ASCII_F5; +			break; +		case Common::KEYCODE_F6: +			e.kbd.ascii = Common::ASCII_F6; +			break; +		case Common::KEYCODE_F7: +			e.kbd.ascii = Common::ASCII_F7; +			break; +		case Common::KEYCODE_F8: +			e.kbd.ascii = Common::ASCII_F8; +			break; +		case Common::KEYCODE_F9: +			e.kbd.ascii = Common::ASCII_F9; +			break; +		case Common::KEYCODE_F10: +			e.kbd.ascii = Common::ASCII_F10; +			break; +		case Common::KEYCODE_F11: +			e.kbd.ascii = Common::ASCII_F11; +			break; +		case Common::KEYCODE_F12: +			e.kbd.ascii = Common::ASCII_F12; +			break; +		default: +			e.kbd.ascii = arg3; +			break; +		} + +		if (arg4 & JMETA_SHIFT) +			e.kbd.flags |= Common::KBD_SHIFT; +		if (arg4 & JMETA_ALT) +			e.kbd.flags |= Common::KBD_ALT; +		if (arg4 & (JMETA_SYM | JMETA_CTRL)) +			e.kbd.flags |= Common::KBD_CTRL; + +		lockMutex(_event_queue_lock); +		_event_queue.push(e); +		unlockMutex(_event_queue_lock); + +		return; + +	case JE_DPAD: +		switch (arg2) { +		case JKEYCODE_DPAD_UP: +		case JKEYCODE_DPAD_DOWN: +		case JKEYCODE_DPAD_LEFT: +		case JKEYCODE_DPAD_RIGHT: +			if (arg1 != JACTION_DOWN) +				return; + +			e.type = Common::EVENT_MOUSEMOVE; + +			e.mouse = getEventManager()->getMousePos(); + +			{ +				int16 *c; +				int s; + +				if (arg2 == JKEYCODE_DPAD_UP || arg2 == JKEYCODE_DPAD_DOWN) { +					c = &e.mouse.y; +					s = _eventScaleY; +				} else { +					c = &e.mouse.x; +					s = _eventScaleX; +				} + +				// the longer the button held, the faster the pointer is +				// TODO put these values in some option dlg? +				int f = CLIP(arg4, 1, 8) * _dpad_scale * 100 / s; + +				if (arg2 == JKEYCODE_DPAD_UP || arg2 == JKEYCODE_DPAD_LEFT) +					*c -= f; +				else +					*c += f; +			} + +			clipMouse(e.mouse); + +			lockMutex(_event_queue_lock); +			_event_queue.push(e); +			unlockMutex(_event_queue_lock); + +			return; + +		case JKEYCODE_DPAD_CENTER: +			switch (arg1) { +			case JACTION_DOWN: +				e.type = Common::EVENT_LBUTTONDOWN; +				break; +			case JACTION_UP: +				e.type = Common::EVENT_LBUTTONUP; +				break; +			default: +				LOGE("unhandled jaction on dpad key: %d", arg1); +				return; +			} + +			e.mouse = getEventManager()->getMousePos(); + +			lockMutex(_event_queue_lock); +			_event_queue.push(e); +			unlockMutex(_event_queue_lock); + +			return; +		} + +	case JE_DOWN: +		_touch_pt_down = getEventManager()->getMousePos(); +		_touch_pt_scroll.x = -1; +		_touch_pt_scroll.y = -1; +		break; + +	case JE_SCROLL: +		e.type = Common::EVENT_MOUSEMOVE; + +		if (_touchpad_mode) { +			if (_touch_pt_scroll.x == -1 && _touch_pt_scroll.y == -1) { +				_touch_pt_scroll.x = arg3; +				_touch_pt_scroll.y = arg4; +				return; +			} + +			scaleMouse(e.mouse, arg3 - _touch_pt_scroll.x, +						arg4 - _touch_pt_scroll.y, false); +			e.mouse += _touch_pt_down; +			clipMouse(e.mouse); +		} else { +			scaleMouse(e.mouse, arg3, arg4); +			clipMouse(e.mouse); +		} + +		lockMutex(_event_queue_lock); +		_event_queue.push(e); +		unlockMutex(_event_queue_lock); + +		return; + +	case JE_TAP: +		if (_fingersDown > 0) { +			_fingersDown = 0; +			return; +		} + +		e.type = Common::EVENT_MOUSEMOVE; + +		if (_touchpad_mode) { +			e.mouse = getEventManager()->getMousePos(); +		} else { +			scaleMouse(e.mouse, arg1, arg2); +			clipMouse(e.mouse); +		} + +		{ +			Common::EventType down, up; + +			// TODO put these values in some option dlg? +			if (arg3 > 1000) { +				down = Common::EVENT_MBUTTONDOWN; +				up = Common::EVENT_MBUTTONUP; +			} else if (arg3 > 500) { +				down = Common::EVENT_RBUTTONDOWN; +				up = Common::EVENT_RBUTTONUP; +			} else { +				down = Common::EVENT_LBUTTONDOWN; +				up = Common::EVENT_LBUTTONUP; +			} + +			lockMutex(_event_queue_lock); + +			if (!_touchpad_mode) +				_event_queue.push(e); + +			e.type = down; +			_event_queue.push(e); +			e.type = up; +			_event_queue.push(e); + +			unlockMutex(_event_queue_lock); +		} + +		return; + +	case JE_DOUBLE_TAP: +		e.type = Common::EVENT_MOUSEMOVE; + +		if (_touchpad_mode) { +			e.mouse = getEventManager()->getMousePos(); +		} else { +			scaleMouse(e.mouse, arg1, arg2); +			clipMouse(e.mouse); +		} + +		{ +			Common::EventType dptype = Common::EVENT_INVALID; + +			switch (arg3) { +			case JACTION_DOWN: +				dptype = Common::EVENT_LBUTTONDOWN; +				_touch_pt_dt.x = -1; +				_touch_pt_dt.y = -1; +				break; +			case JACTION_UP: +				dptype = Common::EVENT_LBUTTONUP; +				break; +			// held and moved +			case JACTION_MULTIPLE: +				if (_touch_pt_dt.x == -1 && _touch_pt_dt.y == -1) { +					_touch_pt_dt.x = arg1; +					_touch_pt_dt.y = arg2; +					return; +				} + +				dptype = Common::EVENT_MOUSEMOVE; + +				if (_touchpad_mode) { +					scaleMouse(e.mouse, arg1 - _touch_pt_dt.x, +								arg2 - _touch_pt_dt.y, false); +					e.mouse += _touch_pt_down; + +					clipMouse(e.mouse); +				} + +				break; +			default: +				LOGE("unhandled jaction on double tap: %d", arg3); +				return; +			} + +			lockMutex(_event_queue_lock); +			_event_queue.push(e); +			e.type = dptype; +			_event_queue.push(e); +			unlockMutex(_event_queue_lock); +		} + +		return; + +	case JE_MULTI: +		switch (arg2) { +		case JACTION_POINTER_DOWN: +			if (arg1 > _fingersDown) +				_fingersDown = arg1; + +			return; + +		case JACTION_POINTER_UP: +			if (arg1 != _fingersDown) +				return; + +			{ +				Common::EventType up; + +				switch (_fingersDown) { +				case 1: +					e.type = Common::EVENT_RBUTTONDOWN; +					up = Common::EVENT_RBUTTONUP; +					break; +				case 2: +					e.type = Common::EVENT_MBUTTONDOWN; +					up = Common::EVENT_MBUTTONUP; +					break; +				default: +					LOGD("unmapped multi tap: %d", _fingersDown); +					return; +				} + +				e.mouse = getEventManager()->getMousePos(); + +				lockMutex(_event_queue_lock); + +				_event_queue.push(e); +				e.type = up; +				_event_queue.push(e); + +				unlockMutex(_event_queue_lock); +				return; + +			default: +				LOGE("unhandled jaction on multi tap: %d", arg2); +				return; +			} +		} + +		return; + +	case JE_BALL: +		e.mouse = getEventManager()->getMousePos(); + +		switch (arg1) { +		case JACTION_DOWN: +			e.type = Common::EVENT_LBUTTONDOWN; +			break; +		case JACTION_UP: +			e.type = Common::EVENT_LBUTTONUP; +			break; +		case JACTION_MULTIPLE: +			e.type = Common::EVENT_MOUSEMOVE; + +			// already multiplied by 100 +			e.mouse.x += arg2 * _trackball_scale / _eventScaleX; +			e.mouse.y += arg3 * _trackball_scale / _eventScaleY; + +			clipMouse(e.mouse); + +			break; +		default: +			LOGE("unhandled jaction on system key: %d", arg1); +			return; +		} + +		lockMutex(_event_queue_lock); +		_event_queue.push(e); +		unlockMutex(_event_queue_lock); + +		return; + +	case JE_QUIT: +		e.type = Common::EVENT_QUIT; + +		lockMutex(_event_queue_lock); +		_event_queue.push(e); +		unlockMutex(_event_queue_lock); + +		return; + +	default: +		LOGE("unknown jevent type: %d", type); + +		break; +	} +} + +bool OSystem_Android::pollEvent(Common::Event &event) { +	//ENTER(); + +	if (pthread_self() == _main_thread) { +		if (_screen_changeid != JNI::surface_changeid) { +			if (JNI::egl_surface_width > 0 && JNI::egl_surface_height > 0) { +				// surface changed +				JNI::deinitSurface(); +				initSurface(); +				initViewport(); +				updateScreenRect(); +				updateEventScale(); + +				// double buffered, flip twice +				clearScreen(kClearUpdate, 2); + +				event.type = Common::EVENT_SCREEN_CHANGED; + +				return true; +			} else { +				// surface lost +				deinitSurface(); +			} +		} + +		if (JNI::pause) { +			deinitSurface(); + +			LOGD("main thread going to sleep"); +			sem_wait(&JNI::pause_sem); +			LOGD("main thread woke up"); +		} +	} + +	lockMutex(_event_queue_lock); + +	if (_event_queue.empty()) { +		unlockMutex(_event_queue_lock); +		return false; +	} + +	event = _event_queue.pop(); + +	unlockMutex(_event_queue_lock); + +	if (event.type == Common::EVENT_MOUSEMOVE) { +		const Common::Point &m = getEventManager()->getMousePos(); + +		if (m != event.mouse) +			_force_redraw = true; +	} + +	return true; +} + +#endif + diff --git a/backends/platform/android/gfx.cpp b/backends/platform/android/gfx.cpp new file mode 100644 index 0000000000..ebce58e291 --- /dev/null +++ b/backends/platform/android/gfx.cpp @@ -0,0 +1,836 @@ +/* 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__) + +// 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(); + +	// Initialise 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, int cursorTargetScale, +										const Graphics::PixelFormat *format) { +	ENTER("%p, %u, %u, %d, %d, %u, %d, %p", buf, w, h, hotspotX, hotspotY, +			keycolor, cursorTargetScale, 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); +	_mouse_targetscale = cursorTargetScale; +} + +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(bool disable) { +	ENTER("%d", disable); + +	// when disabling the cursor palette, and we're running a clut8 game, +	// it expects the game palette to be used for the cursor +	if (disable && _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); +	} + +	_use_mouse_palette = !disable; +} + +#endif + diff --git a/backends/platform/android/jni.cpp b/backends/platform/android/jni.cpp new file mode 100644 index 0000000000..13aef11fa2 --- /dev/null +++ b/backends/platform/android/jni.cpp @@ -0,0 +1,624 @@ +/* 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__) + +// 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 "base/main.h" +#include "base/version.h" +#include "common/config-manager.h" +#include "common/error.h" +#include "common/textconsole.h" +#include "engines/engine.h" + +#include "backends/platform/android/android.h" +#include "backends/platform/android/asset-archive.h" +#include "backends/platform/android/jni.h" + +__attribute__ ((visibility("default"))) +jint JNICALL JNI_OnLoad(JavaVM *vm, void *) { +	return JNI::onLoad(vm); +} + +JavaVM *JNI::_vm = 0; +jobject JNI::_jobj = 0; +jobject JNI::_jobj_audio_track = 0; +jobject JNI::_jobj_egl = 0; +jobject JNI::_jobj_egl_display = 0; +jobject JNI::_jobj_egl_surface = 0; + +Common::Archive *JNI::_asset_archive = 0; +OSystem_Android *JNI::_system = 0; + +bool JNI::pause = false; +sem_t JNI::pause_sem = { 0 }; + +int JNI::surface_changeid = 0; +int JNI::egl_surface_width = 0; +int JNI::egl_surface_height = 0; +bool JNI::_ready_for_events = 0; + +jmethodID JNI::_MID_getDPI = 0; +jmethodID JNI::_MID_displayMessageOnOSD = 0; +jmethodID JNI::_MID_setWindowCaption = 0; +jmethodID JNI::_MID_showVirtualKeyboard = 0; +jmethodID JNI::_MID_getSysArchives = 0; +jmethodID JNI::_MID_getPluginDirectories = 0; +jmethodID JNI::_MID_initSurface = 0; +jmethodID JNI::_MID_deinitSurface = 0; + +jmethodID JNI::_MID_EGL10_eglSwapBuffers = 0; + +jmethodID JNI::_MID_AudioTrack_flush = 0; +jmethodID JNI::_MID_AudioTrack_pause = 0; +jmethodID JNI::_MID_AudioTrack_play = 0; +jmethodID JNI::_MID_AudioTrack_stop = 0; +jmethodID JNI::_MID_AudioTrack_write = 0; + +const JNINativeMethod JNI::_natives[] = { +	{ "create", "(Landroid/content/res/AssetManager;" +				"Ljavax/microedition/khronos/egl/EGL10;" +				"Ljavax/microedition/khronos/egl/EGLDisplay;" +				"Landroid/media/AudioTrack;II)V", +		(void *)JNI::create }, +	{ "destroy", "()V", +		(void *)JNI::destroy }, +	{ "setSurface", "(II)V", +		(void *)JNI::setSurface }, +	{ "main", "([Ljava/lang/String;)I", +		(void *)JNI::main }, +	{ "pushEvent", "(IIIIII)V", +		(void *)JNI::pushEvent }, +	{ "enableZoning", "(Z)V", +		(void *)JNI::enableZoning }, +	{ "setPause", "(Z)V", +		(void *)JNI::setPause } +}; + +JNI::JNI() { +} + +JNI::~JNI() { +} + +jint JNI::onLoad(JavaVM *vm) { +	_vm = vm; + +	JNIEnv *env; + +	if (_vm->GetEnv((void **)&env, JNI_VERSION_1_2)) +		return JNI_ERR; + +	jclass cls = env->FindClass("org/inodes/gus/scummvm/ScummVM"); +	if (cls == 0) +		return JNI_ERR; + +	if (env->RegisterNatives(cls, _natives, ARRAYSIZE(_natives)) < 0) +		return JNI_ERR; + +	return JNI_VERSION_1_2; +} + +JNIEnv *JNI::getEnv() { +	JNIEnv *env = 0; + +	jint res = _vm->GetEnv((void **)&env, JNI_VERSION_1_2); + +	if (res != JNI_OK) { +		LOGE("GetEnv() failed: %d", res); +		abort(); +	} + +	return env; +} + +void JNI::attachThread() { +	JNIEnv *env = 0; + +	jint res = _vm->AttachCurrentThread(&env, 0); + +	if (res != JNI_OK) { +		LOGE("AttachCurrentThread() failed: %d", res); +		abort(); +	} +} + +void JNI::detachThread() { +	jint res = _vm->DetachCurrentThread(); + +	if (res != JNI_OK) { +		LOGE("DetachCurrentThread() failed: %d", res); +		abort(); +	} +} + +void JNI::setReadyForEvents(bool ready) { +	_ready_for_events = ready; +} + +void JNI::throwByName(JNIEnv *env, const char *name, const char *msg) { +	jclass cls = env->FindClass(name); + +	// if cls is 0, an exception has already been thrown +	if (cls != 0) +		env->ThrowNew(cls, msg); + +	env->DeleteLocalRef(cls); +} + +void JNI::throwRuntimeException(JNIEnv *env, const char *msg) { +	throwByName(env, "java/lang/RuntimeException", msg); +} + +// calls to the dark side + +void JNI::getDPI(float *values) { +	values[0] = 0.0; +	values[1] = 0.0; + +	JNIEnv *env = JNI::getEnv(); + +	jfloatArray array = env->NewFloatArray(2); + +	env->CallVoidMethod(_jobj, _MID_getDPI, array); + +	if (env->ExceptionCheck()) { +		LOGE("Failed to get DPIs"); + +		env->ExceptionDescribe(); +		env->ExceptionClear(); +	} else { +		jfloat *res = env->GetFloatArrayElements(array, 0); + +		if (res) { +			values[0] = res[0]; +			values[1] = res[1]; + +			env->ReleaseFloatArrayElements(array, res, 0); +		} +	} + +	env->DeleteLocalRef(array); +} + +void JNI::displayMessageOnOSD(const char *msg) { +	JNIEnv *env = JNI::getEnv(); +	jstring java_msg = env->NewStringUTF(msg); + +	env->CallVoidMethod(_jobj, _MID_displayMessageOnOSD, java_msg); + +	if (env->ExceptionCheck()) { +		LOGE("Failed to display OSD message"); + +		env->ExceptionDescribe(); +		env->ExceptionClear(); +	} + +	env->DeleteLocalRef(java_msg); +} + +void JNI::setWindowCaption(const char *caption) { +	JNIEnv *env = JNI::getEnv(); +	jstring java_caption = env->NewStringUTF(caption); + +	env->CallVoidMethod(_jobj, _MID_setWindowCaption, java_caption); + +	if (env->ExceptionCheck()) { +		LOGE("Failed to set window caption"); + +		env->ExceptionDescribe(); +		env->ExceptionClear(); +	} + +	env->DeleteLocalRef(java_caption); +} + +void JNI::showVirtualKeyboard(bool enable) { +	JNIEnv *env = JNI::getEnv(); + +	env->CallVoidMethod(_jobj, _MID_showVirtualKeyboard, enable); + +	if (env->ExceptionCheck()) { +		LOGE("Error trying to show virtual keyboard"); + +		env->ExceptionDescribe(); +		env->ExceptionClear(); +	} +} + +void JNI::addSysArchivesToSearchSet(Common::SearchSet &s, int priority) { +	JNIEnv *env = JNI::getEnv(); + +	s.add("ASSET", _asset_archive, priority, false); + +	jobjectArray array = +		(jobjectArray)env->CallObjectMethod(_jobj, _MID_getSysArchives); + +	if (env->ExceptionCheck()) { +		LOGE("Error finding system archive path"); + +		env->ExceptionDescribe(); +		env->ExceptionClear(); + +		return; +	} + +	jsize size = env->GetArrayLength(array); +	for (jsize i = 0; i < size; ++i) { +		jstring path_obj = (jstring)env->GetObjectArrayElement(array, i); +		const char *path = env->GetStringUTFChars(path_obj, 0); + +		if (path != 0) { +			s.addDirectory(path, path, priority); +			env->ReleaseStringUTFChars(path_obj, path); +		} + +		env->DeleteLocalRef(path_obj); +	} +} + +void JNI::getPluginDirectories(Common::FSList &dirs) { +	JNIEnv *env = JNI::getEnv(); + +	jobjectArray array = +		(jobjectArray)env->CallObjectMethod(_jobj, _MID_getPluginDirectories); + +	if (env->ExceptionCheck()) { +		LOGE("Error finding plugin directories"); + +		env->ExceptionDescribe(); +		env->ExceptionClear(); + +		return; +	} + +	jsize size = env->GetArrayLength(array); +	for (jsize i = 0; i < size; ++i) { +		jstring path_obj = (jstring)env->GetObjectArrayElement(array, i); + +		if (path_obj == 0) +			continue; + +		const char *path = env->GetStringUTFChars(path_obj, 0); + +		if (path == 0) { +			LOGE("Error getting string characters from plugin directory"); + +			env->ExceptionClear(); +			env->DeleteLocalRef(path_obj); + +			continue; +		} + +		dirs.push_back(Common::FSNode(path)); + +		env->ReleaseStringUTFChars(path_obj, path); +		env->DeleteLocalRef(path_obj); +	} +} + +bool JNI::initSurface() { +	JNIEnv *env = JNI::getEnv(); + +	jobject obj = env->CallObjectMethod(_jobj, _MID_initSurface); + +	if (!obj || env->ExceptionCheck()) { +		LOGE("initSurface failed"); + +		env->ExceptionDescribe(); +		env->ExceptionClear(); + +		return false; +	} + +	_jobj_egl_surface = env->NewGlobalRef(obj); + +	return true; +} + +void JNI::deinitSurface() { +	JNIEnv *env = JNI::getEnv(); + +	env->CallVoidMethod(_jobj, _MID_deinitSurface); + +	if (env->ExceptionCheck()) { +		LOGE("deinitSurface failed"); + +		env->ExceptionDescribe(); +		env->ExceptionClear(); +	} + +	env->DeleteGlobalRef(_jobj_egl_surface); +	_jobj_egl_surface = 0; +} + +void JNI::setAudioPause() { +	JNIEnv *env = JNI::getEnv(); + +	env->CallVoidMethod(_jobj_audio_track, _MID_AudioTrack_flush); + +	if (env->ExceptionCheck()) { +		LOGE("Error flushing AudioTrack"); + +		env->ExceptionDescribe(); +		env->ExceptionClear(); +	} + +	env->CallVoidMethod(_jobj_audio_track, _MID_AudioTrack_pause); + +	if (env->ExceptionCheck()) { +		LOGE("Error setting AudioTrack: pause"); + +		env->ExceptionDescribe(); +		env->ExceptionClear(); +	} +} + +void JNI::setAudioPlay() { +	JNIEnv *env = JNI::getEnv(); + +	env->CallVoidMethod(_jobj_audio_track, _MID_AudioTrack_play); + +	if (env->ExceptionCheck()) { +		LOGE("Error setting AudioTrack: play"); + +		env->ExceptionDescribe(); +		env->ExceptionClear(); +	} +} + +void JNI::setAudioStop() { +	JNIEnv *env = JNI::getEnv(); + +	env->CallVoidMethod(_jobj_audio_track, _MID_AudioTrack_stop); + +	if (env->ExceptionCheck()) { +		LOGE("Error setting AudioTrack: stop"); + +		env->ExceptionDescribe(); +		env->ExceptionClear(); +	} +} + +// natives for the dark side + +void JNI::create(JNIEnv *env, jobject self, jobject asset_manager, +				jobject egl, jobject egl_display, +				jobject at, jint audio_sample_rate, jint audio_buffer_size) { +	LOGI(gScummVMFullVersion); + +	assert(!_system); + +	pause = false; +	// initial value of zero! +	sem_init(&pause_sem, 0, 0); + +	_asset_archive = new AndroidAssetArchive(asset_manager); +	assert(_asset_archive); + +	_system = new OSystem_Android(audio_sample_rate, audio_buffer_size); +	assert(_system); + +	// weak global ref to allow class to be unloaded +	// ... except dalvik implements NewWeakGlobalRef only on froyo +	//_jobj = env->NewWeakGlobalRef(self); + +	_jobj = env->NewGlobalRef(self); + +	jclass cls = env->GetObjectClass(_jobj); + +#define FIND_METHOD(prefix, name, signature) do {							\ +		_MID_ ## prefix ## name = env->GetMethodID(cls, #name, signature);	\ +		if (_MID_ ## prefix ## name == 0)									\ +			return;															\ +	} while (0) + +	FIND_METHOD(, setWindowCaption, "(Ljava/lang/String;)V"); +	FIND_METHOD(, getDPI, "([F)V"); +	FIND_METHOD(, displayMessageOnOSD, "(Ljava/lang/String;)V"); +	FIND_METHOD(, showVirtualKeyboard, "(Z)V"); +	FIND_METHOD(, getSysArchives, "()[Ljava/lang/String;"); +	FIND_METHOD(, getPluginDirectories, "()[Ljava/lang/String;"); +	FIND_METHOD(, initSurface, "()Ljavax/microedition/khronos/egl/EGLSurface;"); +	FIND_METHOD(, deinitSurface, "()V"); + +	_jobj_egl = env->NewGlobalRef(egl); +	_jobj_egl_display = env->NewGlobalRef(egl_display); + +	cls = env->GetObjectClass(_jobj_egl); + +	FIND_METHOD(EGL10_, eglSwapBuffers, +				"(Ljavax/microedition/khronos/egl/EGLDisplay;" +				"Ljavax/microedition/khronos/egl/EGLSurface;)Z"); + +	_jobj_audio_track = env->NewGlobalRef(at); + +	cls = env->GetObjectClass(_jobj_audio_track); + +	FIND_METHOD(AudioTrack_, flush, "()V"); +	FIND_METHOD(AudioTrack_, pause, "()V"); +	FIND_METHOD(AudioTrack_, play, "()V"); +	FIND_METHOD(AudioTrack_, stop, "()V"); +	FIND_METHOD(AudioTrack_, write, "([BII)I"); + +#undef FIND_METHOD + +	g_system = _system; +} + +void JNI::destroy(JNIEnv *env, jobject self) { +	delete _asset_archive; +	_asset_archive = 0; + +	delete _system; +	g_system = 0; +	_system = 0; + +	sem_destroy(&pause_sem); + +	// see above +	//JNI::getEnv()->DeleteWeakGlobalRef(_jobj); + +	JNI::getEnv()->DeleteGlobalRef(_jobj_egl_display); +	JNI::getEnv()->DeleteGlobalRef(_jobj_egl); +	JNI::getEnv()->DeleteGlobalRef(_jobj_audio_track); +	JNI::getEnv()->DeleteGlobalRef(_jobj); +} + +void JNI::setSurface(JNIEnv *env, jobject self, jint width, jint height) { +	egl_surface_width = width; +	egl_surface_height = height; +	surface_changeid++; +} + +jint JNI::main(JNIEnv *env, jobject self, jobjectArray args) { +	assert(_system); + +	const int MAX_NARGS = 32; +	int res = -1; + +	int argc = env->GetArrayLength(args); +	if (argc > MAX_NARGS) { +		throwByName(env, "java/lang/IllegalArgumentException", +					"too many arguments"); +		return 0; +	} + +	char *argv[MAX_NARGS]; + +	// note use in cleanup loop below +	int nargs; + +	for (nargs = 0; nargs < argc; ++nargs) { +		jstring arg = (jstring)env->GetObjectArrayElement(args, nargs); + +		if (arg == 0) { +			argv[nargs] = 0; +		} else { +			const char *cstr = env->GetStringUTFChars(arg, 0); + +			argv[nargs] = const_cast<char *>(cstr); + +			// exception already thrown? +			if (cstr == 0) +				goto cleanup; +		} + +		env->DeleteLocalRef(arg); +	} + +#ifdef DYNAMIC_MODULES +	PluginManager::instance().addPluginProvider(new AndroidPluginProvider()); +#endif + +	LOGI("Entering scummvm_main with %d args", argc); + +	res = scummvm_main(argc, argv); + +	LOGI("scummvm_main exited with code %d", res); + +	_system->quit(); + +cleanup: +	nargs--; + +	for (int i = 0; i < nargs; ++i) { +		if (argv[i] == 0) +			continue; + +		jstring arg = (jstring)env->GetObjectArrayElement(args, nargs); + +		// Exception already thrown? +		if (arg == 0) +			return res; + +		env->ReleaseStringUTFChars(arg, argv[i]); +		env->DeleteLocalRef(arg); +	} + +	return res; +} + +void JNI::pushEvent(JNIEnv *env, jobject self, int type, int arg1, int arg2, +					int arg3, int arg4, int arg5) { +	// drop events until we're ready and after we quit +	if (!_ready_for_events) { +		LOGW("dropping event"); +		return; +	} + +	assert(_system); + +	_system->pushEvent(type, arg1, arg2, arg3, arg4, arg5); +} + +void JNI::enableZoning(JNIEnv *env, jobject self, jboolean enable) { +	assert(_system); + +	_system->enableZoning(enable); +} + +void JNI::setPause(JNIEnv *env, jobject self, jboolean value) { +	if (!_system) +		return; + +	if (g_engine) { +		LOGD("pauseEngine: %d", value); + +		g_engine->pauseEngine(value); + +		if (value && +				g_engine->hasFeature(Engine::kSupportsSavingDuringRuntime) && +				g_engine->canSaveGameStateCurrently()) +			g_engine->saveGameState(0, "Android parachute"); +	} + +	pause = value; + +	if (!pause) { +		// wake up all threads +		for (uint i = 0; i < 3; ++i) +			sem_post(&pause_sem); +	} +} + +#endif + diff --git a/backends/platform/android/jni.h b/backends/platform/android/jni.h new file mode 100644 index 0000000000..d029f1a2a8 --- /dev/null +++ b/backends/platform/android/jni.h @@ -0,0 +1,151 @@ +/* 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$ + * + */ + +#ifndef _ANDROID_JNI_H_ +#define _ANDROID_JNI_H_ + +#if defined(__ANDROID__) + +#include <jni.h> +#include <semaphore.h> + +#include "common/fs.h" +#include "common/archive.h" + +class OSystem_Android; + +class JNI { +private: +	JNI(); +	virtual ~JNI(); + +public: +	static bool pause; +	static sem_t pause_sem; + +	static int surface_changeid; +	static int egl_surface_width; +	static int egl_surface_height; + +	static jint onLoad(JavaVM *vm); + +	static JNIEnv *getEnv(); + +	static void attachThread(); +	static void detachThread(); + +	static void setReadyForEvents(bool ready); + +	static void getPluginDirectories(Common::FSList &dirs); +	static void setWindowCaption(const char *caption); +	static void getDPI(float *values); +	static void displayMessageOnOSD(const char *msg); +	static void showVirtualKeyboard(bool enable); +	static void addSysArchivesToSearchSet(Common::SearchSet &s, int priority); + +	static inline bool haveSurface(); +	static inline bool swapBuffers(); +	static bool initSurface(); +	static void deinitSurface(); + +	static void setAudioPause(); +	static void setAudioPlay(); +	static void setAudioStop(); + +	static inline int writeAudio(JNIEnv *env, jbyteArray &data, int offset, +									int size); + +private: +	static JavaVM *_vm; +	// back pointer to (java) peer instance +	static jobject _jobj; +	static jobject _jobj_audio_track; +	static jobject _jobj_egl; +	static jobject _jobj_egl_display; +	static jobject _jobj_egl_surface; + +	static Common::Archive *_asset_archive; +	static OSystem_Android *_system; + +	static bool _ready_for_events; + +	static jmethodID _MID_getDPI; +	static jmethodID _MID_displayMessageOnOSD; +	static jmethodID _MID_setWindowCaption; +	static jmethodID _MID_showVirtualKeyboard; +	static jmethodID _MID_getSysArchives; +	static jmethodID _MID_getPluginDirectories; +	static jmethodID _MID_initSurface; +	static jmethodID _MID_deinitSurface; + +	static jmethodID _MID_EGL10_eglSwapBuffers; + +	static jmethodID _MID_AudioTrack_flush; +	static jmethodID _MID_AudioTrack_pause; +	static jmethodID _MID_AudioTrack_play; +	static jmethodID _MID_AudioTrack_stop; +	static jmethodID _MID_AudioTrack_write; + +	static const JNINativeMethod _natives[]; + +	static void throwByName(JNIEnv *env, const char *name, const char *msg); +	static void throwRuntimeException(JNIEnv *env, const char *msg); + +	// natives for the dark side +	static void create(JNIEnv *env, jobject self, jobject asset_manager, +						jobject egl, jobject egl_display, +						jobject at, jint audio_sample_rate, +						jint audio_buffer_size); +	static void destroy(JNIEnv *env, jobject self); + +	static void setSurface(JNIEnv *env, jobject self, jint width, jint height); +	static jint main(JNIEnv *env, jobject self, jobjectArray args); + +	static void pushEvent(JNIEnv *env, jobject self, int type, int arg1, +							int arg2, int arg3, int arg4, int arg5); +	static void enableZoning(JNIEnv *env, jobject self, jboolean enable); + +	static void setPause(JNIEnv *env, jobject self, jboolean value); +}; + +inline bool JNI::haveSurface() { +	return _jobj_egl_surface != 0; +} + +inline bool JNI::swapBuffers() { +	JNIEnv *env = JNI::getEnv(); + +	return env->CallBooleanMethod(_jobj_egl, _MID_EGL10_eglSwapBuffers, +									_jobj_egl_display, _jobj_egl_surface); +} + +inline int JNI::writeAudio(JNIEnv *env, jbyteArray &data, int offset, int size) { +	return env->CallIntMethod(_jobj_audio_track, _MID_AudioTrack_write, data, +								offset, size); +} + +#endif +#endif + diff --git a/backends/platform/android/module.mk b/backends/platform/android/module.mk index 8b120b21ff..2fe4b40585 100644 --- a/backends/platform/android/module.mk +++ b/backends/platform/android/module.mk @@ -1,9 +1,12 @@  MODULE := backends/platform/android  MODULE_OBJS := \ -	android.o \ +	jni.o \ +	texture.o \  	asset-archive.o \ -	video.o +	android.o \ +	gfx.o \ +	events.o  # We don't use rules.mk but rather manually update OBJS and MODULE_DIRS.  MODULE_OBJS := $(addprefix $(MODULE)/, $(MODULE_OBJS)) diff --git a/backends/platform/android/org/inodes/gus/scummvm/EditableSurfaceView.java b/backends/platform/android/org/inodes/gus/scummvm/EditableSurfaceView.java index 5b71d4a3a5..cede7eedd4 100644 --- a/backends/platform/android/org/inodes/gus/scummvm/EditableSurfaceView.java +++ b/backends/platform/android/org/inodes/gus/scummvm/EditableSurfaceView.java @@ -19,13 +19,13 @@ public class EditableSurfaceView extends SurfaceView {  	}  	public EditableSurfaceView(Context context, AttributeSet attrs, -							   int defStyle) { +								int defStyle) {  		super(context, attrs, defStyle);  	}  	@Override  	public boolean onCheckIsTextEditor() { -		return true; +		return false;  	}  	private class MyInputConnection extends BaseInputConnection { @@ -40,7 +40,9 @@ public class EditableSurfaceView extends SurfaceView {  					getContext().getSystemService(Context.INPUT_METHOD_SERVICE);  				imm.hideSoftInputFromWindow(getWindowToken(), 0);  			} -			return super.performEditorAction(actionCode); // Sends enter key + +			// Sends enter key +			return super.performEditorAction(actionCode);  		}  	} @@ -49,11 +51,12 @@ public class EditableSurfaceView extends SurfaceView {  		outAttrs.initialCapsMode = 0;  		outAttrs.initialSelEnd = outAttrs.initialSelStart = -1;  		outAttrs.inputType = (InputType.TYPE_CLASS_TEXT | -							  InputType.TYPE_TEXT_VARIATION_NORMAL | -							  InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE); +								InputType.TYPE_TEXT_VARIATION_NORMAL | +								InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE);  		outAttrs.imeOptions = (EditorInfo.IME_ACTION_DONE | -							   EditorInfo.IME_FLAG_NO_EXTRACT_UI); +								EditorInfo.IME_FLAG_NO_EXTRACT_UI);  		return new MyInputConnection();  	}  } + diff --git a/backends/platform/android/org/inodes/gus/scummvm/Event.java b/backends/platform/android/org/inodes/gus/scummvm/Event.java deleted file mode 100644 index f9c7aba93b..0000000000 --- a/backends/platform/android/org/inodes/gus/scummvm/Event.java +++ /dev/null @@ -1,330 +0,0 @@ -package org.inodes.gus.scummvm; - -import android.view.KeyEvent; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -public class Event { -	// Common::EventType enum. -	// Must be kept in sync with common/events.h -	public final static int EVENT_INVALID = 0; -	public final static int EVENT_KEYDOWN = 1; -	public final static int EVENT_KEYUP = 2; -	public final static int EVENT_MOUSEMOVE = 3; -	public final static int EVENT_LBUTTONDOWN = 4; -	public final static int EVENT_LBUTTONUP = 5; -	public final static int EVENT_RBUTTONDOWN = 6; -	public final static int EVENT_RBUTTONUP = 7; -	public final static int EVENT_WHEELUP = 8; -	public final static int EVENT_WHEELDOWN = 9; -	public final static int EVENT_QUIT = 10; -	public final static int EVENT_SCREEN_CHANGED = 11; -	public final static int EVENT_PREDICTIVE_DIALOG = 12; -	public final static int EVENT_MBUTTONDOWN = 13; -	public final static int EVENT_MBUTTONUP = 14; -	public final static int EVENT_MAINMENU = 15; -	public final static int EVENT_RTL = 16; - -	// common/keyboard.h -	public final static int ASCII_F1 = 315; -	public final static int ASCII_F2 = 316; -	public final static int ASCII_F3 = 317; -	public final static int ASCII_F4 = 318; -	public final static int ASCII_F5 = 319; -	public final static int ASCII_F6 = 320; -	public final static int ASCII_F7 = 321; -	public final static int ASCII_F8 = 322; -	public final static int ASCII_F9 = 323; -	public final static int ASCII_F10 = 324; -	public final static int ASCII_F11 = 325; -	public final static int ASCII_F12 = 326; -	public final static int KBD_CTRL  = 1 << 0; -	public final static int KBD_ALT	  = 1 << 1; -	public final static int KBD_SHIFT = 1 << 2; - -	public final static int KEYCODE_INVALID = 0; -	public final static int KEYCODE_BACKSPACE = 8; -	public final static int KEYCODE_TAB = 9; -	public final static int KEYCODE_CLEAR = 12; -	public final static int KEYCODE_RETURN = 13; -	public final static int KEYCODE_PAUSE = 19; -	public final static int KEYCODE_ESCAPE = 27; -	public final static int KEYCODE_SPACE = 32; -	public final static int KEYCODE_EXCLAIM = 33; -	public final static int KEYCODE_QUOTEDBL = 34; -	public final static int KEYCODE_HASH = 35; -	public final static int KEYCODE_DOLLAR = 36; -	public final static int KEYCODE_AMPERSAND = 38; -	public final static int KEYCODE_QUOTE = 39; -	public final static int KEYCODE_LEFTPAREN = 40; -	public final static int KEYCODE_RIGHTPAREN = 41; -	public final static int KEYCODE_ASTERISK = 42; -	public final static int KEYCODE_PLUS = 43; -	public final static int KEYCODE_COMMA = 44; -	public final static int KEYCODE_MINUS = 45; -	public final static int KEYCODE_PERIOD = 46; -	public final static int KEYCODE_SLASH = 47; -	public final static int KEYCODE_0 = 48; -	public final static int KEYCODE_1 = 49; -	public final static int KEYCODE_2 = 50; -	public final static int KEYCODE_3 = 51; -	public final static int KEYCODE_4 = 52; -	public final static int KEYCODE_5 = 53; -	public final static int KEYCODE_6 = 54; -	public final static int KEYCODE_7 = 55; -	public final static int KEYCODE_8 = 56; -	public final static int KEYCODE_9 = 57; -	public final static int KEYCODE_COLON = 58; -	public final static int KEYCODE_SEMICOLON = 59; -	public final static int KEYCODE_LESS = 60; -	public final static int KEYCODE_EQUALS = 61; -	public final static int KEYCODE_GREATER = 62; -	public final static int KEYCODE_QUESTION = 63; -	public final static int KEYCODE_AT = 64; -	public final static int KEYCODE_LEFTBRACKET = 91; -	public final static int KEYCODE_BACKSLASH = 92; -	public final static int KEYCODE_RIGHTBRACKET = 93; -	public final static int KEYCODE_CARET = 94; -	public final static int KEYCODE_UNDERSCORE = 95; -	public final static int KEYCODE_BACKQUOTE = 96; -	public final static int KEYCODE_a = 97; -	public final static int KEYCODE_b = 98; -	public final static int KEYCODE_c = 99; -	public final static int KEYCODE_d = 100; -	public final static int KEYCODE_e = 101; -	public final static int KEYCODE_f = 102; -	public final static int KEYCODE_g = 103; -	public final static int KEYCODE_h = 104; -	public final static int KEYCODE_i = 105; -	public final static int KEYCODE_j = 106; -	public final static int KEYCODE_k = 107; -	public final static int KEYCODE_l = 108; -	public final static int KEYCODE_m = 109; -	public final static int KEYCODE_n = 110; -	public final static int KEYCODE_o = 111; -	public final static int KEYCODE_p = 112; -	public final static int KEYCODE_q = 113; -	public final static int KEYCODE_r = 114; -	public final static int KEYCODE_s = 115; -	public final static int KEYCODE_t = 116; -	public final static int KEYCODE_u = 117; -	public final static int KEYCODE_v = 118; -	public final static int KEYCODE_w = 119; -	public final static int KEYCODE_x = 120; -	public final static int KEYCODE_y = 121; -	public final static int KEYCODE_z = 122; -	public final static int KEYCODE_DELETE = 127; -	// Numeric keypad -	public final static int KEYCODE_KP0 = 256; -	public final static int KEYCODE_KP1 = 257; -	public final static int KEYCODE_KP2 = 258; -	public final static int KEYCODE_KP3 = 259; -	public final static int KEYCODE_KP4 = 260; -	public final static int KEYCODE_KP5 = 261; -	public final static int KEYCODE_KP6 = 262; -	public final static int KEYCODE_KP7 = 263; -	public final static int KEYCODE_KP8 = 264; -	public final static int KEYCODE_KP9 = 265; -	public final static int KEYCODE_KP_PERIOD = 266; -	public final static int KEYCODE_KP_DIVIDE = 267; -	public final static int KEYCODE_KP_MULTIPLY = 268; -	public final static int KEYCODE_KP_MINUS = 269; -	public final static int KEYCODE_KP_PLUS = 270; -	public final static int KEYCODE_KP_ENTER = 271; -	public final static int KEYCODE_KP_EQUALS = 272; -	// Arrows + Home/End pad -	public final static int KEYCODE_UP = 273; -	public final static int KEYCODE_DOWN = 274; -	public final static int KEYCODE_RIGHT = 275; -	public final static int KEYCODE_LEFT = 276; -	public final static int KEYCODE_INSERT = 277; -	public final static int KEYCODE_HOME = 278; -	public final static int KEYCODE_END = 279; -	public final static int KEYCODE_PAGEUP = 280; -	public final static int KEYCODE_PAGEDOWN = 281; -	// Function keys -	public final static int KEYCODE_F1 = 282; -	public final static int KEYCODE_F2 = 283; -	public final static int KEYCODE_F3 = 284; -	public final static int KEYCODE_F4 = 285; -	public final static int KEYCODE_F5 = 286; -	public final static int KEYCODE_F6 = 287; -	public final static int KEYCODE_F7 = 288; -	public final static int KEYCODE_F8 = 289; -	public final static int KEYCODE_F9 = 290; -	public final static int KEYCODE_F10 = 291; -	public final static int KEYCODE_F11 = 292; -	public final static int KEYCODE_F12 = 293; -	public final static int KEYCODE_F13 = 294; -	public final static int KEYCODE_F14 = 295; -	public final static int KEYCODE_F15 = 296; -	// Key state modifier keys -	public final static int KEYCODE_NUMLOCK = 300; -	public final static int KEYCODE_CAPSLOCK = 301; -	public final static int KEYCODE_SCROLLOCK = 302; -	public final static int KEYCODE_RSHIFT = 303; -	public final static int KEYCODE_LSHIFT = 304; -	public final static int KEYCODE_RCTRL = 305; -	public final static int KEYCODE_LCTRL = 306; -	public final static int KEYCODE_RALT = 307; -	public final static int KEYCODE_LALT = 308; -	public final static int KEYCODE_RMETA = 309; -	public final static int KEYCODE_LMETA = 310; -	public final static int KEYCODE_LSUPER = 311; // Left "Windows" key -	public final static int KEYCODE_RSUPER = 312; // Right "Windows" key -	public final static int KEYCODE_MODE = 313; // "Alt Gr" key -	public final static int KEYCODE_COMPOSE = 314; // Multi-key compose key -	// Miscellaneous function keys -	public final static int KEYCODE_HELP = 315; -	public final static int KEYCODE_PRINT = 316; -	public final static int KEYCODE_SYSREQ = 317; -	public final static int KEYCODE_BREAK = 318; -	public final static int KEYCODE_MENU = 319; -	public final static int KEYCODE_POWER = 320; // Power Macintosh power key -	public final static int KEYCODE_EURO = 321; // Some european keyboards -	public final static int KEYCODE_UNDO = 322; // Atari keyboard has Undo - -	// Android KeyEvent keycode -> ScummVM keycode -	public final static Map<Integer, Integer> androidKeyMap; -	static { -		Map<Integer, Integer> map = new HashMap<Integer, Integer>(); - -		map.put(KeyEvent.KEYCODE_DEL, KEYCODE_BACKSPACE); -		map.put(KeyEvent.KEYCODE_TAB, KEYCODE_TAB); -		map.put(KeyEvent.KEYCODE_CLEAR, KEYCODE_CLEAR); -		map.put(KeyEvent.KEYCODE_ENTER, KEYCODE_RETURN); -		//map.put(??, KEYCODE_PAUSE); -		map.put(KeyEvent.KEYCODE_BACK, KEYCODE_ESCAPE); -		map.put(KeyEvent.KEYCODE_SPACE, KEYCODE_SPACE); -		//map.put(??, KEYCODE_EXCLAIM); -		//map.put(??, KEYCODE_QUOTEDBL); -		map.put(KeyEvent.KEYCODE_POUND, KEYCODE_HASH); -		//map.put(??, KEYCODE_DOLLAR); -		//map.put(??, KEYCODE_AMPERSAND); -		map.put(KeyEvent.KEYCODE_APOSTROPHE, KEYCODE_QUOTE); -		//map.put(??, KEYCODE_LEFTPAREN); -		//map.put(??, KEYCODE_RIGHTPAREN); -		//map.put(??, KEYCODE_ASTERISK); -		map.put(KeyEvent.KEYCODE_PLUS, KEYCODE_PLUS); -		map.put(KeyEvent.KEYCODE_COMMA, KEYCODE_COMMA); -		map.put(KeyEvent.KEYCODE_MINUS, KEYCODE_MINUS); -		map.put(KeyEvent.KEYCODE_PERIOD, KEYCODE_PERIOD); -		map.put(KeyEvent.KEYCODE_SLASH, KEYCODE_SLASH); -		map.put(KeyEvent.KEYCODE_0, KEYCODE_0); -		map.put(KeyEvent.KEYCODE_1, KEYCODE_1); -		map.put(KeyEvent.KEYCODE_2, KEYCODE_2); -		map.put(KeyEvent.KEYCODE_3, KEYCODE_3); -		map.put(KeyEvent.KEYCODE_4, KEYCODE_4); -		map.put(KeyEvent.KEYCODE_5, KEYCODE_5); -		map.put(KeyEvent.KEYCODE_6, KEYCODE_6); -		map.put(KeyEvent.KEYCODE_7, KEYCODE_7); -		map.put(KeyEvent.KEYCODE_8, KEYCODE_8); -		map.put(KeyEvent.KEYCODE_9, KEYCODE_9); -		//map.put(??, KEYCODE_COLON); -		map.put(KeyEvent.KEYCODE_SEMICOLON, KEYCODE_SEMICOLON); -		//map.put(??, KEYCODE_LESS); -		map.put(KeyEvent.KEYCODE_EQUALS, KEYCODE_EQUALS); -		//map.put(??, KEYCODE_GREATER); -		//map.put(??, KEYCODE_QUESTION); -		map.put(KeyEvent.KEYCODE_AT, KEYCODE_AT); -		map.put(KeyEvent.KEYCODE_LEFT_BRACKET, KEYCODE_LEFTBRACKET); -		map.put(KeyEvent.KEYCODE_BACKSLASH, KEYCODE_BACKSLASH); -		map.put(KeyEvent.KEYCODE_RIGHT_BRACKET, KEYCODE_RIGHTBRACKET); -		//map.put(??, KEYCODE_CARET); -		//map.put(??, KEYCODE_UNDERSCORE); -		//map.put(??, KEYCODE_BACKQUOTE); -		map.put(KeyEvent.KEYCODE_A, KEYCODE_a); -		map.put(KeyEvent.KEYCODE_B, KEYCODE_b); -		map.put(KeyEvent.KEYCODE_C, KEYCODE_c); -		map.put(KeyEvent.KEYCODE_D, KEYCODE_d); -		map.put(KeyEvent.KEYCODE_E, KEYCODE_e); -		map.put(KeyEvent.KEYCODE_F, KEYCODE_f); -		map.put(KeyEvent.KEYCODE_G, KEYCODE_g); -		map.put(KeyEvent.KEYCODE_H, KEYCODE_h); -		map.put(KeyEvent.KEYCODE_I, KEYCODE_i); -		map.put(KeyEvent.KEYCODE_J, KEYCODE_j); -		map.put(KeyEvent.KEYCODE_K, KEYCODE_k); -		map.put(KeyEvent.KEYCODE_L, KEYCODE_l); -		map.put(KeyEvent.KEYCODE_M, KEYCODE_m); -		map.put(KeyEvent.KEYCODE_N, KEYCODE_n); -		map.put(KeyEvent.KEYCODE_O, KEYCODE_o); -		map.put(KeyEvent.KEYCODE_P, KEYCODE_p); -		map.put(KeyEvent.KEYCODE_Q, KEYCODE_q); -		map.put(KeyEvent.KEYCODE_R, KEYCODE_r); -		map.put(KeyEvent.KEYCODE_S, KEYCODE_s); -		map.put(KeyEvent.KEYCODE_T, KEYCODE_t); -		map.put(KeyEvent.KEYCODE_U, KEYCODE_u); -		map.put(KeyEvent.KEYCODE_V, KEYCODE_v); -		map.put(KeyEvent.KEYCODE_W, KEYCODE_w); -		map.put(KeyEvent.KEYCODE_X, KEYCODE_x); -		map.put(KeyEvent.KEYCODE_Y, KEYCODE_y); -		map.put(KeyEvent.KEYCODE_Z, KEYCODE_z); -		//map.put(KeyEvent.KEYCODE_DEL, KEYCODE_DELETE); use BACKSPACE instead -		//map.put(??, KEYCODE_KP_*); -		map.put(KeyEvent.KEYCODE_DPAD_UP, KEYCODE_UP); -		map.put(KeyEvent.KEYCODE_DPAD_DOWN, KEYCODE_DOWN); -		map.put(KeyEvent.KEYCODE_DPAD_RIGHT, KEYCODE_RIGHT); -		map.put(KeyEvent.KEYCODE_DPAD_LEFT, KEYCODE_LEFT); -		//map.put(??, KEYCODE_INSERT); -		//map.put(??, KEYCODE_HOME); -		//map.put(??, KEYCODE_END); -		//map.put(??, KEYCODE_PAGEUP); -		//map.put(??, KEYCODE_PAGEDOWN); -		//map.put(??, KEYCODE_F{1-15}); -		map.put(KeyEvent.KEYCODE_NUM, KEYCODE_NUMLOCK); -		//map.put(??, KEYCODE_CAPSLOCK); -		//map.put(??, KEYCODE_SCROLLLOCK); -		map.put(KeyEvent.KEYCODE_SHIFT_RIGHT, KEYCODE_RSHIFT); -		map.put(KeyEvent.KEYCODE_SHIFT_LEFT, KEYCODE_LSHIFT); -		//map.put(??, KEYCODE_RCTRL); -		//map.put(??, KEYCODE_LCTRL); -		map.put(KeyEvent.KEYCODE_ALT_RIGHT, KEYCODE_RALT); -		map.put(KeyEvent.KEYCODE_ALT_LEFT, KEYCODE_LALT); -		// ?? META, SUPER -		// ?? MODE, COMPOSE -		// ?? HELP, PRINT, SYSREQ, BREAK, EURO, UNDO -		map.put(KeyEvent.KEYCODE_MENU, KEYCODE_MENU); -		map.put(KeyEvent.KEYCODE_POWER, KEYCODE_POWER); - -		androidKeyMap = Collections.unmodifiableMap(map); -	} - -	public int type; -	public boolean synthetic; -	public int kbd_keycode; -	public int kbd_ascii; -	public int kbd_flags; -	public int mouse_x; -	public int mouse_y; -	public boolean mouse_relative;	// Used for trackball events - -	public Event() { -		type = EVENT_INVALID; -		synthetic = false; -	} - -	public Event(int type) { -		this.type = type; -		synthetic = false; -	} - -	public static Event KeyboardEvent(int type, int keycode, int ascii, -									  int flags) { -		Event e = new Event(); -		e.type = type; -		e.kbd_keycode = keycode; -		e.kbd_ascii = ascii; -		e.kbd_flags = flags; -		return e; -	} - -	public static Event MouseEvent(int type, int x, int y) { -		Event e = new Event(); -		e.type = type; -		e.mouse_x = x; -		e.mouse_y = y; -		return e; -	} -} diff --git a/backends/platform/android/org/inodes/gus/scummvm/PluginProvider.java b/backends/platform/android/org/inodes/gus/scummvm/PluginProvider.java index c94ab0a3ff..3c91d9f5dc 100644 --- a/backends/platform/android/org/inodes/gus/scummvm/PluginProvider.java +++ b/backends/platform/android/org/inodes/gus/scummvm/PluginProvider.java @@ -28,7 +28,7 @@ public class PluginProvider extends BroadcastReceiver {  		try {  			info = context.getPackageManager()  				.getReceiverInfo(new ComponentName(context, this.getClass()), -								 PackageManager.GET_META_DATA); +									PackageManager.GET_META_DATA);  		} catch (PackageManager.NameNotFoundException e) {  			Log.e(LOG_TAG, "Error finding my own info?", e);  			return; @@ -38,17 +38,17 @@ public class PluginProvider extends BroadcastReceiver {  		if (mylib != null) {  			ArrayList<String> all_libs =  				extras.getStringArrayList(ScummVMApplication.EXTRA_UNPACK_LIBS); -  			all_libs.add(new Uri.Builder() -						 .scheme("plugin") -						 .authority(context.getPackageName()) -						 .path(mylib) -						 .toString()); +							.scheme("plugin") +							.authority(context.getPackageName()) +							.path(mylib) +							.toString());  			extras.putStringArrayList(ScummVMApplication.EXTRA_UNPACK_LIBS, -									  all_libs); +										all_libs);  		}  		setResultExtras(extras);  	}  } + diff --git a/backends/platform/android/org/inodes/gus/scummvm/ScummVM.java b/backends/platform/android/org/inodes/gus/scummvm/ScummVM.java index 0e905f43a5..c4de6d62f8 100644 --- a/backends/platform/android/org/inodes/gus/scummvm/ScummVM.java +++ b/backends/platform/android/org/inodes/gus/scummvm/ScummVM.java @@ -1,446 +1,435 @@  package org.inodes.gus.scummvm; -import android.content.Context; +import android.util.Log;  import android.content.res.AssetManager; +import android.view.SurfaceHolder;  import android.media.AudioFormat;  import android.media.AudioManager;  import android.media.AudioTrack; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Process; -import android.util.Log; -import android.view.Surface; -import android.view.SurfaceHolder; -import javax.microedition.khronos.opengles.GL;  import javax.microedition.khronos.opengles.GL10;  import javax.microedition.khronos.egl.EGL10; -import javax.microedition.khronos.egl.EGL11;  import javax.microedition.khronos.egl.EGLConfig;  import javax.microedition.khronos.egl.EGLContext;  import javax.microedition.khronos.egl.EGLDisplay;  import javax.microedition.khronos.egl.EGLSurface;  import java.io.File; -import java.util.concurrent.Semaphore; -import java.util.Map;  import java.util.LinkedHashMap; +public abstract class ScummVM implements SurfaceHolder.Callback, Runnable { +	final protected static String LOG_TAG = "ScummVM"; +	final private AssetManager _asset_manager; +	final private Object _sem_surface; + +	private EGL10 _egl; +	private EGLDisplay _egl_display = EGL10.EGL_NO_DISPLAY; +	private EGLConfig _egl_config; +	private EGLContext _egl_context = EGL10.EGL_NO_CONTEXT; +	private EGLSurface _egl_surface = EGL10.EGL_NO_SURFACE; + +	private SurfaceHolder _surface_holder; +	private AudioTrack _audio_track; +	private int _sample_rate = 0; +	private int _buffer_size = 0; + +	private String[] _args; + +	final private native void create(AssetManager asset_manager, +										EGL10 egl, EGLDisplay egl_display, +										AudioTrack audio_track, +										int sample_rate, int buffer_size); +	final private native void destroy(); +	final private native void setSurface(int width, int height); +	final private native int main(String[] args); + +	// pause the engine and all native threads +	final public native void setPause(boolean pause); +	final public native void enableZoning(boolean enable); +	// Feed an event to ScummVM.  Safe to call from other threads. +	final public native void pushEvent(int type, int arg1, int arg2, int arg3, +										int arg4, int arg5); -// At least in Android 2.1, eglCreateWindowSurface() requires an -// EGLNativeWindowSurface object, which is hidden deep in the bowels -// of libui.  Until EGL is properly exposed, it's probably safer to -// use the Java versions of most EGL functions :( - -public class ScummVM implements SurfaceHolder.Callback { -	protected final static String LOG_TAG = "ScummVM"; +	// Callbacks from C++ peer instance +	abstract protected void getDPI(float[] values); +	abstract protected void displayMessageOnOSD(String msg); +	abstract protected void setWindowCaption(String caption); +	abstract protected String[] getPluginDirectories(); +	abstract protected void showVirtualKeyboard(boolean enable); +	abstract protected String[] getSysArchives(); -	private final int AUDIO_FRAME_SIZE = 2 * 2;	 // bytes. 16bit audio * stereo -	public static class AudioSetupException extends Exception {} +	public ScummVM(AssetManager asset_manager, SurfaceHolder holder) { +		_asset_manager = asset_manager; +		_sem_surface = new Object(); -	private long nativeScummVM;	// native code hangs itself here -	boolean scummVMRunning = false; +		holder.addCallback(this); +	} -	private native void create(AssetManager am); +	// SurfaceHolder callback +	final public void surfaceCreated(SurfaceHolder holder) { +		Log.d(LOG_TAG, "surfaceCreated"); -	public ScummVM(Context context) { -		create(context.getAssets());  // Init C++ code, set nativeScummVM +		// no need to do anything, surfaceChanged() will be called in any case  	} -	private native void nativeDestroy(); +	// SurfaceHolder callback +	final public void surfaceChanged(SurfaceHolder holder, int format, +										int width, int height) { +		// the orientation may reset on standby mode and the theme manager +		// could assert when using a portrait resolution. so lets not do that. +		if (height > width) { +			Log.d(LOG_TAG, String.format("Ignoring surfaceChanged: %dx%d (%d)", +											width, height, format)); +			return; +		} + +		Log.d(LOG_TAG, String.format("surfaceChanged: %dx%d (%d)", +										width, height, format)); -	public synchronized void destroy() { -		if (nativeScummVM != 0) { -			nativeDestroy(); -			nativeScummVM = 0; +		synchronized(_sem_surface) { +			_surface_holder = holder; +			_sem_surface.notifyAll();  		} -	} -	protected void finalize() { -		destroy(); -	} -	// Surface creation: -	// GUI thread: create surface, release lock -	// ScummVM thread: acquire lock (block), read surface -	// -	// Surface deletion: -	// GUI thread: post event, acquire lock (block), return -	// ScummVM thread: read event, free surface, release lock -	// -	// In other words, ScummVM thread does this: -	//	acquire lock -	//	setup surface -	//	when SCREEN_CHANGED arrives: -	//	 destroy surface -	//	 release lock -	//	back to acquire lock -	static final int configSpec[] = { -		EGL10.EGL_RED_SIZE, 5, -		EGL10.EGL_GREEN_SIZE, 5, -		EGL10.EGL_BLUE_SIZE, 5, -		EGL10.EGL_DEPTH_SIZE, 0, -		EGL10.EGL_SURFACE_TYPE, EGL10.EGL_WINDOW_BIT, -		EGL10.EGL_NONE, -	}; -	EGL10 egl; -	EGLDisplay eglDisplay = EGL10.EGL_NO_DISPLAY; -	EGLConfig eglConfig; -	EGLContext eglContext = EGL10.EGL_NO_CONTEXT; -	EGLSurface eglSurface = EGL10.EGL_NO_SURFACE; -	Semaphore surfaceLock = new Semaphore(0, true); -	SurfaceHolder nativeSurface; - -	public void surfaceCreated(SurfaceHolder holder) { -		nativeSurface = holder; -		surfaceLock.release(); +		// store values for the native code +		setSurface(width, height);  	} -	public void surfaceChanged(SurfaceHolder holder, int format, -							   int width, int height) { -		// Disabled while I debug GL problems -		pushEvent(new Event(Event.EVENT_SCREEN_CHANGED)); -	} +	// SurfaceHolder callback +	final public void surfaceDestroyed(SurfaceHolder holder) { +		Log.d(LOG_TAG, "surfaceDestroyed"); -	public void surfaceDestroyed(SurfaceHolder holder) { -		try { -			surfaceLock.acquire(); -		} catch (InterruptedException e) { -			Log.e(LOG_TAG, "Interrupted while waiting for surface lock", e); +		synchronized(_sem_surface) { +			_surface_holder = null; +			_sem_surface.notifyAll();  		} + +		// clear values for the native code +		setSurface(0, 0);  	} -	// For debugging -	private static final Map<String, Integer> attribs; -	static { -		attribs = new LinkedHashMap<String, Integer>(); -		attribs.put("CONFIG_ID", EGL10.EGL_CONFIG_ID); -		attribs.put("BUFFER_SIZE", EGL10.EGL_BUFFER_SIZE); -		attribs.put("RED_SIZE", EGL10.EGL_RED_SIZE); -		attribs.put("GREEN_SIZE", EGL10.EGL_GREEN_SIZE); -		attribs.put("BLUE_SIZE", EGL10.EGL_BLUE_SIZE); -		attribs.put("ALPHA_SIZE", EGL10.EGL_ALPHA_SIZE); -		//attribs.put("BIND_TO_RGB", EGL10.EGL_BIND_TO_TEXTURE_RGB); -		//attribs.put("BIND_TO_RGBA", EGL10.EGL_BIND_TO_TEXTURE_RGBA); -		attribs.put("CONFIG_CAVEAT", EGL10.EGL_CONFIG_CAVEAT); -		attribs.put("DEPTH_SIZE", EGL10.EGL_DEPTH_SIZE); -		attribs.put("LEVEL", EGL10.EGL_LEVEL); -		attribs.put("MAX_PBUFFER_WIDTH", EGL10.EGL_MAX_PBUFFER_WIDTH); -		attribs.put("MAX_PBUFFER_HEIGHT", EGL10.EGL_MAX_PBUFFER_HEIGHT); -		attribs.put("MAX_PBUFFER_PIXELS", EGL10.EGL_MAX_PBUFFER_PIXELS); -		//attribs.put("MAX_SWAP_INTERVAL", EGL10.EGL_MAX_SWAP_INTERVAL); -		//attribs.put("MIN_SWAP_INTERVAL", EGL10.EGL_MIN_SWAP_INTERVAL); -		attribs.put("NATIVE_RENDERABLE", EGL10.EGL_NATIVE_RENDERABLE); -		attribs.put("NATIVE_VISUAL_ID", EGL10.EGL_NATIVE_VISUAL_ID); -		attribs.put("NATIVE_VISUAL_TYPE", EGL10.EGL_NATIVE_VISUAL_TYPE); -		attribs.put("SAMPLE_BUFFERS", EGL10.EGL_SAMPLE_BUFFERS); -		attribs.put("SAMPLES", EGL10.EGL_SAMPLES); -		attribs.put("STENCIL_SIZE", EGL10.EGL_STENCIL_SIZE); -		attribs.put("SURFACE_TYPE", EGL10.EGL_SURFACE_TYPE); -		attribs.put("TRANSPARENT_TYPE", EGL10.EGL_TRANSPARENT_TYPE); -		attribs.put("TRANSPARENT_RED_VALUE", EGL10.EGL_TRANSPARENT_RED_VALUE); -		attribs.put("TRANSPARENT_GREEN_VALUE", EGL10.EGL_TRANSPARENT_GREEN_VALUE); -		attribs.put("TRANSPARENT_BLUE_VALUE", EGL10.EGL_TRANSPARENT_BLUE_VALUE); +	final public void setArgs(String[] args) { +		_args = args;  	} -	private void dumpEglConfig(EGLConfig config) { -		int[] value = new int[1]; -		for (Map.Entry<String, Integer> entry : attribs.entrySet()) { -			egl.eglGetConfigAttrib(eglDisplay, config, -								   entry.getValue(), value); -			if (value[0] == EGL10.EGL_NONE) -				Log.d(LOG_TAG, entry.getKey() + ": NONE"); -			else -				Log.d(LOG_TAG, String.format("%s: %d", entry.getKey(), value[0])); + +	final public void run() { +		try { +			initAudio(); +			initEGL(); + +			// wait for the surfaceChanged callback +			synchronized(_sem_surface) { +				while (_surface_holder == null) +					_sem_surface.wait(); +			} +		} catch (Exception e) { +			deinitEGL(); +			deinitAudio(); + +			throw new RuntimeException("Error preparing the ScummVM thread", e);  		} + +		create(_asset_manager, _egl, _egl_display, +				_audio_track, _sample_rate, _buffer_size); + +		int res = main(_args); + +		destroy(); + +		deinitEGL(); +		deinitAudio(); + +		// On exit, tear everything down for a fresh restart next time. +		System.exit(res);  	} -	// Called by ScummVM thread (from initBackend) -	private void createScummVMGLContext() { -		egl = (EGL10)EGLContext.getEGL(); -		eglDisplay = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); +	final private void initEGL() throws Exception { +		_egl = (EGL10)EGLContext.getEGL(); +		_egl_display = _egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); +  		int[] version = new int[2]; -		egl.eglInitialize(eglDisplay, version); +		_egl.eglInitialize(_egl_display, version); +  		int[] num_config = new int[1]; -		egl.eglChooseConfig(eglDisplay, configSpec, null, 0, num_config); +		_egl.eglGetConfigs(_egl_display, null, 0, num_config);  		final int numConfigs = num_config[0]; +  		if (numConfigs <= 0) -			throw new IllegalArgumentException("No configs match configSpec"); +			throw new IllegalArgumentException("No EGL configs");  		EGLConfig[] configs = new EGLConfig[numConfigs]; -		egl.eglChooseConfig(eglDisplay, configSpec, configs, numConfigs, -							num_config); - -		if (false) { -			Log.d(LOG_TAG, String.format("Found %d EGL configurations.", numConfigs)); -			for (EGLConfig config : configs) -				dumpEglConfig(config); -		} +		_egl.eglGetConfigs(_egl_display, configs, numConfigs, num_config);  		// Android's eglChooseConfig is busted in several versions and -		// devices so we have to filter/rank the configs again ourselves. -		eglConfig = chooseEglConfig(configs); -		if (false) { -			Log.d(LOG_TAG, String.format("Chose EGL config from %d possibilities.", numConfigs)); -			dumpEglConfig(eglConfig); -		} +		// devices so we have to filter/rank the configs ourselves. +		_egl_config = chooseEglConfig(configs); -		eglContext = egl.eglCreateContext(eglDisplay, eglConfig, -										  EGL10.EGL_NO_CONTEXT, null); -		if (eglContext == EGL10.EGL_NO_CONTEXT) -			throw new RuntimeException("Failed to create context"); -	} +		_egl_context = _egl.eglCreateContext(_egl_display, _egl_config, +											EGL10.EGL_NO_CONTEXT, null); -	private EGLConfig chooseEglConfig(EGLConfig[] configs) { -		int best = 0; -		int bestScore = -1; -		int[] value = new int[1]; +		if (_egl_context == EGL10.EGL_NO_CONTEXT) +			throw new Exception(String.format("Failed to create context: 0x%x", +												_egl.eglGetError())); +	} -		for (int i = 0; i < configs.length; i++) { -			EGLConfig config = configs[i]; -			int score = 10000; -			egl.eglGetConfigAttrib(eglDisplay, config, -								   EGL10.EGL_SURFACE_TYPE, value); -			if ((value[0] & EGL10.EGL_WINDOW_BIT) == 0) -				continue;  // must have - -			egl.eglGetConfigAttrib(eglDisplay, config, -								   EGL10.EGL_CONFIG_CAVEAT, value); -			if (value[0] != EGL10.EGL_NONE) -				score -= 1000; +	// Callback from C++ peer instance +	final protected EGLSurface initSurface() throws Exception { +		_egl_surface = _egl.eglCreateWindowSurface(_egl_display, _egl_config, +													_surface_holder, null); -			// Must be at least 555, but then smaller is better -			final int[] colorBits = {EGL10.EGL_RED_SIZE, -									 EGL10.EGL_GREEN_SIZE, -									 EGL10.EGL_BLUE_SIZE, -									 EGL10.EGL_ALPHA_SIZE}; -			for (int component : colorBits) { -				egl.eglGetConfigAttrib(eglDisplay, config, -									   component, value); -				if (value[0] >= 5) -					score += 10;   // boost if >5 bits accuracy -				score -= value[0]; // penalize for wasted bits -			} +		if (_egl_surface == EGL10.EGL_NO_SURFACE) +			throw new Exception(String.format( +					"eglCreateWindowSurface failed: 0x%x", _egl.eglGetError())); -			egl.eglGetConfigAttrib(eglDisplay, config, -								   EGL10.EGL_DEPTH_SIZE, value); -			score -= value[0];  // penalize for wasted bits +		_egl.eglMakeCurrent(_egl_display, _egl_surface, _egl_surface, +							_egl_context); -			if (score > bestScore) { -				best = i; -				bestScore = score; -			} -		} +		GL10 gl = (GL10)_egl_context.getGL(); -		if (bestScore < 0) { -			Log.e(LOG_TAG, "Unable to find an acceptable EGL config, expect badness."); -			return configs[0]; -		} +		Log.i(LOG_TAG, String.format("Using EGL %s (%s); GL %s/%s (%s)", +						_egl.eglQueryString(_egl_display, EGL10.EGL_VERSION), +						_egl.eglQueryString(_egl_display, EGL10.EGL_VENDOR), +						gl.glGetString(GL10.GL_VERSION), +						gl.glGetString(GL10.GL_RENDERER), +						gl.glGetString(GL10.GL_VENDOR))); -		return configs[best]; +		return _egl_surface;  	} -	// Called by ScummVM thread -	static private boolean _log_version = true; -	protected void setupScummVMSurface() { -		try { -			surfaceLock.acquire(); -		} catch (InterruptedException e) { -			Log.e(LOG_TAG, "Interrupted while waiting for surface lock", e); -			return; -		} -		eglSurface = egl.eglCreateWindowSurface(eglDisplay, eglConfig, -												nativeSurface, null); -		if (eglSurface == EGL10.EGL_NO_SURFACE) -			Log.e(LOG_TAG, "CreateWindowSurface failed!"); -		egl.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext); - -		GL10 gl = (GL10)eglContext.getGL(); - -		if (_log_version) { -			Log.i(LOG_TAG, String.format("Using EGL %s (%s); GL %s/%s (%s)", -										 egl.eglQueryString(eglDisplay, EGL10.EGL_VERSION), -										 egl.eglQueryString(eglDisplay, EGL10.EGL_VENDOR), -										 gl.glGetString(GL10.GL_VERSION), -										 gl.glGetString(GL10.GL_RENDERER), -										 gl.glGetString(GL10.GL_VENDOR))); -			_log_version = false; // only log this once +	// Callback from C++ peer instance +	final protected void deinitSurface() { +		if (_egl_display != EGL10.EGL_NO_DISPLAY) { +			_egl.eglMakeCurrent(_egl_display, EGL10.EGL_NO_SURFACE, +								EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); + +			if (_egl_surface != EGL10.EGL_NO_SURFACE) +				_egl.eglDestroySurface(_egl_display, _egl_surface);  		} -		int[] value = new int[1]; -		egl.eglQuerySurface(eglDisplay, eglSurface, EGL10.EGL_WIDTH, value); -		int width = value[0]; -		egl.eglQuerySurface(eglDisplay, eglSurface, EGL10.EGL_HEIGHT, value); -		int height = value[0]; -		Log.i(LOG_TAG, String.format("New surface is %dx%d", width, height)); -		setSurfaceSize(width, height); +		_egl_surface = EGL10.EGL_NO_SURFACE;  	} -	// Called by ScummVM thread -	protected void destroyScummVMSurface() { -		if (eglSurface != null) { -			egl.eglMakeCurrent(eglDisplay, EGL10.EGL_NO_SURFACE, -							   EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); -			egl.eglDestroySurface(eglDisplay, eglSurface); -			eglSurface = EGL10.EGL_NO_SURFACE; +	final private void deinitEGL() { +		if (_egl_display != EGL10.EGL_NO_DISPLAY) { +			_egl.eglMakeCurrent(_egl_display, EGL10.EGL_NO_SURFACE, +								EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); + +			if (_egl_surface != EGL10.EGL_NO_SURFACE) +				_egl.eglDestroySurface(_egl_display, _egl_surface); + +			if (_egl_context != EGL10.EGL_NO_CONTEXT) +				_egl.eglDestroyContext(_egl_display, _egl_context); + +			_egl.eglTerminate(_egl_display);  		} -		surfaceLock.release(); +		_egl_surface = EGL10.EGL_NO_SURFACE; +		_egl_context = EGL10.EGL_NO_CONTEXT; +		_egl_config = null; +		_egl_display = EGL10.EGL_NO_DISPLAY; +		_egl = null;  	} -	public void setSurface(SurfaceHolder holder) { -		holder.addCallback(this); -	} +	final private void initAudio() throws Exception { +		_sample_rate = AudioTrack.getNativeOutputSampleRate( +									AudioManager.STREAM_MUSIC); +		_buffer_size = AudioTrack.getMinBufferSize(_sample_rate, +									AudioFormat.CHANNEL_CONFIGURATION_STEREO, +									AudioFormat.ENCODING_PCM_16BIT); + +		// ~50ms +		int buffer_size_want = (_sample_rate * 2 * 2 / 20) & ~1023; + +		if (_buffer_size < buffer_size_want) { +			Log.w(LOG_TAG, String.format( +				"adjusting audio buffer size (was: %d)", _buffer_size)); -	final public boolean swapBuffers() { -		if (!egl.eglSwapBuffers(eglDisplay, eglSurface)) { -			int error = egl.eglGetError(); -			Log.w(LOG_TAG, String.format("eglSwapBuffers exited with error 0x%x", error)); -			if (error == EGL11.EGL_CONTEXT_LOST) -				return false; +			_buffer_size = buffer_size_want;  		} -		return true; -	} -	// Set scummvm config options -	final public native static void loadConfigFile(String path); -	final public native static void setConfMan(String key, int value); -	final public native static void setConfMan(String key, String value); -	final public native void enableZoning(boolean enable); -	final public native void setSurfaceSize(int width, int height); +		Log.i(LOG_TAG, String.format("Using %d bytes buffer for %dHz audio", +										_buffer_size, _sample_rate)); -	// Feed an event to ScummVM.  Safe to call from other threads. -	final public native void pushEvent(Event e); +		_audio_track = new AudioTrack(AudioManager.STREAM_MUSIC, +									_sample_rate, +									AudioFormat.CHANNEL_CONFIGURATION_STEREO, +									AudioFormat.ENCODING_PCM_16BIT, +									_buffer_size, +									AudioTrack.MODE_STREAM); -	final private native void audioMixCallback(byte[] buf); +		if (_audio_track.getState() != AudioTrack.STATE_INITIALIZED) +			throw new Exception( +				String.format("Error initialising AudioTrack: %d", +								_audio_track.getState())); +	} -	// Runs the actual ScummVM program and returns when it does. -	// This should not be called from multiple threads simultaneously... -	final public native int scummVMMain(String[] argv); +	final private void deinitAudio() { +		if (_audio_track != null) +			_audio_track.stop(); -	// Callbacks from C++ peer instance -	//protected GraphicsMode[] getSupportedGraphicsModes() {} -	protected void displayMessageOnOSD(String msg) {} -	protected void setWindowCaption(String caption) {} -	protected void showVirtualKeyboard(boolean enable) {} -	protected String[] getSysArchives() { return new String[0]; } -	protected String[] getPluginDirectories() { return new String[0]; } -	protected void initBackend() throws AudioSetupException { -		createScummVMGLContext(); -		initAudio(); +		_audio_track = null; +		_buffer_size = 0; +		_sample_rate = 0;  	} -	private static class AudioThread extends Thread { -		final private int buf_size; -		private boolean is_paused = false; -		final private ScummVM scummvm; -		final private AudioTrack audio_track; - -		AudioThread(ScummVM scummvm, AudioTrack audio_track, int buf_size) { -			super("AudioThread"); -			this.scummvm = scummvm; -			this.audio_track = audio_track; -			this.buf_size = buf_size; -			setPriority(Thread.MAX_PRIORITY); -			setDaemon(true); -		} +	private static final int[] s_eglAttribs = { +		EGL10.EGL_CONFIG_ID, +		EGL10.EGL_BUFFER_SIZE, +		EGL10.EGL_RED_SIZE, +		EGL10.EGL_GREEN_SIZE, +		EGL10.EGL_BLUE_SIZE, +		EGL10.EGL_ALPHA_SIZE, +		EGL10.EGL_CONFIG_CAVEAT, +		EGL10.EGL_DEPTH_SIZE, +		EGL10.EGL_LEVEL, +		EGL10.EGL_MAX_PBUFFER_WIDTH, +		EGL10.EGL_MAX_PBUFFER_HEIGHT, +		EGL10.EGL_MAX_PBUFFER_PIXELS, +		EGL10.EGL_NATIVE_RENDERABLE, +		EGL10.EGL_NATIVE_VISUAL_ID, +		EGL10.EGL_NATIVE_VISUAL_TYPE, +		EGL10.EGL_SAMPLE_BUFFERS, +		EGL10.EGL_SAMPLES, +		EGL10.EGL_STENCIL_SIZE, +		EGL10.EGL_SURFACE_TYPE, +		EGL10.EGL_TRANSPARENT_TYPE, +		EGL10.EGL_TRANSPARENT_RED_VALUE, +		EGL10.EGL_TRANSPARENT_GREEN_VALUE, +		EGL10.EGL_TRANSPARENT_BLUE_VALUE +	}; + +	final private class EglAttribs extends LinkedHashMap<Integer, Integer> { +		public EglAttribs(EGLConfig config) { +			super(s_eglAttribs.length); + +			int[] value = new int[1]; + +			for (int i : s_eglAttribs) { +				_egl.eglGetConfigAttrib(_egl_display, config, i, value); -		public void pauseAudio() { -			synchronized (this) { -				is_paused = true; +				put(i, value[0]);  			} -			audio_track.pause();  		} -		public void resumeAudio() { -			synchronized (this) { -				is_paused = false; -				notifyAll(); -			} -			audio_track.play(); +		private int weightBits(int attr, int size) { +			final int value = get(attr); + +			int score = 0; + +			if (value == size || (size > 0 && value > size)) +				score += 10; + +			// penalize for wasted bits +			score -= value - size; + +			return score;  		} -		public void run() { -			byte[] buf = new byte[buf_size]; -			audio_track.play(); -			int offset = 0; -			try { -				while (true) { -					synchronized (this) { -						while (is_paused) -							wait(); -					} - -					if (offset == buf.length) { -						// Grab new audio data -						scummvm.audioMixCallback(buf); -						offset = 0; -					} -					int len = buf.length - offset; -					int ret = audio_track.write(buf, offset, len); -					if (ret < 0) { -						Log.w(LOG_TAG, String.format( -							"AudioTrack.write(%dB) returned error %d", -							buf.length, ret)); -						break; -					} else if (ret != len) { -						Log.w(LOG_TAG, String.format( -							"Short audio write.	 Wrote %dB, not %dB", -							ret, buf.length)); -						// Buffer is full, so yield cpu for a while -						Thread.sleep(100); -					} -					offset += ret; -				} -			} catch (InterruptedException e) { -				Log.e(LOG_TAG, "Audio thread interrupted", e); -			} +		public int weight() { +			int score = 10000; + +			if (get(EGL10.EGL_CONFIG_CAVEAT) != EGL10.EGL_NONE) +				score -= 1000; + +			// less MSAA is better +			score -= get(EGL10.EGL_SAMPLES) * 100; + +			// Must be at least 565, but then smaller is better +			score += weightBits(EGL10.EGL_RED_SIZE, 5); +			score += weightBits(EGL10.EGL_GREEN_SIZE, 6); +			score += weightBits(EGL10.EGL_BLUE_SIZE, 5); +			score += weightBits(EGL10.EGL_ALPHA_SIZE, 0); +			score += weightBits(EGL10.EGL_DEPTH_SIZE, 0); +			score += weightBits(EGL10.EGL_STENCIL_SIZE, 0); + +			return score;  		} -	} -	private AudioThread audio_thread; -	final public int audioSampleRate() { -		return AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_MUSIC); -	} +		public String toString() { +			String s; -	private void initAudio() throws AudioSetupException { -		int sample_rate = audioSampleRate(); -		int buf_size = -			AudioTrack.getMinBufferSize(sample_rate, -										AudioFormat.CHANNEL_CONFIGURATION_STEREO, -										AudioFormat.ENCODING_PCM_16BIT); -		if (buf_size < 0) { -			int guess = AUDIO_FRAME_SIZE * sample_rate / 100;  // 10ms of audio -			Log.w(LOG_TAG, String.format( -				"Unable to get min audio buffer size (error %d). Guessing %dB.", -				buf_size, guess)); -			buf_size = guess; +			if (get(EGL10.EGL_ALPHA_SIZE) > 0) +				s = String.format("[%d] RGBA%d%d%d%d", +									get(EGL10.EGL_CONFIG_ID), +									get(EGL10.EGL_RED_SIZE), +									get(EGL10.EGL_GREEN_SIZE), +									get(EGL10.EGL_BLUE_SIZE), +									get(EGL10.EGL_ALPHA_SIZE)); +			else +				s = String.format("[%d] RGB%d%d%d", +									get(EGL10.EGL_CONFIG_ID), +									get(EGL10.EGL_RED_SIZE), +									get(EGL10.EGL_GREEN_SIZE), +									get(EGL10.EGL_BLUE_SIZE)); + +			if (get(EGL10.EGL_DEPTH_SIZE) > 0) +				s += String.format(" D%d", get(EGL10.EGL_DEPTH_SIZE)); + +			if (get(EGL10.EGL_STENCIL_SIZE) > 0) +				s += String.format(" S%d", get(EGL10.EGL_STENCIL_SIZE)); + +			if (get(EGL10.EGL_SAMPLES) > 0) +				s += String.format(" MSAAx%d", get(EGL10.EGL_SAMPLES)); + +			if ((get(EGL10.EGL_SURFACE_TYPE) & EGL10.EGL_WINDOW_BIT) > 0) +				s += " W"; +			if ((get(EGL10.EGL_SURFACE_TYPE) & EGL10.EGL_PBUFFER_BIT) > 0) +				s += " P"; +			if ((get(EGL10.EGL_SURFACE_TYPE) & EGL10.EGL_PIXMAP_BIT) > 0) +				s += " X"; + +			switch (get(EGL10.EGL_CONFIG_CAVEAT)) { +			case EGL10.EGL_NONE: +				break; + +			case EGL10.EGL_SLOW_CONFIG: +				s += " SLOW"; +				break; + +			case EGL10.EGL_NON_CONFORMANT_CONFIG: +				s += " NON_CONFORMANT"; + +			default: +				s += String.format(" unknown CAVEAT 0x%x", +									get(EGL10.EGL_CONFIG_CAVEAT)); +			} + +			return s;  		} -		Log.d(LOG_TAG, String.format("Using %dB buffer for %dHZ audio", -									 buf_size, sample_rate)); -		AudioTrack audio_track = -			new AudioTrack(AudioManager.STREAM_MUSIC, -						   sample_rate, -						   AudioFormat.CHANNEL_CONFIGURATION_STEREO, -						   AudioFormat.ENCODING_PCM_16BIT, -						   buf_size, -						   AudioTrack.MODE_STREAM); -		if (audio_track.getState() != AudioTrack.STATE_INITIALIZED) { -			Log.e(LOG_TAG, "Error initialising Android audio system."); -			throw new AudioSetupException(); +	}; + +	final private EGLConfig chooseEglConfig(EGLConfig[] configs) { +		EGLConfig res = configs[0]; +		int bestScore = -1; + +		Log.d(LOG_TAG, "EGL configs:"); + +		for (EGLConfig config : configs) { +			EglAttribs attr = new EglAttribs(config); + +			// must have +			if ((attr.get(EGL10.EGL_SURFACE_TYPE) & EGL10.EGL_WINDOW_BIT) == 0) +				continue; + +			int score = attr.weight(); + +			Log.d(LOG_TAG, String.format("%s (%d)", attr.toString(), score)); + +			if (score > bestScore) { +				res = config; +				bestScore = score; +			}  		} -		audio_thread = new AudioThread(this, audio_track, buf_size); -		audio_thread.start(); -	} +		if (bestScore < 0) +			Log.e(LOG_TAG, +					"Unable to find an acceptable EGL config, expect badness."); -	public void pause() { -		audio_thread.pauseAudio(); -		// TODO: need to pause engine too -	} +		Log.d(LOG_TAG, String.format("Chosen EGL config: %s", +										new EglAttribs(res).toString())); -	public void resume() { -		// TODO: need to resume engine too -		audio_thread.resumeAudio(); +		return res;  	}  	static { @@ -448,15 +437,16 @@ public class ScummVM implements SurfaceHolder.Callback {  		final boolean sleep_for_debugger = false;  		if (sleep_for_debugger) {  			try { -				Thread.sleep(20*1000); +				Thread.sleep(20 * 1000);  			} catch (InterruptedException e) {  			}  		} -		//System.loadLibrary("scummvm");  		File cache_dir = ScummVMApplication.getLastCacheDir();  		String libname = System.mapLibraryName("scummvm");  		File libpath = new File(cache_dir, libname); +  		System.load(libpath.getPath());  	}  } + diff --git a/backends/platform/android/org/inodes/gus/scummvm/ScummVMActivity.java b/backends/platform/android/org/inodes/gus/scummvm/ScummVMActivity.java index fae35b6695..1978b690d0 100644 --- a/backends/platform/android/org/inodes/gus/scummvm/ScummVMActivity.java +++ b/backends/platform/android/org/inodes/gus/scummvm/ScummVMActivity.java @@ -3,41 +3,27 @@ package org.inodes.gus.scummvm;  import android.app.Activity;  import android.app.AlertDialog;  import android.content.DialogInterface; -import android.content.res.Configuration;  import android.media.AudioManager;  import android.os.Bundle;  import android.os.Environment; -import android.os.Handler; -import android.os.Message;  import android.util.DisplayMetrics;  import android.util.Log; -import android.view.KeyEvent; -import android.view.MotionEvent;  import android.view.SurfaceView; -import android.view.View; -import android.view.ViewConfiguration; +import android.view.SurfaceHolder; +import android.view.MotionEvent;  import android.view.inputmethod.InputMethodManager;  import android.widget.Toast; -import java.io.IOException; -  public class ScummVMActivity extends Activity { -	private boolean _do_right_click; -	private boolean _last_click_was_right; - -	// game pixels to move per trackball/dpad event. -	// FIXME: replace this with proper mouse acceleration -	private final static int TRACKBALL_SCALE = 2;  	private class MyScummVM extends ScummVM { -		private boolean scummvmRunning = false; -  		private boolean usingSmallScreen() {  			// Multiple screen sizes came in with Android 1.6.  Have  			// to use reflection in order to continue supporting 1.5  			// devices :(  			DisplayMetrics metrics = new DisplayMetrics();  			getWindowManager().getDefaultDisplay().getMetrics(metrics); +  			try {  				// This 'density' term is very confusing.  				int DENSITY_LOW = metrics.getClass().getField("DENSITY_LOW").getInt(null); @@ -48,27 +34,22 @@ public class ScummVMActivity extends Activity {  			}  		} -		public MyScummVM() { -			super(ScummVMActivity.this); +		public MyScummVM(SurfaceHolder holder) { +			super(ScummVMActivity.this.getAssets(), holder);  			// Enable ScummVM zoning on 'small' screens. -			enableZoning(usingSmallScreen()); +			// FIXME make this optional for the user +			// disabled for now since it crops too much +			//enableZoning(usingSmallScreen());  		}  		@Override -		protected void initBackend() throws ScummVM.AudioSetupException { -			synchronized (this) { -				scummvmRunning = true; -				notifyAll(); -			} -			super.initBackend(); -		} +		protected void getDPI(float[] values) { +			DisplayMetrics metrics = new DisplayMetrics(); +			getWindowManager().getDefaultDisplay().getMetrics(metrics); -		public void waitUntilRunning() throws InterruptedException { -			synchronized (this) { -				while (!scummvmRunning) -					wait(); -			} +			values[0] = metrics.xdpi; +			values[1] = metrics.ydpi;  		}  		@Override @@ -95,24 +76,28 @@ public class ScummVMActivity extends Activity {  		@Override  		protected void showVirtualKeyboard(final boolean enable) { -			if (getResources().getConfiguration().keyboard == -				Configuration.KEYBOARD_NOKEYS) { -				runOnUiThread(new Runnable() { -						public void run() { -							showKeyboard(enable); -						} -					}); -			} +			runOnUiThread(new Runnable() { +					public void run() { +						showKeyboard(enable); +					} +				}); +		} + +		@Override +		protected String[] getSysArchives() { +			return new String[0];  		} +  	} -	private MyScummVM scummvm; -	private Thread scummvm_thread; + +	private MyScummVM _scummvm; +	private ScummVMEvents _events; +	private Thread _scummvm_thread;  	@Override  	public void onCreate(Bundle savedInstanceState) {  		super.onCreate(savedInstanceState); -		_do_right_click = false;  		setVolumeControlStream(AudioManager.STREAM_MUSIC);  		setContentView(R.layout.main); @@ -126,336 +111,110 @@ public class ScummVMActivity extends Activity {  				.setIcon(android.R.drawable.ic_dialog_alert)  				.setMessage(R.string.no_sdcard)  				.setNegativeButton(R.string.quit, -								   new DialogInterface.OnClickListener() { -									   public void onClick(DialogInterface dialog, -														   int which) { -										   finish(); -									   } -								   }) +									new DialogInterface.OnClickListener() { +										public void onClick(DialogInterface dialog, +															int which) { +											finish(); +										} +									})  				.show(); +  			return;  		}  		SurfaceView main_surface = (SurfaceView)findViewById(R.id.main_surface); -		 -		main_surface.setOnTouchListener(new View.OnTouchListener() { -				public boolean onTouch(View v, MotionEvent event) { -					return onTouchEvent(event); -				} -			}); -		main_surface.setOnKeyListener(new View.OnKeyListener() { -				public boolean onKey(View v, int code, KeyEvent ev) { -					return onKeyDown(code, ev); -				} -			}); +  		main_surface.requestFocus(); -		// Start ScummVM -		scummvm = new MyScummVM(); -		scummvm_thread = new Thread(new Runnable() { -				public void run() { -					try { -						runScummVM(); -					} catch (Exception e) { -						Log.e(ScummVM.LOG_TAG, "Fatal error in ScummVM thread", e); -						new AlertDialog.Builder(ScummVMActivity.this) -							.setTitle("Error") -							.setMessage(e.toString()) -							.setIcon(android.R.drawable.ic_dialog_alert) -							.show(); -						finish(); -					} -				} -			}, "ScummVM"); -		scummvm_thread.start(); - -		// Block UI thread until ScummVM has started.  In particular, -		// this means that surface and event callbacks should be safe -		// after this point. -		try { -			scummvm.waitUntilRunning(); -		} catch (InterruptedException e) { -			Log.e(ScummVM.LOG_TAG, "Interrupted while waiting for ScummVM.initBackend", e); -			finish(); -		} +		getFilesDir().mkdirs(); -		scummvm.setSurface(main_surface.getHolder()); -	} +		// Start ScummVM +		_scummvm = new MyScummVM(main_surface.getHolder()); -	// Runs in another thread -	private void runScummVM() throws IOException { -		getFilesDir().mkdirs(); -		String[] args = { -			"ScummVM-lib", +		_scummvm.setArgs(new String[] { +			"ScummVM",  			"--config=" + getFileStreamPath("scummvmrc").getPath(),  			"--path=" + Environment.getExternalStorageDirectory().getPath(),  			"--gui-theme=scummmodern",  			"--savepath=" + getDir("saves", 0).getPath() -		}; +		}); -		int ret = scummvm.scummVMMain(args); +		_events = new ScummVMEvents(this, _scummvm); -		// On exit, tear everything down for a fresh -		// restart next time. -		System.exit(ret); -	} +		main_surface.setOnKeyListener(_events); +		main_surface.setOnTouchListener(_events); -	private boolean was_paused = false; +		_scummvm_thread = new Thread(_scummvm, "ScummVM"); +		_scummvm_thread.start(); +	}  	@Override -	public void onPause() { -		if (scummvm != null) { -			was_paused = true; -			scummvm.pause(); -		} -		super.onPause(); +	public void onStart() { +		Log.d(ScummVM.LOG_TAG, "onStart"); + +		super.onStart();  	}  	@Override  	public void onResume() { +		Log.d(ScummVM.LOG_TAG, "onResume"); +  		super.onResume(); -		if (scummvm != null && was_paused) -			scummvm.resume(); -		was_paused = false; + +		if (_scummvm != null) +			_scummvm.setPause(false);  	}  	@Override -	public void onStop() { -		if (scummvm != null) { -			scummvm.pushEvent(new Event(Event.EVENT_QUIT)); -			try { -				scummvm_thread.join(1000);	// 1s timeout -			} catch (InterruptedException e) { -				Log.i(ScummVM.LOG_TAG, "Error while joining ScummVM thread", e); -			} -		} -		super.onStop(); -	} +	public void onPause() { +		Log.d(ScummVM.LOG_TAG, "onPause"); -	static final int MSG_MENU_LONG_PRESS = 1; -	private final Handler keycodeMenuTimeoutHandler = new Handler() { -			@Override -			public void handleMessage(Message msg) { -				if (msg.what == MSG_MENU_LONG_PRESS) { -					InputMethodManager imm = (InputMethodManager) -						getSystemService(INPUT_METHOD_SERVICE); -					if (imm != null) -						imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); -				} -			} -		}; +		super.onPause(); -	@Override -	public boolean onKeyUp(int keyCode, KeyEvent kevent) { -		return onKeyDown(keyCode, kevent); +		if (_scummvm != null) +			_scummvm.setPause(true);  	}  	@Override -	public boolean onKeyMultiple(int keyCode, int repeatCount, -									 KeyEvent kevent) { -		return onKeyDown(keyCode, kevent); +	public void onStop() { +		Log.d(ScummVM.LOG_TAG, "onStop"); + +		super.onStop();  	}  	@Override -	public boolean onKeyDown(int keyCode, KeyEvent kevent) { -		// Filter out "special" keys -		switch (keyCode) { -		case KeyEvent.KEYCODE_MENU: -			// Have to reimplement hold-down-menu-brings-up-softkeybd -			// ourselves, since we are otherwise hijacking the menu -			// key :( -			// See com.android.internal.policy.impl.PhoneWindow.onKeyDownPanel() -			// for the usual Android implementation of this feature. -			if (kevent.getRepeatCount() > 0) -				// Ignore keyrepeat for menu -				return false; -			boolean timeout_fired = false; -			if (getResources().getConfiguration().keyboard == -				Configuration.KEYBOARD_NOKEYS) { -				timeout_fired = !keycodeMenuTimeoutHandler.hasMessages(MSG_MENU_LONG_PRESS); -				keycodeMenuTimeoutHandler.removeMessages(MSG_MENU_LONG_PRESS); -				if (kevent.getAction() == KeyEvent.ACTION_DOWN) { -					keycodeMenuTimeoutHandler.sendMessageDelayed( -																 keycodeMenuTimeoutHandler.obtainMessage(MSG_MENU_LONG_PRESS), -																 ViewConfiguration.getLongPressTimeout()); -					return true; -				} -			} -			if (kevent.getAction() == KeyEvent.ACTION_UP) { -				if (!timeout_fired) -					scummvm.pushEvent(new Event(Event.EVENT_MAINMENU)); -				return true; -			} -			return false; -		case KeyEvent.KEYCODE_CAMERA: -		case KeyEvent.KEYCODE_SEARCH: -			_do_right_click = (kevent.getAction() == KeyEvent.ACTION_DOWN); -			return true; -		case KeyEvent.KEYCODE_DPAD_CENTER: -		case KeyEvent.KEYCODE_DPAD_UP: -		case KeyEvent.KEYCODE_DPAD_DOWN: -		case KeyEvent.KEYCODE_DPAD_LEFT: -		case KeyEvent.KEYCODE_DPAD_RIGHT: { -			// HTC Hero doesn't seem to generate -			// MotionEvent.ACTION_DOWN events on trackball press :( -			// We'll have to just fake one here. -			// Some other handsets lack a trackball, so the DPAD is -			// the only way of moving the cursor. -			int motion_action; -			// FIXME: this logic is a mess. -			if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { -				switch (kevent.getAction()) { -				case KeyEvent.ACTION_DOWN: -					motion_action = MotionEvent.ACTION_DOWN; -					break; -				case KeyEvent.ACTION_UP: -					motion_action = MotionEvent.ACTION_UP; -					break; -				default:  // ACTION_MULTIPLE -					return false; -				} -			} else -				motion_action = MotionEvent.ACTION_MOVE; - -			Event e = new Event(getEventType(motion_action)); -			e.mouse_x = 0; -			e.mouse_y = 0; -			e.mouse_relative = true; -			switch (keyCode) { -			case KeyEvent.KEYCODE_DPAD_UP: -				e.mouse_y = -TRACKBALL_SCALE; -				break; -			case KeyEvent.KEYCODE_DPAD_DOWN: -				e.mouse_y = TRACKBALL_SCALE; -				break; -			case KeyEvent.KEYCODE_DPAD_LEFT: -				e.mouse_x = -TRACKBALL_SCALE; -				break; -			case KeyEvent.KEYCODE_DPAD_RIGHT: -				e.mouse_x = TRACKBALL_SCALE; -				break; -			} -			scummvm.pushEvent(e); -			return true; -		} -		case KeyEvent.KEYCODE_BACK: -			// skip isSystem() check and fall through to main code -			break; -		default: -			if (kevent.isSystem()) -				return false; -		} +	public void onDestroy() { +		Log.d(ScummVM.LOG_TAG, "onDestroy"); -		// FIXME: what do I need to do for composed characters? - -		Event e = new Event(); - -		switch (kevent.getAction()) { -		case KeyEvent.ACTION_DOWN: -			e.type = Event.EVENT_KEYDOWN; -			e.synthetic = false; -			break; -		case KeyEvent.ACTION_UP: -			e.type = Event.EVENT_KEYUP; -			e.synthetic = false; -			break; -		case KeyEvent.ACTION_MULTIPLE: -			// e.type is handled below -			e.synthetic = true; -			break; -		default: -			return false; -		} +		super.onDestroy(); -		e.kbd_keycode = Event.androidKeyMap.containsKey(keyCode) ? -			Event.androidKeyMap.get(keyCode) : Event.KEYCODE_INVALID; -		e.kbd_ascii = kevent.getUnicodeChar(); -		if (e.kbd_ascii == 0) -			e.kbd_ascii = e.kbd_keycode; // scummvm keycodes are mostly ascii - - -		e.kbd_flags = 0; -		if (kevent.isAltPressed()) -			e.kbd_flags |= Event.KBD_ALT; -		if (kevent.isSymPressed()) // no ctrl key in android, so use sym (?) -			e.kbd_flags |= Event.KBD_CTRL; -		if (kevent.isShiftPressed()) { -			if (keyCode >= KeyEvent.KEYCODE_0 && -				keyCode <= KeyEvent.KEYCODE_9) { -				// Shift+number -> convert to F* key -				int offset = keyCode == KeyEvent.KEYCODE_0 ? -					10 : keyCode - KeyEvent.KEYCODE_1; // turn 0 into 10 -				e.kbd_keycode = Event.KEYCODE_F1 + offset; -				e.kbd_ascii = Event.ASCII_F1 + offset; -			} else -				e.kbd_flags |= Event.KBD_SHIFT; -		} +		if (_events != null) { +			_events.sendQuitEvent(); -		if (kevent.getAction() == KeyEvent.ACTION_MULTIPLE) { -			for (int i = 0; i <= kevent.getRepeatCount(); i++) { -				e.type = Event.EVENT_KEYDOWN; -				scummvm.pushEvent(e); -				e.type = Event.EVENT_KEYUP; -				scummvm.pushEvent(e); +			try { +				// 1s timeout +				_scummvm_thread.join(1000); +			} catch (InterruptedException e) { +				Log.i(ScummVM.LOG_TAG, "Error while joining ScummVM thread", e);  			} -		} else -			scummvm.pushEvent(e); - -		return true; -	} -	private int getEventType(int action) { -		switch (action) { -		case MotionEvent.ACTION_DOWN: -			_last_click_was_right = _do_right_click; -			return _last_click_was_right ? -				Event.EVENT_RBUTTONDOWN : Event.EVENT_LBUTTONDOWN; -		case MotionEvent.ACTION_UP: -			return _last_click_was_right ? -				Event.EVENT_RBUTTONUP : Event.EVENT_LBUTTONUP; -		case MotionEvent.ACTION_MOVE: -			return Event.EVENT_MOUSEMOVE; -		default: -			return Event.EVENT_INVALID; +			_scummvm = null;  		}  	}  	@Override -	public boolean onTrackballEvent(MotionEvent event) { -		int type = getEventType(event.getAction()); -		if (type == Event.EVENT_INVALID) -			return false; - -		Event e = new Event(type); -		e.mouse_x = -			(int)(event.getX() * event.getXPrecision()) * TRACKBALL_SCALE; -		e.mouse_y = -			(int)(event.getY() * event.getYPrecision()) * TRACKBALL_SCALE; -		e.mouse_relative = true; -		scummvm.pushEvent(e); - -		return true; -	} +	public boolean onTrackballEvent(MotionEvent e) { +		if (_events != null) +			return _events.onTrackballEvent(e); -	@Override -	public boolean onTouchEvent(MotionEvent event) { -		int type = getEventType(event.getAction()); -		if (type == Event.EVENT_INVALID) -			return false; - -		Event e = new Event(type); -		e.mouse_x = (int)event.getX(); -		e.mouse_y = (int)event.getY(); -		e.mouse_relative = false; -		scummvm.pushEvent(e); - -		return true; +		return false;  	}  	private void showKeyboard(boolean show) {  		SurfaceView main_surface = (SurfaceView)findViewById(R.id.main_surface);  		InputMethodManager imm = (InputMethodManager)  			getSystemService(INPUT_METHOD_SERVICE); +  		if (show)  			imm.showSoftInput(main_surface, InputMethodManager.SHOW_IMPLICIT);  		else @@ -463,3 +222,4 @@ public class ScummVMActivity extends Activity {  										InputMethodManager.HIDE_IMPLICIT_ONLY);  	}  } + diff --git a/backends/platform/android/org/inodes/gus/scummvm/ScummVMApplication.java b/backends/platform/android/org/inodes/gus/scummvm/ScummVMApplication.java index 37a9d09e1a..f9eec72eac 100644 --- a/backends/platform/android/org/inodes/gus/scummvm/ScummVMApplication.java +++ b/backends/platform/android/org/inodes/gus/scummvm/ScummVMApplication.java @@ -8,22 +8,24 @@ public class ScummVMApplication extends Application {  	public final static String ACTION_PLUGIN_QUERY = "org.inodes.gus.scummvm.action.PLUGIN_QUERY";  	public final static String EXTRA_UNPACK_LIBS = "org.inodes.gus.scummvm.extra.UNPACK_LIBS"; -	private static File cache_dir; +	private static File _cache_dir;  	@Override  	public void onCreate() {  		super.onCreate(); +  		// This is still on /data :( -		cache_dir = getCacheDir(); +		_cache_dir = getCacheDir();  		// This is mounted noexec :(  		//cache_dir = new File(Environment.getExternalStorageDirectory(), -		//			 "/.ScummVM.tmp"); +		//				"/.ScummVM.tmp");  		// This is owned by download manager and requires special  		// permissions to access :(  		//cache_dir = Environment.getDownloadCacheDirectory();  	}  	public static File getLastCacheDir() { -		return cache_dir; +		return _cache_dir;  	}  } + diff --git a/backends/platform/android/org/inodes/gus/scummvm/ScummVMEvents.java b/backends/platform/android/org/inodes/gus/scummvm/ScummVMEvents.java new file mode 100644 index 0000000000..2d5c100a1c --- /dev/null +++ b/backends/platform/android/org/inodes/gus/scummvm/ScummVMEvents.java @@ -0,0 +1,232 @@ +package org.inodes.gus.scummvm; + +import android.os.Handler; +import android.os.Message; +import android.util.Log; +import android.content.Context; +import android.view.KeyEvent; +import android.view.KeyCharacterMap; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.GestureDetector; +import android.view.inputmethod.InputMethodManager; + +public class ScummVMEvents implements +		android.view.View.OnKeyListener, +		android.view.View.OnTouchListener, +		android.view.GestureDetector.OnGestureListener, +		android.view.GestureDetector.OnDoubleTapListener { + +	public static final int JE_SYS_KEY = 0; +	public static final int JE_KEY = 1; +	public static final int JE_DPAD = 2; +	public static final int JE_DOWN = 3; +	public static final int JE_SCROLL = 4; +	public static final int JE_TAP = 5; +	public static final int JE_DOUBLE_TAP = 6; +	public static final int JE_MULTI = 7; +	public static final int JE_BALL = 8; +	public static final int JE_QUIT = 0x1000; + +	final protected Context _context; +	final protected ScummVM _scummvm; +	final protected GestureDetector _gd; +	final protected int _longPress; + +	public ScummVMEvents(Context context, ScummVM scummvm) { +		_context = context; +		_scummvm = scummvm; + +		_gd = new GestureDetector(context, this); +		_gd.setOnDoubleTapListener(this); +		_gd.setIsLongpressEnabled(false); + +		_longPress = ViewConfiguration.getLongPressTimeout(); +	} + +	final public void sendQuitEvent() { +		_scummvm.pushEvent(JE_QUIT, 0, 0, 0, 0, 0); +	} + +	public boolean onTrackballEvent(MotionEvent e) { +		_scummvm.pushEvent(JE_BALL, e.getAction(), +							(int)(e.getX() * e.getXPrecision() * 100), +							(int)(e.getY() * e.getYPrecision() * 100), +							0, 0); +		return true; +	} + +	final static int MSG_MENU_LONG_PRESS = 1; + +	final private Handler keyHandler = new Handler() { +		@Override +		public void handleMessage(Message msg) { +			if (msg.what == MSG_MENU_LONG_PRESS) { +				InputMethodManager imm = (InputMethodManager) +					_context.getSystemService(_context.INPUT_METHOD_SERVICE); + +				if (imm != null) +					imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); +			} +		} +	}; + +	// OnKeyListener +	final public boolean onKey(View v, int keyCode, KeyEvent e) { +		final int action = e.getAction(); + +		if (e.isSystem()) { +			// filter what we handle +			switch (keyCode) { +			case KeyEvent.KEYCODE_BACK: +			case KeyEvent.KEYCODE_MENU: +			case KeyEvent.KEYCODE_CAMERA: +			case KeyEvent.KEYCODE_SEARCH: +				break; + +			default: +				return false; +			} + +			// no repeats for system keys +			if (e.getRepeatCount() > 0) +				return false; + +			// Have to reimplement hold-down-menu-brings-up-softkeybd +			// ourselves, since we are otherwise hijacking the menu key :( +			// See com.android.internal.policy.impl.PhoneWindow.onKeyDownPanel() +			// for the usual Android implementation of this feature. +			if (keyCode == KeyEvent.KEYCODE_MENU) { +				final boolean fired = +					!keyHandler.hasMessages(MSG_MENU_LONG_PRESS); + +				keyHandler.removeMessages(MSG_MENU_LONG_PRESS); + +				if (action == KeyEvent.ACTION_DOWN) { +					keyHandler.sendMessageDelayed(keyHandler.obtainMessage( +									MSG_MENU_LONG_PRESS), _longPress); +					return true; +				} + +				if (fired) +					return true; + +				// only send up events of the menu button to the native side +				if (action != KeyEvent.ACTION_UP) +					return true; +			} + +			_scummvm.pushEvent(JE_SYS_KEY, action, keyCode, 0, 0, 0); + +			return true; +		} + +		// sequence of characters +		if (action == KeyEvent.ACTION_MULTIPLE && +				keyCode == KeyEvent.KEYCODE_UNKNOWN) { +			final KeyCharacterMap m = KeyCharacterMap.load(e.getDeviceId()); +			final KeyEvent[] es = m.getEvents(e.getCharacters().toCharArray()); + +			if (es == null) +				return true; + +			for (KeyEvent s : es) { +				_scummvm.pushEvent(JE_KEY, s.getAction(), s.getKeyCode(), +					s.getUnicodeChar() & KeyCharacterMap.COMBINING_ACCENT_MASK, +					s.getMetaState(), s.getRepeatCount()); +			} + +			return true; +		} + +		switch (keyCode) { +		case KeyEvent.KEYCODE_DPAD_UP: +		case KeyEvent.KEYCODE_DPAD_DOWN: +		case KeyEvent.KEYCODE_DPAD_LEFT: +		case KeyEvent.KEYCODE_DPAD_RIGHT: +		case KeyEvent.KEYCODE_DPAD_CENTER: +			_scummvm.pushEvent(JE_DPAD, action, keyCode, +								(int)(e.getEventTime() - e.getDownTime()), +								e.getRepeatCount(), 0); +			return true; +		} + +		_scummvm.pushEvent(JE_KEY, action, keyCode, +					e.getUnicodeChar() & KeyCharacterMap.COMBINING_ACCENT_MASK, +					e.getMetaState(), e.getRepeatCount()); + +		return true; +	} + +	// OnTouchListener +	final public boolean onTouch(View v, MotionEvent e) { +		final int action = e.getAction(); + +		// constants from APIv5: +		// (action & ACTION_POINTER_INDEX_MASK) >> ACTION_POINTER_INDEX_SHIFT +		final int pointer = (action & 0xff00) >> 8; + +		if (pointer > 0) { +			_scummvm.pushEvent(JE_MULTI, pointer, action & 0xff, // ACTION_MASK +								(int)e.getX(), (int)e.getY(), 0); +			return true; +		} + +		return _gd.onTouchEvent(e); +	} + +	// OnGestureListener +	final public boolean onDown(MotionEvent e) { +		_scummvm.pushEvent(JE_DOWN, (int)e.getX(), (int)e.getY(), 0, 0, 0); +		return true; +	} + +	final public boolean onFling(MotionEvent e1, MotionEvent e2, +									float velocityX, float velocityY) { +		//Log.d(ScummVM.LOG_TAG, String.format("onFling: %s -> %s (%.3f %.3f)", +		//										e1.toString(), e2.toString(), +		//										velocityX, velocityY)); + +		return true; +	} + +	final public void onLongPress(MotionEvent e) { +		// disabled, interferes with drag&drop +	} + +	final public boolean onScroll(MotionEvent e1, MotionEvent e2, +									float distanceX, float distanceY) { +		_scummvm.pushEvent(JE_SCROLL, (int)e1.getX(), (int)e1.getY(), +							(int)e2.getX(), (int)e2.getY(), 0); + +		return true; +	} + +	final public void onShowPress(MotionEvent e) { +	} + +	final public boolean onSingleTapUp(MotionEvent e) { +		_scummvm.pushEvent(JE_TAP, (int)e.getX(), (int)e.getY(), +							(int)(e.getEventTime() - e.getDownTime()), 0, 0); + +		return true; +	} + +	// OnDoubleTapListener +	final public boolean onDoubleTap(MotionEvent e) { +		return true; +	} + +	final public boolean onDoubleTapEvent(MotionEvent e) { +		_scummvm.pushEvent(JE_DOUBLE_TAP, (int)e.getX(), (int)e.getY(), +							e.getAction(), 0, 0); + +		return true; +	} + +	final public boolean onSingleTapConfirmed(MotionEvent e) { +		return true; +	} +} + diff --git a/backends/platform/android/org/inodes/gus/scummvm/Unpacker.java b/backends/platform/android/org/inodes/gus/scummvm/Unpacker.java index 8811b1f3ae..c4b2ad7f5d 100644 --- a/backends/platform/android/org/inodes/gus/scummvm/Unpacker.java +++ b/backends/platform/android/org/inodes/gus/scummvm/Unpacker.java @@ -370,3 +370,4 @@ public class Unpacker extends Activity {  		}  	}  } + diff --git a/backends/platform/android/texture.cpp b/backends/platform/android/texture.cpp new file mode 100644 index 0000000000..c830676c07 --- /dev/null +++ b/backends/platform/android/texture.cpp @@ -0,0 +1,494 @@ +/* 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__) + +// 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 "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" + +// 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 GLESBaseTexture::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 +	} +} + +GLESBaseTexture::GLESBaseTexture(GLenum glFormat, GLenum glType, +									Graphics::PixelFormat pixelFormat) : +	_glFormat(glFormat), +	_glType(glType), +	_glFilter(GL_NEAREST), +	_texture_name(0), +	_surface(), +	_texture_width(0), +	_texture_height(0), +	_draw_rect(), +	_all_dirty(false), +	_dirty_rect(), +	_pixelFormat(pixelFormat), +	_palettePixelFormat() +{ +	GLCALL(glGenTextures(1, &_texture_name)); +} + +GLESBaseTexture::~GLESBaseTexture() { +	release(); +} + +void GLESBaseTexture::release() { +	if (_texture_name) { +		LOGD("Destroying texture %u", _texture_name); + +		GLCALL(glDeleteTextures(1, &_texture_name)); +		_texture_name = 0; +	} +} + +void GLESBaseTexture::reinit() { +	GLCALL(glGenTextures(1, &_texture_name)); + +	initSize(); + +	setDirty(); +} + +void GLESBaseTexture::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, _glFilter)); +	GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, _glFilter)); +	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 GLESBaseTexture::setLinearFilter(bool value) { +	if (value) +		_glFilter = GL_LINEAR; +	else +		_glFilter = GL_NEAREST; + +	GLCALL(glBindTexture(GL_TEXTURE_2D, _texture_name)); + +	GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, _glFilter)); +	GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, _glFilter)); +} + +void GLESBaseTexture::allocBuffer(GLuint w, GLuint h) { +	_surface.w = w; +	_surface.h = h; +	_surface.format = _pixelFormat; + +	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); +	} + +	initSize(); +} + +void GLESBaseTexture::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 &GLESBaseTexture::getPixelFormat() const { +	return _pixelFormat; +} + +GLESTexture::GLESTexture(GLenum glFormat, GLenum glType, +							Graphics::PixelFormat pixelFormat) : +	GLESBaseTexture(glFormat, glType, pixelFormat), +	_pixels(0), +	_buf(0) { +} + +GLESTexture::~GLESTexture() { +	delete[] _buf; +	delete[] _pixels; +} + +void GLESTexture::allocBuffer(GLuint w, GLuint h) { +	GLuint oldw = _surface.w; +	GLuint oldh = _surface.h; + +	GLESBaseTexture::allocBuffer(w, h); + +	_surface.pitch = w * _pixelFormat.bytesPerPixel; + +	if (_surface.w == oldw && _surface.h == oldh) { +		fillBuffer(0); +		return; +	} + +	delete[] _buf; +	delete[] _pixels; + +	_pixels = new byte[w * h * _surface.format.bytesPerPixel]; +	assert(_pixels); + +	_surface.pixels = _pixels; + +	fillBuffer(0); + +	_buf = new byte[w * h * _surface.format.bytesPerPixel]; +	assert(_buf); +} + +void GLESTexture::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 * _surface.format.bytesPerPixel; + +	do { +		memcpy(dst, src, w * _surface.format.bytesPerPixel); +		dst += _surface.pitch; +		src += pitch_buf; +	} while (--h); +} + +void GLESTexture::fillBuffer(uint32 color) { +	assert(_surface.pixels); + +	if (_pixelFormat.bytesPerPixel == 1 || +			((color & 0xff) == ((color >> 8) & 0xff))) +		memset(_pixels, color & 0xff, _surface.pitch * _surface.h); +	else +		Common::set_to(_pixels, _pixels + _surface.pitch * _surface.h, +						(uint16)color); + +	setDirty(); +} + +void GLESTexture::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()) { +		byte *_tex; + +		int16 dwidth = _dirty_rect.width(); +		int16 dheight = _dirty_rect.height(); + +		if (dwidth == _surface.w) { +			_tex = _pixels + _dirty_rect.top * _surface.pitch; +		} else { +			_tex = _buf; + +			byte *src = _pixels + _dirty_rect.top * _surface.pitch + +						_dirty_rect.left * _surface.format.bytesPerPixel; +			byte *dst = _buf; + +			uint16 l = dwidth * _surface.format.bytesPerPixel; + +			for (uint16 i = 0; i < dheight; ++i) { +				memcpy(dst, src, l); +				src += _surface.pitch; +				dst += l; +			} +		} + +		GLCALL(glBindTexture(GL_TEXTURE_2D, _texture_name)); +		GLCALL(glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); + +		GLCALL(glTexSubImage2D(GL_TEXTURE_2D, 0, +								_dirty_rect.left, _dirty_rect.top, +								dwidth, dheight, _glFormat, _glType, _tex)); +	} + +	GLESBaseTexture::drawTexture(x, y, w, h); +} + +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() { +} + +GLESFakePaletteTexture::GLESFakePaletteTexture(GLenum glFormat, GLenum glType, +									Graphics::PixelFormat pixelFormat) : +	GLESBaseTexture(glFormat, glType, pixelFormat), +	_palette(0), +	_pixels(0), +	_buf(0) +{ +	_palettePixelFormat = pixelFormat; +	_fake_format = Graphics::PixelFormat::createFormatCLUT8(); + +	_palette = new uint16[256]; +	assert(_palette); + +	memset(_palette, 0, 256 * 2); +} + +GLESFakePaletteTexture::~GLESFakePaletteTexture() { +	delete[] _buf; +	delete[] _pixels; +	delete[] _palette; +} + +void GLESFakePaletteTexture::allocBuffer(GLuint w, GLuint h) { +	GLuint oldw = _surface.w; +	GLuint oldh = _surface.h; + +	GLESBaseTexture::allocBuffer(w, h); + +	_surface.format = Graphics::PixelFormat::createFormatCLUT8(); +	_surface.pitch = w; + +	if (_surface.w == oldw && _surface.h == oldh) { +		fillBuffer(0); +		return; +	} + +	delete[] _buf; +	delete[] _pixels; + +	_pixels = new byte[w * h]; +	assert(_pixels); + +	// fixup surface, for the outside this is a CLUT8 surface +	_surface.pixels = _pixels; + +	fillBuffer(0); + +	_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)); +	} + +	GLESBaseTexture::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() { +} + +GLESFakePalette5551Texture::GLESFakePalette5551Texture() : +	GLESFakePaletteTexture(GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, +							GLES5551Texture::pixelFormat()) { +} + +GLESFakePalette5551Texture::~GLESFakePalette5551Texture() { +} + +#endif + diff --git a/backends/platform/android/texture.h b/backends/platform/android/texture.h new file mode 100644 index 0000000000..6344326259 --- /dev/null +++ b/backends/platform/android/texture.h @@ -0,0 +1,277 @@ +/* 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$ + * + */ + +#ifndef _ANDROID_TEXTURE_H_ +#define _ANDROID_TEXTURE_H_ + +#if defined(__ANDROID__) + +#include <GLES/gl.h> + +#include "graphics/surface.h" +#include "graphics/pixelformat.h" + +#include "common/rect.h" +#include "common/array.h" + +class GLESBaseTexture { +public: +	static void initGLExtensions(); + +protected: +	GLESBaseTexture(GLenum glFormat, GLenum glType, +					Graphics::PixelFormat pixelFormat); + +public: +	virtual ~GLESBaseTexture(); + +	void release(); +	void reinit(); +	void initSize(); + +	void setLinearFilter(bool value); + +	virtual void allocBuffer(GLuint w, GLuint h); + +	virtual void updateBuffer(GLuint x, GLuint y, GLuint width, GLuint height, +								const void *buf, int pitch_buf) = 0; +	virtual void fillBuffer(uint32 color) = 0; + +	virtual void drawTexture(GLshort x, GLshort y, GLshort w, GLshort h); + +	inline void setDrawRect(const Common::Rect &rect) { +		_draw_rect = rect; +	} + +	inline void setDrawRect(int16 w, int16 h) { +		_draw_rect = Common::Rect(w, h); +	} + +	inline void setDrawRect(int16 x1, int16 y1, int16 x2, int16 y2) { +		_draw_rect = Common::Rect(x1, y1, x2, y2); +	} + +	inline const Common::Rect &getDrawRect() const { +		return _draw_rect; +	} + +	inline void drawTextureRect() { +		drawTexture(_draw_rect.left, _draw_rect.top, +					_draw_rect.width(), _draw_rect.height()); +	} + +	inline void drawTextureOrigin() { +			drawTexture(0, 0, _surface.w, _surface.h); +	} + +	inline GLuint width() const { +		return _surface.w; +	} + +	inline GLuint height() const { +		return _surface.h; +	} + +	inline uint16 pitch() const { +		return _surface.pitch; +	} + +	inline bool isEmpty() const { +		return _surface.w == 0 || _surface.h == 0; +	} + +	inline const Graphics::Surface *surface_const() const { +		return &_surface; +	} + +	inline Graphics::Surface *surface() { +		setDirty(); +		return &_surface; +	} + +	virtual const byte *palette_const() const { +		return 0; +	}; + +	virtual byte *palette() { +		return 0; +	}; + +	inline bool hasPalette() const { +		return _palettePixelFormat.bytesPerPixel > 0; +	} + +	inline bool dirty() const { +		return _all_dirty || !_dirty_rect.isEmpty(); +	} + +	virtual const Graphics::PixelFormat &getPixelFormat() const; + +	inline const Graphics::PixelFormat &getPalettePixelFormat() const { +		return _palettePixelFormat; +	} + +protected: +	inline void setDirty() { +		_all_dirty = true; +	} + +	inline void clearDirty() { +		_all_dirty = false; +		_dirty_rect.top = 0; +		_dirty_rect.left = 0; +		_dirty_rect.bottom = 0; +		_dirty_rect.right = 0; +	} + +	inline void setDirtyRect(const Common::Rect& r) { +		if (!_all_dirty) { +			if (_dirty_rect.isEmpty()) +				_dirty_rect = r; +			else +				_dirty_rect.extend(r); +		} +	} + +	GLenum _glFormat; +	GLenum _glType; +	GLint _glFilter; + +	GLuint _texture_name; +	Graphics::Surface _surface; +	GLuint _texture_width; +	GLuint _texture_height; + +	Common::Rect _draw_rect; + +	bool _all_dirty; +	Common::Rect _dirty_rect; + +	Graphics::PixelFormat _pixelFormat; +	Graphics::PixelFormat _palettePixelFormat; +}; + +class GLESTexture : public GLESBaseTexture { +protected: +	GLESTexture(GLenum glFormat, GLenum glType, +				Graphics::PixelFormat pixelFormat); + +public: +	virtual ~GLESTexture(); + +	virtual void allocBuffer(GLuint w, GLuint h); + +	virtual void updateBuffer(GLuint x, GLuint y, GLuint width, GLuint height, +								const void *buf, int pitch_buf); +	virtual void fillBuffer(uint32 color); + +	virtual void drawTexture(GLshort x, GLshort y, GLshort w, GLshort h); + +protected: +	byte *_pixels; +	byte *_buf; +}; + +// RGBA4444 texture +class GLES4444Texture : public GLESTexture { +public: +	GLES4444Texture(); +	virtual ~GLES4444Texture(); + +	static Graphics::PixelFormat pixelFormat() { +		return Graphics::PixelFormat(2, 4, 4, 4, 4, 12, 8, 4, 0); +	} +}; + +// RGBA5551 texture +class GLES5551Texture : public GLESTexture { +public: +	GLES5551Texture(); +	virtual ~GLES5551Texture(); + +	static inline Graphics::PixelFormat pixelFormat() { +		return Graphics::PixelFormat(2, 5, 5, 5, 1, 11, 6, 1, 0); +	} +}; + +// RGB565 texture +class GLES565Texture : public GLESTexture { +public: +	GLES565Texture(); +	virtual ~GLES565Texture(); + +	static inline Graphics::PixelFormat pixelFormat() { +		return Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0); +	} +}; + +class GLESFakePaletteTexture : public GLESBaseTexture { +protected: +	GLESFakePaletteTexture(GLenum glFormat, GLenum glType, +							Graphics::PixelFormat pixelFormat); + +public: +	virtual ~GLESFakePaletteTexture(); + +	virtual void allocBuffer(GLuint w, GLuint h); +	virtual void updateBuffer(GLuint x, GLuint y, GLuint width, GLuint height, +								const void *buf, int pitch_buf); +	virtual void fillBuffer(uint32 color); + +	virtual void drawTexture(GLshort x, GLshort y, GLshort w, GLshort h); + +	virtual const byte *palette_const() const { +		return (byte *)_palette; +	}; + +	virtual byte *palette() { +		setDirty(); +		return (byte *)_palette; +	}; + +	virtual const Graphics::PixelFormat &getPixelFormat() const; + +protected: +	Graphics::PixelFormat _fake_format; +	uint16 *_palette; +	byte *_pixels; +	uint16 *_buf; +}; + +class GLESFakePalette565Texture : public GLESFakePaletteTexture { +public: +	GLESFakePalette565Texture(); +	virtual ~GLESFakePalette565Texture(); +}; + +class GLESFakePalette5551Texture : public GLESFakePaletteTexture { +public: +	GLESFakePalette5551Texture(); +	virtual ~GLESFakePalette5551Texture(); +}; + +#endif +#endif + diff --git a/backends/platform/android/video.cpp b/backends/platform/android/video.cpp deleted file mode 100644 index f8427c2ac8..0000000000 --- a/backends/platform/android/video.cpp +++ /dev/null @@ -1,342 +0,0 @@ -/* 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/android.h" -#include "backends/platform/android/video.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() : -	_texture_width(0), -	_texture_height(0), -	_all_dirty(true) -{ -	GLCALL(glGenTextures(1, &_texture_name)); - -	// This all gets reset later in allocBuffer: -	_surface.w = 0; -	_surface.h = 0; -	_surface.pitch = _texture_width; -	_surface.pixels = 0; -	_surface.bytesPerPixel = 0; -} - -GLESTexture::~GLESTexture() { -	debug("Destroying texture %u", _texture_name); -	GLCALL(glDeleteTextures(1, &_texture_name)); -} - -void GLESTexture::reinitGL() { -	GLCALL(glDeleteTextures(1, &_texture_name)); -	GLCALL(glGenTextures(1, &_texture_name)); - -	// bypass allocBuffer() shortcut to reinit the texture properly -	_texture_width = 0; -	_texture_height = 0; - -	allocBuffer(_surface.w, _surface.h); -	setDirty(); -} - -void GLESTexture::allocBuffer(GLuint w, GLuint h) { -	int bpp = bytesPerPixel(); -	_surface.w = w; -	_surface.h = h; -	_surface.bytesPerPixel = bpp; - -	// 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 * bpp; - -	// 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::updateBuffer(GLuint x, GLuint y, GLuint w, GLuint h, -								const void *buf, int pitch) { -	ENTER("%u, %u, %u, %u, %p, %d", x, y, w, h, buf, pitch); - -	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) * bytesPerPixel() == pitch) { -		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 (h == 0) -			return; - -#if TEXSUBIMAGE_IS_EXPENSIVE -		byte tmpbuf[w * h * bytesPerPixel()]; -		const byte *src = static_cast<const byte *>(buf); -		byte *dst = tmpbuf; -		GLuint count = h; - -		do { -			memcpy(dst, src, w * bytesPerPixel()); -			dst += w * bytesPerPixel(); -			src += pitch; -		} while (--count); - -		GLCALL(glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, -								glFormat(), glType(), tmpbuf)); -#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; -		} while (--h); -#endif -	} -} - -void GLESTexture::fillBuffer(byte x) { -	int rowbytes = _surface.w * bytesPerPixel(); -	byte tmpbuf[_surface.h * rowbytes]; -	memset(tmpbuf, x, _surface.h * rowbytes); -	updateBuffer(0, 0, _surface.w, _surface.h, tmpbuf, rowbytes); -} - -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 && paletteSize() == 0) { -		//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)); -	} - -	_all_dirty = false; -	_dirty_rect = Common::Rect(); -} - -GLESPaletteTexture::GLESPaletteTexture() : -	GLESTexture(), -	_texture(0) -{ -} - -GLESPaletteTexture::~GLESPaletteTexture() { -	delete[] _texture; -} - -void GLESPaletteTexture::allocBuffer(GLuint w, GLuint h) { -	int bpp = bytesPerPixel(); -	_surface.w = w; -	_surface.h = h; -	_surface.bytesPerPixel = bpp; - -	// 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 * bpp; - -	// Texture gets uploaded later (from drawTexture()) - -	byte *new_buffer = new byte[paletteSize() + -		_texture_width * _texture_height * bytesPerPixel()]; -	if (_texture) { -		// preserve palette -		memcpy(new_buffer, _texture, paletteSize()); -		delete[] _texture; -	} - -	_texture = new_buffer; -	_surface.pixels = _texture + paletteSize(); -} - -void GLESPaletteTexture::fillBuffer(byte x) { -	assert(_surface.pixels); -	memset(_surface.pixels, x, _surface.pitch * _surface.h); -	setDirty(); -} - -void GLESPaletteTexture::updateBuffer(GLuint x, GLuint y, -										GLuint w, GLuint h, -										const void *buf, int pitch) { -	_all_dirty = true; - -	const byte * src = static_cast<const byte *>(buf); -	byte *dst = static_cast<byte *>(_surface.getBasePtr(x, y)); - -	do { -		memcpy(dst, src, w * bytesPerPixel()); -		dst += _surface.pitch; -		src += pitch; -	} while (--h); -} - -void GLESPaletteTexture::uploadTexture() const { -	const size_t texture_size = -		paletteSize() + _texture_width * _texture_height * bytesPerPixel(); - -	GLCALL(glCompressedTexImage2D(GL_TEXTURE_2D, 0, glType(), -									_texture_width, _texture_height, -									0, texture_size, _texture)); -} - -void GLESPaletteTexture::drawTexture(GLshort x, GLshort y, GLshort w, GLshort h) { -	if (_all_dirty) { -		GLCALL(glBindTexture(GL_TEXTURE_2D, _texture_name)); -		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)); -		uploadTexture(); -		_all_dirty = false; -	} - -	GLESTexture::drawTexture(x, y, w, h); -} - -#endif - diff --git a/backends/platform/android/video.h b/backends/platform/android/video.h deleted file mode 100644 index da42ea876d..0000000000 --- a/backends/platform/android/video.h +++ /dev/null @@ -1,209 +0,0 @@ -/* 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 <GLES/gl.h> - -#include "graphics/surface.h" - -#include "common/rect.h" -#include "common/array.h" - -class GLESTexture { -public: -	static void initGLExtensions(); - -	GLESTexture(); -	virtual ~GLESTexture(); - -	virtual void reinitGL(); -	virtual void allocBuffer(GLuint width, GLuint height); - -	virtual void updateBuffer(GLuint x, GLuint y, GLuint width, GLuint height, -								const void *buf, int pitch); -	virtual void fillBuffer(byte x); - -	virtual void drawTexture(GLshort x, GLshort y, GLshort w, GLshort h); - -	inline GLuint width() const { -		return _surface.w; -	} - -	inline GLuint height() const { -		return _surface.h; -	} - -	inline GLuint texture_name() const { -		return _texture_name; -	} - -	inline const Graphics::Surface *surface_const() const { -		return &_surface; -	} - -	inline Graphics::Surface *surface() { -		setDirty(); -		return &_surface; -	} - -	inline bool dirty() const { -		return _all_dirty || !_dirty_rect.isEmpty(); -	} - -	inline void drawTexture() { -		drawTexture(0, 0, _surface.w, _surface.h); -	} - -protected: -	virtual byte bytesPerPixel() const = 0; -	virtual GLenum glFormat() const = 0; -	virtual GLenum glType() const = 0; - -	virtual size_t paletteSize() const { -		return 0; -	} - -	inline void setDirty() { -		_all_dirty = true; -		_dirty_rect = Common::Rect(); -	} - -	inline void setDirtyRect(const Common::Rect& r) { -		if (!_all_dirty) { -			if (_dirty_rect.isEmpty()) -				_dirty_rect = r; -			else -				_dirty_rect.extend(r); -		} -	} - -	GLuint _texture_name; -	Graphics::Surface _surface; -	GLuint _texture_width; -	GLuint _texture_height; -	bool _all_dirty; - -	// Covers dirty area -	Common::Rect _dirty_rect; -}; - -// RGBA4444 texture -class GLES4444Texture : public GLESTexture { -protected: -	virtual byte bytesPerPixel() const { -		return 2; -	} - -	virtual GLenum glFormat() const { -		return GL_RGBA; -	} - -	virtual GLenum glType() const { -		return GL_UNSIGNED_SHORT_4_4_4_4; -	} -}; - -// RGB565 texture -class GLES565Texture : public GLESTexture { -protected: -	virtual byte bytesPerPixel() const { -		return 2; -	} - -	virtual GLenum glFormat() const { -		return GL_RGB; -	} - -	virtual GLenum glType() const { -		return GL_UNSIGNED_SHORT_5_6_5; -	} -}; - -// RGB888 256-entry paletted texture -class GLESPaletteTexture : public GLESTexture { -public: -	GLESPaletteTexture(); -	virtual ~GLESPaletteTexture(); - -	virtual void allocBuffer(GLuint width, GLuint height); -	virtual void updateBuffer(GLuint x, GLuint y, GLuint width, GLuint height, -								const void *buf, int pitch); -	virtual void fillBuffer(byte x); - -	virtual void drawTexture(GLshort x, GLshort y, GLshort w, GLshort h); - -	inline void drawTexture() { -		drawTexture(0, 0, _surface.w, _surface.h); -	} - -	inline const byte *palette_const() const { -		return _texture; -	}; - -	inline byte *palette() { -		setDirty(); -		return _texture; -	}; - -protected: -	virtual byte bytesPerPixel() const { -		return 1; -	} - -	virtual GLenum glFormat() const { -		return GL_RGB; -	} - -	virtual GLenum glType() const { -		return GL_PALETTE8_RGB8_OES; -	} - -	virtual size_t paletteSize() const { -		return 256 * 3; -	} - -	void uploadTexture() const; - -	byte *_texture; -}; - -// RGBA8888 256-entry paletted texture -class GLESPaletteATexture : public GLESPaletteTexture { -protected: -	virtual GLenum glFormat() const { -		return GL_RGBA; -	} - -	virtual GLenum glType() const { -		return GL_PALETTE8_RGBA8_OES; -	} - -	virtual size_t paletteSize() const { -		return 256 * 4; -	} -}; - -#endif  | 
