Search
lxdream.org :: lxdream/src/gui_android.c :: diff
lxdream 0.9.1
released Jun 29
Download Now
filename src/gui_android.c
changeset 1245:01e0020adf88
prev1241:74f8e11ab4b8
next1253:5aa14eeaad5a
author nkeynes
date Fri Mar 02 23:49:10 2012 +1000 (8 years ago)
permissions -rw-r--r--
last change Android WIP:
* Rename gui_jni.c to gui_android.c - now quite android specific.
* Implement generic EGL driver with very minimal Java wrapper
* Run emulation in separate thread, and implement simple queue for
inter-thread communication.
* Add menu/action-bar items for start + reset
file annotate diff log raw
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/src/gui_android.c Fri Mar 02 23:49:10 2012 +1000
1.3 @@ -0,0 +1,331 @@
1.4 +/**
1.5 + * $Id$
1.6 + *
1.7 + * Native shims for the Android front-end (implemented in Java).
1.8 + *
1.9 + * This is complicated by the fact that all the emulation runs in a
1.10 + * separate thread.
1.11 + *
1.12 + * Copyright (c) 2012 Nathan Keynes.
1.13 + *
1.14 + * This program is free software; you can redistribute it and/or modify
1.15 + * it under the terms of the GNU General Public License as published by
1.16 + * the Free Software Foundation; either version 2 of the License, or
1.17 + * (at your option) any later version.
1.18 + *
1.19 + * This program is distributed in the hope that it will be useful,
1.20 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
1.21 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1.22 + * GNU General Public License for more details.
1.23 + */
1.24 +
1.25 +#include <jni.h>
1.26 +#include <pthread.h>
1.27 +#include <android/log.h>
1.28 +#include <android/native_window_jni.h>
1.29 +#include <libisofs.h>
1.30 +#include "dream.h"
1.31 +#include "dreamcast.h"
1.32 +#include "gui.h"
1.33 +#include "config.h"
1.34 +#include "lxpaths.h"
1.35 +#include "tqueue.h"
1.36 +#include "display.h"
1.37 +#include "gdlist.h"
1.38 +#include "gdrom/gdrom.h"
1.39 +#include "hotkeys.h"
1.40 +#include "serial.h"
1.41 +#include "aica/audio.h"
1.42 +#include "drivers/video_egl.h"
1.43 +#include "maple/maple.h"
1.44 +#include "vmu/vmulist.h"
1.45 +
1.46 +struct surface_info {
1.47 + ANativeWindow *win;
1.48 + int width, height, format;
1.49 +};
1.50 +
1.51 +static struct surface_info current_surface;
1.52 +static const char *appHome = NULL;
1.53 +
1.54 +/**
1.55 + * Count of running nanoseconds - used to cut back on the GUI runtime
1.56 + */
1.57 +static uint32_t android_gui_nanos = 0;
1.58 +static uint32_t android_gui_ticks = 0;
1.59 +static struct timeval android_gui_lasttv;
1.60 +
1.61 +
1.62 +void android_gui_start( void )
1.63 +{
1.64 + /* Dreamcast starting up hook */
1.65 +}
1.66 +
1.67 +void android_gui_stop( void )
1.68 +{
1.69 + /* Dreamcast stopping hook */
1.70 +}
1.71 +
1.72 +void android_gui_reset( void )
1.73 +{
1.74 + /* Dreamcast reset hook */
1.75 +}
1.76 +
1.77 +/**
1.78 + * The main emulation thread. (as opposed to the UI thread).
1.79 + */
1.80 +void *android_thread_main(void *data)
1.81 +{
1.82 + while(1) {
1.83 + tqueue_process_wait();
1.84 + }
1.85 + return NULL;
1.86 +}
1.87 +
1.88 +int android_set_surface(void *data)
1.89 +{
1.90 + struct surface_info *surface = (struct surface_info *)data;
1.91 + video_egl_set_window(surface->win, surface->width, surface->height, surface->format);
1.92 + return 0;
1.93 +}
1.94 +
1.95 +int android_clear_surface(void *data)
1.96 +{
1.97 + struct surface_info *surface = (struct surface_info *)data;
1.98 +
1.99 + if( dreamcast_is_running() ) {
1.100 + dreamcast_stop();
1.101 + }
1.102 + video_egl_clear_window();
1.103 + ANativeWindow_release(surface->win);
1.104 + surface->win = NULL;
1.105 + return 0;
1.106 +}
1.107 +
1.108 +static pthread_t dreamcast_thread;
1.109 +
1.110 +void android_start_thread()
1.111 +{
1.112 + pthread_attr_t attr;
1.113 +
1.114 + pthread_attr_init(&attr);
1.115 + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
1.116 + int status = pthread_create(&dreamcast_thread, &attr, android_thread_main, NULL);
1.117 + if( status != 0 ) {
1.118 + /* Handle errors */
1.119 + }
1.120 +}
1.121 +
1.122 +/** tqueue callback wrapper to get the right call type for simple events */
1.123 +int android_callback_wrapper( void *fn )
1.124 +{
1.125 + void (*cast_fn)(void) = fn;
1.126 + cast_fn();
1.127 +}
1.128 +
1.129 +int android_mount_disc( void *data )
1.130 +{
1.131 + char *s = (char *)data;
1.132 + ERROR err;
1.133 + gboolean result = gdrom_mount_image( s, &err ); /* TODO: Report error */
1.134 + return result;
1.135 +}
1.136 +
1.137 +int android_toggle_run( void *data )
1.138 +{
1.139 + if( dreamcast_is_running() ) {
1.140 + dreamcast_stop();
1.141 + } else {
1.142 + dreamcast_run();
1.143 + }
1.144 +}
1.145 +
1.146 +uint32_t android_gui_run_slice( uint32_t nanosecs )
1.147 +{
1.148 + android_gui_nanos += nanosecs;
1.149 + if( android_gui_nanos > GUI_TICK_PERIOD ) { /* 10 ms */
1.150 + android_gui_nanos -= GUI_TICK_PERIOD;
1.151 + android_gui_ticks ++;
1.152 + uint32_t current_period = android_gui_ticks * GUI_TICK_PERIOD;
1.153 +
1.154 + // Run the event loop
1.155 + tqueue_process_all();
1.156 +
1.157 + struct timeval tv;
1.158 + gettimeofday(&tv,NULL);
1.159 + uint32_t ns = ((tv.tv_sec - android_gui_lasttv.tv_sec) * 1000000000) +
1.160 + (tv.tv_usec - android_gui_lasttv.tv_usec)*1000;
1.161 + if( (ns * 1.05) < current_period ) {
1.162 + // We've gotten ahead - sleep for a little bit
1.163 + struct timespec tv;
1.164 + tv.tv_sec = 0;
1.165 + tv.tv_nsec = current_period - ns;
1.166 + nanosleep(&tv, &tv);
1.167 + }
1.168 +
1.169 +#if 0
1.170 + /* Update the display every 10 ticks (ie 10 times a second) and
1.171 + * save the current tv value */
1.172 + if( android_gui_ticks > 10 ) {
1.173 + android_gui_ticks -= 10;
1.174 +
1.175 + double speed = (float)( (double)current_period * 100.0 / ns );
1.176 + android_gui_lasttv.tv_sec = tv.tv_sec;
1.177 + android_gui_lasttv.tv_usec = tv.tv_usec;
1.178 + main_window_set_speed( main_win, speed );
1.179 + }
1.180 +#endif
1.181 + }
1.182 + return nanosecs;
1.183 +
1.184 +
1.185 +}
1.186 +
1.187 +struct dreamcast_module android_gui_module = { "gui", NULL,
1.188 + android_gui_reset,
1.189 + android_gui_start,
1.190 + android_gui_run_slice,
1.191 + android_gui_stop,
1.192 + NULL, NULL };
1.193 +
1.194 +gboolean gui_error_dialog( const char *fmt, ... )
1.195 +{
1.196 + return FALSE; /* TODO */
1.197 +}
1.198 +
1.199 +void gui_update_state()
1.200 +{
1.201 + /* TODO */
1.202 +}
1.203 +
1.204 +void gui_set_use_grab( gboolean grab )
1.205 +{
1.206 + /* No implementation - mouse grab doesn't exist */
1.207 +}
1.208 +
1.209 +void gui_update_io_activity( io_activity_type activity, gboolean active )
1.210 +{
1.211 + /* No implementation */
1.212 +}
1.213 +
1.214 +void gui_do_later( do_later_callback_t func )
1.215 +{
1.216 + func(); /* TODO */
1.217 +}
1.218 +
1.219 +static void android_init( const char *appHomeDir )
1.220 +{
1.221 + set_global_log_level("info");
1.222 + appHome = appHomeDir;
1.223 + const char *confFile = g_strdup_printf("%s/lxdreamrc", appHome);
1.224 + set_user_data_path(appHome);
1.225 + lxdream_set_config_filename( confFile );
1.226 + lxdream_make_config_dir( );
1.227 + lxdream_load_config( );
1.228 + iso_init();
1.229 + gdrom_list_init();
1.230 + vmulist_init();
1.231 + dreamcast_init(1);
1.232 +
1.233 + dreamcast_register_module( &android_gui_module );
1.234 + audio_init_driver(NULL);
1.235 + display_driver_t display_driver = get_display_driver_by_name(NULL);
1.236 + display_set_driver(display_driver);
1.237 +
1.238 + hotkeys_init();
1.239 + serial_init();
1.240 + maple_reattach_all();
1.241 + INFO( "%s! ready...", APP_NAME );
1.242 + android_start_thread();
1.243 +}
1.244 +
1.245 +
1.246 +/************************* Dreamcast native entry points **************************/
1.247 +
1.248 +static char *getStringChars( JNIEnv *env, jstring str );
1.249 +
1.250 +/**
1.251 + * Main initialization entry point. We need to do all the setup that main()
1.252 + * would normally do, as well as any UI specific setup.
1.253 + */
1.254 +JNIEXPORT void JNICALL Java_org_lxdream_Dreamcast_init(JNIEnv * env, jclass obj, jstring homeDir )
1.255 +{
1.256 + android_init( getStringChars(env, homeDir) );
1.257 +}
1.258 +
1.259 +JNIEXPORT void JNICALL Java_org_lxdream_Dreamcast_run(JNIEnv * env, jclass obj)
1.260 +{
1.261 + tqueue_post_message( android_callback_wrapper, dreamcast_run );
1.262 +}
1.263 +
1.264 +JNIEXPORT void JNICALL Java_org_lxdream_Dreamcast_toggleRun(JNIEnv * env, jclass obj)
1.265 +{
1.266 + tqueue_post_message( android_toggle_run, NULL );
1.267 +}
1.268 +
1.269 +JNIEXPORT void JNICALL Java_org_lxdream_Dreamcast_reset(JNIEnv * env, jclass obj)
1.270 +{
1.271 + tqueue_post_message( android_callback_wrapper, dreamcast_reset );
1.272 +}
1.273 +
1.274 +JNIEXPORT void JNICALL Java_org_lxdream_Dreamcast_stop(JNIEnv * env, jclass obj)
1.275 +{
1.276 + /* Need to make sure this completely shuts down before we return */
1.277 + tqueue_send_message( android_callback_wrapper, dreamcast_stop );
1.278 +}
1.279 +
1.280 +JNIEXPORT jboolean JNICALL Java_org_lxdream_Dreamcast_isRunning(JNIEnv *env, jclass obj)
1.281 +{
1.282 + return dreamcast_is_running();
1.283 +}
1.284 +
1.285 +JNIEXPORT jboolean JNICALL Java_org_lxdream_Dreamcast_isRunnable(JNIEnv *env, jclass obj)
1.286 +{
1.287 + return dreamcast_can_run();
1.288 +}
1.289 +
1.290 +JNIEXPORT jboolean JNICALL Java_org_lxdream_Dreamcast_mount(JNIEnv *env, jclass obj, jstring str)
1.291 +{
1.292 + char *s = getStringChars(env, str);
1.293 + return tqueue_send_message( android_mount_disc, s );
1.294 +}
1.295 +
1.296 +JNIEXPORT jboolean JNICALL Java_org_lxdream_Dreamcast_unmount(JNIEnv *env, jclass obj)
1.297 +{
1.298 + tqueue_post_message( android_callback_wrapper, gdrom_unmount_disc );
1.299 +}
1.300 +
1.301 +
1.302 +/************************* LxdreamView native entry points **************************/
1.303 +
1.304 +JNIEXPORT void JNICALL Java_org_lxdream_LxdreamView_setSurface(JNIEnv * env, jobject view, jobject surface, jint width, jint height)
1.305 +{
1.306 + current_surface.win = ANativeWindow_fromSurface(env, surface);
1.307 + current_surface.width = width;
1.308 + current_surface.height = height;
1.309 + int fmt = ANativeWindow_getFormat(current_surface.win);
1.310 + if( fmt == WINDOW_FORMAT_RGB_565 ) {
1.311 + current_surface.format = COLFMT_RGB565;
1.312 + } else {
1.313 + current_surface.format = COLFMT_RGB888;
1.314 + }
1.315 + tqueue_post_message( android_set_surface, &current_surface );
1.316 +}
1.317 +
1.318 +JNIEXPORT void JNICALL Java_org_lxdream_LxdreamView_clearSurface(JNIEnv * env, jobject view, jobject surface)
1.319 +{
1.320 + /* Need to make sure this completely shuts down before we return */
1.321 + tqueue_send_message( android_clear_surface, &current_surface );
1.322 +}
1.323 +
1.324 +
1.325 +/************************* JNI Support functions **************************/
1.326 +
1.327 +static char *getStringChars( JNIEnv *env, jstring str )
1.328 +{
1.329 + jboolean iscopy;
1.330 + const char *p = (*env)->GetStringUTFChars(env, str, &iscopy);
1.331 + char *result = strdup(p);
1.332 + (*env)->ReleaseStringUTFChars(env,str,p);
1.333 + return result;
1.334 +}
.